Skip to content
Snippets Groups Projects
llprocess.cpp 7.81 KiB
Newer Older
/** 
 * @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 "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):
		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);
	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 LLSDParamAdapter<Params>& params)
{
	PROCESS_INFORMATION pinfo;
	STARTUPINFOA sinfo;
	memset(&sinfo, 0, sizeof(sinfo));
	
	std::string args = quote(params.executable);
	BOOST_FOREACH(const std::string& arg, 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.
	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 LLSDParamAdapter<Params>& params)
{
	// flush all buffers before the child inherits them
	::fflush(NULL);

	pid_t child = vfork();
	if (child == 0)
	{
		// child process

		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;
}

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