• MacOS - make child processes die with parent

    From Muttley@dastardlyhq.com@21:1/5 to All on Fri Jan 21 14:15:28 2022
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From William Ahern@21:1/5 to Muttley@dastardlyhq.com on Thu Jan 27 15:28:15 2022
    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)
  • From Geoff Clare@21:1/5 to William Ahern on Fri Jan 28 13:58:25 2022
    William Ahern wrote:

    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.

    Seems you missed these statements on the _Exit() page (under
    "Consequences of Process Termination", which says it applies to all
    forms of termination, not just exiting):

    * If the process is a controlling process, the SIGHUP signal shall be
    sent to each process in the foreground process group of the
    controlling terminal belonging to the calling process.

    * If the exit of the process causes a process group to become orphaned,
    and if any member of the newly-orphaned process group is stopped,
    then a SIGHUP signal followed by a SIGCONT signal shall be sent to
    each process in the newly-orphaned process group.

    --
    Geoff Clare <netnews@gclare.org.uk>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Muttley@dastardlyhq.com@21:1/5 to William Ahern on Fri Jan 28 14:44:24 2022
    On Thu, 27 Jan 2022 15:28:15 -0800
    William Ahern <william@25thandClement.com> wrote:
    Muttley@dastardlyhq.com wrote:
    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

    Hi, thanks for the long explanation. However in the following code compiled
    and run on MacOS the signal handler is never called yet the child process
    is simply reparented by init (launchd) and carries on running:

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>

    void sighandler(int sig)
    {
    printf("pid %d got signal %d\n",getpid(),sig);
    }


    int main()
    {
    signal(SIGHUP,sighandler);

    switch(fork())
    {
    case -1:
    perror("fork()");
    return 1;
    case 0:
    printf("child pid %d\n",getpid());
    sleep(3);
    puts("child exiting");
    return 0;
    }
    printf("parent pid %d exiting\n",getpid());
    return 0;
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)