Muttley@dastardlyhq.com wrote:
Making all child processes exit when the parent exits (without having to add extra code into the child, eg getppid() polling) is done rather simply using
prctl(PR_SET_PDEATHSIG, SIGKILL)
with the proviso that there may be a race condition if the parent exits immediately after the fork().
However I (or rather google) can't find a method of emulating that in MacOS and
I presume this applies to BSD also. Does anyone know a way?
Thanks for any help.
You can use ptys, sessions, and process groups to emulate this behavior, and without having to modify children. This relies on a *little* ambiguity in
the POSIX standard, but macOS and all other Unix-like systems I've tried (Linux, various BSDs, Solaris, AIX) behave the same way.
The trick is that on "modem disconnect" SIGHUP is sent to all processes in
the process group associated with the controlling terminal (i.e. sent to the foreground process group), *presuming* at least one process currently has an open descriptor to the slave tty. POSIX only requires SIGHUP to be sent to
the controlling process--i.e. the session leader. See POSIX.1-2017 11.1.10
at
https://pubs.opengroup.org/onlinepubs/9699919799/. But in practice SIGHUP
is sent to the foreground process group so long as the slave tty still has
an valid, open descriptor reference. (Slave tty can still exist without any open descriptor references as that's how opening /dev/tty works, and how a process can have a controlling terminal even if it dup'd over references at stdin, stdout, stderr. But the historical SIGHUP seems to check for some
valid, open descriptor reference.)
The second aspect to understand is that when the controlling process (i.e. session leader) of a pty pair exits, this is treated as a modem disconnect, triggering SIGHUP. POSIX is ambiguous on this point. Section 11.1.3
says
"When a controlling process terminates, the controlling terminal is
dissociated from the current session, allowing it to be acquired by a new
session leader. Subsequent access to the terminal by other processes in
the earlier session may be denied, with attempts to access the terminal
treated as if a modem disconnect had been sensed."
To me that doesn't require SIGHUP to be sent unless/until a process performs
an "access" operation, but again in practice the behavior I've seen
everywhere I've tested is for SIGHUP to be delivered immediately upon exit
of the controlling process.
Lastly, the default signal handler for SIGHUP is termination. Processes
could change this, unlike SIGKILL. In practice very few command-line
utilities or simple processes will do this. One notable counter example
would be daemon services, where it's not uncommon to overload SIGHUP to mean reload configuration. (OTOH, in the process in optional step #3, below, you could *catch* SIGHUP and re-send SIGKILL to the process group.)
The sequence is pretty simple:
1) Invoke setsid from the parent process, which (if successful--not already a
leader) will become the leader of a new session and process group,
disassociated from any controlling terminal.
2) Create a new pty master/slave pair from the parent process:
a) Create a new master pty (mtty):
i) mtty = posix_openpt(O_RDWR|O_NOCTTY)
ii) grantpt(mtty)
iii) unlockpt(mtty)
b) Open slave pty (stty):
i) stty = open(ptsname(mtty), O_RDWR)
c) If TIOCSCTTY is defined, assume that we need to explicitly set slave
pty (stty) as controlling terminal of our session as POSIX leaves
unspecified how a controlling terminal is assigned (NB: theoretically
a system might require some other operation, but in practice most
systems assign controlling terminal by default upon opening unless
O_NOCTTY is specified, and most others use TIOCSCTTY):
i) ioctl(stty, TIOCSCTTY)
3) If you're worried about children closing slave tty references (e.g. if
stty is accidentally at stdin, stdout, or stderr, or if a process might unconditionally close unknown descriptors using closefrom(2) or similar pattern) then fork at least one child that will sleep indefinitely holding
an open reference.
And that's it. Now you can fork all you want, and whenever the original
parent (i.e. PID == setsid == getpgrp) exits, SIGHUP will be sent to all processes that have that controlling terminal (i.e. every descendent that hasn't called setsid or otherwise disassociated).
The above sequence is translated from a regression test I wrote:
https://github.com/wahern/lunix/blob/98877f3/regress/0-ctty-sighup.lua
There are almost certainly some caveats here that are unknown to me,
especially if we're trying to be portable across *all* systems that will
send SIGHUP to processes other than the controlling process. For example, I have vague recollections that on some systems (AIX?) the master tty may also need to have an open, valid descriptor reference at least in the controlling process itself. But at least across macOS and Linux--systems where I
regularly test and use my software--I haven't hit such exceptions. I've
tested it on FreeBSD, NetBSD, OpenBSD, Solaris, and AIX, but only sample programs. Frankly I'm still a little surprised it worked on AIX.
Unfortunately, I no longer have access to AIX since polarhome.com retired.
--- SoupGate-Win32 v1.05
* Origin: fsxNet Usenet Gateway (21:1/5)