C Sample daemon in C

The handbook has so much good information. After a few minutes of reading, I understood enough to create a bare bones do-nothing daemon. Enough to install it and either start / stop it using the service command and a ridiculously short rc.d script.

First, the rc.d script that goes under /usr/local/etc/rc.d/ and is named 'placebo'.

Code:
#!/bin/sh
# PROVIDE: placebo
# REQUIRE: DAEMON
# BEFORE:  LOGIN
# KEYWORD:
. /etc/rc.subr
name=placebo
rcvar=placebo_enable
command="/usr/local/bin/${name}"
pidfile="/var/run/${name}.pid"
load_rc_config $name
run_rc_command "$1"
 
Here's the C code for the actual daemon. All it does is fork. The child then goes to sleep waiting for a signal. This is just enough to give the rc.d script something to start and stop. File name is 'placebo.c':

Code:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <libutil.h>

#define _countof(arg) ( (sizeof arg) / (sizeof arg[0]) )

static void control(int sig)
{
    static const char *labels[] =
    {
        "-", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", "SIGPIPE", "SIGALRM", "SIGTERM"
    };
    if (sig > 0 && sig < (int)_countof(labels))
        fprintf(stderr, "Something bad %s happened.\n", labels[sig]);
    else
        fprintf(stderr, "Something bad %d happened.\n", sig);
    exit(sig);
}

int main()
{
    struct pidfh *pfh = NULL;
    pid_t otherpid = 0;
   
    signal(SIGABRT, &control);
    signal(SIGALRM, &control);
    signal(SIGILL, &control);
    signal(SIGFPE, &control);
    signal(SIGHUP, &control);
    signal(SIGINFO, &control);
    signal(SIGINT, &control);

    pfh = pidfile_open("/var/run/placebo.pid", 0600, &otherpid);
    if (pfh == NULL)
    {
        if (errno == EEXIST)
        {
            fprintf(stderr, "Daemon already running, pid: %d.", otherpid);
            exit(EXIT_FAILURE);
        }
        fputs("Cannot open or create pidfile", stderr);
    }

    if (daemon(0, 0) == -1)
    {
        fputs("daemon() fails.", stderr);
        pidfile_remove(pfh);
        exit(EXIT_FAILURE);
    }

    pidfile_write(pfh);
   
    while (1)
    {
        struct timespec snooze, ignore;
        snooze.tv_nsec = 10000000L;
        snooze.tv_sec = 0;
        ignore = snooze;
        nanosleep(&snooze, &ignore);
    }
    pidfile_remove(pfh);
    return 0;
}
 
Not critical, but here's the CMakeLists.txt used to generate the makefile:

Code:
cmake_minimum_required(VERSION 3.0)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -ffast-math -mavx -O3")

project(placebo)
link_directories(/usr/lib /usr/local/lib)
add_executable(placebo placebo.c)
target_include_directories(placebo SYSTEM PUBLIC /usr/include /usr/local/include )
target_link_libraries(placebo util)
install(TARGETS placebo DESTINATION /usr/local/bin)
install(PROGRAMS placebo DESTINATION /usr/local/etc/rc.d)

Updated to link to and use the pidfile(3) functions in libutil.
 
Updated the cmake script to generate an 'install' target. The standard cmake idiom works:

Code:
# mkdir build
# cd build
# cmake ..
# make
# make install

At this point, the service can be run. Without touching /etc/rc.conf I can use the 'onestart' tail to manually start/stop.

Code:
# service placebo onestart
# top (look for 'placebo')
# service placebo onestop

If I add the following line to /etc/rc.conf - the service will start automatically at boot.

Code:
placebo_enable="YES"

After reboot, I must use 'start' or 'stop' instead of 'onestart' or 'onestop'.

Code:
# top (look for 'placebo')
# service placebo stop
# top ('placebo' should be gone)
 
There are lots of articles on the net about how to write a good daemon. I didn't look at your code in detail, but forking is a good first step. I think there is a lot of other steps, like forking twice (don't remember why, but there are reasons), disconnecting from the controlling terminal, changing the working directory to somewhere else, dealing with GID/SID bits, and all that junk. I wrote that once as a routine in Python, and it ends up being a hundred lines or more.
 
The best approach is to use what the BSD developers provided:
  • for programmers: daemon(3)(): /usr/src/lib/libc/gen/daemon.c
  • for shell script writers: daemon(8)(): /usr/src/usr.sbin/daemon/daemon.c
Only System V derivatives still need to fork twice, so portability requires extra work.

Beware Java programmers, who often know nothing of system calls, and wonder why their "daemons" die gratuitously (from stray signals attributable to inherited controlling tty, process group, or session).
 
I read the documentation on the nosh init system - which has some very good information on comparing init systems.

I brought up nosh here - to the sound of crickets.

It claims that the service(8) command is flawed. I wanted to test this out myself. Does a service - when (re)started by the service(8) command - inherit the interactive terminal's environment?

To test this, I added code to my placebo service above to dump out the environment strings in the process. I did this in two places - before and after the call to daemon(3). The former could be sent to stderr. The latter had to be written to a file.

I then ran the service command via sudo(8) from an xterm session.

Code:
static void dumpenv(FILE *file)
{
    extern char **environ;
    for (unsigned i = 0; environ[i] != NULL; i++)
    {
        fputs(environ[i], file);
        fputs("\n", file);
    }
}

The output - both to stderr - and to the log file from the fully daemonized process ...

Code:
PATH=/sbin:/bin:/usr/sbin:/usr/bin
PWD=/
HOME=/
RC_PID=xxxx

The PID varied with each run. In contrast, running 'set | wc' from sh(1) returned 32 lines of environment variables.

To summarize - I see no such leak of information - at least not today on 12.0-RELEASE.
 
Back
Top