#include "apue.h"
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
void
daemonize() {
pid_t pid, sid;
int fd;
struct sigaction sa;
pid = fork();
switch (pid) {
case -1:
printf("fork() failed: %s", strerror(errno));
exit(1);
case 0:
break;
default:
/* parent terminates */
_exit(0);
}
/* After the first fork()
* ppid is 0,
* gid is the parent id which has exited,
* sid is the current session id (normally is the login shell id),
* tty is the current session tty.
* current process attached with the controlling terminal,
* so the daemon still mabe be invoked from the shell then this returns control to the user.
* logout the session(pkill -t tty) will kill this child process.
*/
sid = setsid();
if (sid < 0) {
printf("setuid() failed, %s", strerror(errno));
exit(1);
}
/* After setsid()
* Start a new session, start a new process group, so we can observed:
*
* ppid still be 0,
* gid == pid,
* sid == pid,
* tty == null,
* current process has been the session leader and group leader,
* and disassociated from its controlling terminal
*/
/* 1. SIGHUP is sent to the controlling process (session leader) associated
* with a controlling terminal if a disconnect is detected by the terminal interface.
* 2. This signal is also generated if the session leader terminates(match this approach).
*
* In this case, daemon should not have a controlling terminal and would normally never receive this signal.
* Whereas, when the session leader terminates (the first child),
* all processes in the session (our second child) receive the SIGHUP signal.
* So, before the second forking it is necessary to ignore SIGHUP
*
* However, in practice, we can't catch the SIGHUP signal be generated,
* and the daemonize function still can work properly
* even if we remove the sigaction related routine. This is because after the setuid(),
* current process is session leader but don't attached
* a controlling terminal, so no SIGHUP generated. Nevertheless,
*if the process open() a tty device (such as a pty slave) to gain a controlling terminal,
* then lose it again, and then you should get the SIGHUP signal.
* So, always ignore the SIGHUP is is just guarantee the daemon conventions correctly.
* */
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0) {
printf("sigaction() failed, can't ignore SIGHUOP\n");
exit(1);
}
/* If a session leader has no controlling terminal and it opens a terminal device
* (for example, by opening /dev/tty or the like)
* then that device automatically becomes the controlling terminal.
* Again, this is something that you do not want to happen.
* The solution is to perform a second fork, again allowing the parent process to terminate.
* This ensures that the child process is not a session leader.
* */
pid = fork();
switch (pid) {
case -1:
printf("fork() failed: %s", strerror(errno));
exit(1);
case 0:
break;
default:
exit(0);
}
/*After the second fork(), current process is not group leader and not session leader:
* ppid still be 0
* gid = the first child pid
* sid = the first child pid
* tty = null
* */
/* Change the current working directory to the root
* so we won’t prevent file systems from being unmounted
* */
if (chdir("/") < 0) {
printf("chdir(/) failed: %s", strerror(errno));
}
/* clear file mode creation mask */
umask(0);
/* redirect stdin, stdout, stderr to /dev/null */
fd = open("/dev/null", O_RDWR);
if (fd < 0) {
printf("open(\"/dev/null\") failed: %s", strerror(errno));
exit(1);
}
if (dup2(fd, STDIN_FILENO) < 0) {
printf("dup2(%d, STDIN) failed: %s", fd, strerror(errno));
close(fd);
exit(1);
}
if (dup2(fd, STDOUT_FILENO) < 0) {
printf("dup2(%d, STDOUT) failed: %s", fd, strerror(errno));
close(fd);
exit(1);
}
if (dup2(fd, STDERR_FILENO) < 0) {
printf("dup2(%d, STDERR) failed: %s", fd, strerror(errno));
close(fd);
exit(1);
}
/* close /dev/null , in fact is close stdin, stdout, stderr */
if (fd > STDERR_FILENO) {
close(fd);
}
}
int
main() {
daemonize();
for (; ;) {
pause();
}
exit(0);
}