17

I found the following function in the source code of catwm (a minimalistic window manager):

void spawn(const Arg arg) {
    if(fork() == 0) {
        if(fork() == 0) {
            if(dis)
                close(ConnectionNumber(dis));
        setsid();
        execvp((char*)arg.com[0],(char**)arg.com);
    }
    exit(0);
}

}

(see it on github)

I don't understand why not simply

void spawn(const Arg arg) {
    if(fork() == 0) {
        if(dis)
            close(ConnectionNumber(dis));
    setsid();
    execvp((char*)arg.com[0],(char**)arg.com);
}

}

? Are there any benefits of using the double fork() here?

chicks
  • 1,112
Alex
  • 351

1 Answers1

21

The following paragraphs, quoted from Stevens and Rago Advanced Programming in the UNIX Environment, describe two of six coding rules for writing a daemon. Specifically, they implement them in a single daemonize function listed in Figure 13.1 in case you want to look it up.

  1. Call fork and have the parent exit. This does several things. First, if the daemon was started as a simple shell command, having the parent terminate makes the shell think that the command is done. Second, the child inherits the process group ID of the parent but gets a new process ID, so we’re guaranteed that the child is not a process group leader. This is a prerequisite for the call to setsid that is done next.
  2. Call setsid to create a new session. The three steps listed in Section 9.5 occur. The process (a) becomes the leader of a new session, (b) becomes the leader of a new process group, and (c) is disassociated from its controlling terminal.
    • Under System V–based systems, some people recommend calling fork again at this point, terminating the parent, and continuing the daemon in the child. This guarantees that the daemon is not a session leader, which prevents it from acquiring a controlling terminal under the System V rules (Section 9.6). Alternatively, to avoid acquiring a controlling terminal, be sure to specify O_NOCTTY whenever opening a terminal device.

In your changed code, the parent doesn't exit() which will continue its execution after spawn() is called; the exact behavior would depend on what follows the spawn() call.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
don_aman
  • 1,373
  • 1
    stupid question, is modern Linux (or whatever you'd be likely to run catwm on, seeing that the makefile of catwm defaults to gcc, I've assumed it's Linux) a System V-based system? – Marcus Müller Aug 28 '22 at 20:16
  • 1
    @MarcusMüller i don't really know about unix history, but Linux does have System V features, like the init system (though it's more a user space thing), IPC, shared memory, etc. regarding what the book says, you can see the credentials(7) man page for information about controlling terminals: All of the processes in a session share a controlling terminal. The controlling terminal is established when the session leader first opens a terminal (unless the O_NOCTTY flag is specified when calling open(2)). A terminal may be the controlling terminal of at most one session. So it holds for Linux. – don_aman Aug 28 '22 at 20:29
  • 2
    @MarcusMüller, this reminds me of the few instances where I've heard that there were some kernel-level behavioural differences between SysV and BSD -based systems (back in the day anyway). With regard to those, Linux would still be exactly as SysV as it always was, since kernel behaviours like that, ones that are visible to user space, would not have been changed. Even if most modern Linuxes dumped the SysV style init system in favor of systemd in userspace. – ilkkachu Aug 28 '22 at 20:40
  • 1
    And of course, these days you almost never want to actually write a traditional "daemon": we're all running modern process monitors and container runtimes that work best if your program simply runs in the foreground. – larsks Aug 30 '22 at 02:26