0

I am reading a book about Unix system programming. In the book there is a function to create a daemon process.

Part of the code is not very clear to me, particularly the following:

struct sigaction    sa;
....
/* *Become a session leader to lose controlling TTY. */
if ((pid = fork()) < 0)
{
    err_quit("%s: can’t fork", cmd);
}
else if (pid != 0) /* parent */
{
    exit(0); //the parent will exit
}
setsid();

/* *Ensure future opens won’t allocate controlling TTYs. */
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
    err_quit("%s: can’t ignore SIGHUP", cmd);
}

where

SIGHUPis the signal sent to the controlling process (session leader) associated with a controlling terminal if a disconnect is detected by the terminal interface.

So basically the parent process calls fork and then exit. In this way we are guaranteed the child not a group leader. The child becomes a session leader with setsid.

I do not understand when the signal SIG_UP is generated: from the definition it seems it is generated when closing a Terminal window, but from the comment in the code

/* *Ensure future opens won’t allocate controlling TTYs. */

it seems it is generated in a different situation: when is it generated?

Secondly it wants to ignore this signal so it sets sa.sa_handler = SIG_IGN and then call sigaction. If it is ignoring the signal setting SIG_IGN as its handler, why is it setting the mask passed to sigaction as sigemptyset(&sa.sa_mask);? I mean if there is no handler, the mask set before executing the handler is not used: is it?

The complete function is the following:

void daemonize(const char *cmd)
{
    int i, fd0, fd1, fd2;
    pid_t pid;
    struct rlimit       rl;
    struct sigaction    sa;
    /* *Clear file creation mask.*/
    umask(0);
    /* *Get maximum number of file descriptors. */
    if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
    {
        err_quit("%s: can’t get file limit", cmd);
    }
    /* *Become a session leader to lose controlling TTY. */
    if ((pid = fork()) < 0)
    {
        err_quit("%s: can’t fork", cmd);
    }
    else if (pid != 0) /* parent */
    {
        exit(0); //the parent will exit
    }
    setsid();
    /* *Ensure future opens won’t allocate controlling TTYs. */
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGHUP, &sa, NULL) < 0)
    {
        err_quit("%s: can’t ignore SIGHUP", cmd);
    }
    if ((pid = fork()) < 0)
    {
        err_quit("%s: can’t fork", cmd);
    }
    else if (pid != 0) /* parent */
    {
        exit(0);
    }
    /*
    *Change the current working directory to the root so
    * we won’t prevent file systems from being unmounted.
    */
    if (chdir("/") < 0)
    {
        err_quit("%s: can’t change directory to /", cmd);
    }
    /*
    *Close all open file descriptors.
    */
    if (rl.rlim_max == RLIM_INFINITY)
    {
        rl.rlim_max = 1024;
    }
    for (i = 0; i < rl.rlim_max; i++)
    {
        close(i);
    }
    /*
    *Attach file descriptors 0, 1, and 2 to /dev/null.
    */
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);
    /*
    *Initialize the log file.
    */
    openlog(cmd, LOG_CONS, LOG_DAEMON);
    if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
        syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
        exit(1);
    }
}

EDIT

Also I have an additional question. Why is fork called twice in the function?

roschach
  • 8,390
  • 14
  • 74
  • 124

1 Answers1

1

So basically ...

Yes, the parent process forks a child process, and that child does setsid() so that it will be the process group leader (and the only process) in the new process group, and have no controlling terminal. That last part is the key.

(If there was a reason why the child process should run in the same process group as the parent process, one could use int fd = open("/dev/tty", O_RDWR); if (fd != -1) ioctl(fd, TIOCNOTTY); to detach from the controlling terminal. setsid() is easier, and it is usually preferable to have the child run in a new process group anyway, as it and its children can be sent a signal without affecting any other processes.)

Now, whenever a process that has no controlling terminal opens a terminal device (a tty or a pseudo-tty), that device will become its controlling terminal (unless the O_NOCTTY flag was used when opening the device).

Whenever the controlling terminal is disconnected, a SIGHUP signal is delivered to each process having that terminal as their controlling terminal. (That SIG_UP thing is just a typo. Signal names do not have an underscore, only the special handlers SIG_DFL, SIG_IGN, and SIG_ERR do.)

If the daemon process opens a terminal device for any reason -- for example, because a library wants to print an error message to a console, and opens /dev/tty1 or similar to do so --, the daemon will inadvertently acquire a controlling terminal. Other than interposing open(), fopen(), opendir(), and so on, to ensure their underlying open() flags will include O_NOCTTY, there is not much a daemon can do to ensure it will not inadvertently acquire a controlling terminal. Instead, the easier option is to just assume that it might, and simply ensure that that does not cause too much trouble. To avoid the most typical issue, dying from SIGHUP when the controlling terminal is disconnected, the daemon process can simply ignore the delivery of the SIGHUP signal.

In short, it is a belt-and-suspenders approach. The setsid() detaches the process from the controlling terminal; and SIGHUP is ignored in case the daemon inadvertently acquires a controlling terminal by opening a tty device without using the O_NOCTTY flag.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • First of all thank you for your answer. What about `sigemptyset(&sa.sa_mask);` in conjunction with `sigaction(SIGHUP, &sa, NULL) < 0)`? Why is it setting the mask for a `NULL` handler? – roschach Dec 09 '18 at 10:45
  • @FrancescoBoi: `.sa_mask` is the mask of signals blocked during the execution of the signal handler, in addition to whatever signals are blocked in the thread when the signal is delivered. It must be initialized to the empty set using `sigemptyset(&sa.sa_mask);`. It is common to leave it empty. The third parameter to `sigaction()` is not the handler, but a pointer to a `struct sigaction` filled with the details of the old signal action this new one replaced; or NULL if the information is not needed. – Nominal Animal Dec 09 '18 at 12:07
  • So is it just out of convention? – roschach Dec 09 '18 at 22:40
  • @FrancescoBoi: Not just convention, it is **required**. It initializes the `.sa_mask` member to a known state: empty mask. – Nominal Animal Dec 10 '18 at 01:53