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
|
child. The \c stdin, \c stdout, and \c stderr file descriptors are
|
||||||
never closed.
|
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
|
\value IgnoreSigPipe Always sets the \c SIGPIPE signal to ignored
|
||||||
(\c SIG_IGN), even if the \c ResetSignalHandlers flag was set. By
|
(\c SIG_IGN), even if the \c ResetSignalHandlers flag was set. By
|
||||||
default, if the child attempts to write to its standard output or
|
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
|
// some room if we want to add IgnoreSigHup or so
|
||||||
CloseFileDescriptors = 0x0010,
|
CloseFileDescriptors = 0x0010,
|
||||||
UseVFork = 0x0020, // like POSIX_SPAWN_USEVFORK
|
UseVFork = 0x0020, // like POSIX_SPAWN_USEVFORK
|
||||||
|
CreateNewSession = 0x0040, // like POSIX_SPAWN_SETSID
|
||||||
|
DisconnectControllingTerminal = 0x0080,
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(UnixProcessFlags, UnixProcessFlag)
|
Q_DECLARE_FLAGS(UnixProcessFlags, UnixProcessFlag)
|
||||||
struct UnixProcessParameters
|
struct UnixProcessParameters
|
||||||
|
@ -39,6 +39,9 @@
|
|||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if __has_include(<paths.h>)
|
||||||
|
# include <paths.h>
|
||||||
|
#endif
|
||||||
#if __has_include(<linux/close_range.h>)
|
#if __has_include(<linux/close_range.h>)
|
||||||
// FreeBSD's is in <unistd.h>
|
// FreeBSD's is in <unistd.h>
|
||||||
# include <linux/close_range.h>
|
# include <linux/close_range.h>
|
||||||
@ -51,6 +54,12 @@
|
|||||||
#ifndef O_PATH
|
#ifndef O_PATH
|
||||||
# define O_PATH 0
|
# define O_PATH 0
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef _PATH_DEV
|
||||||
|
# define _PATH_DEV "/dev/"
|
||||||
|
#endif
|
||||||
|
#ifndef _PATH_TTY
|
||||||
|
# define _PATH_TTY _PATH_DEV "tty"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_FREEBSD
|
#ifdef Q_OS_FREEBSD
|
||||||
__attribute__((weak))
|
__attribute__((weak))
|
||||||
@ -774,7 +783,7 @@ void QProcess::failChildProcessModifier(const char *description, int error) noex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See IMPORTANT notice below
|
// See IMPORTANT notice below
|
||||||
static void applyProcessParameters(const QProcess::UnixProcessParameters ¶ms)
|
static const char *applyProcessParameters(const QProcess::UnixProcessParameters ¶ms)
|
||||||
{
|
{
|
||||||
// Apply Unix signal handler parameters.
|
// Apply Unix signal handler parameters.
|
||||||
// We don't expect signal() to fail, so we ignore its return value
|
// 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);
|
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
|
// the noexcept here adds an extra layer of protection
|
||||||
@ -857,7 +889,8 @@ void QChildProcess::startProcess() const noexcept
|
|||||||
callChildProcessModifier(d);
|
callChildProcessModifier(d);
|
||||||
|
|
||||||
// then we apply our other user-provided parameters
|
// 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;
|
auto flags = d->unixExtras->processParameters.flags;
|
||||||
using P = QProcess::UnixProcessFlag;
|
using P = QProcess::UnixProcessFlag;
|
||||||
|
@ -80,6 +80,22 @@ int main(int argc, char **argv)
|
|||||||
return EXIT_SUCCESS;
|
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());
|
fprintf(stderr, "Unknown command \"%s\"", cmd.data());
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,8 @@ private slots:
|
|||||||
void raiseInChildProcessModifier();
|
void raiseInChildProcessModifier();
|
||||||
void unixProcessParameters_data();
|
void unixProcessParameters_data();
|
||||||
void unixProcessParameters();
|
void unixProcessParameters();
|
||||||
|
void impossibleUnixProcessParameters_data();
|
||||||
|
void impossibleUnixProcessParameters();
|
||||||
void unixProcessParametersAndChildModifier();
|
void unixProcessParametersAndChildModifier();
|
||||||
void unixProcessParametersOtherFileDescriptors();
|
void unixProcessParametersOtherFileDescriptors();
|
||||||
#endif
|
#endif
|
||||||
@ -1730,6 +1732,10 @@ void tst_QProcess::unixProcessParameters_data()
|
|||||||
addRow("reset-sighand", P::ResetSignalHandlers);
|
addRow("reset-sighand", P::ResetSignalHandlers);
|
||||||
addRow("ignore-sigpipe", P::IgnoreSigPipe);
|
addRow("ignore-sigpipe", P::IgnoreSigPipe);
|
||||||
addRow("file-descriptors", P::CloseFileDescriptors);
|
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()
|
void tst_QProcess::unixProcessParameters()
|
||||||
@ -1778,6 +1784,13 @@ void tst_QProcess::unixProcessParameters()
|
|||||||
}
|
}
|
||||||
} scope;
|
} 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;
|
QProcess process;
|
||||||
process.setUnixProcessParameters(params);
|
process.setUnixProcessParameters(params);
|
||||||
process.setStandardInputFile(QProcess::nullDevice()); // so we can't mess with SIGPIPE
|
process.setStandardInputFile(QProcess::nullDevice()); // so we can't mess with SIGPIPE
|
||||||
@ -1798,6 +1811,31 @@ void tst_QProcess::unixProcessParameters()
|
|||||||
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
|
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()
|
void tst_QProcess::unixProcessParametersAndChildModifier()
|
||||||
{
|
{
|
||||||
static constexpr char message[] = "Message from the handler function\n";
|
static constexpr char message[] = "Message from the handler function\n";
|
||||||
@ -1806,6 +1844,8 @@ void tst_QProcess::unixProcessParametersAndChildModifier()
|
|||||||
QAtomicInt vforkControl;
|
QAtomicInt vforkControl;
|
||||||
int pipes[2];
|
int pipes[2];
|
||||||
|
|
||||||
|
pid_t oldpgid = getpgrp();
|
||||||
|
|
||||||
QVERIFY2(pipe(pipes) == 0, qPrintable(qt_error_string()));
|
QVERIFY2(pipe(pipes) == 0, qPrintable(qt_error_string()));
|
||||||
auto pipeGuard0 = qScopeGuard([=] { close(pipes[0]); });
|
auto pipeGuard0 = qScopeGuard([=] { close(pipes[0]); });
|
||||||
{
|
{
|
||||||
@ -1813,10 +1853,14 @@ void tst_QProcess::unixProcessParametersAndChildModifier()
|
|||||||
|
|
||||||
// verify that our modifier runs before the parameters are applied
|
// verify that our modifier runs before the parameters are applied
|
||||||
process.setChildProcessModifier([=, &vforkControl] {
|
process.setChildProcessModifier([=, &vforkControl] {
|
||||||
|
const char *pgidmsg = "PGID mismatch. ";
|
||||||
|
if (getpgrp() != oldpgid)
|
||||||
|
write(pipes[1], pgidmsg, strlen(pgidmsg));
|
||||||
write(pipes[1], message, strlen(message));
|
write(pipes[1], message, strlen(message));
|
||||||
vforkControl.storeRelaxed(1);
|
vforkControl.storeRelaxed(1);
|
||||||
});
|
});
|
||||||
auto flags = QProcess::UnixProcessFlag::CloseFileDescriptors |
|
auto flags = QProcess::UnixProcessFlag::CloseFileDescriptors |
|
||||||
|
QProcess::UnixProcessFlag::CreateNewSession |
|
||||||
QProcess::UnixProcessFlag::UseVFork;
|
QProcess::UnixProcessFlag::UseVFork;
|
||||||
process.setUnixProcessParameters({ flags });
|
process.setUnixProcessParameters({ flags });
|
||||||
process.setProgram("testUnixProcessParameters/testUnixProcessParameters");
|
process.setProgram("testUnixProcessParameters/testUnixProcessParameters");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user