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
SIGHUP
is 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?
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.