summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2023-05-12 19:50:51 -0700
committerThiago Macieira <thiago.macieira@intel.com>2023-07-08 15:03:23 -0700
commit13a1995e9dc987d1560b38d16b76442261b4aa8d (patch)
treebb2ba2d6ce159b1123721756c616cb10c8170c1a
parent8f1df9aaa87f1d899babce42a8fdbb28c13ae604 (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.cpp16
-rw-r--r--src/corelib/io/qprocess.h2
-rw-r--r--src/corelib/io/qprocess_unix.cpp37
-rw-r--r--tests/auto/corelib/io/qprocess/testUnixProcessParameters/main.cpp16
-rw-r--r--tests/auto/corelib/io/qprocess/tst_qprocess.cpp44
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 &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
@@ -817,6 +826,29 @@ 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";
+ }
+ }
+ }
+
+ 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");