From 057da807ac55f9b0583ff334cd12b3568ab81a18 Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Fri, 20 Jan 2012 13:51:46 -0800 Subject: removed LLXUIXML library moved LLInitParam, and LLRegistry to llcommon moved LLUIColor, LLTrans, and LLXUIParser to llui reviewed by Nat --- indra/newview/CMakeLists.txt | 3 --- indra/newview/llviewerprecompiledheaders.h | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) (limited to 'indra/newview') diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 6b2fe1e45a..baf7627f06 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -30,7 +30,6 @@ include(LLUI) include(LLVFS) include(LLWindow) include(LLXML) -include(LLXUIXML) include(LScript) include(Linking) include(NDOF) @@ -65,7 +64,6 @@ include_directories( ${LLVFS_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LLXUIXML_INCLUDE_DIRS} ${LSCRIPT_INCLUDE_DIRS} ${LSCRIPT_INCLUDE_DIRS}/lscript_compile ${LLLOGIN_INCLUDE_DIRS} @@ -1740,7 +1738,6 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLVFS_LIBRARIES} ${LLWINDOW_LIBRARIES} ${LLXML_LIBRARIES} - ${LLXUIXML_LIBRARIES} ${LSCRIPT_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} diff --git a/indra/newview/llviewerprecompiledheaders.h b/indra/newview/llviewerprecompiledheaders.h index f738b84bb9..6c8a827ba3 100644 --- a/indra/newview/llviewerprecompiledheaders.h +++ b/indra/newview/llviewerprecompiledheaders.h @@ -57,6 +57,8 @@ #include "lldeleteutils.h" #include "imageids.h" #include "indra_constants.h" +#include "llinitparam.h" + //#include "linden_common.h" //#include "llpreprocessor.h" #include "llallocator.h" @@ -124,7 +126,5 @@ // Library includes from llmessage project #include "llcachename.h" -// Library includes from llxuixml -#include "llinitparam.h" #endif -- cgit v1.3 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/CMakeLists.txt | 6 +- indra/llcommon/llprocess.cpp | 338 ++++++++++ indra/llcommon/llprocess.h | 106 +++ indra/llcommon/llprocesslauncher.cpp | 394 ----------- indra/llcommon/llprocesslauncher.h | 107 --- indra/llcommon/tests/llprocess_test.cpp | 706 ++++++++++++++++++++ indra/llcommon/tests/llprocesslauncher_test.cpp | 718 --------------------- indra/llcommon/tests/llsdserialize_test.cpp | 13 +- indra/llplugin/llpluginprocessparent.cpp | 47 +- indra/llplugin/llpluginprocessparent.h | 10 +- indra/newview/llexternaleditor.cpp | 73 +-- indra/newview/llexternaleditor.h | 5 +- .../updater/llupdateinstaller.cpp | 20 +- 13 files changed, 1238 insertions(+), 1305 deletions(-) create mode 100644 indra/llcommon/llprocess.cpp create mode 100644 indra/llcommon/llprocess.h delete mode 100644 indra/llcommon/llprocesslauncher.cpp delete mode 100644 indra/llcommon/llprocesslauncher.h create mode 100644 indra/llcommon/tests/llprocess_test.cpp delete mode 100644 indra/llcommon/tests/llprocesslauncher_test.cpp (limited to 'indra/newview') 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 +#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 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 +#include + +#if LL_WINDOWS +#define WIN32_LEAN_AND_MEAN +#include +#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 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 -#if LL_DARWIN || LL_LINUX -// not required or present on Win32 -#include -#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 -#include -#include - -static std::list 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::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 -#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 mLaunchArguments; - -#if LL_WINDOWS - HANDLE mProcessHandle; -#else - pid_t mProcessID; -#endif -}; - -#endif // LL_LLPROCESSLAUNCHER_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 +#include +// std headers +#include +// external library headers +#include "llapr.h" +#include "apr_thread_proc.h" +#include +#include +#include +#include +//#include +//#include +// 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 +#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 " + */ +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 + 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 +static void python(const std::string& desc, const CONTENT& script) +{ + PythonProcessLauncher py(desc, script); + py.run(); +} + +/// convenience function for PythonProcessLauncher::run_read() +template +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_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(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 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 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 DescFile; + typedef std::list 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 + 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/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp deleted file mode 100644 index 057f83631e..0000000000 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ /dev/null @@ -1,718 +0,0 @@ -/** - * @file llprocesslauncher_test.cpp - * @author Nat Goodspeed - * @date 2011-12-19 - * @brief Test for llprocesslauncher. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Copyright (c) 2011, Linden Research, Inc. - * $/LicenseInfo$ - */ - -// Precompiled header -#include "linden_common.h" -// associated header -#include "llprocesslauncher.h" -// STL headers -#include -#include -// std headers -#include -// external library headers -#include "llapr.h" -#include "apr_thread_proc.h" -#include -#include -#include -#include -//#include -//#include -// other Linden headers -#include "../test/lltut.h" -#include "../test/manageapr.h" -#include "../test/namedtempfile.h" -#include "stringize.h" - -#if defined(LL_WINDOWS) -#define sleep(secs) _sleep((secs) * 1000) -#define EOL "\r\n" -#else -#define EOL "\n" -#include -#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 " - */ -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 LLProcessLauncher 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 - 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); - - mPy.setExecutable(PYTHON); - mPy.addArgument(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 - // 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 LLProcessLauncher, expecting that it will - * write to the file passed as its sys.argv[1]. Retrieve that output. - * - * Until January 2012, LLProcessLauncher 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 - mPy.addArgument(out.getName()); - run(); - // assuming the script wrote to that file, read it - return readfile(out.getName(), STRINGIZE("from " << mDesc << " script")); - } - - LLProcessLauncher mPy; - std::string mDesc; - NamedTempFile mScript; -}; - -/// convenience function for PythonProcessLauncher::run() -template -static void python(const std::string& desc, const CONTENT& script) -{ - PythonProcessLauncher py(desc, script); - py.run(); -} - -/// convenience function for PythonProcessLauncher::run_read() -template -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 llprocesslauncher_data - { - LLAPRPool pool; - }; - typedef test_group llprocesslauncher_group; - typedef llprocesslauncher_group::object object; - llprocesslauncher_group llprocesslaunchergrp("llprocesslauncher"); - - 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(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 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 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 DescFile; - typedef std::list 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("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 - // 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.mPy.setWorkingDirectory(tempdir.getName()); - ensure_equals("os.getcwd()", py.run_read(), tempdir.getName()); - } - - template<> template<> - void object::test<4>() - { - set_test_name("clearArguments()"); - 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 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] - std::string output(py.run_read()); - boost::split_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<5>() - { - 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.mPy.addArgument(out.getName()); - ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0); - // 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<6>() - { - set_test_name("implicit kill()"); - NamedTempFile out("out", "not started"); - LLProcessLauncher::ll_pid_t 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.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(); - // 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 LLProcessLauncher, which should kill the child. - } - // wait for the script to terminate... one way or another. - while (LLProcessLauncher::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<7>() - { - set_test_name("orphan()"); - NamedTempFile from("from", "not started"); - NamedTempFile to("to", ""); - LLProcessLauncher::ll_pid_t pid(0); - { - PythonProcessLauncher py("orphan()", - "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.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(); - // 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") - 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! - } - // 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 (LLProcessLauncher::isRunning(pid)) - { - sleep(1); - } - // If the LLProcessLauncher destructor implicitly called kill(), the - // script could not have written 'ack' as we expect. - ensure_equals("orphan() 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 #include #include -#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: " 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 // 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 +#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/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp index 84f23b3acc..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 #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()); - launcher.addArgument(boost::lexical_cast(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(required)); + params["autokill"] = false; + return LLProcess::create(params)? 0 : -1; } -- cgit v1.3 From 47d94757075e338c480ba4d7d24948242a85a9bb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 21 Jan 2012 11:45:15 -0500 Subject: Convert LLProcess consumers from LLSD to LLProcess::Params block. Using a Params block gives compile-time checking against attribute typos. One might inadvertently set myLLSD["autofill"] = false and only discover it when things behave strangely at runtime; but trying to set myParams.autofill will produce a compile error. However, it's excellent that the same LLProcess::create() method can accept either LLProcess::Params or a properly-constructed LLSD block. --- indra/llcommon/tests/llprocess_test.cpp | 26 ++++++++--------- indra/llcommon/tests/llsdserialize_test.cpp | 6 ++-- indra/llplugin/llpluginprocessparent.cpp | 28 +++++++++--------- indra/llplugin/llpluginprocessparent.h | 12 ++++---- indra/newview/llexternaleditor.cpp | 33 ++++++++++++---------- indra/newview/llexternaleditor.h | 5 ++-- .../updater/llupdateinstaller.cpp | 12 ++++---- 7 files changed, 62 insertions(+), 60 deletions(-) (limited to 'indra/newview') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 55e22abd81..405540e436 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -107,8 +107,8 @@ struct PythonProcessLauncher const char* PYTHON(getenv("PYTHON")); tut::ensure("Set $PYTHON to the Python interpreter", PYTHON); - mParams["executable"] = PYTHON; - mParams["args"].append(mScript.getName()); + mParams.executable = PYTHON; + mParams.args.add(mScript.getName()); } /// Run Python script and wait for it to complete. @@ -142,13 +142,13 @@ struct PythonProcessLauncher { NamedTempFile out("out", ""); // placeholder // pass name of this temporary file to the script - mParams["args"].append(out.getName()); + mParams.args.add(out.getName()); run(); // assuming the script wrote to that file, read it return readfile(out.getName(), STRINGIZE("from " << mDesc << " script")); } - LLSD mParams; + LLProcess::Params mParams; LLProcessPtr mPy; std::string mDesc; NamedTempFile mScript; @@ -515,7 +515,7 @@ namespace tut "with open(sys.argv[1], 'w') as f:\n" " f.write(os.getcwd())\n"); // Before running, call setWorkingDirectory() - py.mParams["cwd"] = tempdir.getName(); + py.mParams.cwd = tempdir.getName(); ensure_equals("os.getcwd()", py.run_read(), tempdir.getName()); } @@ -531,9 +531,9 @@ namespace tut " 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] + // its own NamedTempFile to mParams.args (sys.argv[0]). + py.mParams.args.add("first arg"); // sys.argv[1] + py.mParams.args.add("second arg"); // sys.argv[2] // run_read() appends() one more argument, hence [3] std::string output(py.run_read()); boost::split_iterator @@ -568,7 +568,7 @@ namespace tut "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.mParams.args.add(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 @@ -611,7 +611,7 @@ 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.mParams["args"].append(out.getName()); + py.mParams.args.add(out.getName()); py.mPy = LLProcess::create(py.mParams); ensure("couldn't launch kill() script", py.mPy); // Capture id for later @@ -667,9 +667,9 @@ namespace tut "# 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.mParams.args.add(from.getName()); + py.mParams.args.add(to.getName()); + py.mParams.autokill = false; py.mPy = LLProcess::create(py.mParams); ensure("couldn't launch kill() script", py.mPy); // Capture id for later diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 7756ba6226..e625545763 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1557,9 +1557,9 @@ namespace tut } #else // LL_DARWIN, LL_LINUX - LLSD params; - params["executable"] = PYTHON; - params["args"].append(scriptfile.getName()); + LLProcess::Params params; + params.executable = PYTHON; + params.args.add(scriptfile.getName()); LLProcessPtr py(LLProcess::create(params)); ensure(STRINGIZE("Couldn't launch " << desc << " script"), py); // Implementing timeout would mean messing with alarm() and diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp index 9b225cabb8..f10eaee5b4 100644 --- a/indra/llplugin/llpluginprocessparent.cpp +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -163,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) { - mProcessParams["executable"] = launcher_filename; - mProcessParams["cwd"] = plugin_dir; + mProcessParams.executable = launcher_filename; + mProcessParams.cwd = plugin_dir; mPluginFile = plugin_filename; mPluginDir = plugin_dir; mCPUUsage = 0.0f; @@ -375,7 +375,7 @@ void LLPluginProcessParent::idle(void) // Launch the plugin process. // Only argument to the launcher is the port number we're listening on - mProcessParams["args"].append(stringize(mBoundPort)); + mProcessParams.args.add(stringize(mBoundPort)); if (! (mProcess = LLProcess::create(mProcessParams))) { errorState(); @@ -390,17 +390,17 @@ 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' - 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"); + LLProcess::Params params; + params.executable = "/usr/bin/osascript"; + params.args.add("-e"); + params.args.add("tell application \"Terminal\""); + params.args.add("-e"); + params.args.add(STRINGIZE("set win to do script \"gdb -pid " + << mProcess->getProcessID() << "\"")); + params.args.add("-e"); + params.args.add("do script \"continue\" in win"); + params.args.add("-e"); + params.args.add("end tell"); mDebugger = LLProcess::create(params); #endif diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h index e8bcba75e0..990fc5cbae 100644 --- a/indra/llplugin/llpluginprocessparent.h +++ b/indra/llplugin/llpluginprocessparent.h @@ -140,27 +140,27 @@ private: }; EState mState; void setState(EState state); - + bool pluginLockedUp(); bool pluginLockedUpOrQuit(); bool accept(); - + LLSocket::ptr_t mListenSocket; LLSocket::ptr_t mSocket; U32 mBoundPort; - LLSD mProcessParams; + LLProcess::Params mProcessParams; LLProcessPtr mProcess; - + std::string mPluginFile; std::string mPluginDir; LLPluginProcessParentOwner *mOwner; - + typedef std::map sharedMemoryRegionsType; sharedMemoryRegionsType mSharedMemoryRegions; - + LLSD mMessageClassVersions; std::string mPluginVersionString; diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index ba58cd8067..3dfebad958 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -60,22 +60,22 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env } // Save command. - mProcessParams["executable"] = bin_path; - mProcessParams["args"].clear(); + mProcessParams = LLProcess::Params(); + mProcessParams.executable = bin_path; for (size_t i = 1; i < tokens.size(); ++i) { - mProcessParams["args"].append(tokens[i]); + mProcessParams.args.add(tokens[i]); } // Add the filename marker if missing. if (cmd.find(sFilenameMarker) == std::string::npos) { - mProcessParams["args"].append(sFilenameMarker); + mProcessParams.args.add(sFilenameMarker); llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl; } llinfos << "Setting command [" << bin_path; - BOOST_FOREACH(const std::string& arg, llsd::inArray(mProcessParams["args"])) + BOOST_FOREACH(const std::string& arg, mProcessParams.args) { llcont << " \"" << arg << "\""; } @@ -86,33 +86,36 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path) { - if (mProcessParams["executable"].asString().empty() || ! mProcessParams["args"].size()) + // LLInitParams type wrappers don't seem to have empty() or size() + // methods; try determining emptiness by comparing begin/end iterators. + if (std::string(mProcessParams.executable).empty() || + (mProcessParams.args.begin() == mProcessParams.args.end())) { llwarns << "Editor command not set" << llendl; return EC_NOT_SPECIFIED; } // Copy params block so we can replace sFilenameMarker - LLSD params(mProcessParams); + LLProcess::Params params; + params.executable = mProcessParams.executable; // 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) + BOOST_FOREACH(const std::string& arg, mProcessParams.args) { - std::string sarg(*ai); - LLStringUtil::replaceString(sarg, sFilenameMarker, file_path); - *ai = sarg; + std::string fixed(arg); + LLStringUtil::replaceString(fixed, sFilenameMarker, file_path); + params.args.add(fixed); } // Run the editor. - llinfos << "Running editor command [" << params["executable"]; - BOOST_FOREACH(const std::string& arg, llsd::inArray(params["args"])) + llinfos << "Running editor command [" << std::string(params.executable); + BOOST_FOREACH(const std::string& arg, params.args) { llcont << " \"" << arg << "\""; } llcont << "]" << llendl; // Prevent killing the process in destructor. - params["autokill"] = false; + params.autokill = false; return LLProcess::create(params) ? EC_SUCCESS : EC_FAILED_TO_RUN; } diff --git a/indra/newview/llexternaleditor.h b/indra/newview/llexternaleditor.h index e81c360c24..fd2c25020c 100644 --- a/indra/newview/llexternaleditor.h +++ b/indra/newview/llexternaleditor.h @@ -27,7 +27,7 @@ #ifndef LL_LLEXTERNALEDITOR_H #define LL_LLEXTERNALEDITOR_H -#include "llsd.h" +#include "llprocess.h" /** * Usage: @@ -97,8 +97,7 @@ private: */ static const std::string sSetting; - - LLSD mProcessParams; + LLProcess::Params mProcessParams; }; #endif // LL_LLEXTERNALEDITOR_H diff --git a/indra/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp index e99fd0af7e..2f87d59373 100644 --- a/indra/viewer_components/updater/llupdateinstaller.cpp +++ b/indra/viewer_components/updater/llupdateinstaller.cpp @@ -78,12 +78,12 @@ int ll_install_update(std::string const & script, llinfos << "UpdateInstaller: installing " << updatePath << " using " << actualScriptPath << LL_ENDL; - LLSD params; - params["executable"] = actualScriptPath; - params["args"].append(updatePath); - params["args"].append(ll_install_failed_marker_path()); - params["args"].append(boost::lexical_cast(required)); - params["autokill"] = false; + LLProcess::Params params; + params.executable = actualScriptPath; + params.args.add(updatePath); + params.args.add(ll_install_failed_marker_path()); + params.args.add(boost::lexical_cast(required)); + params.autokill = false; return LLProcess::create(params)? 0 : -1; } -- cgit v1.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 +++++++++++++++ indra/llcommon/llprocess.h | 4 ++++ indra/newview/llexternaleditor.cpp | 14 ++------------ 3 files changed, 21 insertions(+), 12 deletions(-) (limited to 'indra/newview') 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 *****************************************************************************/ diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 9ea129baf2..7dbdf23679 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -31,6 +31,7 @@ #include "llsdparam.h" #include #include +#include // std::ostream #if LL_WINDOWS #define WIN32_LEAN_AND_MEAN @@ -124,4 +125,7 @@ private: bool mAutokill; }; +/// for logging +LL_COMMON_API std::ostream& operator<<(std::ostream&, const LLProcess::Params&); + #endif // LL_LLPROCESS_H diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index 3dfebad958..0d3ed0ba35 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -74,12 +74,7 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl; } - llinfos << "Setting command [" << bin_path; - BOOST_FOREACH(const std::string& arg, mProcessParams.args) - { - llcont << " \"" << arg << "\""; - } - llcont << "]" << llendl; + llinfos << "Setting command [" << mProcessParams << "]" << llendl; return EC_SUCCESS; } @@ -108,12 +103,7 @@ LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path) } // Run the editor. - llinfos << "Running editor command [" << std::string(params.executable); - BOOST_FOREACH(const std::string& arg, params.args) - { - llcont << " \"" << arg << "\""; - } - llcont << "]" << llendl; + llinfos << "Running editor command [" << params << "]" << llendl; // Prevent killing the process in destructor. params.autokill = false; return LLProcess::create(params) ? EC_SUCCESS : EC_FAILED_TO_RUN; -- cgit v1.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 +++++++++++++++++++++++++--------- indra/llcommon/llprocess.h | 7 ++++--- indra/newview/llexternaleditor.cpp | 4 +--- 3 files changed, 30 insertions(+), 15 deletions(-) (limited to 'indra/newview') 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. diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 7dbdf23679..019c33592c 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -97,7 +97,7 @@ public: typedef HANDLE id; #else typedef pid_t id; -#endif +#endif /// Get platform-specific process ID id getProcessID() const { return mProcessID; }; @@ -114,13 +114,14 @@ public: * functionality should be added as nonstatic members operating on * mProcessID. */ - static id isRunning(id); - + static id isRunning(id, const std::string& desc=""); + private: /// constructor is private: use create() instead LLProcess(const LLSDParamAdapter& params); void launch(const LLSDParamAdapter& params); + std::string mDesc; id mProcessID; bool mAutokill; }; diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index 0d3ed0ba35..561b87618c 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -102,9 +102,7 @@ LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path) params.args.add(fixed); } - // Run the editor. - llinfos << "Running editor command [" << params << "]" << llendl; - // Prevent killing the process in destructor. + // Run the editor. Prevent killing the process in destructor. params.autokill = false; return LLProcess::create(params) ? EC_SUCCESS : EC_FAILED_TO_RUN; } -- cgit v1.3 From 323c0ef64ee02d64c983c35eee4f7ac09851e116 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 22 Jan 2012 12:06:26 -0500 Subject: Use LLProcess::create() to launch SLVoice, rather than inline code. This appears to close a long-pending action item, as it seems the original LLProcessLauncher implementation was in fact cloned-and-edited from this logic in LLVivoxVoiceClient::stateMachine(). In any case, leveraging LLProcess buys us: - reduced redundancy; fewer maintenance points - logging for both success and errors - (possibly) better SLVoice.exe lifespan management. --- indra/newview/llvoicevivox.cpp | 140 +++++------------------------------------ 1 file changed, 16 insertions(+), 124 deletions(-) (limited to 'indra/newview') diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index df1d3f2955..820d1d73e1 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -27,8 +27,6 @@ #include "llviewerprecompiledheaders.h" #include "llvoicevivox.h" -#include - #include "llsdutil.h" // Linden library includes @@ -47,6 +45,7 @@ #include "llbase64.h" #include "llviewercontrol.h" #include "llappviewer.h" // for gDisconnected, gDisableVoice +#include "llprocess.h" // Viewer includes #include "llmutelist.h" // to check for muted avatars @@ -242,59 +241,21 @@ void LLVivoxVoiceClientCapResponder::result(const LLSD& content) } } - - -#if LL_WINDOWS -static HANDLE sGatewayHandle = 0; +static LLProcessPtr sGatewayPtr; static bool isGatewayRunning() { - bool result = false; - if(sGatewayHandle != 0) - { - DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0); - if(waitresult != WAIT_OBJECT_0) - { - result = true; - } - } - return result; -} -static void killGateway() -{ - if(sGatewayHandle != 0) - { - TerminateProcess(sGatewayHandle,0); - } -} - -#else // Mac and linux - -static pid_t sGatewayPID = 0; -static bool isGatewayRunning() -{ - bool result = false; - if(sGatewayPID != 0) - { - // A kill with signal number 0 has no effect, just does error checking. It should return an error if the process no longer exists. - if(kill(sGatewayPID, 0) == 0) - { - result = true; - } - } - return result; + return sGatewayPtr && sGatewayPtr->isRunning(); } static void killGateway() { - if(sGatewayPID != 0) + if (sGatewayPtr) { - kill(sGatewayPID, SIGTERM); + sGatewayPtr->kill(); } } -#endif - /////////////////////////////////////////////////////////////////////////////////////////////// LLVivoxVoiceClient::LLVivoxVoiceClient() : @@ -790,7 +751,7 @@ void LLVivoxVoiceClient::stateMachine() } else if(!isGatewayRunning()) { - if(true) + if (true) // production build, not test { // Launch the voice daemon @@ -809,102 +770,33 @@ void LLVivoxVoiceClient::stateMachine() #endif // See if the vivox executable exists llstat s; - if(!LLFile::stat(exe_path, &s)) + if (!LLFile::stat(exe_path, &s)) { // vivox executable exists. Build the command line and launch the daemon. + LLProcess::Params params; + params.executable = exe_path; // SLIM SDK: these arguments are no longer necessary. // std::string args = " -p tcp -h -c"; - std::string args; - std::string cmd; std::string loglevel = gSavedSettings.getString("VivoxDebugLevel"); - if(loglevel.empty()) { loglevel = "-1"; // turn logging off completely } - - args += " -ll "; - args += loglevel; - - LL_DEBUGS("Voice") << "Args for SLVoice: " << args << LL_ENDL; - -#if LL_WINDOWS - PROCESS_INFORMATION pinfo; - STARTUPINFOA sinfo; - - memset(&sinfo, 0, sizeof(sinfo)); - - std::string exe_dir = gDirUtilp->getAppRODataDir(); - cmd = "SLVoice.exe"; - cmd += args; - - // So retarded. Windows requires that the second parameter to CreateProcessA be writable (non-const) string... - char *args2 = new char[args.size() + 1]; - strcpy(args2, args.c_str()); - if(!CreateProcessA(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, exe_dir.c_str(), &sinfo, &pinfo)) - { -// DWORD dwErr = GetLastError(); - } - else - { - // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on - // CloseHandle(pinfo.hProcess); // stops leaks - nothing else - sGatewayHandle = pinfo.hProcess; - CloseHandle(pinfo.hThread); // stops leaks - nothing else - } - - delete[] args2; -#else // LL_WINDOWS - // This should be the same for mac and linux - { - std::vector arglist; - arglist.push_back(exe_path); - - // Split the argument string into separate strings for each argument - typedef boost::tokenizer > tokenizer; - boost::char_separator sep(" "); - tokenizer tokens(args, sep); - tokenizer::iterator token_iter; - for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) - { - arglist.push_back(*token_iter); - } - - // create an argv vector for the child process - char **fakeargv = new char*[arglist.size() + 1]; - int i; - for(i=0; i < arglist.size(); i++) - fakeargv[i] = const_cast(arglist[i].c_str()); + params.args.add("-ll"); + params.args.add(loglevel); + params.cwd = gDirUtilp->getAppRODataDir(); + sGatewayPtr = LLProcess::create(params); - fakeargv[i] = NULL; - - fflush(NULL); // flush all buffers before the child inherits them - pid_t id = vfork(); - if(id == 0) - { - // child - execv(exe_path.c_str(), fakeargv); - - // If we reach this point, the exec failed. - // Use _exit() instead of exit() per the vfork man page. - _exit(0); - } - - // parent - delete[] fakeargv; - sGatewayPID = id; - } -#endif // LL_WINDOWS mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost").c_str(), gSavedSettings.getU32("VivoxVoicePort")); - } + } else { LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL; - } + } } else - { + { // SLIM SDK: port changed from 44124 to 44125. // We can connect to a client gateway running on another host. This is useful for testing. // To do this, launch the gateway on a nearby host like this: -- cgit v1.3 From 199e875210435cbc914e80bf3eb6be6c985fce1c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Jan 2012 17:04:18 -0500 Subject: Use LLProcess::Params::args::empty() instead of comparing iterators. --- indra/newview/llexternaleditor.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'indra/newview') diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index 561b87618c..db482f023e 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -81,10 +81,7 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path) { - // LLInitParams type wrappers don't seem to have empty() or size() - // methods; try determining emptiness by comparing begin/end iterators. - if (std::string(mProcessParams.executable).empty() || - (mProcessParams.args.begin() == mProcessParams.args.end())) + if (std::string(mProcessParams.executable).empty() || mProcessParams.args.empty()) { llwarns << "Editor command not set" << llendl; return EC_NOT_SPECIFIED; -- cgit v1.3 From 028a05e79467e0c1fd7d63224fb447a964ab7a0b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 10 Feb 2012 12:04:27 -0500 Subject: Use wildcards instead of many version-specific lib names on Linux. viewer_manifest.py's Linux_i686Manifest class has contained directives to copy library files with names like (e.g.) "libapr-1.so.0.4.2", which means that every update to any such library requires messing with viewer_manifest.py. But LLManifest.path() claims to support wildcards, and it's more robust to specify "libapr-1.so*" instead. Unfortunately LLManifest.path()'s wildcard support only used to work for files in the source tree (vs. the artwork tree or the build tree). The logic in path() tries each tree in turn, relying on an exception to make it try the next tree. This exception was raised for a nonexistent specific filename -- but it never used to raise that exception for a wildcard matching 0 files. Instead it would simply report "0 files" and proceed, producing an invalid viewer install. Raise that exception for a wildcard matching nothing. This forces path() to consider the artwork tree and the build tree, permitting us to use wildcards in library names. Define an exception specific to LLManifest: ManifestException rather than the generic Python RuntimeException. Make it a subclass of RuntimeException so any existing callers expecting to catch RuntimeException will continue to work. --- indra/lib/python/indra/util/llmanifest.py | 27 +++++++++++++------- indra/newview/viewer_manifest.py | 42 +++++++++++-------------------- 2 files changed, 32 insertions(+), 37 deletions(-) (limited to 'indra/newview') diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index c33a03034a..237153b756 100644 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -41,6 +41,9 @@ import tarfile import errno import subprocess +class ManifestError(RuntimeError): + pass + def path_ancestors(path): drive, path = os.path.splitdrive(os.path.normpath(path)) result = [] @@ -180,6 +183,9 @@ def usage(srctree=""): arg['description'] % nd) def main(): +## import itertools +## print ' '.join((("'%s'" % item) if ' ' in item else item) +## for item in itertools.chain([sys.executable], sys.argv)) option_names = [arg['name'] + '=' for arg in ARGUMENTS] option_names.append('help') options, remainder = getopt.getopt(sys.argv[1:], "", option_names) @@ -385,7 +391,7 @@ class LLManifest(object): child.stdout.close() status = child.wait() if status: - raise RuntimeError( + raise ManifestError( "Command %s returned non-zero status (%s) \noutput:\n%s" % (command, status, output) ) return output @@ -395,7 +401,7 @@ class LLManifest(object): a) verify that you really have created it b) schedule it for cleanup""" if not os.path.exists(path): - raise RuntimeError, "Should be something at path " + path + raise ManifestError, "Should be something at path " + path self.created_paths.append(path) def put_in_file(self, contents, dst): @@ -550,7 +556,7 @@ class LLManifest(object): except (IOError, os.error), why: errors.append((srcname, dstname, why)) if errors: - raise RuntimeError, errors + raise ManifestError, errors def cmakedirs(self, path): @@ -598,13 +604,16 @@ class LLManifest(object): def check_file_exists(self, path): if not os.path.exists(path) and not os.path.islink(path): - raise RuntimeError("Path %s doesn't exist" % ( - os.path.normpath(os.path.join(os.getcwd(), path)),)) + raise ManifestError("Path %s doesn't exist" % (os.path.abspath(path),)) - wildcard_pattern = re.compile('\*') + wildcard_pattern = re.compile(r'\*') def expand_globs(self, src, dst): src_list = glob.glob(src) + # Assume that if caller specifies a wildcard, s/he wants it to match + # at least one file... + if not src_list: + raise ManifestError("Path %s doesn't exist" % (os.path.abspath(src),)) src_re, d_template = self.wildcard_regex(src.replace('\\', '/'), dst.replace('\\', '/')) for s in src_list: @@ -615,7 +624,7 @@ class LLManifest(object): sys.stdout.write("Processing %s => %s ... " % (src, dst)) sys.stdout.flush() if src == None: - raise RuntimeError("No source file, dst is " + dst) + raise ManifestError("No source file, dst is " + dst) if dst == None: dst = src dst = os.path.join(self.get_dst_prefix(), dst) @@ -639,10 +648,10 @@ class LLManifest(object): return count try: count = try_path(os.path.join(self.get_src_prefix(), src)) - except RuntimeError: + except ManifestError: try: count = try_path(os.path.join(self.get_artwork_prefix(), src)) - except RuntimeError: + except ManifestError: count = try_path(os.path.join(self.get_build_prefix(), src)) print "%d files" % count diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 0931c4ec9b..d65d51ce32 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -1025,41 +1025,27 @@ class Linux_i686Manifest(LinuxManifest): super(Linux_i686Manifest, self).construct() if self.prefix("../packages/lib/release", dst="lib"): - self.path("libapr-1.so") - self.path("libapr-1.so.0") - self.path("libapr-1.so.0.4.2") - self.path("libaprutil-1.so") - self.path("libaprutil-1.so.0") - self.path("libaprutil-1.so.0.3.10") - self.path("libbreakpad_client.so.0.0.0") - self.path("libbreakpad_client.so.0") - self.path("libbreakpad_client.so") + self.path("libapr-1.so*") + self.path("libaprutil-1.so*") + self.path("libbreakpad_client.so*") self.path("libcollada14dom.so") - self.path("libdb-5.1.so") - self.path("libdb-5.so") - self.path("libdb.so") - self.path("libcrypto.so.1.0.0") - self.path("libexpat.so.1.5.2") + self.path("libdb*.so") + self.path("libcrypto.so.*") + self.path("libexpat.so.*") self.path("libssl.so.1.0.0") self.path("libglod.so") self.path("libminizip.so") - self.path("libuuid.so") - self.path("libuuid.so.16") - self.path("libuuid.so.16.0.22") - self.path("libSDL-1.2.so.0.11.3") - self.path("libdirectfb-1.4.so.5.0.4") - self.path("libfusion-1.4.so.5.0.4") - self.path("libdirect-1.4.so.5.0.4") - self.path("libopenjpeg.so.1.4.0") - self.path("libopenjpeg.so.1") - self.path("libopenjpeg.so") + self.path("libuuid.so*") + self.path("libSDL-1.2.so.*") + self.path("libdirectfb-1.*.so.*") + self.path("libfusion-1.*.so.*") + self.path("libdirect-1.*.so.*") + self.path("libopenjpeg.so*") self.path("libalut.so") self.path("libopenal.so", "libopenal.so.1") self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname - self.path("libfontconfig.so.1.4.4") - self.path("libtcmalloc.so", "libtcmalloc.so") #formerly called google perf tools - self.path("libtcmalloc.so.0", "libtcmalloc.so.0") #formerly called google perf tools - self.path("libtcmalloc.so.0.1.0", "libtcmalloc.so.0.1.0") #formerly called google perf tools + self.path("libfontconfig.so.*") + self.path("libtcmalloc.so*") #formerly called google perf tools try: self.path("libfmod-3.75.so") pass -- cgit v1.3 From 4a7848148e886676dd24bfcf4f50db06bffb28da Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 26 Feb 2012 07:21:51 -0500 Subject: Add TODOs for getTokens() to known places that scan command lines. Lacking time to properly test new LLStringUtil::getTokens() against the present (different!) command-line scanners in LLExternalEditor::tokenize() and LLCommandLineParser::parseCommandLineString(), just annotate as future work the goal of unifying them... SIGH. --- indra/newview/llcommandlineparser.cpp | 9 +++++++++ indra/newview/llexternaleditor.cpp | 6 ++++++ 2 files changed, 15 insertions(+) (limited to 'indra/newview') diff --git a/indra/newview/llcommandlineparser.cpp b/indra/newview/llcommandlineparser.cpp index 65c61c4a8b..17d403bbe1 100644 --- a/indra/newview/llcommandlineparser.cpp +++ b/indra/newview/llcommandlineparser.cpp @@ -342,6 +342,15 @@ bool LLCommandLineParser::parseCommandLine(int argc, char **argv) return parseAndStoreResults(clp); } +// TODO: +// - Break out this funky parsing logic into separate method +// - Unit-test it with tests like LLStringUtil::getTokens() (the command-line +// overload that supports quoted tokens) +// - Unless this logic offers significant semantic benefits, replace it with +// LLStringUtil::getTokens(). This would fix a known bug: you cannot --set a +// string-valued variable to the empty string, because empty strings are +// eliminated below. + bool LLCommandLineParser::parseCommandLineString(const std::string& str) { // Split the string content into tokens diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index db482f023e..9480e54809 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -119,6 +119,12 @@ std::string LLExternalEditor::getErrorMessage(EErrorCode code) return LLTrans::getString("Unknown"); } +// TODO: +// - Unit-test this with tests like LLStringUtil::getTokens() (the +// command-line overload that supports quoted tokens) +// - Unless there are significant semantic differences, eliminate this method +// and use LLStringUtil::getTokens() instead. + // static size_t LLExternalEditor::tokenize(string_vec_t& tokens, const std::string& str) { -- cgit v1.3 From 106bac8ed77339e7c986b3066bf96210d327898d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Mar 2012 21:21:29 -0500 Subject: Alphabetize cmd_line.xml. This separate commit is just to order the keys. Data are unchanged, as established by: $ hg cat -rtip cmd_line.xml >cmd_line.xml.tip $ python Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53) [GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from llbase import llsd >>> tipdata = llsd.parse(open("cmd_line.xml.tip").read()) >>> newdata = llsd.parse(open("cmd_line.xml").read()) >>> tipdata == newdata True --- indra/newview/app_settings/cmd_line.xml | 373 ++++++++++++++++---------------- 1 file changed, 187 insertions(+), 186 deletions(-) (limited to 'indra/newview') diff --git a/indra/newview/app_settings/cmd_line.xml b/indra/newview/app_settings/cmd_line.xml index 15434f2b8f..1a8b4560e6 100644 --- a/indra/newview/app_settings/cmd_line.xml +++ b/indra/newview/app_settings/cmd_line.xml @@ -1,45 +1,101 @@ - help + + analyzeperformance desc - display this help message + When used in conjunction with logperformance, analyzes result of log against baseline. + map-to + AnalyzePerformance + - short - h + autologin + + desc + log in as last saved user + map-to + AutoLogin - port + channel count 1 - map-to - UserConnectionPort + - drop + console count 1 map-to - PacketDropPercentage + ShowConsoleWindow - inbw + cooperative + desc + Yield some idle time to local host. count 1 map-to - InBandwidth + YieldTime - outbw + crashonstartup + + desc + Crashes on startup. For QA use. + map-to + CrashOnStartup + + + debugsession + + desc + Run as if RenderDebugGL is TRUE, but log errors until end of session. + map-to + DebugSession + + + debugviews + + map-to + DebugViews + + + disablecrashlogger + + desc + Disables the crash logger and lets the OS handle crashes + map-to + DisableCrashLogger + + + drop count 1 map-to - OutBandwidth + PacketDropPercentage + + + god + + desc + Log in a god if you have god access. + map-to + ConnectAsGod + + + graphicslevel + + desc + Set the detail level. +0 - low, 1 - medium, 2 - high, 3 - ultra + count + 1 grid @@ -52,16 +108,13 @@ CmdLineGridChoice - loginuri + help desc - login server and CGI script to use - count - 1 - compose - true - map-to - CmdLineLoginURI + display this help message + + short + h helperuri @@ -74,44 +127,60 @@ CmdLineHelperURI - debugviews + ignorepixeldepth + desc + Ignore pixel depth settings. map-to - DebugViews + IgnorePixelDepth - skin + inbw - desc - ui/branding skin folder to use count 1 map-to - SkinFolder + InBandwidth - autologin + logfile + + count + 1 + map-to + UserLogFile + + + login desc - log in as last saved user + 3 tokens: first, last and password + count + 3 map-to - AutoLogin + UserLoginInfo - quitafter + loginpage + desc + Login authentication page to use. count 1 map-to - QuitAfterSeconds + LoginPage - logperformance + loginuri desc - Log performance metrics for benchmarking + login server and CGI script to use + count + 1 + compose + true map-to - LogPerformance + CmdLineLoginURI logmetrics @@ -123,29 +192,35 @@ map-to LogMetrics - - analyzeperformance + + logperformance desc - When used in conjunction with logperformance, analyzes result of log against baseline. + Log performance metrics for benchmarking map-to - AnalyzePerformance + LogPerformance - debugsession + multiple desc - Run as if RenderDebugGL is TRUE, but log errors until end of session. + Allow multiple viewers. map-to - DebugSession + AllowMultipleViewers - replaysession + noaudio + + map-to + NoAudio + + + noinvlib desc - After login, replay last recorded session and quit. + Do not request the inventory library. map-to - ReplaySession + NoInventoryLibrary nonotifications @@ -156,22 +231,10 @@ IgnoreAllNotifications - rotate - - map-to - RotateRight - - - noaudio - - map-to - NoAudio - - - nosound + nopreload map-to - NoAudio + NoPreload noprobe @@ -186,151 +249,134 @@ NoQuickTime - nopreload + nosound map-to - NoPreload + NoAudio - purge + no-verify-ssl-cert - desc - Delete files in the cache. map-to - PurgeCacheOnNextStartup + NoVerifySSLCert - noinvlib + novoice desc - Do not request the inventory library. + Disable voice. map-to - NoInventoryLibrary + CmdLineDisableVoice - logfile + outbw count 1 map-to - UserLogFile + OutBandwidth - graphicslevel + port - desc - Set the detail level. -0 - low, 1 - medium, 2 - high, 3 - ultra count 1 + map-to + UserConnectionPort - setdefault + purge desc - specify the value of a particular configuration variable which can be overridden by settings.xml. - count - 2 - + Delete files in the cache. + map-to + PurgeCacheOnNextStartup - set + qa desc - specify the value of a particular configuration variable that overrides all other settings. - count - 2 - compose - true - + Activated debugging menu in Advanced Settings. + map-to + QAMode - settings + quitafter - desc - Specify the filename of a configuration file. count 1 - + map-to + QuitAfterSeconds - - sessionsettings + + replaysession desc - Specify the filename of a configuration file that contains temporary per-session configuration overrides. - count - 1 - + After login, replay last recorded session and quit. + map-to + ReplaySession - usersessionsettings - - desc - Specify the filename of a configuration file that contains temporary per-session configuration user overrides. - count - 1 - - - - login + rotate - desc - 3 tokens: first, last and password - count - 3 map-to - UserLoginInfo + RotateRight - god + safe desc - Log in a god if you have god access. + Reset preferences, run in safe mode. map-to - ConnectAsGod + SafeMode - console + sessionsettings + desc + Specify the filename of a configuration file that contains temporary per-session configuration overrides. count 1 - map-to - ShowConsoleWindow + - safe + set desc - Reset preferences, run in safe mode. - map-to - SafeMode + specify the value of a particular configuration variable that overrides all other settings. + count + 2 + compose + true + - multiple + setdefault desc - Allow multiple viewers. - map-to - AllowMultipleViewers + specify the value of a particular configuration variable which can be overridden by settings.xml. + count + 2 + - novoice + settings desc - Disable voice. - map-to - CmdLineDisableVoice + Specify the filename of a configuration file. + count + 1 + - url + skin desc - Startup location + ui/branding skin folder to use count 1 - last_option - true - + map-to + SkinFolder slurl @@ -346,69 +392,24 @@ - ignorepixeldepth - - desc - Ignore pixel depth settings. - map-to - IgnorePixelDepth - - - cooperative + url desc - Yield some idle time to local host. - count - 1 - map-to - YieldTime - - - no-verify-ssl-cert - - map-to - NoVerifySSLCert - - - channel - + Startup location count 1 + last_option + true - loginpage + usersessionsettings desc - Login authentication page to use. + Specify the filename of a configuration file that contains temporary per-session configuration user overrides. count 1 - map-to - LoginPage - - - qa - - desc - Activated debugging menu in Advanced Settings. - map-to - QAMode - - - crashonstartup - - desc - Crashes on startup. For QA use. - map-to - CrashOnStartup - - - disablecrashlogger - - desc - Disables the crash logger and lets the OS handle crashes - map-to - DisableCrashLogger + -- cgit v1.3 From cb38ceb89fc34105ad2ba2fdfa35faa2918d0346 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 14 Mar 2012 15:42:22 -0400 Subject: Add --leap command-line switch to launch one or more LEAP plugins. You can specify one or more instances of --leap 'command line'. Each such command line is parsed using bash-like conventions, notably honoring double quotes, e.g. --leap '"c:/Program Files/Something/something.exe" arg1 arg2'. (Specifying such an argument in a Windows Command Prompt may be tricky.) Such a program should read its stdin and write to its stdout using LLSD Event API Plugin protocol: length:serialized_LLSD where 'length' is the decimal integer count of bytes in serialized_LLSD, ':' is a literal colon character, and 'serialized_LLSD' is notation-format LLSD. A typical LLSD object is a map containing 'pump' and 'data' keys, where 'pump' is the name of the LLEventPump on which to send 'data' (or on which 'data' was received). In particular, the initial LLSD object on stdin mentions the name of this plugin's reply LLEventPump: the LLEventPump that will send every subsequent received event to the plugin's stdin. Anything written to the plugin's stderr will be logged in the viewer log. In addition to being generally useful, this helps debug problems with particular plugins. --- indra/newview/app_settings/cmd_line.xml | 13 ++++ indra/newview/app_settings/settings.xml | 13 +++- indra/newview/llappviewer.cpp | 117 ++++++-------------------------- indra/newview/llappviewer.h | 6 -- 4 files changed, 46 insertions(+), 103 deletions(-) (limited to 'indra/newview') diff --git a/indra/newview/app_settings/cmd_line.xml b/indra/newview/app_settings/cmd_line.xml index 1a8b4560e6..be79f91919 100644 --- a/indra/newview/app_settings/cmd_line.xml +++ b/indra/newview/app_settings/cmd_line.xml @@ -143,6 +143,19 @@ InBandwidth + leap + + desc + command line to run an LLSD Event API Plugin + count + 1 + + compose + true + map-to + LeapCommand + + logfile count diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 0e26013152..e9dd405bc6 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4579,6 +4579,17 @@ Value 0 + LeapCommand + + Comment + Zero or more command lines to run LLSD Event API Plugin programs. + Persist + 0 + Type + LLSD + Value + + LSLFindCaseInsensitivity Comment @@ -7142,7 +7153,7 @@ QAModeEventHostPort Comment - Port on which lleventhost should listen + DEPRECATED: Port on which lleventhost should listen Persist 0 Type diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 1174d108d2..85bd836104 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -111,6 +111,8 @@ #include "llnotifications.h" #include "llnotificationsutil.h" +#include "llleap.h" + // Third party library includes #include #include @@ -124,7 +126,6 @@ #endif #include "llapr.h" -#include "apr_dso.h" #include #include "llviewerkeyboard.h" @@ -161,6 +162,7 @@ #include "llcontainerview.h" #include "lltooltip.h" +#include "llsdutil.h" #include "llsdserialize.h" #include "llworld.h" @@ -1038,11 +1040,27 @@ bool LLAppViewer::init() gGLActive = FALSE; + + // Iterate over --leap command-line options + BOOST_FOREACH(const std::string& leap, llsd::inArray(gSavedSettings.getLLSD("LeapCommand"))) + { + LL_DEBUGS("InitInfo") << "processing --leap \"" << leap << '"' << LL_ENDL; + // We don't have any better description of this plugin than the + // user-specified command line. Passing "" causes LLLeap to derive a + // description from the command line itself. + // Suppress LLLeap::Error exception: trust LLLeap's own logging. We + // don't consider any one --leap command mission-critical, so if one + // fails, log it, shrug and carry on. + LLLeap::create("", leap, false); // exception=false + } + if (gSavedSettings.getBOOL("QAMode") && gSavedSettings.getS32("QAModeEventHostPort") > 0) { - loadEventHostModule(gSavedSettings.getS32("QAModeEventHostPort")); + LL_WARNS("InitInfo") << "QAModeEventHostPort DEPRECATED: " + << "lleventhost no longer supported as a dynamic library" + << LL_ENDL; } - + LLViewerMedia::initClass(); LL_INFOS("InitInfo") << "Viewer media initialized." << LL_ENDL ; @@ -1515,18 +1533,6 @@ bool LLAppViewer::cleanup() gDirUtilp->deleteFilesInDir(logdir, "*-*-*-*-*.dmp"); } - // *TODO - generalize this and move DSO wrangling to a helper class -brad - std::set::const_iterator i; - for(i = mPlugins.begin(); i != mPlugins.end(); ++i) - { - int (*ll_plugin_stop_func)(void) = NULL; - apr_status_t rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll_plugin_stop_func, *i, "ll_plugin_stop"); - ll_plugin_stop_func(); - - rv = apr_dso_unload(*i); - } - mPlugins.clear(); - //flag all elements as needing to be destroyed immediately // to ensure shutdown order LLMortician::setZealous(TRUE); @@ -4958,87 +4964,6 @@ void LLAppViewer::handleLoginComplete() writeDebugInfo(); } -// *TODO - generalize this and move DSO wrangling to a helper class -brad -void LLAppViewer::loadEventHostModule(S32 listen_port) -{ - std::string dso_name = -#if LL_WINDOWS - "lleventhost.dll"; -#elif LL_DARWIN - "liblleventhost.dylib"; -#else - "liblleventhost.so"; -#endif - - std::string dso_path = gDirUtilp->findFile(dso_name, - gDirUtilp->getAppRODataDir(), - gDirUtilp->getExecutableDir()); - - if(dso_path == "") - { - llerrs << "QAModeEventHost requested but module \"" << dso_name << "\" not found!" << llendl; - return; - } - - LL_INFOS("eventhost") << "Found lleventhost at '" << dso_path << "'" << LL_ENDL; -#if ! defined(LL_WINDOWS) - { - std::string outfile("/tmp/lleventhost.file.out"); - std::string command("file '" + dso_path + "' > '" + outfile + "' 2>&1"); - int rc = system(command.c_str()); - if (rc != 0) - { - LL_WARNS("eventhost") << command << " ==> " << rc << ':' << LL_ENDL; - } - else - { - LL_INFOS("eventhost") << command << ':' << LL_ENDL; - } - { - std::ifstream reader(outfile.c_str()); - std::string line; - while (std::getline(reader, line)) - { - size_t len = line.length(); - if (len && line[len-1] == '\n') - line.erase(len-1); - LL_INFOS("eventhost") << line << LL_ENDL; - } - } - remove(outfile.c_str()); - } -#endif // LL_WINDOWS - - apr_dso_handle_t * eventhost_dso_handle = NULL; - apr_pool_t * eventhost_dso_memory_pool = NULL; - - //attempt to load the shared library - apr_pool_create(&eventhost_dso_memory_pool, NULL); - apr_status_t rv = apr_dso_load(&eventhost_dso_handle, - dso_path.c_str(), - eventhost_dso_memory_pool); - llassert_always(! ll_apr_warn_status(rv, eventhost_dso_handle)); - llassert_always(eventhost_dso_handle != NULL); - - int (*ll_plugin_start_func)(LLSD const &) = NULL; - rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll_plugin_start_func, eventhost_dso_handle, "ll_plugin_start"); - - llassert_always(! ll_apr_warn_status(rv, eventhost_dso_handle)); - llassert_always(ll_plugin_start_func != NULL); - - LLSD args; - args["listen_port"] = listen_port; - - int status = ll_plugin_start_func(args); - - if(status != 0) - { - llerrs << "problem loading eventhost plugin, status: " << status << llendl; - } - - mPlugins.insert(eventhost_dso_handle); -} - void LLAppViewer::launchUpdater() { LLSD query_map = LLSD::emptyMap(); diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 71a7868191..f7d019ccba 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -41,8 +41,6 @@ class LLTextureFetch; class LLWatchdogTimeout; class LLUpdaterService; -struct apr_dso_handle_t; - class LLAppViewer : public LLApp { public: @@ -220,8 +218,6 @@ private: void sendLogoutRequest(); void disconnectViewer(); - - void loadEventHostModule(S32 listen_port); // *FIX: the app viewer class should be some sort of singleton, no? // Perhaps its child class is the singleton and this should be an abstract base. @@ -270,8 +266,6 @@ private: LLAllocator mAlloc; - std::set mPlugins; - LLFrameTimer mMemCheckTimer; boost::scoped_ptr mUpdater; -- cgit v1.3 From 7de3161fa866bc415b4b87f26ca3f7fc3670af78 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 14 Mar 2012 23:59:57 -0400 Subject: Fix --leap assumption that LeapCommand setting is ALWAYS an array. Nuance of command-line processing: when there's exactly one --leap switch, the resulting LLSD is a scalar string rather than an array with one entry. Fix processing code to handle either case. --- indra/newview/llappviewer.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'indra/newview') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 85bd836104..7e0162d026 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1041,10 +1041,21 @@ bool LLAppViewer::init() gGLActive = FALSE; - // Iterate over --leap command-line options - BOOST_FOREACH(const std::string& leap, llsd::inArray(gSavedSettings.getLLSD("LeapCommand"))) + // Iterate over --leap command-line options. But this is a bit tricky: if + // there's only one, it won't be an array at all. + LLSD LeapCommand(gSavedSettings.getLLSD("LeapCommand")); + LL_DEBUGS("InitInfo") << "LeapCommand: " << LeapCommand << LL_ENDL; + if (LeapCommand.isDefined() && ! LeapCommand.isArray()) { - LL_DEBUGS("InitInfo") << "processing --leap \"" << leap << '"' << LL_ENDL; + // If LeapCommand is actually a scalar value, make an array of it. + // Have to do it in two steps because LeapCommand.append(LeapCommand) + // trashes content! :-P + LLSD item(LeapCommand); + LeapCommand.append(item); + } + BOOST_FOREACH(const std::string& leap, llsd::inArray(LeapCommand)) + { + LL_INFOS("InitInfo") << "processing --leap \"" << leap << '"' << LL_ENDL; // We don't have any better description of this plugin than the // user-specified command line. Passing "" causes LLLeap to derive a // description from the command line itself. -- cgit v1.3 From 2a6f919cdbe5c878444a722fe91768ccd4e46c98 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 15 Mar 2012 16:16:05 -0400 Subject: Explicitly clean up all LLLeap instances during viewer shutdown. This code replaces the previous cleanup of DLLs loaded by APR. --- indra/newview/llappviewer.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'indra/newview') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 7e0162d026..f18e5d2c9e 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1544,6 +1544,25 @@ bool LLAppViewer::cleanup() gDirUtilp->deleteFilesInDir(logdir, "*-*-*-*-*.dmp"); } + { + // Kill off LLLeap objects. We can find them all because LLLeap is derived + // from LLInstanceTracker. But collect instances first: LLInstanceTracker + // specifically forbids adding/deleting instances while iterating. + std::vector leaps; + leaps.reserve(LLLeap::instanceCount()); + for (LLLeap::instance_iter li(LLLeap::beginInstances()), lend(LLLeap::endInstances()); + li != lend; ++li) + { + leaps.push_back(&*li); + } + // Okay, now trash them all. We don't have to NULL or erase the entry + // in 'leaps' because the whole vector is going away momentarily. + BOOST_FOREACH(LLLeap* leap, leaps) + { + delete leap; + } + } // destroy 'leaps' + //flag all elements as needing to be destroyed immediately // to ensure shutdown order LLMortician::setZealous(TRUE); -- cgit v1.3 From 9d9ad9a876d6498a89f9aeefc9bf258e1674dae7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Mar 2012 14:23:00 -0400 Subject: Tighten Linux treatment of command-line args to 'secondlife' script. New --leap switch takes a quoted command line likely to contain spaces. Sloppy handling of quoted arguments definitely gets us into trouble. Fix that. --- indra/newview/linux_tools/wrapper.sh | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'indra/newview') diff --git a/indra/newview/linux_tools/wrapper.sh b/indra/newview/linux_tools/wrapper.sh index 283a28a0aa..90771f1174 100755 --- a/indra/newview/linux_tools/wrapper.sh +++ b/indra/newview/linux_tools/wrapper.sh @@ -110,22 +110,22 @@ export SAVED_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" # fi #fi -export SL_ENV='LD_LIBRARY_PATH="`pwd`"/lib:"${LD_LIBRARY_PATH}"' -export SL_CMD='$LL_WRAPPER bin/do-not-directly-run-secondlife-bin' -export SL_OPT="`cat etc/gridargs.dat` $@" +export LD_LIBRARY_PATH="$PWD/lib:${LD_LIBRARY_PATH}" -# Run the program -eval ${SL_ENV} ${SL_CMD} ${SL_OPT} || LL_RUN_ERR=runerr +# Run the program. +# Don't quote $LL_WRAPPER because, if empty, it should simply vanish from the +# command line. Similar remarks about the contents of gridargs.dat. But DO +# quote "$@": preserve separate args as individually quoted. +$LL_WRAPPER bin/do-not-directly-run-secondlife-bin $( Date: Thu, 29 Mar 2012 09:53:53 -0400 Subject: IQA-463: fix Linux wrapper.sh (aka secondlife) gridargs.dat handling. Previous change to wrapper.sh naively read $( Date: Wed, 11 Apr 2012 08:56:38 -0400 Subject: IQA-463: Linux fonts look better without libfontconfig.so.1 symlink. Making llmanifest.py support library-file wildcards allows viewer_manifest.py to avoid specifying the exact version number of every shared library we want to package. Specifying "libfontconfig.so.*" was copying the libfontconfig.so.1 symlink as well as the libfontconfig.so.1.4.4 binary. To my dismay, packaging that symlink makes the Linux viewer fonts look WORSE! I suspect that means that the released Linux viewer completely ignores our packaged libfontconfig.so.1.4.4 library, finding the system fontconfig instead. But that would be a whole different project. For present purposes it suffices to make the updated viewer_manifest.py copy the same files as the older one. --- indra/newview/viewer_manifest.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'indra/newview') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index d65d51ce32..8da1be2769 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -1044,7 +1044,24 @@ class Linux_i686Manifest(LinuxManifest): self.path("libalut.so") self.path("libopenal.so", "libopenal.so.1") self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname - self.path("libfontconfig.so.*") + # KLUDGE: As of 2012-04-11, the 'fontconfig' package installs + # libfontconfig.so.1.4.4, along with symlinks libfontconfig.so.1 + # and libfontconfig.so. Before we added support for library-file + # wildcards, though, this self.path() call specifically named + # libfontconfig.so.1.4.4 WITHOUT also copying the symlinks. When I + # (nat) changed the call to self.path("libfontconfig.so.*"), we + # ended up with the libfontconfig.so.1 symlink in the target + # directory as well. But guess what! At least on Ubuntu 10.04, + # certain viewer fonts look terrible with libfontconfig.so.1 + # present in the target directory. Removing that symlink suffices + # to improve them. I suspect that means we actually do better when + # the viewer fails to find our packaged libfontconfig.so*, falling + # back on the system one instead -- but diagnosing and fixing that + # is a bit out of scope for the present project. Meanwhile, this + # particular wildcard specification gets us exactly what the + # previous call did, without having to explicitly state the + # version number. + self.path("libfontconfig.so.*.*") self.path("libtcmalloc.so*") #formerly called google perf tools try: self.path("libfmod-3.75.so") -- cgit v1.3