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 <thiago.macieira@intel.com>
Reviewed-by: Lars Knoll <lars.knoll@nokia.com>
This commit is contained in:
Rafael Roquetto 2012-02-15 16:52:43 +01:00 committed by Qt by Nokia
parent 147a38faa6
commit e3363fd945
4 changed files with 160 additions and 18 deletions

View 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__)

View 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.
*/

View 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();

View 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()
{