Enables QProcess back on QNX.
authorRafael Roquetto <rafael.roquetto@kdab.com>
Wed, 15 Feb 2012 15:52:43 +0000 (16:52 +0100)
committerQt by Nokia <qt-info@nokia.com>
Fri, 17 Feb 2012 07:46:40 +0000 (08:46 +0100)
Because fork()/vfork() on QNX are not supported on multithreaded applications,
QProcess had been disabled on this platform. The corresponding code has now
been replaced with functions that wrap around spawn().

Change-Id: I46091b7d41f322a5cad07d17893aa929c84941ef
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Lars Knoll <lars.knoll@nokia.com>
src/corelib/global/qglobal.h
src/corelib/io/qprocess.cpp
src/corelib/io/qprocess_p.h
src/corelib/io/qprocess_unix.cpp

index 22e73ed..7330a49 100644 (file)
@@ -1751,8 +1751,6 @@ Q_CORE_EXPORT int qrand();
 #  define QT_NO_QWS_SHARE_FONTS
 #  define QT_NO_SYSTEMSEMAPHORE
 #  define QT_NO_SHAREDMEMORY
-// QNX currently doesn't support forking in a thread, so disable QProcess
-#  define QT_NO_PROCESS
 #endif
 
 #if defined (__ELF__)
index 0a0097c..1312e9b 100644 (file)
@@ -520,6 +520,11 @@ void QProcessPrivate::Channel::clear()
     setWorkingDirectory(). By default, processes are run in the
     current working directory of the calling process.
 
+    \note On QNX, setting the working directory may cause all
+    application threads, with the exception of the QProcess caller
+    thread, to momentaneusly freeze, owing to a limitation in
+    the operating system.
+
     \section1 Synchronous Process API
 
     QProcess provides a set of functions which allow it to be used
@@ -1433,6 +1438,9 @@ QString QProcess::workingDirectory() const
     process in this directory. The default behavior is to start the
     process in the working directory of the calling process.
 
+    \note On QNX, this may cause all application threads to
+    momentaneusly freeze.
+
     \sa workingDirectory(), start()
 */
 void QProcess::setWorkingDirectory(const QString &dir)
@@ -1755,7 +1763,7 @@ void QProcess::setProcessState(ProcessState state)
     exit().
 
     \warning This function is called by QProcess on Unix and Mac OS X
