Search code examples
clinuxforkdaemonsystemd

Child doesn't execute after double fork in systemd


I have this C code to daemonize a systemd service:

static void daemon_me(char *my_name) {
    pid_t new_pid;
    struct sigaction sig_act;
    int i;
    int f0, f1, f2;
    int my_mask;
    umask(0);

    if((new_pid = fork()) < 0) {
        err_exit("%s: Errore sulla fork", my_name);
    }
    else if(new_pid != 0) {
        exit(0);
    }
    syslog(LOG_CRIT, "PRINT0 pid %d", new_pid);
    setsid(); 

    if((new_pid = fork()) < 0) {
        syslog(LOG_CRIT, "PRINT1 pid %d", new_pid);
        err_exit("%s: Errore sulla fork", my_name);
    }
    else if(new_pid != 0) {
        syslog(LOG_CRIT, "PRINT2 pid %d", new_pid);
        exit(0);
    }
    syslog(LOG_CRIT, "PRINT3 pid %d", new_pid);

and it doesn't execute the child process after the second fork(). the log is:

PRINT0 pid 0
WAN_Application.out: PRINT2 pid 3009

if i run it manually it works fine:

PRINT0 pid 0
PRINT2 pid 3080
PRINT3 pid 0

Why it happens only using systemd?

It should have the same behaviour

EDIT: full example

#include <string.h>                             /* strrchr, strerror, strcat, memset */
#include <syslog.h>                             /* openlog, syslog, closelog, setlogmask */
#include <stdlib.h>                             /* strtol, exit */
#include <stdio.h>                              /* fputs, vsnprintf, snprintf, fflush */
#include <sys/types.h>                          /* umask, open, accept, setsockopt */
#include <sys/stat.h>                           /* umask, open */
#include <sys/resource.h>                       /* getrlimit */
#include <sys/socket.h>                         /* accept, shutdown, setsockopt */
#include <stdarg.h>                             /* va_start */
#include <unistd.h>                             /* fork, setsid, chdir. close, dup, sync, sleep */
#include <signal.h>                             /* sigemptyset, sigaction */
#include <fcntl.h>                              /* open */
#include <sys/mman.h>                           /* mlockall */
#include <sys/un.h>                             /* sockaddr_un */
#include <sys/shm.h>                            /* shmdt, shmctl */

#define MAX_MSG_SIZE (512)

char main_strbuff[MAX_MSG_SIZE];

static void term_handler(int signo) {
    syslog(LOG_CRIT, "term_handler: rcv %d.", signo);
    closelog();
    exit(0);

} /* term_handler */

static void err_handle(int has_errno, int my_errno, const char *my_args,
                        va_list my_list) {
    char my_string[MAX_MSG_SIZE];           /* Buffer di diagnostica */

    vsnprintf(my_string, MAX_MSG_SIZE, my_args, my_list);
    if(has_errno) {
        snprintf(my_string + strlen(my_string), MAX_MSG_SIZE - strlen(my_string), ":%s", strerror_r(my_errno, main_strbuff, MAX_MSG_SIZE));
    }
    strcat(my_string, "\n");
    fflush(stdout);
    fputs(my_string, stderr);
    fflush(NULL);

}

static void err_exit(const char *my_args, ...) {
    va_list args_list;

    va_start(args_list, my_args);
    err_handle(0, 0, my_args, args_list);
    va_end(args_list);

    exit(1);

}

static void err_print(const char *my_args, ...) {
    va_list args_list;

    va_start(args_list, my_args);
    err_handle(0, 0, my_args, args_list);
    va_end(args_list);
}

static void daemon_me(char *my_name) {
    struct rlimit file_lim;
    pid_t new_pid;          
    struct sigaction sig_act;
    int i;                  
    int f0, f1, f2;         
    int my_mask;            

    umask(0);

    sig_act.sa_handler = SIG_IGN;
    sigemptyset(&sig_act.sa_mask);
    sig_act.sa_flags = 0;
    if(sigaction(SIGHUP, &sig_act, NULL) < 0) {
        syslog(LOG_CRIT, "Impossibile ignorare SIGHUP");
        exit(1);
    }

    sig_act.sa_handler = term_handler;
    sigemptyset(&sig_act.sa_mask);
    sig_act.sa_flags = SA_INTERRUPT;
    if(sigaction(SIGTERM, &sig_act, NULL) < 0) {
        /* Errore di sistema */
        syslog(LOG_CRIT, "Impossibile configurare SIGTERM");
        exit(1);
    }

    if((new_pid = fork()) < 0) {
        err_exit("%s: Errore sulla fork", my_name);
    }
    else if(new_pid != 0) {
        exit(0);
    }

    setsid(); 

    if((new_pid = fork()) < 0) {
        err_exit("%s: Errore sulla fork", my_name);
    }
    else if(new_pid != 0) {
        exit(0);
    }
}

int main(int argc, char **argv) {
    daemon_me("programNAME");

    syslog(LOG_CRIT, "Success!");
    while(1);
}

systemd service keep restarting itself. Launch it manually and it will work.

EDIT2: OS info /etc/issue: Linux COM-Blade-0 4.19.0-25-amd64 #1 SMP Debian 4.19.289-1 (2023-07-24) x86_64 GNU/Linux

EDIT3: I tried to comment from if(chdir("/") < 0) to the end of function but nothing changed.

EDIT 4: new example the SIGTERM handling. while lunching with systemd it prints: term_handler: rcv 15 which is SIGTERM


Solution

  • With a Type=forking service, systemd treats the initial process exiting as an indication that startup is done and the service is now ready – whatever is left becomes the main service process.

    At this point, the service is 'active', therefore if that process then exits (even if it's part of double-forking), systemd takes that as an indication of the entire service stopping.

    Solution 1: Do not use double-fork in systemd. You literally don't need it. When your process is run from a service manager, it's a daemon from the very beginning – it's already in "background"; there is no tty to detach from; there is no session to setsid() away from; there are no FDs to close – double-forking or daemonizing serves no purpose here.

    So you can just #if 0 all of the code and switch the .service unit to Type=simple.

    (Ideally though you should use Type=notify and call sd_notify(0, "READY=1") from libsystemd when the daemon is, in fact, ready. This is still the same as Type=simple in that it needs no forking, but has the advantage of more accurate monitoring, needing only minimal changes. Later, you could even add some sd_notify(0, "STATUS=Brewing coffee (%d done)", progress) and such...)

    Solution 2: Create a pipe before forking, then have the parent process wait before exiting until the grandchild sends it a 'ready' indication over the pipe. This is the traditional method and will improve the service's behavior for all service managers (including sysv init), not just systemd.