diff options
Diffstat (limited to 'src/corelib/io/qprocess_unix.cpp')
-rw-r--r-- | src/corelib/io/qprocess_unix.cpp | 1285 |
1 files changed, 795 insertions, 490 deletions
diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp index 5fce848c2c..5c696433fd 100644 --- a/src/corelib/io/qprocess_unix.cpp +++ b/src/corelib/io/qprocess_unix.cpp @@ -1,88 +1,11 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// 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" - -#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 <private/qdebug_p.h> #include "qplatformdefs.h" #include "qprocess.h" @@ -91,7 +14,7 @@ QT_END_NAMESPACE #include "private/qcore_unix_p.h" #include "private/qlocking_p.h" -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN #include <private/qcore_mac_p.h> #endif @@ -104,27 +27,51 @@ QT_END_NAMESPACE #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 <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 -#if !defined(Q_OS_DARWIN) +using namespace Qt::StringLiterals; -QT_BEGIN_INCLUDE_NAMESPACE -extern char **environ; -QT_END_INCLUDE_NAMESPACE +#if !defined(Q_OS_DARWIN) QProcessEnvironment QProcessEnvironment::systemEnvironment() { @@ -147,20 +94,113 @@ QProcessEnvironment QProcessEnvironment::systemEnvironment() #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(int timeout); + 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]; } - pollfd &childStartedPipe() { return pfds[4]; } - enum { n_pfds = 5 }; + enum { n_pfds = 4 }; pollfd pfds[n_pfds]; }; @@ -178,15 +218,196 @@ QProcessPoller::QProcessPoller(const QProcessPrivate &proc) } 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); + + restoreThreadCancellations(); + restoreSignalMask(); + } + + 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'; + } - if (proc.processState == QProcess::Starting) - childStartedPipe().fd = proc.childStartedPipe[0]; + updatePointers(count); } -int QProcessPoller::poll(int timeout) +QChildProcess::CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment) { - const nfds_t nfds = (childStartedPipe().fd == -1) ? 4 : 5; - return qt_poll_msecs(pfds, nfds, timeout); + 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 @@ -203,7 +424,8 @@ static int qt_create_pipe(int *pipe) qt_safe_close(pipe[1]); int pipe_ret = qt_safe_pipe(pipe); if (pipe_ret != 0) { - qErrnoWarning("QProcessPrivate::createPipe: Cannot create pipe %p", pipe); + QScopedValueRollback rollback(errno); + qErrnoWarning("QProcess: Cannot create pipe"); } return pipe_ret; } @@ -222,40 +444,51 @@ void QProcessPrivate::destroyPipe(int *pipe) 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. - - 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) + 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(channel.pipe[1], - QSocketNotifier::Write, q); - channel.notifier->setEnabled(false); + 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(channel.pipe[0], - QSocketNotifier::Read, q); + channel.notifier = new QSocketNotifier(QSocketNotifier::Read, q); + channel.notifier->setSocket(channel.pipe[0]); const char *receiver; if (&channel == &stdoutChannel) receiver = SLOT(_q_canReadStandardOutput()); @@ -292,7 +525,6 @@ bool QProcessPrivate::openChannel(Channel &channel) 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"); @@ -324,8 +556,10 @@ bool QProcessPrivate::openChannel(Channel &channel) 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) + 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]; @@ -334,77 +568,37 @@ bool QProcessPrivate::openChannel(Channel &channel) } } -static char **_q_dupEnvironment(const QProcessEnvironmentPrivate::Map &environment, int *envc) +void QProcessPrivate::commitChannels() const { - *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()); - } + // 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); - return envp; + // 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); + } } -void QProcessPrivate::startProcess() +inline QString QChildProcess::resolveExecutable(const QString &program) { - 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 +#ifdef Q_OS_DARWIN // allow invoking of .app bundles on the Mac. QFileInfo fileInfo(program); - if (encodedProgramName.endsWith(".app") && fileInfo.isDir()) { + 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. - static QBasicMutex cfbundleMutex; + 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 ... @@ -414,77 +608,116 @@ void QProcessPrivate::startProcess() } if (url) { const QCFString str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); - encodedProgramName += (QDir::separator() + QDir(program).relativeFilePath(QString::fromCFString(str))).toUtf8(); + return QString::fromCFString(str); } } #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 (!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); } - 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); + 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; } - // 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(); + 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())); } - // Select FFD_USE_FORK and FFD_VFORK_SEMANTICS based on whether there's - // user code running in the child process: if there is, we don't know what - // the user will want to do, so we err on the safe side and request an - // actual fork() (for example, the user could attempt to do some - // synchronization with the parent process). But if there isn't, then our - // code in execChild() is just a handful of dup2() and a chdir(), so it's - // safe with vfork semantics: suspend the parent execution until the child - // either execve()s or _exit()s. - int ffdflags = FFD_CLOEXEC; - if (childProcessModifier) - ffdflags |= FFD_USE_FORK; - - // QTBUG-86285 -#if !QT_CONFIG(forkfd_pidfd) - ffdflags |= FFD_USE_FORK; -#endif + // Prepare the arguments and the environment + QChildProcess childProcess(this); + if (!childProcess.ok()) { + Q_ASSERT(processError != QProcess::UnknownError); + return; + } - pid_t childPid; - forkfd = ::forkfd(ffdflags , &childPid); + // Start the child. + forkfd = childProcess.startChild(&pid); 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) @@ -497,13 +730,7 @@ void QProcessPrivate::startProcess() return; } - // Start the child. - if (forkfd == FFD_CHILD_PROCESS) { - execChild(workingDirPtr, argv, envp); - ::_exit(-1); - } - - pid = Q_PID(childPid); + Q_ASSERT(pid > 0); // parent // close the ends we don't use and make all pipes non-blocking @@ -532,99 +759,229 @@ void QProcessPrivate::startProcess() } 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())); - } +// 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)); } -struct ChildError +Q_NORETURN void +failChildProcess(const QProcessPrivate *d, const char *description, int code) noexcept { - int code; - char function[8]; -}; + ChildError error = {}; + error.code = code; + qstrncpy(error.function, description, sizeof(error.function)); + qt_safe_write(d->childStartedPipe[1], &error, sizeof(error)); + _exit(-1); +} -void QProcessPrivate::execChild(const char *workingDir, char **argv, char **envp) +void QProcess::failChildProcessModifier(const char *description, int error) noexcept { - ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored + // We signal user errors with negative errnos + failChildProcess(d_func(), description, -error); +} - ChildError error = { 0, {} }; // force zeroing of function[8] +// 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); + } - // copy the stdin socket if asked to (without closing on exec) - if (inputChannelMode != QProcess::ForwardedInputChannel) - qt_safe_dup2(stdinChannel.pipe[0], STDIN_FILENO, 0); + // and unmask all signals + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, nullptr); + } - // 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); + // 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); + } + } - // 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); + // 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(childStartedPipe[0]); + qt_safe_close(d->childStartedPipe[0]); // enter the working directory - if (workingDir && QT_CHDIR(workingDir) == -1) { - // failed, stop the process - strcpy(error.function, "chdir"); - goto report_errno; + 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(); } - - if (childProcessModifier) - childProcessModifier(); // execute the process - if (!envp) { + if (!envp.pointers) 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 + else 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; + failChildProcess(d, "execve", errno); } bool QProcessPrivate::processStarted(QString *errorMessage) { + Q_Q(QProcess); + ChildError buf; - int ret = qt_safe_read(childStartedPipe[0], &buf, sizeof(buf)); + ssize_t ret = qt_safe_read(childStartedPipe[0], &buf, sizeof(buf)); - if (startupSocketNotifier) { - startupSocketNotifier->setEnabled(false); - startupSocketNotifier->deleteLater(); - startupSocketNotifier = nullptr; + if (stateNotifier) { + stateNotifier->setEnabled(false); + stateNotifier->disconnect(q); } qt_safe_close(childStartedPipe[0]); childStartedPipe[0] = -1; #if defined (QPROCESS_DEBUG) - qDebug("QProcessPrivate::processStarted() == %s", i <= 0 ? "true" : "false"); + 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 (ret > 0 && errorMessage) - *errorMessage = QLatin1String(buf.function) + QLatin1String(": ") + qt_error_string(buf.code); + if (errorMessage) + *errorMessage = startFailureErrorMessage(buf, ret); - return ret <= 0; + return false; } qint64 QProcessPrivate::bytesAvailableInChannel(const Channel *channel) const @@ -648,7 +1005,7 @@ qint64 QProcessPrivate::readFromChannel(const Channel *channel, char *data, qint int save_errno = errno; qDebug("QProcessPrivate::readFromChannel(%d, %p \"%s\", %lld) == %lld", int(channel - &stdinChannel), - data, qt_prettyDebug(data, bytesRead, 16).constData(), maxlen, bytesRead); + data, QtDebugUtils::toPrintable(data, bytesRead, 16).constData(), maxlen, bytesRead); errno = save_errno; #endif if (bytesRead == -1 && errno == EWOULDBLOCK) @@ -656,6 +1013,52 @@ qint64 QProcessPrivate::readFromChannel(const Channel *channel, char *data, qint 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(); @@ -663,8 +1066,8 @@ bool QProcessPrivate::writeToStdin() 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); + 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 @@ -691,34 +1094,35 @@ bool QProcessPrivate::writeToStdin() void QProcessPrivate::terminateProcess() { #if defined (QPROCESS_DEBUG) - qDebug("QProcessPrivate::terminateProcess()"); + qDebug("QProcessPrivate::terminateProcess() pid=%jd", intmax_t(pid)); #endif - if (pid) - ::kill(pid_t(pid), SIGTERM); + if (pid > 0) + ::kill(pid, SIGTERM); } void QProcessPrivate::killProcess() { #if defined (QPROCESS_DEBUG) - qDebug("QProcessPrivate::killProcess()"); + qDebug("QProcessPrivate::killProcess() pid=%jd", intmax_t(pid)); #endif - if (pid) - ::kill(pid_t(pid), SIGKILL); + if (pid > 0) + ::kill(pid, SIGKILL); } -bool QProcessPrivate::waitForStarted(int msecs) +bool QProcessPrivate::waitForStarted(const QDeadlineTimer &deadline) { #if defined (QPROCESS_DEBUG) - qDebug("QProcessPrivate::waitForStarted(%d) waiting for child to start (fd = %d)", msecs, - childStartedPipe[0]); + 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_poll_msecs(&pfd, 1, msecs) == 0) { + if (qt_safe_poll(&pfd, 1, deadline) == 0) { setError(QProcess::Timedout); #if defined (QPROCESS_DEBUG) - qDebug("QProcessPrivate::waitForStarted(%d) == false (timed out)", msecs); + qDebug("QProcessPrivate::waitForStarted(%lld) == false (timed out)", msecs); #endif return false; } @@ -730,20 +1134,16 @@ bool QProcessPrivate::waitForStarted(int msecs) return startedEmitted; } -bool QProcessPrivate::waitForReadyRead(int msecs) +bool QProcessPrivate::waitForReadyRead(const QDeadlineTimer &deadline) { #if defined (QPROCESS_DEBUG) - qDebug("QProcessPrivate::waitForReadyRead(%d)", msecs); + qDebug("QProcessPrivate::waitForReadyRead(%lld)", deadline.remainingTime()); #endif - QElapsedTimer stopWatch; - stopWatch.start(); - forever { QProcessPoller poller(*this); - int timeout = qt_subtract_from_timeout(msecs, stopWatch.elapsed()); - int ret = poller.poll(timeout); + int ret = poller.poll(deadline); if (ret < 0) { break; @@ -753,22 +1153,14 @@ bool QProcessPrivate::waitForReadyRead(int msecs) 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)) { - bool canRead = _q_canReadStandardOutput(); - if (currentReadChannel == QProcess::StandardOutput && canRead) - readyReadEmitted = true; - } - if (qt_pollfd_check(poller.stderrPipe(), POLLIN)) { - bool canRead = _q_canReadStandardError(); - if (currentReadChannel == QProcess::StandardError && canRead) - readyReadEmitted = true; - } + 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; @@ -779,28 +1171,26 @@ bool QProcessPrivate::waitForReadyRead(int msecs) 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)) { - if (_q_processDied()) - return false; + processFinished(); + return false; } } return false; } -bool QProcessPrivate::waitForBytesWritten(int msecs) +bool QProcessPrivate::waitForBytesWritten(const QDeadlineTimer &deadline) { #if defined (QPROCESS_DEBUG) - qDebug("QProcessPrivate::waitForBytesWritten(%d)", msecs); + qDebug("QProcessPrivate::waitForBytesWritten(%lld)", deadline.remainingTime()); #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); + int ret = poller.poll(deadline); if (ret < 0) { break; @@ -811,11 +1201,6 @@ bool QProcessPrivate::waitForBytesWritten(int msecs) return false; } - if (qt_pollfd_check(poller.childStartedPipe(), POLLIN)) { - if (!_q_startupNotification()) - return false; - } - if (qt_pollfd_check(poller.stdinPipe(), POLLOUT)) return _q_canWrite(); @@ -830,28 +1215,24 @@ bool QProcessPrivate::waitForBytesWritten(int msecs) return false; if (qt_pollfd_check(poller.forkfd(), POLLIN)) { - if (_q_processDied()) - return false; + processFinished(); + return false; } } return false; } -bool QProcessPrivate::waitForFinished(int msecs) +bool QProcessPrivate::waitForFinished(const QDeadlineTimer &deadline) { #if defined (QPROCESS_DEBUG) - qDebug("QProcessPrivate::waitForFinished(%d)", msecs); + qDebug("QProcessPrivate::waitForFinished(%lld)", deadline.remainingTime()); #endif - QElapsedTimer stopWatch; - stopWatch.start(); - forever { QProcessPoller poller(*this); - int timeout = qt_subtract_from_timeout(msecs, stopWatch.elapsed()); - int ret = poller.poll(timeout); + int ret = poller.poll(deadline); if (ret < 0) { break; @@ -861,10 +1242,6 @@ bool QProcessPrivate::waitForFinished(int msecs) return false; } - if (qt_pollfd_check(poller.childStartedPipe(), POLLIN)) { - if (!_q_startupNotification()) - return false; - } if (qt_pollfd_check(poller.stdinPipe(), POLLOUT)) _q_canWrite(); @@ -879,185 +1256,113 @@ bool QProcessPrivate::waitForFinished(int msecs) return true; if (qt_pollfd_check(poller.forkfd(), POLLIN)) { - if (_q_processDied()) - return true; + processFinished(); + return true; } } return false; } -void QProcessPrivate::findExitCode() -{ -} - -bool QProcessPrivate::waitForDeadChild() +void QProcessPrivate::waitForDeadChild() { - if (forkfd == -1) - return true; // child has already exited + Q_ASSERT(forkfd != -1); // read the process information from our fd - forkfd_info info; + forkfd_info info = {}; // Silence -Wmaybe-uninitialized; Thiago says forkfd_wait cannot fail here + // (QTBUG-119081) int ret; - EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr)); + QT_EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr)); exitCode = info.status; - crashed = info.code != CLD_EXITED; + exitStatus = info.code == CLD_EXITED ? QProcess::NormalExit : QProcess::CrashExit; - delete deathNotifier; - deathNotifier = nullptr; + delete stateNotifier; + stateNotifier = nullptr; - EINTR_LOOP(ret, forkfd_close(forkfd)); + 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?" << crashed; + << exitCode << ", crashed?" << (info.code != CLD_EXITED); #endif - return true; } 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]); + AutoPipe startedPipe, pidPipe; + if (!startedPipe || !pidPipe) { + setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno)); 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]); + if (!openChannelsForDetached()) { + // openChannel sets the error string + closeChannels(); 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); + // 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 = 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; + pid_t doubleForkPid; + if (childProcess.startChild(&doubleForkPid) == -1) + failChildProcess(this, "fork", errno); - // 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(); + // success + qt_safe_write(pidPipe[1], &doubleForkPid, sizeof(pid_t)); + return 0; + }); + childStartedPipe[1] = -1; - 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); - } + int savedErrno = errno; + closeChannels(); - 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); + if (childPid == -1) { + setErrorAndEmit(QProcess::FailedToStart, "fork: "_L1 + qt_error_string(savedErrno)); + return false; } - closeChannel(&stdinChannel); - closeChannel(&stdoutChannel); - closeChannel(&stderrChannel); - qt_safe_close(startedPipe[1]); + // 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; - if (childPid == -1) { - qt_safe_close(startedPipe[0]); - qt_safe_close(pidPipe[0]); - return false; - } + // 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)); - char reply = '\0'; - int startResult = qt_safe_read(startedPipe[0], &reply, 1); + // reap the intermediate child int result; - qt_safe_close(startedPipe[0]); qt_safe_waitpid(childPid, &result, 0); - bool success = (startResult != -1 && reply == '\0'); + + bool success = (startResult == 0); // nothing written -> no error 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; - } + 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)); } - qt_safe_close(pidPipe[0]); return success; } |