Anatomy of a typical Unix-like/Linux daemon

25 Apr 2020 - tsp
Last update 06 Jul 2020
Reading time 7 mins

This short blog post should provide a short introduction into the anatomy of the typical Unix or Linux daemon. It describes the typical steps taken by a daemon to demonize itself.

What is a daemon process anyways?

A daemon process is a process that doesn’t directly interact with a terminal or graphical user interface, runs in the background and provides some service. Typical examples are webservers, mail servers, chronological job schedulers (cron), your audio server (like jack, etc.), the syslog server, etc. These processes normally expose a service and one or more methods of interaction. They are of course different to batch jobs that often also run inside demonized containers.

Which steps does a daemon undertake?

Alternative flow

An alternative flow might substitute the first fork and the call to the setsid function as well as closing stdint as well as stdout and stderr pipes by calling the daemon(3) function. This function allows one to move into background, detach from the controlling terminal and optionally do a chdir into the root directory / and optionally redirection all standard input and output pipes to /dev/null. The following example won’t use that approach.

How to provide logging from inside a daemon process

There are a few different approaches one might take:

It’s a good idea to provide a way to run the process for debugging purposes in foreground (and of course also allow termination using the SIGINT signal - that’s the signal that gets transmitted when one uses CTRL+C on the controlling terminal). This allows easy debugging under many circumstances during development and manual deployment scenarios.

A basic skeleton

With all that in mind we now can develop a simple basic daemon (the code is provided as a GitHub GIST):

/*
Compilation: clang -o basicdaemon -Wall -ansi -std=c99 -Werror -pedantic ./basicdaemon.c -lutil
*/
#include <unistd.h>
#include <libutil.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
/* Used for perror and (f)printf: */
#include <stdio.h>
/*
Currently we employ a simple signal handelr that
sets a flag that will get checked periodically by
the daemon. If SIGTERM is received we will terminate
our wait loop.
*/
static int termSignaled = 0;
static void signalHandlerSIGTERM(int sigio) {
termSignaled = 1;
}
/* Main entry point */
int main(int argc, char* argv[]) {
struct pidfh *pidFileHandle;
struct sigaction sAction;
pid_t otherDaemonPid;
pid_t pidChildProcess;
pid_t pidLeader;
int fileNull;
/*
Note that we start with fork. Maybe one wants to
do some CLI processing or reading of configuration
files before that to allwo modification of startup
behaviour, configuration of PIDs and GIDs to be
used, etc. These will be configured as static constants
in this example.
This code would belong here ...
*/
/* open pidfile */
pidFileHandle = pidfile_open(NULL, 0600, &otherDaemonPid);
if(pidFileHandle == NULL) {
if(errno == EEXIST) {
fprintf(stderr, "Daemon already running, pid: %jd.", (intmax_t)otherDaemonPid);
return -1;
}
perror("Failed to open PID-file");
return -1;
}
/* Now its time for the first fork (moving to background) */
pidChildProcess = fork();
if(pidChildProcess < 0) {
/* The fork failed */
perror("Forking failed\n");
return -1;
} else if(pidChildProcess > 0) {
/* We are the parent. We'll terminate silently */
return 0;
}
#ifdef DEBUG
printf("%s:%u Moved to background\n", __FILE__, __LINE__);
#endif
/*
At this point we are the child that has moved
to the background but is still attached to
the original process group.
Now detach from the session ...
*/
pidLeader = setsid();
if(pidLeader < 0) {
perror("Failed to become session leader\n");
/*
This will happen if the calling
process is already a process group
leader or the process group ID of
another process other than the
calling process matches the process ID
of the calling process.
*/
return -1;
}
#ifdef DEBUG
/*
Note that debug output still works and shows
up in the original terminal since we inherited
the stdin and stdout pipes from our parents ...
*/
printf("%s:%u Moved to new process group\n", __FILE__, __LINE__);
#endif
/*
Now we will ensure detachment from the previous
process leader by forking one last time ...
*/
pidChildProcess = fork();
if(pidChildProcess < 0) {
perror("Forking failed\n");
return -1;
} else if(pidChildProcess > 0) {
/* We are the parent and will terminate silently again */
return 0;
}
/*
Register signal handler for SIGTERM
*/
sAction.sa_sigaction = NULL;
sAction.sa_handler = &signalHandlerSIGTERM;
sAction.sa_flags = SA_RESTART; /* Restart any interrupted operations like file I/O */
sigfillset(&sAction.sa_mask); /* Mask all signals during our signal handler */
if(sigaction(SIGTERM, &sAction, NULL) < 0) {
/* We failed to register our handler ... */
perror("Failed to register SIGTERM handler\n");
pidfile_close(pidFileHandle);
return -1;
}
/*
Write our PID into the PID-file
*/
pidfile_write(pidFileHandle);
/*
At this point we are the child process that
has moved into background and has detached from
the original process group
*/
#ifdef DEBUG
/* Still works, we've inherited stdin, stdout and stderr ... */
printf("%s:%u Greetings from background detached process\n", __FILE__, __LINE__);
#endif
/*
Now we should
* close our standard I/O handles of our parent process
(or do that after the following operations just in
case we want to provide direct error output)
* Do some mandatory access control manipulation if required
* Jail if we want to
* setgid, setuid
* chroot
*/
/* close stdin,stdout and stderr. Now printf won't work any more ... */
close(STDOUT_FILENO);
close(STDERR_FILENO);
close(STDIN_FILENO);
/* Open /dev/null as target for stdout and stderr as well as source for stdin */
fileNull = open("/dev/null", 0);
dup2(fileNull, STDOUT_FILENO);
dup2(fileNull, STDERR_FILENO);
dup2(fileNull, STDIN_FILENO);
/*
Main daemon code goes here ...
*/
/* Now remove our PID-file */
pidfile_remove(pidFileHandle);
/* And terminate process ... */
return 0;
}
view raw basicdaemon.c hosted with ❤ by GitHub

This article is tagged:


Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)

This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support