summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qprocess_unix.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io/qprocess_unix.cpp')
-rw-r--r--src/corelib/io/qprocess_unix.cpp623
1 files changed, 410 insertions, 213 deletions
diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp
index 90ee3cb0b4..5c696433fd 100644
--- a/src/corelib/io/qprocess_unix.cpp
+++ b/src/corelib/io/qprocess_unix.cpp
@@ -37,8 +37,12 @@
#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>
@@ -51,39 +55,24 @@
#ifndef O_PATH
# define O_PATH 0
#endif
+#ifndef _PATH_DEV
+# define _PATH_DEV "/dev/"
+#endif
+#ifndef _PATH_TTY
+# define _PATH_TTY _PATH_DEV "tty"
+#endif
+
+#ifdef Q_OS_FREEBSD
+__attribute__((weak))
+#endif
+extern char **environ;
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-namespace {
-struct PThreadCancelGuard
-{
-#if defined(PTHREAD_CANCEL_DISABLE)
- int oldstate;
- PThreadCancelGuard() noexcept(false)
- {
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
- }
- ~PThreadCancelGuard() noexcept(false)
- {
- reenable();
- }
- void reenable() noexcept(false)
- {
- // this doesn't touch errno
- pthread_setcancelstate(oldstate, nullptr);
- }
-#endif
-};
-}
-
#if !defined(Q_OS_DARWIN)
-QT_BEGIN_INCLUDE_NAMESPACE
-extern char **environ;
-QT_END_INCLUDE_NAMESPACE
-
QProcessEnvironment QProcessEnvironment::systemEnvironment()
{
QProcessEnvironment env;
@@ -105,6 +94,60 @@ 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
@@ -139,21 +182,12 @@ struct AutoPipe
struct ChildError
{
int code;
- char function[12];
-};
-
-// 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);
-
-private:
- QByteArray data;
- void updatePointers(qsizetype count);
+ 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
{
@@ -188,10 +222,139 @@ QProcessPoller::QProcessPoller(const QProcessPrivate &proc)
int QProcessPoller::poll(const QDeadlineTimer &deadline)
{
- return qt_poll_msecs(pfds, n_pfds, deadline.remainingTime());
+ return qt_safe_poll(pfds, n_pfds, deadline);
}
-CharPointerList::CharPointerList(const QString &program, const QStringList &args)
+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]);
@@ -214,7 +377,7 @@ CharPointerList::CharPointerList(const QString &program, const QStringList &args
updatePointers(count);
}
-CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment)
+QChildProcess::CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment)
{
if (!environment)
return;
@@ -240,7 +403,7 @@ CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment)
updatePointers(count);
}
-void CharPointerList::updatePointers(qsizetype count)
+void QChildProcess::CharPointerList::updatePointers(qsizetype count)
{
char *const base = const_cast<char *>(data.constBegin());
for (qsizetype i = 0; i < count; ++i)
@@ -261,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;
}
@@ -310,8 +474,10 @@ bool QProcessPrivate::openChannel(Channel &channel)
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()) {
@@ -359,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");
@@ -391,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];
@@ -419,7 +586,7 @@ void QProcessPrivate::commitChannels() const
}
}
-static QString resolveExecutable(const QString &program)
+inline QString QChildProcess::resolveExecutable(const QString &program)
{
#ifdef Q_OS_DARWIN
// allow invoking of .app bundles on the Mac.
@@ -455,34 +622,63 @@ static QString resolveExecutable(const QString &program)
return program;
}
-static int useForkFlags(const QProcessPrivate::UnixExtras *unixExtras)
+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 FFD_USE_FORK;
+ 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 FFD_USE_FORK;
+ return false;
#endif
- if (!unixExtras || !unixExtras->childProcessModifier)
- return 0; // no modifier was supplied
+ // 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 = unixExtras->processParameters.flags;
- if (flags.testFlag(QProcess::UnixProcessFlag::UseVFork))
- return 0;
- return FFD_USE_FORK;
+ 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()");
@@ -491,6 +687,8 @@ void QProcessPrivate::startProcess()
// Initialize pipes
if (!openChannels()) {
// openChannel sets the error string
+ Q_ASSERT(!errorString.isEmpty());
+ cleanup();
return;
}
if (qt_create_pipe(childStartedPipe) != 0) {
@@ -509,44 +707,17 @@ void QProcessPrivate::startProcess()
q, SLOT(_q_startupNotification()));
}
- int workingDirFd = -1;
- if (!workingDirectory.isEmpty()) {
- workingDirFd = opendirfd(QFile::encodeName(workingDirectory));
- if (workingDirFd == -1) {
- setErrorAndEmit(QProcess::FailedToStart, "chdir: "_L1 + qt_error_string());
- cleanup();
- return;
- }
- }
-
- // Start the process (platform dependent)
- q->setProcessState(QProcess::Starting);
-
// Prepare the arguments and the environment
- const CharPointerList argv(resolveExecutable(program), arguments);
- const CharPointerList envp(environment.d.constData());
-
- // Disable PThread cancellation from this point on: we mustn't have it
- // enabled when the child starts running nor while our state could get
- // corrupted if we abruptly exited this function.
- [[maybe_unused]] PThreadCancelGuard cancelGuard;
+ QChildProcess childProcess(this);
+ if (!childProcess.ok()) {
+ Q_ASSERT(processError != QProcess::UnknownError);
+ return;
+ }
// Start the child.
- auto execChild1 = [this, workingDirFd, &argv, &envp]() {
- execChild(workingDirFd, argv.pointers.get(), envp.pointers.get());
- };
- auto execChild2 = [](void *lambda) {
- static_cast<decltype(execChild1) *>(lambda)->operator()();
- return -1;
- };
-
- int ffdflags = FFD_CLOEXEC | useForkFlags(unixExtras.get());
- forkfd = ::vforkfd(ffdflags, &pid, execChild2, &execChild1);
+ forkfd = childProcess.startChild(&pid);
int lastForkErrno = errno;
- if (workingDirFd != -1)
- close(workingDirFd);
-
if (forkfd == -1) {
// Cleanup, report error and return
#if defined (QPROCESS_DEBUG)
@@ -592,34 +763,72 @@ void QProcessPrivate::startProcess()
// 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 =
-#ifdef ECANCELED
- ECANCELED
-#else
- ESHUTDOWN
-#endif
- ;
+static constexpr int FakeErrnoForThrow = std::numeric_limits<int>::max();
+
+static QString startFailureErrorMessage(ChildError &err, [[maybe_unused]] ssize_t bytesRead)
+{
+ // ChildError is less than the POSIX pipe buffer atomic size, so the read
+ // must not have been truncated
+ Q_ASSERT(bytesRead == sizeof(err));
+
+ qsizetype len = qstrnlen(err.function, sizeof(err.function));
+ QString complement = QString::fromUtf8(err.function, len);
+ if (err.code == FakeErrnoForThrow)
+ return QProcess::tr("Child process modifier threw an exception: %1")
+ .arg(std::move(complement));
+ if (err.code == 0)
+ return QProcess::tr("Child process modifier reported error: %1")
+ .arg(std::move(complement));
+ if (err.code < 0)
+ return QProcess::tr("Child process modifier reported error: %1: %2")
+ .arg(std::move(complement), qt_error_string(-err.code));
+ return QProcess::tr("Child process set up failed: %1: %2")
+ .arg(std::move(complement), qt_error_string(err.code));
+}
+
+Q_NORETURN void
+failChildProcess(const QProcessPrivate *d, const char *description, int code) noexcept
+{
+ ChildError error = {};
+ error.code = code;
+ qstrncpy(error.function, description, sizeof(error.function));
+ qt_safe_write(d->childStartedPipe[1], &error, sizeof(error));
+ _exit(-1);
+}
+
+void QProcess::failChildProcessModifier(const char *description, int error) noexcept
+{
+ // We signal user errors with negative errnos
+ failChildProcess(d_func(), description, -error);
+}
// See IMPORTANT notice below
-static void applyProcessParameters(const QProcess::UnixProcessParameters &params)
+static const char *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!
+ 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)
- signal(sig, SIG_DFL);
+ QtVforkSafe::sigaction(sig, &sa, nullptr);
}
+
+ // and unmask all signals
+ sigset_t set;
+ sigemptyset(&set);
+ sigprocmask(SIG_SETMASK, &set, nullptr);
}
// Close all file descriptors above stderr.
// This isn't expected to fail, so we ignore close()'s return value.
- if (params.flags.testFlag(QProcess::UnixProcessFlag::CloseNonStandardFileDescriptors)) {
+ if (params.flags.testFlag(QProcess::UnixProcessFlag::CloseFileDescriptors)) {
int r = -1;
- int fd = STDERR_FILENO + 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.
@@ -637,73 +846,102 @@ static void applyProcessParameters(const QProcess::UnixProcessParameters &params
close(fd);
}
}
+
+ // Apply session and process group settings. This may fail.
+ if (params.flags.testFlag(QProcess::UnixProcessFlag::CreateNewSession)) {
+ if (setsid() < 0)
+ return "setsid";
+ }
+
+ // Disconnect from the controlling TTY. This probably won't fail. Must be
+ // done after the session settings from above.
+ if (params.flags.testFlag(QProcess::UnixProcessFlag::DisconnectControllingTerminal)) {
+ if (int fd = open(_PATH_TTY, O_RDONLY | O_NOCTTY); fd >= 0) {
+ // we still have a controlling TTY; give it up
+ int r = ioctl(fd, TIOCNOTTY);
+ int savedErrno = errno;
+ close(fd);
+ if (r != 0) {
+ errno = savedErrno;
+ return "ioctl";
+ }
+ }
+ }
+
+ // Apply UID and GID parameters last. This isn't expected to fail either:
+ // either we're trying to impersonate what we already are, or we're EUID or
+ // EGID root, in which case we are allowed to do this.
+ if (params.flags.testFlag(QProcess::UnixProcessFlag::ResetIds)) {
+ int r = setgid(getgid());
+ r = setuid(getuid());
+ (void) r;
+ }
+
+ return nullptr;
}
// the noexcept here adds an extra layer of protection
-static const char *callChildProcessModifier(const QProcessPrivate::UnixExtras *unixExtras) noexcept
+static void callChildProcessModifier(const QProcessPrivate *d) noexcept
{
QT_TRY {
- if (unixExtras->childProcessModifier)
- unixExtras->childProcessModifier();
+ if (d->unixExtras->childProcessModifier)
+ d->unixExtras->childProcessModifier();
+ } QT_CATCH (std::exception &e) {
+ failChildProcess(d, e.what(), FakeErrnoForThrow);
} QT_CATCH (...) {
- errno = FakeErrnoForThrow;
- return "throw";
+ failChildProcess(d, "throw", FakeErrnoForThrow);
}
- return nullptr;
}
-// this function doesn't return if the execution succeeds
-static const char *doExecChild(char **argv, char **envp, int workingDirFd,
- const QProcessPrivate::UnixExtras *unixExtras) noexcept
+// IMPORTANT:
+//
+// This function is called in a vfork() context on some OSes (notably, Linux
+// with forkfd), so it MUST NOT modify any non-local variable because it's
+// still sharing memory with the parent process.
+void QChildProcess::startProcess() const noexcept
{
+ // Render channels configuration.
+ d->commitChannels();
+
+ // make sure this fd is closed if execv() succeeds
+ qt_safe_close(d->childStartedPipe[0]);
+
// enter the working directory
- if (workingDirFd != -1 && fchdir(workingDirFd) == -1)
- return "fchdir";
+ if (workingDirectory >= 0 && fchdir(workingDirectory) == -1)
+ failChildProcess(d, "fchdir", errno);
- if (unixExtras) {
+ 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
- if (const char *what = callChildProcessModifier(unixExtras))
- return what;
+ callChildProcessModifier(d);
// then we apply our other user-provided parameters
- applyProcessParameters(unixExtras->processParameters);
+ if (const char *what = applyProcessParameters(d->unixExtras->processParameters))
+ failChildProcess(d, what, errno);
+
+ auto flags = d->unixExtras->processParameters.flags;
+ using P = QProcess::UnixProcessFlag;
+ sigpipeHandled = flags.testAnyFlags(P::ResetSignalHandlers | P::IgnoreSigPipe);
+ sigmaskHandled = flags.testFlag(P::ResetSignalHandlers);
+ }
+ if (!sigpipeHandled) {
+ // reset the signal that we ignored
+ QtVforkSafe::change_sigpipe(SIG_DFL); // reset the signal that we ignored
+ }
+ if (!sigmaskHandled) {
+ // restore the signal mask from the parent, if applyProcessParameters()
+ // hasn't completely reset it
+ restoreSignalMask();
}
// execute the process
- if (!envp)
+ if (!envp.pointers)
qt_safe_execv(argv[0], argv);
else
qt_safe_execve(argv[0], argv, envp);
- return "execve";
-}
-
-
-// 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 QProcessPrivate::execChild(int workingDir, char **argv, char **envp) const noexcept
-{
- ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored
-
- ChildError error = { 0, {} }; // force zeroing of function[8]
-
- // Render channels configuration.
- commitChannels();
-
- // make sure this fd is closed if execv() succeeds
- qt_safe_close(childStartedPipe[0]);
-
- const char *what = doExecChild(argv, envp, workingDir, unixExtras.get());
- strcpy(error.function, what);
-
- // notify failure
- // don't use strerror or any other routines that may allocate memory, since
- // some buggy libc versions can deadlock on locked mutexes.
- error.code = errno;
- qt_safe_write(childStartedPipe[1], &error, sizeof(error));
+ failChildProcess(d, "execve", errno);
}
bool QProcessPrivate::processStarted(QString *errorMessage)
@@ -740,12 +978,8 @@ bool QProcessPrivate::processStarted(QString *errorMessage)
}
// did we read an error message?
- if (errorMessage) {
- if (buf.code == FakeErrnoForThrow)
- *errorMessage = QProcess::tr("childProcessModifier() function threw an exception");
- else
- *errorMessage = QLatin1StringView(buf.function) + ": "_L1 + qt_error_string(buf.code);
- }
+ if (errorMessage)
+ *errorMessage = startFailureErrorMessage(buf, ret);
return false;
}
@@ -877,15 +1111,15 @@ void QProcessPrivate::killProcess()
bool QProcessPrivate::waitForStarted(const QDeadlineTimer &deadline)
{
- const qint64 msecs = deadline.remainingTime();
#if defined (QPROCESS_DEBUG)
+ const qint64 msecs = deadline.remainingTime();
qDebug("QProcessPrivate::waitForStarted(%lld) waiting for child to start (fd = %d)",
msecs, childStartedPipe[0]);
#endif
pollfd pfd = qt_make_pollfd(childStartedPipe[0], POLLIN);
- if (qt_poll_msecs(&pfd, 1, msecs) == 0) {
+ if (qt_safe_poll(&pfd, 1, deadline) == 0) {
setError(QProcess::Timedout);
#if defined (QPROCESS_DEBUG)
qDebug("QProcessPrivate::waitForStarted(%lld) == false (timed out)", msecs);
@@ -1034,9 +1268,10 @@ void QProcessPrivate::waitForDeadChild()
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;
exitStatus = info.code == CLD_EXITED ? QProcess::NormalExit : QProcess::CrashExit;
@@ -1044,7 +1279,7 @@ void QProcessPrivate::waitForDeadChild()
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
@@ -1055,14 +1290,6 @@ void QProcessPrivate::waitForDeadChild()
bool QProcessPrivate::startDetached(qint64 *pid)
{
-
-#ifdef PIPE_BUF
- static_assert(PIPE_BUF >= sizeof(ChildError));
-#else
- static_assert(_POSIX_PIPE_BUF >= sizeof(ChildError));
-#endif
- ChildError childStatus = { 0, {} };
-
AutoPipe startedPipe, pidPipe;
if (!startedPipe || !pidPipe) {
setErrorAndEmit(QProcess::FailedToStart, "pipe: "_L1 + qt_error_string(errno));
@@ -1075,61 +1302,32 @@ bool QProcessPrivate::startDetached(qint64 *pid)
return false;
}
- int workingDirFd = -1;
- if (!workingDirectory.isEmpty()) {
- workingDirFd = opendirfd(QFile::encodeName(workingDirectory));
- if (workingDirFd == -1) {
- setErrorAndEmit(QProcess::FailedToStart, "chdir: "_L1 + qt_error_string(errno));
- return false;
- }
+ // see startProcess() for more information
+ QChildProcess childProcess(this);
+ if (!childProcess.ok()) {
+ Q_ASSERT(processError != QProcess::UnknownError);
+ return false;
}
- const CharPointerList argv(resolveExecutable(program), arguments);
- const CharPointerList envp(environment.d.constData());
-
- // see startProcess() for more information
- [[maybe_unused]] PThreadCancelGuard cancelGuard;
-
- auto doFork = [this]() {
- if (useForkFlags(unixExtras.get()))
- return fork;
- QT_IGNORE_DEPRECATIONS(return vfork;)
- }();
- pid_t childPid = doFork();
- if (childPid == 0) {
- ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored
+ childStartedPipe[1] = startedPipe[1]; // for failChildProcess()
+ pid_t childPid = childProcess.doFork([&] {
::setsid();
qt_safe_close(startedPipe[0]);
qt_safe_close(pidPipe[0]);
- auto reportFailed = [&](const char *function) {
- childStatus.code = errno;
- strcpy(childStatus.function, function);
- qt_safe_write(startedPipe[1], &childStatus, sizeof(childStatus));
- ::_exit(1);
- };
-
- pid_t doubleForkPid = doFork();
- if (doubleForkPid == 0) {
- // Render channels configuration.
- commitChannels();
-
- reportFailed(doExecChild(argv.pointers.get(), envp.pointers.get(), workingDirFd,
- unixExtras.get()));
- } else if (doubleForkPid == -1) {
- reportFailed("fork: ");
- }
+ pid_t doubleForkPid;
+ if (childProcess.startChild(&doubleForkPid) == -1)
+ failChildProcess(this, "fork", errno);
// success
qt_safe_write(pidPipe[1], &doubleForkPid, sizeof(pid_t));
- ::_exit(1);
- }
+ return 0;
+ });
+ childStartedPipe[1] = -1;
int savedErrno = errno;
closeChannels();
- if (workingDirFd != -1)
- close(workingDirFd);
if (childPid == -1) {
setErrorAndEmit(QProcess::FailedToStart, "fork: "_L1 + qt_error_string(savedErrno));
@@ -1146,6 +1344,7 @@ bool QProcessPrivate::startDetached(qint64 *pid)
// successfully execve()'d the target process. If it returns any positive
// result, it means one of the two children wrote an error result. Negative
// values should not happen.
+ ChildError childStatus;
ssize_t startResult = qt_safe_read(startedPipe[0], &childStatus, sizeof(childStatus));
// reap the intermediate child
@@ -1161,10 +1360,8 @@ bool QProcessPrivate::startDetached(qint64 *pid)
} else if (!success) {
if (pid)
*pid = -1;
- QString msg;
- if (startResult == sizeof(childStatus))
- msg = QLatin1StringView(childStatus.function) + qt_error_string(childStatus.code);
- setErrorAndEmit(QProcess::FailedToStart, msg);
+ setErrorAndEmit(QProcess::FailedToStart,
+ startFailureErrorMessage(childStatus, startResult));
}
return success;
}