diff options
author | Thiago Macieira <thiago.macieira@intel.com> | 2023-05-12 19:50:51 -0700 |
---|---|---|
committer | Thiago Macieira <thiago.macieira@intel.com> | 2023-07-08 15:03:23 -0700 |
commit | 13a1995e9dc987d1560b38d16b76442261b4aa8d (patch) | |
tree | bb2ba2d6ce159b1123721756c616cb10c8170c1a | |
parent | 8f1df9aaa87f1d899babce42a8fdbb28c13ae604 (diff) |
QProcess/Unix: add a few, basic session & terminal management flags
Doing setsid() and disconnecting from the controlling terminal are, in
addition to resetting the standard file descriptors to /dev/null, a
common task that daemons do. These options allow a QProcess to force a
child to be a daemon.
QProcess ensures that the operations are done in the correct order.
Change-Id: I3e3bfef633af4130a03afffd175e9451d2716d7a
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
-rw-r--r-- | src/corelib/io/qprocess.cpp | 16 | ||||
-rw-r--r-- | src/corelib/io/qprocess.h | 2 | ||||
-rw-r--r-- | src/corelib/io/qprocess_unix.cpp | 37 | ||||
-rw-r--r-- | tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp | 16 | ||||
-rw-r--r-- | tests/auto/corelib/io/qprocess/tst_qprocess.cpp | 44 |
5 files changed, 113 insertions, 2 deletions
diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index ab7219a6cd..78d2de7779 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -849,6 +849,22 @@ void QProcessPrivate::Channel::clear() 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 diff --git a/src/corelib/io/qprocess.h b/src/corelib/io/qprocess.h index 770be0cbf0..42bee0691b 100644 --- a/src/corelib/io/qprocess.h +++ b/src/corelib/io/qprocess.h @@ -182,6 +182,8 @@ public: // some room if we want to add IgnoreSigHup or so CloseFileDescriptors = 0x0010, UseVFork = 0x0020, // like POSIX_SPAWN_USEVFORK + CreateNewSession = 0x0040, // like POSIX_SPAWN_SETSID + DisconnectControllingTerminal = 0x0080, }; Q_DECLARE_FLAGS(UnixProcessFlags, UnixProcessFlag) struct UnixProcessParameters diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp index 64f48accb2..02438359f2 100644 --- a/src/corelib/io/qprocess_unix.cpp +++ b/src/corelib/io/qprocess_unix.cpp @@ -39,6 +39,9 @@ #include <sys/resource.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,6 +54,12 @@ #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)) @@ -774,7 +783,7 @@ void QProcess::failChildProcessModifier(const char *description, int error) noex } // See IMPORTANT notice below -static void applyProcessParameters(const QProcess::UnixProcessParameters ¶ms) +static const char *applyProcessParameters(const QProcess::UnixProcessParameters ¶ms) { // Apply Unix signal handler parameters. // We don't expect signal() to fail, so we ignore its return value @@ -817,6 +826,29 @@ static void applyProcessParameters(const QProcess::UnixProcessParameters ¶ms 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"; + } + } + } + + return nullptr; } // the noexcept here adds an extra layer of protection @@ -857,7 +889,8 @@ void QChildProcess::startProcess() const noexcept callChildProcessModifier(d); // then we apply our other user-provided parameters - applyProcessParameters(d->unixExtras->processParameters); + if (const char *what = applyProcessParameters(d->unixExtras->processParameters)) + failChildProcess(d, what, errno); auto flags = d->unixExtras->processParameters.flags; using P = QProcess::UnixProcessFlag; diff --git a/tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp b/tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp index 96425eca2f..962a1ee1c0 100644 --- a/tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp +++ b/tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp @@ -80,6 +80,22 @@ int main(int argc, char **argv) return EXIT_SUCCESS; } + if (cmd == "noctty") { + int fd = open("/dev/tty", O_RDONLY); + if (fd == -1) + return EXIT_SUCCESS; + fprintf(stderr, "Could open /dev/tty\n"); + return EXIT_FAILURE; + } + + if (cmd == "setsid") { + pid_t pgid = getpgrp(); + if (pgid == getpid()) + return EXIT_SUCCESS; + fprintf(stderr, "Process group was %d\n", pgid); + return EXIT_FAILURE; + } + fprintf(stderr, "Unknown command \"%s\"", cmd.data()); return EXIT_FAILURE; } diff --git a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp index b4012c69c3..1e091d1178 100644 --- a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp +++ b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp @@ -126,6 +126,8 @@ private slots: void raiseInChildProcessModifier(); void unixProcessParameters_data(); void unixProcessParameters(); + void impossibleUnixProcessParameters_data(); + void impossibleUnixProcessParameters(); void unixProcessParametersAndChildModifier(); void unixProcessParametersOtherFileDescriptors(); #endif @@ -1730,6 +1732,10 @@ void tst_QProcess::unixProcessParameters_data() addRow("reset-sighand", P::ResetSignalHandlers); addRow("ignore-sigpipe", P::IgnoreSigPipe); addRow("file-descriptors", P::CloseFileDescriptors); + addRow("setsid", P::CreateNewSession); + + // On FreeBSD, we need to be session leader to disconnect from the CTTY + addRow("noctty", P::DisconnectControllingTerminal | P::CreateNewSession); } void tst_QProcess::unixProcessParameters() @@ -1778,6 +1784,13 @@ void tst_QProcess::unixProcessParameters() } } scope; + if (params.flags & QProcess::UnixProcessFlag::DisconnectControllingTerminal) { + if (int fd = open("/dev/tty", O_RDONLY); fd < 0) { + qInfo("Process has no controlling terminal; this test will do nothing"); + close(fd); + } + } + QProcess process; process.setUnixProcessParameters(params); process.setStandardInputFile(QProcess::nullDevice()); // so we can't mess with SIGPIPE @@ -1798,6 +1811,31 @@ void tst_QProcess::unixProcessParameters() QCOMPARE(process.exitStatus(), QProcess::NormalExit); } +void tst_QProcess::impossibleUnixProcessParameters_data() +{ + using P = QProcess::UnixProcessParameters; + QTest::addColumn<P>("params"); + QTest::newRow("setsid") << P{ QProcess::UnixProcessFlag::CreateNewSession }; +} + +void tst_QProcess::impossibleUnixProcessParameters() +{ + QFETCH(QProcess::UnixProcessParameters, params); + + QProcess process; + if (params.flags & QProcess::UnixProcessFlag::CreateNewSession) { + process.setChildProcessModifier([]() { + // double setsid() should cause the second to fail + setsid(); + }); + } + process.setUnixProcessParameters(params); + process.start("testProcessNormal/testProcessNormal"); + + QVERIFY(!process.waitForStarted(5000)); + qDebug() << process.errorString(); +} + void tst_QProcess::unixProcessParametersAndChildModifier() { static constexpr char message[] = "Message from the handler function\n"; @@ -1806,6 +1844,8 @@ void tst_QProcess::unixProcessParametersAndChildModifier() QAtomicInt vforkControl; int pipes[2]; + pid_t oldpgid = getpgrp(); + QVERIFY2(pipe(pipes) == 0, qPrintable(qt_error_string())); auto pipeGuard0 = qScopeGuard([=] { close(pipes[0]); }); { @@ -1813,10 +1853,14 @@ void tst_QProcess::unixProcessParametersAndChildModifier() // verify that our modifier runs before the parameters are applied process.setChildProcessModifier([=, &vforkControl] { + const char *pgidmsg = "PGID mismatch. "; + if (getpgrp() != oldpgid) + write(pipes[1], pgidmsg, strlen(pgidmsg)); write(pipes[1], message, strlen(message)); vforkControl.storeRelaxed(1); }); auto flags = QProcess::UnixProcessFlag::CloseFileDescriptors | + QProcess::UnixProcessFlag::CreateNewSession | QProcess::UnixProcessFlag::UseVFork; process.setUnixProcessParameters({ flags }); process.setProgram("testUnixProcessParameters/testUnixProcessParameters"); |