Subject: Fast start-stop-daemon in C

From: Marek Michalkiewicz <[email-masked]>
Date: Thu, 6 Jun 1996 05:24:18 +0200 (MET DST)

Some time ago I wrote a faster C replacement for the start-stop-daemon
perl script.  I use it for some time now (the most recent changes were
just a nicer help screen; the code is quite stable).

This makes the system boot faster (especially on low end machines),
and important system startup scripts no longer depend on another big
package (perl).  Maybe in the future we can get to the point where
a minimal system will work without perl installed at all (packages
which need it in {pre,post}{inst,rm} scripts would depend on perl).

The only problem known so far to me is that I have to reinstall this
program after every dpkg upgrade which overwrites it with that nice
slooow perl script :-).

Just compile this program and drop the binary in /usr/sbin instead
of the original /usr/sbin/start-stop-daemon perl script (make a copy
of it first, just in case).  See below for source code.  I placed it
in the public domain, but if it has to be GPL-ed to be included in
dpkg, just tell me.  Including it in dpkg would close Bug#1670.

I am posting it here so that it can be tested by more people than
just me.  Bugs are unlikely though.

Have fun,

Marek

/*
 * A rewrite of the original Debian's start-stop-daemon Perl script
 * in C (faster - it is executed many times during system startup).
 *
 * Written by Marek Michalkiewicz <[email-masked]>,
 * public domain.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <signal.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <getopt.h>
#include <pwd.h>

#define VERSION "version 0.3, 1996-06-05"

static int testmode = 0;
static int quietmode = 0;
static int exitnodo = 1;
static int start = 0;
static int stop = 0;
static int signal_nr = 15;
static int user_id = -1;
static const char *userspec = NULL;
static const char *cmdname = NULL;
static char *execname = NULL;
static char *startas = NULL;
static const char *pidfile = NULL;
static const char *progname = "";

static struct stat exec_stat;

struct pid_list {
	struct pid_list *next;
	int pid;
};

static struct pid_list *found = NULL;
static struct pid_list *killed = NULL;

static void *xmalloc(int size);
static void push(struct pid_list **list, int pid);
static void do_help(void);
static void parse_options(int argc, char * const *argv);
static int pid_is_exec(int pid, const struct stat *esb);
static int pid_is_user(int pid, int uid);
static int pid_is_cmd(int pid, const char *name);
static void check(int pid);
static void do_pidfile(const char *name);
static void do_procfs(void);
static void do_stop(void);

#ifdef __GNUC__
static void fatal(const char *format, ...)
	__attribute__((noreturn, format(printf, 1, 2)));
static void badusage(const char *msg)
	__attribute__((noreturn));
#else
static void fatal(const char *format, ...);
static void badusage(const char *msg);
#endif

static void
fatal(const char *format, ...)
{
	va_list arglist;

	fprintf(stderr, "%s: ", progname);
	va_start(arglist, format);
	vfprintf(stderr, format, arglist);
	va_end(arglist);
	putc('\n', stderr);
	exit(2);
}

static void *
xmalloc(int size)
{
	void *ptr;

	ptr = malloc(size);
	if (ptr)
		return ptr;
	fatal("malloc(%d) failed", size);
}

static void
push(struct pid_list **list, int pid)
{
	struct pid_list *p;

	p = xmalloc(sizeof(*p));
	p->next = *list;
	p->pid = pid;
	*list = p;
}

static void
do_help(void)
{
	printf("\
start-stop-daemon for Debian Linux - small and fast C version written by\n\
Marek Michalkiewicz <[email-masked]>, public domain.\n"
VERSION "\n\
\n\
Usage:
    start-stop-daemon -S|--start options ... -- arguments ...\n\
    start-stop-daemon -K|--stop options ...\n\
    start-stop-daemon -H|--help\n\
    start-stop-daemon -V|--version\n\
\n\
Options (at least one of --exec|--pidfile|--user is required):
    -x|--exec <executable>       program to start/check if it is running\n\
    -p|--pidfile <pid-file>      pid file to check\n\
    -u|--user <username>|<uid>   stop this user's processes\n\
    -n|--name <process-name>     stop processes with this name\n\
    -s|--signal <signal>         signal to send (default 15)\n\
    -a|--startas <pathname>      program to start (default <executable>)\n\
    -t|--test                    test mode, don't do anything\n\
    -o|--oknodo                  exit status 0 (not 1) if nothing done\n\
    -q|--quiet  |  -v, --verbose\n\
\n\
Exit status:  0 = done  1 = nothing done (=> 0 if --oknodo)  2 = trouble\n");
}

static void
badusage(const char *msg)
{
	if (msg && *msg)
		fprintf(stderr, "%s: %s\n", progname, msg);
	fprintf(stderr, "Try `%s --help' for more information.\n", progname);
	exit(2);
}

static void
parse_options(int argc, char * const *argv)
{
	static struct option longopts[] = {
		{ "help",	0, NULL, 'H'},
		{ "stop",	0, NULL, 'K'},
		{ "start",	0, NULL, 'S'},
		{ "version",	0, NULL, 'V'},
		{ "startas",	1, NULL, 'a'},
		{ "name",	1, NULL, 'n'},
		{ "oknodo",	0, NULL, 'o'},
		{ "pidfile",	1, NULL, 'p'},
		{ "quiet",	0, NULL, 'q'},
		{ "signal",	1, NULL, 's'},
		{ "test",	0, NULL, 't'},
		{ "user",	1, NULL, 'u'},
		{ "verbose",	0, NULL, 'v'},
		{ "exec",	1, NULL, 'x'},
		{ NULL,		0, NULL, 0}
	};
	int c;

	for (;;) {
		c = getopt_long(argc, argv, "HKSVa:n:op:qs:tu:vx:",
				longopts, (int *) 0);
		if (c == -1)
			break;
		switch (c) {
		case 'H':  /* --help */
			do_help();
			exit(0);
		case 'K':  /* --stop */
			stop = 1;
			break;
		case 'S':  /* --start */
			start = 1;
			break;
		case 'V':  /* --version */
			printf("start-stop-daemon " VERSION "\n");
			exit(0);
		case 'a':  /* --startas <pathname> */
			startas = optarg;
			break;
		case 'n':  /* --name <process-name> */
			cmdname = optarg;
			break;
		case 'o':  /* --oknodo */
			exitnodo = 0;
			break;
		case 'p':  /* --pidfile <pid-file> */
			pidfile = optarg;
			break;
		case 'q':  /* --quiet */
			quietmode = 1;
			break;
		case 's':  /* --signal <signal> */
			if (sscanf(optarg, "%d", &signal_nr) != 1)
				badusage("--signal takes a numeric argument");
			break;
		case 't':  /* --test */
			testmode = 1;
			break;
		case 'u':  /* --user <username>|<uid> */
			userspec = optarg;
			break;
		case 'v':  /* --verbose */
			quietmode = -1;
			break;
		case 'x':  /* --exec <executable> */
			execname = optarg;
			break;
		default:
			badusage("");  /* message printed by getopt */
		}
	}

	if (start == stop)
		badusage("need one of --start or --stop");

	if (!execname && !pidfile && !userspec)
		badusage("need at least one of --exec, --pidfile or --user");

	if (!startas)
		startas = execname;

	if (start && !startas)
		badusage("--start needs --exec or --startas");
}

static int
pid_is_exec(int pid, const struct stat *esb)
{
	struct stat sb;
	char buf[32];

	sprintf(buf, "/proc/%d/exe", pid);
	if (stat(buf, &sb) != 0)
		return 0;
	return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino);
}

static int
pid_is_user(int pid, int uid)
{
	struct stat sb;
	char buf[32];

	sprintf(buf, "/proc/%d", pid);
	if (stat(buf, &sb) != 0)
		return 0;
	return (sb.st_uid == uid);
}

static int
pid_is_cmd(int pid, const char *name)
{
	char buf[32];
	FILE *f;
	int c;

	sprintf(buf, "/proc/%d/stat", pid);
	f = fopen(buf, "r");
	if (!f)
		return 0;
	while ((c = getc(f)) != EOF && c != '(')
		;
	if (c != '(') {
		fclose(f);
		return 0;
	}
	/* this hopefully handles command names containing ')' */
	while ((c = getc(f)) != EOF && c == *name)
		name++;
	fclose(f);
	return (c == ')' && *name == '\0');
}

static void
check(int pid)
{
	if (execname && !pid_is_exec(pid, &exec_stat))
		return;
	if (userspec && !pid_is_user(pid, user_id))
		return;
	if (cmdname && !pid_is_cmd(pid, cmdname))
		return;
	push(&found, pid);
}

static void
do_pidfile(const char *name)
{
	FILE *f;
	int pid;

	f = fopen(name, "r");
	if (f) {
		if (fscanf(f, "%d", &pid) == 1)
			check(pid);
		fclose(f);
	}
}

static void
do_procfs(void)
{
	DIR *procdir;
	struct dirent *entry;
	int foundany, pid;

	procdir = opendir("/proc");
	if (!procdir)
		fatal("opendir /proc: %s", strerror(errno));

	foundany = 0;
	while ((entry = readdir(procdir)) != NULL) {
		if (sscanf(entry->d_name, "%d", &pid) != 1)
			continue;
		foundany++;
		check(pid);
	}
	closedir(procdir);
	if (!foundany)
		fatal("nothing in /proc - not mounted?");
}

static void
do_stop(void)
{
	char what[1024];
	struct pid_list *p;

	if (cmdname)
		strcpy(what, cmdname);
	else if (execname)
		strcpy(what, execname);
	else if (pidfile)
		sprintf(what, "process in pidfile `%s'", pidfile);
	else if (userspec)
		sprintf(what, "process(es) owned by `%s'", userspec);
	else
		fatal("internal error, please report");

	if (!found) {
		if (quietmode <= 0)
			printf("no %s found; none killed.\n", what);
		exit(exitnodo);
	}
	for (p = found; p; p = p->next) {
		if (testmode)
			printf("would send signal %d to %d.\n",
			       signal_nr, p->pid);
		else if (kill(p->pid, signal_nr) == 0)
			push(&killed, p->pid);
		else
			printf("%s: warning: failed to kill %d: %s\n",
			       progname, p->pid, strerror(errno));
	}
	if (quietmode < 0 && killed) {
		printf("stopped %s (pid", what);
		for (p = killed; p; p = p->next)
			printf(" %d", p->pid);
		printf(").\n");
	}
}

int
main(int argc, char **argv)
{
	progname = argv[0];

	parse_options(argc, argv);
	argc -= optind;
	argv += optind;

	if (execname && stat(execname, &exec_stat))
		fatal("stat %s: %s", execname, strerror(errno));

	if (userspec && sscanf(userspec, "%d", &user_id) != 1) {
		struct passwd *pw;

		pw = getpwnam(userspec);
		if (!pw)
			fatal("user `%s' not found\n", userspec);

		user_id = pw->pw_uid;
	}

	if (pidfile)
		do_pidfile(pidfile);
	else
		do_procfs();

	if (stop) {
		do_stop();
		exit(0);
	}

	if (found) {
		if (quietmode <= 0)
			printf("%s already running.\n", execname);
		exit(exitnodo);
	}
	if (testmode) {
		printf("would start %s ", startas);
		while (argc-- > 0)
			printf("%s ", *argv++);
		printf(".\n");
		exit(0);
	}
	if (quietmode < 0)
		printf("starting %s ...\n", startas);
	*--argv = startas;
	execv(startas, argv);
	fatal("unable to start %s: %s", startas, strerror(errno));
}

---

From: Miquel van Smoorenburg <[email-masked]>
Date: Thu, 6 Jun 1996 12:55:04 +0200 (MET DST)

> just a nicer help screen; the code is quite stable).

I still had it somewhere, I've saved the new version, thanks. I think
we're going to move start-stop-daemon to sysvinit, after 1.1.

Mike.
-- 
  Miquel van    | Cistron Internet Services   --    Alphen aan den Rijn.
  Smoorenburg,  | mailto:[email-masked]          http://www.cistron.nl/
[email-masked] | Tel: +31-172-419445 (Voice) 430979 (Fax) 442580 (Data)

---

From: Ian Jackson <[email-masked]>
Date: Mon, 10 Jun 96 01:59 BST

> This makes the system boot faster (especially on low end machines),

I'd be very happy to include this in the dpkg source tree, but after
the 1.1 release.  Can you remind me then ?

> which need it in {pre,post}{inst,rm} scripts would depend on perl).

Hmm, this would be quite a big policy change (and would require a lot
of Pre-Depends).  Remember that install-info and many other programs
are written in Perl.

> program after every dpkg upgrade which overwrites it with that nice

Try using dpkg-divert.

> dpkg, just tell me.  Including it in dpkg would close Bug#1670.

When I include it in dpkg I shall probably GPL it, if that's OK with
you.

> just me.  Bugs are unlikely though.

One thing you could do that would make it much nicer for me to include
in the dpkg source tree would be to have it use
dpkg/dselect/dpkg-deb/dpkg-split/&c's standard common argument
parsing, error handling, malloc wrapper, &c scheme.  You'll find that
looking in the dpkg source tree's lib and include directories, and
using dpkg-split (say) as an example, ought to be quite enough
information anyway.

If I include your code it'll be on those terms anyway, and it'll be
much faster if I don't have to do the work myself :-).

Thanks,
Ian.

---

From: Marek Michalkiewicz <[email-masked]>
Date: Tue, 11 Jun 1996 03:17:01 +0200 (MET DST)

> the 1.1 release.  Can you remind me then ?

OK.

> dpkg/dselect/dpkg-deb/dpkg-split/&c's standard common argument

We'll see.  Miquel said something about moving start-stop-daemon
to the sysvinit package (looks reasonable to me, as it is run from
the init scripts).  In this case it would be a standalone program
as it is now.

Marek