#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);
}

results matching ""

    No results matching ""