/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
//#define QPROCESS_DEBUG
#include "qdebug.h"
#ifndef QT_NO_PROCESS
#if defined QPROCESS_DEBUG
#include "qstring.h"
#include <ctype.h>
/*
Returns a human readable representation of the first \a len
characters in \a data.
*/
QT_BEGIN_NAMESPACE
static QByteArray qt_prettyDebug(const char *data, int len, int maxSize)
{
if (!data) return "(null)";
QByteArray out;
for (int i = 0; i < len; ++i) {
char c = data[i];
if (isprint(c)) {
out += c;
} else switch (c) {
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
default:
QString tmp;
tmp.sprintf("\\%o", c);
out += tmp.toLatin1();
}
}
if (len < maxSize)
out += "...";
return out;
}
QT_END_NAMESPACE
#endif
#include "qplatformdefs.h"
#include "qprocess.h"
#include "qprocess_p.h"
#include "private/qcore_unix_p.h"
#ifdef Q_OS_MAC
#include <private/qcore_mac_p.h>
#endif
#include <private/qcoreapplication_p.h>
#include <private/qthread_p.h>
#include <qdatetime.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qlist.h>
#include <qmap.h>
#include <qmutex.h>
#include <qsemaphore.h>
#include <qsocketnotifier.h>
#include <qthread.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
QT_BEGIN_NAMESPACE
// POSIX requires PIPE_BUF to be 512 or larger
// so we will use 512
static const int errorBufferMax = 512;
#ifdef Q_OS_INTEGRITY
static inline char *strdup(const char *data)
{
return qstrdup(data);
}
#endif
static int qt_qprocess_deadChild_pipe[2];
static void (*qt_sa_old_sigchld_handler)(int) = 0;
static void qt_sa_sigchld_handler(int signum)
{
qt_safe_write(qt_qprocess_deadChild_pipe[1], "", 1);
#if defined (QPROCESS_DEBUG)
fprintf(stderr, "*** SIGCHLD\n");
#endif
if (qt_sa_old_sigchld_handler && qt_sa_old_sigchld_handler != SIG_IGN)
qt_sa_old_sigchld_handler(signum);
}
static inline void add_fd(int &nfds, int fd, fd_set *fdset)
{
FD_SET(fd, fdset);
if ((fd) > nfds)
nfds = fd;
}
struct QProcessInfo {
QProcess *process;
int deathPipe;
int exitResult;
pid_t pid;
int serialNumber;
};
class QProcessManager : public QThread
{
Q_OBJECT
public:
QProcessManager();
~QProcessManager();
void run();
void catchDeadChildren();
void add(pid_t pid, QProcess *process);
void remove(QProcess *process);
void lock();
void unlock();
private:
QMutex mutex;
QMap<int, QProcessInfo *> children;
};
Q_GLOBAL_STATIC(QMutex, processManagerGlobalMutex)
static QProcessManager *processManager() {
// The constructor of QProcessManager should be called only once
// so we cannot use Q_GLOBAL_STATIC directly for QProcessManager
QMutex *mutex = processManagerGlobalMutex();
QMutexLocker locker(mutex);
static QProcessManager processManager;
return &processManager;
}
QProcessManager::QProcessManager()
{
#if defined (QPROCESS_DEBUG)
qDebug() << "QProcessManager::QProcessManager()";
#endif
// initialize the dead child pipe and make it non-blocking. in the
// extremely unlikely event that the pipe fills up, we do not under any
// circumstances want to block.
qt_safe_pipe(qt_qprocess_deadChild_pipe, O_NONBLOCK);
// set up the SIGCHLD handler, which writes a single byte to the dead
// child pipe every time a child dies.
struct sigaction oldAction;
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = qt_sa_sigchld_handler;
action.sa_flags = SA_NOCLDSTOP;
::sigaction(SIGCHLD, &action, &oldAction);
if (oldAction.sa_handler != qt_sa_sigchld_handler)
qt_sa_old_sigchld_handler = oldAction.sa_handler;
}
QProcessManager::~QProcessManager()
{
// notify the thread that we're shutting down.
qt_safe_write(qt_qprocess_deadChild_pipe[1], "@", 1);
qt_safe_close(qt_qprocess_deadChild_pipe[1]);
wait();
// on certain unixes, closing the reading end of the pipe will cause
// select in run() to block forever, rather than return with EBADF.
qt_safe_close(qt_qprocess_deadChild_pipe[0]);
qt_qprocess_deadChild_pipe[0] = -1;
qt_qprocess_deadChild_pipe[1] = -1;
qDeleteAll(children.values());
children.clear();
struct sigaction oldAction;
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = qt_sa_old_sigchld_handler;
action.sa_flags = SA_NOCLDSTOP;
::sigaction(SIGCHLD, &action, &oldAction);
if (oldAction.sa_handler != qt_sa_sigchld_handler) {
::sigaction(SIGCHLD, &oldAction, 0);
}
}
void QProcessManager::run()
{
forever {
fd_set readset;
FD_ZERO(&readset);
FD_SET(qt_qprocess_deadChild_pipe[0], &readset);
#if defined (QPROCESS_DEBUG)
qDebug() << "QProcessManager::run() waiting for children to die";
#endif
// block forever, or until activity is detected on the dead child
// pipe. the only other peers are the SIGCHLD signal handler, and the
// QProcessManager destructor.
int nselect = select(qt_qprocess_deadChild_pipe[0] + 1, &readset, 0, 0, 0);
if (nselect < 0) {
if (errno == EINTR)
continue;
break;
}
// empty only one byte from the pipe, even though several SIGCHLD
// signals may have been delivered in the meantime, to avoid race
// conditions.
char c;
if (qt_safe_read(qt_qprocess_deadChild_pipe[0], &c, 1) < 0 || c == '@')
break;
// catch any and all children that we can.
catchDeadChildren();
}
}
void QProcessManager::catchDeadChildren()
{
QMutexLocker locker(&mutex);
// try to catch all children whose pid we have registered, and whose
// deathPipe is still valid (i.e, we have not already notified it).
QMap<int, QProcessInfo *>::Iterator it = children.begin();
while (it != children.end()) {
// notify all children that they may have died. they need to run
// waitpid() in their own thread.
QProcessInfo *info = it.value();
qt_safe_write(info->deathPipe, "", 1);
#if defined (QPROCESS_DEBUG)
qDebug() << "QProcessManager::run() sending death notice to" << info->process;
#endif
++it;
}
}
static QBasicAtomicInt idCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
void QProcessManager::add(pid_t pid, QProcess *process)
{
#if defined (QPROCESS_DEBUG)
qDebug() << "QProcessManager::add() adding pid" << pid << "process" << process;
#endif
// insert a new info structure for this process
QProcessInfo *info = new QProcessInfo;
info->process = process;
info->deathPipe = process->d_func()->deathPipe[1];
info->exitResult = 0;
info->pid = pid;
int serial = idCounter.fetchAndAddRelaxed(1);
process->d_func()->serial = serial;
children.insert(serial, info);
}
void QProcessManager::remove(QProcess *process)
{
QMutexLocker locker(&mutex);
int serial = process->d_func()->serial;
QProcessInfo *info = children.value(serial);
if (!info)
return;
#if defined (QPROCESS_DEBUG)
qDebug() << "QProcessManager::remove() removing pid" << info->pid << "process" << info->process;
#endif
children.remove(serial);
delete info;
}
void QProcessManager::lock()
{
mutex.lock();
}
void QProcessManager::unlock()
{
mutex.unlock();
}
static void qt_create_pipe(int *pipe)
{
if (pipe[0] != -1)
qt_safe_close(pipe[0]);
if (pipe[1] != -1)
qt_safe_close(pipe[1]);
if (qt_safe_pipe(pipe) != 0) {
qWarning("QProcessPrivate::createPipe: Cannot create pipe %p: %s",
pipe, qPrintable(qt_error_string(errno)));
}
}
void QProcessPrivate::destroyPipe(int *pipe)
{
if (pipe[1] != -1) {
qt_safe_close(pipe[1]);
pipe[1] = -1;
}
if (pipe[0] != -1) {
qt_safe_close(pipe[0]);
pipe[0] = -1;
}
}
/*
Create the pipes to a QProcessPrivate::Channel.
This function must be called in order: stdin, stdout, stderr
*/
bool QProcessPrivate::createChannel(Channel &channel)
{
Q_Q(QProcess);
if (&channel == &stderrChannel && processChannelMode == QProcess::MergedChannels) {
channel.pipe[0] = -1;
channel.pipe[1] = -1;
return true;
}
if (channel.type == Channel::Normal) {
// we're piping this channel to our own process
qt_create_pipe(channel.pipe);
// create the socket notifiers
if (threadData->eventDispatcher) {
if (&channel == &stdinChannel) {
channel.notifier = new QSocketNotifier(channel.pipe[1],
QSocketNotifier::Write, q);
channel.notifier->setEnabled(false);
QObject::connect(channel.notifier, SIGNAL(activated(int)),
q, SLOT(_q_canWrite()));
} else {
channel.notifier = new QSocketNotifier(channel.pipe[0],
QSocketNotifier::Read, q);
const char *receiver;
if (&channel == &stdoutChannel)
receiver = SLOT(_q_canReadStandardOutput());
else
receiver = SLOT(_q_canReadStandardError());
QObject::connect(channel.notifier, SIGNAL(activated(int)),
q, receiver);
}
}
return true;
} else if (channel.type == Channel::Redirect) {
// we're redirecting the channel to/from a file
QByteArray fname = QFile::encodeName(channel.file);
if (&channel == &stdinChannel) {
// try to open in read-only mode
channel.pipe[1] = -1;
if ( (channel.pipe[0] = qt_safe_open(fname, O_RDONLY)) != -1)
return true; // success
q->setErrorString(QProcess::tr("Could not open input redirection for reading"));
} else {
int mode = O_WRONLY | O_CREAT;
if (channel.append)
mode |= O_APPEND;
else
mode |= O_TRUNC;
channel.pipe[0] = -1;
if ( (channel.pipe[1] = qt_safe_open(fname, mode, 0666)) != -1)
return true; // success
q->setErrorString(QProcess::tr("Could not open output redirection for writing"));
}
// could not open file
processError = QProcess::FailedToStart;
emit q->error(processError);
cleanup();
return false;
} else {
Q_ASSERT_X(channel.process, "QProcess::start", "Internal error");
Channel *source;
Channel *sink;
if (channel.type == Channel::PipeSource) {
// we are the source
source = &channel;
sink = &channel.process->stdinChannel;
Q_ASSERT(source == &stdoutChannel);
Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink);
} else {
// we are the sink;
source = &channel.process->stdoutChannel;
sink = &channel;
Q_ASSERT(sink == &stdinChannel);
Q_ASSERT(source->process == this && source->type == Channel::PipeSource);
}
if (source->pipe[1] != INVALID_Q_PIPE || sink->pipe[0] != INVALID_Q_PIPE) {
// already created, do nothing
return true;
} else {
Q_ASSERT(source->pipe[0] == INVALID_Q_PIPE && source->pipe[1] == INVALID_Q_PIPE);
Q_ASSERT(sink->pipe[0] == INVALID_Q_PIPE && sink->pipe[1] == INVALID_Q_PIPE);
Q_PIPE pipe[2] = { -1, -1 };
qt_create_pipe(pipe);
sink->pipe[0] = pipe[0];
source->pipe[1] = pipe[1];
return true;
}
}
}
static char **_q_dupEnvironment(const QHash<QByteArray, QByteArray> &environment, int *envc)
{
*envc = 0;
if (environment.isEmpty())
return 0;
// if LD_LIBRARY_PATH exists in the current environment, but
// not in the environment list passed by the programmer, then
// copy it over.
#if defined(Q_OS_MAC)
static const char libraryPath[] = "DYLD_LIBRARY_PATH";
#else
static const char libraryPath[] = "LD_LIBRARY_PATH";
#endif
const QByteArray envLibraryPath = qgetenv(libraryPath);
bool needToAddLibraryPath = !envLibraryPath.isEmpty() &&
!environment.contains(libraryPath);
char **envp = new char *[environment.count() + 2];
envp[environment.count()] = 0;
envp[environment.count() + 1] = 0;
QHash<QByteArray, QByteArray>::ConstIterator it = environment.constBegin();
const QHash<QByteArray, QByteArray>::ConstIterator end = environment.constEnd();
for ( ; it != end; ++it) {
QByteArray key = it.key();
QByteArray value = it.value();
key.reserve(key.length() + 1 + value.length());
key.append('=');
key.append(value);
envp[(*envc)++] = ::strdup(key.constData());
}
if (needToAddLibraryPath)
envp[(*envc)++] = ::strdup(QByteArray(libraryPath) + '=' +
envLibraryPath);
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
}
#ifdef Q_OS_MAC
Q_GLOBAL_STATIC(QMutex, cfbundleMutex);
#endif
void QProcessPrivate::startProcess()
{
Q_Q(QProcess);
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::startProcess()");
#endif
processManager()->start();
// Initialize pipes
if (!createChannel(stdinChannel) ||
!createChannel(stdoutChannel) ||
!createChannel(stderrChannel))
return;
qt_create_pipe(childStartedPipe);
qt_create_pipe(deathPipe);
if (threadData->eventDispatcher) {
startupSocketNotifier = new QSocketNotifier(childStartedPipe[0],
QSocketNotifier::Read, q);
QObject::connect(startupSocketNotifier, SIGNAL(activated(int)),
q, SLOT(_q_startupNotification()));
deathNotifier = new QSocketNotifier(deathPipe[0],
QSocketNotifier::Read, q);
QObject::connect(deathNotifier, SIGNAL(activated(int)),
q, SLOT(_q_processDied()));
}
// Start the process (platform dependent)
q->setProcessState(QProcess::Starting);
// Create argument list with right number of elements, and set the final
// one to 0.
char **argv = new char *[arguments.count() + 2];
argv[arguments.count() + 1] = 0;
// Encode the program name.
QByteArray encodedProgramName = QFile::encodeName(program);
#ifdef Q_OS_MAC
// allow invoking of .app bundles on the Mac.
QFileInfo fileInfo(QString::fromUtf8(encodedProgramName.constData()));
if (encodedProgramName.endsWith(".app") && fileInfo.isDir()) {
QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0,
QCFString(fileInfo.absoluteFilePath()),
kCFURLPOSIXPathStyle, true);
{
// CFBundle is not reentrant, since CFBundleCreate might return a reference
// to a cached bundle object. Protect the bundle calls with a mutex lock.
QMutexLocker lock(cfbundleMutex());
QCFType<CFBundleRef> bundle = CFBundleCreate(0, url);
url = CFBundleCopyExecutableURL(bundle);
}
if (url) {
QCFString str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
encodedProgramName += "/Contents/MacOS/" + static_cast<QString>(str).toUtf8();
}
}
#endif
// Add the program name to the argument list.
char *dupProgramName = ::strdup(encodedProgramName.constData());
argv[0] = dupProgramName;
// Add every argument to the list
for (int i = 0; i < arguments.count(); ++i) {
QString arg = arguments.at(i);
#ifdef Q_OS_MAC
// Mac OS X uses UTF8 for exec, regardless of the system locale.
argv[i + 1] = ::strdup(arg.toUtf8().constData());
#else
argv[i + 1] = ::strdup(arg.toLocal8Bit().constData());
#endif
}
// Duplicate the environment.
int envc = 0;
char **envp = 0;
if (environment.d.constData())
envp = _q_dupEnvironment(environment.d.constData()->hash, &envc);
// 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();
}
// If the program does not specify a path, generate a list of possible
// locations for the binary using the PATH environment variable.
char **path = 0;
int pathc = 0;
if (!program.contains(QLatin1Char('/'))) {
const QString pathEnv = QString::fromLocal8Bit(::getenv("PATH"));
if (!pathEnv.isEmpty()) {
QStringList pathEntries = pathEnv.split(QLatin1Char(':'), QString::SkipEmptyParts);
if (!pathEntries.isEmpty()) {
pathc = pathEntries.size();
path = new char *[pathc + 1];
path[pathc] = 0;
for (int k = 0; k < pathEntries.size(); ++k) {
QByteArray tmp = QFile::encodeName(pathEntries.at(k));
if (!tmp.endsWith('/')) tmp += '/';
tmp += encodedProgramName;
path[k] = ::strdup(tmp.constData());
}
}
}
}
// Start the process manager, and fork off the child process.
processManager()->lock();
pid_t childPid = qt_fork();
int lastForkErrno = errno;
if (childPid != 0) {
// Clean up duplicated memory.
free(dupProgramName);
for (int i = 1; i <= arguments.count(); ++i)
free(argv[i]);
for (int i = 0; i < envc; ++i)
free(envp[i]);
for (int i = 0; i < pathc; ++i)
free(path[i]);
delete [] argv;
delete [] envp;
delete [] path;
}
if (childPid < 0) {
// Cleanup, report error and return
#if defined (QPROCESS_DEBUG)
qDebug("qt_fork failed: %s", qt_error_string(lastForkErrno));
#endif
processManager()->unlock();
q->setProcessState(QProcess::NotRunning);
processError = QProcess::FailedToStart;
q->setErrorString(QProcess::tr("Resource error (fork failure): %1").arg(qt_error_string(lastForkErrno)));
emit q->error(processError);
cleanup();
return;
}
// Start the child.
if (childPid == 0) {
execChild(workingDirPtr, path, argv, envp);
::_exit(-1);
}
// 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.
processManager()->add(childPid, q);
pid = Q_PID(childPid);
processManager()->unlock();
// 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);
qt_safe_close(childStartedPipe[1]);
childStartedPipe[1] = -1;
if (stdinChannel.pipe[0] != -1) {
qt_safe_close(stdinChannel.pipe[0]);
stdinChannel.pipe[0] = -1;
}
if (stdinChannel.pipe[1] != -1)
::fcntl(stdinChannel.pipe[1], F_SETFL, ::fcntl(stdinChannel.pipe[1], F_GETFL) | O_NONBLOCK);
if (stdoutChannel.pipe[1] != -1) {
qt_safe_close(stdoutChannel.pipe[1]);
stdoutChannel.pipe[1] = -1;
}
if (stdoutChannel.pipe[0] != -1)
::fcntl(stdoutChannel.pipe[0], F_SETFL, ::fcntl(stdoutChannel.pipe[0], F_GETFL) | O_NONBLOCK);
if (stderrChannel.pipe[1] != -1) {
qt_safe_close(stderrChannel.pipe[1]);
stderrChannel.pipe[1] = -1;
}
if (stderrChannel.pipe[0] != -1)
::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK);
}
void QProcessPrivate::execChild(const char *workingDir, char **path, char **argv, char **envp)
{
::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored
Q_Q(QProcess);
// copy the stdin socket (without closing on exec)
qt_safe_dup2(stdinChannel.pipe[0], fileno(stdin), 0);
// copy the stdout and stderr if asked to
if (processChannelMode != QProcess::ForwardedChannels) {
qt_safe_dup2(stdoutChannel.pipe[1], fileno(stdout), 0);
// merge stdout and stderr if asked to
if (processChannelMode == QProcess::MergedChannels) {
qt_safe_dup2(fileno(stdout), fileno(stderr), 0);
} else {
qt_safe_dup2(stderrChannel.pipe[1], fileno(stderr), 0);
}
}
// make sure this fd is closed if execvp() succeeds
qt_safe_close(childStartedPipe[0]);
// enter the working directory
if (workingDir)
QT_CHDIR(workingDir);
// this is a virtual call, and it base behavior is to do nothing.
q->setupChildProcess();
// execute the process
if (!envp) {
qt_safe_execvp(argv[0], argv);
} else {
if (path) {
char **arg = path;
while (*arg) {
argv[0] = *arg;
#if defined (QPROCESS_DEBUG)
fprintf(stderr, "QProcessPrivate::execChild() searching / starting %s\n", argv[0]);
#endif
qt_safe_execve(argv[0], argv, envp);
++arg;
}
} else {
#if defined (QPROCESS_DEBUG)
fprintf(stderr, "QProcessPrivate::execChild() starting %s\n", argv[0]);
#endif
qt_safe_execve(argv[0], argv, envp);
}
}
// notify failure
QString error = qt_error_string(errno);
#if defined (QPROCESS_DEBUG)
fprintf(stderr, "QProcessPrivate::execChild() failed (%s), notifying parent process\n", qPrintable(error));
#endif
qt_safe_write(childStartedPipe[1], error.data(), error.length() * sizeof(QChar));
qt_safe_close(childStartedPipe[1]);
childStartedPipe[1] = -1;
}
bool QProcessPrivate::processStarted()
{
ushort buf[errorBufferMax];
int i = qt_safe_read(childStartedPipe[0], &buf, sizeof buf);
if (startupSocketNotifier) {
startupSocketNotifier->setEnabled(false);
startupSocketNotifier->deleteLater();
startupSocketNotifier = 0;
}
qt_safe_close(childStartedPipe[0]);
childStartedPipe[0] = -1;
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::processStarted() == %s", i <= 0 ? "true" : "false");
#endif
// did we read an error message?
if (i > 0)
q_func()->setErrorString(QString((const QChar *)buf, i / sizeof(QChar)));
return i <= 0;
}
qint64 QProcessPrivate::bytesAvailableFromStdout() const
{
int nbytes = 0;
qint64 available = 0;
if (::ioctl(stdoutChannel.pipe[0], FIONREAD, (char *) &nbytes) >= 0)
available = (qint64) nbytes;
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::bytesAvailableFromStdout() == %lld", available);
#endif
return available;
}
qint64 QProcessPrivate::bytesAvailableFromStderr() const
{
int nbytes = 0;
qint64 available = 0;
if (::ioctl(stderrChannel.pipe[0], FIONREAD, (char *) &nbytes) >= 0)
available = (qint64) nbytes;
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::bytesAvailableFromStderr() == %lld", available);
#endif
return available;
}
qint64 QProcessPrivate::readFromStdout(char *data, qint64 maxlen)
{
qint64 bytesRead = qt_safe_read(stdoutChannel.pipe[0], data, maxlen);
#if defined QPROCESS_DEBUG
qDebug("QProcessPrivate::readFromStdout(%p \"%s\", %lld) == %lld",
data, qt_prettyDebug(data, bytesRead, 16).constData(), maxlen, bytesRead);
#endif
return bytesRead;
}
qint64 QProcessPrivate::readFromStderr(char *data, qint64 maxlen)
{
qint64 bytesRead = qt_safe_read(stderrChannel.pipe[0], data, maxlen);
#if defined QPROCESS_DEBUG
qDebug("QProcessPrivate::readFromStderr(%p \"%s\", %lld) == %lld",
data, qt_prettyDebug(data, bytesRead, 16).constData(), maxlen, bytesRead);
#endif
return bytesRead;
}
static void qt_ignore_sigpipe()
{
// Set to ignore SIGPIPE once only.
static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0);
if (atom.testAndSetRelaxed(0, 1)) {
struct sigaction noaction;
memset(&noaction, 0, sizeof(noaction));
noaction.sa_handler = SIG_IGN;
::sigaction(SIGPIPE, &noaction, 0);
}
}
qint64 QProcessPrivate::writeToStdin(const char *data, qint64 maxlen)
{
qt_ignore_sigpipe();
qint64 written = qt_safe_write(stdinChannel.pipe[1], data, maxlen);
#if defined QPROCESS_DEBUG
qDebug("QProcessPrivate::writeToStdin(%p \"%s\", %lld) == %lld",
data, qt_prettyDebug(data, maxlen, 16).constData(), maxlen, written);
#endif
return written;
}
void QProcessPrivate::terminateProcess()
{
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::killProcess()");
#endif
if (pid)
::kill(pid_t(pid), SIGTERM);
}
void QProcessPrivate::killProcess()
{
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::killProcess()");
#endif
if (pid)
::kill(pid_t(pid), SIGKILL);
}
static int select_msecs(int nfds, fd_set *fdread, fd_set *fdwrite, int timeout)
{
if (timeout < 0)
return qt_safe_select(nfds, fdread, fdwrite, 0, 0);
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
return qt_safe_select(nfds, fdread, fdwrite, 0, &tv);
}
/*
Returns the difference between msecs and elapsed. If msecs is -1,
however, -1 is returned.
*/
static int qt_timeout_value(int msecs, int elapsed)
{
if (msecs == -1)
return -1;
int timeout = msecs - elapsed;
return timeout < 0 ? 0 : timeout;
}
bool QProcessPrivate::waitForStarted(int msecs)
{
Q_Q(QProcess);
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::waitForStarted(%d) waiting for child to start (fd = %d)", msecs,
childStartedPipe[0]);
#endif
fd_set fds;
FD_ZERO(&fds);
FD_SET(childStartedPipe[0], &fds);
if (select_msecs(childStartedPipe[0] + 1, &fds, 0, msecs) == 0) {
processError = QProcess::Timedout;
q->setErrorString(QProcess::tr("Process operation timed out"));
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::waitForStarted(%d) == false (timed out)", msecs);
#endif
return false;
}
bool startedEmitted = _q_startupNotification();
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::waitForStarted() == %s", startedEmitted ? "true" : "false");
#endif
return startedEmitted;
}
bool QProcessPrivate::waitForReadyRead(int msecs)
{
Q_Q(QProcess);
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::waitForReadyRead(%d)", msecs);
#endif
QTime stopWatch;
stopWatch.start();
forever {
fd_set fdread;
fd_set fdwrite;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
int nfds = deathPipe[0];
FD_SET(deathPipe[0], &fdread);
if (processState == QProcess::Starting)
add_fd(nfds, childStartedPipe[0], &fdread);
if (stdoutChannel.pipe[0] != -1)
add_fd(nfds, stdoutChannel.pipe[0], &fdread);
if (stderrChannel.pipe[0] != -1)
add_fd(nfds, stderrChannel.pipe[0], &fdread);
if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1)
add_fd(nfds, stdinChannel.pipe[1], &fdwrite);
int timeout = qt_timeout_value(msecs, stopWatch.elapsed());
int ret = select_msecs(nfds + 1, &fdread, &fdwrite, timeout);
if (ret < 0) {
break;
}
if (ret == 0) {
processError = QProcess::Timedout;
q->setErrorString(QProcess::tr("Process operation timed out"));
return false;
}
if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) {
if (!_q_startupNotification())
return false;
}
bool readyReadEmitted = false;
if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread)) {
bool canRead = _q_canReadStandardOutput();
if (processChannel == QProcess::StandardOutput && canRead)
readyReadEmitted = true;
}
if (stderrChannel.pipe[0] != -1 && FD_ISSET(stderrChannel.pipe[0], &fdread)) {
bool canRead = _q_canReadStandardError();
if (processChannel == QProcess::StandardError && canRead)
readyReadEmitted = true;
}
if (readyReadEmitted)
return true;
if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite))
_q_canWrite();
if (deathPipe[0] == -1 || FD_ISSET(deathPipe[0], &fdread)) {
if (_q_processDied())
return false;
}
}
return false;
}
bool QProcessPrivate::waitForBytesWritten(int msecs)
{
Q_Q(QProcess);
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::waitForBytesWritten(%d)", msecs);
#endif
QTime stopWatch;
stopWatch.start();
while (!writeBuffer.isEmpty()) {
fd_set fdread;
fd_set fdwrite;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
int nfds = deathPipe[0];
FD_SET(deathPipe[0], &fdread);
if (processState == QProcess::Starting)
add_fd(nfds, childStartedPipe[0], &fdread);
if (stdoutChannel.pipe[0] != -1)
add_fd(nfds, stdoutChannel.pipe[0], &fdread);
if (stderrChannel.pipe[0] != -1)
add_fd(nfds, stderrChannel.pipe[0], &fdread);
if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1)
add_fd(nfds, stdinChannel.pipe[1], &fdwrite);
int timeout = qt_timeout_value(msecs, stopWatch.elapsed());
int ret = select_msecs(nfds + 1, &fdread, &fdwrite, timeout);
if (ret < 0) {
break;
}
if (ret == 0) {
processError = QProcess::Timedout;
q->setErrorString(QProcess::tr("Process operation timed out"));
return false;
}
if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) {
if (!_q_startupNotification())
return false;
}
if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite))
return _q_canWrite();
if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread))
_q_canReadStandardOutput();
if (stderrChannel.pipe[0] != -1 && FD_ISSET(stderrChannel.pipe[0], &fdread))
_q_canReadStandardError();
if (deathPipe[0] == -1 || FD_ISSET(deathPipe[0], &fdread)) {
if (_q_processDied())
return false;
}
}
return false;
}
bool QProcessPrivate::waitForFinished(int msecs)
{
Q_Q(QProcess);
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::waitForFinished(%d)", msecs);
#endif
QTime stopWatch;
stopWatch.start();
forever {
fd_set fdread;
fd_set fdwrite;
int nfds = -1;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
if (processState == QProcess::Starting)
add_fd(nfds, childStartedPipe[0], &fdread);
if (stdoutChannel.pipe[0] != -1)
add_fd(nfds, stdoutChannel.pipe[0], &fdread);
if (stderrChannel.pipe[0] != -1)
add_fd(nfds, stderrChannel.pipe[0], &fdread);
if (processState == QProcess::Running)
add_fd(nfds, deathPipe[0], &fdread);
if (!writeBuffer.isEmpty() && stdinChannel.pipe[1] != -1)
add_fd(nfds, stdinChannel.pipe[1], &fdwrite);
int timeout = qt_timeout_value(msecs, stopWatch.elapsed());
int ret = select_msecs(nfds + 1, &fdread, &fdwrite, timeout);
if (ret < 0) {
break;
}
if (ret == 0) {
processError = QProcess::Timedout;
q->setErrorString(QProcess::tr("Process operation timed out"));
return false;
}
if (childStartedPipe[0] != -1 && FD_ISSET(childStartedPipe[0], &fdread)) {
if (!_q_startupNotification())
return false;
}
if (stdinChannel.pipe[1] != -1 && FD_ISSET(stdinChannel.pipe[1], &fdwrite))
_q_canWrite();
if (stdoutChannel.pipe[0] != -1 && FD_ISSET(stdoutChannel.pipe[0], &fdread))
_q_canReadStandardOutput();
if (stderrChannel.pipe[0] != -1 && FD_ISSET(stderrChannel.pipe[0], &fdread))
_q_canReadStandardError();
if (deathPipe[0] == -1 || FD_ISSET(deathPipe[0], &fdread)) {
if (_q_processDied())
return true;
}
}
return false;
}
bool QProcessPrivate::waitForWrite(int msecs)
{
fd_set fdwrite;
FD_ZERO(&fdwrite);
FD_SET(stdinChannel.pipe[1], &fdwrite);
return select_msecs(stdinChannel.pipe[1] + 1, 0, &fdwrite, msecs < 0 ? 0 : msecs) == 1;
}
void QProcessPrivate::findExitCode()
{
Q_Q(QProcess);
processManager()->remove(q);
}
bool QProcessPrivate::waitForDeadChild()
{
Q_Q(QProcess);
// read a byte from the death pipe
char c;
qt_safe_read(deathPipe[0], &c, 1);
// check if our process is dead
int exitStatus;
if (qt_safe_waitpid(pid_t(pid), &exitStatus, WNOHANG) > 0) {
processManager()->remove(q);
crashed = !WIFEXITED(exitStatus);
exitCode = WEXITSTATUS(exitStatus);
#if defined QPROCESS_DEBUG
qDebug() << "QProcessPrivate::waitForDeadChild() dead with exitCode"
<< exitCode << ", crashed?" << crashed;
#endif
return true;
}
#if defined QPROCESS_DEBUG
qDebug() << "QProcessPrivate::waitForDeadChild() not dead!";
#endif
return false;
}
void QProcessPrivate::_q_notified()
{
}
bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
{
processManager()->start();
QByteArray encodedWorkingDirectory = QFile::encodeName(workingDirectory);
// To catch the startup of the child
int startedPipe[2];
qt_safe_pipe(startedPipe);
// To communicate the pid of the child
int pidPipe[2];
qt_safe_pipe(pidPipe);
pid_t childPid = qt_fork();
if (childPid == 0) {
struct sigaction noaction;
memset(&noaction, 0, sizeof(noaction));
noaction.sa_handler = SIG_IGN;
::sigaction(SIGPIPE, &noaction, 0);
::setsid();
qt_safe_close(startedPipe[0]);
qt_safe_close(pidPipe[0]);
pid_t doubleForkPid = qt_fork();
if (doubleForkPid == 0) {
qt_safe_close(pidPipe[1]);
if (!encodedWorkingDirectory.isEmpty())
QT_CHDIR(encodedWorkingDirectory.constData());
char **argv = new char *[arguments.size() + 2];
for (int i = 0; i < arguments.size(); ++i) {
#ifdef Q_OS_MAC
argv[i + 1] = ::strdup(arguments.at(i).toUtf8().constData());
#else
argv[i + 1] = ::strdup(arguments.at(i).toLocal8Bit().constData());
#endif
}
argv[arguments.size() + 1] = 0;
if (!program.contains(QLatin1Char('/'))) {
const QString path = QString::fromLocal8Bit(::getenv("PATH"));
if (!path.isEmpty()) {
QStringList pathEntries = path.split(QLatin1Char(':'));
for (int k = 0; k < pathEntries.size(); ++k) {
QByteArray tmp = QFile::encodeName(pathEntries.at(k));
if (!tmp.endsWith('/')) tmp += '/';
tmp += QFile::encodeName(program);
argv[0] = tmp.data();
qt_safe_execv(argv[0], argv);
}
}
} else {
QByteArray tmp = QFile::encodeName(program);
argv[0] = tmp.data();
qt_safe_execv(argv[0], argv);
}
struct sigaction noaction;
memset(&noaction, 0, sizeof(noaction));
noaction.sa_handler = SIG_IGN;
::sigaction(SIGPIPE, &noaction, 0);
// '\1' means execv failed
char c = '\1';
qt_safe_write(startedPipe[1], &c, 1);
qt_safe_close(startedPipe[1]);
::_exit(1);
} else if (doubleForkPid == -1) {
struct sigaction noaction;
memset(&noaction, 0, sizeof(noaction));
noaction.sa_handler = SIG_IGN;
::sigaction(SIGPIPE, &noaction, 0);
// '\2' means internal error
char c = '\2';
qt_safe_write(startedPipe[1], &c, 1);
}
qt_safe_close(startedPipe[1]);
qt_safe_write(pidPipe[1], (const char *)&doubleForkPid, sizeof(pid_t));
QT_CHDIR("/");
::_exit(1);
}
qt_safe_close(startedPipe[1]);
qt_safe_close(pidPipe[1]);
if (childPid == -1) {
qt_safe_close(startedPipe[0]);
qt_safe_close(pidPipe[0]);
return false;
}
char reply = '\0';
int startResult = qt_safe_read(startedPipe[0], &reply, 1);
int result;
qt_safe_close(startedPipe[0]);
qt_safe_waitpid(childPid, &result, 0);
bool success = (startResult != -1 && reply == '\0');
if (success && pid) {
pid_t actualPid = 0;
if (qt_safe_read(pidPipe[0], (char *)&actualPid, sizeof(pid_t)) == sizeof(pid_t)) {
*pid = actualPid;
} else {
*pid = 0;
}
}
qt_safe_close(pidPipe[0]);
return success;
}
void QProcessPrivate::initializeProcessManager()
{
(void) processManager();
}
QT_END_NAMESPACE
#include "qprocess_unix.moc"
#endif // QT_NO_PROCESS