]> git.lyx.org Git - lyx.git/blobdiff - src/support/ForkedCalls.cpp
* layouttranslations.review - remove dupes
[lyx.git] / src / support / ForkedCalls.cpp
index 90ce2993a841290d07e954cafb49b591dd4068ee..d77248eadcb12bb35e45e959afbc755e38808d74 100644 (file)
 #include "support/os.h"
 #include "support/Timeout.h"
 
-#include <boost/bind.hpp>
+#include "support/bind.h"
 
-#include <vector>
 #include <cerrno>
+#include <queue>
 #include <sstream>
+#include <utility>
+#include <vector>
 
 #ifdef _WIN32
 # define SIGHUP 1
 # define SIGKILL 9
 # include <windows.h>
 # include <process.h>
+# undef max
 #else
 # include <csignal>
 # include <cstdlib>
 #  include <unistd.h>
 # endif
 # include <sys/wait.h>
-# ifndef CXX_GLOBAL_CSTD
-  using std::signal;
-  using std::strerror;
-# endif
 #endif
 
-using boost::bind;
+using namespace std;
 
-using std::endl;
-using std::equal_to;
-using std::find_if;
-using std::string;
-using std::vector;
 
 
 namespace lyx {
@@ -70,11 +64,10 @@ public:
        //
        static void killItDead(int secs, pid_t pid)
        {
-               if (secs > 0) {
+               if (secs > 0)
                        new Murder(secs, pid);
-               } else if (pid != 0) {
+               else if (pid != 0)
                        support::kill(pid, SIGKILL);
-               }
        }
 
        //
@@ -82,27 +75,21 @@ public:
        {
                if (pid_ != 0)
                        support::kill(pid_, SIGKILL);
-               lyxerr << "Killed " << pid_ << std::endl;
+               lyxerr << "Killed " << pid_ << endl;
                delete this;
        }
 
 private:
        //
        Murder(int secs, pid_t pid)
-               : timeout_(0), pid_(pid)
+               : timeout_(1000*secs, Timeout::ONETIME), pid_(pid)
        {
-               timeout_ = new Timeout(1000*secs, Timeout::ONETIME);
-               timeout_->timeout.connect(boost::bind(&Murder::kill, this));
-               timeout_->start();
+               timeout_.timeout.connect(lyx::bind(&Murder::kill, this));
+               timeout_.start();
        }
 
        //
-       ~Murder()
-       {
-               delete timeout_;
-       }
-       //
-       Timeout * timeout_;
+       Timeout timeout_;
        //
        pid_t pid_;
 };
@@ -121,9 +108,12 @@ ForkedProcess::ForkedProcess()
 {}
 
 
