27 Nov 2019

Einen Unix-Daemon programmieren

Wenn man einen einfachen Unix-Daemon programmieren will, kann man im Internet viele Anleitungen finden. Manche sind einfach nur verschwendete Lebenszeit.

Dazu gehört auch dieses Beispiel im bekannten Offenen Buch: Nett gemeint, die Erklärungen sind ok, der Code aber suboptimal. Howto von Rheinwerk

Deshalb hier eine Version, welche sich mit meinen langjährigen Erfahrungen deckt, und noch von den Tipps von Microhowto untermauert wird. Stark davon abweichende Versionen des Daemonizing-Codes sind leider verschwendete Lebenszeit.

Kompilieren und Test:

fehmarn ~ $ gcc -o daemon daemon.c
fehmarn ~ $ ./daemon 
fehmarn ~ $ ps ax|grep daemon
[...]
14487 ?        S      0:00 ./daemon
[...]
fehmarn ~ $ su
[...]
fehmarn ~ # tail -5 /var/log/messages
Nov 27 20:12:40 fehmarn su[14476]: + /dev/pts/0 chrissie:root
Nov 27 20:12:40 fehmarn su[14476]: pam_unix(su:session): session opened for user root by ...
Nov 27 20:12:59 fehmarn daemon[14487]: Daemon gestartet ...
Nov 27 20:13:14 fehmarn daemon[14487]: Daemon lief 15 Sekunden
Nov 27 20:13:14 fehmarn daemon[14487]: Daemon hat sich beendet
fehmarn ~ # 

Code unten kopieren oder einfach hier downloaden daemon.c

/* daemon.c 
 * corrections by chrissie 11/2019
 */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>

/* 
 * microhowtos daemonizing code, 
 * der sich auch mit meinen Erfahrungen deckt 
 */
static void start_daemon () {
       // Fork, allowing the parent process to terminate.
    pid_t pid = fork();
    if (pid == -1) {
        fprintf(stderr, "failed to fork while daemonising (errno=%d)",errno);
        exit(errno);
    } else if (pid != 0) {
        exit(0);
    }

    // Start a new session for the daemon.
    if (setsid()==-1) {
        fprintf(stderr, "failed to become a session leader (errno=%d)",errno);
        exit(errno);
    }

    // Fork again, allowing the parent process to terminate.
    signal(SIGHUP,SIG_IGN);
    pid=fork();
    if (pid == -1) {
        fprintf(stderr, "failed to fork while daemonising (errno=%d)",errno);
        exit(errno);
    } else if (pid != 0) {
        exit(0);
    }

    // Set the current working directory to the root directory.
    if (chdir("/") == -1) {
        fprintf(stderr, "failed to change working directory (errno=%d)",errno);
        exit(errno);
    }

    // Set the user file creation mask to zero.
    umask(0);

    // Close then reopen standard file descriptors.
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    if (open("/dev/null",O_RDONLY) == -1) {
        fprintf(stderr, "failed to reopen stdin (errno=%d)",errno);
        exit (errno);
    }
    if (open("/dev/null",O_WRONLY) == -1) {
        fprintf(stderr, "failed to reopen stdout (errno=%d)",errno);
        exit (errno);
    }
    if (open("/dev/null",O_RDWR) == -1) {
        fprintf(stderr, "failed to reopen stderr (errno=%d)",errno);
        exit(errno);
    }
}

int main (int argc, char **argv) {
   int time = 15;
   start_daemon ();
   while (1) {
      /* Enlosschleifen: Hier sollte nun der Code für den
       * Daemon stehen, was immer er auch tun soll.
       * Bei Fehlermeldungen beispielsweise:
       * if(dies_ist_passiert)
       *    syslog(LOG_WARNING, "dies_ist_Passiert");
       * else if(das_ist_Passiert)
       *    syslog(LOG_INFO, "das_ist_Passiert");
       */
      syslog( LOG_NOTICE, "Daemon gestartet ...\n");
      sleep(time);
      syslog( LOG_NOTICE,
              "Daemon lief %d Sekunden\n", time );
      break;    /* endlosschleife verlassen, daemon beenden */
   }
   syslog( LOG_NOTICE, "Daemon hat sich beendet\n");
   closelog();
   return EXIT_SUCCESS;
}