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>
This commit is contained in:
parent
8f1df9aaa8
commit
13a1995e9d
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user