+bool ForkedProcess::IAmAChild = false;
+
+
 void ForkedProcess::emitSignal()
 {
-       if (signal_.get()) {
+       if (signal_) {
                signal_->operator()(pid_, retval_);
        }
 }
@@ -132,10 +122,14 @@ void ForkedProcess::emitSignal()
 // Spawn the child process
 int ForkedProcess::run(Starttype type)
 {
-       retval_  = 0;
+       retval_ = 0;
        pid_ = generateChild();
        if (pid_ <= 0) { // child or fork failed.
                retval_ = 1;
+               if (pid_ == 0)
+                       //we also do this in fork(), too, but maybe someone will try
+                       //to bypass that
+                       IAmAChild = true; 
                return retval_;
        }
 
@@ -145,8 +139,7 @@ int ForkedProcess::run(Starttype type)
                break;
        case DontWait: {
                // Integrate into the Controller
-               ForkedCallsController & contr = ForkedCallsController::get();
-               contr.addCall(*this);
+               ForkedCallsController::addCall(*this);
                break;
        }
        }
@@ -157,7 +150,7 @@ int ForkedProcess::run(Starttype type)
 
 bool ForkedProcess::running() const
 {
-       if (!pid())
+       if (pid() <= 0)
                return false;
 
 #if !defined (_WIN32)
@@ -177,31 +170,39 @@ bool ForkedProcess::running() const
 void ForkedProcess::kill(int tol)
 {
        lyxerr << "ForkedProcess::kill(" << tol << ')' << endl;
-       if (pid() == 0) {
+       if (pid() <= 0) {
                lyxerr << "Can't kill non-existent process!" << endl;
                return;
        }
 
-       // The weird (std::max)(a,b) signature prevents expansion
-       // of an evil MSVC macro.
-       int const tolerance = (std::max)(0, tol);
+       int const tolerance = max(0, tol);
        if (tolerance == 0) {
                // Kill it dead NOW!
                Murder::killItDead(0, pid());
-
        } else {
                int ret = support::kill(pid(), SIGHUP);
 
                // The process is already dead if wait_for_death is false
                bool const wait_for_death = (ret == 0 && errno != ESRCH);
 
-               if (wait_for_death) {
+               if (wait_for_death)
                        Murder::killItDead(tolerance, pid());
-               }
        }
 }
 
 
+pid_t ForkedProcess::fork() {
+#if !defined (HAVE_FORK)
+       return -1;
+#else
+       pid_t pid = ::fork();
+       if (pid == 0)
+               IAmAChild = true;
+       return pid;
+#endif
+}
+
+
 // Wait for child process to finish. Returns returncode from child.
 int ForkedProcess::waitForChild()
 {
@@ -218,14 +219,14 @@ int ForkedProcess::waitForChild()
                DWORD exit_code = 0;
                if (!GetExitCodeProcess(hProcess, &exit_code)) {
                        lyxerr << "GetExitCodeProcess failed waiting for child\n"
-                              << getChildErrorMessage() << std::endl;
+                              << getChildErrorMessage() << endl;
                } else
                        retval_ = exit_code;
                break;
        }
        case WAIT_FAILED:
                lyxerr << "WaitForSingleObject failed waiting for child\n"
-                      << getChildErrorMessage() << std::endl;
+                      << getChildErrorMessage() << endl;
                break;
        }
 
@@ -269,6 +270,10 @@ int ForkedProcess::waitForChild()
 //
 /////////////////////////////////////////////////////////////////////
 
+ForkedCall::ForkedCall(string const & path)
+       : cmd_prefix_(to_filesystem8bit(from_utf8(latexEnvCmdPrefix(path))))
+{}
+
 
 int ForkedCall::startScript(Starttype wait, string const & what)
 {
@@ -277,7 +282,7 @@ int ForkedCall::startScript(Starttype wait, string const & what)
                return retval_;
        }
 
-       command_ = what;
+       command_ = trim(what);
        signal_.reset();
        return run(Wait);
 }
@@ -285,7 +290,7 @@ int ForkedCall::startScript(Starttype wait, string const & what)
 
 int ForkedCall::startScript(string const & what, SignalTypePtr signal)
 {
-       command_ = what;
+       command_ = trim(what);
        signal_  = signal;
 
        return run(DontWait);
@@ -295,10 +300,17 @@ int ForkedCall::startScript(string const & what, SignalTypePtr signal)
 // generate child in background
 int ForkedCall::generateChild()
 {
-       string line = trim(command_);
-       if (line.empty())
+       if (command_.empty())
                return 1;
 
+       // Make sure that a V2 python is run, if available.
+       string const line = cmd_prefix_ +
+               (prefixIs(command_, "python -tt")
+                ? os::python() + command_.substr(10) : command_);
+
+#if !defined (_WIN32)
+       // POSIX
+
        // Split the input command up into an array of words stored
        // in a contiguous block of memory. The array contains pointers
        // to each word.
@@ -315,37 +327,44 @@ int ForkedCall::generateChild()
        // 2. If we are inside quotes, then don't replace the white space
        //    but do remove the quotes themselves. We do this naively by
        //    replacing the quote with '\0' which is fine if quotes
-       //    delimit the entire word.
+       //    delimit the entire word. However, if quotes do not delimit the
+       //    entire word (i.e., open quote is inside word), simply discard
+       //    them such as not to break the current word.
        char inside_quote = 0;
+       char c_before_open_quote = ' ';
        vector<char>::iterator it = vec.begin();
+       vector<char>::iterator itc = vec.begin();
        vector<char>::iterator const end = vec.end();
-       for (; it != end; ++it) {
+       for (; it != end; ++it, ++itc) {
                char const c = *it;
                if (!inside_quote) {
-                       if (c == ' ')
-                               *it = '\0';
-                       else if (c == '\'' || c == '"') {
-#if defined (_WIN32)
-                               // How perverse!
-                               // spawnvp *requires* the quotes or it will
-                               // split the arg at the internal whitespace!
-                               // Make shure the quote is a DOS-style one.
-                               *it = '"';
-#else
-                               *it = '\0';
-#endif
+                       if (c == '\'' || c == '"') {
+                               if (c_before_open_quote == ' ')
+                                       *itc = '\0';
+                               else
+                                       --itc;
                                inside_quote = c;
+                       } else {
+                               if (c == ' ')
+                                       *itc = '\0';
+                               else
+                                       *itc = c;
+                               c_before_open_quote = c;
                        }
                } else if (c == inside_quote) {
-#if defined (_WIN32)
-                       *it = '"';
-#else
-                       *it = '\0';
-#endif
+                       if (c_before_open_quote == ' ')
+                               *itc = '\0';
+                       else
+                               --itc;
                        inside_quote = 0;
-               }
+               } else
+                       *itc = c;
        }
 
+       // Clear what remains.
+       for (; itc != end; ++itc)
+               *itc = '\0';
+
        // Build an array of pointers to each word.
        it = vec.begin();
        vector<char *> argv;
@@ -366,12 +385,9 @@ int ForkedCall::generateChild()
                for (; ait != aend; ++ait)
                        if (*ait)
                                lyxerr << '\t'<< *ait << '\n';
-               lyxerr << "</command>" << std::endl;
+               lyxerr << "</command>" << endl;
        }
 
-#ifdef _WIN32
-       pid_t const cpid = spawnvp(_P_NOWAIT, argv[0], &*argv.begin());
-#else // POSIX
        pid_t const cpid = ::fork();
        if (cpid == 0) {
                // Child
@@ -382,6 +398,24 @@ int ForkedCall::generateChild()
                       << strerror(errno) << endl;
                _exit(1);
        }
+#else
+       // Windows
+
+       pid_t cpid = -1;
+
+       STARTUPINFO startup; 
+       PROCESS_INFORMATION process; 
+
+       memset(&startup, 0, sizeof(STARTUPINFO));
+       memset(&process, 0, sizeof(PROCESS_INFORMATION));
+    
+       startup.cb = sizeof(STARTUPINFO);
+
+       if (CreateProcess(0, (LPSTR)line.c_str(), 0, 0, FALSE,
+               CREATE_NO_WINDOW, 0, 0, &startup, &process)) {
+               CloseHandle(process.hThread);
+               cpid = (pid_t)process.hProcess;
+       }
 #endif
 
        if (cpid < 0) {
@@ -399,15 +433,31 @@ int ForkedCall::generateChild()
 //
 /////////////////////////////////////////////////////////////////////
 
+namespace ForkedCallQueue {
 
-ForkedCallQueue & ForkedCallQueue::get()
-{
-       static ForkedCallQueue singleton;
-       return singleton;
-}
+/// A process in the queue
+typedef pair<string, ForkedCall::SignalTypePtr> Process;
+/** Add a process to the queue. Processes are forked sequentially
+ *  only one is running at a time.
+ *  Connect to the returned signal and you'll be informed when
+ *  the process has ended.
+ */
+ForkedCall::SignalTypePtr add(string const & process);
+
+/// in-progress queue
+static queue<Process> callQueue_;
+
+/// flag whether queue is running
+static bool running_ = 0;
 
+///
+void startCaller();
+///
+void stopCaller();
+///
+void callback(pid_t, int);
 
-ForkedCall::SignalTypePtr ForkedCallQueue::add(string const & process)
+ForkedCall::SignalTypePtr add(string const & process)
 {
        ForkedCall::SignalTypePtr ptr;
        ptr.reset(new ForkedCall::SignalType);
@@ -418,24 +468,23 @@ ForkedCall::SignalTypePtr ForkedCallQueue::add(string const & process)
 }
 
 
-void ForkedCallQueue::callNext()
+void callNext()
 {
        if (callQueue_.empty())
                return;
        Process pro = callQueue_.front();
        callQueue_.pop();
        // Bind our chain caller
-       pro.second->connect(boost::bind(&ForkedCallQueue::callback,
-                                        this, _1, _2));
+       pro.second->connect(lyx::bind(&ForkedCallQueue::callback, _1, _2));
        ForkedCall call;
-       // If we fail to fork the process, then emit the signal
-       // to tell the outside world that it failed.
+       //If we fail to fork the process, then emit the signal
+       //to tell the outside world that it failed.
        if (call.startScript(pro.first, pro.second) > 0)
                pro.second->operator()(0,1);
 }
 
 
-void ForkedCallQueue::callback(pid_t, int)
+void callback(pid_t, int)
 {
        if (callQueue_.empty())
                stopCaller();
@@ -444,12 +493,7 @@ void ForkedCallQueue::callback(pid_t, int)
 }
 
 
-ForkedCallQueue::ForkedCallQueue()
-       : running_(false)
-{}
-
-
-void ForkedCallQueue::startCaller()
+void startCaller()
 {
        LYXERR(Debug::GRAPHICS, "ForkedCallQueue: waking up");
        running_ = true ;
@@ -457,18 +501,21 @@ void ForkedCallQueue::startCaller()
 }
 
 
-void ForkedCallQueue::stopCaller()
+void stopCaller()
 {
        running_ = false ;
        LYXERR(Debug::GRAPHICS, "ForkedCallQueue: I'm going to sleep");
 }
 
 
-bool ForkedCallQueue::running() const
+bool running()
 {
-       return running_ ;
+       return running_;
 }
 
+} // namespace ForkedCallsQueue
+
+
 
 /////////////////////////////////////////////////////////////////////
 //
@@ -490,7 +537,7 @@ string const getChildErrorMessage()
                (LPTSTR) &t_message, 0, 0
                ) != 0;
 
-       std::ostringstream ss;
+       ostringstream ss;
        ss << "LyX: Error waiting for child: " << error_code;
 
        if (ok) {
@@ -504,33 +551,33 @@ string const getChildErrorMessage()
 #endif
 
 
-// Ensure, that only one controller exists inside process
-ForkedCallsController & ForkedCallsController::get()
-{
-       static ForkedCallsController singleton;
-       return singleton;
-}
+namespace ForkedCallsController {
 
+typedef shared_ptr<ForkedProcess> ForkedProcessPtr;
+typedef list<ForkedProcessPtr> ListType;
+typedef ListType::iterator iterator;
 
-ForkedCallsController::ForkedCallsController()
-{}
 
+/// The child processes
+static ListType forkedCalls;
 
-// open question: should we stop childs here?
-// Asger says no: I like to have my xdvi open after closing LyX. Maybe
-// I want to print or something.
-ForkedCallsController::~ForkedCallsController()
-{}
+iterator find_pid(pid_t pid)
+{
+       return find_if(forkedCalls.begin(), forkedCalls.end(),
+                           lyx::bind(equal_to<pid_t>(),
+                           lyx::bind(&ForkedCall::pid, _1),
+                           pid));
+}
 
 
-void ForkedCallsController::addCall(ForkedProcess const & newcall)
+void addCall(ForkedProcess const & newcall)
 {
        forkedCalls.push_back(newcall.clone());
 }
 
 
 // Check the list of dead children and emit any associated signals.
-void ForkedCallsController::handleCompletedProcesses()
+void handleCompletedProcesses()
 {
        ListType::iterator it  = forkedCalls.begin();
        ListType::iterator end = forkedCalls.end();
@@ -551,19 +598,21 @@ void ForkedCallsController::handleCompletedProcesses()
                        DWORD exit_code = 0;
                        if (!GetExitCodeProcess(hProcess, &exit_code)) {
                                lyxerr << "GetExitCodeProcess failed waiting for child\n"
-                                      << getChildErrorMessage() << std::endl;
+                                      << getChildErrorMessage() << endl;
                                // Child died, so pretend it returned 1
                                actCall->setRetValue(1);
                        } else {
                                actCall->setRetValue(exit_code);
                        }
+                       CloseHandle(hProcess);
                        remove_it = true;
                        break;
                }
                case WAIT_FAILED:
                        lyxerr << "WaitForSingleObject failed waiting for child\n"
-                              << getChildErrorMessage() << std::endl;
+                              << getChildErrorMessage() << endl;
                        actCall->setRetValue(1);
+                       CloseHandle(hProcess);
                        remove_it = true;
                        break;
                }
@@ -624,18 +673,9 @@ void ForkedCallsController::handleCompletedProcesses()
 }
 
 
-ForkedCallsController::iterator ForkedCallsController::find_pid(pid_t pid)
-{
-       return find_if(forkedCalls.begin(), forkedCalls.end(),
-                      bind(equal_to<pid_t>(),
-                           bind(&ForkedCall::pid, _1),
-                           pid));
-}
-
-
 // Kill the process prematurely and remove it from the list
 // within tolerance secs
-void ForkedCallsController::kill(pid_t pid, int tolerance)
+void kill(pid_t pid, int tolerance)
 {
        ListType::iterator it = find_pid(pid);
        if (it == forkedCalls.end())
@@ -645,5 +685,7 @@ void ForkedCallsController::kill(pid_t pid, int tolerance)
        forkedCalls.erase(it);
 }
 
+} // namespace ForkedCallsController
+
 } // namespace support
 } // namespace lyx