std::vector<std::basic_string<charT> > split_unix( const std::basic_string<charT>& cmdline, const std::basic_string<charT>& seperator, const std::basic_string<charT>& quote, const std::basic_string<charT>& escape) { typedef boost::tokenizer< boost::escaped_list_separator<charT>, typename std::basic_string<charT>::const_iterator, std::basic_string<charT> > tokenizerT; tokenizerT tok(cmdline.begin(), cmdline.end(), boost::escaped_list_separator< charT >(escape, seperator, quote)); std::vector< std::basic_string<charT> > result; for (typename tokenizerT::iterator cur_token(tok.begin()), end_token(tok.end()); cur_token != end_token; ++cur_token) { if (!cur_token->empty()) result.push_back(*cur_token); } return result; }
void LLPluginProcessParent::idle(void) { bool idle_again; do { // process queued messages mIncomingQueueMutex.lock(); while(!mIncomingQueue.empty()) { LLPluginMessage message = mIncomingQueue.front(); mIncomingQueue.pop(); mIncomingQueueMutex.unlock(); receiveMessage(message); mIncomingQueueMutex.lock(); } mIncomingQueueMutex.unlock(); // Give time to network processing if(mMessagePipe) { // Drain any queued outgoing messages mMessagePipe->pumpOutput(); // Only do input processing here if this instance isn't in a pollset. if(!mPolledInput) { mMessagePipe->pumpInput(); } } if(mState <= STATE_RUNNING) { if(APR_STATUS_IS_EOF(mSocketError)) { // Plugin socket was closed. This covers both normal plugin termination and plugin crashes. errorState(); } else if(mSocketError != APR_SUCCESS) { // The socket is in an error state -- the plugin is gone. LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL; errorState(); } } // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). // USE THIS CAREFULLY, since it can starve other code. Specifically make sure there's no way to get into a closed cycle and never return. // When in doubt, don't do it. idle_again = false; switch(mState) { case STATE_UNINITIALIZED: break; case STATE_INITIALIZED: { apr_status_t status = APR_SUCCESS; apr_sockaddr_t* addr = NULL; mListenSocket = LLSocket::create(LLSocket::STREAM_TCP); mBoundPort = 0; // This code is based on parts of LLSocket::create() in lliosocket.cpp. status = apr_sockaddr_info_get( &addr, "127.0.0.1", APR_INET, 0, // port 0 = ephemeral ("find me a port") 0, AIAPRRootPool::get()()); if(ll_apr_warn_status(status)) { killSockets(); errorState(); break; } // This allows us to reuse the address on quick down/up. This is unlikely to create problems. ll_apr_warn_status(apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_REUSEADDR, 1)); status = apr_socket_bind(mListenSocket->getSocket(), addr); if(ll_apr_warn_status(status)) { killSockets(); errorState(); break; } // Get the actual port the socket was bound to { apr_sockaddr_t* bound_addr = NULL; if(ll_apr_warn_status(apr_socket_addr_get(&bound_addr, APR_LOCAL, mListenSocket->getSocket()))) { killSockets(); errorState(); break; } mBoundPort = bound_addr->port; if(mBoundPort == 0) { LL_WARNS("Plugin") << "Bound port number unknown, bailing out." << LL_ENDL; killSockets(); errorState(); break; } } LL_DEBUGS("Plugin") << "Bound tcp socket to port: " << addr->port << LL_ENDL; // Make the listen socket non-blocking status = apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_NONBLOCK, 1); if(ll_apr_warn_status(status)) { killSockets(); errorState(); break; } apr_socket_timeout_set(mListenSocket->getSocket(), 0); if(ll_apr_warn_status(status)) { killSockets(); errorState(); break; } // If it's a stream based socket, we need to tell the OS // to keep a queue of incoming connections for ACCEPT. status = apr_socket_listen( mListenSocket->getSocket(), 10); // FIXME: Magic number for queue size if(ll_apr_warn_status(status)) { killSockets(); errorState(); break; } // If we got here, we're listening. setState(STATE_LISTENING); } break; case STATE_LISTENING: { // 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) { errorState(); } else { if(mDebug) { // If we're set to debug, start up a gdb instance in a new terminal window and have it attach to the plugin process and continue. std::stringstream cmd; #if LL_DARWIN // 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' 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(); #elif LL_LINUX // The command we're constructing would look like this on the command line: // /usr/bin/xterm -geometry 160x24-0+0 -e '/usr/bin/gdb -n /proc/12345/exe 12345' // This can be changed by setting the following environment variables, for example: // export LL_DEBUG_TERMINAL_COMMAND="/usr/bin/gnome-terminal --geometry=165x24-0+0 -e %s" // export LL_DEBUG_GDB_PATH=/usr/bin/gdb char const* env; std::string const terminal_command = (env = getenv("LL_DEBUG_TERMINAL_COMMAND")) ? env : "/usr/bin/xterm -geometry 160x24+0+0 -e %s"; char const* const gdb_path = (env = getenv("LL_DEBUG_GDB_PATH")) ? env : "/usr/bin/gdb"; cmd << gdb_path << " -n /proc/" << mProcess.getProcessID() << "/exe " << mProcess.getProcessID(); typedef boost::tokenizer< boost::escaped_list_separator< char>, typename std::basic_string< char>::const_iterator, std::basic_string<char> > tokenizerT; tokenizerT tok(terminal_command.begin(), terminal_command.end(), boost::escaped_list_separator< char >("\\", " ", "'\"")); std::vector< std::basic_string<char> > tokens; for (typename tokenizerT::iterator cur_token(tok.begin()), end_token(tok.end()); cur_token != end_token; ++cur_token) { if (!cur_token->empty()) tokens.push_back(*cur_token); } std::vector<std::string>::iterator token = tokens.begin(); mDebugger.setExecutable(*token); while (++token != tokens.end()) { if (*token == "%s") { mDebugger.addArgument(cmd.str()); } else { mDebugger.addArgument(*token); } } mDebugger.launch(); #endif } // This will allow us to time out if the process never starts. mHeartbeat.start(); mHeartbeat.setTimerExpirySec(mPluginLaunchTimeout); setState(STATE_LAUNCHED); } } break; case STATE_LAUNCHED: // waiting for the plugin to connect if(pluginLockedUpOrQuit()) { errorState(); } else { // Check for the incoming connection. if(accept()) { // Stop listening on the server port mListenSocket.reset(); setState(STATE_CONNECTED); } } break; case STATE_CONNECTED: // waiting for hello message from the plugin if(pluginLockedUpOrQuit()) { errorState(); } break; case STATE_HELLO: LL_DEBUGS("Plugin") << "received hello message" << LL_ENDL; // Send the message to load the plugin { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin"); message.setValue("file", mPluginFile); sendMessage(message); } setState(STATE_LOADING); break; case STATE_LOADING: // The load_plugin_response message will kick us from here into STATE_RUNNING if(pluginLockedUpOrQuit()) { errorState(); } break; case STATE_RUNNING: if(pluginLockedUpOrQuit()) { errorState(); } break; case STATE_EXITING: if(!mProcess.isRunning()) { setState(STATE_CLEANUP); } else if(pluginLockedUp()) { LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << LL_ENDL; errorState(); } break; case STATE_LAUNCH_FAILURE: if(mOwner != NULL) { mOwner->pluginLaunchFailed(); } setState(STATE_CLEANUP); break; case STATE_ERROR: if(mOwner != NULL) { mOwner->pluginDied(); } setState(STATE_CLEANUP); break; case STATE_CLEANUP: mProcess.kill(); killSockets(); setState(STATE_DONE); break; case STATE_DONE: // just sit here. break; } } while (idle_again); }