Files
SingularityViewer/indra/llcommon/llprocesslauncher.cpp
2010-04-02 02:48:44 -03:00

347 lines
7.4 KiB
C++

/**
* @file llprocesslauncher.cpp
* @brief Utility class for launching, terminating, and tracking the state of processes.
*
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/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;
}
void LLProcessLauncher::clearArguments()
{
mLaunchArguments.clear();
}
void LLProcessLauncher::addArgument(const std::string &arg)
{
mLaunchArguments.push_back(arg);
}
void LLProcessLauncher::addArgument(const char *arg)
{
mLaunchArguments.push_back(std::string(arg));
}
#if LL_WINDOWS
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 = "\"" + mExecutable + "\"";
for(int i = 0; i < (int)mLaunchArguments.size(); i++)
{
args += " ";
args += mLaunchArguments[i];
}
LL_INFOS("Plugin") << "Executable: " << mExecutable << " arguments: " << args << LL_ENDL;
// 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());
if( ! CreateProcessA( NULL, args2, NULL, NULL, FALSE, 0, NULL, NULL, &sinfo, &pinfo ) )
{
// TODO: do better than returning the OS-specific error code on failure...
result = GetLastError();
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)
{
if(mProcessHandle != 0)
{
DWORD waitresult = WaitForSingleObject(mProcessHandle, 0);
if(waitresult == WAIT_OBJECT_0)
{
// the process has completed.
mProcessHandle = 0;
}
}
return (mProcessHandle != 0);
}
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.
// Use _exit() instead of exit() per the vfork man page.
_exit(0);
}
// 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;
// At this point, the child process will have been created (since that's how vfork works -- the child borrowed our execution context until it forked)
// If the process doesn't exist at this point, the exec failed.
if(!isRunning())
{
result = -1;
}
return result;
}
bool LLProcessLauncher::isRunning(void)
{
if(mProcessID != 0)
{
// Check whether the process has exited, and reap it if it has.
if(reap_pid(mProcessID))
{
// the process has exited.
mProcessID = 0;
}
}
return (mProcessID != 0);
}
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