How to Daemonize in Linux

One of the things I keep running across is Linux daemons that don’t properly daemonize themselves. To properly daemonize, the following steps must be followed.

  • The fork() call is used to create a separate process.
  • The setsid() call is used to detach the process from the parent (normally a shell).
  • The file mask should be reset.
  • The current directory should be changed to something benign.
  • The standard files (stdin,stdout and stderr) need to be reopened.

Failure to do any of these steps will lead to a daemon process that can misbehave. The typical symptoms are as follows.

  • Starting the daemon and then logging out will cause the terminal to hang. This is particularly nasty with ssh.
  • The directory from which the daemon was launched remains locked.
  • Spurious output appears in the shell from which the daemon was started.

Simple Example

The following example program performs the bare minimum steps required to launch a daemon process.

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

 

#define EXIT_SUCCESS 0

#define EXIT_FAILURE 1

 

static void daemonize(void)

{

    pid_t pid, sid;

 

    /* already a daemon */

    if ( getppid() == 1 ) return;

 

    /* Fork off the parent process */

    pid = fork();

    if (pid < 0) {

        exit(EXIT_FAILURE);

    }

    /* If we got a good PID, then we can exit the parent process. */

    if (pid > 0) {

        exit(EXIT_SUCCESS);

    }

 

    /* At this point we are executing as the child process */

 

    /* Change the file mode mask */

    umask(0);

 

    /* Create a new SID for the child process */

    sid = setsid();

    if (sid < 0) {

        exit(EXIT_FAILURE);

    }

 

    /* Change the current working directory.  This prevents the current

       directory from being locked; hence not being able to remove it. */

    if ((chdir("/")) < 0) {

        exit(EXIT_FAILURE);

    }

 

    /* Redirect standard files to /dev/null */

    freopen( "/dev/null", "r", stdin);

    freopen( "/dev/null", "w", stdout);

    freopen( "/dev/null", "w", stderr);

}

 

int main( int argc, char *argv[] ) {

    daemonize();

 

    /* Now we are a daemon -- do the work for which we were paid */

 

 

    return 0;

}

It has been brought to my attention that a second call to fork() may be required to fully detach the process from the controller terminal (in other words: fork, setsid, fork). This does not seem to be required in Linux. A second fork would not cause any problems, although it would complicate the child/parent signalling below.

A More Useful Example

The following program extends the basic daemon by adding the following features.

  • Logs messages to the system log (via syslog).
  • Creates a lock file to prevent the daemon from being run twice.
  • Changes the effective user (drops privileges).
  • Startup errors are reported to the main process.

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <syslog.h>

#include <errno.h>

#include <pwd.h>

#include <signal.h>

 

/* Change this to whatever your daemon is called */

#define DAEMON_NAME "mydaemon"

 

/* Change this to the user under which to run */

#define RUN_AS_USER "daemon"

 

#define EXIT_SUCCESS 0

#define EXIT_FAILURE 1

 

 

static void child_handler(int signum)

{

    switch(signum) {

    case SIGALRM: exit(EXIT_FAILURE); break;

    case SIGUSR1: exit(EXIT_SUCCESS); break;

    case SIGCHLD: exit(EXIT_FAILURE); break;

    }

}

 

static void daemonize( const char *lockfile )

{

    pid_t pid, sid, parent;

    int lfp = -1;

 

    /* already a daemon */

    if ( getppid() == 1 ) return;

 

    /* Create the lock file as the current user */

    if ( lockfile && lockfile[0] ) {

        lfp = open(lockfile,O_RDWR|O_CREAT,0640);

        if ( lfp < 0 ) {

            syslog( LOG_ERR, "unable to create lock file %s, code=%d (%s)",

                    lockfile, errno, strerror(errno) );

            exit(EXIT_FAILURE);

        }

    }

 

    /* Drop user if there is one, and we were run as root */

    if ( getuid() == 0 || geteuid() == 0 ) {

        struct passwd *pw = getpwnam(RUN_AS_USER);

        if ( pw ) {

            syslog( LOG_NOTICE, "setting user to " RUN_AS_USER );

            setuid( pw->pw_uid );

        }

    }

 

    /* Trap signals that we expect to recieve */

    signal(SIGCHLD,child_handler);

    signal(SIGUSR1,child_handler);

    signal(SIGALRM,child_handler);

 

    /* Fork off the parent process */

    pid = fork();

    if (pid < 0) {

        syslog( LOG_ERR, "unable to fork daemon, code=%d (%s)",

                errno, strerror(errno) );

        exit(EXIT_FAILURE);

    }

    /* If we got a good PID, then we can exit the parent process. */

    if (pid > 0) {

 

        /* Wait for confirmation from the child via SIGTERM or SIGCHLD, or

           for two seconds to elapse (SIGALRM).  pause() should not return. */

        alarm(2);

        pause();

 

        exit(EXIT_FAILURE);

    }

 

    /* At this point we are executing as the child process */

    parent = getppid();

 

    /* Cancel certain signals */

    signal(SIGCHLD,SIG_DFL); /* A child process dies */

    signal(SIGTSTP,SIG_IGN); /* Various TTY signals */

    signal(SIGTTOU,SIG_IGN);

    signal(SIGTTIN,SIG_IGN);

    signal(SIGHUP, SIG_IGN); /* Ignore hangup signal */

    signal(SIGTERM,SIG_DFL); /* Die on SIGTERM */

 

    /* Change the file mode mask */

    umask(0);

 

    /* Create a new SID for the child process */

    sid = setsid();

    if (sid < 0) {

        syslog( LOG_ERR, "unable to create a new session, code %d (%s)",

                errno, strerror(errno) );

        exit(EXIT_FAILURE);

    }

 

    /* Change the current working directory.  This prevents the current

       directory from being locked; hence not being able to remove it. */

    if ((chdir("/")) < 0) {

        syslog( LOG_ERR, "unable to change directory to %s, code %d (%s)",

                "/", errno, strerror(errno) );

        exit(EXIT_FAILURE);

    }

 

    /* Redirect standard files to /dev/null */

    freopen( "/dev/null", "r", stdin);

    freopen( "/dev/null", "w", stdout);

    freopen( "/dev/null", "w", stderr);

 

    /* Tell the parent process that we are A-okay */

    kill( parent, SIGUSR1 );

}

 

int main( int argc, char *argv[] ) {

    /* Initialize the logging interface */

    openlog( DAEMON_NAME, LOG_PID, LOG_LOCAL5 );

    syslog( LOG_INFO, "starting" );

 

    /* One may wish to process command line arguments here */

 

    /* Daemonize */

    daemonize( "/var/lock/subsys/" DAEMON_NAME );

 

    /* Now we are a daemon -- do the work for which we were paid */

 

 

    /* Finish up */

    syslog( LOG_NOTICE, "terminated" );

    closelog();

    return 0;

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值