That way we will restore the thread's signal mask even if a thread cancellation is pending and thus forces an immediate stack unwind when we re-enable them. Change-Id: I36589f84c5f4d8929129fffdbb77b2020c6fa5a6 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> (cherry picked from commit 7400db0f82632a4bc41c1e6fc628ae4d4c1b9385) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
1372 lines
42 KiB
C++
1372 lines
42 KiB
C++
// Copyright (C) 2020 The Qt Company Ltd.
|
|
// Copyright (C) 2022 Intel Corporation.
|
|
// Copyright (C) 2021 Alex Trotsenko.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
|
|
//#define QPROCESS_DEBUG
|
|
#include "qdebug.h"
|
|
#include <private/qdebug_p.h>
|
|
#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_DARWIN
|
|
#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>
|
|
|
|
#ifdef Q_OS_QNX
|
|
# include <sys/neutrino.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/resource.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#if __has_include(<paths.h>)
|
|
# include <paths.h>
|
|
#endif
|
|
#if __has_include(<linux/close_range.h>)
|
|
// FreeBSD's is in <unistd.h>
|
|
# include <linux/close_range.h>
|
|
#endif
|
|
|
|
#if QT_CONFIG(process)
|
|
#include <forkfd.h>
|
|
#endif
|
|
|
|
#ifndef O_PATH
|
|
# define O_PATH 0
|
|
#endif
|
|
#ifndef _PATH_DEV
|
|
# define _PATH_DEV "/dev/"
|
|
#endif
|
|
#ifndef _PATH_TTY
|
|
# define _PATH_TTY _PATH_DEV "tty"
|
|
#endif
|
|
|
|
#ifdef Q_OS_FREEBSD
|
|
__attribute__((weak))
|
|
#endif
|
|
extern char **environ;
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
#if !defined(Q_OS_DARWIN)
|
|
|
|
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 QtVforkSafe {
|
|
// Certain libc functions we need to call in the child process scenario aren't
|
|
// safe under vfork() because they do more than just place the system call to
|
|
// the kernel and set errno on return. For those, we'll create a function
|
|
// pointer like:
|
|
// static constexpr auto foobar = __libc_foobar;
|
|
// while for all other OSes, it'll be
|
|
// using ::foobar;
|
|
// allowing the code for the child side of the vfork to simply use
|
|
// QtVforkSafe::foobar(args);
|
|
//
|
|
// Currently known issues are:
|
|
//
|
|
// - FreeBSD's libthr sigaction() wrapper locks a rwlock
|
|
// https://github.com/freebsd/freebsd-src/blob/8dad5ece49479ba6cdcd5bb4c2799bbd61add3e6/lib/libthr/thread/thr_sig.c#L575-L641
|
|
// - MUSL's sigaction() locks a mutex if the signal is SIGABR
|
|
// https://github.com/bminor/musl/blob/718f363bc2067b6487900eddc9180c84e7739f80/src/signal/sigaction.c#L63-L85
|
|
//
|
|
// All other functions called in the child side are vfork-safe, provided that
|
|
// PThread cancellation is disabled and Unix signals are blocked.
|
|
#if defined(__MUSL__)
|
|
# define LIBC_PREFIX __libc_
|
|
#elif defined(Q_OS_FREEBSD)
|
|
// will cause QtCore to link to ELF version "FBSDprivate_1.0"
|
|
# define LIBC_PREFIX _
|
|
#endif
|
|
|
|
#ifdef LIBC_PREFIX
|
|
# define CONCAT(x, y) CONCAT2(x, y)
|
|
# define CONCAT2(x, y) x ## y
|
|
# define DECLARE_FUNCTIONS(NAME) \
|
|
extern decltype(::NAME) CONCAT(LIBC_PREFIX, NAME); \
|
|
static constexpr auto NAME = std::addressof(CONCAT(LIBC_PREFIX, NAME));
|
|
#else // LIBC_PREFIX
|
|
# define DECLARE_FUNCTIONS(NAME) using ::NAME;
|
|
#endif // LIBC_PREFIX
|
|
|
|
extern "C" {
|
|
DECLARE_FUNCTIONS(sigaction)
|
|
}
|
|
|
|
#undef LIBC_PREFIX
|
|
#undef DECLARE_FUNCTIONS
|
|
|
|
// similar to qt_ignore_sigpipe() in qcore_unix_p.h, but vfork-safe
|
|
static void change_sigpipe(decltype(SIG_DFL) new_handler)
|
|
{
|
|
struct sigaction sa;
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = new_handler;
|
|
sigaction(SIGPIPE, &sa, nullptr);
|
|
}
|
|
} // namespace QtVforkSafe
|
|
|
|
static int opendirfd(QByteArray encodedName)
|
|
{
|
|
// We append "/." to the name to ensure that the directory is actually
|
|
// traversable (i.e., has the +x bit set). This avoids later problems
|
|
// with fchdir().
|
|
if (encodedName != "/" && !encodedName.endsWith("/."))
|
|
encodedName += "/.";
|
|
return qt_safe_open(encodedName, QT_OPEN_RDONLY | O_DIRECTORY | O_PATH);
|
|
}
|
|
|
|
namespace {
|
|
struct AutoPipe
|
|
{
|
|
int pipe[2] = { -1, -1 };
|
|
AutoPipe(int flags = 0)
|
|
{
|
|
qt_safe_pipe(pipe, flags);
|
|
}
|
|
~AutoPipe()
|
|
{
|
|
for (int fd : pipe) {
|
|
if (fd >= 0)
|
|
qt_safe_close(fd);
|
|
}
|
|
}
|
|
|
|
explicit operator bool() const { return pipe[0] >= 0; }
|
|
int &operator[](int idx) { return pipe[idx]; }
|
|
int operator[](int idx) const { return pipe[idx]; }
|
|
};
|
|
|
|
struct ChildError
|
|
{
|
|
int code;
|
|
char function[_POSIX_PIPE_BUF - sizeof(code)];
|
|
};
|
|
static_assert(std::is_trivial_v<ChildError>);
|
|
#ifdef PIPE_BUF
|
|
static_assert(PIPE_BUF >= sizeof(ChildError)); // PIPE_BUF may be bigger
|
|
#endif
|
|
|
|
struct QProcessPoller
|
|
{
|
|
QProcessPoller(const QProcessPrivate &proc);
|
|
|
|
int poll(const QDeadlineTimer &deadline);
|
|
|
|
pollfd &stdinPipe() { return pfds[0]; }
|
|
pollfd &stdoutPipe() { return pfds[1]; }
|
|
pollfd &stderrPipe() { return pfds[2]; }
|
|
pollfd &forkfd() { return pfds[3]; }
|
|
|
|
enum { n_pfds = 4 };
|
|
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;
|
|
}
|
|
|
|
int QProcessPoller::poll(const QDeadlineTimer &deadline)
|
|
{
|
|
return qt_safe_poll(pfds, n_pfds, deadline);
|
|
}
|
|
|
|
struct QChildProcess
|
|
{
|
|
// Used for argv and envp arguments to execve()
|
|
struct CharPointerList
|
|
{
|
|
std::unique_ptr<char *[]> pointers;
|
|
|
|
CharPointerList(const QString &argv0, const QStringList &args);
|
|
explicit CharPointerList(const QProcessEnvironmentPrivate *env);
|
|
/*implicit*/ operator char **() const { return pointers.get(); }
|
|
|
|
private:
|
|
QByteArray data;
|
|
void updatePointers(qsizetype count);
|
|
};
|
|
|
|
const QProcessPrivate *d;
|
|
CharPointerList argv;
|
|
CharPointerList envp;
|
|
sigset_t oldsigset;
|
|
int workingDirectory = -2;
|
|
bool isUsingVfork = usingVfork();
|
|
|
|
bool ok() const
|
|
{
|
|
return workingDirectory != -1;
|
|
}
|
|
|
|
QChildProcess(QProcessPrivate *d)
|
|
: d(d), argv(resolveExecutable(d->program), d->arguments),
|
|
envp(d->environmentPrivate())
|
|
{
|
|
// Block Unix signals, to ensure the user's handlers aren't run in the
|
|
// child side and do something weird, especially if the handler and the
|
|
// user of QProcess are completely different codebases.
|
|
maybeBlockSignals();
|
|
|
|
// Disable PThread cancellation until the child has successfully been
|
|
// executed. We make a number of POSIX calls in the child that are thread
|
|
// cancellation points and could cause an unexpected stack unwind. That
|
|
// would be bad enough with regular fork(), but it's likely fatal with
|
|
// vfork().
|
|
disableThreadCancellations();
|
|
|
|
if (!d->workingDirectory.isEmpty()) {
|
|
workingDirectory = opendirfd(QFile::encodeName(d->workingDirectory));
|
|
if (workingDirectory < 0) {
|
|
d->setErrorAndEmit(QProcess::FailedToStart, "chdir: "_L1 + qt_error_string());
|
|
d->cleanup();
|
|
}
|
|
}
|
|
|
|
}
|
|
~QChildProcess() noexcept(false)
|
|
{
|
|
if (workingDirectory >= 0)
|
|
close(workingDirectory);
|
|
|
|
restoreSignalMask();
|
|
restoreThreadCancellations();
|
|
}
|
|
|
|
void maybeBlockSignals() noexcept
|
|
{
|
|
// We only block Unix signals if we're using vfork(), to avoid a
|
|
// changing behavior to the user's modifier and because in some OSes
|
|
// this action would block crashing signals too.
|
|
if (isUsingVfork) {
|
|
sigset_t emptyset;
|
|
sigfillset(&emptyset);
|
|
pthread_sigmask(SIG_SETMASK, &emptyset, &oldsigset);
|
|
}
|
|
}
|
|
|
|
void restoreSignalMask() const noexcept
|
|
{
|
|
if (isUsingVfork)
|
|
pthread_sigmask(SIG_SETMASK, &oldsigset, nullptr);
|
|
}
|
|
|
|
bool usingVfork() const noexcept;
|
|
|
|
template <typename Lambda> int doFork(Lambda &&childLambda)
|
|
{
|
|
pid_t pid;
|
|
if (isUsingVfork) {
|
|
QT_IGNORE_DEPRECATIONS(pid = vfork();)
|
|
} else {
|
|
pid = fork();
|
|
}
|
|
if (pid == 0)
|
|
_exit(childLambda());
|
|
return pid;
|
|
}
|
|
|
|
int startChild(pid_t *pid)
|
|
{
|
|
int ffdflags = FFD_CLOEXEC | (isUsingVfork ? 0 : FFD_USE_FORK);
|
|
return ::vforkfd(ffdflags, pid, &QChildProcess::startProcess, this);
|
|
}
|
|
|
|
private:
|
|
Q_NORETURN void startProcess() const noexcept;
|
|
static int startProcess(void *self) noexcept
|
|
{
|
|
static_cast<QChildProcess *>(self)->startProcess();
|
|
Q_UNREACHABLE_RETURN(-1);
|
|
}
|
|
|
|
#if defined(PTHREAD_CANCEL_DISABLE)
|
|
int oldstate;
|
|
void disableThreadCancellations() noexcept
|
|
{
|
|
// the following is *not* noexcept, but it won't throw while disabling
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
|
|
}
|
|
void restoreThreadCancellations() noexcept(false)
|
|
{
|
|
// this doesn't touch errno
|
|
pthread_setcancelstate(oldstate, nullptr);
|
|
}
|
|
#else
|
|
void disableThreadCancellations() noexcept {}
|
|
void restoreThreadCancellations() {}
|
|
#endif
|
|
|
|
static QString resolveExecutable(const QString &program);
|
|
};
|
|
|
|
QChildProcess::CharPointerList::CharPointerList(const QString &program, const QStringList &args)
|
|
{
|
|
qsizetype count = 1 + args.size();
|
|
pointers.reset(new char *[count + 1]);
|
|
pointers[count] = nullptr;
|
|
|
|
// we abuse the pointer array to store offsets first (QByteArray will
|
|
// reallocate, after all)
|
|
pointers[0] = reinterpret_cast<char *>(0);
|
|
data = QFile::encodeName(program);
|
|
data += '\0';
|
|
|
|
const auto end = args.end();
|
|
auto it = args.begin();
|
|
for (qsizetype i = 1; it != end; ++it, ++i) {
|
|
pointers[i] = reinterpret_cast<char *>(data.size());
|
|
data += QFile::encodeName(*it);
|
|
data += '\0';
|
|
}
|
|
|
|
updatePointers(count);
|
|
}
|
|
|
|
QChildProcess::CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment)
|
|
{
|
|
if (!environment)
|
|
return;
|
|
|
|
const QProcessEnvironmentPrivate::Map &env = environment->vars;
|
|
qsizetype count = env.size();
|
|
pointers.reset(new char *[count + 1]);
|
|
pointers[count] = nullptr;
|
|
|
|
const auto end = env.end();
|
|
auto it = env.begin();
|
|
for (qsizetype i = 0; it != end; ++it, ++i) {
|
|
// we abuse the pointer array to store offsets first (QByteArray will
|
|
// reallocate, after all)
|
|
pointers[i] = reinterpret_cast<char *>(data.size());
|
|
|
|
data += it.key();
|
|
data += '=';
|
|
data += it->bytes();
|
|
data += '\0';
|
|
}
|
|
|
|
updatePointers(count);
|
|
}
|
|
|
|
void QChildProcess::CharPointerList::updatePointers(qsizetype count)
|
|
{
|
|
char *const base = const_cast<char *>(data.constBegin());
|
|
for (qsizetype i = 0; i < count; ++i)
|
|
pointers[i] = base + qptrdiff(pointers[i]);
|
|
}
|
|
} // 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) {
|
|
QScopedValueRollback rollback(errno);
|
|
qErrnoWarning("QProcess: Cannot create 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)
|
|
{
|
|
delete channel->notifier;
|
|
channel->notifier = nullptr;
|
|
|
|
destroyPipe(channel->pipe);
|
|
}
|
|
|
|
void QProcessPrivate::cleanup()
|
|
{
|
|
q_func()->setProcessState(QProcess::NotRunning);
|
|
|
|
closeChannels();
|
|
delete stateNotifier;
|
|
stateNotifier = nullptr;
|
|
destroyPipe(childStartedPipe);
|
|
pid = 0;
|
|
if (forkfd != -1) {
|
|
qt_safe_close(forkfd);
|
|
forkfd = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Create the pipes to a QProcessPrivate::Channel.
|
|
*/
|
|
bool QProcessPrivate::openChannel(Channel &channel)
|
|
{
|
|
Q_Q(QProcess);
|
|
|
|
if (channel.type == Channel::Normal) {
|
|
// we're piping this channel to our own process
|
|
if (qt_create_pipe(channel.pipe) != 0) {
|
|
setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
|
|
return false;
|
|
}
|
|
|
|
// create the socket notifiers
|
|
if (threadData.loadRelaxed()->hasEventDispatcher()) {
|
|
if (&channel == &stdinChannel) {
|
|
channel.notifier = new QSocketNotifier(QSocketNotifier::Write, q);
|
|
channel.notifier->setSocket(channel.pipe[1]);
|
|
QObject::connect(channel.notifier, SIGNAL(activated(QSocketDescriptor)),
|
|
q, SLOT(_q_canWrite()));
|
|
} else {
|
|
channel.notifier = new QSocketNotifier(QSocketNotifier::Read, q);
|
|
channel.notifier->setSocket(channel.pipe[0]);
|
|
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"));
|
|
}
|
|
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) {
|
|
setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
|
|
return false;
|
|
}
|
|
sink->pipe[0] = pipe[0];
|
|
source->pipe[1] = pipe[1];
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QProcessPrivate::commitChannels() const
|
|
{
|
|
// copy the stdin socket if asked to (without closing on exec)
|
|
if (stdinChannel.pipe[0] != INVALID_Q_PIPE)
|
|
qt_safe_dup2(stdinChannel.pipe[0], STDIN_FILENO, 0);
|
|
|
|
// copy the stdout and stderr if asked to
|
|
if (stdoutChannel.pipe[1] != INVALID_Q_PIPE)
|
|
qt_safe_dup2(stdoutChannel.pipe[1], STDOUT_FILENO, 0);
|
|
if (stderrChannel.pipe[1] != INVALID_Q_PIPE) {
|
|
qt_safe_dup2(stderrChannel.pipe[1], STDERR_FILENO, 0);
|
|
} else {
|
|
// merge stdout and stderr if asked to
|
|
if (processChannelMode == QProcess::MergedChannels)
|
|
qt_safe_dup2(STDOUT_FILENO, STDERR_FILENO, 0);
|
|
}
|
|
}
|
|
|
|
inline QString QChildProcess::resolveExecutable(const QString &program)
|
|
{
|
|
#ifdef Q_OS_DARWIN
|
|
// allow invoking of .app bundles on the Mac.
|
|
QFileInfo fileInfo(program);
|
|
if (program.endsWith(".app"_L1) && 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.
|
|
Q_CONSTINIT 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);
|
|
return QString::fromCFString(str);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!program.contains(u'/')) {
|
|
// findExecutable() returns its argument if it's an absolute path,
|
|
// otherwise it searches $PATH; returns empty if not found (we handle
|
|
// that case much later)
|
|
return QStandardPaths::findExecutable(program);
|
|
}
|
|
return program;
|
|
}
|
|
|
|
extern "C" {
|
|
__attribute__((weak)) pid_t __interceptor_vfork();
|
|
}
|
|
|
|
inline bool globalUsingVfork() noexcept
|
|
{
|
|
#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
|
|
// ASan writes to global memory, so we mustn't use vfork().
|
|
return false;
|
|
#endif
|
|
#if defined(__SANITIZE_THREAD__) || __has_feature(thread_sanitizer)
|
|
// Ditto, apparently
|
|
return false;
|
|
#endif
|
|
#if defined(Q_OS_LINUX) && !QT_CONFIG(forkfd_pidfd)
|
|
// some broken environments are known to have problems with the new Linux
|
|
// API, so we have a way for users to opt-out during configure time (see
|
|
// QTBUG-86285)
|
|
return false;
|
|
#endif
|
|
#if defined(Q_OS_DARWIN)
|
|
// Using vfork() for startDetached() is causing problems. We don't know
|
|
// why: without the tools to investigate why it happens, we didn't bother.
|
|
return false;
|
|
#endif
|
|
|
|
// Dynamically detect whether libasan or libtsan are loaded into the
|
|
// process' memory. We need this because the user's code may be compiled
|
|
// with ASan or TSan, but not Qt.
|
|
return __interceptor_vfork == nullptr;
|
|
}
|
|
|
|
inline bool QChildProcess::usingVfork() const noexcept
|
|
{
|
|
if (!globalUsingVfork())
|
|
return false;
|
|
|
|
if (!d->unixExtras || !d->unixExtras->childProcessModifier)
|
|
return true; // no modifier was supplied
|
|
|
|
// if a modifier was supplied, use fork() unless the user opts in to
|
|
// vfork()
|
|
auto flags = d->unixExtras->processParameters.flags;
|
|
return flags.testFlag(QProcess::UnixProcessFlag::UseVFork);
|
|
}
|
|
|
|
#ifdef QT_BUILD_INTERNAL
|
|
Q_AUTOTEST_EXPORT bool _qprocessUsingVfork() noexcept
|
|
{
|
|
return globalUsingVfork();
|
|
}
|
|
#endif
|
|
|
|
void QProcessPrivate::startProcess()
|
|
{
|
|
Q_Q(QProcess);
|
|
q->setProcessState(QProcess::Starting);
|
|
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::startProcess()");
|
|
#endif
|
|
|
|
// Initialize pipes
|
|
if (!openChannels()) {
|
|
// openChannel sets the error string
|
|
Q_ASSERT(!errorString.isEmpty());
|
|
cleanup();
|
|
return;
|
|
}
|
|
if (qt_create_pipe(childStartedPipe) != 0) {
|
|
setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
|
|
cleanup();
|
|
return;
|
|
}
|
|
|
|
if (threadData.loadRelaxed()->hasEventDispatcher()) {
|
|
// Set up to notify about startup completion (and premature death).
|
|
// Once the process has started successfully, we reconfigure the
|
|
// notifier to watch the fork_fd for expected death.
|
|
stateNotifier = new QSocketNotifier(childStartedPipe[0],
|
|
QSocketNotifier::Read, q);
|
|
QObject::connect(stateNotifier, SIGNAL(activated(QSocketDescriptor)),
|
|
q, SLOT(_q_startupNotification()));
|
|
}
|
|
|
|
// Prepare the arguments and the environment
|
|
QChildProcess childProcess(this);
|
|
if (!childProcess.ok()) {
|
|
Q_ASSERT(processError != QProcess::UnknownError);
|
|
return;
|
|
}
|
|
|
|
// Start the child.
|
|
forkfd = childProcess.startChild(&pid);
|
|
int lastForkErrno = errno;
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// we need an errno number to use to indicate the child process modifier threw,
|
|
// something the regular operations shouldn't set.
|
|
static constexpr int FakeErrnoForThrow = std::numeric_limits<int>::max();
|
|
|
|
static QString startFailureErrorMessage(ChildError &err, [[maybe_unused]] ssize_t bytesRead)
|
|
{
|
|
// ChildError is less than the POSIX pipe buffer atomic size, so the read
|
|
// must not have been truncated
|
|
Q_ASSERT(bytesRead == sizeof(err));
|
|
|
|
qsizetype len = qstrnlen(err.function, sizeof(err.function));
|
|
QString complement = QString::fromUtf8(err.function, len);
|
|
if (err.code == FakeErrnoForThrow)
|
|
return QProcess::tr("Child process modifier threw an exception: %1")
|
|
.arg(std::move(complement));
|
|
if (err.code == 0)
|
|
return QProcess::tr("Child process modifier reported error: %1")
|
|
.arg(std::move(complement));
|
|
if (err.code < 0)
|
|
return QProcess::tr("Child process modifier reported error: %1: %2")
|
|
.arg(std::move(complement), qt_error_string(-err.code));
|
|
return QProcess::tr("Child process set up failed: %1: %2")
|
|
.arg(std::move(complement), qt_error_string(err.code));
|
|
}
|
|
|
|
Q_NORETURN void
|
|
failChildProcess(const QProcessPrivate *d, const char *description, int code) noexcept
|
|
{
|
|
ChildError error = {};
|
|
error.code = code;
|
|
qstrncpy(error.function, description, sizeof(error.function));
|
|
qt_safe_write(d->childStartedPipe[1], &error, sizeof(error));
|
|
_exit(-1);
|
|
}
|
|
|
|
void QProcess::failChildProcessModifier(const char *description, int error) noexcept
|
|
{
|
|
// We signal user errors with negative errnos
|
|
failChildProcess(d_func(), description, -error);
|
|
}
|
|
|
|
// See IMPORTANT notice below
|
|
static const char *applyProcessParameters(const QProcess::UnixProcessParameters ¶ms)
|
|
{
|
|
// Apply Unix signal handler parameters.
|
|
// We don't expect signal() to fail, so we ignore its return value
|
|
bool ignore_sigpipe = params.flags.testFlag(QProcess::UnixProcessFlag::IgnoreSigPipe);
|
|
if (ignore_sigpipe)
|
|
QtVforkSafe::change_sigpipe(SIG_IGN);
|
|
if (params.flags.testFlag(QProcess::UnixProcessFlag::ResetSignalHandlers)) {
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
for (int sig = 1; sig < NSIG; ++sig) {
|
|
if (!ignore_sigpipe || sig != SIGPIPE)
|
|
QtVforkSafe::sigaction(sig, &sa, nullptr);
|
|
}
|
|
|
|
// and unmask all signals
|
|
sigset_t set;
|
|
sigemptyset(&set);
|
|
sigprocmask(SIG_SETMASK, &set, nullptr);
|
|
}
|
|
|
|
// Close all file descriptors above stderr.
|
|
// This isn't expected to fail, so we ignore close()'s return value.
|
|
if (params.flags.testFlag(QProcess::UnixProcessFlag::CloseFileDescriptors)) {
|
|
int r = -1;
|
|
int fd = qMax(STDERR_FILENO + 1, params.lowestFileDescriptorToClose);
|
|
#if QT_CONFIG(close_range)
|
|
// On FreeBSD, this probably won't fail.
|
|
// On Linux, this will fail with ENOSYS before kernel 5.9.
|
|
r = close_range(fd, INT_MAX, 0);
|
|
#endif
|
|
if (r == -1) {
|
|
// We *could* read /dev/fd to find out what file descriptors are
|
|
// open, but we won't. We CANNOT use opendir() here because it
|
|
// allocates memory. Using getdents(2) plus either strtoul() or
|
|
// std::from_chars() would be acceptable.
|
|
int max_fd = INT_MAX;
|
|
if (struct rlimit limit; getrlimit(RLIMIT_NOFILE, &limit) == 0)
|
|
max_fd = limit.rlim_cur;
|
|
for ( ; fd < max_fd; ++fd)
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
// Apply session and process group settings. This may fail.
|
|
if (params.flags.testFlag(QProcess::UnixProcessFlag::CreateNewSession)) {
|
|
if (setsid() < 0)
|
|
return "setsid";
|
|
}
|
|
|
|
// Disconnect from the controlling TTY. This probably won't fail. Must be
|
|
// done after the session settings from above.
|
|
if (params.flags.testFlag(QProcess::UnixProcessFlag::DisconnectControllingTerminal)) {
|
|
if (int fd = open(_PATH_TTY, O_RDONLY | O_NOCTTY); fd >= 0) {
|
|
// we still have a controlling TTY; give it up
|
|
int r = ioctl(fd, TIOCNOTTY);
|
|
int savedErrno = errno;
|
|
close(fd);
|
|
if (r != 0) {
|
|
errno = savedErrno;
|
|
return "ioctl";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply UID and GID parameters last. This isn't expected to fail either:
|
|
// either we're trying to impersonate what we already are, or we're EUID or
|
|
// EGID root, in which case we are allowed to do this.
|
|
if (params.flags.testFlag(QProcess::UnixProcessFlag::ResetIds)) {
|
|
int r = setgid(getgid());
|
|
r = setuid(getuid());
|
|
(void) r;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// the noexcept here adds an extra layer of protection
|
|
static void callChildProcessModifier(const QProcessPrivate *d) noexcept
|
|
{
|
|
QT_TRY {
|
|
if (d->unixExtras->childProcessModifier)
|
|
d->unixExtras->childProcessModifier();
|
|
} QT_CATCH (std::exception &e) {
|
|
failChildProcess(d, e.what(), FakeErrnoForThrow);
|
|
} QT_CATCH (...) {
|
|
failChildProcess(d, "throw", FakeErrnoForThrow);
|
|
}
|
|
}
|
|
|
|
// IMPORTANT:
|
|
//
|
|
// This function is called in a vfork() context on some OSes (notably, Linux
|
|
// with forkfd), so it MUST NOT modify any non-local variable because it's
|
|
// still sharing memory with the parent process.
|
|
void QChildProcess::startProcess() const noexcept
|
|
{
|
|
// Render channels configuration.
|
|
d->commitChannels();
|
|
|
|
// make sure this fd is closed if execv() succeeds
|
|
qt_safe_close(d->childStartedPipe[0]);
|
|
|
|
// enter the working directory
|
|
if (workingDirectory >= 0 && fchdir(workingDirectory) == -1)
|
|
failChildProcess(d, "fchdir", errno);
|
|
|
|
bool sigpipeHandled = false;
|
|
bool sigmaskHandled = false;
|
|
if (d->unixExtras) {
|
|
// FIRST we call the user modifier function, before we dropping
|
|
// privileges or closing non-standard file descriptors
|
|
callChildProcessModifier(d);
|
|
|
|
// then we apply our other user-provided parameters
|
|
if (const char *what = applyProcessParameters(d->unixExtras->processParameters))
|
|
failChildProcess(d, what, errno);
|
|
|
|
auto flags = d->unixExtras->processParameters.flags;
|
|
using P = QProcess::UnixProcessFlag;
|
|
sigpipeHandled = flags.testAnyFlags(P::ResetSignalHandlers | P::IgnoreSigPipe);
|
|
sigmaskHandled = flags.testFlag(P::ResetSignalHandlers);
|
|
}
|
|
if (!sigpipeHandled) {
|
|
// reset the signal that we ignored
|
|
QtVforkSafe::change_sigpipe(SIG_DFL); // reset the signal that we ignored
|
|
}
|
|
if (!sigmaskHandled) {
|
|
// restore the signal mask from the parent, if applyProcessParameters()
|
|
// hasn't completely reset it
|
|
restoreSignalMask();
|
|
}
|
|
|
|
// execute the process
|
|
if (!envp.pointers)
|
|
qt_safe_execv(argv[0], argv);
|
|
else
|
|
qt_safe_execve(argv[0], argv, envp);
|
|
failChildProcess(d, "execve", errno);
|
|
}
|
|
|
|
bool QProcessPrivate::processStarted(QString *errorMessage)
|
|
{
|
|
Q_Q(QProcess);
|
|
|
|
ChildError buf;
|
|
ssize_t ret = qt_safe_read(childStartedPipe[0], &buf, sizeof(buf));
|
|
|
|
if (stateNotifier) {
|
|
stateNotifier->setEnabled(false);
|
|
stateNotifier->disconnect(q);
|
|
}
|
|
qt_safe_close(childStartedPipe[0]);
|
|
childStartedPipe[0] = -1;
|
|
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::processStarted() == %s", ret <= 0 ? "true" : "false");
|
|
#endif
|
|
|
|
if (ret <= 0) { // process successfully started
|
|
if (stateNotifier) {
|
|
QObject::connect(stateNotifier, SIGNAL(activated(QSocketDescriptor)),
|
|
q, SLOT(_q_processDied()));
|
|
stateNotifier->setSocket(forkfd);
|
|
stateNotifier->setEnabled(true);
|
|
}
|
|
if (stdoutChannel.notifier)
|
|
stdoutChannel.notifier->setEnabled(true);
|
|
if (stderrChannel.notifier)
|
|
stderrChannel.notifier->setEnabled(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
// did we read an error message?
|
|
if (errorMessage)
|
|
*errorMessage = startFailureErrorMessage(buf, ret);
|
|
|
|
return false;
|
|
}
|
|
|
|
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, QtDebugUtils::toPrintable(data, bytesRead, 16).constData(), maxlen, bytesRead);
|
|
errno = save_errno;
|
|
#endif
|
|
if (bytesRead == -1 && errno == EWOULDBLOCK)
|
|
return -2;
|
|
return bytesRead;
|
|
}
|
|
|
|
/*! \reimp
|
|
*/
|
|
qint64 QProcess::writeData(const char *data, qint64 len)
|
|
{
|
|
Q_D(QProcess);
|
|
|
|
if (d->stdinChannel.closed) {
|
|
#if defined QPROCESS_DEBUG
|
|
qDebug("QProcess::writeData(%p \"%s\", %lld) == 0 (write channel closing)",
|
|
data, QtDebugUtils::toPrintable(data, len, 16).constData(), len);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
d->write(data, len);
|
|
if (d->stdinChannel.notifier)
|
|
d->stdinChannel.notifier->setEnabled(true);
|
|
|
|
#if defined QPROCESS_DEBUG
|
|
qDebug("QProcess::writeData(%p \"%s\", %lld) == %lld (written to buffer)",
|
|
data, QtDebugUtils::toPrintable(data, len, 16).constData(), len, len);
|
|
#endif
|
|
return len;
|
|
}
|
|
|
|
bool QProcessPrivate::_q_canWrite()
|
|
{
|
|
if (writeBuffer.isEmpty()) {
|
|
if (stdinChannel.notifier)
|
|
stdinChannel.notifier->setEnabled(false);
|
|
#if defined QPROCESS_DEBUG
|
|
qDebug("QProcessPrivate::canWrite(), not writing anything (empty write buffer).");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
const bool writeSucceeded = writeToStdin();
|
|
|
|
if (writeBuffer.isEmpty() && stdinChannel.closed)
|
|
closeWriteChannel();
|
|
else if (stdinChannel.notifier)
|
|
stdinChannel.notifier->setEnabled(!writeBuffer.isEmpty());
|
|
|
|
return writeSucceeded;
|
|
}
|
|
|
|
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,
|
|
QtDebugUtils::toPrintable(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, SIGTERM);
|
|
}
|
|
|
|
void QProcessPrivate::killProcess()
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::killProcess() pid=%jd", intmax_t(pid));
|
|
#endif
|
|
if (pid > 0)
|
|
::kill(pid, SIGKILL);
|
|
}
|
|
|
|
bool QProcessPrivate::waitForStarted(const QDeadlineTimer &deadline)
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
const qint64 msecs = deadline.remainingTime();
|
|
qDebug("QProcessPrivate::waitForStarted(%lld) waiting for child to start (fd = %d)",
|
|
msecs, childStartedPipe[0]);
|
|
#endif
|
|
|
|
pollfd pfd = qt_make_pollfd(childStartedPipe[0], POLLIN);
|
|
|
|
if (qt_safe_poll(&pfd, 1, deadline) == 0) {
|
|
setError(QProcess::Timedout);
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::waitForStarted(%lld) == 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(const QDeadlineTimer &deadline)
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::waitForReadyRead(%lld)", deadline.remainingTime());
|
|
#endif
|
|
|
|
forever {
|
|
QProcessPoller poller(*this);
|
|
|
|
int ret = poller.poll(deadline);
|
|
|
|
if (ret < 0) {
|
|
break;
|
|
}
|
|
if (ret == 0) {
|
|
setError(QProcess::Timedout);
|
|
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;
|
|
|
|
// We do this after checking the pipes, so we cannot reach it as long
|
|
// as there is any data left to be read from an already dead process.
|
|
if (qt_pollfd_check(poller.forkfd(), POLLIN)) {
|
|
processFinished();
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool QProcessPrivate::waitForBytesWritten(const QDeadlineTimer &deadline)
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::waitForBytesWritten(%lld)", deadline.remainingTime());
|
|
#endif
|
|
|
|
while (!writeBuffer.isEmpty()) {
|
|
QProcessPoller poller(*this);
|
|
|
|
int ret = poller.poll(deadline);
|
|
|
|
if (ret < 0) {
|
|
break;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
setError(QProcess::Timedout);
|
|
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)) {
|
|
processFinished();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QProcessPrivate::waitForFinished(const QDeadlineTimer &deadline)
|
|
{
|
|
#if defined (QPROCESS_DEBUG)
|
|
qDebug("QProcessPrivate::waitForFinished(%lld)", deadline.remainingTime());
|
|
#endif
|
|
|
|
forever {
|
|
QProcessPoller poller(*this);
|
|
|
|
int ret = poller.poll(deadline);
|
|
|
|
if (ret < 0) {
|
|
break;
|
|
}
|
|
if (ret == 0) {
|
|
setError(QProcess::Timedout);
|
|
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)) {
|
|
processFinished();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QProcessPrivate::waitForDeadChild()
|
|
{
|
|
Q_ASSERT(forkfd != -1);
|
|
|
|
// read the process information from our fd
|
|
forkfd_info info = {}; // Silence -Wmaybe-uninitialized; Thiago says forkfd_wait cannot fail here
|
|
// (QTBUG-119081)
|
|
int ret;
|
|
QT_EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr));
|
|
|
|
exitCode = info.status;
|
|
exitStatus = info.code == CLD_EXITED ? QProcess::NormalExit : QProcess::CrashExit;
|
|
|
|
delete stateNotifier;
|
|
stateNotifier = nullptr;
|
|
|
|
QT_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?" << (info.code != CLD_EXITED);
|
|
#endif
|
|
}
|
|
|
|
bool QProcessPrivate::startDetached(qint64 *pid)
|
|
{
|
|
AutoPipe startedPipe, pidPipe;
|
|
if (!startedPipe || !pidPipe) {
|
|
setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
|
|
return false;
|
|
}
|
|
|
|
if (!openChannelsForDetached()) {
|
|
// openChannel sets the error string
|
|
closeChannels();
|
|
return false;
|
|
}
|
|
|
|
// see startProcess() for more information
|
|
QChildProcess childProcess(this);
|
|
if (!childProcess.ok()) {
|
|
Q_ASSERT(processError != QProcess::UnknownError);
|
|
return false;
|
|
}
|
|
|
|
childStartedPipe[1] = startedPipe[1]; // for failChildProcess()
|
|
pid_t childPid = childProcess.doFork([&] {
|
|
::setsid();
|
|
|
|
qt_safe_close(startedPipe[0]);
|
|
qt_safe_close(pidPipe[0]);
|
|
|
|
pid_t doubleForkPid;
|
|
if (childProcess.startChild(&doubleForkPid) == -1)
|
|
failChildProcess(this, "fork", errno);
|
|
|
|
// success
|
|
qt_safe_write(pidPipe[1], &doubleForkPid, sizeof(pid_t));
|
|
return 0;
|
|
});
|
|
childStartedPipe[1] = -1;
|
|
|
|
int savedErrno = errno;
|
|
closeChannels();
|
|
|
|
if (childPid == -1) {
|
|
setErrorAndEmit(QProcess::FailedToStart, "fork: "_L1 + qt_error_string(savedErrno));
|
|
return false;
|
|
}
|
|
|
|
// close the writing ends of the pipes so we can properly get EOFs
|
|
qt_safe_close(pidPipe[1]);
|
|
qt_safe_close(startedPipe[1]);
|
|
pidPipe[1] = startedPipe[1] = -1;
|
|
|
|
// This read() will block until we're cleared to proceed. If it returns 0
|
|
// (EOF), it means the direct child has exited and the grandchild
|
|
// successfully execve()'d the target process. If it returns any positive
|
|
// result, it means one of the two children wrote an error result. Negative
|
|
// values should not happen.
|
|
ChildError childStatus;
|
|
ssize_t startResult = qt_safe_read(startedPipe[0], &childStatus, sizeof(childStatus));
|
|
|
|
// reap the intermediate child
|
|
int result;
|
|
qt_safe_waitpid(childPid, &result, 0);
|
|
|
|
bool success = (startResult == 0); // nothing written -> no error
|
|
if (success && pid) {
|
|
pid_t actualPid;
|
|
if (qt_safe_read(pidPipe[0], &actualPid, sizeof(pid_t)) != sizeof(pid_t))
|
|
actualPid = 0; // this shouldn't happen!
|
|
*pid = actualPid;
|
|
} else if (!success) {
|
|
if (pid)
|
|
*pid = -1;
|
|
setErrorAndEmit(QProcess::FailedToStart,
|
|
startFailureErrorMessage(childStatus, startResult));
|
|
}
|
|
return success;
|
|
}
|
|
|
|
#endif // QT_CONFIG(process)
|
|
|
|
QT_END_NAMESPACE
|