summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt6
-rw-r--r--indra/llcommon/llprocess.cpp338
-rw-r--r--indra/llcommon/llprocess.h106
-rw-r--r--indra/llcommon/llprocesslauncher.cpp394
-rw-r--r--indra/llcommon/llprocesslauncher.h107
-rw-r--r--indra/llcommon/tests/llprocess_test.cpp (renamed from indra/llcommon/tests/llprocesslauncher_test.cpp)130
-rw-r--r--indra/llcommon/tests/llsdserialize_test.cpp13
7 files changed, 513 insertions, 581 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 2c376bb016..e2af7265aa 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -74,7 +74,7 @@ set(llcommon_SOURCE_FILES
llmortician.cpp
lloptioninterface.cpp
llptrto.cpp
- llprocesslauncher.cpp
+ llprocess.cpp
llprocessor.cpp
llqueuedthread.cpp
llrand.cpp
@@ -197,7 +197,7 @@ set(llcommon_HEADER_FILES
llpointer.h
llpreprocessor.h
llpriqueuemap.h
- llprocesslauncher.h
+ llprocess.h
llprocessor.h
llptrskiplist.h
llptrskipmap.h
@@ -328,7 +328,7 @@ 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(llprocesslauncher "" "${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}")
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 5791d14ec0..0000000000
--- a/indra/llcommon/llprocesslauncher.cpp
+++ /dev/null
@@ -1,394 +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);
-}
-
-#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 + "\"";
-}
-
-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 = quote(mExecutable);
- for(int i = 0; i < (int)mLaunchArguments.size(); i++)
- {
- args += " ";
- args += quote(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)
-{
- mProcessHandle = isRunning(mProcessHandle);
- return (mProcessHandle != 0);
-}
-
-LLProcessLauncher::ll_pid_t LLProcessLauncher::isRunning(ll_pid_t handle)
-{
- if (! handle)
- return 0;
-
- DWORD waitresult = WaitForSingleObject(handle, 0);
- if(waitresult == WAIT_OBJECT_0)
- {
- // the process has completed.
- return 0;
- }
-
- return handle;
-}
-
-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.
- LL_WARNS("LLProcessLauncher") << "failed to launch: ";
- for (const char * const * ai = fake_argv; *ai; ++ai)
- {
- LL_CONT << *ai << ' ';
- }
- 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
-
- 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)
-{
- mProcessID = isRunning(mProcessID);
- return (mProcessID != 0);
-}
-
-LLProcessLauncher::ll_pid_t LLProcessLauncher::isRunning(ll_pid_t 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 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 63193abd8f..0000000000
--- a/indra/llcommon/llprocesslauncher.h
+++ /dev/null
@@ -1,107 +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
-#define WIN32_LEAN_AND_MEAN
-#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);
-
- int launch(void);
- // 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);
-
- // 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.
- // (However, as of 2012-01-12 there are no such calls in the viewer code base. :-P )
- static void reap(void);
-
- // Accessors for platform-specific process ID
-#if LL_WINDOWS
- // (Windows flavor unused as of 2012-01-12)
- typedef HANDLE ll_pid_t;
- HANDLE getProcessHandle() const { return mProcessHandle; }
- ll_pid_t getProcessID() const { return mProcessHandle; }
-#else
- typedef pid_t ll_pid_t;
- ll_pid_t getProcessID() const { return mProcessID; };
-#endif
- /**
- * Test if a process (ll_pid_t obtained from getProcessID()) is still
- * running. Return is same nonzero ll_pid_t 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 = LLProcessLauncher::isRunning(childpid);
- * @endcode
- */
- static ll_pid_t isRunning(ll_pid_t);
-
-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/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 057f83631e..55e22abd81 100644
--- a/indra/llcommon/tests/llprocesslauncher_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -1,8 +1,8 @@
/**
- * @file llprocesslauncher_test.cpp
+ * @file llprocess_test.cpp
* @author Nat Goodspeed
* @date 2011-12-19
- * @brief Test for llprocesslauncher.
+ * @brief Test for llprocess.
*
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
* Copyright (c) 2011, Linden Research, Inc.
@@ -12,7 +12,7 @@
// Precompiled header
#include "linden_common.h"
// associated header
-#include "llprocesslauncher.h"
+#include "llprocess.h"
// STL headers
#include <vector>
#include <list>
@@ -32,6 +32,7 @@
#include "../test/manageapr.h"
#include "../test/namedtempfile.h"
#include "stringize.h"
+#include "llsdutil.h"
#if defined(LL_WINDOWS)
#define sleep(secs) _sleep((secs) * 1000)
@@ -88,7 +89,7 @@ static std::string readfile(const std::string& pathname, const std::string& desc
}
/**
- * Construct an LLProcessLauncher to run a Python script.
+ * Construct an LLProcess to run a Python script.
*/
struct PythonProcessLauncher
{
@@ -106,30 +107,30 @@ struct PythonProcessLauncher
const char* PYTHON(getenv("PYTHON"));
tut::ensure("Set $PYTHON to the Python interpreter", PYTHON);
- mPy.setExecutable(PYTHON);
- mPy.addArgument(mScript.getName());
+ mParams["executable"] = PYTHON;
+ mParams["args"].append(mScript.getName());
}
/// Run Python script and wait for it to complete.
void run()
{
- tut::ensure_equals(STRINGIZE("Couldn't launch " << mDesc << " script"),
- mPy.launch(), 0);
- // One of the irritating things about LLProcessLauncher is that
+ 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())
+ while (mPy->isRunning())
{
sleep(1);
}
}
/**
- * Run a Python script using LLProcessLauncher, expecting that it will
+ * 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, LLProcessLauncher provided distressingly few
+ * 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
@@ -141,13 +142,14 @@ struct PythonProcessLauncher
{
NamedTempFile out("out", ""); // placeholder
// pass name of this temporary file to the script
- mPy.addArgument(out.getName());
+ mParams["args"].append(out.getName());
run();
// assuming the script wrote to that file, read it
return readfile(out.getName(), STRINGIZE("from " << mDesc << " script"));
}
- LLProcessLauncher mPy;
+ LLSD mParams;
+ LLProcessPtr mPy;
std::string mDesc;
NamedTempFile mScript;
};
@@ -203,13 +205,13 @@ private:
*****************************************************************************/
namespace tut
{
- struct llprocesslauncher_data
+ struct llprocess_data
{
LLAPRPool pool;
};
- typedef test_group<llprocesslauncher_data> llprocesslauncher_group;
- typedef llprocesslauncher_group::object object;
- llprocesslauncher_group llprocesslaunchergrp("llprocesslauncher");
+ typedef test_group<llprocess_data> llprocess_group;
+ typedef llprocess_group::object object;
+ llprocess_group llprocessgrp("llprocess");
struct Item
{
@@ -502,17 +504,6 @@ namespace tut
template<> template<>
void object::test<2>()
{
- set_test_name("set/getExecutable()");
- LLProcessLauncher child;
- child.setExecutable("nonsense string");
- ensure_equals("setExecutable() 0", child.getExecutable(), "nonsense string");
- child.setExecutable("python");
- ensure_equals("setExecutable() 1", child.getExecutable(), "python");
- }
-
- template<> template<>
- void object::test<3>()
- {
set_test_name("setWorkingDirectory()");
// We want to test setWorkingDirectory(). But what directory is
// guaranteed to exist on every machine, under every OS? Have to
@@ -524,14 +515,14 @@ namespace tut
"with open(sys.argv[1], 'w') as f:\n"
" f.write(os.getcwd())\n");
// Before running, call setWorkingDirectory()
- py.mPy.setWorkingDirectory(tempdir.getName());
+ py.mParams["cwd"] = tempdir.getName();
ensure_equals("os.getcwd()", py.run_read(), tempdir.getName());
}
template<> template<>
- void object::test<4>()
+ void object::test<3>()
{
- set_test_name("clearArguments()");
+ set_test_name("arguments");
PythonProcessLauncher py("args",
"from __future__ import with_statement\n"
"import sys\n"
@@ -539,15 +530,11 @@ namespace tut
"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 called
- // addArgument() with the name of its own NamedTempFile. But let's
- // change it up.
- py.mPy.clearArguments();
- // re-add script pathname
- py.mPy.addArgument(py.mScript.getName()); // sys.argv[0]
- py.mPy.addArgument("first arg"); // sys.argv[1]
- py.mPy.addArgument("second arg"); // sys.argv[2]
- // run_read() calls addArgument() one more time, hence [3]
+ // 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;
@@ -567,7 +554,7 @@ namespace tut
}
template<> template<>
- void object::test<5>()
+ void object::test<4>()
{
set_test_name("explicit kill()");
PythonProcessLauncher py("kill()",
@@ -581,8 +568,9 @@ namespace tut
"with open(sys.argv[1], 'w') as f:\n"
" f.write('bad')\n");
NamedTempFile out("out", "not started");
- py.mPy.addArgument(out.getName());
- ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0);
+ 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)
@@ -594,9 +582,9 @@ namespace tut
// 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();
+ py.mPy->kill();
// wait for the script to terminate... one way or another.
- while (py.mPy.isRunning())
+ while (py.mPy->isRunning())
{
sleep(1);
}
@@ -607,11 +595,11 @@ namespace tut
}
template<> template<>
- void object::test<6>()
+ void object::test<5>()
{
set_test_name("implicit kill()");
NamedTempFile out("out", "not started");
- LLProcessLauncher::ll_pid_t pid(0);
+ LLProcess::id pid(0);
{
PythonProcessLauncher py("kill()",
"from __future__ import with_statement\n"
@@ -623,10 +611,11 @@ namespace tut
"# 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.mPy.addArgument(out.getName());
- ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0);
- // Capture ll_pid_t for later
- pid = py.mPy.getProcessID();
+ 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)
@@ -638,10 +627,10 @@ namespace tut
// 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 LLProcessLauncher, which should kill the child.
+ // Destroy the LLProcess, which should kill the child.
}
// wait for the script to terminate... one way or another.
- while (LLProcessLauncher::isRunning(pid))
+ while (LLProcess::isRunning(pid))
{
sleep(1);
}
@@ -652,14 +641,14 @@ namespace tut
}
template<> template<>
- void object::test<7>()
+ void object::test<6>()
{
- set_test_name("orphan()");
+ set_test_name("autokill");
NamedTempFile from("from", "not started");
NamedTempFile to("to", "");
- LLProcessLauncher::ll_pid_t pid(0);
+ LLProcess::id pid(0);
{
- PythonProcessLauncher py("orphan()",
+ PythonProcessLauncher py("autokill",
"from __future__ import with_statement\n"
"import sys, time\n"
"with open(sys.argv[1], 'w') as f:\n"
@@ -678,25 +667,24 @@ namespace tut
"# okay, saw 'go', write 'ack'\n"
"with open(sys.argv[1], 'w') as f:\n"
" f.write('ack')\n");
- py.mPy.addArgument(from.getName());
- py.mPy.addArgument(to.getName());
- ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0);
- // Capture ll_pid_t for later
- pid = py.mPy.getProcessID();
+ 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 orphan() script") == "ok")
+ 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);
- // Script has performed its first write and should now be waiting
- // for us. Orphan it.
- py.mPy.orphan();
- // Now destroy the LLProcessLauncher, which should NOT kill the child!
+ // Now destroy the LLProcess, which should NOT kill the child!
}
// If the destructor killed the child anyway, give it time to die
sleep(2);
@@ -707,12 +695,12 @@ namespace tut
outf << "go";
} // flush and close.
// now wait for the script to terminate... one way or another.
- while (LLProcessLauncher::isRunning(pid))
+ while (LLProcess::isRunning(pid))
{
sleep(1);
}
- // If the LLProcessLauncher destructor implicitly called kill(), the
+ // If the LLProcess destructor implicitly called kill(), the
// script could not have written 'ack' as we expect.
- ensure_equals("orphan() script output", readfile(from.getName()), "ack");
+ 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 4359e9afb9..7756ba6226 100644
--- a/indra/llcommon/tests/llsdserialize_test.cpp
+++ b/indra/llcommon/tests/llsdserialize_test.cpp
@@ -40,7 +40,7 @@ typedef U32 uint32_t;
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
-#include "llprocesslauncher.h"
+#include "llprocess.h"
#endif
#include "boost/range.hpp"
@@ -1557,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: "