summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/corelib/configure.cmake17
-rw-r--r--src/corelib/io/qprocess.cpp135
-rw-r--r--src/corelib/io/qprocess.h22
-rw-r--r--src/corelib/io/qprocess_p.h1
-rw-r--r--src/corelib/io/qprocess_unix.cpp52
5 files changed, 219 insertions, 8 deletions
diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake
index 8e1b9dc37b6..dd89b8ac998 100644
--- a/src/corelib/configure.cmake
+++ b/src/corelib/configure.cmake
@@ -98,6 +98,18 @@ clock_gettime(CLOCK_MONOTONIC, &ts);
}
")
+# close_range
+qt_config_compile_test(close_range
+ LABEL "close_range()"
+ CODE
+"#include <unistd.h>
+
+int main()
+{
+ return close_range(3, 1024, 0) != 0;
+}
+")
+
# cloexec
qt_config_compile_test(cloexec
LABEL "O_CLOEXEC"
@@ -551,6 +563,11 @@ qt_feature("clock-monotonic" PUBLIC
CONDITION QT_FEATURE_clock_gettime AND TEST_clock_monotonic
)
qt_feature_definition("clock-monotonic" "QT_NO_CLOCK_MONOTONIC" NEGATE VALUE "1")
+qt_feature("close_range" PRIVATE
+ LABEL "close_range()"
+ CONDITION QT_FEATURE_process AND TEST_close_range
+ AUTODETECT UNIX
+)
qt_feature("doubleconversion" PRIVATE
LABEL "DoubleConversion"
)
diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp
index 7f3cc7bd894..ddcd7a4768a 100644
--- a/src/corelib/io/qprocess.cpp
+++ b/src/corelib/io/qprocess.cpp
@@ -805,6 +805,58 @@ 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
+ \endlist
+
+ 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::UnixProcessFlags
+ \since 6.6
+
+ These flags can be used in the \c flags field of \l UnixProcessParameters.
+
+ \value CloseNonStandardFileDescriptors Close all file descriptors besides
+ \c stdin, \c stdout, and \c stderr, preventing any currently open
+ descriptor in the parent process from accidentally leaking to the
+ child.
+
+ \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 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.
+
+ \sa setUnixProcessParameters(), unixProcessParameters()
+*/
+
+/*!
\fn void QProcess::errorOccurred(QProcess::ProcessError error)
\since 5.6
@@ -1553,7 +1605,7 @@ 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
{
@@ -1567,12 +1619,9 @@ 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()} 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 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:
@@ -1582,13 +1631,22 @@ std::function<void(void)> QProcess::childProcessModifier() const
If the modifier function needs to exit the process, remember to use
\c{_exit()}, not \c{exit()}.
+ 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
other threads (in general, using only functions defined by POSIX as
"async-signal-safe" is advised). Most of the Qt API is unsafe inside this
callback, including qDebug(), and may lead to deadlocks.
- \sa childProcessModifier()
+ \sa childProcessModifier(), setUnixProcessParameters()
*/
void QProcess::setChildProcessModifier(const std::function<void(void)> &modifier)
{
@@ -1597,6 +1655,67 @@ void QProcess::setChildProcessModifier(const std::function<void(void)> &modifier
d->unixExtras.reset(new QProcessPrivate::UnixExtras);
d->unixExtras->childProcessModifier = modifier;
}
+
+/*!
+ \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 &params)
+{
+ 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
/*!
diff --git a/src/corelib/io/qprocess.h b/src/corelib/io/qprocess.h
index 4fa1037b38b..710b5e2f78a 100644
--- a/src/corelib/io/qprocess.h
+++ b/src/corelib/io/qprocess.h
@@ -1,4 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2023 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QPROCESS_H
@@ -173,6 +174,23 @@ public:
#if defined(Q_OS_UNIX) || defined(Q_QDOC)
std::function<void(void)> childProcessModifier() const;
void setChildProcessModifier(const std::function<void(void)> &modifier);
+
+ enum UnixProcessFlag : quint32 {
+ ResetSignalHandlers = 0x0001, // like POSIX_SPAWN_SETSIGDEF
+ IgnoreSigPipe = 0x0002,
+ // some room if we want to add IgnoreSigHup or so
+ CloseNonStandardFileDescriptors = 0x0010,
+ };
+ Q_DECLARE_FLAGS(UnixProcessFlags, UnixProcessFlag)
+ struct UnixProcessParameters
+ {
+ UnixProcessFlags flags = {};
+
+ quint32 _reserved[7] {};
+ };
+ UnixProcessParameters unixProcessParameters() const noexcept;
+ void setUnixProcessParameters(const UnixProcessParameters &params);
+ void setUnixProcessParameters(UnixProcessFlags flagsOnly);
#endif
QString workingDirectory() const;
@@ -256,6 +274,10 @@ private:
Q_PRIVATE_SLOT(d_func(), void _q_processDied())
};
+#ifdef Q_OS_UNIX
+Q_DECLARE_OPERATORS_FOR_FLAGS(QProcess::UnixProcessFlags)
+#endif
+
#endif // QT_CONFIG(process)
QT_END_NAMESPACE
diff --git a/src/corelib/io/qprocess_p.h b/src/corelib/io/qprocess_p.h
index dacf41b77aa..3b862bc5f32 100644
--- a/src/corelib/io/qprocess_p.h
+++ b/src/corelib/io/qprocess_p.h
@@ -282,6 +282,7 @@ public:
#else
struct UnixExtras {
std::function<void(void)> childProcessModifier;
+ QProcess::UnixProcessParameters processParameters;
};
std::unique_ptr<UnixExtras> unixExtras;
QSocketNotifier *stateNotifier = nullptr;
diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp
index 2de5cded529..c0b10c130e1 100644
--- a/src/corelib/io/qprocess_unix.cpp
+++ b/src/corelib/io/qprocess_unix.cpp
@@ -36,6 +36,13 @@
#include <limits.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#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>
@@ -576,6 +583,46 @@ static constexpr int FakeErrnoForThrow =
#endif
;
+// See IMPORTANT notice below
+static void applyProcessParameters(const QProcess::UnixProcessParameters &params)
+{
+ // 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)
+ signal(SIGPIPE, SIG_IGN); // don't use qt_ignore_sigpipe!
+ if (params.flags.testFlag(QProcess::UnixProcessFlag::ResetSignalHandlers)) {
+ for (int sig = 1; sig < NSIG; ++sig) {
+ if (!ignore_sigpipe || sig != SIGPIPE)
+ signal(sig, SIG_DFL);
+ }
+ }
+
+ // 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::CloseNonStandardFileDescriptors)) {
+ int r = -1;
+ int fd = STDERR_FILENO + 1;
+#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);
+ }
+ }
+}
+
+// the noexcept here adds an extra layer of protection
static const char *callChildProcessModifier(const QProcessPrivate::UnixExtras *unixExtras) noexcept
{
QT_TRY {
@@ -597,8 +644,13 @@ static const char *doExecChild(char **argv, char **envp, int workingDirFd,
return "fchdir";
if (unixExtras) {
+ // FIRST we call the user modifier function, before we dropping
+ // privileges or closing non-standard file descriptors
if (const char *what = callChildProcessModifier(unixExtras))
return what;
+
+ // then we apply our other user-provided parameters
+ applyProcessParameters(unixExtras->processParameters);
}
// execute the process