From f0dbb878337082d3f581874c12e6df2f4659a464 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Jan 2012 18:10:40 -0500 Subject: Per Richard, replace LLProcessLauncher with LLProcess. LLProcessLauncher had the somewhat fuzzy mandate of (1) accumulating parameters with which to launch a child process and (2) sometimes tracking the lifespan of the ensuing child process. But a valid LLProcessLauncher object might or might not have ever been associated with an actual child process. LLProcess specifically tracks a child process. In effect, it's a fairly thin wrapper around a process HANDLE (on Windows) or pid_t (elsewhere), with lifespan management thrown in. A static LLProcess::create() method launches a new child; create() accepts an LLSD bundle with child parameters. So building up a parameter bundle is deferred to LLSD rather than conflated with the process management object. Reconcile all known LLProcessLauncher consumers in the viewer code base, notably the class unit tests. --- indra/llcommon/llprocess.cpp | 338 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 indra/llcommon/llprocess.cpp (limited to 'indra/llcommon/llprocess.cpp') 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 +#include +#include + +/// 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 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 +#include +#include +#include + +// 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 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 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(&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 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::iterator iter = sZombies.begin(); + while(iter != sZombies.end()) + { + if(reap_pid(*iter)) + { + iter = sZombies.erase(iter); + } + else + { + iter++; + } + } +} +|*==========================================================================*/ + +#endif -- cgit v1.2.3 From 6e214960ce203d1d50d7bd6bd04eedee3afd0fa3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Jan 2012 20:19:50 -0500 Subject: Define LLProcess::Params; accept create(const LLSDParamAdapter&). This allows callers to pass either LLSD formatted as before -- which all callers still do -- or an actual LLProcess::Params block. --- indra/llcommon/llprocess.cpp | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 8c0caca680..dfb2ed69e9 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -26,7 +26,6 @@ #include "linden_common.h" #include "llprocess.h" -#include "llsd.h" #include "llsdserialize.h" #include "stringize.h" @@ -41,7 +40,7 @@ struct LLProcessError: public std::runtime_error LLProcessError(const std::string& msg): std::runtime_error(msg) {} }; -LLProcessPtr LLProcess::create(const LLSD& params) +LLProcessPtr LLProcess::create(const LLSDParamAdapter& params) { try { @@ -54,16 +53,13 @@ LLProcessPtr LLProcess::create(const LLSD& params) } } -LLProcess::LLProcess(const LLSD& params): +LLProcess::LLProcess(const LLSDParamAdapter& params): mProcessID(0), - mAutokill(params["autokill"].asBoolean()) + mAutokill(params.autokill) { - // nonstandard default bool value - if (! params.has("autokill")) - mAutokill = true; - if (! params.has("executable")) + if (! params.validateBlock(true)) { - throw LLProcessError(STRINGIZE("not launched: missing 'executable'\n" + throw LLProcessError(STRINGIZE("not launched: failed parameter validation\n" << LLSDNotationStreamer(params))); } @@ -108,14 +104,14 @@ static std::string quote(const std::string& str) return result + "\""; } -void LLProcess::launch(const LLSD& params) +void LLProcess::launch(const LLSDParamAdapter& 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"])) + std::string args = quote(params.executable); + BOOST_FOREACH(const std::string& arg, params.args) { args += " "; args += quote(arg); @@ -128,7 +124,7 @@ void LLProcess::launch(const LLSD& params) // 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"]); + std::string cwd(params.cwd); const char * working_directory = 0; if (! cwd.empty()) working_directory = cwd.c_str(); @@ -212,7 +208,7 @@ static bool reap_pid(pid_t pid) return false; } -void LLProcess::launch(const LLSD& params) +void LLProcess::launch(const LLSDParamAdapter& params) { // flush all buffers before the child inherits them ::fflush(NULL); @@ -222,7 +218,7 @@ void LLProcess::launch(const LLSD& params) { // child process - std::string cwd(params["cwd"]); + std::string cwd(params.cwd); if (! cwd.empty()) { // change to the desired child working directory @@ -239,12 +235,11 @@ void LLProcess::launch(const LLSD& params) std::vector fake_argv; // add the executable path - std::string executable(params["executable"]); + std::string executable(params.executable); fake_argv.push_back(executable.c_str()); // and any arguments - const LLSD& params_args(params["args"]); - std::vector args(params_args.beginArray(), params_args.endArray()); + std::vector args(params.args.begin(), params.args.end()); BOOST_FOREACH(const std::string& arg, args) { fake_argv.push_back(arg.c_str()); -- cgit v1.2.3 From b9a03b95aafb07eb32a8f99a671f2216acce96d4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 22 Jan 2012 09:16:11 -0500 Subject: On Windows, introduce viewer Job Object and assign children to it. The idea is that, with the right flag settings, this will cause the OS to terminate remaining viewer child processes when the viewer terminates -- whether or not it terminates intentionally. Of course, if LLProcess's caller specifies autokill=false, e.g. to run the viewer updater, that asserts that we WANT the child to persist beyond the viewer session itself. --- indra/llcommon/llprocess.cpp | 167 +++++++++++++++++++++++++++++++++---------- 1 file changed, 128 insertions(+), 39 deletions(-) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index dfb2ed69e9..d30d87411d 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -27,6 +27,7 @@ #include "linden_common.h" #include "llprocess.h" #include "llsdserialize.h" +#include "llsingleton.h" #include "stringize.h" #include @@ -80,43 +81,84 @@ bool LLProcess::isRunning(void) return (mProcessID != 0); } +/***************************************************************************** +* Windows specific +*****************************************************************************/ #if LL_WINDOWS -static std::string quote(const std::string& str) +static std::string WindowsErrorString(const std::string& operation); +static std::string quote(const std::string&); + +/** + * Wrap a Windows Job Object for use in managing child-process lifespan. + * + * On Windows, we use a Job Object to constrain the lifespan of any + * autokill=true child process to the viewer's own lifespan: + * http://stackoverflow.com/questions/53208/how-do-i-automatically-destroy-child-processes-in-windows + * (thanks Richard!). + * + * We manage it using an LLSingleton for a couple of reasons: + * + * # Lazy initialization: if some viewer session never launches a child + * process, we should never have to create a Job Object. + * # Cross-DLL support: be wary of C++ statics when multiple DLLs are + * involved. + */ +class LLJob: public LLSingleton { - 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] == '"') +public: + void assignProcess(const std::string& prog, HANDLE hProcess) { - return str; + // If we never managed to initialize this Job Object, can't use it -- + // but don't keep spamming the log, we already emitted warnings when + // we first tried to create. + if (! mJob) + return; + + if (! AssignProcessToJobObject(mJob, hProcess)) + { + LL_WARNS("LLProcess") << WindowsErrorString(STRINGIZE("AssignProcessToJobObject(\"" + << prog << "\")")) << LL_ENDL; + } } - // Not already quoted: do it. - std::string result("\""); - for (std::string::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) +private: + LLJob(): + mJob(0) { - if (*ci == '"') + mJob = CreateJobObject(NULL, NULL); + if (! mJob) { - result.append("\\"); + LL_WARNS("LLProcess") << WindowsErrorString("CreateJobObject()") << LL_ENDL; + return; + } + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 }; + + // Configure all child processes associated with this new job object + // to terminate when the calling process (us!) terminates. + jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (! SetInformationJobObject(mJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) + { + LL_WARNS("LLProcess") << WindowsErrorString("SetInformationJobObject()") << LL_ENDL; } - result.push_back(*ci); } - return result + "\""; -} + + HANDLE mJob; +}; void LLProcess::launch(const LLSDParamAdapter& params) { PROCESS_INFORMATION pinfo; - STARTUPINFOA sinfo; - memset(&sinfo, 0, sizeof(sinfo)); - + STARTUPINFOA sinfo = { sizeof(sinfo) }; + std::string args = quote(params.executable); BOOST_FOREACH(const std::string& arg, params.args) { args += " "; args += quote(arg); } - + // So retarded. Windows requires that the second parameter to // CreateProcessA be a writable (non-const) string... std::vector args2(args.begin(), args.end()); @@ -130,28 +172,14 @@ void LLProcess::launch(const LLSDParamAdapter& params) 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")); + throw LLProcessError(WindowsErrorString("CreateProcessA")); + } + + // Now associate the new child process with our Job Object -- unless + // autokill is false, i.e. caller asserts the child should persist. + if (params.autokill) + { + LLJob::instance().assignProcess(params.executable, pinfo.hProcess); } // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on @@ -184,6 +212,67 @@ bool LLProcess::kill(void) return ! isRunning(); } +/** + * Double-quote an argument string, unless it's already double-quoted. If we + * quote it, escape any embedded double-quote with backslash. + * + * LLProcess::create()'s caller passes a Unix-style array of strings for + * command-line arguments. Our caller can and should expect that these will be + * passed to the child process as individual arguments, regardless of content + * (e.g. embedded spaces). But because Windows invokes any child process with + * a single command-line string, this means we must quote each argument behind + * the scenes. + */ +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 + "\""; +} + +/// GetLastError()/FormatMessage() boilerplate +static std::string WindowsErrorString(const std::string& operation) +{ + int result = GetLastError(); + + LPTSTR error_str = 0; + if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + result, + 0, + (LPTSTR)&error_str, + 0, + NULL) + != 0) + { + char message[256]; + wcstombs(message, error_str, sizeof(message)); + message[sizeof(message)-1] = 0; + LocalFree(error_str); + return STRINGIZE(operation << " failed (" << result << "): " << message); + } + return STRINGIZE(operation << " failed (" << result + << "), but FormatMessage() did not explain"); +} + +/***************************************************************************** +* Non-Windows specific +*****************************************************************************/ #else // Mac and linux #include -- cgit v1.2.3 From aa1bbe3277842a9a6e7db5227b35f1fbea50b7a6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 22 Jan 2012 10:58:16 -0500 Subject: Make LLProcess::Params streamable; use that in LLExternalEditor. --- indra/llcommon/llprocess.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index d30d87411d..9d6c19f1dd 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -81,6 +81,21 @@ bool LLProcess::isRunning(void) return (mProcessID != 0); } +std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) +{ + std::string cwd(params.cwd); + if (! cwd.empty()) + { + out << "cd '" << cwd << "': "; + } + out << '"' << std::string(params.executable) << '"'; + BOOST_FOREACH(const std::string& arg, params.args) + { + out << " \"" << arg << '"'; + } + return out; +} + /***************************************************************************** * Windows specific *****************************************************************************/ -- cgit v1.2.3 From 748d1b311fdecf123df40bd7d22dd7e19afaca84 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 22 Jan 2012 11:56:38 -0500 Subject: Add LLProcess logging on launch(), kill(), isRunning(). Much as I dislike viewer log spam, seems to me starting a child process, killing it and observing its termination are noteworthy events. New logging makes LLExternalEditor launch message redundant; removed. --- indra/llcommon/llprocess.cpp | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 9d6c19f1dd..6d329a3fa1 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -77,7 +77,7 @@ LLProcess::~LLProcess() bool LLProcess::isRunning(void) { - mProcessID = isRunning(mProcessID); + mProcessID = isRunning(mProcessID, mDesc); return (mProcessID != 0); } @@ -190,20 +190,23 @@ void LLProcess::launch(const LLSDParamAdapter& params) throw LLProcessError(WindowsErrorString("CreateProcessA")); } + // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on + // CloseHandle(pinfo.hProcess); // stops leaks - nothing else + mProcessID = pinfo.hProcess; + CloseHandle(pinfo.hThread); // stops leaks - nothing else + + mDesc = STRINGIZE('"' << std::string(params.executable) << "\" (" << pinfo.dwProcessId << ')'); + LL_INFOS("LLProcess") << "Launched " << params << " (" << pinfo.dwProcessId << ")" << LL_ENDL; + // Now associate the new child process with our Job Object -- unless // autokill is false, i.e. caller asserts the child should persist. if (params.autokill) { - LLJob::instance().assignProcess(params.executable, pinfo.hProcess); + LLJob::instance().assignProcess(mDesc, mProcessID); } - - // 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) +LLProcess::id LLProcess::isRunning(id handle, const std::string& desc) { if (! handle) return 0; @@ -212,6 +215,10 @@ LLProcess::id LLProcess::isRunning(id handle) if(waitresult == WAIT_OBJECT_0) { // the process has completed. + if (! desc.empty()) + { + LL_INFOS("LLProcess") << desc << " terminated" << LL_ENDL; + } return 0; } @@ -223,6 +230,7 @@ bool LLProcess::kill(void) if (! mProcessID) return false; + LL_INFOS("LLProcess") << "killing " << mDesc << LL_ENDL; TerminateProcess(mProcessID, 0); return ! isRunning(); } @@ -369,9 +377,12 @@ void LLProcess::launch(const LLSDParamAdapter& params) // parent process mProcessID = child; + + mDesc = STRINGIZE('"' << std::string(params.executable) << "\" (" << mProcessID << ')'); + LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL; } -LLProcess::id LLProcess::isRunning(id pid) +LLProcess::id LLProcess::isRunning(id pid, const std::string& desc) { if (! pid) return 0; @@ -380,6 +391,10 @@ LLProcess::id LLProcess::isRunning(id pid) if(reap_pid(pid)) { // the process has exited. + if (! desc.empty()) + { + LL_INFOS("LLProcess") << desc << " terminated" << LL_ENDL; + } return 0; } @@ -393,6 +408,7 @@ bool LLProcess::kill(void) // Try to kill the process. We'll do approximately the same thing whether // the kill returns an error or not, so we ignore the result. + LL_INFOS("LLProcess") << "killing " << mDesc << LL_ENDL; (void)::kill(mProcessID, SIGTERM); // This will have the side-effect of reaping the zombie if the process has exited. -- cgit v1.2.3 From 738483e6302af5a9ad00fa3df17efe5336a03a41 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 22 Jan 2012 13:05:34 -0500 Subject: Every singleton needs a friend... --- indra/llcommon/llprocess.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 6d329a3fa1..eb7ce4129b 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -138,6 +138,7 @@ public: } private: + friend class LLSingleton; LLJob(): mJob(0) { -- cgit v1.2.3 From 507e136f9a25179992b2093e10ade1093cc71698 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Jan 2012 16:24:33 -0500 Subject: Per Richard: close unusable Job Object; move quote() to LLStringUtil. If LLProcess can't set the right flag on a Windows Job Object, the object isn't useful to us, so we might as well discard it. quote() is sufficiently general that it belongs in LLStringUtil instead of buried as a static helper function in llprocess.cpp. --- indra/llcommon/llprocess.cpp | 49 ++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 36 deletions(-) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index eb7ce4129b..2c7512419d 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -28,6 +28,7 @@ #include "llprocess.h" #include "llsdserialize.h" #include "llsingleton.h" +#include "llstring.h" #include "stringize.h" #include @@ -102,7 +103,6 @@ std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) #if LL_WINDOWS static std::string WindowsErrorString(const std::string& operation); -static std::string quote(const std::string&); /** * Wrap a Windows Job Object for use in managing child-process lifespan. @@ -157,6 +157,10 @@ private: if (! SetInformationJobObject(mJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) { LL_WARNS("LLProcess") << WindowsErrorString("SetInformationJobObject()") << LL_ENDL; + // This Job Object is useless to us + CloseHandle(mJob); + // prevent assignProcess() from trying to use it + mJob = 0; } } @@ -168,11 +172,17 @@ void LLProcess::launch(const LLSDParamAdapter& params) PROCESS_INFORMATION pinfo; STARTUPINFOA sinfo = { sizeof(sinfo) }; - std::string args = quote(params.executable); + // LLProcess::create()'s caller passes a Unix-style array of strings for + // command-line arguments. Our caller can and should expect that these will be + // passed to the child process as individual arguments, regardless of content + // (e.g. embedded spaces). But because Windows invokes any child process with + // a single command-line string, this means we must quote each argument behind + // the scenes. + std::string args = LLStringUtil::quote(params.executable); BOOST_FOREACH(const std::string& arg, params.args) { args += " "; - args += quote(arg); + args += LLStringUtil::quote(arg); } // So retarded. Windows requires that the second parameter to @@ -236,39 +246,6 @@ bool LLProcess::kill(void) return ! isRunning(); } -/** - * Double-quote an argument string, unless it's already double-quoted. If we - * quote it, escape any embedded double-quote with backslash. - * - * LLProcess::create()'s caller passes a Unix-style array of strings for - * command-line arguments. Our caller can and should expect that these will be - * passed to the child process as individual arguments, regardless of content - * (e.g. embedded spaces). But because Windows invokes any child process with - * a single command-line string, this means we must quote each argument behind - * the scenes. - */ -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 + "\""; -} - /// GetLastError()/FormatMessage() boilerplate static std::string WindowsErrorString(const std::string& operation) { -- cgit v1.2.3 From 27df0a84564d3a886661aae0faae74c2157cd31b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 27 Jan 2012 23:46:00 -0500 Subject: On Windows, only quote LLProcess arguments if they seem to need it. On Posix platforms, the OS argument mechanism makes quoting/reparsing unnecessary anyway, so this only affects Windows. Add optional 'triggers' parameter to LLStringUtils::quote() (default: space and double-quote). Only if the passed string contains a character in 'triggers' will it be double-quoted. This is observed to fix a Windows-specific problem in which plugin child process would fail to start because it wasn't expecting a quoted number. Use LLStringUtils::quote() more consistently in LLProcess implementation for logging. --- indra/llcommon/llprocess.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 2c7512419d..2b7a534fb3 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -87,12 +87,12 @@ std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) std::string cwd(params.cwd); if (! cwd.empty()) { - out << "cd '" << cwd << "': "; + out << "cd " << LLStringUtil::quote(cwd) << ": "; } - out << '"' << std::string(params.executable) << '"'; + out << LLStringUtil::quote(params.executable); BOOST_FOREACH(const std::string& arg, params.args) { - out << " \"" << arg << '"'; + out << ' ' << LLStringUtil::quote(arg); } return out; } @@ -132,8 +132,8 @@ public: if (! AssignProcessToJobObject(mJob, hProcess)) { - LL_WARNS("LLProcess") << WindowsErrorString(STRINGIZE("AssignProcessToJobObject(\"" - << prog << "\")")) << LL_ENDL; + LL_WARNS("LLProcess") << WindowsErrorString(STRINGIZE("AssignProcessToJobObject(" + << prog << ")")) << LL_ENDL; } } @@ -206,7 +206,7 @@ void LLProcess::launch(const LLSDParamAdapter& params) mProcessID = pinfo.hProcess; CloseHandle(pinfo.hThread); // stops leaks - nothing else - mDesc = STRINGIZE('"' << std::string(params.executable) << "\" (" << pinfo.dwProcessId << ')'); + mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << pinfo.dwProcessId << ')'); LL_INFOS("LLProcess") << "Launched " << params << " (" << pinfo.dwProcessId << ")" << LL_ENDL; // Now associate the new child process with our Job Object -- unless @@ -356,7 +356,7 @@ void LLProcess::launch(const LLSDParamAdapter& params) // parent process mProcessID = child; - mDesc = STRINGIZE('"' << std::string(params.executable) << "\" (" << mProcessID << ')'); + mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcessID << ')'); LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL; } -- cgit v1.2.3 From 803acbc5efde19c0acacfc7fe4990841dbf31a3e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 30 Jan 2012 10:14:10 -0500 Subject: Trim trailing "\r\n" from Windows FormatMessage() string for logging. --- indra/llcommon/llprocess.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 2b7a534fb3..8c0e8fe65e 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -261,11 +261,15 @@ static std::string WindowsErrorString(const std::string& operation) NULL) != 0) { + // convert from wide-char string to multi-byte string char message[256]; wcstombs(message, error_str, sizeof(message)); message[sizeof(message)-1] = 0; LocalFree(error_str); - return STRINGIZE(operation << " failed (" << result << "): " << message); + // convert to std::string to trim trailing whitespace + std::string mbsstr(message); + mbsstr.erase(mbsstr.find_last_not_of(" \t\r\n")); + return STRINGIZE(operation << " failed (" << result << "): " << mbsstr); } return STRINGIZE(operation << " failed (" << result << "), but FormatMessage() did not explain"); -- cgit v1.2.3 From 85581eefa63d8f8e8c5132c4cd7e137f6cb88869 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 30 Jan 2012 12:11:44 -0500 Subject: Expose 'handle' as well as 'id' on LLProcess objects. On Posix, these and the corresponding getProcessID()/getProcessHandle() accessors produce the same pid_t value; but on Windows, it's useful to distinguish an int-like 'id' useful to human log readers versus an opaque 'handle' for passing to platform-specific API functions. So make the distinction in a platform-independent way. --- indra/llcommon/llprocess.cpp | 50 +++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 19 deletions(-) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 8c0e8fe65e..a7bafb8cb0 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -42,7 +42,7 @@ struct LLProcessError: public std::runtime_error LLProcessError(const std::string& msg): std::runtime_error(msg) {} }; -LLProcessPtr LLProcess::create(const LLSDParamAdapter& params) +LLProcessPtr LLProcess::create(const LLSDOrParams& params) { try { @@ -55,8 +55,9 @@ LLProcessPtr LLProcess::create(const LLSDParamAdapter& params) } } -LLProcess::LLProcess(const LLSDParamAdapter& params): +LLProcess::LLProcess(const LLSDOrParams& params): mProcessID(0), + mProcessHandle(0), mAutokill(params.autokill) { if (! params.validateBlock(true)) @@ -78,8 +79,18 @@ LLProcess::~LLProcess() bool LLProcess::isRunning(void) { - mProcessID = isRunning(mProcessID, mDesc); - return (mProcessID != 0); + mProcessHandle = isRunning(mProcessHandle, mDesc); + return (mProcessHandle != 0); +} + +LLProcess::id LLProcess::getProcessID() const +{ + return mProcessID; +} + +LLProcess::handle LLProcess::getProcessHandle() const +{ + return mProcessHandle; } std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) @@ -122,7 +133,7 @@ static std::string WindowsErrorString(const std::string& operation); class LLJob: public LLSingleton { public: - void assignProcess(const std::string& prog, HANDLE hProcess) + void assignProcess(const std::string& prog, handle hProcess) { // If we never managed to initialize this Job Object, can't use it -- // but don't keep spamming the log, we already emitted warnings when @@ -164,10 +175,10 @@ private: } } - HANDLE mJob; + handle mJob; }; -void LLProcess::launch(const LLSDParamAdapter& params) +void LLProcess::launch(const LLSDOrParams& params) { PROCESS_INFORMATION pinfo; STARTUPINFOA sinfo = { sizeof(sinfo) }; @@ -201,28 +212,28 @@ void LLProcess::launch(const LLSDParamAdapter& params) throw LLProcessError(WindowsErrorString("CreateProcessA")); } - // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on // CloseHandle(pinfo.hProcess); // stops leaks - nothing else - mProcessID = pinfo.hProcess; + mProcessID = pinfo.dwProcessId; + mProcessHandle = pinfo.hProcess; CloseHandle(pinfo.hThread); // stops leaks - nothing else - mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << pinfo.dwProcessId << ')'); - LL_INFOS("LLProcess") << "Launched " << params << " (" << pinfo.dwProcessId << ")" << LL_ENDL; + mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcessID << ')'); + LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL; // Now associate the new child process with our Job Object -- unless // autokill is false, i.e. caller asserts the child should persist. if (params.autokill) { - LLJob::instance().assignProcess(mDesc, mProcessID); + LLJob::instance().assignProcess(mDesc, mProcessHandle); } } -LLProcess::id LLProcess::isRunning(id handle, const std::string& desc) +LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) { - if (! handle) + if (! h) return 0; - DWORD waitresult = WaitForSingleObject(handle, 0); + DWORD waitresult = WaitForSingleObject(h, 0); if(waitresult == WAIT_OBJECT_0) { // the process has completed. @@ -233,16 +244,16 @@ LLProcess::id LLProcess::isRunning(id handle, const std::string& desc) return 0; } - return handle; + return h; } bool LLProcess::kill(void) { - if (! mProcessID) + if (! mProcessHandle) return false; LL_INFOS("LLProcess") << "killing " << mDesc << LL_ENDL; - TerminateProcess(mProcessID, 0); + TerminateProcess(mProcessHandle, 0); return ! isRunning(); } @@ -302,7 +313,7 @@ static bool reap_pid(pid_t pid) return false; } -void LLProcess::launch(const LLSDParamAdapter& params) +void LLProcess::launch(const LLSDOrParams& params) { // flush all buffers before the child inherits them ::fflush(NULL); @@ -359,6 +370,7 @@ void LLProcess::launch(const LLSDParamAdapter& params) // parent process mProcessID = child; + mProcessHandle = child; mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcessID << ')'); LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL; -- cgit v1.2.3 From 60a777d2e3f9bec7a20e5de2df7cb3ecd2455b98 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 30 Jan 2012 12:32:05 -0500 Subject: LLProcess::handle must be qualified when used in LLJob class. --- indra/llcommon/llprocess.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index a7bafb8cb0..1e27f8ce1d 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -133,7 +133,7 @@ static std::string WindowsErrorString(const std::string& operation); class LLJob: public LLSingleton { public: - void assignProcess(const std::string& prog, handle hProcess) + void assignProcess(const std::string& prog, LLProcess::handle hProcess) { // If we never managed to initialize this Job Object, can't use it -- // but don't keep spamming the log, we already emitted warnings when @@ -175,7 +175,7 @@ private: } } - handle mJob; + LLProcess::handle mJob; }; void LLProcess::launch(const LLSDOrParams& params) -- cgit v1.2.3 From 491cd825561be1cf4a6f428a535811cbe0e3f179 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 30 Jan 2012 17:47:57 -0500 Subject: Set bit flag on CreateProcess() to allow AssignProcessToJobObject(). Windows 7 and friends tend to create a process already implicitly allocated to a job object, and a process can only belong to a single job object. Passing CREATE_BREAKAWAY_FROM_JOB in CreateProcessA()'s dwCreationFlags seems to bypass the access-denied error observed with AssignProcessToJobObject() otherwise. This change should (!) enable OS lifespan management for SLVoice.exe et al. --- indra/llcommon/llprocess.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'indra/llcommon/llprocess.cpp') diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 1e27f8ce1d..8611d67f25 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -207,7 +207,26 @@ void LLProcess::launch(const LLSDOrParams& params) 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 ) ) + + // It's important to pass CREATE_BREAKAWAY_FROM_JOB because Windows 7 et + // al. tend to implicitly launch new processes already bound to a job. From + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949%28v=vs.85%29.aspx : + // "The process must not already be assigned to a job; if it is, the + // function fails with ERROR_ACCESS_DENIED." ... + // "If the process is being monitored by the Program Compatibility + // Assistant (PCA), it is placed into a compatibility job. Therefore, the + // process must be created using CREATE_BREAKAWAY_FROM_JOB before it can + // be placed in another job." + if( ! CreateProcessA(NULL, // lpApplicationName + &args2[0], // lpCommandLine + NULL, // lpProcessAttributes + NULL, // lpThreadAttributes + FALSE, // bInheritHandles + CREATE_BREAKAWAY_FROM_JOB, // dwCreationFlags + NULL, // lpEnvironment + working_directory, // lpCurrentDirectory + &sinfo, // lpStartupInfo + &pinfo ) ) // lpProcessInformation { throw LLProcessError(WindowsErrorString("CreateProcessA")); } @@ -225,7 +244,7 @@ void LLProcess::launch(const LLSDOrParams& params) if (params.autokill) { LLJob::instance().assignProcess(mDesc, mProcessHandle); - } +} } LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) @@ -287,7 +306,7 @@ static std::string WindowsErrorString(const std::string& operation) } /***************************************************************************** -* Non-Windows specific +* Posix specific *****************************************************************************/ #else // Mac and linux @@ -444,4 +463,4 @@ void LLProcess::reap(void) } |*==========================================================================*/ -#endif +#endif // Posix -- cgit v1.2.3