[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Fast start-stop-daemon in C



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 <marekm@i17linuxb.ists.pwr.wroc.pl>,
 * 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 <marekm@i17linuxb.ists.pwr.wroc.pl>, 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));
}


Reply to: