Simple daemon example using kqueue

The below code is a simple daemon. This daemon listens the port of number 4,226. When a client connects to this, this daemon disconnects immediately. This daemon stops for SIGTERM. This daemon works on FreeBSD.

I wrote this daemon to investigate other software, but I found that this is helpful to start writing a new daemon using kqueue(2).

This code is also available in Gist.

/*
 * This is public domain.
 */
#include <sys/types.h>
#include <sys/event.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <errno.h>
#include <libgen.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

static void
die(const char *fmt, ...)
{
	va_list ap;
	int errnum;
	char buf[8192], buf2[8192];

	errnum = errno;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);
	snprintf(buf2, sizeof(buf2), "%s: %s", buf, strerror(errnum));

	fputs(buf2, stderr);
	syslog(LOG_ERR, "%s", buf2);

	exit(1);
}

#define	array_sizeof(a)	(sizeof((a)) / sizeof((a)[0]))
#define	PORT		4226

int
main(int argc, const char *argv[])
{
	struct sockaddr_storage addr;
	struct sockaddr_in *paddr;
	struct kevent changelist[2], eventlist[2];
	sigset_t set;
	uintptr_t ident;
	int fd, i, kq, level, nchanges, nev, nevents, optname, optval, sock;
	u_short flags;
	char buf[256];

	strncpy(buf, argv[0], sizeof(buf));
	openlog(basename(buf), LOG_PID, LOG_DAEMON);

	sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock == -1)
		die("cannot socket(2)");
	level = SOL_SOCKET;
	optname = SO_REUSEADDR;
	optval = 1;
	if (setsockopt(sock, level, optname, &optval, sizeof(optval)) == -1)
		die("cannot setsockopt(2)");
	paddr = (struct sockaddr_in *)&addr;
	paddr->sin_len = sizeof(*paddr);
	paddr->sin_family = AF_INET;
	paddr->sin_port = htons(PORT);
	paddr->sin_addr.s_addr = INADDR_ANY;
	if (bind(sock, (struct sockaddr *)paddr, paddr->sin_len) == -1)
		die("cannot bind(2)");
	if (listen(sock, 0) == -1)
		die("cannot listen(2)");

	if (daemon(1, 0) == -1)
		die("cannot daemon(3)");
	syslog(LOG_INFO, "started");

	kq = kqueue();
	if (kq == -1)
		die("cannot kqueue(2)");
	flags = EV_ADD | EV_ENABLE;
	EV_SET(&changelist[0], sock, EVFILT_READ, flags, 0, 0, NULL);
	EV_SET(&changelist[1], SIGTERM, EVFILT_SIGNAL, flags, 0, 0, NULL);
	nchanges = array_sizeof(changelist);
	if (kevent(kq, changelist, nchanges, NULL, 0, NULL) == -1)
		die("cannot kevent(2)");

	if (sigemptyset(&set) == -1)
		die("cannot sigemptyset(3)");
	if (sigaddset(&set, SIGTERM) == -1)
		die("cannot sigaddset(3)");
	if (sigprocmask(SIG_BLOCK, &set, NULL) == -1)
		die("cannot sigprocmask(2)");

	nevents = array_sizeof(eventlist);
	while (0 < (nev = kevent(kq, NULL, 0, eventlist, nevents, NULL)))
		for (i = 0; i < nev; i++)
			switch (eventlist[i].filter) {
			case EVFILT_READ:
				if ((fd = accept(sock, NULL, 0)) == -1)
					die("cannot accept(2)");
				syslog(LOG_INFO, "connected");
				if (close(fd) == -1)
					die("cannot close(2)");
				break;
			case EVFILT_SIGNAL:
				goto exit;
			default:
				break;
			}

exit:
	close(kq);
	close(sock);
	syslog(LOG_INFO, "exit");
	closelog();

	return (0);
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s