/** * @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