Both on Unix and Windows, _q_processDied() unconditionally releases all resources associated with the terminated child process, resets QProcess to the initial state, and emits finished() signal. Thus, we can omit reporting success, which also eliminates the related checks from callers. Change-Id: I40e32d1a9ccc8d488be6badba934355d734a8abd Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
1054 lines
33 KiB
C++
1054 lines
33 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Copyright (C) 2016 Intel Corporation.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the QtCore module of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 2.0 or (at your option) the GNU General
|
|
** Public license version 3 or any later version approved by the KDE Free
|
|
** Qt Foundation. The licenses are as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
//#define QPROCESS_DEBUG
|
|
#include "qdebug.h"
|
|
|
|
#if QT_CONFIG(process) && defined(QPROCESS_DEBUG)
|
|
#include "private/qtools_p.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: {
|
|
const char buf[] = {
|
|
'\\',
|
|
QtMiscUtils::toOct(uchar(c) / 64),
|
|
QtMiscUtils::toOct(uchar(c) % 64 / 8),
|
|
QtMiscUtils::toOct(uchar(c) % 8),
|
|
0
|
|
};
|
|
out += buf;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (len < maxSize)
|
|
out += "...";
|
|
|
|
return out;
|
|
}
|
|
QT_END_NAMESPACE
|
|
#endif
|
|
|
|
#include "qplatformdefs.h"
|
|
|
|
#include "qprocess.h"
|
|
#include "qprocess_p.h"
|
|
#include "qstandardpaths.h"
|
|
#include "private/qcore_unix_p.h"
|
|
#include "private/qlocking_p.h"
|
|
|
|
#ifdef Q_OS_MAC
|
|
#include <private/qcore_mac_p.h>
|
|
#endif
|
|
|
|
#include <private/qcoreapplication_p.h>
|
|
#include <private/qthread_p.h>
|
|
#include <qfile.h>
|
|
#include <qfileinfo.h>
|
|
#include <qdir.h>
|
|
#include <qlist.h>
|
|
#include <qmutex.h>
|
|
#include <qsocketnotifier.h>
|
|
#include <qthread.h>
|
|
#include <qelapsedtimer.h>
|
|
|
|
#ifdef Q_OS_QNX
|
|
# include <sys/neutrino.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if QT_CONFIG(process)
|
|
#include <forkfd.h>
|
|
#endif
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
#if !defined(Q_OS_DARWIN)
|
|
|
|
QT_BEGIN_INCLUDE_NAMESPACE
|
|
extern char **environ;
|
|
QT_END_INCLUDE_NAMESPACE
|
|
|
|
QProcessEnvironment QProcessEnvironment::systemEnvironment()
|
|
{
|
|
QProcessEnvironment env;
|
|
const char *entry;
|
|
for (int count = 0; (entry = environ[count]); ++count) {
|
|
const char *equal = strchr(entry, '=');
|
|
if (!equal)
|
|
continue;
|
|
|
|
QByteArray name(entry, equal - entry);
|
|
QByteArray value(equal + 1);
|
|
env.d->vars.insert(QProcessEnvironmentPrivate::Key(name),
|
|
QProcessEnvironmentPrivate::Value(value));
|
|
}
|
|
return env;
|
|
}
|
|
|
|
#endif // !defined(Q_OS_DARWIN)
|
|
|
|
#if QT_CONFIG(process)
|
|
|
|
namespace {
|
|
struct QProcessPoller
|
|
{
|
|
QProcessPoller(const QProcessPrivate &proc);
|
|
|
|
int poll(int timeout);
|
|
|
|
pollfd &stdinPipe() { return pfds[0]; }
|
|
pollfd &stdoutPipe() { return pfds[1]; }
|
|
pollfd &stderrPipe() { return pfds[2]; }
|
|
pollfd &forkfd() { return pfds[3]; }
|
|
pollfd &childStartedPipe() { return pfds[4]; }
|
|
|
|
enum { n_pfds = 5 };
|
|
pollfd pfds[n_pfds];
|
|
};
|
|
|
|
QProcessPoller::QProcessPoller(const QProcessPrivate &proc)
|
|
{
|
|
for (int i = 0; i < n_pfds; i++)
|
|
pfds[i] = qt_make_pollfd(-1, POLLIN);
|
|
|
|
stdoutPipe().fd = proc.stdoutChannel.pipe[0];
|
|
stderrPipe().fd = proc.stderrChannel.pipe[0];
|
|
|
|
if (!proc.writeBuffer.isEmpty()) {
|
|
stdinPipe().fd = proc.stdinChannel.pipe[1];
|
|
stdinPipe().events = POLLOUT;
|
|
}
|
|
|
|
forkfd().fd = proc.forkfd;
|
|
|
|
if (proc.processState == QProcess::Starting)
|
|
childStartedPipe().fd = proc.childStartedPipe[0];
|
|
}
|
|
|
|
int QProcessPoller::poll(int timeout)
|
|
{
|
|
const nfds_t nfds = (childStartedPipe().fd == -1) ? 4 : 5;
|
|
return qt_poll_msecs(pfds, nfds, timeout);
|
|
}
|
|
} // anonymous namespace
|
|
|
|
static bool qt_pollfd_check(const pollfd &pfd, short revents)
|
|
{
|
|
return pfd.fd >= 0 && (pfd.revents & (revents | POLLHUP | POLLERR | POLLNVAL)) != 0;
|
|
}
|
|
|
|
static int qt_create_pipe(int *pipe)
|
|
{
|
|
if (pipe[0] != -1)
|
|
qt_safe_close(pipe[0]);
|
|
if (pipe[1] != -1)
|
|
qt_safe_close(pipe[1]);
|
|
int pipe_ret = qt_safe_pipe(pipe);
|
|
if (pipe_ret != 0) {
|
|
qErrnoWarning("QProcessPrivate::createPipe: Cannot create pipe %p", pipe);
|
|
}
|
|
return pipe_ret;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void QProcessPrivate::closeChannel(Channel *channel)
|
|
{
|
|
destroyPipe(channel->pipe);
|
|
}
|
|
|
|
/*
|
|
Create the pipes to a QProcessPrivate::Channel.
|
|
|
|
This function must be called in order: stdin, stdout, stderr
|
|
*/
|
|
bool QProcessPrivate::openChannel(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
|
|
if (qt_create_pipe(channel.pipe) != 0)
|
|
return false;
|
|
|
|
// create the socket notifiers
|
|
if (threadData.loadRelaxed()->hasEventDispatcher()) {
|
|
if (&channel == &stdinChannel) {
|
|
channel.notifier = new QSocketNotifier(channel.pipe[1],
|
|
QSocketNotifier::Write, q);
|
|
channel.notifier->setEnabled(false);
|
|
QObject::connect(channel.notifier, SIGNAL(activated(QSocketDescriptor)),
|
|
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(QSocketDescriptor)),
|
|
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
|
|
setErrorAndEmit(QProcess::FailedToStart,
|
|
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
|
|
|
|
setErrorAndEmit(QProcess::FailedToStart,
|
|
QProcess::tr("Could not open input redirection for reading"));
|
|
}
|
|
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 };
|
|
if (qt_create_pipe(pipe) != 0)
|
|
return false;
|
|
sink->pipe[0] = pipe[0];
|
|
source->pipe[1] = pipe[1];
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static char **_q_dupEnvironment(const QProcessEnvironmentPrivate::Map &environment, int *envc)
|
|
{
|
|
*envc = 0;
|
|
if (environment.isEmpty())
|
|
return nullptr;
|
|
|
|
char **envp = new char *[environment.count() + 2];
|
|
envp[environment.count()] = nullptr;
|
|
envp[environment.count() + 1] = nullptr;
|
|
|
|
auto it = environment.constBegin();
|
|
const auto end = environment.constEnd();
|
|
for ( ; it != end; ++it) {
|
|
QByteArray key = it.key();
|
|
QByteArray value = it.value().bytes();
|
|
key.reserve(key.length() + 1 + value.length());
|
|
key.append('=');
|
|
key.append(value);
|
|
|
|
envp[(*envc)++] = ::strdup(key.constData());
|
|
}
|
|
|
|
return envp;
|
|
}
|
|
|
|
void QProcessPrivate::startProcess()
|
|
{
|
|
Q_Q(QProcess);
|
|
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::startProcess()");
|
|
#endif
|
|
|
|
// Initialize pipes
|
|
if (!openChannel(stdinChannel) ||
|
|
!openChannel(stdoutChannel) ||
|
|
!openChannel(stderrChannel) ||
|
|
qt_create_pipe(childStartedPipe) != 0) {
|
|
setErrorAndEmit(QProcess::FailedToStart, qt_error_string(errno));
|
|
cleanup();
|
|
return;
|
|
}
|
|
|
|
if (threadData.loadRelaxed()->hasEventDispatcher()) {
|
|
startupSocketNotifier = new QSocketNotifier(childStartedPipe[0],
|
|
QSocketNotifier::Read, q);
|
|
QObject::connect(startupSocketNotifier, SIGNAL(activated(QSocketDescriptor)),
|
|
q, SLOT(_q_startupNotification()));
|
|
}
|
|
|
|
// 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] = nullptr;
|
|
|
|
// Encode the program name.
|
|
QByteArray encodedProgramName = QFile::encodeName(program);
|
|
#ifdef Q_OS_MAC
|
|
// allow invoking of .app bundles on the Mac.
|
|
QFileInfo fileInfo(program);
|
|
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.
|
|
static QBasicMutex cfbundleMutex;
|
|
const auto locker = qt_scoped_lock(cfbundleMutex);
|
|
QCFType<CFBundleRef> bundle = CFBundleCreate(0, url);
|
|
// 'executableURL' can be either relative or absolute ...
|
|
QCFType<CFURLRef> executableURL = CFBundleCopyExecutableURL(bundle);
|
|
// not to depend on caching - make sure it's always absolute.
|
|
url = CFURLCopyAbsoluteURL(executableURL);
|
|
}
|
|
if (url) {
|
|
const QCFString str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
|
|
encodedProgramName += (QDir::separator() + QDir(program).relativeFilePath(QString::fromCFString(str))).toUtf8();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Add the program name to the argument list.
|
|
argv[0] = nullptr;
|
|
if (!program.contains(QLatin1Char('/'))) {
|
|
const QString &exeFilePath = QStandardPaths::findExecutable(program);
|
|
if (!exeFilePath.isEmpty()) {
|
|
const QByteArray &tmp = QFile::encodeName(exeFilePath);
|
|
argv[0] = ::strdup(tmp.constData());
|
|
}
|
|
}
|
|
if (!argv[0])
|
|
argv[0] = ::strdup(encodedProgramName.constData());
|
|
|
|
// Add every argument to the list
|
|
for (int i = 0; i < arguments.count(); ++i)
|
|
argv[i + 1] = ::strdup(QFile::encodeName(arguments.at(i)).constData());
|
|
|
|
// Duplicate the environment.
|
|
int envc = 0;
|
|
char **envp = nullptr;
|
|
if (environment.d.constData()) {
|
|
envp = _q_dupEnvironment(environment.d.constData()->vars, &envc);
|
|
}
|
|
|
|
// Encode the working directory if it's non-empty, otherwise just pass 0.
|
|
const char *workingDirPtr = nullptr;
|
|
QByteArray encodedWorkingDirectory;
|
|
if (!workingDirectory.isEmpty()) {
|
|
encodedWorkingDirectory = QFile::encodeName(workingDirectory);
|
|
workingDirPtr = encodedWorkingDirectory.constData();
|
|
}
|
|
|
|
int ffdflags = FFD_CLOEXEC;
|
|
|
|
// QTBUG-86285
|
|
#if !QT_CONFIG(forkfd_pidfd)
|
|
ffdflags |= FFD_USE_FORK;
|
|
#endif
|
|
|
|
pid_t childPid;
|
|
forkfd = ::forkfd(ffdflags , &childPid);
|
|
int lastForkErrno = errno;
|
|
if (forkfd != FFD_CHILD_PROCESS) {
|
|
// Parent process.
|
|
// Clean up duplicated memory.
|
|
for (int i = 0; i <= arguments.count(); ++i)
|
|
free(argv[i]);
|
|
for (int i = 0; i < envc; ++i)
|
|
free(envp[i]);
|
|
delete [] argv;
|
|
delete [] envp;
|
|
}
|
|
|
|
// On QNX, if spawnChild failed, childPid will be -1 but forkfd is still 0.
|
|
// This is intentional because we only want to handle failure to fork()
|
|
// here, which is a rare occurrence. Handling of the failure to start is
|
|
// done elsewhere.
|
|
if (forkfd == -1) {
|
|
// Cleanup, report error and return
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("fork failed: %ls", qUtf16Printable(qt_error_string(lastForkErrno)));
|
|
#endif
|
|
q->setProcessState(QProcess::NotRunning);
|
|
setErrorAndEmit(QProcess::FailedToStart,
|
|
QProcess::tr("Resource error (fork failure): %1").arg(qt_error_string(lastForkErrno)));
|
|
cleanup();
|
|
return;
|
|
}
|
|
|
|
// Start the child.
|
|
if (forkfd == FFD_CHILD_PROCESS) {
|
|
execChild(workingDirPtr, argv, envp);
|
|
::_exit(-1);
|
|
}
|
|
|
|
pid = qint64(childPid);
|
|
Q_ASSERT(pid > 0);
|
|
|
|
// parent
|
|
// close the ends we don't use and make all pipes non-blocking
|
|
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);
|
|
|
|
if (threadData.loadRelaxed()->eventDispatcher.loadAcquire()) {
|
|
deathNotifier = new QSocketNotifier(forkfd, QSocketNotifier::Read, q);
|
|
QObject::connect(deathNotifier, SIGNAL(activated(QSocketDescriptor)),
|
|
q, SLOT(_q_processDied()));
|
|
}
|
|
}
|
|
|
|
struct ChildError
|
|
{
|
|
int code;
|
|
char function[8];
|
|
};
|
|
|
|
void QProcessPrivate::execChild(const char *workingDir, char **argv, char **envp)
|
|
{
|
|
::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored
|
|
|
|
ChildError error = { 0, {} }; // force zeroing of function[8]
|
|
|
|
// copy the stdin socket if asked to (without closing on exec)
|
|
if (inputChannelMode != QProcess::ForwardedInputChannel)
|
|
qt_safe_dup2(stdinChannel.pipe[0], STDIN_FILENO, 0);
|
|
|
|
// copy the stdout and stderr if asked to
|
|
if (processChannelMode != QProcess::ForwardedChannels) {
|
|
if (processChannelMode != QProcess::ForwardedOutputChannel)
|
|
qt_safe_dup2(stdoutChannel.pipe[1], STDOUT_FILENO, 0);
|
|
|
|
// merge stdout and stderr if asked to
|
|
if (processChannelMode == QProcess::MergedChannels) {
|
|
qt_safe_dup2(STDOUT_FILENO, STDERR_FILENO, 0);
|
|
} else if (processChannelMode != QProcess::ForwardedErrorChannel) {
|
|
qt_safe_dup2(stderrChannel.pipe[1], STDERR_FILENO, 0);
|
|
}
|
|
}
|
|
|
|
// make sure this fd is closed if execv() succeeds
|
|
qt_safe_close(childStartedPipe[0]);
|
|
|
|
// enter the working directory
|
|
if (workingDir && QT_CHDIR(workingDir) == -1) {
|
|
// failed, stop the process
|
|
strcpy(error.function, "chdir");
|
|
goto report_errno;
|
|
}
|
|
|
|
if (childProcessModifier)
|
|
childProcessModifier();
|
|
|
|
// execute the process
|
|
if (!envp) {
|
|
qt_safe_execv(argv[0], argv);
|
|
strcpy(error.function, "execvp");
|
|
} else {
|
|
#if defined (QPROCESS_DEBUG)
|
|
fprintf(stderr, "QProcessPrivate::execChild() starting %s\n", argv[0]);
|
|
#endif
|
|
qt_safe_execve(argv[0], argv, envp);
|
|
strcpy(error.function, "execve");
|
|
}
|
|
|
|
// notify failure
|
|
// don't use strerror or any other routines that may allocate memory, since
|
|
// some buggy libc versions can deadlock on locked mutexes.
|
|
report_errno:
|
|
error.code = errno;
|
|
qt_safe_write(childStartedPipe[1], &error, sizeof(error));
|
|
childStartedPipe[1] = -1;
|
|
}
|
|
|
|
bool QProcessPrivate::processStarted(QString *errorMessage)
|
|
{
|
|
ChildError buf;
|
|
int ret = qt_safe_read(childStartedPipe[0], &buf, sizeof(buf));
|
|
|
|
if (startupSocketNotifier) {
|
|
startupSocketNotifier->setEnabled(false);
|
|
startupSocketNotifier->deleteLater();
|
|
startupSocketNotifier = nullptr;
|
|
}
|
|
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 (ret > 0 && errorMessage)
|
|
*errorMessage = QLatin1String(buf.function) + QLatin1String(": ") + qt_error_string(buf.code);
|
|
|
|
return ret <= 0;
|
|
}
|
|
|
|
qint64 QProcessPrivate::bytesAvailableInChannel(const Channel *channel) const
|
|
{
|
|
Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE);
|
|
int nbytes = 0;
|
|
qint64 available = 0;
|
|
if (::ioctl(channel->pipe[0], FIONREAD, (char *) &nbytes) >= 0)
|
|
available = (qint64) nbytes;
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::bytesAvailableInChannel(%d) == %lld", int(channel - &stdinChannel), available);
|
|
#endif
|
|
return available;
|
|
}
|
|
|
|
qint64 QProcessPrivate::readFromChannel(const Channel *channel, char *data, qint64 maxlen)
|
|
{
|
|
Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE);
|
|
qint64 bytesRead = qt_safe_read(channel->pipe[0], data, maxlen);
|
|
#if defined QPROCESS_DEBUG
|
|
int save_errno = errno;
|
|
qDebug("QProcessPrivate::readFromChannel(%d, %p \"%s\", %lld) == %lld",
|
|
int(channel - &stdinChannel),
|
|
data, qt_prettyDebug(data, bytesRead, 16).constData(), maxlen, bytesRead);
|
|
errno = save_errno;
|
|
#endif
|
|
if (bytesRead == -1 && errno == EWOULDBLOCK)
|
|
return -2;
|
|
return bytesRead;
|
|
}
|
|
|
|
bool QProcessPrivate::writeToStdin()
|
|
{
|
|
const char *data = writeBuffer.readPointer();
|
|
const qint64 bytesToWrite = writeBuffer.nextDataBlockSize();
|
|
|
|
qint64 written = qt_safe_write_nosignal(stdinChannel.pipe[1], data, bytesToWrite);
|
|
#if defined QPROCESS_DEBUG
|
|
qDebug("QProcessPrivate::writeToStdin(), write(%p \"%s\", %lld) == %lld",
|
|
data, qt_prettyDebug(data, bytesToWrite, 16).constData(), bytesToWrite, written);
|
|
if (written == -1)
|
|
qDebug("QProcessPrivate::writeToStdin(), failed to write (%ls)", qUtf16Printable(qt_error_string(errno)));
|
|
#endif
|
|
if (written == -1) {
|
|
// If the O_NONBLOCK flag is set and If some data can be written without blocking
|
|
// the process, write() will transfer what it can and return the number of bytes written.
|
|
// Otherwise, it will return -1 and set errno to EAGAIN
|
|
if (errno == EAGAIN)
|
|
return true;
|
|
|
|
closeChannel(&stdinChannel);
|
|
setErrorAndEmit(QProcess::WriteError);
|
|
return false;
|
|
}
|
|
writeBuffer.free(written);
|
|
if (!emittedBytesWritten && written != 0) {
|
|
emittedBytesWritten = true;
|
|
emit q_func()->bytesWritten(written);
|
|
emittedBytesWritten = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QProcessPrivate::terminateProcess()
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::terminateProcess() pid=%jd", intmax_t(pid));
|
|
#endif
|
|
if (pid > 0)
|
|
::kill(pid_t(pid), SIGTERM);
|
|
}
|
|
|
|
void QProcessPrivate::killProcess()
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::killProcess() pid=%jd", intmax_t(pid));
|
|
#endif
|
|
if (pid > 0)
|
|
::kill(pid_t(pid), SIGKILL);
|
|
}
|
|
|
|
bool QProcessPrivate::waitForStarted(int msecs)
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::waitForStarted(%d) waiting for child to start (fd = %d)", msecs,
|
|
childStartedPipe[0]);
|
|
#endif
|
|
|
|
pollfd pfd = qt_make_pollfd(childStartedPipe[0], POLLIN);
|
|
|
|
if (qt_poll_msecs(&pfd, 1, msecs) == 0) {
|
|
setError(QProcess::Timedout);
|
|
#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)
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::waitForReadyRead(%d)", msecs);
|
|
#endif
|
|
|
|
QElapsedTimer stopWatch;
|
|
stopWatch.start();
|
|
|
|
forever {
|
|
QProcessPoller poller(*this);
|
|
|
|
int timeout = qt_subtract_from_timeout(msecs, stopWatch.elapsed());
|
|
int ret = poller.poll(timeout);
|
|
|
|
if (ret < 0) {
|
|
break;
|
|
}
|
|
if (ret == 0) {
|
|
setError(QProcess::Timedout);
|
|
return false;
|
|
}
|
|
|
|
if (qt_pollfd_check(poller.childStartedPipe(), POLLIN)) {
|
|
if (!_q_startupNotification())
|
|
return false;
|
|
}
|
|
|
|
// This calls QProcessPrivate::tryReadFromChannel(), which returns true
|
|
// if we emitted readyRead() signal on the current read channel.
|
|
bool readyReadEmitted = false;
|
|
if (qt_pollfd_check(poller.stdoutPipe(), POLLIN) && _q_canReadStandardOutput())
|
|
readyReadEmitted = true;
|
|
if (qt_pollfd_check(poller.stderrPipe(), POLLIN) && _q_canReadStandardError())
|
|
readyReadEmitted = true;
|
|
|
|
if (readyReadEmitted)
|
|
return true;
|
|
|
|
if (qt_pollfd_check(poller.stdinPipe(), POLLOUT))
|
|
_q_canWrite();
|
|
|
|
// Signals triggered by I/O may have stopped this process:
|
|
if (processState == QProcess::NotRunning)
|
|
return false;
|
|
|
|
if (qt_pollfd_check(poller.forkfd(), POLLIN)) {
|
|
_q_processDied();
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool QProcessPrivate::waitForBytesWritten(int msecs)
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::waitForBytesWritten(%d)", msecs);
|
|
#endif
|
|
|
|
QElapsedTimer stopWatch;
|
|
stopWatch.start();
|
|
|
|
while (!writeBuffer.isEmpty()) {
|
|
QProcessPoller poller(*this);
|
|
|
|
int timeout = qt_subtract_from_timeout(msecs, stopWatch.elapsed());
|
|
int ret = poller.poll(timeout);
|
|
|
|
if (ret < 0) {
|
|
break;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
setError(QProcess::Timedout);
|
|
return false;
|
|
}
|
|
|
|
if (qt_pollfd_check(poller.childStartedPipe(), POLLIN)) {
|
|
if (!_q_startupNotification())
|
|
return false;
|
|
}
|
|
|
|
if (qt_pollfd_check(poller.stdinPipe(), POLLOUT))
|
|
return _q_canWrite();
|
|
|
|
if (qt_pollfd_check(poller.stdoutPipe(), POLLIN))
|
|
_q_canReadStandardOutput();
|
|
|
|
if (qt_pollfd_check(poller.stderrPipe(), POLLIN))
|
|
_q_canReadStandardError();
|
|
|
|
// Signals triggered by I/O may have stopped this process:
|
|
if (processState == QProcess::NotRunning)
|
|
return false;
|
|
|
|
if (qt_pollfd_check(poller.forkfd(), POLLIN)) {
|
|
_q_processDied();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QProcessPrivate::waitForFinished(int msecs)
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::waitForFinished(%d)", msecs);
|
|
#endif
|
|
|
|
QElapsedTimer stopWatch;
|
|
stopWatch.start();
|
|
|
|
forever {
|
|
QProcessPoller poller(*this);
|
|
|
|
int timeout = qt_subtract_from_timeout(msecs, stopWatch.elapsed());
|
|
int ret = poller.poll(timeout);
|
|
|
|
if (ret < 0) {
|
|
break;
|
|
}
|
|
if (ret == 0) {
|
|
setError(QProcess::Timedout);
|
|
return false;
|
|
}
|
|
|
|
if (qt_pollfd_check(poller.childStartedPipe(), POLLIN)) {
|
|
if (!_q_startupNotification())
|
|
return false;
|
|
}
|
|
if (qt_pollfd_check(poller.stdinPipe(), POLLOUT))
|
|
_q_canWrite();
|
|
|
|
if (qt_pollfd_check(poller.stdoutPipe(), POLLIN))
|
|
_q_canReadStandardOutput();
|
|
|
|
if (qt_pollfd_check(poller.stderrPipe(), POLLIN))
|
|
_q_canReadStandardError();
|
|
|
|
// Signals triggered by I/O may have stopped this process:
|
|
if (processState == QProcess::NotRunning)
|
|
return true;
|
|
|
|
if (qt_pollfd_check(poller.forkfd(), POLLIN)) {
|
|
_q_processDied();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QProcessPrivate::findExitCode()
|
|
{
|
|
}
|
|
|
|
void QProcessPrivate::waitForDeadChild()
|
|
{
|
|
if (forkfd == -1)
|
|
return; // child has already been reaped
|
|
|
|
// read the process information from our fd
|
|
forkfd_info info;
|
|
int ret;
|
|
EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr));
|
|
|
|
exitCode = info.status;
|
|
crashed = info.code != CLD_EXITED;
|
|
|
|
delete deathNotifier;
|
|
deathNotifier = nullptr;
|
|
|
|
EINTR_LOOP(ret, forkfd_close(forkfd));
|
|
forkfd = -1; // Child is dead, don't try to kill it anymore
|
|
|
|
#if defined QPROCESS_DEBUG
|
|
qDebug() << "QProcessPrivate::waitForDeadChild() dead with exitCode"
|
|
<< exitCode << ", crashed?" << crashed;
|
|
#endif
|
|
}
|
|
|
|
bool QProcessPrivate::startDetached(qint64 *pid)
|
|
{
|
|
QByteArray encodedWorkingDirectory = QFile::encodeName(workingDirectory);
|
|
|
|
// To catch the startup of the child
|
|
int startedPipe[2];
|
|
if (qt_safe_pipe(startedPipe) != 0)
|
|
return false;
|
|
// To communicate the pid of the child
|
|
int pidPipe[2];
|
|
if (qt_safe_pipe(pidPipe) != 0) {
|
|
qt_safe_close(startedPipe[0]);
|
|
qt_safe_close(startedPipe[1]);
|
|
return false;
|
|
}
|
|
|
|
if ((stdinChannel.type == Channel::Redirect && !openChannel(stdinChannel))
|
|
|| (stdoutChannel.type == Channel::Redirect && !openChannel(stdoutChannel))
|
|
|| (stderrChannel.type == Channel::Redirect && !openChannel(stderrChannel))) {
|
|
closeChannel(&stdinChannel);
|
|
closeChannel(&stdoutChannel);
|
|
closeChannel(&stderrChannel);
|
|
qt_safe_close(pidPipe[0]);
|
|
qt_safe_close(pidPipe[1]);
|
|
qt_safe_close(startedPipe[0]);
|
|
qt_safe_close(startedPipe[1]);
|
|
return false;
|
|
}
|
|
|
|
pid_t childPid = fork();
|
|
if (childPid == 0) {
|
|
struct sigaction noaction;
|
|
memset(&noaction, 0, sizeof(noaction));
|
|
noaction.sa_handler = SIG_IGN;
|
|
::sigaction(SIGPIPE, &noaction, nullptr);
|
|
|
|
::setsid();
|
|
|
|
qt_safe_close(startedPipe[0]);
|
|
qt_safe_close(pidPipe[0]);
|
|
|
|
pid_t doubleForkPid = fork();
|
|
if (doubleForkPid == 0) {
|
|
qt_safe_close(pidPipe[1]);
|
|
|
|
// copy the stdin socket if asked to (without closing on exec)
|
|
if (stdinChannel.type == Channel::Redirect)
|
|
qt_safe_dup2(stdinChannel.pipe[0], STDIN_FILENO, 0);
|
|
|
|
// copy the stdout and stderr if asked to
|
|
if (stdoutChannel.type == Channel::Redirect)
|
|
qt_safe_dup2(stdoutChannel.pipe[1], STDOUT_FILENO, 0);
|
|
if (stderrChannel.type == Channel::Redirect)
|
|
qt_safe_dup2(stderrChannel.pipe[1], STDERR_FILENO, 0);
|
|
|
|
if (!encodedWorkingDirectory.isEmpty()) {
|
|
if (QT_CHDIR(encodedWorkingDirectory.constData()) == -1)
|
|
qWarning("QProcessPrivate::startDetached: failed to chdir to %s", encodedWorkingDirectory.constData());
|
|
}
|
|
|
|
char **argv = new char *[arguments.size() + 2];
|
|
for (int i = 0; i < arguments.size(); ++i)
|
|
argv[i + 1] = ::strdup(QFile::encodeName(arguments.at(i)).constData());
|
|
argv[arguments.size() + 1] = nullptr;
|
|
|
|
// Duplicate the environment.
|
|
int envc = 0;
|
|
char **envp = nullptr;
|
|
if (environment.d.constData()) {
|
|
envp = _q_dupEnvironment(environment.d.constData()->vars, &envc);
|
|
}
|
|
|
|
QByteArray tmp;
|
|
if (!program.contains(QLatin1Char('/'))) {
|
|
const QString &exeFilePath = QStandardPaths::findExecutable(program);
|
|
if (!exeFilePath.isEmpty())
|
|
tmp = QFile::encodeName(exeFilePath);
|
|
}
|
|
if (tmp.isEmpty())
|
|
tmp = QFile::encodeName(program);
|
|
argv[0] = tmp.data();
|
|
|
|
if (envp)
|
|
qt_safe_execve(argv[0], argv, envp);
|
|
else
|
|
qt_safe_execv(argv[0], argv);
|
|
|
|
struct sigaction noaction;
|
|
memset(&noaction, 0, sizeof(noaction));
|
|
noaction.sa_handler = SIG_IGN;
|
|
::sigaction(SIGPIPE, &noaction, nullptr);
|
|
|
|
// '\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, nullptr);
|
|
|
|
// '\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));
|
|
if (QT_CHDIR("/") == -1)
|
|
qWarning("QProcessPrivate::startDetached: failed to chdir to /");
|
|
::_exit(1);
|
|
}
|
|
|
|
closeChannel(&stdinChannel);
|
|
closeChannel(&stdoutChannel);
|
|
closeChannel(&stderrChannel);
|
|
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;
|
|
}
|
|
|
|
#endif // QT_CONFIG(process)
|
|
|
|
QT_END_NAMESPACE
|