diff options
| author | Nat Goodspeed <nat@lindenlab.com> | 2012-01-20 19:08:00 -0500 | 
|---|---|---|
| committer | Nat Goodspeed <nat@lindenlab.com> | 2012-01-20 19:08:00 -0500 | 
| commit | 50d0446dd9378c4fee684ae0770f112b08a81254 (patch) | |
| tree | f157652d086356db8c3bf7ee4ce27b5147b66b9e | |
| parent | 4287dcaacf0804a5a73dbf37c629471e2855733c (diff) | |
| parent | f0dbb878337082d3f581874c12e6df2f4659a464 (diff) | |
Automated merge with http://hg.lindenlab.com/richard/viewer-initparam-llcommon
| -rw-r--r-- | indra/llcommon/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | indra/llcommon/llprocess.cpp | 338 | ||||
| -rw-r--r-- | indra/llcommon/llprocess.h | 106 | ||||
| -rw-r--r-- | indra/llcommon/llprocesslauncher.cpp | 357 | ||||
| -rw-r--r-- | indra/llcommon/llprocesslauncher.h | 90 | ||||
| -rw-r--r-- | indra/llcommon/llstreamqueue.cpp | 24 | ||||
| -rw-r--r-- | indra/llcommon/llstreamqueue.h | 240 | ||||
| -rw-r--r-- | indra/llcommon/tests/llprocess_test.cpp | 706 | ||||
| -rw-r--r-- | indra/llcommon/tests/llsdserialize_test.cpp | 274 | ||||
| -rw-r--r-- | indra/llcommon/tests/llstreamqueue_test.cpp | 197 | ||||
| -rw-r--r-- | indra/llplugin/llpluginprocessparent.cpp | 47 | ||||
| -rw-r--r-- | indra/llplugin/llpluginprocessparent.h | 10 | ||||
| -rw-r--r-- | indra/newview/llexternaleditor.cpp | 73 | ||||
| -rw-r--r-- | indra/newview/llexternaleditor.h | 5 | ||||
| -rw-r--r-- | indra/test/manageapr.h | 45 | ||||
| -rw-r--r-- | indra/test/namedtempfile.h | 114 | ||||
| -rw-r--r-- | indra/viewer_components/updater/llupdateinstaller.cpp | 20 | 
17 files changed, 1870 insertions, 785 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index a1aa887d3a..3255e28e8e 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -75,7 +75,7 @@ set(llcommon_SOURCE_FILES      llmortician.cpp      lloptioninterface.cpp      llptrto.cpp  -    llprocesslauncher.cpp +    llprocess.cpp      llprocessor.cpp      llqueuedthread.cpp      llrand.cpp @@ -90,6 +90,7 @@ set(llcommon_SOURCE_FILES      llsingleton.cpp      llstat.cpp      llstacktrace.cpp +    llstreamqueue.cpp      llstreamtools.cpp      llstring.cpp      llstringtable.cpp @@ -199,7 +200,7 @@ set(llcommon_HEADER_FILES      llpointer.h      llpreprocessor.h      llpriqueuemap.h -    llprocesslauncher.h +    llprocess.h      llprocessor.h      llptrskiplist.h      llptrskipmap.h @@ -226,6 +227,7 @@ set(llcommon_HEADER_FILES      llstat.h      llstatenums.h      llstl.h +    llstreamqueue.h      llstreamtools.h      llstrider.h      llstring.h @@ -331,6 +333,9 @@ 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(llprocess "" "${test_libs}" +                          "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py") +  LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")    # *TODO - reenable these once tcmalloc libs no longer break the build.    #ADD_BUILD_TEST(llallocator llcommon) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp new file mode 100644 index 0000000000..8c0caca680 --- /dev/null +++ b/indra/llcommon/llprocess.cpp @@ -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 diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h new file mode 100644 index 0000000000..9a74cfe829 --- /dev/null +++ b/indra/llcommon/llprocess.h @@ -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 diff --git a/indra/llcommon/llprocesslauncher.cpp b/indra/llcommon/llprocesslauncher.cpp deleted file mode 100644 index 10950181fd..0000000000 --- a/indra/llcommon/llprocesslauncher.cpp +++ /dev/null @@ -1,357 +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); -} - -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]; -	} -	 -	// 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) -{ -	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; - -	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 diff --git a/indra/llcommon/llprocesslauncher.h b/indra/llcommon/llprocesslauncher.h deleted file mode 100644 index 954c249147..0000000000 --- a/indra/llcommon/llprocesslauncher.h +++ /dev/null @@ -1,90 +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 -#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); -	void addArgument(const char *arg); -		 -	int launch(void); -	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. -	static void reap(void); -	 -	// Accessors for platform-specific process ID -#if LL_WINDOWS -	HANDLE getProcessHandle() { return mProcessHandle; }; -#else -	pid_t getProcessID() { return mProcessID; }; -#endif	 -	 -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 diff --git a/indra/llcommon/llstreamqueue.cpp b/indra/llcommon/llstreamqueue.cpp new file mode 100644 index 0000000000..1116a2b6a2 --- /dev/null +++ b/indra/llcommon/llstreamqueue.cpp @@ -0,0 +1,24 @@ +/** + * @file   llstreamqueue.cpp + * @author Nat Goodspeed + * @date   2012-01-05 + * @brief  Implementation for llstreamqueue. + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llstreamqueue.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +// As of this writing, llstreamqueue.h is entirely template-based, therefore +// we don't strictly need a corresponding .cpp file. However, our CMake test +// macro assumes one. Here it is. +bool llstreamqueue_cpp_ignored = true; diff --git a/indra/llcommon/llstreamqueue.h b/indra/llcommon/llstreamqueue.h new file mode 100644 index 0000000000..0726bad175 --- /dev/null +++ b/indra/llcommon/llstreamqueue.h @@ -0,0 +1,240 @@ +/** + * @file   llstreamqueue.h + * @author Nat Goodspeed + * @date   2012-01-04 + * @brief  Definition of LLStreamQueue + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLSTREAMQUEUE_H) +#define LL_LLSTREAMQUEUE_H + +#include <string> +#include <list> +#include <iosfwd>                   // std::streamsize +#include <boost/iostreams/categories.hpp> + +/** + * This class is a growable buffer between a producer and consumer. It serves + * as a queue usable with Boost.Iostreams -- hence, a "stream queue." + * + * This is especially useful for buffering nonblocking I/O. For instance, we + * want application logic to be able to serialize LLSD to a std::ostream. We + * may write more data than the destination pipe can handle all at once, but + * it's imperative NOT to block the application-level serialization call. So + * we buffer it instead. Successive frames can try nonblocking writes to the + * destination pipe until all buffered data has been sent. + * + * Similarly, we want application logic be able to deserialize LLSD from a + * std::istream. Again, we must not block that deserialize call waiting for + * more data to arrive from the input pipe! Instead we build up a buffer over + * a number of frames, using successive nonblocking reads, until we have + * "enough" data to be able to present it through a std::istream. + * + * @note The use cases for this class overlap somewhat with those for the + * LLIOPipe/LLPumpIO hierarchies, and indeed we considered using those. This + * class has two virtues over the older machinery: + * + * # It's vastly simpler -- way fewer concepts. It's not clear to me whether + *   there were ever LLIOPipe/etc. use cases that demanded all the fanciness + *   rolled in, or whether they were simply overdesigned. In any case, no + *   remaining Lindens will admit to familiarity with those classes -- and + *   they're sufficiently obtuse that it would take considerable learning + *   curve to figure out how to use them properly. The bottom line is that + *   current management is not keen on any more engineers climbing that curve. + * # This class is designed around available components such as std::string, + *   std::list, Boost.Iostreams. There's less proprietary code. + */ +template <typename Ch> +class LLGenericStreamQueue +{ +public: +    LLGenericStreamQueue(): +        mSize(0), +        mClosed(false) +    {} + +    /** +     * Boost.Iostreams Source Device facade for use with other Boost.Iostreams +     * functionality. LLGenericStreamQueue doesn't quite fit any of the Boost +     * 1.48 Iostreams concepts; instead it behaves as both a Sink and a +     * Source. This is its Source facade. +     */ +    struct Source +    { +        typedef Ch char_type; +        typedef boost::iostreams::source_tag category; + +        /// Bind the underlying LLGenericStreamQueue +        Source(LLGenericStreamQueue& sq): +            mStreamQueue(sq) +        {} + +        // Read up to n characters from the underlying data source into the +        // buffer s, returning the number of characters read; return -1 to +        // indicate EOF +        std::streamsize read(Ch* s, std::streamsize n) +        { +            return mStreamQueue.read(s, n); +        } + +        LLGenericStreamQueue& mStreamQueue; +    }; + +    /** +     * Boost.Iostreams Sink Device facade for use with other Boost.Iostreams +     * functionality. LLGenericStreamQueue doesn't quite fit any of the Boost +     * 1.48 Iostreams concepts; instead it behaves as both a Sink and a +     * Source. This is its Sink facade. +     */ +    struct Sink +    { +        typedef Ch char_type; +        typedef boost::iostreams::sink_tag category; + +        /// Bind the underlying LLGenericStreamQueue +        Sink(LLGenericStreamQueue& sq): +            mStreamQueue(sq) +        {} + +        /// Write up to n characters from the buffer s to the output sequence, +        /// returning the number of characters written +        std::streamsize write(const Ch* s, std::streamsize n) +        { +            return mStreamQueue.write(s, n); +        } + +        /// Send EOF to consumer +        void close() +        { +            mStreamQueue.close(); +        } + +        LLGenericStreamQueue& mStreamQueue; +    }; + +    /// Present Boost.Iostreams Source facade +    Source asSource() { return Source(*this); } +    /// Present Boost.Iostreams Sink facade +    Sink   asSink()   { return Sink(*this); } + +    /// append data to buffer +    std::streamsize write(const Ch* s, std::streamsize n) +    { +        // Unclear how often we might be asked to write 0 bytes -- perhaps a +        // naive caller responding to an unready nonblocking read. But if we +        // do get such a call, don't add a completely empty BufferList entry. +        if (n == 0) +            return n; +        // We could implement this using a single std::string object, a la +        // ostringstream. But the trouble with appending to a string is that +        // you might have to recopy all previous contents to grow its size. If +        // we want this to scale to large data volumes, better to allocate +        // individual pieces. +        mBuffer.push_back(string(s, n)); +        mSize += n; +        return n; +    } + +    /** +     * Inform this LLGenericStreamQueue that no further data are forthcoming. +     * For our purposes, close() is strictly a producer-side operation; +     * there's little point in closing the consumer side. +     */ +    void close() +    { +        mClosed = true; +    } + +    /// consume data from buffer +    std::streamsize read(Ch* s, std::streamsize n) +    { +        // read() is actually a convenience method for peek() followed by +        // skip(). +        std::streamsize got(peek(s, n)); +        // We can only skip() as many characters as we can peek(); ignore +        // skip() return here. +        skip(n); +        return got; +    } + +    /// Retrieve data from buffer without consuming. Like read(), return -1 on +    /// EOF. +    std::streamsize peek(Ch* s, std::streamsize n) const; + +    /// Consume data from buffer without retrieving. Unlike read() and peek(), +    /// at EOF we simply skip 0 characters. +    std::streamsize skip(std::streamsize n); + +    /// How many characters do we currently have buffered? +    std::streamsize size() const +    { +        return mSize; +    } + +private: +    typedef std::basic_string<Ch> string; +    typedef std::list<string> BufferList; +    BufferList mBuffer; +    std::streamsize mSize; +    bool mClosed; +}; + +template <typename Ch> +std::streamsize LLGenericStreamQueue<Ch>::peek(Ch* s, std::streamsize n) const +{ +    // Here we may have to build up 'n' characters from an arbitrary +    // number of individual BufferList entries. +    typename BufferList::const_iterator bli(mBuffer.begin()), blend(mBuffer.end()); +    // Indicate EOF if producer has closed the pipe AND we've exhausted +    // all previously-buffered data. +    if (mClosed && bli == blend) +    { +        return -1; +    } +    // Here either producer hasn't yet closed, or we haven't yet exhausted +    // remaining data. +    std::streamsize needed(n), got(0); +    // Loop until either we run out of BufferList entries or we've +    // completely satisfied the request. +    for ( ; bli != blend && needed; ++bli) +    { +        std::streamsize chunk(std::min(needed, std::streamsize(bli->length()))); +        std::copy(bli->begin(), bli->begin() + chunk, s); +        needed -= chunk; +        s      += chunk; +        got    += chunk; +    } +    return got; +} + +template <typename Ch> +std::streamsize LLGenericStreamQueue<Ch>::skip(std::streamsize n) +{ +    typename BufferList::iterator bli(mBuffer.begin()), blend(mBuffer.end()); +    std::streamsize toskip(n), skipped(0); +    while (bli != blend && toskip >= bli->length()) +    { +        std::streamsize chunk(bli->length()); +        typename BufferList::iterator zap(bli++); +        mBuffer.erase(zap); +        mSize   -= chunk; +        toskip  -= chunk; +        skipped += chunk; +    } +    if (bli != blend && toskip) +    { +        bli->erase(bli->begin(), bli->begin() + toskip); +        mSize   -= toskip; +        skipped += toskip; +    } +    return skipped; +} + +typedef LLGenericStreamQueue<char>    LLStreamQueue; +typedef LLGenericStreamQueue<wchar_t> LLWStreamQueue; + +#endif /* ! defined(LL_LLSTREAMQUEUE_H) */ diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp new file mode 100644 index 0000000000..55e22abd81 --- /dev/null +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -0,0 +1,706 @@ +/** + * @file   llprocess_test.cpp + * @author Nat Goodspeed + * @date   2011-12-19 + * @brief  Test for llprocess. + *  + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Copyright (c) 2011, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llprocess.h" +// STL headers +#include <vector> +#include <list> +// std headers +#include <fstream> +// external library headers +#include "llapr.h" +#include "apr_thread_proc.h" +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/algorithm/string/find_iterator.hpp> +#include <boost/algorithm/string/finder.hpp> +//#include <boost/lambda/lambda.hpp> +//#include <boost/lambda/bind.hpp> +// other Linden headers +#include "../test/lltut.h" +#include "../test/manageapr.h" +#include "../test/namedtempfile.h" +#include "stringize.h" +#include "llsdutil.h" + +#if defined(LL_WINDOWS) +#define sleep(secs) _sleep((secs) * 1000) +#define EOL "\r\n" +#else +#define EOL "\n" +#include <sys/wait.h> +#endif + +//namespace lambda = boost::lambda; + +// static instance of this manages APR init/cleanup +static ManageAPR manager; + +/***************************************************************************** +*   Helpers +*****************************************************************************/ + +#define ensure_equals_(left, right) \ +        ensure_equals(STRINGIZE(#left << " != " << #right), (left), (right)) + +#define aprchk(expr) aprchk_(#expr, (expr)) +static void aprchk_(const char* call, apr_status_t rv, apr_status_t expected=APR_SUCCESS) +{ +    tut::ensure_equals(STRINGIZE(call << " => " << rv << ": " << manager.strerror(rv)), +                       rv, expected); +} + +/** + * Read specified file using std::getline(). It is assumed to be an error if + * the file is empty: don't use this function if that's an acceptable case. + * Last line will not end with '\n'; this is to facilitate the usual case of + * string compares with a single line of output. + * @param pathname The file to read. + * @param desc Optional description of the file for error message; + * defaults to "in <pathname>" + */ +static std::string readfile(const std::string& pathname, const std::string& desc="") +{ +    std::string use_desc(desc); +    if (use_desc.empty()) +    { +        use_desc = STRINGIZE("in " << pathname); +    } +    std::ifstream inf(pathname.c_str()); +    std::string output; +    tut::ensure(STRINGIZE("No output " << use_desc), std::getline(inf, output)); +    std::string more; +    while (std::getline(inf, more)) +    { +        output += '\n' + more; +    } +    return output; +} + +/** + * Construct an LLProcess to run a Python script. + */ +struct PythonProcessLauncher +{ +    /** +     * @param desc Arbitrary description for error messages +     * @param script Python script, any form acceptable to NamedTempFile, +     * typically either a std::string or an expression of the form +     * (lambda::_1 << "script content with " << variable_data) +     */ +    template <typename CONTENT> +    PythonProcessLauncher(const std::string& desc, const CONTENT& script): +        mDesc(desc), +        mScript("py", script) +    { +        const char* PYTHON(getenv("PYTHON")); +        tut::ensure("Set $PYTHON to the Python interpreter", PYTHON); + +        mParams["executable"] = PYTHON; +        mParams["args"].append(mScript.getName()); +    } + +    /// Run Python script and wait for it to complete. +    void run() +    { +        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()) +        { +            sleep(1); +        } +    } + +    /** +     * 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, 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 +     * as sys.argv[1] and expect the script to write its output to that +     * file. This function implements the C++ (parent process) side of +     * that convention. +     */ +    std::string run_read() +    { +        NamedTempFile out("out", ""); // placeholder +        // pass name of this temporary file to the script +        mParams["args"].append(out.getName()); +        run(); +        // assuming the script wrote to that file, read it +        return readfile(out.getName(), STRINGIZE("from " << mDesc << " script")); +    } + +    LLSD mParams; +    LLProcessPtr mPy; +    std::string mDesc; +    NamedTempFile mScript; +}; + +/// convenience function for PythonProcessLauncher::run() +template <typename CONTENT> +static void python(const std::string& desc, const CONTENT& script) +{ +    PythonProcessLauncher py(desc, script); +    py.run(); +} + +/// convenience function for PythonProcessLauncher::run_read() +template <typename CONTENT> +static std::string python_out(const std::string& desc, const CONTENT& script) +{ +    PythonProcessLauncher py(desc, script); +    return py.run_read(); +} + +/// Create a temporary directory and clean it up later. +class NamedTempDir: public boost::noncopyable +{ +public: +    // Use python() function to create a temp directory: I've found +    // nothing in either Boost.Filesystem or APR quite like Python's +    // tempfile.mkdtemp(). +    // Special extra bonus: on Mac, mkdtemp() reports a pathname +    // starting with /var/folders/something, whereas that's really a +    // symlink to /private/var/folders/something. Have to use +    // realpath() to compare properly. +    NamedTempDir(): +        mPath(python_out("mkdtemp()", +                         "from __future__ import with_statement\n" +                         "import os.path, sys, tempfile\n" +                         "with open(sys.argv[1], 'w') as f:\n" +                         "    f.write(os.path.realpath(tempfile.mkdtemp()))\n")) +    {} + +    ~NamedTempDir() +    { +        aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp)); +    } + +    std::string getName() const { return mPath; } + +private: +    std::string mPath; +}; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct llprocess_data +    { +        LLAPRPool pool; +    }; +    typedef test_group<llprocess_data> llprocess_group; +    typedef llprocess_group::object object; +    llprocess_group llprocessgrp("llprocess"); + +    struct Item +    { +        Item(): tries(0) {} +        unsigned    tries; +        std::string which; +        std::string what; +    }; + +/*==========================================================================*| +#define tabent(symbol) { symbol, #symbol } +    static struct ReasonCode +    { +        int code; +        const char* name; +    } reasons[] = +    { +        tabent(APR_OC_REASON_DEATH), +        tabent(APR_OC_REASON_UNWRITABLE), +        tabent(APR_OC_REASON_RESTART), +        tabent(APR_OC_REASON_UNREGISTER), +        tabent(APR_OC_REASON_LOST), +        tabent(APR_OC_REASON_RUNNING) +    }; +#undef tabent +|*==========================================================================*/ + +    struct WaitInfo +    { +        WaitInfo(apr_proc_t* child_): +            child(child_), +            rv(-1),                 // we haven't yet called apr_proc_wait() +            rc(0), +            why(apr_exit_why_e(0)) +        {} +        apr_proc_t* child;          // which subprocess +        apr_status_t rv;            // return from apr_proc_wait() +        int rc;                     // child's exit code +        apr_exit_why_e why;         // APR_PROC_EXIT, APR_PROC_SIGNAL, APR_PROC_SIGNAL_CORE +    }; + +    void child_status_callback(int reason, void* data, int status) +    { +/*==========================================================================*| +        std::string reason_str; +        BOOST_FOREACH(const ReasonCode& rcp, reasons) +        { +            if (reason == rcp.code) +            { +                reason_str = rcp.name; +                break; +            } +        } +        if (reason_str.empty()) +        { +            reason_str = STRINGIZE("unknown reason " << reason); +        } +        std::cout << "child_status_callback(" << reason_str << ")\n"; +|*==========================================================================*/ + +        if (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST) +        { +            // Somewhat oddly, APR requires that you explicitly unregister +            // even when it already knows the child has terminated. +            apr_proc_other_child_unregister(data); + +            WaitInfo* wi(static_cast<WaitInfo*>(data)); +            // It's just wrong to call apr_proc_wait() here. The only way APR +            // knows to call us with APR_OC_REASON_DEATH is that it's already +            // reaped this child process, so calling wait() will only produce +            // "huh?" from the OS. We must rely on the status param passed in, +            // which unfortunately comes straight from the OS wait() call. +//          wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); +            wi->rv = APR_CHILD_DONE; // fake apr_proc_wait() results +#if defined(LL_WINDOWS) +            wi->why = APR_PROC_EXIT; +            wi->rc  = status;         // no encoding on Windows (no signals) +#else  // Posix +            if (WIFEXITED(status)) +            { +                wi->why = APR_PROC_EXIT; +                wi->rc  = WEXITSTATUS(status); +            } +            else if (WIFSIGNALED(status)) +            { +                wi->why = APR_PROC_SIGNAL; +                wi->rc  = WTERMSIG(status); +            } +            else                    // uh, shouldn't happen? +            { +                wi->why = APR_PROC_EXIT; +                wi->rc  = status;   // someone else will have to decode +            } +#endif // Posix +        } +    } + +    template<> template<> +    void object::test<1>() +    { +        set_test_name("raw APR nonblocking I/O"); + +        // Create a script file in a temporary place. +        NamedTempFile script("py", +            "import sys" EOL +            "import time" EOL +            EOL +            "time.sleep(2)" EOL +            "print >>sys.stdout, 'stdout after wait'" EOL +            "sys.stdout.flush()" EOL +            "time.sleep(2)" EOL +            "print >>sys.stderr, 'stderr after wait'" EOL +            "sys.stderr.flush()" EOL +            ); + +        // Arrange to track the history of our interaction with child: what we +        // fetched, which pipe it came from, how many tries it took before we +        // got it. +        std::vector<Item> history; +        history.push_back(Item()); + +        // Run the child process. +        apr_procattr_t *procattr = NULL; +        aprchk(apr_procattr_create(&procattr, pool.getAPRPool())); +        aprchk(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)); +        aprchk(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH)); + +        std::vector<const char*> argv; +        apr_proc_t child; +        argv.push_back("python"); +        // Have to have a named copy of this std::string so its c_str() value +        // will persist. +        std::string scriptname(script.getName()); +        argv.push_back(scriptname.c_str()); +        argv.push_back(NULL); + +        aprchk(apr_proc_create(&child, argv[0], +                               &argv[0], +                               NULL, // if we wanted to pass explicit environment +                               procattr, +                               pool.getAPRPool())); + +        // We do not want this child process to outlive our APR pool. On +        // destruction of the pool, forcibly kill the process. Tell APR to try +        // SIGTERM and wait 3 seconds. If that didn't work, use SIGKILL. +        apr_pool_note_subprocess(pool.getAPRPool(), &child, APR_KILL_AFTER_TIMEOUT); + +        // arrange to call child_status_callback() +        WaitInfo wi(&child); +        apr_proc_other_child_register(&child, child_status_callback, &wi, child.in, pool.getAPRPool()); + +        // TODO: +        // Stuff child.in until it (would) block to verify EWOULDBLOCK/EAGAIN. +        // Have child script clear it later, then write one more line to prove +        // that it gets through. + +        // Monitor two different output pipes. Because one will be closed +        // before the other, keep them in a list so we can drop whichever of +        // them is closed first. +        typedef std::pair<std::string, apr_file_t*> DescFile; +        typedef std::list<DescFile> DescFileList; +        DescFileList outfiles; +        outfiles.push_back(DescFile("out", child.out)); +        outfiles.push_back(DescFile("err", child.err)); + +        while (! outfiles.empty()) +        { +            // This peculiar for loop is designed to let us erase(dfli). With +            // a list, that invalidates only dfli itself -- but even so, we +            // lose the ability to increment it for the next item. So at the +            // top of every loop, while dfli is still valid, increment +            // dflnext. Then before the next iteration, set dfli to dflnext. +            for (DescFileList::iterator +                     dfli(outfiles.begin()), dflnext(outfiles.begin()), dflend(outfiles.end()); +                 dfli != dflend; dfli = dflnext) +            { +                // Only valid to increment dflnext once we're sure it's not +                // already at dflend. +                ++dflnext; + +                char buf[4096]; + +                apr_status_t rv = apr_file_gets(buf, sizeof(buf), dfli->second); +                if (APR_STATUS_IS_EOF(rv)) +                { +//                  std::cout << "(EOF on " << dfli->first << ")\n"; +//                  history.back().which = dfli->first; +//                  history.back().what  = "*eof*"; +//                  history.push_back(Item()); +                    outfiles.erase(dfli); +                    continue; +                } +                if (rv == EWOULDBLOCK || rv == EAGAIN) +                { +//                  std::cout << "(waiting; apr_file_gets(" << dfli->first << ") => " << rv << ": " << manager.strerror(rv) << ")\n"; +                    ++history.back().tries; +                    continue; +                } +                aprchk_("apr_file_gets(buf, sizeof(buf), dfli->second)", rv); +                // Is it even possible to get APR_SUCCESS but read 0 bytes? +                // Hope not, but defend against that anyway. +                if (buf[0]) +                { +//                  std::cout << dfli->first << ": " << buf; +                    history.back().which = dfli->first; +                    history.back().what.append(buf); +                    if (buf[strlen(buf) - 1] == '\n') +                        history.push_back(Item()); +                    else +                    { +                        // Just for pretty output... if we only read a partial +                        // line, terminate it. +//                      std::cout << "...\n"; +                    } +                } +            } +            // Do this once per tick, as we expect the viewer will +            apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); +            sleep(1); +        } +        apr_file_close(child.in); +        apr_file_close(child.out); +        apr_file_close(child.err); + +        // Okay, we've broken the loop because our pipes are all closed. If we +        // haven't yet called wait, give the callback one more chance. This +        // models the fact that unlike this small test program, the viewer +        // will still be running. +        if (wi.rv == -1) +        { +            std::cout << "last gasp apr_proc_other_child_refresh_all()\n"; +            apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); +        } + +        if (wi.rv == -1) +        { +            std::cout << "child_status_callback(APR_OC_REASON_DEATH) wasn't called" << std::endl; +            wi.rv = apr_proc_wait(wi.child, &wi.rc, &wi.why, APR_NOWAIT); +        } +//      std::cout << "child done: rv = " << rv << " (" << manager.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; +        aprchk_("apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT)", wi.rv, APR_CHILD_DONE); +        ensure_equals_(wi.why, APR_PROC_EXIT); +        ensure_equals_(wi.rc, 0); + +        // Beyond merely executing all the above successfully, verify that we +        // obtained expected output -- and that we duly got control while +        // waiting, proving the non-blocking nature of these pipes. +        try +        { +            unsigned i = 0; +            ensure("blocking I/O on child pipe (0)", history[i].tries); +            ensure_equals_(history[i].which, "out"); +            ensure_equals_(history[i].what,  "stdout after wait" EOL); +//          ++i; +//          ensure_equals_(history[i].which, "out"); +//          ensure_equals_(history[i].what,  "*eof*"); +            ++i; +            ensure("blocking I/O on child pipe (1)", history[i].tries); +            ensure_equals_(history[i].which, "err"); +            ensure_equals_(history[i].what,  "stderr after wait" EOL); +//          ++i; +//          ensure_equals_(history[i].which, "err"); +//          ensure_equals_(history[i].what,  "*eof*"); +        } +        catch (const failure&) +        { +            std::cout << "History:\n"; +            BOOST_FOREACH(const Item& item, history) +            { +                std::string what(item.what); +                if ((! what.empty()) && what[what.length() - 1] == '\n') +                { +                    what.erase(what.length() - 1); +                    if ((! what.empty()) && what[what.length() - 1] == '\r') +                    { +                        what.erase(what.length() - 1); +                        what.append("\\r"); +                    } +                    what.append("\\n"); +                } +                std::cout << "  " << item.which << ": '" << what << "' (" +                          << item.tries << " tries)\n"; +            } +            std::cout << std::flush; +            // re-raise same error; just want to enrich the output +            throw; +        } +    } + +    template<> template<> +    void object::test<2>() +    { +        set_test_name("setWorkingDirectory()"); +        // We want to test setWorkingDirectory(). But what directory is +        // guaranteed to exist on every machine, under every OS? Have to +        // create one. Naturally, ensure we clean it up when done. +        NamedTempDir tempdir; +        PythonProcessLauncher py("getcwd()", +                                 "from __future__ import with_statement\n" +                                 "import os, sys\n" +                                 "with open(sys.argv[1], 'w') as f:\n" +                                 "    f.write(os.getcwd())\n"); +        // Before running, call setWorkingDirectory() +        py.mParams["cwd"] = tempdir.getName(); +        ensure_equals("os.getcwd()", py.run_read(), tempdir.getName()); +    } + +    template<> template<> +    void object::test<3>() +    { +        set_test_name("arguments"); +        PythonProcessLauncher py("args", +                                 "from __future__ import with_statement\n" +                                 "import sys\n" +                                 // note nonstandard output-file arg! +                                 "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 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; +        ensure("didn't get first arg", li != lend); +        std::string arg(li->begin(), li->end()); +        ensure_equals(arg, "first arg"); +        ++li; +        ensure("didn't get second arg", li != lend); +        arg.assign(li->begin(), li->end()); +        ensure_equals(arg, "second arg"); +        ++li; +        ensure("didn't get output filename?!", li != lend); +        arg.assign(li->begin(), li->end()); +        ensure("output filename empty?!", ! arg.empty()); +        ++li; +        ensure("too many args", li == lend); +    } + +    template<> template<> +    void object::test<4>() +    { +        set_test_name("explicit kill()"); +        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"); +        NamedTempFile out("out", "not started"); +        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) +        { +            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. +        py.mPy->kill(); +        // wait for the script to terminate... one way or another. +        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); +        } +        // 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<6>() +    { +        set_test_name("autokill"); +        NamedTempFile from("from", "not started"); +        NamedTempFile to("to", ""); +        LLProcess::id pid(0); +        { +            PythonProcessLauncher py("autokill", +                                     "from __future__ import with_statement\n" +                                     "import sys, time\n" +                                     "with open(sys.argv[1], 'w') as f:\n" +                                     "    f.write('ok')\n" +                                     "# wait for 'go' from test program\n" +                                     "for i in xrange(60):\n" +                                     "    time.sleep(1)\n" +                                     "    with open(sys.argv[2]) as f:\n" +                                     "        go = f.read()\n" +                                     "    if go == 'go':\n" +                                     "        break\n" +                                     "else:\n" +                                     "    with open(sys.argv[1], 'w') as f:\n" +                                     "        f.write('never saw go')\n" +                                     "    sys.exit(1)\n" +                                     "# okay, saw 'go', write 'ack'\n" +                                     "with open(sys.argv[1], 'w') as f:\n" +                                     "    f.write('ack')\n"); +            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 autokill script") == "ok") +                    break; +            } +            // If we broke this loop because of the counter, something's wrong +            ensure("script never started", i < timeout); +            // Now destroy the LLProcess, which should NOT kill the child! +        } +        // If the destructor killed the child anyway, give it time to die +        sleep(2); +        // How do we know it's not terminated? By making it respond to +        // a specific stimulus in a specific way. +        { +            std::ofstream outf(to.getName().c_str()); +            outf << "go"; +        } // flush and close. +        // now wait for the script to terminate... one way or another. +        while (LLProcess::isRunning(pid)) +        { +            sleep(1); +        } +        // If the LLProcess destructor implicitly called kill(), the +        // script could not have written 'ack' as we expect. +        ensure_equals("autokill script output", readfile(from.getName()), "ack"); +    } +} // namespace tut diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 72322c3b72..7756ba6226 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -40,41 +40,15 @@ typedef U32 uint32_t;  #include <fcntl.h>  #include <sys/stat.h>  #include <sys/wait.h> -#include "llprocesslauncher.h" +#include "llprocess.h"  #endif -#include <sstream> - -/*==========================================================================*| -// Whoops, seems Linden's Boost package and the viewer are built with -// different settings of VC's /Zc:wchar_t switch! Using Boost.Filesystem -// pathname operations produces Windows link errors: -// unresolved external symbol "private: static class std::codecvt<unsigned short, -// char,int> const * & __cdecl boost::filesystem3::path::wchar_t_codecvt_facet()" -// unresolved external symbol "void __cdecl boost::filesystem3::path_traits::convert()" -// See: -// http://boost.2283326.n4.nabble.com/filesystem-v3-unicode-and-std-codecvt-linker-error-td3455549.html -// which points to: -// http://msdn.microsoft.com/en-us/library/dh8che7s%28v=VS.100%29.aspx - -// As we're not trying to preserve compatibility with old Boost.Filesystem -// code, but rather writing brand-new code, use the newest available -// Filesystem API. -#define BOOST_FILESYSTEM_VERSION 3 -#include "boost/filesystem.hpp" -#include "boost/filesystem/v3/fstream.hpp" -|*==========================================================================*/  #include "boost/range.hpp"  #include "boost/foreach.hpp"  #include "boost/function.hpp"  #include "boost/lambda/lambda.hpp"  #include "boost/lambda/bind.hpp"  namespace lambda = boost::lambda; -/*==========================================================================*| -// Aaaarrgh, Linden's Boost package doesn't even include Boost.Iostreams! -#include "boost/iostreams/stream.hpp" -#include "boost/iostreams/device/file_descriptor.hpp" -|*==========================================================================*/  #include "../llsd.h"  #include "../llsdserialize.h" @@ -82,236 +56,17 @@ namespace lambda = boost::lambda;  #include "../llformat.h"  #include "../test/lltut.h" +#include "../test/manageapr.h" +#include "../test/namedtempfile.h"  #include "stringize.h" +static ManageAPR manager; +  std::vector<U8> string_to_vector(const std::string& str)  {  	return std::vector<U8>(str.begin(), str.end());  } -#if ! LL_WINDOWS -// We want to call strerror_r(), but alarmingly, there are two different -// variants. The one that returns int always populates the passed buffer -// (except in case of error), whereas the other one always returns a valid -// char* but might or might not populate the passed buffer. How do we know -// which one we're getting? Define adapters for each and let the compiler -// select the applicable adapter. - -// strerror_r() returns char* -std::string message_from(int /*orig_errno*/, const char* /*buffer*/, const char* strerror_ret) -{ -    return strerror_ret; -} - -// strerror_r() returns int -std::string message_from(int orig_errno, const char* buffer, int strerror_ret) -{ -    if (strerror_ret == 0) -    { -        return buffer; -    } -    // Here strerror_r() has set errno. Since strerror_r() has already failed, -    // seems like a poor bet to call it again to diagnose its own error... -    int stre_errno = errno; -    if (stre_errno == ERANGE) -    { -        return STRINGIZE("strerror_r() can't explain errno " << orig_errno -                         << " (buffer too small)"); -    } -    if (stre_errno == EINVAL) -    { -        return STRINGIZE("unknown errno " << orig_errno); -    } -    // Here we don't even understand the errno from strerror_r()! -    return STRINGIZE("strerror_r() can't explain errno " << orig_errno -                     << " (error " << stre_errno << ')'); -} -#endif  // ! LL_WINDOWS - -// boost::filesystem::temp_directory_path() isn't yet in Boost 1.45! :-( -std::string temp_directory_path() -{ -#if LL_WINDOWS -    char buffer[4096]; -    GetTempPathA(sizeof(buffer), buffer); -    return buffer; - -#else  // LL_DARWIN, LL_LINUX -    static const char* vars[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR" }; -    BOOST_FOREACH(const char* var, vars) -    { -        const char* found = getenv(var); -        if (found) -            return found; -    } -    return "/tmp"; -#endif // LL_DARWIN, LL_LINUX -} - -// Windows presents a kinda sorta compatibility layer. Code to the yucky -// Windows names because they're less likely than the Posix names to collide -// with any other names in this source. -#if LL_WINDOWS -#define _remove   DeleteFileA -#else  // ! LL_WINDOWS -#define _open     open -#define _write    write -#define _close    close -#define _remove   remove -#endif  // ! LL_WINDOWS - -// Create a text file with specified content "somewhere in the -// filesystem," cleaning up when it goes out of scope. -class NamedTempFile -{ -public: -    // Function that accepts an ostream ref and (presumably) writes stuff to -    // it, e.g.: -    // (lambda::_1 << "the value is " << 17 << '\n') -    typedef boost::function<void(std::ostream&)> Streamer; - -    NamedTempFile(const std::string& ext, const std::string& content): -        mPath(temp_directory_path()) -    { -        createFile(ext, lambda::_1 << content); -    } - -    // Disambiguate when passing string literal -    NamedTempFile(const std::string& ext, const char* content): -        mPath(temp_directory_path()) -    { -        createFile(ext, lambda::_1 << content); -    } - -    NamedTempFile(const std::string& ext, const Streamer& func): -        mPath(temp_directory_path()) -    { -        createFile(ext, func); -    } - -    ~NamedTempFile() -    { -        _remove(mPath.c_str()); -    } - -    std::string getName() const { return mPath; } - -private: -    void createFile(const std::string& ext, const Streamer& func) -    { -        // Silly maybe, but use 'ext' as the name prefix. Strip off a leading -        // '.' if present. -        int pfx_offset = ((! ext.empty()) && ext[0] == '.')? 1 : 0; - -#if ! LL_WINDOWS -        // Make sure mPath ends with a directory separator, if it doesn't already. -        if (mPath.empty() || -            ! (mPath[mPath.length() - 1] == '\\' || mPath[mPath.length() - 1] == '/')) -        { -            mPath.append("/"); -        } - -        // mkstemp() accepts and modifies a char* template string. Generate -        // the template string, then copy to modifiable storage. -        // mkstemp() requires its template string to end in six X's. -        mPath += ext.substr(pfx_offset) + "XXXXXX"; -        // Copy to vector<char> -        std::vector<char> pathtemplate(mPath.begin(), mPath.end()); -        // append a nul byte for classic-C semantics -        pathtemplate.push_back('\0'); -        // std::vector promises that a pointer to the 0th element is the same -        // as a pointer to a contiguous classic-C array -        int fd(mkstemp(&pathtemplate[0])); -        if (fd == -1) -        { -            // The documented errno values (http://linux.die.net/man/3/mkstemp) -            // are used in a somewhat unusual way, so provide context-specific -            // errors. -            if (errno == EEXIST) -            { -                LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath -                                         << "\") could not create unique file " << LL_ENDL; -            } -            if (errno == EINVAL) -            { -                LL_ERRS("NamedTempFile") << "bad mkstemp() file path template '" -                                         << mPath << "'" << LL_ENDL; -            } -            // Shrug, something else -            int mkst_errno = errno; -            char buffer[256]; -            LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath << "\") failed: " -                                     << message_from(mkst_errno, buffer, -                                                     strerror_r(mkst_errno, buffer, sizeof(buffer))) -                                     << LL_ENDL; -        } -        // mkstemp() seems to have worked! Capture the modified filename. -        // Avoid the nul byte we appended. -        mPath.assign(pathtemplate.begin(), (pathtemplate.end()-1)); - -/*==========================================================================*| -        // Define an ostream on the open fd. Tell it to close fd on destruction. -        boost::iostreams::stream<boost::iostreams::file_descriptor_sink> -            out(fd, boost::iostreams::close_handle); -|*==========================================================================*/ - -        // Write desired content. -        std::ostringstream out; -        // Stream stuff to it. -        func(out); - -        std::string data(out.str()); -        int written(_write(fd, data.c_str(), data.length())); -        int closed(_close(fd)); -        llassert_always(written == data.length() && closed == 0); - -#else // LL_WINDOWS -        // GetTempFileName() is documented to require a MAX_PATH buffer. -        char tempname[MAX_PATH]; -        // Use 'ext' as filename prefix, but skip leading '.' if any. -        // The 0 param is very important: requests iterating until we get a -        // unique name. -        if (0 == GetTempFileNameA(mPath.c_str(), ext.c_str() + pfx_offset, 0, tempname)) -        { -            // I always have to look up this call...  :-P -            LPSTR msgptr; -            FormatMessageA( -                FORMAT_MESSAGE_ALLOCATE_BUFFER |  -                FORMAT_MESSAGE_FROM_SYSTEM | -                FORMAT_MESSAGE_IGNORE_INSERTS, -                NULL, -                GetLastError(), -                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), -                LPSTR(&msgptr),     // have to cast (char**) to (char*) -                0, NULL ); -            LL_ERRS("NamedTempFile") << "GetTempFileName(\"" << mPath << "\", \"" -                                     << (ext.c_str() + pfx_offset) << "\") failed: " -                                     << msgptr << LL_ENDL; -            LocalFree(msgptr); -        } -        // GetTempFileName() appears to have worked! Capture the actual -        // filename. -        mPath = tempname; -        // Open the file and stream content to it. Destructor will close. -        std::ofstream out(tempname); -        func(out); - -#endif  // LL_WINDOWS -    } - -    void peep() -    { -        std::cout << "File '" << mPath << "' contains:\n"; -        std::ifstream reader(mPath.c_str()); -        std::string line; -        while (std::getline(reader, line)) -            std::cout << line << '\n'; -        std::cout << "---\n"; -    } - -    std::string mPath; -}; -  namespace tut  {  	struct sd_xml_data @@ -1783,7 +1538,7 @@ namespace tut              const char* PYTHON(getenv("PYTHON"));              ensure("Set $PYTHON to the Python interpreter", PYTHON); -            NamedTempFile scriptfile(".py", script); +            NamedTempFile scriptfile("py", script);  #if LL_WINDOWS              std::string q("\""); @@ -1802,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: " @@ -1888,12 +1644,12 @@ namespace tut              "    else:\n"              "        assert False, 'Too many data items'\n"; -        // Create a something.llsd file containing 'data' serialized to +        // Create an llsdXXXXXX file containing 'data' serialized to          // notation. It's important to separate with newlines because Python's          // llsd module doesn't support parsing from a file stream, only from a          // string, so we have to know how much of the file to read into a          // string. -        NamedTempFile file(".llsd", +        NamedTempFile file("llsd",                             // NamedTempFile's boost::function constructor                             // takes a callable. To this callable it passes the                             // std::ostream with which it's writing the @@ -1926,7 +1682,7 @@ namespace tut          // Create an empty data file. This is just a placeholder for our          // script to write into. Create it to establish a unique name that          // we know. -        NamedTempFile file(".llsd", ""); +        NamedTempFile file("llsd", "");          python("write Python notation",                 lambda::_1 << diff --git a/indra/llcommon/tests/llstreamqueue_test.cpp b/indra/llcommon/tests/llstreamqueue_test.cpp new file mode 100644 index 0000000000..050ad5c5bf --- /dev/null +++ b/indra/llcommon/tests/llstreamqueue_test.cpp @@ -0,0 +1,197 @@ +/** + * @file   llstreamqueue_test.cpp + * @author Nat Goodspeed + * @date   2012-01-05 + * @brief  Test for llstreamqueue. + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llstreamqueue.h" +// STL headers +#include <vector> +// std headers +// external library headers +#include <boost/foreach.hpp> +// other Linden headers +#include "../test/lltut.h" +#include "stringize.h" + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct llstreamqueue_data +    { +        llstreamqueue_data(): +            // we want a buffer with actual bytes in it, not an empty vector +            buffer(10) +        {} +        // As LLStreamQueue is merely a typedef for +        // LLGenericStreamQueue<char>, and no logic in LLGenericStreamQueue is +        // specific to the <char> instantiation, we're comfortable for now +        // testing only the narrow-char version. +        LLStreamQueue strq; +        // buffer for use in multiple tests +        std::vector<char> buffer; +    }; +    typedef test_group<llstreamqueue_data> llstreamqueue_group; +    typedef llstreamqueue_group::object object; +    llstreamqueue_group llstreamqueuegrp("llstreamqueue"); + +    template<> template<> +    void object::test<1>() +    { +        set_test_name("empty LLStreamQueue"); +        ensure_equals("brand-new LLStreamQueue isn't empty", +                      strq.size(), 0); +        ensure_equals("brand-new LLStreamQueue returns data", +                      strq.asSource().read(&buffer[0], buffer.size()), 0); +        strq.asSink().close(); +        ensure_equals("closed empty LLStreamQueue not at EOF", +                      strq.asSource().read(&buffer[0], buffer.size()), -1); +    } + +    template<> template<> +    void object::test<2>() +    { +        set_test_name("one internal block, one buffer"); +        LLStreamQueue::Sink sink(strq.asSink()); +        ensure_equals("write(\"\")", sink.write("", 0), 0); +        ensure_equals("0 write should leave LLStreamQueue empty (size())", +                      strq.size(), 0); +        ensure_equals("0 write should leave LLStreamQueue empty (peek())", +                      strq.peek(&buffer[0], buffer.size()), 0); +        // The meaning of "atomic" is that it must be smaller than our buffer. +        std::string atomic("atomic"); +        ensure("test data exceeds buffer", atomic.length() < buffer.size()); +        ensure_equals(STRINGIZE("write(\"" << atomic << "\")"), +                      sink.write(&atomic[0], atomic.length()), atomic.length()); +        ensure_equals("size() after write()", strq.size(), atomic.length()); +        size_t peeklen(strq.peek(&buffer[0], buffer.size())); +        ensure_equals(STRINGIZE("peek(\"" << atomic << "\")"), +                      peeklen, atomic.length()); +        ensure_equals(STRINGIZE("peek(\"" << atomic << "\") result"), +                      std::string(buffer.begin(), buffer.begin() + peeklen), atomic); +        ensure_equals("size() after peek()", strq.size(), atomic.length()); +        // peek() should not consume. Use a different buffer to prove it isn't +        // just leftover data from the first peek(). +        std::vector<char> again(buffer.size()); +        peeklen = size_t(strq.peek(&again[0], again.size())); +        ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again"), +                      peeklen, atomic.length()); +        ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again result"), +                      std::string(again.begin(), again.begin() + peeklen), atomic); +        // now consume. +        std::vector<char> third(buffer.size()); +        size_t readlen(strq.read(&third[0], third.size())); +        ensure_equals(STRINGIZE("read(\"" << atomic << "\")"), +                      readlen, atomic.length()); +        ensure_equals(STRINGIZE("read(\"" << atomic << "\") result"), +                      std::string(third.begin(), third.begin() + readlen), atomic); +        ensure_equals("peek() after read()", strq.peek(&buffer[0], buffer.size()), 0); +        ensure_equals("size() after read()", strq.size(), 0); +    } + +    template<> template<> +    void object::test<3>() +    { +        set_test_name("basic skip()"); +        std::string lovecraft("lovecraft"); +        ensure("test data exceeds buffer", lovecraft.length() < buffer.size()); +        ensure_equals(STRINGIZE("write(\"" << lovecraft << "\")"), +                      strq.write(&lovecraft[0], lovecraft.length()), lovecraft.length()); +        size_t peeklen(strq.peek(&buffer[0], buffer.size())); +        ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\")"), +                      peeklen, lovecraft.length()); +        ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\") result"), +                      std::string(buffer.begin(), buffer.begin() + peeklen), lovecraft); +        std::streamsize skip1(4); +        ensure_equals(STRINGIZE("skip(" << skip1 << ")"), strq.skip(skip1), skip1); +        ensure_equals("size() after skip()", strq.size(), lovecraft.length() - skip1); +        size_t readlen(strq.read(&buffer[0], buffer.size())); +        ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\")"), +                      readlen, lovecraft.length() - skip1); +        ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\") result"), +                      std::string(buffer.begin(), buffer.begin() + readlen), +                      lovecraft.substr(skip1)); +        ensure_equals("unconsumed", strq.read(&buffer[0], buffer.size()), 0); +    } + +    template<> template<> +    void object::test<4>() +    { +        set_test_name("skip() multiple blocks"); +        std::string blocks[] = { "books of ", "H.P. ", "Lovecraft" }; +        std::streamsize total(blocks[0].length() + blocks[1].length() + blocks[2].length()); +        std::streamsize leave(5);   // len("craft") above +        std::streamsize skip(total - leave); +        std::streamsize written(0); +        BOOST_FOREACH(const std::string& block, blocks) +        { +            written += strq.write(&block[0], block.length()); +            ensure_equals("size() after write()", strq.size(), written); +        } +        std::streamsize skiplen(strq.skip(skip)); +        ensure_equals(STRINGIZE("skip(" << skip << ")"), skiplen, skip); +        ensure_equals("size() after skip()", strq.size(), leave); +        size_t readlen(strq.read(&buffer[0], buffer.size())); +        ensure_equals("read(\"craft\")", readlen, leave); +        ensure_equals("read(\"craft\") result", +                      std::string(buffer.begin(), buffer.begin() + readlen), "craft"); +    } + +    template<> template<> +    void object::test<5>() +    { +        set_test_name("concatenate blocks"); +        std::string blocks[] = { "abcd", "efghij", "klmnopqrs" }; +        BOOST_FOREACH(const std::string& block, blocks) +        { +            strq.write(&block[0], block.length()); +        } +        std::vector<char> longbuffer(30); +        std::streamsize readlen(strq.read(&longbuffer[0], longbuffer.size())); +        ensure_equals("read() multiple blocks", +                      readlen, blocks[0].length() + blocks[1].length() + blocks[2].length()); +        ensure_equals("read() multiple blocks result", +                      std::string(longbuffer.begin(), longbuffer.begin() + readlen), +                      blocks[0] + blocks[1] + blocks[2]); +    } + +    template<> template<> +    void object::test<6>() +    { +        set_test_name("split blocks"); +        std::string blocks[] = { "abcdefghijklm", "nopqrstuvwxyz" }; +        BOOST_FOREACH(const std::string& block, blocks) +        { +            strq.write(&block[0], block.length()); +        } +        strq.close(); +        // We've already verified what strq.size() should be at this point; +        // see above test named "skip() multiple blocks" +        std::streamsize chksize(strq.size()); +        std::streamsize readlen(strq.read(&buffer[0], buffer.size())); +        ensure_equals("read() 0", readlen, buffer.size()); +        ensure_equals("read() 0 result", std::string(buffer.begin(), buffer.end()), "abcdefghij"); +        chksize -= readlen; +        ensure_equals("size() after read() 0", strq.size(), chksize); +        readlen = strq.read(&buffer[0], buffer.size()); +        ensure_equals("read() 1", readlen, buffer.size()); +        ensure_equals("read() 1 result", std::string(buffer.begin(), buffer.end()), "klmnopqrst"); +        chksize -= readlen; +        ensure_equals("size() after read() 1", strq.size(), chksize); +        readlen = strq.read(&buffer[0], buffer.size()); +        ensure_equals("read() 2", readlen, chksize); +        ensure_equals("read() 2 result", +                      std::string(buffer.begin(), buffer.begin() + readlen), "uvwxyz"); +        ensure_equals("read() 3", strq.read(&buffer[0], buffer.size()), -1); +    } +} // namespace tut diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp index 110fac0f23..9b225cabb8 100644 --- a/indra/llplugin/llpluginprocessparent.cpp +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -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; diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h index c66723f175..e8bcba75e0 100644 --- a/indra/llplugin/llpluginprocessparent.h +++ b/indra/llplugin/llpluginprocessparent.h @@ -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. diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index ed1d7e860a..ba58cd8067 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -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]); +	} + +	// 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 << "\"";  	} -	llinfos << "Setting command [" << bin_path << " " << mArgs << "]" << llendl; +	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;  	} -	// 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); +	// Copy params block so we can replace sFilenameMarker +	LLSD params(mProcessParams); -	// 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) +	// Substitute the filename marker in the command with the actual passed file name. +	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 diff --git a/indra/newview/llexternaleditor.h b/indra/newview/llexternaleditor.h index ef5db56c6e..e81c360c24 100644 --- a/indra/newview/llexternaleditor.h +++ b/indra/newview/llexternaleditor.h @@ -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 diff --git a/indra/test/manageapr.h b/indra/test/manageapr.h new file mode 100644 index 0000000000..0c1ca7b7be --- /dev/null +++ b/indra/test/manageapr.h @@ -0,0 +1,45 @@ +/** + * @file   manageapr.h + * @author Nat Goodspeed + * @date   2012-01-13 + * @brief  ManageAPR class for simple test programs + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_MANAGEAPR_H) +#define LL_MANAGEAPR_H + +#include "llapr.h" + +/** + * Declare a static instance of this class for dead-simple ll_init_apr() at + * program startup, ll_cleanup_apr() at termination. This is recommended for + * use only with simple test programs. Once you start introducing static + * instances of other classes that depend on APR already being initialized, + * the indeterminate static-constructor-order problem rears its ugly head. + */ +class ManageAPR +{ +public: +    ManageAPR() +    { +        ll_init_apr(); +    } + +    ~ManageAPR() +    { +        ll_cleanup_apr(); +    } + +    static std::string strerror(apr_status_t rv) +    { +        char errbuf[256]; +        apr_strerror(rv, errbuf, sizeof(errbuf)); +        return errbuf; +    } +}; + +#endif /* ! defined(LL_MANAGEAPR_H) */ diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h new file mode 100644 index 0000000000..aa7058b111 --- /dev/null +++ b/indra/test/namedtempfile.h @@ -0,0 +1,114 @@ +/** + * @file   namedtempfile.h + * @author Nat Goodspeed + * @date   2012-01-13 + * @brief  NamedTempFile class for tests that need disk files as fixtures. + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_NAMEDTEMPFILE_H) +#define LL_NAMEDTEMPFILE_H + +#include "llapr.h" +#include "apr_file_io.h" +#include <string> +#include <boost/function.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> +#include <boost/noncopyable.hpp> +#include <iostream> +#include <sstream> + +/** + * Create a text file with specified content "somewhere in the + * filesystem," cleaning up when it goes out of scope. + */ +class NamedTempFile: public boost::noncopyable +{ +public: +    NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp): +        mPool(pool) +    { +        createFile(pfx, boost::lambda::_1 << content); +    } + +    // Disambiguate when passing string literal +    NamedTempFile(const std::string& pfx, const char* content, apr_pool_t* pool=gAPRPoolp): +        mPool(pool) +    { +        createFile(pfx, boost::lambda::_1 << content); +    } + +    // Function that accepts an ostream ref and (presumably) writes stuff to +    // it, e.g.: +    // (boost::lambda::_1 << "the value is " << 17 << '\n') +    typedef boost::function<void(std::ostream&)> Streamer; + +    NamedTempFile(const std::string& pfx, const Streamer& func, apr_pool_t* pool=gAPRPoolp): +        mPool(pool) +    { +        createFile(pfx, func); +    } + +    ~NamedTempFile() +    { +        ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool)); +    } + +    std::string getName() const { return mPath; } + +    void peep() +    { +        std::cout << "File '" << mPath << "' contains:\n"; +        std::ifstream reader(mPath.c_str()); +        std::string line; +        while (std::getline(reader, line)) +            std::cout << line << '\n'; +        std::cout << "---\n"; +    } + +private: +    void createFile(const std::string& pfx, const Streamer& func) +    { +        // Create file in a temporary place. +        const char* tempdir = NULL; +        ll_apr_assert_status(apr_temp_dir_get(&tempdir, mPool)); + +        // Construct a temp filename template in that directory. +        char *tempname = NULL; +        ll_apr_assert_status(apr_filepath_merge(&tempname, +                                                tempdir, +                                                (pfx + "XXXXXX").c_str(), +                                                0, +                                                mPool)); + +        // Create a temp file from that template. +        apr_file_t* fp = NULL; +        ll_apr_assert_status(apr_file_mktemp(&fp, +                                             tempname, +                                             APR_CREATE | APR_WRITE | APR_EXCL, +                                             mPool)); +        // apr_file_mktemp() alters tempname with the actual name. Not until +        // now is it valid to capture as our mPath. +        mPath = tempname; + +        // Write desired content. +        std::ostringstream out; +        // Stream stuff to it. +        func(out); + +        std::string data(out.str()); +        apr_size_t writelen(data.length()); +        ll_apr_assert_status(apr_file_write(fp, data.c_str(), &writelen)); +        ll_apr_assert_status(apr_file_close(fp)); +        llassert_always(writelen == data.length()); +    } + +    std::string mPath; +    apr_pool_t* mPool; +}; + +#endif /* ! defined(LL_NAMEDTEMPFILE_H) */ diff --git a/indra/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp index c7b70c2de8..e99fd0af7e 100644 --- a/indra/viewer_components/updater/llupdateinstaller.cpp +++ b/indra/viewer_components/updater/llupdateinstaller.cpp @@ -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().c_str()); -	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;  } | 