-    only. On Windows, it is not called.
+    only. On Windows and QNX, it is not called.
 */
 void QProcess::setupChildProcess()
 {
@@ -2149,6 +2157,9 @@ int QProcess::execute(const QString &program)
 
     The process will be started in the directory \a workingDirectory.
 
+    \note On QNX, this may cause all application threads to
+    momentaneusly freeze.
+
     If the function is successful then *\a pid is set to the process
     identifier of the started process.
 */
index 236e716..311b5ce 100644 (file)
@@ -304,8 +304,10 @@ public:
 #endif
 
     void startProcess();
-#if defined(Q_OS_UNIX)
+#if defined(Q_OS_UNIX) && !defined(Q_OS_QNX)
     void execChild(const char *workingDirectory, char **path, char **argv, char **envp);
+#elif defined(Q_OS_QNX)
+    pid_t spawnChild(const char *workingDirectory, char **argv, char **envp);
 #endif
     bool processStarted();
     void terminateProcess();
index 9e35978..2da2913 100644 (file)
@@ -48,6 +48,7 @@
 #include "qstring.h"
 #include <ctype.h>
 
+
 /*
     Returns a human readable representation of the first \a len
     characters in \a data.
@@ -105,6 +106,10 @@ QT_END_NAMESPACE
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
+#ifdef Q_OS_QNX
+#include <spawn.h>
+#include <sys/neutrino.h>
+#endif
 
 QT_BEGIN_NAMESPACE
 
@@ -538,16 +543,6 @@ static char **_q_dupEnvironment(const QProcessEnvironmentPrivate::Hash &environm
     return envp;
 }
 
-// under QNX RTOS we have to use vfork() when multithreading
-inline pid_t qt_fork()
-{
-#if defined(Q_OS_QNX)
-    return vfork();
-#else
-    return fork();
-#endif
-}
-
 void QProcessPrivate::startProcess()
 {
     Q_Q(QProcess);
@@ -664,8 +659,12 @@ void QProcessPrivate::startProcess()
 
     // Start the process manager, and fork off the child process.
     processManager()->lock();
-    pid_t childPid = qt_fork();
+#if defined(Q_OS_QNX)
+    pid_t childPid = spawnChild(workingDirPtr, argv, envp);
+#else
+    pid_t childPid = fork();
     int lastForkErrno = errno;
+#endif
     if (childPid != 0) {
         // Clean up duplicated memory.
         free(dupProgramName);
@@ -679,10 +678,22 @@ void QProcessPrivate::startProcess()
         delete [] envp;
         delete [] path;
     }
+
+    // This is not a valid check under QNX, because the semantics are
+    // different. While under other platforms where fork() may succeed and exec() can still fail,
+    // causing the childPid to hold a valid value (and thus evaluating the
+    // following if to false), and then signaling the error via
+    // childStartedPipe, under QNX on the other hand, spawn() return value will be assigned
+    // to childPid (which will be -1 in case of failure). This will force
+    // QProcess to cleanup, instead of signaling the error via
+    // childStartedPipe. Since it will invalidade the pipes, functions like
+    // QProcess::waitForStarted() will fail, for childStartedPipe will be
+    // '-1' and mess with the select() calls.
+#if !defined(Q_OS_QNX)
     if (childPid < 0) {
         // Cleanup, report error and return
 #if defined (QPROCESS_DEBUG)
-        qDebug("qt_fork failed: %s", qPrintable(qt_error_string(lastForkErrno)));
+        qDebug("fork failed: %s", qPrintable(qt_error_string(lastForkErrno)));
 #endif
         processManager()->unlock();
         q->setProcessState(QProcess::NotRunning);
@@ -698,6 +709,7 @@ void QProcessPrivate::startProcess()
         execChild(workingDirPtr, path, argv, envp);
         ::_exit(-1);
     }
+#endif
 
     // Register the child. In the mean time, we can get a SIGCHLD, so we need
     // to keep the lock held to avoid a race to catch the child.
@@ -735,6 +747,87 @@ void QProcessPrivate::startProcess()
         ::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK);
 }
 
+#if defined(Q_OS_QNX)
+static pid_t doSpawn(int fd_count, int fd_map[], char **argv, char **envp,
+        const char *workingDir, bool spawn_detached)
+{
+    // A multi threaded QNX Process can't fork so we call spawn() instead.
+
+    struct inheritance inherit;
+    memset(&inherit, 0, sizeof(inherit));
+    inherit.flags |= SPAWN_SETSID;
+    inherit.flags |= SPAWN_CHECK_SCRIPT;
+    if (spawn_detached)
+        inherit.flags |= SPAWN_NOZOMBIE;
+    inherit.flags |= SPAWN_SETSIGDEF;
+    sigaddset(&inherit.sigdefault, SIGPIPE); // reset the signal that we ignored
+
+    // enter the working directory
+    const char *oldWorkingDir = 0;
+    char buff[PATH_MAX + 1];
+
+    if (workingDir) {
+        //we need to freeze everyone in order to avoid race conditions with //chdir().
+        if (ThreadCtl(_NTO_TCTL_THREADS_HOLD, 0) == -1)
+            qWarning("ThreadCtl(): cannot hold threads: %s", qPrintable(qt_error_string(errno)));
+
+        oldWorkingDir = QT_GETCWD(buff, PATH_MAX + 1);
+        QT_CHDIR(workingDir);
+    }
+
+    pid_t childPid;
+    EINTR_LOOP(childPid, ::spawn(argv[0], fd_count, fd_map, &inherit, argv, envp));
+    if (childPid == -1) {
+        inherit.flags |= SPAWN_SEARCH_PATH;
+        EINTR_LOOP(childPid, ::spawn(argv[0], fd_count, fd_map, &inherit, argv, envp));
+    }
+
+    if (oldWorkingDir) {
+        QT_CHDIR(oldWorkingDir);
+
+        if (ThreadCtl(_NTO_TCTL_THREADS_CONT, 0) == -1)
+            qFatal("ThreadCtl(): cannot resume threads: %s", qPrintable(qt_error_string(errno)));
+    }
+
+    return childPid;
+}
+
+pid_t QProcessPrivate::spawnChild(const char *workingDir, char **argv, char **envp)
+{
+    const int fd_count = 3;
+    int fd_map[fd_count];
+    switch (processChannelMode) {
+    case QProcess::ForwardedChannels:
+        fd_map[0] = stdinChannel.pipe[0];
+        fd_map[1] = QT_FILENO(stdout);
+        fd_map[2] = QT_FILENO(stderr);
+        break;
+    case QProcess::MergedChannels:
+        fd_map[0] = stdinChannel.pipe[0];
+        fd_map[1] = stdoutChannel.pipe[1];
+        fd_map[2] = stdoutChannel.pipe[1];
+        break;
+    case QProcess::SeparateChannels:
+        fd_map[0] = stdinChannel.pipe[0];
+        fd_map[1] = stdoutChannel.pipe[1];
+        fd_map[2] = stderrChannel.pipe[1];
+        break;
+    }
+
+    pid_t childPid = doSpawn(fd_count, fd_map, argv, envp, workingDir, false);
+
+    if (childPid == -1) {
+        QString error = qt_error_string(errno);
+        qt_safe_write(childStartedPipe[1], error.data(), error.length() * sizeof(QChar));
+        qt_safe_close(childStartedPipe[1]);
+        childStartedPipe[1] = -1;
+    }
+
+    return childPid;
+}
+
+#else
+
 void QProcessPrivate::execChild(const char *workingDir, char **path, char **argv, char **envp)
 {
     ::signal(SIGPIPE, SIG_DFL);         // reset the signal that we ignored
@@ -797,6 +890,7 @@ void QProcessPrivate::execChild(const char *workingDir, char **path, char **argv
     qt_safe_close(childStartedPipe[1]);
     childStartedPipe[1] = -1;
 }
+#endif
 
 bool QProcessPrivate::processStarted()
 {
@@ -1213,6 +1307,42 @@ void QProcessPrivate::_q_notified()
 {
 }
 
+#if defined(Q_OS_QNX)
+bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
+{
+    const int fd_count = 3;
+    int fd_map[fd_count] = { QT_FILENO(stdin), QT_FILENO(stdout), QT_FILENO(stderr) };
+
+    QList<QByteArray> enc_args;
+    enc_args.append(QFile::encodeName(program));
+    for (int i = 0; i < arguments.size(); ++i)
+        enc_args.append(arguments.at(i).toLocal8Bit());
+
+    const int argc = enc_args.size();
+    QScopedArrayPointer<char*> raw_argv(new char*[argc + 1]);
+    for (int i = 0; i < argc; ++i)
+        raw_argv[i] = const_cast<char *>(enc_args.at(i).data());
+    raw_argv[argc] = 0;
+
+    char **envp = 0; // inherit environment
+
+    // Encode the working directory if it's non-empty, otherwise just pass 0.
+    const char *workingDirPtr = 0;
+    QByteArray encodedWorkingDirectory;
+    if (!workingDirectory.isEmpty()) {
+        encodedWorkingDirectory = QFile::encodeName(workingDirectory);
+        workingDirPtr = encodedWorkingDirectory.constData();
+    }
+
+    pid_t childPid = doSpawn(fd_count, fd_map, raw_argv.data(), envp, workingDirPtr, true);
+    if (pid && childPid != -1)
+        *pid = childPid;
+
+    return childPid != -1;
+}
+
+#else
+
 bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
 {
     processManager()->start();
@@ -1226,7 +1356,7 @@ bool QProcessPrivate::startDetached(const QString &program, const QStringList &a
     int pidPipe[2];
     qt_safe_pipe(pidPipe);
 
-    pid_t childPid = qt_fork();
+    pid_t childPid = fork();
     if (childPid == 0) {
         struct sigaction noaction;
         memset(&noaction, 0, sizeof(noaction));
@@ -1238,7 +1368,7 @@ bool QProcessPrivate::startDetached(const QString &program, const QStringList &a
         qt_safe_close(startedPipe[0]);
         qt_safe_close(pidPipe[0]);
 
-        pid_t doubleForkPid = qt_fork();
+        pid_t doubleForkPid = fork();
         if (doubleForkPid == 0) {
             qt_safe_close(pidPipe[1]);
 
@@ -1326,6 +1456,7 @@ bool QProcessPrivate::startDetached(const QString &program, const QStringList &a
     qt_safe_close(pidPipe[0]);
     return success;
 }
+#endif
 
 void QProcessPrivate::initializeProcessManager()
 {