432 lines
12 KiB
C++
432 lines
12 KiB
C++
/**
|
|
* @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 "llsdserialize.h"
|
|
#include "llsingleton.h"
|
|
#include "llstring.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 LLSDParamAdapter<Params>& params)
|
|
{
|
|
try
|
|
{
|
|
return LLProcessPtr(new LLProcess(params));
|
|
}
|
|
catch (const LLProcessError& e)
|
|
{
|
|
LL_WARNS("LLProcess") << e.what() << LL_ENDL;
|
|
return LLProcessPtr();
|
|
}
|
|
}
|
|
|
|
LLProcess::LLProcess(const LLSDParamAdapter<Params>& params):
|
|
mProcessID(0),
|
|
mAutokill(params.autokill)
|
|
{
|
|
if (! params.validateBlock(true))
|
|
{
|
|
throw LLProcessError(STRINGIZE("not launched: failed parameter validation\n"
|
|
<< LLSDNotationStreamer(params)));
|
|
}
|
|
|
|
launch(params);
|
|
}
|
|
|
|
LLProcess::~LLProcess()
|
|
{
|
|
if (mAutokill)
|
|
{
|
|
kill();
|
|
}
|
|
}
|
|
|
|
bool LLProcess::isRunning(void)
|
|
{
|
|
mProcessID = isRunning(mProcessID, mDesc);
|
|
return (mProcessID != 0);
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params)
|
|
{
|
|
std::string cwd(params.cwd);
|
|
if (! cwd.empty())
|
|
{
|
|
out << "cd " << LLStringUtil::quote(cwd) << ": ";
|
|
}
|
|
out << LLStringUtil::quote(params.executable);
|
|
BOOST_FOREACH(const std::string& arg, params.args)
|
|
{
|
|
out << ' ' << LLStringUtil::quote(arg);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Windows specific
|
|
*****************************************************************************/
|
|
#if LL_WINDOWS
|
|
|
|
static std::string WindowsErrorString(const std::string& operation);
|
|
|
|
/**
|
|
* Wrap a Windows Job Object for use in managing child-process lifespan.
|
|
*
|
|
* On Windows, we use a Job Object to constrain the lifespan of any
|
|
* autokill=true child process to the viewer's own lifespan:
|
|
* http://stackoverflow.com/questions/53208/how-do-i-automatically-destroy-child-processes-in-windows
|
|
* (thanks Richard!).
|
|
*
|
|
* We manage it using an LLSingleton for a couple of reasons:
|
|
*
|
|
* # Lazy initialization: if some viewer session never launches a child
|
|
* process, we should never have to create a Job Object.
|
|
* # Cross-DLL support: be wary of C++ statics when multiple DLLs are
|
|
* involved.
|
|
*/
|
|
class LLJob: public LLSingleton<LLJob>
|
|
{
|
|
public:
|
|
void assignProcess(const std::string& prog, HANDLE hProcess)
|
|
{
|
|
// If we never managed to initialize this Job Object, can't use it --
|
|
// but don't keep spamming the log, we already emitted warnings when
|
|
// we first tried to create.
|
|
if (! mJob)
|
|
return;
|
|
|
|
if (! AssignProcessToJobObject(mJob, hProcess))
|
|
{
|
|
LL_WARNS("LLProcess") << WindowsErrorString(STRINGIZE("AssignProcessToJobObject("
|
|
<< prog << ")")) << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
private:
|
|
friend class LLSingleton<LLJob>;
|
|
LLJob():
|
|
mJob(0)
|
|
{
|
|
mJob = CreateJobObject(NULL, NULL);
|
|
if (! mJob)
|
|
{
|
|
LL_WARNS("LLProcess") << WindowsErrorString("CreateJobObject()") << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
|
|
|
|
// Configure all child processes associated with this new job object
|
|
// to terminate when the calling process (us!) terminates.
|
|
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
if (! SetInformationJobObject(mJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)))
|
|
{
|
|
LL_WARNS("LLProcess") << WindowsErrorString("SetInformationJobObject()") << LL_ENDL;
|
|
// This Job Object is useless to us
|
|
CloseHandle(mJob);
|
|
// prevent assignProcess() from trying to use it
|
|
mJob = 0;
|
|
}
|
|
}
|
|
|
|
HANDLE mJob;
|
|
};
|
|
|
|
void LLProcess::launch(const LLSDParamAdapter<Params>& params)
|
|
{
|
|
PROCESS_INFORMATION pinfo;
|
|
STARTUPINFOA sinfo = { sizeof(sinfo) };
|
|
|
|
// LLProcess::create()'s caller passes a Unix-style array of strings for
|
|
// command-line arguments. Our caller can and should expect that these will be
|
|
// passed to the child process as individual arguments, regardless of content
|
|
// (e.g. embedded spaces). But because Windows invokes any child process with
|
|
// a single command-line string, this means we must quote each argument behind
|
|
// the scenes.
|
|
std::string args = LLStringUtil::quote(params.executable);
|
|
BOOST_FOREACH(const std::string& arg, params.args)
|
|
{
|
|
args += " ";
|
|
args += LLStringUtil::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 ) )
|
|
{
|
|
throw LLProcessError(WindowsErrorString("CreateProcessA"));
|
|
}
|
|
|
|
// 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
|
|
|
|
mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << pinfo.dwProcessId << ')');
|
|
LL_INFOS("LLProcess") << "Launched " << params << " (" << pinfo.dwProcessId << ")" << LL_ENDL;
|
|
|
|
// Now associate the new child process with our Job Object -- unless
|
|
// autokill is false, i.e. caller asserts the child should persist.
|
|
if (params.autokill)
|
|
{
|
|
LLJob::instance().assignProcess(mDesc, mProcessID);
|
|
}
|
|
}
|
|
|
|
LLProcess::id LLProcess::isRunning(id handle, const std::string& desc)
|
|
{
|
|
if (! handle)
|
|
return 0;
|
|
|
|
DWORD waitresult = WaitForSingleObject(handle, 0);
|
|
if(waitresult == WAIT_OBJECT_0)
|
|
{
|
|
// the process has completed.
|
|
if (! desc.empty())
|
|
{
|
|
LL_INFOS("LLProcess") << desc << " terminated" << LL_ENDL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
bool LLProcess::kill(void)
|
|
{
|
|
if (! mProcessID)
|
|
return false;
|
|
|
|
LL_INFOS("LLProcess") << "killing " << mDesc << LL_ENDL;
|
|
TerminateProcess(mProcessID, 0);
|
|
return ! isRunning();
|
|
}
|
|
|
|
/// GetLastError()/FormatMessage() boilerplate
|
|
static std::string WindowsErrorString(const std::string& operation)
|
|
{
|
|
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);
|
|
return STRINGIZE(operation << " failed (" << result << "): " << message);
|
|
}
|
|
return STRINGIZE(operation << " failed (" << result
|
|
<< "), but FormatMessage() did not explain");
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Non-Windows specific
|
|
*****************************************************************************/
|
|
#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 LLSDParamAdapter<Params>& 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
|
|
std::vector<std::string> args(params.args.begin(), params.args.end());
|
|
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;
|
|
|
|
mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcessID << ')');
|
|
LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL;
|
|
}
|
|
|
|
LLProcess::id LLProcess::isRunning(id pid, const std::string& desc)
|
|
{
|
|
if (! pid)
|
|
return 0;
|
|
|
|
// Check whether the process has exited, and reap it if it has.
|
|
if(reap_pid(pid))
|
|
{
|
|
// the process has exited.
|
|
if (! desc.empty())
|
|
{
|
|
LL_INFOS("LLProcess") << desc << " terminated" << LL_ENDL;
|
|
}
|
|
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.
|
|
LL_INFOS("LLProcess") << "killing " << mDesc << LL_ENDL;
|
|
(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
|