From 14c09c3ce597e47f5c41bb45246e89a1f31d89b0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 21 Dec 2011 10:06:26 -0500 Subject: Add unit-test module for LLProcessLauncher. As always with llcommon, this is expressed as an "integration test" to sidestep a circular dependency: the llcommon build depends on its unit tests, but all our unit tests depend on llcommon. Initial test code is more for human verification than automated verification: does APR's child-process management in fact support nonblocking operations? --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/tests/llprocesslauncher_test.cpp | 144 ++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 indra/llcommon/tests/llprocesslauncher_test.cpp diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 0a3eaec5c5..c8e1827584 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -326,6 +326,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}") # *TODO - reenable these once tcmalloc libs no longer break the build. #ADD_BUILD_TEST(llallocator llcommon) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp new file mode 100644 index 0000000000..3b5602f620 --- /dev/null +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -0,0 +1,144 @@ +/** + * @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 +// std headers +// external library headers +#include "llapr.h" +#include "apr_thread_proc.h" +#include "apr_file_io.h" +// other Linden headers +#include "../test/lltut.h" + +class APR +{ +public: + APR(): + pool(NULL) + { + apr_initialize(); + apr_pool_create(&pool, NULL); + } + + ~APR() + { + apr_terminate(); + } + + std::string strerror(apr_status_t rv) + { + char errbuf[256]; + apr_strerror(rv, errbuf, sizeof(errbuf)); + return errbuf; + } + + apr_pool_t *pool; +}; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llprocesslauncher_data + { + void aprchk(apr_status_t rv) + { + ensure_equals(apr.strerror(rv), rv, APR_SUCCESS); + } + + APR apr; + }; + typedef test_group llprocesslauncher_group; + typedef llprocesslauncher_group::object object; + llprocesslauncher_group llprocesslaunchergrp("llprocesslauncher"); + + template<> template<> + void object::test<1>() + { + set_test_name("raw APR nonblocking I/O"); + + apr_procattr_t *procattr = NULL; + aprchk(apr_procattr_create(&procattr, apr.pool)); + 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"); + argv.push_back("-c"); + argv.push_back("raise RuntimeError('Hello from Python!')"); + argv.push_back(NULL); + + aprchk(apr_proc_create(&child, argv[0], + static_cast(&argv[0]), + NULL, // if we wanted to pass explicit environment + procattr, + apr.pool)); + + typedef std::pair DescFile; + typedef std::vector DescFileVec; + DescFileVec outfiles; + outfiles.push_back(DescFile("out", child.out)); + outfiles.push_back(DescFile("err", child.err)); + + while (! outfiles.empty()) + { + DescFileVec iterfiles(outfiles); + for (size_t i = 0; i < iterfiles.size(); ++i) + { + char buf[4096]; + + apr_status_t rv = apr_file_gets(buf, sizeof(buf), iterfiles[i].second); + if (APR_STATUS_IS_EOF(rv)) + { + std::cout << "(EOF on " << iterfiles[i].first << ")\n"; + outfiles.erase(outfiles.begin() + i); + continue; + } + if (rv != APR_SUCCESS) + { + std::cout << "(waiting; apr_file_gets(" << iterfiles[i].first << ") => " << rv << ": " << apr.strerror(rv) << ")\n"; + continue; + } + // Is it even possible to get APR_SUCCESS but read 0 bytes? + // Hope not, but defend against that anyway. + if (buf[0]) + { + std::cout << iterfiles[i].first << ": " << buf; + // Just for pretty output... if we only read a partial + // line, terminate it. + if (buf[strlen(buf) - 1] != '\n') + std::cout << "...\n"; + } + } + sleep(1); + } + apr_file_close(child.in); + apr_file_close(child.out); + apr_file_close(child.err); + + int rc = 0; + apr_exit_why_e why; + apr_status_t rv; + while (! APR_STATUS_IS_CHILD_DONE(rv = apr_proc_wait(&child, &rc, &why, APR_NOWAIT))) + { + std::cout << "child not done (" << rv << "): " << apr.strerror(rv) << '\n'; + sleep(0.5); + } + std::cout << "child done: rv = " << rv << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; + } +} // namespace tut -- cgit v1.2.3 From 7832d8eccb00d32b6122e5851238e962f65af1e8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 21 Dec 2011 11:12:48 -0500 Subject: Fix llprocesslauncher_test.cpp to work on Windows. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 3b5602f620..4d14e1be53 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -12,6 +12,7 @@ // Precompiled header #include "linden_common.h" // associated header +#define WIN32_LEAN_AND_MEAN #include "llprocesslauncher.h" // STL headers #include @@ -23,6 +24,10 @@ // other Linden headers #include "../test/lltut.h" +#if defined(LL_WINDOWS) +#define sleep _sleep +#endif + class APR { public: -- cgit v1.2.3 From 2fd0bc8648e71aa2f141fde4b3a6a0165f7ef4d6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 21 Dec 2011 17:00:43 -0500 Subject: Change llprocesslauncher_test.cpp eyeballing to program verification. That is, where before we just flung stuff to stdout with the expectation that a human user would verify, replace with assertions in the test code itself. Quiet previous noise on stdout. Introduce a temp script file that produces output on both stdout and stderr, with sleep() calls so we predictably have to wait for it. Track and then verify the history of our interaction with the child process, noting especially EWOULDBLOCK attempts. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 97 ++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 12 deletions(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 4d14e1be53..ca06b3164e 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -17,6 +17,7 @@ // STL headers #include // std headers +#include // external library headers #include "llapr.h" #include "apr_thread_proc.h" @@ -71,11 +72,53 @@ namespace tut typedef llprocesslauncher_group::object object; llprocesslauncher_group llprocesslaunchergrp("llprocesslauncher"); + struct Item + { + Item(): tries(0) {} + unsigned tries; + std::string which; + std::string what; + }; + template<> template<> void object::test<1>() { set_test_name("raw APR nonblocking I/O"); + // Create a script file in a temporary place. + const char* tempdir = NULL; + aprchk(apr_temp_dir_get(&tempdir, apr.pool)); + + // Construct a temp filename template in that directory. + char *tempname = NULL; + aprchk(apr_filepath_merge(&tempname, tempdir, "testXXXXXX", 0, apr.pool)); + + // Create a temp file from that template. + apr_file_t* fp = NULL; + aprchk(apr_file_mktemp(&fp, tempname, APR_CREATE | APR_WRITE | APR_EXCL, apr.pool)); + + // Write it. + const char script[] = + "import sys\n" + "import time\n" + "\n" + "time.sleep(2)\n" + "print >>sys.stdout, \"stdout after wait\"\n" + "sys.stdout.flush()\n" + "time.sleep(2)\n" + "print >>sys.stderr, \"stderr after wait\"\n" + "sys.stderr.flush()\n" + ; + apr_size_t len(sizeof(script)-1); + aprchk(apr_file_write(fp, script, &len)); + aprchk(apr_file_close(fp)); + + // 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()); + apr_procattr_t *procattr = NULL; aprchk(apr_procattr_create(&procattr, apr.pool)); aprchk(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)); @@ -84,8 +127,7 @@ namespace tut std::vector argv; apr_proc_t child; argv.push_back("python"); - argv.push_back("-c"); - argv.push_back("raise RuntimeError('Hello from Python!')"); + argv.push_back(tempname); argv.push_back(NULL); aprchk(apr_proc_create(&child, argv[0], @@ -110,24 +152,35 @@ namespace tut apr_status_t rv = apr_file_gets(buf, sizeof(buf), iterfiles[i].second); if (APR_STATUS_IS_EOF(rv)) { - std::cout << "(EOF on " << iterfiles[i].first << ")\n"; +// std::cout << "(EOF on " << iterfiles[i].first << ")\n"; + history.back().which = iterfiles[i].first; + history.back().what = "*eof*"; + history.push_back(Item()); outfiles.erase(outfiles.begin() + i); continue; } - if (rv != APR_SUCCESS) + if (rv == EWOULDBLOCK) { - std::cout << "(waiting; apr_file_gets(" << iterfiles[i].first << ") => " << rv << ": " << apr.strerror(rv) << ")\n"; +// std::cout << "(waiting; apr_file_gets(" << iterfiles[i].first << ") => " << rv << ": " << apr.strerror(rv) << ")\n"; + ++history.back().tries; continue; } + ensure_equals(rv, APR_SUCCESS); // Is it even possible to get APR_SUCCESS but read 0 bytes? // Hope not, but defend against that anyway. if (buf[0]) { - std::cout << iterfiles[i].first << ": " << buf; - // Just for pretty output... if we only read a partial - // line, terminate it. - if (buf[strlen(buf) - 1] != '\n') - std::cout << "...\n"; +// std::cout << iterfiles[i].first << ": " << buf; + history.back().which = iterfiles[i].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"; + } } } sleep(1); @@ -141,9 +194,29 @@ namespace tut apr_status_t rv; while (! APR_STATUS_IS_CHILD_DONE(rv = apr_proc_wait(&child, &rc, &why, APR_NOWAIT))) { - std::cout << "child not done (" << rv << "): " << apr.strerror(rv) << '\n'; +// std::cout << "child not done (" << rv << "): " << apr.strerror(rv) << '\n'; sleep(0.5); } - std::cout << "child done: rv = " << rv << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; +// std::cout << "child done: rv = " << rv << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; + ensure_equals(rv, APR_CHILD_DONE); + ensure_equals(why, APR_PROC_EXIT); + ensure_equals(rc, 0); + + // Remove temp script file + aprchk(apr_file_remove(tempname, apr.pool)); + + // 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. + ensure("blocking I/O on child pipe (0)", history[0].tries); + ensure_equals(history[0].which, "out"); + ensure_equals(history[0].what, "stdout after wait\n"); + ensure("blocking I/O on child pipe (1)", history[1].tries); + ensure_equals(history[1].which, "out"); + ensure_equals(history[1].what, "*eof*"); + ensure_equals(history[2].which, "err"); + ensure_equals(history[2].what, "stderr after wait\n"); + ensure_equals(history[3].which, "err"); + ensure_equals(history[3].what, "*eof*"); } } // namespace tut -- cgit v1.2.3 From 25ef0cd2236aeb2d0047881e11a0022c4355cd48 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 21 Dec 2011 20:42:11 -0500 Subject: Tweak llprocesslauncher_test.cpp to run properly on Windows. Fix EOL issues: "\r\n" vs. "\n". On Windows, requesting a read in nonblocking mode can produce EAGAIN instead of EWOULDBLOCK. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 27 ++++++++++++++----------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index ca06b3164e..bdae81770f 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -27,6 +27,9 @@ #if defined(LL_WINDOWS) #define sleep _sleep +#define EOL "\r\n" +#else +#define EOL "\n" #endif class APR @@ -99,15 +102,15 @@ namespace tut // Write it. const char script[] = - "import sys\n" - "import time\n" - "\n" - "time.sleep(2)\n" - "print >>sys.stdout, \"stdout after wait\"\n" - "sys.stdout.flush()\n" - "time.sleep(2)\n" - "print >>sys.stderr, \"stderr after wait\"\n" - "sys.stderr.flush()\n" + "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 ; apr_size_t len(sizeof(script)-1); aprchk(apr_file_write(fp, script, &len)); @@ -159,7 +162,7 @@ namespace tut outfiles.erase(outfiles.begin() + i); continue; } - if (rv == EWOULDBLOCK) + if (rv == EWOULDBLOCK || rv == EAGAIN) { // std::cout << "(waiting; apr_file_gets(" << iterfiles[i].first << ") => " << rv << ": " << apr.strerror(rv) << ")\n"; ++history.back().tries; @@ -210,12 +213,12 @@ namespace tut // waiting, proving the non-blocking nature of these pipes. ensure("blocking I/O on child pipe (0)", history[0].tries); ensure_equals(history[0].which, "out"); - ensure_equals(history[0].what, "stdout after wait\n"); + ensure_equals(history[0].what, "stdout after wait" EOL); ensure("blocking I/O on child pipe (1)", history[1].tries); ensure_equals(history[1].which, "out"); ensure_equals(history[1].what, "*eof*"); ensure_equals(history[2].which, "err"); - ensure_equals(history[2].what, "stderr after wait\n"); + ensure_equals(history[2].what, "stderr after wait" EOL); ensure_equals(history[3].which, "err"); ensure_equals(history[3].what, "*eof*"); } -- cgit v1.2.3 From 39c3efbda3bc4c7b415aa851ec4f42f05acda0cb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Dec 2011 16:20:19 -0500 Subject: Add child_status_callback() function and arrange to call periodically. At least on OS X 10.7, a call to apr_proc_wait(APR_NOWAIT) in fact seems to block the caller. So instead of polling apr_proc_wait(), use APR callback mechanism (apr_proc_other_child_register() et al.) and poll that using apr_proc_other_child_refresh_all(). Evidently this polls the underlying system waitpid(), but the internal call seems to better support nonblocking. On arrival in the child_status_callback(APR_OC_REASON_DEATH) call, though, apr_proc_wait() produces ECHILD: the child process in question has already been reaped. The OS-encoded wait() status does get passed to the callback, but then we have to use OS-dependent macros to tease apart voluntary termination vs. killed by signal... a bit of a hole in APR's abstraction layer. Wrap ensure_equals() calls with a macro to explain which comparison failed. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 161 ++++++++++++++++++++---- 1 file changed, 138 insertions(+), 23 deletions(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index bdae81770f..7d67d13960 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -22,14 +22,17 @@ #include "llapr.h" #include "apr_thread_proc.h" #include "apr_file_io.h" +#include // other Linden headers #include "../test/lltut.h" +#include "stringize.h" #if defined(LL_WINDOWS) #define sleep _sleep #define EOL "\r\n" #else #define EOL "\n" +#include #endif class APR @@ -57,6 +60,10 @@ public: apr_pool_t *pool; }; +#define ensure_equals_(left, right) \ + ensure_equals(STRINGIZE(#left << " != " << #right), (left), (right)) +#define aprchk(expr) aprchk_(#expr, (expr)) + /***************************************************************************** * TUT *****************************************************************************/ @@ -64,9 +71,10 @@ namespace tut { struct llprocesslauncher_data { - void aprchk(apr_status_t rv) + void aprchk_(const char* call, apr_status_t rv) { - ensure_equals(apr.strerror(rv), rv, APR_SUCCESS); + ensure_equals(STRINGIZE(call << " => " << rv << ": " << apr.strerror(rv)), + rv, APR_SUCCESS); } APR apr; @@ -83,6 +91,91 @@ namespace tut 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)); + wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); + if (wi->rv == ECHILD) + { + std::cout << "apr_proc_wait() got ECHILD during child_status_callback(" + << reason_str << ")\n"; + // So -- is this why we have a 'status' param? + wi->rv = APR_CHILD_DONE; // pretend this call worked; fake results +#if defined(LL_WINDOWS) + wi->why = APR_PROC_EXIT; + wi->rc = status; // correct?? +#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>() { @@ -106,10 +199,10 @@ namespace tut "import time" EOL EOL "time.sleep(2)" EOL - "print >>sys.stdout, \"stdout after wait\"" EOL + "print >>sys.stdout, 'stdout after wait'" EOL "sys.stdout.flush()" EOL "time.sleep(2)" EOL - "print >>sys.stderr, \"stderr after wait\"" EOL + "print >>sys.stderr, 'stderr after wait'" EOL "sys.stderr.flush()" EOL ; apr_size_t len(sizeof(script)-1); @@ -122,6 +215,7 @@ namespace tut std::vector history; history.push_back(Item()); + // Run the child process. apr_procattr_t *procattr = NULL; aprchk(apr_procattr_create(&procattr, apr.pool)); aprchk(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)); @@ -134,11 +228,23 @@ namespace tut argv.push_back(NULL); aprchk(apr_proc_create(&child, argv[0], - static_cast(&argv[0]), + &argv[0], NULL, // if we wanted to pass explicit environment procattr, apr.pool)); + // 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(apr.pool, &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, apr.pool); + + // Monitor two different output pipes. Because one will be closed + // before the other, keep them in a vector so we can drop whichever of + // them is closed first. typedef std::pair DescFile; typedef std::vector DescFileVec; DescFileVec outfiles; @@ -168,7 +274,7 @@ namespace tut ++history.back().tries; continue; } - ensure_equals(rv, APR_SUCCESS); + aprchk_("apr_file_gets(buf, sizeof(buf), iterfiles[i].second)", rv); // Is it even possible to get APR_SUCCESS but read 0 bytes? // Hope not, but defend against that anyway. if (buf[0]) @@ -186,24 +292,33 @@ namespace tut } } } + // 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); - int rc = 0; - apr_exit_why_e why; - apr_status_t rv; - while (! APR_STATUS_IS_CHILD_DONE(rv = apr_proc_wait(&child, &rc, &why, APR_NOWAIT))) + // 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 not done (" << rv << "): " << apr.strerror(rv) << '\n'; - sleep(0.5); + std::cout << "child_status_callback() wasn't called\n"; + wi.rv = apr_proc_wait(wi.child, &wi.rc, &wi.why, APR_NOWAIT); } // std::cout << "child done: rv = " << rv << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; - ensure_equals(rv, APR_CHILD_DONE); - ensure_equals(why, APR_PROC_EXIT); - ensure_equals(rc, 0); + ensure_equals_(wi.rv, APR_CHILD_DONE); + ensure_equals_(wi.why, APR_PROC_EXIT); + ensure_equals_(wi.rc, 0); // Remove temp script file aprchk(apr_file_remove(tempname, apr.pool)); @@ -212,14 +327,14 @@ namespace tut // obtained expected output -- and that we duly got control while // waiting, proving the non-blocking nature of these pipes. ensure("blocking I/O on child pipe (0)", history[0].tries); - ensure_equals(history[0].which, "out"); - ensure_equals(history[0].what, "stdout after wait" EOL); + ensure_equals_(history[0].which, "out"); + ensure_equals_(history[0].what, "stdout after wait" EOL); ensure("blocking I/O on child pipe (1)", history[1].tries); - ensure_equals(history[1].which, "out"); - ensure_equals(history[1].what, "*eof*"); - ensure_equals(history[2].which, "err"); - ensure_equals(history[2].what, "stderr after wait" EOL); - ensure_equals(history[3].which, "err"); - ensure_equals(history[3].what, "*eof*"); + ensure_equals_(history[1].which, "out"); + ensure_equals_(history[1].what, "*eof*"); + ensure_equals_(history[2].which, "err"); + ensure_equals_(history[2].what, "stderr after wait" EOL); + ensure_equals_(history[3].which, "err"); + ensure_equals_(history[3].what, "*eof*"); } } // namespace tut -- cgit v1.2.3 From 6ccba8810102cc13def8057a82463c9787b21e57 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Dec 2011 17:18:02 -0500 Subject: Never call apr_proc_wait() inside child_status_callback(). Quiet the temporary child_status_callback() output. Add a bit of diagnostic info if apr_proc_wait() returns anything but APR_CHILD_DONE. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 57 +++++++++++++------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 7d67d13960..bd7666313e 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -71,10 +71,10 @@ namespace tut { struct llprocesslauncher_data { - void aprchk_(const char* call, apr_status_t rv) + void aprchk_(const char* call, apr_status_t rv, apr_status_t expected=APR_SUCCESS) { ensure_equals(STRINGIZE(call << " => " << rv << ": " << apr.strerror(rv)), - rv, APR_SUCCESS); + rv, expected); } APR apr; @@ -123,6 +123,7 @@ namespace tut void child_status_callback(int reason, void* data, int status) { +/*==========================================================================*| std::string reason_str; BOOST_FOREACH(const ReasonCode& rcp, reasons) { @@ -137,6 +138,7 @@ namespace tut 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) { @@ -145,34 +147,33 @@ namespace tut apr_proc_other_child_unregister(data); WaitInfo* wi(static_cast(data)); - wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); - if (wi->rv == ECHILD) - { - std::cout << "apr_proc_wait() got ECHILD during child_status_callback(" - << reason_str << ")\n"; - // So -- is this why we have a 'status' param? - wi->rv = APR_CHILD_DONE; // pretend this call worked; fake results + // 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; // correct?? + 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 + 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 } } @@ -316,7 +317,7 @@ namespace tut wi.rv = apr_proc_wait(wi.child, &wi.rc, &wi.why, APR_NOWAIT); } // std::cout << "child done: rv = " << rv << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; - ensure_equals_(wi.rv, APR_CHILD_DONE); + 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); -- cgit v1.2.3 From 29273ffba68d254ce3e6d9939a854c778a377721 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Dec 2011 17:27:53 -0500 Subject: Comment out lookup table used only by commented-out code. Otherwise the unreferenced declaration causes a fatal warning. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index bd7666313e..b3e0796191 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -91,6 +91,7 @@ namespace tut std::string what; }; +/*==========================================================================*| #define tabent(symbol) { symbol, #symbol } static struct ReasonCode { @@ -106,6 +107,7 @@ namespace tut tabent(APR_OC_REASON_RUNNING) }; #undef tabent +|*==========================================================================*/ struct WaitInfo { -- cgit v1.2.3 From cf7c6f93f28534fee2c13e29501b6ab6e7b77d61 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Dec 2011 15:23:03 -0500 Subject: Make pipe-management logic more robust. Previous logic was vulnerable to the case in which both pipes reached EOF in the same loop iteration. Now we use std::list instead of std::vector, allowing us to iterate and delete with a single pass. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 43 ++++++++++++++++--------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index bd7666313e..d6d05ed769 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -16,6 +16,7 @@ #include "llprocesslauncher.h" // STL headers #include +#include // std headers #include // external library headers @@ -28,7 +29,7 @@ #include "stringize.h" #if defined(LL_WINDOWS) -#define sleep _sleep +#define sleep(secs) _sleep((secs) * 1000) #define EOL "\r\n" #else #define EOL "\n" @@ -244,44 +245,54 @@ namespace tut apr_proc_other_child_register(&child, child_status_callback, &wi, child.in, apr.pool); // Monitor two different output pipes. Because one will be closed - // before the other, keep them in a vector so we can drop whichever of + // before the other, keep them in a list so we can drop whichever of // them is closed first. typedef std::pair DescFile; - typedef std::vector DescFileVec; - DescFileVec outfiles; + typedef std::list DescFileList; + DescFileList outfiles; outfiles.push_back(DescFile("out", child.out)); outfiles.push_back(DescFile("err", child.err)); while (! outfiles.empty()) { - DescFileVec iterfiles(outfiles); - for (size_t i = 0; i < iterfiles.size(); ++i) + // 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), iterfiles[i].second); + apr_status_t rv = apr_file_gets(buf, sizeof(buf), dfli->second); if (APR_STATUS_IS_EOF(rv)) { -// std::cout << "(EOF on " << iterfiles[i].first << ")\n"; - history.back().which = iterfiles[i].first; +// std::cout << "(EOF on " << dfli->first << ")\n"; + history.back().which = dfli->first; history.back().what = "*eof*"; history.push_back(Item()); - outfiles.erase(outfiles.begin() + i); + outfiles.erase(dfli); continue; } if (rv == EWOULDBLOCK || rv == EAGAIN) { -// std::cout << "(waiting; apr_file_gets(" << iterfiles[i].first << ") => " << rv << ": " << apr.strerror(rv) << ")\n"; +// std::cout << "(waiting; apr_file_gets(" << dfli->first << ") => " << rv << ": " << apr.strerror(rv) << ")\n"; ++history.back().tries; continue; } - aprchk_("apr_file_gets(buf, sizeof(buf), iterfiles[i].second)", rv); + 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 << iterfiles[i].first << ": " << buf; - history.back().which = iterfiles[i].first; +// 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()); @@ -295,7 +306,7 @@ namespace tut } // Do this once per tick, as we expect the viewer will apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); - sleep(1); + sleep(0.5); } apr_file_close(child.in); apr_file_close(child.out); @@ -313,7 +324,7 @@ namespace tut if (wi.rv == -1) { - std::cout << "child_status_callback() wasn't called\n"; + 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 << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; -- cgit v1.2.3 From 8008d540e5177aa4fb0c802b157eec2695c8334a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Dec 2011 16:45:05 -0500 Subject: Should we expect EOF on one pipe before we finish reading the other? Defend test against the ambiguous answer to that question by not recording, or testing for, EOF history events. Enrich output for history-verification failures: display whole history array. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 61 +++++++++++++++++++------ 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 895325c705..dbbe54e9fa 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -246,6 +246,11 @@ namespace tut WaitInfo wi(&child); apr_proc_other_child_register(&child, child_status_callback, &wi, child.in, apr.pool); + // 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. @@ -276,9 +281,9 @@ namespace tut 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()); +// history.back().which = dfli->first; +// history.back().what = "*eof*"; +// history.push_back(Item()); outfiles.erase(dfli); continue; } @@ -340,15 +345,45 @@ namespace tut // 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. - ensure("blocking I/O on child pipe (0)", history[0].tries); - ensure_equals_(history[0].which, "out"); - ensure_equals_(history[0].what, "stdout after wait" EOL); - ensure("blocking I/O on child pipe (1)", history[1].tries); - ensure_equals_(history[1].which, "out"); - ensure_equals_(history[1].what, "*eof*"); - ensure_equals_(history[2].which, "err"); - ensure_equals_(history[2].what, "stderr after wait" EOL); - ensure_equals_(history[3].which, "err"); - ensure_equals_(history[3].what, "*eof*"); + 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; + } } } // namespace tut -- cgit v1.2.3 From 97876f6118eadf6a2669826d68412cc020975a64 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Dec 2011 17:38:34 -0500 Subject: Fix sleep(0.5) to sleep(1) -- truncation to int makes that dubious. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index dbbe54e9fa..4d8f850d92 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -313,7 +313,7 @@ namespace tut } // Do this once per tick, as we expect the viewer will apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); - sleep(0.5); + sleep(1); } apr_file_close(child.in); apr_file_close(child.out); -- cgit v1.2.3 From 61b5f3143e4ea53c9f64e5a1a5ad19f2edf3e776 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 5 Jan 2012 15:43:23 -0500 Subject: Introduce LLStreamQueue to buffer nonblocking I/O. Add unit tests to verify basic functionality. --- indra/llcommon/CMakeLists.txt | 3 + indra/llcommon/llstreamqueue.cpp | 24 +++ indra/llcommon/llstreamqueue.h | 229 ++++++++++++++++++++++++++++ indra/llcommon/tests/llstreamqueue_test.cpp | 177 +++++++++++++++++++++ 4 files changed, 433 insertions(+) create mode 100644 indra/llcommon/llstreamqueue.cpp create mode 100644 indra/llcommon/llstreamqueue.h create mode 100644 indra/llcommon/tests/llstreamqueue_test.cpp diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index c8e1827584..334f78cbff 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -88,6 +88,7 @@ set(llcommon_SOURCE_FILES llsingleton.cpp llstat.cpp llstacktrace.cpp + llstreamqueue.cpp llstreamtools.cpp llstring.cpp llstringtable.cpp @@ -221,6 +222,7 @@ set(llcommon_HEADER_FILES llstat.h llstatenums.h llstl.h + llstreamqueue.h llstreamtools.h llstrider.h llstring.h @@ -327,6 +329,7 @@ if (LL_TESTS) 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(llstreamqueue "" "${test_libs}") # *TODO - reenable these once tcmalloc libs no longer break the build. #ADD_BUILD_TEST(llallocator llcommon) diff --git a/indra/llcommon/llstreamqueue.cpp b/indra/llcommon/llstreamqueue.cpp new file mode 100644 index 0000000000..1116a2b6a2 --- /dev/null +++ b/indra/llcommon/llstreamqueue.cpp @@ -0,0 +1,24 @@ +/** + * @file llstreamqueue.cpp + * @author Nat Goodspeed + * @date 2012-01-05 + * @brief Implementation for llstreamqueue. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llstreamqueue.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +// As of this writing, llstreamqueue.h is entirely template-based, therefore +// we don't strictly need a corresponding .cpp file. However, our CMake test +// macro assumes one. Here it is. +bool llstreamqueue_cpp_ignored = true; diff --git a/indra/llcommon/llstreamqueue.h b/indra/llcommon/llstreamqueue.h new file mode 100644 index 0000000000..2fbc2067d2 --- /dev/null +++ b/indra/llcommon/llstreamqueue.h @@ -0,0 +1,229 @@ +/** + * @file llstreamqueue.h + * @author Nat Goodspeed + * @date 2012-01-04 + * @brief Definition of LLStreamQueue + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLSTREAMQUEUE_H) +#define LL_LLSTREAMQUEUE_H + +#include +#include +#include // std::streamsize +#include + +/** + * This class is a growable buffer between a producer and consumer. It serves + * as a queue usable with Boost.Iostreams -- hence, a "stream queue." + * + * This is especially useful for buffering nonblocking I/O. For instance, we + * want application logic to be able to serialize LLSD to a std::ostream. We + * may write more data than the destination pipe can handle all at once, but + * it's imperative NOT to block the application-level serialization call. So + * we buffer it instead. Successive frames can try nonblocking writes to the + * destination pipe until all buffered data has been sent. + * + * Similarly, we want application logic be able to deserialize LLSD from a + * std::istream. Again, we must not block that deserialize call waiting for + * more data to arrive from the input pipe! Instead we build up a buffer over + * a number of frames, using successive nonblocking reads, until we have + * "enough" data to be able to present it through a std::istream. + * + * @note The use cases for this class overlap somewhat with those for the + * LLIOPipe/LLPumpIO hierarchies, and indeed we considered using those. This + * class has two virtues over the older machinery: + * + * # It's vastly simpler -- way fewer concepts. It's not clear to me whether + * there were ever LLIOPipe/etc. use cases that demanded all the fanciness + * rolled in, or whether they were simply overdesigned. In any case, no + * remaining Lindens will admit to familiarity with those classes -- and + * they're sufficiently obtuse that it would take considerable learning + * curve to figure out how to use them properly. The bottom line is that + * current management is not keen on any more engineers climbing that curve. + * # This class is designed around available components such as std::string, + * std::list, Boost.Iostreams. There's less proprietary code. + */ +template +class LLGenericStreamQueue +{ +public: + LLGenericStreamQueue(): + mClosed(false) + {} + + /** + * Boost.Iostreams Source Device facade for use with other Boost.Iostreams + * functionality. LLGenericStreamQueue doesn't quite fit any of the Boost + * 1.48 Iostreams concepts; instead it behaves as both a Sink and a + * Source. This is its Source facade. + */ + struct Source + { + typedef Ch char_type; + typedef boost::iostreams::source_tag category; + + /// Bind the underlying LLGenericStreamQueue + Source(LLGenericStreamQueue& sq): + mStreamQueue(sq) + {} + + // Read up to n characters from the underlying data source into the + // buffer s, returning the number of characters read; return -1 to + // indicate EOF + std::streamsize read(Ch* s, std::streamsize n) + { + return mStreamQueue.read(s, n); + } + + LLGenericStreamQueue& mStreamQueue; + }; + + /** + * Boost.Iostreams Sink Device facade for use with other Boost.Iostreams + * functionality. LLGenericStreamQueue doesn't quite fit any of the Boost + * 1.48 Iostreams concepts; instead it behaves as both a Sink and a + * Source. This is its Sink facade. + */ + struct Sink + { + typedef Ch char_type; + typedef boost::iostreams::sink_tag category; + + /// Bind the underlying LLGenericStreamQueue + Sink(LLGenericStreamQueue& sq): + mStreamQueue(sq) + {} + + /// Write up to n characters from the buffer s to the output sequence, + /// returning the number of characters written + std::streamsize write(const Ch* s, std::streamsize n) + { + return mStreamQueue.write(s, n); + } + + /// Send EOF to consumer + void close() + { + mStreamQueue.close(); + } + + LLGenericStreamQueue& mStreamQueue; + }; + + /// Present Boost.Iostreams Source facade + Source asSource() { return Source(*this); } + /// Present Boost.Iostreams Sink facade + Sink asSink() { return Sink(*this); } + + /// append data to buffer + std::streamsize write(const Ch* s, std::streamsize n) + { + // Unclear how often we might be asked to write 0 bytes -- perhaps a + // naive caller responding to an unready nonblocking read. But if we + // do get such a call, don't add a completely empty BufferList entry. + if (n == 0) + return n; + // We could implement this using a single std::string object, a la + // ostringstream. But the trouble with appending to a string is that + // you might have to recopy all previous contents to grow its size. If + // we want this to scale to large data volumes, better to allocate + // individual pieces. + mBuffer.push_back(string(s, n)); + return n; + } + + /** + * Inform this LLGenericStreamQueue that no further data are forthcoming. + * For our purposes, close() is strictly a producer-side operation; + * there's little point in closing the consumer side. + */ + void close() + { + mClosed = true; + } + + /// consume data from buffer + std::streamsize read(Ch* s, std::streamsize n) + { + // read() is actually a convenience method for peek() followed by + // skip(). + std::streamsize got(peek(s, n)); + // We can only skip() as many characters as we can peek(); ignore + // skip() return here. + skip(n); + return got; + } + + /// Retrieve data from buffer without consuming. Like read(), return -1 on + /// EOF. + std::streamsize peek(Ch* s, std::streamsize n) const; + + /// Consume data from buffer without retrieving. Unlike read() and peek(), + /// at EOF we simply skip 0 characters. + std::streamsize skip(std::streamsize n); + +private: + typedef std::basic_string string; + typedef std::list BufferList; + BufferList mBuffer; + bool mClosed; +}; + +template +std::streamsize LLGenericStreamQueue::peek(Ch* s, std::streamsize n) const +{ + // Here we may have to build up 'n' characters from an arbitrary + // number of individual BufferList entries. + typename BufferList::const_iterator bli(mBuffer.begin()), blend(mBuffer.end()); + // Indicate EOF if producer has closed the pipe AND we've exhausted + // all previously-buffered data. + if (mClosed && bli == blend) + { + return -1; + } + // Here either producer hasn't yet closed, or we haven't yet exhausted + // remaining data. + std::streamsize needed(n), got(0); + // Loop until either we run out of BufferList entries or we've + // completely satisfied the request. + for ( ; bli != blend && needed; ++bli) + { + std::streamsize chunk(std::min(needed, std::streamsize(bli->length()))); + std::copy(bli->begin(), bli->begin() + chunk, s); + needed -= chunk; + s += chunk; + got += chunk; + } + return got; +} + +template +std::streamsize LLGenericStreamQueue::skip(std::streamsize n) +{ + typename BufferList::iterator bli(mBuffer.begin()), blend(mBuffer.end()); + std::streamsize toskip(n), skipped(0); + while (bli != blend && toskip >= bli->length()) + { + std::streamsize chunk(bli->length()); + typename BufferList::iterator zap(bli++); + mBuffer.erase(zap); + toskip -= chunk; + skipped += chunk; + } + if (bli != blend && toskip) + { + bli->erase(bli->begin(), bli->begin() + toskip); + skipped += toskip; + } + return skipped; +} + +typedef LLGenericStreamQueue LLStreamQueue; +typedef LLGenericStreamQueue LLWStreamQueue; + +#endif /* ! defined(LL_LLSTREAMQUEUE_H) */ diff --git a/indra/llcommon/tests/llstreamqueue_test.cpp b/indra/llcommon/tests/llstreamqueue_test.cpp new file mode 100644 index 0000000000..e88c37d5be --- /dev/null +++ b/indra/llcommon/tests/llstreamqueue_test.cpp @@ -0,0 +1,177 @@ +/** + * @file llstreamqueue_test.cpp + * @author Nat Goodspeed + * @date 2012-01-05 + * @brief Test for llstreamqueue. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llstreamqueue.h" +// STL headers +#include +// std headers +// external library headers +#include +// other Linden headers +#include "../test/lltut.h" +#include "stringize.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llstreamqueue_data + { + llstreamqueue_data(): + // we want a buffer with actual bytes in it, not an empty vector + buffer(10) + {} + // As LLStreamQueue is merely a typedef for + // LLGenericStreamQueue, and no logic in LLGenericStreamQueue is + // specific to the instantiation, we're comfortable for now + // testing only the narrow-char version. + LLStreamQueue strq; + // buffer for use in multiple tests + std::vector buffer; + }; + typedef test_group llstreamqueue_group; + typedef llstreamqueue_group::object object; + llstreamqueue_group llstreamqueuegrp("llstreamqueue"); + + template<> template<> + void object::test<1>() + { + set_test_name("empty LLStreamQueue"); + ensure_equals("brand-new LLStreamQueue isn't empty", + strq.asSource().read(&buffer[0], buffer.size()), 0); + strq.asSink().close(); + ensure_equals("closed empty LLStreamQueue not at EOF", + strq.asSource().read(&buffer[0], buffer.size()), -1); + } + + template<> template<> + void object::test<2>() + { + set_test_name("one internal block, one buffer"); + LLStreamQueue::Sink sink(strq.asSink()); + ensure_equals("write(\"\")", sink.write("", 0), 0); + ensure_equals("0 write should leave LLStreamQueue empty", + strq.peek(&buffer[0], buffer.size()), 0); + // The meaning of "atomic" is that it must be smaller than our buffer. + std::string atomic("atomic"); + ensure("test data exceeds buffer", atomic.length() < buffer.size()); + ensure_equals(STRINGIZE("write(\"" << atomic << "\")"), + sink.write(&atomic[0], atomic.length()), atomic.length()); + size_t peeklen(strq.peek(&buffer[0], buffer.size())); + ensure_equals(STRINGIZE("peek(\"" << atomic << "\")"), + peeklen, atomic.length()); + ensure_equals(STRINGIZE("peek(\"" << atomic << "\") result"), + std::string(buffer.begin(), buffer.begin() + peeklen), atomic); + // peek() should not consume. Use a different buffer to prove it isn't + // just leftover data from the first peek(). + std::vector again(buffer.size()); + peeklen = size_t(strq.peek(&again[0], again.size())); + ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again"), + peeklen, atomic.length()); + ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again result"), + std::string(again.begin(), again.begin() + peeklen), atomic); + // now consume. + std::vector third(buffer.size()); + size_t readlen(strq.read(&third[0], third.size())); + ensure_equals(STRINGIZE("read(\"" << atomic << "\")"), + readlen, atomic.length()); + ensure_equals(STRINGIZE("read(\"" << atomic << "\") result"), + std::string(third.begin(), third.begin() + readlen), atomic); + ensure_equals("peek() after read()", strq.peek(&buffer[0], buffer.size()), 0); + } + + template<> template<> + void object::test<3>() + { + set_test_name("basic skip()"); + std::string lovecraft("lovecraft"); + ensure("test data exceeds buffer", lovecraft.length() < buffer.size()); + ensure_equals(STRINGIZE("write(\"" << lovecraft << "\")"), + strq.write(&lovecraft[0], lovecraft.length()), lovecraft.length()); + size_t peeklen(strq.peek(&buffer[0], buffer.size())); + ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\")"), + peeklen, lovecraft.length()); + ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\") result"), + std::string(buffer.begin(), buffer.begin() + peeklen), lovecraft); + std::streamsize skip1(4); + ensure_equals(STRINGIZE("skip(" << skip1 << ")"), strq.skip(skip1), skip1); + size_t readlen(strq.read(&buffer[0], buffer.size())); + ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\")"), + readlen, lovecraft.length() - skip1); + ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\") result"), + std::string(buffer.begin(), buffer.begin() + readlen), + lovecraft.substr(skip1)); + ensure_equals("unconsumed", strq.read(&buffer[0], buffer.size()), 0); + } + + template<> template<> + void object::test<4>() + { + set_test_name("skip() multiple blocks"); + std::string blocks[] = { "books of ", "H.P. ", "Lovecraft" }; + std::streamsize skip(blocks[0].length() + blocks[1].length() + 4); + BOOST_FOREACH(const std::string& block, blocks) + { + strq.write(&block[0], block.length()); + } + std::streamsize skiplen(strq.skip(skip)); + ensure_equals(STRINGIZE("skip(" << skip << ")"), skiplen, skip); + size_t readlen(strq.read(&buffer[0], buffer.size())); + ensure_equals("read(\"craft\")", readlen, 5); + ensure_equals("read(\"craft\") result", + std::string(buffer.begin(), buffer.begin() + readlen), "craft"); + } + + template<> template<> + void object::test<5>() + { + set_test_name("concatenate blocks"); + std::string blocks[] = { "abcd", "efghij", "klmnopqrs" }; + BOOST_FOREACH(const std::string& block, blocks) + { + strq.write(&block[0], block.length()); + } + std::vector longbuffer(30); + std::streamsize readlen(strq.read(&longbuffer[0], longbuffer.size())); + ensure_equals("read() multiple blocks", + readlen, blocks[0].length() + blocks[1].length() + blocks[2].length()); + ensure_equals("read() multiple blocks result", + std::string(longbuffer.begin(), longbuffer.begin() + readlen), + blocks[0] + blocks[1] + blocks[2]); + } + + template<> template<> + void object::test<6>() + { + set_test_name("split blocks"); + std::string blocks[] = { "abcdefghijklm", "nopqrstuvwxyz" }; + BOOST_FOREACH(const std::string& block, blocks) + { + strq.write(&block[0], block.length()); + } + strq.close(); + std::streamsize readlen(strq.read(&buffer[0], buffer.size())); + ensure_equals("read() 0", readlen, buffer.size()); + ensure_equals("read() 0 result", std::string(buffer.begin(), buffer.end()), "abcdefghij"); + readlen = strq.read(&buffer[0], buffer.size()); + ensure_equals("read() 1", readlen, buffer.size()); + ensure_equals("read() 1 result", std::string(buffer.begin(), buffer.end()), "klmnopqrst"); + readlen = strq.read(&buffer[0], buffer.size()); + ensure_equals("read() 2", readlen, 6); + ensure_equals("read() 2 result", + std::string(buffer.begin(), buffer.begin() + readlen), "uvwxyz"); + ensure_equals("read() 3", strq.read(&buffer[0], buffer.size()), -1); + } +} // namespace tut -- cgit v1.2.3 From 39a86eda8d6d810bd7f4dd6b96f022548a496ba1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Jan 2012 12:19:27 -0500 Subject: Add LLStreamQueue::size() and tests to exercise it. --- indra/llcommon/llstreamqueue.h | 11 +++++++++++ indra/llcommon/tests/llstreamqueue_test.cpp | 30 ++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/indra/llcommon/llstreamqueue.h b/indra/llcommon/llstreamqueue.h index 2fbc2067d2..0726bad175 100644 --- a/indra/llcommon/llstreamqueue.h +++ b/indra/llcommon/llstreamqueue.h @@ -53,6 +53,7 @@ class LLGenericStreamQueue { public: LLGenericStreamQueue(): + mSize(0), mClosed(false) {} @@ -134,6 +135,7 @@ public: // we want this to scale to large data volumes, better to allocate // individual pieces. mBuffer.push_back(string(s, n)); + mSize += n; return n; } @@ -167,10 +169,17 @@ public: /// at EOF we simply skip 0 characters. std::streamsize skip(std::streamsize n); + /// How many characters do we currently have buffered? + std::streamsize size() const + { + return mSize; + } + private: typedef std::basic_string string; typedef std::list BufferList; BufferList mBuffer; + std::streamsize mSize; bool mClosed; }; @@ -212,12 +221,14 @@ std::streamsize LLGenericStreamQueue::skip(std::streamsize n) std::streamsize chunk(bli->length()); typename BufferList::iterator zap(bli++); mBuffer.erase(zap); + mSize -= chunk; toskip -= chunk; skipped += chunk; } if (bli != blend && toskip) { bli->erase(bli->begin(), bli->begin() + toskip); + mSize -= toskip; skipped += toskip; } return skipped; diff --git a/indra/llcommon/tests/llstreamqueue_test.cpp b/indra/llcommon/tests/llstreamqueue_test.cpp index e88c37d5be..050ad5c5bf 100644 --- a/indra/llcommon/tests/llstreamqueue_test.cpp +++ b/indra/llcommon/tests/llstreamqueue_test.cpp @@ -50,6 +50,8 @@ namespace tut { set_test_name("empty LLStreamQueue"); ensure_equals("brand-new LLStreamQueue isn't empty", + strq.size(), 0); + ensure_equals("brand-new LLStreamQueue returns data", strq.asSource().read(&buffer[0], buffer.size()), 0); strq.asSink().close(); ensure_equals("closed empty LLStreamQueue not at EOF", @@ -62,18 +64,22 @@ namespace tut set_test_name("one internal block, one buffer"); LLStreamQueue::Sink sink(strq.asSink()); ensure_equals("write(\"\")", sink.write("", 0), 0); - ensure_equals("0 write should leave LLStreamQueue empty", + ensure_equals("0 write should leave LLStreamQueue empty (size())", + strq.size(), 0); + ensure_equals("0 write should leave LLStreamQueue empty (peek())", strq.peek(&buffer[0], buffer.size()), 0); // The meaning of "atomic" is that it must be smaller than our buffer. std::string atomic("atomic"); ensure("test data exceeds buffer", atomic.length() < buffer.size()); ensure_equals(STRINGIZE("write(\"" << atomic << "\")"), sink.write(&atomic[0], atomic.length()), atomic.length()); + ensure_equals("size() after write()", strq.size(), atomic.length()); size_t peeklen(strq.peek(&buffer[0], buffer.size())); ensure_equals(STRINGIZE("peek(\"" << atomic << "\")"), peeklen, atomic.length()); ensure_equals(STRINGIZE("peek(\"" << atomic << "\") result"), std::string(buffer.begin(), buffer.begin() + peeklen), atomic); + ensure_equals("size() after peek()", strq.size(), atomic.length()); // peek() should not consume. Use a different buffer to prove it isn't // just leftover data from the first peek(). std::vector again(buffer.size()); @@ -90,6 +96,7 @@ namespace tut ensure_equals(STRINGIZE("read(\"" << atomic << "\") result"), std::string(third.begin(), third.begin() + readlen), atomic); ensure_equals("peek() after read()", strq.peek(&buffer[0], buffer.size()), 0); + ensure_equals("size() after read()", strq.size(), 0); } template<> template<> @@ -107,6 +114,7 @@ namespace tut std::string(buffer.begin(), buffer.begin() + peeklen), lovecraft); std::streamsize skip1(4); ensure_equals(STRINGIZE("skip(" << skip1 << ")"), strq.skip(skip1), skip1); + ensure_equals("size() after skip()", strq.size(), lovecraft.length() - skip1); size_t readlen(strq.read(&buffer[0], buffer.size())); ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\")"), readlen, lovecraft.length() - skip1); @@ -121,15 +129,20 @@ namespace tut { set_test_name("skip() multiple blocks"); std::string blocks[] = { "books of ", "H.P. ", "Lovecraft" }; - std::streamsize skip(blocks[0].length() + blocks[1].length() + 4); + std::streamsize total(blocks[0].length() + blocks[1].length() + blocks[2].length()); + std::streamsize leave(5); // len("craft") above + std::streamsize skip(total - leave); + std::streamsize written(0); BOOST_FOREACH(const std::string& block, blocks) { - strq.write(&block[0], block.length()); + written += strq.write(&block[0], block.length()); + ensure_equals("size() after write()", strq.size(), written); } std::streamsize skiplen(strq.skip(skip)); ensure_equals(STRINGIZE("skip(" << skip << ")"), skiplen, skip); + ensure_equals("size() after skip()", strq.size(), leave); size_t readlen(strq.read(&buffer[0], buffer.size())); - ensure_equals("read(\"craft\")", readlen, 5); + ensure_equals("read(\"craft\")", readlen, leave); ensure_equals("read(\"craft\") result", std::string(buffer.begin(), buffer.begin() + readlen), "craft"); } @@ -162,14 +175,21 @@ namespace tut strq.write(&block[0], block.length()); } strq.close(); + // We've already verified what strq.size() should be at this point; + // see above test named "skip() multiple blocks" + std::streamsize chksize(strq.size()); std::streamsize readlen(strq.read(&buffer[0], buffer.size())); ensure_equals("read() 0", readlen, buffer.size()); ensure_equals("read() 0 result", std::string(buffer.begin(), buffer.end()), "abcdefghij"); + chksize -= readlen; + ensure_equals("size() after read() 0", strq.size(), chksize); readlen = strq.read(&buffer[0], buffer.size()); ensure_equals("read() 1", readlen, buffer.size()); ensure_equals("read() 1 result", std::string(buffer.begin(), buffer.end()), "klmnopqrst"); + chksize -= readlen; + ensure_equals("size() after read() 1", strq.size(), chksize); readlen = strq.read(&buffer[0], buffer.size()); - ensure_equals("read() 2", readlen, 6); + ensure_equals("read() 2", readlen, chksize); ensure_equals("read() 2 result", std::string(buffer.begin(), buffer.begin() + readlen), "uvwxyz"); ensure_equals("read() 3", strq.read(&buffer[0], buffer.size()), -1); -- cgit v1.2.3 From b6a08ad007deb855ce4d428654279206853a3b99 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Jan 2012 12:41:54 -0500 Subject: Extract APR and temp-fixture-file helper code to indra/test. Specifically: Introduce ManageAPR class in indra/test/manageapr.h. This is useful for a simple test program without lots of static constructors. Extract NamedTempFile from llsdserialize_test.cpp to indra/test/ namedtempfile.h. Refactor to use APR file operations rather than platform- dependent APIs. Use NamedTempFile for llprocesslauncher_test.cpp. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 73 ++----- indra/llcommon/tests/llsdserialize_test.cpp | 261 +----------------------- indra/test/manageapr.h | 45 ++++ indra/test/namedtempfile.h | 113 ++++++++++ 4 files changed, 184 insertions(+), 308 deletions(-) create mode 100644 indra/test/manageapr.h create mode 100644 indra/test/namedtempfile.h diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 4d8f850d92..3935c64a94 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -18,14 +18,14 @@ #include #include // std headers -#include // external library headers #include "llapr.h" #include "apr_thread_proc.h" -#include "apr_file_io.h" #include // other Linden headers #include "../test/lltut.h" +#include "../test/manageapr.h" +#include "../test/namedtempfile.h" #include "stringize.h" #if defined(LL_WINDOWS) @@ -36,30 +36,8 @@ #include #endif -class APR -{ -public: - APR(): - pool(NULL) - { - apr_initialize(); - apr_pool_create(&pool, NULL); - } - - ~APR() - { - apr_terminate(); - } - - std::string strerror(apr_status_t rv) - { - char errbuf[256]; - apr_strerror(rv, errbuf, sizeof(errbuf)); - return errbuf; - } - - apr_pool_t *pool; -}; +// static instance of this manages APR init/cleanup +static ManageAPR manager; #define ensure_equals_(left, right) \ ensure_equals(STRINGIZE(#left << " != " << #right), (left), (right)) @@ -74,11 +52,11 @@ namespace tut { void aprchk_(const char* call, apr_status_t rv, apr_status_t expected=APR_SUCCESS) { - ensure_equals(STRINGIZE(call << " => " << rv << ": " << apr.strerror(rv)), + ensure_equals(STRINGIZE(call << " => " << rv << ": " << manager.strerror(rv)), rv, expected); } - APR apr; + LLAPRPool pool; }; typedef test_group llprocesslauncher_group; typedef llprocesslauncher_group::object object; @@ -186,19 +164,7 @@ namespace tut set_test_name("raw APR nonblocking I/O"); // Create a script file in a temporary place. - const char* tempdir = NULL; - aprchk(apr_temp_dir_get(&tempdir, apr.pool)); - - // Construct a temp filename template in that directory. - char *tempname = NULL; - aprchk(apr_filepath_merge(&tempname, tempdir, "testXXXXXX", 0, apr.pool)); - - // Create a temp file from that template. - apr_file_t* fp = NULL; - aprchk(apr_file_mktemp(&fp, tempname, APR_CREATE | APR_WRITE | APR_EXCL, apr.pool)); - - // Write it. - const char script[] = + NamedTempFile script("py", "import sys" EOL "import time" EOL EOL @@ -208,10 +174,7 @@ namespace tut "time.sleep(2)" EOL "print >>sys.stderr, 'stderr after wait'" EOL "sys.stderr.flush()" EOL - ; - apr_size_t len(sizeof(script)-1); - aprchk(apr_file_write(fp, script, &len)); - aprchk(apr_file_close(fp)); + ); // 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 @@ -221,30 +184,33 @@ namespace tut // Run the child process. apr_procattr_t *procattr = NULL; - aprchk(apr_procattr_create(&procattr, apr.pool)); + 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"); - argv.push_back(tempname); + // 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, - apr.pool)); + 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(apr.pool, &child, APR_KILL_AFTER_TIMEOUT); + 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, apr.pool); + 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. @@ -289,7 +255,7 @@ namespace tut } if (rv == EWOULDBLOCK || rv == EAGAIN) { -// std::cout << "(waiting; apr_file_gets(" << dfli->first << ") => " << rv << ": " << apr.strerror(rv) << ")\n"; +// std::cout << "(waiting; apr_file_gets(" << dfli->first << ") => " << rv << ": " << manager.strerror(rv) << ")\n"; ++history.back().tries; continue; } @@ -334,14 +300,11 @@ namespace tut 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 << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; +// 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); - // Remove temp script file - aprchk(apr_file_remove(tempname, apr.pool)); - // 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. diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 72322c3b72..4359e9afb9 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -43,38 +43,12 @@ typedef U32 uint32_t; #include "llprocesslauncher.h" #endif -#include - -/*==========================================================================*| -// Whoops, seems Linden's Boost package and the viewer are built with -// different settings of VC's /Zc:wchar_t switch! Using Boost.Filesystem -// pathname operations produces Windows link errors: -// unresolved external symbol "private: static class std::codecvt const * & __cdecl boost::filesystem3::path::wchar_t_codecvt_facet()" -// unresolved external symbol "void __cdecl boost::filesystem3::path_traits::convert()" -// See: -// http://boost.2283326.n4.nabble.com/filesystem-v3-unicode-and-std-codecvt-linker-error-td3455549.html -// which points to: -// http://msdn.microsoft.com/en-us/library/dh8che7s%28v=VS.100%29.aspx - -// As we're not trying to preserve compatibility with old Boost.Filesystem -// code, but rather writing brand-new code, use the newest available -// Filesystem API. -#define BOOST_FILESYSTEM_VERSION 3 -#include "boost/filesystem.hpp" -#include "boost/filesystem/v3/fstream.hpp" -|*==========================================================================*/ #include "boost/range.hpp" #include "boost/foreach.hpp" #include "boost/function.hpp" #include "boost/lambda/lambda.hpp" #include "boost/lambda/bind.hpp" namespace lambda = boost::lambda; -/*==========================================================================*| -// Aaaarrgh, Linden's Boost package doesn't even include Boost.Iostreams! -#include "boost/iostreams/stream.hpp" -#include "boost/iostreams/device/file_descriptor.hpp" -|*==========================================================================*/ #include "../llsd.h" #include "../llsdserialize.h" @@ -82,236 +56,17 @@ namespace lambda = boost::lambda; #include "../llformat.h" #include "../test/lltut.h" +#include "../test/manageapr.h" +#include "../test/namedtempfile.h" #include "stringize.h" +static ManageAPR manager; + std::vector string_to_vector(const std::string& str) { return std::vector(str.begin(), str.end()); } -#if ! LL_WINDOWS -// We want to call strerror_r(), but alarmingly, there are two different -// variants. The one that returns int always populates the passed buffer -// (except in case of error), whereas the other one always returns a valid -// char* but might or might not populate the passed buffer. How do we know -// which one we're getting? Define adapters for each and let the compiler -// select the applicable adapter. - -// strerror_r() returns char* -std::string message_from(int /*orig_errno*/, const char* /*buffer*/, const char* strerror_ret) -{ - return strerror_ret; -} - -// strerror_r() returns int -std::string message_from(int orig_errno, const char* buffer, int strerror_ret) -{ - if (strerror_ret == 0) - { - return buffer; - } - // Here strerror_r() has set errno. Since strerror_r() has already failed, - // seems like a poor bet to call it again to diagnose its own error... - int stre_errno = errno; - if (stre_errno == ERANGE) - { - return STRINGIZE("strerror_r() can't explain errno " << orig_errno - << " (buffer too small)"); - } - if (stre_errno == EINVAL) - { - return STRINGIZE("unknown errno " << orig_errno); - } - // Here we don't even understand the errno from strerror_r()! - return STRINGIZE("strerror_r() can't explain errno " << orig_errno - << " (error " << stre_errno << ')'); -} -#endif // ! LL_WINDOWS - -// boost::filesystem::temp_directory_path() isn't yet in Boost 1.45! :-( -std::string temp_directory_path() -{ -#if LL_WINDOWS - char buffer[4096]; - GetTempPathA(sizeof(buffer), buffer); - return buffer; - -#else // LL_DARWIN, LL_LINUX - static const char* vars[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR" }; - BOOST_FOREACH(const char* var, vars) - { - const char* found = getenv(var); - if (found) - return found; - } - return "/tmp"; -#endif // LL_DARWIN, LL_LINUX -} - -// Windows presents a kinda sorta compatibility layer. Code to the yucky -// Windows names because they're less likely than the Posix names to collide -// with any other names in this source. -#if LL_WINDOWS -#define _remove DeleteFileA -#else // ! LL_WINDOWS -#define _open open -#define _write write -#define _close close -#define _remove remove -#endif // ! LL_WINDOWS - -// Create a text file with specified content "somewhere in the -// filesystem," cleaning up when it goes out of scope. -class NamedTempFile -{ -public: - // Function that accepts an ostream ref and (presumably) writes stuff to - // it, e.g.: - // (lambda::_1 << "the value is " << 17 << '\n') - typedef boost::function Streamer; - - NamedTempFile(const std::string& ext, const std::string& content): - mPath(temp_directory_path()) - { - createFile(ext, lambda::_1 << content); - } - - // Disambiguate when passing string literal - NamedTempFile(const std::string& ext, const char* content): - mPath(temp_directory_path()) - { - createFile(ext, lambda::_1 << content); - } - - NamedTempFile(const std::string& ext, const Streamer& func): - mPath(temp_directory_path()) - { - createFile(ext, func); - } - - ~NamedTempFile() - { - _remove(mPath.c_str()); - } - - std::string getName() const { return mPath; } - -private: - void createFile(const std::string& ext, const Streamer& func) - { - // Silly maybe, but use 'ext' as the name prefix. Strip off a leading - // '.' if present. - int pfx_offset = ((! ext.empty()) && ext[0] == '.')? 1 : 0; - -#if ! LL_WINDOWS - // Make sure mPath ends with a directory separator, if it doesn't already. - if (mPath.empty() || - ! (mPath[mPath.length() - 1] == '\\' || mPath[mPath.length() - 1] == '/')) - { - mPath.append("/"); - } - - // mkstemp() accepts and modifies a char* template string. Generate - // the template string, then copy to modifiable storage. - // mkstemp() requires its template string to end in six X's. - mPath += ext.substr(pfx_offset) + "XXXXXX"; - // Copy to vector - std::vector pathtemplate(mPath.begin(), mPath.end()); - // append a nul byte for classic-C semantics - pathtemplate.push_back('\0'); - // std::vector promises that a pointer to the 0th element is the same - // as a pointer to a contiguous classic-C array - int fd(mkstemp(&pathtemplate[0])); - if (fd == -1) - { - // The documented errno values (http://linux.die.net/man/3/mkstemp) - // are used in a somewhat unusual way, so provide context-specific - // errors. - if (errno == EEXIST) - { - LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath - << "\") could not create unique file " << LL_ENDL; - } - if (errno == EINVAL) - { - LL_ERRS("NamedTempFile") << "bad mkstemp() file path template '" - << mPath << "'" << LL_ENDL; - } - // Shrug, something else - int mkst_errno = errno; - char buffer[256]; - LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath << "\") failed: " - << message_from(mkst_errno, buffer, - strerror_r(mkst_errno, buffer, sizeof(buffer))) - << LL_ENDL; - } - // mkstemp() seems to have worked! Capture the modified filename. - // Avoid the nul byte we appended. - mPath.assign(pathtemplate.begin(), (pathtemplate.end()-1)); - -/*==========================================================================*| - // Define an ostream on the open fd. Tell it to close fd on destruction. - boost::iostreams::stream - out(fd, boost::iostreams::close_handle); -|*==========================================================================*/ - - // Write desired content. - std::ostringstream out; - // Stream stuff to it. - func(out); - - std::string data(out.str()); - int written(_write(fd, data.c_str(), data.length())); - int closed(_close(fd)); - llassert_always(written == data.length() && closed == 0); - -#else // LL_WINDOWS - // GetTempFileName() is documented to require a MAX_PATH buffer. - char tempname[MAX_PATH]; - // Use 'ext' as filename prefix, but skip leading '.' if any. - // The 0 param is very important: requests iterating until we get a - // unique name. - if (0 == GetTempFileNameA(mPath.c_str(), ext.c_str() + pfx_offset, 0, tempname)) - { - // I always have to look up this call... :-P - LPSTR msgptr; - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - LPSTR(&msgptr), // have to cast (char**) to (char*) - 0, NULL ); - LL_ERRS("NamedTempFile") << "GetTempFileName(\"" << mPath << "\", \"" - << (ext.c_str() + pfx_offset) << "\") failed: " - << msgptr << LL_ENDL; - LocalFree(msgptr); - } - // GetTempFileName() appears to have worked! Capture the actual - // filename. - mPath = tempname; - // Open the file and stream content to it. Destructor will close. - std::ofstream out(tempname); - func(out); - -#endif // LL_WINDOWS - } - - void peep() - { - std::cout << "File '" << mPath << "' contains:\n"; - std::ifstream reader(mPath.c_str()); - std::string line; - while (std::getline(reader, line)) - std::cout << line << '\n'; - std::cout << "---\n"; - } - - std::string mPath; -}; - namespace tut { struct sd_xml_data @@ -1783,7 +1538,7 @@ namespace tut const char* PYTHON(getenv("PYTHON")); ensure("Set $PYTHON to the Python interpreter", PYTHON); - NamedTempFile scriptfile(".py", script); + NamedTempFile scriptfile("py", script); #if LL_WINDOWS std::string q("\""); @@ -1888,12 +1643,12 @@ namespace tut " else:\n" " assert False, 'Too many data items'\n"; - // Create a something.llsd file containing 'data' serialized to + // Create an llsdXXXXXX file containing 'data' serialized to // notation. It's important to separate with newlines because Python's // llsd module doesn't support parsing from a file stream, only from a // string, so we have to know how much of the file to read into a // string. - NamedTempFile file(".llsd", + NamedTempFile file("llsd", // NamedTempFile's boost::function constructor // takes a callable. To this callable it passes the // std::ostream with which it's writing the @@ -1926,7 +1681,7 @@ namespace tut // Create an empty data file. This is just a placeholder for our // script to write into. Create it to establish a unique name that // we know. - NamedTempFile file(".llsd", ""); + NamedTempFile file("llsd", ""); python("write Python notation", lambda::_1 << diff --git a/indra/test/manageapr.h b/indra/test/manageapr.h new file mode 100644 index 0000000000..0c1ca7b7be --- /dev/null +++ b/indra/test/manageapr.h @@ -0,0 +1,45 @@ +/** + * @file manageapr.h + * @author Nat Goodspeed + * @date 2012-01-13 + * @brief ManageAPR class for simple test programs + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_MANAGEAPR_H) +#define LL_MANAGEAPR_H + +#include "llapr.h" + +/** + * Declare a static instance of this class for dead-simple ll_init_apr() at + * program startup, ll_cleanup_apr() at termination. This is recommended for + * use only with simple test programs. Once you start introducing static + * instances of other classes that depend on APR already being initialized, + * the indeterminate static-constructor-order problem rears its ugly head. + */ +class ManageAPR +{ +public: + ManageAPR() + { + ll_init_apr(); + } + + ~ManageAPR() + { + ll_cleanup_apr(); + } + + static std::string strerror(apr_status_t rv) + { + char errbuf[256]; + apr_strerror(rv, errbuf, sizeof(errbuf)); + return errbuf; + } +}; + +#endif /* ! defined(LL_MANAGEAPR_H) */ diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h new file mode 100644 index 0000000000..9670d4db53 --- /dev/null +++ b/indra/test/namedtempfile.h @@ -0,0 +1,113 @@ +/** + * @file namedtempfile.h + * @author Nat Goodspeed + * @date 2012-01-13 + * @brief NamedTempFile class for tests that need disk files as fixtures. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_NAMEDTEMPFILE_H) +#define LL_NAMEDTEMPFILE_H + +#include "llapr.h" +#include "apr_file_io.h" +#include +#include +#include "boost/lambda/lambda.hpp" +#include "boost/lambda/bind.hpp" +#include +#include + +/** + * Create a text file with specified content "somewhere in the + * filesystem," cleaning up when it goes out of scope. + */ +class NamedTempFile +{ +public: + NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp): + mPool(pool) + { + createFile(pfx, boost::lambda::_1 << content); + } + + // Disambiguate when passing string literal + NamedTempFile(const std::string& pfx, const char* content, apr_pool_t* pool=gAPRPoolp): + mPool(pool) + { + createFile(pfx, boost::lambda::_1 << content); + } + + // Function that accepts an ostream ref and (presumably) writes stuff to + // it, e.g.: + // (boost::lambda::_1 << "the value is " << 17 << '\n') + typedef boost::function Streamer; + + NamedTempFile(const std::string& pfx, const Streamer& func, apr_pool_t* pool=gAPRPoolp): + mPool(pool) + { + createFile(pfx, func); + } + + ~NamedTempFile() + { + ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool)); + } + + std::string getName() const { return mPath; } + +private: + void createFile(const std::string& pfx, const Streamer& func) + { + // Create file in a temporary place. + const char* tempdir = NULL; + ll_apr_assert_status(apr_temp_dir_get(&tempdir, mPool)); + + // Construct a temp filename template in that directory. + char *tempname = NULL; + ll_apr_assert_status(apr_filepath_merge(&tempname, + tempdir, + (pfx + "XXXXXX").c_str(), + 0, + mPool)); + + // Create a temp file from that template. + apr_file_t* fp = NULL; + ll_apr_assert_status(apr_file_mktemp(&fp, + tempname, + APR_CREATE | APR_WRITE | APR_EXCL, + mPool)); + // apr_file_mktemp() alters tempname with the actual name. Not until + // now is it valid to capture as our mPath. + mPath = tempname; + + // Write desired content. + std::ostringstream out; + // Stream stuff to it. + func(out); + + std::string data(out.str()); + apr_size_t writelen(data.length()); + ll_apr_assert_status(apr_file_write(fp, data.c_str(), &writelen)); + ll_apr_assert_status(apr_file_close(fp)); + llassert_always(writelen == data.length()); + } + + void peep() + { + std::cout << "File '" << mPath << "' contains:\n"; + std::ifstream reader(mPath.c_str()); + std::string line; + while (std::getline(reader, line)) + std::cout << line << '\n'; + std::cout << "---\n"; + } + + std::string mPath; + apr_pool_t* mPool; +}; + +#endif /* ! defined(LL_NAMEDTEMPFILE_H) */ -- cgit v1.2.3 From a01dd3549cca620de47fae824198473c51a12f49 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Jan 2012 18:40:05 -0500 Subject: Make NamedTempFile::peep() a public member for debugging unit tests. --- indra/test/namedtempfile.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 9670d4db53..7ffb2836cc 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -59,6 +59,16 @@ public: std::string getName() const { return mPath; } + void peep() + { + std::cout << "File '" << mPath << "' contains:\n"; + std::ifstream reader(mPath.c_str()); + std::string line; + while (std::getline(reader, line)) + std::cout << line << '\n'; + std::cout << "---\n"; + } + private: void createFile(const std::string& pfx, const Streamer& func) { @@ -96,16 +106,6 @@ private: llassert_always(writelen == data.length()); } - void peep() - { - std::cout << "File '" << mPath << "' contains:\n"; - std::ifstream reader(mPath.c_str()); - std::string line; - while (std::getline(reader, line)) - std::cout << line << '\n'; - std::cout << "---\n"; - } - std::string mPath; apr_pool_t* mPool; }; -- cgit v1.2.3 From c0731c1c05cafe508c91c5f583301234ba3b8403 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Jan 2012 18:55:42 -0500 Subject: Add log message if LLProcessLauncher child fails to execv(). On a Posix platform (vfork()/execv() implementation), if for any reason the execv() failed (e.g. executable not on PATH), the viewer would never know, nor the user: the vfork() child produced no output, and terminated with rc 0! Add logging, make child terminate with nonzero rc. Remove pointless addArgument(const char*) overload: this does nothing for you that the compiler won't do implicitly. In llupdateinstaller.cpp, remove pointless c_str() call in addArgument() arg: we were starting with a std::string, then extracting its c_str(), only to construct a whole new std::string from it! --- indra/llcommon/llprocesslauncher.cpp | 20 +++++++++++--------- indra/llcommon/llprocesslauncher.h | 4 +++- .../viewer_components/updater/llupdateinstaller.cpp | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/indra/llcommon/llprocesslauncher.cpp b/indra/llcommon/llprocesslauncher.cpp index 10950181fd..25d64e9e28 100644 --- a/indra/llcommon/llprocesslauncher.cpp +++ b/indra/llcommon/llprocesslauncher.cpp @@ -73,11 +73,6 @@ void LLProcessLauncher::addArgument(const std::string &arg) mLaunchArguments.push_back(arg); } -void LLProcessLauncher::addArgument(const char *arg) -{ - mLaunchArguments.push_back(std::string(arg)); -} - #if LL_WINDOWS int LLProcessLauncher::launch(void) @@ -262,12 +257,19 @@ int LLProcessLauncher::launch(void) if(id == 0) { // child process - ::execv(mExecutable.c_str(), (char * const *)fake_argv); - + // If we reach this point, the exec failed. - // Use _exit() instead of exit() per the vfork man page. - _exit(0); + 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 diff --git a/indra/llcommon/llprocesslauncher.h b/indra/llcommon/llprocesslauncher.h index 954c249147..1daa980c58 100644 --- a/indra/llcommon/llprocesslauncher.h +++ b/indra/llcommon/llprocesslauncher.h @@ -28,6 +28,7 @@ #define LL_LLPROCESSLAUNCHER_H #if LL_WINDOWS +#define WIN32_LEAN_AND_MEAN #include #endif @@ -51,7 +52,6 @@ public: void clearArguments(); void addArgument(const std::string &arg); - void addArgument(const char *arg); int launch(void); bool isRunning(void); @@ -66,10 +66,12 @@ public: 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) HANDLE getProcessHandle() { return mProcessHandle; }; #else pid_t getProcessID() { return mProcessID; }; diff --git a/indra/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp index c7b70c2de8..84f23b3acc 100644 --- a/indra/viewer_components/updater/llupdateinstaller.cpp +++ b/indra/viewer_components/updater/llupdateinstaller.cpp @@ -81,7 +81,7 @@ int ll_install_update(std::string const & script, LLProcessLauncher launcher; launcher.setExecutable(actualScriptPath); launcher.addArgument(updatePath); - launcher.addArgument(ll_install_failed_marker_path().c_str()); + launcher.addArgument(ll_install_failed_marker_path()); launcher.addArgument(boost::lexical_cast(required)); int result = launcher.launch(); launcher.orphan(); -- cgit v1.2.3 From 74fbd31813494fe120211fbdad3ed6da9c2d5d8b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Jan 2012 19:03:17 -0500 Subject: Add first couple of LLProcessLauncher tests. Run INTEGRATION_TEST_llprocesslauncher using setpython.py so we can find the Python interpreter of interest. Introduce python() function to run a Python script specified using NamedTempFile conventions. Introduce a convention by which we can read output from a Python script using only the limited pre-January-2012 LLProcessLauncher API. Introduce python_out() function to leverage that convention. Exercise a couple of LLProcessLauncher methods using all the above. --- indra/llcommon/CMakeLists.txt | 3 +- indra/llcommon/tests/llprocesslauncher_test.cpp | 146 ++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 334f78cbff..2c376bb016 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -328,7 +328,8 @@ 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(llprocesslauncher "" "${test_libs}" + "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py") LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") # *TODO - reenable these once tcmalloc libs no longer break the build. diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 3935c64a94..aebd280c2e 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -18,10 +18,14 @@ #include #include // std headers +#include // external library headers #include "llapr.h" #include "apr_thread_proc.h" #include +#include +#include +#include // other Linden headers #include "../test/lltut.h" #include "../test/manageapr.h" @@ -36,6 +40,8 @@ #include #endif +namespace lambda = boost::lambda; + // static instance of this manages APR init/cleanup static ManageAPR manager; @@ -56,6 +62,116 @@ namespace tut rv, expected); } + /** + * Run a Python script using LLProcessLauncher. + * @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) + * @param arg If specified, will be passed to script as its + * sys.argv[1] + * @param tweak "Do something" to LLProcessLauncher object before + * calling its launch() method. This program is to test + * LLProcessLauncher, but many such tests are "just like" this + * python() function but for one or two extra method calls before + * launch(). This avoids us having to clone & edit this function for + * such tests. + */ + template + void python(const std::string& desc, const CONTENT& script, const std::string& arg="", + const boost::function tweak=lambda::_1) + { + const char* PYTHON(getenv("PYTHON")); + ensure("Set $PYTHON to the Python interpreter", PYTHON); + + NamedTempFile scriptfile("py", script); + LLProcessLauncher py; + py.setExecutable(PYTHON); + py.addArgument(scriptfile.getName()); + if (! arg.empty()) + { + py.addArgument(arg); + } + tweak(py); + ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.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 (py.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. + * + * @param desc as for python() + * @param script as for python() + * @param tweak as for python() + */ + template + std::string python_out(const std::string& desc, const CONTENT& script, + const boost::function tweak=lambda::_1) + { + NamedTempFile out("out", ""); // placeholder + // pass name of this temporary file to the script + python(desc, script, out.getName(), tweak); + // assuming the script wrote a line to that file, read it + std::string output; + { + std::ifstream inf(out.getName().c_str()); + ensure(STRINGIZE("No output from " << desc << " script"), + std::getline(inf, output)); + std::string more; + while (std::getline(inf, more)) + { + output += '\n' + more; + } + } // important to close inf BEFORE removing NamedTempFile + return output; + } + + class NamedTempDir + { + 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(llprocesslauncher_data* ths): + mThis(ths), + mPath(ths->python_out("mkdtemp()", + "import os.path, sys, tempfile\n" + "with open(sys.argv[1], 'w') as f:\n" + " f.write(os.path.realpath(tempfile.mkdtemp()))\n")) + {} + + ~NamedTempDir() + { + mThis->aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp)); + } + + std::string getName() const { return mPath; } + + private: + llprocesslauncher_data* mThis; + std::string mPath; + }; + LLAPRPool pool; }; typedef test_group llprocesslauncher_group; @@ -349,4 +465,34 @@ namespace tut 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. + NamedTempDir tempdir(this); + std::string cwd(python_out("getcwd()", + "import os, sys\n" + "with open(sys.argv[1], 'w') as f:\n" + " f.write(os.getcwd())\n", + // Before LLProcessLauncher::launch(), call setWorkingDirectory() + lambda::bind(&LLProcessLauncher::setWorkingDirectory, + lambda::_1, + tempdir.getName()))); + ensure_equals("os.getcwd()", cwd, tempdir.getName()); + } } // namespace tut -- cgit v1.2.3 From 51b26cab9ad8dc54277c6158ad40afdf3ed0e6d0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Jan 2012 20:30:46 -0500 Subject: Any proper RAII class must either handle copying or be noncopyable. NamedTempFile makes no attempt to deal with copying, therefore make it noncopyable. --- indra/test/namedtempfile.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 7ffb2836cc..aa7058b111 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -16,8 +16,9 @@ #include "apr_file_io.h" #include #include -#include "boost/lambda/lambda.hpp" -#include "boost/lambda/bind.hpp" +#include +#include +#include #include #include @@ -25,7 +26,7 @@ * Create a text file with specified content "somewhere in the * filesystem," cleaning up when it goes out of scope. */ -class NamedTempFile +class NamedTempFile: public boost::noncopyable { public: NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp): -- cgit v1.2.3 From 2ae9f921f2e1d6bd10e4c334a19312761a914046 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Jan 2012 20:44:26 -0500 Subject: Refactor llprocesslauncher_test.cpp for better code reuse. Instead of free python() and python_out() functions containing a local temporary LLProcessLauncher instance, with a 'tweak' callback param to "do stuff" to that inaccessible object, change to a PythonProcessLauncher class that sets up a (public) LLProcessLauncher member, then allows you to run() or run() and then readfile() the output. Now you can construct an instance and tweak to your heart's content -- without funky callback syntax -- before running the script. Move all such helpers from TUT fixture struct to namespace scope. While fixture-struct methods can freely call one another, introducing a nested class gets awkward: constructor must explicitly require and bind a fixture-struct pointer or reference. Namespace scope solves this. (Truthfully, I only put them in the fixture struct originally because I thought it necessary for calling ensure() et al. But ensure() and friends are free functions; need only qualify them with tut:: namespace.) --- indra/llcommon/tests/llprocesslauncher_test.cpp | 282 +++++++++++++----------- 1 file changed, 155 insertions(+), 127 deletions(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index aebd280c2e..310271e465 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -12,7 +12,6 @@ // Precompiled header #include "linden_common.h" // associated header -#define WIN32_LEAN_AND_MEAN #include "llprocesslauncher.h" // STL headers #include @@ -24,8 +23,8 @@ #include "apr_thread_proc.h" #include #include -#include -#include +//#include +//#include // other Linden headers #include "../test/lltut.h" #include "../test/manageapr.h" @@ -40,138 +39,169 @@ #include #endif -namespace lambda = boost::lambda; +//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); +} -/***************************************************************************** -* TUT -*****************************************************************************/ -namespace tut +/** + * 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="") { - struct llprocesslauncher_data + std::string use_desc(desc); + if (use_desc.empty()) { - void aprchk_(const char* call, apr_status_t rv, apr_status_t expected=APR_SUCCESS) - { - ensure_equals(STRINGIZE(call << " => " << rv << ": " << manager.strerror(rv)), - rv, expected); - } + 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; +} - /** - * Run a Python script using LLProcessLauncher. - * @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) - * @param arg If specified, will be passed to script as its - * sys.argv[1] - * @param tweak "Do something" to LLProcessLauncher object before - * calling its launch() method. This program is to test - * LLProcessLauncher, but many such tests are "just like" this - * python() function but for one or two extra method calls before - * launch(). This avoids us having to clone & edit this function for - * such tests. - */ - template - void python(const std::string& desc, const CONTENT& script, const std::string& arg="", - const boost::function tweak=lambda::_1) - { - const char* PYTHON(getenv("PYTHON")); - ensure("Set $PYTHON to the Python interpreter", PYTHON); - - NamedTempFile scriptfile("py", script); - LLProcessLauncher py; - py.setExecutable(PYTHON); - py.addArgument(scriptfile.getName()); - if (! arg.empty()) - { - py.addArgument(arg); - } - tweak(py); - ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.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 (py.isRunning()) - { - sleep(1); - } - } +/** + * 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 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. - * - * @param desc as for python() - * @param script as for python() - * @param tweak as for python() - */ - template - std::string python_out(const std::string& desc, const CONTENT& script, - const boost::function tweak=lambda::_1) + /// 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()) { - NamedTempFile out("out", ""); // placeholder - // pass name of this temporary file to the script - python(desc, script, out.getName(), tweak); - // assuming the script wrote a line to that file, read it - std::string output; - { - std::ifstream inf(out.getName().c_str()); - ensure(STRINGIZE("No output from " << desc << " script"), - std::getline(inf, output)); - std::string more; - while (std::getline(inf, more)) - { - output += '\n' + more; - } - } // important to close inf BEFORE removing NamedTempFile - return output; + sleep(1); } + } - class NamedTempDir - { - 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(llprocesslauncher_data* ths): - mThis(ths), - mPath(ths->python_out("mkdtemp()", - "import os.path, sys, tempfile\n" - "with open(sys.argv[1], 'w') as f:\n" - " f.write(os.path.realpath(tempfile.mkdtemp()))\n")) - {} - - ~NamedTempDir() - { - mThis->aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp)); - } + /** + * 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()", + "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; } + std::string getName() const { return mPath; } - private: - llprocesslauncher_data* mThis; - std::string mPath; - }; +private: + std::string mPath; +}; +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llprocesslauncher_data + { LLAPRPool pool; }; typedef test_group llprocesslauncher_group; @@ -484,15 +514,13 @@ namespace tut // We want to test setWorkingDirectory(). But what directory is // guaranteed to exist on every machine, under every OS? Have to // create one. - NamedTempDir tempdir(this); - std::string cwd(python_out("getcwd()", - "import os, sys\n" - "with open(sys.argv[1], 'w') as f:\n" - " f.write(os.getcwd())\n", - // Before LLProcessLauncher::launch(), call setWorkingDirectory() - lambda::bind(&LLProcessLauncher::setWorkingDirectory, - lambda::_1, - tempdir.getName()))); - ensure_equals("os.getcwd()", cwd, tempdir.getName()); + NamedTempDir tempdir; + PythonProcessLauncher py("getcwd()", + "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()); } } // namespace tut -- cgit v1.2.3 From 4bfd84d3be8d33bc6eb0dab22d2b3034de0800c9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Jan 2012 21:40:41 -0500 Subject: Add tests for child-process args management and for kill() method. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 80 ++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 310271e465..4f6a6ed922 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -23,6 +23,8 @@ #include "apr_thread_proc.h" #include #include +#include +#include //#include //#include // other Linden headers @@ -513,7 +515,7 @@ namespace tut 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. + // create one. Naturally, ensure we clean it up when done. NamedTempDir tempdir; PythonProcessLauncher py("getcwd()", "import os, sys\n" @@ -523,4 +525,80 @@ namespace tut 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", + "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("kill()"); + PythonProcessLauncher py("kill()", + "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"); + } } // namespace tut -- cgit v1.2.3 From 06f9dbd8db9895f81d7bd325d8cf616f68533396 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Jan 2012 01:09:50 -0500 Subject: Introduce static LLProcessLauncher::isRunning(ll_pid_t) method. typedef LLProcessLauncher::ll_pid_t to be HANDLE on Windows, pid_t elsewhere. Then we can define getProcessID() returning ll_pid_t on all platforms, retaining getProcessHandle() for hypothetical existing consumers... of which there are none in practice. This lets us define isRunning(ll_pid_t) to encapsulate the platform-specific logic to actually check on a running child process, turning non-static isRunning() into a fairly trivial wrapper. --- indra/llcommon/llprocesslauncher.cpp | 49 +++++++++++++++++++++++------------- indra/llcommon/llprocesslauncher.h | 19 ++++++++++++-- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/indra/llcommon/llprocesslauncher.cpp b/indra/llcommon/llprocesslauncher.cpp index 25d64e9e28..e1af49c2fb 100644 --- a/indra/llcommon/llprocesslauncher.cpp +++ b/indra/llcommon/llprocesslauncher.cpp @@ -143,18 +143,25 @@ int LLProcessLauncher::launch(void) bool LLProcessLauncher::isRunning(void) { - if(mProcessHandle != 0) + 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) { - DWORD waitresult = WaitForSingleObject(mProcessHandle, 0); - if(waitresult == WAIT_OBJECT_0) - { - // the process has completed. - mProcessHandle = 0; - } + // the process has completed. + return 0; } - return (mProcessHandle != 0); + return handle; } + bool LLProcessLauncher::kill(void) { bool result = true; @@ -293,19 +300,25 @@ int LLProcessLauncher::launch(void) bool LLProcessLauncher::isRunning(void) { - if(mProcessID != 0) - { - // Check whether the process has exited, and reap it if it has. - if(reap_pid(mProcessID)) - { - // the process has exited. - mProcessID = 0; - } - } - + 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; diff --git a/indra/llcommon/llprocesslauncher.h b/indra/llcommon/llprocesslauncher.h index 1daa980c58..63193abd8f 100644 --- a/indra/llcommon/llprocesslauncher.h +++ b/indra/llcommon/llprocesslauncher.h @@ -54,6 +54,8 @@ public: 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. @@ -72,10 +74,23 @@ public: // Accessors for platform-specific process ID #if LL_WINDOWS // (Windows flavor unused as of 2012-01-12) - HANDLE getProcessHandle() { return mProcessHandle; }; + typedef HANDLE ll_pid_t; + HANDLE getProcessHandle() const { return mProcessHandle; } + ll_pid_t getProcessID() const { return mProcessHandle; } #else - pid_t getProcessID() { return mProcessID; }; + 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; -- cgit v1.2.3 From ff4addd1b427344c6064734bdb59952e78f759fd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Jan 2012 01:10:52 -0500 Subject: Add tests for implicit-kill-on-destroy, also orphan() method. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 110 +++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 4f6a6ed922..7c0f0eaa84 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -566,7 +566,7 @@ namespace tut template<> template<> void object::test<5>() { - set_test_name("kill()"); + set_test_name("explicit kill()"); PythonProcessLauncher py("kill()", "import sys, time\n" "with open(sys.argv[1], 'w') as f:\n" @@ -601,4 +601,112 @@ namespace tut // 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()", + "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()", + "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 -- cgit v1.2.3 From 1ed5bb3adaea0b4fee1e471575459039df8ced2f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Jan 2012 10:56:13 -0500 Subject: Make embedded Python scripts compatible with Python 2.5 *SIGH* Apparently our TeamCity build machines are still not up to Python 2.6. --- indra/llcommon/tests/llprocesslauncher_test.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/llcommon/tests/llprocesslauncher_test.cpp b/indra/llcommon/tests/llprocesslauncher_test.cpp index 7c0f0eaa84..057f83631e 100644 --- a/indra/llcommon/tests/llprocesslauncher_test.cpp +++ b/indra/llcommon/tests/llprocesslauncher_test.cpp @@ -181,6 +181,7 @@ public: // 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")) @@ -518,6 +519,7 @@ namespace tut // 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"); @@ -531,6 +533,7 @@ namespace tut { 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" @@ -568,6 +571,7 @@ namespace tut { 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" @@ -610,6 +614,7 @@ namespace tut 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" @@ -655,6 +660,7 @@ namespace tut 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" -- cgit v1.2.3 From 6fddfab6dd8e49dcbf4933bde23dbcdfe3e213db Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 19 Jan 2012 16:41:17 -0500 Subject: Try to fix argument quoting on Windows. Despite LLProcessLauncher's list-of-argument-strings API, on Windows it must ram them all into a single command-line string anyway. This means that if arguments contain spaces (or anything else that would confuse Windows command- line parsing), the target process won't receive the intended arguments. Introduce double quotes for any arguments not already double-quoted by caller. --- indra/llcommon/llprocesslauncher.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/llprocesslauncher.cpp b/indra/llcommon/llprocesslauncher.cpp index e1af49c2fb..adad3546fe 100644 --- a/indra/llcommon/llprocesslauncher.cpp +++ b/indra/llcommon/llprocesslauncher.cpp @@ -75,6 +75,28 @@ void LLProcessLauncher::addArgument(const std::string &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.append(*ci); + } + return result + "\""; +} + int LLProcessLauncher::launch(void) { // If there was already a process associated with this object, kill it. @@ -87,11 +109,11 @@ int LLProcessLauncher::launch(void) STARTUPINFOA sinfo; memset(&sinfo, 0, sizeof(sinfo)); - std::string args = mExecutable; + std::string args = quote(mExecutable); for(int i = 0; i < (int)mLaunchArguments.size(); i++) { args += " "; - args += mLaunchArguments[i]; + args += quote(mLaunchArguments[i]); } // So retarded. Windows requires that the second parameter to CreateProcessA be a writable (non-const) string... -- cgit v1.2.3 From 083a9e0927144a9e2f052bc8573da8a26259a257 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 19 Jan 2012 17:04:55 -0500 Subject: To grow std::string by a char, use push_back() instead of append(). --- indra/llcommon/llprocesslauncher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llprocesslauncher.cpp b/indra/llcommon/llprocesslauncher.cpp index adad3546fe..5791d14ec0 100644 --- a/indra/llcommon/llprocesslauncher.cpp +++ b/indra/llcommon/llprocesslauncher.cpp @@ -92,7 +92,7 @@ static std::string quote(const std::string& str) { result.append("\\"); } - result.append(*ci); + result.push_back(*ci); } return result + "\""; } -- cgit v1.2.3 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/CMakeLists.txt | 1 - .../integration_tests/llui_libtest/CMakeLists.txt | 2 - indra/llcommon/CMakeLists.txt | 3 + indra/llcommon/llinitparam.cpp | 469 ++++ indra/llcommon/llinitparam.h | 2294 ++++++++++++++++++++ indra/llcommon/llregistry.h | 351 +++ indra/llui/CMakeLists.txt | 8 +- indra/llui/lltrans.cpp | 295 +++ indra/llui/lltrans.h | 133 ++ indra/llui/lluicolor.cpp | 87 + indra/llui/lluicolor.h | 71 + indra/llui/llxuiparser.cpp | 1756 +++++++++++++++ indra/llui/llxuiparser.h | 242 +++ indra/llui/tests/llurlentry_stub.cpp | 22 - indra/llui/tests/llurlentry_test.cpp | 15 - indra/llui/tests/llurlmatch_test.cpp | 34 - indra/llxuixml/CMakeLists.txt | 45 - indra/llxuixml/llinitparam.cpp | 469 ---- indra/llxuixml/llinitparam.h | 2294 -------------------- indra/llxuixml/llregistry.h | 351 --- indra/llxuixml/lltrans.cpp | 295 --- indra/llxuixml/lltrans.h | 133 -- indra/llxuixml/lluicolor.cpp | 87 - indra/llxuixml/lluicolor.h | 71 - indra/llxuixml/llxuiparser.cpp | 1756 --------------- indra/llxuixml/llxuiparser.h | 242 --- indra/newview/CMakeLists.txt | 3 - indra/newview/llviewerprecompiledheaders.h | 4 +- 28 files changed, 5709 insertions(+), 5824 deletions(-) create mode 100644 indra/llcommon/llinitparam.cpp create mode 100644 indra/llcommon/llinitparam.h create mode 100644 indra/llcommon/llregistry.h create mode 100644 indra/llui/lltrans.cpp create mode 100644 indra/llui/lltrans.h create mode 100644 indra/llui/lluicolor.cpp create mode 100644 indra/llui/lluicolor.h create mode 100644 indra/llui/llxuiparser.cpp create mode 100644 indra/llui/llxuiparser.h delete mode 100644 indra/llxuixml/CMakeLists.txt delete mode 100644 indra/llxuixml/llinitparam.cpp delete mode 100644 indra/llxuixml/llinitparam.h delete mode 100644 indra/llxuixml/llregistry.h delete mode 100644 indra/llxuixml/lltrans.cpp delete mode 100644 indra/llxuixml/lltrans.h delete mode 100644 indra/llxuixml/lluicolor.cpp delete mode 100644 indra/llxuixml/lluicolor.h delete mode 100644 indra/llxuixml/llxuiparser.cpp delete mode 100644 indra/llxuixml/llxuiparser.h diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 4b1bf49d07..1cebb53a07 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -66,7 +66,6 @@ if (VIEWER) add_subdirectory(${LIBS_OPEN_PREFIX}llcrashlogger) add_subdirectory(${LIBS_OPEN_PREFIX}llplugin) add_subdirectory(${LIBS_OPEN_PREFIX}llui) - add_subdirectory(${LIBS_OPEN_PREFIX}llxuixml) add_subdirectory(${LIBS_OPEN_PREFIX}viewer_components) # Legacy C++ tests. Build always, run if LL_TESTS is true. diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt index 1180460f4b..633ad84159 100644 --- a/indra/integration_tests/llui_libtest/CMakeLists.txt +++ b/indra/integration_tests/llui_libtest/CMakeLists.txt @@ -18,7 +18,6 @@ include(LLWindow) include(LLUI) include(LLVFS) # ugh, needed for LLDir include(LLXML) -include(LLXUIXML) include(Linking) # include(Tut) @@ -32,7 +31,6 @@ include_directories( ${LLVFS_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LLXUIXML_INCLUDE_DIRS} ) set(llui_libtest_SOURCE_FILES diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 0a3eaec5c5..72b1d363b0 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -61,6 +61,7 @@ set(llcommon_SOURCE_FILES llformat.cpp llframetimer.cpp llheartbeat.cpp + llinitparam.cpp llinstancetracker.cpp llliveappconfig.cpp lllivefile.cpp @@ -173,6 +174,7 @@ set(llcommon_HEADER_FILES llheartbeat.h llhttpstatuscodes.h llindexedqueue.h + llinitparam.h llinstancetracker.h llkeythrottle.h lllazy.h @@ -204,6 +206,7 @@ set(llcommon_HEADER_FILES llqueuedthread.h llrand.h llrefcount.h + llregistry.h llrun.h llrefcount.h llsafehandle.h diff --git a/indra/llcommon/llinitparam.cpp b/indra/llcommon/llinitparam.cpp new file mode 100644 index 0000000000..db72aa19b9 --- /dev/null +++ b/indra/llcommon/llinitparam.cpp @@ -0,0 +1,469 @@ +/** + * @file llinitparam.cpp + * @brief parameter block abstraction for creating complex objects and + * parsing construction parameters from xml and LLSD + * + * $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 "llinitparam.h" + + +namespace LLInitParam +{ + // + // Param + // + Param::Param(BaseBlock* enclosing_block) + : mIsProvided(false) + { + const U8* my_addr = reinterpret_cast(this); + const U8* block_addr = reinterpret_cast(enclosing_block); + mEnclosingBlockOffset = 0x7FFFffff & (U32)(my_addr - block_addr); + } + + // + // ParamDescriptor + // + ParamDescriptor::ParamDescriptor(param_handle_t p, + merge_func_t merge_func, + deserialize_func_t deserialize_func, + serialize_func_t serialize_func, + validation_func_t validation_func, + inspect_func_t inspect_func, + S32 min_count, + S32 max_count) + : mParamHandle(p), + mMergeFunc(merge_func), + mDeserializeFunc(deserialize_func), + mSerializeFunc(serialize_func), + mValidationFunc(validation_func), + mInspectFunc(inspect_func), + mMinCount(min_count), + mMaxCount(max_count), + mUserData(NULL) + {} + + ParamDescriptor::ParamDescriptor() + : mParamHandle(0), + mMergeFunc(NULL), + mDeserializeFunc(NULL), + mSerializeFunc(NULL), + mValidationFunc(NULL), + mInspectFunc(NULL), + mMinCount(0), + mMaxCount(0), + mUserData(NULL) + {} + + ParamDescriptor::~ParamDescriptor() + { + delete mUserData; + } + + // + // Parser + // + Parser::~Parser() + {} + + void Parser::parserWarning(const std::string& message) + { + if (mParseSilently) return; + llwarns << message << llendl; + } + + void Parser::parserError(const std::string& message) + { + if (mParseSilently) return; + llerrs << message << llendl; + } + + + // + // BlockDescriptor + // + void BlockDescriptor::aggregateBlockData(BlockDescriptor& src_block_data) + { + mNamedParams.insert(src_block_data.mNamedParams.begin(), src_block_data.mNamedParams.end()); + std::copy(src_block_data.mUnnamedParams.begin(), src_block_data.mUnnamedParams.end(), std::back_inserter(mUnnamedParams)); + std::copy(src_block_data.mValidationList.begin(), src_block_data.mValidationList.end(), std::back_inserter(mValidationList)); + std::copy(src_block_data.mAllParams.begin(), src_block_data.mAllParams.end(), std::back_inserter(mAllParams)); + } + + BlockDescriptor::BlockDescriptor() + : mMaxParamOffset(0), + mInitializationState(UNINITIALIZED), + mCurrentBlockPtr(NULL) + {} + + // called by each derived class in least to most derived order + void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) + { + descriptor.mCurrentBlockPtr = this; + descriptor.mMaxParamOffset = block_size; + + switch(descriptor.mInitializationState) + { + case BlockDescriptor::UNINITIALIZED: + // copy params from base class here + descriptor.aggregateBlockData(base_descriptor); + + descriptor.mInitializationState = BlockDescriptor::INITIALIZING; + break; + case BlockDescriptor::INITIALIZING: + descriptor.mInitializationState = BlockDescriptor::INITIALIZED; + break; + case BlockDescriptor::INITIALIZED: + // nothing to do + break; + } + } + + param_handle_t BaseBlock::getHandleFromParam(const Param* param) const + { + const U8* param_address = reinterpret_cast(param); + const U8* baseblock_address = reinterpret_cast(this); + return (param_address - baseblock_address); + } + + bool BaseBlock::submitValue(Parser::name_stack_t& name_stack, Parser& p, bool silent) + { + if (!deserializeBlock(p, std::make_pair(name_stack.begin(), name_stack.end()), true)) + { + if (!silent) + { + p.parserWarning(llformat("Failed to parse parameter \"%s\"", p.getCurrentElementName().c_str())); + } + return false; + } + return true; + } + + + bool BaseBlock::validateBlock(bool emit_errors) const + { + const BlockDescriptor& block_data = mostDerivedBlockDescriptor(); + for (BlockDescriptor::param_validation_list_t::const_iterator it = block_data.mValidationList.begin(); it != block_data.mValidationList.end(); ++it) + { + const Param* param = getParamFromHandle(it->first); + if (!it->second(param)) + { + if (emit_errors) + { + llwarns << "Invalid param \"" << getParamName(block_data, param) << "\"" << llendl; + } + return false; + } + } + return true; + } + + void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const + { + // named param is one like LLView::Params::follows + // unnamed param is like LLView::Params::rect - implicit + const BlockDescriptor& block_data = mostDerivedBlockDescriptor(); + + for (BlockDescriptor::param_list_t::const_iterator it = block_data.mUnnamedParams.begin(); + it != block_data.mUnnamedParams.end(); + ++it) + { + param_handle_t param_handle = (*it)->mParamHandle; + const Param* param = getParamFromHandle(param_handle); + ParamDescriptor::serialize_func_t serialize_func = (*it)->mSerializeFunc; + if (serialize_func) + { + const Param* diff_param = diff_block ? diff_block->getParamFromHandle(param_handle) : NULL; + // each param descriptor remembers its serial number + // so we can inspect the same param under different names + // and see that it has the same number + name_stack.push_back(std::make_pair("", true)); + serialize_func(*param, parser, name_stack, diff_param); + name_stack.pop_back(); + } + } + + for(BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); + it != block_data.mNamedParams.end(); + ++it) + { + param_handle_t param_handle = it->second->mParamHandle; + const Param* param = getParamFromHandle(param_handle); + ParamDescriptor::serialize_func_t serialize_func = it->second->mSerializeFunc; + if (serialize_func && param->anyProvided()) + { + // Ensure this param has not already been serialized + // Prevents from being serialized as its own tag. + bool duplicate = false; + for (BlockDescriptor::param_list_t::const_iterator it2 = block_data.mUnnamedParams.begin(); + it2 != block_data.mUnnamedParams.end(); + ++it2) + { + if (param_handle == (*it2)->mParamHandle) + { + duplicate = true; + break; + } + } + + //FIXME: for now, don't attempt to serialize values under synonyms, as current parsers + // don't know how to detect them + if (duplicate) + { + continue; + } + + name_stack.push_back(std::make_pair(it->first, !duplicate)); + const Param* diff_param = diff_block ? diff_block->getParamFromHandle(param_handle) : NULL; + serialize_func(*param, parser, name_stack, diff_param); + name_stack.pop_back(); + } + } + } + + bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_count, S32 max_count) const + { + // named param is one like LLView::Params::follows + // unnamed param is like LLView::Params::rect - implicit + const BlockDescriptor& block_data = mostDerivedBlockDescriptor(); + + for (BlockDescriptor::param_list_t::const_iterator it = block_data.mUnnamedParams.begin(); + it != block_data.mUnnamedParams.end(); + ++it) + { + param_handle_t param_handle = (*it)->mParamHandle; + const Param* param = getParamFromHandle(param_handle); + ParamDescriptor::inspect_func_t inspect_func = (*it)->mInspectFunc; + if (inspect_func) + { + name_stack.push_back(std::make_pair("", true)); + inspect_func(*param, parser, name_stack, (*it)->mMinCount, (*it)->mMaxCount); + name_stack.pop_back(); + } + } + + for(BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); + it != block_data.mNamedParams.end(); + ++it) + { + param_handle_t param_handle = it->second->mParamHandle; + const Param* param = getParamFromHandle(param_handle); + ParamDescriptor::inspect_func_t inspect_func = it->second->mInspectFunc; + if (inspect_func) + { + // Ensure this param has not already been inspected + bool duplicate = false; + for (BlockDescriptor::param_list_t::const_iterator it2 = block_data.mUnnamedParams.begin(); + it2 != block_data.mUnnamedParams.end(); + ++it2) + { + if (param_handle == (*it2)->mParamHandle) + { + duplicate = true; + break; + } + } + + name_stack.push_back(std::make_pair(it->first, !duplicate)); + inspect_func(*param, parser, name_stack, it->second->mMinCount, it->second->mMaxCount); + name_stack.pop_back(); + } + } + + return true; + } + + bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool ignored) + { + BlockDescriptor& block_data = mostDerivedBlockDescriptor(); + bool names_left = name_stack_range.first != name_stack_range.second; + + bool new_name = names_left + ? name_stack_range.first->second + : true; + + if (names_left) + { + const std::string& top_name = name_stack_range.first->first; + + ParamDescriptor::deserialize_func_t deserialize_func = NULL; + Param* paramp = NULL; + + BlockDescriptor::param_map_t::iterator found_it = block_data.mNamedParams.find(top_name); + if (found_it != block_data.mNamedParams.end()) + { + // find pointer to member parameter from offset table + paramp = getParamFromHandle(found_it->second->mParamHandle); + deserialize_func = found_it->second->mDeserializeFunc; + + Parser::name_stack_range_t new_name_stack(name_stack_range.first, name_stack_range.second); + ++new_name_stack.first; + if (deserialize_func(*paramp, p, new_name_stack, new_name)) + { + // value is no longer new, we know about it now + name_stack_range.first->second = false; + return true; + } + else + { + return false; + } + } + } + + // try to parse unnamed parameters, in declaration order + for ( BlockDescriptor::param_list_t::iterator it = block_data.mUnnamedParams.begin(); + it != block_data.mUnnamedParams.end(); + ++it) + { + Param* paramp = getParamFromHandle((*it)->mParamHandle); + ParamDescriptor::deserialize_func_t deserialize_func = (*it)->mDeserializeFunc; + + if (deserialize_func && deserialize_func(*paramp, p, name_stack_range, new_name)) + { + return true; + } + } + + // if no match, and no names left on stack, this is just an existence assertion of this block + // verify by calling readValue with NoParamValue type, an inherently unparseable type + if (!names_left) + { + Flag no_value; + return p.readValue(no_value); + } + + return false; + } + + //static + void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name) + { + // create a copy of the param descriptor in mAllParams + // so other data structures can store a pointer to it + block_data.mAllParams.push_back(in_param); + ParamDescriptorPtr param(block_data.mAllParams.back()); + + std::string name(char_name); + if ((size_t)param->mParamHandle > block_data.mMaxParamOffset) + { + llerrs << "Attempted to register param with block defined for parent class, make sure to derive from LLInitParam::Block" << llendl; + } + + if (name.empty()) + { + block_data.mUnnamedParams.push_back(param); + } + else + { + // don't use insert, since we want to overwrite existing entries + block_data.mNamedParams[name] = param; + } + + if (param->mValidationFunc) + { + block_data.mValidationList.push_back(std::make_pair(param->mParamHandle, param->mValidationFunc)); + } + } + + void BaseBlock::addSynonym(Param& param, const std::string& synonym) + { + BlockDescriptor& block_data = mostDerivedBlockDescriptor(); + if (block_data.mInitializationState == BlockDescriptor::INITIALIZING) + { + param_handle_t handle = getHandleFromParam(¶m); + + // check for invalid derivation from a paramblock (i.e. without using + // Block + if ((size_t)handle > block_data.mMaxParamOffset) + { + llerrs << "Attempted to register param with block defined for parent class, make sure to derive from LLInitParam::Block" << llendl; + } + + ParamDescriptorPtr param_descriptor = findParamDescriptor(param); + if (param_descriptor) + { + if (synonym.empty()) + { + block_data.mUnnamedParams.push_back(param_descriptor); + } + else + { + block_data.mNamedParams[synonym] = param_descriptor; + } + } + } + } + + const std::string& BaseBlock::getParamName(const BlockDescriptor& block_data, const Param* paramp) const + { + param_handle_t handle = getHandleFromParam(paramp); + for (BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); it != block_data.mNamedParams.end(); ++it) + { + if (it->second->mParamHandle == handle) + { + return it->first; + } + } + + return LLStringUtil::null; + } + + ParamDescriptorPtr BaseBlock::findParamDescriptor(const Param& param) + { + param_handle_t handle = getHandleFromParam(¶m); + BlockDescriptor& descriptor = mostDerivedBlockDescriptor(); + BlockDescriptor::all_params_list_t::iterator end_it = descriptor.mAllParams.end(); + for (BlockDescriptor::all_params_list_t::iterator it = descriptor.mAllParams.begin(); + it != end_it; + ++it) + { + if ((*it)->mParamHandle == handle) return *it; + } + return ParamDescriptorPtr(); + } + + // take all provided params from other and apply to self + // NOTE: this requires that "other" is of the same derived type as this + bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) + { + bool some_param_changed = false; + BlockDescriptor::all_params_list_t::const_iterator end_it = block_data.mAllParams.end(); + for (BlockDescriptor::all_params_list_t::const_iterator it = block_data.mAllParams.begin(); + it != end_it; + ++it) + { + const Param* other_paramp = other.getParamFromHandle((*it)->mParamHandle); + ParamDescriptor::merge_func_t merge_func = (*it)->mMergeFunc; + if (merge_func) + { + Param* paramp = getParamFromHandle((*it)->mParamHandle); + llassert(paramp->mEnclosingBlockOffset == (*it)->mParamHandle); + some_param_changed |= merge_func(*paramp, *other_paramp, overwrite); + } + } + return some_param_changed; + } +} diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h new file mode 100644 index 0000000000..550e1608cc --- /dev/null +++ b/indra/llcommon/llinitparam.h @@ -0,0 +1,2294 @@ +/** + * @file llinitparam.h + * @brief parameter block abstraction for creating complex objects and + * parsing construction parameters from xml and LLSD + * + * $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_LLPARAM_H +#define LL_LLPARAM_H + +#include +#include +#include +#include +#include + +#include "llerror.h" + +namespace LLInitParam +{ + // used to indicate no matching value to a given name when parsing + struct Flag{}; + + template const T& defaultValue() { static T value; return value; } + + template ::value > + struct ParamCompare + { + static bool equals(const T &a, const T &b) + { + return a == b; + } + }; + + // boost function types are not comparable + template + struct ParamCompare + { + static bool equals(const T&a, const T &b) + { + return false; + } + }; + + template<> + struct ParamCompare + { + static bool equals(const LLSD &a, const LLSD &b) { return false; } + }; + + template<> + struct ParamCompare + { + static bool equals(const Flag& a, const Flag& b) { return false; } + }; + + + // helper functions and classes + typedef ptrdiff_t param_handle_t; + + // empty default implementation of key cache + // leverages empty base class optimization + template + class TypeValues + { + private: + struct Inaccessable{}; + public: + typedef std::map value_name_map_t; + typedef Inaccessable name_t; + + void setValueName(const std::string& key) {} + std::string getValueName() const { return ""; } + std::string calcValueName(const T& value) const { return ""; } + void clearValueName() const {} + + static bool getValueFromName(const std::string& name, T& value) + { + return false; + } + + static bool valueNamesExist() + { + return false; + } + + static std::vector* getPossibleValues() + { + return NULL; + } + + static value_name_map_t* getValueNames() {return NULL;} + }; + + template > + class TypeValuesHelper + { + public: + typedef typename std::map value_name_map_t; + typedef std::string name_t; + + //TODO: cache key by index to save on param block size + void setValueName(const std::string& value_name) + { + mValueName = value_name; + } + + std::string getValueName() const + { + return mValueName; + } + + std::string calcValueName(const T& value) const + { + value_name_map_t* map = getValueNames(); + for (typename value_name_map_t::iterator it = map->begin(), end_it = map->end(); + it != end_it; + ++it) + { + if (ParamCompare::equals(it->second, value)) + { + return it->first; + } + } + + return ""; + } + + void clearValueName() const + { + mValueName.clear(); + } + + static bool getValueFromName(const std::string& name, T& value) + { + value_name_map_t* map = getValueNames(); + typename value_name_map_t::iterator found_it = map->find(name); + if (found_it == map->end()) return false; + + value = found_it->second; + return true; + } + + static bool valueNamesExist() + { + return !getValueNames()->empty(); + } + + static value_name_map_t* getValueNames() + { + static value_name_map_t sMap; + static bool sInitialized = false; + + if (!sInitialized) + { + sInitialized = true; + DERIVED_TYPE::declareValues(); + } + return &sMap; + } + + static std::vector* getPossibleValues() + { + static std::vector sValues; + + value_name_map_t* map = getValueNames(); + for (typename value_name_map_t::iterator it = map->begin(), end_it = map->end(); + it != end_it; + ++it) + { + sValues.push_back(it->first); + } + return &sValues; + } + + static void declare(const std::string& name, const T& value) + { + (*getValueNames())[name] = value; + } + + protected: + static void getName(const std::string& name, const T& value) + {} + + mutable std::string mValueName; + }; + + class LL_COMMON_API Parser + { + LOG_CLASS(Parser); + + public: + + struct CompareTypeID + { + bool operator()(const std::type_info* lhs, const std::type_info* rhs) const + { + return lhs->before(*rhs); + } + }; + + typedef std::vector > name_stack_t; + typedef std::pair name_stack_range_t; + typedef std::vector possible_values_t; + + typedef bool (*parser_read_func_t)(Parser& parser, void* output); + typedef bool (*parser_write_func_t)(Parser& parser, const void*, name_stack_t&); + typedef boost::function parser_inspect_func_t; + + typedef std::map parser_read_func_map_t; + typedef std::map parser_write_func_map_t; + typedef std::map parser_inspect_func_map_t; + + Parser(parser_read_func_map_t& read_map, parser_write_func_map_t& write_map, parser_inspect_func_map_t& inspect_map) + : mParseSilently(false), + mParserReadFuncs(&read_map), + mParserWriteFuncs(&write_map), + mParserInspectFuncs(&inspect_map) + {} + virtual ~Parser(); + + template bool readValue(T& param) + { + parser_read_func_map_t::iterator found_it = mParserReadFuncs->find(&typeid(T)); + if (found_it != mParserReadFuncs->end()) + { + return found_it->second(*this, (void*)¶m); + } + return false; + } + + template bool writeValue(const T& param, name_stack_t& name_stack) + { + parser_write_func_map_t::iterator found_it = mParserWriteFuncs->find(&typeid(T)); + if (found_it != mParserWriteFuncs->end()) + { + return found_it->second(*this, (const void*)¶m, name_stack); + } + return false; + } + + // dispatch inspection to registered inspection functions, for each parameter in a param block + template bool inspectValue(name_stack_t& name_stack, S32 min_count, S32 max_count, const possible_values_t* possible_values) + { + parser_inspect_func_map_t::iterator found_it = mParserInspectFuncs->find(&typeid(T)); + if (found_it != mParserInspectFuncs->end()) + { + found_it->second(name_stack, min_count, max_count, possible_values); + return true; + } + return false; + } + + virtual std::string getCurrentElementName() = 0; + virtual void parserWarning(const std::string& message); + virtual void parserError(const std::string& message); + void setParseSilently(bool silent) { mParseSilently = silent; } + + protected: + template + void registerParserFuncs(parser_read_func_t read_func, parser_write_func_t write_func = NULL) + { + mParserReadFuncs->insert(std::make_pair(&typeid(T), read_func)); + mParserWriteFuncs->insert(std::make_pair(&typeid(T), write_func)); + } + + template + void registerInspectFunc(parser_inspect_func_t inspect_func) + { + mParserInspectFuncs->insert(std::make_pair(&typeid(T), inspect_func)); + } + + bool mParseSilently; + + private: + parser_read_func_map_t* mParserReadFuncs; + parser_write_func_map_t* mParserWriteFuncs; + parser_inspect_func_map_t* mParserInspectFuncs; + }; + + class Param; + + // various callbacks and constraints associated with an individual param + struct LL_COMMON_API ParamDescriptor + { + struct UserData + { + virtual ~UserData() {} + }; + + typedef bool(*merge_func_t)(Param&, const Param&, bool); + typedef bool(*deserialize_func_t)(Param&, Parser&, const Parser::name_stack_range_t&, bool); + typedef void(*serialize_func_t)(const Param&, Parser&, Parser::name_stack_t&, const Param* diff_param); + typedef void(*inspect_func_t)(const Param&, Parser&, Parser::name_stack_t&, S32 min_count, S32 max_count); + typedef bool(*validation_func_t)(const Param*); + + ParamDescriptor(param_handle_t p, + merge_func_t merge_func, + deserialize_func_t deserialize_func, + serialize_func_t serialize_func, + validation_func_t validation_func, + inspect_func_t inspect_func, + S32 min_count, + S32 max_count); + + ParamDescriptor(); + ~ParamDescriptor(); + + param_handle_t mParamHandle; + merge_func_t mMergeFunc; + deserialize_func_t mDeserializeFunc; + serialize_func_t mSerializeFunc; + inspect_func_t mInspectFunc; + validation_func_t mValidationFunc; + S32 mMinCount; + S32 mMaxCount; + S32 mNumRefs; + UserData* mUserData; + }; + + typedef boost::shared_ptr ParamDescriptorPtr; + + // each derived Block class keeps a static data structure maintaining offsets to various params + class LL_COMMON_API BlockDescriptor + { + public: + BlockDescriptor(); + + typedef enum e_initialization_state + { + UNINITIALIZED, + INITIALIZING, + INITIALIZED + } EInitializationState; + + void aggregateBlockData(BlockDescriptor& src_block_data); + + typedef boost::unordered_map param_map_t; + typedef std::vector param_list_t; + typedef std::list all_params_list_t; + typedef std::vector > param_validation_list_t; + + param_map_t mNamedParams; // parameters with associated names + param_list_t mUnnamedParams; // parameters with_out_ associated names + param_validation_list_t mValidationList; // parameters that must be validated + all_params_list_t mAllParams; // all parameters, owns descriptors + size_t mMaxParamOffset; + EInitializationState mInitializationState; // whether or not static block data has been initialized + class BaseBlock* mCurrentBlockPtr; // pointer to block currently being constructed + }; + + class LL_COMMON_API BaseBlock + { + public: + //TODO: implement in terms of owned_ptr + template + class Lazy + { + public: + Lazy() + : mPtr(NULL) + {} + + ~Lazy() + { + delete mPtr; + } + + Lazy(const Lazy& other) + { + if (other.mPtr) + { + mPtr = new T(*other.mPtr); + } + else + { + mPtr = NULL; + } + } + + Lazy& operator = (const Lazy& other) + { + if (other.mPtr) + { + mPtr = new T(*other.mPtr); + } + else + { + mPtr = NULL; + } + return *this; + } + + bool empty() const + { + return mPtr == NULL; + } + + void set(const T& other) + { + delete mPtr; + mPtr = new T(other); + } + + const T& get() const + { + return ensureInstance(); + } + + T& get() + { + return ensureInstance(); + } + + private: + // lazily allocate an instance of T + T* ensureInstance() const + { + if (mPtr == NULL) + { + mPtr = new T(); + } + return mPtr; + } + + private: + // if you get a compilation error with this, that means you are using a forward declared struct for T + // unfortunately, the type traits we rely on don't work with forward declared typed + //static const int dummy = sizeof(T); + + mutable T* mPtr; + }; + + // "Multiple" constraint types, put here in root class to avoid ambiguity during use + struct AnyAmount + { + enum { minCount = 0 }; + enum { maxCount = U32_MAX }; + }; + + template + struct AtLeast + { + enum { minCount = MIN_AMOUNT }; + enum { maxCount = U32_MAX }; + }; + + template + struct AtMost + { + enum { minCount = 0 }; + enum { maxCount = MAX_AMOUNT }; + }; + + template + struct Between + { + enum { minCount = MIN_AMOUNT }; + enum { maxCount = MAX_AMOUNT }; + }; + + template + struct Exactly + { + enum { minCount = EXACT_COUNT }; + enum { maxCount = EXACT_COUNT }; + }; + + // this typedef identifies derived classes as being blocks + typedef void baseblock_base_class_t; + LOG_CLASS(BaseBlock); + friend class Param; + + virtual ~BaseBlock() {} + bool submitValue(Parser::name_stack_t& name_stack, Parser& p, bool silent=false); + + param_handle_t getHandleFromParam(const Param* param) const; + bool validateBlock(bool emit_errors = true) const; + + Param* getParamFromHandle(const param_handle_t param_handle) + { + if (param_handle == 0) return NULL; + + U8* baseblock_address = reinterpret_cast(this); + return reinterpret_cast(baseblock_address + param_handle); + } + + const Param* getParamFromHandle(const param_handle_t param_handle) const + { + const U8* baseblock_address = reinterpret_cast(this); + return reinterpret_cast(baseblock_address + param_handle); + } + + void addSynonym(Param& param, const std::string& synonym); + + // Blocks can override this to do custom tracking of changes + virtual void paramChanged(const Param& changed_param, bool user_provided) {} + + bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); + void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const; + bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const; + + virtual const BlockDescriptor& mostDerivedBlockDescriptor() const { return selfBlockDescriptor(); } + virtual BlockDescriptor& mostDerivedBlockDescriptor() { return selfBlockDescriptor(); } + + // take all provided params from other and apply to self + bool overwriteFrom(const BaseBlock& other) + { + return false; + } + + // take all provided params that are not already provided, and apply to self + bool fillFrom(const BaseBlock& other) + { + return false; + } + + static void addParam(BlockDescriptor& block_data, ParamDescriptorPtr param, const char* name); + + ParamDescriptorPtr findParamDescriptor(const Param& param); + + protected: + void init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size); + + + bool mergeBlockParam(bool source_provided, bool dst_provided, BlockDescriptor& block_data, const BaseBlock& source, bool overwrite) + { + return mergeBlock(block_data, source, overwrite); + } + // take all provided params from other and apply to self + bool mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite); + + static BlockDescriptor& selfBlockDescriptor() + { + static BlockDescriptor sBlockDescriptor; + return sBlockDescriptor; + } + + private: + const std::string& getParamName(const BlockDescriptor& block_data, const Param* paramp) const; + }; + + template + struct ParamCompare, false > + { + static bool equals(const BaseBlock::Lazy& a, const BaseBlock::Lazy& b) { return !a.empty() || !b.empty(); } + }; + + class LL_COMMON_API Param + { + public: + void setProvided(bool is_provided = true) + { + mIsProvided = is_provided; + enclosingBlock().paramChanged(*this, is_provided); + } + + Param& operator =(const Param& other) + { + mIsProvided = other.mIsProvided; + // don't change mEnclosingblockoffset + return *this; + } + protected: + + bool anyProvided() const { return mIsProvided; } + + Param(BaseBlock* enclosing_block); + + // store pointer to enclosing block as offset to reduce space and allow for quick copying + BaseBlock& enclosingBlock() const + { + const U8* my_addr = reinterpret_cast(this); + // get address of enclosing BLOCK class using stored offset to enclosing BaseBlock class + return *const_cast + (reinterpret_cast + (my_addr - (ptrdiff_t)(S32)mEnclosingBlockOffset)); + } + + private: + friend class BaseBlock; + + U32 mEnclosingBlockOffset:31; + U32 mIsProvided:1; + + }; + + // these templates allow us to distinguish between template parameters + // that derive from BaseBlock and those that don't + template + struct IsBlock + { + static const bool value = false; + struct EmptyBase {}; + typedef EmptyBase base_class_t; + }; + + template + struct IsBlock + { + static const bool value = true; + typedef BaseBlock base_class_t; + }; + + template + struct IsBlock, typename T::baseblock_base_class_t > + { + static const bool value = true; + typedef BaseBlock base_class_t; + }; + + template::value> + class ParamValue : public NAME_VALUE_LOOKUP + { + public: + typedef const T& value_assignment_t; + typedef T value_t; + typedef ParamValue self_t; + + ParamValue(): mValue() {} + ParamValue(value_assignment_t other) : mValue(other) {} + + void setValue(value_assignment_t val) + { + mValue = val; + } + + value_assignment_t getValue() const + { + return mValue; + } + + T& getValue() + { + return mValue; + } + + operator value_assignment_t() const + { + return mValue; + } + + value_assignment_t operator()() const + { + return mValue; + } + + void operator ()(const typename NAME_VALUE_LOOKUP::name_t& name) + { + *this = name; + } + + self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) + { + if (NAME_VALUE_LOOKUP::getValueFromName(name, mValue)) + { + setValueName(name); + } + + return *this; + } + + protected: + T mValue; + }; + + template + class ParamValue + : public T, + public NAME_VALUE_LOOKUP + { + public: + typedef const T& value_assignment_t; + typedef T value_t; + typedef ParamValue self_t; + + ParamValue() + : T(), + mValidated(false) + {} + + ParamValue(value_assignment_t other) + : T(other), + mValidated(false) + {} + + void setValue(value_assignment_t val) + { + *this = val; + } + + value_assignment_t getValue() const + { + return *this; + } + + T& getValue() + { + return *this; + } + + operator value_assignment_t() const + { + return *this; + } + + value_assignment_t operator()() const + { + return *this; + } + + void operator ()(const typename NAME_VALUE_LOOKUP::name_t& name) + { + *this = name; + } + + self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) + { + if (NAME_VALUE_LOOKUP::getValueFromName(name, *this)) + { + setValueName(name); + } + + return *this; + } + + protected: + mutable bool mValidated; // lazy validation flag + }; + + template + class ParamValue + : public NAME_VALUE_LOOKUP + { + public: + typedef const std::string& value_assignment_t; + typedef std::string value_t; + typedef ParamValue self_t; + + ParamValue(): mValue() {} + ParamValue(value_assignment_t other) : mValue(other) {} + + void setValue(value_assignment_t val) + { + if (NAME_VALUE_LOOKUP::getValueFromName(val, mValue)) + { + NAME_VALUE_LOOKUP::setValueName(val); + } + else + { + mValue = val; + } + } + + value_assignment_t getValue() const + { + return mValue; + } + + std::string& getValue() + { + return mValue; + } + + operator value_assignment_t() const + { + return mValue; + } + + value_assignment_t operator()() const + { + return mValue; + } + + protected: + std::string mValue; + }; + + + template > + struct ParamIterator + { + typedef typename std::vector >::const_iterator const_iterator; + typedef typename std::vector >::iterator iterator; + }; + + // specialize for custom parsing/decomposition of specific classes + // e.g. TypedParam has left, top, right, bottom, etc... + template, + bool HAS_MULTIPLE_VALUES = false, + bool VALUE_IS_BLOCK = IsBlock >::value> + class TypedParam + : public Param, + public ParamValue + { + public: + typedef TypedParam self_t; + typedef ParamValue param_value_t; + typedef typename param_value_t::value_assignment_t value_assignment_t; + typedef NAME_VALUE_LOOKUP name_value_lookup_t; + + using param_value_t::operator(); + + TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) + : Param(block_descriptor.mCurrentBlockPtr) + { + if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) + { + ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( + block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), + &mergeWith, + &deserializeParam, + &serializeParam, + validate_func, + &inspectParam, + min_count, max_count)); + BaseBlock::addParam(block_descriptor, param_descriptor, name); + } + + setValue(value); + } + + bool isProvided() const { return Param::anyProvided(); } + + static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) + { + self_t& typed_param = static_cast(param); + // no further names in stack, attempt to parse value now + if (name_stack_range.first == name_stack_range.second) + { + if (parser.readValue(typed_param.getValue())) + { + typed_param.clearValueName(); + typed_param.setProvided(); + return true; + } + + // try to parse a known named value + if(name_value_lookup_t::valueNamesExist()) + { + // try to parse a known named value + std::string name; + if (parser.readValue(name)) + { + // try to parse a per type named value + if (name_value_lookup_t::getValueFromName(name, typed_param.getValue())) + { + typed_param.setValueName(name); + typed_param.setProvided(); + return true; + } + + } + } + } + return false; + } + + static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) + { + const self_t& typed_param = static_cast(param); + if (!typed_param.isProvided()) return; + + if (!name_stack.empty()) + { + name_stack.back().second = true; + } + + std::string key = typed_param.getValueName(); + + // first try to write out name of name/value pair + + if (!key.empty()) + { + if (!diff_param || !ParamCompare::equals(static_cast(diff_param)->getValueName(), key)) + { + parser.writeValue(key, name_stack); + } + } + // then try to serialize value directly + else if (!diff_param || !ParamCompare::equals(typed_param.getValue(), static_cast(diff_param)->getValue())) + { + if (!parser.writeValue(typed_param.getValue(), name_stack)) + { + std::string calculated_key = typed_param.calcValueName(typed_param.getValue()); + if (!diff_param || !ParamCompare::equals(static_cast(diff_param)->getValueName(), calculated_key)) + { + parser.writeValue(calculated_key, name_stack); + } + } + } + } + + static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) + { + // tell parser about our actual type + parser.inspectValue(name_stack, min_count, max_count, NULL); + // then tell it about string-based alternatives ("red", "blue", etc. for LLColor4) + if (name_value_lookup_t::getPossibleValues()) + { + parser.inspectValue(name_stack, min_count, max_count, name_value_lookup_t::getPossibleValues()); + } + } + + void set(value_assignment_t val, bool flag_as_provided = true) + { + param_value_t::clearValueName(); + setValue(val); + setProvided(flag_as_provided); + } + + self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) + { + return static_cast(param_value_t::operator =(name)); + } + + protected: + + self_t& operator =(const self_t& other) + { + param_value_t::operator =(other); + Param::operator =(other); + return *this; + } + + static bool mergeWith(Param& dst, const Param& src, bool overwrite) + { + const self_t& src_typed_param = static_cast(src); + self_t& dst_typed_param = static_cast(dst); + + if (src_typed_param.isProvided() + && (overwrite || !dst_typed_param.isProvided())) + { + dst_typed_param.set(src_typed_param.getValue()); + return true; + } + return false; + } + }; + + // parameter that is a block + template + class TypedParam + : public Param, + public ParamValue + { + public: + typedef ParamValue param_value_t; + typedef typename param_value_t::value_assignment_t value_assignment_t; + typedef TypedParam self_t; + typedef NAME_VALUE_LOOKUP name_value_lookup_t; + + using param_value_t::operator(); + + TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) + : Param(block_descriptor.mCurrentBlockPtr), + param_value_t(value) + { + if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) + { + ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( + block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), + &mergeWith, + &deserializeParam, + &serializeParam, + validate_func, + &inspectParam, + min_count, max_count)); + BaseBlock::addParam(block_descriptor, param_descriptor, name); + } + } + + static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) + { + self_t& typed_param = static_cast(param); + // attempt to parse block... + if(typed_param.deserializeBlock(parser, name_stack_range, new_name)) + { + typed_param.clearValueName(); + typed_param.setProvided(); + return true; + } + + if(name_value_lookup_t::valueNamesExist()) + { + // try to parse a known named value + std::string name; + if (parser.readValue(name)) + { + // try to parse a per type named value + if (name_value_lookup_t::getValueFromName(name, typed_param.getValue())) + { + typed_param.setValueName(name); + typed_param.setProvided(); + return true; + } + + } + } + return false; + } + + static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) + { + const self_t& typed_param = static_cast(param); + if (!typed_param.isProvided()) return; + + if (!name_stack.empty()) + { + name_stack.back().second = true; + } + + std::string key = typed_param.getValueName(); + if (!key.empty()) + { + if (!parser.writeValue(key, name_stack)) + { + return; + } + } + else + { + typed_param.serializeBlock(parser, name_stack, static_cast(diff_param)); + } + } + + static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) + { + // I am a param that is also a block, so just recurse into my contents + const self_t& typed_param = static_cast(param); + typed_param.inspectBlock(parser, name_stack, min_count, max_count); + } + + // a param-that-is-a-block is provided when the user has set one of its child params + // *and* the block as a whole validates + bool isProvided() const + { + // only validate block when it hasn't already passed validation with current data + if (Param::anyProvided() && !param_value_t::mValidated) + { + // a sub-block is "provided" when it has been filled in enough to be valid + param_value_t::mValidated = param_value_t::validateBlock(false); + } + return Param::anyProvided() && param_value_t::mValidated; + } + + // assign block contents to this param-that-is-a-block + void set(value_assignment_t val, bool flag_as_provided = true) + { + setValue(val); + param_value_t::clearValueName(); + // force revalidation of block + // next call to isProvided() will update provision status based on validity + param_value_t::mValidated = false; + setProvided(flag_as_provided); + } + + self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) + { + return static_cast(param_value_t::operator =(name)); + } + + // propagate changed status up to enclosing block + /*virtual*/ void paramChanged(const Param& changed_param, bool user_provided) + { + param_value_t::paramChanged(changed_param, user_provided); + if (user_provided) + { + // a child param has been explicitly changed + // so *some* aspect of this block is now provided + param_value_t::mValidated = false; + setProvided(); + param_value_t::clearValueName(); + } + else + { + Param::enclosingBlock().paramChanged(*this, user_provided); + } + } + + protected: + + self_t& operator =(const self_t& other) + { + param_value_t::operator =(other); + Param::operator =(other); + return *this; + } + + static bool mergeWith(Param& dst, const Param& src, bool overwrite) + { + const self_t& src_typed_param = static_cast(src); + self_t& dst_typed_param = static_cast(dst); + + if (src_typed_param.anyProvided()) + { + if (dst_typed_param.mergeBlockParam(src_typed_param.isProvided(), dst_typed_param.isProvided(), param_value_t::selfBlockDescriptor(), src_typed_param, overwrite)) + { + dst_typed_param.clearValueName(); + dst_typed_param.setProvided(true); + return true; + } + } + return false; + } + }; + + // container of non-block parameters + template + class TypedParam + : public Param + { + public: + typedef TypedParam self_t; + typedef ParamValue param_value_t; + typedef typename std::vector container_t; + typedef const container_t& value_assignment_t; + + typedef typename param_value_t::value_t value_t; + typedef NAME_VALUE_LOOKUP name_value_lookup_t; + + TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) + : Param(block_descriptor.mCurrentBlockPtr) + { + std::copy(value.begin(), value.end(), std::back_inserter(mValues)); + + if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) + { + ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( + block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), + &mergeWith, + &deserializeParam, + &serializeParam, + validate_func, + &inspectParam, + min_count, max_count)); + BaseBlock::addParam(block_descriptor, param_descriptor, name); + } + } + + bool isProvided() const { return Param::anyProvided(); } + + static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) + { + self_t& typed_param = static_cast(param); + value_t value; + // no further names in stack, attempt to parse value now + if (name_stack_range.first == name_stack_range.second) + { + // attempt to read value directly + if (parser.readValue(value)) + { + typed_param.add(value); + return true; + } + + // try to parse a known named value + if(name_value_lookup_t::valueNamesExist()) + { + // try to parse a known named value + std::string name; + if (parser.readValue(name)) + { + // try to parse a per type named value + if (name_value_lookup_t::getValueFromName(name, value)) + { + typed_param.add(value); + typed_param.mValues.back().setValueName(name); + return true; + } + + } + } + } + return false; + } + + static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) + { + const self_t& typed_param = static_cast(param); + if (!typed_param.isProvided() || name_stack.empty()) return; + + for (const_iterator it = typed_param.mValues.begin(), end_it = typed_param.mValues.end(); + it != end_it; + ++it) + { + std::string key = it->getValueName(); + name_stack.back().second = true; + + if(key.empty()) + // not parsed via name values, write out value directly + { + bool value_written = parser.writeValue(*it, name_stack); + if (!value_written) + { + std::string calculated_key = it->calcValueName(it->getValue()); + if (!parser.writeValue(calculated_key, name_stack)) + { + break; + } + } + } + else + { + if(!parser.writeValue(key, name_stack)) + { + break; + } + } + } + } + + static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) + { + parser.inspectValue(name_stack, min_count, max_count, NULL); + if (name_value_lookup_t::getPossibleValues()) + { + parser.inspectValue(name_stack, min_count, max_count, name_value_lookup_t::getPossibleValues()); + } + } + + void set(value_assignment_t val, bool flag_as_provided = true) + { + mValues = val; + setProvided(flag_as_provided); + } + + param_value_t& add() + { + mValues.push_back(param_value_t(value_t())); + Param::setProvided(); + return mValues.back(); + } + + void add(const value_t& item) + { + param_value_t param_value; + param_value.setValue(item); + mValues.push_back(param_value); + setProvided(); + } + + void add(const typename name_value_lookup_t::name_t& name) + { + value_t value; + + // try to parse a per type named value + if (name_value_lookup_t::getValueFromName(name, value)) + { + add(value); + mValues.back().setValueName(name); + } + } + + // implicit conversion + operator value_assignment_t() const { return mValues; } + // explicit conversion + value_assignment_t operator()() const { return mValues; } + + typedef typename container_t::iterator iterator; + typedef typename container_t::const_iterator const_iterator; + iterator begin() { return mValues.begin(); } + iterator end() { return mValues.end(); } + const_iterator begin() const { return mValues.begin(); } + const_iterator end() const { return mValues.end(); } + bool empty() const { return mValues.empty(); } + size_t size() const { return mValues.size(); } + + U32 numValidElements() const + { + return mValues.size(); + } + + protected: + static bool mergeWith(Param& dst, const Param& src, bool overwrite) + { + const self_t& src_typed_param = static_cast(src); + self_t& dst_typed_param = static_cast(dst); + + if (overwrite) + { + std::copy(src_typed_param.begin(), src_typed_param.end(), std::back_inserter(dst_typed_param.mValues)); + } + else + { + container_t new_values(src_typed_param.mValues); + std::copy(dst_typed_param.begin(), dst_typed_param.end(), std::back_inserter(new_values)); + std::swap(dst_typed_param.mValues, new_values); + } + + if (src_typed_param.begin() != src_typed_param.end()) + { + dst_typed_param.setProvided(); + } + return true; + } + + container_t mValues; + }; + + // container of block parameters + template + class TypedParam + : public Param + { + public: + typedef TypedParam self_t; + typedef ParamValue param_value_t; + typedef typename std::vector container_t; + typedef const container_t& value_assignment_t; + typedef typename param_value_t::value_t value_t; + typedef NAME_VALUE_LOOKUP name_value_lookup_t; + + TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) + : Param(block_descriptor.mCurrentBlockPtr) + { + std::copy(value.begin(), value.end(), back_inserter(mValues)); + + if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) + { + ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( + block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), + &mergeWith, + &deserializeParam, + &serializeParam, + validate_func, + &inspectParam, + min_count, max_count)); + BaseBlock::addParam(block_descriptor, param_descriptor, name); + } + } + + bool isProvided() const { return Param::anyProvided(); } + + static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) + { + self_t& typed_param = static_cast(param); + bool new_value = false; + + if (new_name || typed_param.mValues.empty()) + { + new_value = true; + typed_param.mValues.push_back(value_t()); + } + + param_value_t& value = typed_param.mValues.back(); + + // attempt to parse block... + if(value.deserializeBlock(parser, name_stack_range, new_name)) + { + typed_param.setProvided(); + return true; + } + else if(name_value_lookup_t::valueNamesExist()) + { + // try to parse a known named value + std::string name; + if (parser.readValue(name)) + { + // try to parse a per type named value + if (name_value_lookup_t::getValueFromName(name, value.getValue())) + { + typed_param.mValues.back().setValueName(name); + typed_param.setProvided(); + return true; + } + + } + } + + if (new_value) + { // failed to parse new value, pop it off + typed_param.mValues.pop_back(); + } + + return false; + } + + static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) + { + const self_t& typed_param = static_cast(param); + if (!typed_param.isProvided() || name_stack.empty()) return; + + for (const_iterator it = typed_param.mValues.begin(), end_it = typed_param.mValues.end(); + it != end_it; + ++it) + { + name_stack.back().second = true; + + std::string key = it->getValueName(); + if (!key.empty()) + { + parser.writeValue(key, name_stack); + } + // Not parsed via named values, write out value directly + // NOTE: currently we don't worry about removing default values in Multiple + else + { + it->serializeBlock(parser, name_stack, NULL); + } + } + } + + static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) + { + // I am a vector of blocks, so describe my contents recursively + param_value_t(value_t()).inspectBlock(parser, name_stack, min_count, max_count); + } + + void set(value_assignment_t val, bool flag_as_provided = true) + { + mValues = val; + setProvided(flag_as_provided); + } + + param_value_t& add() + { + mValues.push_back(value_t()); + setProvided(); + return mValues.back(); + } + + void add(const value_t& item) + { + mValues.push_back(item); + setProvided(); + } + + void add(const typename name_value_lookup_t::name_t& name) + { + value_t value; + + // try to parse a per type named value + if (name_value_lookup_t::getValueFromName(name, value)) + { + add(value); + mValues.back().setValueName(name); + } + } + + // implicit conversion + operator value_assignment_t() const { return mValues; } + // explicit conversion + value_assignment_t operator()() const { return mValues; } + + typedef typename container_t::iterator iterator; + typedef typename container_t::const_iterator const_iterator; + iterator begin() { return mValues.begin(); } + iterator end() { return mValues.end(); } + const_iterator begin() const { return mValues.begin(); } + const_iterator end() const { return mValues.end(); } + bool empty() const { return mValues.empty(); } + size_t size() const { return mValues.size(); } + + U32 numValidElements() const + { + U32 count = 0; + for (const_iterator it = mValues.begin(), end_it = mValues.end(); + it != end_it; + ++it) + { + if(it->validateBlock(false)) count++; + } + return count; + } + + protected: + + static bool mergeWith(Param& dst, const Param& src, bool overwrite) + { + const self_t& src_typed_param = static_cast(src); + self_t& dst_typed_param = static_cast(dst); + + if (overwrite) + { + std::copy(src_typed_param.begin(), src_typed_param.end(), std::back_inserter(dst_typed_param.mValues)); + } + else + { + container_t new_values(src_typed_param.mValues); + std::copy(dst_typed_param.begin(), dst_typed_param.end(), std::back_inserter(new_values)); + std::swap(dst_typed_param.mValues, new_values); + } + + if (src_typed_param.begin() != src_typed_param.end()) + { + dst_typed_param.setProvided(); + } + + return true; + } + + container_t mValues; + }; + + template + class ChoiceBlock : public BASE_BLOCK + { + typedef ChoiceBlock self_t; + typedef ChoiceBlock enclosing_block_t; + typedef BASE_BLOCK base_block_t; + + LOG_CLASS(self_t); + public: + // take all provided params from other and apply to self + bool overwriteFrom(const self_t& other) + { + return static_cast(this)->mergeBlock(selfBlockDescriptor(), other, true); + } + + // take all provided params that are not already provided, and apply to self + bool fillFrom(const self_t& other) + { + return static_cast(this)->mergeBlock(selfBlockDescriptor(), other, false); + } + + bool mergeBlockParam(bool source_provided, bool dest_provided, BlockDescriptor& block_data, const self_t& source, bool overwrite) + { + bool source_override = source_provided && (overwrite || !dest_provided); + + if (source_override || source.mCurChoice == mCurChoice) + { + return mergeBlock(block_data, source, overwrite); + } + return false; + } + + // merge with other block + bool mergeBlock(BlockDescriptor& block_data, const self_t& other, bool overwrite) + { + mCurChoice = other.mCurChoice; + return base_block_t::mergeBlock(selfBlockDescriptor(), other, overwrite); + } + + // clear out old choice when param has changed + /*virtual*/ void paramChanged(const Param& changed_param, bool user_provided) + { + param_handle_t changed_param_handle = base_block_t::getHandleFromParam(&changed_param); + // if we have a new choice... + if (changed_param_handle != mCurChoice) + { + // clear provided flag on previous choice + Param* previous_choice = base_block_t::getParamFromHandle(mCurChoice); + if (previous_choice) + { + previous_choice->setProvided(false); + } + mCurChoice = changed_param_handle; + } + base_block_t::paramChanged(changed_param, user_provided); + } + + virtual const BlockDescriptor& mostDerivedBlockDescriptor() const { return selfBlockDescriptor(); } + virtual BlockDescriptor& mostDerivedBlockDescriptor() { return selfBlockDescriptor(); } + + protected: + ChoiceBlock() + : mCurChoice(0) + { + BaseBlock::init(selfBlockDescriptor(), base_block_t::selfBlockDescriptor(), sizeof(DERIVED_BLOCK)); + } + + // Alternatives are mutually exclusive wrt other Alternatives in the same block. + // One alternative in a block will always have isChosen() == true. + // At most one alternative in a block will have isProvided() == true. + template > + class Alternative : public TypedParam + { + public: + friend class ChoiceBlock; + + typedef Alternative self_t; + typedef TypedParam >::value> super_t; + typedef typename super_t::value_assignment_t value_assignment_t; + + using super_t::operator =; + + explicit Alternative(const char* name = "", value_assignment_t val = defaultValue()) + : super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, val, NULL, 0, 1), + mOriginalValue(val) + { + // assign initial choice to first declared option + DERIVED_BLOCK* blockp = ((DERIVED_BLOCK*)DERIVED_BLOCK::selfBlockDescriptor().mCurrentBlockPtr); + if (LL_UNLIKELY(DERIVED_BLOCK::selfBlockDescriptor().mInitializationState == BlockDescriptor::INITIALIZING)) + { + if(blockp->mCurChoice == 0) + { + blockp->mCurChoice = Param::enclosingBlock().getHandleFromParam(this); + } + } + } + + void choose() + { + static_cast(Param::enclosingBlock()).paramChanged(*this, true); + } + + void chooseAs(value_assignment_t val) + { + super_t::set(val); + } + + void operator =(value_assignment_t val) + { + super_t::set(val); + } + + void operator()(typename super_t::value_assignment_t val) + { + super_t::set(val); + } + + operator value_assignment_t() const + { + return (*this)(); + } + + value_assignment_t operator()() const + { + if (static_cast(Param::enclosingBlock()).getCurrentChoice() == this) + { + return super_t::getValue(); + } + return mOriginalValue; + } + + bool isChosen() const + { + return static_cast(Param::enclosingBlock()).getCurrentChoice() == this; + } + + private: + T mOriginalValue; + }; + + protected: + static BlockDescriptor& selfBlockDescriptor() + { + static BlockDescriptor sBlockDescriptor; + return sBlockDescriptor; + } + + private: + param_handle_t mCurChoice; + + const Param* getCurrentChoice() const + { + return base_block_t::getParamFromHandle(mCurChoice); + } + }; + + template + class Block + : public BASE_BLOCK + { + typedef Block self_t; + typedef Block block_t; + + public: + typedef BASE_BLOCK base_block_t; + + // take all provided params from other and apply to self + bool overwriteFrom(const self_t& other) + { + return static_cast(this)->mergeBlock(selfBlockDescriptor(), other, true); + } + + // take all provided params that are not already provided, and apply to self + bool fillFrom(const self_t& other) + { + return static_cast(this)->mergeBlock(selfBlockDescriptor(), other, false); + } + + virtual const BlockDescriptor& mostDerivedBlockDescriptor() const { return selfBlockDescriptor(); } + virtual BlockDescriptor& mostDerivedBlockDescriptor() { return selfBlockDescriptor(); } + + protected: + Block() + { + //#pragma message("Parsing LLInitParam::Block") + BaseBlock::init(selfBlockDescriptor(), BASE_BLOCK::selfBlockDescriptor(), sizeof(DERIVED_BLOCK)); + } + + // + // Nested classes for declaring parameters + // + template > + class Optional : public TypedParam + { + public: + typedef TypedParam >::value> super_t; + typedef typename super_t::value_assignment_t value_assignment_t; + + using super_t::operator(); + using super_t::operator =; + + explicit Optional(const char* name = "", value_assignment_t val = defaultValue()) + : super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, val, NULL, 0, 1) + { + //#pragma message("Parsing LLInitParam::Block::Optional") + } + + Optional& operator =(value_assignment_t val) + { + set(val); + return *this; + } + + DERIVED_BLOCK& operator()(value_assignment_t val) + { + super_t::set(val); + return static_cast(Param::enclosingBlock()); + } + }; + + template > + class Mandatory : public TypedParam + { + public: + typedef TypedParam >::value> super_t; + typedef Mandatory self_t; + typedef typename super_t::value_assignment_t value_assignment_t; + + using super_t::operator(); + using super_t::operator =; + + // mandatory parameters require a name to be parseable + explicit Mandatory(const char* name = "", value_assignment_t val = defaultValue()) + : super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, val, &validate, 1, 1) + {} + + Mandatory& operator =(value_assignment_t val) + { + set(val); + return *this; + } + + DERIVED_BLOCK& operator()(typename super_t::value_assignment_t val) + { + super_t::set(val); + return static_cast(Param::enclosingBlock()); + } + + static bool validate(const Param* p) + { + // valid only if provided + return static_cast(p)->isProvided(); + } + + }; + + template > + class Multiple : public TypedParam + { + public: + typedef TypedParam >::value> super_t; + typedef Multiple self_t; + typedef typename super_t::container_t container_t; + typedef typename super_t::value_assignment_t value_assignment_t; + typedef typename super_t::iterator iterator; + typedef typename super_t::const_iterator const_iterator; + + explicit Multiple(const char* name = "") + : super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, container_t(), &validate, RANGE::minCount, RANGE::maxCount) + {} + + Multiple& operator =(value_assignment_t val) + { + set(val); + return *this; + } + + DERIVED_BLOCK& operator()(typename super_t::value_assignment_t val) + { + super_t::set(val); + return static_cast(Param::enclosingBlock()); + } + + static bool validate(const Param* paramp) + { + U32 num_valid = ((super_t*)paramp)->numValidElements(); + return RANGE::minCount <= num_valid && num_valid <= RANGE::maxCount; + } + }; + + class Deprecated : public Param + { + public: + explicit Deprecated(const char* name) + : Param(DERIVED_BLOCK::selfBlockDescriptor().mCurrentBlockPtr) + { + BlockDescriptor& block_descriptor = DERIVED_BLOCK::selfBlockDescriptor(); + if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) + { + ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( + block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), + NULL, + &deserializeParam, + NULL, + NULL, + NULL, + 0, S32_MAX)); + BaseBlock::addParam(block_descriptor, param_descriptor, name); + } + } + + static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) + { + if (name_stack_range.first == name_stack_range.second) + { + //std::string message = llformat("Deprecated value %s ignored", getName().c_str()); + //parser.parserWarning(message); + return true; + } + + return false; + } + }; + + // different semantics for documentation purposes, but functionally identical + typedef Deprecated Ignored; + + protected: + static BlockDescriptor& selfBlockDescriptor() + { + static BlockDescriptor sBlockDescriptor; + return sBlockDescriptor; + } + + template + void changeDefault(TypedParam& param, + typename TypedParam::value_assignment_t value) + { + if (!param.isProvided()) + { + param.set(value, false); + } + } + + }; + + template + class BatchBlock + : public Block + { + public: + typedef BatchBlock self_t; + typedef Block super_t; + + BatchBlock() + {} + + bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name) + { + if (new_name) + { + // reset block + *static_cast(this) = defaultBatchValue(); + } + return super_t::deserializeBlock(p, name_stack_range, new_name); + } + + bool mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) + { + if (overwrite) + { + *static_cast(this) = defaultBatchValue(); + // merge individual parameters into destination + return super_t::mergeBlock(super_t::selfBlockDescriptor(), other, overwrite); + } + return false; + } + protected: + static const DERIVED_BLOCK& defaultBatchValue() + { + static DERIVED_BLOCK default_value; + return default_value; + } + }; + + // FIXME: this specialization is not currently used, as it only matches against the BatchBlock base class + // and not the derived class with the actual params + template + class ParamValue , + NAME_VALUE_LOOKUP, + true> + : public NAME_VALUE_LOOKUP, + protected BatchBlock + { + public: + typedef BatchBlock block_t; + typedef const BatchBlock& value_assignment_t; + typedef block_t value_t; + + ParamValue() + : block_t(), + mValidated(false) + {} + + ParamValue(value_assignment_t other) + : block_t(other), + mValidated(false) + { + } + + void setValue(value_assignment_t val) + { + *this = val; + } + + value_assignment_t getValue() const + { + return *this; + } + + BatchBlock& getValue() + { + return *this; + } + + operator value_assignment_t() const + { + return *this; + } + + value_assignment_t operator()() const + { + return *this; + } + + protected: + mutable bool mValidated; // lazy validation flag + }; + + template + class ParamValue , + TypeValues, + IS_BLOCK> + : public IsBlock::base_class_t + { + public: + typedef ParamValue , TypeValues, false> self_t; + typedef const T& value_assignment_t; + typedef T value_t; + + ParamValue() + : mValue(), + mValidated(false) + {} + + ParamValue(value_assignment_t other) + : mValue(other), + mValidated(false) + {} + + void setValue(value_assignment_t val) + { + mValue.set(val); + } + + value_assignment_t getValue() const + { + return mValue.get(); + } + + T& getValue() + { + return mValue.get(); + } + + operator value_assignment_t() const + { + return mValue.get(); + } + + value_assignment_t operator()() const + { + return mValue.get(); + } + + bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name) + { + return mValue.get().deserializeBlock(p, name_stack_range, new_name); + } + + void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const + { + if (mValue.empty()) return; + + mValue.get().serializeBlock(p, name_stack, diff_block); + } + + bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const + { + if (mValue.empty()) return false; + + return mValue.get().inspectBlock(p, name_stack, min_count, max_count); + } + + protected: + mutable bool mValidated; // lazy validation flag + + private: + BaseBlock::Lazy mValue; + }; + + template <> + class ParamValue , + false> + : public TypeValues, + public BaseBlock + { + public: + typedef ParamValue, false> self_t; + typedef const LLSD& value_assignment_t; + + ParamValue() + : mValidated(false) + {} + + ParamValue(value_assignment_t other) + : mValue(other), + mValidated(false) + {} + + void setValue(value_assignment_t val) { mValue = val; } + + value_assignment_t getValue() const { return mValue; } + LLSD& getValue() { return mValue; } + + operator value_assignment_t() const { return mValue; } + value_assignment_t operator()() const { return mValue; } + + + // block param interface + bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); + void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const; + bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const + { + //TODO: implement LLSD params as schema type Any + return true; + } + + protected: + mutable bool mValidated; // lazy validation flag + + private: + static void serializeElement(Parser& p, const LLSD& sd, Parser::name_stack_t& name_stack); + + LLSD mValue; + }; + + template + class CustomParamValue + : public Block > >, + public TypeValues + { + public: + typedef enum e_value_age + { + VALUE_NEEDS_UPDATE, // mValue needs to be refreshed from the block parameters + VALUE_AUTHORITATIVE, // mValue holds the authoritative value (which has been replicated to the block parameters via updateBlockFromValue) + BLOCK_AUTHORITATIVE // mValue is derived from the block parameters, which are authoritative + } EValueAge; + + typedef ParamValue > derived_t; + typedef CustomParamValue self_t; + typedef Block block_t; + typedef const T& value_assignment_t; + typedef T value_t; + + + CustomParamValue(const T& value = T()) + : mValue(value), + mValueAge(VALUE_AUTHORITATIVE), + mValidated(false) + {} + + bool deserializeBlock(Parser& parser, Parser::name_stack_range_t name_stack_range, bool new_name) + { + derived_t& typed_param = static_cast(*this); + // try to parse direct value T + if (name_stack_range.first == name_stack_range.second) + { + if(parser.readValue(typed_param.mValue)) + { + typed_param.mValueAge = VALUE_AUTHORITATIVE; + typed_param.updateBlockFromValue(false); + + typed_param.clearValueName(); + + return true; + } + } + + // fall back on parsing block components for T + return typed_param.BaseBlock::deserializeBlock(parser, name_stack_range, new_name); + } + + void serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const + { + const derived_t& typed_param = static_cast(*this); + const derived_t* diff_param = static_cast(diff_block); + + std::string key = typed_param.getValueName(); + + // first try to write out name of name/value pair + if (!key.empty()) + { + if (!diff_param || !ParamCompare::equals(diff_param->getValueName(), key)) + { + parser.writeValue(key, name_stack); + } + } + // then try to serialize value directly + else if (!diff_param || !ParamCompare::equals(typed_param.getValue(), diff_param->getValue())) + { + + if (!parser.writeValue(typed_param.getValue(), name_stack)) + { + //RN: *always* serialize provided components of BlockValue (don't pass diff_param on), + // since these tend to be viewed as the constructor arguments for the value T. It seems + // cleaner to treat the uniqueness of a BlockValue according to the generated value, and + // not the individual components. This way will not + // be exported as , since it was probably the intent of the user to + // be specific about the RGB color values. This also fixes an issue where we distinguish + // between rect.left not being provided and rect.left being explicitly set to 0 (same as default) + + if (typed_param.mValueAge == VALUE_AUTHORITATIVE) + { + // if the value is authoritative but the parser doesn't accept the value type + // go ahead and make a copy, and splat the value out to its component params + // and serialize those params + derived_t copy(typed_param); + copy.updateBlockFromValue(true); + copy.block_t::serializeBlock(parser, name_stack, NULL); + } + else + { + block_t::serializeBlock(parser, name_stack, NULL); + } + } + } + } + + bool inspectBlock(Parser& parser, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const + { + // first, inspect with actual type... + parser.inspectValue(name_stack, min_count, max_count, NULL); + if (TypeValues::getPossibleValues()) + { + //...then inspect with possible string values... + parser.inspectValue(name_stack, min_count, max_count, TypeValues::getPossibleValues()); + } + // then recursively inspect contents... + return block_t::inspectBlock(parser, name_stack, min_count, max_count); + } + + bool validateBlock(bool emit_errors = true) const + { + if (mValueAge == VALUE_NEEDS_UPDATE) + { + if (block_t::validateBlock(emit_errors)) + { + // clear stale keyword associated with old value + TypeValues::clearValueName(); + mValueAge = BLOCK_AUTHORITATIVE; + static_cast(const_cast(this))->updateValueFromBlock(); + return true; + } + else + { + //block value incomplete, so not considered provided + // will attempt to revalidate on next call to isProvided() + return false; + } + } + else + { + // we have a valid value in hand + return true; + } + } + + // propagate change status up to enclosing block + /*virtual*/ void paramChanged(const Param& changed_param, bool user_provided) + { + BaseBlock::paramChanged(changed_param, user_provided); + if (user_provided) + { + // a parameter changed, so our value is out of date + mValueAge = VALUE_NEEDS_UPDATE; + } + } + + void setValue(value_assignment_t val) + { + derived_t& typed_param = static_cast(*this); + // set param version number to be up to date, so we ignore block contents + mValueAge = VALUE_AUTHORITATIVE; + mValue = val; + typed_param.clearValueName(); + static_cast(this)->updateBlockFromValue(false); + } + + value_assignment_t getValue() const + { + validateBlock(true); + return mValue; + } + + T& getValue() + { + validateBlock(true); + return mValue; + } + + operator value_assignment_t() const + { + return getValue(); + } + + value_assignment_t operator()() const + { + return getValue(); + } + + protected: + + // use this from within updateValueFromBlock() to set the value without making it authoritative + void updateValue(value_assignment_t value) + { + mValue = value; + } + + bool mergeBlockParam(bool source_provided, bool dst_provided, BlockDescriptor& block_data, const BaseBlock& source, bool overwrite) + { + bool source_override = source_provided && (overwrite || !dst_provided); + + const derived_t& src_typed_param = static_cast(source); + + if (source_override && src_typed_param.mValueAge == VALUE_AUTHORITATIVE) + { + // copy value over + setValue(src_typed_param.getValue()); + return true; + } + // merge individual parameters into destination + if (mValueAge == VALUE_AUTHORITATIVE) + { + static_cast(this)->updateBlockFromValue(dst_provided); + } + return mergeBlock(block_data, source, overwrite); + } + + bool mergeBlock(BlockDescriptor& block_data, const BaseBlock& source, bool overwrite) + { + return block_t::mergeBlock(block_data, source, overwrite); + } + + mutable bool mValidated; // lazy validation flag + + private: + mutable T mValue; + mutable EValueAge mValueAge; + }; +} + + +#endif // LL_LLPARAM_H diff --git a/indra/llcommon/llregistry.h b/indra/llcommon/llregistry.h new file mode 100644 index 0000000000..36ce6a97b7 --- /dev/null +++ b/indra/llcommon/llregistry.h @@ -0,0 +1,351 @@ +/** + * @file llregistry.h + * @brief template classes for registering name, value pairs in nested scopes, statically, etc. + * + * $LicenseInfo:firstyear=2001&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_LLREGISTRY_H +#define LL_LLREGISTRY_H + +#include + +#include +#include "llsingleton.h" + +template +class LLRegistryDefaultComparator +{ + bool operator()(const T& lhs, const T& rhs) { return lhs < rhs; } +}; + +template > +class LLRegistry +{ +public: + typedef LLRegistry registry_t; + typedef typename boost::add_reference::type>::type ref_const_key_t; + typedef typename boost::add_reference::type>::type ref_const_value_t; + typedef typename boost::add_reference::type ref_value_t; + typedef typename boost::add_pointer::type>::type ptr_const_value_t; + typedef typename boost::add_pointer::type ptr_value_t; + + class Registrar + { + friend class LLRegistry; + public: + typedef typename std::map registry_map_t; + + bool add(ref_const_key_t key, ref_const_value_t value) + { + if (mMap.insert(std::make_pair(key, value)).second == false) + { + llwarns << "Tried to register " << key << " but it was already registered!" << llendl; + return false; + } + return true; + } + + void remove(ref_const_key_t key) + { + mMap.erase(key); + } + + void replace(ref_const_key_t key, ref_const_value_t value) + { + mMap[key] = value; + } + + typename registry_map_t::const_iterator beginItems() const + { + return mMap.begin(); + } + + typename registry_map_t::const_iterator endItems() const + { + return mMap.end(); + } + + protected: + ptr_value_t getValue(ref_const_key_t key) + { + typename registry_map_t::iterator found_it = mMap.find(key); + if (found_it != mMap.end()) + { + return &(found_it->second); + } + return NULL; + } + + ptr_const_value_t getValue(ref_const_key_t key) const + { + typename registry_map_t::const_iterator found_it = mMap.find(key); + if (found_it != mMap.end()) + { + return &(found_it->second); + } + return NULL; + } + + // if the registry is used to store pointers, and null values are valid entries + // then use this function to check the existence of an entry + bool exists(ref_const_key_t key) const + { + return mMap.find(key) != mMap.end(); + } + + bool empty() const + { + return mMap.empty(); + } + + protected: + // use currentRegistrar() or defaultRegistrar() + Registrar() {} + ~Registrar() {} + + private: + registry_map_t mMap; + }; + + typedef typename std::list scope_list_t; + typedef typename std::list::iterator scope_list_iterator_t; + typedef typename std::list::const_iterator scope_list_const_iterator_t; + + LLRegistry() + {} + + ~LLRegistry() {} + + ptr_value_t getValue(ref_const_key_t key) + { + for(scope_list_iterator_t it = mActiveScopes.begin(); + it != mActiveScopes.end(); + ++it) + { + ptr_value_t valuep = (*it)->getValue(key); + if (valuep != NULL) return valuep; + } + return mDefaultRegistrar.getValue(key); + } + + ptr_const_value_t getValue(ref_const_key_t key) const + { + for(scope_list_const_iterator_t it = mActiveScopes.begin(); + it != mActiveScopes.end(); + ++it) + { + ptr_value_t valuep = (*it)->getValue(key); + if (valuep != NULL) return valuep; + } + return mDefaultRegistrar.getValue(key); + } + + bool exists(ref_const_key_t key) const + { + for(scope_list_const_iterator_t it = mActiveScopes.begin(); + it != mActiveScopes.end(); + ++it) + { + if ((*it)->exists(key)) return true; + } + + return mDefaultRegistrar.exists(key); + } + + bool empty() const + { + for(scope_list_const_iterator_t it = mActiveScopes.begin(); + it != mActiveScopes.end(); + ++it) + { + if (!(*it)->empty()) return false; + } + + return mDefaultRegistrar.empty(); + } + + + Registrar& defaultRegistrar() + { + return mDefaultRegistrar; + } + + const Registrar& defaultRegistrar() const + { + return mDefaultRegistrar; + } + + + Registrar& currentRegistrar() + { + if (!mActiveScopes.empty()) + { + return *mActiveScopes.front(); + } + + return mDefaultRegistrar; + } + + const Registrar& currentRegistrar() const + { + if (!mActiveScopes.empty()) + { + return *mActiveScopes.front(); + } + + return mDefaultRegistrar; + } + + +protected: + void addScope(Registrar* scope) + { + // newer scopes go up front + mActiveScopes.insert(mActiveScopes.begin(), scope); + } + + void removeScope(Registrar* scope) + { + // O(N) but should be near the beggining and N should be small and this is safer than storing iterators + scope_list_iterator_t iter = std::find(mActiveScopes.begin(), mActiveScopes.end(), scope); + if (iter != mActiveScopes.end()) + { + mActiveScopes.erase(iter); + } + } + +private: + scope_list_t mActiveScopes; + Registrar mDefaultRegistrar; +}; + +template > +class LLRegistrySingleton + : public LLRegistry, + public LLSingleton +{ + friend class LLSingleton; +public: + typedef LLRegistry registry_t; + typedef const KEY& ref_const_key_t; + typedef const VALUE& ref_const_value_t; + typedef VALUE* ptr_value_t; + typedef const VALUE* ptr_const_value_t; + typedef LLSingleton singleton_t; + + class ScopedRegistrar : public registry_t::Registrar + { + public: + ScopedRegistrar(bool push_scope = true) + { + if (push_scope) + { + pushScope(); + } + } + + ~ScopedRegistrar() + { + if (!singleton_t::destroyed()) + { + popScope(); + } + } + + void pushScope() + { + singleton_t::instance().addScope(this); + } + + void popScope() + { + singleton_t::instance().removeScope(this); + } + + ptr_value_t getValueFromScope(ref_const_key_t key) + { + return getValue(key); + } + + ptr_const_value_t getValueFromScope(ref_const_key_t key) const + { + return getValue(key); + } + + private: + typename std::list::iterator mListIt; + }; + + class StaticRegistrar : public registry_t::Registrar + { + public: + virtual ~StaticRegistrar() {} + StaticRegistrar(ref_const_key_t key, ref_const_value_t value) + { + singleton_t::instance().mStaticScope->add(key, value); + } + }; + + // convenience functions + typedef typename LLRegistry::Registrar& ref_registrar_t; + static ref_registrar_t currentRegistrar() + { + return singleton_t::instance().registry_t::currentRegistrar(); + } + + static ref_registrar_t defaultRegistrar() + { + return singleton_t::instance().registry_t::defaultRegistrar(); + } + + static ptr_value_t getValue(ref_const_key_t key) + { + return singleton_t::instance().registry_t::getValue(key); + } + +protected: + // DERIVED_TYPE needs to derive from LLRegistrySingleton + LLRegistrySingleton() + : mStaticScope(NULL) + {} + + virtual void initSingleton() + { + mStaticScope = new ScopedRegistrar(); + } + + virtual ~LLRegistrySingleton() + { + delete mStaticScope; + } + +private: + ScopedRegistrar* mStaticScope; +}; + +// helper macro for doing static registration +#define GLUED_TOKEN(x, y) x ## y +#define GLUE_TOKENS(x, y) GLUED_TOKEN(x, y) +#define LLREGISTER_STATIC(REGISTRY, KEY, VALUE) static REGISTRY::StaticRegistrar GLUE_TOKENS(reg, __LINE__)(KEY, VALUE); + +#endif diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 772f173f17..9226f36e73 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -12,7 +12,6 @@ include(LLRender) include(LLWindow) include(LLVFS) include(LLXML) -include(LLXUIXML) include_directories( ${LLCOMMON_INCLUDE_DIRS} @@ -24,7 +23,6 @@ include_directories( ${LLWINDOW_INCLUDE_DIRS} ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LLXUIXML_INCLUDE_DIRS} ) set(llui_SOURCE_FILES @@ -100,11 +98,13 @@ set(llui_SOURCE_FILES lltextutil.cpp lltextvalidate.cpp lltimectrl.cpp + lltrans.cpp lltransutil.cpp lltoggleablemenu.cpp lltoolbar.cpp lltooltip.cpp llui.cpp + lluicolor.cpp lluicolortable.cpp lluictrl.cpp lluictrlfactory.cpp @@ -121,6 +121,7 @@ set(llui_SOURCE_FILES llview.cpp llviewquery.cpp llwindowshade.cpp + llxuiparser.cpp ) set(llui_HEADER_FILES @@ -208,6 +209,7 @@ set(llui_HEADER_FILES lltoggleablemenu.h lltoolbar.h lltooltip.h + lltrans.h lltransutil.h lluicolortable.h lluiconstants.h @@ -215,6 +217,7 @@ set(llui_HEADER_FILES lluictrl.h lluifwd.h llui.h + lluicolor.h lluiimage.h lluistring.h llundo.h @@ -228,6 +231,7 @@ set(llui_HEADER_FILES llview.h llviewquery.h llwindowshade.h + llxuiparser.h ) set_source_files_properties(${llui_HEADER_FILES} diff --git a/indra/llui/lltrans.cpp b/indra/llui/lltrans.cpp new file mode 100644 index 0000000000..5388069c24 --- /dev/null +++ b/indra/llui/lltrans.cpp @@ -0,0 +1,295 @@ +/** + * @file lltrans.cpp + * @brief LLTrans implementation + * + * $LicenseInfo:firstyear=2000&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 "lltrans.h" + +#include "llfasttimer.h" // for call count statistics +#include "llxuiparser.h" +#include "llsd.h" +#include "llxmlnode.h" + +#include + +LLTrans::template_map_t LLTrans::sStringTemplates; +LLStringUtil::format_map_t LLTrans::sDefaultArgs; + +struct StringDef : public LLInitParam::Block +{ + Mandatory name; + Mandatory value; + + StringDef() + : name("name"), + value("value") + {} +}; + +struct StringTable : public LLInitParam::Block +{ + Multiple strings; + StringTable() + : strings("string") + {} +}; + +//static +bool LLTrans::parseStrings(LLXMLNodePtr &root, const std::set& default_args) +{ + std::string xml_filename = "(strings file)"; + if (!root->hasName("strings")) + { + llerrs << "Invalid root node name in " << xml_filename + << ": was " << root->getName() << ", expected \"strings\"" << llendl; + } + + StringTable string_table; + LLXUIParser parser; + parser.readXUI(root, string_table, xml_filename); + + if (!string_table.validateBlock()) + { + llerrs << "Problem reading strings: " << xml_filename << llendl; + return false; + } + + sStringTemplates.clear(); + sDefaultArgs.clear(); + + for(LLInitParam::ParamIterator::const_iterator it = string_table.strings.begin(); + it != string_table.strings.end(); + ++it) + { + LLTransTemplate xml_template(it->name, it->value); + sStringTemplates[xml_template.mName] = xml_template; + + std::set::const_iterator iter = default_args.find(xml_template.mName); + if (iter != default_args.end()) + { + std::string name = *iter; + if (name[0] != '[') + name = llformat("[%s]",name.c_str()); + sDefaultArgs[name] = xml_template.mText; + } + } + + return true; +} + + +//static +bool LLTrans::parseLanguageStrings(LLXMLNodePtr &root) +{ + std::string xml_filename = "(language strings file)"; + if (!root->hasName("strings")) + { + llerrs << "Invalid root node name in " << xml_filename + << ": was " << root->getName() << ", expected \"strings\"" << llendl; + } + + StringTable string_table; + LLXUIParser parser; + parser.readXUI(root, string_table, xml_filename); + + if (!string_table.validateBlock()) + { + llerrs << "Problem reading strings: " << xml_filename << llendl; + return false; + } + + for(LLInitParam::ParamIterator::const_iterator it = string_table.strings.begin(); + it != string_table.strings.end(); + ++it) + { + // share the same map with parseStrings() so we can search the strings using the same getString() function.- angela + LLTransTemplate xml_template(it->name, it->value); + sStringTemplates[xml_template.mName] = xml_template; + } + + return true; +} + + + +static LLFastTimer::DeclareTimer FTM_GET_TRANS("Translate string"); + +//static +std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args) +{ + // Don't care about time as much as call count. Make sure we're not + // calling LLTrans::getString() in an inner loop. JC + LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format_map_t args = sDefaultArgs; + args.insert(msg_args.begin(), msg_args.end()); + LLStringUtil::format(text, args); + + return text; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return "MissingString("+xml_desc+")"; + } +} + +//static +std::string LLTrans::getString(const std::string &xml_desc, const LLSD& msg_args) +{ + // Don't care about time as much as call count. Make sure we're not + // calling LLTrans::getString() in an inner loop. JC + LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format(text, msg_args); + return text; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return "MissingString("+xml_desc+")"; + } +} + +//static +bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args) +{ + LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format_map_t args = sDefaultArgs; + args.insert(msg_args.begin(), msg_args.end()); + LLStringUtil::format(text, args); + result = text; + return true; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return false; + } +} + +//static +bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLSD& msg_args) +{ + LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format(text, msg_args); + result = text; + return true; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return false; + } +} + +//static +std::string LLTrans::getCountString(const std::string& language, const std::string& xml_desc, S32 count) +{ + // Compute which string identifier to use + const char* form = ""; + if (language == "ru") // Russian + { + // From GNU ngettext() + // Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; + if (count % 10 == 1 + && count % 100 != 11) + { + // singular, "1 item" + form = "A"; + } + else if (count % 10 >= 2 + && count % 10 <= 4 + && (count % 100 < 10 || count % 100 >= 20) ) + { + // special case "2 items", "23 items", but not "13 items" + form = "B"; + } + else + { + // English-style plural, "5 items" + form = "C"; + } + } + else if (language == "fr" || language == "pt") // French, Brazilian Portuguese + { + // French and Portuguese treat zero as a singular "0 item" not "0 items" + if (count == 0 || count == 1) + { + form = "A"; + } + else + { + // English-style plural + form = "B"; + } + } + else // default + { + // languages like English with 2 forms, singular and plural + if (count == 1) + { + // "1 item" + form = "A"; + } + else + { + // "2 items", also use plural for "0 items" + form = "B"; + } + } + + // Translate that string + LLStringUtil::format_map_t args; + args["[COUNT]"] = llformat("%d", count); + + // Look up "AgeYearsB" or "AgeWeeksC" including the "form" + std::string key = llformat("%s%s", xml_desc.c_str(), form); + return getString(key, args); +} + +void LLTrans::setDefaultArg(const std::string& name, const std::string& value) +{ + sDefaultArgs[name] = value; +} diff --git a/indra/llui/lltrans.h b/indra/llui/lltrans.h new file mode 100644 index 0000000000..128b51d383 --- /dev/null +++ b/indra/llui/lltrans.h @@ -0,0 +1,133 @@ +/** + * @file lltrans.h + * @brief LLTrans definition + * + * $LicenseInfo:firstyear=2000&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_TRANS_H +#define LL_TRANS_H + +#include + +#include "llpointer.h" +#include "llstring.h" + +class LLXMLNode; + +class LLSD; + +/** + * @brief String template loaded from strings.xml + */ +class LLTransTemplate +{ +public: + LLTransTemplate(const std::string& name = LLStringUtil::null, const std::string& text = LLStringUtil::null) : mName(name), mText(text) {} + + std::string mName; + std::string mText; +}; + +/** + * @brief Localized strings class + * This class is used to retrieve translations of strings used to build larger ones, as well as + * strings with a general usage that don't belong to any specific floater. For example, + * "Owner:", "Retrieving..." used in the place of a not yet known name, etc. + */ +class LLTrans +{ +public: + LLTrans(); + + /** + * @brief Parses the xml root that holds the strings. Used once on startup +// *FIXME * @param xml_filename Filename to parse + * @param default_args Set of strings (expected to be in the file) to use as default replacement args, e.g. "SECOND_LIFE" + * @returns true if the file was parsed successfully, true if something went wrong + */ + static bool parseStrings(LLPointer & root, const std::set& default_args); + + static bool parseLanguageStrings(LLPointer & root); + + /** + * @brief Returns a translated string + * @param xml_desc String's description + * @param args A list of substrings to replace in the string + * @returns Translated string + */ + static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args); + static std::string getString(const std::string &xml_desc, const LLSD& args); + static bool findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& args); + static bool findString(std::string &result, const std::string &xml_desc, const LLSD& args); + + // Returns translated string with [COUNT] replaced with a number, following + // special per-language logic for plural nouns. For example, some languages + // may have different plurals for 0, 1, 2 and > 2. + // See "AgeWeeksA", "AgeWeeksB", etc. in strings.xml for examples. + static std::string getCountString(const std::string& language, const std::string& xml_desc, S32 count); + + /** + * @brief Returns a translated string + * @param xml_desc String's description + * @returns Translated string + */ + static std::string getString(const std::string &xml_desc) + { + LLStringUtil::format_map_t empty; + return getString(xml_desc, empty); + } + + static bool findString(std::string &result, const std::string &xml_desc) + { + LLStringUtil::format_map_t empty; + return findString(result, xml_desc, empty); + } + + static std::string getKeyboardString(const char* keystring) + { + std::string key_str(keystring); + std::string trans_str; + return findString(trans_str, key_str) ? trans_str : key_str; + } + + // get the default args + static const LLStringUtil::format_map_t& getDefaultArgs() + { + return sDefaultArgs; + } + + static void setDefaultArg(const std::string& name, const std::string& value); + + // insert default args into an arg list + static void getArgs(LLStringUtil::format_map_t& args) + { + args.insert(sDefaultArgs.begin(), sDefaultArgs.end()); + } + +private: + typedef std::map template_map_t; + static template_map_t sStringTemplates; + static LLStringUtil::format_map_t sDefaultArgs; +}; + +#endif diff --git a/indra/llui/lluicolor.cpp b/indra/llui/lluicolor.cpp new file mode 100644 index 0000000000..f9bb80f8c5 --- /dev/null +++ b/indra/llui/lluicolor.cpp @@ -0,0 +1,87 @@ +/** + * @file lluicolor.cpp + * @brief brief LLUIColor class implementation file + * + * $LicenseInfo:firstyear=2009&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 "lluicolor.h" + +LLUIColor::LLUIColor() + :mColorPtr(NULL) +{ +} + + +LLUIColor::LLUIColor(const LLColor4& color) +: mColor(color), + mColorPtr(NULL) +{ +} + +LLUIColor::LLUIColor(const LLUIColor* color) +: mColorPtr(color) +{ +} + +void LLUIColor::set(const LLColor4& color) +{ + mColor = color; + mColorPtr = NULL; +} + +void LLUIColor::set(const LLUIColor* color) +{ + mColorPtr = color; +} + +const LLColor4& LLUIColor::get() const +{ + return (mColorPtr == NULL ? mColor : mColorPtr->get()); +} + +LLUIColor::operator const LLColor4& () const +{ + return get(); +} + +const LLColor4& LLUIColor::operator()() const +{ + return get(); +} + +bool LLUIColor::isReference() const +{ + return mColorPtr != NULL; +} + +namespace LLInitParam +{ + // used to detect equivalence with default values on export + bool ParamCompare::equals(const LLUIColor &a, const LLUIColor &b) + { + // do not detect value equivalence, treat pointers to colors as distinct from color values + return (a.mColorPtr == NULL && b.mColorPtr == NULL ? a.mColor == b.mColor : a.mColorPtr == b.mColorPtr); + } +} diff --git a/indra/llui/lluicolor.h b/indra/llui/lluicolor.h new file mode 100644 index 0000000000..97ebea854a --- /dev/null +++ b/indra/llui/lluicolor.h @@ -0,0 +1,71 @@ +/** + * @file lluicolor.h + * @brief brief LLUIColor class header file + * + * $LicenseInfo:firstyear=2009&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_LLUICOLOR_H_ +#define LL_LLUICOLOR_H_ + +#include "v4color.h" + +namespace LLInitParam +{ + template + struct ParamCompare; +} + +class LLUIColor +{ +public: + LLUIColor(); + LLUIColor(const LLColor4& color); + LLUIColor(const LLUIColor* color); + + void set(const LLColor4& color); + void set(const LLUIColor* color); + + const LLColor4& get() const; + + operator const LLColor4& () const; + const LLColor4& operator()() const; + + bool isReference() const; + +private: + friend struct LLInitParam::ParamCompare; + + const LLUIColor* mColorPtr; + LLColor4 mColor; +}; + +namespace LLInitParam +{ + template<> + struct ParamCompare + { + static bool equals(const LLUIColor& a, const LLUIColor& b); + }; +} + +#endif diff --git a/indra/llui/llxuiparser.cpp b/indra/llui/llxuiparser.cpp new file mode 100644 index 0000000000..afc76024d1 --- /dev/null +++ b/indra/llui/llxuiparser.cpp @@ -0,0 +1,1756 @@ +/** + * @file llxuiparser.cpp + * @brief Utility functions for handling XUI structures in XML + * + * $LicenseInfo:firstyear=2003&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 "llxuiparser.h" + +#include "llxmlnode.h" + +#ifdef LL_STANDALONE +#include +#else +#include "expat/expat.h" +#endif + +#include +#include +//#include +#include + +#include "lluicolor.h" + +using namespace BOOST_SPIRIT_CLASSIC_NS; + +const S32 MAX_STRING_ATTRIBUTE_SIZE = 40; + +static LLInitParam::Parser::parser_read_func_map_t sXSDReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sXSDWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sXSDInspectFuncs; + +static LLInitParam::Parser::parser_read_func_map_t sSimpleXUIReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sSimpleXUIWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sSimpleXUIInspectFuncs; + +const char* NO_VALUE_MARKER = "no_value"; + +const S32 LINE_NUMBER_HERE = 0; + +struct MaxOccursValues : public LLInitParam::TypeValuesHelper +{ + static void declareValues() + { + declare("unbounded", U32_MAX); + } +}; + +struct Occurs : public LLInitParam::Block +{ + Optional minOccurs; + Optional maxOccurs; + + Occurs() + : minOccurs("minOccurs", 0), + maxOccurs("maxOccurs", U32_MAX) + + {} +}; + + +typedef enum +{ + USE_REQUIRED, + USE_OPTIONAL +} EUse; + +namespace LLInitParam +{ + template<> + struct TypeValues : public TypeValuesHelper + { + static void declareValues() + { + declare("required", USE_REQUIRED); + declare("optional", USE_OPTIONAL); + } + }; +} + +struct Element; +struct Group; +struct Choice; +struct Sequence; +struct Any; + +struct Attribute : public LLInitParam::Block +{ + Mandatory name; + Mandatory type; + Mandatory use; + + Attribute() + : name("name"), + type("type"), + use("use") + {} +}; + +struct Any : public LLInitParam::Block +{ + Optional _namespace; + + Any() + : _namespace("namespace") + {} +}; + +struct All : public LLInitParam::Block +{ + Multiple< Lazy > elements; + + All() + : elements("element") + { + maxOccurs = 1; + } +}; + +struct Choice : public LLInitParam::ChoiceBlock +{ + Alternative< Lazy > element; + Alternative< Lazy > group; + Alternative< Lazy > choice; + Alternative< Lazy > sequence; + Alternative< Lazy > any; + + Choice() + : element("element"), + group("group"), + choice("choice"), + sequence("sequence"), + any("any") + {} + +}; + +struct Sequence : public LLInitParam::ChoiceBlock +{ + Alternative< Lazy > element; + Alternative< Lazy > group; + Alternative< Lazy > choice; + Alternative< Lazy > sequence; + Alternative< Lazy > any; +}; + +struct GroupContents : public LLInitParam::ChoiceBlock +{ + Alternative all; + Alternative choice; + Alternative sequence; + + GroupContents() + : all("all"), + choice("choice"), + sequence("sequence") + {} +}; + +struct Group : public LLInitParam::Block +{ + Optional name, + ref; + + Group() + : name("name"), + ref("ref") + {} +}; + +struct Restriction : public LLInitParam::Block +{ +}; + +struct Extension : public LLInitParam::Block +{ +}; + +struct SimpleContent : public LLInitParam::ChoiceBlock +{ + Alternative restriction; + Alternative extension; + + SimpleContent() + : restriction("restriction"), + extension("extension") + {} +}; + +struct SimpleType : public LLInitParam::Block +{ + // TODO +}; + +struct ComplexContent : public LLInitParam::Block +{ + Optional mixed; + + ComplexContent() + : mixed("mixed", true) + {} +}; + +struct ComplexTypeContents : public LLInitParam::ChoiceBlock +{ + Alternative simple_content; + Alternative complex_content; + Alternative group; + Alternative all; + Alternative choice; + Alternative sequence; + + ComplexTypeContents() + : simple_content("simpleContent"), + complex_content("complexContent"), + group("group"), + all("all"), + choice("choice"), + sequence("sequence") + {} +}; + +struct ComplexType : public LLInitParam::Block +{ + Optional name; + Optional mixed; + + Multiple attribute; + Multiple< Lazy > elements; + + ComplexType() + : name("name"), + attribute("xs:attribute"), + elements("xs:element"), + mixed("mixed") + { + } +}; + +struct ElementContents : public LLInitParam::ChoiceBlock +{ + Alternative simpleType; + Alternative complexType; + + ElementContents() + : simpleType("simpleType"), + complexType("complexType") + {} +}; + +struct Element : public LLInitParam::Block +{ + Optional name, + ref, + type; + + Element() + : name("xs:name"), + ref("xs:ref"), + type("xs:type") + {} +}; + +struct Schema : public LLInitParam::Block +{ +private: + Mandatory targetNamespace, + xmlns, + xs; + +public: + Optional attributeFormDefault, + elementFormDefault; + + Mandatory root_element; + + void setNameSpace(const std::string& ns) {targetNamespace = ns; xmlns = ns;} + + Schema(const std::string& ns = LLStringUtil::null) + : attributeFormDefault("attributeFormDefault"), + elementFormDefault("elementFormDefault"), + xs("xmlns:xs"), + targetNamespace("targetNamespace"), + xmlns("xmlns"), + root_element("xs:element") + { + attributeFormDefault = "unqualified"; + elementFormDefault = "qualified"; + xs = "http://www.w3.org/2001/XMLSchema"; + if (!ns.empty()) + { + setNameSpace(ns); + }; + } + +}; + +// +// LLXSDWriter +// +LLXSDWriter::LLXSDWriter() +: Parser(sXSDReadFuncs, sXSDWriteFuncs, sXSDInspectFuncs) +{ + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:boolean", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedByte", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedByte", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedShort", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedShort", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedInt", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:integer", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:float", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:double", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); +} + +void LLXSDWriter::writeXSD(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace) +{ + Schema schema(xml_namespace); + + schema.root_element.name = type_name; + Choice& choice = schema.root_element.complexType.choice; + + choice.minOccurs = 0; + choice.maxOccurs = "unbounded"; + + mSchemaNode = node; + //node->setName("xs:schema"); + //node->createChild("attributeFormDefault", true)->setStringValue("unqualified"); + //node->createChild("elementFormDefault", true)->setStringValue("qualified"); + //node->createChild("targetNamespace", true)->setStringValue(xml_namespace); + //node->createChild("xmlns:xs", true)->setStringValue("http://www.w3.org/2001/XMLSchema"); + //node->createChild("xmlns", true)->setStringValue(xml_namespace); + + //node = node->createChild("xs:complexType", false); + //node->createChild("name", true)->setStringValue(type_name); + //node->createChild("mixed", true)->setStringValue("true"); + + //mAttributeNode = node; + //mElementNode = node->createChild("xs:choice", false); + //mElementNode->createChild("minOccurs", true)->setStringValue("0"); + //mElementNode->createChild("maxOccurs", true)->setStringValue("unbounded"); + block.inspectBlock(*this); + + // duplicate element choices + LLXMLNodeList children; + mElementNode->getChildren("xs:element", children, FALSE); + for (LLXMLNodeList::iterator child_it = children.begin(); child_it != children.end(); ++child_it) + { + LLXMLNodePtr child_copy = child_it->second->deepCopy(); + std::string child_name; + child_copy->getAttributeString("name", child_name); + child_copy->setAttributeString("name", type_name + "." + child_name); + mElementNode->addChild(child_copy); + } + + LLXMLNodePtr element_declaration_node = mSchemaNode->createChild("xs:element", false); + element_declaration_node->createChild("name", true)->setStringValue(type_name); + element_declaration_node->createChild("type", true)->setStringValue(type_name); +} + +void LLXSDWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector* possible_values) +{ + name_stack_t non_empty_names; + std::string attribute_name; + for (name_stack_t::const_iterator it = stack.begin(); + it != stack.end(); + ++it) + { + const std::string& name = it->first; + if (!name.empty()) + { + non_empty_names.push_back(*it); + } + } + + for (name_stack_t::const_iterator it = non_empty_names.begin(); + it != non_empty_names.end(); + ++it) + { + if (!attribute_name.empty()) + { + attribute_name += "."; + } + attribute_name += it->first; + } + + // only flag non-nested attributes as mandatory, nested attributes have variant syntax + // that can't be properly constrained in XSD + // e.g. vs + bool attribute_mandatory = min_count == 1 && max_count == 1 && non_empty_names.size() == 1; + + // don't bother supporting "Multiple" params as xml attributes + if (max_count <= 1) + { + // add compound attribute to root node + addAttributeToSchema(mAttributeNode, attribute_name, type, attribute_mandatory, possible_values); + } + + // now generated nested elements for compound attributes + if (non_empty_names.size() > 1 && !attribute_mandatory) + { + std::string element_name; + + // traverse all but last element, leaving that as an attribute name + name_stack_t::const_iterator end_it = non_empty_names.end(); + end_it--; + + for (name_stack_t::const_iterator it = non_empty_names.begin(); + it != end_it; + ++it) + { + if (it != non_empty_names.begin()) + { + element_name += "."; + } + element_name += it->first; + } + + std::string short_attribute_name = non_empty_names.back().first; + + LLXMLNodePtr complex_type_node; + + // find existing element node here, starting at tail of child list + if (mElementNode->mChildren.notNull()) + { + for(LLXMLNodePtr element = mElementNode->mChildren->tail; + element.notNull(); + element = element->mPrev) + { + std::string name; + if(element->getAttributeString("name", name) && name == element_name) + { + complex_type_node = element->mChildren->head; + break; + } + } + } + //create complex_type node + // + // + // + // + // + if(complex_type_node.isNull()) + { + complex_type_node = mElementNode->createChild("xs:element", false); + + complex_type_node->createChild("minOccurs", true)->setIntValue(min_count); + complex_type_node->createChild("maxOccurs", true)->setIntValue(max_count); + complex_type_node->createChild("name", true)->setStringValue(element_name); + complex_type_node = complex_type_node->createChild("xs:complexType", false); + } + + addAttributeToSchema(complex_type_node, short_attribute_name, type, false, possible_values); + } +} + +void LLXSDWriter::addAttributeToSchema(LLXMLNodePtr type_declaration_node, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector* possible_values) +{ + if (!attribute_name.empty()) + { + LLXMLNodePtr new_enum_type_node; + if (possible_values != NULL) + { + // custom attribute type, for example + // + // + // + // + // + // + new_enum_type_node = new LLXMLNode("xs:simpleType", false); + + LLXMLNodePtr restriction_node = new_enum_type_node->createChild("xs:restriction", false); + restriction_node->createChild("base", true)->setStringValue("xs:string"); + + for (std::vector::const_iterator it = possible_values->begin(); + it != possible_values->end(); + ++it) + { + LLXMLNodePtr enum_node = restriction_node->createChild("xs:enumeration", false); + enum_node->createChild("value", true)->setStringValue(*it); + } + } + + string_set_t& attributes_written = mAttributesWritten[type_declaration_node]; + + string_set_t::iterator found_it = attributes_written.lower_bound(attribute_name); + + // attribute not yet declared + if (found_it == attributes_written.end() || attributes_written.key_comp()(attribute_name, *found_it)) + { + attributes_written.insert(found_it, attribute_name); + + LLXMLNodePtr attribute_node = type_declaration_node->createChild("xs:attribute", false); + + // attribute name + attribute_node->createChild("name", true)->setStringValue(attribute_name); + + if (new_enum_type_node.notNull()) + { + attribute_node->addChild(new_enum_type_node); + } + else + { + // simple attribute type + attribute_node->createChild("type", true)->setStringValue(type); + } + + // required or optional + attribute_node->createChild("use", true)->setStringValue(mandatory ? "required" : "optional"); + } + // attribute exists...handle collision of same name attributes with potentially different types + else + { + LLXMLNodePtr attribute_declaration; + if (type_declaration_node.notNull()) + { + for(LLXMLNodePtr node = type_declaration_node->mChildren->tail; + node.notNull(); + node = node->mPrev) + { + std::string name; + if (node->getAttributeString("name", name) && name == attribute_name) + { + attribute_declaration = node; + break; + } + } + } + + bool new_type_is_enum = new_enum_type_node.notNull(); + bool existing_type_is_enum = !attribute_declaration->hasAttribute("type"); + + // either type is enum, revert to string in collision + // don't bother to check for enum equivalence + if (new_type_is_enum || existing_type_is_enum) + { + if (attribute_declaration->hasAttribute("type")) + { + attribute_declaration->setAttributeString("type", "xs:string"); + } + else + { + attribute_declaration->createChild("type", true)->setStringValue("xs:string"); + } + attribute_declaration->deleteChildren("xs:simpleType"); + } + else + { + // check for collision of different standard types + std::string existing_type; + attribute_declaration->getAttributeString("type", existing_type); + // if current type is not the same as the new type, revert to strnig + if (existing_type != type) + { + // ...than use most general type, string + attribute_declaration->setAttributeString("type", "string"); + } + } + } + } +} + +// +// LLXUIXSDWriter +// +void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& path, const LLInitParam::BaseBlock& block) +{ + std::string file_name(path); + file_name += type_name + ".xsd"; + LLXMLNodePtr root_nodep = new LLXMLNode(); + + LLXSDWriter::writeXSD(type_name, root_nodep, block, "http://www.lindenlab.com/xui"); + + // add includes for all possible children + const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name); + const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type); + + // add choices for valid children + if (widget_registryp) + { + // add include declarations for all valid children + for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); + it != widget_registryp->currentRegistrar().endItems(); + ++it) + { + std::string widget_name = it->first; + if (widget_name == type_name) + { + continue; + } + LLXMLNodePtr nodep = new LLXMLNode("xs:include", false); + nodep->createChild("schemaLocation", true)->setStringValue(widget_name + ".xsd"); + + // add to front of schema + mSchemaNode->addChild(nodep, mSchemaNode); + } + + for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); + it != widget_registryp->currentRegistrar().endItems(); + ++it) + { + std::string widget_name = it->first; + // + LLXMLNodePtr widget_node = mElementNode->createChild("xs:element", false); + widget_node->createChild("name", true)->setStringValue(widget_name); + widget_node->createChild("type", true)->setStringValue(widget_name); + } + } + + LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), "w"); + LLXMLNode::writeHeaderToFile(xsd_file); + root_nodep->writeToFile(xsd_file); + fclose(xsd_file); +} + +static LLInitParam::Parser::parser_read_func_map_t sXUIReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sXUIWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sXUIInspectFuncs; + +// +// LLXUIParser +// +LLXUIParser::LLXUIParser() +: Parser(sXUIReadFuncs, sXUIWriteFuncs, sXUIInspectFuncs), + mCurReadDepth(0) +{ + if (sXUIReadFuncs.empty()) + { + registerParserFuncs(readFlag, writeFlag); + registerParserFuncs(readBoolValue, writeBoolValue); + registerParserFuncs(readStringValue, writeStringValue); + registerParserFuncs(readU8Value, writeU8Value); + registerParserFuncs(readS8Value, writeS8Value); + registerParserFuncs(readU16Value, writeU16Value); + registerParserFuncs(readS16Value, writeS16Value); + registerParserFuncs(readU32Value, writeU32Value); + registerParserFuncs(readS32Value, writeS32Value); + registerParserFuncs(readF32Value, writeF32Value); + registerParserFuncs(readF64Value, writeF64Value); + registerParserFuncs(readColor4Value, writeColor4Value); + registerParserFuncs(readUIColorValue, writeUIColorValue); + registerParserFuncs(readUUIDValue, writeUUIDValue); + registerParserFuncs(readSDValue, writeSDValue); + } +} + +static LLFastTimer::DeclareTimer FTM_PARSE_XUI("XUI Parsing"); +const LLXMLNodePtr DUMMY_NODE = new LLXMLNode(); + +void LLXUIParser::readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename, bool silent) +{ + LLFastTimer timer(FTM_PARSE_XUI); + mNameStack.clear(); + mRootNodeName = node->getName()->mString; + mCurFileName = filename; + mCurReadDepth = 0; + setParseSilently(silent); + + if (node.isNull()) + { + parserWarning("Invalid node"); + } + else + { + readXUIImpl(node, block); + } +} + +bool LLXUIParser::readXUIImpl(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + bool values_parsed = false; + bool silent = mCurReadDepth > 0; + + if (nodep->getFirstChild().isNull() + && nodep->mAttributes.empty() + && nodep->getSanitizedValue().empty()) + { + // empty node, just parse as flag + mCurReadNode = DUMMY_NODE; + return block.submitValue(mNameStack, *this, silent); + } + + // submit attributes for current node + values_parsed |= readAttributes(nodep, block); + + // treat text contents of xml node as "value" parameter + std::string text_contents = nodep->getSanitizedValue(); + if (!text_contents.empty()) + { + mCurReadNode = nodep; + mNameStack.push_back(std::make_pair(std::string("value"), true)); + // child nodes are not necessarily valid parameters (could be a child widget) + // so don't complain once we've recursed + if (!block.submitValue(mNameStack, *this, true)) + { + mNameStack.pop_back(); + block.submitValue(mNameStack, *this, silent); + } + else + { + mNameStack.pop_back(); + } + } + + // then traverse children + // child node must start with last name of parent node (our "scope") + // for example: "" + // which equates to the following nesting: + // button + // param + // nested_param1 + // nested_param2 + // nested_param3 + mCurReadDepth++; + for(LLXMLNodePtr childp = nodep->getFirstChild(); childp.notNull();) + { + std::string child_name(childp->getName()->mString); + S32 num_tokens_pushed = 0; + + // for non "dotted" child nodes check to see if child node maps to another widget type + // and if not, treat as a child element of the current node + // e.g. will interpret as "button.rect" + // since there is no widget named "rect" + if (child_name.find(".") == std::string::npos) + { + mNameStack.push_back(std::make_pair(child_name, true)); + num_tokens_pushed++; + } + else + { + // parse out "dotted" name into individual tokens + tokenizer name_tokens(child_name, sep); + + tokenizer::iterator name_token_it = name_tokens.begin(); + if(name_token_it == name_tokens.end()) + { + childp = childp->getNextSibling(); + continue; + } + + // check for proper nesting + if (mNameStack.empty()) + { + if (*name_token_it != mRootNodeName) + { + childp = childp->getNextSibling(); + continue; + } + } + else if(mNameStack.back().first != *name_token_it) + { + childp = childp->getNextSibling(); + continue; + } + + // now ignore first token + ++name_token_it; + + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + } + + // recurse and visit children XML nodes + if(readXUIImpl(childp, block)) + { + // child node successfully parsed, remove from DOM + + values_parsed = true; + LLXMLNodePtr node_to_remove = childp; + childp = childp->getNextSibling(); + + nodep->deleteChild(node_to_remove); + } + else + { + childp = childp->getNextSibling(); + } + + while(num_tokens_pushed-- > 0) + { + mNameStack.pop_back(); + } + } + mCurReadDepth--; + return values_parsed; +} + +bool LLXUIParser::readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + bool any_parsed = false; + bool silent = mCurReadDepth > 0; + + for(LLXMLAttribList::const_iterator attribute_it = nodep->mAttributes.begin(); + attribute_it != nodep->mAttributes.end(); + ++attribute_it) + { + S32 num_tokens_pushed = 0; + std::string attribute_name(attribute_it->first->mString); + mCurReadNode = attribute_it->second; + + tokenizer name_tokens(attribute_name, sep); + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + + // child nodes are not necessarily valid attributes, so don't complain once we've recursed + any_parsed |= block.submitValue(mNameStack, *this, silent); + + while(num_tokens_pushed-- > 0) + { + mNameStack.pop_back(); + } + } + + return any_parsed; +} + +void LLXUIParser::writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock &block, const LLInitParam::BaseBlock* diff_block) +{ + mWriteRootNode = node; + name_stack_t name_stack = Parser::name_stack_t(); + block.serializeBlock(*this, name_stack, diff_block); + mOutNodes.clear(); +} + +// go from a stack of names to a specific XML node +LLXMLNodePtr LLXUIParser::getNode(name_stack_t& stack) +{ + LLXMLNodePtr out_node = mWriteRootNode; + + name_stack_t::iterator next_it = stack.begin(); + for (name_stack_t::iterator it = stack.begin(); + it != stack.end(); + it = next_it) + { + ++next_it; + if (it->first.empty()) + { + it->second = false; + continue; + } + + out_nodes_t::iterator found_it = mOutNodes.find(it->first); + + // node with this name not yet written + if (found_it == mOutNodes.end() || it->second) + { + // make an attribute if we are the last element on the name stack + bool is_attribute = next_it == stack.end(); + LLXMLNodePtr new_node = new LLXMLNode(it->first.c_str(), is_attribute); + out_node->addChild(new_node); + mOutNodes[it->first] = new_node; + out_node = new_node; + it->second = false; + } + else + { + out_node = found_it->second; + } + } + + return (out_node == mWriteRootNode ? LLXMLNodePtr(NULL) : out_node); +} + +bool LLXUIParser::readFlag(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode == DUMMY_NODE; +} + +bool LLXUIParser::writeFlag(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + // just create node + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + return node.notNull(); +} + +bool LLXUIParser::readBoolValue(Parser& parser, void* val_ptr) +{ + S32 value; + LLXUIParser& self = static_cast(parser); + bool success = self.mCurReadNode->getBoolValue(1, &value); + *((bool*)val_ptr) = (value != FALSE); + return success; +} + +bool LLXUIParser::writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setBoolValue(*((bool*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readStringValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + *((std::string*)val_ptr) = self.mCurReadNode->getSanitizedValue(); + return true; +} + +bool LLXUIParser::writeStringValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + const std::string* string_val = reinterpret_cast(val_ptr); + if (string_val->find('\n') != std::string::npos + || string_val->size() > MAX_STRING_ATTRIBUTE_SIZE) + { + // don't write strings with newlines into attributes + std::string attribute_name = node->getName()->mString; + LLXMLNodePtr parent_node = node->mParent; + parent_node->deleteChild(node); + // write results in text contents of node + if (attribute_name == "value") + { + // "value" is implicit, just write to parent + node = parent_node; + } + else + { + // create a child that is not an attribute, but with same name + node = parent_node->createChild(attribute_name.c_str(), false); + } + } + node->setStringValue(*string_val); + return true; + } + return false; +} + +bool LLXUIParser::readU8Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getByteValue(1, (U8*)val_ptr); +} + +bool LLXUIParser::writeU8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setUnsignedValue(*((U8*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readS8Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + S32 value; + if(self.mCurReadNode->getIntValue(1, &value)) + { + *((S8*)val_ptr) = value; + return true; + } + return false; +} + +bool LLXUIParser::writeS8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setIntValue(*((S8*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readU16Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + U32 value; + if(self.mCurReadNode->getUnsignedValue(1, &value)) + { + *((U16*)val_ptr) = value; + return true; + } + return false; +} + +bool LLXUIParser::writeU16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setUnsignedValue(*((U16*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readS16Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + S32 value; + if(self.mCurReadNode->getIntValue(1, &value)) + { + *((S16*)val_ptr) = value; + return true; + } + return false; +} + +bool LLXUIParser::writeS16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setIntValue(*((S16*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readU32Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getUnsignedValue(1, (U32*)val_ptr); +} + +bool LLXUIParser::writeU32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setUnsignedValue(*((U32*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readS32Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getIntValue(1, (S32*)val_ptr); +} + +bool LLXUIParser::writeS32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setIntValue(*((S32*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readF32Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getFloatValue(1, (F32*)val_ptr); +} + +bool LLXUIParser::writeF32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setFloatValue(*((F32*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readF64Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getDoubleValue(1, (F64*)val_ptr); +} + +bool LLXUIParser::writeF64Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setDoubleValue(*((F64*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readColor4Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLColor4* colorp = (LLColor4*)val_ptr; + if(self.mCurReadNode->getFloatValue(4, colorp->mV) >= 3) + { + return true; + } + + return false; +} + +bool LLXUIParser::writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + LLColor4 color = *((LLColor4*)val_ptr); + node->setFloatValue(4, color.mV); + return true; + } + return false; +} + +bool LLXUIParser::readUIColorValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLUIColor* param = (LLUIColor*)val_ptr; + LLColor4 color; + bool success = self.mCurReadNode->getFloatValue(4, color.mV) >= 3; + if (success) + { + param->set(color); + return true; + } + return false; +} + +bool LLXUIParser::writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + LLUIColor color = *((LLUIColor*)val_ptr); + //RN: don't write out the color that is represented by a function + // rely on param block exporting to get the reference to the color settings + if (color.isReference()) return false; + node->setFloatValue(4, color.get().mV); + return true; + } + return false; +} + +bool LLXUIParser::readUUIDValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLUUID temp_id; + // LLUUID::set is destructive, so use temporary value + if (temp_id.set(self.mCurReadNode->getSanitizedValue())) + { + *(LLUUID*)(val_ptr) = temp_id; + return true; + } + return false; +} + +bool LLXUIParser::writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setStringValue(((LLUUID*)val_ptr)->asString()); + return true; + } + return false; +} + +bool LLXUIParser::readSDValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + *((LLSD*)val_ptr) = LLSD(self.mCurReadNode->getSanitizedValue()); + return true; +} + +bool LLXUIParser::writeSDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + std::string string_val = ((LLSD*)val_ptr)->asString(); + if (string_val.find('\n') != std::string::npos || string_val.size() > MAX_STRING_ATTRIBUTE_SIZE) + { + // don't write strings with newlines into attributes + std::string attribute_name = node->getName()->mString; + LLXMLNodePtr parent_node = node->mParent; + parent_node->deleteChild(node); + // write results in text contents of node + if (attribute_name == "value") + { + // "value" is implicit, just write to parent + node = parent_node; + } + else + { + node = parent_node->createChild(attribute_name.c_str(), false); + } + } + + node->setStringValue(string_val); + return true; + } + return false; +} + +/*virtual*/ std::string LLXUIParser::getCurrentElementName() +{ + std::string full_name; + for (name_stack_t::iterator it = mNameStack.begin(); + it != mNameStack.end(); + ++it) + { + full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." + } + + return full_name; +} + +void LLXUIParser::parserWarning(const std::string& message) +{ +#ifdef LL_WINDOWS + // use Visual Studo friendly formatting of output message for easy access to originating xml + llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); + utf16str += '\n'; + OutputDebugString(utf16str.c_str()); +#else + Parser::parserWarning(message); +#endif +} + +void LLXUIParser::parserError(const std::string& message) +{ +#ifdef LL_WINDOWS + llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); + utf16str += '\n'; + OutputDebugString(utf16str.c_str()); +#else + Parser::parserError(message); +#endif +} + + +// +// LLSimpleXUIParser +// + +struct ScopedFile +{ + ScopedFile( const std::string& filename, const char* accessmode ) + { + mFile = LLFile::fopen(filename, accessmode); + } + + ~ScopedFile() + { + fclose(mFile); + mFile = NULL; + } + + S32 getRemainingBytes() + { + if (!isOpen()) return 0; + + S32 cur_pos = ftell(mFile); + fseek(mFile, 0L, SEEK_END); + S32 file_size = ftell(mFile); + fseek(mFile, cur_pos, SEEK_SET); + return file_size - cur_pos; + } + + bool isOpen() { return mFile != NULL; } + + LLFILE* mFile; +}; +LLSimpleXUIParser::LLSimpleXUIParser(LLSimpleXUIParser::element_start_callback_t element_cb) +: Parser(sSimpleXUIReadFuncs, sSimpleXUIWriteFuncs, sSimpleXUIInspectFuncs), + mCurReadDepth(0), + mElementCB(element_cb) +{ + if (sSimpleXUIReadFuncs.empty()) + { + registerParserFuncs(readFlag); + registerParserFuncs(readBoolValue); + registerParserFuncs(readStringValue); + registerParserFuncs(readU8Value); + registerParserFuncs(readS8Value); + registerParserFuncs(readU16Value); + registerParserFuncs(readS16Value); + registerParserFuncs(readU32Value); + registerParserFuncs(readS32Value); + registerParserFuncs(readF32Value); + registerParserFuncs(readF64Value); + registerParserFuncs(readColor4Value); + registerParserFuncs(readUIColorValue); + registerParserFuncs(readUUIDValue); + registerParserFuncs(readSDValue); + } +} + +LLSimpleXUIParser::~LLSimpleXUIParser() +{ +} + + +bool LLSimpleXUIParser::readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent) +{ + LLFastTimer timer(FTM_PARSE_XUI); + + mParser = XML_ParserCreate(NULL); + XML_SetUserData(mParser, this); + XML_SetElementHandler( mParser, startElementHandler, endElementHandler); + XML_SetCharacterDataHandler( mParser, characterDataHandler); + + mOutputStack.push_back(std::make_pair(&block, 0)); + mNameStack.clear(); + mCurFileName = filename; + mCurReadDepth = 0; + setParseSilently(silent); + + ScopedFile file(filename, "rb"); + if( !file.isOpen() ) + { + LL_WARNS("ReadXUI") << "Unable to open file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + S32 bytes_read = 0; + + S32 buffer_size = file.getRemainingBytes(); + void* buffer = XML_GetBuffer(mParser, buffer_size); + if( !buffer ) + { + LL_WARNS("ReadXUI") << "Unable to allocate XML buffer while reading file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + bytes_read = (S32)fread(buffer, 1, buffer_size, file.mFile); + if( bytes_read <= 0 ) + { + LL_WARNS("ReadXUI") << "Error while reading file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + mEmptyLeafNode.push_back(false); + + if( !XML_ParseBuffer(mParser, bytes_read, TRUE ) ) + { + LL_WARNS("ReadXUI") << "Error while parsing file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + mEmptyLeafNode.pop_back(); + + XML_ParserFree( mParser ); + return true; +} + +void LLSimpleXUIParser::startElementHandler(void *userData, const char *name, const char **atts) +{ + LLSimpleXUIParser* self = reinterpret_cast(userData); + self->startElement(name, atts); +} + +void LLSimpleXUIParser::endElementHandler(void *userData, const char *name) +{ + LLSimpleXUIParser* self = reinterpret_cast(userData); + self->endElement(name); +} + +void LLSimpleXUIParser::characterDataHandler(void *userData, const char *s, int len) +{ + LLSimpleXUIParser* self = reinterpret_cast(userData); + self->characterData(s, len); +} + +void LLSimpleXUIParser::characterData(const char *s, int len) +{ + mTextContents += std::string(s, len); +} + +void LLSimpleXUIParser::startElement(const char *name, const char **atts) +{ + processText(); + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + if (mElementCB) + { + LLInitParam::BaseBlock* blockp = mElementCB(*this, name); + if (blockp) + { + mOutputStack.push_back(std::make_pair(blockp, 0)); + } + } + + mOutputStack.back().second++; + S32 num_tokens_pushed = 0; + std::string child_name(name); + + if (mOutputStack.back().second == 1) + { // root node for this block + mScope.push_back(child_name); + } + else + { // compound attribute + if (child_name.find(".") == std::string::npos) + { + mNameStack.push_back(std::make_pair(child_name, true)); + num_tokens_pushed++; + mScope.push_back(child_name); + } + else + { + // parse out "dotted" name into individual tokens + tokenizer name_tokens(child_name, sep); + + tokenizer::iterator name_token_it = name_tokens.begin(); + if(name_token_it == name_tokens.end()) + { + return; + } + + // check for proper nesting + if(!mScope.empty() && *name_token_it != mScope.back()) + { + return; + } + + // now ignore first token + ++name_token_it; + + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + mScope.push_back(mNameStack.back().first); + } + } + + // parent node is not empty + mEmptyLeafNode.back() = false; + // we are empty if we have no attributes + mEmptyLeafNode.push_back(atts[0] == NULL); + + mTokenSizeStack.push_back(num_tokens_pushed); + readAttributes(atts); + +} + +void LLSimpleXUIParser::endElement(const char *name) +{ + bool has_text = processText(); + + // no text, attributes, or children + if (!has_text && mEmptyLeafNode.back()) + { + // submit this as a valueless name (even though there might be text contents we haven't seen yet) + mCurAttributeValueBegin = NO_VALUE_MARKER; + mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); + } + + if (--mOutputStack.back().second == 0) + { + if (mOutputStack.empty()) + { + LL_ERRS("ReadXUI") << "Parameter block output stack popped while empty." << LL_ENDL; + } + mOutputStack.pop_back(); + } + + S32 num_tokens_to_pop = mTokenSizeStack.back(); + mTokenSizeStack.pop_back(); + while(num_tokens_to_pop-- > 0) + { + mNameStack.pop_back(); + } + mScope.pop_back(); + mEmptyLeafNode.pop_back(); +} + +bool LLSimpleXUIParser::readAttributes(const char **atts) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + bool any_parsed = false; + for(S32 i = 0; atts[i] && atts[i+1]; i += 2 ) + { + std::string attribute_name(atts[i]); + mCurAttributeValueBegin = atts[i+1]; + + S32 num_tokens_pushed = 0; + tokenizer name_tokens(attribute_name, sep); + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + + // child nodes are not necessarily valid attributes, so don't complain once we've recursed + any_parsed |= mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); + + while(num_tokens_pushed-- > 0) + { + mNameStack.pop_back(); + } + } + return any_parsed; +} + +bool LLSimpleXUIParser::processText() +{ + if (!mTextContents.empty()) + { + LLStringUtil::trim(mTextContents); + if (!mTextContents.empty()) + { + mNameStack.push_back(std::make_pair(std::string("value"), true)); + mCurAttributeValueBegin = mTextContents.c_str(); + mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); + mNameStack.pop_back(); + } + mTextContents.clear(); + return true; + } + return false; +} + +/*virtual*/ std::string LLSimpleXUIParser::getCurrentElementName() +{ + std::string full_name; + for (name_stack_t::iterator it = mNameStack.begin(); + it != mNameStack.end(); + ++it) + { + full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." + } + + return full_name; +} + +void LLSimpleXUIParser::parserWarning(const std::string& message) +{ +#ifdef LL_WINDOWS + // use Visual Studo friendly formatting of output message for easy access to originating xml + llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), LINE_NUMBER_HERE, message.c_str()).c_str()); + utf16str += '\n'; + OutputDebugString(utf16str.c_str()); +#else + Parser::parserWarning(message); +#endif +} + +void LLSimpleXUIParser::parserError(const std::string& message) +{ +#ifdef LL_WINDOWS + llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), LINE_NUMBER_HERE, message.c_str()).c_str()); + utf16str += '\n'; + OutputDebugString(utf16str.c_str()); +#else + Parser::parserError(message); +#endif +} + +bool LLSimpleXUIParser::readFlag(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return self.mCurAttributeValueBegin == NO_VALUE_MARKER; +} + +bool LLSimpleXUIParser::readBoolValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + if (!strcmp(self.mCurAttributeValueBegin, "true")) + { + *((bool*)val_ptr) = true; + return true; + } + else if (!strcmp(self.mCurAttributeValueBegin, "false")) + { + *((bool*)val_ptr) = false; + return true; + } + + return false; +} + +bool LLSimpleXUIParser::readStringValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + *((std::string*)val_ptr) = self.mCurAttributeValueBegin; + return true; +} + +bool LLSimpleXUIParser::readU8Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U8*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readS8Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S8*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readU16Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U16*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readS16Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S16*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readU32Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U32*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readS32Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S32*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readF32Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F32*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readF64Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F64*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readColor4Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + LLColor4 value; + + if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) + { + *(LLColor4*)(val_ptr) = value; + return true; + } + return false; +} + +bool LLSimpleXUIParser::readUIColorValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + LLColor4 value; + LLUIColor* colorp = (LLUIColor*)val_ptr; + + if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) + { + colorp->set(value); + return true; + } + return false; +} + +bool LLSimpleXUIParser::readUUIDValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + LLUUID temp_id; + // LLUUID::set is destructive, so use temporary value + if (temp_id.set(std::string(self.mCurAttributeValueBegin))) + { + *(LLUUID*)(val_ptr) = temp_id; + return true; + } + return false; +} + +bool LLSimpleXUIParser::readSDValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + *((LLSD*)val_ptr) = LLSD(self.mCurAttributeValueBegin); + return true; +} diff --git a/indra/llui/llxuiparser.h b/indra/llui/llxuiparser.h new file mode 100644 index 0000000000..d7cd256967 --- /dev/null +++ b/indra/llui/llxuiparser.h @@ -0,0 +1,242 @@ +/** + * @file llxuiparser.h + * @brief Utility functions for handling XUI structures in XML + * + * $LicenseInfo:firstyear=2003&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 LLXUIPARSER_H +#define LLXUIPARSER_H + +#include "llinitparam.h" +#include "llregistry.h" +#include "llpointer.h" + +#include +#include +#include +#include + + + +class LLView; + + +typedef LLPointer LLXMLNodePtr; + + +// lookup widget type by name +class LLWidgetTypeRegistry +: public LLRegistrySingleton +{}; + + +// global static instance for registering all widget types +typedef boost::function LLWidgetCreatorFunc; + +typedef LLRegistry widget_registry_t; + +class LLChildRegistryRegistry +: public LLRegistrySingleton +{}; + + + +class LLXSDWriter : public LLInitParam::Parser +{ + LOG_CLASS(LLXSDWriter); +public: + void writeXSD(const std::string& name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace); + + /*virtual*/ std::string getCurrentElementName() { return LLStringUtil::null; } + + LLXSDWriter(); + +protected: + void writeAttribute(const std::string& type, const Parser::name_stack_t&, S32 min_count, S32 max_count, const std::vector* possible_values); + void addAttributeToSchema(LLXMLNodePtr nodep, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector* possible_values); + LLXMLNodePtr mAttributeNode; + LLXMLNodePtr mElementNode; + LLXMLNodePtr mSchemaNode; + + typedef std::set string_set_t; + typedef std::map attributes_map_t; + attributes_map_t mAttributesWritten; +}; + + + +// NOTE: DOES NOT WORK YET +// should support child widgets for XUI +class LLXUIXSDWriter : public LLXSDWriter +{ +public: + void writeXSD(const std::string& name, const std::string& path, const LLInitParam::BaseBlock& block); +}; + + +class LLXUIParserImpl; + +class LLXUIParser : public LLInitParam::Parser +{ +LOG_CLASS(LLXUIParser); + +public: + LLXUIParser(); + typedef LLInitParam::Parser::name_stack_t name_stack_t; + + /*virtual*/ std::string getCurrentElementName(); + /*virtual*/ void parserWarning(const std::string& message); + /*virtual*/ void parserError(const std::string& message); + + void readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename = LLStringUtil::null, bool silent=false); + void writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const LLInitParam::BaseBlock* diff_block = NULL); + +private: + bool readXUIImpl(LLXMLNodePtr node, LLInitParam::BaseBlock& block); + bool readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block); + + //reader helper functions + static bool readFlag(Parser& parser, void* val_ptr); + static bool readBoolValue(Parser& parser, void* val_ptr); + static bool readStringValue(Parser& parser, void* val_ptr); + static bool readU8Value(Parser& parser, void* val_ptr); + static bool readS8Value(Parser& parser, void* val_ptr); + static bool readU16Value(Parser& parser, void* val_ptr); + static bool readS16Value(Parser& parser, void* val_ptr); + static bool readU32Value(Parser& parser, void* val_ptr); + static bool readS32Value(Parser& parser, void* val_ptr); + static bool readF32Value(Parser& parser, void* val_ptr); + static bool readF64Value(Parser& parser, void* val_ptr); + static bool readColor4Value(Parser& parser, void* val_ptr); + static bool readUIColorValue(Parser& parser, void* val_ptr); + static bool readUUIDValue(Parser& parser, void* val_ptr); + static bool readSDValue(Parser& parser, void* val_ptr); + + //writer helper functions + static bool writeFlag(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeStringValue(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeU8Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeS8Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeU16Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeS16Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeU32Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeS32Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeF32Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeF64Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t&); + static bool writeSDValue(Parser& parser, const void* val_ptr, name_stack_t&); + + LLXMLNodePtr getNode(name_stack_t& stack); + +private: + Parser::name_stack_t mNameStack; + LLXMLNodePtr mCurReadNode; + // Root of the widget XML sub-tree, for example, "line_editor" + LLXMLNodePtr mWriteRootNode; + + typedef std::map out_nodes_t; + out_nodes_t mOutNodes; + LLXMLNodePtr mLastWrittenChild; + S32 mCurReadDepth; + std::string mCurFileName; + std::string mRootNodeName; +}; + +// LLSimpleXUIParser is a streamlined SAX-based XUI parser that does not support localization +// or parsing of a tree of independent param blocks, such as child widgets. +// Use this for reading non-localized files that only need a single param block as a result. +// +// NOTE: In order to support nested block parsing, we need callbacks for start element that +// push new blocks contexts on the mScope stack. +// NOTE: To support localization without building a DOM, we need to enforce consistent +// ordering of child elements from base file to localized diff file. Then we can use a pair +// of coroutines to perform matching of xml nodes during parsing. Not sure if the overhead +// of coroutines would offset the gain from SAX parsing +class LLSimpleXUIParserImpl; + +class LLSimpleXUIParser : public LLInitParam::Parser +{ +LOG_CLASS(LLSimpleXUIParser); +public: + typedef LLInitParam::Parser::name_stack_t name_stack_t; + typedef LLInitParam::BaseBlock* (*element_start_callback_t)(LLSimpleXUIParser&, const char* block_name); + + LLSimpleXUIParser(element_start_callback_t element_cb = NULL); + virtual ~LLSimpleXUIParser(); + + /*virtual*/ std::string getCurrentElementName(); + /*virtual*/ void parserWarning(const std::string& message); + /*virtual*/ void parserError(const std::string& message); + + bool readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent=false); + + +private: + //reader helper functions + static bool readFlag(Parser&, void* val_ptr); + static bool readBoolValue(Parser&, void* val_ptr); + static bool readStringValue(Parser&, void* val_ptr); + static bool readU8Value(Parser&, void* val_ptr); + static bool readS8Value(Parser&, void* val_ptr); + static bool readU16Value(Parser&, void* val_ptr); + static bool readS16Value(Parser&, void* val_ptr); + static bool readU32Value(Parser&, void* val_ptr); + static bool readS32Value(Parser&, void* val_ptr); + static bool readF32Value(Parser&, void* val_ptr); + static bool readF64Value(Parser&, void* val_ptr); + static bool readColor4Value(Parser&, void* val_ptr); + static bool readUIColorValue(Parser&, void* val_ptr); + static bool readUUIDValue(Parser&, void* val_ptr); + static bool readSDValue(Parser&, void* val_ptr); + +private: + static void startElementHandler(void *userData, const char *name, const char **atts); + static void endElementHandler(void *userData, const char *name); + static void characterDataHandler(void *userData, const char *s, int len); + + void startElement(const char *name, const char **atts); + void endElement(const char *name); + void characterData(const char *s, int len); + bool readAttributes(const char **atts); + bool processText(); + + Parser::name_stack_t mNameStack; + struct XML_ParserStruct* mParser; + LLXMLNodePtr mLastWrittenChild; + S32 mCurReadDepth; + std::string mCurFileName; + std::string mTextContents; + const char* mCurAttributeValueBegin; + std::vector mTokenSizeStack; + std::vector mScope; + std::vector mEmptyLeafNode; + element_start_callback_t mElementCB; + + std::vector > mOutputStack; +}; + + +#endif //LLXUIPARSER_H diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index c75df86891..cb3b7abb14 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -105,28 +105,6 @@ LLStyle::Params::Params() namespace LLInitParam { - Param::Param(BaseBlock* enclosing_block) - : mIsProvided(false) - { - const U8* my_addr = reinterpret_cast(this); - const U8* block_addr = reinterpret_cast(enclosing_block); - mEnclosingBlockOffset = (U16)(my_addr - block_addr); - } - - void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name){} - void BaseBlock::addSynonym(Param& param, const std::string& synonym) {} - param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;} - - void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) - { - descriptor.mCurrentBlockPtr = this; - } - bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; } - void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {} - bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_value, S32 max_value) const { return true; } - bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } - bool BaseBlock::validateBlock(bool emit_errors) const { return true; } - ParamValue >::ParamValue(const LLUIColor& color) : super_t(color) {} diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index c1fb050206..8f0a48018f 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -70,21 +70,6 @@ S32 LLUIImage::getHeight() const return 0; } -namespace LLInitParam -{ - BlockDescriptor::BlockDescriptor() {} - ParamDescriptor::ParamDescriptor(param_handle_t p, - merge_func_t merge_func, - deserialize_func_t deserialize_func, - serialize_func_t serialize_func, - validation_func_t validation_func, - inspect_func_t inspect_func, - S32 min_count, - S32 max_count){} - ParamDescriptor::~ParamDescriptor() {} - -} - namespace tut { struct LLUrlEntryData diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index 7183413463..963473c92a 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -63,40 +63,6 @@ S32 LLUIImage::getHeight() const namespace LLInitParam { - BlockDescriptor::BlockDescriptor() {} - ParamDescriptor::ParamDescriptor(param_handle_t p, - merge_func_t merge_func, - deserialize_func_t deserialize_func, - serialize_func_t serialize_func, - validation_func_t validation_func, - inspect_func_t inspect_func, - S32 min_count, - S32 max_count){} - ParamDescriptor::~ParamDescriptor() {} - - void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name){} - param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;} - void BaseBlock::addSynonym(Param& param, const std::string& synonym) {} - - void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) - { - descriptor.mCurrentBlockPtr = this; - } - - Param::Param(BaseBlock* enclosing_block) - : mIsProvided(false) - { - const U8* my_addr = reinterpret_cast(this); - const U8* block_addr = reinterpret_cast(enclosing_block); - mEnclosingBlockOffset = 0x7FFFffff & ((U32)(my_addr - block_addr)); - } - - bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; } - void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {} - bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_count, S32 max_count) const { return true; } - bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } - bool BaseBlock::validateBlock(bool emit_errors) const { return true; } - ParamValue >::ParamValue(const LLUIColor& color) : super_t(color) {} diff --git a/indra/llxuixml/CMakeLists.txt b/indra/llxuixml/CMakeLists.txt deleted file mode 100644 index daed4de6ce..0000000000 --- a/indra/llxuixml/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -*- cmake -*- - -project(llxuixml) - -include(00-Common) -include(LLCommon) -include(LLMath) -include(LLXML) - -include_directories( - ${LLCOMMON_INCLUDE_DIRS} - ${LLMATH_INCLUDE_DIRS} - ${LLXML_INCLUDE_DIRS} - ) - -set(llxuixml_SOURCE_FILES - llinitparam.cpp - lltrans.cpp - lluicolor.cpp - llxuiparser.cpp - ) - -set(llxuixml_HEADER_FILES - CMakeLists.txt - - llinitparam.h - lltrans.h - llregistry.h - lluicolor.h - llxuiparser.h - ) - -set_source_files_properties(${llxuixml_HEADER_FILES} - PROPERTIES HEADER_FILE_ONLY TRUE) - -list(APPEND llxuixml_SOURCE_FILES ${llxuixml_HEADER_FILES}) - -add_library (llxuixml ${llxuixml_SOURCE_FILES}) -# Libraries on which this library depends, needed for Linux builds -# Sort by high-level to low-level -target_link_libraries(llxuixml - llxml - llcommon - llmath - ) diff --git a/indra/llxuixml/llinitparam.cpp b/indra/llxuixml/llinitparam.cpp deleted file mode 100644 index db72aa19b9..0000000000 --- a/indra/llxuixml/llinitparam.cpp +++ /dev/null @@ -1,469 +0,0 @@ -/** - * @file llinitparam.cpp - * @brief parameter block abstraction for creating complex objects and - * parsing construction parameters from xml and LLSD - * - * $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 "llinitparam.h" - - -namespace LLInitParam -{ - // - // Param - // - Param::Param(BaseBlock* enclosing_block) - : mIsProvided(false) - { - const U8* my_addr = reinterpret_cast(this); - const U8* block_addr = reinterpret_cast(enclosing_block); - mEnclosingBlockOffset = 0x7FFFffff & (U32)(my_addr - block_addr); - } - - // - // ParamDescriptor - // - ParamDescriptor::ParamDescriptor(param_handle_t p, - merge_func_t merge_func, - deserialize_func_t deserialize_func, - serialize_func_t serialize_func, - validation_func_t validation_func, - inspect_func_t inspect_func, - S32 min_count, - S32 max_count) - : mParamHandle(p), - mMergeFunc(merge_func), - mDeserializeFunc(deserialize_func), - mSerializeFunc(serialize_func), - mValidationFunc(validation_func), - mInspectFunc(inspect_func), - mMinCount(min_count), - mMaxCount(max_count), - mUserData(NULL) - {} - - ParamDescriptor::ParamDescriptor() - : mParamHandle(0), - mMergeFunc(NULL), - mDeserializeFunc(NULL), - mSerializeFunc(NULL), - mValidationFunc(NULL), - mInspectFunc(NULL), - mMinCount(0), - mMaxCount(0), - mUserData(NULL) - {} - - ParamDescriptor::~ParamDescriptor() - { - delete mUserData; - } - - // - // Parser - // - Parser::~Parser() - {} - - void Parser::parserWarning(const std::string& message) - { - if (mParseSilently) return; - llwarns << message << llendl; - } - - void Parser::parserError(const std::string& message) - { - if (mParseSilently) return; - llerrs << message << llendl; - } - - - // - // BlockDescriptor - // - void BlockDescriptor::aggregateBlockData(BlockDescriptor& src_block_data) - { - mNamedParams.insert(src_block_data.mNamedParams.begin(), src_block_data.mNamedParams.end()); - std::copy(src_block_data.mUnnamedParams.begin(), src_block_data.mUnnamedParams.end(), std::back_inserter(mUnnamedParams)); - std::copy(src_block_data.mValidationList.begin(), src_block_data.mValidationList.end(), std::back_inserter(mValidationList)); - std::copy(src_block_data.mAllParams.begin(), src_block_data.mAllParams.end(), std::back_inserter(mAllParams)); - } - - BlockDescriptor::BlockDescriptor() - : mMaxParamOffset(0), - mInitializationState(UNINITIALIZED), - mCurrentBlockPtr(NULL) - {} - - // called by each derived class in least to most derived order - void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) - { - descriptor.mCurrentBlockPtr = this; - descriptor.mMaxParamOffset = block_size; - - switch(descriptor.mInitializationState) - { - case BlockDescriptor::UNINITIALIZED: - // copy params from base class here - descriptor.aggregateBlockData(base_descriptor); - - descriptor.mInitializationState = BlockDescriptor::INITIALIZING; - break; - case BlockDescriptor::INITIALIZING: - descriptor.mInitializationState = BlockDescriptor::INITIALIZED; - break; - case BlockDescriptor::INITIALIZED: - // nothing to do - break; - } - } - - param_handle_t BaseBlock::getHandleFromParam(const Param* param) const - { - const U8* param_address = reinterpret_cast(param); - const U8* baseblock_address = reinterpret_cast(this); - return (param_address - baseblock_address); - } - - bool BaseBlock::submitValue(Parser::name_stack_t& name_stack, Parser& p, bool silent) - { - if (!deserializeBlock(p, std::make_pair(name_stack.begin(), name_stack.end()), true)) - { - if (!silent) - { - p.parserWarning(llformat("Failed to parse parameter \"%s\"", p.getCurrentElementName().c_str())); - } - return false; - } - return true; - } - - - bool BaseBlock::validateBlock(bool emit_errors) const - { - const BlockDescriptor& block_data = mostDerivedBlockDescriptor(); - for (BlockDescriptor::param_validation_list_t::const_iterator it = block_data.mValidationList.begin(); it != block_data.mValidationList.end(); ++it) - { - const Param* param = getParamFromHandle(it->first); - if (!it->second(param)) - { - if (emit_errors) - { - llwarns << "Invalid param \"" << getParamName(block_data, param) << "\"" << llendl; - } - return false; - } - } - return true; - } - - void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const - { - // named param is one like LLView::Params::follows - // unnamed param is like LLView::Params::rect - implicit - const BlockDescriptor& block_data = mostDerivedBlockDescriptor(); - - for (BlockDescriptor::param_list_t::const_iterator it = block_data.mUnnamedParams.begin(); - it != block_data.mUnnamedParams.end(); - ++it) - { - param_handle_t param_handle = (*it)->mParamHandle; - const Param* param = getParamFromHandle(param_handle); - ParamDescriptor::serialize_func_t serialize_func = (*it)->mSerializeFunc; - if (serialize_func) - { - const Param* diff_param = diff_block ? diff_block->getParamFromHandle(param_handle) : NULL; - // each param descriptor remembers its serial number - // so we can inspect the same param under different names - // and see that it has the same number - name_stack.push_back(std::make_pair("", true)); - serialize_func(*param, parser, name_stack, diff_param); - name_stack.pop_back(); - } - } - - for(BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); - it != block_data.mNamedParams.end(); - ++it) - { - param_handle_t param_handle = it->second->mParamHandle; - const Param* param = getParamFromHandle(param_handle); - ParamDescriptor::serialize_func_t serialize_func = it->second->mSerializeFunc; - if (serialize_func && param->anyProvided()) - { - // Ensure this param has not already been serialized - // Prevents from being serialized as its own tag. - bool duplicate = false; - for (BlockDescriptor::param_list_t::const_iterator it2 = block_data.mUnnamedParams.begin(); - it2 != block_data.mUnnamedParams.end(); - ++it2) - { - if (param_handle == (*it2)->mParamHandle) - { - duplicate = true; - break; - } - } - - //FIXME: for now, don't attempt to serialize values under synonyms, as current parsers - // don't know how to detect them - if (duplicate) - { - continue; - } - - name_stack.push_back(std::make_pair(it->first, !duplicate)); - const Param* diff_param = diff_block ? diff_block->getParamFromHandle(param_handle) : NULL; - serialize_func(*param, parser, name_stack, diff_param); - name_stack.pop_back(); - } - } - } - - bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_count, S32 max_count) const - { - // named param is one like LLView::Params::follows - // unnamed param is like LLView::Params::rect - implicit - const BlockDescriptor& block_data = mostDerivedBlockDescriptor(); - - for (BlockDescriptor::param_list_t::const_iterator it = block_data.mUnnamedParams.begin(); - it != block_data.mUnnamedParams.end(); - ++it) - { - param_handle_t param_handle = (*it)->mParamHandle; - const Param* param = getParamFromHandle(param_handle); - ParamDescriptor::inspect_func_t inspect_func = (*it)->mInspectFunc; - if (inspect_func) - { - name_stack.push_back(std::make_pair("", true)); - inspect_func(*param, parser, name_stack, (*it)->mMinCount, (*it)->mMaxCount); - name_stack.pop_back(); - } - } - - for(BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); - it != block_data.mNamedParams.end(); - ++it) - { - param_handle_t param_handle = it->second->mParamHandle; - const Param* param = getParamFromHandle(param_handle); - ParamDescriptor::inspect_func_t inspect_func = it->second->mInspectFunc; - if (inspect_func) - { - // Ensure this param has not already been inspected - bool duplicate = false; - for (BlockDescriptor::param_list_t::const_iterator it2 = block_data.mUnnamedParams.begin(); - it2 != block_data.mUnnamedParams.end(); - ++it2) - { - if (param_handle == (*it2)->mParamHandle) - { - duplicate = true; - break; - } - } - - name_stack.push_back(std::make_pair(it->first, !duplicate)); - inspect_func(*param, parser, name_stack, it->second->mMinCount, it->second->mMaxCount); - name_stack.pop_back(); - } - } - - return true; - } - - bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool ignored) - { - BlockDescriptor& block_data = mostDerivedBlockDescriptor(); - bool names_left = name_stack_range.first != name_stack_range.second; - - bool new_name = names_left - ? name_stack_range.first->second - : true; - - if (names_left) - { - const std::string& top_name = name_stack_range.first->first; - - ParamDescriptor::deserialize_func_t deserialize_func = NULL; - Param* paramp = NULL; - - BlockDescriptor::param_map_t::iterator found_it = block_data.mNamedParams.find(top_name); - if (found_it != block_data.mNamedParams.end()) - { - // find pointer to member parameter from offset table - paramp = getParamFromHandle(found_it->second->mParamHandle); - deserialize_func = found_it->second->mDeserializeFunc; - - Parser::name_stack_range_t new_name_stack(name_stack_range.first, name_stack_range.second); - ++new_name_stack.first; - if (deserialize_func(*paramp, p, new_name_stack, new_name)) - { - // value is no longer new, we know about it now - name_stack_range.first->second = false; - return true; - } - else - { - return false; - } - } - } - - // try to parse unnamed parameters, in declaration order - for ( BlockDescriptor::param_list_t::iterator it = block_data.mUnnamedParams.begin(); - it != block_data.mUnnamedParams.end(); - ++it) - { - Param* paramp = getParamFromHandle((*it)->mParamHandle); - ParamDescriptor::deserialize_func_t deserialize_func = (*it)->mDeserializeFunc; - - if (deserialize_func && deserialize_func(*paramp, p, name_stack_range, new_name)) - { - return true; - } - } - - // if no match, and no names left on stack, this is just an existence assertion of this block - // verify by calling readValue with NoParamValue type, an inherently unparseable type - if (!names_left) - { - Flag no_value; - return p.readValue(no_value); - } - - return false; - } - - //static - void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name) - { - // create a copy of the param descriptor in mAllParams - // so other data structures can store a pointer to it - block_data.mAllParams.push_back(in_param); - ParamDescriptorPtr param(block_data.mAllParams.back()); - - std::string name(char_name); - if ((size_t)param->mParamHandle > block_data.mMaxParamOffset) - { - llerrs << "Attempted to register param with block defined for parent class, make sure to derive from LLInitParam::Block" << llendl; - } - - if (name.empty()) - { - block_data.mUnnamedParams.push_back(param); - } - else - { - // don't use insert, since we want to overwrite existing entries - block_data.mNamedParams[name] = param; - } - - if (param->mValidationFunc) - { - block_data.mValidationList.push_back(std::make_pair(param->mParamHandle, param->mValidationFunc)); - } - } - - void BaseBlock::addSynonym(Param& param, const std::string& synonym) - { - BlockDescriptor& block_data = mostDerivedBlockDescriptor(); - if (block_data.mInitializationState == BlockDescriptor::INITIALIZING) - { - param_handle_t handle = getHandleFromParam(¶m); - - // check for invalid derivation from a paramblock (i.e. without using - // Block - if ((size_t)handle > block_data.mMaxParamOffset) - { - llerrs << "Attempted to register param with block defined for parent class, make sure to derive from LLInitParam::Block" << llendl; - } - - ParamDescriptorPtr param_descriptor = findParamDescriptor(param); - if (param_descriptor) - { - if (synonym.empty()) - { - block_data.mUnnamedParams.push_back(param_descriptor); - } - else - { - block_data.mNamedParams[synonym] = param_descriptor; - } - } - } - } - - const std::string& BaseBlock::getParamName(const BlockDescriptor& block_data, const Param* paramp) const - { - param_handle_t handle = getHandleFromParam(paramp); - for (BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); it != block_data.mNamedParams.end(); ++it) - { - if (it->second->mParamHandle == handle) - { - return it->first; - } - } - - return LLStringUtil::null; - } - - ParamDescriptorPtr BaseBlock::findParamDescriptor(const Param& param) - { - param_handle_t handle = getHandleFromParam(¶m); - BlockDescriptor& descriptor = mostDerivedBlockDescriptor(); - BlockDescriptor::all_params_list_t::iterator end_it = descriptor.mAllParams.end(); - for (BlockDescriptor::all_params_list_t::iterator it = descriptor.mAllParams.begin(); - it != end_it; - ++it) - { - if ((*it)->mParamHandle == handle) return *it; - } - return ParamDescriptorPtr(); - } - - // take all provided params from other and apply to self - // NOTE: this requires that "other" is of the same derived type as this - bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) - { - bool some_param_changed = false; - BlockDescriptor::all_params_list_t::const_iterator end_it = block_data.mAllParams.end(); - for (BlockDescriptor::all_params_list_t::const_iterator it = block_data.mAllParams.begin(); - it != end_it; - ++it) - { - const Param* other_paramp = other.getParamFromHandle((*it)->mParamHandle); - ParamDescriptor::merge_func_t merge_func = (*it)->mMergeFunc; - if (merge_func) - { - Param* paramp = getParamFromHandle((*it)->mParamHandle); - llassert(paramp->mEnclosingBlockOffset == (*it)->mParamHandle); - some_param_changed |= merge_func(*paramp, *other_paramp, overwrite); - } - } - return some_param_changed; - } -} diff --git a/indra/llxuixml/llinitparam.h b/indra/llxuixml/llinitparam.h deleted file mode 100644 index ab20957760..0000000000 --- a/indra/llxuixml/llinitparam.h +++ /dev/null @@ -1,2294 +0,0 @@ -/** - * @file llinitparam.h - * @brief parameter block abstraction for creating complex objects and - * parsing construction parameters from xml and LLSD - * - * $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_LLPARAM_H -#define LL_LLPARAM_H - -#include -#include -#include -#include -#include - -#include "llerror.h" - -namespace LLInitParam -{ - // used to indicate no matching value to a given name when parsing - struct Flag{}; - - template const T& defaultValue() { static T value; return value; } - - template ::value > - struct ParamCompare - { - static bool equals(const T &a, const T &b) - { - return a == b; - } - }; - - // boost function types are not comparable - template - struct ParamCompare - { - static bool equals(const T&a, const T &b) - { - return false; - } - }; - - template<> - struct ParamCompare - { - static bool equals(const LLSD &a, const LLSD &b) { return false; } - }; - - template<> - struct ParamCompare - { - static bool equals(const Flag& a, const Flag& b) { return false; } - }; - - - // helper functions and classes - typedef ptrdiff_t param_handle_t; - - // empty default implementation of key cache - // leverages empty base class optimization - template - class TypeValues - { - private: - struct Inaccessable{}; - public: - typedef std::map value_name_map_t; - typedef Inaccessable name_t; - - void setValueName(const std::string& key) {} - std::string getValueName() const { return ""; } - std::string calcValueName(const T& value) const { return ""; } - void clearValueName() const {} - - static bool getValueFromName(const std::string& name, T& value) - { - return false; - } - - static bool valueNamesExist() - { - return false; - } - - static std::vector* getPossibleValues() - { - return NULL; - } - - static value_name_map_t* getValueNames() {return NULL;} - }; - - template > - class TypeValuesHelper - { - public: - typedef typename std::map value_name_map_t; - typedef std::string name_t; - - //TODO: cache key by index to save on param block size - void setValueName(const std::string& value_name) - { - mValueName = value_name; - } - - std::string getValueName() const - { - return mValueName; - } - - std::string calcValueName(const T& value) const - { - value_name_map_t* map = getValueNames(); - for (typename value_name_map_t::iterator it = map->begin(), end_it = map->end(); - it != end_it; - ++it) - { - if (ParamCompare::equals(it->second, value)) - { - return it->first; - } - } - - return ""; - } - - void clearValueName() const - { - mValueName.clear(); - } - - static bool getValueFromName(const std::string& name, T& value) - { - value_name_map_t* map = getValueNames(); - typename value_name_map_t::iterator found_it = map->find(name); - if (found_it == map->end()) return false; - - value = found_it->second; - return true; - } - - static bool valueNamesExist() - { - return !getValueNames()->empty(); - } - - static value_name_map_t* getValueNames() - { - static value_name_map_t sMap; - static bool sInitialized = false; - - if (!sInitialized) - { - sInitialized = true; - DERIVED_TYPE::declareValues(); - } - return &sMap; - } - - static std::vector* getPossibleValues() - { - static std::vector sValues; - - value_name_map_t* map = getValueNames(); - for (typename value_name_map_t::iterator it = map->begin(), end_it = map->end(); - it != end_it; - ++it) - { - sValues.push_back(it->first); - } - return &sValues; - } - - static void declare(const std::string& name, const T& value) - { - (*getValueNames())[name] = value; - } - - protected: - static void getName(const std::string& name, const T& value) - {} - - mutable std::string mValueName; - }; - - class Parser - { - LOG_CLASS(Parser); - - public: - - struct CompareTypeID - { - bool operator()(const std::type_info* lhs, const std::type_info* rhs) const - { - return lhs->before(*rhs); - } - }; - - typedef std::vector > name_stack_t; - typedef std::pair name_stack_range_t; - typedef std::vector possible_values_t; - - typedef bool (*parser_read_func_t)(Parser& parser, void* output); - typedef bool (*parser_write_func_t)(Parser& parser, const void*, name_stack_t&); - typedef boost::function parser_inspect_func_t; - - typedef std::map parser_read_func_map_t; - typedef std::map parser_write_func_map_t; - typedef std::map parser_inspect_func_map_t; - - Parser(parser_read_func_map_t& read_map, parser_write_func_map_t& write_map, parser_inspect_func_map_t& inspect_map) - : mParseSilently(false), - mParserReadFuncs(&read_map), - mParserWriteFuncs(&write_map), - mParserInspectFuncs(&inspect_map) - {} - virtual ~Parser(); - - template bool readValue(T& param) - { - parser_read_func_map_t::iterator found_it = mParserReadFuncs->find(&typeid(T)); - if (found_it != mParserReadFuncs->end()) - { - return found_it->second(*this, (void*)¶m); - } - return false; - } - - template bool writeValue(const T& param, name_stack_t& name_stack) - { - parser_write_func_map_t::iterator found_it = mParserWriteFuncs->find(&typeid(T)); - if (found_it != mParserWriteFuncs->end()) - { - return found_it->second(*this, (const void*)¶m, name_stack); - } - return false; - } - - // dispatch inspection to registered inspection functions, for each parameter in a param block - template bool inspectValue(name_stack_t& name_stack, S32 min_count, S32 max_count, const possible_values_t* possible_values) - { - parser_inspect_func_map_t::iterator found_it = mParserInspectFuncs->find(&typeid(T)); - if (found_it != mParserInspectFuncs->end()) - { - found_it->second(name_stack, min_count, max_count, possible_values); - return true; - } - return false; - } - - virtual std::string getCurrentElementName() = 0; - virtual void parserWarning(const std::string& message); - virtual void parserError(const std::string& message); - void setParseSilently(bool silent) { mParseSilently = silent; } - - protected: - template - void registerParserFuncs(parser_read_func_t read_func, parser_write_func_t write_func = NULL) - { - mParserReadFuncs->insert(std::make_pair(&typeid(T), read_func)); - mParserWriteFuncs->insert(std::make_pair(&typeid(T), write_func)); - } - - template - void registerInspectFunc(parser_inspect_func_t inspect_func) - { - mParserInspectFuncs->insert(std::make_pair(&typeid(T), inspect_func)); - } - - bool mParseSilently; - - private: - parser_read_func_map_t* mParserReadFuncs; - parser_write_func_map_t* mParserWriteFuncs; - parser_inspect_func_map_t* mParserInspectFuncs; - }; - - class Param; - - // various callbacks and constraints associated with an individual param - struct ParamDescriptor - { - struct UserData - { - virtual ~UserData() {} - }; - - typedef bool(*merge_func_t)(Param&, const Param&, bool); - typedef bool(*deserialize_func_t)(Param&, Parser&, const Parser::name_stack_range_t&, bool); - typedef void(*serialize_func_t)(const Param&, Parser&, Parser::name_stack_t&, const Param* diff_param); - typedef void(*inspect_func_t)(const Param&, Parser&, Parser::name_stack_t&, S32 min_count, S32 max_count); - typedef bool(*validation_func_t)(const Param*); - - ParamDescriptor(param_handle_t p, - merge_func_t merge_func, - deserialize_func_t deserialize_func, - serialize_func_t serialize_func, - validation_func_t validation_func, - inspect_func_t inspect_func, - S32 min_count, - S32 max_count); - - ParamDescriptor(); - ~ParamDescriptor(); - - param_handle_t mParamHandle; - merge_func_t mMergeFunc; - deserialize_func_t mDeserializeFunc; - serialize_func_t mSerializeFunc; - inspect_func_t mInspectFunc; - validation_func_t mValidationFunc; - S32 mMinCount; - S32 mMaxCount; - S32 mNumRefs; - UserData* mUserData; - }; - - typedef boost::shared_ptr ParamDescriptorPtr; - - // each derived Block class keeps a static data structure maintaining offsets to various params - class BlockDescriptor - { - public: - BlockDescriptor(); - - typedef enum e_initialization_state - { - UNINITIALIZED, - INITIALIZING, - INITIALIZED - } EInitializationState; - - void aggregateBlockData(BlockDescriptor& src_block_data); - - typedef boost::unordered_map param_map_t; - typedef std::vector param_list_t; - typedef std::list all_params_list_t; - typedef std::vector > param_validation_list_t; - - param_map_t mNamedParams; // parameters with associated names - param_list_t mUnnamedParams; // parameters with_out_ associated names - param_validation_list_t mValidationList; // parameters that must be validated - all_params_list_t mAllParams; // all parameters, owns descriptors - size_t mMaxParamOffset; - EInitializationState mInitializationState; // whether or not static block data has been initialized - class BaseBlock* mCurrentBlockPtr; // pointer to block currently being constructed - }; - - class BaseBlock - { - public: - //TODO: implement in terms of owned_ptr - template - class Lazy - { - public: - Lazy() - : mPtr(NULL) - {} - - ~Lazy() - { - delete mPtr; - } - - Lazy(const Lazy& other) - { - if (other.mPtr) - { - mPtr = new T(*other.mPtr); - } - else - { - mPtr = NULL; - } - } - - Lazy& operator = (const Lazy& other) - { - if (other.mPtr) - { - mPtr = new T(*other.mPtr); - } - else - { - mPtr = NULL; - } - return *this; - } - - bool empty() const - { - return mPtr == NULL; - } - - void set(const T& other) - { - delete mPtr; - mPtr = new T(other); - } - - const T& get() const - { - return ensureInstance(); - } - - T& get() - { - return ensureInstance(); - } - - private: - // lazily allocate an instance of T - T* ensureInstance() const - { - if (mPtr == NULL) - { - mPtr = new T(); - } - return mPtr; - } - - private: - // if you get a compilation error with this, that means you are using a forward declared struct for T - // unfortunately, the type traits we rely on don't work with forward declared typed - //static const int dummy = sizeof(T); - - mutable T* mPtr; - }; - - // "Multiple" constraint types, put here in root class to avoid ambiguity during use - struct AnyAmount - { - enum { minCount = 0 }; - enum { maxCount = U32_MAX }; - }; - - template - struct AtLeast - { - enum { minCount = MIN_AMOUNT }; - enum { maxCount = U32_MAX }; - }; - - template - struct AtMost - { - enum { minCount = 0 }; - enum { maxCount = MAX_AMOUNT }; - }; - - template - struct Between - { - enum { minCount = MIN_AMOUNT }; - enum { maxCount = MAX_AMOUNT }; - }; - - template - struct Exactly - { - enum { minCount = EXACT_COUNT }; - enum { maxCount = EXACT_COUNT }; - }; - - // this typedef identifies derived classes as being blocks - typedef void baseblock_base_class_t; - LOG_CLASS(BaseBlock); - friend class Param; - - virtual ~BaseBlock() {} - bool submitValue(Parser::name_stack_t& name_stack, Parser& p, bool silent=false); - - param_handle_t getHandleFromParam(const Param* param) const; - bool validateBlock(bool emit_errors = true) const; - - Param* getParamFromHandle(const param_handle_t param_handle) - { - if (param_handle == 0) return NULL; - - U8* baseblock_address = reinterpret_cast(this); - return reinterpret_cast(baseblock_address + param_handle); - } - - const Param* getParamFromHandle(const param_handle_t param_handle) const - { - const U8* baseblock_address = reinterpret_cast(this); - return reinterpret_cast(baseblock_address + param_handle); - } - - void addSynonym(Param& param, const std::string& synonym); - - // Blocks can override this to do custom tracking of changes - virtual void paramChanged(const Param& changed_param, bool user_provided) {} - - bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); - void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const; - bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const; - - virtual const BlockDescriptor& mostDerivedBlockDescriptor() const { return selfBlockDescriptor(); } - virtual BlockDescriptor& mostDerivedBlockDescriptor() { return selfBlockDescriptor(); } - - // take all provided params from other and apply to self - bool overwriteFrom(const BaseBlock& other) - { - return false; - } - - // take all provided params that are not already provided, and apply to self - bool fillFrom(const BaseBlock& other) - { - return false; - } - - static void addParam(BlockDescriptor& block_data, ParamDescriptorPtr param, const char* name); - - ParamDescriptorPtr findParamDescriptor(const Param& param); - - protected: - void init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size); - - - bool mergeBlockParam(bool source_provided, bool dst_provided, BlockDescriptor& block_data, const BaseBlock& source, bool overwrite) - { - return mergeBlock(block_data, source, overwrite); - } - // take all provided params from other and apply to self - bool mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite); - - static BlockDescriptor& selfBlockDescriptor() - { - static BlockDescriptor sBlockDescriptor; - return sBlockDescriptor; - } - - private: - const std::string& getParamName(const BlockDescriptor& block_data, const Param* paramp) const; - }; - - template - struct ParamCompare, false > - { - static bool equals(const BaseBlock::Lazy& a, const BaseBlock::Lazy& b) { return !a.empty() || !b.empty(); } - }; - - class Param - { - public: - void setProvided(bool is_provided = true) - { - mIsProvided = is_provided; - enclosingBlock().paramChanged(*this, is_provided); - } - - Param& operator =(const Param& other) - { - mIsProvided = other.mIsProvided; - // don't change mEnclosingblockoffset - return *this; - } - protected: - - bool anyProvided() const { return mIsProvided; } - - Param(BaseBlock* enclosing_block); - - // store pointer to enclosing block as offset to reduce space and allow for quick copying - BaseBlock& enclosingBlock() const - { - const U8* my_addr = reinterpret_cast(this); - // get address of enclosing BLOCK class using stored offset to enclosing BaseBlock class - return *const_cast - (reinterpret_cast - (my_addr - (ptrdiff_t)(S32)mEnclosingBlockOffset)); - } - - private: - friend class BaseBlock; - - U32 mEnclosingBlockOffset:31; - U32 mIsProvided:1; - - }; - - // these templates allow us to distinguish between template parameters - // that derive from BaseBlock and those that don't - template - struct IsBlock - { - static const bool value = false; - struct EmptyBase {}; - typedef EmptyBase base_class_t; - }; - - template - struct IsBlock - { - static const bool value = true; - typedef BaseBlock base_class_t; - }; - - template - struct IsBlock, typename T::baseblock_base_class_t > - { - static const bool value = true; - typedef BaseBlock base_class_t; - }; - - template::value> - class ParamValue : public NAME_VALUE_LOOKUP - { - public: - typedef const T& value_assignment_t; - typedef T value_t; - typedef ParamValue self_t; - - ParamValue(): mValue() {} - ParamValue(value_assignment_t other) : mValue(other) {} - - void setValue(value_assignment_t val) - { - mValue = val; - } - - value_assignment_t getValue() const - { - return mValue; - } - - T& getValue() - { - return mValue; - } - - operator value_assignment_t() const - { - return mValue; - } - - value_assignment_t operator()() const - { - return mValue; - } - - void operator ()(const typename NAME_VALUE_LOOKUP::name_t& name) - { - *this = name; - } - - self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) - { - if (NAME_VALUE_LOOKUP::getValueFromName(name, mValue)) - { - setValueName(name); - } - - return *this; - } - - protected: - T mValue; - }; - - template - class ParamValue - : public T, - public NAME_VALUE_LOOKUP - { - public: - typedef const T& value_assignment_t; - typedef T value_t; - typedef ParamValue self_t; - - ParamValue() - : T(), - mValidated(false) - {} - - ParamValue(value_assignment_t other) - : T(other), - mValidated(false) - {} - - void setValue(value_assignment_t val) - { - *this = val; - } - - value_assignment_t getValue() const - { - return *this; - } - - T& getValue() - { - return *this; - } - - operator value_assignment_t() const - { - return *this; - } - - value_assignment_t operator()() const - { - return *this; - } - - void operator ()(const typename NAME_VALUE_LOOKUP::name_t& name) - { - *this = name; - } - - self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) - { - if (NAME_VALUE_LOOKUP::getValueFromName(name, *this)) - { - setValueName(name); - } - - return *this; - } - - protected: - mutable bool mValidated; // lazy validation flag - }; - - template - class ParamValue - : public NAME_VALUE_LOOKUP - { - public: - typedef const std::string& value_assignment_t; - typedef std::string value_t; - typedef ParamValue self_t; - - ParamValue(): mValue() {} - ParamValue(value_assignment_t other) : mValue(other) {} - - void setValue(value_assignment_t val) - { - if (NAME_VALUE_LOOKUP::getValueFromName(val, mValue)) - { - NAME_VALUE_LOOKUP::setValueName(val); - } - else - { - mValue = val; - } - } - - value_assignment_t getValue() const - { - return mValue; - } - - std::string& getValue() - { - return mValue; - } - - operator value_assignment_t() const - { - return mValue; - } - - value_assignment_t operator()() const - { - return mValue; - } - - protected: - std::string mValue; - }; - - - template > - struct ParamIterator - { - typedef typename std::vector >::const_iterator const_iterator; - typedef typename std::vector >::iterator iterator; - }; - - // specialize for custom parsing/decomposition of specific classes - // e.g. TypedParam has left, top, right, bottom, etc... - template, - bool HAS_MULTIPLE_VALUES = false, - bool VALUE_IS_BLOCK = IsBlock >::value> - class TypedParam - : public Param, - public ParamValue - { - public: - typedef TypedParam self_t; - typedef ParamValue param_value_t; - typedef typename param_value_t::value_assignment_t value_assignment_t; - typedef NAME_VALUE_LOOKUP name_value_lookup_t; - - using param_value_t::operator(); - - TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) - : Param(block_descriptor.mCurrentBlockPtr) - { - if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) - { - ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( - block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), - &mergeWith, - &deserializeParam, - &serializeParam, - validate_func, - &inspectParam, - min_count, max_count)); - BaseBlock::addParam(block_descriptor, param_descriptor, name); - } - - setValue(value); - } - - bool isProvided() const { return Param::anyProvided(); } - - static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) - { - self_t& typed_param = static_cast(param); - // no further names in stack, attempt to parse value now - if (name_stack_range.first == name_stack_range.second) - { - if (parser.readValue(typed_param.getValue())) - { - typed_param.clearValueName(); - typed_param.setProvided(); - return true; - } - - // try to parse a known named value - if(name_value_lookup_t::valueNamesExist()) - { - // try to parse a known named value - std::string name; - if (parser.readValue(name)) - { - // try to parse a per type named value - if (name_value_lookup_t::getValueFromName(name, typed_param.getValue())) - { - typed_param.setValueName(name); - typed_param.setProvided(); - return true; - } - - } - } - } - return false; - } - - static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) - { - const self_t& typed_param = static_cast(param); - if (!typed_param.isProvided()) return; - - if (!name_stack.empty()) - { - name_stack.back().second = true; - } - - std::string key = typed_param.getValueName(); - - // first try to write out name of name/value pair - - if (!key.empty()) - { - if (!diff_param || !ParamCompare::equals(static_cast(diff_param)->getValueName(), key)) - { - parser.writeValue(key, name_stack); - } - } - // then try to serialize value directly - else if (!diff_param || !ParamCompare::equals(typed_param.getValue(), static_cast(diff_param)->getValue())) - { - if (!parser.writeValue(typed_param.getValue(), name_stack)) - { - std::string calculated_key = typed_param.calcValueName(typed_param.getValue()); - if (!diff_param || !ParamCompare::equals(static_cast(diff_param)->getValueName(), calculated_key)) - { - parser.writeValue(calculated_key, name_stack); - } - } - } - } - - static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) - { - // tell parser about our actual type - parser.inspectValue(name_stack, min_count, max_count, NULL); - // then tell it about string-based alternatives ("red", "blue", etc. for LLColor4) - if (name_value_lookup_t::getPossibleValues()) - { - parser.inspectValue(name_stack, min_count, max_count, name_value_lookup_t::getPossibleValues()); - } - } - - void set(value_assignment_t val, bool flag_as_provided = true) - { - param_value_t::clearValueName(); - setValue(val); - setProvided(flag_as_provided); - } - - self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) - { - return static_cast(param_value_t::operator =(name)); - } - - protected: - - self_t& operator =(const self_t& other) - { - param_value_t::operator =(other); - Param::operator =(other); - return *this; - } - - static bool mergeWith(Param& dst, const Param& src, bool overwrite) - { - const self_t& src_typed_param = static_cast(src); - self_t& dst_typed_param = static_cast(dst); - - if (src_typed_param.isProvided() - && (overwrite || !dst_typed_param.isProvided())) - { - dst_typed_param.set(src_typed_param.getValue()); - return true; - } - return false; - } - }; - - // parameter that is a block - template - class TypedParam - : public Param, - public ParamValue - { - public: - typedef ParamValue param_value_t; - typedef typename param_value_t::value_assignment_t value_assignment_t; - typedef TypedParam self_t; - typedef NAME_VALUE_LOOKUP name_value_lookup_t; - - using param_value_t::operator(); - - TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) - : Param(block_descriptor.mCurrentBlockPtr), - param_value_t(value) - { - if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) - { - ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( - block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), - &mergeWith, - &deserializeParam, - &serializeParam, - validate_func, - &inspectParam, - min_count, max_count)); - BaseBlock::addParam(block_descriptor, param_descriptor, name); - } - } - - static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) - { - self_t& typed_param = static_cast(param); - // attempt to parse block... - if(typed_param.deserializeBlock(parser, name_stack_range, new_name)) - { - typed_param.clearValueName(); - typed_param.setProvided(); - return true; - } - - if(name_value_lookup_t::valueNamesExist()) - { - // try to parse a known named value - std::string name; - if (parser.readValue(name)) - { - // try to parse a per type named value - if (name_value_lookup_t::getValueFromName(name, typed_param.getValue())) - { - typed_param.setValueName(name); - typed_param.setProvided(); - return true; - } - - } - } - return false; - } - - static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) - { - const self_t& typed_param = static_cast(param); - if (!typed_param.isProvided()) return; - - if (!name_stack.empty()) - { - name_stack.back().second = true; - } - - std::string key = typed_param.getValueName(); - if (!key.empty()) - { - if (!parser.writeValue(key, name_stack)) - { - return; - } - } - else - { - typed_param.serializeBlock(parser, name_stack, static_cast(diff_param)); - } - } - - static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) - { - // I am a param that is also a block, so just recurse into my contents - const self_t& typed_param = static_cast(param); - typed_param.inspectBlock(parser, name_stack, min_count, max_count); - } - - // a param-that-is-a-block is provided when the user has set one of its child params - // *and* the block as a whole validates - bool isProvided() const - { - // only validate block when it hasn't already passed validation with current data - if (Param::anyProvided() && !param_value_t::mValidated) - { - // a sub-block is "provided" when it has been filled in enough to be valid - param_value_t::mValidated = param_value_t::validateBlock(false); - } - return Param::anyProvided() && param_value_t::mValidated; - } - - // assign block contents to this param-that-is-a-block - void set(value_assignment_t val, bool flag_as_provided = true) - { - setValue(val); - param_value_t::clearValueName(); - // force revalidation of block - // next call to isProvided() will update provision status based on validity - param_value_t::mValidated = false; - setProvided(flag_as_provided); - } - - self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) - { - return static_cast(param_value_t::operator =(name)); - } - - // propagate changed status up to enclosing block - /*virtual*/ void paramChanged(const Param& changed_param, bool user_provided) - { - param_value_t::paramChanged(changed_param, user_provided); - if (user_provided) - { - // a child param has been explicitly changed - // so *some* aspect of this block is now provided - param_value_t::mValidated = false; - setProvided(); - param_value_t::clearValueName(); - } - else - { - Param::enclosingBlock().paramChanged(*this, user_provided); - } - } - - protected: - - self_t& operator =(const self_t& other) - { - param_value_t::operator =(other); - Param::operator =(other); - return *this; - } - - static bool mergeWith(Param& dst, const Param& src, bool overwrite) - { - const self_t& src_typed_param = static_cast(src); - self_t& dst_typed_param = static_cast(dst); - - if (src_typed_param.anyProvided()) - { - if (dst_typed_param.mergeBlockParam(src_typed_param.isProvided(), dst_typed_param.isProvided(), param_value_t::selfBlockDescriptor(), src_typed_param, overwrite)) - { - dst_typed_param.clearValueName(); - dst_typed_param.setProvided(true); - return true; - } - } - return false; - } - }; - - // container of non-block parameters - template - class TypedParam - : public Param - { - public: - typedef TypedParam self_t; - typedef ParamValue param_value_t; - typedef typename std::vector container_t; - typedef const container_t& value_assignment_t; - - typedef typename param_value_t::value_t value_t; - typedef NAME_VALUE_LOOKUP name_value_lookup_t; - - TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) - : Param(block_descriptor.mCurrentBlockPtr) - { - std::copy(value.begin(), value.end(), std::back_inserter(mValues)); - - if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) - { - ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( - block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), - &mergeWith, - &deserializeParam, - &serializeParam, - validate_func, - &inspectParam, - min_count, max_count)); - BaseBlock::addParam(block_descriptor, param_descriptor, name); - } - } - - bool isProvided() const { return Param::anyProvided(); } - - static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) - { - self_t& typed_param = static_cast(param); - value_t value; - // no further names in stack, attempt to parse value now - if (name_stack_range.first == name_stack_range.second) - { - // attempt to read value directly - if (parser.readValue(value)) - { - typed_param.add(value); - return true; - } - - // try to parse a known named value - if(name_value_lookup_t::valueNamesExist()) - { - // try to parse a known named value - std::string name; - if (parser.readValue(name)) - { - // try to parse a per type named value - if (name_value_lookup_t::getValueFromName(name, value)) - { - typed_param.add(value); - typed_param.mValues.back().setValueName(name); - return true; - } - - } - } - } - return false; - } - - static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) - { - const self_t& typed_param = static_cast(param); - if (!typed_param.isProvided() || name_stack.empty()) return; - - for (const_iterator it = typed_param.mValues.begin(), end_it = typed_param.mValues.end(); - it != end_it; - ++it) - { - std::string key = it->getValueName(); - name_stack.back().second = true; - - if(key.empty()) - // not parsed via name values, write out value directly - { - bool value_written = parser.writeValue(*it, name_stack); - if (!value_written) - { - std::string calculated_key = it->calcValueName(it->getValue()); - if (!parser.writeValue(calculated_key, name_stack)) - { - break; - } - } - } - else - { - if(!parser.writeValue(key, name_stack)) - { - break; - } - } - } - } - - static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) - { - parser.inspectValue(name_stack, min_count, max_count, NULL); - if (name_value_lookup_t::getPossibleValues()) - { - parser.inspectValue(name_stack, min_count, max_count, name_value_lookup_t::getPossibleValues()); - } - } - - void set(value_assignment_t val, bool flag_as_provided = true) - { - mValues = val; - setProvided(flag_as_provided); - } - - param_value_t& add() - { - mValues.push_back(param_value_t(value_t())); - Param::setProvided(); - return mValues.back(); - } - - void add(const value_t& item) - { - param_value_t param_value; - param_value.setValue(item); - mValues.push_back(param_value); - setProvided(); - } - - void add(const typename name_value_lookup_t::name_t& name) - { - value_t value; - - // try to parse a per type named value - if (name_value_lookup_t::getValueFromName(name, value)) - { - add(value); - mValues.back().setValueName(name); - } - } - - // implicit conversion - operator value_assignment_t() const { return mValues; } - // explicit conversion - value_assignment_t operator()() const { return mValues; } - - typedef typename container_t::iterator iterator; - typedef typename container_t::const_iterator const_iterator; - iterator begin() { return mValues.begin(); } - iterator end() { return mValues.end(); } - const_iterator begin() const { return mValues.begin(); } - const_iterator end() const { return mValues.end(); } - bool empty() const { return mValues.empty(); } - size_t size() const { return mValues.size(); } - - U32 numValidElements() const - { - return mValues.size(); - } - - protected: - static bool mergeWith(Param& dst, const Param& src, bool overwrite) - { - const self_t& src_typed_param = static_cast(src); - self_t& dst_typed_param = static_cast(dst); - - if (overwrite) - { - std::copy(src_typed_param.begin(), src_typed_param.end(), std::back_inserter(dst_typed_param.mValues)); - } - else - { - container_t new_values(src_typed_param.mValues); - std::copy(dst_typed_param.begin(), dst_typed_param.end(), std::back_inserter(new_values)); - std::swap(dst_typed_param.mValues, new_values); - } - - if (src_typed_param.begin() != src_typed_param.end()) - { - dst_typed_param.setProvided(); - } - return true; - } - - container_t mValues; - }; - - // container of block parameters - template - class TypedParam - : public Param - { - public: - typedef TypedParam self_t; - typedef ParamValue param_value_t; - typedef typename std::vector container_t; - typedef const container_t& value_assignment_t; - typedef typename param_value_t::value_t value_t; - typedef NAME_VALUE_LOOKUP name_value_lookup_t; - - TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) - : Param(block_descriptor.mCurrentBlockPtr) - { - std::copy(value.begin(), value.end(), back_inserter(mValues)); - - if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) - { - ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( - block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), - &mergeWith, - &deserializeParam, - &serializeParam, - validate_func, - &inspectParam, - min_count, max_count)); - BaseBlock::addParam(block_descriptor, param_descriptor, name); - } - } - - bool isProvided() const { return Param::anyProvided(); } - - static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) - { - self_t& typed_param = static_cast(param); - bool new_value = false; - - if (new_name || typed_param.mValues.empty()) - { - new_value = true; - typed_param.mValues.push_back(value_t()); - } - - param_value_t& value = typed_param.mValues.back(); - - // attempt to parse block... - if(value.deserializeBlock(parser, name_stack_range, new_name)) - { - typed_param.setProvided(); - return true; - } - else if(name_value_lookup_t::valueNamesExist()) - { - // try to parse a known named value - std::string name; - if (parser.readValue(name)) - { - // try to parse a per type named value - if (name_value_lookup_t::getValueFromName(name, value.getValue())) - { - typed_param.mValues.back().setValueName(name); - typed_param.setProvided(); - return true; - } - - } - } - - if (new_value) - { // failed to parse new value, pop it off - typed_param.mValues.pop_back(); - } - - return false; - } - - static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) - { - const self_t& typed_param = static_cast(param); - if (!typed_param.isProvided() || name_stack.empty()) return; - - for (const_iterator it = typed_param.mValues.begin(), end_it = typed_param.mValues.end(); - it != end_it; - ++it) - { - name_stack.back().second = true; - - std::string key = it->getValueName(); - if (!key.empty()) - { - parser.writeValue(key, name_stack); - } - // Not parsed via named values, write out value directly - // NOTE: currently we don't worry about removing default values in Multiple - else - { - it->serializeBlock(parser, name_stack, NULL); - } - } - } - - static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) - { - // I am a vector of blocks, so describe my contents recursively - param_value_t(value_t()).inspectBlock(parser, name_stack, min_count, max_count); - } - - void set(value_assignment_t val, bool flag_as_provided = true) - { - mValues = val; - setProvided(flag_as_provided); - } - - param_value_t& add() - { - mValues.push_back(value_t()); - setProvided(); - return mValues.back(); - } - - void add(const value_t& item) - { - mValues.push_back(item); - setProvided(); - } - - void add(const typename name_value_lookup_t::name_t& name) - { - value_t value; - - // try to parse a per type named value - if (name_value_lookup_t::getValueFromName(name, value)) - { - add(value); - mValues.back().setValueName(name); - } - } - - // implicit conversion - operator value_assignment_t() const { return mValues; } - // explicit conversion - value_assignment_t operator()() const { return mValues; } - - typedef typename container_t::iterator iterator; - typedef typename container_t::const_iterator const_iterator; - iterator begin() { return mValues.begin(); } - iterator end() { return mValues.end(); } - const_iterator begin() const { return mValues.begin(); } - const_iterator end() const { return mValues.end(); } - bool empty() const { return mValues.empty(); } - size_t size() const { return mValues.size(); } - - U32 numValidElements() const - { - U32 count = 0; - for (const_iterator it = mValues.begin(), end_it = mValues.end(); - it != end_it; - ++it) - { - if(it->validateBlock(false)) count++; - } - return count; - } - - protected: - - static bool mergeWith(Param& dst, const Param& src, bool overwrite) - { - const self_t& src_typed_param = static_cast(src); - self_t& dst_typed_param = static_cast(dst); - - if (overwrite) - { - std::copy(src_typed_param.begin(), src_typed_param.end(), std::back_inserter(dst_typed_param.mValues)); - } - else - { - container_t new_values(src_typed_param.mValues); - std::copy(dst_typed_param.begin(), dst_typed_param.end(), std::back_inserter(new_values)); - std::swap(dst_typed_param.mValues, new_values); - } - - if (src_typed_param.begin() != src_typed_param.end()) - { - dst_typed_param.setProvided(); - } - - return true; - } - - container_t mValues; - }; - - template - class ChoiceBlock : public BASE_BLOCK - { - typedef ChoiceBlock self_t; - typedef ChoiceBlock enclosing_block_t; - typedef BASE_BLOCK base_block_t; - - LOG_CLASS(self_t); - public: - // take all provided params from other and apply to self - bool overwriteFrom(const self_t& other) - { - return static_cast(this)->mergeBlock(selfBlockDescriptor(), other, true); - } - - // take all provided params that are not already provided, and apply to self - bool fillFrom(const self_t& other) - { - return static_cast(this)->mergeBlock(selfBlockDescriptor(), other, false); - } - - bool mergeBlockParam(bool source_provided, bool dest_provided, BlockDescriptor& block_data, const self_t& source, bool overwrite) - { - bool source_override = source_provided && (overwrite || !dest_provided); - - if (source_override || source.mCurChoice == mCurChoice) - { - return mergeBlock(block_data, source, overwrite); - } - return false; - } - - // merge with other block - bool mergeBlock(BlockDescriptor& block_data, const self_t& other, bool overwrite) - { - mCurChoice = other.mCurChoice; - return base_block_t::mergeBlock(selfBlockDescriptor(), other, overwrite); - } - - // clear out old choice when param has changed - /*virtual*/ void paramChanged(const Param& changed_param, bool user_provided) - { - param_handle_t changed_param_handle = base_block_t::getHandleFromParam(&changed_param); - // if we have a new choice... - if (changed_param_handle != mCurChoice) - { - // clear provided flag on previous choice - Param* previous_choice = base_block_t::getParamFromHandle(mCurChoice); - if (previous_choice) - { - previous_choice->setProvided(false); - } - mCurChoice = changed_param_handle; - } - base_block_t::paramChanged(changed_param, user_provided); - } - - virtual const BlockDescriptor& mostDerivedBlockDescriptor() const { return selfBlockDescriptor(); } - virtual BlockDescriptor& mostDerivedBlockDescriptor() { return selfBlockDescriptor(); } - - protected: - ChoiceBlock() - : mCurChoice(0) - { - BaseBlock::init(selfBlockDescriptor(), base_block_t::selfBlockDescriptor(), sizeof(DERIVED_BLOCK)); - } - - // Alternatives are mutually exclusive wrt other Alternatives in the same block. - // One alternative in a block will always have isChosen() == true. - // At most one alternative in a block will have isProvided() == true. - template > - class Alternative : public TypedParam - { - public: - friend class ChoiceBlock; - - typedef Alternative self_t; - typedef TypedParam >::value> super_t; - typedef typename super_t::value_assignment_t value_assignment_t; - - using super_t::operator =; - - explicit Alternative(const char* name = "", value_assignment_t val = defaultValue()) - : super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, val, NULL, 0, 1), - mOriginalValue(val) - { - // assign initial choice to first declared option - DERIVED_BLOCK* blockp = ((DERIVED_BLOCK*)DERIVED_BLOCK::selfBlockDescriptor().mCurrentBlockPtr); - if (LL_UNLIKELY(DERIVED_BLOCK::selfBlockDescriptor().mInitializationState == BlockDescriptor::INITIALIZING)) - { - if(blockp->mCurChoice == 0) - { - blockp->mCurChoice = Param::enclosingBlock().getHandleFromParam(this); - } - } - } - - void choose() - { - static_cast(Param::enclosingBlock()).paramChanged(*this, true); - } - - void chooseAs(value_assignment_t val) - { - super_t::set(val); - } - - void operator =(value_assignment_t val) - { - super_t::set(val); - } - - void operator()(typename super_t::value_assignment_t val) - { - super_t::set(val); - } - - operator value_assignment_t() const - { - return (*this)(); - } - - value_assignment_t operator()() const - { - if (static_cast(Param::enclosingBlock()).getCurrentChoice() == this) - { - return super_t::getValue(); - } - return mOriginalValue; - } - - bool isChosen() const - { - return static_cast(Param::enclosingBlock()).getCurrentChoice() == this; - } - - private: - T mOriginalValue; - }; - - protected: - static BlockDescriptor& selfBlockDescriptor() - { - static BlockDescriptor sBlockDescriptor; - return sBlockDescriptor; - } - - private: - param_handle_t mCurChoice; - - const Param* getCurrentChoice() const - { - return base_block_t::getParamFromHandle(mCurChoice); - } - }; - - template - class Block - : public BASE_BLOCK - { - typedef Block self_t; - typedef Block block_t; - - public: - typedef BASE_BLOCK base_block_t; - - // take all provided params from other and apply to self - bool overwriteFrom(const self_t& other) - { - return static_cast(this)->mergeBlock(selfBlockDescriptor(), other, true); - } - - // take all provided params that are not already provided, and apply to self - bool fillFrom(const self_t& other) - { - return static_cast(this)->mergeBlock(selfBlockDescriptor(), other, false); - } - - virtual const BlockDescriptor& mostDerivedBlockDescriptor() const { return selfBlockDescriptor(); } - virtual BlockDescriptor& mostDerivedBlockDescriptor() { return selfBlockDescriptor(); } - - protected: - Block() - { - //#pragma message("Parsing LLInitParam::Block") - BaseBlock::init(selfBlockDescriptor(), BASE_BLOCK::selfBlockDescriptor(), sizeof(DERIVED_BLOCK)); - } - - // - // Nested classes for declaring parameters - // - template > - class Optional : public TypedParam - { - public: - typedef TypedParam >::value> super_t; - typedef typename super_t::value_assignment_t value_assignment_t; - - using super_t::operator(); - using super_t::operator =; - - explicit Optional(const char* name = "", value_assignment_t val = defaultValue()) - : super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, val, NULL, 0, 1) - { - //#pragma message("Parsing LLInitParam::Block::Optional") - } - - Optional& operator =(value_assignment_t val) - { - set(val); - return *this; - } - - DERIVED_BLOCK& operator()(value_assignment_t val) - { - super_t::set(val); - return static_cast(Param::enclosingBlock()); - } - }; - - template > - class Mandatory : public TypedParam - { - public: - typedef TypedParam >::value> super_t; - typedef Mandatory self_t; - typedef typename super_t::value_assignment_t value_assignment_t; - - using super_t::operator(); - using super_t::operator =; - - // mandatory parameters require a name to be parseable - explicit Mandatory(const char* name = "", value_assignment_t val = defaultValue()) - : super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, val, &validate, 1, 1) - {} - - Mandatory& operator =(value_assignment_t val) - { - set(val); - return *this; - } - - DERIVED_BLOCK& operator()(typename super_t::value_assignment_t val) - { - super_t::set(val); - return static_cast(Param::enclosingBlock()); - } - - static bool validate(const Param* p) - { - // valid only if provided - return static_cast(p)->isProvided(); - } - - }; - - template > - class Multiple : public TypedParam - { - public: - typedef TypedParam >::value> super_t; - typedef Multiple self_t; - typedef typename super_t::container_t container_t; - typedef typename super_t::value_assignment_t value_assignment_t; - typedef typename super_t::iterator iterator; - typedef typename super_t::const_iterator const_iterator; - - explicit Multiple(const char* name = "") - : super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, container_t(), &validate, RANGE::minCount, RANGE::maxCount) - {} - - Multiple& operator =(value_assignment_t val) - { - set(val); - return *this; - } - - DERIVED_BLOCK& operator()(typename super_t::value_assignment_t val) - { - super_t::set(val); - return static_cast(Param::enclosingBlock()); - } - - static bool validate(const Param* paramp) - { - U32 num_valid = ((super_t*)paramp)->numValidElements(); - return RANGE::minCount <= num_valid && num_valid <= RANGE::maxCount; - } - }; - - class Deprecated : public Param - { - public: - explicit Deprecated(const char* name) - : Param(DERIVED_BLOCK::selfBlockDescriptor().mCurrentBlockPtr) - { - BlockDescriptor& block_descriptor = DERIVED_BLOCK::selfBlockDescriptor(); - if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) - { - ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( - block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), - NULL, - &deserializeParam, - NULL, - NULL, - NULL, - 0, S32_MAX)); - BaseBlock::addParam(block_descriptor, param_descriptor, name); - } - } - - static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) - { - if (name_stack_range.first == name_stack_range.second) - { - //std::string message = llformat("Deprecated value %s ignored", getName().c_str()); - //parser.parserWarning(message); - return true; - } - - return false; - } - }; - - // different semantics for documentation purposes, but functionally identical - typedef Deprecated Ignored; - - protected: - static BlockDescriptor& selfBlockDescriptor() - { - static BlockDescriptor sBlockDescriptor; - return sBlockDescriptor; - } - - template - void changeDefault(TypedParam& param, - typename TypedParam::value_assignment_t value) - { - if (!param.isProvided()) - { - param.set(value, false); - } - } - - }; - - template - class BatchBlock - : public Block - { - public: - typedef BatchBlock self_t; - typedef Block super_t; - - BatchBlock() - {} - - bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name) - { - if (new_name) - { - // reset block - *static_cast(this) = defaultBatchValue(); - } - return super_t::deserializeBlock(p, name_stack_range, new_name); - } - - bool mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) - { - if (overwrite) - { - *static_cast(this) = defaultBatchValue(); - // merge individual parameters into destination - return super_t::mergeBlock(super_t::selfBlockDescriptor(), other, overwrite); - } - return false; - } - protected: - static const DERIVED_BLOCK& defaultBatchValue() - { - static DERIVED_BLOCK default_value; - return default_value; - } - }; - - // FIXME: this specialization is not currently used, as it only matches against the BatchBlock base class - // and not the derived class with the actual params - template - class ParamValue , - NAME_VALUE_LOOKUP, - true> - : public NAME_VALUE_LOOKUP, - protected BatchBlock - { - public: - typedef BatchBlock block_t; - typedef const BatchBlock& value_assignment_t; - typedef block_t value_t; - - ParamValue() - : block_t(), - mValidated(false) - {} - - ParamValue(value_assignment_t other) - : block_t(other), - mValidated(false) - { - } - - void setValue(value_assignment_t val) - { - *this = val; - } - - value_assignment_t getValue() const - { - return *this; - } - - BatchBlock& getValue() - { - return *this; - } - - operator value_assignment_t() const - { - return *this; - } - - value_assignment_t operator()() const - { - return *this; - } - - protected: - mutable bool mValidated; // lazy validation flag - }; - - template - class ParamValue , - TypeValues, - IS_BLOCK> - : public IsBlock::base_class_t - { - public: - typedef ParamValue , TypeValues, false> self_t; - typedef const T& value_assignment_t; - typedef T value_t; - - ParamValue() - : mValue(), - mValidated(false) - {} - - ParamValue(value_assignment_t other) - : mValue(other), - mValidated(false) - {} - - void setValue(value_assignment_t val) - { - mValue.set(val); - } - - value_assignment_t getValue() const - { - return mValue.get(); - } - - T& getValue() - { - return mValue.get(); - } - - operator value_assignment_t() const - { - return mValue.get(); - } - - value_assignment_t operator()() const - { - return mValue.get(); - } - - bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name) - { - return mValue.get().deserializeBlock(p, name_stack_range, new_name); - } - - void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const - { - if (mValue.empty()) return; - - mValue.get().serializeBlock(p, name_stack, diff_block); - } - - bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const - { - if (mValue.empty()) return false; - - return mValue.get().inspectBlock(p, name_stack, min_count, max_count); - } - - protected: - mutable bool mValidated; // lazy validation flag - - private: - BaseBlock::Lazy mValue; - }; - - template <> - class ParamValue , - false> - : public TypeValues, - public BaseBlock - { - public: - typedef ParamValue, false> self_t; - typedef const LLSD& value_assignment_t; - - ParamValue() - : mValidated(false) - {} - - ParamValue(value_assignment_t other) - : mValue(other), - mValidated(false) - {} - - void setValue(value_assignment_t val) { mValue = val; } - - value_assignment_t getValue() const { return mValue; } - LLSD& getValue() { return mValue; } - - operator value_assignment_t() const { return mValue; } - value_assignment_t operator()() const { return mValue; } - - - // block param interface - bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); - void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const; - bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const - { - //TODO: implement LLSD params as schema type Any - return true; - } - - protected: - mutable bool mValidated; // lazy validation flag - - private: - static void serializeElement(Parser& p, const LLSD& sd, Parser::name_stack_t& name_stack); - - LLSD mValue; - }; - - template - class CustomParamValue - : public Block > >, - public TypeValues - { - public: - typedef enum e_value_age - { - VALUE_NEEDS_UPDATE, // mValue needs to be refreshed from the block parameters - VALUE_AUTHORITATIVE, // mValue holds the authoritative value (which has been replicated to the block parameters via updateBlockFromValue) - BLOCK_AUTHORITATIVE // mValue is derived from the block parameters, which are authoritative - } EValueAge; - - typedef ParamValue > derived_t; - typedef CustomParamValue self_t; - typedef Block block_t; - typedef const T& value_assignment_t; - typedef T value_t; - - - CustomParamValue(const T& value = T()) - : mValue(value), - mValueAge(VALUE_AUTHORITATIVE), - mValidated(false) - {} - - bool deserializeBlock(Parser& parser, Parser::name_stack_range_t name_stack_range, bool new_name) - { - derived_t& typed_param = static_cast(*this); - // try to parse direct value T - if (name_stack_range.first == name_stack_range.second) - { - if(parser.readValue(typed_param.mValue)) - { - typed_param.mValueAge = VALUE_AUTHORITATIVE; - typed_param.updateBlockFromValue(false); - - typed_param.clearValueName(); - - return true; - } - } - - // fall back on parsing block components for T - return typed_param.BaseBlock::deserializeBlock(parser, name_stack_range, new_name); - } - - void serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const - { - const derived_t& typed_param = static_cast(*this); - const derived_t* diff_param = static_cast(diff_block); - - std::string key = typed_param.getValueName(); - - // first try to write out name of name/value pair - if (!key.empty()) - { - if (!diff_param || !ParamCompare::equals(diff_param->getValueName(), key)) - { - parser.writeValue(key, name_stack); - } - } - // then try to serialize value directly - else if (!diff_param || !ParamCompare::equals(typed_param.getValue(), diff_param->getValue())) - { - - if (!parser.writeValue(typed_param.getValue(), name_stack)) - { - //RN: *always* serialize provided components of BlockValue (don't pass diff_param on), - // since these tend to be viewed as the constructor arguments for the value T. It seems - // cleaner to treat the uniqueness of a BlockValue according to the generated value, and - // not the individual components. This way will not - // be exported as , since it was probably the intent of the user to - // be specific about the RGB color values. This also fixes an issue where we distinguish - // between rect.left not being provided and rect.left being explicitly set to 0 (same as default) - - if (typed_param.mValueAge == VALUE_AUTHORITATIVE) - { - // if the value is authoritative but the parser doesn't accept the value type - // go ahead and make a copy, and splat the value out to its component params - // and serialize those params - derived_t copy(typed_param); - copy.updateBlockFromValue(true); - copy.block_t::serializeBlock(parser, name_stack, NULL); - } - else - { - block_t::serializeBlock(parser, name_stack, NULL); - } - } - } - } - - bool inspectBlock(Parser& parser, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const - { - // first, inspect with actual type... - parser.inspectValue(name_stack, min_count, max_count, NULL); - if (TypeValues::getPossibleValues()) - { - //...then inspect with possible string values... - parser.inspectValue(name_stack, min_count, max_count, TypeValues::getPossibleValues()); - } - // then recursively inspect contents... - return block_t::inspectBlock(parser, name_stack, min_count, max_count); - } - - bool validateBlock(bool emit_errors = true) const - { - if (mValueAge == VALUE_NEEDS_UPDATE) - { - if (block_t::validateBlock(emit_errors)) - { - // clear stale keyword associated with old value - TypeValues::clearValueName(); - mValueAge = BLOCK_AUTHORITATIVE; - static_cast(const_cast(this))->updateValueFromBlock(); - return true; - } - else - { - //block value incomplete, so not considered provided - // will attempt to revalidate on next call to isProvided() - return false; - } - } - else - { - // we have a valid value in hand - return true; - } - } - - // propagate change status up to enclosing block - /*virtual*/ void paramChanged(const Param& changed_param, bool user_provided) - { - BaseBlock::paramChanged(changed_param, user_provided); - if (user_provided) - { - // a parameter changed, so our value is out of date - mValueAge = VALUE_NEEDS_UPDATE; - } - } - - void setValue(value_assignment_t val) - { - derived_t& typed_param = static_cast(*this); - // set param version number to be up to date, so we ignore block contents - mValueAge = VALUE_AUTHORITATIVE; - mValue = val; - typed_param.clearValueName(); - static_cast(this)->updateBlockFromValue(false); - } - - value_assignment_t getValue() const - { - validateBlock(true); - return mValue; - } - - T& getValue() - { - validateBlock(true); - return mValue; - } - - operator value_assignment_t() const - { - return getValue(); - } - - value_assignment_t operator()() const - { - return getValue(); - } - - protected: - - // use this from within updateValueFromBlock() to set the value without making it authoritative - void updateValue(value_assignment_t value) - { - mValue = value; - } - - bool mergeBlockParam(bool source_provided, bool dst_provided, BlockDescriptor& block_data, const BaseBlock& source, bool overwrite) - { - bool source_override = source_provided && (overwrite || !dst_provided); - - const derived_t& src_typed_param = static_cast(source); - - if (source_override && src_typed_param.mValueAge == VALUE_AUTHORITATIVE) - { - // copy value over - setValue(src_typed_param.getValue()); - return true; - } - // merge individual parameters into destination - if (mValueAge == VALUE_AUTHORITATIVE) - { - static_cast(this)->updateBlockFromValue(dst_provided); - } - return mergeBlock(block_data, source, overwrite); - } - - bool mergeBlock(BlockDescriptor& block_data, const BaseBlock& source, bool overwrite) - { - return block_t::mergeBlock(block_data, source, overwrite); - } - - mutable bool mValidated; // lazy validation flag - - private: - mutable T mValue; - mutable EValueAge mValueAge; - }; -} - - -#endif // LL_LLPARAM_H diff --git a/indra/llxuixml/llregistry.h b/indra/llxuixml/llregistry.h deleted file mode 100644 index 36ce6a97b7..0000000000 --- a/indra/llxuixml/llregistry.h +++ /dev/null @@ -1,351 +0,0 @@ -/** - * @file llregistry.h - * @brief template classes for registering name, value pairs in nested scopes, statically, etc. - * - * $LicenseInfo:firstyear=2001&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_LLREGISTRY_H -#define LL_LLREGISTRY_H - -#include - -#include -#include "llsingleton.h" - -template -class LLRegistryDefaultComparator -{ - bool operator()(const T& lhs, const T& rhs) { return lhs < rhs; } -}; - -template > -class LLRegistry -{ -public: - typedef LLRegistry registry_t; - typedef typename boost::add_reference::type>::type ref_const_key_t; - typedef typename boost::add_reference::type>::type ref_const_value_t; - typedef typename boost::add_reference::type ref_value_t; - typedef typename boost::add_pointer::type>::type ptr_const_value_t; - typedef typename boost::add_pointer::type ptr_value_t; - - class Registrar - { - friend class LLRegistry; - public: - typedef typename std::map registry_map_t; - - bool add(ref_const_key_t key, ref_const_value_t value) - { - if (mMap.insert(std::make_pair(key, value)).second == false) - { - llwarns << "Tried to register " << key << " but it was already registered!" << llendl; - return false; - } - return true; - } - - void remove(ref_const_key_t key) - { - mMap.erase(key); - } - - void replace(ref_const_key_t key, ref_const_value_t value) - { - mMap[key] = value; - } - - typename registry_map_t::const_iterator beginItems() const - { - return mMap.begin(); - } - - typename registry_map_t::const_iterator endItems() const - { - return mMap.end(); - } - - protected: - ptr_value_t getValue(ref_const_key_t key) - { - typename registry_map_t::iterator found_it = mMap.find(key); - if (found_it != mMap.end()) - { - return &(found_it->second); - } - return NULL; - } - - ptr_const_value_t getValue(ref_const_key_t key) const - { - typename registry_map_t::const_iterator found_it = mMap.find(key); - if (found_it != mMap.end()) - { - return &(found_it->second); - } - return NULL; - } - - // if the registry is used to store pointers, and null values are valid entries - // then use this function to check the existence of an entry - bool exists(ref_const_key_t key) const - { - return mMap.find(key) != mMap.end(); - } - - bool empty() const - { - return mMap.empty(); - } - - protected: - // use currentRegistrar() or defaultRegistrar() - Registrar() {} - ~Registrar() {} - - private: - registry_map_t mMap; - }; - - typedef typename std::list scope_list_t; - typedef typename std::list::iterator scope_list_iterator_t; - typedef typename std::list::const_iterator scope_list_const_iterator_t; - - LLRegistry() - {} - - ~LLRegistry() {} - - ptr_value_t getValue(ref_const_key_t key) - { - for(scope_list_iterator_t it = mActiveScopes.begin(); - it != mActiveScopes.end(); - ++it) - { - ptr_value_t valuep = (*it)->getValue(key); - if (valuep != NULL) return valuep; - } - return mDefaultRegistrar.getValue(key); - } - - ptr_const_value_t getValue(ref_const_key_t key) const - { - for(scope_list_const_iterator_t it = mActiveScopes.begin(); - it != mActiveScopes.end(); - ++it) - { - ptr_value_t valuep = (*it)->getValue(key); - if (valuep != NULL) return valuep; - } - return mDefaultRegistrar.getValue(key); - } - - bool exists(ref_const_key_t key) const - { - for(scope_list_const_iterator_t it = mActiveScopes.begin(); - it != mActiveScopes.end(); - ++it) - { - if ((*it)->exists(key)) return true; - } - - return mDefaultRegistrar.exists(key); - } - - bool empty() const - { - for(scope_list_const_iterator_t it = mActiveScopes.begin(); - it != mActiveScopes.end(); - ++it) - { - if (!(*it)->empty()) return false; - } - - return mDefaultRegistrar.empty(); - } - - - Registrar& defaultRegistrar() - { - return mDefaultRegistrar; - } - - const Registrar& defaultRegistrar() const - { - return mDefaultRegistrar; - } - - - Registrar& currentRegistrar() - { - if (!mActiveScopes.empty()) - { - return *mActiveScopes.front(); - } - - return mDefaultRegistrar; - } - - const Registrar& currentRegistrar() const - { - if (!mActiveScopes.empty()) - { - return *mActiveScopes.front(); - } - - return mDefaultRegistrar; - } - - -protected: - void addScope(Registrar* scope) - { - // newer scopes go up front - mActiveScopes.insert(mActiveScopes.begin(), scope); - } - - void removeScope(Registrar* scope) - { - // O(N) but should be near the beggining and N should be small and this is safer than storing iterators - scope_list_iterator_t iter = std::find(mActiveScopes.begin(), mActiveScopes.end(), scope); - if (iter != mActiveScopes.end()) - { - mActiveScopes.erase(iter); - } - } - -private: - scope_list_t mActiveScopes; - Registrar mDefaultRegistrar; -}; - -template > -class LLRegistrySingleton - : public LLRegistry, - public LLSingleton -{ - friend class LLSingleton; -public: - typedef LLRegistry registry_t; - typedef const KEY& ref_const_key_t; - typedef const VALUE& ref_const_value_t; - typedef VALUE* ptr_value_t; - typedef const VALUE* ptr_const_value_t; - typedef LLSingleton singleton_t; - - class ScopedRegistrar : public registry_t::Registrar - { - public: - ScopedRegistrar(bool push_scope = true) - { - if (push_scope) - { - pushScope(); - } - } - - ~ScopedRegistrar() - { - if (!singleton_t::destroyed()) - { - popScope(); - } - } - - void pushScope() - { - singleton_t::instance().addScope(this); - } - - void popScope() - { - singleton_t::instance().removeScope(this); - } - - ptr_value_t getValueFromScope(ref_const_key_t key) - { - return getValue(key); - } - - ptr_const_value_t getValueFromScope(ref_const_key_t key) const - { - return getValue(key); - } - - private: - typename std::list::iterator mListIt; - }; - - class StaticRegistrar : public registry_t::Registrar - { - public: - virtual ~StaticRegistrar() {} - StaticRegistrar(ref_const_key_t key, ref_const_value_t value) - { - singleton_t::instance().mStaticScope->add(key, value); - } - }; - - // convenience functions - typedef typename LLRegistry::Registrar& ref_registrar_t; - static ref_registrar_t currentRegistrar() - { - return singleton_t::instance().registry_t::currentRegistrar(); - } - - static ref_registrar_t defaultRegistrar() - { - return singleton_t::instance().registry_t::defaultRegistrar(); - } - - static ptr_value_t getValue(ref_const_key_t key) - { - return singleton_t::instance().registry_t::getValue(key); - } - -protected: - // DERIVED_TYPE needs to derive from LLRegistrySingleton - LLRegistrySingleton() - : mStaticScope(NULL) - {} - - virtual void initSingleton() - { - mStaticScope = new ScopedRegistrar(); - } - - virtual ~LLRegistrySingleton() - { - delete mStaticScope; - } - -private: - ScopedRegistrar* mStaticScope; -}; - -// helper macro for doing static registration -#define GLUED_TOKEN(x, y) x ## y -#define GLUE_TOKENS(x, y) GLUED_TOKEN(x, y) -#define LLREGISTER_STATIC(REGISTRY, KEY, VALUE) static REGISTRY::StaticRegistrar GLUE_TOKENS(reg, __LINE__)(KEY, VALUE); - -#endif diff --git a/indra/llxuixml/lltrans.cpp b/indra/llxuixml/lltrans.cpp deleted file mode 100644 index 5388069c24..0000000000 --- a/indra/llxuixml/lltrans.cpp +++ /dev/null @@ -1,295 +0,0 @@ -/** - * @file lltrans.cpp - * @brief LLTrans implementation - * - * $LicenseInfo:firstyear=2000&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 "lltrans.h" - -#include "llfasttimer.h" // for call count statistics -#include "llxuiparser.h" -#include "llsd.h" -#include "llxmlnode.h" - -#include - -LLTrans::template_map_t LLTrans::sStringTemplates; -LLStringUtil::format_map_t LLTrans::sDefaultArgs; - -struct StringDef : public LLInitParam::Block -{ - Mandatory name; - Mandatory value; - - StringDef() - : name("name"), - value("value") - {} -}; - -struct StringTable : public LLInitParam::Block -{ - Multiple strings; - StringTable() - : strings("string") - {} -}; - -//static -bool LLTrans::parseStrings(LLXMLNodePtr &root, const std::set& default_args) -{ - std::string xml_filename = "(strings file)"; - if (!root->hasName("strings")) - { - llerrs << "Invalid root node name in " << xml_filename - << ": was " << root->getName() << ", expected \"strings\"" << llendl; - } - - StringTable string_table; - LLXUIParser parser; - parser.readXUI(root, string_table, xml_filename); - - if (!string_table.validateBlock()) - { - llerrs << "Problem reading strings: " << xml_filename << llendl; - return false; - } - - sStringTemplates.clear(); - sDefaultArgs.clear(); - - for(LLInitParam::ParamIterator::const_iterator it = string_table.strings.begin(); - it != string_table.strings.end(); - ++it) - { - LLTransTemplate xml_template(it->name, it->value); - sStringTemplates[xml_template.mName] = xml_template; - - std::set::const_iterator iter = default_args.find(xml_template.mName); - if (iter != default_args.end()) - { - std::string name = *iter; - if (name[0] != '[') - name = llformat("[%s]",name.c_str()); - sDefaultArgs[name] = xml_template.mText; - } - } - - return true; -} - - -//static -bool LLTrans::parseLanguageStrings(LLXMLNodePtr &root) -{ - std::string xml_filename = "(language strings file)"; - if (!root->hasName("strings")) - { - llerrs << "Invalid root node name in " << xml_filename - << ": was " << root->getName() << ", expected \"strings\"" << llendl; - } - - StringTable string_table; - LLXUIParser parser; - parser.readXUI(root, string_table, xml_filename); - - if (!string_table.validateBlock()) - { - llerrs << "Problem reading strings: " << xml_filename << llendl; - return false; - } - - for(LLInitParam::ParamIterator::const_iterator it = string_table.strings.begin(); - it != string_table.strings.end(); - ++it) - { - // share the same map with parseStrings() so we can search the strings using the same getString() function.- angela - LLTransTemplate xml_template(it->name, it->value); - sStringTemplates[xml_template.mName] = xml_template; - } - - return true; -} - - - -static LLFastTimer::DeclareTimer FTM_GET_TRANS("Translate string"); - -//static -std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args) -{ - // Don't care about time as much as call count. Make sure we're not - // calling LLTrans::getString() in an inner loop. JC - LLFastTimer timer(FTM_GET_TRANS); - - template_map_t::iterator iter = sStringTemplates.find(xml_desc); - if (iter != sStringTemplates.end()) - { - std::string text = iter->second.mText; - LLStringUtil::format_map_t args = sDefaultArgs; - args.insert(msg_args.begin(), msg_args.end()); - LLStringUtil::format(text, args); - - return text; - } - else - { - LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; - return "MissingString("+xml_desc+")"; - } -} - -//static -std::string LLTrans::getString(const std::string &xml_desc, const LLSD& msg_args) -{ - // Don't care about time as much as call count. Make sure we're not - // calling LLTrans::getString() in an inner loop. JC - LLFastTimer timer(FTM_GET_TRANS); - - template_map_t::iterator iter = sStringTemplates.find(xml_desc); - if (iter != sStringTemplates.end()) - { - std::string text = iter->second.mText; - LLStringUtil::format(text, msg_args); - return text; - } - else - { - LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; - return "MissingString("+xml_desc+")"; - } -} - -//static -bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args) -{ - LLFastTimer timer(FTM_GET_TRANS); - - template_map_t::iterator iter = sStringTemplates.find(xml_desc); - if (iter != sStringTemplates.end()) - { - std::string text = iter->second.mText; - LLStringUtil::format_map_t args = sDefaultArgs; - args.insert(msg_args.begin(), msg_args.end()); - LLStringUtil::format(text, args); - result = text; - return true; - } - else - { - LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; - return false; - } -} - -//static -bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLSD& msg_args) -{ - LLFastTimer timer(FTM_GET_TRANS); - - template_map_t::iterator iter = sStringTemplates.find(xml_desc); - if (iter != sStringTemplates.end()) - { - std::string text = iter->second.mText; - LLStringUtil::format(text, msg_args); - result = text; - return true; - } - else - { - LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; - return false; - } -} - -//static -std::string LLTrans::getCountString(const std::string& language, const std::string& xml_desc, S32 count) -{ - // Compute which string identifier to use - const char* form = ""; - if (language == "ru") // Russian - { - // From GNU ngettext() - // Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; - if (count % 10 == 1 - && count % 100 != 11) - { - // singular, "1 item" - form = "A"; - } - else if (count % 10 >= 2 - && count % 10 <= 4 - && (count % 100 < 10 || count % 100 >= 20) ) - { - // special case "2 items", "23 items", but not "13 items" - form = "B"; - } - else - { - // English-style plural, "5 items" - form = "C"; - } - } - else if (language == "fr" || language == "pt") // French, Brazilian Portuguese - { - // French and Portuguese treat zero as a singular "0 item" not "0 items" - if (count == 0 || count == 1) - { - form = "A"; - } - else - { - // English-style plural - form = "B"; - } - } - else // default - { - // languages like English with 2 forms, singular and plural - if (count == 1) - { - // "1 item" - form = "A"; - } - else - { - // "2 items", also use plural for "0 items" - form = "B"; - } - } - - // Translate that string - LLStringUtil::format_map_t args; - args["[COUNT]"] = llformat("%d", count); - - // Look up "AgeYearsB" or "AgeWeeksC" including the "form" - std::string key = llformat("%s%s", xml_desc.c_str(), form); - return getString(key, args); -} - -void LLTrans::setDefaultArg(const std::string& name, const std::string& value) -{ - sDefaultArgs[name] = value; -} diff --git a/indra/llxuixml/lltrans.h b/indra/llxuixml/lltrans.h deleted file mode 100644 index 128b51d383..0000000000 --- a/indra/llxuixml/lltrans.h +++ /dev/null @@ -1,133 +0,0 @@ -/** - * @file lltrans.h - * @brief LLTrans definition - * - * $LicenseInfo:firstyear=2000&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_TRANS_H -#define LL_TRANS_H - -#include - -#include "llpointer.h" -#include "llstring.h" - -class LLXMLNode; - -class LLSD; - -/** - * @brief String template loaded from strings.xml - */ -class LLTransTemplate -{ -public: - LLTransTemplate(const std::string& name = LLStringUtil::null, const std::string& text = LLStringUtil::null) : mName(name), mText(text) {} - - std::string mName; - std::string mText; -}; - -/** - * @brief Localized strings class - * This class is used to retrieve translations of strings used to build larger ones, as well as - * strings with a general usage that don't belong to any specific floater. For example, - * "Owner:", "Retrieving..." used in the place of a not yet known name, etc. - */ -class LLTrans -{ -public: - LLTrans(); - - /** - * @brief Parses the xml root that holds the strings. Used once on startup -// *FIXME * @param xml_filename Filename to parse - * @param default_args Set of strings (expected to be in the file) to use as default replacement args, e.g. "SECOND_LIFE" - * @returns true if the file was parsed successfully, true if something went wrong - */ - static bool parseStrings(LLPointer & root, const std::set& default_args); - - static bool parseLanguageStrings(LLPointer & root); - - /** - * @brief Returns a translated string - * @param xml_desc String's description - * @param args A list of substrings to replace in the string - * @returns Translated string - */ - static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args); - static std::string getString(const std::string &xml_desc, const LLSD& args); - static bool findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& args); - static bool findString(std::string &result, const std::string &xml_desc, const LLSD& args); - - // Returns translated string with [COUNT] replaced with a number, following - // special per-language logic for plural nouns. For example, some languages - // may have different plurals for 0, 1, 2 and > 2. - // See "AgeWeeksA", "AgeWeeksB", etc. in strings.xml for examples. - static std::string getCountString(const std::string& language, const std::string& xml_desc, S32 count); - - /** - * @brief Returns a translated string - * @param xml_desc String's description - * @returns Translated string - */ - static std::string getString(const std::string &xml_desc) - { - LLStringUtil::format_map_t empty; - return getString(xml_desc, empty); - } - - static bool findString(std::string &result, const std::string &xml_desc) - { - LLStringUtil::format_map_t empty; - return findString(result, xml_desc, empty); - } - - static std::string getKeyboardString(const char* keystring) - { - std::string key_str(keystring); - std::string trans_str; - return findString(trans_str, key_str) ? trans_str : key_str; - } - - // get the default args - static const LLStringUtil::format_map_t& getDefaultArgs() - { - return sDefaultArgs; - } - - static void setDefaultArg(const std::string& name, const std::string& value); - - // insert default args into an arg list - static void getArgs(LLStringUtil::format_map_t& args) - { - args.insert(sDefaultArgs.begin(), sDefaultArgs.end()); - } - -private: - typedef std::map template_map_t; - static template_map_t sStringTemplates; - static LLStringUtil::format_map_t sDefaultArgs; -}; - -#endif diff --git a/indra/llxuixml/lluicolor.cpp b/indra/llxuixml/lluicolor.cpp deleted file mode 100644 index f9bb80f8c5..0000000000 --- a/indra/llxuixml/lluicolor.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @file lluicolor.cpp - * @brief brief LLUIColor class implementation file - * - * $LicenseInfo:firstyear=2009&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 "lluicolor.h" - -LLUIColor::LLUIColor() - :mColorPtr(NULL) -{ -} - - -LLUIColor::LLUIColor(const LLColor4& color) -: mColor(color), - mColorPtr(NULL) -{ -} - -LLUIColor::LLUIColor(const LLUIColor* color) -: mColorPtr(color) -{ -} - -void LLUIColor::set(const LLColor4& color) -{ - mColor = color; - mColorPtr = NULL; -} - -void LLUIColor::set(const LLUIColor* color) -{ - mColorPtr = color; -} - -const LLColor4& LLUIColor::get() const -{ - return (mColorPtr == NULL ? mColor : mColorPtr->get()); -} - -LLUIColor::operator const LLColor4& () const -{ - return get(); -} - -const LLColor4& LLUIColor::operator()() const -{ - return get(); -} - -bool LLUIColor::isReference() const -{ - return mColorPtr != NULL; -} - -namespace LLInitParam -{ - // used to detect equivalence with default values on export - bool ParamCompare::equals(const LLUIColor &a, const LLUIColor &b) - { - // do not detect value equivalence, treat pointers to colors as distinct from color values - return (a.mColorPtr == NULL && b.mColorPtr == NULL ? a.mColor == b.mColor : a.mColorPtr == b.mColorPtr); - } -} diff --git a/indra/llxuixml/lluicolor.h b/indra/llxuixml/lluicolor.h deleted file mode 100644 index 97ebea854a..0000000000 --- a/indra/llxuixml/lluicolor.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @file lluicolor.h - * @brief brief LLUIColor class header file - * - * $LicenseInfo:firstyear=2009&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_LLUICOLOR_H_ -#define LL_LLUICOLOR_H_ - -#include "v4color.h" - -namespace LLInitParam -{ - template - struct ParamCompare; -} - -class LLUIColor -{ -public: - LLUIColor(); - LLUIColor(const LLColor4& color); - LLUIColor(const LLUIColor* color); - - void set(const LLColor4& color); - void set(const LLUIColor* color); - - const LLColor4& get() const; - - operator const LLColor4& () const; - const LLColor4& operator()() const; - - bool isReference() const; - -private: - friend struct LLInitParam::ParamCompare; - - const LLUIColor* mColorPtr; - LLColor4 mColor; -}; - -namespace LLInitParam -{ - template<> - struct ParamCompare - { - static bool equals(const LLUIColor& a, const LLUIColor& b); - }; -} - -#endif diff --git a/indra/llxuixml/llxuiparser.cpp b/indra/llxuixml/llxuiparser.cpp deleted file mode 100644 index afc76024d1..0000000000 --- a/indra/llxuixml/llxuiparser.cpp +++ /dev/null @@ -1,1756 +0,0 @@ -/** - * @file llxuiparser.cpp - * @brief Utility functions for handling XUI structures in XML - * - * $LicenseInfo:firstyear=2003&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 "llxuiparser.h" - -#include "llxmlnode.h" - -#ifdef LL_STANDALONE -#include -#else -#include "expat/expat.h" -#endif - -#include -#include -//#include -#include - -#include "lluicolor.h" - -using namespace BOOST_SPIRIT_CLASSIC_NS; - -const S32 MAX_STRING_ATTRIBUTE_SIZE = 40; - -static LLInitParam::Parser::parser_read_func_map_t sXSDReadFuncs; -static LLInitParam::Parser::parser_write_func_map_t sXSDWriteFuncs; -static LLInitParam::Parser::parser_inspect_func_map_t sXSDInspectFuncs; - -static LLInitParam::Parser::parser_read_func_map_t sSimpleXUIReadFuncs; -static LLInitParam::Parser::parser_write_func_map_t sSimpleXUIWriteFuncs; -static LLInitParam::Parser::parser_inspect_func_map_t sSimpleXUIInspectFuncs; - -const char* NO_VALUE_MARKER = "no_value"; - -const S32 LINE_NUMBER_HERE = 0; - -struct MaxOccursValues : public LLInitParam::TypeValuesHelper -{ - static void declareValues() - { - declare("unbounded", U32_MAX); - } -}; - -struct Occurs : public LLInitParam::Block -{ - Optional minOccurs; - Optional maxOccurs; - - Occurs() - : minOccurs("minOccurs", 0), - maxOccurs("maxOccurs", U32_MAX) - - {} -}; - - -typedef enum -{ - USE_REQUIRED, - USE_OPTIONAL -} EUse; - -namespace LLInitParam -{ - template<> - struct TypeValues : public TypeValuesHelper - { - static void declareValues() - { - declare("required", USE_REQUIRED); - declare("optional", USE_OPTIONAL); - } - }; -} - -struct Element; -struct Group; -struct Choice; -struct Sequence; -struct Any; - -struct Attribute : public LLInitParam::Block -{ - Mandatory name; - Mandatory type; - Mandatory use; - - Attribute() - : name("name"), - type("type"), - use("use") - {} -}; - -struct Any : public LLInitParam::Block -{ - Optional _namespace; - - Any() - : _namespace("namespace") - {} -}; - -struct All : public LLInitParam::Block -{ - Multiple< Lazy > elements; - - All() - : elements("element") - { - maxOccurs = 1; - } -}; - -struct Choice : public LLInitParam::ChoiceBlock -{ - Alternative< Lazy > element; - Alternative< Lazy > group; - Alternative< Lazy > choice; - Alternative< Lazy > sequence; - Alternative< Lazy > any; - - Choice() - : element("element"), - group("group"), - choice("choice"), - sequence("sequence"), - any("any") - {} - -}; - -struct Sequence : public LLInitParam::ChoiceBlock -{ - Alternative< Lazy > element; - Alternative< Lazy > group; - Alternative< Lazy > choice; - Alternative< Lazy > sequence; - Alternative< Lazy > any; -}; - -struct GroupContents : public LLInitParam::ChoiceBlock -{ - Alternative all; - Alternative choice; - Alternative sequence; - - GroupContents() - : all("all"), - choice("choice"), - sequence("sequence") - {} -}; - -struct Group : public LLInitParam::Block -{ - Optional name, - ref; - - Group() - : name("name"), - ref("ref") - {} -}; - -struct Restriction : public LLInitParam::Block -{ -}; - -struct Extension : public LLInitParam::Block -{ -}; - -struct SimpleContent : public LLInitParam::ChoiceBlock -{ - Alternative restriction; - Alternative extension; - - SimpleContent() - : restriction("restriction"), - extension("extension") - {} -}; - -struct SimpleType : public LLInitParam::Block -{ - // TODO -}; - -struct ComplexContent : public LLInitParam::Block -{ - Optional mixed; - - ComplexContent() - : mixed("mixed", true) - {} -}; - -struct ComplexTypeContents : public LLInitParam::ChoiceBlock -{ - Alternative simple_content; - Alternative complex_content; - Alternative group; - Alternative all; - Alternative choice; - Alternative sequence; - - ComplexTypeContents() - : simple_content("simpleContent"), - complex_content("complexContent"), - group("group"), - all("all"), - choice("choice"), - sequence("sequence") - {} -}; - -struct ComplexType : public LLInitParam::Block -{ - Optional name; - Optional mixed; - - Multiple attribute; - Multiple< Lazy > elements; - - ComplexType() - : name("name"), - attribute("xs:attribute"), - elements("xs:element"), - mixed("mixed") - { - } -}; - -struct ElementContents : public LLInitParam::ChoiceBlock -{ - Alternative simpleType; - Alternative complexType; - - ElementContents() - : simpleType("simpleType"), - complexType("complexType") - {} -}; - -struct Element : public LLInitParam::Block -{ - Optional name, - ref, - type; - - Element() - : name("xs:name"), - ref("xs:ref"), - type("xs:type") - {} -}; - -struct Schema : public LLInitParam::Block -{ -private: - Mandatory targetNamespace, - xmlns, - xs; - -public: - Optional attributeFormDefault, - elementFormDefault; - - Mandatory root_element; - - void setNameSpace(const std::string& ns) {targetNamespace = ns; xmlns = ns;} - - Schema(const std::string& ns = LLStringUtil::null) - : attributeFormDefault("attributeFormDefault"), - elementFormDefault("elementFormDefault"), - xs("xmlns:xs"), - targetNamespace("targetNamespace"), - xmlns("xmlns"), - root_element("xs:element") - { - attributeFormDefault = "unqualified"; - elementFormDefault = "qualified"; - xs = "http://www.w3.org/2001/XMLSchema"; - if (!ns.empty()) - { - setNameSpace(ns); - }; - } - -}; - -// -// LLXSDWriter -// -LLXSDWriter::LLXSDWriter() -: Parser(sXSDReadFuncs, sXSDWriteFuncs, sXSDInspectFuncs) -{ - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:boolean", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedByte", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedByte", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedShort", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedShort", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedInt", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:integer", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:float", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:double", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); -} - -void LLXSDWriter::writeXSD(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace) -{ - Schema schema(xml_namespace); - - schema.root_element.name = type_name; - Choice& choice = schema.root_element.complexType.choice; - - choice.minOccurs = 0; - choice.maxOccurs = "unbounded"; - - mSchemaNode = node; - //node->setName("xs:schema"); - //node->createChild("attributeFormDefault", true)->setStringValue("unqualified"); - //node->createChild("elementFormDefault", true)->setStringValue("qualified"); - //node->createChild("targetNamespace", true)->setStringValue(xml_namespace); - //node->createChild("xmlns:xs", true)->setStringValue("http://www.w3.org/2001/XMLSchema"); - //node->createChild("xmlns", true)->setStringValue(xml_namespace); - - //node = node->createChild("xs:complexType", false); - //node->createChild("name", true)->setStringValue(type_name); - //node->createChild("mixed", true)->setStringValue("true"); - - //mAttributeNode = node; - //mElementNode = node->createChild("xs:choice", false); - //mElementNode->createChild("minOccurs", true)->setStringValue("0"); - //mElementNode->createChild("maxOccurs", true)->setStringValue("unbounded"); - block.inspectBlock(*this); - - // duplicate element choices - LLXMLNodeList children; - mElementNode->getChildren("xs:element", children, FALSE); - for (LLXMLNodeList::iterator child_it = children.begin(); child_it != children.end(); ++child_it) - { - LLXMLNodePtr child_copy = child_it->second->deepCopy(); - std::string child_name; - child_copy->getAttributeString("name", child_name); - child_copy->setAttributeString("name", type_name + "." + child_name); - mElementNode->addChild(child_copy); - } - - LLXMLNodePtr element_declaration_node = mSchemaNode->createChild("xs:element", false); - element_declaration_node->createChild("name", true)->setStringValue(type_name); - element_declaration_node->createChild("type", true)->setStringValue(type_name); -} - -void LLXSDWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector* possible_values) -{ - name_stack_t non_empty_names; - std::string attribute_name; - for (name_stack_t::const_iterator it = stack.begin(); - it != stack.end(); - ++it) - { - const std::string& name = it->first; - if (!name.empty()) - { - non_empty_names.push_back(*it); - } - } - - for (name_stack_t::const_iterator it = non_empty_names.begin(); - it != non_empty_names.end(); - ++it) - { - if (!attribute_name.empty()) - { - attribute_name += "."; - } - attribute_name += it->first; - } - - // only flag non-nested attributes as mandatory, nested attributes have variant syntax - // that can't be properly constrained in XSD - // e.g. vs - bool attribute_mandatory = min_count == 1 && max_count == 1 && non_empty_names.size() == 1; - - // don't bother supporting "Multiple" params as xml attributes - if (max_count <= 1) - { - // add compound attribute to root node - addAttributeToSchema(mAttributeNode, attribute_name, type, attribute_mandatory, possible_values); - } - - // now generated nested elements for compound attributes - if (non_empty_names.size() > 1 && !attribute_mandatory) - { - std::string element_name; - - // traverse all but last element, leaving that as an attribute name - name_stack_t::const_iterator end_it = non_empty_names.end(); - end_it--; - - for (name_stack_t::const_iterator it = non_empty_names.begin(); - it != end_it; - ++it) - { - if (it != non_empty_names.begin()) - { - element_name += "."; - } - element_name += it->first; - } - - std::string short_attribute_name = non_empty_names.back().first; - - LLXMLNodePtr complex_type_node; - - // find existing element node here, starting at tail of child list - if (mElementNode->mChildren.notNull()) - { - for(LLXMLNodePtr element = mElementNode->mChildren->tail; - element.notNull(); - element = element->mPrev) - { - std::string name; - if(element->getAttributeString("name", name) && name == element_name) - { - complex_type_node = element->mChildren->head; - break; - } - } - } - //create complex_type node - // - // - // - // - // - if(complex_type_node.isNull()) - { - complex_type_node = mElementNode->createChild("xs:element", false); - - complex_type_node->createChild("minOccurs", true)->setIntValue(min_count); - complex_type_node->createChild("maxOccurs", true)->setIntValue(max_count); - complex_type_node->createChild("name", true)->setStringValue(element_name); - complex_type_node = complex_type_node->createChild("xs:complexType", false); - } - - addAttributeToSchema(complex_type_node, short_attribute_name, type, false, possible_values); - } -} - -void LLXSDWriter::addAttributeToSchema(LLXMLNodePtr type_declaration_node, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector* possible_values) -{ - if (!attribute_name.empty()) - { - LLXMLNodePtr new_enum_type_node; - if (possible_values != NULL) - { - // custom attribute type, for example - // - // - // - // - // - // - new_enum_type_node = new LLXMLNode("xs:simpleType", false); - - LLXMLNodePtr restriction_node = new_enum_type_node->createChild("xs:restriction", false); - restriction_node->createChild("base", true)->setStringValue("xs:string"); - - for (std::vector::const_iterator it = possible_values->begin(); - it != possible_values->end(); - ++it) - { - LLXMLNodePtr enum_node = restriction_node->createChild("xs:enumeration", false); - enum_node->createChild("value", true)->setStringValue(*it); - } - } - - string_set_t& attributes_written = mAttributesWritten[type_declaration_node]; - - string_set_t::iterator found_it = attributes_written.lower_bound(attribute_name); - - // attribute not yet declared - if (found_it == attributes_written.end() || attributes_written.key_comp()(attribute_name, *found_it)) - { - attributes_written.insert(found_it, attribute_name); - - LLXMLNodePtr attribute_node = type_declaration_node->createChild("xs:attribute", false); - - // attribute name - attribute_node->createChild("name", true)->setStringValue(attribute_name); - - if (new_enum_type_node.notNull()) - { - attribute_node->addChild(new_enum_type_node); - } - else - { - // simple attribute type - attribute_node->createChild("type", true)->setStringValue(type); - } - - // required or optional - attribute_node->createChild("use", true)->setStringValue(mandatory ? "required" : "optional"); - } - // attribute exists...handle collision of same name attributes with potentially different types - else - { - LLXMLNodePtr attribute_declaration; - if (type_declaration_node.notNull()) - { - for(LLXMLNodePtr node = type_declaration_node->mChildren->tail; - node.notNull(); - node = node->mPrev) - { - std::string name; - if (node->getAttributeString("name", name) && name == attribute_name) - { - attribute_declaration = node; - break; - } - } - } - - bool new_type_is_enum = new_enum_type_node.notNull(); - bool existing_type_is_enum = !attribute_declaration->hasAttribute("type"); - - // either type is enum, revert to string in collision - // don't bother to check for enum equivalence - if (new_type_is_enum || existing_type_is_enum) - { - if (attribute_declaration->hasAttribute("type")) - { - attribute_declaration->setAttributeString("type", "xs:string"); - } - else - { - attribute_declaration->createChild("type", true)->setStringValue("xs:string"); - } - attribute_declaration->deleteChildren("xs:simpleType"); - } - else - { - // check for collision of different standard types - std::string existing_type; - attribute_declaration->getAttributeString("type", existing_type); - // if current type is not the same as the new type, revert to strnig - if (existing_type != type) - { - // ...than use most general type, string - attribute_declaration->setAttributeString("type", "string"); - } - } - } - } -} - -// -// LLXUIXSDWriter -// -void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& path, const LLInitParam::BaseBlock& block) -{ - std::string file_name(path); - file_name += type_name + ".xsd"; - LLXMLNodePtr root_nodep = new LLXMLNode(); - - LLXSDWriter::writeXSD(type_name, root_nodep, block, "http://www.lindenlab.com/xui"); - - // add includes for all possible children - const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name); - const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type); - - // add choices for valid children - if (widget_registryp) - { - // add include declarations for all valid children - for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); - it != widget_registryp->currentRegistrar().endItems(); - ++it) - { - std::string widget_name = it->first; - if (widget_name == type_name) - { - continue; - } - LLXMLNodePtr nodep = new LLXMLNode("xs:include", false); - nodep->createChild("schemaLocation", true)->setStringValue(widget_name + ".xsd"); - - // add to front of schema - mSchemaNode->addChild(nodep, mSchemaNode); - } - - for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); - it != widget_registryp->currentRegistrar().endItems(); - ++it) - { - std::string widget_name = it->first; - // - LLXMLNodePtr widget_node = mElementNode->createChild("xs:element", false); - widget_node->createChild("name", true)->setStringValue(widget_name); - widget_node->createChild("type", true)->setStringValue(widget_name); - } - } - - LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), "w"); - LLXMLNode::writeHeaderToFile(xsd_file); - root_nodep->writeToFile(xsd_file); - fclose(xsd_file); -} - -static LLInitParam::Parser::parser_read_func_map_t sXUIReadFuncs; -static LLInitParam::Parser::parser_write_func_map_t sXUIWriteFuncs; -static LLInitParam::Parser::parser_inspect_func_map_t sXUIInspectFuncs; - -// -// LLXUIParser -// -LLXUIParser::LLXUIParser() -: Parser(sXUIReadFuncs, sXUIWriteFuncs, sXUIInspectFuncs), - mCurReadDepth(0) -{ - if (sXUIReadFuncs.empty()) - { - registerParserFuncs(readFlag, writeFlag); - registerParserFuncs(readBoolValue, writeBoolValue); - registerParserFuncs(readStringValue, writeStringValue); - registerParserFuncs(readU8Value, writeU8Value); - registerParserFuncs(readS8Value, writeS8Value); - registerParserFuncs(readU16Value, writeU16Value); - registerParserFuncs(readS16Value, writeS16Value); - registerParserFuncs(readU32Value, writeU32Value); - registerParserFuncs(readS32Value, writeS32Value); - registerParserFuncs(readF32Value, writeF32Value); - registerParserFuncs(readF64Value, writeF64Value); - registerParserFuncs(readColor4Value, writeColor4Value); - registerParserFuncs(readUIColorValue, writeUIColorValue); - registerParserFuncs(readUUIDValue, writeUUIDValue); - registerParserFuncs(readSDValue, writeSDValue); - } -} - -static LLFastTimer::DeclareTimer FTM_PARSE_XUI("XUI Parsing"); -const LLXMLNodePtr DUMMY_NODE = new LLXMLNode(); - -void LLXUIParser::readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename, bool silent) -{ - LLFastTimer timer(FTM_PARSE_XUI); - mNameStack.clear(); - mRootNodeName = node->getName()->mString; - mCurFileName = filename; - mCurReadDepth = 0; - setParseSilently(silent); - - if (node.isNull()) - { - parserWarning("Invalid node"); - } - else - { - readXUIImpl(node, block); - } -} - -bool LLXUIParser::readXUIImpl(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) -{ - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("."); - - bool values_parsed = false; - bool silent = mCurReadDepth > 0; - - if (nodep->getFirstChild().isNull() - && nodep->mAttributes.empty() - && nodep->getSanitizedValue().empty()) - { - // empty node, just parse as flag - mCurReadNode = DUMMY_NODE; - return block.submitValue(mNameStack, *this, silent); - } - - // submit attributes for current node - values_parsed |= readAttributes(nodep, block); - - // treat text contents of xml node as "value" parameter - std::string text_contents = nodep->getSanitizedValue(); - if (!text_contents.empty()) - { - mCurReadNode = nodep; - mNameStack.push_back(std::make_pair(std::string("value"), true)); - // child nodes are not necessarily valid parameters (could be a child widget) - // so don't complain once we've recursed - if (!block.submitValue(mNameStack, *this, true)) - { - mNameStack.pop_back(); - block.submitValue(mNameStack, *this, silent); - } - else - { - mNameStack.pop_back(); - } - } - - // then traverse children - // child node must start with last name of parent node (our "scope") - // for example: "" - // which equates to the following nesting: - // button - // param - // nested_param1 - // nested_param2 - // nested_param3 - mCurReadDepth++; - for(LLXMLNodePtr childp = nodep->getFirstChild(); childp.notNull();) - { - std::string child_name(childp->getName()->mString); - S32 num_tokens_pushed = 0; - - // for non "dotted" child nodes check to see if child node maps to another widget type - // and if not, treat as a child element of the current node - // e.g. will interpret as "button.rect" - // since there is no widget named "rect" - if (child_name.find(".") == std::string::npos) - { - mNameStack.push_back(std::make_pair(child_name, true)); - num_tokens_pushed++; - } - else - { - // parse out "dotted" name into individual tokens - tokenizer name_tokens(child_name, sep); - - tokenizer::iterator name_token_it = name_tokens.begin(); - if(name_token_it == name_tokens.end()) - { - childp = childp->getNextSibling(); - continue; - } - - // check for proper nesting - if (mNameStack.empty()) - { - if (*name_token_it != mRootNodeName) - { - childp = childp->getNextSibling(); - continue; - } - } - else if(mNameStack.back().first != *name_token_it) - { - childp = childp->getNextSibling(); - continue; - } - - // now ignore first token - ++name_token_it; - - // copy remaining tokens on to our running token list - for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) - { - mNameStack.push_back(std::make_pair(*token_to_push, true)); - num_tokens_pushed++; - } - } - - // recurse and visit children XML nodes - if(readXUIImpl(childp, block)) - { - // child node successfully parsed, remove from DOM - - values_parsed = true; - LLXMLNodePtr node_to_remove = childp; - childp = childp->getNextSibling(); - - nodep->deleteChild(node_to_remove); - } - else - { - childp = childp->getNextSibling(); - } - - while(num_tokens_pushed-- > 0) - { - mNameStack.pop_back(); - } - } - mCurReadDepth--; - return values_parsed; -} - -bool LLXUIParser::readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) -{ - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("."); - - bool any_parsed = false; - bool silent = mCurReadDepth > 0; - - for(LLXMLAttribList::const_iterator attribute_it = nodep->mAttributes.begin(); - attribute_it != nodep->mAttributes.end(); - ++attribute_it) - { - S32 num_tokens_pushed = 0; - std::string attribute_name(attribute_it->first->mString); - mCurReadNode = attribute_it->second; - - tokenizer name_tokens(attribute_name, sep); - // copy remaining tokens on to our running token list - for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) - { - mNameStack.push_back(std::make_pair(*token_to_push, true)); - num_tokens_pushed++; - } - - // child nodes are not necessarily valid attributes, so don't complain once we've recursed - any_parsed |= block.submitValue(mNameStack, *this, silent); - - while(num_tokens_pushed-- > 0) - { - mNameStack.pop_back(); - } - } - - return any_parsed; -} - -void LLXUIParser::writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock &block, const LLInitParam::BaseBlock* diff_block) -{ - mWriteRootNode = node; - name_stack_t name_stack = Parser::name_stack_t(); - block.serializeBlock(*this, name_stack, diff_block); - mOutNodes.clear(); -} - -// go from a stack of names to a specific XML node -LLXMLNodePtr LLXUIParser::getNode(name_stack_t& stack) -{ - LLXMLNodePtr out_node = mWriteRootNode; - - name_stack_t::iterator next_it = stack.begin(); - for (name_stack_t::iterator it = stack.begin(); - it != stack.end(); - it = next_it) - { - ++next_it; - if (it->first.empty()) - { - it->second = false; - continue; - } - - out_nodes_t::iterator found_it = mOutNodes.find(it->first); - - // node with this name not yet written - if (found_it == mOutNodes.end() || it->second) - { - // make an attribute if we are the last element on the name stack - bool is_attribute = next_it == stack.end(); - LLXMLNodePtr new_node = new LLXMLNode(it->first.c_str(), is_attribute); - out_node->addChild(new_node); - mOutNodes[it->first] = new_node; - out_node = new_node; - it->second = false; - } - else - { - out_node = found_it->second; - } - } - - return (out_node == mWriteRootNode ? LLXMLNodePtr(NULL) : out_node); -} - -bool LLXUIParser::readFlag(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode == DUMMY_NODE; -} - -bool LLXUIParser::writeFlag(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - // just create node - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - return node.notNull(); -} - -bool LLXUIParser::readBoolValue(Parser& parser, void* val_ptr) -{ - S32 value; - LLXUIParser& self = static_cast(parser); - bool success = self.mCurReadNode->getBoolValue(1, &value); - *((bool*)val_ptr) = (value != FALSE); - return success; -} - -bool LLXUIParser::writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setBoolValue(*((bool*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readStringValue(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - *((std::string*)val_ptr) = self.mCurReadNode->getSanitizedValue(); - return true; -} - -bool LLXUIParser::writeStringValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - const std::string* string_val = reinterpret_cast(val_ptr); - if (string_val->find('\n') != std::string::npos - || string_val->size() > MAX_STRING_ATTRIBUTE_SIZE) - { - // don't write strings with newlines into attributes - std::string attribute_name = node->getName()->mString; - LLXMLNodePtr parent_node = node->mParent; - parent_node->deleteChild(node); - // write results in text contents of node - if (attribute_name == "value") - { - // "value" is implicit, just write to parent - node = parent_node; - } - else - { - // create a child that is not an attribute, but with same name - node = parent_node->createChild(attribute_name.c_str(), false); - } - } - node->setStringValue(*string_val); - return true; - } - return false; -} - -bool LLXUIParser::readU8Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getByteValue(1, (U8*)val_ptr); -} - -bool LLXUIParser::writeU8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setUnsignedValue(*((U8*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readS8Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - S32 value; - if(self.mCurReadNode->getIntValue(1, &value)) - { - *((S8*)val_ptr) = value; - return true; - } - return false; -} - -bool LLXUIParser::writeS8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setIntValue(*((S8*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readU16Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - U32 value; - if(self.mCurReadNode->getUnsignedValue(1, &value)) - { - *((U16*)val_ptr) = value; - return true; - } - return false; -} - -bool LLXUIParser::writeU16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setUnsignedValue(*((U16*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readS16Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - S32 value; - if(self.mCurReadNode->getIntValue(1, &value)) - { - *((S16*)val_ptr) = value; - return true; - } - return false; -} - -bool LLXUIParser::writeS16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setIntValue(*((S16*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readU32Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getUnsignedValue(1, (U32*)val_ptr); -} - -bool LLXUIParser::writeU32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setUnsignedValue(*((U32*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readS32Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getIntValue(1, (S32*)val_ptr); -} - -bool LLXUIParser::writeS32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setIntValue(*((S32*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readF32Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getFloatValue(1, (F32*)val_ptr); -} - -bool LLXUIParser::writeF32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setFloatValue(*((F32*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readF64Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getDoubleValue(1, (F64*)val_ptr); -} - -bool LLXUIParser::writeF64Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setDoubleValue(*((F64*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readColor4Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - LLColor4* colorp = (LLColor4*)val_ptr; - if(self.mCurReadNode->getFloatValue(4, colorp->mV) >= 3) - { - return true; - } - - return false; -} - -bool LLXUIParser::writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - LLColor4 color = *((LLColor4*)val_ptr); - node->setFloatValue(4, color.mV); - return true; - } - return false; -} - -bool LLXUIParser::readUIColorValue(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - LLUIColor* param = (LLUIColor*)val_ptr; - LLColor4 color; - bool success = self.mCurReadNode->getFloatValue(4, color.mV) >= 3; - if (success) - { - param->set(color); - return true; - } - return false; -} - -bool LLXUIParser::writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - LLUIColor color = *((LLUIColor*)val_ptr); - //RN: don't write out the color that is represented by a function - // rely on param block exporting to get the reference to the color settings - if (color.isReference()) return false; - node->setFloatValue(4, color.get().mV); - return true; - } - return false; -} - -bool LLXUIParser::readUUIDValue(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - LLUUID temp_id; - // LLUUID::set is destructive, so use temporary value - if (temp_id.set(self.mCurReadNode->getSanitizedValue())) - { - *(LLUUID*)(val_ptr) = temp_id; - return true; - } - return false; -} - -bool LLXUIParser::writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setStringValue(((LLUUID*)val_ptr)->asString()); - return true; - } - return false; -} - -bool LLXUIParser::readSDValue(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - *((LLSD*)val_ptr) = LLSD(self.mCurReadNode->getSanitizedValue()); - return true; -} - -bool LLXUIParser::writeSDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - std::string string_val = ((LLSD*)val_ptr)->asString(); - if (string_val.find('\n') != std::string::npos || string_val.size() > MAX_STRING_ATTRIBUTE_SIZE) - { - // don't write strings with newlines into attributes - std::string attribute_name = node->getName()->mString; - LLXMLNodePtr parent_node = node->mParent; - parent_node->deleteChild(node); - // write results in text contents of node - if (attribute_name == "value") - { - // "value" is implicit, just write to parent - node = parent_node; - } - else - { - node = parent_node->createChild(attribute_name.c_str(), false); - } - } - - node->setStringValue(string_val); - return true; - } - return false; -} - -/*virtual*/ std::string LLXUIParser::getCurrentElementName() -{ - std::string full_name; - for (name_stack_t::iterator it = mNameStack.begin(); - it != mNameStack.end(); - ++it) - { - full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." - } - - return full_name; -} - -void LLXUIParser::parserWarning(const std::string& message) -{ -#ifdef LL_WINDOWS - // use Visual Studo friendly formatting of output message for easy access to originating xml - llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); - utf16str += '\n'; - OutputDebugString(utf16str.c_str()); -#else - Parser::parserWarning(message); -#endif -} - -void LLXUIParser::parserError(const std::string& message) -{ -#ifdef LL_WINDOWS - llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); - utf16str += '\n'; - OutputDebugString(utf16str.c_str()); -#else - Parser::parserError(message); -#endif -} - - -// -// LLSimpleXUIParser -// - -struct ScopedFile -{ - ScopedFile( const std::string& filename, const char* accessmode ) - { - mFile = LLFile::fopen(filename, accessmode); - } - - ~ScopedFile() - { - fclose(mFile); - mFile = NULL; - } - - S32 getRemainingBytes() - { - if (!isOpen()) return 0; - - S32 cur_pos = ftell(mFile); - fseek(mFile, 0L, SEEK_END); - S32 file_size = ftell(mFile); - fseek(mFile, cur_pos, SEEK_SET); - return file_size - cur_pos; - } - - bool isOpen() { return mFile != NULL; } - - LLFILE* mFile; -}; -LLSimpleXUIParser::LLSimpleXUIParser(LLSimpleXUIParser::element_start_callback_t element_cb) -: Parser(sSimpleXUIReadFuncs, sSimpleXUIWriteFuncs, sSimpleXUIInspectFuncs), - mCurReadDepth(0), - mElementCB(element_cb) -{ - if (sSimpleXUIReadFuncs.empty()) - { - registerParserFuncs(readFlag); - registerParserFuncs(readBoolValue); - registerParserFuncs(readStringValue); - registerParserFuncs(readU8Value); - registerParserFuncs(readS8Value); - registerParserFuncs(readU16Value); - registerParserFuncs(readS16Value); - registerParserFuncs(readU32Value); - registerParserFuncs(readS32Value); - registerParserFuncs(readF32Value); - registerParserFuncs(readF64Value); - registerParserFuncs(readColor4Value); - registerParserFuncs(readUIColorValue); - registerParserFuncs(readUUIDValue); - registerParserFuncs(readSDValue); - } -} - -LLSimpleXUIParser::~LLSimpleXUIParser() -{ -} - - -bool LLSimpleXUIParser::readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent) -{ - LLFastTimer timer(FTM_PARSE_XUI); - - mParser = XML_ParserCreate(NULL); - XML_SetUserData(mParser, this); - XML_SetElementHandler( mParser, startElementHandler, endElementHandler); - XML_SetCharacterDataHandler( mParser, characterDataHandler); - - mOutputStack.push_back(std::make_pair(&block, 0)); - mNameStack.clear(); - mCurFileName = filename; - mCurReadDepth = 0; - setParseSilently(silent); - - ScopedFile file(filename, "rb"); - if( !file.isOpen() ) - { - LL_WARNS("ReadXUI") << "Unable to open file " << filename << LL_ENDL; - XML_ParserFree( mParser ); - return false; - } - - S32 bytes_read = 0; - - S32 buffer_size = file.getRemainingBytes(); - void* buffer = XML_GetBuffer(mParser, buffer_size); - if( !buffer ) - { - LL_WARNS("ReadXUI") << "Unable to allocate XML buffer while reading file " << filename << LL_ENDL; - XML_ParserFree( mParser ); - return false; - } - - bytes_read = (S32)fread(buffer, 1, buffer_size, file.mFile); - if( bytes_read <= 0 ) - { - LL_WARNS("ReadXUI") << "Error while reading file " << filename << LL_ENDL; - XML_ParserFree( mParser ); - return false; - } - - mEmptyLeafNode.push_back(false); - - if( !XML_ParseBuffer(mParser, bytes_read, TRUE ) ) - { - LL_WARNS("ReadXUI") << "Error while parsing file " << filename << LL_ENDL; - XML_ParserFree( mParser ); - return false; - } - - mEmptyLeafNode.pop_back(); - - XML_ParserFree( mParser ); - return true; -} - -void LLSimpleXUIParser::startElementHandler(void *userData, const char *name, const char **atts) -{ - LLSimpleXUIParser* self = reinterpret_cast(userData); - self->startElement(name, atts); -} - -void LLSimpleXUIParser::endElementHandler(void *userData, const char *name) -{ - LLSimpleXUIParser* self = reinterpret_cast(userData); - self->endElement(name); -} - -void LLSimpleXUIParser::characterDataHandler(void *userData, const char *s, int len) -{ - LLSimpleXUIParser* self = reinterpret_cast(userData); - self->characterData(s, len); -} - -void LLSimpleXUIParser::characterData(const char *s, int len) -{ - mTextContents += std::string(s, len); -} - -void LLSimpleXUIParser::startElement(const char *name, const char **atts) -{ - processText(); - - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("."); - - if (mElementCB) - { - LLInitParam::BaseBlock* blockp = mElementCB(*this, name); - if (blockp) - { - mOutputStack.push_back(std::make_pair(blockp, 0)); - } - } - - mOutputStack.back().second++; - S32 num_tokens_pushed = 0; - std::string child_name(name); - - if (mOutputStack.back().second == 1) - { // root node for this block - mScope.push_back(child_name); - } - else - { // compound attribute - if (child_name.find(".") == std::string::npos) - { - mNameStack.push_back(std::make_pair(child_name, true)); - num_tokens_pushed++; - mScope.push_back(child_name); - } - else - { - // parse out "dotted" name into individual tokens - tokenizer name_tokens(child_name, sep); - - tokenizer::iterator name_token_it = name_tokens.begin(); - if(name_token_it == name_tokens.end()) - { - return; - } - - // check for proper nesting - if(!mScope.empty() && *name_token_it != mScope.back()) - { - return; - } - - // now ignore first token - ++name_token_it; - - // copy remaining tokens on to our running token list - for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) - { - mNameStack.push_back(std::make_pair(*token_to_push, true)); - num_tokens_pushed++; - } - mScope.push_back(mNameStack.back().first); - } - } - - // parent node is not empty - mEmptyLeafNode.back() = false; - // we are empty if we have no attributes - mEmptyLeafNode.push_back(atts[0] == NULL); - - mTokenSizeStack.push_back(num_tokens_pushed); - readAttributes(atts); - -} - -void LLSimpleXUIParser::endElement(const char *name) -{ - bool has_text = processText(); - - // no text, attributes, or children - if (!has_text && mEmptyLeafNode.back()) - { - // submit this as a valueless name (even though there might be text contents we haven't seen yet) - mCurAttributeValueBegin = NO_VALUE_MARKER; - mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); - } - - if (--mOutputStack.back().second == 0) - { - if (mOutputStack.empty()) - { - LL_ERRS("ReadXUI") << "Parameter block output stack popped while empty." << LL_ENDL; - } - mOutputStack.pop_back(); - } - - S32 num_tokens_to_pop = mTokenSizeStack.back(); - mTokenSizeStack.pop_back(); - while(num_tokens_to_pop-- > 0) - { - mNameStack.pop_back(); - } - mScope.pop_back(); - mEmptyLeafNode.pop_back(); -} - -bool LLSimpleXUIParser::readAttributes(const char **atts) -{ - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("."); - - bool any_parsed = false; - for(S32 i = 0; atts[i] && atts[i+1]; i += 2 ) - { - std::string attribute_name(atts[i]); - mCurAttributeValueBegin = atts[i+1]; - - S32 num_tokens_pushed = 0; - tokenizer name_tokens(attribute_name, sep); - // copy remaining tokens on to our running token list - for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) - { - mNameStack.push_back(std::make_pair(*token_to_push, true)); - num_tokens_pushed++; - } - - // child nodes are not necessarily valid attributes, so don't complain once we've recursed - any_parsed |= mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); - - while(num_tokens_pushed-- > 0) - { - mNameStack.pop_back(); - } - } - return any_parsed; -} - -bool LLSimpleXUIParser::processText() -{ - if (!mTextContents.empty()) - { - LLStringUtil::trim(mTextContents); - if (!mTextContents.empty()) - { - mNameStack.push_back(std::make_pair(std::string("value"), true)); - mCurAttributeValueBegin = mTextContents.c_str(); - mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); - mNameStack.pop_back(); - } - mTextContents.clear(); - return true; - } - return false; -} - -/*virtual*/ std::string LLSimpleXUIParser::getCurrentElementName() -{ - std::string full_name; - for (name_stack_t::iterator it = mNameStack.begin(); - it != mNameStack.end(); - ++it) - { - full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." - } - - return full_name; -} - -void LLSimpleXUIParser::parserWarning(const std::string& message) -{ -#ifdef LL_WINDOWS - // use Visual Studo friendly formatting of output message for easy access to originating xml - llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), LINE_NUMBER_HERE, message.c_str()).c_str()); - utf16str += '\n'; - OutputDebugString(utf16str.c_str()); -#else - Parser::parserWarning(message); -#endif -} - -void LLSimpleXUIParser::parserError(const std::string& message) -{ -#ifdef LL_WINDOWS - llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), LINE_NUMBER_HERE, message.c_str()).c_str()); - utf16str += '\n'; - OutputDebugString(utf16str.c_str()); -#else - Parser::parserError(message); -#endif -} - -bool LLSimpleXUIParser::readFlag(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return self.mCurAttributeValueBegin == NO_VALUE_MARKER; -} - -bool LLSimpleXUIParser::readBoolValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - if (!strcmp(self.mCurAttributeValueBegin, "true")) - { - *((bool*)val_ptr) = true; - return true; - } - else if (!strcmp(self.mCurAttributeValueBegin, "false")) - { - *((bool*)val_ptr) = false; - return true; - } - - return false; -} - -bool LLSimpleXUIParser::readStringValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - *((std::string*)val_ptr) = self.mCurAttributeValueBegin; - return true; -} - -bool LLSimpleXUIParser::readU8Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U8*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readS8Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S8*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readU16Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U16*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readS16Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S16*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readU32Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U32*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readS32Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S32*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readF32Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F32*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readF64Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F64*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readColor4Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - LLColor4 value; - - if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) - { - *(LLColor4*)(val_ptr) = value; - return true; - } - return false; -} - -bool LLSimpleXUIParser::readUIColorValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - LLColor4 value; - LLUIColor* colorp = (LLUIColor*)val_ptr; - - if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) - { - colorp->set(value); - return true; - } - return false; -} - -bool LLSimpleXUIParser::readUUIDValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - LLUUID temp_id; - // LLUUID::set is destructive, so use temporary value - if (temp_id.set(std::string(self.mCurAttributeValueBegin))) - { - *(LLUUID*)(val_ptr) = temp_id; - return true; - } - return false; -} - -bool LLSimpleXUIParser::readSDValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - *((LLSD*)val_ptr) = LLSD(self.mCurAttributeValueBegin); - return true; -} diff --git a/indra/llxuixml/llxuiparser.h b/indra/llxuixml/llxuiparser.h deleted file mode 100644 index d7cd256967..0000000000 --- a/indra/llxuixml/llxuiparser.h +++ /dev/null @@ -1,242 +0,0 @@ -/** - * @file llxuiparser.h - * @brief Utility functions for handling XUI structures in XML - * - * $LicenseInfo:firstyear=2003&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 LLXUIPARSER_H -#define LLXUIPARSER_H - -#include "llinitparam.h" -#include "llregistry.h" -#include "llpointer.h" - -#include -#include -#include -#include - - - -class LLView; - - -typedef LLPointer LLXMLNodePtr; - - -// lookup widget type by name -class LLWidgetTypeRegistry -: public LLRegistrySingleton -{}; - - -// global static instance for registering all widget types -typedef boost::function LLWidgetCreatorFunc; - -typedef LLRegistry widget_registry_t; - -class LLChildRegistryRegistry -: public LLRegistrySingleton -{}; - - - -class LLXSDWriter : public LLInitParam::Parser -{ - LOG_CLASS(LLXSDWriter); -public: - void writeXSD(const std::string& name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace); - - /*virtual*/ std::string getCurrentElementName() { return LLStringUtil::null; } - - LLXSDWriter(); - -protected: - void writeAttribute(const std::string& type, const Parser::name_stack_t&, S32 min_count, S32 max_count, const std::vector* possible_values); - void addAttributeToSchema(LLXMLNodePtr nodep, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector* possible_values); - LLXMLNodePtr mAttributeNode; - LLXMLNodePtr mElementNode; - LLXMLNodePtr mSchemaNode; - - typedef std::set string_set_t; - typedef std::map attributes_map_t; - attributes_map_t mAttributesWritten; -}; - - - -// NOTE: DOES NOT WORK YET -// should support child widgets for XUI -class LLXUIXSDWriter : public LLXSDWriter -{ -public: - void writeXSD(const std::string& name, const std::string& path, const LLInitParam::BaseBlock& block); -}; - - -class LLXUIParserImpl; - -class LLXUIParser : public LLInitParam::Parser -{ -LOG_CLASS(LLXUIParser); - -public: - LLXUIParser(); - typedef LLInitParam::Parser::name_stack_t name_stack_t; - - /*virtual*/ std::string getCurrentElementName(); - /*virtual*/ void parserWarning(const std::string& message); - /*virtual*/ void parserError(const std::string& message); - - void readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename = LLStringUtil::null, bool silent=false); - void writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const LLInitParam::BaseBlock* diff_block = NULL); - -private: - bool readXUIImpl(LLXMLNodePtr node, LLInitParam::BaseBlock& block); - bool readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block); - - //reader helper functions - static bool readFlag(Parser& parser, void* val_ptr); - static bool readBoolValue(Parser& parser, void* val_ptr); - static bool readStringValue(Parser& parser, void* val_ptr); - static bool readU8Value(Parser& parser, void* val_ptr); - static bool readS8Value(Parser& parser, void* val_ptr); - static bool readU16Value(Parser& parser, void* val_ptr); - static bool readS16Value(Parser& parser, void* val_ptr); - static bool readU32Value(Parser& parser, void* val_ptr); - static bool readS32Value(Parser& parser, void* val_ptr); - static bool readF32Value(Parser& parser, void* val_ptr); - static bool readF64Value(Parser& parser, void* val_ptr); - static bool readColor4Value(Parser& parser, void* val_ptr); - static bool readUIColorValue(Parser& parser, void* val_ptr); - static bool readUUIDValue(Parser& parser, void* val_ptr); - static bool readSDValue(Parser& parser, void* val_ptr); - - //writer helper functions - static bool writeFlag(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeStringValue(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeU8Value(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeS8Value(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeU16Value(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeS16Value(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeU32Value(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeS32Value(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeF32Value(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeF64Value(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t&); - static bool writeSDValue(Parser& parser, const void* val_ptr, name_stack_t&); - - LLXMLNodePtr getNode(name_stack_t& stack); - -private: - Parser::name_stack_t mNameStack; - LLXMLNodePtr mCurReadNode; - // Root of the widget XML sub-tree, for example, "line_editor" - LLXMLNodePtr mWriteRootNode; - - typedef std::map out_nodes_t; - out_nodes_t mOutNodes; - LLXMLNodePtr mLastWrittenChild; - S32 mCurReadDepth; - std::string mCurFileName; - std::string mRootNodeName; -}; - -// LLSimpleXUIParser is a streamlined SAX-based XUI parser that does not support localization -// or parsing of a tree of independent param blocks, such as child widgets. -// Use this for reading non-localized files that only need a single param block as a result. -// -// NOTE: In order to support nested block parsing, we need callbacks for start element that -// push new blocks contexts on the mScope stack. -// NOTE: To support localization without building a DOM, we need to enforce consistent -// ordering of child elements from base file to localized diff file. Then we can use a pair -// of coroutines to perform matching of xml nodes during parsing. Not sure if the overhead -// of coroutines would offset the gain from SAX parsing -class LLSimpleXUIParserImpl; - -class LLSimpleXUIParser : public LLInitParam::Parser -{ -LOG_CLASS(LLSimpleXUIParser); -public: - typedef LLInitParam::Parser::name_stack_t name_stack_t; - typedef LLInitParam::BaseBlock* (*element_start_callback_t)(LLSimpleXUIParser&, const char* block_name); - - LLSimpleXUIParser(element_start_callback_t element_cb = NULL); - virtual ~LLSimpleXUIParser(); - - /*virtual*/ std::string getCurrentElementName(); - /*virtual*/ void parserWarning(const std::string& message); - /*virtual*/ void parserError(const std::string& message); - - bool readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent=false); - - -private: - //reader helper functions - static bool readFlag(Parser&, void* val_ptr); - static bool readBoolValue(Parser&, void* val_ptr); - static bool readStringValue(Parser&, void* val_ptr); - static bool readU8Value(Parser&, void* val_ptr); - static bool readS8Value(Parser&, void* val_ptr); - static bool readU16Value(Parser&, void* val_ptr); - static bool readS16Value(Parser&, void* val_ptr); - static bool readU32Value(Parser&, void* val_ptr); - static bool readS32Value(Parser&, void* val_ptr); - static bool readF32Value(Parser&, void* val_ptr); - static bool readF64Value(Parser&, void* val_ptr); - static bool readColor4Value(Parser&, void* val_ptr); - static bool readUIColorValue(Parser&, void* val_ptr); - static bool readUUIDValue(Parser&, void* val_ptr); - static bool readSDValue(Parser&, void* val_ptr); - -private: - static void startElementHandler(void *userData, const char *name, const char **atts); - static void endElementHandler(void *userData, const char *name); - static void characterDataHandler(void *userData, const char *s, int len); - - void startElement(const char *name, const char **atts); - void endElement(const char *name); - void characterData(const char *s, int len); - bool readAttributes(const char **atts); - bool processText(); - - Parser::name_stack_t mNameStack; - struct XML_ParserStruct* mParser; - LLXMLNodePtr mLastWrittenChild; - S32 mCurReadDepth; - std::string mCurFileName; - std::string mTextContents; - const char* mCurAttributeValueBegin; - std::vector mTokenSizeStack; - std::vector mScope; - std::vector mEmptyLeafNode; - element_start_callback_t mElementCB; - - std::vector > mOutputStack; -}; - - -#endif //LLXUIPARSER_H 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.2.3 From 4287dcaacf0804a5a73dbf37c629471e2855733c Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Fri, 20 Jan 2012 14:55:39 -0800 Subject: moved LLSDParam to llcommon so that LLSD<->Param Block conversion are usable by everyone --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/llinitparam.h | 4 +- indra/llcommon/llsdparam.cpp | 342 ++++++++++++++++++++++++++++++++++++++++++ indra/llcommon/llsdparam.h | 126 ++++++++++++++++ indra/llui/CMakeLists.txt | 2 - indra/llui/llsdparam.cpp | 342 ------------------------------------------ indra/llui/llsdparam.h | 126 ---------------- 7 files changed, 472 insertions(+), 472 deletions(-) create mode 100644 indra/llcommon/llsdparam.cpp create mode 100644 indra/llcommon/llsdparam.h delete mode 100644 indra/llui/llsdparam.cpp delete mode 100644 indra/llui/llsdparam.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 72b1d363b0..a1aa887d3a 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -82,6 +82,7 @@ set(llcommon_SOURCE_FILES llrefcount.cpp llrun.cpp llsd.cpp + llsdparam.cpp llsdserialize.cpp llsdserialize_xml.cpp llsdutil.cpp @@ -211,6 +212,7 @@ set(llcommon_HEADER_FILES llrefcount.h llsafehandle.h llsd.h + llsdparam.h llsdserialize.h llsdserialize_xml.h llsdutil.h diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h index 550e1608cc..beaf07e56b 100644 --- a/indra/llcommon/llinitparam.h +++ b/indra/llcommon/llinitparam.h @@ -2057,8 +2057,8 @@ namespace LLInitParam // block param interface - bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); - void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const; + LL_COMMON_API bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); + LL_COMMON_API void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const; bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const { //TODO: implement LLSD params as schema type Any diff --git a/indra/llcommon/llsdparam.cpp b/indra/llcommon/llsdparam.cpp new file mode 100644 index 0000000000..0e29873bb0 --- /dev/null +++ b/indra/llcommon/llsdparam.cpp @@ -0,0 +1,342 @@ +/** + * @file llsdparam.cpp + * @brief parameter block abstraction for creating complex objects and + * parsing construction parameters from xml and LLSD + * + * $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" + +// Project includes +#include "llsdparam.h" +#include "llsdutil.h" + +static LLInitParam::Parser::parser_read_func_map_t sReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sInspectFuncs; +static const LLSD NO_VALUE_MARKER; + +LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR("LLSD to LLInitParam conversion"); + +// +// LLParamSDParser +// +LLParamSDParser::LLParamSDParser() +: Parser(sReadFuncs, sWriteFuncs, sInspectFuncs) +{ + using boost::bind; + + if (sReadFuncs.empty()) + { + registerParserFuncs(readFlag, &LLParamSDParser::writeFlag); + registerParserFuncs(readS32, &LLParamSDParser::writeTypedValue); + registerParserFuncs(readU32, &LLParamSDParser::writeU32Param); + registerParserFuncs(readF32, &LLParamSDParser::writeTypedValue); + registerParserFuncs(readF64, &LLParamSDParser::writeTypedValue); + registerParserFuncs(readBool, &LLParamSDParser::writeTypedValue); + registerParserFuncs(readString, &LLParamSDParser::writeTypedValue); + registerParserFuncs(readUUID, &LLParamSDParser::writeTypedValue); + registerParserFuncs(readDate, &LLParamSDParser::writeTypedValue); + registerParserFuncs(readURI, &LLParamSDParser::writeTypedValue); + registerParserFuncs(readSD, &LLParamSDParser::writeTypedValue); + } +} + +// special case handling of U32 due to ambiguous LLSD::assign overload +bool LLParamSDParser::writeU32Param(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) +{ + LLParamSDParser& sdparser = static_cast(parser); + if (!sdparser.mWriteRootSD) return false; + + parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); + LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); + sd_to_write.assign((S32)*((const U32*)val_ptr)); + + return true; +} + +bool LLParamSDParser::writeFlag(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) +{ + LLParamSDParser& sdparser = static_cast(parser); + if (!sdparser.mWriteRootSD) return false; + + parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); + LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); + + return true; +} + +void LLParamSDParser::submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack) +{ + mCurReadSD = &sd; + block.submitValue(name_stack, *this); +} + +void LLParamSDParser::readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent) +{ + mCurReadSD = NULL; + mNameStack.clear(); + setParseSilently(silent); + + LLParamSDParserUtilities::readSDValues(boost::bind(&LLParamSDParser::submit, this, boost::ref(block), _1, _2), sd, mNameStack); + //readSDValues(sd, block); +} + +void LLParamSDParser::writeSD(LLSD& sd, const LLInitParam::BaseBlock& block) +{ + mNameStack.clear(); + mWriteRootSD = &sd; + + name_stack_t name_stack; + block.serializeBlock(*this, name_stack); +} + +/*virtual*/ std::string LLParamSDParser::getCurrentElementName() +{ + std::string full_name = "sd"; + for (name_stack_t::iterator it = mNameStack.begin(); + it != mNameStack.end(); + ++it) + { + full_name += llformat("[%s]", it->first.c_str()); + } + + return full_name; +} + + +bool LLParamSDParser::readFlag(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + return self.mCurReadSD == &NO_VALUE_MARKER; +} + + +bool LLParamSDParser::readS32(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((S32*)val_ptr) = self.mCurReadSD->asInteger(); + return true; +} + +bool LLParamSDParser::readU32(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((U32*)val_ptr) = self.mCurReadSD->asInteger(); + return true; +} + +bool LLParamSDParser::readF32(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((F32*)val_ptr) = self.mCurReadSD->asReal(); + return true; +} + +bool LLParamSDParser::readF64(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((F64*)val_ptr) = self.mCurReadSD->asReal(); + return true; +} + +bool LLParamSDParser::readBool(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((bool*)val_ptr) = self.mCurReadSD->asBoolean(); + return true; +} + +bool LLParamSDParser::readString(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((std::string*)val_ptr) = self.mCurReadSD->asString(); + return true; +} + +bool LLParamSDParser::readUUID(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((LLUUID*)val_ptr) = self.mCurReadSD->asUUID(); + return true; +} + +bool LLParamSDParser::readDate(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((LLDate*)val_ptr) = self.mCurReadSD->asDate(); + return true; +} + +bool LLParamSDParser::readURI(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((LLURI*)val_ptr) = self.mCurReadSD->asURI(); + return true; +} + +bool LLParamSDParser::readSD(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast(parser); + + *((LLSD*)val_ptr) = *self.mCurReadSD; + return true; +} + +// static +LLSD& LLParamSDParserUtilities::getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range) +{ + LLSD* sd_to_write = &input; + + for (LLInitParam::Parser::name_stack_t::iterator it = name_stack_range.first; + it != name_stack_range.second; + ++it) + { + bool new_traversal = it->second; + + LLSD* child_sd = it->first.empty() ? sd_to_write : &(*sd_to_write)[it->first]; + + if (child_sd->isArray()) + { + if (new_traversal) + { + // write to new element at end + sd_to_write = &(*child_sd)[child_sd->size()]; + } + else + { + // write to last of existing elements, or first element if empty + sd_to_write = &(*child_sd)[llmax(0, child_sd->size() - 1)]; + } + } + else + { + if (new_traversal + && child_sd->isDefined() + && !child_sd->isArray()) + { + // copy child contents into first element of an array + LLSD new_array = LLSD::emptyArray(); + new_array.append(*child_sd); + // assign array to slot that previously held the single value + *child_sd = new_array; + // return next element in that array + sd_to_write = &((*child_sd)[1]); + } + else + { + sd_to_write = child_sd; + } + } + it->second = false; + } + + return *sd_to_write; +} + +//static +void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack) +{ + if (sd.isMap()) + { + for (LLSD::map_const_iterator it = sd.beginMap(); + it != sd.endMap(); + ++it) + { + stack.push_back(make_pair(it->first, true)); + readSDValues(cb, it->second, stack); + stack.pop_back(); + } + } + else if (sd.isArray()) + { + for (LLSD::array_const_iterator it = sd.beginArray(); + it != sd.endArray(); + ++it) + { + stack.back().second = true; + readSDValues(cb, *it, stack); + } + } + else if (sd.isUndefined()) + { + if (!cb.empty()) + { + cb(NO_VALUE_MARKER, stack); + } + } + else + { + if (!cb.empty()) + { + cb(sd, stack); + } + } +} + +//static +void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd) +{ + LLInitParam::Parser::name_stack_t stack = LLInitParam::Parser::name_stack_t(); + readSDValues(cb, sd, stack); +} +namespace LLInitParam +{ + // LLSD specialization + // block param interface + bool ParamValue, false>::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name) + { + LLSD& sd = LLParamSDParserUtilities::getSDWriteNode(mValue, name_stack); + + LLSD::String string; + + if (p.readValue(string)) + { + sd = string; + return true; + } + return false; + } + + //static + void ParamValue, false>::serializeElement(Parser& p, const LLSD& sd, Parser::name_stack_t& name_stack) + { + p.writeValue(sd.asString(), name_stack); + } + + void ParamValue, false>::serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block) const + { + // read from LLSD value and serialize out to parser (which could be LLSD, XUI, etc) + Parser::name_stack_t stack; + LLParamSDParserUtilities::readSDValues(boost::bind(&serializeElement, boost::ref(p), _1, _2), mValue, stack); + } +} diff --git a/indra/llcommon/llsdparam.h b/indra/llcommon/llsdparam.h new file mode 100644 index 0000000000..6ef5debd7b --- /dev/null +++ b/indra/llcommon/llsdparam.h @@ -0,0 +1,126 @@ +/** + * @file llsdparam.h + * @brief parameter block abstraction for creating complex objects and + * parsing construction parameters from xml and LLSD + * + * $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_LLSDPARAM_H +#define LL_LLSDPARAM_H + +#include "llinitparam.h" +#include "boost/function.hpp" + +struct LL_COMMON_API LLParamSDParserUtilities +{ + static LLSD& getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range); + + typedef boost::function read_sd_cb_t; + static void readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack); + static void readSDValues(read_sd_cb_t cb, const LLSD& sd); +}; + +class LL_COMMON_API LLParamSDParser +: public LLInitParam::Parser +{ +LOG_CLASS(LLParamSDParser); + +typedef LLInitParam::Parser parser_t; + +public: + LLParamSDParser(); + void readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent = false); + void writeSD(LLSD& sd, const LLInitParam::BaseBlock& block); + + /*virtual*/ std::string getCurrentElementName(); + +private: + void submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack); + + template + static bool writeTypedValue(Parser& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) + { + LLParamSDParser& sdparser = static_cast(parser); + if (!sdparser.mWriteRootSD) return false; + + LLInitParam::Parser::name_stack_range_t range(name_stack.begin(), name_stack.end()); + LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); + + sd_to_write.assign(*((const T*)val_ptr)); + return true; + } + + static bool writeU32Param(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); + static bool writeFlag(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); + + static bool readFlag(Parser& parser, void* val_ptr); + static bool readS32(Parser& parser, void* val_ptr); + static bool readU32(Parser& parser, void* val_ptr); + static bool readF32(Parser& parser, void* val_ptr); + static bool readF64(Parser& parser, void* val_ptr); + static bool readBool(Parser& parser, void* val_ptr); + static bool readString(Parser& parser, void* val_ptr); + static bool readUUID(Parser& parser, void* val_ptr); + static bool readDate(Parser& parser, void* val_ptr); + static bool readURI(Parser& parser, void* val_ptr); + static bool readSD(Parser& parser, void* val_ptr); + + Parser::name_stack_t mNameStack; + const LLSD* mCurReadSD; + LLSD* mWriteRootSD; + LLSD* mCurWriteSD; +}; + + +extern LL_COMMON_API LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR; +template +class LLSDParamAdapter : public T +{ +public: + LLSDParamAdapter() {} + LLSDParamAdapter(const LLSD& sd) + { + LLFastTimer _(FTM_SD_PARAM_ADAPTOR); + LLParamSDParser parser; + // don't spam for implicit parsing of LLSD, as we want to allow arbitrary freeform data and ignore most of it + bool parse_silently = true; + parser.readSD(sd, *this, parse_silently); + } + + operator LLSD() const + { + LLParamSDParser parser; + LLSD sd; + parser.writeSD(sd, *this); + return sd; + } + + LLSDParamAdapter(const T& val) + : T(val) + { + T::operator=(val); + } +}; + +#endif // LL_LLSDPARAM_H + diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 9226f36e73..20c3456a56 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -81,7 +81,6 @@ set(llui_SOURCE_FILES llscrolllistcolumn.cpp llscrolllistctrl.cpp llscrolllistitem.cpp - llsdparam.cpp llsearcheditor.cpp llslider.cpp llsliderctrl.cpp @@ -190,7 +189,6 @@ set(llui_HEADER_FILES llscrolllistcolumn.h llscrolllistctrl.h llscrolllistitem.h - llsdparam.h llsliderctrl.h llslider.h llspinctrl.h diff --git a/indra/llui/llsdparam.cpp b/indra/llui/llsdparam.cpp deleted file mode 100644 index 0e29873bb0..0000000000 --- a/indra/llui/llsdparam.cpp +++ /dev/null @@ -1,342 +0,0 @@ -/** - * @file llsdparam.cpp - * @brief parameter block abstraction for creating complex objects and - * parsing construction parameters from xml and LLSD - * - * $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" - -// Project includes -#include "llsdparam.h" -#include "llsdutil.h" - -static LLInitParam::Parser::parser_read_func_map_t sReadFuncs; -static LLInitParam::Parser::parser_write_func_map_t sWriteFuncs; -static LLInitParam::Parser::parser_inspect_func_map_t sInspectFuncs; -static const LLSD NO_VALUE_MARKER; - -LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR("LLSD to LLInitParam conversion"); - -// -// LLParamSDParser -// -LLParamSDParser::LLParamSDParser() -: Parser(sReadFuncs, sWriteFuncs, sInspectFuncs) -{ - using boost::bind; - - if (sReadFuncs.empty()) - { - registerParserFuncs(readFlag, &LLParamSDParser::writeFlag); - registerParserFuncs(readS32, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readU32, &LLParamSDParser::writeU32Param); - registerParserFuncs(readF32, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readF64, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readBool, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readString, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readUUID, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readDate, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readURI, &LLParamSDParser::writeTypedValue); - registerParserFuncs(readSD, &LLParamSDParser::writeTypedValue); - } -} - -// special case handling of U32 due to ambiguous LLSD::assign overload -bool LLParamSDParser::writeU32Param(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) -{ - LLParamSDParser& sdparser = static_cast(parser); - if (!sdparser.mWriteRootSD) return false; - - parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); - LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); - sd_to_write.assign((S32)*((const U32*)val_ptr)); - - return true; -} - -bool LLParamSDParser::writeFlag(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) -{ - LLParamSDParser& sdparser = static_cast(parser); - if (!sdparser.mWriteRootSD) return false; - - parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); - LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); - - return true; -} - -void LLParamSDParser::submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack) -{ - mCurReadSD = &sd; - block.submitValue(name_stack, *this); -} - -void LLParamSDParser::readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent) -{ - mCurReadSD = NULL; - mNameStack.clear(); - setParseSilently(silent); - - LLParamSDParserUtilities::readSDValues(boost::bind(&LLParamSDParser::submit, this, boost::ref(block), _1, _2), sd, mNameStack); - //readSDValues(sd, block); -} - -void LLParamSDParser::writeSD(LLSD& sd, const LLInitParam::BaseBlock& block) -{ - mNameStack.clear(); - mWriteRootSD = &sd; - - name_stack_t name_stack; - block.serializeBlock(*this, name_stack); -} - -/*virtual*/ std::string LLParamSDParser::getCurrentElementName() -{ - std::string full_name = "sd"; - for (name_stack_t::iterator it = mNameStack.begin(); - it != mNameStack.end(); - ++it) - { - full_name += llformat("[%s]", it->first.c_str()); - } - - return full_name; -} - - -bool LLParamSDParser::readFlag(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - return self.mCurReadSD == &NO_VALUE_MARKER; -} - - -bool LLParamSDParser::readS32(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((S32*)val_ptr) = self.mCurReadSD->asInteger(); - return true; -} - -bool LLParamSDParser::readU32(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((U32*)val_ptr) = self.mCurReadSD->asInteger(); - return true; -} - -bool LLParamSDParser::readF32(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((F32*)val_ptr) = self.mCurReadSD->asReal(); - return true; -} - -bool LLParamSDParser::readF64(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((F64*)val_ptr) = self.mCurReadSD->asReal(); - return true; -} - -bool LLParamSDParser::readBool(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((bool*)val_ptr) = self.mCurReadSD->asBoolean(); - return true; -} - -bool LLParamSDParser::readString(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((std::string*)val_ptr) = self.mCurReadSD->asString(); - return true; -} - -bool LLParamSDParser::readUUID(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((LLUUID*)val_ptr) = self.mCurReadSD->asUUID(); - return true; -} - -bool LLParamSDParser::readDate(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((LLDate*)val_ptr) = self.mCurReadSD->asDate(); - return true; -} - -bool LLParamSDParser::readURI(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((LLURI*)val_ptr) = self.mCurReadSD->asURI(); - return true; -} - -bool LLParamSDParser::readSD(Parser& parser, void* val_ptr) -{ - LLParamSDParser& self = static_cast(parser); - - *((LLSD*)val_ptr) = *self.mCurReadSD; - return true; -} - -// static -LLSD& LLParamSDParserUtilities::getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range) -{ - LLSD* sd_to_write = &input; - - for (LLInitParam::Parser::name_stack_t::iterator it = name_stack_range.first; - it != name_stack_range.second; - ++it) - { - bool new_traversal = it->second; - - LLSD* child_sd = it->first.empty() ? sd_to_write : &(*sd_to_write)[it->first]; - - if (child_sd->isArray()) - { - if (new_traversal) - { - // write to new element at end - sd_to_write = &(*child_sd)[child_sd->size()]; - } - else - { - // write to last of existing elements, or first element if empty - sd_to_write = &(*child_sd)[llmax(0, child_sd->size() - 1)]; - } - } - else - { - if (new_traversal - && child_sd->isDefined() - && !child_sd->isArray()) - { - // copy child contents into first element of an array - LLSD new_array = LLSD::emptyArray(); - new_array.append(*child_sd); - // assign array to slot that previously held the single value - *child_sd = new_array; - // return next element in that array - sd_to_write = &((*child_sd)[1]); - } - else - { - sd_to_write = child_sd; - } - } - it->second = false; - } - - return *sd_to_write; -} - -//static -void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack) -{ - if (sd.isMap()) - { - for (LLSD::map_const_iterator it = sd.beginMap(); - it != sd.endMap(); - ++it) - { - stack.push_back(make_pair(it->first, true)); - readSDValues(cb, it->second, stack); - stack.pop_back(); - } - } - else if (sd.isArray()) - { - for (LLSD::array_const_iterator it = sd.beginArray(); - it != sd.endArray(); - ++it) - { - stack.back().second = true; - readSDValues(cb, *it, stack); - } - } - else if (sd.isUndefined()) - { - if (!cb.empty()) - { - cb(NO_VALUE_MARKER, stack); - } - } - else - { - if (!cb.empty()) - { - cb(sd, stack); - } - } -} - -//static -void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd) -{ - LLInitParam::Parser::name_stack_t stack = LLInitParam::Parser::name_stack_t(); - readSDValues(cb, sd, stack); -} -namespace LLInitParam -{ - // LLSD specialization - // block param interface - bool ParamValue, false>::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name) - { - LLSD& sd = LLParamSDParserUtilities::getSDWriteNode(mValue, name_stack); - - LLSD::String string; - - if (p.readValue(string)) - { - sd = string; - return true; - } - return false; - } - - //static - void ParamValue, false>::serializeElement(Parser& p, const LLSD& sd, Parser::name_stack_t& name_stack) - { - p.writeValue(sd.asString(), name_stack); - } - - void ParamValue, false>::serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block) const - { - // read from LLSD value and serialize out to parser (which could be LLSD, XUI, etc) - Parser::name_stack_t stack; - LLParamSDParserUtilities::readSDValues(boost::bind(&serializeElement, boost::ref(p), _1, _2), mValue, stack); - } -} diff --git a/indra/llui/llsdparam.h b/indra/llui/llsdparam.h deleted file mode 100644 index 3dfc6d020e..0000000000 --- a/indra/llui/llsdparam.h +++ /dev/null @@ -1,126 +0,0 @@ -/** - * @file llsdparam.h - * @brief parameter block abstraction for creating complex objects and - * parsing construction parameters from xml and LLSD - * - * $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_LLSDPARAM_H -#define LL_LLSDPARAM_H - -#include "llinitparam.h" -#include "boost/function.hpp" - -struct LLParamSDParserUtilities -{ - static LLSD& getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range); - - typedef boost::function read_sd_cb_t; - static void readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack); - static void readSDValues(read_sd_cb_t cb, const LLSD& sd); -}; - -class LLParamSDParser -: public LLInitParam::Parser -{ -LOG_CLASS(LLParamSDParser); - -typedef LLInitParam::Parser parser_t; - -public: - LLParamSDParser(); - void readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent = false); - void writeSD(LLSD& sd, const LLInitParam::BaseBlock& block); - - /*virtual*/ std::string getCurrentElementName(); - -private: - void submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack); - - template - static bool writeTypedValue(Parser& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) - { - LLParamSDParser& sdparser = static_cast(parser); - if (!sdparser.mWriteRootSD) return false; - - LLInitParam::Parser::name_stack_range_t range(name_stack.begin(), name_stack.end()); - LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); - - sd_to_write.assign(*((const T*)val_ptr)); - return true; - } - - static bool writeU32Param(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); - static bool writeFlag(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); - - static bool readFlag(Parser& parser, void* val_ptr); - static bool readS32(Parser& parser, void* val_ptr); - static bool readU32(Parser& parser, void* val_ptr); - static bool readF32(Parser& parser, void* val_ptr); - static bool readF64(Parser& parser, void* val_ptr); - static bool readBool(Parser& parser, void* val_ptr); - static bool readString(Parser& parser, void* val_ptr); - static bool readUUID(Parser& parser, void* val_ptr); - static bool readDate(Parser& parser, void* val_ptr); - static bool readURI(Parser& parser, void* val_ptr); - static bool readSD(Parser& parser, void* val_ptr); - - Parser::name_stack_t mNameStack; - const LLSD* mCurReadSD; - LLSD* mWriteRootSD; - LLSD* mCurWriteSD; -}; - - -extern LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR; -template -class LLSDParamAdapter : public T -{ -public: - LLSDParamAdapter() {} - LLSDParamAdapter(const LLSD& sd) - { - LLFastTimer _(FTM_SD_PARAM_ADAPTOR); - LLParamSDParser parser; - // don't spam for implicit parsing of LLSD, as we want to allow arbitrary freeform data and ignore most of it - bool parse_silently = true; - parser.readSD(sd, *this, parse_silently); - } - - operator LLSD() const - { - LLParamSDParser parser; - LLSD sd; - parser.writeSD(sd, *this); - return sd; - } - - LLSDParamAdapter(const T& val) - : T(val) - { - T::operator=(val); - } -}; - -#endif // LL_LLSDPARAM_H - -- cgit v1.2.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 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.2.3 From 6e214960ce203d1d50d7bd6bd04eedee3afd0fa3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Jan 2012 20:19:50 -0500 Subject: Define LLProcess::Params; accept create(const LLSDParamAdapter&). This allows callers to pass either LLSD formatted as before -- which all callers still do -- or an actual LLProcess::Params block. --- indra/llcommon/llprocess.cpp | 31 +++++++++++++------------------ indra/llcommon/llprocess.h | 33 +++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 8c0caca680..dfb2ed69e9 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -26,7 +26,6 @@ #include "linden_common.h" #include "llprocess.h" -#include "llsd.h" #include "llsdserialize.h" #include "stringize.h" @@ -41,7 +40,7 @@ struct LLProcessError: public std::runtime_error LLProcessError(const std::string& msg): std::runtime_error(msg) {} }; -LLProcessPtr LLProcess::create(const LLSD& params) +LLProcessPtr LLProcess::create(const LLSDParamAdapter& params) { try { @@ -54,16 +53,13 @@ LLProcessPtr LLProcess::create(const LLSD& params) } } -LLProcess::LLProcess(const LLSD& params): +LLProcess::LLProcess(const LLSDParamAdapter& params): mProcessID(0), - mAutokill(params["autokill"].asBoolean()) + mAutokill(params.autokill) { - // nonstandard default bool value - if (! params.has("autokill")) - mAutokill = true; - if (! params.has("executable")) + if (! params.validateBlock(true)) { - throw LLProcessError(STRINGIZE("not launched: missing 'executable'\n" + throw LLProcessError(STRINGIZE("not launched: failed parameter validation\n" << LLSDNotationStreamer(params))); } @@ -108,14 +104,14 @@ static std::string quote(const std::string& str) return result + "\""; } -void LLProcess::launch(const LLSD& params) +void LLProcess::launch(const LLSDParamAdapter& params) { PROCESS_INFORMATION pinfo; STARTUPINFOA sinfo; memset(&sinfo, 0, sizeof(sinfo)); - std::string args = quote(params["executable"]); - BOOST_FOREACH(const std::string& arg, llsd::inArray(params["args"])) + std::string args = quote(params.executable); + BOOST_FOREACH(const std::string& arg, params.args) { args += " "; args += quote(arg); @@ -128,7 +124,7 @@ void LLProcess::launch(const LLSD& params) // Convert wrapper to a real std::string so we can use c_str(); but use a // named variable instead of a temporary so c_str() pointer remains valid. - std::string cwd(params["cwd"]); + std::string cwd(params.cwd); const char * working_directory = 0; if (! cwd.empty()) working_directory = cwd.c_str(); @@ -212,7 +208,7 @@ static bool reap_pid(pid_t pid) return false; } -void LLProcess::launch(const LLSD& params) +void LLProcess::launch(const LLSDParamAdapter& params) { // flush all buffers before the child inherits them ::fflush(NULL); @@ -222,7 +218,7 @@ void LLProcess::launch(const LLSD& params) { // child process - std::string cwd(params["cwd"]); + std::string cwd(params.cwd); if (! cwd.empty()) { // change to the desired child working directory @@ -239,12 +235,11 @@ void LLProcess::launch(const LLSD& params) std::vector fake_argv; // add the executable path - std::string executable(params["executable"]); + std::string executable(params.executable); fake_argv.push_back(executable.c_str()); // and any arguments - const LLSD& params_args(params["args"]); - std::vector args(params_args.beginArray(), params_args.endArray()); + std::vector args(params.args.begin(), params.args.end()); BOOST_FOREACH(const std::string& arg, args) { fake_argv.push_back(arg.c_str()); diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 9a74cfe829..9ea129baf2 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -27,6 +27,8 @@ #ifndef LL_LLPROCESS_H #define LL_LLPROCESS_H +#include "llinitparam.h" +#include "llsdparam.h" #include #include @@ -35,8 +37,6 @@ #include #endif -class LLSD; - class LLProcess; /// LLProcess instances are created on the heap by static factory methods and /// managed by ref-counted pointers. @@ -50,17 +50,38 @@ class LL_COMMON_API LLProcess: public boost::noncopyable { LOG_CLASS(LLProcess); public: + /// Param block definition + struct Params: public LLInitParam::Block + { + Params(): + executable("executable"), + args("args"), + cwd("cwd"), + autokill("autokill", true) + {} + + /// pathname of executable + Mandatory executable; + /// zero or more additional command-line arguments + Multiple args; + /// current working directory, if need it changed + Optional cwd; + /// implicitly kill process on destruction of LLProcess object + Optional autokill; + }; /** - * Factory accepting LLSD::Map. + * Factory accepting either plain LLSD::Map or Params block. * MAY RETURN DEFAULT-CONSTRUCTED LLProcessPtr if params invalid! * + * Redundant with Params definition above? + * * 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); + static LLProcessPtr create(const LLSDParamAdapter& params); virtual ~LLProcess(); // isRunning isn't const because, if child isn't running, it clears stored @@ -96,8 +117,8 @@ public: private: /// constructor is private: use create() instead - LLProcess(const LLSD& params); - void launch(const LLSD& params); + LLProcess(const LLSDParamAdapter& params); + void launch(const LLSDParamAdapter& params); id mProcessID; bool mAutokill; -- cgit v1.2.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(-) 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.2.3 From e690373b059117e316f7704309321aaca2839668 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 21 Jan 2012 19:46:46 -0500 Subject: Since lltrans.h moved to llui, fix linux_updater/CMakeLists.txt. --- indra/linux_updater/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/linux_updater/CMakeLists.txt b/indra/linux_updater/CMakeLists.txt index 00a78b2a8f..4377a6333c 100644 --- a/indra/linux_updater/CMakeLists.txt +++ b/indra/linux_updater/CMakeLists.txt @@ -10,14 +10,14 @@ include(UI) include(LLCommon) include(LLVFS) include(LLXML) -include(LLXUIXML) +include(LLUI) include(Linking) include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LLXUIXML_INCLUDE_DIRS} + ${LLUI_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} ${CARES_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} @@ -42,7 +42,7 @@ target_link_libraries(linux-updater ${CRYPTO_LIBRARIES} ${UI_LIBRARIES} ${LLXML_LIBRARIES} - ${LLXUIXML_LIBRARIES} + ${LLUI_LIBRARIES} ${LLVFS_LIBRARIES} ${LLCOMMON_LIBRARIES} ) -- cgit v1.2.3 From b9a03b95aafb07eb32a8f99a671f2216acce96d4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 22 Jan 2012 09:16:11 -0500 Subject: On Windows, introduce viewer Job Object and assign children to it. The idea is that, with the right flag settings, this will cause the OS to terminate remaining viewer child processes when the viewer terminates -- whether or not it terminates intentionally. Of course, if LLProcess's caller specifies autokill=false, e.g. to run the viewer updater, that asserts that we WANT the child to persist beyond the viewer session itself. --- indra/llcommon/llprocess.cpp | 167 +++++++++++++++++++++++++++++++++---------- 1 file changed, 128 insertions(+), 39 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index dfb2ed69e9..d30d87411d 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -27,6 +27,7 @@ #include "linden_common.h" #include "llprocess.h" #include "llsdserialize.h" +#include "llsingleton.h" #include "stringize.h" #include @@ -80,43 +81,84 @@ bool LLProcess::isRunning(void) return (mProcessID != 0); } +/***************************************************************************** +* Windows specific +*****************************************************************************/ #if LL_WINDOWS -static std::string quote(const std::string& str) +static std::string WindowsErrorString(const std::string& operation); +static std::string quote(const std::string&); + +/** + * Wrap a Windows Job Object for use in managing child-process lifespan. + * + * On Windows, we use a Job Object to constrain the lifespan of any + * autokill=true child process to the viewer's own lifespan: + * http://stackoverflow.com/questions/53208/how-do-i-automatically-destroy-child-processes-in-windows + * (thanks Richard!). + * + * We manage it using an LLSingleton for a couple of reasons: + * + * # Lazy initialization: if some viewer session never launches a child + * process, we should never have to create a Job Object. + * # Cross-DLL support: be wary of C++ statics when multiple DLLs are + * involved. + */ +class LLJob: public LLSingleton { - std::string::size_type len(str.length()); - // If the string is already quoted, assume user knows what s/he's doing. - if (len >= 2 && str[0] == '"' && str[len-1] == '"') +public: + void assignProcess(const std::string& prog, HANDLE hProcess) { - return str; + // If we never managed to initialize this Job Object, can't use it -- + // but don't keep spamming the log, we already emitted warnings when + // we first tried to create. + if (! mJob) + return; + + if (! AssignProcessToJobObject(mJob, hProcess)) + { + LL_WARNS("LLProcess") << WindowsErrorString(STRINGIZE("AssignProcessToJobObject(\"" + << prog << "\")")) << LL_ENDL; + } } - // Not already quoted: do it. - std::string result("\""); - for (std::string::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) +private: + LLJob(): + mJob(0) { - if (*ci == '"') + mJob = CreateJobObject(NULL, NULL); + if (! mJob) { - result.append("\\"); + LL_WARNS("LLProcess") << WindowsErrorString("CreateJobObject()") << LL_ENDL; + return; + } + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 }; + + // Configure all child processes associated with this new job object + // to terminate when the calling process (us!) terminates. + jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (! SetInformationJobObject(mJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) + { + LL_WARNS("LLProcess") << WindowsErrorString("SetInformationJobObject()") << LL_ENDL; } - result.push_back(*ci); } - return result + "\""; -} + + HANDLE mJob; +}; void LLProcess::launch(const LLSDParamAdapter& params) { PROCESS_INFORMATION pinfo; - STARTUPINFOA sinfo; - memset(&sinfo, 0, sizeof(sinfo)); - + STARTUPINFOA sinfo = { sizeof(sinfo) }; + std::string args = quote(params.executable); BOOST_FOREACH(const std::string& arg, params.args) { args += " "; args += quote(arg); } - + // So retarded. Windows requires that the second parameter to // CreateProcessA be a writable (non-const) string... std::vector args2(args.begin(), args.end()); @@ -130,28 +172,14 @@ void LLProcess::launch(const LLSDParamAdapter& params) working_directory = cwd.c_str(); if( ! CreateProcessA( NULL, &args2[0], NULL, NULL, FALSE, 0, NULL, working_directory, &sinfo, &pinfo ) ) { - int result = GetLastError(); - - LPTSTR error_str = 0; - if( - FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - result, - 0, - (LPTSTR)&error_str, - 0, - NULL) - != 0) - { - char message[256]; - wcstombs(message, error_str, sizeof(message)); - message[sizeof(message)-1] = 0; - LocalFree(error_str); - throw LLProcessError(STRINGIZE("CreateProcessA failed (" << result << "): " - << message)); - } - throw LLProcessError(STRINGIZE("CreateProcessA failed (" << result - << "), but FormatMessage() did not explain")); + throw LLProcessError(WindowsErrorString("CreateProcessA")); + } + + // Now associate the new child process with our Job Object -- unless + // autokill is false, i.e. caller asserts the child should persist. + if (params.autokill) + { + LLJob::instance().assignProcess(params.executable, pinfo.hProcess); } // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on @@ -184,6 +212,67 @@ bool LLProcess::kill(void) return ! isRunning(); } +/** + * Double-quote an argument string, unless it's already double-quoted. If we + * quote it, escape any embedded double-quote with backslash. + * + * LLProcess::create()'s caller passes a Unix-style array of strings for + * command-line arguments. Our caller can and should expect that these will be + * passed to the child process as individual arguments, regardless of content + * (e.g. embedded spaces). But because Windows invokes any child process with + * a single command-line string, this means we must quote each argument behind + * the scenes. + */ +static std::string quote(const std::string& str) +{ + std::string::size_type len(str.length()); + // If the string is already quoted, assume user knows what s/he's doing. + if (len >= 2 && str[0] == '"' && str[len-1] == '"') + { + return str; + } + + // Not already quoted: do it. + std::string result("\""); + for (std::string::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) + { + if (*ci == '"') + { + result.append("\\"); + } + result.push_back(*ci); + } + return result + "\""; +} + +/// GetLastError()/FormatMessage() boilerplate +static std::string WindowsErrorString(const std::string& operation) +{ + int result = GetLastError(); + + LPTSTR error_str = 0; + if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + result, + 0, + (LPTSTR)&error_str, + 0, + NULL) + != 0) + { + char message[256]; + wcstombs(message, error_str, sizeof(message)); + message[sizeof(message)-1] = 0; + LocalFree(error_str); + return STRINGIZE(operation << " failed (" << result << "): " << message); + } + return STRINGIZE(operation << " failed (" << result + << "), but FormatMessage() did not explain"); +} + +/***************************************************************************** +* Non-Windows specific +*****************************************************************************/ #else // Mac and linux #include -- cgit v1.2.3 From aa1bbe3277842a9a6e7db5227b35f1fbea50b7a6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 22 Jan 2012 10:58:16 -0500 Subject: Make LLProcess::Params streamable; use that in LLExternalEditor. --- indra/llcommon/llprocess.cpp | 15 +++++++++++++++ indra/llcommon/llprocess.h | 4 ++++ indra/newview/llexternaleditor.cpp | 14 ++------------ 3 files changed, 21 insertions(+), 12 deletions(-) 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.2.3 From 748d1b311fdecf123df40bd7d22dd7e19afaca84 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 22 Jan 2012 11:56:38 -0500 Subject: Add LLProcess logging on launch(), kill(), isRunning(). Much as I dislike viewer log spam, seems to me starting a child process, killing it and observing its termination are noteworthy events. New logging makes LLExternalEditor launch message redundant; removed. --- indra/llcommon/llprocess.cpp | 34 +++++++++++++++++++++++++--------- indra/llcommon/llprocess.h | 7 ++++--- indra/newview/llexternaleditor.cpp | 4 +--- 3 files changed, 30 insertions(+), 15 deletions(-) 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.2.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(-) 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.2.3 From 738483e6302af5a9ad00fa3df17efe5336a03a41 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 22 Jan 2012 13:05:34 -0500 Subject: Every singleton needs a friend... --- indra/llcommon/llprocess.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 6d329a3fa1..eb7ce4129b 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -138,6 +138,7 @@ public: } private: + friend class LLSingleton; LLJob(): mJob(0) { -- cgit v1.2.3 From 507e136f9a25179992b2093e10ade1093cc71698 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Jan 2012 16:24:33 -0500 Subject: Per Richard: close unusable Job Object; move quote() to LLStringUtil. If LLProcess can't set the right flag on a Windows Job Object, the object isn't useful to us, so we might as well discard it. quote() is sufficiently general that it belongs in LLStringUtil instead of buried as a static helper function in llprocess.cpp. --- indra/llcommon/llprocess.cpp | 49 +++------- indra/llcommon/llstring.h | 223 +++++++++++++++++++++++++------------------ 2 files changed, 141 insertions(+), 131 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index eb7ce4129b..2c7512419d 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -28,6 +28,7 @@ #include "llprocess.h" #include "llsdserialize.h" #include "llsingleton.h" +#include "llstring.h" #include "stringize.h" #include @@ -102,7 +103,6 @@ std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) #if LL_WINDOWS static std::string WindowsErrorString(const std::string& operation); -static std::string quote(const std::string&); /** * Wrap a Windows Job Object for use in managing child-process lifespan. @@ -157,6 +157,10 @@ private: if (! SetInformationJobObject(mJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) { LL_WARNS("LLProcess") << WindowsErrorString("SetInformationJobObject()") << LL_ENDL; + // This Job Object is useless to us + CloseHandle(mJob); + // prevent assignProcess() from trying to use it + mJob = 0; } } @@ -168,11 +172,17 @@ void LLProcess::launch(const LLSDParamAdapter& params) PROCESS_INFORMATION pinfo; STARTUPINFOA sinfo = { sizeof(sinfo) }; - std::string args = quote(params.executable); + // LLProcess::create()'s caller passes a Unix-style array of strings for + // command-line arguments. Our caller can and should expect that these will be + // passed to the child process as individual arguments, regardless of content + // (e.g. embedded spaces). But because Windows invokes any child process with + // a single command-line string, this means we must quote each argument behind + // the scenes. + std::string args = LLStringUtil::quote(params.executable); BOOST_FOREACH(const std::string& arg, params.args) { args += " "; - args += quote(arg); + args += LLStringUtil::quote(arg); } // So retarded. Windows requires that the second parameter to @@ -236,39 +246,6 @@ bool LLProcess::kill(void) return ! isRunning(); } -/** - * Double-quote an argument string, unless it's already double-quoted. If we - * quote it, escape any embedded double-quote with backslash. - * - * LLProcess::create()'s caller passes a Unix-style array of strings for - * command-line arguments. Our caller can and should expect that these will be - * passed to the child process as individual arguments, regardless of content - * (e.g. embedded spaces). But because Windows invokes any child process with - * a single command-line string, this means we must quote each argument behind - * the scenes. - */ -static std::string quote(const std::string& str) -{ - std::string::size_type len(str.length()); - // If the string is already quoted, assume user knows what s/he's doing. - if (len >= 2 && str[0] == '"' && str[len-1] == '"') - { - return str; - } - - // Not already quoted: do it. - std::string result("\""); - for (std::string::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) - { - if (*ci == '"') - { - result.append("\\"); - } - result.push_back(*ci); - } - return result + "\""; -} - /// GetLastError()/FormatMessage() boilerplate static std::string WindowsErrorString(const std::string& operation) { diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 7e41e787b5..d3f1d01aa2 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -237,40 +237,41 @@ private: static std::string sLocale; public: - typedef typename std::basic_string::size_type size_type; + typedef std::basic_string string_type; + typedef typename string_type::size_type size_type; public: ///////////////////////////////////////////////////////////////////////////////////////// // Static Utility functions that operate on std::strings - static const std::basic_string null; + static const string_type null; typedef std::map format_map_t; - LL_COMMON_API static void getTokens(const std::basic_string& instr, std::vector >& tokens, const std::basic_string& delims); - LL_COMMON_API static void formatNumber(std::basic_string& numStr, std::basic_string decimals); - LL_COMMON_API static bool formatDatetime(std::basic_string& replacement, std::basic_string token, std::basic_string param, S32 secFromEpoch); - LL_COMMON_API static S32 format(std::basic_string& s, const format_map_t& substitutions); - LL_COMMON_API static S32 format(std::basic_string& s, const LLSD& substitutions); - LL_COMMON_API static bool simpleReplacement(std::basic_string& replacement, std::basic_string token, const format_map_t& substitutions); - LL_COMMON_API static bool simpleReplacement(std::basic_string& replacement, std::basic_string token, const LLSD& substitutions); + LL_COMMON_API static void getTokens(const string_type& instr, std::vector& tokens, const string_type& delims); + LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals); + LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch); + LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions); + LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions); + LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions); + LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions); LL_COMMON_API static void setLocale (std::string inLocale); LL_COMMON_API static std::string getLocale (void); - static bool isValidIndex(const std::basic_string& string, size_type i) + static bool isValidIndex(const string_type& string, size_type i) { return !string.empty() && (0 <= i) && (i <= string.size()); } - static void trimHead(std::basic_string& string); - static void trimTail(std::basic_string& string); - static void trim(std::basic_string& string) { trimHead(string); trimTail(string); } - static void truncate(std::basic_string& string, size_type count); + static void trimHead(string_type& string); + static void trimTail(string_type& string); + static void trim(string_type& string) { trimHead(string); trimTail(string); } + static void truncate(string_type& string, size_type count); - static void toUpper(std::basic_string& string); - static void toLower(std::basic_string& string); + static void toUpper(string_type& string); + static void toLower(string_type& string); // True if this is the head of s. - static BOOL isHead( const std::basic_string& string, const T* s ); + static BOOL isHead( const string_type& string, const T* s ); /** * @brief Returns true if string starts with substr @@ -278,8 +279,8 @@ public: * If etither string or substr are empty, this method returns false. */ static bool startsWith( - const std::basic_string& string, - const std::basic_string& substr); + const string_type& string, + const string_type& substr); /** * @brief Returns true if string ends in substr @@ -287,19 +288,26 @@ public: * If etither string or substr are empty, this method returns false. */ static bool endsWith( - const std::basic_string& string, - const std::basic_string& substr); + const string_type& string, + const string_type& substr); - static void addCRLF(std::basic_string& string); - static void removeCRLF(std::basic_string& string); + static void addCRLF(string_type& string); + static void removeCRLF(string_type& string); - static void replaceTabsWithSpaces( std::basic_string& string, size_type spaces_per_tab ); - static void replaceNonstandardASCII( std::basic_string& string, T replacement ); - static void replaceChar( std::basic_string& string, T target, T replacement ); - static void replaceString( std::basic_string& string, std::basic_string target, std::basic_string replacement ); + static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab ); + static void replaceNonstandardASCII( string_type& string, T replacement ); + static void replaceChar( string_type& string, T target, T replacement ); + static void replaceString( string_type& string, string_type target, string_type replacement ); - static BOOL containsNonprintable(const std::basic_string& string); - static void stripNonprintable(std::basic_string& string); + static BOOL containsNonprintable(const string_type& string); + static void stripNonprintable(string_type& string); + + /** + * Double-quote an argument string, unless it's already double-quoted. If we + * quote it, escape any embedded double-quote with the escape string (default + * backslash). + */ + string_type quote(const string_type& str, const string_type& escape="\\"); /** * @brief Unsafe way to make ascii characters. You should probably @@ -308,18 +316,18 @@ public: * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII * should work. */ - static void _makeASCII(std::basic_string& string); + static void _makeASCII(string_type& string); // Conversion to other data types - static BOOL convertToBOOL(const std::basic_string& string, BOOL& value); - static BOOL convertToU8(const std::basic_string& string, U8& value); - static BOOL convertToS8(const std::basic_string& string, S8& value); - static BOOL convertToS16(const std::basic_string& string, S16& value); - static BOOL convertToU16(const std::basic_string& string, U16& value); - static BOOL convertToU32(const std::basic_string& string, U32& value); - static BOOL convertToS32(const std::basic_string& string, S32& value); - static BOOL convertToF32(const std::basic_string& string, F32& value); - static BOOL convertToF64(const std::basic_string& string, F64& value); + static BOOL convertToBOOL(const string_type& string, BOOL& value); + static BOOL convertToU8(const string_type& string, U8& value); + static BOOL convertToS8(const string_type& string, S8& value); + static BOOL convertToS16(const string_type& string, S16& value); + static BOOL convertToU16(const string_type& string, U16& value); + static BOOL convertToU32(const string_type& string, U32& value); + static BOOL convertToS32(const string_type& string, S32& value); + static BOOL convertToF32(const string_type& string, F32& value); + static BOOL convertToF64(const string_type& string, F64& value); ///////////////////////////////////////////////////////////////////////////////////////// // Utility functions for working with char*'s and strings @@ -327,24 +335,24 @@ public: // Like strcmp but also handles empty strings. Uses // current locale. static S32 compareStrings(const T* lhs, const T* rhs); - static S32 compareStrings(const std::basic_string& lhs, const std::basic_string& rhs); + static S32 compareStrings(const string_type& lhs, const string_type& rhs); // case insensitive version of above. Uses current locale on // Win32, and falls back to a non-locale aware comparison on // Linux. static S32 compareInsensitive(const T* lhs, const T* rhs); - static S32 compareInsensitive(const std::basic_string& lhs, const std::basic_string& rhs); + static S32 compareInsensitive(const string_type& lhs, const string_type& rhs); // Case sensitive comparison with good handling of numbers. Does not use current locale. // a.k.a. strdictcmp() - static S32 compareDict(const std::basic_string& a, const std::basic_string& b); + static S32 compareDict(const string_type& a, const string_type& b); // Case *in*sensitive comparison with good handling of numbers. Does not use current locale. // a.k.a. strdictcmp() - static S32 compareDictInsensitive(const std::basic_string& a, const std::basic_string& b); + static S32 compareDictInsensitive(const string_type& a, const string_type& b); // Puts compareDict() in a form appropriate for LL container classes to use for sorting. - static BOOL precedesDict( const std::basic_string& a, const std::basic_string& b ); + static BOOL precedesDict( const string_type& a, const string_type& b ); // A replacement for strncpy. // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds @@ -352,7 +360,7 @@ public: static void copy(T* dst, const T* src, size_type dst_size); // Copies src into dst at a given offset. - static void copyInto(std::basic_string& dst, const std::basic_string& src, size_type offset); + static void copyInto(string_type& dst, const string_type& src, size_type offset); static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); } @@ -362,7 +370,7 @@ public: #endif private: - LL_COMMON_API static size_type getSubstitution(const std::basic_string& instr, size_type& start, std::vector >& tokens); + LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector& tokens); }; template const std::basic_string LLStringUtilBase::null; @@ -669,7 +677,7 @@ S32 LLStringUtilBase::compareStrings(const T* lhs, const T* rhs) //static template -S32 LLStringUtilBase::compareStrings(const std::basic_string& lhs, const std::basic_string& rhs) +S32 LLStringUtilBase::compareStrings(const string_type& lhs, const string_type& rhs) { return LLStringOps::collate(lhs.c_str(), rhs.c_str()); } @@ -695,8 +703,8 @@ S32 LLStringUtilBase::compareInsensitive(const T* lhs, const T* rhs ) } else { - std::basic_string lhs_string(lhs); - std::basic_string rhs_string(rhs); + string_type lhs_string(lhs); + string_type rhs_string(rhs); LLStringUtilBase::toUpper(lhs_string); LLStringUtilBase::toUpper(rhs_string); result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); @@ -706,10 +714,10 @@ S32 LLStringUtilBase::compareInsensitive(const T* lhs, const T* rhs ) //static template -S32 LLStringUtilBase::compareInsensitive(const std::basic_string& lhs, const std::basic_string& rhs) +S32 LLStringUtilBase::compareInsensitive(const string_type& lhs, const string_type& rhs) { - std::basic_string lhs_string(lhs); - std::basic_string rhs_string(rhs); + string_type lhs_string(lhs); + string_type rhs_string(rhs); LLStringUtilBase::toUpper(lhs_string); LLStringUtilBase::toUpper(rhs_string); return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); @@ -720,7 +728,7 @@ S32 LLStringUtilBase::compareInsensitive(const std::basic_string& lhs, con //static template -S32 LLStringUtilBase::compareDict(const std::basic_string& astr, const std::basic_string& bstr) +S32 LLStringUtilBase::compareDict(const string_type& astr, const string_type& bstr) { const T* a = astr.c_str(); const T* b = bstr.c_str(); @@ -761,7 +769,7 @@ S32 LLStringUtilBase::compareDict(const std::basic_string& astr, const std // static template -S32 LLStringUtilBase::compareDictInsensitive(const std::basic_string& astr, const std::basic_string& bstr) +S32 LLStringUtilBase::compareDictInsensitive(const string_type& astr, const string_type& bstr) { const T* a = astr.c_str(); const T* b = bstr.c_str(); @@ -796,7 +804,7 @@ S32 LLStringUtilBase::compareDictInsensitive(const std::basic_string& astr // Puts compareDict() in a form appropriate for LL container classes to use for sorting. // static template -BOOL LLStringUtilBase::precedesDict( const std::basic_string& a, const std::basic_string& b ) +BOOL LLStringUtilBase::precedesDict( const string_type& a, const string_type& b ) { if( a.size() && b.size() ) { @@ -810,7 +818,7 @@ BOOL LLStringUtilBase::precedesDict( const std::basic_string& a, const std //static template -void LLStringUtilBase::toUpper(std::basic_string& string) +void LLStringUtilBase::toUpper(string_type& string) { if( !string.empty() ) { @@ -824,7 +832,7 @@ void LLStringUtilBase::toUpper(std::basic_string& string) //static template -void LLStringUtilBase::toLower(std::basic_string& string) +void LLStringUtilBase::toLower(string_type& string) { if( !string.empty() ) { @@ -838,7 +846,7 @@ void LLStringUtilBase::toLower(std::basic_string& string) //static template -void LLStringUtilBase::trimHead(std::basic_string& string) +void LLStringUtilBase::trimHead(string_type& string) { if( !string.empty() ) { @@ -853,7 +861,7 @@ void LLStringUtilBase::trimHead(std::basic_string& string) //static template -void LLStringUtilBase::trimTail(std::basic_string& string) +void LLStringUtilBase::trimTail(string_type& string) { if( string.size() ) { @@ -872,7 +880,7 @@ void LLStringUtilBase::trimTail(std::basic_string& string) // Replace line feeds with carriage return-line feed pairs. //static template -void LLStringUtilBase::addCRLF(std::basic_string& string) +void LLStringUtilBase::addCRLF(string_type& string) { const T LF = 10; const T CR = 13; @@ -914,7 +922,7 @@ void LLStringUtilBase::addCRLF(std::basic_string& string) // Remove all carriage returns //static template -void LLStringUtilBase::removeCRLF(std::basic_string& string) +void LLStringUtilBase::removeCRLF(string_type& string) { const T CR = 13; @@ -935,10 +943,10 @@ void LLStringUtilBase::removeCRLF(std::basic_string& string) //static template -void LLStringUtilBase::replaceChar( std::basic_string& string, T target, T replacement ) +void LLStringUtilBase::replaceChar( string_type& string, T target, T replacement ) { size_type found_pos = 0; - while( (found_pos = string.find(target, found_pos)) != std::basic_string::npos ) + while( (found_pos = string.find(target, found_pos)) != string_type::npos ) { string[found_pos] = replacement; found_pos++; // avoid infinite defeat if target == replacement @@ -947,10 +955,10 @@ void LLStringUtilBase::replaceChar( std::basic_string& string, T target, T //static template -void LLStringUtilBase::replaceString( std::basic_string& string, std::basic_string target, std::basic_string replacement ) +void LLStringUtilBase::replaceString( string_type& string, string_type target, string_type replacement ) { size_type found_pos = 0; - while( (found_pos = string.find(target, found_pos)) != std::basic_string::npos ) + while( (found_pos = string.find(target, found_pos)) != string_type::npos ) { string.replace( found_pos, target.length(), replacement ); found_pos += replacement.length(); // avoid infinite defeat if replacement contains target @@ -959,7 +967,7 @@ void LLStringUtilBase::replaceString( std::basic_string& string, std::basi //static template -void LLStringUtilBase::replaceNonstandardASCII( std::basic_string& string, T replacement ) +void LLStringUtilBase::replaceNonstandardASCII( string_type& string, T replacement ) { const char LF = 10; const S8 MIN = 32; @@ -979,12 +987,12 @@ void LLStringUtilBase::replaceNonstandardASCII( std::basic_string& string, //static template -void LLStringUtilBase::replaceTabsWithSpaces( std::basic_string& str, size_type spaces_per_tab ) +void LLStringUtilBase::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab ) { const T TAB = '\t'; const T SPACE = ' '; - std::basic_string out_str; + string_type out_str; // Replace tabs with spaces for (size_type i = 0; i < str.length(); i++) { @@ -1003,7 +1011,7 @@ void LLStringUtilBase::replaceTabsWithSpaces( std::basic_string& str, size //static template -BOOL LLStringUtilBase::containsNonprintable(const std::basic_string& string) +BOOL LLStringUtilBase::containsNonprintable(const string_type& string) { const char MIN = 32; BOOL rv = FALSE; @@ -1020,7 +1028,7 @@ BOOL LLStringUtilBase::containsNonprintable(const std::basic_string& strin //static template -void LLStringUtilBase::stripNonprintable(std::basic_string& string) +void LLStringUtilBase::stripNonprintable(string_type& string) { const char MIN = 32; size_type j = 0; @@ -1051,8 +1059,33 @@ void LLStringUtilBase::stripNonprintable(std::basic_string& string) delete []c_string; } +template +std::basic_string LLStringUtilBase::quote(const string_type& str, const string_type& escape) +{ + 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. + string_type result; + result.push_back('"'); + for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) + { + if (*ci == '"') + { + result.append(escape); + } + result.push_back(*ci); + } + result.push_back('"'); + return result; +} + template -void LLStringUtilBase::_makeASCII(std::basic_string& string) +void LLStringUtilBase::_makeASCII(string_type& string) { // Replace non-ASCII chars with LL_UNKNOWN_CHAR for (size_type i = 0; i < string.length(); i++) @@ -1082,7 +1115,7 @@ void LLStringUtilBase::copy( T* dst, const T* src, size_type dst_size ) // static template -void LLStringUtilBase::copyInto(std::basic_string& dst, const std::basic_string& src, size_type offset) +void LLStringUtilBase::copyInto(string_type& dst, const string_type& src, size_type offset) { if ( offset == dst.length() ) { @@ -1092,7 +1125,7 @@ void LLStringUtilBase::copyInto(std::basic_string& dst, const std::basic_s } else { - std::basic_string tail = dst.substr(offset); + string_type tail = dst.substr(offset); dst = dst.substr(0, offset); dst += src; @@ -1103,7 +1136,7 @@ void LLStringUtilBase::copyInto(std::basic_string& dst, const std::basic_s // True if this is the head of s. //static template -BOOL LLStringUtilBase::isHead( const std::basic_string& string, const T* s ) +BOOL LLStringUtilBase::isHead( const string_type& string, const T* s ) { if( string.empty() ) { @@ -1119,8 +1152,8 @@ BOOL LLStringUtilBase::isHead( const std::basic_string& string, const T* s // static template bool LLStringUtilBase::startsWith( - const std::basic_string& string, - const std::basic_string& substr) + const string_type& string, + const string_type& substr) { if(string.empty() || (substr.empty())) return false; if(0 == string.find(substr)) return true; @@ -1130,8 +1163,8 @@ bool LLStringUtilBase::startsWith( // static template bool LLStringUtilBase::endsWith( - const std::basic_string& string, - const std::basic_string& substr) + const string_type& string, + const string_type& substr) { if(string.empty() || (substr.empty())) return false; std::string::size_type idx = string.rfind(substr); @@ -1141,14 +1174,14 @@ bool LLStringUtilBase::endsWith( template -BOOL LLStringUtilBase::convertToBOOL(const std::basic_string& string, BOOL& value) +BOOL LLStringUtilBase::convertToBOOL(const string_type& string, BOOL& value) { if( string.empty() ) { return FALSE; } - std::basic_string temp( string ); + string_type temp( string ); trim(temp); if( (temp == "1") || @@ -1178,7 +1211,7 @@ BOOL LLStringUtilBase::convertToBOOL(const std::basic_string& string, BOOL } template -BOOL LLStringUtilBase::convertToU8(const std::basic_string& string, U8& value) +BOOL LLStringUtilBase::convertToU8(const string_type& string, U8& value) { S32 value32 = 0; BOOL success = convertToS32(string, value32); @@ -1191,7 +1224,7 @@ BOOL LLStringUtilBase::convertToU8(const std::basic_string& string, U8& va } template -BOOL LLStringUtilBase::convertToS8(const std::basic_string& string, S8& value) +BOOL LLStringUtilBase::convertToS8(const string_type& string, S8& value) { S32 value32 = 0; BOOL success = convertToS32(string, value32); @@ -1204,7 +1237,7 @@ BOOL LLStringUtilBase::convertToS8(const std::basic_string& string, S8& va } template -BOOL LLStringUtilBase::convertToS16(const std::basic_string& string, S16& value) +BOOL LLStringUtilBase::convertToS16(const string_type& string, S16& value) { S32 value32 = 0; BOOL success = convertToS32(string, value32); @@ -1217,7 +1250,7 @@ BOOL LLStringUtilBase::convertToS16(const std::basic_string& string, S16& } template -BOOL LLStringUtilBase::convertToU16(const std::basic_string& string, U16& value) +BOOL LLStringUtilBase::convertToU16(const string_type& string, U16& value) { S32 value32 = 0; BOOL success = convertToS32(string, value32); @@ -1230,17 +1263,17 @@ BOOL LLStringUtilBase::convertToU16(const std::basic_string& string, U16& } template -BOOL LLStringUtilBase::convertToU32(const std::basic_string& string, U32& value) +BOOL LLStringUtilBase::convertToU32(const string_type& string, U32& value) { if( string.empty() ) { return FALSE; } - std::basic_string temp( string ); + string_type temp( string ); trim(temp); U32 v; - std::basic_istringstream i_stream((std::basic_string)temp); + std::basic_istringstream i_stream((string_type)temp); if(i_stream >> v) { value = v; @@ -1250,17 +1283,17 @@ BOOL LLStringUtilBase::convertToU32(const std::basic_string& string, U32& } template -BOOL LLStringUtilBase::convertToS32(const std::basic_string& string, S32& value) +BOOL LLStringUtilBase::convertToS32(const string_type& string, S32& value) { if( string.empty() ) { return FALSE; } - std::basic_string temp( string ); + string_type temp( string ); trim(temp); S32 v; - std::basic_istringstream i_stream((std::basic_string)temp); + std::basic_istringstream i_stream((string_type)temp); if(i_stream >> v) { //TODO: figure out overflow and underflow reporting here @@ -1277,7 +1310,7 @@ BOOL LLStringUtilBase::convertToS32(const std::basic_string& string, S32& } template -BOOL LLStringUtilBase::convertToF32(const std::basic_string& string, F32& value) +BOOL LLStringUtilBase::convertToF32(const string_type& string, F32& value) { F64 value64 = 0.0; BOOL success = convertToF64(string, value64); @@ -1290,17 +1323,17 @@ BOOL LLStringUtilBase::convertToF32(const std::basic_string& string, F32& } template -BOOL LLStringUtilBase::convertToF64(const std::basic_string& string, F64& value) +BOOL LLStringUtilBase::convertToF64(const string_type& string, F64& value) { if( string.empty() ) { return FALSE; } - std::basic_string temp( string ); + string_type temp( string ); trim(temp); F64 v; - std::basic_istringstream i_stream((std::basic_string)temp); + std::basic_istringstream i_stream((string_type)temp); if(i_stream >> v) { //TODO: figure out overflow and underflow reporting here @@ -1317,7 +1350,7 @@ BOOL LLStringUtilBase::convertToF64(const std::basic_string& string, F64& } template -void LLStringUtilBase::truncate(std::basic_string& string, size_type count) +void LLStringUtilBase::truncate(string_type& string, size_type count) { size_type cur_size = string.size(); string.resize(count < cur_size ? count : cur_size); -- cgit v1.2.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(-) 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.2.3 From da5d243c8f76f43a6bb4000402fade76eee0be33 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Jan 2012 17:29:42 -0500 Subject: LLStringUtil methods are conventionally static. --- indra/llcommon/llstring.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index d3f1d01aa2..4c3936f9ab 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -307,7 +307,7 @@ public: * quote it, escape any embedded double-quote with the escape string (default * backslash). */ - string_type quote(const string_type& str, const string_type& escape="\\"); + static string_type quote(const string_type& str, const string_type& escape="\\"); /** * @brief Unsafe way to make ascii characters. You should probably -- cgit v1.2.3 From 61e98256df80822f9504a2037d5cbb029c39506d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Jan 2012 17:38:06 -0500 Subject: Clarify that items in LLProcess::Params::args are implicitly quoted. That is, we try to pass through each args entry as a separate child-process arvg[] entry, whitespace and all. --- indra/llcommon/llprocess.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 019c33592c..51c42582ea 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -63,7 +63,16 @@ public: /// pathname of executable Mandatory executable; - /// zero or more additional command-line arguments + /** + * zero or more additional command-line arguments. Arguments are + * passed through as exactly as we can manage, whitespace and all. + * @note On Windows we manage this by implicitly double-quoting each + * argument while assembling the command line. BUT if a given argument + * is already double-quoted, we don't double-quote it again. Try to + * avoid making use of this, though, as on Mac and Linux explicitly + * double-quoted args will be passed to the child process including + * the double quotes. + */ Multiple args; /// current working directory, if need it changed Optional cwd; -- cgit v1.2.3 From 27df0a84564d3a886661aae0faae74c2157cd31b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 27 Jan 2012 23:46:00 -0500 Subject: On Windows, only quote LLProcess arguments if they seem to need it. On Posix platforms, the OS argument mechanism makes quoting/reparsing unnecessary anyway, so this only affects Windows. Add optional 'triggers' parameter to LLStringUtils::quote() (default: space and double-quote). Only if the passed string contains a character in 'triggers' will it be double-quoted. This is observed to fix a Windows-specific problem in which plugin child process would fail to start because it wasn't expecting a quoted number. Use LLStringUtils::quote() more consistently in LLProcess implementation for logging. --- indra/llcommon/llprocess.cpp | 14 +++++++------- indra/llcommon/llstring.h | 26 +++++++++++++++++++++----- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 2c7512419d..2b7a534fb3 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -87,12 +87,12 @@ std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) std::string cwd(params.cwd); if (! cwd.empty()) { - out << "cd '" << cwd << "': "; + out << "cd " << LLStringUtil::quote(cwd) << ": "; } - out << '"' << std::string(params.executable) << '"'; + out << LLStringUtil::quote(params.executable); BOOST_FOREACH(const std::string& arg, params.args) { - out << " \"" << arg << '"'; + out << ' ' << LLStringUtil::quote(arg); } return out; } @@ -132,8 +132,8 @@ public: if (! AssignProcessToJobObject(mJob, hProcess)) { - LL_WARNS("LLProcess") << WindowsErrorString(STRINGIZE("AssignProcessToJobObject(\"" - << prog << "\")")) << LL_ENDL; + LL_WARNS("LLProcess") << WindowsErrorString(STRINGIZE("AssignProcessToJobObject(" + << prog << ")")) << LL_ENDL; } } @@ -206,7 +206,7 @@ void LLProcess::launch(const LLSDParamAdapter& params) mProcessID = pinfo.hProcess; CloseHandle(pinfo.hThread); // stops leaks - nothing else - mDesc = STRINGIZE('"' << std::string(params.executable) << "\" (" << pinfo.dwProcessId << ')'); + mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << pinfo.dwProcessId << ')'); LL_INFOS("LLProcess") << "Launched " << params << " (" << pinfo.dwProcessId << ")" << LL_ENDL; // Now associate the new child process with our Job Object -- unless @@ -356,7 +356,7 @@ void LLProcess::launch(const LLSDParamAdapter& params) // parent process mProcessID = child; - mDesc = STRINGIZE('"' << std::string(params.executable) << "\" (" << mProcessID << ')'); + mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcessID << ')'); LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL; } diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 4c3936f9ab..7b24b5e279 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -303,11 +303,17 @@ public: static void stripNonprintable(string_type& string); /** - * Double-quote an argument string, unless it's already double-quoted. If we - * quote it, escape any embedded double-quote with the escape string (default + * Double-quote an argument string if needed, unless it's already + * double-quoted. Decide whether it's needed based on the presence of any + * character in @a triggers (default space or double-quote). If we quote + * it, escape any embedded double-quote with the @a escape string (default * backslash). + * + * Passing triggers="" means always quote, unless it's already double-quoted. */ - static string_type quote(const string_type& str, const string_type& escape="\\"); + static string_type quote(const string_type& str, + const string_type& triggers=" \"", + const string_type& escape="\\"); /** * @brief Unsafe way to make ascii characters. You should probably @@ -1060,7 +1066,9 @@ void LLStringUtilBase::stripNonprintable(string_type& string) } template -std::basic_string LLStringUtilBase::quote(const string_type& str, const string_type& escape) +std::basic_string LLStringUtilBase::quote(const string_type& str, + const string_type& triggers, + const string_type& escape) { size_type len(str.length()); // If the string is already quoted, assume user knows what s/he's doing. @@ -1069,7 +1077,15 @@ std::basic_string LLStringUtilBase::quote(const string_type& str, const st return str; } - // Not already quoted: do it. + // Not already quoted: do we need to? triggers.empty() is a special case + // meaning "always quote." + if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos) + { + // no trigger characters, don't bother quoting + return str; + } + + // For whatever reason, we must quote this string. string_type result; result.push_back('"'); for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) -- cgit v1.2.3 From 803acbc5efde19c0acacfc7fe4990841dbf31a3e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 30 Jan 2012 10:14:10 -0500 Subject: Trim trailing "\r\n" from Windows FormatMessage() string for logging. --- indra/llcommon/llprocess.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 2b7a534fb3..8c0e8fe65e 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -261,11 +261,15 @@ static std::string WindowsErrorString(const std::string& operation) NULL) != 0) { + // convert from wide-char string to multi-byte string char message[256]; wcstombs(message, error_str, sizeof(message)); message[sizeof(message)-1] = 0; LocalFree(error_str); - return STRINGIZE(operation << " failed (" << result << "): " << message); + // convert to std::string to trim trailing whitespace + std::string mbsstr(message); + mbsstr.erase(mbsstr.find_last_not_of(" \t\r\n")); + return STRINGIZE(operation << " failed (" << result << "): " << mbsstr); } return STRINGIZE(operation << " failed (" << result << "), but FormatMessage() did not explain"); -- cgit v1.2.3 From 85581eefa63d8f8e8c5132c4cd7e137f6cb88869 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 30 Jan 2012 12:11:44 -0500 Subject: Expose 'handle' as well as 'id' on LLProcess objects. On Posix, these and the corresponding getProcessID()/getProcessHandle() accessors produce the same pid_t value; but on Windows, it's useful to distinguish an int-like 'id' useful to human log readers versus an opaque 'handle' for passing to platform-specific API functions. So make the distinction in a platform-independent way. --- indra/llcommon/llprocess.cpp | 50 ++++++++++++++++++++------------- indra/llcommon/llprocess.h | 42 +++++++++++++++++---------- indra/llcommon/tests/llprocess_test.cpp | 16 +++++------ 3 files changed, 66 insertions(+), 42 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 8c0e8fe65e..a7bafb8cb0 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -42,7 +42,7 @@ struct LLProcessError: public std::runtime_error LLProcessError(const std::string& msg): std::runtime_error(msg) {} }; -LLProcessPtr LLProcess::create(const LLSDParamAdapter& params) +LLProcessPtr LLProcess::create(const LLSDOrParams& params) { try { @@ -55,8 +55,9 @@ LLProcessPtr LLProcess::create(const LLSDParamAdapter& params) } } -LLProcess::LLProcess(const LLSDParamAdapter& params): +LLProcess::LLProcess(const LLSDOrParams& params): mProcessID(0), + mProcessHandle(0), mAutokill(params.autokill) { if (! params.validateBlock(true)) @@ -78,8 +79,18 @@ LLProcess::~LLProcess() bool LLProcess::isRunning(void) { - mProcessID = isRunning(mProcessID, mDesc); - return (mProcessID != 0); + mProcessHandle = isRunning(mProcessHandle, mDesc); + return (mProcessHandle != 0); +} + +LLProcess::id LLProcess::getProcessID() const +{ + return mProcessID; +} + +LLProcess::handle LLProcess::getProcessHandle() const +{ + return mProcessHandle; } std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) @@ -122,7 +133,7 @@ static std::string WindowsErrorString(const std::string& operation); class LLJob: public LLSingleton { public: - void assignProcess(const std::string& prog, HANDLE hProcess) + void assignProcess(const std::string& prog, handle hProcess) { // If we never managed to initialize this Job Object, can't use it -- // but don't keep spamming the log, we already emitted warnings when @@ -164,10 +175,10 @@ private: } } - HANDLE mJob; + handle mJob; }; -void LLProcess::launch(const LLSDParamAdapter& params) +void LLProcess::launch(const LLSDOrParams& params) { PROCESS_INFORMATION pinfo; STARTUPINFOA sinfo = { sizeof(sinfo) }; @@ -201,28 +212,28 @@ void LLProcess::launch(const LLSDParamAdapter& params) throw LLProcessError(WindowsErrorString("CreateProcessA")); } - // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on // CloseHandle(pinfo.hProcess); // stops leaks - nothing else - mProcessID = pinfo.hProcess; + mProcessID = pinfo.dwProcessId; + mProcessHandle = pinfo.hProcess; CloseHandle(pinfo.hThread); // stops leaks - nothing else - mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << pinfo.dwProcessId << ')'); - LL_INFOS("LLProcess") << "Launched " << params << " (" << pinfo.dwProcessId << ")" << LL_ENDL; + mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcessID << ')'); + LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL; // Now associate the new child process with our Job Object -- unless // autokill is false, i.e. caller asserts the child should persist. if (params.autokill) { - LLJob::instance().assignProcess(mDesc, mProcessID); + LLJob::instance().assignProcess(mDesc, mProcessHandle); } } -LLProcess::id LLProcess::isRunning(id handle, const std::string& desc) +LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) { - if (! handle) + if (! h) return 0; - DWORD waitresult = WaitForSingleObject(handle, 0); + DWORD waitresult = WaitForSingleObject(h, 0); if(waitresult == WAIT_OBJECT_0) { // the process has completed. @@ -233,16 +244,16 @@ LLProcess::id LLProcess::isRunning(id handle, const std::string& desc) return 0; } - return handle; + return h; } bool LLProcess::kill(void) { - if (! mProcessID) + if (! mProcessHandle) return false; LL_INFOS("LLProcess") << "killing " << mDesc << LL_ENDL; - TerminateProcess(mProcessID, 0); + TerminateProcess(mProcessHandle, 0); return ! isRunning(); } @@ -302,7 +313,7 @@ static bool reap_pid(pid_t pid) return false; } -void LLProcess::launch(const LLSDParamAdapter& params) +void LLProcess::launch(const LLSDOrParams& params) { // flush all buffers before the child inherits them ::fflush(NULL); @@ -359,6 +370,7 @@ void LLProcess::launch(const LLSDParamAdapter& params) // parent process mProcessID = child; + mProcessHandle = child; mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcessID << ')'); LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL; diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 51c42582ea..8a842589ec 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -35,7 +35,7 @@ #if LL_WINDOWS #define WIN32_LEAN_AND_MEAN -#include +#include // HANDLE (eye roll) #endif class LLProcess; @@ -79,6 +79,7 @@ public: /// implicitly kill process on destruction of LLProcess object Optional autokill; }; + typedef LLSDParamAdapter LLSDOrParams; /** * Factory accepting either plain LLSD::Map or Params block. @@ -91,7 +92,7 @@ public: * 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 LLSDParamAdapter& params); + static LLProcessPtr create(const LLSDOrParams& params); virtual ~LLProcess(); // isRunning isn't const because, if child isn't running, it clears stored @@ -103,35 +104,46 @@ public: bool kill(void); #if LL_WINDOWS - typedef HANDLE id; + typedef int id; ///< as returned by getProcessID() + typedef HANDLE handle; ///< as returned by getProcessHandle() #else - typedef pid_t id; + typedef pid_t id; + typedef pid_t handle; #endif - /// Get platform-specific process ID - id getProcessID() const { return mProcessID; }; + /** + * Get an int-like id value. This is primarily intended for a human reader + * to differentiate processes. + */ + id getProcessID() const; + /** + * Get a "handle" of a kind that you might pass to platform-specific API + * functions to engage features not directly supported by LLProcess. + */ + handle getProcessHandle() const; /** - * Test if a process (id obtained from getProcessID()) is still - * running. Return is same nonzero id value if still running, else + * Test if a process (@c handle obtained from getProcessHandle()) is still + * running. Return same nonzero @c handle 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); + * hchild = LLProcess::isRunning(hchild); * @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. + * a whole set of operations supported on freestanding @c handle values. + * New functionality should be added as nonstatic members operating on + * the same data as getProcessHandle(). */ - static id isRunning(id, const std::string& desc=""); + static handle isRunning(handle, const std::string& desc=""); private: /// constructor is private: use create() instead - LLProcess(const LLSDParamAdapter& params); - void launch(const LLSDParamAdapter& params); + LLProcess(const LLSDOrParams& params); + void launch(const LLSDOrParams& params); std::string mDesc; id mProcessID; + handle mProcessHandle; bool mAutokill; }; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 405540e436..4ad45bdf27 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -599,7 +599,7 @@ namespace tut { set_test_name("implicit kill()"); NamedTempFile out("out", "not started"); - LLProcess::id pid(0); + LLProcess::handle phandle(0); { PythonProcessLauncher py("kill()", "from __future__ import with_statement\n" @@ -614,8 +614,8 @@ namespace tut py.mParams.args.add(out.getName()); py.mPy = LLProcess::create(py.mParams); ensure("couldn't launch kill() script", py.mPy); - // Capture id for later - pid = py.mPy->getProcessID(); + // Capture handle for later + phandle = py.mPy->getProcessHandle(); // Wait for the script to wake up and do its first write int i = 0, timeout = 60; for ( ; i < timeout; ++i) @@ -630,7 +630,7 @@ namespace tut // Destroy the LLProcess, which should kill the child. } // wait for the script to terminate... one way or another. - while (LLProcess::isRunning(pid)) + while (LLProcess::isRunning(phandle)) { sleep(1); } @@ -646,7 +646,7 @@ namespace tut set_test_name("autokill"); NamedTempFile from("from", "not started"); NamedTempFile to("to", ""); - LLProcess::id pid(0); + LLProcess::handle phandle(0); { PythonProcessLauncher py("autokill", "from __future__ import with_statement\n" @@ -672,8 +672,8 @@ namespace tut 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(); + // Capture handle for later + phandle = py.mPy->getProcessHandle(); // Wait for the script to wake up and do its first write int i = 0, timeout = 60; for ( ; i < timeout; ++i) @@ -695,7 +695,7 @@ namespace tut outf << "go"; } // flush and close. // now wait for the script to terminate... one way or another. - while (LLProcess::isRunning(pid)) + while (LLProcess::isRunning(phandle)) { sleep(1); } -- cgit v1.2.3 From 60a777d2e3f9bec7a20e5de2df7cb3ecd2455b98 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 30 Jan 2012 12:32:05 -0500 Subject: LLProcess::handle must be qualified when used in LLJob class. --- indra/llcommon/llprocess.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index a7bafb8cb0..1e27f8ce1d 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -133,7 +133,7 @@ static std::string WindowsErrorString(const std::string& operation); class LLJob: public LLSingleton { public: - void assignProcess(const std::string& prog, handle hProcess) + void assignProcess(const std::string& prog, LLProcess::handle hProcess) { // If we never managed to initialize this Job Object, can't use it -- // but don't keep spamming the log, we already emitted warnings when @@ -175,7 +175,7 @@ private: } } - handle mJob; + LLProcess::handle mJob; }; void LLProcess::launch(const LLSDOrParams& params) -- cgit v1.2.3 From 491cd825561be1cf4a6f428a535811cbe0e3f179 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 30 Jan 2012 17:47:57 -0500 Subject: Set bit flag on CreateProcess() to allow AssignProcessToJobObject(). Windows 7 and friends tend to create a process already implicitly allocated to a job object, and a process can only belong to a single job object. Passing CREATE_BREAKAWAY_FROM_JOB in CreateProcessA()'s dwCreationFlags seems to bypass the access-denied error observed with AssignProcessToJobObject() otherwise. This change should (!) enable OS lifespan management for SLVoice.exe et al. --- indra/llcommon/llprocess.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 1e27f8ce1d..8611d67f25 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -207,7 +207,26 @@ void LLProcess::launch(const LLSDOrParams& params) const char * working_directory = 0; if (! cwd.empty()) working_directory = cwd.c_str(); - if( ! CreateProcessA( NULL, &args2[0], NULL, NULL, FALSE, 0, NULL, working_directory, &sinfo, &pinfo ) ) + + // It's important to pass CREATE_BREAKAWAY_FROM_JOB because Windows 7 et + // al. tend to implicitly launch new processes already bound to a job. From + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949%28v=vs.85%29.aspx : + // "The process must not already be assigned to a job; if it is, the + // function fails with ERROR_ACCESS_DENIED." ... + // "If the process is being monitored by the Program Compatibility + // Assistant (PCA), it is placed into a compatibility job. Therefore, the + // process must be created using CREATE_BREAKAWAY_FROM_JOB before it can + // be placed in another job." + if( ! CreateProcessA(NULL, // lpApplicationName + &args2[0], // lpCommandLine + NULL, // lpProcessAttributes + NULL, // lpThreadAttributes + FALSE, // bInheritHandles + CREATE_BREAKAWAY_FROM_JOB, // dwCreationFlags + NULL, // lpEnvironment + working_directory, // lpCurrentDirectory + &sinfo, // lpStartupInfo + &pinfo ) ) // lpProcessInformation { throw LLProcessError(WindowsErrorString("CreateProcessA")); } @@ -225,7 +244,7 @@ void LLProcess::launch(const LLSDOrParams& params) if (params.autokill) { LLJob::instance().assignProcess(mDesc, mProcessHandle); - } +} } LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) @@ -287,7 +306,7 @@ static std::string WindowsErrorString(const std::string& operation) } /***************************************************************************** -* Non-Windows specific +* Posix specific *****************************************************************************/ #else // Mac and linux @@ -444,4 +463,4 @@ void LLProcess::reap(void) } |*==========================================================================*/ -#endif +#endif // Posix -- cgit v1.2.3 From 5e74293b0cfe562e8e3184217f27b3e86afe5277 Mon Sep 17 00:00:00 2001 From: Xiaohong Bao Date: Fri, 3 Feb 2012 22:01:46 -0700 Subject: fix for SH-2941: crash at LLCurl::Easy::~Easy() --- indra/llmessage/llcurl.cpp | 9 +++++++-- indra/llmessage/llcurl.h | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index 3bcaffc275..2ef43d9c00 100644 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -583,10 +583,10 @@ LLCurl::Multi::Multi(F32 idle_time_out) LLCurl::Multi::~Multi() { - cleanup() ; + cleanup(true) ; } -void LLCurl::Multi::cleanup() +void LLCurl::Multi::cleanup(bool deleted) { if(!mCurlMultiHandle) { @@ -599,6 +599,11 @@ void LLCurl::Multi::cleanup() { Easy* easy = *iter; check_curl_multi_code(curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle())); + + if(deleted) + { + easy->mResponder = NULL ; //avoid triggering mResponder. + } delete easy; } mEasyActiveList.clear(); diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h index fd664c0fa1..579ba0b1ab 100644 --- a/indra/llmessage/llcurl.h +++ b/indra/llmessage/llcurl.h @@ -318,7 +318,7 @@ public: private: void easyFree(LLCurl::Easy*); - void cleanup() ; + void cleanup(bool deleted = false) ; CURLM* mCurlMultiHandle; -- cgit v1.2.3 From d99acd56cdc41d72a073a4419e3e51c356e675bb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 6 Feb 2012 17:06:55 -0500 Subject: ManageAPR should be noncopyable. Make that explicit. Any RAII class should either be noncopyable or should deal appropriately with a copy operation. ManageAPR is intended only for extremely simple cases, and hence should be noncopyable. --- indra/test/manageapr.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/test/manageapr.h b/indra/test/manageapr.h index 0c1ca7b7be..2452fb6ae4 100644 --- a/indra/test/manageapr.h +++ b/indra/test/manageapr.h @@ -13,6 +13,7 @@ #define LL_MANAGEAPR_H #include "llapr.h" +#include /** * Declare a static instance of this class for dead-simple ll_init_apr() at @@ -21,7 +22,7 @@ * instances of other classes that depend on APR already being initialized, * the indeterminate static-constructor-order problem rears its ugly head. */ -class ManageAPR +class ManageAPR: public boost::noncopyable { public: ManageAPR() -- cgit v1.2.3 From aafb03b29f5166e8978931ad8b717be32d942836 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 7 Feb 2012 10:53:23 -0500 Subject: Convert LLProcess implementation from platform-specific to using APR. Include logic to engage Linden apr_procattr_autokill_set() extension: on Windows, magic CreateProcess() flag must be pushed down into apr_proc_create() level. When using an APR package without that extension, present implementation should lock (e.g.) SLVoice.exe lifespan to viewer's on Windows XP but probably won't on Windows 7: need magic flag on CreateProcess(). Using APR child-termination callback requires us to define state (e.g. LLProcess::RUNNING). Take the opportunity to present Status, capturing state and (if terminated) rc or signal number; but since most of the time all caller really wants is to log the outcome, also present status string, encapsulating logic to examine state and describe exited-with-rc vs. killed-by-signal. New Status logic may report clearer results in the case of a Windows child process killed by exception. Clarify that static LLProcess::isRunning(handle) overload is only for use when the original LLProcess object has been destroyed: really only for unit tests. We necessarily retain our original platform-specific implementations for just that one method. (Nonstatic isRunning() no longer calls static method.) Clarify log output from llprocess_test.cpp in a couple places. --- indra/llcommon/llprocess.cpp | 552 +++++++++++++++++++++++--------- indra/llcommon/llprocess.h | 64 +++- indra/llcommon/tests/llprocess_test.cpp | 6 +- 3 files changed, 455 insertions(+), 167 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 8611d67f25..bc27002701 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -30,11 +30,15 @@ #include "llsingleton.h" #include "llstring.h" #include "stringize.h" +#include "llapr.h" #include #include #include +static std::string empty; +static LLProcess::Status interpret_status(int status); + /// Need an exception to avoid constructing an invalid LLProcess object, but /// internal use only struct LLProcessError: public std::runtime_error @@ -55,9 +59,14 @@ LLProcessPtr LLProcess::create(const LLSDOrParams& params) } } +/// Call an apr function returning apr_status_t. On failure, log warning and +/// throw LLProcessError mentioning the function call that produced that +/// result. +#define chkapr(func) \ + if (ll_apr_warn_status(func)) \ + throw LLProcessError(#func " failed") + LLProcess::LLProcess(const LLSDOrParams& params): - mProcessID(0), - mProcessHandle(0), mAutokill(params.autokill) { if (! params.validateBlock(true)) @@ -66,31 +75,298 @@ LLProcess::LLProcess(const LLSDOrParams& params): << LLSDNotationStreamer(params))); } - launch(params); + apr_procattr_t *procattr = NULL; + chkapr(apr_procattr_create(&procattr, gAPRPoolp)); + + // For which of stdin, stdout, stderr should we create a pipe to the + // child? In the viewer, there are only a couple viable + // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx + // handle (APR_NO_PIPE, e.g. for stdout, stderr), or create a pipe that's + // blocking on the child end but nonblocking at the viewer end + // (APR_CHILD_BLOCK). The viewer can't block for anything: the parent end + // MUST be nonblocking. As the APR documentation itself points out, it + // makes very little sense to set nonblocking I/O for the child end of a + // pipe: only a specially-written child could deal with that. + // Other major options could include explicitly creating a single APR pipe + // and passing it as both stdout and stderr (apr_procattr_child_out_set(), + // apr_procattr_child_err_set()), or accepting a filename, opening it and + // passing that apr_file_t (simple <, >, 2> redirect emulation). +// chkapr(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)); + chkapr(apr_procattr_io_set(procattr, APR_NO_PIPE, APR_NO_PIPE, APR_NO_PIPE)); + + // Thumbs down on implicitly invoking the shell to invoke the child. From + // our point of view, the other major alternative to APR_PROGRAM_PATH + // would be APR_PROGRAM_ENV: still copy environment, but require full + // executable pathname. I don't see a downside to searching the PATH, + // though: if our caller wants (e.g.) a specific Python interpreter, s/he + // can still pass the full pathname. + chkapr(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH)); + // YES, do extra work if necessary to report child exec() failures back to + // parent process. + chkapr(apr_procattr_error_check_set(procattr, 1)); + // Do not start a non-autokill child in detached state. On Posix + // platforms, this setting attempts to daemonize the new child, closing + // std handles and the like, and that's a bit more detachment than we + // want. autokill=false just means not to implicitly kill the child when + // the parent terminates! +// chkapr(apr_procattr_detach_set(procattr, params.autokill? 0 : 1)); + + if (params.autokill) + { +#if defined(APR_HAS_PROCATTR_AUTOKILL_SET) + apr_status_t ok = apr_procattr_autokill_set(procattr, 1); +# if LL_WINDOWS + // As of 2012-02-02, we only expect this to be implemented on Windows. + // Avoid spamming the log with warnings we fully expect. + ll_apr_warn_status(ok); +# endif // LL_WINDOWS +#else + LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL; +#endif + } + + // Have to instantiate named std::strings for string params items so their + // c_str() values persist. + std::string cwd(params.cwd); + if (! cwd.empty()) + { + chkapr(apr_procattr_dir_set(procattr, cwd.c_str())); + } + + // create an argv vector for the child process + std::vector argv; + + // add the executable path + std::string executable(params.executable); + argv.push_back(executable.c_str()); + + // and any arguments + std::vector args(params.args.begin(), params.args.end()); + BOOST_FOREACH(const std::string& arg, args) + { + argv.push_back(arg.c_str()); + } + + // terminate with a null pointer + argv.push_back(NULL); + + // Launch! The NULL would be the environment block, if we were passing one. + chkapr(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, gAPRPoolp)); + + // arrange to call status_callback() + apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, + gAPRPoolp); + mStatus.mState = RUNNING; + + mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcess.pid << ')'); + LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcess.pid << ")" << LL_ENDL; + + // Unless caller explicitly turned off autokill (child should persist), + // take steps to terminate the child. This is all suspenders-and-belt: in + // theory our destructor should kill an autokill child, but in practice + // that doesn't always work (e.g. VWR-21538). + if (params.autokill) + { + // Tie the lifespan of this child process to the lifespan of 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(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT); + + // On Windows, associate the new child process with our Job Object. + autokill(); + } } LLProcess::~LLProcess() { + // Only in state RUNNING are we registered for callback. In UNSTARTED we + // haven't yet registered. And since receiving the callback is the only + // way we detect child termination, we only change from state RUNNING at + // the same time we unregister. + if (mStatus.mState == RUNNING) + { + // We're still registered for a callback: unregister. Do it before + // we even issue the kill(): even if kill() somehow prompted an + // instantaneous callback (unlikely), this object is going away! Any + // information updated in this object by such a callback is no longer + // available to any consumer anyway. + apr_proc_other_child_unregister(this); + } + if (mAutokill) { - kill(); + kill("destructor"); + } +} + +bool LLProcess::kill(const std::string& who) +{ + if (isRunning()) + { + LL_INFOS("LLProcess") << who << " killing " << mDesc << LL_ENDL; + +#if LL_WINDOWS + int sig = -1; +#else // Posix + int sig = SIGTERM; +#endif + + ll_apr_warn_status(apr_proc_kill(&mProcess, sig)); } + + return ! isRunning(); } bool LLProcess::isRunning(void) { - mProcessHandle = isRunning(mProcessHandle, mDesc); - return (mProcessHandle != 0); + return getStatus().mState == RUNNING; +} + +LLProcess::Status LLProcess::getStatus() +{ + // Only when mState is RUNNING might the status change dynamically. For + // any other value, pointless to attempt to update status: it won't + // change. + if (mStatus.mState == RUNNING) + { + // Tell APR to sense whether the child is still running and call + // handle_status() appropriately. We should be able to get the same + // info from an apr_proc_wait(APR_NOWAIT) call; but at least in APR + // 1.4.2, testing suggests that even with APR_NOWAIT, apr_proc_wait() + // blocks the caller. We can't have that in the viewer. Hence the + // callback rigmarole. Once we update APR, it's probably worth testing + // again. Also -- although there's an apr_proc_other_child_refresh() + // call, i.e. get that information for one specific child, it accepts + // an 'apr_other_child_rec_t*' that's mentioned NOWHERE else in the + // documentation or header files! I would use the specific call if I + // knew how. As it is, each call to this method will call callbacks + // for ALL still-running child processes. Sigh... + apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); + } + + return mStatus; +} + +std::string LLProcess::getStatusString() +{ + return getStatusString(getStatus()); +} + +std::string LLProcess::getStatusString(const Status& status) +{ + return getStatusString(mDesc, status); +} + +//static +std::string LLProcess::getStatusString(const std::string& desc, const Status& status) +{ + if (status.mState == UNSTARTED) + return desc + " was never launched"; + + if (status.mState == RUNNING) + return desc + " running"; + + if (status.mState == EXITED) + return STRINGIZE(desc << " exited with code " << status.mData); + + if (status.mState == KILLED) +#if LL_WINDOWS + return STRINGIZE(desc << " killed with exception " << std::hex << status.mData); +#else + return STRINGIZE(desc << " killed by signal " << status.mData); +#endif + + + return STRINGIZE(desc << " in unknown state " << status.mState << " (" << status.mData << ")"); +} + +// Classic-C-style APR callback +void LLProcess::status_callback(int reason, void* data, int status) +{ + // Our only role is to bounce this static method call back into object + // space. + static_cast(data)->handle_status(reason, status); +} + +#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 + +// Object-oriented callback +void LLProcess::handle_status(int reason, int status) +{ + { + // This odd appearance of LL_DEBUGS is just to bracket a lookup that will + // only be performed if in fact we're going to produce the log message. + LL_DEBUGS("LLProcess") << empty; + 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); + } + LL_CONT << mDesc << ": handle_status(" << reason_str << ", " << status << ")" << LL_ENDL; + } + + if (! (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST)) + { + // We're only interested in the call when the child terminates. + return; + } + + // Somewhat oddly, APR requires that you explicitly unregister even when + // it already knows the child has terminated. We must pass the same 'data' + // pointer as for the register() call, which was our 'this'. + apr_proc_other_child_unregister(this); + // We overload mStatus.mState to indicate whether the child is registered + // for APR callback: only RUNNING means registered. Track that we've + // unregistered. We know the child has terminated; might be EXITED or + // KILLED; refine below. + mStatus.mState = EXITED; + +// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); + // 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, which means we have to decode it by + // hand. + mStatus = interpret_status(status); + LL_INFOS("LLProcess") << getStatusString() << LL_ENDL; } LLProcess::id LLProcess::getProcessID() const { - return mProcessID; + return mProcess.pid; } LLProcess::handle LLProcess::getProcessHandle() const { - return mProcessHandle; +#if LL_WINDOWS + return mProcess.hproc; +#else + return mProcess.pid; +#endif } std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) @@ -178,77 +454,15 @@ private: LLProcess::handle mJob; }; -void LLProcess::launch(const LLSDOrParams& params) +void LLProcess::autokill() { - PROCESS_INFORMATION pinfo; - STARTUPINFOA sinfo = { sizeof(sinfo) }; - - // LLProcess::create()'s caller passes a Unix-style array of strings for - // command-line arguments. Our caller can and should expect that these will be - // passed to the child process as individual arguments, regardless of content - // (e.g. embedded spaces). But because Windows invokes any child process with - // a single command-line string, this means we must quote each argument behind - // the scenes. - std::string args = LLStringUtil::quote(params.executable); - BOOST_FOREACH(const std::string& arg, params.args) - { - args += " "; - args += LLStringUtil::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(); - - // It's important to pass CREATE_BREAKAWAY_FROM_JOB because Windows 7 et - // al. tend to implicitly launch new processes already bound to a job. From - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949%28v=vs.85%29.aspx : - // "The process must not already be assigned to a job; if it is, the - // function fails with ERROR_ACCESS_DENIED." ... - // "If the process is being monitored by the Program Compatibility - // Assistant (PCA), it is placed into a compatibility job. Therefore, the - // process must be created using CREATE_BREAKAWAY_FROM_JOB before it can - // be placed in another job." - if( ! CreateProcessA(NULL, // lpApplicationName - &args2[0], // lpCommandLine - NULL, // lpProcessAttributes - NULL, // lpThreadAttributes - FALSE, // bInheritHandles - CREATE_BREAKAWAY_FROM_JOB, // dwCreationFlags - NULL, // lpEnvironment - working_directory, // lpCurrentDirectory - &sinfo, // lpStartupInfo - &pinfo ) ) // lpProcessInformation - { - throw LLProcessError(WindowsErrorString("CreateProcessA")); - } - - // CloseHandle(pinfo.hProcess); // stops leaks - nothing else - mProcessID = pinfo.dwProcessId; - mProcessHandle = pinfo.hProcess; - CloseHandle(pinfo.hThread); // stops leaks - nothing else - - mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcessID << ')'); - LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL; - - // Now associate the new child process with our Job Object -- unless - // autokill is false, i.e. caller asserts the child should persist. - if (params.autokill) - { - LLJob::instance().assignProcess(mDesc, mProcessHandle); -} + LLJob::instance().assignProcess(mDesc, mProcess.hproc); } LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) { + // This direct Windows implementation is because we have no access to the + // apr_proc_t struct: we expect it's been destroyed. if (! h) return 0; @@ -258,22 +472,44 @@ LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) // the process has completed. if (! desc.empty()) { - LL_INFOS("LLProcess") << desc << " terminated" << LL_ENDL; + DWORD status = 0; + if (! GetExitCodeProcess(h, &status)) + { + LL_WARNS("LLProcess") << desc << " terminated, but " + << WindowsErrorString("GetExitCodeProcess()") << LL_ENDL; + } + { + LL_INFOS("LLProcess") << getStatusString(desc, interpret_status(status)) + << LL_ENDL; + } } + CloseHandle(h); return 0; } return h; } -bool LLProcess::kill(void) +static LLProcess::Status interpret_status(int status) { - if (! mProcessHandle) - return false; + LLProcess::Status result; + + // This bit of code is cribbed from apr/threadproc/win32/proc.c, a + // function (unfortunately static) called why_from_exit_code(): + /* See WinNT.h STATUS_ACCESS_VIOLATION and family for how + * this class of failures was determined + */ + if ((status & 0xFFFF0000) == 0xC0000000) + { + result.mState = KILLED; + } + else + { + result.mState = EXITED; + } + result.mData = status; - LL_INFOS("LLProcess") << "killing " << mDesc << LL_ENDL; - TerminateProcess(mProcessHandle, 0); - return ! isRunning(); + return result; } /// GetLastError()/FormatMessage() boilerplate @@ -315,98 +551,91 @@ static std::string WindowsErrorString(const std::string& operation) #include #include +void LLProcess::autokill() +{ + // What we ought to do here is to: + // 1. create a unique process group and run all autokill children in that + // group (see https://jira.secondlife.com/browse/SWAT-563); + // 2. figure out a way to intercept control when the viewer exits -- + // gracefully or not; + // 3. when the viewer exits, kill off the aforementioned process group. + + // It's point 2 that's troublesome. Although I've seen some signal- + // handling logic in the Posix viewer code, I haven't yet found any bit of + // code that's run no matter how the viewer exits (a try/finally for the + // whole process, as it were). +} + // 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) +static bool reap_pid(pid_t pid, LLProcess::Status* pstatus=NULL) { - pid_t wait_result = ::waitpid(pid, NULL, WNOHANG); + LLProcess::Status dummy; + if (! pstatus) + { + // If caller doesn't want to see Status, give us a target anyway so we + // don't have to have a bunch of conditionals. + pstatus = &dummy; + } + + int status = 0; + pid_t wait_result = ::waitpid(pid, &status, WNOHANG); if (wait_result == pid) { + *pstatus = interpret_status(status); return true; } - if (wait_result == -1 && errno == ECHILD) + if (wait_result == 0) { - // No such process -- this may mean we're ignoring SIGCHILD. - return true; + pstatus->mState = LLProcess::RUNNING; + pstatus->mData = 0; + return false; } - - return false; -} -void LLProcess::launch(const LLSDOrParams& params) -{ - // flush all buffers before the child inherits them - ::fflush(NULL); + // Clear caller's Status block; caller must interpret UNSTARTED to mean + // "if this PID was ever valid, it no longer is." + *pstatus = LLProcess::Status(); - pid_t child = vfork(); - if (child == 0) + // We've dealt with the success cases: we were able to reap the child + // (wait_result == pid) or it's still running (wait_result == 0). It may + // be that the child terminated but didn't hang around long enough for us + // to reap. In that case we still have no Status to report, but we can at + // least state that it's not running. + if (wait_result == -1 && errno == ECHILD) { - // 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 - std::vector args(params.args.begin(), params.args.end()); - 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); + // No such process -- this may mean we're ignoring SIGCHILD. + return true; } - // parent process - mProcessID = child; - mProcessHandle = child; - - mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcessID << ')'); - LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcessID << ")" << LL_ENDL; + // Uh, should never happen?! + LL_WARNS("LLProcess") << "LLProcess::reap_pid(): waitpid(" << pid << ") returned " + << wait_result << "; not meaningful?" << LL_ENDL; + // If caller is looping until this pid terminates, and if we can't find + // out, better to break the loop than to claim it's still running. + return true; } LLProcess::id LLProcess::isRunning(id pid, const std::string& desc) { + // This direct Posix implementation is because we have no access to the + // apr_proc_t struct: we expect it's been destroyed. if (! pid) return 0; // Check whether the process has exited, and reap it if it has. - if(reap_pid(pid)) + LLProcess::Status status; + if(reap_pid(pid, &status)) { // the process has exited. if (! desc.empty()) { - LL_INFOS("LLProcess") << desc << " terminated" << LL_ENDL; + std::string statstr(desc + " apparently terminated: no status available"); + // We don't just pass UNSTARTED to getStatusString() because, in + // the context of reap_pid(), that state has special meaning. + if (status.mState != UNSTARTED) + { + statstr = getStatusString(desc, status); + } + LL_INFOS("LLProcess") << statstr << LL_ENDL; } return 0; } @@ -414,18 +643,27 @@ LLProcess::id LLProcess::isRunning(id pid, const std::string& desc) return pid; } -bool LLProcess::kill(void) +static LLProcess::Status interpret_status(int status) { - if (! mProcessID) - return false; + LLProcess::Status result; - // 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); + if (WIFEXITED(status)) + { + result.mState = LLProcess::EXITED; + result.mData = WEXITSTATUS(status); + } + else if (WIFSIGNALED(status)) + { + result.mState = LLProcess::KILLED; + result.mData = WTERMSIG(status); + } + else // uh, shouldn't happen? + { + result.mState = LLProcess::EXITED; + result.mData = status; // someone else will have to decode + } - // This will have the side-effect of reaping the zombie if the process has exited. - return ! isRunning(); + return result; } /*==========================================================================*| diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 8a842589ec..689f8aedab 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -29,6 +29,7 @@ #include "llinitparam.h" #include "llsdparam.h" +#include "apr_thread_proc.h" #include #include #include // std::ostream @@ -95,13 +96,52 @@ public: static LLProcessPtr create(const LLSDOrParams& params); virtual ~LLProcess(); - // isRunning isn't const because, if child isn't running, it clears stored - // process ID + // isRunning() isn't const because, when child terminates, it sets stored + // Status bool isRunning(void); - + + /** + * State of child process + */ + enum state + { + UNSTARTED, ///< initial value, invisible to consumer + RUNNING, ///< child process launched + EXITED, ///< child process terminated voluntarily + KILLED ///< child process terminated involuntarily + }; + + /** + * Status info + */ + struct Status + { + Status(): + mState(UNSTARTED), + mData(0) + {} + + state mState; ///< @see state + /** + * - for mState == EXITED: mData is exit() code + * - for mState == KILLED: mData is signal number (Posix) + * - otherwise: mData is undefined + */ + int mData; + }; + + /// Status query + Status getStatus(); + /// English Status string query, for logging etc. + std::string getStatusString(); + /// English Status string query for previously-captured Status + std::string getStatusString(const Status& status); + /// static English Status string query + static std::string getStatusString(const std::string& desc, const Status& status); + // 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); + bool kill(const std::string& who=""); #if LL_WINDOWS typedef int id; ///< as returned by getProcessID() @@ -133,18 +173,28 @@ public: * a whole set of operations supported on freestanding @c handle values. * New functionality should be added as nonstatic members operating on * the same data as getProcessHandle(). + * + * In particular, if child termination is detected by static isRunning() + * rather than by nonstatic isRunning(), the LLProcess object won't be + * aware of the child's changed status and may encounter OS errors trying + * to obtain it. static isRunning() is only intended for after the + * launching LLProcess object has been destroyed. */ static handle isRunning(handle, const std::string& desc=""); private: /// constructor is private: use create() instead LLProcess(const LLSDOrParams& params); - void launch(const LLSDOrParams& params); + void autokill(); + // Classic-C-style APR callback + static void status_callback(int reason, void* data, int status); + // Object-oriented callback + void handle_status(int reason, int status); std::string mDesc; - id mProcessID; - handle mProcessHandle; + apr_proc_t mProcess; bool mAutokill; + Status mStatus; }; /// for logging diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 4ad45bdf27..60ed12ad6a 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -630,7 +630,7 @@ namespace tut // Destroy the LLProcess, which should kill the child. } // wait for the script to terminate... one way or another. - while (LLProcess::isRunning(phandle)) + while (LLProcess::isRunning(phandle, "kill() script")) { sleep(1); } @@ -643,7 +643,7 @@ namespace tut template<> template<> void object::test<6>() { - set_test_name("autokill"); + set_test_name("autokill=false"); NamedTempFile from("from", "not started"); NamedTempFile to("to", ""); LLProcess::handle phandle(0); @@ -695,7 +695,7 @@ namespace tut outf << "go"; } // flush and close. // now wait for the script to terminate... one way or another. - while (LLProcess::isRunning(phandle)) + while (LLProcess::isRunning(phandle, "autokill script")) { sleep(1); } -- cgit v1.2.3 From 219a010aaf4891fc2ab8480a2121b1244191c24d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 7 Feb 2012 11:17:04 -0500 Subject: LLProcess::Status enum values need qualification in helper function. --- indra/llcommon/llprocess.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index bc27002701..1305c1d764 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -501,11 +501,11 @@ static LLProcess::Status interpret_status(int status) */ if ((status & 0xFFFF0000) == 0xC0000000) { - result.mState = KILLED; + result.mState = LLProcess::KILLED; } else { - result.mState = EXITED; + result.mState = LLProcess::EXITED; } result.mData = status; -- cgit v1.2.3 From 5d2bb536324a906078b224bdc9697b368e96a6b6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 7 Feb 2012 12:06:03 -0500 Subject: On Linux, #undef Status: we use that name for nested LLProcess struct. Apparently something in the Linux system header chain #defines a macro Status as 'int'. That's just Bad in C++ land. It should at the very least be a typedef! #undefining it in llprocess.h permits the viewer to build. --- indra/llcommon/llprocess.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 689f8aedab..0de033c15b 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -37,6 +37,10 @@ #if LL_WINDOWS #define WIN32_LEAN_AND_MEAN #include // HANDLE (eye roll) +#elif LL_LINUX +#if defined(Status) +#undef Status +#endif #endif class LLProcess; -- cgit v1.2.3 From 32e11494ffde368b2ac166e16dc294d66a18492f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 7 Feb 2012 12:28:57 -0500 Subject: Use os.path.normcase(os.path.normpath()) when comparing directories. Once again we've been bitten by comparison failure between "c:\somepath" and "C:\somepath". Normalize paths in both Python helper scripts to make that comparison more robust. --- indra/llcommon/tests/llprocess_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 60ed12ad6a..6d2292fb88 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -186,7 +186,7 @@ public: "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")) + " f.write(os.path.normcase(os.path.normpath(os.path.realpath(tempfile.mkdtemp()))))\n")) {} ~NamedTempDir() @@ -513,7 +513,7 @@ namespace tut "from __future__ import with_statement\n" "import os, sys\n" "with open(sys.argv[1], 'w') as f:\n" - " f.write(os.getcwd())\n"); + " f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n"); // Before running, call setWorkingDirectory() py.mParams.cwd = tempdir.getName(); ensure_equals("os.getcwd()", py.run_read(), tempdir.getName()); -- cgit v1.2.3 From 2d360cb08d2bee098168e3c3f5041ec37206f383 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 9 Feb 2012 07:44:23 -0500 Subject: Use new 3p-apr package builds for Windows, Mac. On Linux, new (Feb 2012) APR package produces link errors. Until those are resolved, leave Linux viewer build with older (Feb 2011) APR package. --- autobuild.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 9914be6867..9cecdd7f09 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - 9868bfa0b6954e4884c49c6f30068c80 + 6e5172597f00380b5004b8a7911098b7 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/apr_suite-1.4.2-darwin-20110217.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249125/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120208.tar.bz2 name darwin @@ -114,9 +114,9 @@ archive hash - 73785c200a5b4ef74a1230b028bb680d + a596a9974ae6bfe48dc61a2cd13bc321 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/apr_suite-1.4.2-windows-20110217.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249125/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120208.tar.bz2 name windows -- cgit v1.2.3 From 58348bd862163261fb650d8afd50fd1dc5e2695a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 9 Feb 2012 15:07:02 -0500 Subject: Use newer (Linden extension) TC APR builds for Windows, Mac. --- autobuild.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 9cecdd7f09..80b1a8a709 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - 6e5172597f00380b5004b8a7911098b7 + 71d4d242c41ec9ac4807989371279708 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249125/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120208.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249241/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120209.tar.bz2 name darwin @@ -114,9 +114,9 @@ archive hash - a596a9974ae6bfe48dc61a2cd13bc321 + 983378883a6eec65665875a3d1ba495f url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249125/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120208.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249241/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120209.tar.bz2 name windows -- cgit v1.2.3 From 90a1b67cc43792e1ca0047eaca51530aa14e134c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 9 Feb 2012 15:22:24 -0500 Subject: Remove LLJob class: apr_procattr_autokill_set() should now handle. LLJob was vestigial code from before migrating Job Object support into APR. Also add APR signal-name string to getStatusString() output. --- indra/llcommon/llprocess.cpp | 70 +++----------------------------------------- 1 file changed, 4 insertions(+), 66 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 1305c1d764..7ccbdeed01 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -31,6 +31,7 @@ #include "llstring.h" #include "stringize.h" #include "llapr.h" +#include "apr_signal.h" #include #include @@ -274,10 +275,10 @@ std::string LLProcess::getStatusString(const std::string& desc, const Status& st #if LL_WINDOWS return STRINGIZE(desc << " killed with exception " << std::hex << status.mData); #else - return STRINGIZE(desc << " killed by signal " << status.mData); + return STRINGIZE(desc << " killed by signal " << status.mData + << " (" << apr_signal_description_get(status.mData) << ")"); #endif - return STRINGIZE(desc << " in unknown state " << status.mState << " (" << status.mData << ")"); } @@ -391,72 +392,9 @@ std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) static std::string WindowsErrorString(const std::string& operation); -/** - * Wrap a Windows Job Object for use in managing child-process lifespan. - * - * On Windows, we use a Job Object to constrain the lifespan of any - * autokill=true child process to the viewer's own lifespan: - * http://stackoverflow.com/questions/53208/how-do-i-automatically-destroy-child-processes-in-windows - * (thanks Richard!). - * - * We manage it using an LLSingleton for a couple of reasons: - * - * # Lazy initialization: if some viewer session never launches a child - * process, we should never have to create a Job Object. - * # Cross-DLL support: be wary of C++ statics when multiple DLLs are - * involved. - */ -class LLJob: public LLSingleton -{ -public: - void assignProcess(const std::string& prog, LLProcess::handle hProcess) - { - // If we never managed to initialize this Job Object, can't use it -- - // but don't keep spamming the log, we already emitted warnings when - // we first tried to create. - if (! mJob) - return; - - if (! AssignProcessToJobObject(mJob, hProcess)) - { - LL_WARNS("LLProcess") << WindowsErrorString(STRINGIZE("AssignProcessToJobObject(" - << prog << ")")) << LL_ENDL; - } - } - -private: - friend class LLSingleton; - LLJob(): - mJob(0) - { - mJob = CreateJobObject(NULL, NULL); - if (! mJob) - { - LL_WARNS("LLProcess") << WindowsErrorString("CreateJobObject()") << LL_ENDL; - return; - } - - JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 }; - - // Configure all child processes associated with this new job object - // to terminate when the calling process (us!) terminates. - jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - if (! SetInformationJobObject(mJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) - { - LL_WARNS("LLProcess") << WindowsErrorString("SetInformationJobObject()") << LL_ENDL; - // This Job Object is useless to us - CloseHandle(mJob); - // prevent assignProcess() from trying to use it - mJob = 0; - } - } - - LLProcess::handle mJob; -}; - void LLProcess::autokill() { - LLJob::instance().assignProcess(mDesc, mProcess.hproc); + // hopefully now handled by apr_procattr_autokill_set() } LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) -- cgit v1.2.3 From b47994601bfd2bc240434eab4e3a255c38d535d1 Mon Sep 17 00:00:00 2001 From: Seth ProductEngine Date: Fri, 10 Feb 2012 01:52:08 +0200 Subject: MAINT-784 FIXED the inventory filter to be restarted when switching from 'Since Logoff' to 'Hours Ago' or 'Days Ago'. Fix: checking for !isSinceLogoff() ensures that the time filter increases from zero and becomes more restrictive. This happens because setting 'Since Logoff' resets the 'Hours Ago' and 'Days Ago' to zero. --- indra/newview/llinventoryfilter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index d54bce4619..0485b04895 100644 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -502,7 +502,7 @@ void LLInventoryFilter::setHoursAgo(U32 hours) bool are_date_limits_valid = mFilterOps.mMinDate == time_min() && mFilterOps.mMaxDate == time_max(); bool is_increasing = hours > mFilterOps.mHoursAgo; - bool is_increasing_from_zero = is_increasing && !mFilterOps.mHoursAgo; + bool is_increasing_from_zero = is_increasing && !mFilterOps.mHoursAgo && !isSinceLogoff(); // *NOTE: need to cache last filter time, in case filter goes stale BOOL less_restrictive = (are_date_limits_valid && ((is_increasing && mFilterOps.mHoursAgo)) || !hours); -- cgit v1.2.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(-) 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.2.3 From 9011b04496796d7e51c20af0241ec05796e1ccac Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 10 Feb 2012 12:11:18 -0500 Subject: Try using Log's new APR packages for Windows, Mac, Linux. This APR merges work from Huseby, Log, Leslie, Nat. --- autobuild.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 80b1a8a709..a3a5dd0057 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - 71d4d242c41ec9ac4807989371279708 + a2c41379bc0abc83b45246eebdafc4ab url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249241/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120209.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/log_3p-apr/rev/249247/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120209.tar.bz2 name darwin @@ -102,9 +102,9 @@ archive hash - ff62946c518a247c86e1066c1e9a5855 + 1ad6e6bff18306cb5079128d5aadeb6e url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/apr_suite-1.4.2-linux-20110309.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/log_3p-apr-lenny/rev/249247/arch/Linux/installer/apr_suite-1.4.5-linux-20120209.tar.bz2 name linux @@ -114,9 +114,9 @@ archive hash - 983378883a6eec65665875a3d1ba495f + 31cd12ea119753e389dc9be5ea82cbb3 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249241/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120209.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/log_3p-apr/rev/249247/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120209.tar.bz2 name windows -- cgit v1.2.3 From acd46062fd2edf4b45ab646cbacaf865b169ec30 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 10 Feb 2012 16:45:18 -0500 Subject: Eliminate ManifestError for wildcards matching 0 files. Turns out that some (many?) wildcard LLManifest.path(wildcard) calls are "just in case": sweep up any (e.g.) "*.tga" files there may be, but no problem if there are none. Change path() logic so it tries the next tree (source, artwork, build) if either a specific (non-wildcard) filename doesn't exist, as now, OR if a wildcard matches 0 files in the current tree. This continues to support "just in case" wildcards, while permitting wildcards to work in the artwork and build trees as well as the source tree. Use a more specific exception than ManifestError for missing file. Only in that case should we try the next tree. Any other ManifestError should propagate. --- indra/lib/python/indra/util/llmanifest.py | 33 ++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index 237153b756..a4fb77357c 100644 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -42,6 +42,11 @@ import errno import subprocess class ManifestError(RuntimeError): + """Use an exception more specific than generic Python RuntimeError""" + pass + +class MissingError(ManifestError): + """You specified a file that doesn't exist""" pass def path_ancestors(path): @@ -604,16 +609,12 @@ class LLManifest(object): def check_file_exists(self, path): if not os.path.exists(path) and not os.path.islink(path): - raise ManifestError("Path %s doesn't exist" % (os.path.abspath(path),)) + raise MissingError("Path %s doesn't exist" % (os.path.abspath(path),)) 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: @@ -646,13 +647,23 @@ class LLManifest(object): else: count += self.process_file(src, dst) return count - try: - count = try_path(os.path.join(self.get_src_prefix(), src)) - except ManifestError: + + for pfx in self.get_src_prefix(), self.get_artwork_prefix(), self.get_build_prefix(): try: - count = try_path(os.path.join(self.get_artwork_prefix(), src)) - except ManifestError: - count = try_path(os.path.join(self.get_build_prefix(), src)) + count = try_path(os.path.join(pfx, src)) + except MissingError: + # If src isn't a wildcard, and if that file doesn't exist in + # this pfx, try next pfx. + count = 0 + continue + + # Here try_path() didn't raise MissingError. Did it process any files? + if count: + break + # Even though try_path() didn't raise MissingError, it returned 0 + # files. src is probably a wildcard meant for some other pfx. Loop + # back to try the next. + print "%d files" % count def do(self, *actions): -- cgit v1.2.3 From 8804fa524c084edb9f49f4122e2c7330a80c79b5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 11 Feb 2012 06:16:39 -0500 Subject: Update viewer to official builds of http://hg.secondlife.com/3p-apr. --- autobuild.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index a3a5dd0057..99fa201ee3 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - a2c41379bc0abc83b45246eebdafc4ab + 15333927db9f249daefcaed70fc6683c url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/log_3p-apr/rev/249247/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120209.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249247/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120210.tar.bz2 name darwin @@ -102,9 +102,9 @@ archive hash - 1ad6e6bff18306cb5079128d5aadeb6e + 297508ece8b0f334bae842fd147f2962 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/log_3p-apr-lenny/rev/249247/arch/Linux/installer/apr_suite-1.4.5-linux-20120209.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249247/arch/Linux/installer/apr_suite-1.4.5-linux-20120211.tar.bz2 name linux @@ -114,9 +114,9 @@ archive hash - 31cd12ea119753e389dc9be5ea82cbb3 + 7ad1e767f4419a6d35a70abba1edbcdc url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/log_3p-apr/rev/249247/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120209.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249247/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120210.tar.bz2 name windows -- cgit v1.2.3 From 0f2882ec95fc6cd2649fbe4e952ce1bc586bb853 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 13 Feb 2012 09:41:50 -0500 Subject: Suppress a specific unused-var warning on Posix platforms. --- indra/llcommon/llprocess.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 7ccbdeed01..de71595f16 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -120,7 +120,9 @@ LLProcess::LLProcess(const LLSDOrParams& params): // As of 2012-02-02, we only expect this to be implemented on Windows. // Avoid spamming the log with warnings we fully expect. ll_apr_warn_status(ok); -# endif // LL_WINDOWS +#else // ! LL_WINDOWS + (void)ok; // suppress 'unused' warning +# endif // ! LL_WINDOWS #else LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL; #endif -- cgit v1.2.3 From d4f887e43ccf0a8b7a84ebbfe6889462a1d9c25f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 13 Feb 2012 16:18:46 -0500 Subject: Add unit tests for LLProcess::Status functionality. --- indra/llcommon/tests/llprocess_test.cpp | 46 +++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 6d2292fb88..711715e373 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -555,6 +555,41 @@ namespace tut template<> template<> void object::test<4>() + { + set_test_name("exit(0)"); + PythonProcessLauncher py("exit(0)", + "import sys\n" + "sys.exit(0)\n"); + py.run(); + ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); + ensure_equals("Status.mData", py.mPy->getStatus().mData, 0); + } + + template<> template<> + void object::test<5>() + { + set_test_name("exit(2)"); + PythonProcessLauncher py("exit(2)", + "import sys\n" + "sys.exit(2)\n"); + py.run(); + ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); + ensure_equals("Status.mData", py.mPy->getStatus().mData, 2); + } + + template<> template<> + void object::test<6>() + { + set_test_name("syntax_error:"); + PythonProcessLauncher py("syntax_error:", + "syntax_error:\n"); + py.run(); + ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); + ensure_equals("Status.mData", py.mPy->getStatus().mData, 1); + } + + template<> template<> + void object::test<7>() { set_test_name("explicit kill()"); PythonProcessLauncher py("kill()", @@ -588,6 +623,13 @@ namespace tut { sleep(1); } +#if LL_WINDOWS + ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); + ensure_equals("Status.mData", py.mPy->getStatus().mData, -1); +#else + ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::KILLED); + ensure_equals("Status.mData", py.mPy->getStatus().mData, SIGTERM); +#endif // 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. @@ -595,7 +637,7 @@ namespace tut } template<> template<> - void object::test<5>() + void object::test<8>() { set_test_name("implicit kill()"); NamedTempFile out("out", "not started"); @@ -641,7 +683,7 @@ namespace tut } template<> template<> - void object::test<6>() + void object::test<9>() { set_test_name("autokill=false"); NamedTempFile from("from", "not started"); -- cgit v1.2.3 From aae61392be822218cabcab91d95eb1e75d471764 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 13 Feb 2012 17:38:25 -0500 Subject: Use per-frame ticks on "mainloop" LLEventPump to update LLProcess. When we reimplemented LLProcess on APR, necessitating APR's funny callback mechanism to sense child-process status, every isRunning() or getStatus() call called the APR poll function that calls ALL registered LLProcess callbacks. In other words, every time any consumer called any LLProcess::isRunning() method, all LLProcess callbacks were redundantly fired. Change that so that the single APR poll function is called once per frame, courtesy of the "mainloop" LLEventPump. Once per viewer frame should be well within the realtime duration in which it's reasonable to expect child-process status to change. In effect, this changes LLProcess's public API to introduce a dependency on "mainloop" ticks. Add such ticks to llprocess_test.cpp as well. --- indra/llcommon/llprocess.cpp | 96 ++++++++++++++++++++++++++------- indra/llcommon/llprocess.h | 20 ++++--- indra/llcommon/tests/llprocess_test.cpp | 42 +++++++++------ 3 files changed, 114 insertions(+), 44 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index de71595f16..b13e8eb8e0 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -32,8 +32,10 @@ #include "stringize.h" #include "llapr.h" #include "apr_signal.h" +#include "llevents.h" #include +#include #include #include @@ -47,6 +49,74 @@ struct LLProcessError: public std::runtime_error LLProcessError(const std::string& msg): std::runtime_error(msg) {} }; +/** + * Ref-counted "mainloop" listener. As long as there are still outstanding + * LLProcess objects, keep listening on "mainloop" so we can keep polling APR + * for process status. + */ +class LLProcessListener +{ + LOG_CLASS(LLProcessListener); +public: + LLProcessListener(): + mCount(0) + {} + + void addPoll(const LLProcess&) + { + // Unconditionally increment mCount. If it was zero before + // incrementing, listen on "mainloop". + if (mCount++ == 0) + { + LL_DEBUGS("LLProcess") << "listening on \"mainloop\"" << LL_ENDL; + mConnection = LLEventPumps::instance().obtain("mainloop") + .listen("LLProcessListener", boost::bind(&LLProcessListener::tick, this, _1)); + } + } + + void dropPoll(const LLProcess&) + { + // Unconditionally decrement mCount. If it's zero after decrementing, + // stop listening on "mainloop". + if (--mCount == 0) + { + LL_DEBUGS("LLProcess") << "disconnecting from \"mainloop\"" << LL_ENDL; + mConnection.disconnect(); + } + } + +private: + /// called once per frame by the "mainloop" LLEventPump + bool tick(const LLSD&) + { + // Tell APR to sense whether each registered LLProcess is still + // running and call handle_status() appropriately. We should be able + // to get the same info from an apr_proc_wait(APR_NOWAIT) call; but at + // least in APR 1.4.2, testing suggests that even with APR_NOWAIT, + // apr_proc_wait() blocks the caller. We can't have that in the + // viewer. Hence the callback rigmarole. (Once we update APR, it's + // probably worth testing again.) Also -- although there's an + // apr_proc_other_child_refresh() call, i.e. get that information for + // one specific child, it accepts an 'apr_other_child_rec_t*' that's + // mentioned NOWHERE else in the documentation or header files! I + // would use the specific call in LLProcess::getStatus() if I knew + // how. As it is, each call to apr_proc_other_child_refresh_all() will + // call callbacks for ALL still-running child processes. That's why we + // centralize such calls, using "mainloop" to ensure it happens once + // per frame, and refcounting running LLProcess objects to remain + // registered only while needed. + LL_DEBUGS("LLProcess") << "calling apr_proc_other_child_refresh_all()" << LL_ENDL; + apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); + return false; + } + + /// If this object is destroyed before mCount goes to zero, stop + /// listening on "mainloop" anyway. + LLTempBoundListener mConnection; + unsigned mCount; +}; +static LLProcessListener sProcessListener; + LLProcessPtr LLProcess::create(const LLSDOrParams& params) { try @@ -159,6 +229,8 @@ LLProcess::LLProcess(const LLSDOrParams& params): // arrange to call status_callback() apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, gAPRPoolp); + // and make sure we poll it once per "mainloop" tick + sProcessListener.addPoll(*this); mStatus.mState = RUNNING; mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcess.pid << ')'); @@ -195,6 +267,8 @@ LLProcess::~LLProcess() // information updated in this object by such a callback is no longer // available to any consumer anyway. apr_proc_other_child_unregister(this); + // One less LLProcess to poll for + sProcessListener.dropPoll(*this); } if (mAutokill) @@ -228,26 +302,6 @@ bool LLProcess::isRunning(void) LLProcess::Status LLProcess::getStatus() { - // Only when mState is RUNNING might the status change dynamically. For - // any other value, pointless to attempt to update status: it won't - // change. - if (mStatus.mState == RUNNING) - { - // Tell APR to sense whether the child is still running and call - // handle_status() appropriately. We should be able to get the same - // info from an apr_proc_wait(APR_NOWAIT) call; but at least in APR - // 1.4.2, testing suggests that even with APR_NOWAIT, apr_proc_wait() - // blocks the caller. We can't have that in the viewer. Hence the - // callback rigmarole. Once we update APR, it's probably worth testing - // again. Also -- although there's an apr_proc_other_child_refresh() - // call, i.e. get that information for one specific child, it accepts - // an 'apr_other_child_rec_t*' that's mentioned NOWHERE else in the - // documentation or header files! I would use the specific call if I - // knew how. As it is, each call to this method will call callbacks - // for ALL still-running child processes. Sigh... - apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); - } - return mStatus; } @@ -341,6 +395,8 @@ void LLProcess::handle_status(int reason, int status) // it already knows the child has terminated. We must pass the same 'data' // pointer as for the register() call, which was our 'this'. apr_proc_other_child_unregister(this); + // don't keep polling for a terminated process + sProcessListener.dropPoll(*this); // We overload mStatus.mState to indicate whether the child is registered // for APR callback: only RUNNING means registered. Track that we've // unregistered. We know the child has terminated; might be EXITED or diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 0de033c15b..b95ae55701 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -49,9 +49,17 @@ class LLProcess; 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. -*/ + * LLProcess handles launching an external process with specified command line + * arguments. It also keeps track of whether the process is still running, and + * can kill it if required. + * + * LLProcess relies on periodic post() calls on the "mainloop" LLEventPump: an + * LLProcess object's Status won't update until the next "mainloop" tick. The + * viewer's main loop already posts to that LLEventPump once per iteration + * (hence the name). See indra/llcommon/tests/llprocess_test.cpp for an + * example of waiting for child-process termination in a standalone test + * context. + */ class LL_COMMON_API LLProcess: public boost::noncopyable { LOG_CLASS(LLProcess); @@ -72,11 +80,7 @@ public: * zero or more additional command-line arguments. Arguments are * passed through as exactly as we can manage, whitespace and all. * @note On Windows we manage this by implicitly double-quoting each - * argument while assembling the command line. BUT if a given argument - * is already double-quoted, we don't double-quote it again. Try to - * avoid making use of this, though, as on Mac and Linux explicitly - * double-quoted args will be passed to the child process including - * the double quotes. + * argument while assembling the command line. */ Multiple args; /// current working directory, if need it changed diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 711715e373..8c21be196b 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -33,6 +33,7 @@ #include "../test/namedtempfile.h" #include "stringize.h" #include "llsdutil.h" +#include "llevents.h" #if defined(LL_WINDOWS) #define sleep(secs) _sleep((secs) * 1000) @@ -88,6 +89,27 @@ static std::string readfile(const std::string& pathname, const std::string& desc return output; } +/// Looping on LLProcess::isRunning() must now be accompanied by pumping +/// "mainloop" -- otherwise the status won't update and you get an infinite +/// loop. +void waitfor(LLProcess& proc) +{ + while (proc.isRunning()) + { + sleep(1); + LLEventPumps::instance().obtain("mainloop").post(LLSD()); + } +} + +void waitfor(LLProcess::handle h, const std::string& desc) +{ + while (LLProcess::isRunning(h, desc)) + { + sleep(1); + LLEventPumps::instance().obtain("mainloop").post(LLSD()); + } +} + /** * Construct an LLProcess to run a Python script. */ @@ -120,10 +142,7 @@ struct PythonProcessLauncher // 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); - } + waitfor(*mPy); } /** @@ -619,10 +638,7 @@ namespace tut // 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); - } + waitfor(*py.mPy); #if LL_WINDOWS ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); ensure_equals("Status.mData", py.mPy->getStatus().mData, -1); @@ -672,10 +688,7 @@ namespace tut // Destroy the LLProcess, which should kill the child. } // wait for the script to terminate... one way or another. - while (LLProcess::isRunning(phandle, "kill() script")) - { - sleep(1); - } + waitfor(phandle, "kill() script"); // 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. @@ -737,10 +750,7 @@ namespace tut outf << "go"; } // flush and close. // now wait for the script to terminate... one way or another. - while (LLProcess::isRunning(phandle, "autokill script")) - { - sleep(1); - } + waitfor(phandle, "autokill script"); // 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"); -- cgit v1.2.3 From e239cad1f509e3d96011acb61614f2481c46af38 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Feb 2012 10:07:09 -0500 Subject: Preliminary pipe support for LLProcess. Add LLProcess::FileParam to specify how to construct each child's standard file slot, with lots of comments about features designed but not yet implemented. The point is to design it with enough flexibility to be able to extend to foreseeable use cases. Add LLProcess::Params::files to collect up to 3 FileParam items. Naturally this extends the accepted LLSD syntax as well. Implement type="" (child inherits parent file descriptor) and "pipe" (parent constructs anonymous pipe to pass to child). Add LLProcess::FILESLOT enum, plus methods: getReadPipe(FILESLOT), getOptReadPipe(FILESLOT) getWritePipe(), getOptWritePipe() getPipeName(FILESLOT): placeholder implementation for now Add LLProcess::ReadPipe and WritePipe classes, as returned by get*Pipe(). WritePipe supports get_ostream() method for streaming to child stdin. ReadPipe supports get_istream() method for reading from child stdout/stderr. It also provides getPump() returning LLEventPump& so interested parties can listen for arrival of new data on the aforementioned std::istream. For "pipe" slots, instantiate appropriate *Pipe class. ReadPipe and WritePipe classes are pure virtual bases for ReadPipeImpl and WritePipeImpl, respectively: all implementation data are hidden in the latter classes, visible only in llprocess.cpp. In fact each *PipeImpl class registers itself for "mainloop" ticks, attempting nonblocking I/O to the underlying apr_file_t on each tick. Data are buffered in a boost::asio::streambuf, which bridges between std::[io]stream and the APR I/O calls. Sanity-test ReadPipeImpl by using a pipe to absorb the Python "SyntaxError" output from the successful syntax_error test, rather than alarming the user. Add first few unit tests for validating FileParam. More tests coming! --- indra/llcommon/llprocess.cpp | 321 ++++++++++++++++++++++++++++++-- indra/llcommon/llprocess.h | 235 ++++++++++++++++++++++- indra/llcommon/tests/llprocess_test.cpp | 171 ++++++++++++++++- 3 files changed, 695 insertions(+), 32 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index b13e8eb8e0..55eb7e69d3 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -26,6 +26,7 @@ #include "linden_common.h" #include "llprocess.h" +#include "llsdutil.h" #include "llsdserialize.h" #include "llsingleton.h" #include "llstring.h" @@ -36,19 +37,20 @@ #include #include +#include +#include #include #include +#include +#include +#include +#include +#include +static const char* whichfile[] = { "stdin", "stdout", "stderr" }; static std::string empty; static LLProcess::Status interpret_status(int status); -/// 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) {} -}; - /** * Ref-counted "mainloop" listener. As long as there are still outstanding * LLProcess objects, keep listening on "mainloop" so we can keep polling APR @@ -117,6 +119,154 @@ private: }; static LLProcessListener sProcessListener; +LLProcess::BasePipe::~BasePipe() {} + +class WritePipeImpl: public LLProcess::WritePipe +{ +public: + WritePipeImpl(const std::string& desc, apr_file_t* pipe): + mDesc(desc), + mPipe(pipe), + // Essential to initialize our std::ostream with our special streambuf! + mStream(&mStreambuf) + { + mConnection = LLEventPumps::instance().obtain("mainloop") + .listen(LLEventPump::inventName("WritePipe"), + boost::bind(&WritePipeImpl::tick, this, _1)); + } + + virtual std::ostream& get_ostream() { return mStream; } + + bool tick(const LLSD&) + { + // If there's anything to send, try to send it. + if (mStreambuf.size()) + { + // Copy data out from mStreambuf to a flat, contiguous buffer to + // write -- but only up to a certain size. + std::streamsize total(mStreambuf.size()); + std::streamsize bufsize((std::min)(4096, total)); + boost::asio::streambuf::const_buffers_type bufs = mStreambuf.data(); + std::vector buffer( + boost::asio::buffers_begin(bufs), + boost::asio::buffers_begin(bufs) + bufsize); + apr_size_t written(bufsize); + ll_apr_warn_status(apr_file_write(mPipe, &buffer[0], &written)); + // 'written' is modified to reflect the number of bytes actually + // written. Since they've been sent, remove them from the + // streambuf so we don't keep trying to send them. This could be + // anywhere from 0 up to mStreambuf.size(); anything we haven't + // yet sent, we'll try again next tick() call. + mStreambuf.consume(written); + LL_DEBUGS("LLProcess") << "wrote " << written << " of " << bufsize + << " bytes to " << mDesc + << " (original " << total << "), " + << mStreambuf.size() << " remaining" << LL_ENDL; + } + return false; + } + +private: + std::string mDesc; + apr_file_t* mPipe; + LLTempBoundListener mConnection; + boost::asio::streambuf mStreambuf; + std::ostream mStream; +}; + +class ReadPipeImpl: public LLProcess::ReadPipe +{ +public: + ReadPipeImpl(const std::string& desc, apr_file_t* pipe): + mDesc(desc), + mPipe(pipe), + // Essential to initialize our std::istream with our special streambuf! + mStream(&mStreambuf), + mPump("ReadPipe"), + // use funky syntax to call max() to avoid blighted max() macros + mLimit((std::numeric_limits::max)()) + { + mConnection = LLEventPumps::instance().obtain("mainloop") + .listen(LLEventPump::inventName("ReadPipe"), + boost::bind(&ReadPipeImpl::tick, this, _1)); + } + + // Much of the implementation is simply connecting the abstract virtual + // methods with implementation data concealed from the base class. + virtual std::istream& get_istream() { return mStream; } + virtual LLEventPump& getPump() { return mPump; } + virtual void setLimit(size_t limit) { mLimit = limit; } + virtual size_t getLimit() const { return mLimit; } + +private: + bool tick(const LLSD&) + { + // Allocate a buffer and try, every time, to read into it. + std::vector buffer(4096); + apr_size_t gotten(buffer.size()); + apr_status_t err = apr_file_read(mPipe, &buffer[0], &gotten); + if (err == APR_EOF) + { + // Handle EOF specially: it's part of normal-case processing. + LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL; + // We won't need any more tick() calls. + mConnection.disconnect(); + } + else if (! ll_apr_warn_status(err)) // validate anything but EOF + { + // 'gotten' was modified to reflect the number of bytes actually + // received. If nonzero, add them to the streambuf and notify + // interested parties. + if (gotten) + { + boost::asio::streambuf::mutable_buffers_type mbufs = mStreambuf.prepare(gotten); + std::copy(buffer.begin(), buffer.begin() + gotten, + boost::asio::buffers_begin(mbufs)); + // Don't forget to "commit" the data! The sequence (prepare(), + // commit()) is obviously intended to allow us to allocate + // buffer space, then read directly into some portion of it, + // then commit only as much as we managed to obtain. But the + // only official (documented) way I can find to populate a + // mutable_buffers_type is to use buffers_begin(). It Would Be + // Nice if we were permitted to directly read into + // mutable_buffers_type (not to mention writing directly from + // const_buffers_type in WritePipeImpl; APR even supports an + // apr_file_writev() function for writing from discontiguous + // buffers) -- but as of 2012-02-14, this copying appears to + // be the safest tactic. + mStreambuf.commit(gotten); + LL_DEBUGS("LLProcess") << "read " << gotten << " of " << buffer.size() + << " bytes from " << mDesc << ", new total " + << mStreambuf.size() << LL_ENDL; + + // Now that we've received new data, publish it on our + // LLEventPump as advertised. Constrain it by mLimit. + std::streamsize datasize((std::min)(mLimit, mStreambuf.size())); + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + mPump.post(LLSDMap("data", LLSD::String( + boost::asio::buffers_begin(cbufs), + boost::asio::buffers_begin(cbufs) + datasize))); + } + } + return false; + } + + std::string mDesc; + apr_file_t* mPipe; + LLTempBoundListener mConnection; + boost::asio::streambuf mStreambuf; + std::istream mStream; + LLEventStream mPump; + size_t mLimit; +}; + +/// 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 LLSDOrParams& params) { try @@ -134,12 +284,23 @@ LLProcessPtr LLProcess::create(const LLSDOrParams& params) /// throw LLProcessError mentioning the function call that produced that /// result. #define chkapr(func) \ - if (ll_apr_warn_status(func)) \ - throw LLProcessError(#func " failed") + if (ll_apr_warn_status(func)) \ + throw LLProcessError(#func " failed") LLProcess::LLProcess(const LLSDOrParams& params): - mAutokill(params.autokill) + mAutokill(params.autokill), + mPipes(NSLOTS) { + // Hmm, when you construct a ptr_vector with a size, it merely reserves + // space, it doesn't actually make it that big. Explicitly make it bigger. + // Because of ptr_vector's odd semantics, have to push_back(0) the right + // number of times! resize() wants to default-construct new BasePipe + // instances, which fails because it's pure virtual. But because of the + // constructor call, these push_back() calls should require no new + // allocation. + for (size_t i = 0; i < mPipes.capacity(); ++i) + mPipes.push_back(0); + if (! params.validateBlock(true)) { throw LLProcessError(STRINGIZE("not launched: failed parameter validation\n" @@ -154,16 +315,46 @@ LLProcess::LLProcess(const LLSDOrParams& params): // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx // handle (APR_NO_PIPE, e.g. for stdout, stderr), or create a pipe that's // blocking on the child end but nonblocking at the viewer end - // (APR_CHILD_BLOCK). The viewer can't block for anything: the parent end - // MUST be nonblocking. As the APR documentation itself points out, it - // makes very little sense to set nonblocking I/O for the child end of a - // pipe: only a specially-written child could deal with that. + // (APR_CHILD_BLOCK). // Other major options could include explicitly creating a single APR pipe // and passing it as both stdout and stderr (apr_procattr_child_out_set(), // apr_procattr_child_err_set()), or accepting a filename, opening it and // passing that apr_file_t (simple <, >, 2> redirect emulation). -// chkapr(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)); - chkapr(apr_procattr_io_set(procattr, APR_NO_PIPE, APR_NO_PIPE, APR_NO_PIPE)); + std::vector fparams(params.files.begin(), params.files.end()); + // By default, pass APR_NO_PIPE for each slot. + std::vector select(LL_ARRAY_SIZE(whichfile), APR_NO_PIPE); + for (size_t i = 0; i < (std::min)(LL_ARRAY_SIZE(whichfile), fparams.size()); ++i) + { + if (std::string(fparams[i].type).empty()) // inherit our file descriptor + { + select[i] = APR_NO_PIPE; + } + else if (std::string(fparams[i].type) == "pipe") // anonymous pipe + { + if (! std::string(fparams[i].name).empty()) + { + LL_WARNS("LLProcess") << "For " << std::string(params.executable) + << ": internal names for reusing pipes ('" + << std::string(fparams[i].name) << "' for " << whichfile[i] + << ") are not yet supported -- creating distinct pipe" + << LL_ENDL; + } + // The viewer can't block for anything: the parent end MUST be + // nonblocking. As the APR documentation itself points out, it + // makes very little sense to set nonblocking I/O for the child + // end of a pipe: only a specially-written child could deal with + // that. + select[i] = APR_CHILD_BLOCK; + } + else + { + throw LLProcessError(STRINGIZE("For " << std::string(params.executable) + << ": unsupported FileParam for " << whichfile[i] + << ": type='" << std::string(fparams[i].type) + << "', name='" << std::string(fparams[i].name) << "'")); + } + } + chkapr(apr_procattr_io_set(procattr, select[STDIN], select[STDOUT], select[STDERR])); // Thumbs down on implicitly invoking the shell to invoke the child. From // our point of view, the other major alternative to APR_PROGRAM_PATH @@ -251,6 +442,27 @@ LLProcess::LLProcess(const LLSDOrParams& params): // On Windows, associate the new child process with our Job Object. autokill(); } + + // Instantiate the proper pipe I/O machinery + // want to be able to point to apr_proc_t::in, out, err by index + typedef apr_file_t* apr_proc_t::*apr_proc_file_ptr; + static apr_proc_file_ptr members[] = + { &apr_proc_t::in, &apr_proc_t::out, &apr_proc_t::err }; + for (size_t i = 0; i < NSLOTS; ++i) + { + if (select[i] != APR_CHILD_BLOCK) + continue; + if (i == STDIN) + { + mPipes.replace(i, new WritePipeImpl(whichfile[i], mProcess.*(members[i]))); + } + else + { + mPipes.replace(i, new ReadPipeImpl(whichfile[i], mProcess.*(members[i]))); + } + LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name() + << "('" << whichfile[i] << "')" << LL_ENDL; + } } LLProcess::~LLProcess() @@ -428,6 +640,83 @@ LLProcess::handle LLProcess::getProcessHandle() const #endif } +std::string LLProcess::getPipeName(FILESLOT) +{ + // LLProcess::FileParam::type "npipe" is not yet implemented + return ""; +} + +template +PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot) +{ + if (slot >= NSLOTS) + { + error = STRINGIZE(mDesc << " has no slot " << slot); + return NULL; + } + if (mPipes.is_null(slot)) + { + error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a monitored pipe"); + return NULL; + } + // Make sure we dynamic_cast in pointer domain so we can test, rather than + // accepting runtime's exception. + PIPETYPE* ppipe = dynamic_cast(&mPipes[slot]); + if (! ppipe) + { + error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a " << typeid(PIPETYPE).name()); + return NULL; + } + + error.clear(); + return ppipe; +} + +template +PIPETYPE& LLProcess::getPipe(FILESLOT slot) +{ + std::string error; + PIPETYPE* wp = getPipePtr(error, slot); + if (! wp) + { + throw NoPipe(error); + } + return *wp; +} + +template +boost::optional LLProcess::getOptPipe(FILESLOT slot) +{ + std::string error; + PIPETYPE* wp = getPipePtr(error, slot); + if (! wp) + { + LL_DEBUGS("LLProcess") << error << LL_ENDL; + return boost::optional(); + } + return *wp; +} + +LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot) +{ + return getPipe(slot); +} + +boost::optional LLProcess::getOptWritePipe(FILESLOT slot) +{ + return getOptPipe(slot); +} + +LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot) +{ + return getPipe(slot); +} + +boost::optional LLProcess::getOptReadPipe(FILESLOT slot) +{ + return getOptPipe(slot); +} + std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) { std::string cwd(params.cwd); diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index b95ae55701..448a88f4c0 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -31,8 +31,11 @@ #include "llsdparam.h" #include "apr_thread_proc.h" #include +#include +#include #include #include // std::ostream +#include #if LL_WINDOWS #define WIN32_LEAN_AND_MEAN @@ -43,6 +46,8 @@ #endif #endif +class LLEventPump; + class LLProcess; /// LLProcess instances are created on the heap by static factory methods and /// managed by ref-counted pointers. @@ -64,6 +69,87 @@ class LL_COMMON_API LLProcess: public boost::noncopyable { LOG_CLASS(LLProcess); public: + /** + * Specify what to pass for each of child stdin, stdout, stderr. + * @see LLProcess::Params::files. + */ + struct FileParam: public LLInitParam::Block + { + /** + * type of file handle to pass to child process + * + * - "" (default): let the child inherit the same file handle used by + * this process. For instance, if passed as stdout, child stdout + * will be interleaved with stdout from this process. In this case, + * @a name is moot and should be left "". + * + * - "file": open an OS filesystem file with the specified @a name. + * Not yet implemented. + * + * - "pipe" or "tpipe" or "npipe": depends on @a name + * + * - @a name.empty(): construct an OS pipe used only for this slot + * of the forthcoming child process. + * + * - ! @a name.empty(): in a global registry, find or create (using + * the specified @a name) an OS pipe. The point of the (purely + * internal) @a name is that passing the same @a name in more than + * one slot for a given LLProcess -- or for slots in different + * LLProcess instances -- means the same pipe. For example, you + * might pass the same @a name value as both stdout and stderr to + * make the child process produce both on the same actual pipe. Or + * you might pass the same @a name as the stdout for one LLProcess + * and the stdin for another to connect the two child processes. + * Use LLProcess::getPipeName() to generate a unique name + * guaranteed not to already exist in the registry. Not yet + * implemented. + * + * The difference between "pipe", "tpipe" and "npipe" is as follows. + * + * - "pipe": direct LLProcess to monitor the parent end of the pipe, + * pumping nonblocking I/O every frame. The expectation (at least + * for stdout or stderr) is that the caller will listen for + * incoming data and consume it as it arrives. It's important not + * to neglect such a pipe, because it's buffered in viewer memory. + * If you suspect the child may produce a great volume of output + * between viewer frames, consider directing the child to write to + * a filesystem file instead, then read the file later. + * + * - "tpipe": do not engage LLProcess machinery to monitor the + * parent end of the pipe. A "tpipe" is used only to connect + * different child processes. As such, it makes little sense to + * pass an empty @a name. Not yet implemented. + * + * - "npipe": like "tpipe", but use an OS named pipe with a + * generated name. Note that @a name is the @em internal name of + * the pipe in our global registry -- it doesn't necessarily have + * anything to do with the pipe's name in the OS filesystem. Use + * LLProcess::getPipeName() to obtain the named pipe's OS + * filesystem name, e.g. to pass it as the @a name to another + * LLProcess instance using @a type "file". This supports usage + * like bash's <(subcommand...) or >(subcommand...) + * constructs. Not yet implemented. + * + * In all cases the open mode (read, write) is determined by the child + * slot you're filling. Child stdin means select the "read" end of a + * pipe, or open a filesystem file for reading; child stdout or stderr + * means select the "write" end of a pipe, or open a filesystem file + * for writing. + * + * Confusion such as passing the same pipe as the stdin of two + * processes (rather than stdout for one and stdin for the other) is + * explicitly permitted: it's up to the caller to construct meaningful + * LLProcess pipe graphs. + */ + Optional type; + Optional name; + + FileParam(const std::string& tp="", const std::string& nm=""): + type("type", tp), + name("name", nm) + {} + }; + /// Param block definition struct Params: public LLInitParam::Block { @@ -71,7 +157,8 @@ public: executable("executable"), args("args"), cwd("cwd"), - autokill("autokill", true) + autokill("autokill", true), + files("files") {} /// pathname of executable @@ -87,19 +174,22 @@ public: Optional cwd; /// implicitly kill process on destruction of LLProcess object Optional autokill; + /** + * Up to three FileParam items: for child stdin, stdout, stderr. + * Passing two FileParam entries means default treatment for stderr, + * and so forth. + * + * @note While it's theoretically plausible to pass additional open + * file handles to a child specifically written to expect them, our + * underlying implementation library doesn't support that. + */ + Multiple files; }; typedef LLSDParamAdapter LLSDOrParams; /** * Factory accepting either plain LLSD::Map or Params block. * MAY RETURN DEFAULT-CONSTRUCTED LLProcessPtr if params invalid! - * - * Redundant with Params definition above? - * - * 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 LLSDOrParams& params); virtual ~LLProcess(); @@ -190,6 +280,125 @@ public: */ static handle isRunning(handle, const std::string& desc=""); + /// Provide symbolic access to child's file slots + enum FILESLOT { STDIN=0, STDOUT=1, STDERR=2, NSLOTS=3 }; + + /** + * For a pipe constructed with @a type "npipe", obtain the generated OS + * filesystem name for the specified pipe. Otherwise returns the empty + * string. @see LLProcess::FileParam::type + */ + std::string getPipeName(FILESLOT); + + /// base of ReadPipe, WritePipe + class BasePipe + { + public: + virtual ~BasePipe() = 0; + }; + + /// As returned by getWritePipe() or getOptWritePipe() + class WritePipe: public BasePipe + { + public: + /** + * Get ostream& on which to write to child's stdin. + * + * @usage + * @code + * myProcess->getWritePipe().get_ostream() << "Hello, child!" << std::endl; + * @endcode + */ + virtual std::ostream& get_ostream() = 0; + }; + + /// As returned by getReadPipe() or getOptReadPipe() + class ReadPipe: public BasePipe + { + public: + /** + * Get istream& on which to read from child's stdout or stderr. + * + * @usage + * @code + * std::string stuff; + * myProcess->getReadPipe().get_istream() >> stuff; + * @endcode + * + * You should be sure in advance that the ReadPipe in question can + * fill the request. @see getPump() + */ + virtual std::istream& get_istream() = 0; + + /** + * Get LLEventPump& on which to listen for incoming data. The posted + * LLSD::Map event will contain a key "data" whose value is an + * LLSD::String containing (part of) the data accumulated in the + * buffer. + * + * If the child sends "abc", and this ReadPipe posts "data"="abc", but + * you don't consume it by reading the std::istream returned by + * get_istream(), and the child next sends "def", ReadPipe will post + * "data"="abcdef". + */ + virtual LLEventPump& getPump() = 0; + + /** + * Set maximum length of buffer data that will be posted in the LLSD + * announcing arrival of new data from the child. If you call + * setLimit(5), and the child sends "abcdef", the LLSD event will + * contain "data"="abcde". However, you may still read the entire + * "abcdef" from get_istream(): this limit affects only the size of + * the data posted with the LLSD event. If you don't call this method, + * all pending data will be posted. + */ + virtual void setLimit(size_t limit) = 0; + + /** + * Query the current setLimit() limit. + */ + virtual size_t getLimit() const = 0; + }; + + /// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to + /// create a pipe at the corresponding FILESLOT. + struct NoPipe: public std::runtime_error + { + NoPipe(const std::string& what): std::runtime_error(what) {} + }; + + /** + * Get a reference to the (only) WritePipe for this LLProcess. @a slot, if + * specified, must be STDIN. Throws NoPipe if you did not request a "pipe" + * for child stdin. Use this method when you know how you created the + * LLProcess in hand. + */ + WritePipe& getWritePipe(FILESLOT slot=STDIN); + + /** + * Get a boost::optional to the (only) WritePipe for this + * LLProcess. @a slot, if specified, must be STDIN. The return value is + * empty if you did not request a "pipe" for child stdin. Use this method + * for inspecting an LLProcess you did not create. + */ + boost::optional getOptWritePipe(FILESLOT slot=STDIN); + + /** + * Get a reference to one of the ReadPipes for this LLProcess. @a slot, if + * specified, must be STDOUT or STDERR. Throws NoPipe if you did not + * request a "pipe" for child stdout or stderr. Use this method when you + * know how you created the LLProcess in hand. + */ + ReadPipe& getReadPipe(FILESLOT slot); + + /** + * Get a boost::optional to one of the ReadPipes for this + * LLProcess. @a slot, if specified, must be STDOUT or STDERR. The return + * value is empty if you did not request a "pipe" for child stdout or + * stderr. Use this method for inspecting an LLProcess you did not create. + */ + boost::optional getOptReadPipe(FILESLOT slot); + private: /// constructor is private: use create() instead LLProcess(const LLSDOrParams& params); @@ -198,11 +407,21 @@ private: static void status_callback(int reason, void* data, int status); // Object-oriented callback void handle_status(int reason, int status); + // implementation for get[Opt][Read|Write]Pipe() + template + PIPETYPE& getPipe(FILESLOT slot); + template + boost::optional getOptPipe(FILESLOT slot); + template + PIPETYPE* getPipePtr(std::string& error, FILESLOT slot); std::string mDesc; apr_proc_t mProcess; bool mAutokill; Status mStatus; + // explicitly want this ptr_vector to be able to store NULLs + typedef boost::ptr_vector< boost::nullable > PipeVector; + PipeVector mPipes; }; /// for logging diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 8c21be196b..d4e9977e63 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -34,6 +34,7 @@ #include "stringize.h" #include "llsdutil.h" #include "llevents.h" +#include "llerrorcontrol.h" #if defined(LL_WINDOWS) #define sleep(secs) _sleep((secs) * 1000) @@ -92,12 +93,18 @@ static std::string readfile(const std::string& pathname, const std::string& desc /// Looping on LLProcess::isRunning() must now be accompanied by pumping /// "mainloop" -- otherwise the status won't update and you get an infinite /// loop. +void yield(int seconds=1) +{ + // This function simulates waiting for another viewer frame + sleep(seconds); + LLEventPumps::instance().obtain("mainloop").post(LLSD()); +} + void waitfor(LLProcess& proc) { while (proc.isRunning()) { - sleep(1); - LLEventPumps::instance().obtain("mainloop").post(LLSD()); + yield(); } } @@ -105,8 +112,7 @@ void waitfor(LLProcess::handle h, const std::string& desc) { while (LLProcess::isRunning(h, desc)) { - sleep(1); - LLEventPumps::instance().obtain("mainloop").post(LLSD()); + yield(); } } @@ -219,6 +225,68 @@ private: std::string mPath; }; +// statically reference the function in test.cpp... it's short, we could +// replicate, but better to reuse +extern void wouldHaveCrashed(const std::string& message); + +/** + * Capture log messages. This is adapted (simplified) from the one in + * llerror_test.cpp. Sigh, should've broken that out into a separate header + * file, but time for this project is short... + */ +class TestRecorder : public LLError::Recorder +{ +public: + TestRecorder(): + // Mostly what we're trying to accomplish by saving and resetting + // LLError::Settings is to bypass the default RecordToStderr and + // RecordToWinDebug Recorders. As these are visible only inside + // llerror.cpp, we can't just call LLError::removeRecorder() with + // each. For certain tests we need to produce, capture and examine + // DEBUG log messages -- but we don't want to spam the user's console + // with that output. If it turns out that saveAndResetSettings() has + // some bad effect, give up and just let the DEBUG level log messages + // display. + mOldSettings(LLError::saveAndResetSettings()) + { + LLError::setFatalFunction(wouldHaveCrashed); + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LLError::addRecorder(this); + } + + ~TestRecorder() + { + LLError::removeRecorder(this); + LLError::restoreSettings(mOldSettings); + } + + void recordMessage(LLError::ELevel level, + const std::string& message) + { + mMessages.push_back(message); + } + + /// Don't assume the message we want is necessarily the LAST log message + /// emitted by the underlying code; search backwards through all messages + /// for the sought string. + std::string messageWith(const std::string& search) + { + for (std::list::const_reverse_iterator rmi(mMessages.rbegin()), + rmend(mMessages.rend()); + rmi != rmend; ++rmi) + { + if (rmi->find(search) != std::string::npos) + return *rmi; + } + // failed to find any such message + return std::string(); + } + + typedef std::list MessageList; + MessageList mMessages; + LLError::Settings* mOldSettings; +}; + /***************************************************************************** * TUT *****************************************************************************/ @@ -602,9 +670,19 @@ namespace tut set_test_name("syntax_error:"); PythonProcessLauncher py("syntax_error:", "syntax_error:\n"); + py.mParams.files.add(LLProcess::FileParam()); // inherit stdin + py.mParams.files.add(LLProcess::FileParam()); // inherit stdout + py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stderr py.run(); ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); ensure_equals("Status.mData", py.mPy->getStatus().mData, 1); + std::istream& rpipe(py.mPy->getReadPipe(LLProcess::STDERR).get_istream()); + std::vector buffer(4096); + rpipe.read(&buffer[0], buffer.size()); + std::streamsize got(rpipe.gcount()); + ensure("Nothing read from stderr pipe", got); + std::string data(&buffer[0], got); + ensure("Didn't find 'SyntaxError:'", data.find("\nSyntaxError:") != std::string::npos); } template<> template<> @@ -629,7 +707,7 @@ namespace tut int i = 0, timeout = 60; for ( ; i < timeout; ++i) { - sleep(1); + yield(); if (readfile(out.getName(), "from kill() script") == "ok") break; } @@ -678,7 +756,7 @@ namespace tut int i = 0, timeout = 60; for ( ; i < timeout; ++i) { - sleep(1); + yield(); if (readfile(out.getName(), "from kill() script") == "ok") break; } @@ -733,7 +811,7 @@ namespace tut int i = 0, timeout = 60; for ( ; i < timeout; ++i) { - sleep(1); + yield(); if (readfile(from.getName(), "from autokill script") == "ok") break; } @@ -742,7 +820,7 @@ namespace tut // Now destroy the LLProcess, which should NOT kill the child! } // If the destructor killed the child anyway, give it time to die - sleep(2); + yield(2); // How do we know it's not terminated? By making it respond to // a specific stimulus in a specific way. { @@ -755,4 +833,81 @@ namespace tut // script could not have written 'ack' as we expect. ensure_equals("autokill script output", readfile(from.getName()), "ack"); } + + template<> template<> + void object::test<10>() + { + set_test_name("'bogus' test"); + TestRecorder recorder; + PythonProcessLauncher py("'bogus' test", + "print 'Hello world'\n"); + py.mParams.files.add(LLProcess::FileParam("bogus")); + py.mPy = LLProcess::create(py.mParams); + ensure("should have rejected 'bogus'", ! py.mPy); + std::string message(recorder.messageWith("bogus")); + ensure("did not log 'bogus' type", ! message.empty()); + ensure_contains("did not name 'stdin'", message, "stdin"); + } + + template<> template<> + void object::test<11>() + { + set_test_name("'file' test"); + // Replace this test with one or more real 'file' tests when we + // implement 'file' support + PythonProcessLauncher py("'file' test", + "print 'Hello world'\n"); + py.mParams.files.add(LLProcess::FileParam()); + py.mParams.files.add(LLProcess::FileParam("file")); + py.mPy = LLProcess::create(py.mParams); + ensure("should have rejected 'file'", ! py.mPy); + } + + template<> template<> + void object::test<12>() + { + set_test_name("'tpipe' test"); + // Replace this test with one or more real 'tpipe' tests when we + // implement 'tpipe' support + TestRecorder recorder; + PythonProcessLauncher py("'tpipe' test", + "print 'Hello world'\n"); + py.mParams.files.add(LLProcess::FileParam()); + py.mParams.files.add(LLProcess::FileParam("tpipe")); + py.mPy = LLProcess::create(py.mParams); + ensure("should have rejected 'tpipe'", ! py.mPy); + std::string message(recorder.messageWith("tpipe")); + ensure("did not log 'tpipe' type", ! message.empty()); + ensure_contains("did not name 'stdout'", message, "stdout"); + } + + template<> template<> + void object::test<13>() + { + set_test_name("'npipe' test"); + // Replace this test with one or more real 'npipe' tests when we + // implement 'npipe' support + TestRecorder recorder; + PythonProcessLauncher py("'npipe' test", + "print 'Hello world'\n"); + py.mParams.files.add(LLProcess::FileParam()); + py.mParams.files.add(LLProcess::FileParam()); + py.mParams.files.add(LLProcess::FileParam("npipe")); + py.mPy = LLProcess::create(py.mParams); + ensure("should have rejected 'npipe'", ! py.mPy); + std::string message(recorder.messageWith("npipe")); + ensure("did not log 'npipe' type", ! message.empty()); + ensure_contains("did not name 'stderr'", message, "stderr"); + } + + // TODO: + // test "pipe" with nonempty name (should log & continue) + // test pipe for just stderr (tests for get[Opt]ReadPipe(), get[Opt]WritePipe()) + // test pipe for stdin, stdout (etc.) + // test getWritePipe().get_ostream(), getReadPipe().get_istream() + // test listening on getReadPipe().getPump(), disconnecting + // test setLimit(), getLimit() + // test EOF -- check logging + // test get(Read|Write)Pipe(3), unmonitored slot, getReadPipe(1), getWritePipe(0) + } // namespace tut -- cgit v1.2.3 From c6ccdb5b5088f3ab24bb89d8c56049c7a17a663e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Feb 2012 12:11:38 -0500 Subject: Add tests for LLProcess::get[Opt][Read|Write]Pipe() validations. --- indra/llcommon/tests/llprocess_test.cpp | 90 +++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index d4e9977e63..06f9f3827f 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -900,14 +900,98 @@ namespace tut ensure_contains("did not name 'stderr'", message, "stderr"); } + template<> template<> + void object::test<14>() + { + set_test_name("internal pipe name warning"); + TestRecorder recorder; + PythonProcessLauncher py("pipe warning", + "import sys\n" + "sys.exit(7)\n"); + py.mParams.files.add(LLProcess::FileParam("pipe", "somename")); + py.run(); // verify that it did launch anyway + ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); + ensure_equals("Status.mData", py.mPy->getStatus().mData, 7); + std::string message(recorder.messageWith("not yet supported")); + ensure("did not log pipe name warning", ! message.empty()); + ensure_contains("log message did not mention internal pipe name", + message, "somename"); + } + +#define TEST_getPipe(PROCESS, GETPIPE, GETOPTPIPE, VALID, NOPIPE, BADPIPE) \ + do \ + { \ + std::string threw; \ + /* Both the following calls should work. */ \ + (PROCESS).GETPIPE(VALID); \ + ensure(#GETOPTPIPE "(" #VALID ") failed", (PROCESS).GETOPTPIPE(VALID)); \ + /* pass obviously bogus PIPESLOT */ \ + CATCH_IN(threw, LLProcess::NoPipe, (PROCESS).GETPIPE(LLProcess::FILESLOT(4))); \ + ensure_contains("didn't reject bad slot", threw, "no slot"); \ + ensure_contains("didn't mention bad slot num", threw, "4"); \ + EXPECT_FAIL_WITH_LOG(threw, (PROCESS).GETOPTPIPE(LLProcess::FILESLOT(4))); \ + /* pass NOPIPE */ \ + CATCH_IN(threw, LLProcess::NoPipe, (PROCESS).GETPIPE(NOPIPE)); \ + ensure_contains("didn't reject non-pipe", threw, "not a monitored"); \ + EXPECT_FAIL_WITH_LOG(threw, (PROCESS).GETOPTPIPE(NOPIPE)); \ + /* pass BADPIPE: FILESLOT isn't empty but wrong direction */ \ + CATCH_IN(threw, LLProcess::NoPipe, (PROCESS).GETPIPE(BADPIPE)); \ + /* sneaky: GETPIPE is getReadPipe or getWritePipe */ \ + /* so skip "get" to obtain ReadPipe or WritePipe :-P */ \ + ensure_contains("didn't reject wrong pipe", threw, (#GETPIPE)+3); \ + EXPECT_FAIL_WITH_LOG(threw, (PROCESS).GETOPTPIPE(BADPIPE)); \ + } while (0) + +/// For expecting exceptions. Execute CODE, catch EXCEPTION, store its what() +/// in std::string THREW, ensure it's not empty (i.e. EXCEPTION did happen). +#define CATCH_IN(THREW, EXCEPTION, CODE) \ + do \ + { \ + (THREW).clear(); \ + try \ + { \ + CODE; \ + } \ + catch (const EXCEPTION& e) \ + { \ + (THREW) = e.what(); \ + } \ + ensure("failed to throw " #EXCEPTION ": " #CODE, ! (THREW).empty()); \ + } while (0) + +#define EXPECT_FAIL_WITH_LOG(EXPECT, CODE) \ + do \ + { \ + TestRecorder recorder; \ + ensure(#CODE " succeeded", ! (CODE)); \ + ensure("wrong log message", ! recorder.messageWith(EXPECT).empty()); \ + } while (0) + + template<> template<> + void object::test<15>() + { + set_test_name("get*Pipe() validation"); + PythonProcessLauncher py("just stderr", + "print 'this output is expected'\n"); + py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stdin + py.mParams.files.add(LLProcess::FileParam()); // inherit stdout + py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stderr + py.run(); + TEST_getPipe(*py.mPy, getWritePipe, getOptWritePipe, + LLProcess::STDIN, // VALID + LLProcess::STDOUT, // NOPIPE + LLProcess::STDERR); // BADPIPE + TEST_getPipe(*py.mPy, getReadPipe, getOptReadPipe, + LLProcess::STDERR, // VALID + LLProcess::STDOUT, // NOPIPE + LLProcess::STDIN); // BADPIPE + } + // TODO: - // test "pipe" with nonempty name (should log & continue) - // test pipe for just stderr (tests for get[Opt]ReadPipe(), get[Opt]WritePipe()) // test pipe for stdin, stdout (etc.) // test getWritePipe().get_ostream(), getReadPipe().get_istream() // test listening on getReadPipe().getPump(), disconnecting // test setLimit(), getLimit() // test EOF -- check logging - // test get(Read|Write)Pipe(3), unmonitored slot, getReadPipe(1), getWritePipe(0) } // namespace tut -- cgit v1.2.3 From 10ab4adc86207f86df30ab23d8858c23e7f550ea Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Feb 2012 13:44:43 -0500 Subject: Fix llprocess_test.cpp's exception catching for Linux. In the course of re-enabling the indra/test tests last year, Log generalized a workaround I'd introduced in llsdmessage_test.cpp. In Linux viewer land, a test program trying to catch an expected exception can't seem to catch it by its specific class (across the libllcommon.so boundary), but must instead catch std::runtime_error and validate the typeid().name() string. Log added a macro for this idiom in llevents_tut.cpp. Generalize that macro further for normal-case processing as well, move it to a header file of its own and use it in all known places -- plus the new exception-catching tests in llprocess_test.cpp. --- indra/llcommon/tests/llprocess_test.cpp | 6 +-- indra/llmessage/tests/llsdmessage_test.cpp | 36 ++----------- indra/test/catch_and_store_what_in.h | 86 ++++++++++++++++++++++++++++++ indra/test/llevents_tut.cpp | 69 +++--------------------- 4 files changed, 98 insertions(+), 99 deletions(-) create mode 100644 indra/test/catch_and_store_what_in.h diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 06f9f3827f..a901c577d6 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -31,6 +31,7 @@ #include "../test/lltut.h" #include "../test/manageapr.h" #include "../test/namedtempfile.h" +#include "../test/catch_and_store_what_in.h" #include "stringize.h" #include "llsdutil.h" #include "llevents.h" @@ -952,10 +953,7 @@ namespace tut { \ CODE; \ } \ - catch (const EXCEPTION& e) \ - { \ - (THREW) = e.what(); \ - } \ + CATCH_AND_STORE_WHAT_IN(THREW, EXCEPTION) \ ensure("failed to throw " #EXCEPTION ": " #CODE, ! (THREW).empty()); \ } while (0) diff --git a/indra/llmessage/tests/llsdmessage_test.cpp b/indra/llmessage/tests/llsdmessage_test.cpp index 0f2c069303..6871ac0d52 100644 --- a/indra/llmessage/tests/llsdmessage_test.cpp +++ b/indra/llmessage/tests/llsdmessage_test.cpp @@ -42,6 +42,7 @@ // external library headers // other Linden headers #include "../test/lltut.h" +#include "../test/catch_and_store_what_in.h" #include "llsdserialize.h" #include "llevents.h" #include "stringize.h" @@ -72,43 +73,14 @@ namespace tut template<> template<> void llsdmessage_object::test<1>() { - bool threw = false; + std::string threw; // This should fail... try { LLSDMessage localListener; } - catch (const LLEventPump::DupPumpName&) - { - threw = true; - } - catch (const std::runtime_error& ex) - { - // This clause is because on Linux, on the viewer side, for this - // one test program (though not others!), the - // LLEventPump::DupPumpName exception isn't caught by the clause - // above. Warn the user... - std::cerr << "Failed to catch " << typeid(ex).name() << std::endl; - // But if the expected exception was thrown, allow the test to - // succeed anyway. Not sure how else to handle this odd case. - if (std::string(typeid(ex).name()) == typeid(LLEventPump::DupPumpName).name()) - { - threw = true; - } - else - { - // We don't even recognize this exception. Let it propagate - // out to TUT to fail the test. - throw; - } - } - catch (...) - { - std::cerr << "Utterly failed to catch expected exception!" << std::endl; - // This case is full of fail. We HAVE to address it. - throw; - } - ensure("second LLSDMessage should throw", threw); + CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupPumpName) + ensure("second LLSDMessage should throw", ! threw.empty()); } template<> template<> diff --git a/indra/test/catch_and_store_what_in.h b/indra/test/catch_and_store_what_in.h new file mode 100644 index 0000000000..59f8cc0085 --- /dev/null +++ b/indra/test/catch_and_store_what_in.h @@ -0,0 +1,86 @@ +/** + * @file catch_and_store_what_in.h + * @author Nat Goodspeed + * @date 2012-02-15 + * @brief CATCH_AND_STORE_WHAT_IN() macro + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_CATCH_AND_STORE_WHAT_IN_H) +#define LL_CATCH_AND_STORE_WHAT_IN_H + +/** + * Idiom useful for test programs: catch an expected exception, store its + * what() string in a specified std::string variable. From there the caller + * can do things like: + * @code + * ensure("expected exception not thrown", ! string.empty()); + * @endcode + * or + * @code + * ensure_contains("exception doesn't mention blah", string, "blah"); + * @endcode + * etc. + * + * The trouble is that when linking to a dynamic libllcommon.so on Linux, we + * generally fail to catch the specific exception. Oddly, we can catch it as + * std::runtime_error and validate its typeid().name(), so we do -- but that's + * a lot of boilerplate per test. Encapsulate with this macro. Usage: + * + * @code + * std::string threw; + * try + * { + * some_call_that_should_throw_Foo(); + * } + * CATCH_AND_STORE_WHAT_IN(threw, Foo) + * ensure("some_call_that_should_throw_Foo() didn't throw", ! threw.empty()); + * @endcode + */ +#define CATCH_AND_STORE_WHAT_IN(THREW, EXCEPTION) \ +catch (const EXCEPTION& ex) \ +{ \ + (THREW) = ex.what(); \ +} \ +CATCH_MISSED_LINUX_EXCEPTION(THREW, EXCEPTION) + +#ifndef LL_LINUX +#define CATCH_MISSED_LINUX_EXCEPTION(THREW, EXCEPTION) \ + /* only needed on Linux */ +#else // LL_LINUX + +#define CATCH_MISSED_LINUX_EXCEPTION(THREW, EXCEPTION) \ +catch (const std::runtime_error& ex) \ +{ \ + /* This clause is needed on Linux, on the viewer side, because */ \ + /* the exception isn't caught by catch (const EXCEPTION&). */ \ + /* But if the expected exception was thrown, allow the test to */ \ + /* succeed anyway. Not sure how else to handle this odd case. */ \ + if (std::string(typeid(ex).name()) == typeid(EXCEPTION).name()) \ + { \ + /* std::cerr << "Caught " << typeid(ex).name() */ \ + /* << " with Linux workaround" << std::endl; */ \ + (THREW) = ex.what(); \ + /*std::cout << ex.what() << std::endl;*/ \ + } \ + else \ + { \ + /* We don't even recognize this exception. Let it propagate */ \ + /* out to TUT to fail the test. */ \ + throw; \ + } \ +} \ +catch (...) \ +{ \ + std::cerr << "Failed to catch expected exception " \ + << #EXCEPTION << "!" << std::endl; \ + /* This indicates a problem in the test that should be addressed. */ \ + throw; \ +} + +#endif // LL_LINUX + +#endif /* ! defined(LL_CATCH_AND_STORE_WHAT_IN_H) */ diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp index 4699bb1827..ca4c74099f 100644 --- a/indra/test/llevents_tut.cpp +++ b/indra/test/llevents_tut.cpp @@ -49,46 +49,12 @@ #include // other Linden headers #include "lltut.h" +#include "catch_and_store_what_in.h" #include "stringize.h" #include "tests/listener.h" using boost::assign::list_of; -#ifdef LL_LINUX -#define CATCH_MISSED_LINUX_EXCEPTION(exception, threw) \ -catch (const std::runtime_error& ex) \ -{ \ - /* This clause is needed on Linux, on the viewer side, because the */ \ - /* exception isn't caught by the clause above. Warn the user... */ \ - std::cerr << "Failed to catch " << typeid(ex).name() << std::endl; \ - /* But if the expected exception was thrown, allow the test to */ \ - /* succeed anyway. Not sure how else to handle this odd case. */ \ - /* This approach is also used in llsdmessage_test.cpp. */ \ - if (std::string(typeid(ex).name()) == typeid(exception).name()) \ - { \ - threw = ex.what(); \ - /*std::cout << ex.what() << std::endl;*/ \ - } \ - else \ - { \ - /* We don't even recognize this exception. Let it propagate */ \ - /* out to TUT to fail the test. */ \ - throw; \ - } \ -} \ -catch (...) \ -{ \ - std::cerr << "Utterly failed to catch expected exception " << #exception << "!" << \ - std::endl; \ - /* This indicates a problem in the test that should be addressed. */ \ - throw; \ -} - -#else // LL_LINUX -#define CATCH_MISSED_LINUX_EXCEPTION(exception, threw) \ - /* Not needed on other platforms */ -#endif // LL_LINUX - template T make(const T& value) { @@ -178,11 +144,7 @@ void events_object::test<1>() per_frame.listen(listener0.getName(), // note bug, dup name boost::bind(&Listener::call, boost::ref(listener1), _1)); } - catch (const LLEventPump::DupListenerName& e) - { - threw = e.what(); - } - CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::DupListenerName, threw) + CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupListenerName) ensure_equals(threw, std::string("DupListenerName: " "Attempt to register duplicate listener name '") + @@ -388,12 +350,7 @@ void events_object::test<7>() // after "Mary" and "checked" -- whoops! make(list_of("Mary")("checked"))); } - catch (const LLEventPump::Cycle& e) - { - threw = e.what(); - // std::cout << "Caught: " << e.what() << '\n'; - } - CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::Cycle, threw) + CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::Cycle) // Obviously the specific wording of the exception text can // change; go ahead and change the test to match. // Establish that it contains: @@ -426,12 +383,7 @@ void events_object::test<7>() make(list_of("shoelaces")), make(list_of("yellow"))); } - catch (const LLEventPump::OrderChange& e) - { - threw = e.what(); - // std::cout << "Caught: " << e.what() << '\n'; - } - CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::OrderChange, threw) + CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::OrderChange) // Same remarks about the specific wording of the exception. Just // ensure that it contains enough information to clarify the // problem and what must be done to resolve it. @@ -459,12 +411,7 @@ void events_object::test<8>() // then another with a duplicate name. LLEventStream bob2("bob"); } - catch (const LLEventPump::DupPumpName& e) - { - threw = e.what(); - // std::cout << "Caught: " << e.what() << '\n'; - } - CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::DupPumpName, threw) + CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupPumpName) ensure("Caught DupPumpName", !threw.empty()); } // delete first 'bob' LLEventStream bob("bob"); // should work, previous one unregistered @@ -505,11 +452,7 @@ void events_object::test<9>() LLListenerOrPumpName empty; empty(17); } - catch (const LLListenerOrPumpName::Empty& e) - { - threw = e.what(); - } - CATCH_MISSED_LINUX_EXCEPTION(LLListenerOrPumpName::Empty, threw) + CATCH_AND_STORE_WHAT_IN(threw, LLListenerOrPumpName::Empty) ensure("threw Empty", !threw.empty()); } -- cgit v1.2.3 From 9b02f483ffe6f313ec86af3f29fa858fa0cb22e4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Feb 2012 14:04:46 -0500 Subject: VS2010 doesn't know how to compute min(4096, size_t) :-P --- indra/llcommon/llprocess.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 55eb7e69d3..d7c297b952 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -144,8 +144,8 @@ public: { // Copy data out from mStreambuf to a flat, contiguous buffer to // write -- but only up to a certain size. - std::streamsize total(mStreambuf.size()); - std::streamsize bufsize((std::min)(4096, total)); + std::size_t total(mStreambuf.size()); + std::size_t bufsize((std::min)(std::size_t(4096), total)); boost::asio::streambuf::const_buffers_type bufs = mStreambuf.data(); std::vector buffer( boost::asio::buffers_begin(bufs), @@ -241,7 +241,7 @@ private: // Now that we've received new data, publish it on our // LLEventPump as advertised. Constrain it by mLimit. - std::streamsize datasize((std::min)(mLimit, mStreambuf.size())); + std::size_t datasize((std::min)(mLimit, mStreambuf.size())); boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); mPump.post(LLSDMap("data", LLSD::String( boost::asio::buffers_begin(cbufs), -- cgit v1.2.3 From 56d931216e67a3e59199669bba022c65a9617bb5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Feb 2012 15:47:03 -0500 Subject: Add LLProcess::ReadPipe::size(), peek(), contains(). Also add "len" key to event data on LLProcess::getPump(). If you've used setLimit(), event["data"].length() may not reflect the length of the accumulated data in the ReadPipe. Add unit test with stdin/stdout handshake with child process. --- indra/llcommon/llprocess.cpp | 31 +++++++++++++++++++----- indra/llcommon/llprocess.h | 25 +++++++++++++++++++ indra/llcommon/tests/llprocess_test.cpp | 43 +++++++++++++++++++++++++++++++-- 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index d7c297b952..1481bf571f 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -197,6 +197,25 @@ public: virtual LLEventPump& getPump() { return mPump; } virtual void setLimit(size_t limit) { mLimit = limit; } virtual size_t getLimit() const { return mLimit; } + virtual std::size_t size() { return mStreambuf.size(); } + + virtual std::string peek(std::size_t offset=0, + std::size_t len=(std::numeric_limits::max)()) + { + // Constrain caller's offset and len to overlap actual buffer content. + std::size_t real_offset = (std::min)(mStreambuf.size(), offset); + std::size_t real_end = (std::min)(mStreambuf.size(), real_offset + len); + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + return std::string(boost::asio::buffers_begin(cbufs) + real_offset, + boost::asio::buffers_begin(cbufs) + real_end); + } + + virtual bool contains(const std::string& seek, std::size_t offset=0) + { + // There may be a more efficient way to search mStreambuf contents, + // but this is far the easiest... + return peek(offset).find(seek) != std::string::npos; + } private: bool tick(const LLSD&) @@ -240,12 +259,13 @@ private: << mStreambuf.size() << LL_ENDL; // Now that we've received new data, publish it on our - // LLEventPump as advertised. Constrain it by mLimit. + // LLEventPump as advertised. Constrain it by mLimit. But show + // listener the actual accumulated buffer size, regardless of + // mLimit. std::size_t datasize((std::min)(mLimit, mStreambuf.size())); - boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); - mPump.post(LLSDMap("data", LLSD::String( - boost::asio::buffers_begin(cbufs), - boost::asio::buffers_begin(cbufs) + datasize))); + mPump.post(LLSDMap + ("data", peek(0, datasize)) + ("len", LLSD::Integer(mStreambuf.size()))); } } return false; @@ -985,5 +1005,4 @@ void LLProcess::reap(void) } } |*==========================================================================*/ - #endif // Posix diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 448a88f4c0..bf0517600d 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -330,6 +330,31 @@ public: */ virtual std::istream& get_istream() = 0; + /** + * Get accumulated buffer length. + * Often we need to refrain from actually reading the std::istream + * returned by get_istream() until we've accumulated enough data to + * make it worthwhile. For instance, if we're expecting a number from + * the child, but the child happens to flush "12" before emitting + * "3\n", get_istream() >> myint could return 12 rather than 123! + */ + virtual std::size_t size() = 0; + + /** + * Peek at accumulated buffer data without consuming it. Optional + * parameters give you substr() functionality. + * + * @note You can discard buffer data using get_istream().ignore(n). + */ + virtual std::string peek(std::size_t offset=0, + std::size_t len=(std::numeric_limits::max)()) = 0; + + /** + * Search accumulated buffer data without retrieving it. Optional + * offset allows you to start at specified position. + */ + virtual bool contains(const std::string& seek, std::size_t offset=0) = 0; + /** * Get LLEventPump& on which to listen for incoming data. The posted * LLSD::Map event will contain a key "data" whose value is an diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index a901c577d6..2db17cae97 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -919,6 +919,7 @@ namespace tut message, "somename"); } + /*-------------- support for "get*Pipe() validation" test --------------*/ #define TEST_getPipe(PROCESS, GETPIPE, GETOPTPIPE, VALID, NOPIPE, BADPIPE) \ do \ { \ @@ -985,11 +986,49 @@ namespace tut LLProcess::STDIN); // BADPIPE } + template<> template<> + void object::test<16>() + { + set_test_name("talk to stdin/stdout"); + PythonProcessLauncher py("stdin/stdout", + "import sys, time\n" + "print 'ok'\n" + "sys.stdout.flush()\n" + "# wait for 'go' from test program\n" + "go = sys.stdin.readline()\n" + "if go != 'go\\n':\n" + " sys.exit('expected \"go\", saw %r' % go)\n" + "print 'ack'\n"); + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.mPy = LLProcess::create(py.mParams); + ensure("couldn't launch stdin/stdout script", py.mPy); + LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); + int i, timeout = 60; + for (i = 0; i < timeout && py.mPy->isRunning() && childout.size() < 3; ++i) + { + yield(); + } + ensure("script never started", i < timeout); + std::string line; + std::getline(childout.get_istream(), line); + ensure_equals("bad wakeup from stdin/stdout script", line, "ok"); + py.mPy->getWritePipe().get_ostream() << "go" << std::endl; + for (i = 0; i < timeout && py.mPy->isRunning() && ! childout.contains("\n"); ++i) + { + yield(); + } + ensure("script never replied", childout.contains("\n")); + std::getline(childout.get_istream(), line); + ensure_equals("child didn't ack", line, "ack"); + ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED); + ensure_equals("bad child exit code", py.mPy->getStatus().mData, 0); + } + // TODO: - // test pipe for stdin, stdout (etc.) - // test getWritePipe().get_ostream(), getReadPipe().get_istream() // test listening on getReadPipe().getPump(), disconnecting // test setLimit(), getLimit() // test EOF -- check logging + // test peek() with substr } // namespace tut -- cgit v1.2.3 From fc6d70db8771320f3b1136e76383fc85ddf1b6b2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Feb 2012 20:57:25 -0500 Subject: Don't be confused by "\r\n" line endings on pipe on Windows. These are all very well when we just want to dump the output to a log, or whatever, but in a unit-test context it matters for comparison. --- indra/llcommon/tests/llprocess_test.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 2db17cae97..6d6b888471 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -288,6 +288,22 @@ public: LLError::Settings* mOldSettings; }; +std::string getline(std::istream& in) +{ + std::string line; + std::getline(in, line); + // Blur the distinction between "\r\n" and plain "\n". std::getline() will + // have eaten the "\n", but we could still end up with a trailing "\r". + std::string::size_type lastpos = line.find_last_not_of("\r"); + if (lastpos != std::string::npos) + { + // Found at least one character that's not a trailing '\r'. SKIP OVER + // IT and then erase the rest of the line. + line.erase(lastpos+1); + } + return line; +} + /***************************************************************************** * TUT *****************************************************************************/ @@ -1010,17 +1026,15 @@ namespace tut yield(); } ensure("script never started", i < timeout); - std::string line; - std::getline(childout.get_istream(), line); - ensure_equals("bad wakeup from stdin/stdout script", line, "ok"); + ensure_equals("bad wakeup from stdin/stdout script", + getline(childout.get_istream()), "ok"); py.mPy->getWritePipe().get_ostream() << "go" << std::endl; for (i = 0; i < timeout && py.mPy->isRunning() && ! childout.contains("\n"); ++i) { yield(); } ensure("script never replied", childout.contains("\n")); - std::getline(childout.get_istream(), line); - ensure_equals("child didn't ack", line, "ack"); + ensure_equals("child didn't ack", getline(childout.get_istream()), "ack"); ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED); ensure_equals("bad child exit code", py.mPy->getStatus().mData, 0); } -- cgit v1.2.3 From 85057908c3f7e48f1dc086ea1c82e672674b2596 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Feb 2012 21:55:53 -0500 Subject: Add unit test for listening on LLProcess::ReadPipe::getPump(). --- indra/llcommon/tests/llprocess_test.cpp | 89 ++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 6d6b888471..31bc833a1d 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -1028,6 +1028,7 @@ namespace tut ensure("script never started", i < timeout); ensure_equals("bad wakeup from stdin/stdout script", getline(childout.get_istream()), "ok"); + // important to get the implicit flush from std::endl py.mPy->getWritePipe().get_ostream() << "go" << std::endl; for (i = 0; i < timeout && py.mPy->isRunning() && ! childout.contains("\n"); ++i) { @@ -1039,8 +1040,94 @@ namespace tut ensure_equals("bad child exit code", py.mPy->getStatus().mData, 0); } + struct EventListener: public boost::noncopyable + { + EventListener(LLEventPump& pump) + { + mConnection = + pump.listen("EventListener", boost::bind(&EventListener::tick, this, _1)); + } + + bool tick(const LLSD& data) + { + mHistory.push_back(data); + return false; + } + + std::list mHistory; + LLTempBoundListener mConnection; + }; + + static bool ack(std::ostream& out, const LLSD& data) + { + out << "continue" << std::endl; + return false; + } + + template<> template<> + void object::test<17>() + { + set_test_name("listen for ReadPipe events"); + PythonProcessLauncher py("ReadPipe listener", + "import sys\n" + "sys.stdout.write('abc')\n" + "sys.stdout.flush()\n" + "sys.stdin.readline()\n" + "sys.stdout.write('def')\n" + "sys.stdout.flush()\n" + "sys.stdin.readline()\n" + "sys.stdout.write('ghi\\n')\n" + "sys.stdout.flush()\n" + "sys.stdin.readline()\n" + "sys.stdout.write('second line\\n')\n"); + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.mPy = LLProcess::create(py.mParams); + ensure("couldn't launch ReadPipe listener script", py.mPy); + std::ostream& childin(py.mPy->getWritePipe(LLProcess::STDIN).get_ostream()); + LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); + // listen for incoming data on childout + EventListener listener(childout.getPump()); + // also listen with a function that prompts the child to continue + // every time we see output + LLTempBoundListener connection( + childout.getPump().listen("ack", boost::bind(ack, boost::ref(childin), _1))); + int i, timeout = 60; + // wait through stuttering first line + for (i = 0; i < timeout && py.mPy->isRunning() && ! childout.contains("\n"); ++i) + { + yield(); + } + ensure("couldn't get first line", i < timeout); + // disconnect from listener + listener.mConnection.disconnect(); + // finish out the run + for (i = 0; i < timeout && py.mPy->isRunning(); ++i) + { + yield(); + } + ensure("child took too long to terminate", i < timeout); + // now verify history + std::list::const_iterator li(listener.mHistory.begin()), + lend(listener.mHistory.end()); + ensure("no events", li != lend); + ensure_equals("history[0]", (*li)["data"].asString(), "abc"); + ensure_equals("history[0] len", (*li)["len"].asInteger(), 3); + ++li; + ensure("only 1 event", li != lend); + ensure_equals("history[1]", (*li)["data"].asString(), "abcdef"); + ensure_equals("history[0] len", (*li)["len"].asInteger(), 6); + ++li; + ensure("only 2 events", li != lend); + ensure_equals("history[2]", (*li)["data"].asString(), "abcdefghi" EOL); + ensure_equals("history[0] len", (*li)["len"].asInteger(), 9 + sizeof(EOL) - 1); + ++li; + // We DO NOT expect a whole new event for the second line because we + // disconnected. + ensure("more than 3 events", li == lend); + } + // TODO: - // test listening on getReadPipe().getPump(), disconnecting // test setLimit(), getLimit() // test EOF -- check logging // test peek() with substr -- cgit v1.2.3 From e92c3113545dd60fb76e115da201163e340c730c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 16 Feb 2012 16:05:04 -0500 Subject: Add LLProcess::ReadPipe::find() methods, with corresponding npos. If it's useful to have contains() to tell you whether incoming data contains a particular substring, and if it's useful for contains() and peek() to accept an offset within that data, then it's useful to allow you to get the offset of a desired substring within that data. But of course a find() returning offset needs something like std::string::npos for "not found"; borrow that convention. Support both find(const std::string&) and find(char); the latter permits a more efficient implementation. In fact, make find(string) recognize a string of length 1 and leverage the find(char) implementation. Given that, reimplement contains(mumble) as shorthand for find(mumble) != npos. Implement find() overloads using std::search() and std::find() on boost::asio::streambuf character iterators, rather than copying to std::string and then using string search like previous contains() implementation. Reimplement WritePipeImpl::tick() and ReadPipeImpl::tick() to write/read directly from/to boost::asio::streambuf data, instead of copying to/from a temporary flat buffer. As long as ReadPipeImpl::tick() keeps successfully filling buffers, keep reading. Previous implementation would only handle a long child write over successive tick() calls. Stop on read error or when we come up short. --- indra/llcommon/llprocess.cpp | 259 ++++++++++++++++++++++---------- indra/llcommon/llprocess.h | 37 ++++- indra/llcommon/tests/llprocess_test.cpp | 2 + 3 files changed, 210 insertions(+), 88 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 1481bf571f..aa22b3f805 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -120,9 +120,12 @@ private: static LLProcessListener sProcessListener; LLProcess::BasePipe::~BasePipe() {} +const LLProcess::BasePipe::size_type + LLProcess::BasePipe::npos((std::numeric_limits::max)()); class WritePipeImpl: public LLProcess::WritePipe { + LOG_CLASS(WritePipeImpl); public: WritePipeImpl(const std::string& desc, apr_file_t* pipe): mDesc(desc), @@ -139,30 +142,53 @@ public: bool tick(const LLSD&) { + typedef boost::asio::streambuf::const_buffers_type const_buffer_sequence; // If there's anything to send, try to send it. - if (mStreambuf.size()) + std::size_t total(mStreambuf.size()), consumed(0); + if (total) { - // Copy data out from mStreambuf to a flat, contiguous buffer to - // write -- but only up to a certain size. - std::size_t total(mStreambuf.size()); - std::size_t bufsize((std::min)(std::size_t(4096), total)); - boost::asio::streambuf::const_buffers_type bufs = mStreambuf.data(); - std::vector buffer( - boost::asio::buffers_begin(bufs), - boost::asio::buffers_begin(bufs) + bufsize); - apr_size_t written(bufsize); - ll_apr_warn_status(apr_file_write(mPipe, &buffer[0], &written)); - // 'written' is modified to reflect the number of bytes actually - // written. Since they've been sent, remove them from the + const_buffer_sequence bufs = mStreambuf.data(); + // In general, our streambuf might contain a number of different + // physical buffers; iterate over those. + for (const_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); + bufi != bufend; ++bufi) + { + // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents + std::size_t towrite(boost::asio::buffer_size(*bufi)); + apr_size_t written(towrite); + apr_status_t err = apr_file_write(mPipe, + boost::asio::buffer_cast(*bufi), + &written); + // EAGAIN is exactly what we want from a nonblocking pipe. + // Rather than waiting for data, it should return immediately. + if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) + { + LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc + << " got " << err << ":" << LL_ENDL; + ll_apr_warn_status(err); + } + + // 'written' is modified to reflect the number of bytes actually + // written. Make sure we consume those later. (Don't consume them + // now, that would invalidate the buffer iterator sequence!) + consumed += written; + LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite + << " bytes to " << mDesc + << " (original " << total << ")" << LL_ENDL; + + // The parent end of this pipe is nonblocking. If we weren't able + // to write everything we wanted, don't keep banging on it -- that + // won't change until the child reads some. Wait for next tick(). + if (written < towrite) + break; + } + // In all, we managed to write 'consumed' bytes. Remove them from the // streambuf so we don't keep trying to send them. This could be - // anywhere from 0 up to mStreambuf.size(); anything we haven't - // yet sent, we'll try again next tick() call. - mStreambuf.consume(written); - LL_DEBUGS("LLProcess") << "wrote " << written << " of " << bufsize - << " bytes to " << mDesc - << " (original " << total << "), " - << mStreambuf.size() << " remaining" << LL_ENDL; + // anywhere from 0 up to mStreambuf.size(); anything we haven't yet + // sent, we'll try again later. + mStreambuf.consume(consumed); } + return false; } @@ -176,6 +202,7 @@ private: class ReadPipeImpl: public LLProcess::ReadPipe { + LOG_CLASS(ReadPipeImpl); public: ReadPipeImpl(const std::string& desc, apr_file_t* pipe): mDesc(desc), @@ -184,7 +211,7 @@ public: mStream(&mStreambuf), mPump("ReadPipe"), // use funky syntax to call max() to avoid blighted max() macros - mLimit((std::numeric_limits::max)()) + mLimit(npos) { mConnection = LLEventPumps::instance().obtain("mainloop") .listen(LLEventPump::inventName("ReadPipe"), @@ -195,79 +222,149 @@ public: // methods with implementation data concealed from the base class. virtual std::istream& get_istream() { return mStream; } virtual LLEventPump& getPump() { return mPump; } - virtual void setLimit(size_t limit) { mLimit = limit; } - virtual size_t getLimit() const { return mLimit; } - virtual std::size_t size() { return mStreambuf.size(); } + virtual void setLimit(size_type limit) { mLimit = limit; } + virtual size_type getLimit() const { return mLimit; } + virtual size_type size() const { return mStreambuf.size(); } - virtual std::string peek(std::size_t offset=0, - std::size_t len=(std::numeric_limits::max)()) + virtual std::string peek(size_type offset=0, size_type len=npos) const { // Constrain caller's offset and len to overlap actual buffer content. - std::size_t real_offset = (std::min)(mStreambuf.size(), offset); - std::size_t real_end = (std::min)(mStreambuf.size(), real_offset + len); + std::size_t real_offset = (std::min)(mStreambuf.size(), std::size_t(offset)); + std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(real_offset + len)); boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); return std::string(boost::asio::buffers_begin(cbufs) + real_offset, boost::asio::buffers_begin(cbufs) + real_end); } - virtual bool contains(const std::string& seek, std::size_t offset=0) + virtual size_type find(const std::string& seek, size_type offset=0) const { - // There may be a more efficient way to search mStreambuf contents, - // but this is far the easiest... - return peek(offset).find(seek) != std::string::npos; + // If we're passing a string of length 1, use find(char), which can + // use an O(n) std::find() rather than the O(n^2) std::search(). + if (seek.length() == 1) + { + return find(seek[0], offset); + } + + // If offset is beyond the whole buffer, can't even construct a valid + // iterator range; can't possibly find the string we seek. + if (offset > mStreambuf.size()) + { + return npos; + } + + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + boost::asio::buffers_iterator + begin(boost::asio::buffers_begin(cbufs)), + end (boost::asio::buffers_end(cbufs)), + found(std::search(begin + offset, end, seek.begin(), seek.end())); + return (found == end)? npos : (found - begin); } -private: - bool tick(const LLSD&) + virtual size_type find(char seek, size_type offset=0) const { - // Allocate a buffer and try, every time, to read into it. - std::vector buffer(4096); - apr_size_t gotten(buffer.size()); - apr_status_t err = apr_file_read(mPipe, &buffer[0], &gotten); - if (err == APR_EOF) + // If offset is beyond the whole buffer, can't even construct a valid + // iterator range; can't possibly find the char we seek. + if (offset > mStreambuf.size()) { - // Handle EOF specially: it's part of normal-case processing. - LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL; - // We won't need any more tick() calls. - mConnection.disconnect(); + return npos; } - else if (! ll_apr_warn_status(err)) // validate anything but EOF + + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + boost::asio::buffers_iterator + begin(boost::asio::buffers_begin(cbufs)), + end (boost::asio::buffers_end(cbufs)), + found(std::find(begin + offset, end, seek)); + return (found == end)? npos : (found - begin); + } + +private: + bool tick(const LLSD&) + { + typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence; + // Try, every time, to read into our streambuf. In fact, we have no + // idea how much data the child might be trying to send: keep trying + // until we're convinced we've temporarily exhausted the pipe. + bool exhausted = false; + std::size_t committed(0); + do { - // 'gotten' was modified to reflect the number of bytes actually - // received. If nonzero, add them to the streambuf and notify - // interested parties. - if (gotten) + // attempt to read an arbitrary size + mutable_buffer_sequence bufs = mStreambuf.prepare(4096); + // In general, the mutable_buffer_sequence returned by prepare() might + // contain a number of different physical buffers; iterate over those. + std::size_t tocommit(0); + for (mutable_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); + bufi != bufend; ++bufi) { - boost::asio::streambuf::mutable_buffers_type mbufs = mStreambuf.prepare(gotten); - std::copy(buffer.begin(), buffer.begin() + gotten, - boost::asio::buffers_begin(mbufs)); - // Don't forget to "commit" the data! The sequence (prepare(), - // commit()) is obviously intended to allow us to allocate - // buffer space, then read directly into some portion of it, - // then commit only as much as we managed to obtain. But the - // only official (documented) way I can find to populate a - // mutable_buffers_type is to use buffers_begin(). It Would Be - // Nice if we were permitted to directly read into - // mutable_buffers_type (not to mention writing directly from - // const_buffers_type in WritePipeImpl; APR even supports an - // apr_file_writev() function for writing from discontiguous - // buffers) -- but as of 2012-02-14, this copying appears to - // be the safest tactic. - mStreambuf.commit(gotten); - LL_DEBUGS("LLProcess") << "read " << gotten << " of " << buffer.size() - << " bytes from " << mDesc << ", new total " - << mStreambuf.size() << LL_ENDL; - - // Now that we've received new data, publish it on our - // LLEventPump as advertised. Constrain it by mLimit. But show - // listener the actual accumulated buffer size, regardless of - // mLimit. - std::size_t datasize((std::min)(mLimit, mStreambuf.size())); - mPump.post(LLSDMap - ("data", peek(0, datasize)) - ("len", LLSD::Integer(mStreambuf.size()))); + // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents + std::size_t toread(boost::asio::buffer_size(*bufi)); + apr_size_t gotten(toread); + apr_status_t err = apr_file_read(mPipe, + boost::asio::buffer_cast(*bufi), + &gotten); + // EAGAIN is exactly what we want from a nonblocking pipe. + // Rather than waiting for data, it should return immediately. + if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) + { + // Handle EOF specially: it's part of normal-case processing. + if (err == APR_EOF) + { + LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL; + } + else + { + LL_WARNS("LLProcess") << "apr_file_read(" << toread << ") on " << mDesc + << " got " << err << ":" << LL_ENDL; + ll_apr_warn_status(err); + } + // Either way, though, we won't need any more tick() calls. + mConnection.disconnect(); + exhausted = true; // also break outer retry loop + break; + } + + // 'gotten' was modified to reflect the number of bytes actually + // received. Make sure we commit those later. (Don't commit them + // now, that would invalidate the buffer iterator sequence!) + tocommit += gotten; + LL_DEBUGS("LLProcess") << "read " << gotten << " of " << toread + << " bytes from " << mDesc << LL_ENDL; + + // The parent end of this pipe is nonblocking. If we weren't even + // able to fill this buffer, don't loop to try to fill the next -- + // that won't change until the child writes more. Wait for next + // tick(). + if (gotten < toread) + { + // break outer retry loop too + exhausted = true; + break; + } } + + // Don't forget to "commit" the data! + mStreambuf.commit(tocommit); + committed += tocommit; + + // 'exhausted' is set when we can't fill any one buffer of the + // mutable_buffer_sequence established by the current prepare() + // call -- whether due to error or not enough bytes. That is, + // 'exhausted' is still false when we've filled every physical + // buffer in the mutable_buffer_sequence. In that case, for all we + // know, the child might have still more data pending -- go for it! + } while (! exhausted); + + if (committed) + { + // If we actually received new data, publish it on our LLEventPump + // as advertised. Constrain it by mLimit. But show listener the + // actual accumulated buffer size, regardless of mLimit. + size_type datasize((std::min)(mLimit, size_type(mStreambuf.size()))); + mPump.post(LLSDMap + ("data", peek(0, datasize)) + ("len", LLSD::Integer(mStreambuf.size()))); } + return false; } @@ -277,7 +374,7 @@ private: boost::asio::streambuf mStreambuf; std::istream mStream; LLEventStream mPump; - size_t mLimit; + size_type mLimit; }; /// Need an exception to avoid constructing an invalid LLProcess object, but @@ -472,16 +569,18 @@ LLProcess::LLProcess(const LLSDOrParams& params): { if (select[i] != APR_CHILD_BLOCK) continue; + std::string desc(STRINGIZE(mDesc << ' ' << whichfile[i])); + apr_file_t* pipe(mProcess.*(members[i])); if (i == STDIN) { - mPipes.replace(i, new WritePipeImpl(whichfile[i], mProcess.*(members[i]))); + mPipes.replace(i, new WritePipeImpl(desc, pipe)); } else { - mPipes.replace(i, new ReadPipeImpl(whichfile[i], mProcess.*(members[i]))); + mPipes.replace(i, new ReadPipeImpl(desc, pipe)); } LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name() - << "('" << whichfile[i] << "')" << LL_ENDL; + << "('" << desc << "')" << LL_ENDL; } } diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index bf0517600d..2c6951b562 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -295,6 +295,9 @@ public: { public: virtual ~BasePipe() = 0; + + typedef std::size_t size_type; + static const size_type npos; }; /// As returned by getWritePipe() or getOptWritePipe() @@ -338,7 +341,7 @@ public: * the child, but the child happens to flush "12" before emitting * "3\n", get_istream() >> myint could return 12 rather than 123! */ - virtual std::size_t size() = 0; + virtual size_type size() const = 0; /** * Peek at accumulated buffer data without consuming it. Optional @@ -346,14 +349,32 @@ public: * * @note You can discard buffer data using get_istream().ignore(n). */ - virtual std::string peek(std::size_t offset=0, - std::size_t len=(std::numeric_limits::max)()) = 0; + virtual std::string peek(size_type offset=0, size_type len=npos) const = 0; + + /** + * Detect presence of a substring (or char) in accumulated buffer data + * without retrieving it. Optional offset allows you to search from + * specified position. + */ + template + bool contains(SEEK seek, size_type offset=0) const + { return find(seek, offset) != npos; } + + /** + * Search for a substring in accumulated buffer data without + * retrieving it. Returns size_type position at which found, or npos + * meaning not found. Optional offset allows you to search from + * specified position. + */ + virtual size_type find(const std::string& seek, size_type offset=0) const = 0; /** - * Search accumulated buffer data without retrieving it. Optional - * offset allows you to start at specified position. + * Search for a char in accumulated buffer data without retrieving it. + * Returns size_type position at which found, or npos meaning not + * found. Optional offset allows you to search from specified + * position. */ - virtual bool contains(const std::string& seek, std::size_t offset=0) = 0; + virtual size_type find(char seek, size_type offset=0) const = 0; /** * Get LLEventPump& on which to listen for incoming data. The posted @@ -377,12 +398,12 @@ public: * the data posted with the LLSD event. If you don't call this method, * all pending data will be posted. */ - virtual void setLimit(size_t limit) = 0; + virtual void setLimit(size_type limit) = 0; /** * Query the current setLimit() limit. */ - virtual size_t getLimit() const = 0; + virtual size_type getLimit() const = 0; }; /// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 31bc833a1d..d7bda34923 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -1131,5 +1131,7 @@ namespace tut // test setLimit(), getLimit() // test EOF -- check logging // test peek() with substr + // test contains(char) + // test find(string, offset), find(char, offset), offset <, =, > size() } // namespace tut -- cgit v1.2.3 From 4ecf9d6a2d981ef27c7b5bddc4807c67d12d5984 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 16 Feb 2012 16:40:14 -0500 Subject: Add unit test for LLProcess::ReadPipe::setLimit(). --- indra/llcommon/tests/llprocess_test.cpp | 62 +++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index d7bda34923..29233d4415 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -140,11 +140,17 @@ struct PythonProcessLauncher mParams.args.add(mScript.getName()); } - /// Run Python script and wait for it to complete. - void run() + /// Launch Python script; verify that it launched + void launch() { mPy = LLProcess::create(mParams); tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), mPy); + } + + /// Run Python script and wait for it to complete. + void run() + { + launch(); // 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 @@ -718,8 +724,7 @@ namespace tut " f.write('bad')\n"); NamedTempFile out("out", "not started"); py.mParams.args.add(out.getName()); - py.mPy = LLProcess::create(py.mParams); - ensure("couldn't launch kill() script", py.mPy); + py.launch(); // Wait for the script to wake up and do its first write int i = 0, timeout = 60; for ( ; i < timeout; ++i) @@ -765,8 +770,7 @@ namespace tut "with open(sys.argv[1], 'w') as f:\n" " f.write('bad')\n"); py.mParams.args.add(out.getName()); - py.mPy = LLProcess::create(py.mParams); - ensure("couldn't launch kill() script", py.mPy); + py.launch(); // Capture handle for later phandle = py.mPy->getProcessHandle(); // Wait for the script to wake up and do its first write @@ -820,8 +824,7 @@ namespace tut 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); + py.launch(); // Capture handle for later phandle = py.mPy->getProcessHandle(); // Wait for the script to wake up and do its first write @@ -1017,8 +1020,7 @@ namespace tut "print 'ack'\n"); py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout - py.mPy = LLProcess::create(py.mParams); - ensure("couldn't launch stdin/stdout script", py.mPy); + py.launch(); LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); int i, timeout = 60; for (i = 0; i < timeout && py.mPy->isRunning() && childout.size() < 3; ++i) @@ -1082,8 +1084,7 @@ namespace tut "sys.stdout.write('second line\\n')\n"); py.mParams.files.add(LLProcess::FileParam("pipe")); // stdin py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout - py.mPy = LLProcess::create(py.mParams); - ensure("couldn't launch ReadPipe listener script", py.mPy); + py.launch(); std::ostream& childin(py.mPy->getWritePipe(LLProcess::STDIN).get_ostream()); LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); // listen for incoming data on childout @@ -1102,11 +1103,7 @@ namespace tut // disconnect from listener listener.mConnection.disconnect(); // finish out the run - for (i = 0; i < timeout && py.mPy->isRunning(); ++i) - { - yield(); - } - ensure("child took too long to terminate", i < timeout); + waitfor(*py.mPy); // now verify history std::list::const_iterator li(listener.mHistory.begin()), lend(listener.mHistory.end()); @@ -1127,8 +1124,37 @@ namespace tut ensure("more than 3 events", li == lend); } + template<> template<> + void object::test<18>() + { + set_test_name("setLimit()"); + PythonProcessLauncher py("setLimit()", + "import sys\n" + "print sys.argv[1]\n"); + std::string abc("abcdefghijklmnopqrstuvwxyz"); + py.mParams.args.add(abc); + py.mParams.files.add(LLProcess::FileParam()); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.launch(); + LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); + // listen for incoming data on childout + EventListener listener(childout.getPump()); + // but set limit + childout.setLimit(10); + ensure_equals("getLimit() after setlimit(10)", childout.getLimit(), 10); + // okay, pump I/O to pick up output from child + waitfor(*py.mPy); + ensure("no events", ! listener.mHistory.empty()); + // For all we know, that data could have arrived in several different + // bursts... probably not, but anyway, only check the last one. + ensure_equals("event[\"len\"]", + listener.mHistory.back()["len"].asInteger(), + abc.length() + sizeof(EOL) - 1); + ensure_equals("length of setLimit(10) data", + listener.mHistory.back()["data"].asString().length(), 10); + } + // TODO: - // test setLimit(), getLimit() // test EOF -- check logging // test peek() with substr // test contains(char) -- cgit v1.2.3 From a06ba836c76ea8b35aeca9d09bd7d3b043a4c962 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 16 Feb 2012 17:35:34 -0500 Subject: Fix bug in LLProcess::ReadPipe::peek() substring computation. Add unit tests for peek() with substring args, reimplemented contains(), various forms of find(). (yay unit tests) --- indra/llcommon/llprocess.cpp | 3 +- indra/llcommon/tests/llprocess_test.cpp | 61 +++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index aa22b3f805..add1649ba5 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -230,7 +230,8 @@ public: { // Constrain caller's offset and len to overlap actual buffer content. std::size_t real_offset = (std::min)(mStreambuf.size(), std::size_t(offset)); - std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(real_offset + len)); + size_type want_end = (len == npos)? npos : (real_offset + len); + std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(want_end)); boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); return std::string(boost::asio::buffers_begin(cbufs) + real_offset, boost::asio::buffers_begin(cbufs) + real_end); diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 29233d4415..e5d873c8ee 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -1130,7 +1130,7 @@ namespace tut set_test_name("setLimit()"); PythonProcessLauncher py("setLimit()", "import sys\n" - "print sys.argv[1]\n"); + "sys.stdout.write(sys.argv[1])\n"); std::string abc("abcdefghijklmnopqrstuvwxyz"); py.mParams.args.add(abc); py.mParams.files.add(LLProcess::FileParam()); // stdin @@ -1148,16 +1148,65 @@ namespace tut // For all we know, that data could have arrived in several different // bursts... probably not, but anyway, only check the last one. ensure_equals("event[\"len\"]", - listener.mHistory.back()["len"].asInteger(), - abc.length() + sizeof(EOL) - 1); + listener.mHistory.back()["len"].asInteger(), abc.length()); ensure_equals("length of setLimit(10) data", listener.mHistory.back()["data"].asString().length(), 10); } + template<> template<> + void object::test<19>() + { + set_test_name("peek() ReadPipe data"); + PythonProcessLauncher py("peek() ReadPipe", + "import sys\n" + "sys.stdout.write(sys.argv[1])\n"); + std::string abc("abcdefghijklmnopqrstuvwxyz"); + py.mParams.args.add(abc); + py.mParams.files.add(LLProcess::FileParam()); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.launch(); + LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); + // okay, pump I/O to pick up output from child + waitfor(*py.mPy); + // peek() with substr args + ensure_equals("peek()", childout.peek(), abc); + ensure_equals("peek(23)", childout.peek(23), abc.substr(23)); + ensure_equals("peek(5, 3)", childout.peek(5, 3), abc.substr(5, 3)); + ensure_equals("peek(27, 2)", childout.peek(27, 2), ""); + ensure_equals("peek(23, 5)", childout.peek(23, 5), "xyz"); + // contains() -- we don't exercise as thoroughly as find() because the + // contains() implementation is trivially (and visibly) based on find() + ensure("contains(\":\")", ! childout.contains(":")); + ensure("contains(':')", ! childout.contains(':')); + ensure("contains(\"d\")", childout.contains("d")); + ensure("contains('d')", childout.contains("d")); + ensure("contains(\"klm\")", childout.contains("klm")); + ensure("contains(\"klx\")", ! childout.contains("klx")); + // find() + ensure("find(\":\")", childout.find(":") == LLProcess::ReadPipe::npos); + ensure("find(':')", childout.find(':') == LLProcess::ReadPipe::npos); + ensure_equals("find(\"d\")", childout.find("d"), 3); + ensure_equals("find('d')", childout.find("d"), 3); + ensure_equals("find(\"d\", 3)", childout.find("d", 3), 3); + ensure_equals("find('d', 3)", childout.find("d", 3), 3); + ensure("find(\"d\", 4)", childout.find("d", 4) == LLProcess::ReadPipe::npos); + ensure("find('d', 4)", childout.find('d', 4) == LLProcess::ReadPipe::npos); + // The case of offset == end and offset > end are different. In the + // first case, we can form a valid (albeit empty) iterator range and + // search that. In the second, guard logic in the implementation must + // realize we can't form a valid iterator range. + ensure("find(\"d\", 26)", childout.find("d", 26) == LLProcess::ReadPipe::npos); + ensure("find('d', 26)", childout.find('d', 26) == LLProcess::ReadPipe::npos); + ensure("find(\"d\", 27)", childout.find("d", 27) == LLProcess::ReadPipe::npos); + ensure("find('d', 27)", childout.find('d', 27) == LLProcess::ReadPipe::npos); + ensure_equals("find(\"ghi\")", childout.find("ghi"), 6); + ensure_equals("find(\"ghi\", 6)", childout.find("ghi"), 6); + ensure("find(\"ghi\", 7)", childout.find("ghi", 7) == LLProcess::ReadPipe::npos); + ensure("find(\"ghi\", 26)", childout.find("ghi", 26) == LLProcess::ReadPipe::npos); + ensure("find(\"ghi\", 27)", childout.find("ghi", 27) == LLProcess::ReadPipe::npos); + } + // TODO: // test EOF -- check logging - // test peek() with substr - // test contains(char) - // test find(string, offset), find(char, offset), offset <, =, > size() } // namespace tut -- cgit v1.2.3 From d6ed77a598a0009d386ce3cd6c49d0f1c4b422e8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 16 Feb 2012 17:39:56 -0500 Subject: Attempt to fix Windows link error for LLProcess::BasePipe::npos. --- indra/llcommon/llprocess.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 2c6951b562..06be0954c0 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -291,7 +291,7 @@ public: std::string getPipeName(FILESLOT); /// base of ReadPipe, WritePipe - class BasePipe + class LL_COMMON_API BasePipe { public: virtual ~BasePipe() = 0; -- cgit v1.2.3 From f52cf4be7003f18813da31b25d204f10eb36db17 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 16 Feb 2012 21:10:06 -0500 Subject: Fix typos in a few LLProcess::ReadPipe::find() unit tests. The typos didn't make for invalid tests, but they made a few tests redundant while leaving other (subtly different) cases untested. --- indra/llcommon/tests/llprocess_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index e5d873c8ee..c67605cc0b 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -1179,16 +1179,16 @@ namespace tut ensure("contains(\":\")", ! childout.contains(":")); ensure("contains(':')", ! childout.contains(':')); ensure("contains(\"d\")", childout.contains("d")); - ensure("contains('d')", childout.contains("d")); + ensure("contains('d')", childout.contains('d')); ensure("contains(\"klm\")", childout.contains("klm")); ensure("contains(\"klx\")", ! childout.contains("klx")); // find() ensure("find(\":\")", childout.find(":") == LLProcess::ReadPipe::npos); ensure("find(':')", childout.find(':') == LLProcess::ReadPipe::npos); ensure_equals("find(\"d\")", childout.find("d"), 3); - ensure_equals("find('d')", childout.find("d"), 3); + ensure_equals("find('d')", childout.find('d'), 3); ensure_equals("find(\"d\", 3)", childout.find("d", 3), 3); - ensure_equals("find('d', 3)", childout.find("d", 3), 3); + ensure_equals("find('d', 3)", childout.find('d', 3), 3); ensure("find(\"d\", 4)", childout.find("d", 4) == LLProcess::ReadPipe::npos); ensure("find('d', 4)", childout.find('d', 4) == LLProcess::ReadPipe::npos); // The case of offset == end and offset > end are different. In the -- cgit v1.2.3 From e98438bda70f92c1caa0621b7e467b72c7e484ad Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 18 Feb 2012 12:00:13 -0500 Subject: Fix subtle bug in ReadPipeImpl: wouldn't tolerate multiple instances. That is, trying to instantiate a ReadPipeImpl while another already existed would throw an LLEventPump::DupPumpName exception. Fortunately this behavior is easily bypassed. --- indra/llcommon/llprocess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index add1649ba5..d6a5a18565 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -209,7 +209,7 @@ public: mPipe(pipe), // Essential to initialize our std::istream with our special streambuf! mStream(&mStreambuf), - mPump("ReadPipe"), + mPump("ReadPipe", true), // tweak name as needed to avoid collisions // use funky syntax to call max() to avoid blighted max() macros mLimit(npos) { -- cgit v1.2.3 From 8b5d5f9652499103b966524e1c0ceef869e29eeb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 20 Feb 2012 12:40:38 -0500 Subject: Make LLProcess post termination event to specified pump if desired. This way a caller need not spin on isRunning(); we can just listen for the requested termination event. Post a similar event containing error message if for any reason LLProcess::create() failed to launch the child. Add unit tests for both cases. --- indra/llcommon/llprocess.cpp | 118 ++++++++++++++++++++------------ indra/llcommon/llprocess.h | 18 ++++- indra/llcommon/tests/llprocess_test.cpp | 59 ++++++++++++++++ 3 files changed, 151 insertions(+), 44 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index d6a5a18565..9799ed1938 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -125,7 +125,7 @@ const LLProcess::BasePipe::size_type class WritePipeImpl: public LLProcess::WritePipe { - LOG_CLASS(WritePipeImpl); + LOG_CLASS(WritePipeImpl); public: WritePipeImpl(const std::string& desc, apr_file_t* pipe): mDesc(desc), @@ -202,7 +202,7 @@ private: class ReadPipeImpl: public LLProcess::ReadPipe { - LOG_CLASS(ReadPipeImpl); + LOG_CLASS(ReadPipeImpl); public: ReadPipeImpl(const std::string& desc, apr_file_t* pipe): mDesc(desc), @@ -394,6 +394,23 @@ LLProcessPtr LLProcess::create(const LLSDOrParams& params) catch (const LLProcessError& e) { LL_WARNS("LLProcess") << e.what() << LL_ENDL; + + // If caller is requesting an event on process termination, send one + // indicating bad launch. This may prevent someone waiting forever for + // a termination post that can't arrive because the child never + // started. + if (! std::string(params.postend).empty()) + { + LLEventPumps::instance().obtain(params.postend) + .post(LLSDMap + // no "id" + ("desc", std::string(params.executable)) + ("state", LLProcess::UNSTARTED) + // no "data" + ("string", e.what()) + ); + } + return LLProcessPtr(); } } @@ -425,6 +442,8 @@ LLProcess::LLProcess(const LLSDOrParams& params): << LLSDNotationStreamer(params))); } + mPostend = params.postend; + apr_procattr_t *procattr = NULL; chkapr(apr_procattr_create(&procattr, gAPRPoolp)); @@ -744,6 +763,19 @@ void LLProcess::handle_status(int reason, int status) // hand. mStatus = interpret_status(status); LL_INFOS("LLProcess") << getStatusString() << LL_ENDL; + + // If caller requested notification on child termination, send it. + if (! mPostend.empty()) + { + LLEventPumps::instance().obtain(mPostend) + .post(LLSDMap + ("id", getProcessID()) + ("desc", mDesc) + ("state", mStatus.mState) + ("data", mStatus.mData) + ("string", getStatusString()) + ); + } } LLProcess::id LLProcess::getProcessID() const @@ -769,72 +801,72 @@ std::string LLProcess::getPipeName(FILESLOT) template PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot) { - if (slot >= NSLOTS) - { - error = STRINGIZE(mDesc << " has no slot " << slot); - return NULL; - } - if (mPipes.is_null(slot)) - { - error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a monitored pipe"); - return NULL; - } - // Make sure we dynamic_cast in pointer domain so we can test, rather than - // accepting runtime's exception. - PIPETYPE* ppipe = dynamic_cast(&mPipes[slot]); - if (! ppipe) - { - error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a " << typeid(PIPETYPE).name()); - return NULL; - } - - error.clear(); - return ppipe; + if (slot >= NSLOTS) + { + error = STRINGIZE(mDesc << " has no slot " << slot); + return NULL; + } + if (mPipes.is_null(slot)) + { + error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a monitored pipe"); + return NULL; + } + // Make sure we dynamic_cast in pointer domain so we can test, rather than + // accepting runtime's exception. + PIPETYPE* ppipe = dynamic_cast(&mPipes[slot]); + if (! ppipe) + { + error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a " << typeid(PIPETYPE).name()); + return NULL; + } + + error.clear(); + return ppipe; } template PIPETYPE& LLProcess::getPipe(FILESLOT slot) { - std::string error; - PIPETYPE* wp = getPipePtr(error, slot); - if (! wp) - { - throw NoPipe(error); - } - return *wp; + std::string error; + PIPETYPE* wp = getPipePtr(error, slot); + if (! wp) + { + throw NoPipe(error); + } + return *wp; } template boost::optional LLProcess::getOptPipe(FILESLOT slot) { - std::string error; - PIPETYPE* wp = getPipePtr(error, slot); - if (! wp) - { - LL_DEBUGS("LLProcess") << error << LL_ENDL; - return boost::optional(); - } - return *wp; + std::string error; + PIPETYPE* wp = getPipePtr(error, slot); + if (! wp) + { + LL_DEBUGS("LLProcess") << error << LL_ENDL; + return boost::optional(); + } + return *wp; } LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot) { - return getPipe(slot); + return getPipe(slot); } boost::optional LLProcess::getOptWritePipe(FILESLOT slot) { - return getOptPipe(slot); + return getOptPipe(slot); } LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot) { - return getPipe(slot); + return getPipe(slot); } boost::optional LLProcess::getOptReadPipe(FILESLOT slot) { - return getOptPipe(slot); + return getOptPipe(slot); } std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) @@ -932,7 +964,7 @@ static std::string WindowsErrorString(const std::string& operation) NULL) != 0) { - // convert from wide-char string to multi-byte string + // convert from wide-char string to multi-byte string char message[256]; wcstombs(message, error_str, sizeof(message)); message[sizeof(message)-1] = 0; diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 06be0954c0..96a3dce5b3 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -158,7 +158,8 @@ public: args("args"), cwd("cwd"), autokill("autokill", true), - files("files") + files("files"), + postend("postend") {} /// pathname of executable @@ -184,6 +185,20 @@ public: * underlying implementation library doesn't support that. */ Multiple files; + /** + * On child-process termination, if this LLProcess object still + * exists, post LLSD event to LLEventPump with specified name (default + * no event). Event contains at least: + * + * - "id" as obtained from getProcessID() + * - "desc" short string description of child (executable + pid) + * - "state" @c state enum value, from Status.mState + * - "data" if "state" is EXITED, exit code; if KILLED, on Posix, + * signal number + * - "string" English text describing "state" and "data" (e.g. "exited + * with code 0") + */ + Optional postend; }; typedef LLSDParamAdapter LLSDOrParams; @@ -462,6 +477,7 @@ private: PIPETYPE* getPipePtr(std::string& error, FILESLOT slot); std::string mDesc; + std::string mPostend; apr_proc_t mProcess; bool mAutokill; Status mStatus; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index c67605cc0b..1a755c283c 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -1206,6 +1206,65 @@ namespace tut ensure("find(\"ghi\", 27)", childout.find("ghi", 27) == LLProcess::ReadPipe::npos); } + template<> template<> + void object::test<20>() + { + set_test_name("good postend"); + PythonProcessLauncher py("postend", + "import sys\n" + "sys.exit(35)\n"); + std::string pumpname("postend"); + EventListener listener(LLEventPumps::instance().obtain(pumpname)); + py.mParams.postend = pumpname; + py.launch(); + LLProcess::id childid(py.mPy->getProcessID()); + // Don't use waitfor(), which calls isRunning(); instead wait for an + // event on pumpname. + int i, timeout = 60; + for (i = 0; i < timeout && listener.mHistory.empty(); ++i) + { + yield(); + } + ensure("no postend event", i < timeout); + ensure_equals("number of postend events", listener.mHistory.size(), 1); + LLSD postend(listener.mHistory.front()); + ensure_equals("id", postend["id"].asInteger(), childid); + ensure("desc empty", ! postend["desc"].asString().empty()); + ensure_equals("state", postend["state"].asInteger(), LLProcess::EXITED); + ensure_equals("data", postend["data"].asInteger(), 35); + std::string str(postend["string"]); + ensure_contains("string", str, "exited"); + ensure_contains("string", str, "35"); + } + + template<> template<> + void object::test<21>() + { + set_test_name("bad postend"); + std::string pumpname("postend"); + EventListener listener(LLEventPumps::instance().obtain(pumpname)); + LLProcess::Params params; + params.postend = pumpname; + LLProcessPtr child = LLProcess::create(params); + ensure("shouldn't have launched", ! child); + ensure_equals("number of postend events", listener.mHistory.size(), 1); + LLSD postend(listener.mHistory.front()); + ensure("has id", ! postend.has("id")); + // Ha ha, in this case the implementation normally sets "desc" to + // params.executable. But as the nature of the problem is that + // params.executable is empty, expecting "desc" to be nonempty is a + // bit unreasonable! + //ensure("desc empty", ! postend["desc"].asString().empty()); + ensure_equals("state", postend["state"].asInteger(), LLProcess::UNSTARTED); + ensure("has data", ! postend.has("data")); + std::string error(postend["string"]); + // All we get from canned parameter validation is a bool, so the + // "validation failed" message we ourselves generate can't mention + // "executable" by name. Just check that it's nonempty. + //ensure_contains("error", error, "executable"); + ensure("string", ! error.empty()); + } + // TODO: // test EOF -- check logging -- cgit v1.2.3 From 999484a60896b11df1af9a44e58ccae6fa6ecbed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 20 Feb 2012 14:22:32 -0500 Subject: Let LLProcess consumer specify desired description for logging. If caller runs (e.g.) a Python script, it's not very helpful to a human log reader to keep seeing LLProcess instances logged as /pathname/to/python (pid). If caller is aware, the code can at least use the script name as the desc -- or maybe even a hint as to the script's purpose. If caller doesn't explicitly pass a desc, at least shorten to just the basename of the executable. --- indra/llcommon/llprocess.cpp | 30 +++++++++++++++++++++++++++--- indra/llcommon/llprocess.h | 10 +++++++++- indra/llcommon/tests/llprocess_test.cpp | 8 +++----- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 9799ed1938..b4c6a647d7 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -50,6 +50,7 @@ static const char* whichfile[] = { "stdin", "stdout", "stderr" }; static std::string empty; static LLProcess::Status interpret_status(int status); +static std::string getDesc(const LLProcess::Params& params); /** * Ref-counted "mainloop" listener. As long as there are still outstanding @@ -404,7 +405,7 @@ LLProcessPtr LLProcess::create(const LLSDOrParams& params) LLEventPumps::instance().obtain(params.postend) .post(LLSDMap // no "id" - ("desc", std::string(params.executable)) + ("desc", getDesc(params)) ("state", LLProcess::UNSTARTED) // no "data" ("string", e.what()) @@ -561,8 +562,8 @@ LLProcess::LLProcess(const LLSDOrParams& params): sProcessListener.addPoll(*this); mStatus.mState = RUNNING; - mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcess.pid << ')'); - LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcess.pid << ")" << LL_ENDL; + mDesc = STRINGIZE(getDesc(params) << " (" << mProcess.pid << ')'); + LL_INFOS("LLProcess") << mDesc << ": launched " << params << LL_ENDL; // Unless caller explicitly turned off autokill (child should persist), // take steps to terminate the child. This is all suspenders-and-belt: in @@ -604,6 +605,29 @@ LLProcess::LLProcess(const LLSDOrParams& params): } } +// Helper to obtain a description string, given a Params block +static std::string getDesc(const LLProcess::Params& params) +{ + // If caller specified a description string, by all means use it. + std::string desc(params.desc); + if (! desc.empty()) + return desc; + + // Caller didn't say. Use the executable name -- but use just the filename + // part. On Mac, for instance, full pathnames get cumbersome. + // If there are Linden utility functions to manipulate pathnames, I + // haven't found them -- and for this usage, Boost.Filesystem seems kind + // of heavyweight. + std::string executable(params.executable); + std::string::size_type delim = executable.find_last_of("\\/"); + // If executable contains no pathname delimiters, return the whole thing. + if (delim == std::string::npos) + return executable; + + // Return just the part beyond the last delimiter. + return executable.substr(delim + 1); +} + LLProcess::~LLProcess() { // Only in state RUNNING are we registered for callback. In UNSTARTED we diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 96a3dce5b3..d005847e18 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -159,7 +159,8 @@ public: cwd("cwd"), autokill("autokill", true), files("files"), - postend("postend") + postend("postend"), + desc("desc") {} /// pathname of executable @@ -199,6 +200,13 @@ public: * with code 0") */ Optional postend; + /** + * Description of child process for logging purposes. It need not be + * unique; the logged description string will contain the PID as well. + * If this is omitted, a description will be derived from the + * executable name. + */ + Optional desc; }; typedef LLSDParamAdapter LLSDOrParams; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 1a755c283c..fe599e7892 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -136,6 +136,7 @@ struct PythonProcessLauncher const char* PYTHON(getenv("PYTHON")); tut::ensure("Set $PYTHON to the Python interpreter", PYTHON); + mParams.desc = desc + " script"; mParams.executable = PYTHON; mParams.args.add(mScript.getName()); } @@ -1244,17 +1245,14 @@ namespace tut std::string pumpname("postend"); EventListener listener(LLEventPumps::instance().obtain(pumpname)); LLProcess::Params params; + params.desc = "bad postend"; params.postend = pumpname; LLProcessPtr child = LLProcess::create(params); ensure("shouldn't have launched", ! child); ensure_equals("number of postend events", listener.mHistory.size(), 1); LLSD postend(listener.mHistory.front()); ensure("has id", ! postend.has("id")); - // Ha ha, in this case the implementation normally sets "desc" to - // params.executable. But as the nature of the problem is that - // params.executable is empty, expecting "desc" to be nonempty is a - // bit unreasonable! - //ensure("desc empty", ! postend["desc"].asString().empty()); + ensure_equals("desc", postend["desc"].asString(), std::string(params.desc)); ensure_equals("state", postend["state"].asInteger(), LLProcess::UNSTARTED); ensure("has data", ! postend.has("data")); std::string error(postend["string"]); -- cgit v1.2.3 From 14ddc6474a0ae83db8d034b00138289fb15e41b7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 23 Feb 2012 13:41:26 -0500 Subject: Tighten up LLProcess pipe support, per Richard's code review. Clarify wording in some of the doc comments; be a bit more explicit about some of the parameter fields. Make some query methods 'const'. Change default LLProcess::ReadPipe::getLimit() value to 0: don't post any incoming data with notification event unless caller requests it. But do post pertinent FILESLOT in case caller reuses same listener for both stdout and stderr. Use more idiomatic, readable syntax for accessing LLProcess::Params data. --- indra/llcommon/llprocess.cpp | 119 +++++++++++++++++++------------- indra/llcommon/llprocess.h | 63 +++++++++++------ indra/llcommon/tests/llprocess_test.cpp | 4 +- 3 files changed, 116 insertions(+), 70 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index b4c6a647d7..3b17b819bd 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -47,11 +47,18 @@ #include #include -static const char* whichfile[] = { "stdin", "stdout", "stderr" }; +static const char* whichfile_[] = { "stdin", "stdout", "stderr" }; static std::string empty; static LLProcess::Status interpret_status(int status); static std::string getDesc(const LLProcess::Params& params); +static std::string whichfile(LLProcess::FILESLOT index) +{ + if (index < LL_ARRAY_SIZE(whichfile_)) + return whichfile_[index]; + return STRINGIZE("file slot " << index); +} + /** * Ref-counted "mainloop" listener. As long as there are still outstanding * LLProcess objects, keep listening on "mainloop" so we can keep polling APR @@ -122,6 +129,7 @@ static LLProcessListener sProcessListener; LLProcess::BasePipe::~BasePipe() {} const LLProcess::BasePipe::size_type + // use funky syntax to call max() to avoid blighted max() macros LLProcess::BasePipe::npos((std::numeric_limits::max)()); class WritePipeImpl: public LLProcess::WritePipe @@ -205,14 +213,14 @@ class ReadPipeImpl: public LLProcess::ReadPipe { LOG_CLASS(ReadPipeImpl); public: - ReadPipeImpl(const std::string& desc, apr_file_t* pipe): + ReadPipeImpl(const std::string& desc, apr_file_t* pipe, LLProcess::FILESLOT index): mDesc(desc), mPipe(pipe), + mIndex(index), // Essential to initialize our std::istream with our special streambuf! mStream(&mStreambuf), mPump("ReadPipe", true), // tweak name as needed to avoid collisions - // use funky syntax to call max() to avoid blighted max() macros - mLimit(npos) + mLimit(0) { mConnection = LLEventPumps::instance().obtain("mainloop") .listen(LLEventPump::inventName("ReadPipe"), @@ -364,7 +372,10 @@ private: size_type datasize((std::min)(mLimit, size_type(mStreambuf.size()))); mPump.post(LLSDMap ("data", peek(0, datasize)) - ("len", LLSD::Integer(mStreambuf.size()))); + ("len", LLSD::Integer(mStreambuf.size())) + ("index", LLSD::Integer(mIndex)) + ("name", whichfile(mIndex)) + ("desc", mDesc)); } return false; @@ -372,6 +383,7 @@ private: std::string mDesc; apr_file_t* mPipe; + LLProcess::FILESLOT mIndex; LLTempBoundListener mConnection; boost::asio::streambuf mStreambuf; std::istream mStream; @@ -400,7 +412,7 @@ LLProcessPtr LLProcess::create(const LLSDOrParams& params) // indicating bad launch. This may prevent someone waiting forever for // a termination post that can't arrive because the child never // started. - if (! std::string(params.postend).empty()) + if (params.postend.isProvided()) { LLEventPumps::instance().obtain(params.postend) .post(LLSDMap @@ -458,22 +470,24 @@ LLProcess::LLProcess(const LLSDOrParams& params): // and passing it as both stdout and stderr (apr_procattr_child_out_set(), // apr_procattr_child_err_set()), or accepting a filename, opening it and // passing that apr_file_t (simple <, >, 2> redirect emulation). - std::vector fparams(params.files.begin(), params.files.end()); - // By default, pass APR_NO_PIPE for each slot. - std::vector select(LL_ARRAY_SIZE(whichfile), APR_NO_PIPE); - for (size_t i = 0; i < (std::min)(LL_ARRAY_SIZE(whichfile), fparams.size()); ++i) + std::vector select; + BOOST_FOREACH(const FileParam& fparam, params.files) { - if (std::string(fparams[i].type).empty()) // inherit our file descriptor + // Every iteration, we're going to append an item to 'select'. At the + // top of the loop, its size() is, in effect, an index. Use that to + // pick a string description for messages. + std::string which(whichfile(FILESLOT(select.size()))); + if (fparam.type().empty()) // inherit our file descriptor { - select[i] = APR_NO_PIPE; + select.push_back(APR_NO_PIPE); } - else if (std::string(fparams[i].type) == "pipe") // anonymous pipe + else if (fparam.type() == "pipe") // anonymous pipe { - if (! std::string(fparams[i].name).empty()) + if (! fparam.name().empty()) { - LL_WARNS("LLProcess") << "For " << std::string(params.executable) + LL_WARNS("LLProcess") << "For " << params.executable() << ": internal names for reusing pipes ('" - << std::string(fparams[i].name) << "' for " << whichfile[i] + << fparam.name() << "' for " << which << ") are not yet supported -- creating distinct pipe" << LL_ENDL; } @@ -482,16 +496,21 @@ LLProcess::LLProcess(const LLSDOrParams& params): // makes very little sense to set nonblocking I/O for the child // end of a pipe: only a specially-written child could deal with // that. - select[i] = APR_CHILD_BLOCK; + select.push_back(APR_CHILD_BLOCK); } else { - throw LLProcessError(STRINGIZE("For " << std::string(params.executable) - << ": unsupported FileParam for " << whichfile[i] - << ": type='" << std::string(fparams[i].type) - << "', name='" << std::string(fparams[i].name) << "'")); + throw LLProcessError(STRINGIZE("For " << params.executable() + << ": unsupported FileParam for " << which + << ": type='" << fparam.type() + << "', name='" << fparam.name() << "'")); } } + // By default, pass APR_NO_PIPE for unspecified slots. + while (select.size() < NSLOTS) + { + select.push_back(APR_NO_PIPE); + } chkapr(apr_procattr_io_set(procattr, select[STDIN], select[STDOUT], select[STDERR])); // Thumbs down on implicitly invoking the shell to invoke the child. From @@ -527,24 +546,32 @@ LLProcess::LLProcess(const LLSDOrParams& params): #endif } - // Have to instantiate named std::strings for string params items so their - // c_str() values persist. - std::string cwd(params.cwd); - if (! cwd.empty()) + // In preparation for calling apr_proc_create(), we collect a number of + // const char* pointers obtained from std::string::c_str(). Turns out + // LLInitParam::Block's helpers Optional, Mandatory, Multiple et al. + // guarantee that converting to the wrapped type (std::string in our + // case), e.g. by calling operator(), returns a reference to *the same + // instance* of the wrapped type that's stored in our Block subclass. + // That's important! We know 'params' persists throughout this method + // call; but without that guarantee, we'd have to assume that converting + // one of its members to std::string might return a different (temp) + // instance. Capturing the c_str() from a temporary std::string is Bad Bad + // Bad. But armed with this knowledge, when you see params.cwd().c_str(), + // grit your teeth and smile and carry on. + + if (params.cwd.isProvided()) { - chkapr(apr_procattr_dir_set(procattr, cwd.c_str())); + chkapr(apr_procattr_dir_set(procattr, params.cwd().c_str())); } // create an argv vector for the child process std::vector argv; - // add the executable path - std::string executable(params.executable); - argv.push_back(executable.c_str()); + // Add the executable path. See above remarks about c_str(). + argv.push_back(params.executable().c_str()); - // and any arguments - std::vector args(params.args.begin(), params.args.end()); - BOOST_FOREACH(const std::string& arg, args) + // Add arguments. See above remarks about c_str(). + BOOST_FOREACH(const std::string& arg, params.args) { argv.push_back(arg.c_str()); } @@ -590,7 +617,7 @@ LLProcess::LLProcess(const LLSDOrParams& params): { if (select[i] != APR_CHILD_BLOCK) continue; - std::string desc(STRINGIZE(mDesc << ' ' << whichfile[i])); + std::string desc(STRINGIZE(mDesc << ' ' << whichfile(FILESLOT(i)))); apr_file_t* pipe(mProcess.*(members[i])); if (i == STDIN) { @@ -598,7 +625,7 @@ LLProcess::LLProcess(const LLSDOrParams& params): } else { - mPipes.replace(i, new ReadPipeImpl(desc, pipe)); + mPipes.replace(i, new ReadPipeImpl(desc, pipe, FILESLOT(i))); } LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name() << "('" << desc << "')" << LL_ENDL; @@ -609,9 +636,8 @@ LLProcess::LLProcess(const LLSDOrParams& params): static std::string getDesc(const LLProcess::Params& params) { // If caller specified a description string, by all means use it. - std::string desc(params.desc); - if (! desc.empty()) - return desc; + if (params.desc.isProvided()) + return params.desc; // Caller didn't say. Use the executable name -- but use just the filename // part. On Mac, for instance, full pathnames get cumbersome. @@ -670,22 +696,22 @@ bool LLProcess::kill(const std::string& who) return ! isRunning(); } -bool LLProcess::isRunning(void) +bool LLProcess::isRunning() const { return getStatus().mState == RUNNING; } -LLProcess::Status LLProcess::getStatus() +LLProcess::Status LLProcess::getStatus() const { return mStatus; } -std::string LLProcess::getStatusString() +std::string LLProcess::getStatusString() const { return getStatusString(getStatus()); } -std::string LLProcess::getStatusString(const Status& status) +std::string LLProcess::getStatusString(const Status& status) const { return getStatusString(mDesc, status); } @@ -816,7 +842,7 @@ LLProcess::handle LLProcess::getProcessHandle() const #endif } -std::string LLProcess::getPipeName(FILESLOT) +std::string LLProcess::getPipeName(FILESLOT) const { // LLProcess::FileParam::type "npipe" is not yet implemented return ""; @@ -832,7 +858,7 @@ PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot) } if (mPipes.is_null(slot)) { - error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a monitored pipe"); + error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a monitored pipe"); return NULL; } // Make sure we dynamic_cast in pointer domain so we can test, rather than @@ -840,7 +866,7 @@ PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot) PIPETYPE* ppipe = dynamic_cast(&mPipes[slot]); if (! ppipe) { - error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a " << typeid(PIPETYPE).name()); + error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name()); return NULL; } @@ -895,10 +921,9 @@ boost::optional LLProcess::getOptReadPipe(FILESLOT slot) std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) { - std::string cwd(params.cwd); - if (! cwd.empty()) + if (params.cwd.isProvided()) { - out << "cd " << LLStringUtil::quote(cwd) << ": "; + out << "cd " << LLStringUtil::quote(params.cwd) << ": "; } out << LLStringUtil::quote(params.executable); BOOST_FOREACH(const std::string& arg, params.args) diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index d005847e18..637b7e2f9c 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -58,12 +58,16 @@ typedef boost::shared_ptr LLProcessPtr; * arguments. It also keeps track of whether the process is still running, and * can kill it if required. * + * In discussing LLProcess, we use the term "parent" to refer to this process + * (the process invoking LLProcess), versus "child" to refer to the process + * spawned by LLProcess. + * * LLProcess relies on periodic post() calls on the "mainloop" LLEventPump: an - * LLProcess object's Status won't update until the next "mainloop" tick. The - * viewer's main loop already posts to that LLEventPump once per iteration - * (hence the name). See indra/llcommon/tests/llprocess_test.cpp for an - * example of waiting for child-process termination in a standalone test - * context. + * LLProcess object's Status won't update until the next "mainloop" tick. For + * instance, the Second Life viewer's main loop already posts to an + * LLEventPump by that name once per iteration. See + * indra/llcommon/tests/llprocess_test.cpp for an example of waiting for + * child-process termination in a standalone test context. */ class LL_COMMON_API LLProcess: public boost::noncopyable { @@ -110,10 +114,10 @@ public: * pumping nonblocking I/O every frame. The expectation (at least * for stdout or stderr) is that the caller will listen for * incoming data and consume it as it arrives. It's important not - * to neglect such a pipe, because it's buffered in viewer memory. - * If you suspect the child may produce a great volume of output - * between viewer frames, consider directing the child to write to - * a filesystem file instead, then read the file later. + * to neglect such a pipe, because it's buffered in memory. If you + * suspect the child may produce a great volume of output between + * frames, consider directing the child to write to a filesystem + * file instead, then read the file later. * * - "tpipe": do not engage LLProcess machinery to monitor the * parent end of the pipe. A "tpipe" is used only to connect @@ -145,9 +149,14 @@ public: Optional name; FileParam(const std::string& tp="", const std::string& nm=""): - type("type", tp), - name("name", nm) - {} + type("type"), + name("name") + { + // If caller wants to specify values, use explicit assignment to + // set them rather than initialization. + if (! tp.empty()) type = tp; + if (! nm.empty()) name = nm; + } }; /// Param block definition @@ -175,17 +184,28 @@ public: /// current working directory, if need it changed Optional cwd; /// implicitly kill process on destruction of LLProcess object + /// (default true) Optional autokill; /** * Up to three FileParam items: for child stdin, stdout, stderr. * Passing two FileParam entries means default treatment for stderr, * and so forth. * + * @note LLInitParam::Block permits usage like this: + * @code + * LLProcess::Params params; + * ... + * params.files + * .add(LLProcess::FileParam()) // stdin + * .add(LLProcess::FileParam().type("pipe") // stdout + * .add(LLProcess::FileParam().type("file").name("error.log")); + * @endcode + * * @note While it's theoretically plausible to pass additional open * file handles to a child specifically written to expect them, our - * underlying implementation library doesn't support that. + * underlying implementation doesn't yet support that. */ - Multiple files; + Multiple > files; /** * On child-process termination, if this LLProcess object still * exists, post LLSD event to LLEventPump with specified name (default @@ -217,9 +237,8 @@ public: static LLProcessPtr create(const LLSDOrParams& params); virtual ~LLProcess(); - // isRunning() isn't const because, when child terminates, it sets stored - // Status - bool isRunning(void); + /// Is child process still running? + bool isRunning() const; /** * State of child process @@ -252,11 +271,11 @@ public: }; /// Status query - Status getStatus(); + Status getStatus() const; /// English Status string query, for logging etc. - std::string getStatusString(); + std::string getStatusString() const; /// English Status string query for previously-captured Status - std::string getStatusString(const Status& status); + std::string getStatusString(const Status& status) const; /// static English Status string query static std::string getStatusString(const std::string& desc, const Status& status); @@ -311,7 +330,7 @@ public: * filesystem name for the specified pipe. Otherwise returns the empty * string. @see LLProcess::FileParam::type */ - std::string getPipeName(FILESLOT); + std::string getPipeName(FILESLOT) const; /// base of ReadPipe, WritePipe class LL_COMMON_API BasePipe @@ -419,7 +438,7 @@ public: * contain "data"="abcde". However, you may still read the entire * "abcdef" from get_istream(): this limit affects only the size of * the data posted with the LLSD event. If you don't call this method, - * all pending data will be posted. + * @em no data will be posted: the default is 0 bytes. */ virtual void setLimit(size_type limit) = 0; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index fe599e7892..d7feddd26b 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -696,7 +696,7 @@ namespace tut "syntax_error:\n"); py.mParams.files.add(LLProcess::FileParam()); // inherit stdin py.mParams.files.add(LLProcess::FileParam()); // inherit stdout - py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stderr + py.mParams.files.add(LLProcess::FileParam().type("pipe")); // pipe for stderr py.run(); ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); ensure_equals("Status.mData", py.mPy->getStatus().mData, 1); @@ -1088,6 +1088,8 @@ namespace tut py.launch(); std::ostream& childin(py.mPy->getWritePipe(LLProcess::STDIN).get_ostream()); LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); + // lift the default limit; allow event to carry (some of) the actual data + childout.setLimit(20); // listen for incoming data on childout EventListener listener(childout.getPump()); // also listen with a function that prompts the child to continue -- cgit v1.2.3 From 025329b6a2ecb8ddee3022d6a73344f862f0d326 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 24 Feb 2012 15:06:44 -0500 Subject: Add LLStringUtil::getTokens() overload handling quoted substrings. We didn't have any tokenizer suitable for scanning something like a bash command line. We do have a couple hacks, e.g. LLExternalEditor::tokenize() and LLCommandLineParser::parseCommandLineString(). Both try to work around boost::tokenizer limitations; but existing boost::tokenizer support just doesn't address this case. Neither of the above is available as a general scanner anyway, and parseCommandLineString() fails outright when passed "". New getTokens() also distinguishes between "drop delimiters" (e.g. space, return, newline) to be discarded from the token stream, versus "keep delimiters" (e.g. "+-*/") to be returned as tokens in their own right. There's an overload that honors escapes and a more efficient one that doesn't; each has a convenience overload that returns the scanned string vector rather than requiring a separate declaration. Tweak and comment older getTokens() implementation. Add unit tests for both old and new getTokens() implementations. Break out StringVec and std::ostream << StringVec from indra/llcommon/tests/listener.h to StringVec.h: that's coming in handy for a number of different TUT test sources. --- indra/llcommon/llstring.cpp | 16 +- indra/llcommon/llstring.h | 355 ++++++++++++++++++++++++++++++++- indra/llcommon/tests/StringVec.h | 37 ++++ indra/llcommon/tests/listener.h | 21 +- indra/llcommon/tests/llstring_test.cpp | 115 +++++++++++ 5 files changed, 517 insertions(+), 27 deletions(-) create mode 100644 indra/llcommon/tests/StringVec.h diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index e7fe656808..fa0eb9f72c 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -912,22 +912,24 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions); template<> void LLStringUtil::getTokens(const std::string& instr, std::vector& tokens, const std::string& delims) { - std::string currToken; - std::string::size_type begIdx, endIdx; - - begIdx = instr.find_first_not_of (delims); - while (begIdx != std::string::npos) + // Starting at offset 0, scan forward for the next non-delimiter. We're + // done when the only characters left in 'instr' are delimiters. + for (std::string::size_type begIdx, endIdx = 0; + (begIdx = instr.find_first_not_of (delims, endIdx)) != std::string::npos; ) { + // Found a non-delimiter. After that, find the next delimiter. endIdx = instr.find_first_of (delims, begIdx); if (endIdx == std::string::npos) { + // No more delimiters: this token extends to the end of the string. endIdx = instr.length(); } - currToken = instr.substr(begIdx, endIdx - begIdx); + // extract the token between begIdx and endIdx; substr() needs length + std::string currToken(instr.substr(begIdx, endIdx - begIdx)); LLStringUtil::trim (currToken); tokens.push_back(currToken); - begIdx = instr.find_first_not_of (delims, endIdx); + // next scan past delimiters starts at endIdx } } diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 7b24b5e279..e4ae54cec5 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -40,6 +40,7 @@ #endif #include +#include #if LL_SOLARIS // stricmp and strnicmp do not exist on Solaris: @@ -247,7 +248,38 @@ public: static const string_type null; typedef std::map format_map_t; - LL_COMMON_API static void getTokens(const string_type& instr, std::vector& tokens, const string_type& delims); + /// considers any sequence of delims as a single field separator + LL_COMMON_API static void getTokens(const string_type& instr, + std::vector& tokens, + const string_type& delims); + /// like simple scan overload, but returns scanned vector + LL_COMMON_API static std::vector getTokens(const string_type& instr, + const string_type& delims); + /// add support for keep_delims and quotes (either could be empty string) + LL_COMMON_API static void getTokens(const string_type& instr, + std::vector& tokens, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes=string_type()); + /// like keep_delims-and-quotes overload, but returns scanned vector + LL_COMMON_API static std::vector getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes=string_type()); + /// add support for escapes (could be empty string) + LL_COMMON_API static void getTokens(const string_type& instr, + std::vector& tokens, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes); + /// like escapes overload, but returns scanned vector + LL_COMMON_API static std::vector getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes); + LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals); LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch); LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions); @@ -262,6 +294,11 @@ public: return !string.empty() && (0 <= i) && (i <= string.size()); } + static bool contains(const string_type& string, T c, size_type i=0) + { + return string.find(c, i) != string_type::npos; + } + static void trimHead(string_type& string); static void trimTail(string_type& string); static void trim(string_type& string) { trimHead(string); trimTail(string); } @@ -650,10 +687,324 @@ namespace LLStringFn //////////////////////////////////////////////////////////// // NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp. // There is no LLWStringUtil::format implementation currently. -// Calling thse for anything other than LLStringUtil will produce link errors. +// Calling these for anything other than LLStringUtil will produce link errors. //////////////////////////////////////////////////////////// +// static +template +std::vector::string_type> +LLStringUtilBase::getTokens(const string_type& instr, const string_type& delims) +{ + std::vector tokens; + getTokens(instr, tokens, delims); + return tokens; +} + +// static +template +std::vector::string_type> +LLStringUtilBase::getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes) +{ + std::vector tokens; + getTokens(instr, tokens, drop_delims, keep_delims, quotes); + return tokens; +} + +// static +template +std::vector::string_type> +LLStringUtilBase::getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes) +{ + std::vector tokens; + getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes); + return tokens; +} + +namespace LLStringUtilBaseImpl +{ + +/** + * Input string scanner helper for getTokens(), or really any other + * character-parsing routine that may have to deal with escape characters. + * This implementation defines the concept (also an interface, should you + * choose to implement the concept by subclassing) and provides trivial + * implementations for a string @em without escape processing. + */ +template +struct InString +{ + typedef std::basic_string string_type; + typedef typename string_type::const_iterator const_iterator; + + InString(const_iterator b, const_iterator e): + iter(b), + end(e) + {} + + bool done() const { return iter == end; } + /// Is the current character (*iter) escaped? This implementation can + /// answer trivially because it doesn't support escapes. + virtual bool escaped() const { return false; } + /// Obtain the current character and advance @c iter. + virtual T next() { return *iter++; } + /// Does the current character match specified character? + virtual bool is(T ch) const { return (! done()) && *iter == ch; } + /// Is the current character any one of the specified characters? + virtual bool oneof(const string_type& delims) const + { + return (! done()) && LLStringUtilBase::contains(delims, *iter); + } + + /** + * Scan forward from @from until either @a delim or end. This is primarily + * useful for processing quoted substrings. + * + * If we do see @a delim, append everything from @from until (excluding) + * @a delim to @a into, advance @c iter to skip @a delim, and return @c + * true. + * + * If we do not see @a delim, do not alter @a into or @c iter and return + * @c false. Do not pass GO, do not collect $200. + * + * @note The @c false case described above implements normal getTokens() + * treatment of an unmatched open quote: treat the quote character as if + * escaped, that is, simply collect it as part of the current token. Other + * plausible behaviors directly affect the way getTokens() deals with an + * unmatched quote: e.g. throwing an exception to treat it as an error, or + * assuming a close quote beyond end of string (in which case return @c + * true). + */ + virtual bool collect_until(string_type& into, const_iterator from, T delim) + { + const_iterator found = std::find(from, end, delim); + // If we didn't find delim, change nothing, just tell caller. + if (found == end) + return false; + // Found delim! Append everything between from and found. + into.append(from, found); + // advance past delim in input + iter = found + 1; + return true; + } + + const_iterator iter, end; +}; + +/// InString subclass that handles escape characters +template +class InEscString: public InString +{ +public: + typedef InString super; + typedef typename super::string_type string_type; + typedef typename super::const_iterator const_iterator; + using super::done; + using super::iter; + using super::end; + + InEscString(const_iterator b, const_iterator e, const string_type& escapes_): + super(b, e), + escapes(escapes_) + { + // Even though we've already initialized 'iter' via our base-class + // constructor, set it again to check for initial escape char. + setiter(b); + } + + /// This implementation uses the answer cached by setiter(). + virtual bool escaped() const { return isesc; } + virtual T next() + { + // If we're looking at the escape character of an escape sequence, + // skip that character. This is the one time we can modify 'iter' + // without using setiter: for this one case we DO NOT CARE if the + // escaped character is itself an escape. + if (isesc) + ++iter; + // If we were looking at an escape character, this is the escaped + // character; otherwise it's just the next character. + T result(*iter); + // Advance iter, checking for escape sequence. + setiter(iter + 1); + return result; + } + + virtual bool is(T ch) const + { + // Like base-class is(), except that an escaped character matches + // nothing. + return (! done()) && (! isesc) && *iter == ch; + } + + virtual bool oneof(const string_type& delims) const + { + // Like base-class oneof(), except that an escaped character matches + // nothing. + return (! done()) && (! isesc) && LLStringUtilBase::contains(delims, *iter); + } + + virtual bool collect_until(string_type& into, const_iterator from, T delim) + { + // Deal with escapes in the characters we collect; that is, an escaped + // character must become just that character without the preceding + // escape. Collect characters in a separate string rather than + // directly appending to 'into' in case we do not find delim, in which + // case we're supposed to leave 'into' unmodified. + string_type collected; + // For scanning purposes, we're going to work directly with 'iter'. + // Save its current value in case we fail to see delim. + const_iterator save_iter(iter); + // Okay, set 'iter', checking for escape. + setiter(from); + while (! done()) + { + // If we see an unescaped delim, stop and report success. + if ((! isesc) && *iter == delim) + { + // Append collected chars to 'into'. + into.append(collected); + // Don't forget to advance 'iter' past delim. + setiter(iter + 1); + return true; + } + // We're not at end, and either we're not looking at delim or it's + // escaped. Collect this character and keep going. + collected.push_back(next()); + } + // Here we hit 'end' without ever seeing delim. Restore iter and tell + // caller. + setiter(save_iter); + return false; + } + +private: + void setiter(const_iterator i) + { + iter = i; + + // Every time we change 'iter', set 'isesc' to be able to repetitively + // answer escaped() without having to rescan 'escapes'. isesc caches + // contains(escapes, *iter). + + // We're looking at an escaped char if we're not already at end (that + // is, *iter is even meaningful); if *iter is in fact one of the + // specified escape characters; and if there's one more character + // following it. That is, if an escape character is the very last + // character of the input string, it loses its special meaning. + isesc = (! done()) && + LLStringUtilBase::contains(escapes, *iter) && + (iter+1) != end; + } + + const string_type escapes; + bool isesc; +}; + +/// getTokens() implementation based on InString concept +template +void getTokens(INSTRING& instr, std::vector& tokens, + const string_type& drop_delims, const string_type& keep_delims, + const string_type& quotes) +{ + // There are times when we want to match either drop_delims or + // keep_delims. Concatenate them up front to speed things up. + string_type all_delims(drop_delims + keep_delims); + // no tokens yet + tokens.clear(); + + // try for another token + while (! instr.done()) + { + // scan past any drop_delims + while (instr.oneof(drop_delims)) + { + // skip this drop_delim + instr.next(); + // but if that was the end of the string, done + if (instr.done()) + return; + } + // found the start of another token: make a slot for it + tokens.push_back(string_type()); + if (instr.oneof(keep_delims)) + { + // *iter is a keep_delim, a token of exactly 1 character. Append + // that character to the new token and proceed. + tokens.back().push_back(instr.next()); + continue; + } + // Here we have a non-delimiter token, which might consist of a mix of + // quoted and unquoted parts. Use bash rules for quoting: you can + // embed a quoted substring in the midst of an unquoted token (e.g. + // ~/"sub dir"/myfile.txt); you can ram two quoted substrings together + // to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge + // from bash in that bash considers an unmatched quote an error. Our + // param signature doesn't allow for errors, so just pretend it's not + // a quote and embed it. + // At this level, keep scanning until we hit the next delimiter of + // either type (drop_delims or keep_delims). + while (! instr.oneof(all_delims)) + { + // If we're looking at an open quote, search forward for + // a close quote, collecting characters along the way. + if (instr.oneof(quotes) && + instr.collect_until(tokens.back(), instr.iter+1, *instr.iter)) + { + // collect_until is cleverly designed to do exactly what we + // need here. No further action needed if it returns true. + } + else + { + // Either *iter isn't a quote, or there's no matching close + // quote: in other words, just an ordinary char. Append it to + // current token. + tokens.back().push_back(instr.next()); + } + // having scanned that segment of this token, if we've reached the + // end of the string, we're done + if (instr.done()) + return; + } + } +} + +} // namespace LLStringUtilBaseImpl + +// static +template +void LLStringUtilBase::getTokens(const string_type& string, std::vector& tokens, + const string_type& drop_delims, const string_type& keep_delims, + const string_type& quotes) +{ + // Because this overload doesn't support escapes, use simple InString to + // manage input range. + LLStringUtilBaseImpl::InString instring(string.begin(), string.end()); + LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes); +} + +// static +template +void LLStringUtilBase::getTokens(const string_type& string, std::vector& tokens, + const string_type& drop_delims, const string_type& keep_delims, + const string_type& quotes, const string_type& escapes) +{ + // This overload must deal with escapes. Delegate that to InEscString + // (unless there ARE no escapes). + boost::scoped_ptr< LLStringUtilBaseImpl::InString > instrp; + if (escapes.empty()) + instrp.reset(new LLStringUtilBaseImpl::InString(string.begin(), string.end())); + else + instrp.reset(new LLStringUtilBaseImpl::InEscString(string.begin(), string.end(), escapes)); + LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes); +} // static template diff --git a/indra/llcommon/tests/StringVec.h b/indra/llcommon/tests/StringVec.h new file mode 100644 index 0000000000..a380b00a05 --- /dev/null +++ b/indra/llcommon/tests/StringVec.h @@ -0,0 +1,37 @@ +/** + * @file StringVec.h + * @author Nat Goodspeed + * @date 2012-02-24 + * @brief Extend TUT ensure_equals() to handle std::vector + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_STRINGVEC_H) +#define LL_STRINGVEC_H + +#include +#include +#include + +typedef std::vector StringVec; + +std::ostream& operator<<(std::ostream& out, const StringVec& strings) +{ + out << '('; + StringVec::const_iterator begin(strings.begin()), end(strings.end()); + if (begin != end) + { + out << '"' << *begin << '"'; + while (++begin != end) + { + out << ", \"" << *begin << '"'; + } + } + out << ')'; + return out; +} + +#endif /* ! defined(LL_STRINGVEC_H) */ diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h index dcdb2412be..9c5c18a150 100644 --- a/indra/llcommon/tests/listener.h +++ b/indra/llcommon/tests/listener.h @@ -30,6 +30,8 @@ #define LL_LISTENER_H #include "llsd.h" +#include "llevents.h" +#include "tests/StringVec.h" #include /***************************************************************************** @@ -133,24 +135,7 @@ struct Collect return false; } void clear() { result.clear(); } - typedef std::vector StringList; - StringList result; + StringVec result; }; -std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings) -{ - out << '('; - Collect::StringList::const_iterator begin(strings.begin()), end(strings.end()); - if (begin != end) - { - out << '"' << *begin << '"'; - while (++begin != end) - { - out << ", \"" << *begin << '"'; - } - } - out << ')'; - return out; -} - #endif /* ! defined(LL_LISTENER_H) */ diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 6a1cbf652a..821deeac21 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -29,7 +29,11 @@ #include "linden_common.h" #include "../test/lltut.h" +#include #include "../llstring.h" +#include "StringVec.h" + +using boost::assign::list_of; namespace tut { @@ -750,4 +754,115 @@ namespace tut ensure("empty substr.", !LLStringUtil::endsWith(empty, value)); ensure("empty everything.", !LLStringUtil::endsWith(empty, empty)); } + + template<> template<> + void string_index_object_t::test<41>() + { + set_test_name("getTokens(\"delims\")"); + ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec()); + ensure_equals("only delims", + LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec()); + ensure_equals("sequence of delims", + LLStringUtil::getTokens(",,, one ,,,", ","), list_of("one")); + // nat considers this a dubious implementation side effect, but I'd + // hate to change it now... + ensure_equals("noncontiguous tokens", + LLStringUtil::getTokens(", ,, , one ,,,", ","), list_of("")("")("one")); + ensure_equals("space-padded tokens", + LLStringUtil::getTokens(", one , two ,", ","), list_of("one")("two")); + ensure_equals("no delims", LLStringUtil::getTokens("one", ","), list_of("one")); + } + + // Shorthand for verifying that getTokens() behaves the same when you + // don't pass a string of escape characters, when you pass an empty string + // (different overloads), and when you pass a string of characters that + // aren't actually present. + void ensure_getTokens(const std::string& desc, + const std::string& string, + const std::string& drop_delims, + const std::string& keep_delims, + const std::string& quotes, + const std::vector& expect) + { + ensure_equals(desc + " - no esc", + LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes), + expect); + ensure_equals(desc + " - empty esc", + LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""), + expect); + ensure_equals(desc + " - unused esc", + LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"), + expect); + } + + void ensure_getTokens(const std::string& desc, + const std::string& string, + const std::string& drop_delims, + const std::string& keep_delims, + const std::vector& expect) + { + ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect); + } + + template<> template<> + void string_index_object_t::test<42>() + { + set_test_name("getTokens(\"delims\", etc.)"); + // Signatures to test in this method: + // getTokens(string, drop_delims, keep_delims [, quotes [, escapes]]) + // If you omit keep_delims, you get the older function (test above). + + // cases like the getTokens(string, delims) tests above + ensure_getTokens("empty string", "", " ", "", StringVec()); + ensure_getTokens("only delims", + " \r\n ", " \r\n", "", StringVec()); + ensure_getTokens("sequence of delims", + ",,, one ,,,", ", ", "", list_of("one")); + // Note contrast with the case in the previous method + ensure_getTokens("noncontiguous tokens", + ", ,, , one ,,,", ", ", "", list_of("one")); + ensure_getTokens("space-padded tokens", + ", one , two ,", ", ", "", + list_of("one")("two")); + ensure_getTokens("no delims", "one", ",", "", list_of("one")); + + // drop_delims vs. keep_delims + ensure_getTokens("arithmetic", + " ab+def / xx* yy ", " ", "+-*/", + list_of("ab")("+")("def")("/")("xx")("*")("yy")); + + // quotes + ensure_getTokens("no quotes", + "She said, \"Don't go.\"", " ", ",", "", + list_of("She")("said")(",")("\"Don't")("go.\"")); + ensure_getTokens("quotes", + "She said, \"Don't go.\"", " ", ",", "\"", + list_of("She")("said")(",")("Don't go.")); + ensure_getTokens("quotes and delims", + "run c:/'Documents and Settings'/someone", " ", "", "'", + list_of("run")("c:/Documents and Settings/someone")); + ensure_getTokens("unmatched quote", + "baby don't leave", " ", "", "'", + list_of("baby")("don't")("leave")); + ensure_getTokens("adjacent quoted", + "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'", + list_of("abcdef \"ghijkl' mnopqr")); + + // escapes + // Don't use backslash as an escape for these tests -- you'll go nuts + // between the C++ string scanner and getTokens() escapes. Test with + // something else! + ensure_equals("escaped delims", + LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"), + list_of(" a")("-")("dog-gone phrase")); + ensure_equals("escaped quotes", + LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"), + list_of("say:")("this isn't working.")); + ensure_equals("escaped escape", + LLStringUtil::getTokens("want x^^2", " ", "", "", "^"), + list_of("want")("x^2")); + ensure_equals("escape at end", + LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"), + list_of("it's up")("there^")); + } } -- cgit v1.2.3 From 5adc39753b9392050d961eb01edb31f8a6fbb05d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 24 Feb 2012 16:02:39 -0500 Subject: Update llevents_tut.cpp to use StringVec, not local StringList. --- indra/test/llevents_tut.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp index ca4c74099f..a9114075fc 100644 --- a/indra/test/llevents_tut.cpp +++ b/indra/test/llevents_tut.cpp @@ -316,7 +316,6 @@ void events_object::test<7>() { set_test_name("listener dependency order"); typedef LLEventPump::NameList NameList; - typedef Collect::StringList StringList; LLEventPump& button(pumps.obtain("button")); Collect collector; button.listen("Mary", @@ -330,7 +329,7 @@ void events_object::test<7>() button.listen("spot", boost::bind(&Collect::add, boost::ref(collector), "spot", _1)); button.post(1); - ensure_equals(collector.result, make(list_of("spot")("checked")("Mary"))); + ensure_equals(collector.result, make(list_of("spot")("checked")("Mary"))); collector.clear(); button.stopListening("Mary"); button.listen("Mary", @@ -339,7 +338,7 @@ void events_object::test<7>() // now "Mary" must come before "spot" make(list_of("spot"))); button.post(2); - ensure_equals(collector.result, make(list_of("Mary")("spot")("checked"))); + ensure_equals(collector.result, make(list_of("Mary")("spot")("checked"))); collector.clear(); button.stopListening("spot"); std::string threw; @@ -373,7 +372,7 @@ void events_object::test<7>() boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1), make(list_of("checked"))); button.post(3); - ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); + ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); collector.clear(); threw.clear(); try @@ -395,7 +394,7 @@ void events_object::test<7>() ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces"); ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow"); button.post(4); - ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); + ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); } template<> template<> -- cgit v1.2.3 From 4edf93d6d991af3f1aa0aba764388ad52ab1464b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 24 Feb 2012 16:50:47 -0500 Subject: "Then there's Windows..." Fix llstring.h to build there too. --- indra/llcommon/llstring.h | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index e4ae54cec5..5085d32f7f 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -253,32 +253,32 @@ public: std::vector& tokens, const string_type& delims); /// like simple scan overload, but returns scanned vector - LL_COMMON_API static std::vector getTokens(const string_type& instr, - const string_type& delims); + static std::vector getTokens(const string_type& instr, + const string_type& delims); /// add support for keep_delims and quotes (either could be empty string) - LL_COMMON_API static void getTokens(const string_type& instr, - std::vector& tokens, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes=string_type()); + static void getTokens(const string_type& instr, + std::vector& tokens, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes=string_type()); /// like keep_delims-and-quotes overload, but returns scanned vector - LL_COMMON_API static std::vector getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes=string_type()); + static std::vector getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes=string_type()); /// add support for escapes (could be empty string) - LL_COMMON_API static void getTokens(const string_type& instr, - std::vector& tokens, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes, - const string_type& escapes); + static void getTokens(const string_type& instr, + std::vector& tokens, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes); /// like escapes overload, but returns scanned vector - LL_COMMON_API static std::vector getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes, - const string_type& escapes); + static std::vector getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes); LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals); LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch); @@ -748,6 +748,7 @@ struct InString iter(b), end(e) {} + virtual ~InString() {} bool done() const { return iter == end; } /// Is the current character (*iter) escaped? This implementation can -- cgit v1.2.3 From d2faf5d25a0f6cc3ccaaf450fe6d3585fef058b7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 24 Feb 2012 17:14:07 -0500 Subject: Get rid of indra/llcommon/tests/setpython.py. run_build_test.py already has the capability to set environment variables, and we may as well direct it to set PYTHON to the running Python interpreter. That completely eliminates one level of process wrapper. --- indra/cmake/LLTestCommand.cmake | 3 +++ indra/llcommon/CMakeLists.txt | 6 ++---- indra/llcommon/tests/setpython.py | 19 ------------------- 3 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 indra/llcommon/tests/setpython.py diff --git a/indra/cmake/LLTestCommand.cmake b/indra/cmake/LLTestCommand.cmake index b5a0580a90..f75c23a5de 100644 --- a/indra/cmake/LLTestCommand.cmake +++ b/indra/cmake/LLTestCommand.cmake @@ -9,6 +9,9 @@ MACRO(LL_TEST_COMMAND OUTVAR LD_LIBRARY_PATH) FOREACH(dir ${LD_LIBRARY_PATH}) LIST(APPEND value "-l${dir}") ENDFOREACH(dir) + # Enough different tests want to be able to find CMake's PYTHON_EXECUTABLE + # that we should just pop it into the environment for everybody. + LIST(APPEND value "-DPYTHON=${PYTHON_EXECUTABLE}") LIST(APPEND value ${ARGN}) SET(${OUTVAR} ${value}) ##IF(LL_TEST_VERBOSE) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 3255e28e8e..1cab648cfa 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -324,8 +324,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(lllazy "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}" - "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py") + LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}") @@ -333,8 +332,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(llprocess "" "${test_libs}" - "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py") + LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") # *TODO - reenable these once tcmalloc libs no longer break the build. diff --git a/indra/llcommon/tests/setpython.py b/indra/llcommon/tests/setpython.py deleted file mode 100644 index df7b90428e..0000000000 --- a/indra/llcommon/tests/setpython.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/python -"""\ -@file setpython.py -@author Nat Goodspeed -@date 2011-07-13 -@brief Set PYTHON environment variable for tests that care. - -$LicenseInfo:firstyear=2011&license=viewerlgpl$ -Copyright (c) 2011, Linden Research, Inc. -$/LicenseInfo$ -""" - -import os -import sys -import subprocess - -if __name__ == "__main__": - os.environ["PYTHON"] = sys.executable - sys.exit(subprocess.call(sys.argv[1:])) -- cgit v1.2.3 From bf7c215692435ceb85d8991a9337933688dc0cf0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 26 Feb 2012 07:17:08 -0500 Subject: Add LLStringUtil::getTokens() test for quoted empty string. This is an important differentiator between getTokens() and the present LLCommandLineParser::parseCommandLineString() logic: you cannot currently --set SomeVar to an empty string value because parseCommandLineString() discards empty strings. --- indra/llcommon/tests/llstring_test.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 821deeac21..93d3968dbf 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -847,6 +847,9 @@ namespace tut ensure_getTokens("adjacent quoted", "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'", list_of("abcdef \"ghijkl' mnopqr")); + ensure_getTokens("quoted empty string", + "--set SomeVar ''", " ", "", "'", + list_of("--set")("SomeVar")("")); // escapes // Don't use backslash as an escape for these tests -- you'll go nuts -- cgit v1.2.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(+) 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.2.3 From c0318d1bf988217e1fbb0593d03c4f0235a13ea3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 27 Feb 2012 11:50:47 -0500 Subject: Make LLInstanceTracker::getInstance(T*) validate passed T*. For the T* specialization (no string, or whatever, key), the original getInstance() method simply returned the passed-in T* value. It was defined, as the comments noted, for completeness of the analogy with the keyed LLInstanceTracker specialization. It turns out, though, that getInstance(T*) can still be useful to ask whether the T* you have in hand still references a valid T instance. Support that usage. --- indra/llcommon/llinstancetracker.h | 21 +++++++++++++++++---- indra/llcommon/tests/llinstancetracker_test.cpp | 7 ++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 34d841a4e0..403df08990 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -167,8 +167,9 @@ public: static T* getInstance(const KEY& k) { - typename InstanceMap::const_iterator found = getMap_().find(k); - return (found == getMap_().end()) ? NULL : found->second; + const InstanceMap& map(getMap_()); + typename InstanceMap::const_iterator found = map.find(k); + return (found == map.end()) ? NULL : found->second; } static instance_iter beginInstances() @@ -239,8 +240,20 @@ class LLInstanceTracker : public LLInstanceTrackerBase public: - /// for completeness of analogy with the generic implementation - static T* getInstance(T* k) { return k; } + /** + * Does a particular instance still exist? Of course, if you already have + * a T* in hand, you need not call getInstance() to @em locate the + * instance -- unlike the case where getInstance() accepts some kind of + * key. Nonetheless this method is still useful to @em validate a + * particular T*, since each instance's destructor removes itself from the + * underlying set. + */ + static T* getInstance(T* k) + { + const InstanceSet& set(getSet_()); + typename InstanceSet::const_iterator found = set.find(k); + return (found == set.end())? NULL : *found; + } static S32 instanceCount() { return getSet_().size(); } class instance_iter : public boost::iterator_facade diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index b34d1c5fd3..294e21bac5 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -95,6 +95,7 @@ namespace tut void object::test<2>() { ensure_equals(Unkeyed::instanceCount(), 0); + Unkeyed* dangling = NULL; { Unkeyed one; ensure_equals(Unkeyed::instanceCount(), 1); @@ -107,7 +108,11 @@ namespace tut ensure_equals(found, two.get()); } ensure_equals(Unkeyed::instanceCount(), 1); - } + // store an unwise pointer to a temp Unkeyed instance + dangling = &one; + } // make that instance vanish + // check the now-invalid pointer to the destroyed instance + ensure("getInstance(T*) failed to track destruction", ! Unkeyed::getInstance(dangling)); ensure_equals(Unkeyed::instanceCount(), 0); } -- cgit v1.2.3 From 6f53796ccf4dc29368920a322baeaceb3fd2266f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 27 Feb 2012 14:47:40 -0500 Subject: Add LLInstanceTracker test for exception in subclass constructor. We want to verify the sequence: LLInstanceTracker constructor adds instance to underlying container Subclass constructor throws exception LLInstanceTracker destructor removes instance from underlying container. --- indra/llcommon/tests/llinstancetracker_test.cpp | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index b34d1c5fd3..fda932c355 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -35,6 +35,7 @@ #include #include #include // std::sort() +#include // std headers // external library headers #include @@ -42,6 +43,11 @@ #include "../test/lltut.h" #include "wrapllerrs.h" +struct Badness: public std::runtime_error +{ + Badness(const std::string& what): std::runtime_error(what) {} +}; + struct Keyed: public LLInstanceTracker { Keyed(const std::string& name): @@ -53,6 +59,17 @@ struct Keyed: public LLInstanceTracker struct Unkeyed: public LLInstanceTracker { + Unkeyed(const std::string& thrw="") + { + // LLInstanceTracker should respond appropriately if a subclass + // constructor throws an exception. Specifically, it should run + // LLInstanceTracker's destructor and remove itself from the + // underlying container. + if (! thrw.empty()) + { + throw Badness(thrw); + } + } }; /***************************************************************************** @@ -229,4 +246,49 @@ namespace tut } ensure(! what.empty()); } + + template<> template<> + void object::test<8>() + { + set_test_name("exception in subclass ctor"); + typedef std::set InstanceSet; + InstanceSet existing; + // We can't use the iterator-range InstanceSet constructor because + // beginInstances() returns an iterator that dereferences to an + // Unkeyed&, not an Unkeyed*. + for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()), + ukend(Unkeyed::endInstances()); + uki != ukend; ++uki) + { + existing.insert(&*uki); + } + Unkeyed* puk = NULL; + try + { + // We don't expect the assignment to take place because we expect + // Unkeyed to respond to the non-empty string param by throwing. + // We know the LLInstanceTracker base-class constructor will have + // run before Unkeyed's constructor, therefore the new instance + // will have added itself to the underlying set. The whole + // question is, when Unkeyed's constructor throws, will + // LLInstanceTracker's destructor remove it from the set? I + // realize we're testing the C++ implementation more than + // Unkeyed's implementation, but this seems an important point to + // nail down. + puk = new Unkeyed("throw"); + } + catch (const Badness&) + { + } + // Ensure that every member of the new, updated set of Unkeyed + // instances was also present in the original set. If that's not true, + // it's because our new Unkeyed ended up in the updated set despite + // its constructor exception. + for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()), + ukend(Unkeyed::endInstances()); + uki != ukend; ++uki) + { + ensure("failed to remove instance", existing.find(&*uki) != existing.end()); + } + } } // namespace tut -- cgit v1.2.3 From 7fd281ac9971caa1dfffd42e6ff16dd44da20179 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 27 Feb 2012 15:24:14 -0500 Subject: Reduce redundancy in llprocess_test.cpp using get_test_name(). --- indra/llcommon/tests/llprocess_test.cpp | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index d7feddd26b..b02a5c0631 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -620,7 +620,7 @@ namespace tut // 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()", + PythonProcessLauncher py(get_test_name(), "from __future__ import with_statement\n" "import os, sys\n" "with open(sys.argv[1], 'w') as f:\n" @@ -634,7 +634,7 @@ namespace tut void object::test<3>() { set_test_name("arguments"); - PythonProcessLauncher py("args", + PythonProcessLauncher py(get_test_name(), "from __future__ import with_statement\n" "import sys\n" // note nonstandard output-file arg! @@ -668,7 +668,7 @@ namespace tut void object::test<4>() { set_test_name("exit(0)"); - PythonProcessLauncher py("exit(0)", + PythonProcessLauncher py(get_test_name(), "import sys\n" "sys.exit(0)\n"); py.run(); @@ -680,7 +680,7 @@ namespace tut void object::test<5>() { set_test_name("exit(2)"); - PythonProcessLauncher py("exit(2)", + PythonProcessLauncher py(get_test_name(), "import sys\n" "sys.exit(2)\n"); py.run(); @@ -692,7 +692,7 @@ namespace tut void object::test<6>() { set_test_name("syntax_error:"); - PythonProcessLauncher py("syntax_error:", + PythonProcessLauncher py(get_test_name(), "syntax_error:\n"); py.mParams.files.add(LLProcess::FileParam()); // inherit stdin py.mParams.files.add(LLProcess::FileParam()); // inherit stdout @@ -713,7 +713,7 @@ namespace tut void object::test<7>() { set_test_name("explicit kill()"); - PythonProcessLauncher py("kill()", + PythonProcessLauncher py(get_test_name(), "from __future__ import with_statement\n" "import sys, time\n" "with open(sys.argv[1], 'w') as f:\n" @@ -750,7 +750,7 @@ namespace tut // 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"); + ensure_equals(get_test_name() + " script output", readfile(out.getName()), "ok"); } template<> template<> @@ -760,7 +760,7 @@ namespace tut NamedTempFile out("out", "not started"); LLProcess::handle phandle(0); { - PythonProcessLauncher py("kill()", + PythonProcessLauncher py(get_test_name(), "from __future__ import with_statement\n" "import sys, time\n" "with open(sys.argv[1], 'w') as f:\n" @@ -792,7 +792,7 @@ namespace tut // 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"); + ensure_equals(get_test_name() + " script output", readfile(out.getName()), "ok"); } template<> template<> @@ -803,7 +803,7 @@ namespace tut NamedTempFile to("to", ""); LLProcess::handle phandle(0); { - PythonProcessLauncher py("autokill", + PythonProcessLauncher py(get_test_name(), "from __future__ import with_statement\n" "import sys, time\n" "with open(sys.argv[1], 'w') as f:\n" @@ -852,7 +852,7 @@ namespace tut waitfor(phandle, "autokill script"); // 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"); + ensure_equals(get_test_name() + " script output", readfile(from.getName()), "ack"); } template<> template<> @@ -860,7 +860,7 @@ namespace tut { set_test_name("'bogus' test"); TestRecorder recorder; - PythonProcessLauncher py("'bogus' test", + PythonProcessLauncher py(get_test_name(), "print 'Hello world'\n"); py.mParams.files.add(LLProcess::FileParam("bogus")); py.mPy = LLProcess::create(py.mParams); @@ -876,7 +876,7 @@ namespace tut set_test_name("'file' test"); // Replace this test with one or more real 'file' tests when we // implement 'file' support - PythonProcessLauncher py("'file' test", + PythonProcessLauncher py(get_test_name(), "print 'Hello world'\n"); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam("file")); @@ -891,7 +891,7 @@ namespace tut // Replace this test with one or more real 'tpipe' tests when we // implement 'tpipe' support TestRecorder recorder; - PythonProcessLauncher py("'tpipe' test", + PythonProcessLauncher py(get_test_name(), "print 'Hello world'\n"); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam("tpipe")); @@ -909,7 +909,7 @@ namespace tut // Replace this test with one or more real 'npipe' tests when we // implement 'npipe' support TestRecorder recorder; - PythonProcessLauncher py("'npipe' test", + PythonProcessLauncher py(get_test_name(), "print 'Hello world'\n"); py.mParams.files.add(LLProcess::FileParam()); py.mParams.files.add(LLProcess::FileParam()); @@ -926,7 +926,7 @@ namespace tut { set_test_name("internal pipe name warning"); TestRecorder recorder; - PythonProcessLauncher py("pipe warning", + PythonProcessLauncher py(get_test_name(), "import sys\n" "sys.exit(7)\n"); py.mParams.files.add(LLProcess::FileParam("pipe", "somename")); @@ -990,7 +990,7 @@ namespace tut void object::test<15>() { set_test_name("get*Pipe() validation"); - PythonProcessLauncher py("just stderr", + PythonProcessLauncher py(get_test_name(), "print 'this output is expected'\n"); py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stdin py.mParams.files.add(LLProcess::FileParam()); // inherit stdout @@ -1010,7 +1010,7 @@ namespace tut void object::test<16>() { set_test_name("talk to stdin/stdout"); - PythonProcessLauncher py("stdin/stdout", + PythonProcessLauncher py(get_test_name(), "import sys, time\n" "print 'ok'\n" "sys.stdout.flush()\n" @@ -1071,7 +1071,7 @@ namespace tut void object::test<17>() { set_test_name("listen for ReadPipe events"); - PythonProcessLauncher py("ReadPipe listener", + PythonProcessLauncher py(get_test_name(), "import sys\n" "sys.stdout.write('abc')\n" "sys.stdout.flush()\n" @@ -1131,7 +1131,7 @@ namespace tut void object::test<18>() { set_test_name("setLimit()"); - PythonProcessLauncher py("setLimit()", + PythonProcessLauncher py(get_test_name(), "import sys\n" "sys.stdout.write(sys.argv[1])\n"); std::string abc("abcdefghijklmnopqrstuvwxyz"); @@ -1160,7 +1160,7 @@ namespace tut void object::test<19>() { set_test_name("peek() ReadPipe data"); - PythonProcessLauncher py("peek() ReadPipe", + PythonProcessLauncher py(get_test_name(), "import sys\n" "sys.stdout.write(sys.argv[1])\n"); std::string abc("abcdefghijklmnopqrstuvwxyz"); @@ -1213,7 +1213,7 @@ namespace tut void object::test<20>() { set_test_name("good postend"); - PythonProcessLauncher py("postend", + PythonProcessLauncher py(get_test_name(), "import sys\n" "sys.exit(35)\n"); std::string pumpname("postend"); @@ -1247,7 +1247,7 @@ namespace tut std::string pumpname("postend"); EventListener listener(LLEventPumps::instance().obtain(pumpname)); LLProcess::Params params; - params.desc = "bad postend"; + params.desc = get_test_name(); params.postend = pumpname; LLProcessPtr child = LLProcess::create(params); ensure("shouldn't have launched", ! child); -- cgit v1.2.3 From 3649eda62ad3a04203e6c562e78815a95896bbd4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 29 Feb 2012 17:10:19 -0500 Subject: Guarantee LLProcess::Params::postend listener any ReadPipe data. Previously one might get process-terminated notification but still have to wait for the child process's final data to arrive on one or more ReadPipes. That required complex consumer timing logic to handle incomplete pending ReadPipe data, e.g. a partial last line with no terminating newline. New code guarantees that by the time LLProcess sends process-terminated notification, all pending pipe data will have been buffered in ReadPipes. Document LLProcess::ReadPipe::getPump() notification event; add "eof" key. Add LLProcess::ReadPipe::getline() and read() convenience methods. Add static LLProcess::getline() and basename() convenience methods, publishing logic already present elsewhere. Use ReadPipe::getline() and read() in unit tests. Add unit test for "eof" event on ReadPipe::getPump(). Add unit test verifying that final data have been buffered by termination notification event. --- indra/llcommon/llprocess.cpp | 107 +++++++++++++++++++----- indra/llcommon/llprocess.h | 31 ++++++- indra/llcommon/tests/llprocess_test.cpp | 139 ++++++++++++++++++++++---------- 3 files changed, 210 insertions(+), 67 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 3b17b819bd..edfdebfe87 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -220,7 +220,8 @@ public: // Essential to initialize our std::istream with our special streambuf! mStream(&mStreambuf), mPump("ReadPipe", true), // tweak name as needed to avoid collisions - mLimit(0) + mLimit(0), + mEOF(false) { mConnection = LLEventPumps::instance().obtain("mainloop") .listen(LLEventPump::inventName("ReadPipe"), @@ -230,11 +231,25 @@ public: // Much of the implementation is simply connecting the abstract virtual // methods with implementation data concealed from the base class. virtual std::istream& get_istream() { return mStream; } + virtual std::string getline() { return LLProcess::getline(mStream); } virtual LLEventPump& getPump() { return mPump; } virtual void setLimit(size_type limit) { mLimit = limit; } virtual size_type getLimit() const { return mLimit; } virtual size_type size() const { return mStreambuf.size(); } + virtual std::string read(size_type len) + { + // Read specified number of bytes into a buffer. Make a buffer big + // enough. + size_type readlen((std::min)(size(), len)); + std::vector buffer(readlen); + mStream.read(&buffer[0], readlen); + // Since we've already clamped 'readlen', we can think of no reason + // why mStream.read() should read fewer than 'readlen' bytes. + // Nonetheless, use the actual retrieved length. + return std::string(&buffer[0], mStream.gcount()); + } + virtual std::string peek(size_type offset=0, size_type len=npos) const { // Constrain caller's offset and len to overlap actual buffer content. @@ -287,14 +302,18 @@ public: return (found == end)? npos : (found - begin); } -private: bool tick(const LLSD&) { + // Once we've hit EOF, skip all the rest of this. + if (mEOF) + return false; + typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence; // Try, every time, to read into our streambuf. In fact, we have no // idea how much data the child might be trying to send: keep trying // until we're convinced we've temporarily exhausted the pipe. - bool exhausted = false; + enum PipeState { RETRY, EXHAUSTED, CLOSED }; + PipeState state = RETRY; std::size_t committed(0); do { @@ -329,7 +348,9 @@ private: } // Either way, though, we won't need any more tick() calls. mConnection.disconnect(); - exhausted = true; // also break outer retry loop + // Ignore any subsequent calls we might get anyway. + mEOF = true; + state = CLOSED; // also break outer retry loop break; } @@ -347,7 +368,7 @@ private: if (gotten < toread) { // break outer retry loop too - exhausted = true; + state = EXHAUSTED; break; } } @@ -356,15 +377,20 @@ private: mStreambuf.commit(tocommit); committed += tocommit; - // 'exhausted' is set when we can't fill any one buffer of the - // mutable_buffer_sequence established by the current prepare() - // call -- whether due to error or not enough bytes. That is, - // 'exhausted' is still false when we've filled every physical + // state is changed from RETRY when we can't fill any one buffer + // of the mutable_buffer_sequence established by the current + // prepare() call -- whether due to error or not enough bytes. + // That is, if state is still RETRY, we've filled every physical // buffer in the mutable_buffer_sequence. In that case, for all we // know, the child might have still more data pending -- go for it! - } while (! exhausted); - - if (committed) + } while (state == RETRY); + + // Once we recognize that the pipe is closed, make one more call to + // listener. The listener might be waiting for a particular substring + // to arrive, or a particular length of data or something. The event + // with "eof" == true announces that nothing further will arrive, so + // use it or lose it. + if (committed || state == CLOSED) { // If we actually received new data, publish it on our LLEventPump // as advertised. Constrain it by mLimit. But show listener the @@ -373,14 +399,16 @@ private: mPump.post(LLSDMap ("data", peek(0, datasize)) ("len", LLSD::Integer(mStreambuf.size())) - ("index", LLSD::Integer(mIndex)) + ("slot", LLSD::Integer(mIndex)) ("name", whichfile(mIndex)) - ("desc", mDesc)); + ("desc", mDesc) + ("eof", state == CLOSED)); } return false; } +private: std::string mDesc; apr_file_t* mPipe; LLProcess::FILESLOT mIndex; @@ -389,6 +417,7 @@ private: std::istream mStream; LLEventStream mPump; size_type mLimit; + bool mEOF; }; /// Need an exception to avoid constructing an invalid LLProcess object, but @@ -641,17 +670,22 @@ static std::string getDesc(const LLProcess::Params& params) // Caller didn't say. Use the executable name -- but use just the filename // part. On Mac, for instance, full pathnames get cumbersome. + return LLProcess::basename(params.executable); +} + +//static +std::string LLProcess::basename(const std::string& path) +{ // If there are Linden utility functions to manipulate pathnames, I // haven't found them -- and for this usage, Boost.Filesystem seems kind // of heavyweight. - std::string executable(params.executable); - std::string::size_type delim = executable.find_last_of("\\/"); - // If executable contains no pathname delimiters, return the whole thing. + std::string::size_type delim = path.find_last_of("\\/"); + // If path contains no pathname delimiters, return the whole thing. if (delim == std::string::npos) - return executable; + return path; // Return just the part beyond the last delimiter. - return executable.substr(delim + 1); + return path.substr(delim + 1); } LLProcess::~LLProcess() @@ -804,6 +838,24 @@ void LLProcess::handle_status(int reason, int status) // KILLED; refine below. mStatus.mState = EXITED; + // Make last-gasp calls for each of the ReadPipes we have on hand. Since + // they're listening on "mainloop", we can be sure they'll eventually + // collect all pending data from the child. But we want to be able to + // guarantee to our consumer that by the time we post on the "postend" + // LLEventPump, our ReadPipes are already buffering all the data there + // will ever be from the child. That lets the "postend" listener decide + // what to do with that final data. + for (size_t i = 0; i < mPipes.size(); ++i) + { + std::string error; + ReadPipeImpl* ppipe = getPipePtr(error, FILESLOT(i)); + if (ppipe) + { + static LLSD trivial; + ppipe->tick(trivial); + } + } + // wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); // 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 @@ -919,6 +971,23 @@ boost::optional LLProcess::getOptReadPipe(FILESLOT slot) return getOptPipe(slot); } +//static +std::string LLProcess::getline(std::istream& in) +{ + std::string line; + std::getline(in, line); + // Blur the distinction between "\r\n" and plain "\n". std::getline() will + // have eaten the "\n", but we could still end up with a trailing "\r". + std::string::size_type lastpos = line.find_last_not_of("\r"); + if (lastpos != std::string::npos) + { + // Found at least one character that's not a trailing '\r'. SKIP OVER + // IT and erase the rest of the line. + line.erase(lastpos+1); + } + return line; +} + std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) { if (params.cwd.isProvided()) diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 637b7e2f9c..06ada83698 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -156,7 +156,7 @@ public: // set them rather than initialization. if (! tp.empty()) type = tp; if (! nm.empty()) name = nm; - } + } }; /// Param block definition @@ -375,6 +375,18 @@ public: */ virtual std::istream& get_istream() = 0; + /** + * Like std::getline(get_istream(), line), but trims off trailing '\r' + * to make calling code less platform-sensitive. + */ + virtual std::string getline() = 0; + + /** + * Like get_istream().read(buffer, n), but returns std::string rather + * than requiring caller to construct a buffer, etc. + */ + virtual std::string read(size_type len) = 0; + /** * Get accumulated buffer length. * Often we need to refrain from actually reading the std::istream @@ -420,9 +432,15 @@ public: /** * Get LLEventPump& on which to listen for incoming data. The posted - * LLSD::Map event will contain a key "data" whose value is an - * LLSD::String containing (part of) the data accumulated in the - * buffer. + * LLSD::Map event will contain: + * + * - "data" part of pending data; see setLimit() + * - "len" entire length of pending data, regardless of setLimit() + * - "slot" this ReadPipe's FILESLOT, e.g. LLProcess::STDOUT + * - "name" e.g. "stdout" + * - "desc" e.g. "SLPlugin (pid) stdout" + * - "eof" @c true means there no more data will arrive on this pipe, + * therefore no more events on this pump * * If the child sends "abc", and this ReadPipe posts "data"="abc", but * you don't consume it by reading the std::istream returned by @@ -487,6 +505,11 @@ public: */ boost::optional getOptReadPipe(FILESLOT slot); + /// little utilities that really should already be somewhere else in the + /// code base + static std::string basename(const std::string& path); + static std::string getline(std::istream&); + private: /// constructor is private: use create() instead LLProcess(const LLSDOrParams& params); diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index b02a5c0631..3537133a47 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -295,22 +295,6 @@ public: LLError::Settings* mOldSettings; }; -std::string getline(std::istream& in) -{ - std::string line; - std::getline(in, line); - // Blur the distinction between "\r\n" and plain "\n". std::getline() will - // have eaten the "\n", but we could still end up with a trailing "\r". - std::string::size_type lastpos = line.find_last_not_of("\r"); - if (lastpos != std::string::npos) - { - // Found at least one character that's not a trailing '\r'. SKIP OVER - // IT and then erase the rest of the line. - line.erase(lastpos+1); - } - return line; -} - /***************************************************************************** * TUT *****************************************************************************/ @@ -1030,7 +1014,7 @@ namespace tut } ensure("script never started", i < timeout); ensure_equals("bad wakeup from stdin/stdout script", - getline(childout.get_istream()), "ok"); + childout.getline(), "ok"); // important to get the implicit flush from std::endl py.mPy->getWritePipe().get_ostream() << "go" << std::endl; for (i = 0; i < timeout && py.mPy->isRunning() && ! childout.contains("\n"); ++i) @@ -1038,7 +1022,7 @@ namespace tut yield(); } ensure("script never replied", childout.contains("\n")); - ensure_equals("child didn't ack", getline(childout.get_istream()), "ack"); + ensure_equals("child didn't ack", childout.getline(), "ack"); ensure_equals("bad child termination", py.mPy->getStatus().mState, LLProcess::EXITED); ensure_equals("bad child exit code", py.mPy->getStatus().mData, 0); } @@ -1129,6 +1113,32 @@ namespace tut template<> template<> void object::test<18>() + { + set_test_name("ReadPipe \"eof\" event"); + PythonProcessLauncher py(get_test_name(), + "print 'Hello from Python!'\n"); + py.mParams.files.add(LLProcess::FileParam()); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.launch(); + LLProcess::ReadPipe& childout(py.mPy->getReadPipe(LLProcess::STDOUT)); + EventListener listener(childout.getPump()); + waitfor(*py.mPy); + // We can't be positive there will only be a single event, if the OS + // (or any other intervening layer) does crazy buffering. What we want + // to ensure is that there was exactly ONE event with "eof" true, and + // that it was the LAST event. + std::list::const_reverse_iterator rli(listener.mHistory.rbegin()), + rlend(listener.mHistory.rend()); + ensure("no events", rli != rlend); + ensure("last event not \"eof\"", (*rli)["eof"].asBoolean()); + while (++rli != rlend) + { + ensure("\"eof\" event not last", ! (*rli)["eof"].asBoolean()); + } + } + + template<> template<> + void object::test<19>() { set_test_name("setLimit()"); PythonProcessLauncher py(get_test_name(), @@ -1157,7 +1167,7 @@ namespace tut } template<> template<> - void object::test<19>() + void object::test<20>() { set_test_name("peek() ReadPipe data"); PythonProcessLauncher py(get_test_name(), @@ -1210,7 +1220,32 @@ namespace tut } template<> template<> - void object::test<20>() + void object::test<21>() + { + set_test_name("bad postend"); + std::string pumpname("postend"); + EventListener listener(LLEventPumps::instance().obtain(pumpname)); + LLProcess::Params params; + params.desc = get_test_name(); + params.postend = pumpname; + LLProcessPtr child = LLProcess::create(params); + ensure("shouldn't have launched", ! child); + ensure_equals("number of postend events", listener.mHistory.size(), 1); + LLSD postend(listener.mHistory.front()); + ensure("has id", ! postend.has("id")); + ensure_equals("desc", postend["desc"].asString(), std::string(params.desc)); + ensure_equals("state", postend["state"].asInteger(), LLProcess::UNSTARTED); + ensure("has data", ! postend.has("data")); + std::string error(postend["string"]); + // All we get from canned parameter validation is a bool, so the + // "validation failed" message we ourselves generate can't mention + // "executable" by name. Just check that it's nonempty. + //ensure_contains("error", error, "executable"); + ensure("string", ! error.empty()); + } + + template<> template<> + void object::test<22>() { set_test_name("good postend"); PythonProcessLauncher py(get_test_name(), @@ -1240,32 +1275,48 @@ namespace tut ensure_contains("string", str, "35"); } + struct PostendListener + { + PostendListener(LLProcess::ReadPipe& rpipe, + const std::string& pumpname, + const std::string& expect): + mReadPipe(rpipe), + mExpect(expect), + mTriggered(false) + { + LLEventPumps::instance().obtain(pumpname) + .listen("PostendListener", boost::bind(&PostendListener::postend, this, _1)); + } + + bool postend(const LLSD&) + { + mTriggered = true; + ensure_equals("postend listener", mReadPipe.read(mReadPipe.size()), mExpect); + return false; + } + + LLProcess::ReadPipe& mReadPipe; + std::string mExpect; + bool mTriggered; + }; + template<> template<> - void object::test<21>() + void object::test<23>() { - set_test_name("bad postend"); + set_test_name("all data visible at postend"); + PythonProcessLauncher py(get_test_name(), + "import sys\n" + // note, no '\n' in written data + "sys.stdout.write('partial line')\n"); std::string pumpname("postend"); - EventListener listener(LLEventPumps::instance().obtain(pumpname)); - LLProcess::Params params; - params.desc = get_test_name(); - params.postend = pumpname; - LLProcessPtr child = LLProcess::create(params); - ensure("shouldn't have launched", ! child); - ensure_equals("number of postend events", listener.mHistory.size(), 1); - LLSD postend(listener.mHistory.front()); - ensure("has id", ! postend.has("id")); - ensure_equals("desc", postend["desc"].asString(), std::string(params.desc)); - ensure_equals("state", postend["state"].asInteger(), LLProcess::UNSTARTED); - ensure("has data", ! postend.has("data")); - std::string error(postend["string"]); - // All we get from canned parameter validation is a bool, so the - // "validation failed" message we ourselves generate can't mention - // "executable" by name. Just check that it's nonempty. - //ensure_contains("error", error, "executable"); - ensure("string", ! error.empty()); + py.mParams.files.add(LLProcess::FileParam()); // stdin + py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout + py.mParams.postend = pumpname; + py.launch(); + PostendListener listener(py.mPy->getReadPipe(LLProcess::STDOUT), + pumpname, + "partial line"); + waitfor(*py.mPy); + ensure("postend never triggered", listener.mTriggered); } - - // TODO: - // test EOF -- check logging - } // namespace tut -- cgit v1.2.3 From 40dc3e0d3bee6ff70fb68d9ba7f0a2ee9da96f68 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 29 Feb 2012 19:40:18 -0500 Subject: When constructing a pipe to child stdin on Posix, ignore SIGPIPE. We can't count on every child process reading everything we try to write to it. And if the child terminates with WritePipe data still pending, unless we explicitly suppress it, Posix will hit us with SIGPIPE. That would terminate the calling process, boom. "Ignoring" it means APR gets the correct errno, passes it back to us, we log it, etc. --- indra/llcommon/llprocess.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index edfdebfe87..8ccd39152b 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -145,6 +145,15 @@ public: mConnection = LLEventPumps::instance().obtain("mainloop") .listen(LLEventPump::inventName("WritePipe"), boost::bind(&WritePipeImpl::tick, this, _1)); + +#if ! LL_WINDOWS + // We can't count on every child process reading everything we try to + // write to it. And if the child terminates with WritePipe data still + // pending, unless we explicitly suppress it, Posix will hit us with + // SIGPIPE. That would terminate the viewer, boom. "Ignoring" it means + // APR gets the correct errno, passes it back to us, we log it, etc. + signal(SIGPIPE, SIG_IGN); +#endif } virtual std::ostream& get_ostream() { return mStream; } -- cgit v1.2.3 From 2596816f316e13b717bcdacddad0da48c90c3b6d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 13:43:52 -0500 Subject: Break out TestRecorder class as CaptureLog into wrapllerrs.h. Giving more unit tests the ability to capture and examine log output is generally useful. Renaming the class just makes it less ambiguous: what's a TestRecorder? Something that records tests? --- indra/llcommon/tests/llprocess_test.cpp | 74 +++------------------------------ indra/llcommon/tests/wrapllerrs.h | 65 +++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 68 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 3537133a47..c07a9d3925 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -35,7 +35,7 @@ #include "stringize.h" #include "llsdutil.h" #include "llevents.h" -#include "llerrorcontrol.h" +#include "wrapllerrs.h" #if defined(LL_WINDOWS) #define sleep(secs) _sleep((secs) * 1000) @@ -233,68 +233,6 @@ private: std::string mPath; }; -// statically reference the function in test.cpp... it's short, we could -// replicate, but better to reuse -extern void wouldHaveCrashed(const std::string& message); - -/** - * Capture log messages. This is adapted (simplified) from the one in - * llerror_test.cpp. Sigh, should've broken that out into a separate header - * file, but time for this project is short... - */ -class TestRecorder : public LLError::Recorder -{ -public: - TestRecorder(): - // Mostly what we're trying to accomplish by saving and resetting - // LLError::Settings is to bypass the default RecordToStderr and - // RecordToWinDebug Recorders. As these are visible only inside - // llerror.cpp, we can't just call LLError::removeRecorder() with - // each. For certain tests we need to produce, capture and examine - // DEBUG log messages -- but we don't want to spam the user's console - // with that output. If it turns out that saveAndResetSettings() has - // some bad effect, give up and just let the DEBUG level log messages - // display. - mOldSettings(LLError::saveAndResetSettings()) - { - LLError::setFatalFunction(wouldHaveCrashed); - LLError::setDefaultLevel(LLError::LEVEL_DEBUG); - LLError::addRecorder(this); - } - - ~TestRecorder() - { - LLError::removeRecorder(this); - LLError::restoreSettings(mOldSettings); - } - - void recordMessage(LLError::ELevel level, - const std::string& message) - { - mMessages.push_back(message); - } - - /// Don't assume the message we want is necessarily the LAST log message - /// emitted by the underlying code; search backwards through all messages - /// for the sought string. - std::string messageWith(const std::string& search) - { - for (std::list::const_reverse_iterator rmi(mMessages.rbegin()), - rmend(mMessages.rend()); - rmi != rmend; ++rmi) - { - if (rmi->find(search) != std::string::npos) - return *rmi; - } - // failed to find any such message - return std::string(); - } - - typedef std::list MessageList; - MessageList mMessages; - LLError::Settings* mOldSettings; -}; - /***************************************************************************** * TUT *****************************************************************************/ @@ -843,7 +781,7 @@ namespace tut void object::test<10>() { set_test_name("'bogus' test"); - TestRecorder recorder; + CaptureLog recorder; PythonProcessLauncher py(get_test_name(), "print 'Hello world'\n"); py.mParams.files.add(LLProcess::FileParam("bogus")); @@ -874,7 +812,7 @@ namespace tut set_test_name("'tpipe' test"); // Replace this test with one or more real 'tpipe' tests when we // implement 'tpipe' support - TestRecorder recorder; + CaptureLog recorder; PythonProcessLauncher py(get_test_name(), "print 'Hello world'\n"); py.mParams.files.add(LLProcess::FileParam()); @@ -892,7 +830,7 @@ namespace tut set_test_name("'npipe' test"); // Replace this test with one or more real 'npipe' tests when we // implement 'npipe' support - TestRecorder recorder; + CaptureLog recorder; PythonProcessLauncher py(get_test_name(), "print 'Hello world'\n"); py.mParams.files.add(LLProcess::FileParam()); @@ -909,7 +847,7 @@ namespace tut void object::test<14>() { set_test_name("internal pipe name warning"); - TestRecorder recorder; + CaptureLog recorder; PythonProcessLauncher py(get_test_name(), "import sys\n" "sys.exit(7)\n"); @@ -965,7 +903,7 @@ namespace tut #define EXPECT_FAIL_WITH_LOG(EXPECT, CODE) \ do \ { \ - TestRecorder recorder; \ + CaptureLog recorder; \ ensure(#CODE " succeeded", ! (CODE)); \ ensure("wrong log message", ! recorder.messageWith(EXPECT).empty()); \ } while (0) diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index ffda84729b..a61f8451b3 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -30,6 +30,14 @@ #define LL_WRAPLLERRS_H #include "llerrorcontrol.h" +#include +#include +#include +#include + +// statically reference the function in test.cpp... it's short, we could +// replicate, but better to reuse +extern void wouldHaveCrashed(const std::string& message); struct WrapLL_ERRS { @@ -70,4 +78,61 @@ struct WrapLL_ERRS LLError::FatalFunction mPriorFatal; }; +/** + * Capture log messages. This is adapted (simplified) from the one in + * llerror_test.cpp. + */ +class CaptureLog : public LLError::Recorder +{ +public: + CaptureLog(): + // Mostly what we're trying to accomplish by saving and resetting + // LLError::Settings is to bypass the default RecordToStderr and + // RecordToWinDebug Recorders. As these are visible only inside + // llerror.cpp, we can't just call LLError::removeRecorder() with + // each. For certain tests we need to produce, capture and examine + // DEBUG log messages -- but we don't want to spam the user's console + // with that output. If it turns out that saveAndResetSettings() has + // some bad effect, give up and just let the DEBUG level log messages + // display. + mOldSettings(LLError::saveAndResetSettings()) + { + LLError::setFatalFunction(wouldHaveCrashed); + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LLError::addRecorder(this); + } + + ~CaptureLog() + { + LLError::removeRecorder(this); + LLError::restoreSettings(mOldSettings); + } + + void recordMessage(LLError::ELevel level, + const std::string& message) + { + mMessages.push_back(message); + } + + /// Don't assume the message we want is necessarily the LAST log message + /// emitted by the underlying code; search backwards through all messages + /// for the sought string. + std::string messageWith(const std::string& search) + { + for (std::list::const_reverse_iterator rmi(mMessages.rbegin()), + rmend(mMessages.rend()); + rmi != rmend; ++rmi) + { + if (rmi->find(search) != std::string::npos) + return *rmi; + } + // failed to find any such message + return std::string(); + } + + typedef std::list MessageList; + MessageList mMessages; + LLError::Settings* mOldSettings; +}; + #endif /* ! defined(LL_WRAPLLERRS_H) */ -- cgit v1.2.3 From 22fcb563ce45e64f23c9911bdcd07b0086bc892a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 14:27:32 -0500 Subject: Log better error message in case of apr_proc_create() failure. We were using uniform macro to report the APR function and its C++ parameter expressions. But specifically for apr_proc_create() failure, better to report the command we're attempting to execute. --- indra/llcommon/llprocess.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 8ccd39152b..9c49517598 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -617,8 +617,14 @@ LLProcess::LLProcess(const LLSDOrParams& params): // terminate with a null pointer argv.push_back(NULL); - // Launch! The NULL would be the environment block, if we were passing one. - chkapr(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, gAPRPoolp)); + // Launch! The NULL would be the environment block, if we were passing + // one. Hand-expand chkapr() macro so we can fill in the actual command + // string instead of the variable names. + if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, + gAPRPoolp))) + { + throw LLProcessError(STRINGIZE(params << " failed")); + } // arrange to call status_callback() apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, -- cgit v1.2.3 From f904867720291bc63ea5e140194069d29f70fb40 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 14:33:23 -0500 Subject: Make CaptureLog::withMessage() raise tut::failure if not found. All known callers were using ensure(! withMessage(...).empty()). Centralize that logic. Make failure message report the string being sought and the log messages in which it wasn't found. In case someone does want to permit the search to fail, add an optional 'required' parameter, default true. Leverage new functionality in llprocess_test.cpp. --- indra/llcommon/tests/llprocess_test.cpp | 6 +----- indra/llcommon/tests/wrapllerrs.h | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index c07a9d3925..6103764f24 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -788,7 +788,6 @@ namespace tut py.mPy = LLProcess::create(py.mParams); ensure("should have rejected 'bogus'", ! py.mPy); std::string message(recorder.messageWith("bogus")); - ensure("did not log 'bogus' type", ! message.empty()); ensure_contains("did not name 'stdin'", message, "stdin"); } @@ -820,7 +819,6 @@ namespace tut py.mPy = LLProcess::create(py.mParams); ensure("should have rejected 'tpipe'", ! py.mPy); std::string message(recorder.messageWith("tpipe")); - ensure("did not log 'tpipe' type", ! message.empty()); ensure_contains("did not name 'stdout'", message, "stdout"); } @@ -839,7 +837,6 @@ namespace tut py.mPy = LLProcess::create(py.mParams); ensure("should have rejected 'npipe'", ! py.mPy); std::string message(recorder.messageWith("npipe")); - ensure("did not log 'npipe' type", ! message.empty()); ensure_contains("did not name 'stderr'", message, "stderr"); } @@ -856,7 +853,6 @@ namespace tut ensure_equals("Status.mState", py.mPy->getStatus().mState, LLProcess::EXITED); ensure_equals("Status.mData", py.mPy->getStatus().mData, 7); std::string message(recorder.messageWith("not yet supported")); - ensure("did not log pipe name warning", ! message.empty()); ensure_contains("log message did not mention internal pipe name", message, "somename"); } @@ -905,7 +901,7 @@ namespace tut { \ CaptureLog recorder; \ ensure(#CODE " succeeded", ! (CODE)); \ - ensure("wrong log message", ! recorder.messageWith(EXPECT).empty()); \ + recorder.messageWith(EXPECT); \ } while (0) template<> template<> diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index a61f8451b3..28ffbf517f 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -29,11 +29,13 @@ #if ! defined(LL_WRAPLLERRS_H) #define LL_WRAPLLERRS_H +#include #include "llerrorcontrol.h" #include #include #include #include +#include // statically reference the function in test.cpp... it's short, we could // replicate, but better to reuse @@ -117,17 +119,26 @@ public: /// Don't assume the message we want is necessarily the LAST log message /// emitted by the underlying code; search backwards through all messages /// for the sought string. - std::string messageWith(const std::string& search) + std::string messageWith(const std::string& search, bool required=true) { - for (std::list::const_reverse_iterator rmi(mMessages.rbegin()), - rmend(mMessages.rend()); + for (MessageList::const_reverse_iterator rmi(mMessages.rbegin()), rmend(mMessages.rend()); rmi != rmend; ++rmi) { if (rmi->find(search) != std::string::npos) return *rmi; } // failed to find any such message - return std::string(); + if (! required) + return std::string(); + + std::ostringstream out; + out << "failed to find '" << search << "' in captured log messages:"; + for (MessageList::const_iterator mi(mMessages.begin()), mend(mMessages.end()); + mi != mend; ++mi) + { + out << '\n' << *mi; + } + throw tut::failure(out.str()); } typedef std::list MessageList; -- cgit v1.2.3 From c8032de94e7fcad3cc8ce45db2d20cb1664ebea9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 14:37:41 -0500 Subject: Add NamedExtTempFile to invent arbitrary name with specified ext. This arises, for instance, if you want to be able to create a temporary Python module you can import from test scripts. The Python module file MUST have the .py extension. --- indra/test/namedtempfile.h | 97 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index aa7058b111..6069064627 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -12,6 +12,7 @@ #if ! defined(LL_NAMEDTEMPFILE_H) #define LL_NAMEDTEMPFILE_H +#include "llerror.h" #include "llapr.h" #include "apr_file_io.h" #include @@ -28,6 +29,7 @@ */ class NamedTempFile: public boost::noncopyable { + LOG_CLASS(NamedTempFile); public: NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp): mPool(pool) @@ -53,12 +55,12 @@ public: createFile(pfx, func); } - ~NamedTempFile() + virtual ~NamedTempFile() { ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool)); } - std::string getName() const { return mPath; } + virtual std::string getName() const { return mPath; } void peep() { @@ -70,7 +72,7 @@ public: std::cout << "---\n"; } -private: +protected: void createFile(const std::string& pfx, const Streamer& func) { // Create file in a temporary place. @@ -111,4 +113,93 @@ private: apr_pool_t* mPool; }; +/** + * Create a NamedTempFile with a specified filename extension. This is useful + * when, for instance, you must be able to use the file in a Python import + * statement. + * + * A NamedExtTempFile actually has two different names. We retain the original + * no-extension name as a placeholder in the temp directory to ensure + * uniqueness; to that we link the name plus the desired extension. Naturally, + * both must be removed on destruction. + */ +class NamedExtTempFile: public NamedTempFile +{ + LOG_CLASS(NamedExtTempFile); +public: + NamedExtTempFile(const std::string& ext, const std::string& content, apr_pool_t* pool=gAPRPoolp): + NamedTempFile(remove_dot(ext), content, pool), + mLink(mPath + ensure_dot(ext)) + { + linkto(mLink); + } + + // Disambiguate when passing string literal + NamedExtTempFile(const std::string& ext, const char* content, apr_pool_t* pool=gAPRPoolp): + NamedTempFile(remove_dot(ext), content, pool), + mLink(mPath + ensure_dot(ext)) + { + linkto(mLink); + } + + NamedExtTempFile(const std::string& ext, const Streamer& func, apr_pool_t* pool=gAPRPoolp): + NamedTempFile(remove_dot(ext), func, pool), + mLink(mPath + ensure_dot(ext)) + { + linkto(mLink); + } + + virtual ~NamedExtTempFile() + { + ll_apr_assert_status(apr_file_remove(mLink.c_str(), mPool)); + } + + // Since the caller has gone to the trouble to create the name with the + // extension, that should be the name we return. In this class, mPath is + // just a placeholder to ensure that future createFile() calls won't + // collide. + virtual std::string getName() const { return mLink; } + + static std::string ensure_dot(const std::string& ext) + { + if (ext.empty()) + { + // What SHOULD we do when the caller makes a point of using + // NamedExtTempFile to generate a file with a particular + // extension, then passes an empty extension? Use just "."? That + // sounds like a Bad Idea, especially on Windows. Treat that as a + // coding error. + LL_ERRS("NamedExtTempFile") << "passed empty extension" << LL_ENDL; + } + if (ext[0] == '.') + { + return ext; + } + return std::string(".") + ext; + } + + static std::string remove_dot(const std::string& ext) + { + std::string::size_type found = ext.find_first_not_of("."); + if (found == std::string::npos) + { + return ext; + } + return ext.substr(found); + } + +private: + void linkto(const std::string& path) + { + // This method assumes that since mPath (without extension) is + // guaranteed by apr_file_mktemp() to be unique, then (mPath + any + // extension) is also unique. This is likely, though not guaranteed: + // files could be created in the same temp directory other than by + // this class. + ll_apr_assert_status(apr_file_link(mPath.c_str(), path.c_str())); + } + + std::string mLink; +}; + #endif /* ! defined(LL_NAMEDTEMPFILE_H) */ -- cgit v1.2.3 From f0612f6fc424ab4726ef187f41a257c0abdd1d34 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 17:30:50 -0500 Subject: Allow CaptureLog's consumer to specify desired log level. Of course, given the way the log machinery works, it's really "everything at that level or stronger." --- indra/llcommon/tests/wrapllerrs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 28ffbf517f..10cf25b39e 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -87,7 +87,7 @@ struct WrapLL_ERRS class CaptureLog : public LLError::Recorder { public: - CaptureLog(): + CaptureLog(LLError::ELevel level=LLError::LEVEL_DEBUG): // Mostly what we're trying to accomplish by saving and resetting // LLError::Settings is to bypass the default RecordToStderr and // RecordToWinDebug Recorders. As these are visible only inside @@ -100,7 +100,7 @@ public: mOldSettings(LLError::saveAndResetSettings()) { LLError::setFatalFunction(wouldHaveCrashed); - LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LLError::setDefaultLevel(level); LLError::addRecorder(this); } -- cgit v1.2.3 From 0ef99cd33b4b450712e105e8ab448c2dab7081d2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 17:46:44 -0500 Subject: Add LLLeap class, initial implementation, initial unit tests. Instantiating LLLeap with a command to execute a particular child process sets up machinery to speak LLSD Event API Plugin protocol with that child process. LLLeap is an LLInstanceTracker subclass, so the code that instantiates need not hold the pointer. LLLeap monitors child-process termination and deletes itself when done. --- indra/llcommon/CMakeLists.txt | 3 + indra/llcommon/llleap.cpp | 379 +++++++++++++++++++++++++++++++++++ indra/llcommon/llleap.h | 80 ++++++++ indra/llcommon/tests/llleap_test.cpp | 261 ++++++++++++++++++++++++ 4 files changed, 723 insertions(+) create mode 100644 indra/llcommon/llleap.cpp create mode 100644 indra/llcommon/llleap.h create mode 100644 indra/llcommon/tests/llleap_test.cpp diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 1cab648cfa..47a8aa96aa 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -63,6 +63,7 @@ set(llcommon_SOURCE_FILES llheartbeat.cpp llinitparam.cpp llinstancetracker.cpp + llleap.cpp llliveappconfig.cpp lllivefile.cpp lllog.cpp @@ -180,6 +181,7 @@ set(llcommon_HEADER_FILES llinstancetracker.h llkeythrottle.h lllazy.h + llleap.h lllistenerwrapper.h lllinkedqueue.h llliveappconfig.h @@ -333,6 +335,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") # *TODO - reenable these once tcmalloc libs no longer break the build. diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp new file mode 100644 index 0000000000..dddf1286ac --- /dev/null +++ b/indra/llcommon/llleap.cpp @@ -0,0 +1,379 @@ +/** + * @file llleap.cpp + * @author Nat Goodspeed + * @date 2012-02-20 + * @brief Implementation for llleap. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llleap.h" +// STL headers +#include +#include +// std headers +// external library headers +#include +#include +#include +// other Linden headers +#include "llerror.h" +#include "llstring.h" +#include "llprocess.h" +#include "llevents.h" +#include "stringize.h" +#include "llsdutil.h" +#include "llsdserialize.h" + +LLLeap::LLLeap() {} +LLLeap::~LLLeap() {} + +class LLLeapImpl: public LLLeap +{ +public: + // Called only by LLLeap::create() + LLLeapImpl(const std::string& desc, const std::vector& plugin): + // We might reassign mDesc in the constructor body if it's empty here. + mDesc(desc), + // We expect multiple LLLeapImpl instances. Definitely tweak + // mDonePump's name for uniqueness. + mDonePump("LLLeap", true), + // Troubling thought: what if one plugin intentionally messes with + // another plugin? LLEventPump names are in a single global namespace. + // Try to make that more difficult by generating a UUID for the reply- + // pump name -- so it should NOT need tweaking for uniqueness. + mReplyPump(LLUUID::generateNewID().asString()), + mExpect(0) + { + // Rule out empty vector + if (plugin.empty()) + { + throw Error("no plugin command"); + } + + // Don't leave desc empty either, but in this case, if we weren't + // given one, we'll fake one. + if (desc.empty()) + { + mDesc = LLProcess::basename(plugin[0]); + // how about a toLower() variant that returns the transformed string?! + std::string desclower(mDesc); + LLStringUtil::toLower(desclower); + // If we're running a Python script, use the script name for the + // desc instead of just 'python'. Arguably we should check for + // more different interpreters as well, but there's a reason to + // notice Python specially: we provide Python LLSD serialization + // support, so there's a pretty good reason to implement plugins + // in that language. + if (plugin.size() >= 2 && (desclower == "python" || desclower == "python.exe")) + { + mDesc = LLProcess::basename(plugin[1]); + } + } + + // Listen for child "termination" right away to catch launch errors. + mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::bad_launch, this, _1)); + + // Okay, launch child. + LLProcess::Params params; + params.desc = mDesc; + std::vector::const_iterator pi(plugin.begin()), pend(plugin.end()); + params.executable = *pi++; + for ( ; pi != pend; ++pi) + { + params.args.add(*pi); + } + params.files.add(LLProcess::FileParam("pipe")); // stdin + params.files.add(LLProcess::FileParam("pipe")); // stdout + params.files.add(LLProcess::FileParam("pipe")); // stderr + params.postend = mDonePump.getName(); + mChild = LLProcess::create(params); + // If that didn't work, no point in keeping this LLLeap object. + if (! mChild) + { + throw Error(STRINGIZE("failed to run " << mDesc)); + } + + // Okay, launch apparently worked. Change our mDonePump listener. + mDonePump.stopListening("LLLeap"); + mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::done, this, _1)); + + // Child might pump large volumes of data through either stdout or + // stderr. Don't bother copying all that data into notification event. + LLProcess::ReadPipe + &childout(mChild->getReadPipe(LLProcess::STDOUT)), + &childerr(mChild->getReadPipe(LLProcess::STDERR)); + childout.setLimit(20); + childerr.setLimit(20); + + // Serialize any event received on mReplyPump to our child's stdin, + // suitably enriched with the pump name on which it was received. + mStdinConnection = mReplyPump + .listen("LLLeap", + boost::bind(&LLLeapImpl::wstdin, this, mReplyPump.getName(), _1)); + + // Listening on stdout is stateful. In general, we're either waiting + // for the length prefix or waiting for the specified length of data. + // We address that with two different listener methods -- one of which + // is blocked at any given time. + mStdoutConnection = childout.getPump() + .listen("prefix", boost::bind(&LLLeapImpl::rstdout, this, _1)); + mStdoutDataConnection = childout.getPump() + .listen("data", boost::bind(&LLLeapImpl::rstdoutData, this, _1)); + mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection)); + + // Log anything sent up through stderr. When a typical program + // encounters an error, it writes its error message to stderr and + // terminates with nonzero exit code. In particular, the Python + // interpreter behaves that way. More generally, though, a plugin + // author can log whatever s/he wants to the viewer log using stderr. + mStderrConnection = childerr.getPump() + .listen("LLLeap", boost::bind(&LLLeapImpl::rstderr, this, _1)); + + // Send child a preliminary event reporting our own reply-pump name -- + // which would otherwise be pretty tricky to guess! +// TODO TODO inject name of command pump here. + wstdin(mReplyPump.getName(), + LLSDMap + ("command", LLSD()) + // Include LLLeap features -- this may be important for child to + // construct (or recognize) current protocol. + ("features", LLSD::emptyMap())); + } + + // Normally we'd expect to arrive here only via done() + virtual ~LLLeapImpl() + { + LL_DEBUGS("LLLeap") << "destroying LLLeap(\"" << mDesc << "\")" << LL_ENDL; + } + + // Listener for failed launch attempt + bool bad_launch(const LLSD& data) + { + LL_WARNS("LLLeap") << data["string"].asString() << LL_ENDL; + return false; + } + + // Listener for child-process termination + bool done(const LLSD& data) + { + // Log the termination + LL_INFOS("LLLeap") << data["string"].asString() << LL_ENDL; + + // Any leftover data at this moment are because protocol was not + // satisfied. Possibly the child was interrupted in the middle of + // sending a message, possibly the child didn't flush stdout before + // terminating, possibly it's just garbage. Log its existence but + // discard it. + LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT)); + if (childout.size()) + { + LLProcess::ReadPipe::size_type + peeklen((std::min)(LLProcess::ReadPipe::size_type(50), childout.size())); + LL_WARNS("LLLeap") << "Discarding final " << childout.size() << " bytes: " + << childout.peek(0, peeklen) << "..." << LL_ENDL; + } + + // Kill this instance. MUST BE LAST before return! + delete this; + return false; + } + + // Listener for events on mReplyPump: send to child stdin + bool wstdin(const std::string& pump, const LLSD& data) + { + LLSD packet(LLSDMap("pump", pump)("data", data)); + + std::ostringstream buffer; + buffer << LLSDNotationStreamer(packet); + + LL_DEBUGS("EventHost") << "Sending: " << buffer.tellp() << ':'; + std::string::size_type truncate(80); + if (buffer.tellp() <= truncate) + { + LL_CONT << buffer.str(); + } + else + { + LL_CONT << buffer.str().substr(0, truncate) << "..."; + } + LL_CONT << LL_ENDL; + + LLProcess::WritePipe& childin(mChild->getWritePipe(LLProcess::STDIN)); + childin.get_ostream() << buffer.tellp() << ':' << buffer.str() << std::flush; + return false; + } + + // Initial state of stateful listening on child stdout: wait for a length + // prefix, followed by ':'. + bool rstdout(const LLSD& data) + { + LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT)); + // It's possible we got notified of a couple digit characters without + // seeing the ':' -- unlikely, but still. Until we see ':', keep + // waiting. + if (childout.contains(':')) + { + std::istream& childstream(childout.get_istream()); + // Saw ':', read length prefix and store in mExpect. + size_t expect; + childstream >> expect; + int colon(childstream.get()); + if (colon != ':') + { + // Protocol failure. Clear out the rest of the pending data in + // childout (well, up to a max length) to log what was wrong. + LLProcess::ReadPipe::size_type + readlen((std::min)(childout.size(), LLProcess::ReadPipe::size_type(80))); + std::vector buffer(readlen + 1); + childstream.read(&buffer[0], readlen); + buffer[childstream.gcount()] = '\0'; + bad_protocol(STRINGIZE(expect << char(colon) << &buffer[0])); + } + else + { + // Saw length prefix, saw colon, life is good. Now wait for + // that length of data to arrive. + mExpect = expect; + // Block calls to this method; resetting mBlocker unblocks + // calls to the other method. + mBlocker.reset(new LLEventPump::Blocker(mStdoutConnection)); + // Go check if we've already received all the advertised data. + if (childout.size()) + { + LLSD updata(data); + updata["len"] = LLSD::Integer(childout.size()); + rstdoutData(updata); + } + } + } + else if (childout.contains('\n')) + { + // Since this is the initial listening state, this is where we'd + // arrive if the child isn't following protocol at all -- say + // because the user specified 'ls' or some darn thing. + bad_protocol(childout.getline()); + } + return false; + } + + // State in which we listen on stdout for the specified length of data to + // arrive. + bool rstdoutData(const LLSD& data) + { + LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT)); + // Until we've accumulated the promised length of data, keep waiting. + if (childout.size() >= mExpect) + { + // Ready to rock and roll. + LLSD data; + LLPointer parser(new LLSDNotationParser()); + S32 parse_status(parser->parse(childout.get_istream(), data, mExpect)); + if (parse_status == LLSDParser::PARSE_FAILURE) + { + bad_protocol("unparseable LLSD data"); + } + else if (! (data.isMap() && data["pump"].isString() && data.has("data"))) + { + // we got an LLSD object, but it lacks required keys + bad_protocol("missing 'pump' or 'data'"); + } + else + { + // The LLSD object we got from our stream contains the keys we + // need. + LLEventPumps::instance().obtain(data["pump"]).post(data["data"]); + // Block calls to this method; resetting mBlocker unblocks calls + // to the other method. + mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection)); + // Go check for any more pending events in the buffer. + if (childout.size()) + { + LLSD updata(data); + data["len"] = LLSD::Integer(childout.size()); + rstdout(updata); + } + } + } + return false; + } + + void bad_protocol(const std::string& data) + { + LL_WARNS("LLLeap") << mDesc << ": invalid protocol: " << data << LL_ENDL; + // No point in continuing to run this child. + mChild->kill(); + } + + // Listen on child stderr and log everything that arrives + bool rstderr(const LLSD& data) + { + LLProcess::ReadPipe& childerr(mChild->getReadPipe(LLProcess::STDERR)); + // We might have gotten a notification involving only a partial line + // -- or multiple lines. Read all complete lines; stop when there's + // only a partial line left. + while (childerr.contains('\n')) + { + // DO NOT make calls with side effects in a logging statement! If + // that log level is suppressed, your side effects WON'T HAPPEN. + std::string line(childerr.getline()); + // Log the received line. Prefix it with the desc so we know which + // plugin it's from. This method name rstderr() is intentionally + // chosen to further qualify the log output. + LL_INFOS("LLLeap") << mDesc << ": " << line << LL_ENDL; + } + // What if child writes a final partial line to stderr? + if (data["eof"].asBoolean() && childerr.size()) + { + std::string rest(childerr.read(childerr.size())); + // Read all remaining bytes and log. + LL_INFOS("LLLeap") << mDesc << ": " << rest << LL_ENDL; + } + return false; + } + +private: + std::string mDesc; + LLEventStream mDonePump; + LLEventStream mReplyPump; + LLProcessPtr mChild; + LLTempBoundListener + mStdinConnection, mStdoutConnection, mStdoutDataConnection, mStderrConnection; + boost::scoped_ptr mBlocker; + LLProcess::ReadPipe::size_type mExpect; +}; + +// This must follow the declaration of LLLeapImpl, so it may as well be last. +LLLeap* LLLeap::create(const std::string& desc, const std::vector& plugin, bool exc) +{ + // If caller is willing to permit exceptions, just instantiate. + if (exc) + return new LLLeapImpl(desc, plugin); + + // Caller insists on suppressing LLLeap::Error. Very well, catch it. + try + { + return new LLLeapImpl(desc, plugin); + } + catch (const LLLeap::Error&) + { + return NULL; + } +} + +LLLeap* LLLeap::create(const std::string& desc, const std::string& plugin, bool exc) +{ + // Use LLStringUtil::getTokens() to parse the command line + return create(desc, + LLStringUtil::getTokens(plugin, + " \t\r\n", // drop_delims + "", // no keep_delims + "\"'", // either kind of quotes + "\\"), // backslash escape + exc); +} diff --git a/indra/llcommon/llleap.h b/indra/llcommon/llleap.h new file mode 100644 index 0000000000..1a1ad23d39 --- /dev/null +++ b/indra/llcommon/llleap.h @@ -0,0 +1,80 @@ +/** + * @file llleap.h + * @author Nat Goodspeed + * @date 2012-02-20 + * @brief Class that implements "LLSD Event API Plugin" + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLLEAP_H) +#define LL_LLLEAP_H + +#include "llinstancetracker.h" +#include +#include +#include + +/** + * LLSD Event API Plugin class. Because instances are managed by + * LLInstanceTracker, you can instantiate LLLeap and forget the instance + * unless you need it later. Each instance manages an LLProcess; when the + * child process terminates, LLLeap deletes itself. We don't require a unique + * LLInstanceTracker key. + * + * The fact that a given LLLeap instance vanishes when its child process + * terminates makes it problematic to store an LLLeap* anywhere. Any stored + * LLLeap* pointer should be validated before use by + * LLLeap::getInstance(LLLeap*) (see LLInstanceTracker). + */ +class LL_COMMON_API LLLeap: public LLInstanceTracker +{ +public: + /** + * Pass a brief string description, mostly for logging purposes. The desc + * need not be unique, but obviously the clearer we can make it, the + * easier these things will be to debug. The strings are the command line + * used to launch the desired plugin process. + * + * Pass exc=false to suppress LLLeap::Error exception. Obviously in that + * case the caller cannot discover the nature of the error, merely that an + * error of some kind occurred (because create() returned NULL). Either + * way, the error is logged. + */ + static LLLeap* create(const std::string& desc, const std::vector& plugin, + bool exc=true); + + /** + * Pass a brief string description, mostly for logging purposes. The desc + * need not be unique, but obviously the clearer we can make it, the + * easier these things will be to debug. Pass a command-line string + * to launch the desired plugin process. + * + * Pass exc=false to suppress LLLeap::Error exception. Obviously in that + * case the caller cannot discover the nature of the error, merely that an + * error of some kind occurred (because create() returned NULL). Either + * way, the error is logged. + */ + static LLLeap* create(const std::string& desc, const std::string& plugin, + bool exc=true); + + /** + * Exception thrown for invalid create() arguments, e.g. no plugin + * program. This is more resiliant than an LL_ERRS failure, because the + * string(s) passed to create() might come from an external source. This + * way the caller can catch LLLeap::Error and try to recover. + */ + struct Error: public std::runtime_error + { + Error(const std::string& what): std::runtime_error(what) {} + }; + + virtual ~LLLeap(); + +protected: + LLLeap(); +}; + +#endif /* ! defined(LL_LLLEAP_H) */ diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp new file mode 100644 index 0000000000..0264442437 --- /dev/null +++ b/indra/llcommon/tests/llleap_test.cpp @@ -0,0 +1,261 @@ +/** + * @file llleap_test.cpp + * @author Nat Goodspeed + * @date 2012-02-21 + * @brief Test for llleap. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llleap.h" +// STL headers +// std headers +// external library headers +#include +#include +#include +// other Linden headers +#include "../test/lltut.h" +#include "../test/namedtempfile.h" +#include "../test/manageapr.h" +#include "../test/catch_and_store_what_in.h" +#include "wrapllerrs.h" +#include "llevents.h" +#include "llprocess.h" +#include "stringize.h" +#include "StringVec.h" + +using boost::assign::list_of; + +static ManageAPR manager; + +StringVec sv(const StringVec& listof) { return listof; } + +#if defined(LL_WINDOWS) +#define sleep(secs) _sleep((secs) * 1000) +#endif + +void waitfor(const std::vector& instances) +{ + int i, timeout = 60; + for (i = 0; i < timeout; ++i) + { + // Every iteration, test whether any of the passed LLLeap instances + // still exist (are still running). + std::vector::const_iterator vli(instances.begin()), vlend(instances.end()); + for ( ; vli != vlend; ++vli) + { + // getInstance() returns NULL if it's terminated/gone, non-NULL if + // it's still running + if (LLLeap::getInstance(*vli)) + break; + } + // If we made it through all of 'instances' without finding one that's + // still running, we're done. + if (vli == vlend) + return; + // Found an instance that's still running. Wait and pump LLProcess. + sleep(1); + LLEventPumps::instance().obtain("mainloop").post(LLSD()); + } + tut::ensure("timed out without terminating", i < timeout); +} + +void waitfor(LLLeap* instance) +{ + std::vector instances; + instances.push_back(instance); + waitfor(instances); +} + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llleap_data + { + llleap_data(): + reader(".py", + // This logic is adapted from vita.viewerclient.receiveEvent() + "import sys\n" + "LEFTOVER = ''\n" + "class ProtocolError(Exception):\n" + " pass\n" + "def get():\n" + " global LEFTOVER\n" + " hdr = LEFTOVER\n" + " if ':' not in hdr:\n" + " hdr += sys.stdin.read(20)\n" + " if not hdr:\n" + " sys.exit(0)\n" + " parts = hdr.split(':', 1)\n" + " if len(parts) != 2:\n" + " raise ProtocolError('Expected len:data, got %r' % hdr)\n" + " try:\n" + " length = int(parts[0])\n" + " except ValueError:\n" + " raise ProtocolError('Non-numeric len %r' % parts[0])\n" + " del parts[0]\n" + " received = len(parts[0])\n" + " while received < length:\n" + " parts.append(sys.stdin.read(length - received))\n" + " received += len(parts[-1])\n" + " if received > length:\n" + " excess = length - received\n" + " LEFTOVER = parts[-1][excess:]\n" + " parts[-1] = parts[-1][:excess]\n" + " data = ''.join(parts)\n" + " assert len(data) == length\n" + " return data\n"), + // Get the actual pathname of the NamedExtTempFile and trim off + // the ".py" extension. (We could cache reader.getName() in a + // separate member variable, but I happen to know getName() just + // returns a NamedExtTempFile member rather than performing any + // computation, so I don't mind calling it twice.) Then take the + // basename. + reader_module(LLProcess::basename( + reader.getName().substr(0, reader.getName().length()-3))), + pPYTHON(getenv("PYTHON")), + PYTHON(pPYTHON? pPYTHON : "") + { + ensure("Set PYTHON to interpreter pathname", pPYTHON); + } + NamedExtTempFile reader; + const std::string reader_module; + const char* pPYTHON; + const std::string PYTHON; + }; + typedef test_group llleap_group; + typedef llleap_group::object object; + llleap_group llleapgrp("llleap"); + + template<> template<> + void object::test<1>() + { + set_test_name("multiple LLLeap instances"); + NamedTempFile script("py", + "import time\n" + "time.sleep(1)\n"); + std::vector instances; + instances.push_back(LLLeap::create(get_test_name(), + sv(list_of(PYTHON)(script.getName())))); + instances.push_back(LLLeap::create(get_test_name(), + sv(list_of(PYTHON)(script.getName())))); + // In this case we're simply establishing that two LLLeap instances + // can coexist without throwing exceptions or bombing in any other + // way. Wait for them to terminate. + waitfor(instances); + } + + template<> template<> + void object::test<2>() + { + set_test_name("stderr to log"); + NamedTempFile script("py", + "import sys\n" + "sys.stderr.write('''Hello from Python!\n" + "note partial line''')\n"); + CaptureLog log(LLError::LEVEL_INFO); + waitfor(LLLeap::create(get_test_name(), + sv(list_of(PYTHON)(script.getName())))); + log.messageWith("Hello from Python!"); + log.messageWith("note partial line"); + } + + template<> template<> + void object::test<3>() + { + set_test_name("empty plugin vector"); + std::string threw; + try + { + LLLeap::create("empty", StringVec()); + } + CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error) + ensure_contains("LLLeap::Error", threw, "no plugin"); + // try the suppress-exception variant + ensure("bad launch returned non-NULL", ! LLLeap::create("empty", StringVec(), false)); + } + + template<> template<> + void object::test<4>() + { + set_test_name("bad launch"); + // Synthesize bogus executable name + std::string BADPYTHON(PYTHON.substr(0, PYTHON.length()-1) + "x"); + CaptureLog log; + std::string threw; + try + { + LLLeap::create("bad exe", BADPYTHON); + } + CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error) + ensure_contains("LLLeap::create() didn't throw", threw, "failed"); + log.messageWith("failed"); + log.messageWith(BADPYTHON); + // try the suppress-exception variant + ensure("bad launch returned non-NULL", ! LLLeap::create("bad exe", BADPYTHON, false)); + } + + // Mimic a dummy little LLEventAPI that merely sends a reply back to its + // requester on the "reply" pump. + struct API + { + API(): + mPump("API", true) + { + mPump.listen("API", boost::bind(&API::entry, this, _1)); + } + + bool entry(const LLSD& request) + { + LLEventPumps::instance().obtain(request["reply"]).post("ack"); + return false; + } + + LLEventStream mPump; + }; + + template<> template<> + void object::test<5>() + { + set_test_name("round trip"); + API api; + NamedTempFile script("py", + boost::lambda::_1 << + "import re\n" + "import sys\n" + "from " << reader_module << " import get\n" + // this will throw if the initial write to stdin + // doesn't follow len:data protocol + "initial = get()\n" + "match = re.search(r\"'pump':'(.*?)'\", initial)\n" + // this will throw if we couldn't find + // 'pump':'etc.' in the initial write + "reply = match.group(1)\n" + "req = '''\\\n" + "{'pump':'" << api.mPump.getName() << "','data':{'reply':'%s'}}\\\n" + "''' % reply\n" + // make a request on our little API + "sys.stdout.write(':'.join((str(len(req)), req)))\n" + "sys.stdout.flush()\n" + // wait for its response + "resp = get()\n" + // it would be cleverer to be order-insensitive + // about 'data' and 'pump'; hopefully the C++ + // serializer doesn't change its rules soon + "result = 'good' if (resp == \"{'data':'ack','pump':'%s'}\" % reply)\\\n" + " else 'bad: ' + resp\n" + // write 'good' or 'bad' to the log so we can observe + "sys.stderr.write(result)\n"); + CaptureLog log(LLError::LEVEL_INFO); + waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); + log.messageWith("good"); + } +} // namespace tut -- cgit v1.2.3 From cfe37cbfb5bdec0aa01c86a608ecc6cdc3df8a2a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 22:45:16 -0500 Subject: Break out std::ostream << CaptureLog routine for general use. --- indra/llcommon/tests/wrapllerrs.h | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 10cf25b39e..a9c0c19c28 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -31,11 +31,11 @@ #include #include "llerrorcontrol.h" +#include "stringize.h" #include #include #include #include -#include // statically reference the function in test.cpp... it's short, we could // replicate, but better to reuse @@ -131,14 +131,9 @@ public: if (! required) return std::string(); - std::ostringstream out; - out << "failed to find '" << search << "' in captured log messages:"; - for (MessageList::const_iterator mi(mMessages.begin()), mend(mMessages.end()); - mi != mend; ++mi) - { - out << '\n' << *mi; - } - throw tut::failure(out.str()); + throw tut::failure(STRINGIZE("failed to find '" << search + << "' in captured log messages:\n" + << *this)); } typedef std::list MessageList; @@ -146,4 +141,20 @@ public: LLError::Settings* mOldSettings; }; +std::ostream& operator<<(std::ostream& out, const CaptureLog& log) +{ + CaptureLog::MessageList::const_iterator mi(log.mMessages.begin()), mend(log.mMessages.end()); + if (mi != mend) + { + // handle first message separately: it doesn't get a newline + out << *mi++; + for ( ; mi != mend; ++mi) + { + // every subsequent message gets a newline + out << '\n' << *mi; + } + } + return out; +} + #endif /* ! defined(LL_WRAPLLERRS_H) */ -- cgit v1.2.3 From 9b5fcb78e7692c82328bd4ff6d178b68b4b47636 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 23:01:37 -0500 Subject: Refactor llleap_test.cpp to streamline adding more unit tests. Migrate logic from specific test to common reader module, notably parsing the wakeup message containing the reply-pump name. Make test script post to Result struct to communicate success/failure to C++ TUT test, rather than just writing to log. Make test script insensitive to key order in serialized LLSD::Map. --- indra/llcommon/tests/llleap_test.cpp | 118 ++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 31 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 0264442437..0ca8c9b684 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -83,6 +83,7 @@ namespace tut llleap_data(): reader(".py", // This logic is adapted from vita.viewerclient.receiveEvent() + "import re\n" "import sys\n" "LEFTOVER = ''\n" "class ProtocolError(Exception):\n" @@ -112,7 +113,29 @@ namespace tut " parts[-1] = parts[-1][:excess]\n" " data = ''.join(parts)\n" " assert len(data) == length\n" - " return data\n"), + " return data\n" + "\n" + "def put(req):\n" + " sys.stdout.write(':'.join((str(len(req)), req)))\n" + " sys.stdout.flush()\n" + "\n" + "# deal with initial stdin message\n" + // this will throw if the initial write to stdin + // doesn't follow len:data protocol + "_initial = get()\n" + "_match = re.search(r\"'pump':'(.*?)'\", _initial)\n" + // this will throw if we couldn't find + // 'pump':'etc.' in the initial write + "_reply = _match.group(1)\n" + "\n" + "def replypump():\n" + " return _reply\n" + "\n" + "def escape(str):\n" + " return ''.join(('\\\\'+c if c in r\"\\'\" else c) for c in str)\n" + "\n" + "def quote(str):\n" + " return \"'%s'\" % escape(str)\n"), // Get the actual pathname of the NamedExtTempFile and trim off // the ".py" extension. (We could cache reader.getName() in a // separate member variable, but I happen to know getName() just @@ -203,23 +226,64 @@ namespace tut ensure("bad launch returned non-NULL", ! LLLeap::create("bad exe", BADPYTHON, false)); } + // Generic self-contained listener: derive from this and override its + // call() method, then tell somebody to post on the pump named getName(). + // Control will reach your call() override. + struct ListenerBase + { + // Pass the pump name you want; will tweak for uniqueness. + ListenerBase(const std::string& name): + mPump(name, true) + { + mPump.listen(name, boost::bind(&ListenerBase::call, this, _1)); + } + + virtual bool call(const LLSD& request) + { + return false; + } + + LLEventPump& getPump() { return mPump; } + const LLEventPump& getPump() const { return mPump; } + + std::string getName() const { return mPump.getName(); } + void post(const LLSD& data) { mPump.post(data); } + + LLEventStream mPump; + }; + // Mimic a dummy little LLEventAPI that merely sends a reply back to its // requester on the "reply" pump. - struct API + struct API: public ListenerBase { - API(): - mPump("API", true) + API(): ListenerBase("API") {} + + virtual bool call(const LLSD& request) { - mPump.listen("API", boost::bind(&API::entry, this, _1)); + LLEventPumps::instance().obtain(request["reply"]).post("ack"); + return false; } + }; + + // Give LLLeap script a way to post success/failure. + struct Result: public ListenerBase + { + Result(): ListenerBase("Result") {} - bool entry(const LLSD& request) + virtual bool call(const LLSD& request) { - LLEventPumps::instance().obtain(request["reply"]).post("ack"); + mData = request; return false; } - LLEventStream mPump; + void ensure() const + { + tut::ensure(std::string("never posted to ") + getName(), mData.isDefined()); + // Post an empty string for success, non-empty string is failure message. + tut::ensure(mData, mData.asString().empty()); + } + + LLSD mData; }; template<> template<> @@ -227,35 +291,27 @@ namespace tut { set_test_name("round trip"); API api; + Result result; NamedTempFile script("py", boost::lambda::_1 << - "import re\n" "import sys\n" - "from " << reader_module << " import get\n" - // this will throw if the initial write to stdin - // doesn't follow len:data protocol - "initial = get()\n" - "match = re.search(r\"'pump':'(.*?)'\", initial)\n" - // this will throw if we couldn't find - // 'pump':'etc.' in the initial write - "reply = match.group(1)\n" - "req = '''\\\n" - "{'pump':'" << api.mPump.getName() << "','data':{'reply':'%s'}}\\\n" - "''' % reply\n" + "from " << reader_module << " import *\n" // make a request on our little API - "sys.stdout.write(':'.join((str(len(req)), req)))\n" - "sys.stdout.flush()\n" + "put(\"{'pump':'" << api.getName() << "','data':{'reply':'%s'}}\" %\n" + " replypump())\n" // wait for its response "resp = get()\n" - // it would be cleverer to be order-insensitive - // about 'data' and 'pump'; hopefully the C++ - // serializer doesn't change its rules soon - "result = 'good' if (resp == \"{'data':'ack','pump':'%s'}\" % reply)\\\n" - " else 'bad: ' + resp\n" - // write 'good' or 'bad' to the log so we can observe - "sys.stderr.write(result)\n"); - CaptureLog log(LLError::LEVEL_INFO); + // We expect "{'data':'ack','pump':'%s'}", but + // don't depend on the order of the keys. + "result = 'bad: ' + resp\n" + "if resp.startswith('{') and resp.endswith('}'):\n" + " expect = set((\"'data':'ack'\", \"'pump':'%s'\" % replypump()))\n" + " actual = set(resp[1:-1].split(','))\n" + " if actual == expect:\n" + " result = ''\n" + "put(\"{'pump':'" << result.getName() << "','data':%s}\" %\n" + " quote(result))\n"); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); - log.messageWith("good"); + result.ensure(); } } // namespace tut -- cgit v1.2.3 From d16ce6bf0422f46e3a2d9966cd4210f4f4e35a83 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Mar 2012 09:47:27 -0500 Subject: Drag in Python llsd module, which greatly simplifies tests. It only took a few examples of trying to wrangle notation LLSD as string data to illustrate how clumsy that is. I'd forgotten that a couple other TUT tests already invoke Python code that depends on the llsd module. The trick is to recognize that at least as of now, there's still an obsolete version of the module in the viewer's own source tree. Python code is careful to try importing llbase.llsd before indra.base.llsd, so that if/when we finally do clear indra/lib/python from the viewer repo, we need only require that llbase be installed on every build machine. --- indra/llcommon/tests/llleap_test.cpp | 67 ++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 0ca8c9b684..bef3bf1543 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -83,8 +83,21 @@ namespace tut llleap_data(): reader(".py", // This logic is adapted from vita.viewerclient.receiveEvent() - "import re\n" + boost::lambda::_1 << + "import os\n" "import sys\n" + // Don't forget that this Python script is written to some + // temp directory somewhere! Its __file__ is useless in + // finding indra/lib/python. Use our __FILE__, with + // raw-string syntax to deal with Windows pathnames. + "mydir = os.path.dirname(r'" << __FILE__ << "')\n" + "try:\n" + " from llbase import llsd\n" + "except ImportError:\n" + // We expect mydir to be .../indra/llcommon/tests. + " sys.path.insert(0,\n" + " os.path.join(mydir, os.pardir, os.pardir, 'lib', 'python'))\n" + " from indra.base import llsd\n" "LEFTOVER = ''\n" "class ProtocolError(Exception):\n" " pass\n" @@ -113,29 +126,28 @@ namespace tut " parts[-1] = parts[-1][:excess]\n" " data = ''.join(parts)\n" " assert len(data) == length\n" - " return data\n" - "\n" - "def put(req):\n" - " sys.stdout.write(':'.join((str(len(req)), req)))\n" - " sys.stdout.flush()\n" + " return llsd.parse(data)\n" "\n" "# deal with initial stdin message\n" - // this will throw if the initial write to stdin - // doesn't follow len:data protocol - "_initial = get()\n" - "_match = re.search(r\"'pump':'(.*?)'\", _initial)\n" - // this will throw if we couldn't find - // 'pump':'etc.' in the initial write - "_reply = _match.group(1)\n" + // this will throw if the initial write to stdin doesn't + // follow len:data protocol, or if we couldn't find 'pump' + // in the dict + "_reply = get()['pump']\n" "\n" "def replypump():\n" " return _reply\n" "\n" - "def escape(str):\n" - " return ''.join(('\\\\'+c if c in r\"\\'\" else c) for c in str)\n" + "def put(req):\n" + " sys.stdout.write(':'.join((str(len(req)), req)))\n" + " sys.stdout.flush()\n" + "\n" + "def send(pump, data):\n" + " put(llsd.format_notation(dict(pump=pump, data=data)))\n" "\n" - "def quote(str):\n" - " return \"'%s'\" % escape(str)\n"), + "def request(pump, data):\n" + " # we expect 'data' is a dict\n" + " data['reply'] = _reply\n" + " send(pump, data)\n"), // Get the actual pathname of the NamedExtTempFile and trim off // the ".py" extension. (We could cache reader.getName() in a // separate member variable, but I happen to know getName() just @@ -297,21 +309,18 @@ namespace tut "import sys\n" "from " << reader_module << " import *\n" // make a request on our little API - "put(\"{'pump':'" << api.getName() << "','data':{'reply':'%s'}}\" %\n" - " replypump())\n" + "request(pump='" << api.getName() << "', data={})\n" // wait for its response "resp = get()\n" - // We expect "{'data':'ack','pump':'%s'}", but - // don't depend on the order of the keys. - "result = 'bad: ' + resp\n" - "if resp.startswith('{') and resp.endswith('}'):\n" - " expect = set((\"'data':'ack'\", \"'pump':'%s'\" % replypump()))\n" - " actual = set(resp[1:-1].split(','))\n" - " if actual == expect:\n" - " result = ''\n" - "put(\"{'pump':'" << result.getName() << "','data':%s}\" %\n" - " quote(result))\n"); + "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" + " else 'bad: ' + str(resp)\n" + "send(pump='" << result.getName() << "', data=result)\n"); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); result.ensure(); } + + // TODO: + // many many small messages buffered in both directions + // very large message in both directions (md5) + } // namespace tut -- cgit v1.2.3 From 260883c11f051853876d34bf1c9448d0063f9f5a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Mar 2012 10:40:15 -0500 Subject: Clarify LLProcess debug log message about reading from child pipe. Previous "read N of M bytes" wording implied that the child had M bytes to send, but we only read N of them. In reality we have no idea how many bytes the child is trying to send, only how many the OS is willing to deliver at this moment. To me, "filled N of M bytes" more clearly implies that M is the buffer size. --- indra/llcommon/llprocess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 9c49517598..ad8e3a930e 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -367,7 +367,7 @@ public: // received. Make sure we commit those later. (Don't commit them // now, that would invalidate the buffer iterator sequence!) tocommit += gotten; - LL_DEBUGS("LLProcess") << "read " << gotten << " of " << toread + LL_DEBUGS("LLProcess") << "filled " << gotten << " of " << toread << " bytes from " << mDesc << LL_ENDL; // The parent end of this pipe is nonblocking. If we weren't even -- cgit v1.2.3 From c6c7cabd1daad23898edc9f352349a028888f8ee Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Mar 2012 12:00:38 -0500 Subject: Add "load test" LLLeap unit tests: many small messages, one large. These tests rule out corruption as we cross buffer boundaries in OS pipes and the LLLeap implementation itself. --- indra/llcommon/tests/llleap_test.cpp | 120 ++++++++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 10 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index bef3bf1543..652a95ac6b 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -40,9 +40,11 @@ StringVec sv(const StringVec& listof) { return listof; } #define sleep(secs) _sleep((secs) * 1000) #endif -void waitfor(const std::vector& instances) +const size_t BUFFERED_LENGTH = 1024*1024; // try wrangling a megabyte of data + +void waitfor(const std::vector& instances, int timeout=60) { - int i, timeout = 60; + int i; for (i = 0; i < timeout; ++i) { // Every iteration, test whether any of the passed LLLeap instances @@ -66,11 +68,11 @@ void waitfor(const std::vector& instances) tut::ensure("timed out without terminating", i < timeout); } -void waitfor(LLLeap* instance) +void waitfor(LLLeap* instance, int timeout=60) { std::vector instances; instances.push_back(instance); - waitfor(instances); + waitfor(instances, timeout); } /***************************************************************************** @@ -266,9 +268,9 @@ namespace tut // Mimic a dummy little LLEventAPI that merely sends a reply back to its // requester on the "reply" pump. - struct API: public ListenerBase + struct AckAPI: public ListenerBase { - API(): ListenerBase("API") {} + AckAPI(): ListenerBase("AckAPI") {} virtual bool call(const LLSD& request) { @@ -302,11 +304,10 @@ namespace tut void object::test<5>() { set_test_name("round trip"); - API api; + AckAPI api; Result result; NamedTempFile script("py", boost::lambda::_1 << - "import sys\n" "from " << reader_module << " import *\n" // make a request on our little API "request(pump='" << api.getName() << "', data={})\n" @@ -319,8 +320,107 @@ namespace tut result.ensure(); } + struct ReqIDAPI: public ListenerBase + { + ReqIDAPI(): ListenerBase("ReqIDAPI") {} + + virtual bool call(const LLSD& request) + { + // free function from llevents.h + sendReply(LLSD(), request); + return false; + } + }; + + template<> template<> + void object::test<6>() + { + set_test_name("many small messages"); + // It's not clear to me whether there's value in iterating many times + // over a send/receive loop -- I don't think that will exercise any + // interesting corner cases. This test first sends a large number of + // messages, then receives all the responses. The intent is to ensure + // that some of that data stream crosses buffer boundaries, loop + // iterations etc. in OS pipes and the LLLeap/LLProcess implementation. + ReqIDAPI api; + Result result; + NamedTempFile script("py", + boost::lambda::_1 << + "import sys\n" + "from " << reader_module << " import *\n" + // Note that since reader imports llsd, this + // 'import *' gets us llsd too. + "sample = llsd.format_notation(dict(pump='" << + api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n" + // The whole packet has length prefix too: "len:data" + "samplen = len(str(len(sample))) + 1 + len(sample)\n" + // guess how many messages it will take to + // accumulate BUFFERED_LENGTH + "count = int(" << BUFFERED_LENGTH << "/samplen)\n" + "print >>sys.stderr, 'Sending %s requests' % count\n" + "for i in xrange(count):\n" + " request('" << api.getName() << "', dict(reqid=i))\n" + // The assumption in this specific test that + // replies will arrive in the same order as + // requests is ONLY valid because the API we're + // invoking sends replies instantly. If the API + // had to wait for some external event before + // sending its reply, replies could arrive in + // arbitrary order, and we'd have to tick them + // off from a set. + "result = ''\n" + "for i in xrange(count):\n" + " resp = get()\n" + " if resp['data']['reqid'] != i:\n" + " result = 'expected reqid=%s in %s' % (i, resp)\n" + " break\n" + "send(pump='" << result.getName() << "', data=result)\n"); + waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))), + 300); // needs more realtime than most tests + result.ensure(); + } + + template<> template<> + void object::test<7>() + { + set_test_name("very large message"); + ReqIDAPI api; + Result result; + NamedTempFile script("py", + boost::lambda::_1 << + "import sys\n" + "from " << reader_module << " import *\n" + // Generate a very large string value. + "desired = int(sys.argv[1])\n" + // 7 chars per item: 6 digits, 1 comma + "count = int((desired - 50)/7)\n" + "large = ''.join('%06d,' % i for i in xrange(count))\n" + // Pass 'large' as reqid because we know the API + // will echo reqid, and we want to receive it back. + "request('" << api.getName() << "', dict(reqid=large))\n" + "resp = get()\n" + "echoed = resp['data']['reqid']\n" + "if echoed == large:\n" + " send('" << result.getName() << "', '')\n" + " sys.exit(0)\n" + // Here we know echoed did NOT match; try to find where + "for i in xrange(count):\n" + " start = 7*i\n" + " end = 7*(i+1)\n" + " if end > len(echoed)\\\n" + " or echoed[start:end] != large[start:end]:\n" + " send('" << result.getName() << "',\n" + " 'at offset %s, expected %r but got %r' %\n" + " (start, large[start:end], echoed[start:end]))\n" + "sys.exit(1)\n"); + waitfor(LLLeap::create(get_test_name(), + sv(list_of + (PYTHON) + (script.getName()) + (stringize(BUFFERED_LENGTH))))); + result.ensure(); + } + // TODO: - // many many small messages buffered in both directions - // very large message in both directions (md5) } // namespace tut -- cgit v1.2.3 From d09d4e1a7e64b01fa1d9f3015de819a2e7069ac4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Mar 2012 13:43:13 -0500 Subject: Add LLLeap unit tests for strange data on child stdout. --- indra/llcommon/tests/llleap_test.cpp | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 652a95ac6b..053a6cea80 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -207,6 +207,34 @@ namespace tut template<> template<> void object::test<3>() + { + set_test_name("bad stdout protocol"); + NamedTempFile script("py", + "print 'Hello from Python!'\n"); + CaptureLog log(LLError::LEVEL_WARN); + waitfor(LLLeap::create(get_test_name(), + sv(list_of(PYTHON)(script.getName())))); + ensure_contains("error log line", + log.messageWith("invalid protocol"), "Hello from Python!"); + } + + template<> template<> + void object::test<4>() + { + set_test_name("leftover stdout"); + NamedTempFile script("py", + "import sys\n" + // note lack of newline + "sys.stdout.write('Hello from Python!')\n"); + CaptureLog log(LLError::LEVEL_WARN); + waitfor(LLLeap::create(get_test_name(), + sv(list_of(PYTHON)(script.getName())))); + ensure_contains("error log line", + log.messageWith("Discarding"), "Hello from Python!"); + } + + template<> template<> + void object::test<5>() { set_test_name("empty plugin vector"); std::string threw; @@ -221,7 +249,7 @@ namespace tut } template<> template<> - void object::test<4>() + void object::test<6>() { set_test_name("bad launch"); // Synthesize bogus executable name @@ -301,7 +329,7 @@ namespace tut }; template<> template<> - void object::test<5>() + void object::test<7>() { set_test_name("round trip"); AckAPI api; @@ -333,7 +361,7 @@ namespace tut }; template<> template<> - void object::test<6>() + void object::test<8>() { set_test_name("many small messages"); // It's not clear to me whether there's value in iterating many times @@ -381,7 +409,7 @@ namespace tut } template<> template<> - void object::test<7>() + void object::test<9>() { set_test_name("very large message"); ReqIDAPI api; -- cgit v1.2.3 From f53fa08c7069f7f604f3fcab70d65af8bb616f08 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 2 Mar 2012 13:35:10 -0600 Subject: MAINT-708 Use texture compression to minimize memory bloat (experimental) --- indra/llrender/llgl.cpp | 13 ++++++++++ indra/llrender/llimagegl.cpp | 43 +++++++++++++++++++++++++++++---- indra/llrender/llimagegl.h | 7 ++++-- indra/llrender/llrendertarget.cpp | 4 +-- indra/newview/app_settings/settings.xml | 11 +++++++++ indra/newview/llappviewer.cpp | 3 ++- indra/newview/lldrawpoolbump.cpp | 7 ++++-- indra/newview/llviewertexture.cpp | 4 +++ indra/newview/llviewertexture.h | 1 + indra/newview/pipeline.cpp | 6 ++--- 10 files changed, 84 insertions(+), 15 deletions(-) diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 197bc2b422..b99465ee47 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -94,6 +94,10 @@ void APIENTRY gl_debug_callback(GLenum source, llwarns << "Severity: " << std::hex << severity << llendl; llwarns << "Message: " << message << llendl; llwarns << "-----------------------" << llendl; + if (severity == GL_DEBUG_SEVERITY_HIGH_ARB) + { + llerrs << "Halting on GL Error" << llendl; + } } #endif @@ -572,6 +576,15 @@ bool LLGLManager::initGL() #endif } + if (mGLVersion >= 3.f && LLImageGL::sCompressTextures) + { //use texture compression + glHint(GL_TEXTURE_COMPRESSION_HINT, GL_FASTEST); + } + else + { //GL version is < 3.0, always disable texture compression + LLImageGL::sCompressTextures = false; + } + // Trailing space necessary to keep "nVidia Corpor_ati_on" cards // from being recognized as ATI. if (mGLVendor.substr(0,4) == "ATI ") diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 78591ddd38..17131c9d8a 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -57,6 +57,7 @@ BOOL LLImageGL::sGlobalUseAnisotropic = FALSE; F32 LLImageGL::sLastFrameTime = 0.f; BOOL LLImageGL::sAllowReadBackRaw = FALSE ; LLImageGL* LLImageGL::sDefaultGLTexture = NULL ; +bool LLImageGL::sCompressTextures = false; std::set LLImageGL::sImageList; @@ -477,6 +478,8 @@ void LLImageGL::init(BOOL usemipmaps) mDiscardLevelInAtlas = -1 ; mTexelsInAtlas = 0 ; mTexelsInGLTexture = 0 ; + + mAllowCompression = true; mTarget = GL_TEXTURE_2D; mBindTarget = LLTexUnit::TT_TEXTURE; @@ -705,7 +708,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips) stop_glerror(); } - LLImageGL::setManualImage(mTarget, gl_level, mFormatInternal, w, h, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in); + LLImageGL::setManualImage(mTarget, gl_level, mFormatInternal, w, h, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in, mAllowCompression); if (gl_level == 0) { analyzeAlpha(data_in, w, h); @@ -747,7 +750,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips) LLImageGL::setManualImage(mTarget, 0, mFormatInternal, w, h, mFormatPrimary, mFormatType, - data_in); + data_in, mAllowCompression); analyzeAlpha(data_in, w, h); stop_glerror(); @@ -805,7 +808,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips) stop_glerror(); } - LLImageGL::setManualImage(mTarget, m, mFormatInternal, w, h, mFormatPrimary, mFormatType, cur_mip_data); + LLImageGL::setManualImage(mTarget, m, mFormatInternal, w, h, mFormatPrimary, mFormatType, cur_mip_data, mAllowCompression); if (m == 0) { analyzeAlpha(data_in, w, h); @@ -863,7 +866,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips) } LLImageGL::setManualImage(mTarget, 0, mFormatInternal, w, h, - mFormatPrimary, mFormatType, (GLvoid *)data_in); + mFormatPrimary, mFormatType, (GLvoid *)data_in, mAllowCompression); analyzeAlpha(data_in, w, h); updatePickMask(w, h, data_in); @@ -1110,7 +1113,7 @@ void LLImageGL::deleteTextures(S32 numTextures, U32 *textures, bool immediate) } // static -void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels) +void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels, bool allow_compression) { bool use_scratch = false; U32* scratch = NULL; @@ -1173,6 +1176,36 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt } } + if (LLImageGL::sCompressTextures && allow_compression) + { + switch (intformat) + { + case GL_RGB: + case GL_RGB8: + intformat = GL_COMPRESSED_RGB; + break; + case GL_RGBA: + case GL_RGBA8: + intformat = GL_COMPRESSED_RGBA; + break; + case GL_LUMINANCE: + case GL_LUMINANCE8: + intformat = GL_COMPRESSED_LUMINANCE; + break; + case GL_LUMINANCE_ALPHA: + case GL_LUMINANCE8_ALPHA8: + intformat = GL_COMPRESSED_LUMINANCE_ALPHA; + break; + case GL_ALPHA: + case GL_ALPHA8: + intformat = GL_COMPRESSED_ALPHA; + break; + default: + llwarns << "Could not compress format: " << std::hex << intformat << llendl; + break; + } + } + stop_glerror(); glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, use_scratch ? scratch : pixels); stop_glerror(); diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index 2cfb15b0d9..e23005fe29 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -94,12 +94,13 @@ public: void setSize(S32 width, S32 height, S32 ncomponents); void setComponents(S32 ncomponents) { mComponents = (S8)ncomponents ;} + void setAllowCompression(bool allow) { mAllowCompression = allow; } // These 3 functions currently wrap glGenTextures(), glDeleteTextures(), and glTexImage2D() // for tracking purposes and will be deprecated in the future static void generateTextures(S32 numTextures, U32 *textures); static void deleteTextures(S32 numTextures, U32 *textures, bool immediate = false); - static void setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels); + static void setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels, bool allow_compression = true); BOOL createGLTexture() ; BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE, @@ -209,6 +210,8 @@ private: U32 mTexelsInAtlas ; U32 mTexelsInGLTexture; + bool mAllowCompression; + protected: LLGLenum mTarget; // Normally GL_TEXTURE2D, sometimes something else (ex. cube maps) LLTexUnit::eTextureType mBindTarget; // Normally TT_TEXTURE, sometimes something else (ex. cube maps) @@ -246,7 +249,7 @@ public: static BOOL sGlobalUseAnisotropic; static LLImageGL* sDefaultGLTexture ; static BOOL sAutomatedTest; - + static bool sCompressTextures; //use GL texture compression #if DEBUG_MISS BOOL mMissed; // Missed on last bind? BOOL getMissed() const { return mMissed; }; diff --git a/indra/llrender/llrendertarget.cpp b/indra/llrender/llrendertarget.cpp index ef2a7395da..780f1dc484 100644 --- a/indra/llrender/llrendertarget.cpp +++ b/indra/llrender/llrendertarget.cpp @@ -143,7 +143,7 @@ bool LLRenderTarget::addColorAttachment(U32 color_fmt) { clear_glerror(); - LLImageGL::setManualImage(LLTexUnit::getInternalType(mUsage), 0, color_fmt, mResX, mResY, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + LLImageGL::setManualImage(LLTexUnit::getInternalType(mUsage), 0, color_fmt, mResX, mResY, GL_RGBA, GL_UNSIGNED_BYTE, NULL, false); if (glGetError() != GL_NO_ERROR) { llwarns << "Could not allocate color buffer for render target." << llendl; @@ -223,7 +223,7 @@ bool LLRenderTarget::allocateDepth() U32 internal_type = LLTexUnit::getInternalType(mUsage); stop_glerror(); clear_glerror(); - LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false); gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); } diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 0e26013152..992618ba11 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -7609,6 +7609,17 @@ Value 1 + RenderCompressTextures + + Comment + Enable texture compression on OpenGL 3.0 and later implementations (EXPERIMENTAL, requires restart) + Persist + 1 + Type + Boolean + Value + 0 + RenderPerformanceTest Comment diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 1174d108d2..5941dbc126 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -528,6 +528,7 @@ static void settings_to_globals() LLRender::sGLCoreProfile = gSavedSettings.getBOOL("RenderGLCoreProfile"); LLImageGL::sGlobalUseAnisotropic = gSavedSettings.getBOOL("RenderAnisotropic"); + LLImageGL::sCompressTextures = gSavedSettings.getBOOL("RenderCompressTextures"); LLVOVolume::sLODFactor = gSavedSettings.getF32("RenderVolumeLODFactor"); LLVOVolume::sDistanceFactor = 1.f-LLVOVolume::sLODFactor * 0.1f; LLVolumeImplFlexible::sUpdateFactor = gSavedSettings.getF32("RenderFlexTimeFactor"); @@ -545,7 +546,7 @@ static void settings_to_globals() gAgentPilot.setNumRuns(gSavedSettings.getS32("StatsNumRuns")); gAgentPilot.setQuitAfterRuns(gSavedSettings.getBOOL("StatsQuitAfterRuns")); gAgent.setHideGroupTitle(gSavedSettings.getBOOL("RenderHideGroupTitle")); - + gDebugWindowProc = gSavedSettings.getBOOL("DebugWindowProc"); gShowObjectUpdates = gSavedSettings.getBOOL("ShowObjectUpdates"); LLWorldMapView::sMapScale = gSavedSettings.getF32("MapScale"); diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp index b58efe62ab..6f71e6ebc8 100644 --- a/indra/newview/lldrawpoolbump.cpp +++ b/indra/newview/lldrawpoolbump.cpp @@ -1341,8 +1341,7 @@ void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerTexture *src_vi, LLI // immediately assign bump to a global smart pointer in case some local smart pointer // accidentally releases it. LLPointer bump = LLViewerTextureManager::getLocalTexture( TRUE ); - - + if (!LLPipeline::sRenderDeferred) { LLFastTimer t(FTM_BUMP_SOURCE_CREATE); @@ -1351,6 +1350,10 @@ void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerTexture *src_vi, LLI } else { //convert to normal map + + //disable compression on normal maps to prevent errors below + bump->getGLTexture()->setAllowCompression(false); + { LLFastTimer t(FTM_BUMP_SOURCE_CREATE); bump->setExplicitFormat(GL_RGBA8, GL_ALPHA); diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 61236edc86..51737149ef 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -3290,10 +3290,14 @@ LLViewerMediaTexture::LLViewerMediaTexture(const LLUUID& id, BOOL usemipmaps, LL sMediaMap.insert(std::make_pair(id, this)); mGLTexturep = gl_image ; + if(mGLTexturep.isNull()) { generateGLTexture() ; } + + mGLTexturep->setAllowCompression(false); + mGLTexturep->setNeedsAlphaAndPickMask(FALSE) ; mIsPlaying = FALSE ; diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index b96441127d..6b1654ae87 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -263,6 +263,7 @@ protected: void reorganizeVolumeList() ; void setTexelsPerImage(); private: + friend class LLBumpImageList; //note: do not make this function public. /*virtual*/ LLImageGL* getGLTexture() const ; virtual void switchToCachedImage(); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index e2cb22e307..fbaded04b0 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -1131,7 +1131,7 @@ void LLPipeline::createGLBuffers() LLImageGL::generateTextures(1, &mNoiseMap); gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mNoiseMap); - LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F_ARB, noiseRes, noiseRes, GL_RGB, GL_FLOAT, noise); + LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F_ARB, noiseRes, noiseRes, GL_RGB, GL_FLOAT, noise, false); gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); } @@ -1146,7 +1146,7 @@ void LLPipeline::createGLBuffers() LLImageGL::generateTextures(1, &mTrueNoiseMap); gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mTrueNoiseMap); - LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F_ARB, noiseRes, noiseRes, GL_RGB,GL_FLOAT, noise); + LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F_ARB, noiseRes, noiseRes, GL_RGB,GL_FLOAT, noise, false); gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); } @@ -1182,7 +1182,7 @@ void LLPipeline::createGLBuffers() LLImageGL::generateTextures(1, &mLightFunc); gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mLightFunc); - LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_R8, lightResX, lightResY, GL_RED, GL_UNSIGNED_BYTE, lg); + LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_R8, lightResX, lightResY, GL_RED, GL_UNSIGNED_BYTE, lg, false); gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_TRILINEAR); -- cgit v1.2.3 From e8f463ef7a9cddda3813d20935957708d3b4aa3b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Mar 2012 14:54:24 -0500 Subject: Use LLProcess::ReadPipe::read() in LLLeap. The code was using LLProcess::ReadPipe::get_istream().read(), but that's much uglier, as it requires constructing a char* buffer etc. etc. --- indra/llcommon/llleap.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index dddf1286ac..beb7fa8333 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -230,10 +230,7 @@ public: // childout (well, up to a max length) to log what was wrong. LLProcess::ReadPipe::size_type readlen((std::min)(childout.size(), LLProcess::ReadPipe::size_type(80))); - std::vector buffer(readlen + 1); - childstream.read(&buffer[0], readlen); - buffer[childstream.gcount()] = '\0'; - bad_protocol(STRINGIZE(expect << char(colon) << &buffer[0])); + bad_protocol(STRINGIZE(expect << char(colon) << childout.read(readlen))); } else { -- cgit v1.2.3 From 674f9fb111919f9dffaddf8c0732267f6dbfdf2a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Mar 2012 14:56:00 -0500 Subject: Add LLLeap unit test for invalid length prefix from child stdout. --- indra/llcommon/tests/llleap_test.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 053a6cea80..328b9c3562 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -235,6 +235,20 @@ namespace tut template<> template<> void object::test<5>() + { + set_test_name("bad stdout len prefix"); + NamedTempFile script("py", + "import sys\n" + "sys.stdout.write('5a2:something')\n"); + CaptureLog log(LLError::LEVEL_WARN); + waitfor(LLLeap::create(get_test_name(), + sv(list_of(PYTHON)(script.getName())))); + ensure_contains("error log line", + log.messageWith("invalid protocol"), "5a2:"); + } + + template<> template<> + void object::test<6>() { set_test_name("empty plugin vector"); std::string threw; @@ -249,7 +263,7 @@ namespace tut } template<> template<> - void object::test<6>() + void object::test<7>() { set_test_name("bad launch"); // Synthesize bogus executable name @@ -329,7 +343,7 @@ namespace tut }; template<> template<> - void object::test<7>() + void object::test<8>() { set_test_name("round trip"); AckAPI api; @@ -361,7 +375,7 @@ namespace tut }; template<> template<> - void object::test<8>() + void object::test<9>() { set_test_name("many small messages"); // It's not clear to me whether there's value in iterating many times @@ -409,7 +423,7 @@ namespace tut } template<> template<> - void object::test<9>() + void object::test<10>() { set_test_name("very large message"); ReqIDAPI api; -- cgit v1.2.3 From 46cff7963b1746ecde70eb6d3e46dab6b2eedf6b Mon Sep 17 00:00:00 2001 From: Xiaohong Bao Date: Fri, 2 Mar 2012 22:07:21 -0700 Subject: debug code for SH-3020: texture stopped loading --- indra/newview/llviewerwindow.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index e0653fec30..0979e47c7c 100755 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -751,8 +751,9 @@ public: LLSelectNode* nodep = LLSelectMgr::instance().getHoverNode(); if (nodep) { - objectp = nodep->getObject(); + objectp = nodep->getObject(); } + if (objectp && !objectp->isDead()) { S32 num_faces = objectp->mDrawable->getNumFaces() ; @@ -766,8 +767,8 @@ public: // facep->mTexExtents[0].mV[1], facep->mTexExtents[1].mV[1])); //ypos += y_inc; - addText(xpos, ypos, llformat("v_size: %.3f: p_size: %.3f", facep->getVirtualSize(), facep->getPixelArea())); - ypos += y_inc; + //addText(xpos, ypos, llformat("v_size: %.3f: p_size: %.3f", facep->getVirtualSize(), facep->getPixelArea())); + //ypos += y_inc; //const LLTextureEntry *tep = facep->getTextureEntry(); //if(tep) @@ -776,11 +777,15 @@ public: // ypos += y_inc; //} - LLViewerTexture* tex = facep->getTexture() ; + LLViewerFetchedTexture* tex = dynamic_cast(facep->getTexture()) ; if(tex) { addText(xpos, ypos, llformat("ID: %s v_size: %.3f", tex->getID().asString().c_str(), tex->getMaxVirtualSize())); ypos += y_inc; + + addText(xpos, ypos, llformat("discard level: %d desired level: %d Missing: %s", tex->getDiscardLevel(), + tex->getDesiredDiscardLevel(), tex->isMissingAsset() ? "Y" : "N")); + ypos += y_inc; } } } -- cgit v1.2.3 From e7d129875a4ccdc09f921e08fea1dccbb025b705 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 3 Mar 2012 06:28:47 -0500 Subject: Add a couple LLLeap DEBUG messages for incoming-events control flow. --- indra/llcommon/llleap.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index beb7fa8333..34f77c3f3a 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -237,6 +237,8 @@ public: // Saw length prefix, saw colon, life is good. Now wait for // that length of data to arrive. mExpect = expect; + LL_DEBUGS("LLLeap") << "got length, waiting for " + << mExpect << " bytes of data" << LL_ENDL; // Block calls to this method; resetting mBlocker unblocks // calls to the other method. mBlocker.reset(new LLEventPump::Blocker(mStdoutConnection)); @@ -268,6 +270,8 @@ public: if (childout.size() >= mExpect) { // Ready to rock and roll. + LL_DEBUGS("LLLeap") << "needed " << mExpect << " bytes, got " + << childout.size() << ", parsing LLSD" << LL_ENDL; LLSD data; LLPointer parser(new LLSDNotationParser()); S32 parse_status(parser->parse(childout.get_istream(), data, mExpect)); -- cgit v1.2.3 From d72d9f73b6ff93f11e9bc4360d4c7bf2e76d1eec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 3 Mar 2012 06:37:05 -0500 Subject: Add debugging output in case LLLeap writes corrupt data to plugin. New llleap_test.cpp load testing turned up Windows issue in which plugin process received corrupt packet, producing LLSDParseError. Add code to dump the bad packet in that case -- but if LLSDParseError is willing to state the offset of the problem, not ALL of the packet. Quiet MSVC warning about little internal base class needing virtual destructor. --- indra/llcommon/tests/llleap_test.cpp | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 328b9c3562..4fb80df788 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -86,6 +86,7 @@ namespace tut reader(".py", // This logic is adapted from vita.viewerclient.receiveEvent() boost::lambda::_1 << + "import re\n" "import os\n" "import sys\n" // Don't forget that this Python script is written to some @@ -128,7 +129,33 @@ namespace tut " parts[-1] = parts[-1][:excess]\n" " data = ''.join(parts)\n" " assert len(data) == length\n" - " return llsd.parse(data)\n" + " try:\n" + " return llsd.parse(data)\n" + " except llsd.LLSDParseError, e:\n" + " print >>sys.stderr, 'Bad received packet (%s), %s bytes:' % \\\n" + " (e, len(data))\n" + " showmax = 40\n" + // We've observed failures with very large packets; + // dumping the entire packet wastes time and space. + // But if the error states a particular byte offset, + // truncate to (near) that offset when dumping data. + " location = re.search(r' at byte ([0-9]+)', str(e))\n" + " if not location:\n" + " # didn't find offset, dump whole thing, no ellipsis\n" + " ellipsis = ''\n" + " else:\n" + " # found offset within error message\n" + " trunc = int(location.group(1)) + showmax\n" + " data = data[:trunc]\n" + " ellipsis = '... (%s more)' % (length - trunc)\n" + " offset = -showmax\n" + " for offset in xrange(0, len(data)-showmax, showmax):\n" + " print >>sys.stderr, '%04d: %r +' % \\\n" + " (offset, data[offset:offset+showmax])\n" + " offset += showmax\n" + " print >>sys.stderr, '%04d: %r%s' % \\\n" + " (offset, data[offset:], ellipsis)\n" + " raise\n" "\n" "# deal with initial stdin message\n" // this will throw if the initial write to stdin doesn't @@ -294,6 +321,8 @@ namespace tut mPump.listen(name, boost::bind(&ListenerBase::call, this, _1)); } + virtual ~ListenerBase() {} // pacify MSVC + virtual bool call(const LLSD& request) { return false; -- cgit v1.2.3 From 75f412549242c949851938ffeac65b9e7145de5e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 3 Mar 2012 06:45:19 -0500 Subject: Break large buffer into chunks to write to LLProcess child pipe. On Windows we ran into trouble trying to write a biggish (~1 MB) buffer of data to the child process's stdin pipe with a single apr_file_write() call. The child actually received corrupted data -- suggesting a possible bug in either APR or Windows pipes; the same test driving the same logic worked fine on Mac and Linux. Empirically, iterating over chunks of the buffered data is more robust. --- indra/llcommon/llprocess.cpp | 74 ++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index ad8e3a930e..d926791e9e 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -168,38 +168,58 @@ public: const_buffer_sequence bufs = mStreambuf.data(); // In general, our streambuf might contain a number of different // physical buffers; iterate over those. + bool keepwriting = true; for (const_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); - bufi != bufend; ++bufi) + bufi != bufend && keepwriting; ++bufi) { // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents - std::size_t towrite(boost::asio::buffer_size(*bufi)); - apr_size_t written(towrite); - apr_status_t err = apr_file_write(mPipe, - boost::asio::buffer_cast(*bufi), - &written); - // EAGAIN is exactly what we want from a nonblocking pipe. - // Rather than waiting for data, it should return immediately. - if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) + // Although apr_file_write() accepts const void*, we + // manipulate const char* so we can increment the pointer. + const char* remainptr = boost::asio::buffer_cast(*bufi); + std::size_t remainlen = boost::asio::buffer_size(*bufi); + while (remainlen) { - LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc - << " got " << err << ":" << LL_ENDL; - ll_apr_warn_status(err); - } + // Tackle the current buffer in discrete chunks. On + // Windows, we've observed strange failures when trying to + // write big lengths (~1 MB) in a single operation. + std::size_t towrite((std::min)(remainlen, std::size_t(32*1024))); + apr_size_t written(towrite); + apr_status_t err = apr_file_write(mPipe, remainptr, &written); + // EAGAIN is exactly what we want from a nonblocking pipe. + // Rather than waiting for data, it should return immediately. + if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) + { + LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc + << " got " << err << ":" << LL_ENDL; + ll_apr_warn_status(err); + } - // 'written' is modified to reflect the number of bytes actually - // written. Make sure we consume those later. (Don't consume them - // now, that would invalidate the buffer iterator sequence!) - consumed += written; - LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite - << " bytes to " << mDesc - << " (original " << total << ")" << LL_ENDL; - - // The parent end of this pipe is nonblocking. If we weren't able - // to write everything we wanted, don't keep banging on it -- that - // won't change until the child reads some. Wait for next tick(). - if (written < towrite) - break; - } + // 'written' is modified to reflect the number of bytes actually + // written. Make sure we consume those later. (Don't consume them + // now, that would invalidate the buffer iterator sequence!) + consumed += written; + // don't forget to advance to next chunk of current buffer + remainptr += written; + remainlen -= written; + + char msgbuf[512]; + LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite + << " bytes to " << mDesc + << " (original " << total << ")," + << " code " << err << ": " + << apr_strerror(err, msgbuf, sizeof(msgbuf)) + << LL_ENDL; + + // The parent end of this pipe is nonblocking. If we weren't able + // to write everything we wanted, don't keep banging on it -- that + // won't change until the child reads some. Wait for next tick(). + if (written < towrite) + { + keepwriting = false; // break outer loop over buffers too + break; + } + } // next chunk of current buffer + } // next buffer // In all, we managed to write 'consumed' bytes. Remove them from the // streambuf so we don't keep trying to send them. This could be // anywhere from 0 up to mStreambuf.size(); anything we haven't yet -- cgit v1.2.3 From ca703b2b9cd9469ad86539f2b95dbf1162b786f2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 4 Mar 2012 16:59:48 -0500 Subject: Make llleap_test.cpp avoid hard limit on MSVC std::ostringstream max. In load testing, we have observed intermittent failures on Windows in which LLSDNotationStreamer into std::ostringstream seems to bump into a hard limit of 1048590 bytes. ostringstream reports that much buffered data and returns that much -- even though, on examination, the notation-serialized stream is incomplete at that point. It's our intention to load-test LLLeap and LLProcess, not the local iostream implementation; we hope that this kind of data volume is comfortably greater than actual usage. Back off the load-testing max size a bit. --- indra/llcommon/tests/llleap_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 4fb80df788..677f4830ea 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -40,7 +40,7 @@ StringVec sv(const StringVec& listof) { return listof; } #define sleep(secs) _sleep((secs) * 1000) #endif -const size_t BUFFERED_LENGTH = 1024*1024; // try wrangling a megabyte of data +const size_t BUFFERED_LENGTH = 1024*1023; // try wrangling just under a megabyte of data void waitfor(const std::vector& instances, int timeout=60) { @@ -139,13 +139,13 @@ namespace tut // dumping the entire packet wastes time and space. // But if the error states a particular byte offset, // truncate to (near) that offset when dumping data. - " location = re.search(r' at byte ([0-9]+)', str(e))\n" + " location = re.search(r' at (byte|index) ([0-9]+)', str(e))\n" " if not location:\n" " # didn't find offset, dump whole thing, no ellipsis\n" " ellipsis = ''\n" " else:\n" " # found offset within error message\n" - " trunc = int(location.group(1)) + showmax\n" + " trunc = int(location.group(2)) + showmax\n" " data = data[:trunc]\n" " ellipsis = '... (%s more)' % (length - trunc)\n" " offset = -showmax\n" -- cgit v1.2.3 From 30e8e23d7cf666c406901d85a59d16401dca67ab Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 4 Mar 2012 21:27:42 -0500 Subject: Simplify llleap_test.cpp plugin by reading individual characters. While we're accumulating the 'length:' prefix, the present socket-based logic reads 20 characters, then reads 'length' more, then discards any excess (in case the whole 'length:data' packet ends up being less than 20 characters). That's probably a bug: whatever characters follow that packet, however short it may be, are probably the 'length:' prefix of the next packet. We probably only get away with it because we probably never send packets that short. Earlier llleap_test.cpp plugin logic still read 20 characters, then, if there were any left after the present packet, cached them as the start of the next packet. This is probably more correct, but complicated. Easier just to read individual characters until we've seen 'length:', then try for exactly the specified length over however many reads that requires. --- indra/llcommon/tests/llleap_test.cpp | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 677f4830ea..73d7217608 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -101,32 +101,25 @@ namespace tut " sys.path.insert(0,\n" " os.path.join(mydir, os.pardir, os.pardir, 'lib', 'python'))\n" " from indra.base import llsd\n" - "LEFTOVER = ''\n" "class ProtocolError(Exception):\n" " pass\n" "def get():\n" - " global LEFTOVER\n" - " hdr = LEFTOVER\n" - " if ':' not in hdr:\n" - " hdr += sys.stdin.read(20)\n" + " hdr = ''\n" + " while ':' not in hdr and len(hdr) < 20:\n" + " hdr += sys.stdin.read(1)\n" " if not hdr:\n" " sys.exit(0)\n" - " parts = hdr.split(':', 1)\n" - " if len(parts) != 2:\n" + " if not hdr.endswith(':'):\n" " raise ProtocolError('Expected len:data, got %r' % hdr)\n" " try:\n" - " length = int(parts[0])\n" + " length = int(hdr[:-1])\n" " except ValueError:\n" - " raise ProtocolError('Non-numeric len %r' % parts[0])\n" - " del parts[0]\n" - " received = len(parts[0])\n" + " raise ProtocolError('Non-numeric len %r' % hdr[:-1])\n" + " parts = []\n" + " received = 0\n" " while received < length:\n" " parts.append(sys.stdin.read(length - received))\n" " received += len(parts[-1])\n" - " if received > length:\n" - " excess = length - received\n" - " LEFTOVER = parts[-1][excess:]\n" - " parts[-1] = parts[-1][:excess]\n" " data = ''.join(parts)\n" " assert len(data) == length\n" " try:\n" -- cgit v1.2.3 From b3b51f012f49d82c3b7f6f5cadfc0d76ef82f8bf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Mar 2012 13:03:23 -0500 Subject: Move std::ostream << CaptureLog logic into CaptureLog::streamto(). That lets us reliably declare the operator<<() free function inline, which permits multiple translation units in the same executable to #include "wrapllerrs.h". --- indra/llcommon/tests/wrapllerrs.h | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index a9c0c19c28..f79acacb22 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -136,25 +136,31 @@ public: << *this)); } + std::ostream& streamto(std::ostream& out) const + { + MessageList::const_iterator mi(mMessages.begin()), mend(mMessages.end()); + if (mi != mend) + { + // handle first message separately: it doesn't get a newline + out << *mi++; + for ( ; mi != mend; ++mi) + { + // every subsequent message gets a newline + out << '\n' << *mi; + } + } + return out; + } + typedef std::list MessageList; MessageList mMessages; LLError::Settings* mOldSettings; }; +inline std::ostream& operator<<(std::ostream& out, const CaptureLog& log) { - CaptureLog::MessageList::const_iterator mi(log.mMessages.begin()), mend(log.mMessages.end()); - if (mi != mend) - { - // handle first message separately: it doesn't get a newline - out << *mi++; - for ( ; mi != mend; ++mi) - { - // every subsequent message gets a newline - out << '\n' << *mi; - } - } - return out; + return log.streamto(out); } #endif /* ! defined(LL_WRAPLLERRS_H) */ -- cgit v1.2.3 From f02ded46fe6f166a07673d97301bf169eb0b9ba8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Mar 2012 13:07:14 -0500 Subject: Make test.cpp support LOGFAIL env var: only failed tests show log. Set LOGFAIL= one of ALL, DEBUG, INFO, WARN, ERROR, NONE. A passing test will run silently, as now; but a failing test will replay log output at the specified level or higher. While at it, support LOGTEST environment variable, same values. This is like setting --debug (or -d), but allows specifying an arbitrary level -- and, unlike --debug, can be set for a TeamCity build config without modifying any scripts or code. Publish LLError::decodeLevel(std::string), previously private to llerror.cpp. --- indra/llcommon/llerror.cpp | 6 +- indra/llcommon/llerrorcontrol.h | 3 +- indra/test/test.cpp | 126 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index e4381dbbd6..7e6eee0f3c 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -654,9 +654,7 @@ namespace LLError g.invalidateCallSites(); s.tagLevelMap[tag_name] = level; } -} -namespace { LLError::ELevel decodeLevel(std::string name) { static LevelMap level_names; @@ -681,7 +679,9 @@ namespace { return i->second; } - +} + +namespace { void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level) { LLSD::array_const_iterator i, end; diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index ed9de002f5..1be49cebc8 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -80,7 +80,8 @@ namespace LLError LL_COMMON_API void setClassLevel(const std::string& class_name, LLError::ELevel); LL_COMMON_API void setFileLevel(const std::string& file_name, LLError::ELevel); LL_COMMON_API void setTagLevel(const std::string& file_name, LLError::ELevel); - + + LL_COMMON_API LLError::ELevel decodeLevel(std::string name); LL_COMMON_API void configure(const LLSD&); // the LLSD can configure all of the settings // usually read automatically from the live errorlog.xml file diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 1adcfb6f45..128d84e428 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -38,6 +38,7 @@ #include "llerrorcontrol.h" #include "lltut.h" #include "stringize.h" +#include "namedtempfile.h" #include "apr_pools.h" #include "apr_getopt.h" @@ -69,17 +70,79 @@ #include #include +#include + +void wouldHaveCrashed(const std::string& message); + namespace tut { std::string sSourceDir; - - test_runner_singleton runner; + + test_runner_singleton runner; } +class LLReplayLog +{ +public: + LLReplayLog() {} + virtual ~LLReplayLog() {} + + virtual void reset() {} + virtual void replay(std::ostream&) {} +}; + +class LLReplayLogReal: public LLReplayLog, public LLError::Recorder +{ +public: + LLReplayLogReal(LLError::ELevel level, apr_pool_t* pool): + mOldSettings(LLError::saveAndResetSettings()), + mTempFile("log", "", pool), // create file + mFile(mTempFile.getName().c_str()) // open it + { + LLError::setFatalFunction(wouldHaveCrashed); + LLError::setDefaultLevel(level); + LLError::addRecorder(this); + } + + virtual ~LLReplayLogReal() + { + LLError::removeRecorder(this); + LLError::restoreSettings(mOldSettings); + } + + virtual void recordMessage(LLError::ELevel level, const std::string& message) + { + mFile << message << std::endl; + } + + virtual void reset() + { + mFile.close(); + mFile.open(mTempFile.getName().c_str()); + } + + virtual void replay(std::ostream& out) + { + mFile.close(); + std::ifstream inf(mTempFile.getName().c_str()); + std::string line; + while (std::getline(inf, line)) + { + out << line << std::endl; + } + } + +private: + LLError::Settings* mOldSettings; + NamedTempFile mTempFile; + std::ofstream mFile; +}; + class LLTestCallback : public tut::callback { public: - LLTestCallback(bool verbose_mode, std::ostream *stream) : + LLTestCallback(bool verbose_mode, std::ostream *stream, + boost::shared_ptr replayer) : mVerboseMode(verbose_mode), mTotalTests(0), mPassedTests(0), @@ -87,7 +150,8 @@ public: mSkippedTests(0), // By default, capture a shared_ptr to std::cout, with a no-op "deleter" // so that destroying the shared_ptr makes no attempt to delete std::cout. - mStream(boost::shared_ptr(&std::cout, boost::lambda::_1)) + mStream(boost::shared_ptr(&std::cout, boost::lambda::_1)), + mReplayer(replayer) { if (stream) { @@ -125,6 +189,16 @@ public: virtual void test_completed(const tut::test_result& tr) { ++mTotalTests; + + // If this test failed, dump requested log messages BEFORE stating the + // test result. + if (tr.result != tut::test_result::ok && tr.result != tut::test_result::skip) + { + mReplayer->replay(*mStream); + } + // Either way, clear stored messages in preparation for next test. + mReplayer->reset(); + std::ostringstream out; out << "[" << tr.group << ", " << tr.test; if (! tr.name.empty()) @@ -205,6 +279,7 @@ protected: int mFailedTests; int mSkippedTests; boost::shared_ptr mStream; + boost::shared_ptr mReplayer; }; // TeamCity specific class which emits service messages @@ -213,8 +288,9 @@ protected: class LLTCTestCallback : public LLTestCallback { public: - LLTCTestCallback(bool verbose_mode, std::ostream *stream) : - LLTestCallback(verbose_mode, stream) + LLTCTestCallback(bool verbose_mode, std::ostream *stream, + boost::shared_ptr replayer) : + LLTestCallback(verbose_mode, stream, replayer) { } @@ -355,6 +431,14 @@ void stream_usage(std::ostream& s, const char* app) ++option; } + s << app << " is also sensitive to environment variables:\n" + << "LOGTEST=level : for all tests, emit log messages at level 'level'\n" + << "LOGFAIL=level : only for failed tests, emit log messages at level 'level'\n" + << "where 'level' is one of ALL, DEBUG, INFO, WARN, ERROR, NONE.\n" + << "--debug is like LOGTEST=DEBUG, but --debug overrides LOGTEST.\n" + << "Setting LOGFAIL overrides both LOGTEST and --debug: the only log\n" + << "messages you will see will be for failed tests.\n\n"; + s << "Examples:" << std::endl; s << " " << app << " --verbose" << std::endl; s << "\tRun all the tests and report all results." << std::endl; @@ -391,8 +475,14 @@ int main(int argc, char **argv) LLError::initForApplication("."); LLError::setFatalFunction(wouldHaveCrashed); LLError::setDefaultLevel(LLError::LEVEL_ERROR); - //< *TODO: should come from error config file. Note that we - // have a command line option that sets this to debug. + // ^ possibly overridden by --debug, LOGTEST or LOGFAIL + + // LOGTEST overrides default, but can be overridden by --debug or LOGFAIL. + const char* LOGTEST = getenv("LOGTEST"); + if (LOGTEST) + { + LLError::setDefaultLevel(LLError::decodeLevel(LOGTEST)); + } #ifdef CTYPE_WORKAROUND ctype_workaround(); @@ -467,8 +557,6 @@ int main(int argc, char **argv) wait_at_exit = true; break; case 'd': - // *TODO: should come from error config file. We set it to - // ERROR by default, so this allows full debug levels. LLError::setDefaultLevel(LLError::LEVEL_DEBUG); break; case 'x': @@ -483,14 +571,28 @@ int main(int argc, char **argv) // run the tests + const char* LOGFAIL = getenv("LOGFAIL"); + boost::shared_ptr replayer; + // As described in stream_usage(), LOGFAIL overrides both --debug and + // LOGTEST. + if (LOGFAIL) + { + LLError::ELevel level = LLError::decodeLevel(LOGFAIL); + replayer.reset(new LLReplayLogReal(level, pool)); + } + else + { + replayer.reset(new LLReplayLog()); + } + LLTestCallback* mycallback; if (getenv("TEAMCITY_PROJECT_NAME")) { - mycallback = new LLTCTestCallback(verbose_mode, output); + mycallback = new LLTCTestCallback(verbose_mode, output, replayer); } else { - mycallback = new LLTestCallback(verbose_mode, output); + mycallback = new LLTestCallback(verbose_mode, output, replayer); } tut::runner.get().set_callback(mycallback); -- cgit v1.2.3 From 63b393da31e9fb972428fe957a05955a3d903113 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Mar 2012 18:49:27 -0500 Subject: Introduce (disabled) LLLeap debugging code to validate stdin writes. While debugging mysterious problem on Windows, one potential failure mode to rule out was the possibility that streaming std::ostringstream << LLSDNotationStreamer(large_LLSD) might itself cause trouble -- even before attempting to write to the LLProcess::WritePipe. The debugging code validated that the correct length is being reported, and that deserializing the resulting buffer produces equivalent LLSD. This code verified correct operation, and so has been disabled, as it's expensive at runtime. --- indra/llcommon/llleap.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 34f77c3f3a..034c809330 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -192,6 +192,31 @@ public: std::ostringstream buffer; buffer << LLSDNotationStreamer(packet); +/*==========================================================================*| + // DEBUGGING ONLY: don't copy str() if we can avoid it. + std::string strdata(buffer.str()); + if (std::size_t(buffer.tellp()) != strdata.length()) + { + LL_ERRS("LLLeap") << "tellp() -> " << buffer.tellp() << " != " + << "str().length() -> " << strdata.length() << LL_ENDL; + } + // DEBUGGING ONLY: reading back is terribly inefficient. + std::istringstream readback(strdata); + LLSD echo; + LLPointer parser(new LLSDNotationParser()); + S32 parse_status(parser->parse(readback, echo, strdata.length())); + if (parse_status == LLSDParser::PARSE_FAILURE) + { + LL_ERRS("LLLeap") << "LLSDNotationParser() cannot parse output of " + << "LLSDNotationStreamer()" << LL_ENDL; + } + if (! llsd_equals(echo, packet)) + { + LL_ERRS("LLLeap") << "LLSDNotationParser() produced different LLSD " + << "than passed to LLSDNotationStreamer()" << LL_ENDL; + } +|*==========================================================================*/ + LL_DEBUGS("EventHost") << "Sending: " << buffer.tellp() << ':'; std::string::size_type truncate(80); if (buffer.tellp() <= truncate) -- cgit v1.2.3 From 2491e2bda53ac3a1f2f021e425e7c7c4859e68ad Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Mar 2012 18:55:48 -0500 Subject: Additional diagnostic code to track down strange Windows pipe error. It seems that under certain circumstances, write logic was duplicating a chunk of the data being streamed down our pipe. But as this condition is only driven with a very large data stream, eyeballing that data stream is tedious. Add code to compare the raw received data with the expected stream, reporting where and how they first differ. --- indra/llcommon/tests/llleap_test.cpp | 51 +++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 73d7217608..f1309c5e32 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -89,6 +89,7 @@ namespace tut "import re\n" "import os\n" "import sys\n" + "\n" // Don't forget that this Python script is written to some // temp directory somewhere! Its __file__ is useless in // finding indra/lib/python. Use our __FILE__, with @@ -101,8 +102,15 @@ namespace tut " sys.path.insert(0,\n" " os.path.join(mydir, os.pardir, os.pardir, 'lib', 'python'))\n" " from indra.base import llsd\n" + "\n" "class ProtocolError(Exception):\n" + " def __init__(self, msg, data):\n" + " Exception.__init__(self, msg)\n" + " self.data = data\n" + "\n" + "class ParseError(ProtocolError):\n" " pass\n" + "\n" "def get():\n" " hdr = ''\n" " while ':' not in hdr and len(hdr) < 20:\n" @@ -110,11 +118,11 @@ namespace tut " if not hdr:\n" " sys.exit(0)\n" " if not hdr.endswith(':'):\n" - " raise ProtocolError('Expected len:data, got %r' % hdr)\n" + " raise ProtocolError('Expected len:data, got %r' % hdr, hdr)\n" " try:\n" " length = int(hdr[:-1])\n" " except ValueError:\n" - " raise ProtocolError('Non-numeric len %r' % hdr[:-1])\n" + " raise ProtocolError('Non-numeric len %r' % hdr[:-1], hdr[:-1])\n" " parts = []\n" " received = 0\n" " while received < length:\n" @@ -124,9 +132,12 @@ namespace tut " assert len(data) == length\n" " try:\n" " return llsd.parse(data)\n" - " except llsd.LLSDParseError, e:\n" - " print >>sys.stderr, 'Bad received packet (%s), %s bytes:' % \\\n" - " (e, len(data))\n" + // Seems the old indra.base.llsd module didn't properly + // convert IndexError (from running off end of string) to + // LLSDParseError. + " except (IndexError, llsd.LLSDParseError), e:\n" + " msg = 'Bad received packet (%s)' % e\n" + " print >>sys.stderr, '%s, %s bytes:' % (msg, len(data))\n" " showmax = 40\n" // We've observed failures with very large packets; // dumping the entire packet wastes time and space. @@ -148,7 +159,7 @@ namespace tut " offset += showmax\n" " print >>sys.stderr, '%04d: %r%s' % \\\n" " (offset, data[offset:], ellipsis)\n" - " raise\n" + " raise ParseError(msg, data)\n" "\n" "# deal with initial stdin message\n" // this will throw if the initial write to stdin doesn't @@ -462,7 +473,33 @@ namespace tut // Pass 'large' as reqid because we know the API // will echo reqid, and we want to receive it back. "request('" << api.getName() << "', dict(reqid=large))\n" - "resp = get()\n" + "try:\n" + " resp = get()\n" + "except ParseError, e:\n" + " # try to find where e.data diverges from expectation\n" + // Normally we'd expect a 'pump' key in there, + // too, with value replypump(). But Python + // serializes keys in a different order than C++, + // so incoming data start with 'data'. + // Truthfully, though, if we get as far as 'pump' + // before we find a difference, something's very + // strange. + " expect = llsd.format_notation(dict(data=dict(reqid=large)))\n" + " chunk = 40\n" + " for offset in xrange(0, max(len(e.data), len(expect)), chunk):\n" + " if e.data[offset:offset+chunk] != \\\n" + " expect[offset:offset+chunk]:\n" + " print >>sys.stderr, 'Offset %06d: expect %r,\\n'\\\n" + " ' get %r' %\\\n" + " (offset,\n" + " expect[offset:offset+chunk],\n" + " e.data[offset:offset+chunk])\n" + " break\n" + " else:\n" + " print >>sys.stderr, 'incoming data matches expect?!'\n" + " send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n" + " sys.exit(1)\n" + "\n" "echoed = resp['data']['reqid']\n" "if echoed == large:\n" " send('" << result.getName() << "', '')\n" -- cgit v1.2.3 From e7ceb82e71ed88354758c6f16525aa051d47bdec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Mar 2012 19:00:23 -0500 Subject: Further reduce the block size that LLProcess writes to child pipe. It seems that on Windows, even 32K is too big: one in three load-test runs fails with a duplicated block. Empirically, reducing it to 4K makes it much more stable -- at least we can run successfully 100 consecutive times, which is a step in the right direction. --- indra/llcommon/llprocess.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index d926791e9e..178ec064c0 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -181,8 +181,15 @@ public: { // Tackle the current buffer in discrete chunks. On // Windows, we've observed strange failures when trying to - // write big lengths (~1 MB) in a single operation. - std::size_t towrite((std::min)(remainlen, std::size_t(32*1024))); + // write big lengths (~1 MB) in a single operation. Even a + // 32K chunk seems too large. At some point along the way + // apr_file_write() returns 11 (Resource temporarily + // unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- + // even though it did write the chunk! Our next write + // attempt retries with the same chunk, resulting in the + // chunk being duplicated at the child end. Using smaller + // chunks is empirically more reliable. + std::size_t towrite((std::min)(remainlen, std::size_t(4*1024))); apr_size_t written(towrite); apr_status_t err = apr_file_write(mPipe, remainptr, &written); // EAGAIN is exactly what we want from a nonblocking pipe. -- cgit v1.2.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(-) 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.2.3 From cd468364a77ca184133f8c49c9042cefae5c6ce1 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 7 Mar 2012 17:26:43 -0600 Subject: MAINT-708 Don't compress UI textures. Use GL_NICEST as compression hint to avoid artifacts. --- indra/llrender/llgl.cpp | 2 +- indra/newview/llviewertexture.h | 2 ++ indra/newview/llviewertexturelist.cpp | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index b99465ee47..fdb674c433 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -578,7 +578,7 @@ bool LLGLManager::initGL() if (mGLVersion >= 3.f && LLImageGL::sCompressTextures) { //use texture compression - glHint(GL_TEXTURE_COMPRESSION_HINT, GL_FASTEST); + glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST); } else { //GL version is < 3.0, always disable texture compression diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 6b1654ae87..af8afef203 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -264,6 +264,8 @@ protected: void setTexelsPerImage(); private: friend class LLBumpImageList; + friend class LLUIImageList; + //note: do not make this function public. /*virtual*/ LLImageGL* getGLTexture() const ; virtual void switchToCachedImage(); diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 089f45ca89..f1555388a2 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1432,6 +1432,9 @@ LLUIImagePtr LLUIImageList::loadUIImage(LLViewerFetchedTexture* imagep, const st imagep->setAddressMode(LLTexUnit::TAM_CLAMP); + //don't compress UI images + imagep->getGLTexture()->setAllowCompression(false); + //all UI images are non-deletable imagep->setNoDelete(); -- cgit v1.2.3 From 83e69659e2c950bec02df8c8e1b15fbc34ca1dc6 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Thu, 8 Mar 2012 15:22:56 -0600 Subject: MAINT-708 Add checkbox to hardware settings for controlling texture compression. Default texture compression to on for cards with 512MB of VRAM or less, off otherwise. --- indra/llrender/llgl.cpp | 2 +- indra/newview/featuretable.txt | 7 +++++++ indra/newview/featuretable_linux.txt | 7 +++++++ indra/newview/featuretable_mac.txt | 8 ++++++++ indra/newview/featuretable_xp.txt | 7 +++++++ indra/newview/llfeaturemanager.cpp | 4 ++++ indra/newview/llfloaterhardwaresettings.cpp | 6 ++++++ .../default/xui/en/floater_hardware_settings.xml | 22 ++++++++++++++++++++++ 8 files changed, 62 insertions(+), 1 deletion(-) diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index fdb674c433..3f78ab20d0 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -576,7 +576,7 @@ bool LLGLManager::initGL() #endif } - if (mGLVersion >= 3.f && LLImageGL::sCompressTextures) + if (mGLVersion >= 2.1f && LLImageGL::sCompressTextures) { //use texture compression glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST); } diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt index 76bb2b0976..99721c3638 100644 --- a/indra/newview/featuretable.txt +++ b/indra/newview/featuretable.txt @@ -61,6 +61,7 @@ WLSkyDetail 1 128 Disregard128DefaultDrawDistance 1 1 Disregard96DefaultDrawDistance 1 1 RenderTextureMemoryMultiple 1 1.0 +RenderCompressTextures 1 1 RenderShaderLightingMaxLevel 1 3 RenderDeferred 1 1 RenderDeferredSSAO 1 1 @@ -222,6 +223,12 @@ RenderVBOEnable 1 1 list Class3 RenderVBOEnable 1 1 +// +// VRAM > 512MB +// +list VRAMGT512 +RenderCompressTextures 1 0 + // // No Pixel Shaders available // diff --git a/indra/newview/featuretable_linux.txt b/indra/newview/featuretable_linux.txt index 5e217e000a..289fd2ffbe 100644 --- a/indra/newview/featuretable_linux.txt +++ b/indra/newview/featuretable_linux.txt @@ -61,6 +61,7 @@ WLSkyDetail 1 128 Disregard128DefaultDrawDistance 1 1 Disregard96DefaultDrawDistance 1 1 RenderTextureMemoryMultiple 1 1.0 +RenderCompressTextures 1 1 RenderShaderLightingMaxLevel 1 3 RenderDeferred 1 1 RenderDeferredSSAO 1 1 @@ -219,6 +220,12 @@ RenderVBOEnable 1 1 list Class3 RenderVBOEnable 1 1 +// +// VRAM > 512MB +// +list VRAMGT512 +RenderCompressTextures 1 0 + // // No Pixel Shaders available // diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt index 915a012a39..3fcfb411d2 100644 --- a/indra/newview/featuretable_mac.txt +++ b/indra/newview/featuretable_mac.txt @@ -61,6 +61,7 @@ WLSkyDetail 1 128 Disregard128DefaultDrawDistance 1 1 Disregard96DefaultDrawDistance 1 1 RenderTextureMemoryMultiple 1 0.5 +RenderCompressTextures 1 1 RenderShaderLightingMaxLevel 1 3 RenderDeferred 1 1 RenderDeferredSSAO 1 1 @@ -247,6 +248,12 @@ RenderDeferred 0 0 RenderDeferredSSAO 0 0 RenderShadowDetail 0 0 +// +// VRAM > 512MB +// +list VRAMGT512 +RenderCompressTextures 1 0 + // // "Default" setups for safe, low, medium, high // @@ -286,6 +293,7 @@ RenderObjectBump 0 0 list OpenGLPre15 RenderVBOEnable 1 0 + list TexUnit8orLess RenderDeferredSSAO 0 0 diff --git a/indra/newview/featuretable_xp.txt b/indra/newview/featuretable_xp.txt index ae2cf910f2..f0f64e898e 100644 --- a/indra/newview/featuretable_xp.txt +++ b/indra/newview/featuretable_xp.txt @@ -61,6 +61,7 @@ WLSkyDetail 1 128 Disregard128DefaultDrawDistance 1 1 Disregard96DefaultDrawDistance 1 1 RenderTextureMemoryMultiple 1 1.0 +RenderCompressTextures 1 1 RenderShaderLightingMaxLevel 1 3 RenderDeferred 1 0 RenderDeferredSSAO 1 0 @@ -221,6 +222,12 @@ RenderVBOEnable 1 1 list Class3 RenderVBOEnable 1 1 +// +// VRAM > 512MB +// +list VRAMGT512 +RenderCompressTextures 1 0 + // // No Pixel Shaders available // diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index 08f9d26705..82f38ae4e9 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -779,6 +779,10 @@ void LLFeatureManager::applyBaseMasks() { maskFeatures("MapBufferRange"); } + if (gGLManager.mVRAM > 512) + { + maskFeatures("VRAMGT512"); + } // now mask by gpu string // Replaces ' ' with '_' in mGPUString to deal with inability for parser to handle spaces diff --git a/indra/newview/llfloaterhardwaresettings.cpp b/indra/newview/llfloaterhardwaresettings.cpp index f9a403cf9f..e8f1d58df8 100644 --- a/indra/newview/llfloaterhardwaresettings.cpp +++ b/indra/newview/llfloaterhardwaresettings.cpp @@ -99,6 +99,12 @@ void LLFloaterHardwareSettings::refreshEnabledState() getChildView("vbo")->setEnabled(FALSE); } + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderCompressTextures") || + !gGLManager.mHasVertexBufferObject) + { + getChildView("texture compression")->setEnabled(FALSE); + } + // if no windlight shaders, turn off nighttime brightness, gamma, and fog distance LLSpinCtrl* gamma_ctrl = getChild("gamma"); gamma_ctrl->setEnabled(!gPipeline.canUseWindLightShaders()); diff --git a/indra/newview/skins/default/xui/en/floater_hardware_settings.xml b/indra/newview/skins/default/xui/en/floater_hardware_settings.xml index 66bb9d3cea..9deb0d2030 100644 --- a/indra/newview/skins/default/xui/en/floater_hardware_settings.xml +++ b/indra/newview/skins/default/xui/en/floater_hardware_settings.xml @@ -132,6 +132,28 @@ name="vbo" tool_tip="Enabling this on modern hardware gives a performance gain. However, older hardware often has poor implementations of VBOs and you may get crashes when this is enabled." width="315" /> + + Enable S3TC: + + Date: Thu, 8 Mar 2012 17:58:25 -0700 Subject: fix for MAINT-701: crash at LLCurl::Multi::markDead() reviewed by davep --- indra/llmessage/llcurl.cpp | 48 ++++++++++++++++++++++++++++++---------------- indra/llmessage/llcurl.h | 3 ++- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index 2ef43d9c00..bac3e6c426 100644 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -548,6 +548,7 @@ LLCurl::Multi::Multi(F32 idle_time_out) mErrorCount(0), mState(STATE_READY), mDead(FALSE), + mValid(TRUE), mMutexp(NULL), mDeletionMutexp(NULL), mEasyMutexp(NULL) @@ -584,6 +585,9 @@ LLCurl::Multi::Multi(F32 idle_time_out) LLCurl::Multi::~Multi() { cleanup(true) ; + + delete mDeletionMutexp ; + mDeletionMutexp = NULL ; } void LLCurl::Multi::cleanup(bool deleted) @@ -592,7 +596,10 @@ void LLCurl::Multi::cleanup(bool deleted) { return ; //nothing to clean. } + llassert_always(deleted || !mValid) ; + LLMutexLock lock(mDeletionMutexp); + // Clean up active for(easy_active_list_t::iterator iter = mEasyActiveList.begin(); iter != mEasyActiveList.end(); ++iter) @@ -615,14 +622,12 @@ void LLCurl::Multi::cleanup(bool deleted) check_curl_multi_code(LLCurl::deleteMultiHandle(mCurlMultiHandle)); mCurlMultiHandle = NULL ; - + delete mMutexp ; mMutexp = NULL ; - delete mDeletionMutexp ; - mDeletionMutexp = NULL ; delete mEasyMutexp ; mEasyMutexp = NULL ; - + mQueued = 0 ; mState = STATE_COMPLETED; @@ -649,10 +654,20 @@ void LLCurl::Multi::unlock() void LLCurl::Multi::markDead() { - LLMutexLock lock(mDeletionMutexp) ; + { + LLMutexLock lock(mDeletionMutexp) ; + + if(mCurlMultiHandle != NULL) + { + mDead = TRUE ; + LLCurl::getCurlThread()->setPriority(mHandle, LLQueuedThread::PRIORITY_URGENT) ; + + return; + } + } - mDead = TRUE ; - LLCurl::getCurlThread()->setPriority(mHandle, LLQueuedThread::PRIORITY_URGENT) ; + //not valid, delete it. + delete this; } void LLCurl::Multi::setState(LLCurl::Multi::ePerformState state) @@ -746,10 +761,14 @@ bool LLCurl::Multi::doPerform() setState(STATE_COMPLETED) ; mIdleTimer.reset() ; } - else if(mIdleTimer.getElapsedTimeF32() > mIdleTimeOut) //idle for too long, remove it. + else if(!mValid && mIdleTimer.getElapsedTimeF32() > mIdleTimeOut) //idle for too long, remove it. { dead = true ; } + else if(mValid && mIdleTimer.getElapsedTimeF32() > mIdleTimeOut - 1.f) //idle for too long, mark it invalid. + { + mValid = FALSE ; + } return dead ; } @@ -971,14 +990,7 @@ void LLCurlThread::killMulti(LLCurl::Multi* multi) return ; } - if(multi->isValid()) - { - multi->markDead() ; - } - else - { - deleteMulti(multi) ; - } + multi->markDead() ; } //private @@ -997,6 +1009,10 @@ void LLCurlThread::deleteMulti(LLCurl::Multi* multi) void LLCurlThread::cleanupMulti(LLCurl::Multi* multi) { multi->cleanup() ; + if(multi->isDead()) //check if marked dead during cleaning up. + { + deleteMulti(multi) ; + } } //------------------------------------------------------------ diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h index 579ba0b1ab..d6a7714d4c 100644 --- a/indra/llmessage/llcurl.h +++ b/indra/llmessage/llcurl.h @@ -304,7 +304,7 @@ public: ePerformState getState() ; bool isCompleted() ; - bool isValid() {return mCurlMultiHandle != NULL ;} + bool isValid() {return mCurlMultiHandle != NULL && mValid;} bool isDead() {return mDead;} bool waitToComplete() ; @@ -333,6 +333,7 @@ private: ePerformState mState; BOOL mDead ; + BOOL mValid ; LLMutex* mMutexp ; LLMutex* mDeletionMutexp ; LLMutex* mEasyMutexp ; -- cgit v1.2.3 From a98516c95ef6ba795b4fb5bff65105a2edb23ed2 Mon Sep 17 00:00:00 2001 From: Xiaohong Bao Date: Thu, 8 Mar 2012 18:18:01 -0700 Subject: for MAINT-682: revert of changeset e63e7b0a4317: fix for SH-2668: "ocean" water is always 20m high instead of the Region Water Height --- indra/newview/llworld.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index 676287c0ad..5740ba1ccf 100644 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -839,7 +839,6 @@ void LLWorld::updateWaterObjects() // Now, get a list of the holes S32 x, y; - F32 water_height = gAgent.getRegion()->getWaterHeight() + 256.f; for (x = min_x; x <= max_x; x += rwidth) { for (y = min_y; y <= max_y; y += rwidth) @@ -851,7 +850,7 @@ void LLWorld::updateWaterObjects() waterp->setUseTexture(FALSE); waterp->setPositionGlobal(LLVector3d(x + rwidth/2, y + rwidth/2, - water_height)); + 256.f+DEFAULT_WATER_HEIGHT)); waterp->setScale(LLVector3((F32)rwidth, (F32)rwidth, 512.f)); gPipeline.createObject(waterp); mHoleWaterObjects.push_back(waterp); @@ -908,7 +907,7 @@ void LLWorld::updateWaterObjects() } waterp->setRegion(gAgent.getRegion()); - LLVector3d water_pos(water_center_x, water_center_y, water_height) ; + LLVector3d water_pos(water_center_x, water_center_y, 256.f+DEFAULT_WATER_HEIGHT) ; LLVector3 water_scale((F32) dim[0], (F32) dim[1], 512.f); //stretch out to horizon -- cgit v1.2.3 From f0c8c4e8fbe240b2e4ba04d47c497b2049ca1313 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Mar 2012 12:05:18 -0400 Subject: For a test program killed by signal, display signal name. --- indra/cmake/run_build_test.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/indra/cmake/run_build_test.py b/indra/cmake/run_build_test.py index ce2d1e0386..a2ef61c8fd 100644 --- a/indra/cmake/run_build_test.py +++ b/indra/cmake/run_build_test.py @@ -46,6 +46,7 @@ $/LicenseInfo$ import os import sys +import signal import subprocess def main(command, libpath=[], vars={}): @@ -113,6 +114,33 @@ def main(command, libpath=[], vars={}): sys.stdout.flush() return subprocess.call(command) +# swiped from vita, sigh, seems like a Bad Idea to introduce dependency +def translate_rc(rc): + """ + Accept an rc encoded as for subprocess.Popen.returncode: + None means still running + int >= 0 means terminated voluntarily with specified rc + int < 0 means terminated by signal (-rc) + + Return a string explaining the outcome. In case of a signal, try to + name the corresponding symbol from the 'signal' module. + """ + if rc is None: + return "still running" + + if rc >= 0: + return "terminated with rc %s" % rc + + # Negative rc means the child was terminated by signal -rc. + rc = -rc + for attr in dir(signal): + if attr.startswith('SIG') and getattr(signal, attr) == rc: + strc = attr + break + else: + strc = str(rc) + return "terminated by signal %s" % strc + if __name__ == "__main__": from optparse import OptionParser parser = OptionParser(usage="usage: %prog [options] command args...") @@ -140,5 +168,5 @@ if __name__ == "__main__": vars=dict([(pair.split('=', 1) + [""])[:2] for pair in opts.vars])) if rc not in (None, 0): print >>sys.stderr, "Failure running: %s" % " ".join(args) - print >>sys.stderr, "Error: %s" % rc + print >>sys.stderr, "Error %s: %s" % (rc, translate_rc(rc)) sys.exit((rc < 0) and 255 or rc) -- cgit v1.2.3 From cf6aabff08d8bc4e38b2b271085978486451eda2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Mar 2012 12:08:35 -0400 Subject: Normalize LLErrorThread::run() loop exit condition. --- indra/llcommon/llerrorthread.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/indra/llcommon/llerrorthread.cpp b/indra/llcommon/llerrorthread.cpp index 902eaa3b72..950fcd6e83 100644 --- a/indra/llcommon/llerrorthread.cpp +++ b/indra/llcommon/llerrorthread.cpp @@ -112,13 +112,8 @@ void LLErrorThread::run() #if !LL_WINDOWS U32 last_sig_child_count = 0; #endif - while (1) + while (! (LLApp::isError() || LLApp::isStopped())) { - if (LLApp::isError() || LLApp::isStopped()) - { - // The application has stopped running, time to take action (maybe) - break; - } #if !LL_WINDOWS // Check whether or not the main thread had a sig child we haven't handled. U32 current_sig_child_count = LLApp::getSigChildCount(); -- cgit v1.2.3 From 7d3cf544c7962ae6cc8e0e8b8e8db817e366a9cc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Mar 2012 14:15:11 -0400 Subject: Add timeout functionality to waitfor() helper functions. Otherwise, a stuck child process could potentially hang the test, and thus the whole viewer build. --- indra/llcommon/tests/llprocess_test.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 6103764f24..99186ed434 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -101,20 +101,26 @@ void yield(int seconds=1) LLEventPumps::instance().obtain("mainloop").post(LLSD()); } -void waitfor(LLProcess& proc) +void waitfor(LLProcess& proc, int timeout=60) { - while (proc.isRunning()) + int i = 0; + for ( ; i < timeout && proc.isRunning(); ++i) { yield(); } + tut::ensure(STRINGIZE("process took longer than " << timeout << " seconds to terminate"), + i < timeout); } -void waitfor(LLProcess::handle h, const std::string& desc) +void waitfor(LLProcess::handle h, const std::string& desc, int timeout=60) { - while (LLProcess::isRunning(h, desc)) + int i = 0; + for ( ; i < timeout && LLProcess::isRunning(h, desc); ++i) { yield(); } + tut::ensure(STRINGIZE("process took longer than " << timeout << " seconds to terminate"), + i < timeout); } /** -- cgit v1.2.3 From 1bdc876b790d054400240e4a4bda6d56d026cf59 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Mar 2012 14:28:19 -0400 Subject: Increase timeout for very-large-message test. Apparently, at least on Mac, there are circumstances in which the very-large- message test can take several times longer than normal, yet still complete successfully. This is always the problem with timeouts: does timeout expiration mean that the code in question is actually hung, or would it complete if given a bit longer? If very-large-message test fails, retry a few times with smaller sizes to try to find a size at which the test runs reliably. The default size, ca 1MB, is intended to be substantially larger than anything we'll encounter in the wild. Is that "unreasonably" large? Is there a "reasonable" size at which the test could consistently pass? Is that "reasonable" size still larger than what we expect to encounter in practice? Need more information, hence this code. --- indra/llcommon/tests/llleap_test.cpp | 72 ++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index f1309c5e32..aedb12a70b 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -60,12 +60,21 @@ void waitfor(const std::vector& instances, int timeout=60) // If we made it through all of 'instances' without finding one that's // still running, we're done. if (vli == vlend) + { +/*==========================================================================*| + std::cout << instances.size() << " LLLeap instances terminated in " + << i << " seconds, proceeding" << std::endl; +|*==========================================================================*/ return; + } // Found an instance that's still running. Wait and pump LLProcess. sleep(1); LLEventPumps::instance().obtain("mainloop").post(LLSD()); } - tut::ensure("timed out without terminating", i < timeout); + tut::ensure(STRINGIZE("at least 1 of " << instances.size() + << " LLLeap instances timed out (" + << timeout << " seconds) without terminating"), + i < timeout); } void waitfor(LLLeap* instance, int timeout=60) @@ -455,10 +464,11 @@ namespace tut result.ensure(); } - template<> template<> - void object::test<10>() + // This is the body of test<10>, extracted so we can run it over a number + // of large-message sizes. + void test_large_message(const std::string& PYTHON, const std::string& reader_module, + const std::string& test_name, size_t size) { - set_test_name("very large message"); ReqIDAPI api; Result result; NamedTempFile script("py", @@ -514,14 +524,62 @@ namespace tut " 'at offset %s, expected %r but got %r' %\n" " (start, large[start:end], echoed[start:end]))\n" "sys.exit(1)\n"); - waitfor(LLLeap::create(get_test_name(), + waitfor(LLLeap::create(test_name, sv(list_of (PYTHON) (script.getName()) - (stringize(BUFFERED_LENGTH))))); + (stringize(size)))), + 180); // try a longer timeout result.ensure(); } - // TODO: + // The point of this function is to try to find a size at which + // test_large_message() can succeed. We still want the overall test to + // fail; otherwise we won't get the coder's attention -- but if + // test_large_message() fails, try to find a plausible size at which it + // DOES work. + void test_or_split(const std::string& PYTHON, const std::string& reader_module, + const std::string& test_name, size_t size) + { + try + { + test_large_message(PYTHON, reader_module, test_name, size); + } + catch (const failure& e) + { + std::cout << "test_large_message(" << size << ") failed: " << e.what() << std::endl; + // If it still fails below 4K, give up: subdividing any further is + // pointless. + if (size >= 4096) + { + try + { + // Recur with half the size + size_t smaller(size/2); + test_or_split(PYTHON, reader_module, test_name, smaller); + // Recursive call will throw if test_large_message() + // failed, therefore we only reach the line below if it + // succeeded. + std::cout << "but test_large_message(" << smaller << ") succeeded" << std::endl; + } + catch (const failure&) + { + // The recursive test_or_split() call above has already + // handled the exception. We don't want our caller to see + // innermost exception; propagate outermost (below). + } + } + // In any case, because we reached here through failure of + // our original test_large_message(size) call, ensure failure + // propagates. + throw e; + } + } + template<> template<> + void object::test<10>() + { + set_test_name("very large message"); + test_or_split(PYTHON, reader_module, get_test_name(), BUFFERED_LENGTH); + } } // namespace tut -- cgit v1.2.3 From ab7fb5944a2c5d851944fec59a86c8b7e0df77d3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Mar 2012 14:40:46 -0400 Subject: Protect LLProcess destructor when run after APR shutdown. A static LLProcessPtr variable won't be destroyed until after procedural code has shut down APR. The trouble is that LLProcess's destructor unregisters itself from APR -- and, for an autokill LLProcess, attempts to kill the child process. All that is ill-advised after APR shutdown. Disable use of apr_pool_note_subprocess() mechanism. This should be another viable way of coping with static autokill LLProcessPtr variables: when the designated APR pool is cleaned up, APR promises to kill the child process. But whether it's an APR bug or a calling error, the present (now disabled) call in LLProcess results in OUR process, the viewer, getting SIGTERM when it asks to clean up the global APR pool. --- indra/llcommon/llprocess.cpp | 62 ++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 178ec064c0..bd08c3ab51 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -47,6 +47,9 @@ #include #include +/***************************************************************************** +* Helpers +*****************************************************************************/ static const char* whichfile_[] = { "stdin", "stdout", "stderr" }; static std::string empty; static LLProcess::Status interpret_status(int status); @@ -127,6 +130,9 @@ private: }; static LLProcessListener sProcessListener; +/***************************************************************************** +* WritePipe and ReadPipe +*****************************************************************************/ LLProcess::BasePipe::~BasePipe() {} const LLProcess::BasePipe::size_type // use funky syntax to call max() to avoid blighted max() macros @@ -456,6 +462,9 @@ private: bool mEOF; }; +/***************************************************************************** +* LLProcess itself +*****************************************************************************/ /// Need an exception to avoid constructing an invalid LLProcess object, but /// internal use only struct LLProcessError: public std::runtime_error @@ -669,11 +678,23 @@ LLProcess::LLProcess(const LLSDOrParams& params): // that doesn't always work (e.g. VWR-21538). if (params.autokill) { +/*==========================================================================*| + // NO: There may be an APR bug, not sure -- but at least on Mac, when + // gAPRPoolp is destroyed, OUR process receives SIGTERM! Apparently + // either our own PID is getting into the list of processes to kill() + // (unlikely), or somehow one of those PIDs is getting zeroed first, + // so that kill() sends SIGTERM to the whole process group -- this + // process included. I'd have to build and link with a debug version + // of APR to know for sure. It's too bad: this mechanism would be just + // right for dealing with static autokill LLProcessPtr variables, + // which aren't destroyed until after APR is no longer available. + // Tie the lifespan of this child process to the lifespan of 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(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT); +|*==========================================================================*/ // On Windows, associate the new child process with our Job Object. autokill(); @@ -732,6 +753,13 @@ std::string LLProcess::basename(const std::string& path) LLProcess::~LLProcess() { + // In the Linden viewer, there's at least one static LLProcessPtr. Its + // destructor will be called *after* ll_cleanup_apr(). In such a case, + // unregistering is pointless (and fatal!) -- and kill(), which also + // relies on APR, is impossible. + if (! gAPRPoolp) + return; + // Only in state RUNNING are we registered for callback. In UNSTARTED we // haven't yet registered. And since receiving the callback is the only // way we detect child termination, we only change from state RUNNING at @@ -1263,38 +1291,4 @@ static LLProcess::Status interpret_status(int status) return result; } -/*==========================================================================*| -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 // Posix -- cgit v1.2.3 From bdc27815cab6fb290c5d0a9bb6837a6451cc0c73 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Mar 2012 17:17:20 -0400 Subject: If very-large-message test fails, search for a size that works. We want to write a robust test that consistently works. On Windows, that appears to require constraining the max message size. I, the coder, could try submitting test runs of varying sizes to TC until I found a size that works... but that could take quite a while. If I were clever, I might even use a manual binary search. But computers are good at binary searching; there are even prepackaged algorithms in the STL. If I were cleverer still, I could make the test program itself search for size that works. --- indra/llcommon/tests/llleap_test.cpp | 90 ++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index aedb12a70b..1b71f7fb72 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -29,6 +29,7 @@ #include "llprocess.h" #include "stringize.h" #include "StringVec.h" +#include using boost::assign::list_of; @@ -533,6 +534,66 @@ namespace tut result.ensure(); } + struct TestLargeMessage: public std::binary_function + { + TestLargeMessage(const std::string& PYTHON_, const std::string& reader_module_, + const std::string& test_name_): + PYTHON(PYTHON_), + reader_module(reader_module_), + test_name(test_name_) + {} + + bool operator()(size_t left, size_t right) const + { + // We don't know whether upper_bound is going to pass the "sought + // value" as the left or the right operand. We pass 0 as the + // "sought value" so we can distinguish it. Of course that means + // the sequence we're searching must not itself contain 0! + size_t size; + bool success; + if (left) + { + size = left; + // Consider our return value carefully. Normal binary_search + // (or, in our case, upper_bound) expects a container sorted + // in ascending order, and defaults to the std::less + // comparator. Our container is in fact in ascending order, so + // return consistently with std::less. Here we were called as + // compare(item, sought). If std::less were called that way, + // 'true' would mean to move right (to higher numbers) within + // the sequence: the item being considered is less than the + // sought value. For us, that means that test_large_message() + // success should return 'true'. + success = true; + } + else + { + size = right; + // Here we were called as compare(sought, item). If std::less + // were called that way, 'true' would mean to move left (to + // lower numbers) within the sequence: the sought value is + // less than the item being considered. For us, that means + // test_large_message() FAILURE should return 'true', hence + // test_large_message() success should return 'false'. + success = false; + } + + try + { + test_large_message(PYTHON, reader_module, test_name, size); + std::cout << "test_large_message(" << size << ") succeeded" << std::endl; + return success; + } + catch (const failure& e) + { + std::cout << "test_large_message(" << size << ") failed: " << e.what() << std::endl; + return ! success; + } + } + + const std::string PYTHON, reader_module, test_name; + }; + // The point of this function is to try to find a size at which // test_large_message() can succeed. We still want the overall test to // fail; otherwise we won't get the coder's attention -- but if @@ -561,6 +622,35 @@ namespace tut // failed, therefore we only reach the line below if it // succeeded. std::cout << "but test_large_message(" << smaller << ") succeeded" << std::endl; + + // Binary search for largest size that works. But since + // std::binary_search() only returns bool, actually use + // std::upper_bound(), consistent with our desire to find + // the LARGEST size that works. First generate a sorted + // container of all the sizes we intend to try, from + // 'smaller' (known to work) to 'size' (known to fail). We + // could whomp up magic iterators to do this dynamically, + // without actually instantiating a vector, but for a test + // program this will do. At least preallocate the vector. + // Per TestLargeMessage comments, it's important that this + // vector not contain 0. + std::vector sizes; + sizes.reserve((size - smaller)/4096 + 1); + for (size_t sz(smaller), szend(size); sz < szend; sz += 4096) + sizes.push_back(sz); + // our comparator + TestLargeMessage tester(PYTHON, reader_module, test_name); + // Per TestLargeMessage comments, pass 0 as the sought value. + std::vector::const_iterator found = + std::upper_bound(sizes.begin(), sizes.end(), 0, tester); + if (found != sizes.end() && found != sizes.begin()) + { + std::cout << "test_large_message(" << *(found - 1) << ") is largest that succeeds" << std::endl; + } + else + { + std::cout << "cannot determine largest test_large_message(size) that succeeds" << std::endl; + } } catch (const failure&) { -- cgit v1.2.3 From 3953477ee5a9f60346a1d448b74bb5829bf18c56 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 13 Mar 2012 17:34:11 -0500 Subject: MAINT-708 Reduce amount of video memory consumed by vertex buffer objects. --- indra/llrender/llvertexbuffer.cpp | 159 +++++++++++++++++++++++++++++++------- indra/llrender/llvertexbuffer.h | 6 +- indra/newview/llviewerwindow.cpp | 5 +- 3 files changed, 140 insertions(+), 30 deletions(-) diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index 8b5503229f..a5e2342367 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -38,6 +38,8 @@ #include "llglslshader.h" #include "llmemory.h" +#define LL_VBO_POOLING 0 + //Next Highest Power Of Two //helper function, returns first number > v that is a power of 2, or v if v is already a power of 2 U32 nhpo2(U32 v) @@ -49,6 +51,35 @@ U32 nhpo2(U32 v) return r; } +//which power of 2 is i? +//assumes i is a power of 2 > 0 +U32 wpo2(U32 i) +{ + llassert(i > 0); + llassert(nhpo2(i) == i); + + U32 r = 0; + + while (i >>= 1) ++r; + + return r; +} + + +const U32 LL_VBO_BLOCK_SIZE = 2048; + +U32 vbo_block_size(U32 size) +{ //what block size will fit size? + U32 mod = size % LL_VBO_BLOCK_SIZE; + return mod == 0 ? size : size + (LL_VBO_BLOCK_SIZE-mod); +} + +U32 vbo_block_index(U32 size) +{ + return vbo_block_size(size)/LL_VBO_BLOCK_SIZE; +} + + //============================================================================ @@ -57,7 +88,11 @@ LLVBOPool LLVertexBuffer::sStreamVBOPool(GL_STREAM_DRAW_ARB, GL_ARRAY_BUFFER_ARB LLVBOPool LLVertexBuffer::sDynamicVBOPool(GL_DYNAMIC_DRAW_ARB, GL_ARRAY_BUFFER_ARB); LLVBOPool LLVertexBuffer::sStreamIBOPool(GL_STREAM_DRAW_ARB, GL_ELEMENT_ARRAY_BUFFER_ARB); LLVBOPool LLVertexBuffer::sDynamicIBOPool(GL_DYNAMIC_DRAW_ARB, GL_ELEMENT_ARRAY_BUFFER_ARB); + U32 LLVBOPool::sBytesPooled = 0; +U32 LLVBOPool::sIndexBytesPooled = 0; +U32 LLVertexBuffer::sAllocatedIndexBytes = 0; +U32 LLVertexBuffer::sIndexCount = 0; LLPrivateMemoryPool* LLVertexBuffer::sPrivatePoolp = NULL; U32 LLVertexBuffer::sBindCount = 0; @@ -74,6 +109,7 @@ U32 LLVertexBuffer::sLastMask = 0; bool LLVertexBuffer::sVBOActive = false; bool LLVertexBuffer::sIBOActive = false; U32 LLVertexBuffer::sAllocatedBytes = 0; +U32 LLVertexBuffer::sVertexCount = 0; bool LLVertexBuffer::sMapped = false; bool LLVertexBuffer::sUseStreamDraw = true; bool LLVertexBuffer::sUseVAO = false; @@ -134,39 +170,35 @@ public: }; -//which power of 2 is i? -//assumes i is a power of 2 > 0 -U32 wpo2(U32 i) -{ - llassert(i > 0); - llassert(nhpo2(i) == i); - - U32 r = 0; - - while (i >>= 1) ++r; - - return r; -} - volatile U8* LLVBOPool::allocate(U32& name, U32 size) { - llassert(nhpo2(size) == size); + llassert(vbo_block_size(size) == size); + + volatile U8* ret = NULL; + +#if LL_VBO_POOLING - U32 i = wpo2(size); + U32 i = vbo_block_index(size); if (mFreeList.size() <= i) { mFreeList.resize(i+1); } - volatile U8* ret = NULL; - if (mFreeList[i].empty()) { //make a new buffer glGenBuffersARB(1, &name); glBindBufferARB(mType, name); - LLVertexBuffer::sAllocatedBytes += size; + + if (mType == GL_ARRAY_BUFFER_ARB) + { + LLVertexBuffer::sAllocatedBytes += size; + } + else + { + LLVertexBuffer::sAllocatedIndexBytes += size; + } if (LLVertexBuffer::sDisableVBOMapping || mUsage != GL_DYNAMIC_DRAW_ARB) { @@ -185,19 +217,55 @@ volatile U8* LLVBOPool::allocate(U32& name, U32 size) name = mFreeList[i].front().mGLName; ret = mFreeList[i].front().mClientData; - sBytesPooled -= size; + if (mType == GL_ARRAY_BUFFER_ARB) + { + sBytesPooled -= size; + } + else + { + sIndexBytesPooled -= size; + } mFreeList[i].pop_front(); } +#else //no pooling + + glGenBuffersARB(1, &name); + glBindBufferARB(mType, name); + + if (mType == GL_ARRAY_BUFFER_ARB) + { + LLVertexBuffer::sAllocatedBytes += size; + } + else + { + LLVertexBuffer::sAllocatedIndexBytes += size; + } + + if (LLVertexBuffer::sDisableVBOMapping || mUsage != GL_DYNAMIC_DRAW_ARB) + { + glBufferDataARB(mType, size, 0, mUsage); + ret = (U8*) ll_aligned_malloc_16(size); + } + else + { //always use a true hint of static draw when allocating non-client-backed buffers + glBufferDataARB(mType, size, 0, GL_STATIC_DRAW_ARB); + } + + glBindBufferARB(mType, 0); + +#endif return ret; } void LLVBOPool::release(U32 name, volatile U8* buffer, U32 size) { - llassert(nhpo2(size) == size); + llassert(vbo_block_size(size) == size); - U32 i = wpo2(size); +#if LL_VBO_POOLING + + U32 i = vbo_block_index(size); llassert(mFreeList.size() > i); @@ -211,9 +279,29 @@ void LLVBOPool::release(U32 name, volatile U8* buffer, U32 size) } else { - sBytesPooled += size; + if (mType == GL_ARRAY_BUFFER_ARB) + { + sBytesPooled += size; + } + else + { + sIndexBytesPooled += size; + } mFreeList[i].push_back(rec); } +#else //no pooling + glDeleteBuffersARB(1, &name); + ll_aligned_free_16((U8*) buffer); + + if (mType == GL_ARRAY_BUFFER_ARB) + { + LLVertexBuffer::sAllocatedBytes -= size; + } + else + { + LLVertexBuffer::sAllocatedIndexBytes -= size; + } +#endif } void LLVBOPool::cleanup() @@ -237,8 +325,16 @@ void LLVBOPool::cleanup() l.pop_front(); - LLVertexBuffer::sAllocatedBytes -= size; - sBytesPooled -= size; + if (mType == GL_ARRAY_BUFFER_ARB) + { + sBytesPooled -= size; + LLVertexBuffer::sAllocatedBytes -= size; + } + else + { + sIndexBytesPooled -= size; + LLVertexBuffer::sAllocatedIndexBytes -= size; + } } size *= 2; @@ -261,7 +357,7 @@ S32 LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_MAX] = sizeof(F32), // TYPE_WEIGHT, sizeof(LLVector4), // TYPE_WEIGHT4, sizeof(LLVector4), // TYPE_CLOTHWEIGHT, - sizeof(LLVector4), // TYPE_TEXTURE_INDEX (actually exists as position.w), no extra data, but stride is 16 bytes + sizeof(U32), // TYPE_TEXTURE_INDEX (actually exists as position.w), no extra data, but stride is 16 bytes }; U32 LLVertexBuffer::sGLMode[LLRender::NUM_MODES] = @@ -898,6 +994,9 @@ LLVertexBuffer::~LLVertexBuffer() mFence = NULL; + sVertexCount -= mNumVerts; + sIndexCount -= mNumIndices; + llassert_always(!mMappedData && !mMappedIndexData); }; @@ -929,7 +1028,7 @@ void LLVertexBuffer::waitFence() const void LLVertexBuffer::genBuffer(U32 size) { - mSize = nhpo2(size); + mSize = vbo_block_size(size); if (mUsage == GL_STREAM_DRAW_ARB) { @@ -945,7 +1044,7 @@ void LLVertexBuffer::genBuffer(U32 size) void LLVertexBuffer::genIndices(U32 size) { - mIndicesSize = nhpo2(size); + mIndicesSize = vbo_block_size(size); if (mUsage == GL_STREAM_DRAW_ARB) { @@ -1121,7 +1220,9 @@ void LLVertexBuffer::updateNumVerts(S32 nverts) createGLBuffer(needed_size); } + sVertexCount -= mNumVerts; mNumVerts = nverts; + sVertexCount += mNumVerts; } void LLVertexBuffer::updateNumIndices(S32 nindices) @@ -1137,7 +1238,9 @@ void LLVertexBuffer::updateNumIndices(S32 nindices) createGLIndices(needed_size); } + sIndexCount -= mNumIndices; mNumIndices = nindices; + sIndexCount += mNumIndices; } void LLVertexBuffer::allocateBuffer(S32 nverts, S32 nindices, bool create) diff --git a/indra/llrender/llvertexbuffer.h b/indra/llrender/llvertexbuffer.h index d859199663..ceaa70c3b6 100644 --- a/indra/llrender/llvertexbuffer.h +++ b/indra/llrender/llvertexbuffer.h @@ -55,7 +55,8 @@ class LLVBOPool { public: static U32 sBytesPooled; - + static U32 sIndexBytesPooled; + LLVBOPool(U32 vboUsage, U32 vboType) : mUsage(vboUsage) , mType(vboType) @@ -332,6 +333,9 @@ public: static bool sIBOActive; static U32 sLastMask; static U32 sAllocatedBytes; + static U32 sAllocatedIndexBytes; + static U32 sVertexCount; + static U32 sIndexCount; static U32 sBindCount; static U32 sSetCount; }; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 0979e47c7c..328e79d76b 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -538,7 +538,10 @@ public: } - addText(xpos, ypos, llformat("%d MB Vertex Data (%d MB Pooled)", LLVertexBuffer::sAllocatedBytes/(1024*1024), LLVBOPool::sBytesPooled/(1024*1024))); + addText(xpos, ypos, llformat("%d MB Index Data (%d MB Pooled, %d KIndices)", LLVertexBuffer::sAllocatedIndexBytes/(1024*1024), LLVBOPool::sIndexBytesPooled/(1024*1024), LLVertexBuffer::sIndexCount/1024)); + ypos += y_inc; + + addText(xpos, ypos, llformat("%d MB Vertex Data (%d MB Pooled, %d KVerts)", LLVertexBuffer::sAllocatedBytes/(1024*1024), LLVBOPool::sBytesPooled/(1024*1024), LLVertexBuffer::sVertexCount/1024)); ypos += y_inc; addText(xpos, ypos, llformat("%d Vertex Buffers", LLVertexBuffer::sGLCount)); -- cgit v1.2.3 From b669b6262a131cef4b769f4c2c223d25c42cc1f2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 14 Mar 2012 09:57:52 -0400 Subject: On Windows, try cutting down the size of a "very large message." Ideally we'd love to be able to nail the underlying bug, but log output suggests it may actually go all the way down to the OS level. To move forward, try to bypass it. --- indra/llcommon/tests/llleap_test.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 1b71f7fb72..e01aedd7ee 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -41,7 +41,30 @@ StringVec sv(const StringVec& listof) { return listof; } #define sleep(secs) _sleep((secs) * 1000) #endif -const size_t BUFFERED_LENGTH = 1024*1023; // try wrangling just under a megabyte of data +#if ! LL_WINDOWS +const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte of data +#else +// "Then there's Windows... sigh." The "very large message" test is flaky in a +// way that seems to point to either the OS (nonblocking writes to pipes) or +// possibly the apr_file_write() function. Poring over log messages reveals +// that at some point along the way apr_file_write() returns 11 (Resource +// temporarily unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- even +// though it did write the chunk! Our next write attempt retries the same +// chunk, resulting in the chunk being duplicated at the child end, corrupting +// the data stream. Much as I would love to be able to fix it for real, such a +// fix would appear to require distinguishing bogus EAGAIN returns from real +// ones -- how?? Empirically this behavior is only observed when writing a +// "very large message". To be able to move forward at all, try to bypass this +// particular failure by adjusting the size of a "very large message" on +// Windows. When the test fails at BUFFERED_LENGTH, the test_or_split() +// function performs a binary search to find the largest size that will work. +// Running several times on a couple different Windows machines produces a +// range of "largest successful size" results... suggesting that it may be a +// matter of available OS buffer space? In any case, pick something small +// enough to be optimistic, while hopefully remaining comfortably larger than +// real messages we'll encounter in the wild. +const size_t BUFFERED_LENGTH = 256*1024; +#endif // LL_WINDOWS void waitfor(const std::vector& instances, int timeout=60) { @@ -645,11 +668,13 @@ namespace tut std::upper_bound(sizes.begin(), sizes.end(), 0, tester); if (found != sizes.end() && found != sizes.begin()) { - std::cout << "test_large_message(" << *(found - 1) << ") is largest that succeeds" << std::endl; + std::cout << "test_large_message(" << *(found - 1) + << ") is largest that succeeds" << std::endl; } else { - std::cout << "cannot determine largest test_large_message(size) that succeeds" << std::endl; + std::cout << "cannot determine largest test_large_message(size) " + << "that succeeds" << std::endl; } } catch (const failure&) -- cgit v1.2.3 From caba4fe1132a8927c8ee7b5a1133ce854986373e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 14 Mar 2012 13:02:37 -0400 Subject: Try new 20120314 APR build to verify Windows pipe write bug fix. --- autobuild.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 99fa201ee3..19913bf8de 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - 15333927db9f249daefcaed70fc6683c + 02eb2d32f4013e23eb0bc2cd1b7f9b82 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249247/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120210.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/251231/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120314.tar.bz2 name darwin @@ -102,9 +102,9 @@ archive hash - 297508ece8b0f334bae842fd147f2962 + 4ade1434e263cbb85a424e7dd35b8025 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249247/arch/Linux/installer/apr_suite-1.4.5-linux-20120211.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/251231/arch/Linux/installer/apr_suite-1.4.5-linux-20120314.tar.bz2 name linux @@ -114,9 +114,9 @@ archive hash - 7ad1e767f4419a6d35a70abba1edbcdc + 6a2f05e8ddf01036aada46377295887f url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/249247/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120210.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/251231/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120314.tar.bz2 name windows -- cgit v1.2.3 From a16f2faa8d4252c7a979c3f2783b65852656e47d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 14 Mar 2012 13:14:24 -0400 Subject: Backed out changeset 51205a909e2c (Windows APR pipe bug workaround) If in fact we've managed to fix the APR bug writing to a Windows named pipe, it should no longer be necessary to try to work around it by testing with a much smaller data volume on Windows! --- indra/llcommon/tests/llleap_test.cpp | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index e01aedd7ee..1b71f7fb72 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -41,30 +41,7 @@ StringVec sv(const StringVec& listof) { return listof; } #define sleep(secs) _sleep((secs) * 1000) #endif -#if ! LL_WINDOWS -const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte of data -#else -// "Then there's Windows... sigh." The "very large message" test is flaky in a -// way that seems to point to either the OS (nonblocking writes to pipes) or -// possibly the apr_file_write() function. Poring over log messages reveals -// that at some point along the way apr_file_write() returns 11 (Resource -// temporarily unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- even -// though it did write the chunk! Our next write attempt retries the same -// chunk, resulting in the chunk being duplicated at the child end, corrupting -// the data stream. Much as I would love to be able to fix it for real, such a -// fix would appear to require distinguishing bogus EAGAIN returns from real -// ones -- how?? Empirically this behavior is only observed when writing a -// "very large message". To be able to move forward at all, try to bypass this -// particular failure by adjusting the size of a "very large message" on -// Windows. When the test fails at BUFFERED_LENGTH, the test_or_split() -// function performs a binary search to find the largest size that will work. -// Running several times on a couple different Windows machines produces a -// range of "largest successful size" results... suggesting that it may be a -// matter of available OS buffer space? In any case, pick something small -// enough to be optimistic, while hopefully remaining comfortably larger than -// real messages we'll encounter in the wild. -const size_t BUFFERED_LENGTH = 256*1024; -#endif // LL_WINDOWS +const size_t BUFFERED_LENGTH = 1024*1023; // try wrangling just under a megabyte of data void waitfor(const std::vector& instances, int timeout=60) { @@ -668,13 +645,11 @@ namespace tut std::upper_bound(sizes.begin(), sizes.end(), 0, tester); if (found != sizes.end() && found != sizes.begin()) { - std::cout << "test_large_message(" << *(found - 1) - << ") is largest that succeeds" << std::endl; + std::cout << "test_large_message(" << *(found - 1) << ") is largest that succeeds" << std::endl; } else { - std::cout << "cannot determine largest test_large_message(size) " - << "that succeeds" << std::endl; + std::cout << "cannot determine largest test_large_message(size) that succeeds" << std::endl; } } catch (const failure&) -- cgit v1.2.3 From 4e889ee98e568328372a915252a3c7906819b018 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 14 Mar 2012 14:38:47 -0400 Subject: Backed out changeset 22664c76b59e (reinstate Windows pipe workaround) Sigh, the rejoicing was premature. --- indra/llcommon/tests/llleap_test.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 1b71f7fb72..e01aedd7ee 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -41,7 +41,30 @@ StringVec sv(const StringVec& listof) { return listof; } #define sleep(secs) _sleep((secs) * 1000) #endif -const size_t BUFFERED_LENGTH = 1024*1023; // try wrangling just under a megabyte of data +#if ! LL_WINDOWS +const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte of data +#else +// "Then there's Windows... sigh." The "very large message" test is flaky in a +// way that seems to point to either the OS (nonblocking writes to pipes) or +// possibly the apr_file_write() function. Poring over log messages reveals +// that at some point along the way apr_file_write() returns 11 (Resource +// temporarily unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- even +// though it did write the chunk! Our next write attempt retries the same +// chunk, resulting in the chunk being duplicated at the child end, corrupting +// the data stream. Much as I would love to be able to fix it for real, such a +// fix would appear to require distinguishing bogus EAGAIN returns from real +// ones -- how?? Empirically this behavior is only observed when writing a +// "very large message". To be able to move forward at all, try to bypass this +// particular failure by adjusting the size of a "very large message" on +// Windows. When the test fails at BUFFERED_LENGTH, the test_or_split() +// function performs a binary search to find the largest size that will work. +// Running several times on a couple different Windows machines produces a +// range of "largest successful size" results... suggesting that it may be a +// matter of available OS buffer space? In any case, pick something small +// enough to be optimistic, while hopefully remaining comfortably larger than +// real messages we'll encounter in the wild. +const size_t BUFFERED_LENGTH = 256*1024; +#endif // LL_WINDOWS void waitfor(const std::vector& instances, int timeout=60) { @@ -645,11 +668,13 @@ namespace tut std::upper_bound(sizes.begin(), sizes.end(), 0, tester); if (found != sizes.end() && found != sizes.begin()) { - std::cout << "test_large_message(" << *(found - 1) << ") is largest that succeeds" << std::endl; + std::cout << "test_large_message(" << *(found - 1) + << ") is largest that succeeds" << std::endl; } else { - std::cout << "cannot determine largest test_large_message(size) that succeeds" << std::endl; + std::cout << "cannot determine largest test_large_message(size) " + << "that succeeds" << std::endl; } } catch (const failure&) -- cgit v1.2.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(-) 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.2.3 From 3894701180e436f313afdb94ed9b5d95f15474ce Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 14 Mar 2012 16:13:23 -0500 Subject: MAINT-708 Fix for pink when enabling RenderUseVAO --- indra/llrender/llvertexbuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index a5e2342367..b4899209c4 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -357,7 +357,7 @@ S32 LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_MAX] = sizeof(F32), // TYPE_WEIGHT, sizeof(LLVector4), // TYPE_WEIGHT4, sizeof(LLVector4), // TYPE_CLOTHWEIGHT, - sizeof(U32), // TYPE_TEXTURE_INDEX (actually exists as position.w), no extra data, but stride is 16 bytes + sizeof(LLVector4), // TYPE_TEXTURE_INDEX (actually exists as position.w), no extra data, but stride is 16 bytes }; U32 LLVertexBuffer::sGLMode[LLRender::NUM_MODES] = -- cgit v1.2.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(-) 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.2.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(+) 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.2.3 From be669d4a1fe0e52ba8524ad851376cf653c822b6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 15 Mar 2012 16:51:34 -0400 Subject: On Windows, make "very large message" test ridiculously small. This test must not be subject to spurious environmental failures, else some kind soul will disable it entirely. We observe that APR specifies a hard-coded buffer size of 64Kbytes for pipe creation -- use that and cross fingers. --- indra/llcommon/tests/llleap_test.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index e01aedd7ee..9b755e9ca5 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -56,14 +56,8 @@ const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte // ones -- how?? Empirically this behavior is only observed when writing a // "very large message". To be able to move forward at all, try to bypass this // particular failure by adjusting the size of a "very large message" on -// Windows. When the test fails at BUFFERED_LENGTH, the test_or_split() -// function performs a binary search to find the largest size that will work. -// Running several times on a couple different Windows machines produces a -// range of "largest successful size" results... suggesting that it may be a -// matter of available OS buffer space? In any case, pick something small -// enough to be optimistic, while hopefully remaining comfortably larger than -// real messages we'll encounter in the wild. -const size_t BUFFERED_LENGTH = 256*1024; +// Windows. +const size_t BUFFERED_LENGTH = 65336; #endif // LL_WINDOWS void waitfor(const std::vector& instances, int timeout=60) -- cgit v1.2.3 From 4edf34ed01611d75bdcd98aa065a2b286845ebd9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 15 Mar 2012 23:30:36 -0400 Subject: Promote LLProcess::ReadPipe::size() to BasePipe (hence WritePipe). Certain use cases need to know whether the WritePipe buffer has been flushed to the pipe, or is still pending. --- indra/llcommon/llprocess.cpp | 1 + indra/llcommon/llprocess.h | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index bd08c3ab51..d4786035ce 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -163,6 +163,7 @@ public: } virtual std::ostream& get_ostream() { return mStream; } + virtual size_type size() const { return mStreambuf.size(); } bool tick(const LLSD&) { diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 06ada83698..51010966f9 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -340,6 +340,20 @@ public: typedef std::size_t size_type; static const size_type npos; + + /** + * Get accumulated buffer length. + * + * For WritePipe, is there still pending data to send to child? + * + * For ReadPipe, we often need to refrain from actually reading the + * std::istream returned by get_istream() until we've accumulated + * enough data to make it worthwhile. For instance, if we're expecting + * a number from the child, but the child happens to flush "12" before + * emitting "3\n", get_istream() >> myint could return 12 rather than + * 123! + */ + virtual size_type size() const = 0; }; /// As returned by getWritePipe() or getOptWritePipe() @@ -387,16 +401,6 @@ public: */ virtual std::string read(size_type len) = 0; - /** - * Get accumulated buffer length. - * Often we need to refrain from actually reading the std::istream - * returned by get_istream() until we've accumulated enough data to - * make it worthwhile. For instance, if we're expecting a number from - * the child, but the child happens to flush "12" before emitting - * "3\n", get_istream() >> myint could return 12 rather than 123! - */ - virtual size_type size() const = 0; - /** * Peek at accumulated buffer data without consuming it. Optional * parameters give you substr() functionality. -- cgit v1.2.3 From cf39274b640e983a5fcc2d03e4c47947a2b36732 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 15 Mar 2012 23:35:19 -0400 Subject: Make LLLeap intercept LL_ERRS termination and notify LEAP plugin. Have to pump "mainloop" a few times to flush the buffer to the pipe, a potentially risky strategy: we have to trust that whatever condition led to the LL_ERRS fatal error didn't break anything that listens on "mainloop". But the worst that could happen is that the plugin won't be notified -- just as if we didn't try in the first place. In other words, no harm in trying. --- indra/llcommon/llleap.cpp | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 034c809330..07880bd818 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -29,12 +29,15 @@ #include "stringize.h" #include "llsdutil.h" #include "llsdserialize.h" +#include "llerrorcontrol.h" +#include "lltimer.h" LLLeap::LLLeap() {} LLLeap::~LLLeap() {} class LLLeapImpl: public LLLeap { + LOG_CLASS(LLLeap); public: // Called only by LLLeap::create() LLLeapImpl(const std::string& desc, const std::vector& plugin): @@ -48,7 +51,8 @@ public: // Try to make that more difficult by generating a UUID for the reply- // pump name -- so it should NOT need tweaking for uniqueness. mReplyPump(LLUUID::generateNewID().asString()), - mExpect(0) + mExpect(0), + mPrevFatalFunction(LLError::getFatalFunction()) { // Rule out empty vector if (plugin.empty()) @@ -135,6 +139,9 @@ public: mStderrConnection = childerr.getPump() .listen("LLLeap", boost::bind(&LLLeapImpl::rstderr, this, _1)); + // For our lifespan, intercept any LL_ERRS so we can notify plugin + LLError::setFatalFunction(boost::bind(&LLLeapImpl::fatalFunction, this, _1)); + // Send child a preliminary event reporting our own reply-pump name -- // which would otherwise be pretty tricky to guess! // TODO TODO inject name of command pump here. @@ -150,6 +157,8 @@ public: virtual ~LLLeapImpl() { LL_DEBUGS("LLLeap") << "destroying LLLeap(\"" << mDesc << "\")" << LL_ENDL; + // Restore original FatalFunction + LLError::setFatalFunction(mPrevFatalFunction); } // Listener for failed launch attempt @@ -363,6 +372,30 @@ public: return false; } + void fatalFunction(const std::string& error) + { + // Notify plugin + LLSD event; + event["type"] = "error"; + event["error"] = error; + mReplyPump.post(event); + + // All the above really accomplished was to buffer the serialized + // event in our WritePipe. Have to pump mainloop a couple times to + // really write it out there... but time out in case we can't write. + LLProcess::WritePipe& childin(mChild->getWritePipe(LLProcess::STDIN)); + LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); + LLSD nop; + F64 until(LLTimer::getElapsedSeconds() + 2); + while (childin.size() && LLTimer::getElapsedSeconds() < until) + { + mainloop.post(nop); + } + + // forward the call to the previous FatalFunction + mPrevFatalFunction(error); + } + private: std::string mDesc; LLEventStream mDonePump; @@ -372,6 +405,7 @@ private: mStdinConnection, mStdoutConnection, mStdoutDataConnection, mStderrConnection; boost::scoped_ptr mBlocker; LLProcess::ReadPipe::size_type mExpect; + LLError::FatalFunction mPrevFatalFunction; }; // This must follow the declaration of LLLeapImpl, so it may as well be last. -- cgit v1.2.3 From ac14d9b50b45abee1899c53e3e0d4145ef81ceea Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Fri, 16 Mar 2012 17:36:10 +0200 Subject: MAINT-290 FIXED ([PUBLIC]Take object (which belongs to another avatar) does not work) Reason: Showing the confirmation dialog resets object selection, thus there is nothing to derez. Fix: pass selection to the confirm_take, so that selection doesn't "die" after confirmation dialog is opened --- indra/newview/llviewermenu.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 99540ccce9..7a873974eb 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -200,7 +200,7 @@ BOOL enable_take(); void handle_take(); void handle_object_show_inspector(); void handle_avatar_show_inspector(); -bool confirm_take(const LLSD& notification, const LLSD& response); +bool confirm_take(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle selection_handle); void handle_buy_object(LLSaleInfo sale_info); void handle_buy_contents(LLSaleInfo sale_info); @@ -4405,7 +4405,10 @@ void handle_take() LLNotification::Params params("ConfirmObjectTakeLock"); params.payload(payload); - params.functor.function(confirm_take); + // MAINT-290 + // Reason: Showing the confirmation dialog resets object selection, thus there is nothing to derez. + // Fix: pass selection to the confirm_take, so that selection doesn't "die" after confirmation dialog is opened + params.functor.function(boost::bind(confirm_take, _1, _2, LLSelectMgr::instance().getSelection())); if(locked_but_takeable_object || !you_own_everything) @@ -4458,7 +4461,7 @@ void handle_avatar_show_inspector() -bool confirm_take(const LLSD& notification, const LLSD& response) +bool confirm_take(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle selection_handle) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if(enable_take() && (option == 0)) -- cgit v1.2.3 From 0c8fac147d2baed8d8ef0e8c9bdcc47cb3082854 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 16 Mar 2012 15:34:21 -0400 Subject: Introduce LLLeapListener, associating one with each LLLeap object. Every LEAP plugin gets its own LLLeapListener, managing its own collection of listeners to various LLEventPumps. LLLeapListener's command LLEventPump now has a UUID for a name, both for uniqueness and to make it tough for a plugin to mess with any other. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/llleap.cpp | 38 +++-- indra/llcommon/llleaplistener.cpp | 287 ++++++++++++++++++++++++++++++++++++++ indra/llcommon/llleaplistener.h | 73 ++++++++++ 4 files changed, 391 insertions(+), 9 deletions(-) create mode 100644 indra/llcommon/llleaplistener.cpp create mode 100644 indra/llcommon/llleaplistener.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 47a8aa96aa..eec5250a23 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -64,6 +64,7 @@ set(llcommon_SOURCE_FILES llinitparam.cpp llinstancetracker.cpp llleap.cpp + llleaplistener.cpp llliveappconfig.cpp lllivefile.cpp lllog.cpp @@ -182,6 +183,7 @@ set(llcommon_HEADER_FILES llkeythrottle.h lllazy.h llleap.h + llleaplistener.h lllistenerwrapper.h lllinkedqueue.h llliveappconfig.h diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 07880bd818..0a57ef1c48 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -31,6 +31,12 @@ #include "llsdserialize.h" #include "llerrorcontrol.h" #include "lltimer.h" +#include "lluuid.h" +#include "llleaplistener.h" + +#if LL_MSVC +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif LLLeap::LLLeap() {} LLLeap::~LLLeap() {} @@ -52,7 +58,13 @@ public: // pump name -- so it should NOT need tweaking for uniqueness. mReplyPump(LLUUID::generateNewID().asString()), mExpect(0), - mPrevFatalFunction(LLError::getFatalFunction()) + mPrevFatalFunction(LLError::getFatalFunction()), + // Instantiate a distinct LLLeapListener for this plugin. (Every + // plugin will want its own collection of managed listeners, etc.) + // Pass it a callback to our connect() method, so it can send events + // from a particular LLEventPump to the plugin without having to know + // this class or method name. + mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2))) { // Rule out empty vector if (plugin.empty()) @@ -115,11 +127,8 @@ public: childout.setLimit(20); childerr.setLimit(20); - // Serialize any event received on mReplyPump to our child's stdin, - // suitably enriched with the pump name on which it was received. - mStdinConnection = mReplyPump - .listen("LLLeap", - boost::bind(&LLLeapImpl::wstdin, this, mReplyPump.getName(), _1)); + // Serialize any event received on mReplyPump to our child's stdin. + mStdinConnection = connect(mReplyPump, "LLLeap"); // Listening on stdout is stateful. In general, we're either waiting // for the length prefix or waiting for the specified length of data. @@ -144,13 +153,12 @@ public: // Send child a preliminary event reporting our own reply-pump name -- // which would otherwise be pretty tricky to guess! -// TODO TODO inject name of command pump here. wstdin(mReplyPump.getName(), LLSDMap - ("command", LLSD()) + ("command", mListener->getName()) // Include LLLeap features -- this may be important for child to // construct (or recognize) current protocol. - ("features", LLSD::emptyMap())); + ("features", LLLeapListener::getFeatures())); } // Normally we'd expect to arrive here only via done() @@ -397,6 +405,17 @@ public: } private: + /// We always want to listen on mReplyPump with wstdin(); under some + /// circumstances we'll also echo other LLEventPumps to the plugin. + LLBoundListener connect(LLEventPump& pump, const std::string& listener) + { + // Serialize any event received on the specified LLEventPump to our + // child's stdin, suitably enriched with the pump name on which it was + // received. + return pump.listen(listener, + boost::bind(&LLLeapImpl::wstdin, this, pump.getName(), _1)); + } + std::string mDesc; LLEventStream mDonePump; LLEventStream mReplyPump; @@ -406,6 +425,7 @@ private: boost::scoped_ptr mBlocker; LLProcess::ReadPipe::size_type mExpect; LLError::FatalFunction mPrevFatalFunction; + boost::scoped_ptr mListener; }; // This must follow the declaration of LLLeapImpl, so it may as well be last. diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp new file mode 100644 index 0000000000..fa5730f112 --- /dev/null +++ b/indra/llcommon/llleaplistener.cpp @@ -0,0 +1,287 @@ +/** + * @file llleaplistener.cpp + * @author Nat Goodspeed + * @date 2012-03-16 + * @brief Implementation for llleaplistener. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llleaplistener.h" +// STL headers +// std headers +// external library headers +#include +// other Linden headers +#include "lluuid.h" +#include "llsdutil.h" +#include "stringize.h" + +/***************************************************************************** +* LEAP FEATURE STRINGS +*****************************************************************************/ +/** + * Implement "getFeatures" command. The LLSD map thus obtained is intended to + * be machine-readable (read: easily-parsed, if parsing be necessary) and to + * highlight the differences between this version of the LEAP protocol and + * the baseline version. A client may thus determine whether or not the + * running viewer supports some recent feature of interest. + * + * This method is defined at the top of this implementation file so it's easy + * to find, easy to spot, easy to update as we enhance the LEAP protocol. + */ +/*static*/ LLSD LLLeapListener::getFeatures() +{ + static LLSD features; + if (features.isUndefined()) + { + features = LLSD::emptyMap(); + + // This initial implementation IS the baseline LEAP protocol; thus the + // set of differences is empty; thus features is initially empty. +// features["featurename"] = "value"; + } + + return features; +} + +LLLeapListener::LLLeapListener(const ConnectFunc& connect): + // Each LEAP plugin has an instance of this listener. Make the command + // pump name difficult for other such plugins to guess. + LLEventAPI(LLUUID::generateNewID().asString(), + "Operations relating to the LLSD Event API Plugin (LEAP) protocol"), + mConnect(connect) +{ + LLSD need_name(LLSDMap("name", LLSD())); + add("newpump", + "Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n" + "If [\"type\"] == \"LLEventQueue\", make LLEventQueue, else LLEventStream.\n" + "Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n" + "Returns actual name in [\"name\"] (may be different if collision).", + &LLLeapListener::newpump, + need_name); + add("killpump", + "Delete LLEventPump [\"name\"] created by \"newpump\".\n" + "Returns [\"status\"] boolean indicating whether such a pump existed.", + &LLLeapListener::killpump, + need_name); + LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD())); + add("listen", + "Listen to an existing LLEventPump named [\"source\"], with listener name\n" + "[\"listener\"].\n" + "By default, send events on [\"source\"] to the plugin, decorated\n" + "with [\"pump\"]=[\"source\"].\n" + "If [\"dest\"] specified, send undecorated events on [\"source\"] to the\n" + "LLEventPump named [\"dest\"].\n" + "Returns [\"status\"] boolean indicating whether the connection was made.", + &LLLeapListener::listen, + need_source_listener); + add("stoplistening", + "Disconnect a connection previously established by \"listen\".\n" + "Pass same [\"source\"] and [\"listener\"] arguments.\n" + "Returns [\"status\"] boolean indicating whether such a listener existed.", + &LLLeapListener::stoplistening, + need_source_listener); + add("ping", + "No arguments, just a round-trip sanity check.", + &LLLeapListener::ping); + add("getAPIs", + "Enumerate all LLEventAPI instances by name and description.", + &LLLeapListener::getAPIs); + add("getAPI", + "Get name, description, dispatch key and operations for LLEventAPI [\"api\"].", + &LLLeapListener::getAPI, + LLSD().with("api", LLSD())); + add("getFeatures", + "Return an LLSD map of feature strings (deltas from baseline LEAP protocol)", + static_cast(&LLLeapListener::getFeatures)); + add("getFeature", + "Return the feature value with key [\"feature\"]", + &LLLeapListener::getFeature, + LLSD().with("feature", LLSD())); +} + +LLLeapListener::~LLLeapListener() +{ + // We'd have stored a map of LLTempBoundListener instances, save that the + // operation of inserting into a std::map necessarily copies the + // value_type, and Bad Things would happen if you copied an + // LLTempBoundListener. (Destruction of the original would disconnect the + // listener, invalidating every stored connection.) + BOOST_FOREACH(ListenersMap::value_type& pair, mListeners) + { + pair.second.disconnect(); + } +} + +void LLLeapListener::newpump(const LLSD& request) +{ + Response reply(LLSD(), request); + + std::string name = request["name"]; + LLSD const & type = request["type"]; + + LLEventPump * new_pump = NULL; + if (type.asString() == "LLEventQueue") + { + new_pump = new LLEventQueue(name, true); // tweak name for uniqueness + } + else + { + if (! (type.isUndefined() || type.asString() == "LLEventStream")) + { + reply.warn(STRINGIZE("unknown 'type' " << type << ", using LLEventStream")); + } + new_pump = new LLEventStream(name, true); // tweak name for uniqueness + } + + name = new_pump->getName(); + + mEventPumps.insert(name, new_pump); + + // Now listen on this new pump with our plugin listener + std::string myname("llleap"); + saveListener(name, myname, mConnect(*new_pump, myname)); + + reply["name"] = name; +} + +void LLLeapListener::killpump(const LLSD& request) +{ + Response reply(LLSD(), request); + + std::string name = request["name"]; + // success == (nonzero number of entries were erased) + reply["status"] = bool(mEventPumps.erase(name)); +} + +void LLLeapListener::listen(const LLSD& request) +{ + Response reply(LLSD(), request); + + std::string source_name = request["source"]; + std::string dest_name = request["dest"]; + std::string listener_name = request["listener"]; + + LLEventPump & source = LLEventPumps::instance().obtain(source_name); + + reply["status"] = false; + if (mListeners.find(ListenersMap::key_type(source_name, listener_name)) == mListeners.end()) + { + try + { + if (request["dest"].isDefined()) + { + // If we're asked to connect the "source" pump to a + // specific "dest" pump, find dest pump and connect it. + LLEventPump & dest = LLEventPumps::instance().obtain(dest_name); + saveListener(source_name, listener_name, + source.listen(listener_name, + boost::bind(&LLEventPump::post, &dest, _1))); + } + else + { + // "dest" unspecified means to direct events on "source" + // to our plugin listener. + saveListener(source_name, listener_name, mConnect(source, listener_name)); + } + reply["status"] = true; + } + catch (const LLEventPump::DupListenerName &) + { + // pass - status already set to false + } + } +} + +void LLLeapListener::stoplistening(const LLSD& request) +{ + Response reply(LLSD(), request); + + std::string source_name = request["source"]; + std::string listener_name = request["listener"]; + + ListenersMap::iterator finder = + mListeners.find(ListenersMap::key_type(source_name, listener_name)); + + reply["status"] = false; + if(finder != mListeners.end()) + { + reply["status"] = true; + finder->second.disconnect(); + mListeners.erase(finder); + } +} + +void LLLeapListener::ping(const LLSD& request) const +{ + // do nothing, default reply suffices + Response(LLSD(), request); +} + +void LLLeapListener::getAPIs(const LLSD& request) const +{ + Response reply(LLSD(), request); + + for (LLEventAPI::instance_iter eai(LLEventAPI::beginInstances()), + eaend(LLEventAPI::endInstances()); + eai != eaend; ++eai) + { + LLSD info; + info["desc"] = eai->getDesc(); + reply[eai->getName()] = info; + } +} + +void LLLeapListener::getAPI(const LLSD& request) const +{ + Response reply(LLSD(), request); + + LLEventAPI* found = LLEventAPI::getInstance(request["api"]); + if (found) + { + reply["name"] = found->getName(); + reply["desc"] = found->getDesc(); + reply["key"] = found->getDispatchKey(); + LLSD ops; + for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end()); + oi != oend; ++oi) + { + ops.append(found->getMetadata(oi->first)); + } + reply["ops"] = ops; + } +} + +void LLLeapListener::getFeatures(const LLSD& request) const +{ + // Merely constructing and destroying a Response object suffices here. + // Giving it a name would only produce fatal 'unreferenced variable' + // warnings. + Response(getFeatures(), request); +} + +void LLLeapListener::getFeature(const LLSD& request) const +{ + Response reply(LLSD(), request); + + LLSD::String feature_name(request["feature"]); + LLSD features(getFeatures()); + if (features[feature_name].isDefined()) + { + reply["feature"] = features[feature_name]; + } +} + +void LLLeapListener::saveListener(const std::string& pump_name, + const std::string& listener_name, + const LLBoundListener& listener) +{ + mListeners.insert(ListenersMap::value_type(ListenersMap::key_type(pump_name, listener_name), + listener)); +} diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h new file mode 100644 index 0000000000..2193d81b9e --- /dev/null +++ b/indra/llcommon/llleaplistener.h @@ -0,0 +1,73 @@ +/** + * @file llleaplistener.h + * @author Nat Goodspeed + * @date 2012-03-16 + * @brief LLEventAPI supporting LEAP plugins + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLLEAPLISTENER_H) +#define LL_LLLEAPLISTENER_H + +#include "lleventapi.h" +#include +#include +#include +#include + +/// Listener class implementing LLLeap query/control operations. +/// See https://jira.lindenlab.com/jira/browse/DEV-31978. +class LLLeapListener: public LLEventAPI +{ +public: + /** + * Decouple LLLeap by dependency injection. Certain LLLeapListener + * operations must be able to cause LLLeap to listen on a specified + * LLEventPump with the LLLeap listener that wraps incoming events in an + * outer (pump=, data=) map and forwards them to the plugin. Very well, + * define the signature for a function that will perform that, and make + * our constructor accept such a function. + */ + typedef boost::function + ConnectFunc; + LLLeapListener(const ConnectFunc& connect); + ~LLLeapListener(); + + static LLSD getFeatures(); + +private: + void newpump(const LLSD&); + void killpump(const LLSD&); + void listen(const LLSD&); + void stoplistening(const LLSD&); + void ping(const LLSD&) const; + void getAPIs(const LLSD&) const; + void getAPI(const LLSD&) const; + void getFeatures(const LLSD&) const; + void getFeature(const LLSD&) const; + + void saveListener(const std::string& pump_name, const std::string& listener_name, + const LLBoundListener& listener); + + ConnectFunc mConnect; + + // In theory, listen() could simply call the relevant LLEventPump's + // listen() method, stoplistening() likewise. Lifespan issues make us + // capture the LLBoundListener objects: when this object goes away, all + // those listeners should be disconnected. But what if the client listens, + // stops, listens again on the same LLEventPump with the same listener + // name? Merely collecting LLBoundListeners wouldn't adequately track + // that. So capture the latest LLBoundListener for this LLEventPump name + // and listener name. + typedef std::map, LLBoundListener> ListenersMap; + ListenersMap mListeners; + // Similar lifespan reasoning applies to LLEventPumps instantiated by + // newpump() operations. + typedef boost::ptr_map EventPumpsMap; + EventPumpsMap mEventPumps; +}; + +#endif /* ! defined(LL_LLLEAPLISTENER_H) */ -- cgit v1.2.3 From aa4ccff19f3f7e2a16509d4e31d7c69caeea96c1 Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Mon, 19 Mar 2012 16:44:50 +0200 Subject: MAINT-261 FIXED Display previously retrieved data (until we get the update) if object inspector is invoked for the same object. --- indra/newview/llinspectobject.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/indra/newview/llinspectobject.cpp b/indra/newview/llinspectobject.cpp index acc139c569..a7b93b8030 100644 --- a/indra/newview/llinspectobject.cpp +++ b/indra/newview/llinspectobject.cpp @@ -111,6 +111,7 @@ private: private: LLUUID mObjectID; + LLUUID mPreviousObjectID; S32 mObjectFace; viewer_media_t mMediaImpl; LLMediaEntry* mMediaEntry; @@ -249,6 +250,7 @@ void LLInspectObject::onClose(bool app_quitting) { // Release selection to deselect mObjectSelection = NULL; + mPreviousObjectID = mObjectID; getChild("gear_btn")->hideMenu(); } @@ -266,6 +268,13 @@ void LLInspectObject::update() LLSelectNode* nodep = selection->getFirstRootNode(); if (!nodep) return; + // If we don't have fresh object info yet and it's the object we inspected last time, + // keep showing the previously retrieved data until we get the update. + if (!nodep->mValid && nodep->getObject()->getID() == mPreviousObjectID) + { + return; + } + updateButtons(nodep); updateName(nodep); updateDescription(nodep); -- cgit v1.2.3 From 7b4d05894df0b9151dfcf95c3d70c652dc9b922c Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Mon, 19 Mar 2012 19:12:03 +0200 Subject: MAINT-141, MAINT-95, MAINT-62 FIXED Don't delete files using masks starting with "/". Encountering such a mask will show a warning message and throw a debug assertion. --- indra/linux_updater/linux_updater.cpp | 2 +- indra/llvfs/lldir.cpp | 7 +++++++ indra/mac_updater/mac_updater.cpp | 4 +--- indra/newview/llappviewer.cpp | 10 +++------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/indra/linux_updater/linux_updater.cpp b/indra/linux_updater/linux_updater.cpp index eed00ac06e..9872c0ce27 100644 --- a/indra/linux_updater/linux_updater.cpp +++ b/indra/linux_updater/linux_updater.cpp @@ -751,7 +751,7 @@ void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state) else if ((!strcmp(argv[i], "--image-dir")) && (++i < argc)) { app_state->image_dir = argv[i]; - app_state->image_dir_iter = new LLDirIterator(argv[i], "/*.jpg"); + app_state->image_dir_iter = new LLDirIterator(argv[i], "*.jpg"); } else if ((!strcmp(argv[i], "--dest")) && (++i < argc)) { diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp index f3ac17d612..32d081d552 100644 --- a/indra/llvfs/lldir.cpp +++ b/indra/llvfs/lldir.cpp @@ -86,6 +86,13 @@ S32 LLDir::deleteFilesInDir(const std::string &dirname, const std::string &mask) std::string fullpath; S32 result; + // File masks starting with "/" will match nothing, so we consider them invalid. + if (LLStringUtil::startsWith(mask, getDirDelimiter())) + { + llwarns << "Invalid file mask: " << mask << llendl; + llassert(!"Invalid file mask"); + } + LLDirIterator iter(dirname, mask); while (iter.next(filename)) { diff --git a/indra/mac_updater/mac_updater.cpp b/indra/mac_updater/mac_updater.cpp index 809f66cb1d..aa45c5d23f 100644 --- a/indra/mac_updater/mac_updater.cpp +++ b/indra/mac_updater/mac_updater.cpp @@ -1188,9 +1188,7 @@ void *updatethreadproc(void*) llinfos << "Clearing cache..." << llendl; - char mask[LL_MAX_PATH]; /* Flawfinder: ignore */ - snprintf(mask, LL_MAX_PATH, "%s*.*", gDirUtilp->getDirDelimiter().c_str()); - gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""),mask); + gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*"); llinfos << "Clear complete." << llendl; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 5941dbc126..3368fdff57 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1512,7 +1512,6 @@ bool LLAppViewer::cleanup() if (! isError()) { std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); - logdir += gDirUtilp->getDirDelimiter(); gDirUtilp->deleteFilesInDir(logdir, "*-*-*-*-*.dmp"); } @@ -1761,8 +1760,7 @@ bool LLAppViewer::cleanup() if (mPurgeOnExit) { llinfos << "Purging all cache files on exit" << llendflush; - std::string mask = gDirUtilp->getDirDelimiter() + "*.*"; - gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""),mask); + gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*"); } removeMarkerFile(); // Any crashes from here on we'll just have to ignore @@ -2996,8 +2994,7 @@ void LLAppViewer::cleanupSavedSettings() void LLAppViewer::removeCacheFiles(const std::string& file_mask) { - std::string mask = gDirUtilp->getDirDelimiter() + file_mask; - gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), mask); + gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), file_mask); } void LLAppViewer::writeSystemInfo() @@ -3856,8 +3853,7 @@ void LLAppViewer::purgeCache() LL_INFOS("AppCache") << "Purging Cache and Texture Cache..." << LL_ENDL; LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE); LLVOCache::getInstance()->removeCache(LL_PATH_CACHE); - std::string mask = "*.*"; - gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), mask); + gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), "*.*"); } std::string LLAppViewer::getSecondLifeTitle() const -- cgit v1.2.3 From 76eeeadeaadf6f7d7b546382f0daac18a2315f81 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 19 Mar 2012 15:31:10 -0500 Subject: MAINT-771 Add RenderAutoHideSurfaceAreaLimit debug setting for protecting against sculpt based crashers. --- indra/llwindow/llwindowwin32.cpp | 3 ++- indra/newview/app_settings/settings.xml | 35 ++++++++++++--------------------- indra/newview/llspatialpartition.cpp | 1 + indra/newview/llspatialpartition.h | 2 ++ indra/newview/llvovolume.cpp | 3 +++ indra/newview/pipeline.cpp | 7 ++++++- indra/newview/pipeline.h | 1 + 7 files changed, 28 insertions(+), 24 deletions(-) diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 228fbefd19..987e9cb567 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -1451,7 +1451,8 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen &size, BO } else { - llinfos << "Created OpenGL " << llformat("%d.%d", attribs[1], attribs[3]) << " context." << llendl; + llinfos << "Created OpenGL " << llformat("%d.%d", attribs[1], attribs[3]) << + (LLRender::sGLCoreProfile ? " core" : " compatibility") << " context." << llendl; done = true; if (LLRender::sGLCoreProfile) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 992618ba11..c11f3cf978 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -9112,28 +9112,19 @@ Value 0 - RenderUseShaderLOD - - Comment - Whether we want to have different shaders for LOD - Persist - 1 - Type - Boolean - Value - 1 - - RenderUseShaderNearParticles - - Comment - Whether we want to use shaders on near particles - Persist - 1 - Type - Boolean - Value - 0 - + + RenderAutoHideSurfaceAreaLimit + + Comment + Maximum surface area of a set of proximal objects inworld before automatically hiding geometry to prevent system overload. + Persist + 1 + Type + F32 + Value + 0 + + RenderVBOEnable Comment diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp index 5d196a465f..38c7dd8283 100644 --- a/indra/newview/llspatialpartition.cpp +++ b/indra/newview/llspatialpartition.cpp @@ -1186,6 +1186,7 @@ void LLSpatialGroup::clearOcclusionState(U32 state, S32 mode) //====================================== LLSpatialGroup::LLSpatialGroup(OctreeNode* node, LLSpatialPartition* part) : + mObjectBoxSize(1.f), mState(0), mGeometryBytes(0), mSurfaceArea(0.f), diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h index 6c14ecf452..1a93145cc5 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -378,6 +378,8 @@ public: LLVector4a mObjectBounds[2]; // bounding box (center, size) of objects in this node LLVector4a mViewAngle; LLVector4a mLastUpdateViewAngle; + + F32 mObjectBoxSize; //cached mObjectBounds[1].getLength3() private: U32 mCurUpdatingTime ; diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index e7c35d8220..080d1f774a 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -4123,6 +4123,9 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) group->mGeometryBytes = 0; group->mSurfaceArea = 0; + //cache object box size since it might be used for determining visibility + group->mObjectBoxSize = group->mObjectBounds[1].getLength3().getF32(); + group->clearDrawMap(); mFaceList.clear(); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index fbaded04b0..de7296b2dc 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -187,6 +187,7 @@ F32 LLPipeline::RenderShadowFOVCutoff; BOOL LLPipeline::CameraOffset; F32 LLPipeline::CameraMaxCoF; F32 LLPipeline::CameraDoFResScale; +F32 LLPipeline::RenderAutoHideSurfaceAreaLimit; const F32 BACKLIGHT_DAY_MAGNITUDE_AVATAR = 0.2f; const F32 BACKLIGHT_NIGHT_MAGNITUDE_AVATAR = 0.1f; @@ -592,6 +593,7 @@ void LLPipeline::init() gSavedSettings.getControl("CameraOffset")->getCommitSignal()->connect(boost::bind(&LLPipeline::refreshCachedSettings)); gSavedSettings.getControl("CameraMaxCoF")->getCommitSignal()->connect(boost::bind(&LLPipeline::refreshCachedSettings)); gSavedSettings.getControl("CameraDoFResScale")->getCommitSignal()->connect(boost::bind(&LLPipeline::refreshCachedSettings)); + gSavedSettings.getControl("RenderAutoHideSurfaceAreaLimit")->getCommitSignal()->connect(boost::bind(&LLPipeline::refreshCachedSettings)); } LLPipeline::~LLPipeline() @@ -1015,6 +1017,7 @@ void LLPipeline::refreshCachedSettings() CameraOffset = gSavedSettings.getBOOL("CameraOffset"); CameraMaxCoF = gSavedSettings.getF32("CameraMaxCoF"); CameraDoFResScale = gSavedSettings.getF32("CameraDoFResScale"); + RenderAutoHideSurfaceAreaLimit = gSavedSettings.getF32("RenderAutoHideSurfaceAreaLimit"); updateRenderDeferred(); } @@ -3341,7 +3344,9 @@ void LLPipeline::postSort(LLCamera& camera) { LLSpatialGroup* group = *i; if (sUseOcclusion && - group->isOcclusionState(LLSpatialGroup::OCCLUDED)) + group->isOcclusionState(LLSpatialGroup::OCCLUDED) || + (RenderAutoHideSurfaceAreaLimit > 0.f && + group->mSurfaceArea > RenderAutoHideSurfaceAreaLimit*llmax(group->mObjectBoxSize, 10.f))) { continue; } diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index 3c4e389ce0..252fe1346c 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -849,6 +849,7 @@ public: static BOOL CameraOffset; static F32 CameraMaxCoF; static F32 CameraDoFResScale; + static F32 RenderAutoHideSurfaceAreaLimit; }; void render_bbox(const LLVector3 &min, const LLVector3 &max); -- cgit v1.2.3 From 89cb4c63e8b7ea62bc5452387b3ed19b12396739 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Tue, 20 Mar 2012 18:51:26 +0200 Subject: MAINT-789 FIXED ([PUBLIC]Button "Buy" isn't work in Object profile) - Set proper callback for buy button - Removed dead code - Fixed values for action combobox in object profile panel --- indra/newview/llsidepaneltaskinfo.cpp | 4 +--- indra/newview/skins/default/xui/en/sidepanel_task_info.xml | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/indra/newview/llsidepaneltaskinfo.cpp b/indra/newview/llsidepaneltaskinfo.cpp index 24cb559fd0..64b82aa0bb 100644 --- a/indra/newview/llsidepaneltaskinfo.cpp +++ b/indra/newview/llsidepaneltaskinfo.cpp @@ -94,7 +94,7 @@ BOOL LLSidepanelTaskInfo::postBuild() mPayBtn = getChild("pay_btn"); mPayBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onPayButtonClicked, this)); mBuyBtn = getChild("buy_btn"); - mBuyBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onBuyButtonClicked, this)); + mBuyBtn->setClickedCallback(boost::bind(&handle_buy)); mDetailsBtn = getChild("details_btn"); mDetailsBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onDetailsButtonClicked, this)); @@ -1114,8 +1114,6 @@ void LLSidepanelTaskInfo::doClickAction(U8 click_action) // Set click action back to its old value U8 click_action = 0; LLSelectMgr::getInstance()->selectionGetClickAction(&click_action); -// box->setCurrentByIndex((S32)click_action); - return; } } diff --git a/indra/newview/skins/default/xui/en/sidepanel_task_info.xml b/indra/newview/skins/default/xui/en/sidepanel_task_info.xml index 6600339ad7..bf2e6bc2d9 100644 --- a/indra/newview/skins/default/xui/en/sidepanel_task_info.xml +++ b/indra/newview/skins/default/xui/en/sidepanel_task_info.xml @@ -267,19 +267,19 @@ + value="Touch" /> + value="Sit" /> + value="Buy" /> + value="Pay" /> 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(-) 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: Tue, 20 Mar 2012 16:05:29 -0500 Subject: SH-795 Potential fix for crash after setting graphics detail to low. --- indra/newview/featuretable.txt | 34 +++++++++++++++++++++++++++++++++- indra/newview/featuretable_linux.txt | 31 +++++++++++++++++++++++++++++++ indra/newview/featuretable_mac.txt | 33 ++++++++++++++++++++++++++++++++- indra/newview/featuretable_xp.txt | 33 ++++++++++++++++++++++++++++++++- indra/newview/llfeaturemanager.cpp | 9 ++++++++- 5 files changed, 136 insertions(+), 4 deletions(-) diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt index 99721c3638..657a6b8945 100644 --- a/indra/newview/featuretable.txt +++ b/indra/newview/featuretable.txt @@ -72,6 +72,38 @@ RenderFSAASamples 1 16 RenderMaxTextureIndex 1 16 +// +// Low Graphics Settings (fixed function) +// +list LowFixedFunction +RenderAnisotropic 1 0 +RenderAvatarCloth 1 0 +RenderAvatarLODFactor 1 0 +RenderAvatarPhysicsLODFactor 1 0 +RenderAvatarMaxVisible 1 3 +RenderAvatarVP 1 0 +RenderFarClip 1 64 +RenderFlexTimeFactor 1 0 +RenderGlowResolutionPow 1 8 +RenderMaxPartCount 1 0 +RenderObjectBump 1 0 +RenderLocalLights 1 0 +RenderReflectionDetail 1 0 +RenderTerrainDetail 1 0 +RenderTerrainLODFactor 1 1 +RenderTransparentWater 1 0 +RenderTreeLODFactor 1 0 +RenderUseImpostors 1 1 +RenderVolumeLODFactor 1 1.125 +VertexShaderEnable 1 0 +WindLightUseAtmosShaders 1 0 +WLSkyDetail 1 48 +RenderDeferred 1 0 +RenderDeferredSSAO 1 0 +RenderShadowDetail 1 0 +RenderFSAASamples 1 0 + + // // Low Graphics Settings // @@ -95,7 +127,7 @@ RenderTransparentWater 1 0 RenderTreeLODFactor 1 0 RenderUseImpostors 1 1 RenderVolumeLODFactor 1 1.125 -VertexShaderEnable 1 0 +VertexShaderEnable 1 1 WindLightUseAtmosShaders 1 0 WLSkyDetail 1 48 RenderDeferred 1 0 diff --git a/indra/newview/featuretable_linux.txt b/indra/newview/featuretable_linux.txt index 289fd2ffbe..54d2286373 100644 --- a/indra/newview/featuretable_linux.txt +++ b/indra/newview/featuretable_linux.txt @@ -69,6 +69,37 @@ RenderShadowDetail 1 2 RenderFSAASamples 1 16 RenderMaxTextureIndex 1 16 +// +// Low Graphics Settings (fixed function) +// +list LowFixedFunction +RenderAnisotropic 1 0 +RenderAvatarCloth 1 0 +RenderAvatarLODFactor 1 0 +RenderAvatarPhysicsLODFactor 1 0 +RenderAvatarMaxVisible 1 3 +RenderAvatarVP 1 0 +RenderFarClip 1 64 +RenderFlexTimeFactor 1 0 +RenderGlowResolutionPow 1 8 +RenderLocalLights 1 0 +RenderMaxPartCount 1 0 +RenderObjectBump 1 0 +RenderReflectionDetail 1 0 +RenderTerrainDetail 1 0 +RenderTerrainLODFactor 1 1 +RenderTransparentWater 1 0 +RenderTreeLODFactor 1 0 +RenderUseImpostors 1 1 +RenderVolumeLODFactor 1 0.5 +VertexShaderEnable 1 1 +WindLightUseAtmosShaders 1 0 +WLSkyDetail 1 48 +RenderDeferred 1 0 +RenderDeferredSSAO 1 0 +RenderShadowDetail 1 0 +RenderFSAASamples 1 0 + // // Low Graphics Settings // diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt index 3fcfb411d2..d9b0c7a0a6 100644 --- a/indra/newview/featuretable_mac.txt +++ b/indra/newview/featuretable_mac.txt @@ -71,6 +71,37 @@ RenderUseStreamVBO 1 1 RenderFSAASamples 1 16 RenderMaxTextureIndex 1 16 +// +// Low Graphics Settings (fixed function) +// +list LowFixedFunction +RenderAnisotropic 1 0 +RenderAvatarCloth 1 0 +RenderAvatarLODFactor 1 0 +RenderAvatarPhysicsLODFactor 1 0 +RenderAvatarMaxVisible 1 3 +RenderAvatarVP 1 0 +RenderFarClip 1 64 +RenderFlexTimeFactor 1 0 +RenderGlowResolutionPow 1 8 +RenderLocalLights 1 0 +RenderMaxPartCount 1 0 +RenderObjectBump 1 0 +RenderReflectionDetail 1 0 +RenderTerrainDetail 1 0 +RenderTerrainLODFactor 1 1 +RenderTransparentWater 1 0 +RenderTreeLODFactor 1 0 +RenderUseImpostors 1 1 +RenderVolumeLODFactor 1 0.5 +VertexShaderEnable 1 0 +WindLightUseAtmosShaders 1 0 +WLSkyDetail 1 48 +RenderDeferred 1 0 +RenderDeferredSSAO 1 0 +RenderShadowDetail 1 0 +RenderFSAASamples 1 0 + // // Low Graphics Settings // @@ -94,7 +125,7 @@ RenderTransparentWater 1 0 RenderTreeLODFactor 1 0 RenderUseImpostors 1 1 RenderVolumeLODFactor 1 0.5 -VertexShaderEnable 1 0 +VertexShaderEnable 1 1 WindLightUseAtmosShaders 1 0 WLSkyDetail 1 48 RenderDeferred 1 0 diff --git a/indra/newview/featuretable_xp.txt b/indra/newview/featuretable_xp.txt index f0f64e898e..b43f2a7ae2 100644 --- a/indra/newview/featuretable_xp.txt +++ b/indra/newview/featuretable_xp.txt @@ -71,6 +71,37 @@ RenderUseStreamVBO 1 1 RenderFSAASamples 1 16 RenderMaxTextureIndex 1 16 +// +// Low Graphics Settings (fixed function) +// +list LowFixedFunction +RenderAnisotropic 1 0 +RenderAvatarCloth 1 0 +RenderAvatarLODFactor 1 0 +RenderAvatarPhysicsLODFactor 1 0 +RenderAvatarMaxVisible 1 3 +RenderAvatarVP 1 0 +RenderFarClip 1 64 +RenderFlexTimeFactor 1 0 +RenderGlowResolutionPow 1 8 +RenderLocalLights 1 0 +RenderMaxPartCount 1 0 +RenderObjectBump 1 0 +RenderReflectionDetail 1 0 +RenderTerrainDetail 1 0 +RenderTerrainLODFactor 1 1 +RenderTransparentWater 1 0 +RenderTreeLODFactor 1 0 +RenderUseImpostors 1 1 +RenderVolumeLODFactor 1 0.5 +VertexShaderEnable 1 0 +WindLightUseAtmosShaders 1 0 +WLSkyDetail 1 48 +RenderDeferred 1 0 +RenderDeferredSSAO 1 0 +RenderShadowDetail 1 0 +RenderFSAASamples 1 0 + // // Low Graphics Settings // @@ -94,7 +125,7 @@ RenderTransparentWater 1 0 RenderTreeLODFactor 1 0 RenderUseImpostors 1 1 RenderVolumeLODFactor 1 0.5 -VertexShaderEnable 1 0 +VertexShaderEnable 1 1 WindLightUseAtmosShaders 1 0 WLSkyDetail 1 48 RenderDeferred 1 0 diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index 82f38ae4e9..ec2493dd2e 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -670,7 +670,14 @@ void LLFeatureManager::setGraphicsLevel(S32 level, bool skipFeatures) switch (level) { case 0: - maskFeatures("Low"); + if (gGLManager.mGLVersion < 3.f || gGLManager.mIsIntel) + { //only use fixed function by default if GL version < 3.0 or this is an intel graphics chip + maskFeatures("LowFixedFunction"); + } + else + { //same as low, but with "Basic Shaders" enabled + maskFeatures("Low"); + } break; case 1: maskFeatures("Mid"); -- cgit v1.2.3 From b0ba9364fce6ae3ff132f63729e3d5f9b100afe9 Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Wed, 21 Mar 2012 17:50:34 +0200 Subject: MAINT-518 FIXED Sometimes nearby chat expand button failed to work. Made the nearby chat expand/collapse logic work regardless of the previous/saved state. --- indra/newview/llnearbychatbar.cpp | 28 ++++++++++++++++------------ indra/newview/llnearbychatbar.h | 3 +++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/indra/newview/llnearbychatbar.cpp b/indra/newview/llnearbychatbar.cpp index 4512c14b7a..c42930f89d 100644 --- a/indra/newview/llnearbychatbar.cpp +++ b/indra/newview/llnearbychatbar.cpp @@ -103,15 +103,17 @@ BOOL LLNearbyChatBar::postBuild() mNearbyChat = getChildView("nearby_chat"); - LLUICtrl* show_btn = getChild("show_nearby_chat"); + gSavedSettings.declareBOOL("nearbychat_history_visibility", mNearbyChat->getVisible(), "Visibility state of nearby chat history", TRUE); + BOOL show_nearby_chat = gSavedSettings.getBOOL("nearbychat_history_visibility"); + + LLButton* show_btn = getChild("show_nearby_chat"); show_btn->setCommitCallback(boost::bind(&LLNearbyChatBar::onToggleNearbyChatPanel, this)); + show_btn->setToggleState(show_nearby_chat); mOutputMonitor = getChild("chat_zone_indicator"); mOutputMonitor->setVisible(FALSE); - gSavedSettings.declareBOOL("nearbychat_history_visibility", mNearbyChat->getVisible(), "Visibility state of nearby chat history", TRUE); - - mNearbyChat->setVisible(gSavedSettings.getBOOL("nearbychat_history_visibility")); + showNearbyChatPanel(show_nearby_chat); // Register for font change notifications LLViewerChat::setFontChangedCallback(boost::bind(&LLNearbyChatBar::onChatFontChange, this, _1)); @@ -392,26 +394,23 @@ void LLNearbyChatBar::sendChat( EChatType type ) } } - -void LLNearbyChatBar::onToggleNearbyChatPanel() +void LLNearbyChatBar::showNearbyChatPanel(bool show) { - LLView* nearby_chat = getChildView("nearby_chat"); - - if (nearby_chat->getVisible()) + if (!show) { - if (!isMinimized()) + if (mNearbyChat->getVisible() && !isMinimized()) { mExpandedHeight = getRect().getHeight(); } setResizeLimits(getMinWidth(), COLLAPSED_HEIGHT); - nearby_chat->setVisible(FALSE); + mNearbyChat->setVisible(FALSE); reshape(getRect().getWidth(), COLLAPSED_HEIGHT); enableResizeCtrls(true, true, false); storeRectControl(); } else { - nearby_chat->setVisible(TRUE); + mNearbyChat->setVisible(TRUE); setResizeLimits(getMinWidth(), EXPANDED_MIN_HEIGHT); reshape(getRect().getWidth(), mExpandedHeight); enableResizeCtrls(true); @@ -421,6 +420,11 @@ void LLNearbyChatBar::onToggleNearbyChatPanel() gSavedSettings.setBOOL("nearbychat_history_visibility", mNearbyChat->getVisible()); } +void LLNearbyChatBar::onToggleNearbyChatPanel() +{ + showNearbyChatPanel(!mNearbyChat->getVisible()); +} + void LLNearbyChatBar::setMinimized(BOOL b) { LLNearbyChat* nearby_chat = getChild("nearby_chat"); diff --git a/indra/newview/llnearbychatbar.h b/indra/newview/llnearbychatbar.h index baf12a06ea..0a320942dd 100644 --- a/indra/newview/llnearbychatbar.h +++ b/indra/newview/llnearbychatbar.h @@ -37,6 +37,8 @@ class LLNearbyChatBar : public LLFloater { + LOG_CLASS(LLNearbyChatBar); + public: // constructor for inline chat-bars (e.g. hosted in chat history window) LLNearbyChatBar(const LLSD& key); @@ -76,6 +78,7 @@ protected: /* virtual */ bool applyRectControl(); + void showNearbyChatPanel(bool show); void onToggleNearbyChatPanel(); static LLWString stripChannelNumber(const LLWString &mesg, S32* channel); -- cgit v1.2.3 From 60756667875748f68bf9deca97de29461c9eb93b Mon Sep 17 00:00:00 2001 From: Seth ProductEngine Date: Wed, 21 Mar 2012 18:34:02 +0200 Subject: MAINT-272 FIXED Animation preview invalid pointer checks added. --- indra/newview/llfloateranimpreview.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/indra/newview/llfloateranimpreview.cpp b/indra/newview/llfloateranimpreview.cpp index 2a3512e21a..adf2d6dbdd 100644 --- a/indra/newview/llfloateranimpreview.cpp +++ b/indra/newview/llfloateranimpreview.cpp @@ -416,6 +416,9 @@ void LLFloaterAnimPreview::draw() //----------------------------------------------------------------------------- void LLFloaterAnimPreview::resetMotion() { + if (!mAnimPreview) + return; + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); BOOL paused = avatarp->areAnimationsPaused(); @@ -535,6 +538,9 @@ BOOL LLFloaterAnimPreview::handleHover(S32 x, S32 y, MASK mask) //----------------------------------------------------------------------------- BOOL LLFloaterAnimPreview::handleScrollWheel(S32 x, S32 y, S32 clicks) { + if (!mAnimPreview) + return false; + mAnimPreview->zoom((F32)clicks * -0.2f); mAnimPreview->requestUpdate(); @@ -670,7 +676,7 @@ void LLFloaterAnimPreview::onCommitBaseAnim() //----------------------------------------------------------------------------- void LLFloaterAnimPreview::onCommitLoop() { - if (!getEnabled()) + if (!getEnabled() || !mAnimPreview) return; LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); @@ -689,7 +695,7 @@ void LLFloaterAnimPreview::onCommitLoop() //----------------------------------------------------------------------------- void LLFloaterAnimPreview::onCommitLoopIn() { - if (!getEnabled()) + if (!getEnabled() || !mAnimPreview) return; LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); @@ -709,7 +715,7 @@ void LLFloaterAnimPreview::onCommitLoopIn() //----------------------------------------------------------------------------- void LLFloaterAnimPreview::onCommitLoopOut() { - if (!getEnabled()) + if (!getEnabled() || !mAnimPreview) return; LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); @@ -729,7 +735,7 @@ void LLFloaterAnimPreview::onCommitLoopOut() //----------------------------------------------------------------------------- void LLFloaterAnimPreview::onCommitName() { - if (!getEnabled()) + if (!getEnabled() || !mAnimPreview) return; LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); @@ -770,7 +776,7 @@ void LLFloaterAnimPreview::onCommitEmote() //----------------------------------------------------------------------------- void LLFloaterAnimPreview::onCommitPriority() { - if (!getEnabled()) + if (!getEnabled() || !mAnimPreview) return; LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); @@ -784,7 +790,7 @@ void LLFloaterAnimPreview::onCommitPriority() //----------------------------------------------------------------------------- void LLFloaterAnimPreview::onCommitEaseIn() { - if (!getEnabled()) + if (!getEnabled() || !mAnimPreview) return; LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); @@ -799,7 +805,7 @@ void LLFloaterAnimPreview::onCommitEaseIn() //----------------------------------------------------------------------------- void LLFloaterAnimPreview::onCommitEaseOut() { - if (!getEnabled()) + if (!getEnabled() || !mAnimPreview) return; LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); @@ -814,7 +820,7 @@ void LLFloaterAnimPreview::onCommitEaseOut() //----------------------------------------------------------------------------- bool LLFloaterAnimPreview::validateEaseIn(const LLSD& data) { - if (!getEnabled()) + if (!getEnabled() || !mAnimPreview) return false; LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); @@ -834,7 +840,7 @@ bool LLFloaterAnimPreview::validateEaseIn(const LLSD& data) //----------------------------------------------------------------------------- bool LLFloaterAnimPreview::validateEaseOut(const LLSD& data) { - if (!getEnabled()) + if (!getEnabled() || !mAnimPreview) return false; LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); -- cgit v1.2.3 From 1e10e1c625035b4bd237dd1439491c39ef44e934 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 21 Mar 2012 15:14:43 -0400 Subject: Revert llversionviewer.h to current viewer-release. At various points along the way, before the process changed, we merged up to viewer-development. One of those must have picked up an llversionviewer.h change to viewer version 3.3.1.0. We have no intention of twiddling llversionviewer.h in this repo -- reset so merging into viewer-release doesn't bump its version number. --- indra/llcommon/llversionviewer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llversionviewer.h b/indra/llcommon/llversionviewer.h index 26ff1b5c55..a869c74189 100644 --- a/indra/llcommon/llversionviewer.h +++ b/indra/llcommon/llversionviewer.h @@ -29,7 +29,7 @@ const S32 LL_VERSION_MAJOR = 3; const S32 LL_VERSION_MINOR = 3; -const S32 LL_VERSION_PATCH = 1; +const S32 LL_VERSION_PATCH = 0; const S32 LL_VERSION_BUILD = 0; const char * const LL_CHANNEL = "Second Life Developer"; -- cgit v1.2.3 From 0af7b7038f9e063cf71808e7d7ceaedcb17b83a9 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 21 Mar 2012 16:05:57 -0500 Subject: MAINT-804, MAINT-810 Disable framebuffer object usage on intel chips (implicitly disables lighting and shadows) --- indra/llrender/llgl.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 3f78ab20d0..c629ac33cc 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -748,6 +748,11 @@ bool LLGLManager::initGL() } #endif + if (mIsIntel) + { //never try to use framebuffer objects on intel (crashy) + mHasFramebufferObject = FALSE; + } + if (mHasFramebufferObject) { glGetIntegerv(GL_MAX_SAMPLES, &mMaxSamples); -- cgit v1.2.3 From 364d6a1f1c592921389f7e04a19082d351f57c80 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 21 Mar 2012 17:40:44 -0500 Subject: MAINT-808 Disable indexed texture rendering and deferred rendering on Linux when OpenGL version is pre-3.0 --- indra/newview/featuretable_linux.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/newview/featuretable_linux.txt b/indra/newview/featuretable_linux.txt index 54d2286373..1894db2c87 100644 --- a/indra/newview/featuretable_linux.txt +++ b/indra/newview/featuretable_linux.txt @@ -329,6 +329,10 @@ RenderObjectBump 0 0 list OpenGLPre15 RenderVBOEnable 1 0 +list OpenGLPre30 +RenderDeferred 0 0 +RenderMaxTextureIndex 1 1 + list Intel RenderAnisotropic 1 0 // Avoid some Intel crashes on Linux -- cgit v1.2.3 From 42d1b51e9302bde4dc290affe781a325c106320b Mon Sep 17 00:00:00 2001 From: Seth ProductEngine Date: Thu, 22 Mar 2012 18:19:02 +0200 Subject: CHUI-54 FIXED Backed out fix for EXP-1550 restoring inventory folder creation date updating logic. An inventory folder creation date should be updated every time a new item is created in it. Othervise the sorting by creation date is broken. However this leaves the EXP-1550 issue unresolved. --- indra/newview/llfolderviewitem.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/indra/newview/llfolderviewitem.cpp b/indra/newview/llfolderviewitem.cpp index 8d6114c887..aee5a47011 100644 --- a/indra/newview/llfolderviewitem.cpp +++ b/indra/newview/llfolderviewitem.cpp @@ -2146,14 +2146,8 @@ BOOL LLFolderViewFolder::addItem(LLFolderViewItem* item) item->dirtyFilter(); - // Update the folder creation date if the folder has no creation date - bool setting_date = false; - const time_t item_creation_date = item->getCreationDate(); - if ((item_creation_date > 0) && (mCreationDate == 0)) - { - setCreationDate(item_creation_date); - setting_date = true; - } + // Update the folder creation date if the child is newer than our current date + setCreationDate(llmax(mCreationDate, item->getCreationDate())); // Handle sorting requestArrange(); @@ -2163,11 +2157,8 @@ BOOL LLFolderViewFolder::addItem(LLFolderViewItem* item) LLFolderViewFolder* parentp = getParentFolder(); while (parentp) { - // Update the parent folder creation date - if (setting_date && (parentp->mCreationDate == 0)) - { - parentp->setCreationDate(item_creation_date); - } + // Update the folder creation date if the child is newer than our current date + parentp->setCreationDate(llmax(parentp->mCreationDate, item->getCreationDate())); if (parentp->mSortFunction.isByDate()) { -- cgit v1.2.3 From ae94c0fcad2eaf94196619aa7ff5cbfc4a936534 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Thu, 22 Mar 2012 17:48:24 -0500 Subject: MAINT-810 Fix for crash in LLRenderTarget when exiting wireframe render mode after resizing window. --- indra/newview/llviewermenu.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 7a873974eb..245095d614 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -88,6 +88,7 @@ #include "lltoolpie.h" #include "lltoolselectland.h" #include "lltrans.h" +#include "llviewerdisplay.h" //for gWindowResized #include "llviewergenericmessage.h" #include "llviewerhelp.h" #include "llviewermenufile.h" // init_menu_file() @@ -1113,6 +1114,7 @@ class LLAdvancedToggleWireframe : public view_listener_t bool handleEvent(const LLSD& userdata) { gUseWireframe = !(gUseWireframe); + gWindowResized = TRUE; LLPipeline::updateRenderDeferred(); gPipeline.resetVertexBuffers(); return true; -- cgit v1.2.3 From 671873287e28258dd42cf6abd22057f547f77331 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Fri, 23 Mar 2012 18:35:37 +0200 Subject: MAINT-424 FIXED ([PUBLIC]IM-window loses focus when tab is closed) - Return focus to the ImFloaterContainer --- indra/newview/llimfloatercontainer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/llimfloatercontainer.cpp b/indra/newview/llimfloatercontainer.cpp index c8e48b0d42..0f0ae896a2 100644 --- a/indra/newview/llimfloatercontainer.cpp +++ b/indra/newview/llimfloatercontainer.cpp @@ -119,6 +119,7 @@ void LLIMFloaterContainer::addFloater(LLFloater* floaterp, void LLIMFloaterContainer::onCloseFloater(LLUUID& id) { mSessions.erase(id); + setFocus(TRUE); } void LLIMFloaterContainer::onNewMessageReceived(const LLSD& data) -- cgit v1.2.3 From 8815cfa5c916ac454cfa5b6f22f9ce312b00a846 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Mar 2012 15:53:44 -0400 Subject: Rename In[Esc]String helper-class data members, per code review. --- indra/llcommon/llstring.h | 92 +++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 5085d32f7f..09733e8e2a 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -745,23 +745,23 @@ struct InString typedef typename string_type::const_iterator const_iterator; InString(const_iterator b, const_iterator e): - iter(b), - end(e) + mIter(b), + mEnd(e) {} virtual ~InString() {} - bool done() const { return iter == end; } - /// Is the current character (*iter) escaped? This implementation can + bool done() const { return mIter == mEnd; } + /// Is the current character (*mIter) escaped? This implementation can /// answer trivially because it doesn't support escapes. virtual bool escaped() const { return false; } - /// Obtain the current character and advance @c iter. - virtual T next() { return *iter++; } + /// Obtain the current character and advance @c mIter. + virtual T next() { return *mIter++; } /// Does the current character match specified character? - virtual bool is(T ch) const { return (! done()) && *iter == ch; } + virtual bool is(T ch) const { return (! done()) && *mIter == ch; } /// Is the current character any one of the specified characters? virtual bool oneof(const string_type& delims) const { - return (! done()) && LLStringUtilBase::contains(delims, *iter); + return (! done()) && LLStringUtilBase::contains(delims, *mIter); } /** @@ -769,10 +769,10 @@ struct InString * useful for processing quoted substrings. * * If we do see @a delim, append everything from @from until (excluding) - * @a delim to @a into, advance @c iter to skip @a delim, and return @c + * @a delim to @a into, advance @c mIter to skip @a delim, and return @c * true. * - * If we do not see @a delim, do not alter @a into or @c iter and return + * If we do not see @a delim, do not alter @a into or @c mIter and return * @c false. Do not pass GO, do not collect $200. * * @note The @c false case described above implements normal getTokens() @@ -785,18 +785,18 @@ struct InString */ virtual bool collect_until(string_type& into, const_iterator from, T delim) { - const_iterator found = std::find(from, end, delim); + const_iterator found = std::find(from, mEnd, delim); // If we didn't find delim, change nothing, just tell caller. - if (found == end) + if (found == mEnd) return false; // Found delim! Append everything between from and found. into.append(from, found); // advance past delim in input - iter = found + 1; + mIter = found + 1; return true; } - const_iterator iter, end; + const_iterator mIter, mEnd; }; /// InString subclass that handles escape characters @@ -808,33 +808,33 @@ public: typedef typename super::string_type string_type; typedef typename super::const_iterator const_iterator; using super::done; - using super::iter; - using super::end; + using super::mIter; + using super::mEnd; - InEscString(const_iterator b, const_iterator e, const string_type& escapes_): + InEscString(const_iterator b, const_iterator e, const string_type& escapes): super(b, e), - escapes(escapes_) + mEscapes(escapes) { - // Even though we've already initialized 'iter' via our base-class + // Even though we've already initialized 'mIter' via our base-class // constructor, set it again to check for initial escape char. setiter(b); } /// This implementation uses the answer cached by setiter(). - virtual bool escaped() const { return isesc; } + virtual bool escaped() const { return mIsEsc; } virtual T next() { // If we're looking at the escape character of an escape sequence, - // skip that character. This is the one time we can modify 'iter' + // skip that character. This is the one time we can modify 'mIter' // without using setiter: for this one case we DO NOT CARE if the // escaped character is itself an escape. - if (isesc) - ++iter; + if (mIsEsc) + ++mIter; // If we were looking at an escape character, this is the escaped // character; otherwise it's just the next character. - T result(*iter); - // Advance iter, checking for escape sequence. - setiter(iter + 1); + T result(*mIter); + // Advance mIter, checking for escape sequence. + setiter(mIter + 1); return result; } @@ -842,14 +842,14 @@ public: { // Like base-class is(), except that an escaped character matches // nothing. - return (! done()) && (! isesc) && *iter == ch; + return (! done()) && (! mIsEsc) && *mIter == ch; } virtual bool oneof(const string_type& delims) const { // Like base-class oneof(), except that an escaped character matches // nothing. - return (! done()) && (! isesc) && LLStringUtilBase::contains(delims, *iter); + return (! done()) && (! mIsEsc) && LLStringUtilBase::contains(delims, *mIter); } virtual bool collect_until(string_type& into, const_iterator from, T delim) @@ -860,27 +860,27 @@ public: // directly appending to 'into' in case we do not find delim, in which // case we're supposed to leave 'into' unmodified. string_type collected; - // For scanning purposes, we're going to work directly with 'iter'. + // For scanning purposes, we're going to work directly with 'mIter'. // Save its current value in case we fail to see delim. - const_iterator save_iter(iter); - // Okay, set 'iter', checking for escape. + const_iterator save_iter(mIter); + // Okay, set 'mIter', checking for escape. setiter(from); while (! done()) { // If we see an unescaped delim, stop and report success. - if ((! isesc) && *iter == delim) + if ((! mIsEsc) && *mIter == delim) { // Append collected chars to 'into'. into.append(collected); - // Don't forget to advance 'iter' past delim. - setiter(iter + 1); + // Don't forget to advance 'mIter' past delim. + setiter(mIter + 1); return true; } // We're not at end, and either we're not looking at delim or it's // escaped. Collect this character and keep going. collected.push_back(next()); } - // Here we hit 'end' without ever seeing delim. Restore iter and tell + // Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell // caller. setiter(save_iter); return false; @@ -889,24 +889,24 @@ public: private: void setiter(const_iterator i) { - iter = i; + mIter = i; - // Every time we change 'iter', set 'isesc' to be able to repetitively - // answer escaped() without having to rescan 'escapes'. isesc caches - // contains(escapes, *iter). + // Every time we change 'mIter', set 'mIsEsc' to be able to repetitively + // answer escaped() without having to rescan 'mEscapes'. mIsEsc caches + // contains(mEscapes, *mIter). // We're looking at an escaped char if we're not already at end (that - // is, *iter is even meaningful); if *iter is in fact one of the + // is, *mIter is even meaningful); if *mIter is in fact one of the // specified escape characters; and if there's one more character // following it. That is, if an escape character is the very last // character of the input string, it loses its special meaning. - isesc = (! done()) && - LLStringUtilBase::contains(escapes, *iter) && - (iter+1) != end; + mIsEsc = (! done()) && + LLStringUtilBase::contains(mEscapes, *mIter) && + (mIter+1) != mEnd; } - const string_type escapes; - bool isesc; + const string_type mEscapes; + bool mIsEsc; }; /// getTokens() implementation based on InString concept @@ -957,7 +957,7 @@ void getTokens(INSTRING& instr, std::vector& tokens, // If we're looking at an open quote, search forward for // a close quote, collecting characters along the way. if (instr.oneof(quotes) && - instr.collect_until(tokens.back(), instr.iter+1, *instr.iter)) + instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter)) { // collect_until is cleverly designed to do exactly what we // need here. No further action needed if it returns true. -- cgit v1.2.3 From 44045a233b969b0a352100d8e46ca1aafc9cf5e1 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Sat, 24 Mar 2012 10:29:23 +0200 Subject: MAINT-807 FIXED ([PUBLIC]Button Clear history from Preferences isn't clear last teleport) - The order of clearing LLTeleportHistory and LLTeleportHistoryStorage does matter: first LLTeleportHistory must be cleared after LLTeleportHistoryStorage. Clearing LLTeleportHistory from LLNavigationBar::draw() breaks this order (see callback_clear_browser_cache) --- indra/newview/llnavigationbar.cpp | 9 +-------- indra/newview/llnavigationbar.h | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/indra/newview/llnavigationbar.cpp b/indra/newview/llnavigationbar.cpp index 2a08cb1845..95caa2731a 100644 --- a/indra/newview/llnavigationbar.cpp +++ b/indra/newview/llnavigationbar.cpp @@ -267,7 +267,6 @@ LLNavigationBar::LLNavigationBar() mBtnForward(NULL), mBtnHome(NULL), mCmbLocation(NULL), - mPurgeTPHistoryItems(false), mSaveToLocationHistory(false) { buildFromFile( "panel_navigation_bar.xml"); @@ -338,12 +337,6 @@ void LLNavigationBar::setVisible(BOOL visible) void LLNavigationBar::draw() { - if(mPurgeTPHistoryItems) - { - LLTeleportHistory::getInstance()->purgeItems(); - mPurgeTPHistoryItems = false; - } - if (isBackgroundVisible()) { static LLUICachedControl drop_shadow_floater ("DropShadowFloater", 0); @@ -687,7 +680,7 @@ void LLNavigationBar::clearHistoryCache() LLLocationHistory* lh = LLLocationHistory::getInstance(); lh->removeItems(); lh->save(); - mPurgeTPHistoryItems= true; + LLTeleportHistory::getInstance()->purgeItems(); } int LLNavigationBar::getDefNavBarHeight() diff --git a/indra/newview/llnavigationbar.h b/indra/newview/llnavigationbar.h index e4ce9e3998..7878bab24e 100644 --- a/indra/newview/llnavigationbar.h +++ b/indra/newview/llnavigationbar.h @@ -145,7 +145,6 @@ private: boost::signals2::connection mTeleportFailedConnection; boost::signals2::connection mTeleportFinishConnection; boost::signals2::connection mHistoryMenuConnection; - bool mPurgeTPHistoryItems; // if true, save location to location history when teleport finishes bool mSaveToLocationHistory; }; -- cgit v1.2.3 From e7462e92051824bfa484215ba32c89f6908a4676 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 26 Mar 2012 12:46:50 -0500 Subject: MAINT-822 Fix for ATI R300 (Radeon 9700) not being recognized on some systems. --- indra/newview/gpu_table.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/gpu_table.txt b/indra/newview/gpu_table.txt index 198e702459..777d54a5c3 100644 --- a/indra/newview/gpu_table.txt +++ b/indra/newview/gpu_table.txt @@ -205,6 +205,7 @@ ATI Radeon X800 .*ATI.*Radeon ?X8.* 2 1 ATI Radeon X900 .*ATI.*Radeon ?X9.* 2 1 ATI Radeon Xpress .*ATI.*Radeon Xpress.* 0 1 ATI Rage 128 .*ATI.*Rage 128.* 0 1 +ATI R300 (9700) .*R300.* 1 1 ATI R350 (9800) .*R350.* 1 1 ATI R580 (X1900) .*R580.* 3 1 ATI RC410 (Xpress 200) .*RC410.* 0 0 -- cgit v1.2.3 From ce0f455b7cb5a5d45e7682e4a4180b577eda3c5a Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 26 Mar 2012 13:09:09 -0500 Subject: MAINT-806 Fix for some shaders failing to link on ATI x800. --- indra/llrender/llgl.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index c629ac33cc..a819ab0b79 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -605,11 +605,8 @@ bool LLGLManager::initGL() #endif // LL_WINDOWS #if (LL_WINDOWS || LL_LINUX) && !LL_MESA_HEADLESS - // release 7277 is a point at which we verify that ATI OpenGL - // drivers get pretty stable with SL, ~Catalyst 8.2, - // for both Win32 and Linux. - if (mDriverVersionRelease < 7277 && - mDriverVersionRelease != 0) // 0 == Undetectable driver version - these get to pretend to be new ATI drivers, though that decision may be revisited. + // count any pre OpenGL 3.0 implementation as an old driver + if (mGLVersion < 3.f) { mATIOldDriver = TRUE; } -- cgit v1.2.3 From bdb75f2c0938fc059b7c78c08a4eae24f5641b64 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 26 Mar 2012 17:44:26 -0500 Subject: MAINT-792 Only allow up to 4x FSAA on Intel graphics. --- indra/newview/llfloaterhardwaresettings.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/indra/newview/llfloaterhardwaresettings.cpp b/indra/newview/llfloaterhardwaresettings.cpp index e8f1d58df8..adf41df5cb 100644 --- a/indra/newview/llfloaterhardwaresettings.cpp +++ b/indra/newview/llfloaterhardwaresettings.cpp @@ -37,6 +37,7 @@ #include "llspinctrl.h" #include "llstartup.h" #include "lltextbox.h" +#include "llcombobox.h" #include "pipeline.h" // Linden library includes @@ -148,6 +149,13 @@ BOOL LLFloaterHardwareSettings::postBuild() { childSetAction("OK", onBtnOK, this); + if (gGLManager.mIsIntel) + { //remove FSAA settings above "4x" + LLComboBox* combo = getChild("fsaa"); + combo->remove("8x"); + combo->remove("16x"); + } + refresh(); center(); -- cgit v1.2.3 From ca4035295002eb121a6014ee40339af7764227f8 Mon Sep 17 00:00:00 2001 From: "simon@Simon-PC.lindenlab.com" Date: Mon, 26 Mar 2012 16:23:53 -0700 Subject: MAINT-852 / VWR-8761 : Cannot delete object description. Now you can. Reviewed by Kelly. --- indra/newview/llselectmgr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 6111255a66..904bbff153 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -4305,8 +4305,8 @@ void LLSelectMgr::packObjectName(LLSelectNode* node, void* user_data) void LLSelectMgr::packObjectDescription(LLSelectNode* node, void* user_data) { const std::string* desc = (const std::string*)user_data; - if(!desc->empty()) - { + if(desc) + { // Empty (non-null, but zero length) descriptions are OK gMessageSystem->nextBlockFast(_PREHASH_ObjectData); gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); gMessageSystem->addStringFast(_PREHASH_Description, *desc); -- cgit v1.2.3 From e4ed409ec3dc5dda151323bc0b4efa8677ee0bb1 Mon Sep 17 00:00:00 2001 From: simon_linden Date: Tue, 27 Mar 2012 09:37:04 -0700 Subject: MAINT-853: Audit sendListToRegions() function calls and fix memory leaks. Reviewed by Kelly --- indra/newview/llselectmgr.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 904bbff153..eec2c0a521 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -3051,11 +3051,11 @@ bool LLSelectMgr::confirmDelete(const LLSD& notification, const LLSD& response, // TODO: Make sure you have delete permissions on all of them. const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); // attempt to derez into the trash. - LLDeRezInfo* info = new LLDeRezInfo(DRD_TRASH, trash_id); + LLDeRezInfo info(DRD_TRASH, trash_id); LLSelectMgr::getInstance()->sendListToRegions("DeRezObject", packDeRezHeader, packObjectLocalID, - (void*)info, + (void*) &info, SEND_ONLY_ROOTS); // VEFFECT: Delete Object - one effect for all deletes if (LLSelectMgr::getInstance()->mSelectedObjects->mSelectType != SELECT_TYPE_HUD) @@ -3745,13 +3745,15 @@ void LLSelectMgr::deselectAllIfTooFar() void LLSelectMgr::selectionSetObjectName(const std::string& name) { + std::string name_copy(name); + // we only work correctly if 1 object is selected. if(mSelectedObjects->getRootObjectCount() == 1) { sendListToRegions("ObjectName", packAgentAndSessionID, packObjectName, - (void*)(new std::string(name)), + (void*)(&name_copy), SEND_ONLY_ROOTS); } else if(mSelectedObjects->getObjectCount() == 1) @@ -3759,20 +3761,22 @@ void LLSelectMgr::selectionSetObjectName(const std::string& name) sendListToRegions("ObjectName", packAgentAndSessionID, packObjectName, - (void*)(new std::string(name)), + (void*)(&name_copy), SEND_INDIVIDUALS); } } void LLSelectMgr::selectionSetObjectDescription(const std::string& desc) { + std::string desc_copy(desc); + // we only work correctly if 1 object is selected. if(mSelectedObjects->getRootObjectCount() == 1) { sendListToRegions("ObjectDescription", packAgentAndSessionID, packObjectDescription, - (void*)(new std::string(desc)), + (void*)(&desc_copy), SEND_ONLY_ROOTS); } else if(mSelectedObjects->getObjectCount() == 1) @@ -3780,7 +3784,7 @@ void LLSelectMgr::selectionSetObjectDescription(const std::string& desc) sendListToRegions("ObjectDescription", packAgentAndSessionID, packObjectDescription, - (void*)(new std::string(desc)), + (void*)(&desc_copy), SEND_INDIVIDUALS); } } @@ -4298,7 +4302,6 @@ void LLSelectMgr::packObjectName(LLSelectNode* node, void* user_data) gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); gMessageSystem->addStringFast(_PREHASH_Name, *name); } - delete name; } // static -- cgit v1.2.3 From 76634dc7afd768c2c33b7aac16f248a82513459c Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 28 Mar 2012 00:49:42 -0500 Subject: MAINT-793 Pop up notification when drivers are likely out of date. Allow Lighting and Shadows on Intel when drivers are up to date. --- indra/llrender/llgl.cpp | 4 ++-- indra/newview/featuretable.txt | 1 + indra/newview/featuretable_linux.txt | 1 + indra/newview/featuretable_mac.txt | 1 + indra/newview/featuretable_xp.txt | 1 + indra/newview/llappviewer.cpp | 9 +++++++++ indra/newview/skins/default/xui/en/notifications.xml | 19 +++++++++++++++++++ 7 files changed, 34 insertions(+), 2 deletions(-) diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index a819ab0b79..f2105f79d0 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -745,8 +745,8 @@ bool LLGLManager::initGL() } #endif - if (mIsIntel) - { //never try to use framebuffer objects on intel (crashy) + if (mIsIntel && mGLVersion <= 3.f) + { //never try to use framebuffer objects on older intel drivers (crashy) mHasFramebufferObject = FALSE; } diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt index 657a6b8945..e8a109e661 100644 --- a/indra/newview/featuretable.txt +++ b/indra/newview/featuretable.txt @@ -341,6 +341,7 @@ RenderMaxTextureIndex 1 1 list Intel RenderAnisotropic 1 0 RenderVBOEnable 1 0 +RenderFSAASamples 1 0 list GeForce2 RenderAnisotropic 1 0 diff --git a/indra/newview/featuretable_linux.txt b/indra/newview/featuretable_linux.txt index 1894db2c87..3a0e7e3697 100644 --- a/indra/newview/featuretable_linux.txt +++ b/indra/newview/featuretable_linux.txt @@ -337,6 +337,7 @@ list Intel RenderAnisotropic 1 0 // Avoid some Intel crashes on Linux RenderCubeMap 0 0 +RenderFSAASamples 1 0 list GeForce2 RenderAnisotropic 1 0 diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt index d9b0c7a0a6..96362ff4bb 100644 --- a/indra/newview/featuretable_mac.txt +++ b/indra/newview/featuretable_mac.txt @@ -334,6 +334,7 @@ RenderDeferredSSAO 1 0 list Intel RenderAnisotropic 1 0 RenderLocalLights 1 0 +RenderFSAASamples 1 0 list GeForce2 RenderAnisotropic 1 0 diff --git a/indra/newview/featuretable_xp.txt b/indra/newview/featuretable_xp.txt index b43f2a7ae2..398a64378e 100644 --- a/indra/newview/featuretable_xp.txt +++ b/indra/newview/featuretable_xp.txt @@ -337,6 +337,7 @@ RenderMaxTextureIndex 1 1 list Intel RenderAnisotropic 1 0 RenderVBOEnable 1 0 +RenderFSAASamples 1 0 list GeForce2 RenderAnisotropic 1 0 diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 3368fdff57..748d36eadf 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1015,6 +1015,15 @@ bool LLAppViewer::init() } } +#if LL_WINDOWS + if (gGLManager.mIsIntel && + LLFeatureManager::getInstance()->getRecommendedValue("WindLightUseAtmosShaders") > 0 && + gGLManager.mGLVersion <= 3.f) + { + LLNotificationsUtil::add("IntelOldDriver"); + } +#endif + // save the graphics card gDebugInfo["GraphicsCard"] = LLFeatureManager::getInstance()->getGPUString(); diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index af75d49353..e1becae6ae 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -1243,6 +1243,25 @@ Visit [_URL] for more information? fail + + There is likely a newer driver for your graphics chip. Updating graphics drivers can substantially improve performance. + + Visit [_URL] to check for driver updates? + confirm + + http://www.intel.com/p/en_US/support/detect/graphics + + + fail + + Date: Wed, 28 Mar 2012 21:19:33 +0300 Subject: MAINT-405 FIXED ([PUBLIC]Ctrl W does not properly close IM tabs using tabbed IM in 2.x/3.x) - Now tabs are closed with Ctrl+W as expected --- indra/llui/llmultifloater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llui/llmultifloater.cpp b/indra/llui/llmultifloater.cpp index f3a48835b1..aa5f577897 100644 --- a/indra/llui/llmultifloater.cpp +++ b/indra/llui/llmultifloater.cpp @@ -349,7 +349,7 @@ void LLMultiFloater::setVisible(BOOL visible) BOOL LLMultiFloater::handleKeyHere(KEY key, MASK mask) { - if (key == 'W' && mask == (MASK_CONTROL|MASK_SHIFT)) + if (key == 'W' && mask == MASK_CONTROL) { LLFloater* floater = getActiveFloater(); // is user closeable and is system closeable -- cgit v1.2.3 From 8fd20991f42ce632ec58459881a8c26b77ecaf0f Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 28 Mar 2012 14:25:42 -0500 Subject: MAINT-809 Disable FSAA above 4x if OpenGL version < 3.0 --- indra/newview/llfloaterhardwaresettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llfloaterhardwaresettings.cpp b/indra/newview/llfloaterhardwaresettings.cpp index adf41df5cb..116bd241c4 100644 --- a/indra/newview/llfloaterhardwaresettings.cpp +++ b/indra/newview/llfloaterhardwaresettings.cpp @@ -149,7 +149,7 @@ BOOL LLFloaterHardwareSettings::postBuild() { childSetAction("OK", onBtnOK, this); - if (gGLManager.mIsIntel) + if (gGLManager.mIsIntel || gGLManager.mGLVersion < 3.f) { //remove FSAA settings above "4x" LLComboBox* combo = getChild("fsaa"); combo->remove("8x"); -- cgit v1.2.3 From 51018f38a25bb9d43775ccd8702525a6f16186fa Mon Sep 17 00:00:00 2001 From: Nat Goodspeed 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: Fri, 30 Mar 2012 15:45:54 +0300 Subject: MAINT-813 FIXED Potential fix for a crash at LLViewerRegion::getPosGlobalFromRegion. As far as I can tell, if login fails with the "Region has begun the logout process" message, gAgentAvatarp gets destroyed, which leads to the crash. Adding a check that should prevent it (could not reproduce to make sure). --- indra/newview/llviewerobject.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 37fb77a10a..e590f29a9a 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -143,7 +143,10 @@ LLViewerObject *LLViewerObject::createObject(const LLUUID &id, const LLPCode pco } else { - gAgentAvatarp->updateRegion(regionp); + if (isAgentAvatarValid()) + { + gAgentAvatarp->updateRegion(regionp); + } } res = gAgentAvatarp; } -- cgit v1.2.3 From a244cff7811b88d76591f429119dd736fa522b1a Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 30 Mar 2012 12:17:34 -0500 Subject: MAINT-778, MAINT-793 Fix for crash when enabling Debug GL on Mobile Intel 4 Express. Don't pop up driver update notification if GPU class is < 1 --- indra/llrender/llgl.cpp | 2 +- indra/newview/llappviewer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index f2105f79d0..628a8d6131 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -1912,7 +1912,7 @@ void LLGLState::checkClientArrays(const std::string& msg, U32 data_mask) glClientActiveTextureARB(GL_TEXTURE0_ARB); gGL.getTexUnit(0)->activate(); - if (gGLManager.mHasVertexShader) + if (gGLManager.mHasVertexShader && LLGLSLShader::sNoFixedFunction) { //make sure vertex attribs are all disabled GLint count; glGetIntegerv(GL_MAX_VERTEX_ATTRIBS_ARB, &count); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 748d36eadf..4d94c90cc6 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1017,7 +1017,7 @@ bool LLAppViewer::init() #if LL_WINDOWS if (gGLManager.mIsIntel && - LLFeatureManager::getInstance()->getRecommendedValue("WindLightUseAtmosShaders") > 0 && + LLFeatureManager::getInstance()->getGPUClass() > 0 && gGLManager.mGLVersion <= 3.f) { LLNotificationsUtil::add("IntelOldDriver"); -- cgit v1.2.3 From cbbbbcb6c31bca07b3453e622ab5905ea9e193b8 Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Mon, 2 Apr 2012 20:36:52 +0300 Subject: MAINT-180 FIXED Reject group and ad-hoc chat messages from lindens when we are busy. --- indra/newview/llimview.cpp | 10 +--------- indra/newview/llviewermessage.cpp | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index a856bd0bdc..66bfb9a37a 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -3223,7 +3223,6 @@ public: //just like a normal IM //this is just replicated code from process_improved_im //and should really go in it's own function -jwolk - LLChat chat; std::string message = message_params["message"].asString(); std::string name = message_params["from_name"].asString(); @@ -3241,14 +3240,7 @@ public: name, LLMute::flagTextChat); - BOOL is_linden = LLMuteList::getInstance()->isLinden(name); - std::string separator_string(": "); - - chat.mMuted = is_muted && !is_linden; - chat.mFromID = from_id; - chat.mFromName = name; - - if (!is_linden && is_busy) + if (is_busy || is_muted) { return; } diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 3c6770df43..acddd3efe7 100755 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -2682,7 +2682,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) case IM_SESSION_SEND: { - if (!is_linden && is_busy) + if (is_busy) { return; } -- cgit v1.2.3 From dc45a131a20445ae68989b896cf09ecd74cd5dce Mon Sep 17 00:00:00 2001 From: Xiaohong Bao Date: Mon, 2 Apr 2012 14:26:47 -0600 Subject: fix for SEC-905, SEC-1025: Show Texture Info Allows People To See and Use Textures And Override Permissions System --- indra/newview/llviewerwindow.cpp | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 328e79d76b..ff979ca99f 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -749,7 +749,6 @@ public: if (gSavedSettings.getBOOL("DebugShowTextureInfo")) { LLViewerObject* objectp = NULL ; - //objectp = = gAgentCamera.getFocusObject(); LLSelectNode* nodep = LLSelectMgr::instance().getHoverNode(); if (nodep) @@ -760,30 +759,27 @@ public: if (objectp && !objectp->isDead()) { S32 num_faces = objectp->mDrawable->getNumFaces() ; - + std::set tex_list; + for(S32 i = 0 ; i < num_faces; i++) { LLFace* facep = objectp->mDrawable->getFace(i) ; if(facep) - { - //addText(xpos, ypos, llformat("ts_min: %.3f ts_max: %.3f tt_min: %.3f tt_max: %.3f", facep->mTexExtents[0].mV[0], facep->mTexExtents[1].mV[0], - // facep->mTexExtents[0].mV[1], facep->mTexExtents[1].mV[1])); - //ypos += y_inc; - - //addText(xpos, ypos, llformat("v_size: %.3f: p_size: %.3f", facep->getVirtualSize(), facep->getPixelArea())); - //ypos += y_inc; - - //const LLTextureEntry *tep = facep->getTextureEntry(); - //if(tep) - //{ - // addText(xpos, ypos, llformat("scale_s: %.3f: scale_t: %.3f", tep->mScaleS, tep->mScaleT)) ; - // ypos += y_inc; - //} - + { LLViewerFetchedTexture* tex = dynamic_cast(facep->getTexture()) ; if(tex) { - addText(xpos, ypos, llformat("ID: %s v_size: %.3f", tex->getID().asString().c_str(), tex->getMaxVirtualSize())); + if(tex_list.find(tex) != tex_list.end()) + { + continue ; //already displayed. + } + tex_list.insert(tex); + + std::string uuid_str; + tex->getID().toString(uuid_str); + uuid_str = uuid_str.substr(0,7); + + addText(xpos, ypos, llformat("ID: %s v_size: %.3f", uuid_str.c_str(), tex->getMaxVirtualSize())); ypos += y_inc; addText(xpos, ypos, llformat("discard level: %d desired level: %d Missing: %s", tex->getDiscardLevel(), -- cgit v1.2.3 From 686c7faf7a71c346e56f4a054644cebbf1fb2356 Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Tue, 3 Apr 2012 18:33:42 +0300 Subject: MAINT-117 FIXED Login screen didn't hide if you minimized viewer window right after pressing "Log In". --- indra/newview/llprogressview.cpp | 34 +++++++++++++++++++++++++--------- indra/newview/llprogressview.h | 7 +++---- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/indra/newview/llprogressview.cpp b/indra/newview/llprogressview.cpp index 5d7a5b1c59..f86e583b9e 100644 --- a/indra/newview/llprogressview.cpp +++ b/indra/newview/llprogressview.cpp @@ -40,6 +40,7 @@ #include "llagent.h" #include "llbutton.h" +#include "llcallbacklist.h" #include "llfocusmgr.h" #include "llnotifications.h" #include "llprogressbar.h" @@ -72,6 +73,8 @@ LLProgressView::LLProgressView() mStartupComplete(false) { mUpdateEvents.listen("self", boost::bind(&LLProgressView::handleUpdate, this, _1)); + mFadeToWorldTimer.stop(); + mFadeFromLoginTimer.stop(); } BOOL LLProgressView::postBuild() @@ -87,8 +90,6 @@ BOOL LLProgressView::postBuild() mCancelBtn = getChild("cancel_btn"); mCancelBtn->setClickedCallback( LLProgressView::onCancelButtonClicked, NULL ); - mFadeToWorldTimer.stop(); - mFadeFromLoginTimer.stop(); getChild("title_text")->setText(LLStringExplicit(LLAppViewer::instance()->getSecondLifeTitle())); @@ -106,6 +107,9 @@ BOOL LLProgressView::postBuild() LLProgressView::~LLProgressView() { + // Just in case something went wrong, make sure we deregister our idle callback. + gIdleCallbacks.deleteFunction(onIdle, this); + gFocusMgr.releaseFocusIfNeeded( this ); sInstance = NULL; @@ -154,6 +158,7 @@ void LLProgressView::revealIntroPanel() } mFadeFromLoginTimer.start(); + gIdleCallbacks.addFunction(onIdle, this); } void LLProgressView::setStartupComplete() @@ -236,13 +241,6 @@ void LLProgressView::draw() } LLPanel::draw(); - - if (mFadeFromLoginTimer.getElapsedTimeF32() > FADE_TO_WORLD_TIME ) - { - mFadeFromLoginTimer.stop(); - LLPanelLogin::closePanel(); - } - return; } @@ -424,3 +422,21 @@ void LLProgressView::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent even } } } + + +// static +void LLProgressView::onIdle(void* user_data) +{ + LLProgressView* self = (LLProgressView*) user_data; + + // Close login panel on mFadeToWorldTimer expiration. + if (self->mFadeFromLoginTimer.getStarted() && + self->mFadeFromLoginTimer.getElapsedTimeF32() > FADE_TO_WORLD_TIME) + { + self->mFadeFromLoginTimer.stop(); + LLPanelLogin::closePanel(); + + // Nothing to do anymore. + gIdleCallbacks.deleteFunction(onIdle, user_data); + } +} diff --git a/indra/newview/llprogressview.h b/indra/newview/llprogressview.h index fac00ad04d..813576b21d 100644 --- a/indra/newview/llprogressview.h +++ b/indra/newview/llprogressview.h @@ -41,6 +41,8 @@ class LLProgressView : public LLViewerMediaObserver { + LOG_CLASS(LLProgressView); + public: LLProgressView(); virtual ~LLProgressView(); @@ -74,10 +76,6 @@ public: static void onClickMessage(void*); bool onAlertModal(const LLSD& sd); - // note - this is not just hiding the intro panel - it also hides the parent panel - // and is used when the intro is finished and we want to show the world - void removeIntroPanel(); - protected: LLProgressBar* mProgressBar; LLMediaCtrl* mMediaCtrl; @@ -96,6 +94,7 @@ protected: LLEventStream mUpdateEvents; bool handleUpdate(const LLSD& event_data); + static void onIdle(void* user_data); }; #endif // LL_LLPROGRESSVIEW_H -- cgit v1.2.3 From d154bc0e0b11f354cd68b3e5f57bdce45faeeabe Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Tue, 3 Apr 2012 21:13:43 +0300 Subject: MAINT-463 FIXED [PUBLIC]URL-name of object is shown as hyperlink in Report Abuse window - Simply disabled url parsing --- indra/newview/skins/default/xui/en/floater_report_abuse.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/skins/default/xui/en/floater_report_abuse.xml b/indra/newview/skins/default/xui/en/floater_report_abuse.xml index e6d749a3f0..9561f67941 100644 --- a/indra/newview/skins/default/xui/en/floater_report_abuse.xml +++ b/indra/newview/skins/default/xui/en/floater_report_abuse.xml @@ -155,6 +155,7 @@ name="object_name" top_delta="0" translate="false" + parse_urls="false" use_ellipses="true" width="185"> Consetetur Sadipscing -- cgit v1.2.3 From 780075cecc3d7b8b1558142081a2c407597402b9 Mon Sep 17 00:00:00 2001 From: Seth ProductEngine Date: Wed, 4 Apr 2012 00:17:02 +0300 Subject: MAINT-845 FIXED Inventory items auto selection is disabled during filtering, unless a search string is being typed in. --- indra/newview/llfolderview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llfolderview.cpp b/indra/newview/llfolderview.cpp index e0d7d67f7d..abbc0f7491 100644 --- a/indra/newview/llfolderview.cpp +++ b/indra/newview/llfolderview.cpp @@ -2195,7 +2195,7 @@ void LLFolderView::doIdle() mFilter->clearModified(); BOOL filter_modified_and_active = mCompletedFilterGeneration < mFilter->getCurrentGeneration() && mFilter->isNotDefault(); - mNeedsAutoSelect = filter_modified_and_active && + mNeedsAutoSelect = mFilter->hasFilterString() && !(gFocusMgr.childHasKeyboardFocus(this) || gFocusMgr.getMouseCapture()); // filter to determine visiblity before arranging -- cgit v1.2.3 From 823a52756a78598cc32efac53d9adb8c760b99e9 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Wed, 4 Apr 2012 20:08:38 +0300 Subject: MAINT-830 FIXED ([PUBLIC]"Items successfully shared" appears even when share fails) - Added check whether sharing inventory category or inventory item was successful --- indra/newview/llavataractions.cpp | 16 ++++++--- indra/newview/llgiveinventory.cpp | 68 ++++++++++++++++++++++++++++++--------- indra/newview/llgiveinventory.h | 7 ++-- 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index 9a7cdcfa21..267e0f03b9 100755 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -594,7 +594,7 @@ namespace action_give_inventory } S32 count = LLShareInfo::instance().mAvatarNames.size(); - bool shared = false; + bool shared = count && !inventory_selected_uuids.empty(); // iterate through avatars for(S32 i = 0; i < count; ++i) @@ -616,8 +616,10 @@ namespace action_give_inventory LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); if (inv_cat) { - LLGiveInventory::doGiveInventoryCategory(avatar_uuid, inv_cat, session_id); - shared = true; + if (!LLGiveInventory::doGiveInventoryCategory(avatar_uuid, inv_cat, session_id, "ItemsShared")) + { + shared = false; + } break; } LLViewerInventoryItem* inv_item = gInventory.getItem(*it); @@ -632,8 +634,10 @@ namespace action_give_inventory } else { - LLGiveInventory::doGiveInventoryItem(avatar_uuid, inv_item, session_id); - shared = true; + if (!LLGiveInventory::doGiveInventoryItem(avatar_uuid, inv_item, session_id)) + { + shared = false; + } } } if (noncopy_items.beginArray() != noncopy_items.endArray()) @@ -643,8 +647,10 @@ namespace action_give_inventory LLSD payload; payload["agent_id"] = avatar_uuid; payload["items"] = noncopy_items; + payload["success_notification"] = "ItemsShared"; LLNotificationsUtil::add("CannotCopyWarning", substitutions, payload, &LLGiveInventory::handleCopyProtectedItem); + shared = false; break; } } diff --git a/indra/newview/llgiveinventory.cpp b/indra/newview/llgiveinventory.cpp index 30858871ec..72bea8db10 100644 --- a/indra/newview/llgiveinventory.cpp +++ b/indra/newview/llgiveinventory.cpp @@ -220,17 +220,25 @@ bool LLGiveInventory::doGiveInventoryItem(const LLUUID& to_agent, return res; } -void LLGiveInventory::doGiveInventoryCategory(const LLUUID& to_agent, +bool LLGiveInventory::doGiveInventoryCategory(const LLUUID& to_agent, const LLInventoryCategory* cat, - const LLUUID& im_session_id) + const LLUUID& im_session_id, + const std::string& notification_name) { - if (!cat) return; + if (!cat) + { + return false; + } llinfos << "LLGiveInventory::giveInventoryCategory() - " << cat->getUUID() << llendl; - if (!isAgentAvatarValid()) return; + if (!isAgentAvatarValid()) + { + return false; + } + bool give_successful = true; // Test out how many items are being given. LLViewerInventoryCategory::cat_array_t cats; LLViewerInventoryItem::item_array_t items; @@ -253,24 +261,24 @@ void LLGiveInventory::doGiveInventoryCategory(const LLUUID& to_agent, if (!complete) { LLNotificationsUtil::add("IncompleteInventory"); - return; + give_successful = false; } count = items.count() + cats.count(); if (count > MAX_ITEMS) { LLNotificationsUtil::add("TooManyItems"); - return; + give_successful = false; } else if (count == 0) { LLNotificationsUtil::add("NoItems"); - return; + give_successful = false; } - else + else if (give_successful) { if (0 == giveable.countNoCopy()) { - LLGiveInventory::commitGiveInventoryCategory(to_agent, cat, im_session_id); + give_successful = LLGiveInventory::commitGiveInventoryCategory(to_agent, cat, im_session_id); } else { @@ -279,9 +287,16 @@ void LLGiveInventory::doGiveInventoryCategory(const LLUUID& to_agent, LLSD payload; payload["agent_id"] = to_agent; payload["folder_id"] = cat->getUUID(); + if (!notification_name.empty()) + { + payload["success_notification"] = notification_name; + } LLNotificationsUtil::add("CannotCopyCountItems", args, payload, &LLGiveInventory::handleCopyProtectedCategory); + give_successful = false; } } + + return give_successful; } ////////////////////////////////////////////////////////////////////////// @@ -325,6 +340,7 @@ bool LLGiveInventory::handleCopyProtectedItem(const LLSD& notification, const LL S32 option = LLNotificationsUtil::getSelectedOption(notification, response); LLSD itmes = notification["payload"]["items"]; LLInventoryItem* item = NULL; + bool give_successful = true; switch(option) { case 0: // "Yes" @@ -343,15 +359,21 @@ bool LLGiveInventory::handleCopyProtectedItem(const LLSD& notification, const LL else { LLNotificationsUtil::add("CannotGiveItem"); + give_successful = false; } } + if (give_successful && notification["payload"]["success_notification"].isDefined()) + { + LLNotificationsUtil::add(notification["payload"]["success_notification"].asString()); + } break; default: // no, cancel, whatever, who cares, not yes. LLNotificationsUtil::add("TransactionCancelled"); + give_successful = false; break; } - return false; + return give_successful; } // static @@ -408,13 +430,14 @@ bool LLGiveInventory::handleCopyProtectedCategory(const LLSD& notification, cons { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); LLInventoryCategory* cat = NULL; + bool give_successful = true; switch(option) { case 0: // "Yes" cat = gInventory.getCategory(notification["payload"]["folder_id"].asUUID()); if (cat) { - LLGiveInventory::commitGiveInventoryCategory(notification["payload"]["agent_id"].asUUID(), + give_successful = LLGiveInventory::commitGiveInventoryCategory(notification["payload"]["agent_id"].asUUID(), cat); LLViewerInventoryCategory::cat_array_t cats; LLViewerInventoryItem::item_array_t items; @@ -430,27 +453,37 @@ bool LLGiveInventory::handleCopyProtectedCategory(const LLSD& notification, cons gInventory.deleteObject(items.get(i)->getUUID()); } gInventory.notifyObservers(); + + if (give_successful && notification["payload"]["success_notification"].isDefined()) + { + LLNotificationsUtil::add(notification["payload"]["success_notification"].asString()); + } } else { LLNotificationsUtil::add("CannotGiveCategory"); + give_successful = false; } break; default: // no, cancel, whatever, who cares, not yes. LLNotificationsUtil::add("TransactionCancelled"); + give_successful = false; break; } - return false; + return give_successful; } // static -void LLGiveInventory::commitGiveInventoryCategory(const LLUUID& to_agent, +bool LLGiveInventory::commitGiveInventoryCategory(const LLUUID& to_agent, const LLInventoryCategory* cat, const LLUUID& im_session_id) { - if (!cat) return; + if (!cat) + { + return false; + } llinfos << "LLGiveInventory::commitGiveInventoryCategory() - " << cat->getUUID() << llendl; @@ -467,6 +500,7 @@ void LLGiveInventory::commitGiveInventoryCategory(const LLUUID& to_agent, LLInventoryModel::EXCLUDE_TRASH, giveable); + bool give_successful = true; // MAX ITEMS is based on (sizeof(uuid)+2) * count must be < // MTUBYTES or 18 * count < 1200 => count < 1200/18 => // 66. I've cut it down a bit from there to give some pad. @@ -474,12 +508,12 @@ void LLGiveInventory::commitGiveInventoryCategory(const LLUUID& to_agent, if (count > MAX_ITEMS) { LLNotificationsUtil::add("TooManyItems"); - return; + give_successful = false; } else if (count == 0) { LLNotificationsUtil::add("NoItems"); - return; + give_successful = false; } else { @@ -545,6 +579,8 @@ void LLGiveInventory::commitGiveInventoryCategory(const LLUUID& to_agent, logInventoryOffer(to_agent, im_session_id); } + + return give_successful; } // EOF diff --git a/indra/newview/llgiveinventory.h b/indra/newview/llgiveinventory.h index e1e221c75b..85bc1ed49c 100644 --- a/indra/newview/llgiveinventory.h +++ b/indra/newview/llgiveinventory.h @@ -62,9 +62,10 @@ public: /** * Gives passed inventory category to specified avatar in specified session. */ - static void doGiveInventoryCategory(const LLUUID& to_agent, + static bool doGiveInventoryCategory(const LLUUID& to_agent, const LLInventoryCategory* item, - const LLUUID &session_id = LLUUID::null); + const LLUUID &session_id = LLUUID::null, + const std::string& notification = std::string()); // give inventory item functionality static bool handleCopyProtectedItem(const LLSD& notification, const LLSD& response); @@ -85,7 +86,7 @@ private: // give inventory category functionality static bool handleCopyProtectedCategory(const LLSD& notification, const LLSD& response); - static void commitGiveInventoryCategory(const LLUUID& to_agent, + static bool commitGiveInventoryCategory(const LLUUID& to_agent, const LLInventoryCategory* cat, const LLUUID &im_session_id = LLUUID::null); -- cgit v1.2.3 From d8b80cdbb635360d36fb374b76edc5d7ab32fad8 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 9 Apr 2012 12:21:38 -0500 Subject: MAINT-890 Fix for freeze on exit when a mesh upload is pending. --- indra/newview/llmeshrepository.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index f461c7e46f..c899e8991e 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1620,7 +1620,7 @@ void LLMeshUploadThread::doWholeModelUpload() mCurlRequest->process(); //sleep for 10ms to prevent eating a whole core apr_sleep(10000); - } while (mCurlRequest->getQueued() > 0); + } while (!LLAppViewer::isQuitting() && mCurlRequest->getQueued() > 0); } delete mCurlRequest; -- cgit v1.2.3 From 4574a9a008690c64158abd6d792d6d485bf0e235 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed 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(-) 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.2.3 From 5459f2ee987014e88ece017632d73829d844cb6f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 11 Apr 2012 11:01:00 -0400 Subject: Fix Linux UI issues introduced by moving llinitparam to llcommon. In a number of places, the viewer uses a lookup based on std::type_info*. We used to use std::map. But on Linux, &typeid(SomeType) can produce different pointer values, depending on the dynamic load module in which the code is executed. Introduce LLTypeInfoLookup, with an API that deliberately mimics std::map. LLTypeInfoLookup::find() first tries an efficient search for the specified std::type_info*. But if that fails, it scans the underlying container for a match on the std::type_info::name() string. If found, it caches the new std::type_info* to optimize subsequent lookups with the same pointer. Use LLTypeInfoLookup instead of std::map in llinitparam.h and llregistry.h. Introduce LLSortedVector, a std::vector> maintained in sorted order with binary-search lookup. It presents a subset of the std::map API. --- indra/linux_updater/CMakeLists.txt | 6 +- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/llinitparam.h | 7 +- indra/llcommon/llregistry.h | 21 ++++- indra/llcommon/llsortedvector.h | 152 +++++++++++++++++++++++++++++++++++++ indra/llcommon/lltypeinfolookup.h | 112 +++++++++++++++++++++++++++ 6 files changed, 293 insertions(+), 7 deletions(-) create mode 100644 indra/llcommon/llsortedvector.h create mode 100644 indra/llcommon/lltypeinfolookup.h diff --git a/indra/linux_updater/CMakeLists.txt b/indra/linux_updater/CMakeLists.txt index 00a78b2a8f..4377a6333c 100644 --- a/indra/linux_updater/CMakeLists.txt +++ b/indra/linux_updater/CMakeLists.txt @@ -10,14 +10,14 @@ include(UI) include(LLCommon) include(LLVFS) include(LLXML) -include(LLXUIXML) +include(LLUI) include(Linking) include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LLXUIXML_INCLUDE_DIRS} + ${LLUI_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} ${CARES_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} @@ -42,7 +42,7 @@ target_link_libraries(linux-updater ${CRYPTO_LIBRARIES} ${UI_LIBRARIES} ${LLXML_LIBRARIES} - ${LLXUIXML_LIBRARIES} + ${LLUI_LIBRARIES} ${LLVFS_LIBRARIES} ${LLCOMMON_LIBRARIES} ) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index a1aa887d3a..f9bc22e98a 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -221,6 +221,7 @@ set(llcommon_HEADER_FILES llsingleton.h llskiplist.h llskipmap.h + llsortedvector.h llstack.h llstacktrace.h llstat.h @@ -235,6 +236,7 @@ set(llcommon_HEADER_FILES llthreadsafequeue.h lltimer.h lltreeiterators.h + lltypeinfolookup.h lluri.h lluuid.h lluuidhashmap.h diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h index beaf07e56b..ef6c6335d1 100644 --- a/indra/llcommon/llinitparam.h +++ b/indra/llcommon/llinitparam.h @@ -35,6 +35,7 @@ #include #include "llerror.h" +#include "lltypeinfolookup.h" namespace LLInitParam { @@ -227,9 +228,9 @@ namespace LLInitParam typedef bool (*parser_write_func_t)(Parser& parser, const void*, name_stack_t&); typedef boost::function parser_inspect_func_t; - typedef std::map parser_read_func_map_t; - typedef std::map parser_write_func_map_t; - typedef std::map parser_inspect_func_map_t; + typedef LLTypeInfoLookup parser_read_func_map_t; + typedef LLTypeInfoLookup parser_write_func_map_t; + typedef LLTypeInfoLookup parser_inspect_func_map_t; Parser(parser_read_func_map_t& read_map, parser_write_func_map_t& write_map, parser_inspect_func_map_t& inspect_map) : mParseSilently(false), diff --git a/indra/llcommon/llregistry.h b/indra/llcommon/llregistry.h index 36ce6a97b7..36d7f7a44c 100644 --- a/indra/llcommon/llregistry.h +++ b/indra/llcommon/llregistry.h @@ -31,6 +31,7 @@ #include #include "llsingleton.h" +#include "lltypeinfolookup.h" template class LLRegistryDefaultComparator @@ -38,6 +39,24 @@ class LLRegistryDefaultComparator bool operator()(const T& lhs, const T& rhs) { return lhs < rhs; } }; +template +struct LLRegistryMapSelector +{ + typedef std::map type; +}; + +template +struct LLRegistryMapSelector +{ + typedef LLTypeInfoLookup type; +}; + +template +struct LLRegistryMapSelector +{ + typedef LLTypeInfoLookup type; +}; + template > class LLRegistry { @@ -53,7 +72,7 @@ public: { friend class LLRegistry; public: - typedef typename std::map registry_map_t; + typedef typename LLRegistryMapSelector::type registry_map_t; bool add(ref_const_key_t key, ref_const_value_t value) { diff --git a/indra/llcommon/llsortedvector.h b/indra/llcommon/llsortedvector.h new file mode 100644 index 0000000000..391b82ee44 --- /dev/null +++ b/indra/llcommon/llsortedvector.h @@ -0,0 +1,152 @@ +/** + * @file llsortedvector.h + * @author Nat Goodspeed + * @date 2012-04-08 + * @brief LLSortedVector class wraps a vector that we maintain in sorted + * order so we can perform binary-search lookups. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLSORTEDVECTOR_H) +#define LL_LLSORTEDVECTOR_H + +#include +#include + +/** + * LLSortedVector contains a std::vector that we keep sorted on the + * first of the pair. This makes insertion somewhat more expensive than simple + * std::vector::push_back(), but allows us to use binary search for lookups. + * It's intended for small aggregates where lookup is far more performance- + * critical than insertion; in such cases a binary search on a small, sorted + * std::vector can be more performant than a std::map lookup. + */ +template +class LLSortedVector +{ +public: + typedef LLSortedVector self; + typedef KEY key_type; + typedef VALUE mapped_type; + typedef std::pair value_type; + typedef std::vector PairVector; + typedef typename PairVector::iterator iterator; + typedef typename PairVector::const_iterator const_iterator; + + /// Empty + LLSortedVector() {} + + /// Fixed initial size + LLSortedVector(std::size_t size): + mVector(size) + {} + + /// Bulk load + template + LLSortedVector(ITER begin, ITER end): + mVector(begin, end) + { + // Allow caller to dump in a bunch of (pairs convertible to) + // value_type if desired, but make sure we sort afterwards. + std::sort(mVector.begin(), mVector.end()); + } + + /// insert(key, value) + std::pair insert(const key_type& key, const mapped_type& value) + { + return insert(value_type(key, value)); + } + + /// insert(value_type) + std::pair insert(const value_type& pair) + { + typedef std::pair iterbool; + iterator found = std::lower_bound(mVector.begin(), mVector.end(), pair, + less()); + // have to check for end() before it's even valid to dereference + if (found == mVector.end()) + { + std::size_t index(mVector.size()); + mVector.push_back(pair); + // don't forget that push_back() invalidates 'found' + return iterbool(mVector.begin() + index, true); + } + if (found->first == pair.first) + { + return iterbool(found, false); + } + // remember that insert() invalidates 'found' -- save index + std::size_t index(found - mVector.begin()); + mVector.insert(found, pair); + // okay, convert from index back to iterator + return iterbool(mVector.begin() + index, true); + } + + iterator begin() { return mVector.begin(); } + iterator end() { return mVector.end(); } + const_iterator begin() const { return mVector.begin(); } + const_iterator end() const { return mVector.end(); } + + bool empty() const { return mVector.empty(); } + std::size_t size() const { return mVector.size(); } + + /// find + iterator find(const key_type& key) + { + iterator found = std::lower_bound(mVector.begin(), mVector.end(), + value_type(key, mapped_type()), + less()); + if (found == mVector.end() || found->first != key) + return mVector.end(); + return found; + } + + const_iterator find(const key_type& key) const + { + return const_cast(this)->find(key); + } + +private: + // Define our own 'less' comparator so we can specialize without messing + // with std::less. + template + struct less: public std::less {}; + + // Specialize 'less' for an LLSortedVector::value_type involving + // std::type_info*. This is one of LLSortedVector's foremost use cases. We + // specialize 'less' rather than just defining a specific comparator + // because LLSortedVector should be usable for other key_types as well. + template + struct less< std::pair >: + public std::binary_function, + std::pair, + bool> + { + bool operator()(const std::pair& lhs, + const std::pair& rhs) const + { + return lhs.first->before(*rhs.first); + } + }; + + // Same as above, but with const std::type_info*. + template + struct less< std::pair >: + public std::binary_function, + std::pair, + bool> + { + bool operator()(const std::pair& lhs, + const std::pair& rhs) const + { + return lhs.first->before(*rhs.first); + } + }; + + PairVector mVector; +}; + +#endif /* ! defined(LL_LLSORTEDVECTOR_H) */ diff --git a/indra/llcommon/lltypeinfolookup.h b/indra/llcommon/lltypeinfolookup.h new file mode 100644 index 0000000000..fc99f7ff33 --- /dev/null +++ b/indra/llcommon/lltypeinfolookup.h @@ -0,0 +1,112 @@ +/** + * @file lltypeinfolookup.h + * @author Nat Goodspeed + * @date 2012-04-08 + * @brief Template data structure like std::map + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLTYPEINFOLOOKUP_H) +#define LL_LLTYPEINFOLOOKUP_H + +#include "llsortedvector.h" +#include + +/** + * LLTypeInfoLookup is specifically designed for use cases for which you might + * consider std::map. We have several such data + * structures in the viewer. The trouble with them is that at least on Linux, + * you can't rely on always getting the same std::type_info* for a given type: + * different load modules will produce different std::type_info*. + * LLTypeInfoLookup contains a workaround to address this issue. + * + * Specifically, when we don't find the passed std::type_info*, + * LLTypeInfoLookup performs a linear search over registered entries to + * compare name() strings. Presuming that this succeeds, we cache the new + * (previously unrecognized) std::type_info* to speed future lookups. + * + * This worst-case fallback search (linear search with string comparison) + * should only happen the first time we look up a given type from a particular + * load module other than the one from which we initially registered types. + * (However, a lookup which wouldn't succeed anyway will always have + * worst-case performance.) This class is probably best used with less than a + * few dozen different types. + */ +template +class LLTypeInfoLookup +{ +public: + typedef LLTypeInfoLookup self; + typedef LLSortedVector vector_type; + typedef typename vector_type::key_type key_type; + typedef typename vector_type::mapped_type mapped_type; + typedef typename vector_type::value_type value_type; + typedef typename vector_type::iterator iterator; + typedef typename vector_type::const_iterator const_iterator; + + LLTypeInfoLookup() {} + + iterator begin() { return mVector.begin(); } + iterator end() { return mVector.end(); } + const_iterator begin() const { return mVector.begin(); } + const_iterator end() const { return mVector.end(); } + bool empty() const { return mVector.empty(); } + std::size_t size() const { return mVector.size(); } + + std::pair insert(const std::type_info* key, const VALUE& value) + { + return insert(value_type(key, value)); + } + + std::pair insert(const value_type& pair) + { + return mVector.insert(pair); + } + + const_iterator find(const std::type_info* key) const + { + return const_cast(this)->find(key); + } + + iterator find(const std::type_info* key) + { + iterator found = mVector.find(key); + if (found != mVector.end()) + { + // If LLSortedVector::find() found, great, we're done. + return found; + } + // Here we didn't find the passed type_info*. On Linux, though, even + // for the same type, typeid(sametype) produces a different type_info* + // when used in different load modules. So the fact that we didn't + // find the type_info* we seek doesn't mean this type isn't + // registered. Scan for matching name() string. + for (typename vector_type::iterator ti(mVector.begin()), tend(mVector.end()); + ti != tend; ++ti) + { + if (std::string(ti->first->name()) == key->name()) + { + // This unrecognized 'key' is for the same type as ti->first. + // To speed future lookups, insert a new entry that lets us + // look up ti->second using this same 'key'. + return insert(key, ti->second).first; + } + } + // We simply have never seen a type with this type_info* from any load + // module. + return mVector.end(); + } + +private: + /// Our LLSortedVector is mutable so that if we're passed an unrecognized + /// std::type_info* for a registered type (which we can identify by + /// searching for the name() string), we can cache the new std::type_info* + /// to speed future lookups -- even when the containing LLTypeInfoLookup + /// is const. + vector_type mVector; +}; + +#endif /* ! defined(LL_LLTYPEINFOLOOKUP_H) */ -- cgit v1.2.3 From 7318e11571237bd55ff5972350db499a73ef6ae5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Apr 2012 09:42:51 -0400 Subject: Fix misleading comments, per Richard's code review. --- indra/llcommon/lltypeinfolookup.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/indra/llcommon/lltypeinfolookup.h b/indra/llcommon/lltypeinfolookup.h index fc99f7ff33..7510cc12ed 100644 --- a/indra/llcommon/lltypeinfolookup.h +++ b/indra/llcommon/lltypeinfolookup.h @@ -66,11 +66,14 @@ public: return mVector.insert(pair); } + // const find() forwards to non-const find(): this can alter mVector! const_iterator find(const std::type_info* key) const { return const_cast(this)->find(key); } + // non-const find() caches previously-unknown type_info* to speed future + // lookups. iterator find(const std::type_info* key) { iterator found = mVector.find(key); @@ -101,11 +104,6 @@ public: } private: - /// Our LLSortedVector is mutable so that if we're passed an unrecognized - /// std::type_info* for a registered type (which we can identify by - /// searching for the name() string), we can cache the new std::type_info* - /// to speed future lookups -- even when the containing LLTypeInfoLookup - /// is const. vector_type mVector; }; -- cgit v1.2.3 From 54228ddf50b3396686a51a6fd51c1133916fc827 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Apr 2012 15:34:15 -0400 Subject: IQA-463: Add error logging for certain LLFile operations. Attempting to debug an observed LLFile::remove() failure, I was floored to find that remove() made no attempt whatsoever to report its lack of success! Add warnif() function to log errno text in platform-dependent way. Support the notion that for some functions, certain errno values are acceptable -- e.g. we expect stat() to frequently hit ENOENT -- and need not be logged. Add commented-out Windows-specific logic to try to provide further information in the case of EACCES ("Permission denied," e.g. another process has the file open). To use, enable the code block, download handle.exe and turn on DEBUG logging for LLFile. handle.exe can be obtained from: http://technet.microsoft.com/en-us/sysinternals/bb896655 --- indra/llcommon/llfile.cpp | 187 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 163 insertions(+), 24 deletions(-) diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index c32a776c3f..c51d042a3d 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -1,4 +1,4 @@ -/** +/** * @file llfile.cpp * @author Michael Schlachter * @date 2006-03-23 @@ -8,60 +8,194 @@ * $LicenseInfo:firstyear=2006&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$ */ #if LL_WINDOWS #include +#include // Windows errno +#else +#include #endif #include "linden_common.h" #include "llfile.h" #include "llstring.h" #include "llerror.h" +#include "stringize.h" using namespace std; +static std::string empty; + +// Many of the methods below use OS-level functions that mess with errno. Wrap +// variants of strerror() to report errors. + +#if LL_WINDOWS +// On Windows, use strerror_s(). +std::string strerr(int errn) +{ + char buffer[256]; + strerror_s(buffer, errn); // infers sizeof(buffer) -- love it! + return buffer; +} + +#else +// On Posix we want to call strerror_r(), but alarmingly, there are two +// different variants. The one that returns int always populates the passed +// buffer (except in case of error), whereas the other one always returns a +// valid char* but might or might not populate the passed buffer. How do we +// know which one we're getting? Define adapters for each and let the compiler +// select the applicable adapter. + +// strerror_r() returns char* +std::string message_from(int /*orig_errno*/, const char* /*buffer*/, size_t /*bufflen*/, + const char* strerror_ret) +{ + return strerror_ret; +} + +// strerror_r() returns int +std::string message_from(int orig_errno, const char* buffer, size_t bufflen, + int strerror_ret) +{ + if (strerror_ret == 0) + { + return buffer; + } + // Here strerror_r() has set errno. Since strerror_r() has already failed, + // seems like a poor bet to call it again to diagnose its own error... + int stre_errno = errno; + if (stre_errno == ERANGE) + { + return STRINGIZE("strerror_r() can't explain errno " << orig_errno + << " (" << bufflen << "-byte buffer too small)"); + } + if (stre_errno == EINVAL) + { + return STRINGIZE("unknown errno " << orig_errno); + } + // Here we don't even understand the errno from strerror_r()! + return STRINGIZE("strerror_r() can't explain errno " << orig_errno + << " (error " << stre_errno << ')'); +} + +std::string strerr(int errn) +{ + char buffer[256]; + // Select message_from() function matching the strerror_r() we have on hand. + return message_from(errn, buffer, sizeof(buffer), + strerror_r(errn, buffer, sizeof(buffer))); +} +#endif // ! LL_WINDOWS + +// On either system, shorthand call just infers global 'errno'. +std::string strerr() +{ + return strerr(errno); +} + +int warnif(const std::string& desc, const std::string& filename, int rc, int accept=0) +{ + if (rc < 0) + { + // Capture errno before we start emitting output + int errn = errno; + // For certain operations, a particular errno value might be + // acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit + // EEXIST. Don't warn if caller explicitly says this errno is okay. + if (errn != accept) + { + LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename + << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL; + } +#if 0 && LL_WINDOWS // turn on to debug file-locking problems + // If the problem is "Permission denied," maybe it's because another + // process has the file open. Try to find out. + if (errn == EACCES) // *not* EPERM + { + // Only do any of this stuff (before LL_ENDL) if it will be logged. + LL_DEBUGS("LLFile") << empty; + const char* TEMP = getenv("TEMP"); + if (! TEMP) + { + LL_CONT << "No $TEMP, not running 'handle'"; + } + else + { + std::string tf(TEMP); + tf += "\\handle.tmp"; + // http://technet.microsoft.com/en-us/sysinternals/bb896655 + std::string cmd(STRINGIZE("handle \"" << filename + // "openfiles /query /v | fgrep -i \"" << filename + << "\" > \"" << tf << '"')); + LL_CONT << cmd; + if (system(cmd.c_str()) != 0) + { + LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655"; + } + else + { + std::ifstream inf(tf); + std::string line; + while (std::getline(inf, line)) + { + LL_CONT << '\n' << line; + } + } + LLFile::remove(tf); + } + LL_CONT << LL_ENDL; + } +#endif // LL_WINDOWS hack to identify processes holding file open + } + return rc; +} + // static int LLFile::mkdir(const std::string& dirname, int perms) { -#if LL_WINDOWS +#if LL_WINDOWS // permissions are ignored on Windows std::string utf8dirname = dirname; llutf16string utf16dirname = utf8str_to_utf16str(utf8dirname); - return _wmkdir(utf16dirname.c_str()); + int rc = _wmkdir(utf16dirname.c_str()); #else - return ::mkdir(dirname.c_str(), (mode_t)perms); + int rc = ::mkdir(dirname.c_str(), (mode_t)perms); #endif + // We often use mkdir() to ensure the existence of a directory that might + // already exist. Don't spam the log if it does. + return warnif("mkdir", dirname, rc, EEXIST); } // static int LLFile::rmdir(const std::string& dirname) { -#if LL_WINDOWS +#if LL_WINDOWS // permissions are ignored on Windows std::string utf8dirname = dirname; llutf16string utf16dirname = utf8str_to_utf16str(utf8dirname); - return _wrmdir(utf16dirname.c_str()); + int rc = _wrmdir(utf16dirname.c_str()); #else - return ::rmdir(dirname.c_str()); + int rc = ::rmdir(dirname.c_str()); #endif + return warnif("rmdir", dirname, rc); } // static @@ -108,10 +242,11 @@ int LLFile::remove(const std::string& filename) #if LL_WINDOWS std::string utf8filename = filename; llutf16string utf16filename = utf8str_to_utf16str(utf8filename); - return _wremove(utf16filename.c_str()); + int rc = _wremove(utf16filename.c_str()); #else - return ::remove(filename.c_str()); + int rc = ::remove(filename.c_str()); #endif + return warnif("remove", filename, rc); } int LLFile::rename(const std::string& filename, const std::string& newname) @@ -121,10 +256,11 @@ int LLFile::rename(const std::string& filename, const std::string& newname) std::string utf8newname = newname; llutf16string utf16filename = utf8str_to_utf16str(utf8filename); llutf16string utf16newname = utf8str_to_utf16str(utf8newname); - return _wrename(utf16filename.c_str(),utf16newname.c_str()); + int rc = _wrename(utf16filename.c_str(),utf16newname.c_str()); #else - return ::rename(filename.c_str(),newname.c_str()); + int rc = ::rename(filename.c_str(),newname.c_str()); #endif + return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc); } int LLFile::stat(const std::string& filename, llstat* filestatus) @@ -132,23 +268,26 @@ int LLFile::stat(const std::string& filename, llstat* filestatus) #if LL_WINDOWS std::string utf8filename = filename; llutf16string utf16filename = utf8str_to_utf16str(utf8filename); - return _wstat(utf16filename.c_str(),filestatus); + int rc = _wstat(utf16filename.c_str(),filestatus); #else - return ::stat(filename.c_str(),filestatus); + int rc = ::stat(filename.c_str(),filestatus); #endif + // We use stat() to determine existence (see isfile(), isdir()). + // Don't spam the log if the subject pathname doesn't exist. + return warnif("stat", filename, rc, ENOENT); } bool LLFile::isdir(const std::string& filename) { llstat st; - + return stat(filename, &st) == 0 && S_ISDIR(st.st_mode); } bool LLFile::isfile(const std::string& filename) { llstat st; - + return stat(filename, &st) == 0 && S_ISREG(st.st_mode); } @@ -260,7 +399,7 @@ void llifstream::open(const std::string& _Filename, /* Flawfinder: ignore */ ios_base::openmode _Mode, int _Prot) { // open a C stream with specified mode - + LLFILE* filep = LLFile::_Fiopen(_Filename,_Mode | ios_base::in, _Prot); if(filep == NULL) { @@ -280,7 +419,7 @@ bool llifstream::is_open() const return false; } llifstream::~llifstream() -{ +{ if (_ShouldClose) { close(); @@ -309,7 +448,7 @@ bool llofstream::is_open() const void llofstream::open(const std::string& _Filename, /* Flawfinder: ignore */ ios_base::openmode _Mode, - int _Prot) + int _Prot) { // open a C stream with specified mode LLFILE* filep = LLFile::_Fiopen(_Filename,_Mode | ios_base::out, _Prot); @@ -340,14 +479,14 @@ void llofstream::close() llofstream::llofstream(const std::string& _Filename, std::ios_base::openmode _Mode, - int _Prot) + int _Prot) : std::basic_ostream >(NULL,true),_Filebuffer(NULL),_ShouldClose(false) { // construct with named file and specified mode open(_Filename, _Mode , _Prot); /* Flawfinder: ignore */ } llofstream::~llofstream() -{ +{ // destroy the object if (_ShouldClose) { -- cgit v1.2.3 From 38e23bb0eb71e160fdfa829398a46ec3db01d7aa Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 18 Apr 2012 15:43:34 -0400 Subject: IQA-463: Make LLProcess call apr_procattr_inherit_set() extension. On Windows, Bad Things happen when apr_proc_create() is allowed to pass TRUE to CreateProcess(bInheritHandles). For instance, the open handle for a new installer executable file being downloaded by the background updater gets inadvertently passed to a couple slplugin.exe instances. When the viewer finishes downloading, closes the file and tries to remove it, Windows balks because the file is still open by another process. Require an apr_suite package that includes the new Linden apr_procattr_inherit_set() extension, and call it to turn off CreateProcess(bInheritHandles). --- autobuild.xml | 12 ++++++------ indra/llcommon/llprocess.cpp | 35 +++++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 19913bf8de..4288b60c30 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - 02eb2d32f4013e23eb0bc2cd1b7f9b82 + 1ac038723ed702a5ad0db924dfe0fe1b url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/251231/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120314.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254105/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120418.tar.bz2 name darwin @@ -102,9 +102,9 @@ archive hash - 4ade1434e263cbb85a424e7dd35b8025 + c60ace3d6c593d95247bd58496d7c557 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/251231/arch/Linux/installer/apr_suite-1.4.5-linux-20120314.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254105/arch/Linux/installer/apr_suite-1.4.5-linux-20120418.tar.bz2 name linux @@ -114,9 +114,9 @@ archive hash - 6a2f05e8ddf01036aada46377295887f + d353e31abcefb554379abdec0ac41ff3 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/251231/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120314.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254105/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120418.tar.bz2 name windows diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index d4786035ce..e96b328365 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -535,6 +535,24 @@ LLProcess::LLProcess(const LLSDOrParams& params): apr_procattr_t *procattr = NULL; chkapr(apr_procattr_create(&procattr, gAPRPoolp)); +#if ! defined(APR_HAS_PROCATTR_INHERIT_SET) + // Our special preprocessor symbol isn't even defined -- wrong APR + LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_inherit_set() extension" << LL_ENDL; +#elif ! APR_HAS_PROCATTR_INHERIT_SET + // Symbol is defined, but to 0: expect apr_procattr_inherit_set() to + // return APR_ENOTIMPL. + LL_DEBUGS("LLProcess") << "apr_procattr_inherit_set() not supported on this platform" << LL_ENDL; +#else // APR_HAS_PROCATTR_INHERIT_SET nonzero + // As of 2012-04-17, the original Windows implementation of + // apr_proc_create() unconditionally passes TRUE for bInheritHandles. That + // seems to assume that all files are opened by APR so you can + // individually control whether each is inherited by a child process. But + // we've been burned by having surprising open file handles inherited by + // our child processes. Turn that OFF for us! + LL_DEBUGS("LLProcess") << "Setting apr_procattr_inherit_set(0)" << LL_ENDL; + ll_apr_warn_status(apr_procattr_inherit_set(procattr, 0)); +#endif + // For which of stdin, stdout, stderr should we create a pipe to the // child? In the viewer, there are only a couple viable // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx @@ -607,17 +625,14 @@ LLProcess::LLProcess(const LLSDOrParams& params): if (params.autokill) { -#if defined(APR_HAS_PROCATTR_AUTOKILL_SET) - apr_status_t ok = apr_procattr_autokill_set(procattr, 1); -# if LL_WINDOWS - // As of 2012-02-02, we only expect this to be implemented on Windows. - // Avoid spamming the log with warnings we fully expect. - ll_apr_warn_status(ok); -#else // ! LL_WINDOWS - (void)ok; // suppress 'unused' warning -# endif // ! LL_WINDOWS -#else +#if ! defined(APR_HAS_PROCATTR_AUTOKILL_SET) + // Our special preprocessor symbol isn't even defined -- wrong APR LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL; +#elif ! APR_HAS_PROCATTR_AUTOKILL_SET + // Symbol is defined, but to 0: expect apr_procattr_autokill_set() to + // return APR_ENOTIMPL. +#else // APR_HAS_PROCATTR_AUTOKILL_SET nonzero + ll_apr_warn_status(apr_procattr_autokill_set(procattr, 1)); #endif } -- cgit v1.2.3 From 9ce481a5ac41777539416da3e0ec92b098af3e49 Mon Sep 17 00:00:00 2001 From: Adam Moss Date: Sat, 21 Apr 2012 17:35:50 +0100 Subject: STORM-1819: Ternary/graded shadow support Coalesced patchset. --- indra/llrender/llshadermgr.cpp | 3 +- indra/llrender/llshadermgr.h | 1 + .../shaders/class1/deferred/shadowAlphaMaskF.glsl | 21 ++++++- .../shaders/class1/deferred/shadowAlphaMaskV.glsl | 17 +++++- .../shaders/class2/deferred/alphaF.glsl | 30 ++++++---- .../shaders/class2/deferred/alphaNonIndexedF.glsl | 30 ++++++---- .../class2/deferred/alphaNonIndexedNoColorF.glsl | 30 ++++++---- .../shaders/class2/deferred/sunLightF.glsl | 64 +++++++++++++--------- .../shaders/class2/deferred/sunLightSSAOF.glsl | 57 ++++++++++--------- indra/newview/pipeline.cpp | 27 +++++++-- indra/newview/pipeline.h | 2 +- 11 files changed, 184 insertions(+), 98 deletions(-) diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp index 7d384450e6..5a6e6cab3e 100644 --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -1062,8 +1062,9 @@ void LLShaderMgr::initAttribsAndUniforms() mReservedUniforms.push_back("proj_shadow_res"); mReservedUniforms.push_back("depth_cutoff"); mReservedUniforms.push_back("norm_cutoff"); + mReservedUniforms.push_back("shadow_target_width"); - llassert(mReservedUniforms.size() == LLShaderMgr::DEFERRED_NORM_CUTOFF+1); + llassert(mReservedUniforms.size() == LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH+1); mReservedUniforms.push_back("tc_scale"); mReservedUniforms.push_back("rcp_screen_res"); diff --git a/indra/llrender/llshadermgr.h b/indra/llrender/llshadermgr.h index e28bda6de2..f792faa8f0 100644 --- a/indra/llrender/llshadermgr.h +++ b/indra/llrender/llshadermgr.h @@ -130,6 +130,7 @@ public: DEFERRED_PROJ_SHADOW_RES, DEFERRED_DEPTH_CUTOFF, DEFERRED_NORM_CUTOFF, + DEFERRED_SHADOW_TARGET_WIDTH, FXAA_TC_SCALE, FXAA_RCP_SCREEN_RES, diff --git a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl index cf8cf8364a..07d5b08191 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl @@ -29,11 +29,18 @@ out vec4 frag_color; #define frag_color gl_FragColor #endif +uniform mat4 modelview_projection_matrix; + uniform float minimum_alpha; uniform sampler2D diffuseMap; -VARYING vec4 post_pos; +//flat VARYING int foo; +VARYING float pos_zd2; +VARYING float pos_w; +VARYING float target_pos_x; +//VARYING vec4 pre_pos; +//VARYING vec4 post_pos; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; @@ -41,12 +48,20 @@ void main() { float alpha = diffuseLookup(vary_texcoord0.xy).a * vertex_color.a; - if (alpha < minimum_alpha) + if (alpha < 0.05) // treat as totally transparent { discard; } + if (alpha < 0.88) // treat as semi-transparent + { + if (fract(0.5*floor(target_pos_x / pos_w )) < 0.25) + { + discard; + } + } + frag_color = vec4(1,1,1,1); - gl_FragDepth = max(post_pos.z/post_pos.w*0.5+0.5, 0.0); + gl_FragDepth = max(pos_zd2/pos_w+0.5, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl index 7d3b06c56e..6397a5c461 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl @@ -25,12 +25,18 @@ uniform mat4 texture_matrix0; uniform mat4 modelview_projection_matrix; +uniform float shadow_target_width; ATTRIBUTE vec3 position; ATTRIBUTE vec4 diffuse_color; ATTRIBUTE vec2 texcoord0; -VARYING vec4 post_pos; +VARYING float pos_zd2; +VARYING float pos_w; +VARYING float target_pos_x; +//flat VARYING int foo; +//VARYING vec4 pre_pos; +//VARYING vec4 post_pos; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; @@ -39,8 +45,13 @@ void passTextureIndex(); void main() { //transform vertex - vec4 pos = modelview_projection_matrix*vec4(position.xyz, 1.0); - post_pos = pos; + vec4 pre_pos = vec4(position.xyz, 1.0); + vec4 pos = modelview_projection_matrix * pre_pos; + //post_pos = pos; + target_pos_x = 0.5 * (shadow_target_width - 1.0) * pos.x; + pos_w = pos.w; + pos_zd2 = pos.z * 0.5; + //foo = int(posxw.x); gl_Position = vec4(pos.x, pos.y, pos.w*0.5, pos.w); diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl index 08f6ec63fe..70a5912258 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl @@ -58,20 +58,28 @@ uniform float shadow_bias; uniform mat4 inv_proj; -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl) +float mex(float a, float b) +{ + return a; +} + +float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) { stc.xyz /= stc.w; stc.z += shadow_bias; + + //stc.x = floor(stc.x + fract(stc.y) * 1.5); + stc.x = floor(stc.x + fract(stc.y*12345)); float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, scl, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, -scl, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, scl, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, -scl, 0.0)).x, cs); - - return shadow/5.0; + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x,cs); + + return shadow*0.2; } @@ -101,7 +109,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap3, lpos, 0.25)*w; + shadow += pcfShadow(shadowMap3, lpos)*w; weight += w; shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); } @@ -114,7 +122,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap2, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap2, lpos)*w; weight += w; } @@ -126,7 +134,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; - shadow += pcfShadow(shadowMap1, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap1, lpos)*w; weight += w; } @@ -138,7 +146,7 @@ void main() float w = 1.0; w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; - shadow += pcfShadow(shadowMap0, lpos, 1.0)*w; + shadow += pcfShadow(shadowMap0, lpos)*w; weight += w; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl index aae6a070e2..5fc4fd72ef 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl @@ -71,20 +71,28 @@ vec4 getPosition(vec2 pos_screen) return pos; } -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl) +float mex(float a, float b) +{ + return a; +} + +float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) { stc.xyz /= stc.w; stc.z += shadow_bias; + + //stc.x = floor(stc.x + fract(stc.y) * 1.5); + stc.x = floor(stc.x + fract(stc.y*12345)); float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, scl, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, -scl, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, scl, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, -scl, 0.0)).x, cs); - - return shadow/5.0; + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x,cs); + + return shadow*0.2; } @@ -114,7 +122,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap3, lpos, 0.25)*w; + shadow += pcfShadow(shadowMap3, lpos)*w; weight += w; shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); } @@ -127,7 +135,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap2, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap2, lpos)*w; weight += w; } @@ -139,7 +147,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; - shadow += pcfShadow(shadowMap1, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap1, lpos)*w; weight += w; } @@ -151,7 +159,7 @@ void main() float w = 1.0; w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; - shadow += pcfShadow(shadowMap0, lpos, 1.0)*w; + shadow += pcfShadow(shadowMap0, lpos)*w; weight += w; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl index 931577359e..785d50b853 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl @@ -70,20 +70,28 @@ vec4 getPosition(vec2 pos_screen) return pos; } -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl) +float mex(float a, float b) +{ + return a; +} + +float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) { stc.xyz /= stc.w; stc.z += shadow_bias; + + //stc.x = floor(stc.x + fract(stc.y) * 1.5); + stc.x = floor(stc.x + fract(stc.y*12345)); float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, scl, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, -scl, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, scl, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, -scl, 0.0)).x, cs); - - return shadow/5.0; + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x,cs); + + return shadow*0.2; } @@ -113,7 +121,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap3, lpos, 0.25)*w; + shadow += pcfShadow(shadowMap3, lpos)*w; weight += w; shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); } @@ -126,7 +134,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap2, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap2, lpos)*w; weight += w; } @@ -138,7 +146,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; - shadow += pcfShadow(shadowMap1, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap1, lpos)*w; weight += w; } @@ -150,7 +158,7 @@ void main() float w = 1.0; w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; - shadow += pcfShadow(shadowMap0, lpos, 1.0)*w; + shadow += pcfShadow(shadowMap0, lpos)*w; weight += w; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl index 8c4ccf9cb3..b0034addb5 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl @@ -78,42 +78,56 @@ vec4 getPosition(vec2 pos_screen) return pos; } -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl) +float mex(float a, float b) +{ + return a; +} + +float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; stc.z += shadow_bias*scl; - + + ////stc.x = floor(stc.x + fract(stc.y*12345)); + //stc.x = floor(stc.x + fract(stc.y)); + stc.x = floor(stc.x + fract(pos_screen.y*0.666666666)); + + //stc.x = floor(stc.x); + //stc.y = floor(stc.y); + + //stc.x += 0.5; + float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(1.5, 1.5, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(1.5, -1.5, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-1.5, 1.5, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-1.5, -1.5, 0.0)).x, cs); - - return shadow/5.0; - //return shadow; + + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, -1.5, 0.0)).x,cs); + + return shadow*0.2; } -float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl) +float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; stc.z += spot_shadow_bias*scl; + stc.x = floor(proj_shadow_res.x * stc.x + fract(pos_screen.y*0.666666666)) / proj_shadow_res.x; // snap float cs = shadow2D(shadowMap, stc.xyz).x; float shadow = cs; - vec2 off = 1.5/proj_shadow_res; + vec2 off = 1.0/proj_shadow_res; + off.y *= 1.5; - shadow += max(shadow2D(shadowMap, stc.xyz+vec3(off.x, off.y, 0.0)).x, cs); - shadow += max(shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x, cs); - shadow += max(shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x, cs); - shadow += max(shadow2D(shadowMap, stc.xyz+vec3(-off.x, -off.y, 0.0)).x, cs); - - return shadow/5.0; - - //return shadow; + shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(off.x*2.0, off.y, 0.0)).x, cs); + shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x, cs); + shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x, cs); + shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(-off.x*2.0, -off.y, 0.0)).x, cs); + + return shadow*0.2; } void main() @@ -166,7 +180,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap3, lpos, 0.25)*w; + shadow += pcfShadow(shadowMap3, lpos, 0.25, pos_screen)*w; weight += w; shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); } @@ -179,7 +193,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap2, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap2, lpos, 0.5, pos_screen)*w; weight += w; } @@ -191,7 +205,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; - shadow += pcfShadow(shadowMap1, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap1, lpos, 0.75, pos_screen)*w; weight += w; } @@ -203,7 +217,7 @@ void main() float w = 1.0; w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; - shadow += pcfShadow(shadowMap0, lpos, 1.0)*w; + shadow += pcfShadow(shadowMap0, lpos, 1.0, pos_screen)*w; weight += w; } @@ -237,11 +251,11 @@ void main() //spotlight shadow 1 vec4 lpos = shadow_matrix[4]*spos; - frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8); + frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8, pos_screen); //spotlight shadow 2 lpos = shadow_matrix[5]*spos; - frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8); + frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8, pos_screen); //frag_color.rgb = pos.xyz; //frag_color.b = shadow; diff --git a/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl b/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl index 02075a7687..8aee76d033 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl @@ -90,7 +90,7 @@ vec2 getKern(int i) kern[5] = vec2(-0.7071, -0.7071) * 0.750*0.750; kern[6] = vec2(-0.7071, 0.7071) * 0.875*0.875; kern[7] = vec2(0.7071, -0.7071) * 1.000*1.000; - + return kern[i]; } @@ -139,42 +139,47 @@ float calcAmbientOcclusion(vec4 pos, vec3 norm) return min(ret, 1.0); } -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl) +float mex(float a, float b) +{ + return a; +} + +float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; stc.z += shadow_bias*scl; + + stc.x = floor(stc.x + fract(pos_screen.y*0.666666666)); float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(1.5, 1.5, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(1.5, -1.5, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-1.5, 1.5, 0.0)).x, cs); - shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-1.5, -1.5, 0.0)).x, cs); - - return shadow/5.0; - //return shadow; + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x,cs); + shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x,cs); + + return shadow*0.2; } -float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl) +float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; stc.z += spot_shadow_bias*scl; + stc.x = floor(proj_shadow_res.x * stc.x + fract(pos_screen.y*0.666666666)) / proj_shadow_res.x; // snap float cs = shadow2D(shadowMap, stc.xyz).x; float shadow = cs; - vec2 off = 1.5/proj_shadow_res; - - shadow += max(shadow2D(shadowMap, stc.xyz+vec3(off.x, off.y, 0.0)).x, cs); - shadow += max(shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x, cs); - shadow += max(shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x, cs); - shadow += max(shadow2D(shadowMap, stc.xyz+vec3(-off.x, -off.y, 0.0)).x, cs); + vec2 off = 1.0/proj_shadow_res; + off.y *= 1.5; - return shadow/5.0; - - //return shadow; + shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(off.x*2.0, off.y, 0.0)).x, cs); + shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x, cs); + shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x, cs); + shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(-off.x*2.0, -off.y, 0.0)).x, cs); + + return shadow*0.2; } void main() @@ -227,7 +232,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap3, lpos, 0.25)*w; + shadow += pcfShadow(shadowMap3, lpos, 0.25, pos_screen)*w; weight += w; shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); } @@ -240,7 +245,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap2, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap2, lpos, 0.5, pos_screen)*w; weight += w; } @@ -252,7 +257,7 @@ void main() float w = 1.0; w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; - shadow += pcfShadow(shadowMap1, lpos, 0.75)*w; + shadow += pcfShadow(shadowMap1, lpos, 0.75, pos_screen)*w; weight += w; } @@ -264,7 +269,7 @@ void main() float w = 1.0; w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; - shadow += pcfShadow(shadowMap0, lpos, 1.0)*w; + shadow += pcfShadow(shadowMap0, lpos, 1.0, pos_screen)*w; weight += w; } @@ -298,11 +303,11 @@ void main() //spotlight shadow 1 vec4 lpos = shadow_matrix[4]*spos; - frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8); + frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8, pos_screen); //spotlight shadow 2 lpos = shadow_matrix[5]*spos; - frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8); + frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8, pos_screen); //frag_color.rgb = pos.xyz; //frag_color.b = shadow; diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index ab994c71cb..22a1a5b45a 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -425,6 +425,8 @@ LLPipeline::LLPipeline() : mLightingDetail(0), mScreenWidth(0), mScreenHeight(0) + // mSunShadowMapWidth(0), + // mSpotShadowMapWidth(0) { mNoiseMap = 0; mTrueNoiseMap = 0; @@ -738,6 +740,8 @@ void LLPipeline::allocatePhysicsBuffer() void LLPipeline::allocateScreenBuffer(U32 resX, U32 resY) { + llwarns << "ALLOCATING SCREEN BUFFER, " << resX << "x" << resY << llendl; + refreshCachedSettings(); U32 samples = RenderFSAASamples; @@ -787,6 +791,7 @@ void LLPipeline::allocateScreenBuffer(U32 resX, U32 resY) bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) { + llwarns << "ALLOCATING SCREEN BUFFER, " << resX << "x" << resY << llendl; refreshCachedSettings(); // remember these dimensions @@ -846,9 +851,10 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) if (shadow_detail > 0) { //allocate 4 sun shadow maps + U32 mSunShadowMapWidth = ((U32(resX*scale)+1)&~1); // must be even to avoid a stripe in the horizontal shadow blur for (U32 i = 0; i < 4; i++) { - if (!mShadow[i].allocate(U32(resX*scale),U32(resY*scale), 0, TRUE, FALSE, LLTexUnit::TT_RECT_TEXTURE)) return false; + if (!mShadow[i].allocate(mSunShadowMapWidth,U32(resY*scale), 0, TRUE, FALSE, LLTexUnit::TT_RECT_TEXTURE)) return false; } } else @@ -857,6 +863,7 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) { mShadow[i].release(); } + //mSunShadowMapWidth = 0; } U32 width = nhpo2(U32(resX*scale))/2; @@ -864,9 +871,10 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) if (shadow_detail > 1) { //allocate two spot shadow maps + U32 mSpotShadowMapWidth = width; for (U32 i = 4; i < 6; i++) { - if (!mShadow[i].allocate(width, height, 0, TRUE, FALSE)) return false; + if (!mShadow[i].allocate(mSpotShadowMapWidth, height, 0, TRUE, FALSE)) return false; } } else @@ -875,6 +883,7 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) { mShadow[i].release(); } + //mSpotShadowMapWidth = 0; } // don't disable shaders on next session @@ -8293,7 +8302,7 @@ static LLFastTimer::DeclareTimer FTM_SHADOW_RENDER("Render Shadows"); static LLFastTimer::DeclareTimer FTM_SHADOW_ALPHA("Alpha Shadow"); static LLFastTimer::DeclareTimer FTM_SHADOW_SIMPLE("Simple Shadow"); -void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera& shadow_cam, LLCullResult &result, BOOL use_shader, BOOL use_occlusion) +void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera& shadow_cam, LLCullResult &result, BOOL use_shader, BOOL use_occlusion, U32 target_width) { LLFastTimer t(FTM_SHADOW_RENDER); @@ -8384,7 +8393,9 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera LLFastTimer ftm(FTM_SHADOW_ALPHA); gDeferredShadowAlphaMaskProgram.bind(); gDeferredShadowAlphaMaskProgram.setMinimumAlpha(0.598f); - + gDeferredShadowAlphaMaskProgram.uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + // llwarns << "target_width is " << target_width << llendl; + U32 mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | @@ -9237,11 +9248,13 @@ void LLPipeline::generateSunShadow(LLCamera& camera) mShadow[j].getViewport(gGLViewport); mShadow[j].clear(); + U32 target_width = mShadow[j].getWidth(); + { static LLCullResult result[4]; //LLGLEnable enable(GL_DEPTH_CLAMP_NV); - renderShadow(view[j], proj[j], shadow_cam, result[j], TRUE); + renderShadow(view[j], proj[j], shadow_cam, result[j], TRUE, TRUE, target_width); } mShadow[j].flush(); @@ -9380,11 +9393,13 @@ void LLPipeline::generateSunShadow(LLCamera& camera) mShadow[i+4].getViewport(gGLViewport); mShadow[i+4].clear(); + U32 target_width = mShadow[i+4].getWidth(); + static LLCullResult result[2]; LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_SHADOW0+i+4; - renderShadow(view[i+4], proj[i+4], shadow_cam, result[i], FALSE, FALSE); + renderShadow(view[i+4], proj[i+4], shadow_cam, result[i], FALSE, FALSE, target_width); mShadow[i+4].flush(); } diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index b8b4f164fe..201952876f 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -264,7 +264,7 @@ public: void setHighlightObject(LLDrawable* obj) { mHighlightObject = obj; } - void renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera& camera, LLCullResult& result, BOOL use_shader = TRUE, BOOL use_occlusion = TRUE); + void renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera& camera, LLCullResult& result, BOOL use_shader, BOOL use_occlusion, U32 target_width); void renderHighlights(); void renderDebug(); void renderPhysicsDisplay(); -- cgit v1.2.3 From 03442801bd8018318efb4a4e763dc8969fd11ed7 Mon Sep 17 00:00:00 2001 From: Adam Moss Date: Sat, 21 Apr 2012 18:17:37 +0100 Subject: contributors.txt update. --- doc/contributions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/contributions.txt b/doc/contributions.txt index 5a24c43d5a..f7242ae7a9 100644 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -1160,9 +1160,9 @@ Tofu Buzzard CTS-411 STORM-546 VWR-24509 - STORM-1684 SH-2477 STORM-1684 + STORM-1819 Tony Kembia Torben Trautman TouchaHoney Perhaps -- cgit v1.2.3 From 442c5cf339ddffb10db9f819704bf77ad941bc28 Mon Sep 17 00:00:00 2001 From: Adam Moss Date: Sat, 21 Apr 2012 18:33:07 +0100 Subject: STORM-1819: Ternary/graded shadow support a bunch of trivial clean-ups and commentary. --- .../shaders/class1/deferred/shadowAlphaMaskF.glsl | 5 ----- .../shaders/class1/deferred/shadowAlphaMaskV.glsl | 5 ----- .../app_settings/shaders/class2/deferred/alphaF.glsl | 3 +-- .../shaders/class2/deferred/alphaNonIndexedF.glsl | 3 +-- .../shaders/class2/deferred/alphaNonIndexedNoColorF.glsl | 3 +-- .../app_settings/shaders/class2/deferred/sunLightF.glsl | 11 +---------- indra/newview/pipeline.cpp | 13 ++++--------- 7 files changed, 8 insertions(+), 35 deletions(-) diff --git a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl index 07d5b08191..6269a051f1 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl @@ -29,18 +29,13 @@ out vec4 frag_color; #define frag_color gl_FragColor #endif -uniform mat4 modelview_projection_matrix; - uniform float minimum_alpha; uniform sampler2D diffuseMap; -//flat VARYING int foo; VARYING float pos_zd2; VARYING float pos_w; VARYING float target_pos_x; -//VARYING vec4 pre_pos; -//VARYING vec4 post_pos; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; diff --git a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl index 6397a5c461..c1f2d90712 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl @@ -34,9 +34,6 @@ ATTRIBUTE vec2 texcoord0; VARYING float pos_zd2; VARYING float pos_w; VARYING float target_pos_x; -//flat VARYING int foo; -//VARYING vec4 pre_pos; -//VARYING vec4 post_pos; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; @@ -47,11 +44,9 @@ void main() //transform vertex vec4 pre_pos = vec4(position.xyz, 1.0); vec4 pos = modelview_projection_matrix * pre_pos; - //post_pos = pos; target_pos_x = 0.5 * (shadow_target_width - 1.0) * pos.x; pos_w = pos.w; pos_zd2 = pos.z * 0.5; - //foo = int(posxw.x); gl_Position = vec4(pos.x, pos.y, pos.w*0.5, pos.w); diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl index 70a5912258..a8a3d3efc1 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl @@ -68,8 +68,7 @@ float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) stc.xyz /= stc.w; stc.z += shadow_bias; - //stc.x = floor(stc.x + fract(stc.y) * 1.5); - stc.x = floor(stc.x + fract(stc.y*12345)); + stc.x = floor(stc.x + fract(stc.y*12345)); // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl index 5fc4fd72ef..d8856a5f1e 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl @@ -81,8 +81,7 @@ float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) stc.xyz /= stc.w; stc.z += shadow_bias; - //stc.x = floor(stc.x + fract(stc.y) * 1.5); - stc.x = floor(stc.x + fract(stc.y*12345)); + stc.x = floor(stc.x + fract(stc.y*12345)); // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl index 785d50b853..f863f07e1a 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl @@ -80,8 +80,7 @@ float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) stc.xyz /= stc.w; stc.z += shadow_bias; - //stc.x = floor(stc.x + fract(stc.y) * 1.5); - stc.x = floor(stc.x + fract(stc.y*12345)); + stc.x = floor(stc.x + fract(stc.y*12345)); // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; diff --git a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl index b0034addb5..be11790881 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl @@ -88,20 +88,11 @@ float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_scr stc.xyz /= stc.w; stc.z += shadow_bias*scl; - ////stc.x = floor(stc.x + fract(stc.y*12345)); - //stc.x = floor(stc.x + fract(stc.y)); - stc.x = floor(stc.x + fract(pos_screen.y*0.666666666)); - - //stc.x = floor(stc.x); - //stc.y = floor(stc.y); - - //stc.x += 0.5; + stc.x = floor(stc.x + fract(pos_screen.y*0.666666666)); // add some jitter to X sample pos according to Y to disguise the snapping going on here float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - //return shadow; - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, 1.5, 0.0)).x,cs); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 22a1a5b45a..e098aa244b 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -425,8 +425,6 @@ LLPipeline::LLPipeline() : mLightingDetail(0), mScreenWidth(0), mScreenHeight(0) - // mSunShadowMapWidth(0), - // mSpotShadowMapWidth(0) { mNoiseMap = 0; mTrueNoiseMap = 0; @@ -851,10 +849,10 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) if (shadow_detail > 0) { //allocate 4 sun shadow maps - U32 mSunShadowMapWidth = ((U32(resX*scale)+1)&~1); // must be even to avoid a stripe in the horizontal shadow blur + U32 sun_shadow_map_width = ((U32(resX*scale)+1)&~1); // must be even to avoid a stripe in the horizontal shadow blur for (U32 i = 0; i < 4; i++) { - if (!mShadow[i].allocate(mSunShadowMapWidth,U32(resY*scale), 0, TRUE, FALSE, LLTexUnit::TT_RECT_TEXTURE)) return false; + if (!mShadow[i].allocate(sun_shadow_map_width,U32(resY*scale), 0, TRUE, FALSE, LLTexUnit::TT_RECT_TEXTURE)) return false; } } else @@ -863,7 +861,6 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) { mShadow[i].release(); } - //mSunShadowMapWidth = 0; } U32 width = nhpo2(U32(resX*scale))/2; @@ -871,10 +868,10 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) if (shadow_detail > 1) { //allocate two spot shadow maps - U32 mSpotShadowMapWidth = width; + U32 spot_shadow_map_width = width; for (U32 i = 4; i < 6; i++) { - if (!mShadow[i].allocate(mSpotShadowMapWidth, height, 0, TRUE, FALSE)) return false; + if (!mShadow[i].allocate(spot_shadow_map_width, height, 0, TRUE, FALSE)) return false; } } else @@ -883,7 +880,6 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) { mShadow[i].release(); } - //mSpotShadowMapWidth = 0; } // don't disable shaders on next session @@ -8394,7 +8390,6 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera gDeferredShadowAlphaMaskProgram.bind(); gDeferredShadowAlphaMaskProgram.setMinimumAlpha(0.598f); gDeferredShadowAlphaMaskProgram.uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - // llwarns << "target_width is " << target_width << llendl; U32 mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | -- cgit v1.2.3 From 26df2b941e64bc62c3f822d130d7dbc0f2bfc8e4 Mon Sep 17 00:00:00 2001 From: Adam Moss Date: Sat, 21 Apr 2012 18:42:47 +0100 Subject: STORM-1819: Ternary/graded shadow support cleanup: get rid of the no-op 'mex' function in the shaders which was being used as a dummy replacement for 'mix' --- .../app_settings/shaders/class2/deferred/alphaF.glsl | 13 ++++--------- .../shaders/class2/deferred/alphaNonIndexedF.glsl | 13 ++++--------- .../shaders/class2/deferred/alphaNonIndexedNoColorF.glsl | 13 ++++--------- .../app_settings/shaders/class2/deferred/sunLightF.glsl | 13 ++++--------- .../app_settings/shaders/class2/deferred/sunLightSSAOF.glsl | 13 ++++--------- 5 files changed, 20 insertions(+), 45 deletions(-) diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl index a8a3d3efc1..8db4cb58cf 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl @@ -58,11 +58,6 @@ uniform float shadow_bias; uniform mat4 inv_proj; -float mex(float a, float b) -{ - return a; -} - float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) { stc.xyz /= stc.w; @@ -73,10 +68,10 @@ float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x,cs); + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x; return shadow*0.2; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl index d8856a5f1e..33958a5010 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl @@ -71,11 +71,6 @@ vec4 getPosition(vec2 pos_screen) return pos; } -float mex(float a, float b) -{ - return a; -} - float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) { stc.xyz /= stc.w; @@ -86,10 +81,10 @@ float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x,cs); + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x; return shadow*0.2; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl index f863f07e1a..ba6f3ace53 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl @@ -70,11 +70,6 @@ vec4 getPosition(vec2 pos_screen) return pos; } -float mex(float a, float b) -{ - return a; -} - float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) { stc.xyz /= stc.w; @@ -85,10 +80,10 @@ float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x,cs); + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x; return shadow*0.2; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl index be11790881..37408329f4 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl @@ -78,11 +78,6 @@ vec4 getPosition(vec2 pos_screen) return pos; } -float mex(float a, float b) -{ - return a; -} - float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; @@ -93,10 +88,10 @@ float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_scr float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, -1.5, 0.0)).x,cs); + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, -1.5, 0.0)).x; return shadow*0.2; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl b/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl index 8aee76d033..358e3a3f92 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl @@ -139,11 +139,6 @@ float calcAmbientOcclusion(vec4 pos, vec3 norm) return min(ret, 1.0); } -float mex(float a, float b) -{ - return a; -} - float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; @@ -154,10 +149,10 @@ float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_scr float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x,cs); - shadow += mex(shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x,cs); + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x; + shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x; return shadow*0.2; } -- cgit v1.2.3 From ab82a96f6e9eecc942aef4a10acd58ba97dfb3fe Mon Sep 17 00:00:00 2001 From: Adam Moss Date: Sat, 21 Apr 2012 18:51:57 +0100 Subject: STORM-1819: Ternary/graded shadow support missed a few spots: cleanup: get rid of the no-op 'mex' function in the shaders which was being used as a dummy replacement for 'mix' --- indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl | 8 ++++---- .../app_settings/shaders/class2/deferred/sunLightSSAOF.glsl | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl index 37408329f4..db3d760359 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl @@ -108,10 +108,10 @@ float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) vec2 off = 1.0/proj_shadow_res; off.y *= 1.5; - shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(off.x*2.0, off.y, 0.0)).x, cs); - shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x, cs); - shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x, cs); - shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(-off.x*2.0, -off.y, 0.0)).x, cs); + shadow += shadow2D(shadowMap, stc.xyz+vec3(off.x*2.0, off.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x*2.0, -off.y, 0.0)).x; return shadow*0.2; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl b/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl index 358e3a3f92..dfe108eb01 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl @@ -169,10 +169,10 @@ float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) vec2 off = 1.0/proj_shadow_res; off.y *= 1.5; - shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(off.x*2.0, off.y, 0.0)).x, cs); - shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x, cs); - shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x, cs); - shadow += mex(shadow2D(shadowMap, stc.xyz+vec3(-off.x*2.0, -off.y, 0.0)).x, cs); + shadow += shadow2D(shadowMap, stc.xyz+vec3(off.x*2.0, off.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x*2.0, -off.y, 0.0)).x; return shadow*0.2; } -- cgit v1.2.3 From 5adf6bdd28c52a601084259ea477ab52b59872f3 Mon Sep 17 00:00:00 2001 From: Adam Moss Date: Sun, 22 Apr 2012 19:53:46 +0100 Subject: remove a frightening llwarns that was just there for my own education. --- indra/newview/pipeline.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index e098aa244b..0e53cfaa86 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -738,8 +738,6 @@ void LLPipeline::allocatePhysicsBuffer() void LLPipeline::allocateScreenBuffer(U32 resX, U32 resY) { - llwarns << "ALLOCATING SCREEN BUFFER, " << resX << "x" << resY << llendl; - refreshCachedSettings(); U32 samples = RenderFSAASamples; -- cgit v1.2.3 From 62f7777b7a027a2f4def11dc923a098ca31bf7bc Mon Sep 17 00:00:00 2001 From: Adam Moss Date: Sun, 22 Apr 2012 19:54:46 +0100 Subject: and another! --- indra/newview/pipeline.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 0e53cfaa86..dbcc7fba9f 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -787,7 +787,6 @@ void LLPipeline::allocateScreenBuffer(U32 resX, U32 resY) bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) { - llwarns << "ALLOCATING SCREEN BUFFER, " << resX << "x" << resY << llendl; refreshCachedSettings(); // remember these dimensions -- cgit v1.2.3 From b6f2b2f399fc004806e525851e86d04bf446564d Mon Sep 17 00:00:00 2001 From: Adam Moss Date: Sun, 22 Apr 2012 19:58:14 +0100 Subject: remove a now-unused uniform. --- .../newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl | 2 -- 1 file changed, 2 deletions(-) diff --git a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl index 6269a051f1..bced4a5577 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl @@ -29,8 +29,6 @@ out vec4 frag_color; #define frag_color gl_FragColor #endif -uniform float minimum_alpha; - uniform sampler2D diffuseMap; VARYING float pos_zd2; -- cgit v1.2.3 From eb1bea222322385e6e5b05206f09f21bb891f3f7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Apr 2012 11:26:18 -0400 Subject: IQA-463: LLError::addRecorder() claims ownership of passed Recorder*. That is, when the underlying LLError::Settings object is destroyed -- possibly at termination, possibly on LLError::restoreSettings() -- the passed Recorder* is deleted. There was much existing code that seemed as unaware of this alarming fact as I was myself. Passing to addRecorder() a pointer to a stack object, or to a member of some other object, is just Bad. It might be preferable to make addRecorder() accept std::auto_ptr to make the ownership transfer more explicit -- or even boost::shared_ptr instead, which would allow the caller to either forget or retain the passed Recorder. This preliminary pass retains the Recorder* dumb pointer API, but documents the ownership issue, and eliminates known instances of passing pointers to anything but a standalone heap Recorder subclass object. --- indra/llcommon/llerrorcontrol.h | 77 ++++++------ indra/llcommon/tests/llerror_test.cpp | 226 +++++++++++++++++----------------- indra/llcommon/tests/wrapllerrs.h | 46 ++++++- indra/test/test.cpp | 10 +- 4 files changed, 206 insertions(+), 153 deletions(-) diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index 1be49cebc8..d53a819d88 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -1,4 +1,4 @@ -/** +/** * @file llerrorcontrol.h * @date December 2006 * @brief error message system control @@ -6,21 +6,21 @@ * $LicenseInfo:firstyear=2007&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$ */ @@ -38,7 +38,7 @@ class LLSD; This is the part of the LLError namespace that manages the messages produced by the logging. The logging support is defined in llerror.h. Most files do not need to include this. - + These implementations are in llerror.cpp. */ @@ -72,7 +72,7 @@ namespace LLError Settings that control what is logged. Setting a level means log messages at that level or above. */ - + LL_COMMON_API void setPrintLocation(bool); LL_COMMON_API void setDefaultLevel(LLError::ELevel); LL_COMMON_API ELevel getDefaultLevel(); @@ -101,31 +101,31 @@ namespace LLError // (by, for example, setting a class level to LEVEL_NONE), will keep // the that message from causing the fatal funciton to be invoked. - LL_COMMON_API FatalFunction getFatalFunction(); - // Retrieve the previously-set FatalFunction - - /// temporarily override the FatalFunction for the duration of a - /// particular scope, e.g. for unit tests - class LL_COMMON_API OverrideFatalFunction - { - public: - OverrideFatalFunction(const FatalFunction& func): - mPrev(getFatalFunction()) - { - setFatalFunction(func); - } - ~OverrideFatalFunction() - { - setFatalFunction(mPrev); - } - - private: - FatalFunction mPrev; - }; + LL_COMMON_API FatalFunction getFatalFunction(); + // Retrieve the previously-set FatalFunction + + /// temporarily override the FatalFunction for the duration of a + /// particular scope, e.g. for unit tests + class LL_COMMON_API OverrideFatalFunction + { + public: + OverrideFatalFunction(const FatalFunction& func): + mPrev(getFatalFunction()) + { + setFatalFunction(func); + } + ~OverrideFatalFunction() + { + setFatalFunction(mPrev); + } + + private: + FatalFunction mPrev; + }; typedef std::string (*TimeFunction)(); LL_COMMON_API std::string utcTime(); - + LL_COMMON_API void setTimeFunction(TimeFunction); // The function is use to return the current time, formatted for // display by those error recorders that want the time included. @@ -137,19 +137,27 @@ namespace LLError // An object that handles the actual output or error messages. public: virtual ~Recorder(); - + virtual void recordMessage(LLError::ELevel, const std::string& message) = 0; // use the level for better display, not for filtering - + virtual bool wantsTime(); // default returns false // override and return true if the recorder wants the time string // included in the text of the message }; - + + /** + * @NOTE: addRecorder() conveys ownership to the underlying Settings + * object -- when destroyed, it will @em delete the passed Recorder*! + */ LL_COMMON_API void addRecorder(Recorder*); + /** + * @NOTE: removeRecorder() reclaims ownership of the Recorder*: its + * lifespan becomes the caller's problem. + */ LL_COMMON_API void removeRecorder(Recorder*); // each error message is passed to each recorder via recordMessage() - + LL_COMMON_API void logToFile(const std::string& filename); LL_COMMON_API void logToFixedBuffer(LLLineBuffer*); // Utilities to add recorders for logging to a file or a fixed buffer @@ -167,10 +175,9 @@ namespace LLError class Settings; LL_COMMON_API Settings* saveAndResetSettings(); LL_COMMON_API void restoreSettings(Settings *); - + LL_COMMON_API std::string abbreviateFile(const std::string& filePath); LL_COMMON_API int shouldLogCallCount(); - }; #endif // LL_LLERRORCONTROL_H diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp index 09a20231de..279a90e51b 100644 --- a/indra/llcommon/tests/llerror_test.cpp +++ b/indra/llcommon/tests/llerror_test.cpp @@ -1,4 +1,4 @@ -/** +/** * @file llerror_test.cpp * @date December 2006 * @brief error unit tests @@ -6,21 +6,21 @@ * $LicenseInfo:firstyear=2006&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$ */ @@ -49,7 +49,7 @@ namespace static bool fatalWasCalled; void fatalCall(const std::string&) { fatalWasCalled = true; } } - + namespace tut { class TestRecorder : public LLError::Recorder @@ -57,59 +57,65 @@ namespace tut public: TestRecorder() : mWantsTime(false) { } ~TestRecorder() { LLError::removeRecorder(this); } - + void recordMessage(LLError::ELevel level, const std::string& message) { mMessages.push_back(message); } - + int countMessages() { return (int) mMessages.size(); } void clearMessages() { mMessages.clear(); } - + void setWantsTime(bool t) { mWantsTime = t; } bool wantsTime() { return mWantsTime; } - + std::string message(int n) { std::ostringstream test_name; test_name << "testing message " << n << ", not enough messages"; - + tut::ensure(test_name.str(), n < countMessages()); return mMessages[n]; } - + private: typedef std::vector MessageVector; MessageVector mMessages; - + bool mWantsTime; }; struct ErrorTestData { - TestRecorder mRecorder; + // addRecorder() expects to be able to later delete the passed + // Recorder*. Even though removeRecorder() reclaims ownership, passing + // a pointer to a data member rather than a heap Recorder subclass + // instance would just be Wrong. + TestRecorder* mRecorder; LLError::Settings* mPriorErrorSettings; - - ErrorTestData() + + ErrorTestData(): + mRecorder(new TestRecorder) { fatalWasCalled = false; - + mPriorErrorSettings = LLError::saveAndResetSettings(); LLError::setDefaultLevel(LLError::LEVEL_DEBUG); LLError::setFatalFunction(fatalCall); - LLError::addRecorder(&mRecorder); + LLError::addRecorder(mRecorder); } - + ~ErrorTestData() { - LLError::removeRecorder(&mRecorder); + LLError::removeRecorder(mRecorder); + delete mRecorder; LLError::restoreSettings(mPriorErrorSettings); } - + void ensure_message_count(int expectedCount) { - ensure_equals("message count", mRecorder.countMessages(), expectedCount); + ensure_equals("message count", mRecorder->countMessages(), expectedCount); } void ensure_message_contains(int n, const std::string& expectedText) @@ -117,7 +123,7 @@ namespace tut std::ostringstream test_name; test_name << "testing message " << n; - ensure_contains(test_name.str(), mRecorder.message(n), expectedText); + ensure_contains(test_name.str(), mRecorder->message(n), expectedText); } void ensure_message_does_not_contain(int n, const std::string& expectedText) @@ -125,22 +131,22 @@ namespace tut std::ostringstream test_name; test_name << "testing message " << n; - ensure_does_not_contain(test_name.str(), mRecorder.message(n), expectedText); + ensure_does_not_contain(test_name.str(), mRecorder->message(n), expectedText); } }; - + typedef test_group ErrorTestGroup; typedef ErrorTestGroup::object ErrorTestObject; - + ErrorTestGroup errorTestGroup("error"); - + template<> template<> void ErrorTestObject::test<1>() // basic test of output { llinfos << "test" << llendl; llinfos << "bob" << llendl; - + ensure_message_contains(0, "test"); ensure_message_contains(1, "bob"); } @@ -159,7 +165,7 @@ namespace }; namespace tut -{ +{ template<> template<> void ErrorTestObject::test<2>() // messages are filtered based on default level @@ -172,7 +178,7 @@ namespace tut ensure_message_contains(3, "error"); ensure_message_contains(4, "four"); ensure_message_count(5); - + LLError::setDefaultLevel(LLError::LEVEL_INFO); writeSome(); ensure_message_contains(5, "two"); @@ -180,20 +186,20 @@ namespace tut ensure_message_contains(7, "error"); ensure_message_contains(8, "four"); ensure_message_count(9); - + LLError::setDefaultLevel(LLError::LEVEL_WARN); writeSome(); ensure_message_contains(9, "three"); ensure_message_contains(10, "error"); ensure_message_contains(11, "four"); ensure_message_count(12); - + LLError::setDefaultLevel(LLError::LEVEL_ERROR); writeSome(); ensure_message_contains(12, "error"); ensure_message_contains(13, "four"); ensure_message_count(14); - + LLError::setDefaultLevel(LLError::LEVEL_NONE); writeSome(); ensure_message_count(14); @@ -218,14 +224,14 @@ namespace tut { std::string thisFile = __FILE__; std::string abbreviateFile = LLError::abbreviateFile(thisFile); - + ensure_ends_with("file name abbreviation", abbreviateFile, "llcommon/tests/llerror_test.cpp" ); ensure_does_not_contain("file name abbreviation", abbreviateFile, "indra"); - + std::string someFile = #if LL_WINDOWS "C:/amy/bob/cam.cpp" @@ -234,12 +240,12 @@ namespace tut #endif ; std::string someAbbreviation = LLError::abbreviateFile(someFile); - + ensure_equals("non-indra file abbreviation", someAbbreviation, someFile); } } - + namespace { std::string locationString(int line) @@ -247,22 +253,22 @@ namespace std::ostringstream location; location << LLError::abbreviateFile(__FILE__) << "(" << line << ") : "; - + return location.str(); } - + std::string writeReturningLocation() { llinfos << "apple" << llendl; int this_line = __LINE__; return locationString(this_line); } - + std::string writeReturningLocationAndFunction() { llinfos << "apple" << llendl; int this_line = __LINE__; return locationString(this_line) + __FUNCTION__; } - + std::string errorReturningLocation() { llerrs << "die" << llendl; int this_line = __LINE__; @@ -271,20 +277,20 @@ namespace } namespace tut -{ +{ template<> template<> void ErrorTestObject::test<5>() // file and line information in log messages { std::string location = writeReturningLocation(); // expecting default to not print location information - + LLError::setPrintLocation(true); writeReturningLocation(); - + LLError::setPrintLocation(false); writeReturningLocation(); - + ensure_message_does_not_contain(0, location); ensure_message_contains(1, location); ensure_message_does_not_contain(2, location); @@ -297,7 +303,7 @@ namespace tut existing log messages often do.) The functions all return their C++ name so that test can be substantial mechanized. */ - + std::string logFromGlobal(bool id) { llinfos << (id ? "logFromGlobal: " : "") << "hi" << llendl; @@ -345,7 +351,7 @@ namespace return "ClassWithNoLogType::logFromStatic"; } }; - + class ClassWithLogType { LOG_CLASS(ClassWithLogType); public: @@ -360,13 +366,13 @@ namespace return "ClassWithLogType::logFromStatic"; } }; - + std::string logFromNamespace(bool id) { return Foo::logFromNamespace(id); } std::string logFromClassWithNoLogTypeMember(bool id) { ClassWithNoLogType c; return c.logFromMember(id); } std::string logFromClassWithNoLogTypeStatic(bool id) { return ClassWithNoLogType::logFromStatic(id); } std::string logFromClassWithLogTypeMember(bool id) { ClassWithLogType c; return c.logFromMember(id); } std::string logFromClassWithLogTypeStatic(bool id) { return ClassWithLogType::logFromStatic(id); } - + void ensure_has(const std::string& message, const std::string& actual, const std::string& expected) { @@ -379,18 +385,18 @@ namespace throw tut::failure(ss.str().c_str()); } } - + typedef std::string (*LogFromFunction)(bool); - void testLogName(tut::TestRecorder& recorder, LogFromFunction f, + void testLogName(tut::TestRecorder* recorder, LogFromFunction f, const std::string& class_name = "") { - recorder.clearMessages(); + recorder->clearMessages(); std::string name = f(false); f(true); - - std::string messageWithoutName = recorder.message(0); - std::string messageWithName = recorder.message(1); - + + std::string messageWithoutName = recorder->message(0); + std::string messageWithName = recorder->message(1); + ensure_has(name + " logged without name", messageWithoutName, name); ensure_has(name + " logged with name", @@ -431,18 +437,18 @@ namespace llinfos << "inside" << llendl; return "moo"; } - + std::string outerLogger() { llinfos << "outside(" << innerLogger() << ")" << llendl; return "bar"; } - + void uberLogger() { llinfos << "uber(" << outerLogger() << "," << innerLogger() << ")" << llendl; } - + class LogWhileLogging { public: @@ -461,11 +467,11 @@ namespace LogWhileLogging l; llinfos << "meta(" << l << ")" << llendl; } - + } namespace tut -{ +{ template<> template<> // handle nested logging void ErrorTestObject::test<7>() @@ -474,31 +480,31 @@ namespace tut ensure_message_contains(0, "inside"); ensure_message_contains(1, "outside(moo)"); ensure_message_count(2); - + uberLogger(); ensure_message_contains(2, "inside"); ensure_message_contains(3, "inside"); ensure_message_contains(4, "outside(moo)"); ensure_message_contains(5, "uber(bar,moo)"); ensure_message_count(6); - + metaLogger(); ensure_message_contains(6, "logging"); ensure_message_contains(7, "meta(baz)"); ensure_message_count(8); } - + template<> template<> // special handling of llerrs calls void ErrorTestObject::test<8>() { LLError::setPrintLocation(false); std::string location = errorReturningLocation(); - + ensure_message_contains(0, location + "error"); ensure_message_contains(1, "die"); ensure_message_count(2); - + ensure("fatal callback called", fatalWasCalled); } } @@ -509,7 +515,7 @@ namespace { return "1947-07-08T03:04:05Z"; } - + void ufoSighting() { llinfos << "ufo" << llendl; @@ -517,35 +523,35 @@ namespace } namespace tut -{ +{ template<> template<> // time in output (for recorders that need it) void ErrorTestObject::test<9>() { LLError::setTimeFunction(roswell); - mRecorder.setWantsTime(false); + mRecorder->setWantsTime(false); ufoSighting(); ensure_message_contains(0, "ufo"); ensure_message_does_not_contain(0, roswell()); - - mRecorder.setWantsTime(true); + + mRecorder->setWantsTime(true); ufoSighting(); ensure_message_contains(1, "ufo"); ensure_message_contains(1, roswell()); } - + template<> template<> // output order void ErrorTestObject::test<10>() { LLError::setPrintLocation(true); LLError::setTimeFunction(roswell); - mRecorder.setWantsTime(true); + mRecorder->setWantsTime(true); std::string locationAndFunction = writeReturningLocationAndFunction(); - + ensure_equals("order is time type location function message", - mRecorder.message(0), + mRecorder->message(0), roswell() + " INFO: " + locationAndFunction + ": apple"); } @@ -553,30 +559,30 @@ namespace tut // multiple recorders void ErrorTestObject::test<11>() { - TestRecorder altRecorder; - LLError::addRecorder(&altRecorder); - + TestRecorder* altRecorder(new TestRecorder); + LLError::addRecorder(altRecorder); + llinfos << "boo" << llendl; ensure_message_contains(0, "boo"); - ensure_equals("alt recorder count", altRecorder.countMessages(), 1); - ensure_contains("alt recorder message 0", altRecorder.message(0), "boo"); - + ensure_equals("alt recorder count", altRecorder->countMessages(), 1); + ensure_contains("alt recorder message 0", altRecorder->message(0), "boo"); + LLError::setTimeFunction(roswell); - TestRecorder anotherRecorder; - anotherRecorder.setWantsTime(true); - LLError::addRecorder(&anotherRecorder); - + TestRecorder* anotherRecorder(new TestRecorder); + anotherRecorder->setWantsTime(true); + LLError::addRecorder(anotherRecorder); + llinfos << "baz" << llendl; std::string when = roswell(); - + ensure_message_does_not_contain(1, when); - ensure_equals("alt recorder count", altRecorder.countMessages(), 2); - ensure_does_not_contain("alt recorder message 1", altRecorder.message(1), when); - ensure_equals("another recorder count", anotherRecorder.countMessages(), 1); - ensure_contains("another recorder message 0", anotherRecorder.message(0), when); + ensure_equals("alt recorder count", altRecorder->countMessages(), 2); + ensure_does_not_contain("alt recorder message 1", altRecorder->message(1), when); + ensure_equals("another recorder count", anotherRecorder->countMessages(), 1); + ensure_contains("another recorder message 0", anotherRecorder->message(0), when); } } @@ -610,10 +616,10 @@ namespace tut { LLError::setDefaultLevel(LLError::LEVEL_WARN); LLError::setClassLevel("TestBeta", LLError::LEVEL_INFO); - + TestAlpha::doAll(); TestBeta::doAll(); - + ensure_message_contains(0, "aim west"); ensure_message_contains(1, "error"); ensure_message_contains(2, "ate eels"); @@ -623,7 +629,7 @@ namespace tut ensure_message_contains(6, "big easy"); ensure_message_count(7); } - + template<> template<> // filtering by function, and that it will override class filtering void ErrorTestObject::test<13>() @@ -632,13 +638,13 @@ namespace tut LLError::setClassLevel("TestBeta", LLError::LEVEL_WARN); LLError::setFunctionLevel("TestBeta::doInfo", LLError::LEVEL_DEBUG); LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE); - + TestBeta::doAll(); ensure_message_contains(0, "buy iron"); ensure_message_contains(1, "bad word"); ensure_message_count(2); } - + template<> template<> // filtering by file // and that it is overridden by both class and function filtering @@ -652,7 +658,7 @@ namespace tut LLError::LEVEL_NONE); LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE); - + TestAlpha::doAll(); TestBeta::doAll(); ensure_message_contains(0, "any idea"); @@ -660,7 +666,7 @@ namespace tut ensure_message_contains(2, "bad word"); ensure_message_count(3); } - + template<> template<> // proper cached, efficient lookup of filtering void ErrorTestObject::test<15>() @@ -690,7 +696,7 @@ namespace tut ensure_message_count(2); ensure_equals("sixth check", LLError::shouldLogCallCount(), 3); } - + template<> template<> // configuration from LLSD void ErrorTestObject::test<16>() @@ -699,26 +705,26 @@ namespace tut LLSD config; config["print-location"] = true; config["default-level"] = "DEBUG"; - + LLSD set1; set1["level"] = "WARN"; set1["files"][0] = this_file; - + LLSD set2; set2["level"] = "INFO"; set2["classes"][0] = "TestAlpha"; - + LLSD set3; set3["level"] = "NONE"; set3["functions"][0] = "TestAlpha::doError"; set3["functions"][1] = "TestBeta::doError"; - + config["settings"][0] = set1; config["settings"][1] = set2; config["settings"][2] = set3; - + LLError::configure(config); - + TestAlpha::doAll(); TestBeta::doAll(); ensure_message_contains(0, "any idea"); @@ -726,13 +732,13 @@ namespace tut ensure_message_contains(1, "aim west"); ensure_message_contains(2, "bad word"); ensure_message_count(3); - + // make sure reconfiguring works LLSD config2; config2["default-level"] = "WARN"; - + LLError::configure(config2); - + TestAlpha::doAll(); TestBeta::doAll(); ensure_message_contains(3, "aim west"); @@ -744,13 +750,13 @@ namespace tut ensure_message_contains(8, "big easy"); ensure_message_count(9); } -} +} /* Tests left: handling of classes without LOG_CLASS - live update of filtering from file - + live update of filtering from file + syslog recorder file recorder cerr/stderr recorder diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index f79acacb22..a4d3a4e026 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -29,10 +29,15 @@ #if ! defined(LL_WRAPLLERRS_H) #define LL_WRAPLLERRS_H +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + #include #include "llerrorcontrol.h" #include "stringize.h" #include +#include #include #include #include @@ -80,11 +85,39 @@ struct WrapLL_ERRS LLError::FatalFunction mPriorFatal; }; +/** + * LLError::addRecorder() accepts ownership of the passed Recorder* -- it + * expects to be able to delete it later. CaptureLog isa Recorder whose + * pointer we want to be able to pass without any ownership implications. + * For such cases, instantiate a new RecorderProxy(yourRecorder) and pass + * that. Your heap RecorderProxy might later be deleted, but not yourRecorder. + */ +class RecorderProxy: public LLError::Recorder +{ +public: + RecorderProxy(LLError::Recorder* recorder): + mRecorder(recorder) + {} + + virtual void recordMessage(LLError::ELevel level, const std::string& message) + { + mRecorder->recordMessage(level, message); + } + + virtual bool wantsTime() + { + return mRecorder->wantsTime(); + } + +private: + LLError::Recorder* mRecorder; +}; + /** * Capture log messages. This is adapted (simplified) from the one in * llerror_test.cpp. */ -class CaptureLog : public LLError::Recorder +class CaptureLog : public LLError::Recorder, public boost::noncopyable { public: CaptureLog(LLError::ELevel level=LLError::LEVEL_DEBUG): @@ -97,16 +130,18 @@ public: // with that output. If it turns out that saveAndResetSettings() has // some bad effect, give up and just let the DEBUG level log messages // display. - mOldSettings(LLError::saveAndResetSettings()) + mOldSettings(LLError::saveAndResetSettings()), + mProxy(new RecorderProxy(this)) { LLError::setFatalFunction(wouldHaveCrashed); LLError::setDefaultLevel(level); - LLError::addRecorder(this); + LLError::addRecorder(mProxy); } ~CaptureLog() { - LLError::removeRecorder(this); + LLError::removeRecorder(mProxy); + delete mProxy; LLError::restoreSettings(mOldSettings); } @@ -133,7 +168,7 @@ public: throw tut::failure(STRINGIZE("failed to find '" << search << "' in captured log messages:\n" - << *this)); + << boost::ref(*this))); } std::ostream& streamto(std::ostream& out) const @@ -155,6 +190,7 @@ public: typedef std::list MessageList; MessageList mMessages; LLError::Settings* mOldSettings; + LLError::Recorder* mProxy; }; inline diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 128d84e428..06128e0902 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -37,6 +37,7 @@ #include "linden_common.h" #include "llerrorcontrol.h" #include "lltut.h" +#include "tests/wrapllerrs.h" // RecorderProxy #include "stringize.h" #include "namedtempfile.h" @@ -91,22 +92,24 @@ public: virtual void replay(std::ostream&) {} }; -class LLReplayLogReal: public LLReplayLog, public LLError::Recorder +class LLReplayLogReal: public LLReplayLog, public LLError::Recorder, public boost::noncopyable { public: LLReplayLogReal(LLError::ELevel level, apr_pool_t* pool): mOldSettings(LLError::saveAndResetSettings()), + mProxy(new RecorderProxy(this)), mTempFile("log", "", pool), // create file mFile(mTempFile.getName().c_str()) // open it { LLError::setFatalFunction(wouldHaveCrashed); LLError::setDefaultLevel(level); - LLError::addRecorder(this); + LLError::addRecorder(mProxy); } virtual ~LLReplayLogReal() { - LLError::removeRecorder(this); + LLError::removeRecorder(mProxy); + delete mProxy; LLError::restoreSettings(mOldSettings); } @@ -134,6 +137,7 @@ public: private: LLError::Settings* mOldSettings; + LLError::Recorder* mProxy; NamedTempFile mTempFile; std::ofstream mFile; }; -- cgit v1.2.3 From 78816bb1561190ac5a882caa90da2865d4aaa353 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Apr 2012 12:01:15 -0400 Subject: IQA-463: Use APR file I/O for downloaded viewer installer .exe. On Windows, calling CreateProcess(bInheritHandles=FALSE) is the wrong idea. In that case, CreateProcess() passes NO handles -- even the files you've explicitly designated as the child's stdin, stdout, stderr in the STARTUPINFO struct! Remove LLProcess code to tweak bInheritHandles; we should also remove the corresponding (useless) APR extension. Instead, given that the Windows file-locking problem we've observed is specific to the viewer installer .exe file downloaded by the background updater logic, use APR file I/O for that specific file. Empirically, both llofstream and std::ofstream seem to make the open file handle inheritable; but apr_file_open() documentation says: "By default, the returned file descriptor will not be inherited by child processes created by apr_proc_create()." And indeed, it does appear to sidestep the locking problem. --- indra/llcommon/llprocess.cpp | 26 ++--- .../updater/llupdatedownloader.cpp | 114 +++++++++++---------- 2 files changed, 68 insertions(+), 72 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index e96b328365..760be6da9b 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -282,9 +282,13 @@ public: virtual std::string read(size_type len) { - // Read specified number of bytes into a buffer. Make a buffer big - // enough. + // Read specified number of bytes into a buffer. size_type readlen((std::min)(size(), len)); + // Formally, &buffer[0] is invalid for a vector of size() 0. Exit + // early in that situation. + if (! readlen) + return ""; + // Make a buffer big enough. std::vector buffer(readlen); mStream.read(&buffer[0], readlen); // Since we've already clamped 'readlen', we can think of no reason @@ -535,24 +539,6 @@ LLProcess::LLProcess(const LLSDOrParams& params): apr_procattr_t *procattr = NULL; chkapr(apr_procattr_create(&procattr, gAPRPoolp)); -#if ! defined(APR_HAS_PROCATTR_INHERIT_SET) - // Our special preprocessor symbol isn't even defined -- wrong APR - LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_inherit_set() extension" << LL_ENDL; -#elif ! APR_HAS_PROCATTR_INHERIT_SET - // Symbol is defined, but to 0: expect apr_procattr_inherit_set() to - // return APR_ENOTIMPL. - LL_DEBUGS("LLProcess") << "apr_procattr_inherit_set() not supported on this platform" << LL_ENDL; -#else // APR_HAS_PROCATTR_INHERIT_SET nonzero - // As of 2012-04-17, the original Windows implementation of - // apr_proc_create() unconditionally passes TRUE for bInheritHandles. That - // seems to assume that all files are opened by APR so you can - // individually control whether each is inherited by a child process. But - // we've been burned by having surprising open file handles inherited by - // our child processes. Turn that OFF for us! - LL_DEBUGS("LLProcess") << "Setting apr_procattr_inherit_set(0)" << LL_ENDL; - ll_apr_warn_status(apr_procattr_inherit_set(procattr, 0)); -#endif - // For which of stdin, stdout, stderr should we create a pipe to the // child? In the viewer, there are only a couple viable // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp index 19ac418e9e..5d48c35b4c 100644 --- a/indra/viewer_components/updater/llupdatedownloader.cpp +++ b/indra/viewer_components/updater/llupdatedownloader.cpp @@ -1,24 +1,24 @@ -/** +/** * @file llupdatedownloader.cpp * * $LicenseInfo:firstyear=2010&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$ */ @@ -40,6 +40,7 @@ #include "llthread.h" #include "llupdaterservice.h" #include "llcurl.h" +#include "llapr.h" class LLUpdateDownloader::Implementation: public LLThread @@ -58,18 +59,18 @@ public: int onProgress(double downloadSize, double bytesDownloaded); void resume(void); void setBandwidthLimit(U64 bytesPerSecond); - + private: curl_off_t mBandwidthLimit; bool mCancelled; LLUpdateDownloader::Client & mClient; CURL * mCurl; LLSD mDownloadData; - llofstream mDownloadStream; + apr_file_t* mDownloadStream; unsigned char mDownloadPercent; std::string mDownloadRecordPath; curl_slist * mHeaderList; - + void initializeCurlGet(std::string const & url, bool processHeader); void resumeDownloading(size_t startByte); void run(void); @@ -93,7 +94,7 @@ namespace { } }; - + const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml"; }; @@ -188,22 +189,23 @@ LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client & mCancelled(false), mClient(client), mCurl(0), + mDownloadStream(NULL), mDownloadPercent(0), mHeaderList(0) { CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case. - llverify(code == CURLE_OK); // TODO: real error handling here. + llverify(code == CURLE_OK); // TODO: real error handling here. } LLUpdateDownloader::Implementation::~Implementation() { - if(isDownloading()) + if(isDownloading()) { cancel(); shutdown(); - } - else + } + else { ; // No op. } @@ -218,7 +220,7 @@ void LLUpdateDownloader::Implementation::cancel(void) { mCancelled = true; } - + void LLUpdateDownloader::Implementation::download(LLURI const & uri, std::string const & hash, @@ -259,24 +261,24 @@ void LLUpdateDownloader::Implementation::resume(void) mClient.downloadError("no download marker"); return; } - + LLSDSerialize::fromXMLDocument(mDownloadData, dataStream); - + if(!mDownloadData.asBoolean()) { mClient.downloadError("no download information in marker"); return; } - + std::string filePath = mDownloadData["path"].asString(); try { - if(LLFile::isfile(filePath)) { + if(LLFile::isfile(filePath)) { llstat fileStatus; LLFile::stat(filePath, &fileStatus); if(fileStatus.st_size != mDownloadData["size"].asInteger()) { resumeDownloading(fileStatus.st_size); } else if(!validateDownload()) { LLFile::remove(filePath); - download(LLURI(mDownloadData["url"].asString()), + download(LLURI(mDownloadData["url"].asString()), mDownloadData["hash"].asString(), mDownloadData["update_version"].asString(), mDownloadData["required"].asBoolean()); @@ -284,7 +286,7 @@ void LLUpdateDownloader::Implementation::resume(void) mClient.downloadComplete(mDownloadData); } } else { - download(LLURI(mDownloadData["url"].asString()), + download(LLURI(mDownloadData["url"].asString()), mDownloadData["hash"].asString(), mDownloadData["update_version"].asString(), mDownloadData["required"].asBoolean()); @@ -301,7 +303,7 @@ void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond) llassert(mCurl != 0); mBandwidthLimit = bytesPerSecond; CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit); - if(code != CURLE_OK) LL_WARNS("UpdateDownload") << + if(code != CURLE_OK) LL_WARNS("UpdateDownload") << "unable to change dowload bandwidth" << LL_ENDL; } else { mBandwidthLimit = bytesPerSecond; @@ -315,7 +317,7 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size) std::string header(headerPtr, headerPtr + size); size_t colonPosition = header.find(':'); if(colonPosition == std::string::npos) return size; // HTML response; ignore. - + if(header.substr(0, colonPosition) == "Content-Length") { try { size_t firstDigitPos = header.find_first_of("0123456789", colonPosition); @@ -323,18 +325,18 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size) std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1); size_t size = boost::lexical_cast(contentLength); LL_INFOS("UpdateDownload") << "download size is " << size << LL_ENDL; - + mDownloadData["size"] = LLSD(LLSD::Integer(size)); llofstream odataStream(mDownloadRecordPath); LLSDSerialize::toPrettyXML(mDownloadData, odataStream); } catch (std::exception const & e) { - LL_WARNS("UpdateDownload") << "unable to read content length (" + LL_WARNS("UpdateDownload") << "unable to read content length (" << e.what() << ")" << LL_ENDL; } } else { ; // No op. } - + return size; } @@ -342,14 +344,11 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size) size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size) { if(mCancelled) return 0; // Forces a write error which will halt curl thread. - if((size == 0) || (buffer == 0)) return 0; - - mDownloadStream.write(reinterpret_cast(buffer), size); - if(mDownloadStream.bad()) { - return 0; - } else { - return size; - } + if((size == 0) || (buffer == 0)) return 0; + + apr_size_t written(size); + ll_apr_warn_status(apr_file_write(mDownloadStream, buffer, &written)); + return written; } @@ -358,7 +357,7 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b int downloadPercent = static_cast(100. * (bytesDownloaded / downloadSize)); if(downloadPercent > mDownloadPercent) { mDownloadPercent = downloadPercent; - + LLSD event; event["pump"] = LLUpdaterService::pumpName(); LLSD payload; @@ -367,12 +366,12 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b payload["bytes_downloaded"] = bytesDownloaded; event["payload"] = payload; LLEventPumps::instance().obtain("mainlooprepeater").post(event); - + LL_INFOS("UpdateDownload") << "progress event " << payload << LL_ENDL; } else { ; // Keep events to a reasonalbe number. } - + return 0; } @@ -380,7 +379,8 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b void LLUpdateDownloader::Implementation::run(void) { CURLcode code = curl_easy_perform(mCurl); - mDownloadStream.close(); + ll_apr_warn_status(apr_file_close(mDownloadStream)); + mDownloadStream = NULL; if(code == CURLE_OK) { LLFile::remove(mDownloadRecordPath); if(validateDownload()) { @@ -396,13 +396,13 @@ void LLUpdateDownloader::Implementation::run(void) LL_INFOS("UpdateDownload") << "download canceled by user" << LL_ENDL; // Do not call back client. } else { - LL_WARNS("UpdateDownload") << "download failed with error '" << + LL_WARNS("UpdateDownload") << "download failed with error '" << curl_easy_strerror(code) << "'" << LL_ENDL; LLFile::remove(mDownloadRecordPath); if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString()); mClient.downloadError("curl error"); } - + if(mHeaderList) { curl_slist_free_all(mHeaderList); mHeaderList = 0; @@ -412,17 +412,17 @@ void LLUpdateDownloader::Implementation::run(void) void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader) { - if(mCurl == 0) + if(mCurl == 0) { mCurl = LLCurl::newEasyHandle(); - } - else + } + else { curl_easy_reset(mCurl); } - + if(mCurl == 0) throw DownloadError("failed to initialize curl"); - + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true)); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true)); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function)); @@ -439,7 +439,7 @@ void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & u // if it's a required update set the bandwidth limit to 0 (unlimited) curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit; throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, limit)); - + mDownloadPercent = 0; } @@ -450,7 +450,7 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte) << " at byte " << startByte << LL_ENDL; initializeCurlGet(mDownloadData["url"].asString(), false); - + // The header 'Range: bytes n-' will request the bytes remaining in the // source begining with byte n and ending with the last byte. boost::format rangeHeaderFormat("Range: bytes=%u-"); @@ -458,9 +458,10 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte) mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str()); if(mHeaderList == 0) throw DownloadError("cannot add Range header"); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList)); - - mDownloadStream.open(mDownloadData["path"].asString(), - std::ios_base::out | std::ios_base::binary | std::ios_base::app); + + ll_apr_warn_status(apr_file_open(&mDownloadStream, mDownloadData["path"].asString().c_str(), + APR_WRITE | APR_APPEND | APR_BINARY | APR_BUFFERED, + APR_OS_DEFAULT, gAPRPoolp)); start(); } @@ -479,11 +480,20 @@ void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std LL_INFOS("UpdateDownload") << "downloading " << filePath << " from " << uri.asString() << LL_ENDL; LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL; - + llofstream dataStream(mDownloadRecordPath); LLSDSerialize::toPrettyXML(mDownloadData, dataStream); - - mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary); + + ll_apr_warn_status(apr_file_open(&mDownloadStream, filePath.c_str(), + APR_WRITE | APR_TRUNCATE | APR_BINARY | APR_BUFFERED, + APR_OS_DEFAULT, gAPRPoolp)); + // IQA-463: Do NOT let this open file be inherited by child processes. + // That's why we switched from llofstream to apr_file_t. From + // apr_file_open() doc + // http://apr.apache.org/docs/apr/1.4/group__apr__file__io.html#gabda14cbf242fb4fe99055434213e5446 : + // "By default, the returned file descriptor will not be inherited by + // child processes created by apr_proc_create(). This can be changed using + // apr_file_inherit_set()." initializeCurlGet(uri.asString(), true); start(); } -- cgit v1.2.3 From 7079513c23f4bbef81d57d9615dd1e45ebfbe5b9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Apr 2012 14:49:32 -0400 Subject: IQA-463: Add apr_file_open(APR_CREATE) flag for downloaded installer. This handles the case when the target file doesn't exist, just as APR_TRUNCATE handles the case when it does. Strengthen error checks concerning downloaded installer file from ll_apr_warn_status() to ll_apr_assert_status(). Failing to recognize (e.g.) failure to open that file only leads to mysterious crashes down the road; this removes the mystery. --- indra/viewer_components/updater/llupdatedownloader.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp index 5d48c35b4c..abe45cc52b 100644 --- a/indra/viewer_components/updater/llupdatedownloader.cpp +++ b/indra/viewer_components/updater/llupdatedownloader.cpp @@ -347,7 +347,7 @@ size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size) if((size == 0) || (buffer == 0)) return 0; apr_size_t written(size); - ll_apr_warn_status(apr_file_write(mDownloadStream, buffer, &written)); + ll_apr_assert_status(apr_file_write(mDownloadStream, buffer, &written)); return written; } @@ -379,7 +379,7 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b void LLUpdateDownloader::Implementation::run(void) { CURLcode code = curl_easy_perform(mCurl); - ll_apr_warn_status(apr_file_close(mDownloadStream)); + ll_apr_assert_status(apr_file_close(mDownloadStream)); mDownloadStream = NULL; if(code == CURLE_OK) { LLFile::remove(mDownloadRecordPath); @@ -459,9 +459,9 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte) if(mHeaderList == 0) throw DownloadError("cannot add Range header"); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList)); - ll_apr_warn_status(apr_file_open(&mDownloadStream, mDownloadData["path"].asString().c_str(), - APR_WRITE | APR_APPEND | APR_BINARY | APR_BUFFERED, - APR_OS_DEFAULT, gAPRPoolp)); + ll_apr_assert_status(apr_file_open(&mDownloadStream, mDownloadData["path"].asString().c_str(), + APR_WRITE | APR_APPEND | APR_BINARY | APR_BUFFERED, + APR_OS_DEFAULT, gAPRPoolp)); start(); } @@ -484,9 +484,9 @@ void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std llofstream dataStream(mDownloadRecordPath); LLSDSerialize::toPrettyXML(mDownloadData, dataStream); - ll_apr_warn_status(apr_file_open(&mDownloadStream, filePath.c_str(), - APR_WRITE | APR_TRUNCATE | APR_BINARY | APR_BUFFERED, - APR_OS_DEFAULT, gAPRPoolp)); + ll_apr_assert_status(apr_file_open(&mDownloadStream, filePath.c_str(), + APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY | APR_BUFFERED, + APR_OS_DEFAULT, gAPRPoolp)); // IQA-463: Do NOT let this open file be inherited by child processes. // That's why we switched from llofstream to apr_file_t. From // apr_file_open() doc -- cgit v1.2.3 From 8c44511d2c31c2265fb67b882dbb699adcf547eb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Apr 2012 14:50:32 -0400 Subject: IQA-463: Use newest APR package without apr_procattr_inherit_set(). --- autobuild.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 4288b60c30..03e95ee9ab 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - 1ac038723ed702a5ad0db924dfe0fe1b + 8f186209cae5c7bf86d6af6a7965bea3 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254105/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120418.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254439/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120423.tar.bz2 name darwin @@ -102,9 +102,9 @@ archive hash - c60ace3d6c593d95247bd58496d7c557 + f8743e0ccc4f651d5cfcf690e789cbe8 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254105/arch/Linux/installer/apr_suite-1.4.5-linux-20120418.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254439/arch/Linux/installer/apr_suite-1.4.5-linux-20120423.tar.bz2 name linux @@ -114,9 +114,9 @@ archive hash - d353e31abcefb554379abdec0ac41ff3 + 9799e268830010c519174da9a669dabe url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254105/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120418.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254439/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120423.tar.bz2 name windows -- cgit v1.2.3 From ca1ecb921cae886369d8f71d8493c76e5a4f2c8a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 3 May 2012 18:07:36 -0400 Subject: IQA-490: Dummy out calls to LLTrans::getString() in linux_updater.cpp. It appears that the LLTrans machinery, or at least the way it's used in this program, is buggy: linux-updater.bin has been crashing. Tracebacks and experimentation identify LLTrans as the culprit, so replace it with baked-in string constants copied from strings.xml. (linux-updater.bin was already producing English-only messages because the update_install shell script that calls it was specifically passing the English version of strings.xml.) --- indra/linux_updater/linux_updater.cpp | 149 ++++++++++++++++++++++++++-------- 1 file changed, 116 insertions(+), 33 deletions(-) diff --git a/indra/linux_updater/linux_updater.cpp b/indra/linux_updater/linux_updater.cpp index eed00ac06e..a615e3f07b 100644 --- a/indra/linux_updater/linux_updater.cpp +++ b/indra/linux_updater/linux_updater.cpp @@ -1,4 +1,4 @@ -/** +/** * @file linux_updater.cpp * @author Kyle Ambroff , Tofu Linden * @brief Viewer update program for unix platforms that support GTK+ @@ -6,21 +6,21 @@ * $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$ */ @@ -34,10 +34,30 @@ #include "llfile.h" #include "lldir.h" #include "lldiriterator.h" + +/*==========================================================================*| +// IQA-490: Use of LLTrans -- by this program at least -- appears to be buggy. +// With it, the 3.3.2 beta 1 linux-updater.bin crashes; without it seems stable. #include "llxmlnode.h" #include "lltrans.h" +|*==========================================================================*/ + +static class LLTrans +{ +public: + LLTrans(); + static std::string getString(const std::string& key); + +private: + std::string _getString(const std::string& key) const; + + typedef std::map MessageMap; + MessageMap mMessages; +} sLLTransInstance; #include +#include +#include extern "C" { #include @@ -85,6 +105,8 @@ void init_default_trans_args() bool translate_init(std::string comma_delim_path_list, std::string base_xml_name) { + return true; +/*==========================================================================*| init_default_trans_args(); // extract paths string vector from comma-delimited flat string @@ -112,6 +134,7 @@ bool translate_init(std::string comma_delim_path_list, LLTrans::parseStrings(root, default_trans_args); return true; } +|*==========================================================================*/ } @@ -151,7 +174,7 @@ void updater_app_ui_init(UpdaterAppState *app_state) GTK_WIN_POS_CENTER_ALWAYS); gtk_container_set_border_width(GTK_CONTAINER(app_state->window), 12); - g_signal_connect(G_OBJECT(app_state->window), "delete-event", + g_signal_connect(G_OBJECT(app_state->window), "delete-event", G_CALLBACK(on_window_closed), app_state); vbox = gtk_vbox_new(FALSE, 6); @@ -165,7 +188,7 @@ void updater_app_ui_init(UpdaterAppState *app_state) summary_label = gtk_label_new(NULL); gtk_label_set_use_markup(GTK_LABEL(summary_label), TRUE); - gtk_label_set_markup(GTK_LABEL(summary_label), + gtk_label_set_markup(GTK_LABEL(summary_label), label_ostr.str().c_str()); gtk_misc_set_alignment(GTK_MISC(summary_label), 0, 0.5); gtk_box_pack_start(GTK_BOX(vbox), summary_label, FALSE, FALSE, 0); @@ -195,9 +218,9 @@ void updater_app_ui_init(UpdaterAppState *app_state) // set up progress bar, and update it roughly every 1/10 of a second app_state->progress_bar = gtk_progress_bar_new(); - gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar), + gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar), LLTrans::getString("UpdaterProgressBarTextWithEllipses").c_str()); - gtk_box_pack_start(GTK_BOX(vbox), + gtk_box_pack_start(GTK_BOX(vbox), app_state->progress_bar, FALSE, TRUE, 0); app_state->progress_update_timeout_id = g_timeout_add (UPDATE_PROGRESS_TIMEOUT, progress_update_timeout, app_state); @@ -299,7 +322,7 @@ gpointer worker_thread_cb(gpointer data) g_error_free(error); throw 0; } - + if(tmp_local_filename != NULL) { app_state->file = tmp_local_filename; @@ -342,7 +365,7 @@ gpointer worker_thread_cb(gpointer data) curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE); curl_easy_setopt(curl, CURLOPT_WRITEDATA, package_file); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE); - curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, &download_progress_cb); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, app_state); @@ -352,8 +375,8 @@ gpointer worker_thread_cb(gpointer data) if (result) { - llerrs << "Failed to download update: " - << app_state->url + llerrs << "Failed to download update: " + << app_state->url << llendl; gdk_threads_enter(); @@ -365,7 +388,7 @@ gpointer worker_thread_cb(gpointer data) throw 0; } } - + // now pulse the progres bar back and forth while the package is // being unpacked gdk_threads_enter(); @@ -386,8 +409,8 @@ gpointer worker_thread_cb(gpointer data) gdk_threads_enter(); display_error(app_state->window, - LLTrans::getString("UpdaterFailInstallTitle"), - LLTrans::getString("UpdaterFailUpdateDescriptive")); + LLTrans::getString("UpdaterFailInstallTitle"), + LLTrans::getString("UpdaterFailUpdateDescriptive")); //"Failed to update " + app_state->app_name, gdk_threads_leave(); throw 0; @@ -402,8 +425,8 @@ gpointer worker_thread_cb(gpointer data) gdk_threads_enter(); display_error(app_state->window, - LLTrans::getString("UpdaterFailStartTitle"), - LLTrans::getString("UpdaterFailUpdateDescriptive")); + LLTrans::getString("UpdaterFailStartTitle"), + LLTrans::getString("UpdaterFailUpdateDescriptive")); gdk_threads_leave(); throw 0; } @@ -448,7 +471,7 @@ gboolean less_anal_gspawnsync(gchar **argv, // restore SIGCHLD handler sigaction(SIGCHLD, &sigchld_backup, NULL); - + return rtn; } @@ -477,7 +500,7 @@ rename_with_sudo_fallback(const std::string& filename, const std::string& newnam { char *src_string_copy = g_strdup(filename.c_str()); char *dst_string_copy = g_strdup(newname.c_str()); - char* argv[] = + char* argv[] = { sudo_cmd, mv_cmd, @@ -492,8 +515,8 @@ rename_with_sudo_fallback(const std::string& filename, const std::string& newnam if (!less_anal_gspawnsync(argv, &stderr_output, &child_exit_status, &spawn_error)) { - llwarns << "Failed to spawn child process: " - << spawn_error->message + llwarns << "Failed to spawn child process: " + << spawn_error->message << llendl; } else if (child_exit_status) @@ -506,7 +529,7 @@ rename_with_sudo_fallback(const std::string& filename, const std::string& newnam { // everything looks good, clear the error code rtncode = 0; - } + } g_free(src_string_copy); g_free(dst_string_copy); @@ -531,7 +554,7 @@ gboolean install_package(std::string package_file, std::string destination) } llinfos << "Found tar command: " << tar_cmd << llendl; - // Unpack the tarball in a temporary place first, then move it to + // Unpack the tarball in a temporary place first, then move it to // its final destination std::string tmp_dest_dir = gDirUtilp->getTempFilename(); if (LLFile::mkdir(tmp_dest_dir, 0744)) @@ -571,8 +594,8 @@ gboolean install_package(std::string package_file, std::string destination) if (!less_anal_gspawnsync(argv, &stderr_output, &child_exit_status, &untar_error)) { - llwarns << "Failed to spawn child process: " - << untar_error->message + llwarns << "Failed to spawn child process: " + << untar_error->message << llendl; return FALSE; } @@ -605,8 +628,8 @@ gboolean install_package(std::string package_file, std::string destination) if (rename_with_sudo_fallback(destination, backup_dir)) { - llwarns << "Failed to move directory: '" - << destination << "' -> '" << backup_dir + llwarns << "Failed to move directory: '" + << destination << "' -> '" << backup_dir << llendl; return FALSE; } @@ -617,7 +640,7 @@ gboolean install_package(std::string package_file, std::string destination) if (rename_with_sudo_fallback(tmp_dest_dir, destination)) { llwarns << "Failed to move installation to the destination: " - << destination + << destination << llendl; return FALSE; } @@ -713,7 +736,7 @@ BOOL spawn_viewer(UpdaterAppState *app_state) if (!success) { - llwarns << "Failed to launch viewer: " << error->message + llwarns << "Failed to launch viewer: " << error->message << llendl; } @@ -772,8 +795,8 @@ void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state) } } - if (app_state->app_name.empty() - || (app_state->url.empty() && app_state->file.empty()) + if (app_state->app_name.empty() + || (app_state->url.empty() && app_state->file.empty()) || app_state->dest_dir.empty()) { show_usage_and_exit(); @@ -799,7 +822,7 @@ int main(int argc, char **argv) (gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); std::string old_log_file = gDirUtilp->getExpandedFilename (LL_PATH_LOGS, "updater.log.old"); - std::string log_file = + std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log"); LLFile::rename(log_file, old_log_file); LLError::logToFile(log_file); @@ -841,3 +864,63 @@ int main(int argc, char **argv) return success ? 0 : 1; } +/***************************************************************************** +* Dummy LLTrans implementation (IQA-490) +*****************************************************************************/ +static LLTrans sStaticStrings; + +// lookup +std::string LLTrans::_getString(const std::string& key) const +{ + MessageMap::const_iterator found = mMessages.find(key); + if (found != mMessages.end()) + { + return found->second; + } + LL_WARNS("linux_updater") << "No message for key '" << key + << "' -- add to LLTrans::LLTrans() in linux_updater.cpp" + << LL_ENDL; + return key; +} + +// static lookup +std::string LLTrans::getString(const std::string& key) +{ + return sLLTransInstance._getString(key); +} + +// initialization +LLTrans::LLTrans() +{ + typedef std::pair Pair; + static const Pair data[] = + { + Pair("UpdaterFailDownloadTitle", + "Failed to download update"), + Pair("UpdaterFailInstallTitle", + "Failed to install update"), + Pair("UpdaterFailStartTitle", + "Failed to start viewer"), + Pair("UpdaterFailUpdateDescriptive", + "An error occurred while updating Second Life. " + "Please download the latest version from www.secondlife.com."), + Pair("UpdaterNowInstalling", + "Installing Second Life..."), + Pair("UpdaterNowUpdating", + "Now updating Second Life..."), + Pair("UpdaterProgressBarText", + "Downloading update"), + Pair("UpdaterProgressBarTextWithEllipses", + "Downloading update..."), + Pair("UpdaterUpdatingDescriptive", + "Your Second Life Viewer is being updated to the latest release. " + "This may take some time, so please be patient."), + Pair("UpdaterWindowTitle", + "Second Life Update") + }; + + BOOST_FOREACH(Pair pair, data) + { + mMessages[pair.first] = pair.second; + } +} -- cgit v1.2.3 From bdd17086726b0c47df18935e6833a10b706e3ee5 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Fri, 4 May 2012 16:40:55 -0400 Subject: increment viewer version to 3.3.3 --- indra/llcommon/llversionviewer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llversionviewer.h b/indra/llcommon/llversionviewer.h index fafc750690..bfb30f900f 100644 --- a/indra/llcommon/llversionviewer.h +++ b/indra/llcommon/llversionviewer.h @@ -29,7 +29,7 @@ const S32 LL_VERSION_MAJOR = 3; const S32 LL_VERSION_MINOR = 3; -const S32 LL_VERSION_PATCH = 2; +const S32 LL_VERSION_PATCH = 3; const S32 LL_VERSION_BUILD = 0; const char * const LL_CHANNEL = "Second Life Developer"; -- cgit v1.2.3 From 5c11b6b2e59ea8042d8dac6071b8be4c81293889 Mon Sep 17 00:00:00 2001 From: Merov Linden Date: Tue, 8 May 2012 10:16:14 -0700 Subject: SH-3047 : Tweak of the performance compression data gathering code --- indra/llimage/llimagej2c.cpp | 6 +++++- indra/llimage/llimagej2c.h | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 4435e0d2a4..452aad25cb 100755 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -464,6 +464,7 @@ LLImageCompressionTester::LLImageCompressionTester() : LLMetricPerformanceTester addMetric("Perf Compression (kB/s)"); mRunBytesInDecompression = 0; + mRunBytesOutDecompression = 0; mRunBytesInCompression = 0; mTotalBytesInDecompression = 0; @@ -556,13 +557,16 @@ void LLImageCompressionTester::updateDecompressionStats(const S32 bytesIn, const mTotalBytesInDecompression += bytesIn; mRunBytesInDecompression += bytesIn; mTotalBytesOutDecompression += bytesOut; + mRunBytesOutDecompression += bytesOut; //if (mRunBytesInDecompression > (1000000)) - if ((mTotalTimeDecompression - mRunTimeDecompression) >= (5.0f)) + if (mRunBytesOutDecompression > (10000000)) + //if ((mTotalTimeDecompression - mRunTimeDecompression) >= (5.0f)) { // Output everything outputTestResults(); // Reset the decompression data of the run mRunBytesInDecompression = 0; + mRunBytesOutDecompression = 0; mRunTimeDecompression = mTotalTimeDecompression; } } diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h index 42093e0e64..ce8195940d 100644 --- a/indra/llimage/llimagej2c.h +++ b/indra/llimage/llimagej2c.h @@ -156,7 +156,8 @@ class LLImageCompressionTester : public LLMetricPerformanceTesterBasic U32 mTotalBytesOutDecompression; // Total bytes produced by decompressor U32 mTotalBytesInCompression; // Total bytes fed to compressor U32 mTotalBytesOutCompression; // Total bytes produced by compressor - U32 mRunBytesInDecompression; // Bytes fed to decompressor in this run + U32 mRunBytesInDecompression; // Bytes fed to decompressor in this run + U32 mRunBytesOutDecompression; // Bytes produced by the decompressor in this run U32 mRunBytesInCompression; // Bytes fed to compressor in this run // // Time -- cgit v1.2.3 From 005ce31f41fc60b7b9c7890d354943b3e3568ac9 Mon Sep 17 00:00:00 2001 From: Merov Linden Date: Tue, 8 May 2012 21:42:53 -0700 Subject: SH-3047 : Fix lossless compression for small textures. --- indra/llkdu/llimagej2ckdu.cpp | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index cbfc34ebb8..53d2a2f3c5 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -619,36 +619,27 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co // This code is where we specify the target number of bytes for each quality layer. // We're using a logarithmic spacing rule that fits with our way of fetching texture data. // Note: For more info on this layers business, read kdu_codestream::flush() doc in kdu_compressed.h - U32 i = FIRST_PACKET_SIZE; + layer_bytes[nb_layers++] = FIRST_PACKET_SIZE; + U32 i = MIN_LAYER_SIZE; while ((i < max_bytes) && (nb_layers < (MAX_NB_LAYERS-1))) { - if (i == FIRST_PACKET_SIZE * 4) - { - // That really just means that the first layer is FIRST_PACKET_SIZE and the second is MIN_LAYER_SIZE - i = MIN_LAYER_SIZE; - } - layer_bytes[nb_layers] = i; - nb_layers++; + layer_bytes[nb_layers++] = i; i *= 4; } + if (layer_bytes[nb_layers-1] < max_bytes) + { + // Set the last quality layer if necessary so to fit the preset compression ratio + // Use 0 for that last layer for reversible images so all remaining code blocks will be flushed + layer_bytes[nb_layers++] = (reversible ? 0 : max_bytes); + } if (reversible) { codestream.access_siz()->parse_string("Creversible=yes"); - // *TODO: we should use yuv in reversible mode and one res level since those images are small. - // Don't turn this on now though as both create problems on decoding for the moment - //codestream.access_siz()->parse_string("Clevels=1"); + // *TODO: we should use yuv in reversible mode + // Don't turn this on now though as it creates problems on decoding for the moment //codestream.access_siz()->parse_string("Cycc=no"); - // In the reversible case, set the last entry of that table to 0 so that all generated bits will - // indeed be output by the time the last quality layer is encountered. - layer_bytes[nb_layers] = 0; - } - else - { - // Truncate the last quality layer if necessary so to fit the set compression ratio - layer_bytes[nb_layers] = max_bytes; } - nb_layers++; std::string layer_string = llformat("Clayers=%d",nb_layers); codestream.access_siz()->parse_string(layer_string.c_str()); -- cgit v1.2.3 From d29f920c22ca67b13f42680c432b689b80909f42 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 9 May 2012 19:55:26 -0400 Subject: CHOP-900: Use new apr_procattr_constrain_handle_set() extension. Now LLProcess explicitly requests APR to limit the handles passed to any child process, instead of wantonly passing whatever happens to be lying around the parent process at the time. This requires the latest APR build. Also revert LLUpdateDownloader::Implementation::mDownloadStream to llofstream (as in rev 1878a57aebd7) instead of apr_file_t*. Using APR for that file was a Band-Aid -- a single whacked mole -- for the problem more systemically addressed by apr_procattr_constrain_handle_set(). --- autobuild.xml | 12 ++++---- indra/llcommon/llprocess.cpp | 17 ++++++++++++ .../updater/llupdatedownloader.cpp | 32 ++++++++-------------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 03e95ee9ab..a2d93a6c3e 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - 8f186209cae5c7bf86d6af6a7965bea3 + 847f1b55c0549b7abe6628f1ea242b48 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254439/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120423.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/256201/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120509.tar.bz2 name darwin @@ -102,9 +102,9 @@ archive hash - f8743e0ccc4f651d5cfcf690e789cbe8 + 998ff5f7a5a9be8c0717e1a0eab05e0c url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254439/arch/Linux/installer/apr_suite-1.4.5-linux-20120423.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/256201/arch/Linux/installer/apr_suite-1.4.5-linux-20120509.tar.bz2 name linux @@ -114,9 +114,9 @@ archive hash - 9799e268830010c519174da9a669dabe + 44bc19a2491e8bd3ac4424c0b641f069 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/254439/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120423.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/256201/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120509.tar.bz2 name windows diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 760be6da9b..9667e4e033 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -539,6 +539,23 @@ LLProcess::LLProcess(const LLSDOrParams& params): apr_procattr_t *procattr = NULL; chkapr(apr_procattr_create(&procattr, gAPRPoolp)); + // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to + // constrain the set of handles passed to the child process. Before we + // changed to APR, the Windows implementation of LLProcessLauncher called + // CreateProcess(bInheritHandles=FALSE), meaning to pass NO open handles + // to the child process. Now that we support pipes, though, we must allow + // apr_proc_create() to pass bInheritHandles=TRUE. But without taking + // special pains, that causes trouble in a number of ways, due to the fact + // that the viewer is constantly opening and closing files -- most of + // which CreateProcess() passes to every child process! +#if ! defined(APR_HAS_PROCATTR_CONSTRAIN_HANDLE_SET) + // Our special preprocessor symbol isn't even defined -- wrong APR + LL_WARNS("LLProcess") << "This version of APR lacks Linden " + << "apr_procattr_constrain_handle_set() extension" << LL_ENDL; +#else + chkapr(apr_procattr_constrain_handle_set(procattr, 1)); +#endif + // For which of stdin, stdout, stderr should we create a pipe to the // child? In the viewer, there are only a couple viable // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp index abe45cc52b..75e455e3f6 100644 --- a/indra/viewer_components/updater/llupdatedownloader.cpp +++ b/indra/viewer_components/updater/llupdatedownloader.cpp @@ -40,7 +40,6 @@ #include "llthread.h" #include "llupdaterservice.h" #include "llcurl.h" -#include "llapr.h" class LLUpdateDownloader::Implementation: public LLThread @@ -66,7 +65,7 @@ private: LLUpdateDownloader::Client & mClient; CURL * mCurl; LLSD mDownloadData; - apr_file_t* mDownloadStream; + llofstream mDownloadStream; unsigned char mDownloadPercent; std::string mDownloadRecordPath; curl_slist * mHeaderList; @@ -189,7 +188,6 @@ LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client & mCancelled(false), mClient(client), mCurl(0), - mDownloadStream(NULL), mDownloadPercent(0), mHeaderList(0) { @@ -346,9 +344,12 @@ size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size) if(mCancelled) return 0; // Forces a write error which will halt curl thread. if((size == 0) || (buffer == 0)) return 0; - apr_size_t written(size); - ll_apr_assert_status(apr_file_write(mDownloadStream, buffer, &written)); - return written; + mDownloadStream.write(static_cast(buffer), size); + if(mDownloadStream.bad()) { + return 0; + } else { + return size; + } } @@ -379,8 +380,7 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b void LLUpdateDownloader::Implementation::run(void) { CURLcode code = curl_easy_perform(mCurl); - ll_apr_assert_status(apr_file_close(mDownloadStream)); - mDownloadStream = NULL; + mDownloadStream.close(); if(code == CURLE_OK) { LLFile::remove(mDownloadRecordPath); if(validateDownload()) { @@ -459,9 +459,8 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte) if(mHeaderList == 0) throw DownloadError("cannot add Range header"); throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList)); - ll_apr_assert_status(apr_file_open(&mDownloadStream, mDownloadData["path"].asString().c_str(), - APR_WRITE | APR_APPEND | APR_BINARY | APR_BUFFERED, - APR_OS_DEFAULT, gAPRPoolp)); + mDownloadStream.open(mDownloadData["path"].asString(), + std::ios_base::out | std::ios_base::binary | std::ios_base::app); start(); } @@ -484,16 +483,7 @@ void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std llofstream dataStream(mDownloadRecordPath); LLSDSerialize::toPrettyXML(mDownloadData, dataStream); - ll_apr_assert_status(apr_file_open(&mDownloadStream, filePath.c_str(), - APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY | APR_BUFFERED, - APR_OS_DEFAULT, gAPRPoolp)); - // IQA-463: Do NOT let this open file be inherited by child processes. - // That's why we switched from llofstream to apr_file_t. From - // apr_file_open() doc - // http://apr.apache.org/docs/apr/1.4/group__apr__file__io.html#gabda14cbf242fb4fe99055434213e5446 : - // "By default, the returned file descriptor will not be inherited by - // child processes created by apr_proc_create(). This can be changed using - // apr_file_inherit_set()." + mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary); initializeCurlGet(uri.asString(), true); start(); } -- cgit v1.2.3 From 7fd22901774ff9dcdfbf4388550275e30200307f Mon Sep 17 00:00:00 2001 From: Merov Linden Date: Wed, 9 May 2012 17:04:02 -0700 Subject: SH-3075 : Fix reversible compression for very small textures. Also supress the forcing to reversible for small textures. --- indra/llkdu/llimagej2ckdu.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index 53d2a2f3c5..cf88de12b4 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -609,13 +609,6 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co llassert (base.mRate > 0.f); max_bytes = (U32)((F32)(max_bytes) * base.mRate); - // If the image is very small, code it in a lossless way. - // Note: it'll also have only 1 layer which is fine as there's no point reordering blocks in that case. - if (max_bytes < FIRST_PACKET_SIZE) - { - reversible = true; - } - // This code is where we specify the target number of bytes for each quality layer. // We're using a logarithmic spacing rule that fits with our way of fetching texture data. // Note: For more info on this layers business, read kdu_codestream::flush() doc in kdu_compressed.h @@ -626,27 +619,32 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co layer_bytes[nb_layers++] = i; i *= 4; } + // Note: for small images, we can have (max_bytes < FIRST_PACKET_SIZE), hence the test if (layer_bytes[nb_layers-1] < max_bytes) { - // Set the last quality layer if necessary so to fit the preset compression ratio - // Use 0 for that last layer for reversible images so all remaining code blocks will be flushed - layer_bytes[nb_layers++] = (reversible ? 0 : max_bytes); + // Set the last quality layer so to fit the preset compression ratio + layer_bytes[nb_layers++] = max_bytes; } if (reversible) { + // Use 0 for a last quality layer for reversible images so all remaining code blocks will be flushed + // Hack: KDU encoding for reversible images has a bug for small images that leads to j2c images that + // cannot be open or are very blurry. Avoiding that last layer prevents the problem to happen. + if ((base.getWidth() >= 32) || (base.getHeight() >= 32)) + { + layer_bytes[nb_layers++] = 0; + } codestream.access_siz()->parse_string("Creversible=yes"); // *TODO: we should use yuv in reversible mode // Don't turn this on now though as it creates problems on decoding for the moment //codestream.access_siz()->parse_string("Cycc=no"); } - + std::string layer_string = llformat("Clayers=%d",nb_layers); codestream.access_siz()->parse_string(layer_string.c_str()); // Set up data ordering, markers, etc... if precincts or blocks specified - // Note: This code is *not* used in the encoding made by the viewer. It is currently used only - // by llimage_libtest to create various j2c and test alternative compression schemes. if ((mBlocksSize != -1) || (mPrecinctsSize != -1)) { if (mPrecinctsSize != -1) -- cgit v1.2.3 From 95a147dea7a8c4f2a20a2623fda0f9af5ce973ad Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 9 May 2012 23:03:37 -0400 Subject: CHOP-900: Fix test.cpp merge errors merging up to viewer-release --- indra/test/test.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 48f20b2e79..9d24383bcc 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -66,6 +66,7 @@ #pragma warning (pop) #endif +#include #include #include #include @@ -147,17 +148,16 @@ class LLTestCallback : public tut::callback public: LLTestCallback(bool verbose_mode, std::ostream *stream, boost::shared_ptr replayer) : - mVerboseMode(verbose_mode), - mTotalTests(0), - mPassedTests(0), - mFailedTests(0), - mSkippedTests(0), - // By default, capture a shared_ptr to std::cout, with a no-op "deleter" - // so that destroying the shared_ptr makes no attempt to delete std::cout. - mStream(boost::shared_ptr(&std::cout, boost::lambda::_1)), - mReplayer(replayer) - if (stream) - { + mVerboseMode(verbose_mode), + mTotalTests(0), + mPassedTests(0), + mFailedTests(0), + mSkippedTests(0), + // By default, capture a shared_ptr to std::cout, with a no-op "deleter" + // so that destroying the shared_ptr makes no attempt to delete std::cout. + mStream(boost::shared_ptr(&std::cout, boost::lambda::_1)), + mReplayer(replayer) + { if (stream) { // We want a boost::iostreams::tee_device that will stream to two @@ -593,11 +593,11 @@ int main(int argc, char **argv) LLTestCallback* mycallback; if (getenv("TEAMCITY_PROJECT_NAME")) { - mycallback = new LLTCTestCallback(verbose_mode, output, replayer); + mycallback = new LLTCTestCallback(verbose_mode, output.get(), replayer); } else { - mycallback = new LLTestCallback(verbose_mode, output, replayer); + mycallback = new LLTestCallback(verbose_mode, output.get(), replayer); } tut::runner.get().set_callback(mycallback); -- cgit v1.2.3 From c4dfdcb338aaeeb4e3adc4e1aef4f5b190ef5c57 Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Mon, 14 May 2012 18:02:53 -0400 Subject: partial fix for SH-3132 - reduce max concurrent http requests to same level as in release --- indra/newview/lltexturefetch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index efb6ed6079..e2af497a7d 100755 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1161,7 +1161,7 @@ bool LLTextureFetchWorker::doWork(S32 param) //1, not openning too many file descriptors at the same time; //2, control the traffic of http so udp gets bandwidth. // - static const S32 MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE = 24 ; + static const S32 MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE = 8 ; if(mFetcher->getNumHTTPRequests() > MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE) { return false ; //wait. -- cgit v1.2.3 From 465e5b484213d95fc590457f1c5b3b42acd32607 Mon Sep 17 00:00:00 2001 From: eli Date: Wed, 16 May 2012 12:05:30 -0700 Subject: FIX VWR-23844 VWR-26542 VWR-28950 --- indra/newview/skins/default/xui/fr/floater_about_land.xml | 2 +- indra/newview/skins/default/xui/fr/strings.xml | 2 +- indra/newview/skins/default/xui/pt/menu_viewer.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/indra/newview/skins/default/xui/fr/floater_about_land.xml b/indra/newview/skins/default/xui/fr/floater_about_land.xml index 6f8885487a..49af1a87e1 100644 --- a/indra/newview/skins/default/xui/fr/floater_about_land.xml +++ b/indra/newview/skins/default/xui/fr/floater_about_land.xml @@ -439,7 +439,7 @@ musique : Son : - + Sons d'avatar : diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index 3eebed450f..8b410128da 100644 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -4509,7 +4509,7 @@ Essayez avec le chemin d'accès à l'éditeur entre guillemets doubles Backsp - Shift + Maj Ctrl diff --git a/indra/newview/skins/default/xui/pt/menu_viewer.xml b/indra/newview/skins/default/xui/pt/menu_viewer.xml index d7d5d59a33..fd973bf3c2 100644 --- a/indra/newview/skins/default/xui/pt/menu_viewer.xml +++ b/indra/newview/skins/default/xui/pt/menu_viewer.xml @@ -118,7 +118,7 @@ - + @@ -163,7 +163,7 @@ - + -- cgit v1.2.3 From 39b7671d050caf09e65a936d02104d9192b4891d Mon Sep 17 00:00:00 2001 From: "simon@Simon-PC.lindenlab.com" Date: Wed, 16 May 2012 17:09:07 -0700 Subject: MAINT-862 and ER-1781 : Sounds not playing the first time. Reviewed by Kelly. --- indra/llaudio/llaudiodecodemgr.cpp | 22 ++++++++++++++++------ indra/llaudio/llaudioengine.cpp | 22 ++++++++++++++-------- indra/llaudio/llaudioengine.h | 9 ++++++--- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index 7f747c2eca..6c97a64ed7 100644 --- a/indra/llaudio/llaudiodecodemgr.cpp +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -571,7 +571,8 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs) llwarns << mCurrentDecodep->getUUID() << " has invalid vorbis data, aborting decode" << llendl; mCurrentDecodep->flushBadFile(); LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID()); - adp->setHasValidData(FALSE); + adp->setHasValidData(false); + adp->setHasCompletedDecode(true); mCurrentDecodep = NULL; done = TRUE; } @@ -586,11 +587,16 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs) if (mCurrentDecodep->finishDecode()) { // We finished! - if (mCurrentDecodep->isValid() && mCurrentDecodep->isDone()) + LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID()); + if (!adp) { - LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID()); - adp->setHasDecodedData(TRUE); - adp->setHasValidData(TRUE); + llwarns << "Missing LLAudioData for decode of " << mCurrentDecodep->getUUID() << llendl; + } + else if (mCurrentDecodep->isValid() && mCurrentDecodep->isDone()) + { + adp->setHasCompletedDecode(true); + adp->setHasDecodedData(true); + adp->setHasValidData(true); // At this point, we could see if anyone needs this sound immediately, but // I'm not sure that there's a reason to - we need to poll all of the playing @@ -599,7 +605,8 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs) } else { - llinfos << "Vorbis decode failed!!!" << llendl; + adp->setHasCompletedDecode(true); + llinfos << "Vorbis decode failed for " << mCurrentDecodep->getUUID() << llendl; } mCurrentDecodep = NULL; } @@ -667,16 +674,19 @@ BOOL LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid) if (gAudiop->hasDecodedFile(uuid)) { // Already have a decoded version, don't need to decode it. + //llinfos << "addDecodeRequest for " << uuid << " has decoded file already" << llendl; return TRUE; } if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND)) { // Just put it on the decode queue. + //llinfos << "addDecodeRequest for " << uuid << " has local asset file already" << llendl; mImpl->mDecodeQueue.push(uuid); return TRUE; } + //llinfos << "addDecodeRequest for " << uuid << " no file available" << llendl; return FALSE; } diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp index 5fa28cb902..72c0091d17 100644 --- a/indra/llaudio/llaudioengine.cpp +++ b/indra/llaudio/llaudioengine.cpp @@ -1221,10 +1221,11 @@ void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::E // Need to mark data as bad to avoid constant rerequests. LLAudioData *adp = gAudiop->getAudioData(uuid); if (adp) - { + { // Make sure everything is cleared adp->setHasValidData(false); adp->setHasLocalData(false); adp->setHasDecodedData(false); + adp->setHasCompletedDecode(true); } } else @@ -1237,6 +1238,7 @@ void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::E } else { + // llinfos << "Got asset callback with good audio data for " << uuid << ", making decode request" << llendl; adp->setHasValidData(true); adp->setHasLocalData(true); gAudioDecodeMgrp->addDecodeRequest(uuid); @@ -1304,16 +1306,18 @@ void LLAudioSource::update() if (!getCurrentBuffer()) { - if (getCurrentData()) + LLAudioData *adp = getCurrentData(); + if (adp) { // Hack - try and load the sound. Will do this as a callback // on decode later. - if (getCurrentData()->load() && getCurrentData()->getBuffer()) + if (adp->load() && adp->getBuffer()) { - play(getCurrentData()->getID()); + play(adp->getID()); } - else + else if (adp->hasCompletedDecode()) // Only mark corrupted after decode is done { + llwarns << "Marking LLAudioSource corrupted for " << adp->getID() << llendl; mCorrupted = true ; } } @@ -1731,6 +1735,7 @@ LLAudioData::LLAudioData(const LLUUID &uuid) : mBufferp(NULL), mHasLocalData(false), mHasDecodedData(false), + mHasCompletedDecode(false), mHasValidData(true) { if (uuid.isNull()) @@ -1742,12 +1747,13 @@ LLAudioData::LLAudioData(const LLUUID &uuid) : if (gAudiop && gAudiop->hasDecodedFile(uuid)) { // Already have a decoded version, don't need to decode it. - mHasLocalData = true; - mHasDecodedData = true; + setHasLocalData(true); + setHasDecodedData(true); + setHasCompletedDecode(true); } else if (gAssetStorage && gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND)) { - mHasLocalData = true; + setHasLocalData(true); } } diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h index 28b69e1973..df1e4dc305 100644 --- a/indra/llaudio/llaudioengine.h +++ b/indra/llaudio/llaudioengine.h @@ -372,10 +372,12 @@ public: bool hasLocalData() const { return mHasLocalData; } bool hasDecodedData() const { return mHasDecodedData; } + bool hasCompletedDecode() const { return mHasCompletedDecode; } bool hasValidData() const { return mHasValidData; } void setHasLocalData(const bool hld) { mHasLocalData = hld; } void setHasDecodedData(const bool hdd) { mHasDecodedData = hdd; } + void setHasCompletedDecode(const bool hcd) { mHasCompletedDecode = hcd; } void setHasValidData(const bool hvd) { mHasValidData = hvd; } friend class LLAudioEngine; // Severe laziness, bad. @@ -383,9 +385,10 @@ public: protected: LLUUID mID; LLAudioBuffer *mBufferp; // If this data is being used by the audio system, a pointer to the buffer will be set here. - bool mHasLocalData; - bool mHasDecodedData; - bool mHasValidData; + bool mHasLocalData; // Set true if the sound asset file is available locally + bool mHasDecodedData; // Set true if the sound file has been decoded + bool mHasCompletedDecode; // Set true when the sound is decoded + bool mHasValidData; // Set false if decoding failed, meaning the sound asset is bad }; -- cgit v1.2.3 From f4edcdd4090cb6704603d5ef0dc8cf7a3f67399e Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Thu, 17 May 2012 21:02:53 +0300 Subject: MAINT-938 FIXED (Make debit-permissions dialog give a stronger warning) - Added new tag called "footer" to the notification. This tag allows to display messages under the buttons in toasts. - Created new class LLToastScriptQuestion for the "ScriptQuestionCaution" notification. This notification supports tag
. --- indra/llui/llnotifications.cpp | 10 ++ indra/llui/llnotifications.h | 1 + indra/llui/llnotificationtemplate.h | 19 ++- indra/newview/CMakeLists.txt | 2 + indra/newview/llnotificationscripthandler.cpp | 2 +- indra/newview/lltoastnotifypanel.cpp | 4 +- indra/newview/lltoastnotifypanel.h | 4 +- indra/newview/lltoastpanel.cpp | 13 +++ indra/newview/lltoastscriptquestion.cpp | 130 +++++++++++++++++++++ indra/newview/lltoastscriptquestion.h | 49 ++++++++ .../newview/skins/default/xui/en/notifications.xml | 21 ++-- .../default/xui/en/panel_script_question_toast.xml | 55 +++++++++ 12 files changed, 292 insertions(+), 18 deletions(-) create mode 100644 indra/newview/lltoastscriptquestion.cpp create mode 100644 indra/newview/lltoastscriptquestion.h create mode 100644 indra/newview/skins/default/xui/en/panel_script_question_toast.xml diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 8aa548b974..1dc657f915 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -399,6 +399,7 @@ LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Par : mName(p.name), mType(p.type), mMessage(p.value), + mFooter(p.footer.value), mLabel(p.label), mIcon(p.icon), mURL(p.url.value), @@ -870,6 +871,15 @@ std::string LLNotification::getMessage() const return message; } +std::string LLNotification::getFooter() const +{ + if (!mTemplatep) + return std::string(); + + std::string message = mTemplatep->mFooter; + return message; +} + std::string LLNotification::getLabel() const { std::string label = mTemplatep->mLabel; diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 3df2efcac3..4ae02b943f 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -509,6 +509,7 @@ public: std::string getType() const; std::string getMessage() const; + std::string getFooter() const; std::string getLabel() const; std::string getURL() const; S32 getURLOption() const; diff --git a/indra/llui/llnotificationtemplate.h b/indra/llui/llnotificationtemplate.h index fb50c9c123..72973789db 100644 --- a/indra/llui/llnotificationtemplate.h +++ b/indra/llui/llnotificationtemplate.h @@ -167,6 +167,17 @@ struct LLNotificationTemplate {} }; + struct Footer : public LLInitParam::Block
+ { + Mandatory value; + + Footer() + : value("value") + { + addSynonym(value, ""); + } + }; + struct Params : public LLInitParam::Block { Mandatory name; @@ -184,7 +195,8 @@ struct LLNotificationTemplate Optional form_ref; Optional priority; - Multiple tags; + Multiple tags; + Optional
footer; Params() @@ -202,7 +214,8 @@ struct LLNotificationTemplate url("url"), unique("unique"), form_ref(""), - tags("tag") + tags("tag"), + footer("footer") {} }; @@ -231,6 +244,8 @@ struct LLNotificationTemplate // The text used to display the notification. Replaceable parameters // are enclosed in square brackets like this []. std::string mMessage; + // The text used to display the notification, but under the form. + std::string mFooter; // The label for the notification; used for // certain classes of notification (those with a window and a window title). // Also used when a notification pops up underneath the current one. diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 3c9bde34b7..e2e63c27b2 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -489,6 +489,7 @@ set(viewer_SOURCE_FILES lltoastnotifypanel.cpp lltoastpanel.cpp lltoastscripttextbox.cpp + lltoastscriptquestion.cpp lltool.cpp lltoolbarview.cpp lltoolbrush.cpp @@ -1036,6 +1037,7 @@ set(viewer_HEADER_FILES lltoastnotifypanel.h lltoastpanel.h lltoastscripttextbox.h + lltoastscriptquestion.h lltool.h lltoolbarview.h lltoolbrush.h diff --git a/indra/newview/llnotificationscripthandler.cpp b/indra/newview/llnotificationscripthandler.cpp index 995915206b..398f54c6f7 100644 --- a/indra/newview/llnotificationscripthandler.cpp +++ b/indra/newview/llnotificationscripthandler.cpp @@ -101,7 +101,7 @@ bool LLScriptHandler::processNotification(const LLSD& notify) } else { - LLToastNotifyPanel* notify_box = new LLToastNotifyPanel(notification); + LLToastPanel* notify_box = LLToastPanel::buidPanelFromNotification(notification); LLToast::Params p; p.notif_id = notification->getID(); diff --git a/indra/newview/lltoastnotifypanel.cpp b/indra/newview/lltoastnotifypanel.cpp index de305bf3d9..05b6a4038d 100644 --- a/indra/newview/lltoastnotifypanel.cpp +++ b/indra/newview/lltoastnotifypanel.cpp @@ -52,7 +52,7 @@ const LLFontGL* LLToastNotifyPanel::sFontSmall = NULL; LLToastNotifyPanel::button_click_signal_t LLToastNotifyPanel::sButtonClickSignal; -LLToastNotifyPanel::LLToastNotifyPanel(LLNotificationPtr& notification, const LLRect& rect, bool show_images) : +LLToastNotifyPanel::LLToastNotifyPanel(const LLNotificationPtr& notification, const LLRect& rect, bool show_images) : LLToastPanel(notification), mTextBox(NULL), mInfoPanel(NULL), @@ -536,7 +536,7 @@ void LLToastNotifyPanel::onToastPanelButtonClicked(const LLUUID& notification_id } } -void LLToastNotifyPanel::disableRespondedOptions(LLNotificationPtr& notification) +void LLToastNotifyPanel::disableRespondedOptions(const LLNotificationPtr& notification) { LLSD response = notification->getResponse(); for (LLSD::map_const_iterator response_it = response.beginMap(); diff --git a/indra/newview/lltoastnotifypanel.h b/indra/newview/lltoastnotifypanel.h index 57711b3d80..db517ec858 100644 --- a/indra/newview/lltoastnotifypanel.h +++ b/indra/newview/lltoastnotifypanel.h @@ -60,7 +60,7 @@ public: * @deprecated if you intend to instantiate LLToastNotifyPanel - it's point to * implement right class for desired toast panel. @see LLGenericTipPanel as example. */ - LLToastNotifyPanel(LLNotificationPtr& pNotification, const LLRect& rect = LLRect::null, bool show_images = true); + LLToastNotifyPanel(const LLNotificationPtr& pNotification, const LLRect& rect = LLRect::null, bool show_images = true); virtual ~LLToastNotifyPanel(); LLPanel * getControlPanel() { return mControlPanel; } @@ -118,7 +118,7 @@ protected: /** * Process response data. Will disable selected options */ - void disableRespondedOptions(LLNotificationPtr& notification); + void disableRespondedOptions(const LLNotificationPtr& notification); bool mIsTip; bool mAddedDefaultBtn; diff --git a/indra/newview/lltoastpanel.cpp b/indra/newview/lltoastpanel.cpp index d2a4ce8745..c33fde99c5 100644 --- a/indra/newview/lltoastpanel.cpp +++ b/indra/newview/lltoastpanel.cpp @@ -29,7 +29,9 @@ #include "llpanelgenerictip.h" #include "llpanelonlinestatus.h" #include "llnotifications.h" +#include "lltoastnotifypanel.h" #include "lltoastpanel.h" +#include "lltoastscriptquestion.h" //static const S32 LLToastPanel::MIN_PANEL_HEIGHT = 40; // VPAD(4)*2 + ICON_HEIGHT(32) @@ -106,6 +108,17 @@ LLToastPanel* LLToastPanel::buidPanelFromNotification( res = new LLPanelGenericTip(notification); } } + else if("notify" == notification->getType()) + { + if (notification->getPriority() == NOTIFICATION_PRIORITY_CRITICAL) + { + res = new LLToastScriptQuestion(notification); + } + else + { + res = new LLToastNotifyPanel(notification); + } + } /* else if(...) create all other specific non-public toast panel diff --git a/indra/newview/lltoastscriptquestion.cpp b/indra/newview/lltoastscriptquestion.cpp new file mode 100644 index 0000000000..feeb8ca77b --- /dev/null +++ b/indra/newview/lltoastscriptquestion.cpp @@ -0,0 +1,130 @@ +/** + * @file lltoastscriptquestion.cpp + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 "llviewerprecompiledheaders.h" + +#include "llbutton.h" +#include "llnotifications.h" +#include "lltoastscriptquestion.h" + +const int LEFT_PAD = 10; +const int BUTTON_HEIGHT = 27; +const int MAX_LINES_COUNT = 50; + +LLToastScriptQuestion::LLToastScriptQuestion(const LLNotificationPtr& notification) +: +LLToastPanel(notification) +{ + buildFromFile("panel_script_question_toast.xml"); +} + +BOOL LLToastScriptQuestion::postBuild() +{ + createButtons(); + + LLTextBox* mMessage = getChild("top_info_message"); + LLTextBox* mFooter = getChild("bottom_info_message"); + + mMessage->setValue(mNotification->getMessage()); + mFooter->setValue(mNotification->getFooter()); + + snapToMessageHeight(); + + return TRUE; +} +void LLToastScriptQuestion::snapToMessageHeight() +{ + LLTextBox* mMessage = getChild("top_info_message"); + LLTextBox* mFooter = getChild("bottom_info_message"); + if (!mMessage || !mFooter) + { + return; + } + + if (mMessage->getVisible() && mFooter->getVisible()) + { + S32 heightDelta = 0; + S32 maxTextHeight = (mMessage->getDefaultFont()->getLineHeight() * MAX_LINES_COUNT) + + (mFooter->getDefaultFont()->getLineHeight() * MAX_LINES_COUNT); + + LLRect messageRect = mMessage->getRect(); + LLRect footerRect = mFooter->getRect(); + + S32 oldTextHeight = messageRect.getHeight() + footerRect.getHeight(); + + S32 requiredTextHeight = mMessage->getTextBoundingRect().getHeight() + mFooter->getTextBoundingRect().getHeight(); + S32 newTextHeight = llmin(requiredTextHeight, maxTextHeight); + + heightDelta = newTextHeight - oldTextHeight - heightDelta; + + reshape( getRect().getWidth(), llmax(getRect().getHeight() + heightDelta, MIN_PANEL_HEIGHT)); + } +} + +void LLToastScriptQuestion::createButtons() +{ + LLNotificationFormPtr form = mNotification->getForm(); + int num_elements = form->getNumElements(); + int buttons_width = 0; + + for (int i = 0; i < num_elements; ++i) + { + LLSD form_element = form->getElement(i); + if ("button" == form_element["type"].asString()) + { + LLButton::Params p; + const LLFontGL* font = LLFontGL::getFontSansSerif(); + p.name(form_element["name"].asString()); + p.label(form_element["text"].asString()); + p.layout("topleft"); + p.font(font); + p.rect.height(BUTTON_HEIGHT); + p.click_callback.function(boost::bind(&LLToastScriptQuestion::onButtonClicked, this, form_element["name"].asString())); + p.rect.left = LEFT_PAD; + p.rect.width = font->getWidth(form_element["text"].asString()); + p.auto_resize = true; + p.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + p.image_color(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); + p.image_color_disabled(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); + + LLButton* button = LLUICtrlFactory::create(p); + button->autoResize(); + getChild("buttons_panel")->addChild(button); + + LLRect rect = button->getRect(); + rect.setLeftTopAndSize(buttons_width, rect.mTop, rect.getWidth(), rect.getHeight()); + button->setRect(rect); + + buttons_width += rect.getWidth() + LEFT_PAD; + } + } +} + +void LLToastScriptQuestion::onButtonClicked(std::string btn_name) +{ + LLSD response = mNotification->getResponseTemplate(); + response[btn_name] = true; + mNotification->respond(response); +} diff --git a/indra/newview/lltoastscriptquestion.h b/indra/newview/lltoastscriptquestion.h new file mode 100644 index 0000000000..3a557f60f6 --- /dev/null +++ b/indra/newview/lltoastscriptquestion.h @@ -0,0 +1,49 @@ +/** + * @file lltoastscriptquestion.h + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 "llviewerprecompiledheaders.h" + +#include "lltoastpanel.h" + +#ifndef LLTOASTSCRIPTQUESTION_H_ +#define LLTOASTSCRIPTQUESTION_H_ + +class LLToastScriptQuestion : public LLToastPanel +{ + LOG_CLASS(LLToastScriptQuestion); + +public: + LLToastScriptQuestion(const LLNotificationPtr& notification); + virtual BOOL postBuild(); + virtual ~LLToastScriptQuestion(){}; + +private: + void snapToMessageHeight(); + + void createButtons(); + void onButtonClicked(std::string btn_name); +}; + +#endif /* LLTOASTSCRIPTQUESTION_H_ */ diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 59dd17ea9d..4e0f52b8fe 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -6373,31 +6373,30 @@ Is this OK? -An object named '<nolink>[OBJECTNAME]</nolink>', owned by '[NAME]' would like to: - -[QUESTIONS] -If you do not trust this object and its creator, you should deny the request. +Warning: The object '<nolink>[OBJECTNAME]</nolink>' wants total access to your Linden Dollars account. If you allow access, it can remove funds from your account at any time, or empty your account completely, on an ongoing basis with no additional warnings. + +It is rare that such a request is legitimate. Do not allow access if you do not fully understand why it wants access to your account. -Grant this request? confirm
- + @@ -163,7 +163,7 @@ - + -- cgit v1.2.3 From 335a8254ae01aa2672bee4b4e636315f16b7e66e Mon Sep 17 00:00:00 2001 From: eli Date: Thu, 24 May 2012 17:16:56 -0700 Subject: FIX VWR-23844 VWR-26542 VWR-28950 --- indra/newview/skins/default/xui/fr/floater_about_land.xml | 2 +- indra/newview/skins/default/xui/fr/strings.xml | 2 +- indra/newview/skins/default/xui/pt/menu_viewer.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/indra/newview/skins/default/xui/fr/floater_about_land.xml b/indra/newview/skins/default/xui/fr/floater_about_land.xml index 6f8885487a..49af1a87e1 100644 --- a/indra/newview/skins/default/xui/fr/floater_about_land.xml +++ b/indra/newview/skins/default/xui/fr/floater_about_land.xml @@ -439,7 +439,7 @@ musique : Son : - + Sons d'avatar : diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index 3eebed450f..8b410128da 100644 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -4509,7 +4509,7 @@ Essayez avec le chemin d'accès à l'éditeur entre guillemets doubles Backsp - Shift + Maj Ctrl diff --git a/indra/newview/skins/default/xui/pt/menu_viewer.xml b/indra/newview/skins/default/xui/pt/menu_viewer.xml index d7d5d59a33..fd973bf3c2 100644 --- a/indra/newview/skins/default/xui/pt/menu_viewer.xml +++ b/indra/newview/skins/default/xui/pt/menu_viewer.xml @@ -118,7 +118,7 @@ - + @@ -163,7 +163,7 @@ - + -- cgit v1.2.3 From b8d248f27599264a3f72e2433d48efa6e6e83602 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Sat, 26 May 2012 06:49:40 -0400 Subject: tag merge of DRTVWR-156 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 9a32bc53cc..99847a08a5 100644 --- a/.hgtags +++ b/.hgtags @@ -111,3 +111,4 @@ d7fcefabdf32bb61a9ea6d6037c1bb26190a85bc 2.6.3-beta1 bb1075286b3b147b1dae2e3d6b2d56f04ff03f35 DRTVWR-52_2.6.6-beta1 bb1075286b3b147b1dae2e3d6b2d56f04ff03f35 2.6.6-beta1 5e349dbe9cc84ea5795af8aeb6d473a0af9d4953 2.6.8-start +bb9932a7a5fd00edf52d95f354e3b37ae6a942db DRTVWR-156 -- cgit v1.2.3 From 5db5102fe05f7d2ef3535516da269abb276dad6b Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Tue, 29 May 2012 12:42:50 -0400 Subject: tag merge of DRTVWR-148 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 6ebdae7707..817a938174 100644 --- a/.hgtags +++ b/.hgtags @@ -299,3 +299,4 @@ c623bbc854b6f7ee1b33a3718f76715046aa2937 viewer-release-candidate 675668bd24d3bea570814f71762a2a806f7e1b8d 3.3.2-release 675668bd24d3bea570814f71762a2a806f7e1b8d viewer-release-candidate 15e90b52dc0297921b022b90d10d797436b8a1bd viewer-release-candidate +6414ecdabc5d89515b08d1f872cf923ed3a5523a DRTVWR-148 -- cgit v1.2.3 From 380c0eb5ac58ec3cf6b8aa949792c9e9d4136344 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Tue, 29 May 2012 14:48:55 -0400 Subject: tag merge of DRTVWR-155 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index b6a9eca2ca..b7bedf1e33 100644 --- a/.hgtags +++ b/.hgtags @@ -144,3 +144,4 @@ a9abb9633a266c8d2fe62411cfd1c86d32da72bf 2.7.1-release 09984bfa6cae17e0f72d02b75c1b7393c65eecfc DRTVWR-69_2.7.5-beta1 09984bfa6cae17e0f72d02b75c1b7393c65eecfc 2.7.5-beta1 e1ed60913230dd64269a7f7fc52cbc6004f6d52c 2.8.0-start +2a3965b3ad202df7ea25d2be689291bb14a1280e DRTVWR-155 -- cgit v1.2.3 From a46b820b0cce4939d9e7ccded0ff9e7a27105e90 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Tue, 29 May 2012 17:50:05 -0400 Subject: tag merge of DRTVWR-153 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 4f3f55ad94..67ec67efa6 100644 --- a/.hgtags +++ b/.hgtags @@ -297,3 +297,4 @@ d29a260119f8d5a5d168e25fed0c7ea6b3f40161 3.3.2-beta1 c623bbc854b6f7ee1b33a3718f76715046aa2937 viewer-release-candidate 675668bd24d3bea570814f71762a2a806f7e1b8d viewer-release-candidate 675668bd24d3bea570814f71762a2a806f7e1b8d 3.3.2-release +24a7281bef42bd4430ceb25db8b195449c2c7de3 DRTVWR-153 -- cgit v1.2.3 From 3361b5e9e2e5400e31fcb1bcc8066bf4ee3b7eaf Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Tue, 29 May 2012 18:09:09 -0400 Subject: Added tag DRTVWR-144, 3.3.3-beta1 for changeset 1b7f311b5a5d --- .hgtags | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.hgtags b/.hgtags index fda8aedd0d..256356cf6f 100644 --- a/.hgtags +++ b/.hgtags @@ -303,3 +303,5 @@ c623bbc854b6f7ee1b33a3718f76715046aa2937 viewer-release-candidate 24a7281bef42bd4430ceb25db8b195449c2c7de3 DRTVWR-153 15e90b52dc0297921b022b90d10d797436b8a1bd viewer-release-candidate 6414ecdabc5d89515b08d1f872cf923ed3a5523a DRTVWR-148 +1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144 +1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c 3.3.3-beta1 -- cgit v1.2.3 From 0fd3ff972d97886ce227424c6c7039e2cc203890 Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Mon, 4 Jun 2012 12:15:08 -0700 Subject: MAINT-1142 use VBO pooling on OSX --- indra/llrender/llvertexbuffer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index b4899209c4..823c6b9dc5 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -38,7 +38,11 @@ #include "llglslshader.h" #include "llmemory.h" +#if LL_DARWIN +#define LL_VBO_POOLING 1 +#else #define LL_VBO_POOLING 0 +#endif //Next Highest Power Of Two //helper function, returns first number > v that is a power of 2, or v if v is already a power of 2 -- cgit v1.2.3 From 332523b0d6a5d91676139eaee51ff98d1a7788ed Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Mon, 4 Jun 2012 21:08:40 -0400 Subject: tag merge of DRTVWR-160 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 256356cf6f..4dd032c154 100644 --- a/.hgtags +++ b/.hgtags @@ -305,3 +305,4 @@ c623bbc854b6f7ee1b33a3718f76715046aa2937 viewer-release-candidate 6414ecdabc5d89515b08d1f872cf923ed3a5523a DRTVWR-148 1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144 1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c 3.3.3-beta1 +5910f8063a7e1ddddf504c2f35ca831cc5e8f469 DRTVWR-160 -- cgit v1.2.3 From fd3ae758d0dc3cb4330ea9c7a9af8c0ffa046dfc Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Mon, 4 Jun 2012 21:11:54 -0400 Subject: Added tag 3.3.3-beta1, DRTVWR-144 for changeset f0a174c2adb4 --- .hgtags | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.hgtags b/.hgtags index 4dd032c154..9aad8eebbb 100644 --- a/.hgtags +++ b/.hgtags @@ -306,3 +306,7 @@ c623bbc854b6f7ee1b33a3718f76715046aa2937 viewer-release-candidate 1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144 1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c 3.3.3-beta1 5910f8063a7e1ddddf504c2f35ca831cc5e8f469 DRTVWR-160 +1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c 3.3.3-beta1 +f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 3.3.3-beta1 +1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144 +f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144 -- cgit v1.2.3 From 166f75d91f180d0ac45f0b8ee7c3a1b6742209fa Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Jun 2012 16:38:16 -0400 Subject: MAINT-1144: Defend against NULL LLPluginProcessParent::mProcess. The change from LLProcessLauncher to LLProcess introduces the possibility of a NULL (default-constructed) LLProcessPtr. Add certain static LLProcess methods accepting LLProcessPtr, forwarding to nonstatic method when non-NULL but doing something reasonable with NULL. Use these methods in LLPLuginProcessParent. --- indra/llcommon/llprocess.cpp | 38 ++++++++++++++++++++++++++++++++ indra/llcommon/llprocess.h | 11 +++++++-- indra/llplugin/llpluginprocessparent.cpp | 13 +++++------ 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 9667e4e033..715df36f39 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -819,16 +819,43 @@ bool LLProcess::kill(const std::string& who) return ! isRunning(); } +//static +bool LLProcess::kill(const LLProcessPtr& p, const std::string& who) +{ + if (! p) + return true; // process dead! (was never running) + return p->kill(who); +} + bool LLProcess::isRunning() const { return getStatus().mState == RUNNING; } +//static +bool LLProcess::isRunning(const LLProcessPtr& p) +{ + if (! p) + return false; + return p->isRunning(); +} + LLProcess::Status LLProcess::getStatus() const { return mStatus; } +//static +LLProcess::Status LLProcess::getStatus(const LLProcessPtr& p) +{ + if (! p) + { + // default-constructed Status has mState == UNSTARTED + return Status(); + } + return p->getStatus(); +} + std::string LLProcess::getStatusString() const { return getStatusString(getStatus()); @@ -839,6 +866,17 @@ std::string LLProcess::getStatusString(const Status& status) const return getStatusString(mDesc, status); } +//static +std::string LLProcess::getStatusString(const std::string& desc, const LLProcessPtr& p) +{ + if (! p) + { + // default-constructed Status has mState == UNSTARTED + return getStatusString(desc, Status()); + } + return desc + " " + p->getStatusString(); +} + //static std::string LLProcess::getStatusString(const std::string& desc, const Status& status) { diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 51010966f9..d711ce2f74 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -239,6 +239,10 @@ public: /// Is child process still running? bool isRunning() const; + // static isRunning(LLProcessPtr), getStatus(LLProcessPtr), + // getStatusString(LLProcessPtr), kill(LLProcessPtr) handle the case in + // which the passed LLProcessPtr might be NULL (default-constructed). + static bool isRunning(const LLProcessPtr&); /** * State of child process @@ -272,8 +276,10 @@ public: /// Status query Status getStatus() const; + static Status getStatus(const LLProcessPtr&); /// English Status string query, for logging etc. std::string getStatusString() const; + static std::string getStatusString(const std::string& desc, const LLProcessPtr&); /// English Status string query for previously-captured Status std::string getStatusString(const Status& status) const; /// static English Status string query @@ -282,6 +288,7 @@ public: // 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(const std::string& who=""); + static bool kill(const LLProcessPtr& p, const std::string& who=""); #if LL_WINDOWS typedef int id; ///< as returned by getProcessID() @@ -314,10 +321,10 @@ public: * New functionality should be added as nonstatic members operating on * the same data as getProcessHandle(). * - * In particular, if child termination is detected by static isRunning() + * In particular, if child termination is detected by this static isRunning() * rather than by nonstatic isRunning(), the LLProcess object won't be * aware of the child's changed status and may encounter OS errors trying - * to obtain it. static isRunning() is only intended for after the + * to obtain it. This static isRunning() is only intended for after the * launching LLProcess object has been destroyed. */ static handle isRunning(handle, const std::string& desc=""); diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp index f10eaee5b4..71a6145b58 100644 --- a/indra/llplugin/llpluginprocessparent.cpp +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -134,11 +134,8 @@ LLPluginProcessParent::~LLPluginProcessParent() // and remove it from our map mSharedMemoryRegions.erase(iter); } - - if (mProcess) - { - mProcess->kill(); - } + + LLProcess::kill(mProcess); killSockets(); } @@ -471,7 +468,7 @@ void LLPluginProcessParent::idle(void) break; case STATE_EXITING: - if (! mProcess->isRunning()) + if (! LLProcess::isRunning(mProcess)) { setState(STATE_CLEANUP); } @@ -499,7 +496,7 @@ void LLPluginProcessParent::idle(void) break; case STATE_CLEANUP: - mProcess->kill(); + LLProcess::kill(mProcess); killSockets(); setState(STATE_DONE); break; @@ -1078,7 +1075,7 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit() { bool result = false; - if (! mProcess->isRunning()) + if (! LLProcess::isRunning(mProcess)) { LL_WARNS("Plugin") << "child exited" << LL_ENDL; result = true; -- cgit v1.2.3 From fd14738857bac91b4e9ef5b212e6b8d8b9dfd61e Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Fri, 8 Jun 2012 10:49:21 -0400 Subject: tag merge of DRTVWR-164 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 9aad8eebbb..223b30f50a 100644 --- a/.hgtags +++ b/.hgtags @@ -310,3 +310,4 @@ c623bbc854b6f7ee1b33a3718f76715046aa2937 viewer-release-candidate f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 3.3.3-beta1 1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144 f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144 +2d6c0634b11e6f3df11002b8510a72a0433da00a DRTVWR-164 -- cgit v1.2.3 From 9ead6d8e5bf9aab7fd990104aed42629f2bd7114 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Fri, 8 Jun 2012 11:40:57 -0400 Subject: tag merge of DRTVWR-162 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 4f3f55ad94..839a34b34d 100644 --- a/.hgtags +++ b/.hgtags @@ -297,3 +297,4 @@ d29a260119f8d5a5d168e25fed0c7ea6b3f40161 3.3.2-beta1 c623bbc854b6f7ee1b33a3718f76715046aa2937 viewer-release-candidate 675668bd24d3bea570814f71762a2a806f7e1b8d viewer-release-candidate 675668bd24d3bea570814f71762a2a806f7e1b8d 3.3.2-release +600f3b3920d94de805ac6dc8bb6def9c069dd360 DRTVWR-162 -- cgit v1.2.3 From 9f000e4b348a9598b43070e8caeeb8bdeebefbea Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Fri, 8 Jun 2012 15:49:11 -0400 Subject: SH-3153 FIX --- indra/newview/lltexturefetch.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) mode change 100644 => 100755 indra/newview/lltexturefetch.cpp diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp old mode 100644 new mode 100755 index e2af497a7d..7e6dfbc9d9 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1860,7 +1860,8 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mTotalHTTPRequests(0), mCurlGetRequest(NULL), mQAMode(qa_mode), - mFetchDebugger(NULL) + mFetchDebugger(NULL), + mFetcherLocked(FALSE) { mCurlPOSTRequestCount = 0; mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS"); -- cgit v1.2.3 From 9ee115f5e5baec231828a60173bf5ce9d393cd58 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Sat, 9 Jun 2012 07:06:38 -0400 Subject: tag merge of DRTVWR-165 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 2e73c71878..ba8a9364f4 100644 --- a/.hgtags +++ b/.hgtags @@ -312,3 +312,4 @@ f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 3.3.3-beta1 1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144 f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144 2d6c0634b11e6f3df11002b8510a72a0433da00a DRTVWR-164 +80b5e5e9775966d3839331ffa7a16a60f9d7c930 DRTVWR-165 -- cgit v1.2.3 From c931c74fdb282fe7b730d9da93aa939d7d63b82b Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Sat, 9 Jun 2012 08:19:14 -0400 Subject: Added tag 3.3.3-beta2 for changeset fdcc08a4f20a --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index ba8a9364f4..d838dc16b2 100644 --- a/.hgtags +++ b/.hgtags @@ -313,3 +313,4 @@ f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 3.3.3-beta1 f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144 2d6c0634b11e6f3df11002b8510a72a0433da00a DRTVWR-164 80b5e5e9775966d3839331ffa7a16a60f9d7c930 DRTVWR-165 +fdcc08a4f20ae9bb060f4693c8980d216534efdf 3.3.3-beta2 -- cgit v1.2.3 From 667ca78b495e903e5e0f0728098c44e2e24de9b5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 18 Jun 2012 12:57:55 -0400 Subject: MAINT-1144: Update to apr_suite containing relevant fix. --- autobuild.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index d58c4a350c..c23996d88d 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - 1fe7c2121916b2c5ff75073742442e2a + f51d7f098da8b58d26fd4177e8adaa48 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/257886/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120523.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259907/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120618.tar.bz2 name darwin @@ -102,9 +102,9 @@ archive hash - eac7926a7caa121144c911434a01b680 + 5afaba3815a39d8fb58be311cb700229 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/257886/arch/Linux/installer/apr_suite-1.4.5-linux-20120523.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259907/arch/Linux/installer/apr_suite-1.4.5-linux-20120618.tar.bz2 name linux @@ -114,9 +114,9 @@ archive hash - 8789d952a33ce3e8b87be8cbf94bc6ba + b3f8b06a38aa9ca0a5dee9af9d1030d3 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/257886/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120523.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259907/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120618.tar.bz2 name windows -- cgit v1.2.3 From 84536935eb339c3b95a96dfbc505f929d7a884f6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 18 Jun 2012 16:14:30 -0400 Subject: MAINT-1144: Update with relevant apr_suite fix. --- autobuild.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index c23996d88d..ba57d09f86 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -90,9 +90,9 @@ archive hash - f51d7f098da8b58d26fd4177e8adaa48 + 2dfcd809e747f714b3fe0bf82a175812 url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259907/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120618.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259951/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120618.tar.bz2 name darwin @@ -102,9 +102,9 @@ archive hash - 5afaba3815a39d8fb58be311cb700229 + f38c966a430012dc157fdc104f23a59b url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259907/arch/Linux/installer/apr_suite-1.4.5-linux-20120618.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259951/arch/Linux/installer/apr_suite-1.4.5-linux-20120618.tar.bz2 name linux @@ -114,9 +114,9 @@ archive hash - b3f8b06a38aa9ca0a5dee9af9d1030d3 + 4a9d040582342699c58c886c5ccd2caf url - http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259907/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120618.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259951/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120618.tar.bz2 name windows -- cgit v1.2.3 From ba01c76e1f5d54021d80665a269cb45f80aa8fab Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Mon, 18 Jun 2012 22:12:15 -0400 Subject: tag merge of DRTVWR-169 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index d838dc16b2..478ec70893 100644 --- a/.hgtags +++ b/.hgtags @@ -314,3 +314,4 @@ f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144 2d6c0634b11e6f3df11002b8510a72a0433da00a DRTVWR-164 80b5e5e9775966d3839331ffa7a16a60f9d7c930 DRTVWR-165 fdcc08a4f20ae9bb060f4693c8980d216534efdf 3.3.3-beta2 +af5f3e43e6e4424b1da19d9e16f6b853a7b822ed DRTVWR-169 -- cgit v1.2.3 From 1d23a8600c0d849cd540d2f3824df98cf1a7b877 Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Mon, 18 Jun 2012 22:15:33 -0400 Subject: Added tag 3.3.3-beta3 for changeset 4b3c68199a86 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 478ec70893..fb5075caa2 100644 --- a/.hgtags +++ b/.hgtags @@ -315,3 +315,4 @@ f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144 80b5e5e9775966d3839331ffa7a16a60f9d7c930 DRTVWR-165 fdcc08a4f20ae9bb060f4693c8980d216534efdf 3.3.3-beta2 af5f3e43e6e4424b1da19d9e16f6b853a7b822ed DRTVWR-169 +4b3c68199a86cabaa5d9466d7b0f7e141e901d7a 3.3.3-beta3 -- cgit v1.2.3 From 90547ff411db177bf6424ca553449a81a808fc0f Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Thu, 21 Jun 2012 16:44:04 -0400 Subject: Added tag 3.3.3-release for changeset 6428242e124b --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index fb5075caa2..6dd9a0e111 100644 --- a/.hgtags +++ b/.hgtags @@ -316,3 +316,4 @@ f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144 fdcc08a4f20ae9bb060f4693c8980d216534efdf 3.3.3-beta2 af5f3e43e6e4424b1da19d9e16f6b853a7b822ed DRTVWR-169 4b3c68199a86cabaa5d9466d7b0f7e141e901d7a 3.3.3-beta3 +6428242e124b523813bfaf4c45b3d422f0298c81 3.3.3-release -- cgit v1.2.3