From ae39f2286b6fb5ddc63223bab4b3fab3736b4ee8 Mon Sep 17 00:00:00 2001 From: Rafael Roquetto Date: Tue, 14 Feb 2012 23:14:14 +0100 Subject: Fixes QProcess on QNX This patch first re-enables childStartedPipe on QNX and uses, just like other platforms, to monitor the inferior process life cycle. This also aligns QNX-specific code logic closer to the common QProcess logic, making it unecessary to have custom versions of functions such as QProcess::waitForStarted(). The only difference that remains is the use of spawn() instead of fork(), because both fork() and vfork() do not support multi-threaded applications on QNX and will segfault. Change-Id: I8e1f9823629fcb5e7c3c128fafc2542a403b969e Reviewed-by: Sean Harmer Reviewed-by: Thiago Macieira --- src/corelib/io/qprocess.cpp | 23 ++++---- src/corelib/io/qprocess_p.h | 4 -- src/corelib/io/qprocess_unix.cpp | 122 ++++++++++++++++++--------------------- 3 files changed, 67 insertions(+), 82 deletions(-) diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index d6a9f2e..060f503 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -528,6 +528,11 @@ void QProcessPrivate::Channel::clear() is not supported. The working directory will always be the private directory of the running process. + \note On QNX, setting the working directory may cause all + application threads, with the exception of the QProcess caller + thread, to temporarily 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 @@ -751,16 +756,12 @@ QProcessPrivate::QProcessPrivate() sequenceNumber = 0; exitCode = 0; exitStatus = QProcess::NormalExit; -#ifndef Q_OS_QNX startupSocketNotifier = 0; -#endif deathNotifier = 0; notifier = 0; pipeWriter = 0; -#ifndef Q_OS_QNX childStartedPipe[0] = INVALID_Q_PIPE; childStartedPipe[1] = INVALID_Q_PIPE; -#endif deathPipe[0] = INVALID_Q_PIPE; deathPipe[1] = INVALID_Q_PIPE; exitCode = 0; @@ -829,13 +830,11 @@ void QProcessPrivate::cleanup() qDeleteInEventHandler(stdinChannel.notifier); stdinChannel.notifier = 0; } -#ifndef Q_OS_QNX if (startupSocketNotifier) { startupSocketNotifier->setEnabled(false); qDeleteInEventHandler(startupSocketNotifier); startupSocketNotifier = 0; } -#endif if (deathNotifier) { deathNotifier->setEnabled(false); qDeleteInEventHandler(deathNotifier); @@ -848,9 +847,7 @@ void QProcessPrivate::cleanup() destroyPipe(stdoutChannel.pipe); destroyPipe(stderrChannel.pipe); destroyPipe(stdinChannel.pipe); -#ifndef Q_OS_QNX destroyPipe(childStartedPipe); -#endif destroyPipe(deathPipe); #ifdef Q_OS_UNIX serial = 0; @@ -1085,10 +1082,8 @@ bool QProcessPrivate::_q_startupNotification() qDebug("QProcessPrivate::startupNotification()"); #endif -#ifndef Q_OS_QNX if (startupSocketNotifier) startupSocketNotifier->setEnabled(false); -#endif if (processStarted()) { q->setProcessState(QProcess::Running); emit q->started(); @@ -1467,6 +1462,9 @@ QString QProcess::workingDirectory() const the private directory of the process is considered its working directory. + \note On QNX, this may cause all application threads to + temporarily freeze. + \sa workingDirectory(), start() */ void QProcess::setWorkingDirectory(const QString &dir) @@ -1789,7 +1787,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() { @@ -2197,6 +2195,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 + temporarily 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 81ba9bc..6eeeead 100644 --- a/src/corelib/io/qprocess_p.h +++ b/src/corelib/io/qprocess_p.h @@ -288,15 +288,11 @@ public: QRingBuffer errorReadBuffer; QRingBuffer writeBuffer; -#ifndef Q_OS_QNX Q_PIPE childStartedPipe[2]; -#endif Q_PIPE deathPipe[2]; void destroyPipe(Q_PIPE pipe[2]); -#ifndef Q_OS_QNX QSocketNotifier *startupSocketNotifier; -#endif QSocketNotifier *deathNotifier; // the wonderful windows notifier diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp index 44adab0..b699611 100644 --- a/src/corelib/io/qprocess_unix.cpp +++ b/src/corelib/io/qprocess_unix.cpp @@ -106,7 +106,8 @@ QT_END_NAMESPACE #include #include #ifdef Q_OS_QNX -# include +#include +#include #endif @@ -544,18 +545,14 @@ void QProcessPrivate::startProcess() !createChannel(stdoutChannel) || !createChannel(stderrChannel)) return; -#if !defined(Q_OS_QNX) qt_create_pipe(childStartedPipe); -#endif qt_create_pipe(deathPipe); if (threadData->eventDispatcher) { -#if !defined(Q_OS_QNX) startupSocketNotifier = new QSocketNotifier(childStartedPipe[0], QSocketNotifier::Read, q); QObject::connect(startupSocketNotifier, SIGNAL(activated(int)), q, SLOT(_q_startupNotification())); -#endif deathNotifier = new QSocketNotifier(deathPipe[0], QSocketNotifier::Read, q); QObject::connect(deathNotifier, SIGNAL(activated(int)), @@ -651,8 +648,8 @@ void QProcessPrivate::startProcess() pid_t childPid = spawnChild(workingDirPtr, argv, envp); #else pid_t childPid = fork(); -#endif int lastForkErrno = errno; +#endif if (childPid != 0) { // Clean up duplicated memory. free(dupProgramName); @@ -666,6 +663,18 @@ 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) @@ -680,7 +689,6 @@ void QProcessPrivate::startProcess() return; } -#if !defined(Q_OS_QNX) // Start the child. if (childPid == 0) { execChild(workingDirPtr, path, argv, envp); @@ -697,10 +705,8 @@ void QProcessPrivate::startProcess() // parent // close the ends we don't use and make all pipes non-blocking ::fcntl(deathPipe[0], F_SETFL, ::fcntl(deathPipe[0], F_GETFL) | O_NONBLOCK); -#if !defined(Q_OS_QNX) qt_safe_close(childStartedPipe[1]); childStartedPipe[1] = -1; -#endif if (stdinChannel.pipe[0] != -1) { qt_safe_close(stdinChannel.pipe[0]); @@ -788,6 +794,8 @@ void QProcessPrivate::execChild(const char *workingDir, char **path, char **argv childStartedPipe[1] = -1; } +#endif //Q_OS_QNX + bool QProcessPrivate::processStarted() { ushort buf[errorBufferMax]; @@ -811,9 +819,9 @@ bool QProcessPrivate::processStarted() return i <= 0; } -#else // Q_OS_QNX - -static pid_t doSpawn(int fd_count, int fd_map[], char **argv, char **envp, bool spawn_detached) +#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. @@ -826,6 +834,19 @@ static pid_t doSpawn(int fd_count, int fd_map[], char **argv, char **envp, bool 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) { @@ -833,13 +854,18 @@ static pid_t doSpawn(int fd_count, int fd_map[], char **argv, char **envp, bool 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) { - Q_Q(QProcess); - const int fd_count = 3; int fd_map[fd_count]; switch (processChannelMode) { @@ -860,36 +886,17 @@ pid_t QProcessPrivate::spawnChild(const char *workingDir, char **argv, char **en break; } - // enter the working directory - char *oldWorkingDir = 0; - char buff[PATH_MAX + 1]; - if (workingDir) { - oldWorkingDir = QT_GETCWD(buff, PATH_MAX + 1); - QT_CHDIR(workingDir); - } + pid_t childPid = doSpawn(fd_count, fd_map, argv, envp, workingDir, false); - pid_t childPid = doSpawn(fd_count, fd_map, argv, envp, false); - - if (oldWorkingDir) - QT_CHDIR(oldWorkingDir); - - if (childPid != -1) { - q->setProcessState(QProcess::Running); - QMetaObject::invokeMethod(q, "_q_startupNotification", Qt::QueuedConnection); + 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; } - -bool QProcessPrivate::processStarted() -{ - return processState == QProcess::Running; -} - -bool QProcessPrivate::waitForStarted(int /*msecs*/) -{ - return processStarted(); -} #endif // Q_OS_QNX qint64 QProcessPrivate::bytesAvailableFromStdout() const @@ -1009,7 +1016,6 @@ static int qt_timeout_value(int msecs, int elapsed) return timeout < 0 ? 0 : timeout; } -#if !defined(Q_OS_QNX) bool QProcessPrivate::waitForStarted(int msecs) { Q_Q(QProcess); @@ -1037,7 +1043,6 @@ bool QProcessPrivate::waitForStarted(int msecs) #endif return startedEmitted; } -#endif // Q_OS_QNX bool QProcessPrivate::waitForReadyRead(int msecs) { @@ -1059,10 +1064,8 @@ bool QProcessPrivate::waitForReadyRead(int msecs) int nfds = deathPipe[0]; FD_SET(deathPipe[0], &fdread); -#if !defined(Q_OS_QNX) if (processState == QProcess::Starting) add_fd(nfds, childStartedPipe[0], &fdread); -#endif if (stdoutChannel.pipe[0] != -1) add_fd(nfds, stdoutChannel.pipe[0], &fdread); @@ -1083,12 +1086,10 @@ bool QProcessPrivate::waitForReadyRead(int msecs) return false; } -#if !defined(Q_OS_QNX) if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) { if (!_q_startupNotification()) return false; } -#endif bool readyReadEmitted = false; if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread)) { @@ -1135,10 +1136,8 @@ bool QProcessPrivate::waitForBytesWritten(int msecs) int nfds = deathPipe[0]; FD_SET(deathPipe[0], &fdread); -#if !defined(Q_OS_QNX) if (processState == QProcess::Starting) add_fd(nfds, childStartedPipe[0], &fdread); -#endif if (stdoutChannel.pipe[0] != -1) add_fd(nfds, stdoutChannel.pipe[0], &fdread); @@ -1161,12 +1160,10 @@ bool QProcessPrivate::waitForBytesWritten(int msecs) return false; } -#if !defined(Q_OS_QNX) if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) { if (!_q_startupNotification()) return false; } -#endif if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite)) return _q_canWrite(); @@ -1204,10 +1201,8 @@ bool QProcessPrivate::waitForFinished(int msecs) FD_ZERO(&fdread); FD_ZERO(&fdwrite); -#if !defined(Q_OS_QNX) if (processState == QProcess::Starting) add_fd(nfds, childStartedPipe[0], &fdread); -#endif if (stdoutChannel.pipe[0] != -1) add_fd(nfds, stdoutChannel.pipe[0], &fdread); @@ -1231,12 +1226,10 @@ bool QProcessPrivate::waitForFinished(int msecs) return false; } -#if !defined(Q_OS_QNX) if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) { if (!_q_startupNotification()) return false; } -#endif if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite)) _q_canWrite(); @@ -1301,16 +1294,6 @@ void QProcessPrivate::_q_notified() #if defined(Q_OS_QNX) bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid) { - QByteArray encodedWorkingDirectory = QFile::encodeName(workingDirectory); - - // enter the working directory - char *oldWorkingDir = 0; - char buff[PATH_MAX + 1]; - if (!encodedWorkingDirectory.isEmpty()) { - oldWorkingDir = QT_GETCWD(buff, PATH_MAX + 1); - QT_CHDIR(encodedWorkingDirectory.constData()); - } - const int fd_count = 3; int fd_map[fd_count] = { QT_FILENO(stdin), QT_FILENO(stdout), QT_FILENO(stderr) }; @@ -1327,13 +1310,18 @@ bool QProcessPrivate::startDetached(const QString &program, const QStringList &a char **envp = 0; // inherit environment - pid_t childPid = doSpawn(fd_count, fd_map, raw_argv.data(), envp, true); + // 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; - if (oldWorkingDir) - QT_CHDIR(oldWorkingDir); - return childPid != -1; } -- cgit v0.12