Previously, we sent SIGKILL directly to the child process. If we're
monitoring the system bus, the child process is owned by root, so the
parent process can't send it signals. In this case, we relied on the
child process dying with "Broken pipe" when it next tries to write to
stdout (which we close).
If you run `pkexec dbus-monitor --system` in a terminal, you are able to
press Ctrl-C to send SIGINT to that privileged child process. This is
because the signal is not sent directly. Instead, the terminal emulator
writes ^C to the child's controlling terminal; the kernel turns this
into SIGINT and send that to the child.
We can do the same thing here. Here are the steps:
* Create a pseudo-terminal (PTY) master/slave (not my terminology) pair
* Make this PTY the controlling terminal for the child process:
* Make the slave FD the stdin for the child
* In a GSubprocessLauncher child_setup function, which runs between
fork() and exec():
* Move the process to a new session with setsid(), removing any
existing controlling terminal
* Call ioctl(STDIN_FILENO, TIOCSCTTY, 0) to set the stdin FD as the
* When it comes time to kill the child, write ^C into the master side of
We continue to send SIGINT (rather than SIGKILL; it seems kinder) the
old-fashioned way (in case something goes wrong setting the controlling
terminal) and closing the pipe so that the child eventually dies with
EPIPE (in case the old-fashioned way fails too).
A potential fly in the works is that, in the Flatpak case, the immediate
child is a flatpak-spawn process; `pkexec dbus-monitor --system` is
actually launched from the session helper. Happily, the session helper
already calls setsid() + TIOCSCTTY if any of stdin/stdout/stderr on the
spawned process are TTYs
so we just skip the child_setup function in that case.
See https://blog.nelhage.com/2011/02/changing-ctty/ for some useful
background reading on controlling terminals.