From e3363fd945b3ab961fba720ee013533dd8ba2930 Mon Sep 17 00:00:00 2001 From: Rafael Roquetto Date: Wed, 15 Feb 2012 16:52:43 +0100 Subject: Enables QProcess back on QNX. 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 Reviewed-by: Lars Knoll --- src/corelib/global/qglobal.h | 2 - src/corelib/io/qprocess.cpp | 13 +++- src/corelib/io/qprocess_p.h | 4 +- src/corelib/io/qprocess_unix.cpp | 159 +++++++++++++++++++++++++++++++++++---- 4 files changed, 160 insertions(+), 18 deletions(-) diff --git a/src/corelib/global/qglobal.h b/src/corelib/global/qglobal.h index 22e73ede26..7330a49939 100644 --- a/src/corelib/global/qglobal.h +++ b/src/corelib/global/qglobal.h @@ -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__) diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index 0a0097c887..1312e9b55f 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -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. */ diff --git a/src/corelib/io/qprocess_p.h b/src/corelib/io/qprocess_p.h index 236e716b34..311b5ce845 100644 --- a/src/corelib/io/qprocess_p.h +++ b/src/corelib/io/qprocess_p.h @@ -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(); diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp index 9e35978c77..2da2913c6f 100644 --- a/src/corelib/io/qprocess_unix.cpp +++ b/src/corelib/io/qprocess_unix.cpp @@ -48,6 +48,7 @@ #include "qstring.h" #include + /* Returns a human readable representation of the first \a len characters in \a data. @@ -105,6 +106,10 @@ QT_END_NAMESPACE #include #include #include +#ifdef Q_OS_QNX +#include +#include +#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 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 raw_argv(new char*[argc + 1]); + for (int i = 0; i < argc; ++i) + raw_argv[i] = const_cast(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() { -- cgit v1.2.3