Per Richard, replace LLProcessLauncher with LLProcess.
LLProcessLauncher had the somewhat fuzzy mandate of (1) accumulating parameters with which to launch a child process and (2) sometimes tracking the lifespan of the ensuing child process. But a valid LLProcessLauncher object might or might not have ever been associated with an actual child process. LLProcess specifically tracks a child process. In effect, it's a fairly thin wrapper around a process HANDLE (on Windows) or pid_t (elsewhere), with lifespan management thrown in. A static LLProcess::create() method launches a new child; create() accepts an LLSD bundle with child parameters. So building up a parameter bundle is deferred to LLSD rather than conflated with the process management object. Reconcile all known LLProcessLauncher consumers in the viewer code base, notably the class unit tests.meow-7.2.2
parent
083a9e0927
commit
f0dbb87833
|
|
@ -74,7 +74,7 @@ set(llcommon_SOURCE_FILES
|
|||
llmortician.cpp
|
||||
lloptioninterface.cpp
|
||||
llptrto.cpp
|
||||
llprocesslauncher.cpp
|
||||
llprocess.cpp
|
||||
llprocessor.cpp
|
||||
llqueuedthread.cpp
|
||||
llrand.cpp
|
||||
|
|
@ -197,7 +197,7 @@ set(llcommon_HEADER_FILES
|
|||
llpointer.h
|
||||
llpreprocessor.h
|
||||
llpriqueuemap.h
|
||||
llprocesslauncher.h
|
||||
llprocess.h
|
||||
llprocessor.h
|
||||
llptrskiplist.h
|
||||
llptrskipmap.h
|
||||
|
|
@ -328,7 +328,7 @@ if (LL_TESTS)
|
|||
LL_ADD_INTEGRATION_TEST(reflection "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llprocesslauncher "" "${test_libs}"
|
||||
LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}"
|
||||
"${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py")
|
||||
LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,338 @@
|
|||
/**
|
||||
* @file llprocess.cpp
|
||||
* @brief Utility class for launching, terminating, and tracking the state of processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
#include "llprocess.h"
|
||||
#include "llsd.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "stringize.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
/// Need an exception to avoid constructing an invalid LLProcess object, but
|
||||
/// internal use only
|
||||
struct LLProcessError: public std::runtime_error
|
||||
{
|
||||
LLProcessError(const std::string& msg): std::runtime_error(msg) {}
|
||||
};
|
||||
|
||||
LLProcessPtr LLProcess::create(const LLSD& params)
|
||||
{
|
||||
try
|
||||
{
|
||||
return LLProcessPtr(new LLProcess(params));
|
||||
}
|
||||
catch (const LLProcessError& e)
|
||||
{
|
||||
LL_WARNS("LLProcess") << e.what() << LL_ENDL;
|
||||
return LLProcessPtr();
|
||||
}
|
||||
}
|
||||
|
||||
LLProcess::LLProcess(const LLSD& params):
|
||||
mProcessID(0),
|
||||
mAutokill(params["autokill"].asBoolean())
|
||||
{
|
||||
// nonstandard default bool value
|
||||
if (! params.has("autokill"))
|
||||
mAutokill = true;
|
||||
if (! params.has("executable"))
|
||||
{
|
||||
throw LLProcessError(STRINGIZE("not launched: missing 'executable'\n"
|
||||
<< LLSDNotationStreamer(params)));
|
||||
}
|
||||
|
||||
launch(params);
|
||||
}
|
||||
|
||||
LLProcess::~LLProcess()
|
||||
{
|
||||
if (mAutokill)
|
||||
{
|
||||
kill();
|
||||
}
|
||||
}
|
||||
|
||||
bool LLProcess::isRunning(void)
|
||||
{
|
||||
mProcessID = isRunning(mProcessID);
|
||||
return (mProcessID != 0);
|
||||
}
|
||||
|
||||
#if LL_WINDOWS
|
||||
|
||||
static std::string quote(const std::string& str)
|
||||
{
|
||||
std::string::size_type len(str.length());
|
||||
// If the string is already quoted, assume user knows what s/he's doing.
|
||||
if (len >= 2 && str[0] == '"' && str[len-1] == '"')
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
// Not already quoted: do it.
|
||||
std::string result("\"");
|
||||
for (std::string::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
|
||||
{
|
||||
if (*ci == '"')
|
||||
{
|
||||
result.append("\\");
|
||||
}
|
||||
result.push_back(*ci);
|
||||
}
|
||||
return result + "\"";
|
||||
}
|
||||
|
||||
void LLProcess::launch(const LLSD& params)
|
||||
{
|
||||
PROCESS_INFORMATION pinfo;
|
||||
STARTUPINFOA sinfo;
|
||||
memset(&sinfo, 0, sizeof(sinfo));
|
||||
|
||||
std::string args = quote(params["executable"]);
|
||||
BOOST_FOREACH(const std::string& arg, llsd::inArray(params["args"]))
|
||||
{
|
||||
args += " ";
|
||||
args += quote(arg);
|
||||
}
|
||||
|
||||
// So retarded. Windows requires that the second parameter to
|
||||
// CreateProcessA be a writable (non-const) string...
|
||||
std::vector<char> args2(args.begin(), args.end());
|
||||
args2.push_back('\0');
|
||||
|
||||
// Convert wrapper to a real std::string so we can use c_str(); but use a
|
||||
// named variable instead of a temporary so c_str() pointer remains valid.
|
||||
std::string cwd(params["cwd"]);
|
||||
const char * working_directory = 0;
|
||||
if (! cwd.empty())
|
||||
working_directory = cwd.c_str();
|
||||
if( ! CreateProcessA( NULL, &args2[0], NULL, NULL, FALSE, 0, NULL, working_directory, &sinfo, &pinfo ) )
|
||||
{
|
||||
int result = GetLastError();
|
||||
|
||||
LPTSTR error_str = 0;
|
||||
if(
|
||||
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
NULL,
|
||||
result,
|
||||
0,
|
||||
(LPTSTR)&error_str,
|
||||
0,
|
||||
NULL)
|
||||
!= 0)
|
||||
{
|
||||
char message[256];
|
||||
wcstombs(message, error_str, sizeof(message));
|
||||
message[sizeof(message)-1] = 0;
|
||||
LocalFree(error_str);
|
||||
throw LLProcessError(STRINGIZE("CreateProcessA failed (" << result << "): "
|
||||
<< message));
|
||||
}
|
||||
throw LLProcessError(STRINGIZE("CreateProcessA failed (" << result
|
||||
<< "), but FormatMessage() did not explain"));
|
||||
}
|
||||
|
||||
// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
|
||||
// CloseHandle(pinfo.hProcess); // stops leaks - nothing else
|
||||
mProcessID = pinfo.hProcess;
|
||||
CloseHandle(pinfo.hThread); // stops leaks - nothing else
|
||||
}
|
||||
|
||||
LLProcess::id LLProcess::isRunning(id handle)
|
||||
{
|
||||
if (! handle)
|
||||
return 0;
|
||||
|
||||
DWORD waitresult = WaitForSingleObject(handle, 0);
|
||||
if(waitresult == WAIT_OBJECT_0)
|
||||
{
|
||||
// the process has completed.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool LLProcess::kill(void)
|
||||
{
|
||||
if (! mProcessID)
|
||||
return false;
|
||||
|
||||
TerminateProcess(mProcessID, 0);
|
||||
return ! isRunning();
|
||||
}
|
||||
|
||||
#else // Mac and linux
|
||||
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise.
|
||||
static bool reap_pid(pid_t pid)
|
||||
{
|
||||
pid_t wait_result = ::waitpid(pid, NULL, WNOHANG);
|
||||
if (wait_result == pid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (wait_result == -1 && errno == ECHILD)
|
||||
{
|
||||
// No such process -- this may mean we're ignoring SIGCHILD.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLProcess::launch(const LLSD& params)
|
||||
{
|
||||
// flush all buffers before the child inherits them
|
||||
::fflush(NULL);
|
||||
|
||||
pid_t child = vfork();
|
||||
if (child == 0)
|
||||
{
|
||||
// child process
|
||||
|
||||
std::string cwd(params["cwd"]);
|
||||
if (! cwd.empty())
|
||||
{
|
||||
// change to the desired child working directory
|
||||
if (::chdir(cwd.c_str()))
|
||||
{
|
||||
// chdir failed
|
||||
LL_WARNS("LLProcess") << "could not chdir(\"" << cwd << "\")" << LL_ENDL;
|
||||
// pointless to throw; this is child process...
|
||||
_exit(248);
|
||||
}
|
||||
}
|
||||
|
||||
// create an argv vector for the child process
|
||||
std::vector<const char*> fake_argv;
|
||||
|
||||
// add the executable path
|
||||
std::string executable(params["executable"]);
|
||||
fake_argv.push_back(executable.c_str());
|
||||
|
||||
// and any arguments
|
||||
const LLSD& params_args(params["args"]);
|
||||
std::vector<std::string> args(params_args.beginArray(), params_args.endArray());
|
||||
BOOST_FOREACH(const std::string& arg, args)
|
||||
{
|
||||
fake_argv.push_back(arg.c_str());
|
||||
}
|
||||
|
||||
// terminate with a null pointer
|
||||
fake_argv.push_back(NULL);
|
||||
|
||||
::execv(executable.c_str(), const_cast<char* const*>(&fake_argv[0]));
|
||||
|
||||
// If we reach this point, the exec failed.
|
||||
LL_WARNS("LLProcess") << "failed to launch: ";
|
||||
BOOST_FOREACH(const char* arg, fake_argv)
|
||||
{
|
||||
LL_CONT << arg << ' ';
|
||||
}
|
||||
LL_CONT << LL_ENDL;
|
||||
// Use _exit() instead of exit() per the vfork man page. Exit with a
|
||||
// distinctive rc: someday soon we'll be able to retrieve it, and it
|
||||
// would be nice to be able to tell that the child process failed!
|
||||
_exit(249);
|
||||
}
|
||||
|
||||
// parent process
|
||||
mProcessID = child;
|
||||
}
|
||||
|
||||
LLProcess::id LLProcess::isRunning(id pid)
|
||||
{
|
||||
if (! pid)
|
||||
return 0;
|
||||
|
||||
// Check whether the process has exited, and reap it if it has.
|
||||
if(reap_pid(pid))
|
||||
{
|
||||
// the process has exited.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
bool LLProcess::kill(void)
|
||||
{
|
||||
if (! mProcessID)
|
||||
return false;
|
||||
|
||||
// Try to kill the process. We'll do approximately the same thing whether
|
||||
// the kill returns an error or not, so we ignore the result.
|
||||
(void)::kill(mProcessID, SIGTERM);
|
||||
|
||||
// This will have the side-effect of reaping the zombie if the process has exited.
|
||||
return ! isRunning();
|
||||
}
|
||||
|
||||
/*==========================================================================*|
|
||||
static std::list<pid_t> sZombies;
|
||||
|
||||
void LLProcess::orphan(void)
|
||||
{
|
||||
// Disassociate the process from this object
|
||||
if(mProcessID != 0)
|
||||
{
|
||||
// We may still need to reap the process's zombie eventually
|
||||
sZombies.push_back(mProcessID);
|
||||
|
||||
mProcessID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void LLProcess::reap(void)
|
||||
{
|
||||
// Attempt to real all saved process ID's.
|
||||
|
||||
std::list<pid_t>::iterator iter = sZombies.begin();
|
||||
while(iter != sZombies.end())
|
||||
{
|
||||
if(reap_pid(*iter))
|
||||
{
|
||||
iter = sZombies.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|*==========================================================================*/
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* @file llprocess.h
|
||||
* @brief Utility class for launching, terminating, and tracking child processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPROCESS_H
|
||||
#define LL_LLPROCESS_H
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
class LLSD;
|
||||
|
||||
class LLProcess;
|
||||
/// LLProcess instances are created on the heap by static factory methods and
|
||||
/// managed by ref-counted pointers.
|
||||
typedef boost::shared_ptr<LLProcess> LLProcessPtr;
|
||||
|
||||
/**
|
||||
* LLProcess handles launching external processes with specified command line arguments.
|
||||
* It also keeps track of whether the process is still running, and can kill it if required.
|
||||
*/
|
||||
class LL_COMMON_API LLProcess: public boost::noncopyable
|
||||
{
|
||||
LOG_CLASS(LLProcess);
|
||||
public:
|
||||
|
||||
/**
|
||||
* Factory accepting LLSD::Map.
|
||||
* MAY RETURN DEFAULT-CONSTRUCTED LLProcessPtr if params invalid!
|
||||
*
|
||||
* executable (required, string): executable pathname
|
||||
* args (optional, string array): extra command-line arguments
|
||||
* cwd (optional, string, dft no chdir): change to this directory before executing
|
||||
* autokill (optional, bool, dft true): implicit kill() on ~LLProcess
|
||||
*/
|
||||
static LLProcessPtr create(const LLSD& params);
|
||||
virtual ~LLProcess();
|
||||
|
||||
// isRunning isn't const because, if child isn't running, it clears stored
|
||||
// process ID
|
||||
bool isRunning(void);
|
||||
|
||||
// Attempt to kill the process -- returns true if the process is no longer running when it returns.
|
||||
// Note that even if this returns false, the process may exit some time after it's called.
|
||||
bool kill(void);
|
||||
|
||||
#if LL_WINDOWS
|
||||
typedef HANDLE id;
|
||||
#else
|
||||
typedef pid_t id;
|
||||
#endif
|
||||
/// Get platform-specific process ID
|
||||
id getProcessID() const { return mProcessID; };
|
||||
|
||||
/**
|
||||
* Test if a process (id obtained from getProcessID()) is still
|
||||
* running. Return is same nonzero id value if still running, else
|
||||
* zero, so you can test it like a bool. But if you want to update a
|
||||
* stored variable as a side effect, you can write code like this:
|
||||
* @code
|
||||
* childpid = LLProcess::isRunning(childpid);
|
||||
* @endcode
|
||||
* @note This method is intended as a unit-test hook, not as the first of
|
||||
* a whole set of operations supported on freestanding @c id values. New
|
||||
* functionality should be added as nonstatic members operating on
|
||||
* mProcessID.
|
||||
*/
|
||||
static id isRunning(id);
|
||||
|
||||
private:
|
||||
/// constructor is private: use create() instead
|
||||
LLProcess(const LLSD& params);
|
||||
void launch(const LLSD& params);
|
||||
|
||||
id mProcessID;
|
||||
bool mAutokill;
|
||||
};
|
||||
|
||||
#endif // LL_LLPROCESS_H
|
||||
|
|
@ -1,394 +0,0 @@
|
|||
/**
|
||||
* @file llprocesslauncher.cpp
|
||||
* @brief Utility class for launching, terminating, and tracking the state of processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llprocesslauncher.h"
|
||||
|
||||
#include <iostream>
|
||||
#if LL_DARWIN || LL_LINUX
|
||||
// not required or present on Win32
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
LLProcessLauncher::LLProcessLauncher()
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
mProcessHandle = 0;
|
||||
#else
|
||||
mProcessID = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
LLProcessLauncher::~LLProcessLauncher()
|
||||
{
|
||||
kill();
|
||||
}
|
||||
|
||||
void LLProcessLauncher::setExecutable(const std::string &executable)
|
||||
{
|
||||
mExecutable = executable;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::setWorkingDirectory(const std::string &dir)
|
||||
{
|
||||
mWorkingDir = dir;
|
||||
}
|
||||
|
||||
const std::string& LLProcessLauncher::getExecutable() const
|
||||
{
|
||||
return mExecutable;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::clearArguments()
|
||||
{
|
||||
mLaunchArguments.clear();
|
||||
}
|
||||
|
||||
void LLProcessLauncher::addArgument(const std::string &arg)
|
||||
{
|
||||
mLaunchArguments.push_back(arg);
|
||||
}
|
||||
|
||||
#if LL_WINDOWS
|
||||
|
||||
static std::string quote(const std::string& str)
|
||||
{
|
||||
std::string::size_type len(str.length());
|
||||
// If the string is already quoted, assume user knows what s/he's doing.
|
||||
if (len >= 2 && str[0] == '"' && str[len-1] == '"')
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
// Not already quoted: do it.
|
||||
std::string result("\"");
|
||||
for (std::string::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
|
||||
{
|
||||
if (*ci == '"')
|
||||
{
|
||||
result.append("\\");
|
||||
}
|
||||
result.push_back(*ci);
|
||||
}
|
||||
return result + "\"";
|
||||
}
|
||||
|
||||
int LLProcessLauncher::launch(void)
|
||||
{
|
||||
// If there was already a process associated with this object, kill it.
|
||||
kill();
|
||||
orphan();
|
||||
|
||||
int result = 0;
|
||||
|
||||
PROCESS_INFORMATION pinfo;
|
||||
STARTUPINFOA sinfo;
|
||||
memset(&sinfo, 0, sizeof(sinfo));
|
||||
|
||||
std::string args = quote(mExecutable);
|
||||
for(int i = 0; i < (int)mLaunchArguments.size(); i++)
|
||||
{
|
||||
args += " ";
|
||||
args += quote(mLaunchArguments[i]);
|
||||
}
|
||||
|
||||
// So retarded. Windows requires that the second parameter to CreateProcessA be a writable (non-const) string...
|
||||
char *args2 = new char[args.size() + 1];
|
||||
strcpy(args2, args.c_str());
|
||||
|
||||
const char * working_directory = 0;
|
||||
if(!mWorkingDir.empty()) working_directory = mWorkingDir.c_str();
|
||||
if( ! CreateProcessA( NULL, args2, NULL, NULL, FALSE, 0, NULL, working_directory, &sinfo, &pinfo ) )
|
||||
{
|
||||
result = GetLastError();
|
||||
|
||||
LPTSTR error_str = 0;
|
||||
if(
|
||||
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
NULL,
|
||||
result,
|
||||
0,
|
||||
(LPTSTR)&error_str,
|
||||
0,
|
||||
NULL)
|
||||
!= 0)
|
||||
{
|
||||
char message[256];
|
||||
wcstombs(message, error_str, 256);
|
||||
message[255] = 0;
|
||||
llwarns << "CreateProcessA failed: " << message << llendl;
|
||||
LocalFree(error_str);
|
||||
}
|
||||
|
||||
if(result == 0)
|
||||
{
|
||||
// Make absolutely certain we return a non-zero value on failure.
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
|
||||
// CloseHandle(pinfo.hProcess); // stops leaks - nothing else
|
||||
mProcessHandle = pinfo.hProcess;
|
||||
CloseHandle(pinfo.hThread); // stops leaks - nothing else
|
||||
}
|
||||
|
||||
delete[] args2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::isRunning(void)
|
||||
{
|
||||
mProcessHandle = isRunning(mProcessHandle);
|
||||
return (mProcessHandle != 0);
|
||||
}
|
||||
|
||||
LLProcessLauncher::ll_pid_t LLProcessLauncher::isRunning(ll_pid_t handle)
|
||||
{
|
||||
if (! handle)
|
||||
return 0;
|
||||
|
||||
DWORD waitresult = WaitForSingleObject(handle, 0);
|
||||
if(waitresult == WAIT_OBJECT_0)
|
||||
{
|
||||
// the process has completed.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::kill(void)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if(mProcessHandle != 0)
|
||||
{
|
||||
TerminateProcess(mProcessHandle,0);
|
||||
|
||||
if(isRunning())
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::orphan(void)
|
||||
{
|
||||
// Forget about the process
|
||||
mProcessHandle = 0;
|
||||
}
|
||||
|
||||
// static
|
||||
void LLProcessLauncher::reap(void)
|
||||
{
|
||||
// No actions necessary on Windows.
|
||||
}
|
||||
|
||||
#else // Mac and linux
|
||||
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
static std::list<pid_t> sZombies;
|
||||
|
||||
// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise.
|
||||
static bool reap_pid(pid_t pid)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
pid_t wait_result = ::waitpid(pid, NULL, WNOHANG);
|
||||
if(wait_result == pid)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else if(wait_result == -1)
|
||||
{
|
||||
if(errno == ECHILD)
|
||||
{
|
||||
// No such process -- this may mean we're ignoring SIGCHILD.
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int LLProcessLauncher::launch(void)
|
||||
{
|
||||
// If there was already a process associated with this object, kill it.
|
||||
kill();
|
||||
orphan();
|
||||
|
||||
int result = 0;
|
||||
int current_wd = -1;
|
||||
|
||||
// create an argv vector for the child process
|
||||
const char ** fake_argv = new const char *[mLaunchArguments.size() + 2]; // 1 for the executable path, 1 for the NULL terminator
|
||||
|
||||
int i = 0;
|
||||
|
||||
// add the executable path
|
||||
fake_argv[i++] = mExecutable.c_str();
|
||||
|
||||
// and any arguments
|
||||
for(int j=0; j < mLaunchArguments.size(); j++)
|
||||
fake_argv[i++] = mLaunchArguments[j].c_str();
|
||||
|
||||
// terminate with a null pointer
|
||||
fake_argv[i] = NULL;
|
||||
|
||||
if(!mWorkingDir.empty())
|
||||
{
|
||||
// save the current working directory
|
||||
current_wd = ::open(".", O_RDONLY);
|
||||
|
||||
// and change to the one the child will be executed in
|
||||
if (::chdir(mWorkingDir.c_str()))
|
||||
{
|
||||
// chdir failed
|
||||
}
|
||||
}
|
||||
|
||||
// flush all buffers before the child inherits them
|
||||
::fflush(NULL);
|
||||
|
||||
pid_t id = vfork();
|
||||
if(id == 0)
|
||||
{
|
||||
// child process
|
||||
::execv(mExecutable.c_str(), (char * const *)fake_argv);
|
||||
|
||||
// If we reach this point, the exec failed.
|
||||
LL_WARNS("LLProcessLauncher") << "failed to launch: ";
|
||||
for (const char * const * ai = fake_argv; *ai; ++ai)
|
||||
{
|
||||
LL_CONT << *ai << ' ';
|
||||
}
|
||||
LL_CONT << LL_ENDL;
|
||||
// Use _exit() instead of exit() per the vfork man page. Exit with a
|
||||
// distinctive rc: someday soon we'll be able to retrieve it, and it
|
||||
// would be nice to be able to tell that the child process failed!
|
||||
_exit(249);
|
||||
}
|
||||
|
||||
// parent process
|
||||
|
||||
if(current_wd >= 0)
|
||||
{
|
||||
// restore the previous working directory
|
||||
if (::fchdir(current_wd))
|
||||
{
|
||||
// chdir failed
|
||||
}
|
||||
::close(current_wd);
|
||||
}
|
||||
|
||||
delete[] fake_argv;
|
||||
|
||||
mProcessID = id;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::isRunning(void)
|
||||
{
|
||||
mProcessID = isRunning(mProcessID);
|
||||
return (mProcessID != 0);
|
||||
}
|
||||
|
||||
LLProcessLauncher::ll_pid_t LLProcessLauncher::isRunning(ll_pid_t pid)
|
||||
{
|
||||
if (! pid)
|
||||
return 0;
|
||||
|
||||
// Check whether the process has exited, and reap it if it has.
|
||||
if(reap_pid(pid))
|
||||
{
|
||||
// the process has exited.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::kill(void)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if(mProcessID != 0)
|
||||
{
|
||||
// Try to kill the process. We'll do approximately the same thing whether the kill returns an error or not, so we ignore the result.
|
||||
(void)::kill(mProcessID, SIGTERM);
|
||||
|
||||
// This will have the side-effect of reaping the zombie if the process has exited.
|
||||
if(isRunning())
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::orphan(void)
|
||||
{
|
||||
// Disassociate the process from this object
|
||||
if(mProcessID != 0)
|
||||
{
|
||||
// We may still need to reap the process's zombie eventually
|
||||
sZombies.push_back(mProcessID);
|
||||
|
||||
mProcessID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void LLProcessLauncher::reap(void)
|
||||
{
|
||||
// Attempt to real all saved process ID's.
|
||||
|
||||
std::list<pid_t>::iterator iter = sZombies.begin();
|
||||
while(iter != sZombies.end())
|
||||
{
|
||||
if(reap_pid(*iter))
|
||||
{
|
||||
iter = sZombies.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
/**
|
||||
* @file llprocesslauncher.h
|
||||
* @brief Utility class for launching, terminating, and tracking the state of processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPROCESSLAUNCHER_H
|
||||
#define LL_LLPROCESSLAUNCHER_H
|
||||
|
||||
#if LL_WINDOWS
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
LLProcessLauncher handles launching external processes with specified command line arguments.
|
||||
It also keeps track of whether the process is still running, and can kill it if required.
|
||||
*/
|
||||
|
||||
class LL_COMMON_API LLProcessLauncher
|
||||
{
|
||||
LOG_CLASS(LLProcessLauncher);
|
||||
public:
|
||||
LLProcessLauncher();
|
||||
virtual ~LLProcessLauncher();
|
||||
|
||||
void setExecutable(const std::string &executable);
|
||||
void setWorkingDirectory(const std::string &dir);
|
||||
|
||||
const std::string& getExecutable() const;
|
||||
|
||||
void clearArguments();
|
||||
void addArgument(const std::string &arg);
|
||||
|
||||
int launch(void);
|
||||
// isRunning isn't const because, if child isn't running, it clears stored
|
||||
// process ID
|
||||
bool isRunning(void);
|
||||
|
||||
// Attempt to kill the process -- returns true if the process is no longer running when it returns.
|
||||
// Note that even if this returns false, the process may exit some time after it's called.
|
||||
bool kill(void);
|
||||
|
||||
// Use this if you want the external process to continue execution after the LLProcessLauncher instance controlling it is deleted.
|
||||
// Normally, the destructor will attempt to kill the process and wait for termination.
|
||||
// This should only be used if the viewer is about to exit -- otherwise, the child process will become a zombie after it exits.
|
||||
void orphan(void);
|
||||
|
||||
// This needs to be called periodically on Mac/Linux to clean up zombie processes.
|
||||
// (However, as of 2012-01-12 there are no such calls in the viewer code base. :-P )
|
||||
static void reap(void);
|
||||
|
||||
// Accessors for platform-specific process ID
|
||||
#if LL_WINDOWS
|
||||
// (Windows flavor unused as of 2012-01-12)
|
||||
typedef HANDLE ll_pid_t;
|
||||
HANDLE getProcessHandle() const { return mProcessHandle; }
|
||||
ll_pid_t getProcessID() const { return mProcessHandle; }
|
||||
#else
|
||||
typedef pid_t ll_pid_t;
|
||||
ll_pid_t getProcessID() const { return mProcessID; };
|
||||
#endif
|
||||
/**
|
||||
* Test if a process (ll_pid_t obtained from getProcessID()) is still
|
||||
* running. Return is same nonzero ll_pid_t value if still running, else
|
||||
* zero, so you can test it like a bool. But if you want to update a
|
||||
* stored variable as a side effect, you can write code like this:
|
||||
* @code
|
||||
* childpid = LLProcessLauncher::isRunning(childpid);
|
||||
* @endcode
|
||||
*/
|
||||
static ll_pid_t isRunning(ll_pid_t);
|
||||
|
||||
private:
|
||||
std::string mExecutable;
|
||||
std::string mWorkingDir;
|
||||
std::vector<std::string> mLaunchArguments;
|
||||
|
||||
#if LL_WINDOWS
|
||||
HANDLE mProcessHandle;
|
||||
#else
|
||||
pid_t mProcessID;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // LL_LLPROCESSLAUNCHER_H
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* @file llprocesslauncher_test.cpp
|
||||
* @file llprocess_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2011-12-19
|
||||
* @brief Test for llprocesslauncher.
|
||||
* @brief Test for llprocess.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
* Copyright (c) 2011, Linden Research, Inc.
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llprocess.h"
|
||||
// STL headers
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
|
@ -32,6 +32,7 @@
|
|||
#include "../test/manageapr.h"
|
||||
#include "../test/namedtempfile.h"
|
||||
#include "stringize.h"
|
||||
#include "llsdutil.h"
|
||||
|
||||
#if defined(LL_WINDOWS)
|
||||
#define sleep(secs) _sleep((secs) * 1000)
|
||||
|
|
@ -88,7 +89,7 @@ static std::string readfile(const std::string& pathname, const std::string& desc
|
|||
}
|
||||
|
||||
/**
|
||||
* Construct an LLProcessLauncher to run a Python script.
|
||||
* Construct an LLProcess to run a Python script.
|
||||
*/
|
||||
struct PythonProcessLauncher
|
||||
{
|
||||
|
|
@ -106,30 +107,30 @@ struct PythonProcessLauncher
|
|||
const char* PYTHON(getenv("PYTHON"));
|
||||
tut::ensure("Set $PYTHON to the Python interpreter", PYTHON);
|
||||
|
||||
mPy.setExecutable(PYTHON);
|
||||
mPy.addArgument(mScript.getName());
|
||||
mParams["executable"] = PYTHON;
|
||||
mParams["args"].append(mScript.getName());
|
||||
}
|
||||
|
||||
/// Run Python script and wait for it to complete.
|
||||
void run()
|
||||
{
|
||||
tut::ensure_equals(STRINGIZE("Couldn't launch " << mDesc << " script"),
|
||||
mPy.launch(), 0);
|
||||
// One of the irritating things about LLProcessLauncher is that
|
||||
mPy = LLProcess::create(mParams);
|
||||
tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), mPy);
|
||||
// One of the irritating things about LLProcess is that
|
||||
// there's no API to wait for the child to terminate -- but given
|
||||
// its use in our graphics-intensive interactive viewer, it's
|
||||
// understandable.
|
||||
while (mPy.isRunning())
|
||||
while (mPy->isRunning())
|
||||
{
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a Python script using LLProcessLauncher, expecting that it will
|
||||
* Run a Python script using LLProcess, expecting that it will
|
||||
* write to the file passed as its sys.argv[1]. Retrieve that output.
|
||||
*
|
||||
* Until January 2012, LLProcessLauncher provided distressingly few
|
||||
* Until January 2012, LLProcess provided distressingly few
|
||||
* mechanisms for a child process to communicate back to its caller --
|
||||
* not even its return code. We've introduced a convention by which we
|
||||
* create an empty temp file, pass the name of that file to our child
|
||||
|
|
@ -141,13 +142,14 @@ struct PythonProcessLauncher
|
|||
{
|
||||
NamedTempFile out("out", ""); // placeholder
|
||||
// pass name of this temporary file to the script
|
||||
mPy.addArgument(out.getName());
|
||||
mParams["args"].append(out.getName());
|
||||
run();
|
||||
// assuming the script wrote to that file, read it
|
||||
return readfile(out.getName(), STRINGIZE("from " << mDesc << " script"));
|
||||
}
|
||||
|
||||
LLProcessLauncher mPy;
|
||||
LLSD mParams;
|
||||
LLProcessPtr mPy;
|
||||
std::string mDesc;
|
||||
NamedTempFile mScript;
|
||||
};
|
||||
|
|
@ -203,13 +205,13 @@ private:
|
|||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
struct llprocesslauncher_data
|
||||
struct llprocess_data
|
||||
{
|
||||
LLAPRPool pool;
|
||||
};
|
||||
typedef test_group<llprocesslauncher_data> llprocesslauncher_group;
|
||||
typedef llprocesslauncher_group::object object;
|
||||
llprocesslauncher_group llprocesslaunchergrp("llprocesslauncher");
|
||||
typedef test_group<llprocess_data> llprocess_group;
|
||||
typedef llprocess_group::object object;
|
||||
llprocess_group llprocessgrp("llprocess");
|
||||
|
||||
struct Item
|
||||
{
|
||||
|
|
@ -501,17 +503,6 @@ namespace tut
|
|||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("set/getExecutable()");
|
||||
LLProcessLauncher child;
|
||||
child.setExecutable("nonsense string");
|
||||
ensure_equals("setExecutable() 0", child.getExecutable(), "nonsense string");
|
||||
child.setExecutable("python");
|
||||
ensure_equals("setExecutable() 1", child.getExecutable(), "python");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("setWorkingDirectory()");
|
||||
// We want to test setWorkingDirectory(). But what directory is
|
||||
|
|
@ -524,14 +515,14 @@ namespace tut
|
|||
"with open(sys.argv[1], 'w') as f:\n"
|
||||
" f.write(os.getcwd())\n");
|
||||
// Before running, call setWorkingDirectory()
|
||||
py.mPy.setWorkingDirectory(tempdir.getName());
|
||||
py.mParams["cwd"] = tempdir.getName();
|
||||
ensure_equals("os.getcwd()", py.run_read(), tempdir.getName());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<4>()
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("clearArguments()");
|
||||
set_test_name("arguments");
|
||||
PythonProcessLauncher py("args",
|
||||
"from __future__ import with_statement\n"
|
||||
"import sys\n"
|
||||
|
|
@ -539,15 +530,11 @@ namespace tut
|
|||
"with open(sys.argv[3], 'w') as f:\n"
|
||||
" for arg in sys.argv[1:]:\n"
|
||||
" print >>f, arg\n");
|
||||
// We expect that PythonProcessLauncher has already called
|
||||
// addArgument() with the name of its own NamedTempFile. But let's
|
||||
// change it up.
|
||||
py.mPy.clearArguments();
|
||||
// re-add script pathname
|
||||
py.mPy.addArgument(py.mScript.getName()); // sys.argv[0]
|
||||
py.mPy.addArgument("first arg"); // sys.argv[1]
|
||||
py.mPy.addArgument("second arg"); // sys.argv[2]
|
||||
// run_read() calls addArgument() one more time, hence [3]
|
||||
// We expect that PythonProcessLauncher has already appended
|
||||
// its own NamedTempFile to mParams["args"] (sys.argv[0]).
|
||||
py.mParams["args"].append("first arg"); // sys.argv[1]
|
||||
py.mParams["args"].append("second arg"); // sys.argv[2]
|
||||
// run_read() appends() one more argument, hence [3]
|
||||
std::string output(py.run_read());
|
||||
boost::split_iterator<std::string::const_iterator>
|
||||
li(output, boost::first_finder("\n")), lend;
|
||||
|
|
@ -567,7 +554,7 @@ namespace tut
|
|||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<5>()
|
||||
void object::test<4>()
|
||||
{
|
||||
set_test_name("explicit kill()");
|
||||
PythonProcessLauncher py("kill()",
|
||||
|
|
@ -581,8 +568,9 @@ namespace tut
|
|||
"with open(sys.argv[1], 'w') as f:\n"
|
||||
" f.write('bad')\n");
|
||||
NamedTempFile out("out", "not started");
|
||||
py.mPy.addArgument(out.getName());
|
||||
ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0);
|
||||
py.mParams["args"].append(out.getName());
|
||||
py.mPy = LLProcess::create(py.mParams);
|
||||
ensure("couldn't launch kill() script", py.mPy);
|
||||
// Wait for the script to wake up and do its first write
|
||||
int i = 0, timeout = 60;
|
||||
for ( ; i < timeout; ++i)
|
||||
|
|
@ -594,9 +582,55 @@ namespace tut
|
|||
// If we broke this loop because of the counter, something's wrong
|
||||
ensure("script never started", i < timeout);
|
||||
// script has performed its first write and should now be sleeping.
|
||||
py.mPy.kill();
|
||||
py.mPy->kill();
|
||||
// wait for the script to terminate... one way or another.
|
||||
while (py.mPy.isRunning())
|
||||
while (py.mPy->isRunning())
|
||||
{
|
||||
sleep(1);
|
||||
}
|
||||
// If kill() failed, the script would have woken up on its own and
|
||||
// overwritten the file with 'bad'. But if kill() succeeded, it should
|
||||
// not have had that chance.
|
||||
ensure_equals("kill() script output", readfile(out.getName()), "ok");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<5>()
|
||||
{
|
||||
set_test_name("implicit kill()");
|
||||
NamedTempFile out("out", "not started");
|
||||
LLProcess::id pid(0);
|
||||
{
|
||||
PythonProcessLauncher py("kill()",
|
||||
"from __future__ import with_statement\n"
|
||||
"import sys, time\n"
|
||||
"with open(sys.argv[1], 'w') as f:\n"
|
||||
" f.write('ok')\n"
|
||||
"# now sleep; expect caller to kill\n"
|
||||
"time.sleep(120)\n"
|
||||
"# if caller hasn't managed to kill by now, bad\n"
|
||||
"with open(sys.argv[1], 'w') as f:\n"
|
||||
" f.write('bad')\n");
|
||||
py.mParams["args"].append(out.getName());
|
||||
py.mPy = LLProcess::create(py.mParams);
|
||||
ensure("couldn't launch kill() script", py.mPy);
|
||||
// Capture id for later
|
||||
pid = py.mPy->getProcessID();
|
||||
// Wait for the script to wake up and do its first write
|
||||
int i = 0, timeout = 60;
|
||||
for ( ; i < timeout; ++i)
|
||||
{
|
||||
sleep(1);
|
||||
if (readfile(out.getName(), "from kill() script") == "ok")
|
||||
break;
|
||||
}
|
||||
// If we broke this loop because of the counter, something's wrong
|
||||
ensure("script never started", i < timeout);
|
||||
// Script has performed its first write and should now be sleeping.
|
||||
// Destroy the LLProcess, which should kill the child.
|
||||
}
|
||||
// wait for the script to terminate... one way or another.
|
||||
while (LLProcess::isRunning(pid))
|
||||
{
|
||||
sleep(1);
|
||||
}
|
||||
|
|
@ -609,57 +643,12 @@ namespace tut
|
|||
template<> template<>
|
||||
void object::test<6>()
|
||||
{
|
||||
set_test_name("implicit kill()");
|
||||
NamedTempFile out("out", "not started");
|
||||
LLProcessLauncher::ll_pid_t pid(0);
|
||||
{
|
||||
PythonProcessLauncher py("kill()",
|
||||
"from __future__ import with_statement\n"
|
||||
"import sys, time\n"
|
||||
"with open(sys.argv[1], 'w') as f:\n"
|
||||
" f.write('ok')\n"
|
||||
"# now sleep; expect caller to kill\n"
|
||||
"time.sleep(120)\n"
|
||||
"# if caller hasn't managed to kill by now, bad\n"
|
||||
"with open(sys.argv[1], 'w') as f:\n"
|
||||
" f.write('bad')\n");
|
||||
py.mPy.addArgument(out.getName());
|
||||
ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0);
|
||||
// Capture ll_pid_t for later
|
||||
pid = py.mPy.getProcessID();
|
||||
// Wait for the script to wake up and do its first write
|
||||
int i = 0, timeout = 60;
|
||||
for ( ; i < timeout; ++i)
|
||||
{
|
||||
sleep(1);
|
||||
if (readfile(out.getName(), "from kill() script") == "ok")
|
||||
break;
|
||||
}
|
||||
// If we broke this loop because of the counter, something's wrong
|
||||
ensure("script never started", i < timeout);
|
||||
// Script has performed its first write and should now be sleeping.
|
||||
// Destroy the LLProcessLauncher, which should kill the child.
|
||||
}
|
||||
// wait for the script to terminate... one way or another.
|
||||
while (LLProcessLauncher::isRunning(pid))
|
||||
{
|
||||
sleep(1);
|
||||
}
|
||||
// If kill() failed, the script would have woken up on its own and
|
||||
// overwritten the file with 'bad'. But if kill() succeeded, it should
|
||||
// not have had that chance.
|
||||
ensure_equals("kill() script output", readfile(out.getName()), "ok");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<7>()
|
||||
{
|
||||
set_test_name("orphan()");
|
||||
set_test_name("autokill");
|
||||
NamedTempFile from("from", "not started");
|
||||
NamedTempFile to("to", "");
|
||||
LLProcessLauncher::ll_pid_t pid(0);
|
||||
LLProcess::id pid(0);
|
||||
{
|
||||
PythonProcessLauncher py("orphan()",
|
||||
PythonProcessLauncher py("autokill",
|
||||
"from __future__ import with_statement\n"
|
||||
"import sys, time\n"
|
||||
"with open(sys.argv[1], 'w') as f:\n"
|
||||
|
|
@ -678,25 +667,24 @@ namespace tut
|
|||
"# okay, saw 'go', write 'ack'\n"
|
||||
"with open(sys.argv[1], 'w') as f:\n"
|
||||
" f.write('ack')\n");
|
||||
py.mPy.addArgument(from.getName());
|
||||
py.mPy.addArgument(to.getName());
|
||||
ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0);
|
||||
// Capture ll_pid_t for later
|
||||
pid = py.mPy.getProcessID();
|
||||
py.mParams["args"].append(from.getName());
|
||||
py.mParams["args"].append(to.getName());
|
||||
py.mParams["autokill"] = false;
|
||||
py.mPy = LLProcess::create(py.mParams);
|
||||
ensure("couldn't launch kill() script", py.mPy);
|
||||
// Capture id for later
|
||||
pid = py.mPy->getProcessID();
|
||||
// Wait for the script to wake up and do its first write
|
||||
int i = 0, timeout = 60;
|
||||
for ( ; i < timeout; ++i)
|
||||
{
|
||||
sleep(1);
|
||||
if (readfile(from.getName(), "from orphan() script") == "ok")
|
||||
if (readfile(from.getName(), "from autokill script") == "ok")
|
||||
break;
|
||||
}
|
||||
// If we broke this loop because of the counter, something's wrong
|
||||
ensure("script never started", i < timeout);
|
||||
// Script has performed its first write and should now be waiting
|
||||
// for us. Orphan it.
|
||||
py.mPy.orphan();
|
||||
// Now destroy the LLProcessLauncher, which should NOT kill the child!
|
||||
// Now destroy the LLProcess, which should NOT kill the child!
|
||||
}
|
||||
// If the destructor killed the child anyway, give it time to die
|
||||
sleep(2);
|
||||
|
|
@ -707,12 +695,12 @@ namespace tut
|
|||
outf << "go";
|
||||
} // flush and close.
|
||||
// now wait for the script to terminate... one way or another.
|
||||
while (LLProcessLauncher::isRunning(pid))
|
||||
while (LLProcess::isRunning(pid))
|
||||
{
|
||||
sleep(1);
|
||||
}
|
||||
// If the LLProcessLauncher destructor implicitly called kill(), the
|
||||
// If the LLProcess destructor implicitly called kill(), the
|
||||
// script could not have written 'ack' as we expect.
|
||||
ensure_equals("orphan() script output", readfile(from.getName()), "ack");
|
||||
ensure_equals("autokill script output", readfile(from.getName()), "ack");
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
@ -40,7 +40,7 @@ typedef U32 uint32_t;
|
|||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llprocess.h"
|
||||
#endif
|
||||
|
||||
#include "boost/range.hpp"
|
||||
|
|
@ -1557,14 +1557,15 @@ namespace tut
|
|||
}
|
||||
|
||||
#else // LL_DARWIN, LL_LINUX
|
||||
LLProcessLauncher py;
|
||||
py.setExecutable(PYTHON);
|
||||
py.addArgument(scriptfile.getName());
|
||||
ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.launch(), 0);
|
||||
LLSD params;
|
||||
params["executable"] = PYTHON;
|
||||
params["args"].append(scriptfile.getName());
|
||||
LLProcessPtr py(LLProcess::create(params));
|
||||
ensure(STRINGIZE("Couldn't launch " << desc << " script"), py);
|
||||
// Implementing timeout would mean messing with alarm() and
|
||||
// catching SIGALRM... later maybe...
|
||||
int status(0);
|
||||
if (waitpid(py.getProcessID(), &status, 0) == -1)
|
||||
if (waitpid(py->getProcessID(), &status, 0) == -1)
|
||||
{
|
||||
int waitpid_errno(errno);
|
||||
ensure_equals(STRINGIZE("Couldn't retrieve rc from " << desc << " script: "
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include "llpluginprocessparent.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llpluginmessageclasses.h"
|
||||
#include "stringize.h"
|
||||
|
||||
#include "llapr.h"
|
||||
|
||||
|
|
@ -134,7 +135,10 @@ LLPluginProcessParent::~LLPluginProcessParent()
|
|||
mSharedMemoryRegions.erase(iter);
|
||||
}
|
||||
|
||||
mProcess.kill();
|
||||
if (mProcess)
|
||||
{
|
||||
mProcess->kill();
|
||||
}
|
||||
killSockets();
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +163,8 @@ void LLPluginProcessParent::errorState(void)
|
|||
|
||||
void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, bool debug)
|
||||
{
|
||||
mProcess.setExecutable(launcher_filename);
|
||||
mProcess.setWorkingDirectory(plugin_dir);
|
||||
mProcessParams["executable"] = launcher_filename;
|
||||
mProcessParams["cwd"] = plugin_dir;
|
||||
mPluginFile = plugin_filename;
|
||||
mPluginDir = plugin_dir;
|
||||
mCPUUsage = 0.0f;
|
||||
|
|
@ -371,10 +375,8 @@ void LLPluginProcessParent::idle(void)
|
|||
// Launch the plugin process.
|
||||
|
||||
// Only argument to the launcher is the port number we're listening on
|
||||
std::stringstream stream;
|
||||
stream << mBoundPort;
|
||||
mProcess.addArgument(stream.str());
|
||||
if(mProcess.launch() != 0)
|
||||
mProcessParams["args"].append(stringize(mBoundPort));
|
||||
if (! (mProcess = LLProcess::create(mProcessParams)))
|
||||
{
|
||||
errorState();
|
||||
}
|
||||
|
|
@ -388,19 +390,18 @@ void LLPluginProcessParent::idle(void)
|
|||
// The command we're constructing would look like this on the command line:
|
||||
// osascript -e 'tell application "Terminal"' -e 'set win to do script "gdb -pid 12345"' -e 'do script "continue" in win' -e 'end tell'
|
||||
|
||||
std::stringstream cmd;
|
||||
|
||||
mDebugger.setExecutable("/usr/bin/osascript");
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("tell application \"Terminal\"");
|
||||
mDebugger.addArgument("-e");
|
||||
cmd << "set win to do script \"gdb -pid " << mProcess.getProcessID() << "\"";
|
||||
mDebugger.addArgument(cmd.str());
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("do script \"continue\" in win");
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("end tell");
|
||||
mDebugger.launch();
|
||||
LLSD params;
|
||||
params["executable"] = "/usr/bin/osascript";
|
||||
params["args"].append("-e");
|
||||
params["args"].append("tell application \"Terminal\"");
|
||||
params["args"].append("-e");
|
||||
params["args"].append(STRINGIZE("set win to do script \"gdb -pid "
|
||||
<< mProcess->getProcessID() << "\""));
|
||||
params["args"].append("-e");
|
||||
params["args"].append("do script \"continue\" in win");
|
||||
params["args"].append("-e");
|
||||
params["args"].append("end tell");
|
||||
mDebugger = LLProcess::create(params);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
|
@ -470,7 +471,7 @@ void LLPluginProcessParent::idle(void)
|
|||
break;
|
||||
|
||||
case STATE_EXITING:
|
||||
if(!mProcess.isRunning())
|
||||
if (! mProcess->isRunning())
|
||||
{
|
||||
setState(STATE_CLEANUP);
|
||||
}
|
||||
|
|
@ -498,7 +499,7 @@ void LLPluginProcessParent::idle(void)
|
|||
break;
|
||||
|
||||
case STATE_CLEANUP:
|
||||
mProcess.kill();
|
||||
mProcess->kill();
|
||||
killSockets();
|
||||
setState(STATE_DONE);
|
||||
break;
|
||||
|
|
@ -1077,7 +1078,7 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()
|
|||
{
|
||||
bool result = false;
|
||||
|
||||
if(!mProcess.isRunning())
|
||||
if (! mProcess->isRunning())
|
||||
{
|
||||
LL_WARNS("Plugin") << "child exited" << LL_ENDL;
|
||||
result = true;
|
||||
|
|
|
|||
|
|
@ -30,13 +30,14 @@
|
|||
#define LL_LLPLUGINPROCESSPARENT_H
|
||||
|
||||
#include "llapr.h"
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llprocess.h"
|
||||
#include "llpluginmessage.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llpluginsharedmemory.h"
|
||||
|
||||
#include "lliosocket.h"
|
||||
#include "llthread.h"
|
||||
#include "llsd.h"
|
||||
|
||||
class LLPluginProcessParentOwner
|
||||
{
|
||||
|
|
@ -148,8 +149,9 @@ private:
|
|||
LLSocket::ptr_t mListenSocket;
|
||||
LLSocket::ptr_t mSocket;
|
||||
U32 mBoundPort;
|
||||
|
||||
LLProcessLauncher mProcess;
|
||||
|
||||
LLSD mProcessParams;
|
||||
LLProcessPtr mProcess;
|
||||
|
||||
std::string mPluginFile;
|
||||
std::string mPluginDir;
|
||||
|
|
@ -171,7 +173,7 @@ private:
|
|||
bool mBlocked;
|
||||
bool mPolledInput;
|
||||
|
||||
LLProcessLauncher mDebugger;
|
||||
LLProcessPtr mDebugger;
|
||||
|
||||
F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch.
|
||||
F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@
|
|||
|
||||
#include "lltrans.h"
|
||||
#include "llui.h"
|
||||
#include "llprocess.h"
|
||||
#include "llsdutil.h"
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
// static
|
||||
const std::string LLExternalEditor::sFilenameMarker = "%s";
|
||||
|
|
@ -45,19 +48,8 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env
|
|||
return EC_NOT_SPECIFIED;
|
||||
}
|
||||
|
||||
// Add the filename marker if missing.
|
||||
if (cmd.find(sFilenameMarker) == std::string::npos)
|
||||
{
|
||||
cmd += " \"" + sFilenameMarker + "\"";
|
||||
llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl;
|
||||
}
|
||||
|
||||
string_vec_t tokens;
|
||||
if (tokenize(tokens, cmd) < 2) // 2 = bin + at least one arg (%s)
|
||||
{
|
||||
llwarns << "Error parsing editor command" << llendl;
|
||||
return EC_PARSE_ERROR;
|
||||
}
|
||||
tokenize(tokens, cmd);
|
||||
|
||||
// Check executable for existence.
|
||||
std::string bin_path = tokens[0];
|
||||
|
|
@ -68,51 +60,60 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env
|
|||
}
|
||||
|
||||
// Save command.
|
||||
mProcess.setExecutable(bin_path);
|
||||
mArgs.clear();
|
||||
mProcessParams["executable"] = bin_path;
|
||||
mProcessParams["args"].clear();
|
||||
for (size_t i = 1; i < tokens.size(); ++i)
|
||||
{
|
||||
if (i > 1) mArgs += " ";
|
||||
mArgs += "\"" + tokens[i] + "\"";
|
||||
mProcessParams["args"].append(tokens[i]);
|
||||
}
|
||||
llinfos << "Setting command [" << bin_path << " " << mArgs << "]" << llendl;
|
||||
|
||||
// Add the filename marker if missing.
|
||||
if (cmd.find(sFilenameMarker) == std::string::npos)
|
||||
{
|
||||
mProcessParams["args"].append(sFilenameMarker);
|
||||
llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl;
|
||||
}
|
||||
|
||||
llinfos << "Setting command [" << bin_path;
|
||||
BOOST_FOREACH(const std::string& arg, llsd::inArray(mProcessParams["args"]))
|
||||
{
|
||||
llcont << " \"" << arg << "\"";
|
||||
}
|
||||
llcont << "]" << llendl;
|
||||
|
||||
return EC_SUCCESS;
|
||||
}
|
||||
|
||||
LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path)
|
||||
{
|
||||
std::string args = mArgs;
|
||||
if (mProcess.getExecutable().empty() || args.empty())
|
||||
if (mProcessParams["executable"].asString().empty() || ! mProcessParams["args"].size())
|
||||
{
|
||||
llwarns << "Editor command not set" << llendl;
|
||||
return EC_NOT_SPECIFIED;
|
||||
}
|
||||
|
||||
// Copy params block so we can replace sFilenameMarker
|
||||
LLSD params(mProcessParams);
|
||||
|
||||
// Substitute the filename marker in the command with the actual passed file name.
|
||||
LLStringUtil::replaceString(args, sFilenameMarker, file_path);
|
||||
|
||||
// Split command into separate tokens.
|
||||
string_vec_t tokens;
|
||||
tokenize(tokens, args);
|
||||
|
||||
// Set process arguments taken from the command.
|
||||
mProcess.clearArguments();
|
||||
for (string_vec_t::const_iterator arg_it = tokens.begin(); arg_it != tokens.end(); ++arg_it)
|
||||
LLSD& args(params["args"]);
|
||||
for (LLSD::array_iterator ai(args.beginArray()), aend(args.endArray()); ai != aend; ++ai)
|
||||
{
|
||||
mProcess.addArgument(*arg_it);
|
||||
std::string sarg(*ai);
|
||||
LLStringUtil::replaceString(sarg, sFilenameMarker, file_path);
|
||||
*ai = sarg;
|
||||
}
|
||||
|
||||
// Run the editor.
|
||||
llinfos << "Running editor command [" << mProcess.getExecutable() + " " + args << "]" << llendl;
|
||||
int result = mProcess.launch();
|
||||
if (result == 0)
|
||||
llinfos << "Running editor command [" << params["executable"];
|
||||
BOOST_FOREACH(const std::string& arg, llsd::inArray(params["args"]))
|
||||
{
|
||||
// Prevent killing the process in destructor (will add it to the zombies list).
|
||||
mProcess.orphan();
|
||||
llcont << " \"" << arg << "\"";
|
||||
}
|
||||
|
||||
return result == 0 ? EC_SUCCESS : EC_FAILED_TO_RUN;
|
||||
llcont << "]" << llendl;
|
||||
// Prevent killing the process in destructor.
|
||||
params["autokill"] = false;
|
||||
return LLProcess::create(params) ? EC_SUCCESS : EC_FAILED_TO_RUN;
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
#ifndef LL_LLEXTERNALEDITOR_H
|
||||
#define LL_LLEXTERNALEDITOR_H
|
||||
|
||||
#include <llprocesslauncher.h>
|
||||
#include "llsd.h"
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
|
|
@ -98,8 +98,7 @@ private:
|
|||
static const std::string sSetting;
|
||||
|
||||
|
||||
std::string mArgs;
|
||||
LLProcessLauncher mProcess;
|
||||
LLSD mProcessParams;
|
||||
};
|
||||
|
||||
#endif // LL_LLEXTERNALEDITOR_H
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@
|
|||
#include "linden_common.h"
|
||||
#include <apr_file_io.h>
|
||||
#include "llapr.h"
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llprocess.h"
|
||||
#include "llupdateinstaller.h"
|
||||
#include "lldir.h"
|
||||
|
||||
#include "llsd.h"
|
||||
|
||||
#if defined(LL_WINDOWS)
|
||||
#pragma warning(disable: 4702) // disable 'unreachable code' so we can use lexical_cast (really!).
|
||||
|
|
@ -78,15 +78,13 @@ int ll_install_update(std::string const & script,
|
|||
llinfos << "UpdateInstaller: installing " << updatePath << " using " <<
|
||||
actualScriptPath << LL_ENDL;
|
||||
|
||||
LLProcessLauncher launcher;
|
||||
launcher.setExecutable(actualScriptPath);
|
||||
launcher.addArgument(updatePath);
|
||||
launcher.addArgument(ll_install_failed_marker_path());
|
||||
launcher.addArgument(boost::lexical_cast<std::string>(required));
|
||||
int result = launcher.launch();
|
||||
launcher.orphan();
|
||||
|
||||
return result;
|
||||
LLSD params;
|
||||
params["executable"] = actualScriptPath;
|
||||
params["args"].append(updatePath);
|
||||
params["args"].append(ll_install_failed_marker_path());
|
||||
params["args"].append(boost::lexical_cast<std::string>(required));
|
||||
params["autokill"] = false;
|
||||
return LLProcess::create(params)? 0 : -1;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue