diff options
Diffstat (limited to 'src/corelib/io/qprocess.cpp')
-rw-r--r-- | src/corelib/io/qprocess.cpp | 398 |
1 files changed, 315 insertions, 83 deletions
diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index 276190426c..f5e9708365 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -35,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 @@ -72,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(u'=', 1); + const qsizetype pos = it->indexOf(u'=', 1); if (pos < 1) continue; @@ -184,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 @@ -200,12 +204,12 @@ 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; - return d && other.d && d->vars == other.d->vars; + return lhs.d && rhs.d && lhs.d->vars == rhs.d->vars; } /*! @@ -483,7 +487,7 @@ void QProcessPrivate::Channel::clear() \endlist To avoid platform-dependent behavior or any issues with how the current - application was launched, it is adviseable to always pass an absolute path + 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 @@ -507,6 +511,42 @@ void QProcessPrivate::Channel::clear() 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 @@ -555,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 @@ -774,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 @@ -891,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)); } /*! @@ -1114,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(); @@ -1125,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"); @@ -1210,7 +1335,7 @@ QProcess::~QProcess() QProcess::ProcessChannelMode QProcess::processChannelMode() const { Q_D(const QProcess); - return d->processChannelMode; + return ProcessChannelMode(d->processChannelMode); } /*! @@ -1240,7 +1365,7 @@ void QProcess::setProcessChannelMode(ProcessChannelMode mode) QProcess::InputChannelMode QProcess::inputChannelMode() const { Q_D(const QProcess); - return d->inputChannelMode; + return InputChannelMode(d->inputChannelMode); } /*! @@ -1378,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() */ @@ -1437,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 @@ -1521,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)>(); } /*! @@ -1535,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 @@ -1556,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 @@ -1585,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) @@ -1655,7 +1901,7 @@ qint64 QProcess::bytesToWrite() const QProcess::ProcessError QProcess::error() const { Q_D(const QProcess); - return d->processError; + return ProcessError(d->processError); } /*! @@ -1666,7 +1912,7 @@ QProcess::ProcessError QProcess::error() const QProcess::ProcessState QProcess::state() const { Q_D(const QProcess); - return d->processState; + return ProcessState(d->processState); } /*! @@ -1713,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) { @@ -1723,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 { @@ -1742,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 @@ -1753,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) @@ -1863,8 +2108,7 @@ void QProcess::setProcessState(ProcessState state) */ auto QProcess::setupChildProcess() -> Use_setChildProcessModifier_Instead { - Q_UNREACHABLE(); - return {}; + Q_UNREACHABLE_RETURN({}); } #endif @@ -1922,18 +2166,22 @@ QByteArray QProcess::readAllStandardError() /*! Starts the given \a program in a new process, passing the command line arguments in \a arguments. See setProgram() for information about how - QProcess searches for the executable to be run. + 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. @@ -1942,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) @@ -2048,9 +2300,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. @@ -2338,7 +2587,7 @@ int QProcess::exitCode() const QProcess::ExitStatus QProcess::exitStatus() const { Q_D(const QProcess); - return d->exitStatus; + return ExitStatus(d->exitStatus); } /*! @@ -2399,18 +2648,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) - Q_CONSTINIT 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 @@ -2432,12 +2669,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(); } /*! |