Simple Linux Daemon Tutorial

Simple Linux Daemon Tutorial

2018, Jan 05    

As you might know daemons are just processes that lurk in the background and do some kind of work when needed without the user explicitly knowing, that they exists. In this post I will show you how to write a simple daemon which executes who command regularly, logs it’s output to a specific file using syslog, has a regular logrotate and is automatically started upon system boot.

As daemons are just some programs we can write them many programming languages, most linux users prefer BASH or C. In this tutorial I will use C.

The typical stucture of a daemon program is split in two parts, the first is the initial setup (forking processes, detaching from parent etc). And the second is the actual functionality of the program.

So the first thing we need to do is detach this process from the parent so that if, the shell that ran the program is closed the daemon continues to live on. You can do it manually by using forks but there is actually a daemon function which does this the proper way so we need to just use it. The other thing that is a good practice is to save the PID of the daemon to some file. We can make the program take command line argument which will be the path to the file where we want to write the PID of the daemon. The setup part of the our example daemon looks like this.

// example.c
int status = daemon(0, 0);
if (status == -1)
{
    exit(5);
}

char* path_to_pid_file;
if (argc == 2)
{
    path_to_pid_file = argv[1];
    FILE* fd = fopen(path_to_pid_file, "w");
    if (fd == NULL)
    {
        exit(6);
    }
    fprintf(fd, "%d\n", getpid());
    fclose(fd);
}

The second part of the daemon is where the actual logic of it resides, most deamons comunicate with other processes and this is done by sockets but in our case we will just log the output of the who command every 5 sec.

// example.c
FILE* pipeHandle;
const char* command = "who";
char data[1024];

while (1)
{
    pipeHandle = popen(command, "r");
    while (fgets(data, 1024, pipeHandle) != NULL)
    {
        syslog(LOG_MAKEPRI(LOG_LOCAL7, LOG_INFO), "%s", data);
    }

    pclose(pipeHandle);
    sleep(5);
}

As you can see we just get the output of who and log it using the syslog utility. In our case we log to the Local7 facility with priority Info. But in order for our logging to work we need to add a configuration to /etc/rsyslog.conf. The line is local7.info /var/log/local7.info. This tells the syslog utility to log the specified facility and priority to local7.info file, which we have created.

After doing this we have a daemon which logs its output regularly. Since daemons tend to be long running we need to use the logrotate system tool. Logrotate is a utility that compresses/removes old logs so that your machine does not run out of disk space. It is usually ran as a daily cronjob.

To make this working we need to add a logrotate configuration file for our daemon. The place to do so is /etc/logrotate.d/my-daemon-logrotate-config-file

#my-daemon-logrotate-config-file
/var/log/local7.info {
	rotate 3
	daily
	missingok
	notifempty
	delaycompress
	compress
	postrotate
		invoke-rc.d rsyslog rotate > /dev/null
	endscript
}

You can read about logrotat in the man page but the gist of the given cofinguration is that we will keep 3 logs, the rotation will be daily and we will compress the logs.

The last thing we need to do is to make an init script. This tutorial is for SysV init style systems. We need to place a shell script in /etc/init.d. The script should support command line arguments start/restart/stop, usually there are a few others but we’ll keep it simple.

if [ "$1" == "start" ]
then
	/usr/bin/exampled /run/exampled.pid
elif [ "$1" == "restart" ]
then
	pkill exampled
	/usr/bin/exampled /run/exampled.pid
elif [ "$1" == "stop" ]
then
	pkill exampled
fi

The script above assumes that you’ve compiled the daemon program with name exampled and placed it int /usr/bin

After we have the init scrip ready there is one last thing we need to do. And that is to make the script run with start argument when the system boots. To do this add symlink in /etc/rc5.d with name S99example which points to the init script we’ve just crated. With this setup, when the system boots on run-level 5 it will execute the init scrip with start argument.

After doing this we have a functional linux daemon, which logs and logrotates it’s messages and is automatically started upon boot. The complete code can be found at my github.