diff options
Diffstat (limited to 'src/corelib/io/qprocess.cpp')
-rw-r--r-- | src/corelib/io/qprocess.cpp | 779 |
1 files changed, 470 insertions, 309 deletions
diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index f03fc067cf..108ee0b7c3 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -1,89 +1,12 @@ -/**************************************************************************** -** -** 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) 2021 The Qt Company Ltd. +// Copyright (C) 2022 Intel Corporation. +// 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 <qdir.h> #include <qscopedvaluerollback.h> -#if defined(Q_OS_WIN) -#include <qtimer.h> -#endif -#if defined QPROCESS_DEBUG -#include <qstring.h> -#include <ctype.h> - -QT_BEGIN_NAMESPACE -/* - Returns a human readable representation of the first \a len - characters in \a data. -*/ -static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) -{ - if (!data) return "(null)"; - QByteArray out; - for (int i = 0; i < len && i < maxSize; ++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: - char buf[5]; - qsnprintf(buf, sizeof(buf), "\\%3o", c); - buf[4] = '\0'; - out += QByteArray(buf); - } - } - - if (len < maxSize) - out += "..."; - - return out; -} - -QT_END_NAMESPACE - -#endif #include "qprocess.h" #include "qprocess_p.h" @@ -91,15 +14,8 @@ QT_END_NAMESPACE #include <qbytearray.h> #include <qdeadlinetimer.h> #include <qcoreapplication.h> -#include <qsocketnotifier.h> #include <qtimer.h> -#ifdef Q_OS_WIN -#include <qwineventnotifier.h> -#else -#include <private/qcore_unix_p.h> -#endif - #if __has_include(<paths.h>) #include <paths.h> #endif @@ -119,6 +35,8 @@ QT_BEGIN_NAMESPACE \reentrant \since 4.6 + \compares equality + A process's environment is composed of a set of key=value pairs known as environment variables. The QProcessEnvironment class wraps that concept and allows easy manipulation of those variables. It's meant to be used @@ -146,7 +64,7 @@ QStringList QProcessEnvironmentPrivate::toList() const QStringList result; result.reserve(vars.size()); for (auto it = vars.cbegin(), end = vars.cend(); it != end; ++it) - result << nameToString(it.key()) + QLatin1Char('=') + valueToString(it.value()); + result << nameToString(it.key()) + u'=' + valueToString(it.value()); return result; } @@ -156,7 +74,7 @@ QProcessEnvironment QProcessEnvironmentPrivate::fromList(const QStringList &list QStringList::ConstIterator it = list.constBegin(), end = list.constEnd(); for ( ; it != end; ++it) { - int pos = it->indexOf(QLatin1Char('='), 1); + const qsizetype pos = it->indexOf(u'=', 1); if (pos < 1) continue; @@ -196,14 +114,43 @@ void QProcessEnvironmentPrivate::insert(const QProcessEnvironmentPrivate &other) } /*! + \enum QProcessEnvironment::Initialization + + This enum contains a token that is used to disambiguate constructors. + + \value InheritFromParent A QProcessEnvironment will be created that, when + set on a QProcess, causes it to inherit variables from its parent. + + \since 6.3 +*/ + +/*! Creates a new QProcessEnvironment object. This constructor creates an empty environment. If set on a QProcess, this will cause the current - environment variables to be removed. + environment variables to be removed (except for PATH and SystemRoot + on Windows). */ -QProcessEnvironment::QProcessEnvironment() - : d(nullptr) -{ -} +QProcessEnvironment::QProcessEnvironment() : d(new QProcessEnvironmentPrivate) { } + +/*! + Creates an object that when set on QProcess will cause it to be executed with + environment variables inherited from its parent process. + + \note The created object does not store any environment variables by itself, + it just indicates to QProcess to arrange for inheriting the environment at the + time when the new process is started. Adding any environment variables to + the created object will disable inheritance of the environment and result in + an environment containing only the added environment variables. + + If a modified version of the parent environment is wanted, start with the + return value of \c systemEnvironment() and modify that (but note that changes to + the parent process's environment after that is created won't be reflected + in the modified environment). + + \sa inheritsFromParent(), systemEnvironment() + \since 6.3 +*/ +QProcessEnvironment::QProcessEnvironment(QProcessEnvironment::Initialization) noexcept { } /*! Frees the resources associated with this QProcessEnvironment object. @@ -239,15 +186,17 @@ QProcessEnvironment &QProcessEnvironment::operator=(const QProcessEnvironment &o */ /*! - \fn bool QProcessEnvironment::operator !=(const QProcessEnvironment &other) const + \fn bool QProcessEnvironment::operator!=(const QProcessEnvironment &lhs, const QProcessEnvironment &rhs) - Returns \c true if this and the \a other QProcessEnvironment objects are different. + Returns \c true if the process environment objects \a lhs and \a rhs are different. \sa operator==() */ /*! - Returns \c true if this and the \a other QProcessEnvironment objects are equal. + \fn bool QProcessEnvironment::operator==(const QProcessEnvironment &lhs, const QProcessEnvironment &rhs) + + Returns \c true if the process environment objects \a lhs and \a rhs are equal. Two QProcessEnvironment objects are considered equal if they have the same set of key=value pairs. The comparison of keys is done case-sensitive on @@ -255,26 +204,22 @@ QProcessEnvironment &QProcessEnvironment::operator=(const QProcessEnvironment &o \sa operator!=(), contains() */ -bool QProcessEnvironment::operator==(const QProcessEnvironment &other) const +bool comparesEqual(const QProcessEnvironment &lhs, const QProcessEnvironment &rhs) { - if (d == other.d) + if (lhs.d == rhs.d) return true; - if (d) { - if (other.d) { - return d->vars == other.d->vars; - } else { - return isEmpty(); - } - } else { - return other.isEmpty(); - } + + return lhs.d && rhs.d && lhs.d->vars == rhs.d->vars; } /*! Returns \c true if this QProcessEnvironment object is empty: that is there are no key=value pairs set. - \sa clear(), systemEnvironment(), insert() + This method also returns \c true for objects that were constructed using + \c{QProcessEnvironment::InheritFromParent}. + + \sa clear(), systemEnvironment(), insert(), inheritsFromParent() */ bool QProcessEnvironment::isEmpty() const { @@ -283,9 +228,24 @@ bool QProcessEnvironment::isEmpty() const } /*! + Returns \c true if this QProcessEnvironment was constructed using + \c{QProcessEnvironment::InheritFromParent}. + + \since 6.3 + \sa isEmpty() +*/ +bool QProcessEnvironment::inheritsFromParent() const +{ + return !d; +} + +/*! Removes all key=value pairs from this QProcessEnvironment object, making it empty. + If the environment was constructed using \c{QProcessEnvironment::InheritFromParent} + it remains unchanged. + \sa isEmpty(), systemEnvironment() */ void QProcessEnvironment::clear() @@ -389,6 +349,9 @@ QStringList QProcessEnvironment::toStringList() const Returns a list containing all the variable names in this QProcessEnvironment object. + + The returned list is empty for objects constructed using + \c{QProcessEnvironment::InheritFromParent}. */ QStringList QProcessEnvironment::keys() const { @@ -428,6 +391,8 @@ void QProcessPrivate::Channel::clear() process->stdoutChannel.type = Normal; process->stdoutChannel.process = nullptr; break; + default: + break; } type = Normal; @@ -491,6 +456,97 @@ void QProcessPrivate::Channel::clear() \note QProcess is not supported on VxWorks, iOS, tvOS, or watchOS. + \section1 Finding the Executable + + The program to be run can be set either by calling setProgram() or directly + in the start() call. The effect of calling start() with the program name + and arguments is equivalent to calling setProgram() and setArguments() + before that function and then calling the overload without those + parameters. + + QProcess interprets the program name in one of three different ways, + similar to how Unix shells and the Windows command interpreter operate in + their own command-lines: + + \list + \li If the program name is an absolute path, then that is the exact + executable that will be launched and QProcess performs no searching. + + \li If the program name is a relative path with more than one path + component (that is, it contains at least one slash), the starting + directory where that relative path is searched is OS-dependent: on + Windows, it's the parent process' current working dir, while on Unix it's + the one set with setWorkingDirectory(). + + \li If the program name is a plain file name with no slashes, the + behavior is operating-system dependent. On Unix systems, QProcess will + search the \c PATH environment variable; on Windows, the search is + performed by the OS and will first the parent process' current directory + before the \c PATH environment variable (see the documentation for + \l{CreateProcess} for the full list). + \endlist + + To avoid platform-dependent behavior or any issues with how the current + application was launched, it is advisable to always pass an absolute path + to the executable to be launched. For auxiliary binaries shipped with the + application, one can construct such a path starting with + QCoreApplication::applicationDirPath(). Similarly, to explicitly run an + executable that is to be found relative to the directory set with + setWorkingDirectory(), use a program path starting with "./" or "../" as + the case may be. + + On Windows, the ".exe" suffix is not required for most uses, except those + outlined in the \l{CreateProcess} documentation. Additionally, QProcess + will convert the Unix-style forward slashes to Windows path backslashes for + the program name. This allows code using QProcess to be written in a + cross-platform manner, as shown in the examples above. + + QProcess does not support directly executing Unix shell or Windows command + interpreter built-in functions, such as \c{cmd.exe}'s \c dir command or the + Bourne shell's \c export. On Unix, even though many shell built-ins are + also provided as separate executables, their behavior may differ from those + implemented as built-ins. To run those commands, one should explicitly + execute the interpreter with suitable options. For Unix systems, launch + "/bin/sh" with two arguments: "-c" and a string with the command-line to be + run. For Windows, due to the non-standard way \c{cmd.exe} parses its + command-line, use setNativeArguments() (for example, "/c dir d:"). + + \section1 Environment variables + + The QProcess API offers methods to manipulate the environment variables + that the child process will see. By default, the child process will have a + copy of the current process environment variables that exist at the time + the start() function is called. This means that any modifications performed + using qputenv() prior to that call will be reflected in the child process' + environment. Note that QProcess makes no attempt to prevent race conditions + with qputenv() happening in other threads, so it is recommended to avoid + qputenv() after the application's initial start up. + + The environment for a specific child can be modified using the + processEnvironment() and setProcessEnvironment() functions, which use the + \l QProcessEnvironment class. By default, processEnvironment() will return + an object for which QProcessEnvironment::inheritsFromParent() is true. + Setting an environment that does not inherit from the parent will cause + QProcess to use exactly that environment for the child when it is started. + + The normal scenario starts from the current environment by calling + QProcessEnvironment::systemEnvironment() and then proceeds to adding, + changing, or removing specific variables. The resulting variable roster can + then be applied to a QProcess with setProcessEnvironment(). + + It is possible to remove all variables from the environment or to start + from an empty environment, using the QProcessEnvironment() default + constructor. This is not advisable outside of controlled and + system-specific conditions, as there may be system variables that are set + in the current process environment and are required for proper execution + of the child process. + + On Windows, QProcess will copy the current process' \c "PATH" and \c + "SystemRoot" environment variables if they were unset. It is not possible + to unset them completely, but it is possible to set them to empty values. + Setting \c "PATH" to empty on Windows will likely cause the child process + to fail to start. + \section1 Communicating via Channels Processes have two predefined output channels: The standard @@ -539,11 +595,6 @@ void QProcessPrivate::Channel::clear() command line option; X11 applications generally accept a \c{-geometry} command line option. - \note On QNX, setting the working directory may cause all - application threads, with the exception of the QProcess caller - thread, to temporarily freeze during the spawning process, - owing to a limitation in the operating system. - \section1 Synchronous Process API QProcess provides a set of functions which allow it to be used @@ -571,15 +622,6 @@ void QProcessPrivate::Channel::clear() \snippet process/process.cpp 0 - \section1 Notes for Windows Users - - Some Windows commands (for example, \c dir) are not provided by - separate applications, but by the command interpreter itself. - If you attempt to use QProcess to execute these commands directly, - it won't work. One possible solution is to execute the command - interpreter itself (\c{cmd.exe} on some Windows systems), and ask - the interpreter to execute the desired command. - \sa QBuffer, QFile, QTcpSocket */ @@ -767,6 +809,98 @@ void QProcessPrivate::Channel::clear() */ /*! + \class QProcess::UnixProcessParameters + \inmodule QtCore + \note This struct is only available on Unix platforms + \since 6.6 + + This struct can be used to pass extra, Unix-specific configuration for the + child process using QProcess::setUnixProcessParameters(). + + Its members are: + \list + \li UnixProcessParameters::flags Flags, see QProcess::UnixProcessFlags + \li UnixProcessParameters::lowestFileDescriptorToClose The lowest file + descriptor to close. + \endlist + + When the QProcess::UnixProcessFlags::CloseFileDescriptors flag is set in + the \c flags field, QProcess closes the application's open file descriptors + before executing the child process. The descriptors 0, 1, and 2 (that is, + \c stdin, \c stdout, and \c stderr) are left alone, along with the ones + numbered lower than the value of the \c lowestFileDescriptorToClose field. + + All of the settings above can also be manually achieved by calling the + respective POSIX function from a handler set with + QProcess::setChildProcessModifier(). This structure allows QProcess to deal + with any platform-specific differences, benefit from certain optimizations, + and reduces code duplication. Moreover, if any of those functions fail, + QProcess will enter QProcess::FailedToStart state, while the child process + modifier callback is not allowed to fail. + + \sa QProcess::setUnixProcessParameters(), QProcess::setChildProcessModifier() +*/ + +/*! + \enum QProcess::UnixProcessFlag + \since 6.6 + + These flags can be used in the \c flags field of \l UnixProcessParameters. + + \value CloseFileDescriptors Close all file descriptors above the threshold + defined by \c lowestFileDescriptorToClose, preventing any currently + open descriptor in the parent process from accidentally leaking to the + child. The \c stdin, \c stdout, and \c stderr file descriptors are + never closed. + + \value [since 6.7] CreateNewSession Starts a new process session, by calling + \c{setsid(2)}. This allows the child process to outlive the session + the current process is in. This is one of the steps that + startDetached() takes to allow the process to detach, and is also one + of the steps to daemonize a process. + + \value [since 6.7] DisconnectControllingTerminal Requests that the process + disconnect from its controlling terminal, if it has one. If it has + none, nothing happens. Processes still connected to a controlling + terminal may get a Hang Up (\c SIGHUP) signal if the terminal + closes, or one of the other terminal-control signals (\c SIGTSTP, \c + SIGTTIN, \c SIGTTOU). Note that on some operating systems, a process + may only disconnect from the controlling terminal if it is the + session leader, meaning the \c CreateNewSession flag may be + required. Like it, this is one of the steps to daemonize a process. + + \value IgnoreSigPipe Always sets the \c SIGPIPE signal to ignored + (\c SIG_IGN), even if the \c ResetSignalHandlers flag was set. By + default, if the child attempts to write to its standard output or + standard error after the respective channel was closed with + QProcess::closeReadChannel(), it would get the \c SIGPIPE signal and + terminate immediately; with this flag, the write operation fails + without a signal and the child may continue executing. + + \value [since 6.7] ResetIds Drops any retained, effective user or group + ID the current process may still have (see \c{setuid(2)} and + \c{setgid(2)}, plus QCoreApplication::setSetuidAllowed()). This is + useful if the current process was setuid or setgid and does not wish + the child process to retain the elevated privileges. + + \value ResetSignalHandlers Resets all Unix signal handlers back to their + default state (that is, pass \c SIG_DFL to \c{signal(2)}). This flag + is useful to ensure any ignored (\c SIG_IGN) signal does not affect + the child's behavior. + + \value UseVFork Requests that QProcess use \c{vfork(2)} to start the child + process. Use this flag to indicate that the callback function set + with setChildProcessModifier() is safe to execute in the child side of + a \c{vfork(2)}; that is, the callback does not modify any non-local + variables (directly or through any function it calls), nor attempts + to communicate with the parent process. It is implementation-defined + if QProcess will actually use \c{vfork(2)} and if \c{vfork(2)} is + different from standard \c{fork(2)}. + + \sa setUnixProcessParameters(), unixProcessParameters() +*/ + +/*! \fn void QProcess::errorOccurred(QProcess::ProcessError error) \since 5.6 @@ -828,7 +962,9 @@ void QProcessPrivate::Channel::clear() QProcessPrivate::QProcessPrivate() { readBufferChunkSize = QRINGBUFFER_CHUNKSIZE; +#ifndef Q_OS_WIN writeBufferChunkSize = QRINGBUFFER_CHUNKSIZE; +#endif } /*! @@ -845,46 +981,6 @@ QProcessPrivate::~QProcessPrivate() /*! \internal */ -void QProcessPrivate::cleanup() -{ - q_func()->setProcessState(QProcess::NotRunning); -#ifdef Q_OS_WIN - if (stdinWriteTrigger) { - delete stdinWriteTrigger; - stdinWriteTrigger = 0; - } - if (processFinishedNotifier) { - delete processFinishedNotifier; - processFinishedNotifier = 0; - } - if (pid) { - CloseHandle(pid->hThread); - CloseHandle(pid->hProcess); - delete pid; - pid = nullptr; - } -#else - pid = 0; -#endif - - if (stateNotifier) { - delete stateNotifier; - stateNotifier = nullptr; - } - closeChannel(&stdoutChannel); - closeChannel(&stderrChannel); - closeChannel(&stdinChannel); - destroyPipe(childStartedPipe); -#ifdef Q_OS_UNIX - if (forkfd != -1) - qt_safe_close(forkfd); - forkfd = -1; -#endif -} - -/*! - \internal -*/ void QProcessPrivate::setError(QProcess::ProcessError error, const QString &description) { processError = error; @@ -922,7 +1018,7 @@ void QProcessPrivate::setErrorAndEmit(QProcess::ProcessError error, const QStrin Q_Q(QProcess); Q_ASSERT(error != QProcess::UnknownError); setError(error, description); - emit q->errorOccurred(processError); + emit q->errorOccurred(QProcess::ProcessError(processError)); } /*! @@ -963,6 +1059,16 @@ bool QProcessPrivate::openChannels() /*! \internal */ +void QProcessPrivate::closeChannels() +{ + closeChannel(&stdoutChannel); + closeChannel(&stderrChannel); + closeChannel(&stdinChannel); +} + +/*! + \internal +*/ bool QProcessPrivate::openChannelsForDetached() { // stdin channel. @@ -1094,29 +1200,6 @@ bool QProcessPrivate::_q_canReadStandardError() /*! \internal */ -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; -} - -/*! - \internal -*/ void QProcessPrivate::_q_processDied() { #if defined QPROCESS_DEBUG @@ -1158,10 +1241,8 @@ void QProcessPrivate::processFinished() cleanup(); - if (crashed) { - exitStatus = QProcess::CrashExit; + if (exitStatus == QProcess::CrashExit) setErrorAndEmit(QProcess::Crashed); - } // we received EOF now: emit q->readChannelFinished(); @@ -1169,7 +1250,7 @@ void QProcessPrivate::processFinished() //emit q->standardOutputClosed(); //emit q->standardErrorClosed(); - emit q->finished(exitCode, exitStatus); + emit q->finished(exitCode, QProcess::ExitStatus(exitStatus)); #if defined QPROCESS_DEBUG qDebug("QProcessPrivate::processFinished(): process is dead"); @@ -1211,11 +1292,6 @@ void QProcessPrivate::closeWriteChannel() qDebug("QProcessPrivate::closeWriteChannel()"); #endif -#ifdef Q_OS_WIN - // ### Find a better fix, feeding the process little by little - // instead. - flushPipeWriter(); -#endif closeChannel(&stdinChannel); } @@ -1259,7 +1335,7 @@ QProcess::~QProcess() QProcess::ProcessChannelMode QProcess::processChannelMode() const { Q_D(const QProcess); - return d->processChannelMode; + return ProcessChannelMode(d->processChannelMode); } /*! @@ -1289,7 +1365,7 @@ void QProcess::setProcessChannelMode(ProcessChannelMode mode) QProcess::InputChannelMode QProcess::inputChannelMode() const { Q_D(const QProcess); - return d->inputChannelMode; + return InputChannelMode(d->inputChannelMode); } /*! @@ -1373,7 +1449,7 @@ void QProcess::closeWriteChannel() { Q_D(QProcess); d->stdinChannel.closed = true; // closing - if (d->writeBuffer.isEmpty()) + if (bytesToWrite() == 0) d->closeWriteChannel(); } @@ -1427,6 +1503,9 @@ void QProcess::setStandardInputFile(const QString &fileName) Calling setStandardOutputFile() after the process has started has no effect. + If \a fileName is an empty string, it stops redirecting the standard + output. This is useful for restoring the standard output after redirection. + \sa setStandardInputFile(), setStandardErrorFile(), setStandardOutputProcess() */ @@ -1486,7 +1565,7 @@ void QProcess::setStandardOutputProcess(QProcess *destination) dto->stdinChannel.pipeFrom(dfrom); } -#if defined(Q_OS_WIN) || defined(Q_CLANG_QDOC) +#if defined(Q_OS_WIN) || defined(Q_QDOC) /*! \since 4.7 @@ -1570,12 +1649,12 @@ void QProcess::setCreateProcessArgumentsModifier(CreateProcessArgumentModifier m \note This function is only available on Unix platforms. - \sa setChildProcessModifier() + \sa setChildProcessModifier(), unixProcessParameters() */ std::function<void(void)> QProcess::childProcessModifier() const { Q_D(const QProcess); - return d->childProcessModifier; + return d->unixExtras ? d->unixExtras->childProcessModifier : std::function<void(void)>(); } /*! @@ -1584,20 +1663,28 @@ std::function<void(void)> QProcess::childProcessModifier() const Sets the \a modifier function for the child process, for Unix systems (including \macos; for Windows, see setCreateProcessArgumentsModifier()). The function contained by the \a modifier argument will be invoked in the - child process after \c{fork()} is completed and QProcess has set up the - standard file descriptors for the child process, but before \c{execve()}, - inside start(). The modifier is useful to change certain properties of the - child process, such as setting up additional file descriptors or closing - others, changing the nice level, disconnecting from the controlling TTY, - etc. + child process after \c{fork()} or \c{vfork()} is completed and QProcess has + set up the standard file descriptors for the child process, but before + \c{execve()}, inside start(). The following shows an example of setting up a child process to run without privileges: \snippet code/src_corelib_io_qprocess.cpp 4 - If the modifier function needs to exit the process, remember to use - \c{_exit()}, not \c{exit()}. + If the modifier function experiences a failure condition, it can use + failChildProcessModifier() to report the situation to the QProcess caller. + Alternatively, it may use other methods of stopping the process, like + \c{_exit()}, or \c{abort()}. + + Certain properties of the child process, such as closing all extraneous + file descriptors or disconnecting from the controlling TTY, can be more + readily achieved by using setUnixProcessParameters(), which can detect + failure and report a \l{QProcess::}{FailedToStart} condition. The modifier + is useful to change certain uncommon properties of the child process, such + as setting up additional file descriptors. If both a child process modifier + and Unix process parameters are set, the modifier is run before these + parameters are applied. \note In multithreaded applications, this function must be careful not to call any functions that may lock mutexes that may have been in use in @@ -1605,12 +1692,125 @@ std::function<void(void)> QProcess::childProcessModifier() const "async-signal-safe" is advised). Most of the Qt API is unsafe inside this callback, including qDebug(), and may lead to deadlocks. - \sa childProcessModifier() + \note If the UnixProcessParameters::UseVFork flag is set via + setUnixProcessParameters(), QProcess may use \c{vfork()} semantics to + start the child process, so this function must obey even stricter + constraints. First, because it is still sharing memory with the parent + process, it must not write to any non-local variable and must obey proper + ordering semantics when reading from them, to avoid data races. Second, + even more library functions may misbehave; therefore, this function should + only make use of low-level system calls, such as \c{read()}, + \c{write()}, \c{setsid()}, \c{nice()}, and similar. + + \sa childProcessModifier(), failChildProcessModifier(), setUnixProcessParameters() */ void QProcess::setChildProcessModifier(const std::function<void(void)> &modifier) { Q_D(QProcess); - d->childProcessModifier = modifier; + if (!d->unixExtras) + d->unixExtras.reset(new QProcessPrivate::UnixExtras); + d->unixExtras->childProcessModifier = modifier; +} + +/*! + \fn void QProcess::failChildProcessModifier(const char *description, int error) noexcept + \since 6.7 + + This functions can be used inside the modifier set with + setChildProcessModifier() to indicate an error condition was encountered. + When the modifier calls these functions, QProcess will emit errorOccurred() + with code QProcess::FailedToStart in the parent process. The \a description + can be used to include some information in errorString() to help diagnose + the problem, usually the name of the call that failed, similar to the C + Library function \c{perror()}. Additionally, the \a error parameter can be + an \c{<errno.h>} error code whose text form will also be included. + + For example, a child modifier could prepare an extra file descriptor for + the child process this way: + + \code + process.setChildProcessModifier([fd, &process]() { + if (dup2(fd, TargetFileDescriptor) < 0) + process.failChildProcessModifier(errno, "aux comm channel"); + }); + process.start(); + \endcode + + Where \c{fd} is a file descriptor currently open in the parent process. If + the \c{dup2()} system call resulted in an \c EBADF condition, the process + errorString() could be "Child process modifier reported error: aux comm + channel: Bad file descriptor". + + This function does not return to the caller. Using it anywhere except in + the child modifier and with the correct QProcess object is undefined + behavior. + + \note The implementation imposes a length limit to the \a description + parameter to about 500 characters. This does not include the text from the + \a error code. + + \sa setChildProcessModifier(), setUnixProcessParameters() +*/ + +/*! + \since 6.6 + Returns the \l UnixProcessParameters object describing extra flags and + settings that will be applied to the child process on Unix systems. The + default settings correspond to a default-constructed UnixProcessParameters. + + \note This function is only available on Unix platforms. + + \sa childProcessModifier() +*/ +auto QProcess::unixProcessParameters() const noexcept -> UnixProcessParameters +{ + Q_D(const QProcess); + return d->unixExtras ? d->unixExtras->processParameters : UnixProcessParameters{}; +} + +/*! + \since 6.6 + Sets the extra settings and parameters for the child process on Unix + systems to be \a params. This function can be used to ask QProcess to + modify the child process before launching the target executable. + + This function can be used to change certain properties of the child + process, such as closing all extraneous file descriptors, changing the nice + level of the child, or disconnecting from the controlling TTY. For more + fine-grained control of the child process or to modify it in other ways, + use the setChildProcessModifier() function. If both a child process + modifier and Unix process parameters are set, the modifier is run before + these parameters are applied. + + \note This function is only available on Unix platforms. + + \sa unixProcessParameters(), setChildProcessModifier() +*/ +void QProcess::setUnixProcessParameters(const UnixProcessParameters ¶ms) +{ + Q_D(QProcess); + if (!d->unixExtras) + d->unixExtras.reset(new QProcessPrivate::UnixExtras); + d->unixExtras->processParameters = params; +} + +/*! + \since 6.6 + \overload + + Sets the extra settings for the child process on Unix systems to \a + flagsOnly. This is the same as the overload with just the \c flags field + set. + \note This function is only available on Unix platforms. + + \sa unixProcessParameters(), setChildProcessModifier() +*/ +void QProcess::setUnixProcessParameters(UnixProcessFlags flagsOnly) +{ + Q_D(QProcess); + if (!d->unixExtras) + d->unixExtras.reset(new QProcessPrivate::UnixExtras); + d->unixExtras->processParameters = { flagsOnly }; } #endif @@ -1634,9 +1834,6 @@ QString QProcess::workingDirectory() const process in this directory. The default behavior is to start the process in the working directory of the calling process. - \note On QNX, this may cause all application threads to - temporarily freeze. - \sa workingDirectory(), start() */ void QProcess::setWorkingDirectory(const QString &dir) @@ -1689,11 +1886,11 @@ bool QProcess::isSequential() const */ qint64 QProcess::bytesToWrite() const { - qint64 size = QIODevice::bytesToWrite(); #ifdef Q_OS_WIN - size += d_func()->pipeWriterBytesToWrite(); + return d_func()->pipeWriterBytesToWrite(); +#else + return QIODevice::bytesToWrite(); #endif - return size; } /*! @@ -1704,7 +1901,7 @@ qint64 QProcess::bytesToWrite() const QProcess::ProcessError QProcess::error() const { Q_D(const QProcess); - return d->processError; + return ProcessError(d->processError); } /*! @@ -1715,7 +1912,7 @@ QProcess::ProcessError QProcess::error() const QProcess::ProcessState QProcess::state() const { Q_D(const QProcess); - return d->processState; + return ProcessState(d->processState); } /*! @@ -1762,7 +1959,8 @@ QStringList QProcess::environment() const Note how, on Windows, environment variable names are case-insensitive. - \sa processEnvironment(), QProcessEnvironment::systemEnvironment(), setEnvironment() + \sa processEnvironment(), QProcessEnvironment::systemEnvironment(), + {Environment variables} */ void QProcess::setProcessEnvironment(const QProcessEnvironment &environment) { @@ -1772,12 +1970,12 @@ void QProcess::setProcessEnvironment(const QProcessEnvironment &environment) /*! \since 4.6 - Returns the environment that QProcess will pass to its child - process, or an empty object if no environment has been set using - setEnvironment() or setProcessEnvironment(). If no environment has - been set, the environment of the calling process will be used. + Returns the environment that QProcess will pass to its child process. If no + environment has been set using setProcessEnvironment(), this method returns + an object indicating the environment will be inherited from the parent. - \sa setProcessEnvironment(), setEnvironment(), QProcessEnvironment::isEmpty() + \sa setProcessEnvironment(), QProcessEnvironment::inheritsFromParent(), + {Environment variables} */ QProcessEnvironment QProcess::processEnvironment() const { @@ -1791,7 +1989,8 @@ QProcessEnvironment QProcess::processEnvironment() const Returns \c true if the process was started successfully; otherwise returns \c false (if the operation timed out or if an error - occurred). + occurred). If the process had already started successfully before this + function, it returns immediately. This function can operate without an event loop. It is useful when writing non-GUI applications and when performing @@ -1802,9 +2001,6 @@ QProcessEnvironment QProcess::processEnvironment() const If msecs is -1, this function will not time out. - \note On some UNIX operating systems, this function may return true but - the process may later report a QProcess::FailedToStart error. - \sa started(), waitForReadyRead(), waitForBytesWritten(), waitForFinished() */ bool QProcess::waitForStarted(int msecs) @@ -1912,8 +2108,7 @@ void QProcess::setProcessState(ProcessState state) */ auto QProcess::setupChildProcess() -> Use_setChildProcessModifier_Instead { - Q_UNREACHABLE(); - return {}; + Q_UNREACHABLE_RETURN({}); } #endif @@ -1930,44 +2125,6 @@ qint64 QProcess::readData(char *data, qint64 maxlen) return 0; } -/*! \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, qt_prettyDebug(data, len, 16).constData(), len); -#endif - return 0; - } - -#if defined(Q_OS_WIN) - if (!d->stdinWriteTrigger) { - d->stdinWriteTrigger = new QTimer; - d->stdinWriteTrigger->setSingleShot(true); - QObjectPrivate::connect(d->stdinWriteTrigger, &QTimer::timeout, - d, &QProcessPrivate::_q_canWrite); - } -#endif - - d->write(data, len); -#ifdef Q_OS_WIN - if (!d->stdinWriteTrigger->isActive()) - d->stdinWriteTrigger->start(); -#else - if (d->stdinChannel.notifier) - d->stdinChannel.notifier->setEnabled(true); -#endif -#if defined QPROCESS_DEBUG - qDebug("QProcess::writeData(%p \"%s\", %lld) == %lld (written to buffer)", - data, qt_prettyDebug(data, len, 16).constData(), len, len); -#endif - return len; -} - /*! Regardless of the current read channel, this function returns all data available from the standard output of the process as a @@ -1993,27 +2150,38 @@ QByteArray QProcess::readAllStandardOutput() */ QByteArray QProcess::readAllStandardError() { - ProcessChannel tmp = readChannel(); - setReadChannel(StandardError); - QByteArray data = readAll(); - setReadChannel(tmp); + Q_D(QProcess); + QByteArray data; + if (d->processChannelMode == MergedChannels) { + qWarning("QProcess::readAllStandardError: Called with MergedChannels"); + } else { + ProcessChannel tmp = readChannel(); + setReadChannel(StandardError); + data = readAll(); + setReadChannel(tmp); + } return data; } /*! Starts the given \a program in a new process, passing the command line - arguments in \a arguments. + arguments in \a arguments. See setProgram() for information about how + QProcess searches for the executable to be run. The OpenMode is set to \a + mode. No further splitting of the arguments is performed. The QProcess object will immediately enter the Starting state. If the process starts successfully, QProcess will emit started(); otherwise, - errorOccurred() will be emitted. - - \note Processes are started asynchronously, which means the started() - and errorOccurred() signals may be delayed. Call waitForStarted() to make - sure the process has started (or has failed to start) and those signals - have been emitted. - - \note No further splitting of the arguments is performed. + errorOccurred() will be emitted. Do note that on platforms that are able to + start child processes synchronously (notably Windows), those signals will + be emitted before this function returns and this QProcess object will + transition to either QProcess::Running or QProcess::NotRunning state, + respectively. On others paltforms, the started() and errorOccurred() + signals will be delayed. + + Call waitForStarted() to make sure the process has started (or has failed + to start) and those signals have been emitted. It is safe to call that + function even if the process starting state is already known, though the + signal will not be emitted again. \b{Windows:} The arguments are quoted and joined into a command line that is compatible with the \c CommandLineToArgvW() Windows function. @@ -2022,12 +2190,16 @@ QByteArray QProcess::readAllStandardError() not follow the \c CommandLineToArgvW() rules is cmd.exe and, by consequence, all batch scripts. - The OpenMode is set to \a mode. - If the QProcess object is already running a process, a warning may be printed at the console, and the existing process will continue running unaffected. + \note Success at starting the child process only implies the operating + system has successfully created the process and assigned the resources + every process has, such as its process ID. The child process may crash or + otherwise fail very early and thus not produce its expected output. On most + operating systems, this may include dynamic linking errors. + \sa processId(), started(), waitForStarted(), setNativeArguments() */ void QProcess::start(const QString &program, const QStringList &arguments, OpenMode mode) @@ -2109,6 +2281,10 @@ void QProcess::start(OpenMode mode) void QProcess::startCommand(const QString &command, OpenMode mode) { QStringList args = splitCommand(command); + if (args.isEmpty()) { + qWarning("QProcess::startCommand: empty or whitespace-only command was provided"); + return; + } const QString program = args.takeFirst(); start(program, args, mode); } @@ -2128,9 +2304,6 @@ void QProcess::startCommand(const QString &command, OpenMode mode) If workingDirectory() is empty, the working directory is inherited from the calling process. - \note On QNX, this may cause all application threads to - temporarily freeze. - If the function is successful then *\a pid is set to the process identifier of the started process; otherwise, it's set to -1. Note that the child process may exit and the PID may become invalid without notice. @@ -2263,7 +2436,7 @@ QStringList QProcess::splitCommand(QStringView command) // "hello world". three consecutive double quotes represent // the quote character itself. for (int i = 0; i < command.size(); ++i) { - if (command.at(i) == QLatin1Char('"')) { + if (command.at(i) == u'"') { ++quoteCount; if (quoteCount == 3) { // third consecutive quote @@ -2311,7 +2484,12 @@ QString QProcess::program() const Set the \a program to use when starting the process. This function must be called before start(). - \sa start(), setArguments(), program() + If \a program is an absolute path, it specifies the exact executable that + will be launched. Relative paths will be resolved in a platform-specific + manner, which includes searching the \c PATH environment variable (see + \l{Finding the Executable} for details). + + \sa start(), setArguments(), program(), QStandardPaths::findExecutable() */ void QProcess::setProgram(const QString &program) { @@ -2413,7 +2591,7 @@ int QProcess::exitCode() const QProcess::ExitStatus QProcess::exitStatus() const { Q_D(const QProcess); - return d->exitStatus; + return ExitStatus(d->exitStatus); } /*! @@ -2474,18 +2652,6 @@ bool QProcess::startDetached(const QString &program, return process.startDetached(pid); } -QT_BEGIN_INCLUDE_NAMESPACE -#if defined(Q_OS_MACOS) -# include <crt_externs.h> -# define environ (*_NSGetEnviron()) -#elif defined(QT_PLATFORM_UIKIT) - static char *qt_empty_environ[] = { 0 }; -#define environ qt_empty_environ -#elif !defined(Q_OS_WIN) - extern char **environ; -#endif -QT_END_INCLUDE_NAMESPACE - /*! \since 4.1 @@ -2507,12 +2673,7 @@ QT_END_INCLUDE_NAMESPACE */ QStringList QProcess::systemEnvironment() { - QStringList tmp; - char *entry = nullptr; - int count = 0; - while ((entry = environ[count++])) - tmp << QString::fromLocal8Bit(entry); - return tmp; + return QProcessEnvironment::systemEnvironment().toStringList(); } /*! |