/*
 * economy init, for mastodon
 *
 *  Copyright (c) 2000 David Parsons. All rights reserved.
 *
 *  This software is released under a berkeley-style copyright; the
 *  details can be found in the file COPYRIGHT.
 */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <ctype.h>
#include <string.h>
#include <setjmp.h>
#include <fcntl.h>
#include <termios.h>
#include <syslog.h>

#include "init.h"

char *pgm;			/* program name, for diagnostics */
char *statusp;			/* pointer to status area in argv[0] */
char  statusl;			/* length of status area in argv[0] */
volatile char runlevel=' ';	/* current runlevel */
int sigmask;			/* pending local signals */
#define IS_HUP	0x01			/* hup */
#define	IS_CAD	0x02			/* c-a-d */
#define	IS_CLK	0x04			/* alarm call */
#define IS_USR1	0x08
#define IS_USR2	0x10
#define IS_TSTP 0x20
int children = 0;		/* how many children are active? */
int bornagain = 1;		/* cleared when init gets a tstp signal */

struct cmd * volatile commands;	/* contents of inittab */
jmp_buf goagain;		/* runlevel change point */

int orders = -1;			/* command fifo */

struct registry *registry = 0;

/*
 * message types:
 *
 *   say()  sends a message to the console
 *   log()  sends a notice to syslog (if it's running)
 *   info() sends an info message to syslog (if configured in)
 */

/*
 * send a message to the console
 */
void
say(char *fmt, ...)
{
    FILE *f;
    va_list ptr;

    if (f = fopen("/dev/console", "a")) {
	va_start(ptr, fmt);
	fprintf(f, "INIT: ");
	vfprintf(f, fmt, ptr);
	fputc('\n', f);
	va_end(ptr);
	fclose(f);
    }
} /* error */


/*
 * send a message to syslog
 */
void
log(char *fmt, ...)
{
    va_list ptr;

    va_start(ptr, fmt);
    openlog("INIT", LOG_CONS, LOG_DAEMON);
    vsyslog(LOG_NOTICE, fmt, ptr);
    closelog();
    va_end(ptr);
} /* log */


/*
 * send an info message to syslog
 */
void
info(char *fmt, ...)
{
    va_list ptr;
#if INFO
    va_start(ptr, fmt);
    openlog("INIT", LOG_CONS, LOG_DAEMON);
    vsyslog(LOG_INFO, fmt, ptr);
    closelog();
    va_end(ptr);
#endif
}


/*
 * return the name of a given signal
 */
char *
signame(int signal)
{
    static char bfr[20];

    switch (signal) {
	case SIGHUP: return "HUP";
	case SIGINT: return "INT";
	case SIGQUIT: return "QUIT";
	case SIGILL: return "ILL";
	case SIGTRAP: return "TRAP";
	case SIGABRT: return "ABRT";
	case SIGBUS: return "BUS";
	case SIGFPE: return "FPE";
	case SIGKILL: return "KILL";
	case SIGUSR1: return "USR1";
	case SIGSEGV: return "SEGV";
	case SIGUSR2: return "USR2";
	case SIGPIPE: return "PIPE";
	case SIGALRM: return "ALRM";
	case SIGTERM: return "TERM";
	case SIGSTKFLT: return "STKFLT";
	case SIGCHLD: return "CHLD";
	case SIGCONT: return "CONT";
	case SIGSTOP: return "STOP";
	case SIGTSTP: return "TSTP";
	case SIGTTIN: return "TTIN";
	case SIGTTOU: return "TTOU";
	case SIGURG: return "URG";
	case SIGXCPU: return "XCPU";
	case SIGXFSZ: return "XFSZ";
	case SIGVTALRM: return "VTALRM";
	case SIGPROF: return "PROF";
	case SIGWINCH: return "WINCH";
	case SIGIO: return "IO";
	case SIGPWR: return "PWR";
    }
    sprintf(bfr, "#%d", signal);
    return bfr;
}


/*
 * set up a status in argv[0], so ps can see what init is up to.
 */
void
status(char *fmt, ...)
{
    va_list ptr;

    if (statusp && statusl > 5) {
	memset(statusp, 0, statusl);
	sprintf(statusp, "[%c] ", runlevel);
    }

    if (fmt != 0) {
	va_start(ptr, fmt);
	if (statusp && statusl > 5)
	    vsnprintf(statusp+4, statusl-4, fmt, ptr);
#if INFO
	vfprintf(stderr, fmt, ptr);
	putc('\n', stderr);
#endif
	va_end(ptr);
    }
} /* status */


/*
 * adjust the console for sanity.
 */
void
fix_console(int fd)
{
    struct termios settings;

    tcflush(fd, TCIFLUSH);	/* just in case there's a bunch of input
				 * piled up. */
    tcgetattr(fd, &settings);

#define CTRL(x)	(x - '@')
    settings.c_cc[VINTR] = CTRL('C');	settings.c_cc[VQUIT] = CTRL('\\');
    settings.c_cc[VERASE] = 127;	settings.c_cc[VKILL] = CTRL('X');
    settings.c_cc[VEOF] = CTRL('D');	settings.c_cc[VSTART] = CTRL('S');
    settings.c_cc[VSTOP] = CTRL('Q');	settings.c_cc[VSUSP] = CTRL('Z');
    settings.c_cc[VTIME] = 0;		settings.c_cc[VMIN] = 1;

    settings.c_cflag = HUPCL|CS8|CLOCAL;
    settings.c_iflag = IGNPAR|ICRNL|IXON|IXANY;
    settings.c_oflag = OPOST|ONLCR;
    settings.c_lflag = ISIG|ICANON|ECHO|ECHOCTL|ECHOPRT|ECHOKE;

    tcsetattr(fd, TCSANOW, &settings);
} /* fix_console */


/*
 * blowaway wipes out a cmd chain
 */
void
blowaway(struct cmd* chain)
{
    if (chain) {
	if (chain->next)
	    blowaway(chain->next);
	if (chain->argv)
	    free(chain->argv);
	free(chain);
    }
} /* blowaway */


/*
 * return the bit in ->when corresponding to a runlevel
 */
int
bit_for_runlevel(char level, int strict)
{
    switch (tolower(level)) {
    case '*':	return strict ? 0 : (AT_HALT|AT_SINGLE|AT_MULTI|AT_REBOOT);
    case '@':	return 0;
    case BOOT:	return AT_BOOT;
    case '0':
    case HALT:	return AT_HALT;
    case '1':
    case SINGLE:return AT_SINGLE;
    case '2': case '3': case '4': case '5':
    case MULTI:	return AT_MULTI;
    case '6':
    case REBOOT:return AT_REBOOT;
    default:	return -1;
    }
} /* bit_for_runlevel */


/*
 * return the character code for a runlevel
 */
char
char_for_runlevel(int bits)
{
    switch (bits) {
    case AT_BOOT:
	return BOOT;
    case AT_SINGLE:
	return SINGLE;
    case AT_MULTI:
	return MULTI;
    case AT_REBOOT:
	return REBOOT;
    case AT_HALT:
	return HALT;
    }
    return SINGLE;
} /* char_for_runlevel */


/*
 * parse a single inittab line
 */
static struct {
    char *name;
    int   how;
} nametohow[] = {
    { "initdefault", DEFAULT },
    { "once", ONCE },
    { "wait", ONCE },
    { "again", AGAIN },
    { "respawn", AGAIN },
    { "ctrlaltdel", CAD },
    { "cad", CAD },
};
#define NR_HOW	(sizeof nametohow/sizeof nametohow[0])

static struct cmd*
parseline(char *text, struct cmd *chain)
{
    struct cmd * tmp = calloc(1, sizeof tmp[0]);
    int     argc;
    char ** argv = calloc(50, sizeof argv[0]);

    char level[20];
    char how[20];

    static char fmt[80] = { 0 };
    unsigned int checksum = 0;
    char *p, *q;
    int b;

    if (tmp == 0 || argv == 0) {
	say("out of memory parsing inittab");
	blowaway(chain);
	return 0;
    }

    if (fmt[0] == 0) {
	sprintf(fmt, "%%%d[^:]:%%%d[^:]:%%%d[^\n]", sizeof level,
						    sizeof how,
						    sizeof tmp->command);
    }

    if (sscanf(text, fmt, level, how, tmp->command) < 2) {
	log("malformed inittab entry (%s)", text);
	blowaway(chain);
	return 0;
    }
    for (checksum = 0, p = tmp->command; *p; ++p) {
	unsigned int carry = checksum >> (8 * (sizeof(checksum)-sizeof(char)));
	checksum ^= (unsigned char)(*p);
	checksum <<= sizeof(char)*8;
	checksum |= carry;
    }
    for (p=level; *p; ++p) {
	b = bit_for_runlevel(*p, 0);
	if (b < 0) {
	    log("unknown runlevel %c in entry (%s)", text);
	    blowaway(chain);
	    return 0;
	}
	else if (b > 0)
	    tmp->when |= b;
    }

    for (argc = 0, q = p = tmp->command; *p && (argc < 40); ) {
	while (isspace(*p))
	    ++p;
	if (*p) {
	    argv[argc] = q;
	    while (*p && !isspace(*p)) {
		if (*p == '"') {
		    ++p;
		    while (*p && *p != '"')
			*q++ = *p++;
		    if (*p)
			++p;
		}
		else 
		    *q++ = *p++;
	    }
	    if (p > q || *p) {
		if (*p) ++p;
		*q++ = 0;
	    }

	    argc++;
	}
	else {
	    argv[argc] = 0;
	    break;
	}
    }
    tmp->argv = argv;
    tmp->argc = argc;

    for (b=0; b < NR_HOW; b++)
	if (strcasecmp(how, nametohow[b].name) == 0) {
	    tmp->how = nametohow[b].how;
	    break;
	}
    if (b == NR_HOW) {
	log("unknown how field in entry (%s)", text);
	blowaway(chain);
	return 0;
    }
    tmp->status  = DEAD;
    tmp->chksum  = checksum;

    if (chain == 0)
	return tmp;
    else {
	struct cmd *tail = chain;

	while (tail->next != 0)
	    tail = tail->next;
	tail->next = tmp;
	return chain;
    }
} /* parseline */


static struct cmd*
readinittab(char *inittab)
{
    struct cmd *tab = 0;
    static char *defaultinit[] = {
	"s:initdefault:",
	"s:respawn:/bin/sh",
	0
    };
    FILE *f;

    if ( (f = fopen(INITTAB, "r")) != 0) {
	char bfr[200];
	char *p;

	status("reading %s", INITTAB);
	while (fgets(bfr, sizeof bfr, f)) {
	    strtok(bfr, "\n");
	    for (p = bfr; *p && isspace(*p); ++p)
		;
	    if (*p == '#' || *p == 0)
		continue;
	    if ( (tab = parseline(p, tab)) == 0)
		goto failsafe;
	}

	fclose(f);
    }
    else {
	int x;
	status("reading default inittab");
	log("cannot open " INITTAB);
failsafe:
	for (x=0; defaultinit[x]; x++) {
	    tab = parseline(defaultinit[x], tab);
	    if (tab == 0)
		say("default inittab format error!");
	}
    }
    return tab;
} /* readinittab */


/*
 * kill off all of the processes that init has started
 */
void
killall(int signal)
{
    struct cmd *run;

    if (children > 0)
	say("Sending processes the %s signal", signame(signal));

#if 0
    if (getpid() == 1) {
	kill(-1,signal);
	sleep(1);
    }
#endif
    for (run = commands; run; run = run->next)
	if (run->status == ALIVE)
	    kill(run->pid, signal);
    sleep(1);
} /* killall */


/*
 * ask for the runlevel of our dreams
 */
char
ask_runlevel()
{
    char bfr[80];
    char *p;
    int  bit;
    int  size;
    int  fd;
    char newlevel;

    if ((fd = open("/dev/console", O_RDWR)) < 0) {
	log("ask_runlevel cannot open /dev/console: %s", strerror(errno));
	return SINGLE;
    }
    fix_console(fd);

    while (1) {
	write(fd,"New runlevel:", 13);
	if ( (size = read(fd, bfr, sizeof bfr)) <= 0) {
	    newlevel = SINGLE;
	    break;
	}
	bfr[size] = 0;

	for (p = bfr; *p && isspace(*p); ++p)
	    ;
	if ((bit=bit_for_runlevel(*p, 1)) > 0) {
	    newlevel = char_for_runlevel(bit);
	    break;
	}
	if (*p) {
	    write(fd, "Can't switch to runlevel ", 25);
	    write(fd, bfr, sizeof bfr);
	}
    }
    close(fd);
    return newlevel;
} /* ask_runlevel */


/*
 * check for a default runlevel, otherwise ask for one
 */
char
default_runlevel()
{
    struct cmd *run;

    for (run = commands; run; run = run->next)
	if (run->how == DEFAULT)
	    return char_for_runlevel(run->when);

    return ask_runlevel();
} /* default_runlevel */


/*
 * forks off and runs a process
 */
int
run(struct cmd *p)
{
    int x;

    p->status = ALIVE;
    time( &(p->started) );
    if ( (p->pid = fork()) < 0) {
	p->status = SUSPENDED;
	p->window = p->started + RUNDELAY;
	return -1;
    }
    else if (p->pid == 0) {
#if 0
	close(0);
	close(1);
	close(2);
	open("/dev/console", O_RDWR);
	dup2(0,1);
	dup2(0,2);
#endif
	setsid();
	for (x=0; x <NSIG; x++)
	    signal(x, SIG_DFL);
	execvp(p->argv[0], p->argv);
	log("cannot run %s: %s", p->argv[0], strerror(errno));
	exit(1);
    }
    children++;
    info("pid %d born + %d sibling%s", p->pid, children-1, (children==2) ? "" : "s");
    return 0;
} /* run */


/*
 * kill off commands that are tagged
 */
void
killsome(struct cmd *list, int flags, int harder)
{
    struct cmd *run;

    status("%s tagged processes",  harder ? "kill" : "stop");

    for (run = list; run; run = run->next)
	if ( (run->status == ALIVE) && (run->flags & flags) ) {
#if INFO
	    info("kill process %s", run->argv[0]);
#endif
	    kill(run->pid, harder ? SIGKILL : SIGTERM);
	}

    if (harder)
	blowaway(list);
    else {
	sleep(1);
	killsome(list, flags, 1);
    }
} /* killsome */


/*
 * start tagged commands
 */
void
startsome(int flags)
{
    struct cmd *p;

    status("start tagged processes");

    for (p = commands; p; p = p->next)
	if (p->flags & flags) {
	    if (p->how == AGAIN) {
		p->window = time(0) + SUSPTIME;
		p->retries = 0;
		run(p);
	    }
	    p->flags &= ~flags;
	}
} /* startsome */


/*
 * reload the inittab and start or stop respawn processes that have appeared
 * or disappeared
 */
void
reload_inittab()
{
    struct cmd *reload;
    struct cmd *p, *q;
    int mask = bit_for_runlevel(runlevel,0);

    log("re-reading inittab");
    bornagain = 1;
    if ( (reload = readinittab(INITTAB)) == 0)
	return;

    status("processing changes");

    /* identify old commands
     */
    for (p = commands; p; p = p->next) {
	for (q = reload; q; q = q->next)
	    if (q->chksum == p->chksum && q->how == p->how
				       && (q->when & mask) == (p->when & mask) )
		break;
	if (q == 0)
	    p->flags |= TAG;
    }
    /* identify new commands
     */
    for (p = reload; p; p = p->next) {
	for (q = commands; q; q = q->next)
	    if (q->chksum == p->chksum
	     && q->how == p->how
	     && (q->when & mask) == (p->when & mask)) {
		p->status = q->status;
		p->pid = q->pid;
		if (p->status == SUSPENDED || p->status == DEAD)
		    p->flags |= TAG;
		break;
	    }
	if (q == 0)
	    p->flags |= TAG;
    }
    p = commands; commands = reload;

    killsome(p, TAG, 0);
    startsome(TAG);
    status(0);
} /* reload_inittab */


/*
 * blow away the registry
 */
void
unregister(pid_t client)
{
    struct registry *p, *q;

    for (p = registry; p ; p = q) {
	q = p->next;
	free(p->token);
	free(p);
    }
    registry = 0;
    kill(client, SIGUSR1);
} /* unregister */


/*
 * dump the registry to the named file
 */
void
list_registry(pid_t client, char *destination)
{
    int destf = open(destination, O_WRONLY|O_NDELAY, 0644);
    struct registry *p;

    if (destf == -1) {
	log("can't list registry to %s for client %d", destination, client);
	kill(client, SIGUSR2);
    }
    kill(client, SIGUSR1);

    for (p = registry; p; p = p->next)
	write(destf, p->token, strlen(p->token)+1);
    close(destf);
} /* list_registry */


/*
 * insert_to_registry a token
 */
int
insert_to_registry(char *token)
{
    struct registry *p;

    for (p = registry; p; p = p->next)
	if (strcasecmp(p->token, token) == 0)
	    return 1;

    if ( (p = malloc(sizeof *p)) && (p->token = strdup(token)) ) {
	p->next = registry;
	registry = p;
	return 0;
    }
    say("out of memory insert_to_registrying %s");
    return 1;
} /* insert_to_registry */


/*
 *  halt the computer and give the user the opportunity to either shut it
 *  off or restart it.
 */
void
grind_to_a_stop()
{
    char bfr[1];
    int fd;
    static char blurb[] = "\r\n"
			  "\t\t**** Okay to power off ****\r\n"
			  "\t\t*** or press [enter] to ***\r\n"
			  "\t\t*** restart this system ***\r\n"
			  "\r\n";

    reboot(0xfee1dead, 672274793, 0xCDEF0123);	/* c-a-d == reboot */

    if ((fd = open("/dev/console", O_RDWR)) != -1) {
	fix_console(fd);
	write(fd, blurb, strlen(blurb));
	sync();

	do { read(fd,bfr,1); } while (bfr[0] != '\r' && bfr[0] != '\n');
	close(fd);
    }

    reboot(0xfee1dead, 672274793, 0);		/* c-a-d == sigint */
    runlevel = BOOT;
} /* grind_to_a_stop */


/*
 * goodbye_cruel_world() attempts to reboot the machine
 */
void
goodbye_cruel_world()
{
    /* Bye! */
    reboot(0xfee1dead, 672274793, 0x01234567);
    exit(0);
} /* goodbye_cruel_world */


/*
 * open (closing first if necessary) the command fifo
 */
void
open_fifo()
{
    int retries;
    struct stat st;

    status("opening " COMMAND);

    if (orders >= 0)
	close(orders);

    for (retries = 0; retries < 10; retries++) {
	if ((orders = open(COMMAND, O_RDONLY|O_NDELAY)) >= 0) {
	    if (fstat(orders, &st) == 0 && (st.st_mode & S_IFIFO))
		return;
	    close(orders);
	    say(COMMAND " exists, but is not a named pipe");
	}
	else if (errno != ENOENT)
	    say("cannot open " COMMAND ": %s", strerror(errno));

	if (unlink(COMMAND) != 0 && errno == EROFS)
	    /* don't even bother if we can't write to the filesystem */
	    break;

	if (mknod(COMMAND, S_IFIFO|0600, 0) != 0)
	    say("cannot mknod " COMMAND ": %s", strerror(errno));
    }
} /* open_fifo */


/*
 * wait for something to happen
 */
void
wait_for_orders(int mask)
{
    struct cmd *p;
    char bfr[80];
    pid_t pid;
    char cmd;
    char arg[200];
    int size;
    fd_set readers;

    while ( (sigmask & mask) != 0) {
	if (sigmask & IS_CAD) {
	    info("reboot signal");
	    for (p = commands; p; p = p->next)
		if (p->how == CAD)
		    run(p);

	    sigmask &= ~IS_CAD;
	}
	if (sigmask & IS_CLK) {
	    info("timer went off");
	    sigmask &= ~IS_CLK;
	}
	if (sigmask & IS_HUP) {
	    sigmask &= ~IS_HUP;
	    reload_inittab();
	}
    }

    status("waiting for fifo");

    FD_ZERO(&readers);
    FD_SET(orders, &readers);

    if (select(orders+1, &readers, 0, 0, 0) > 0 && FD_ISSET(orders, &readers)) {

	memset(bfr, 0, sizeof bfr);
	memset(arg, 0, sizeof arg);

	if ( (size = read(orders, bfr, sizeof bfr)) > 0) {
	    if (bfr[size-1] == '\n')
		--size;
	    if (sscanf(bfr, "%d %c %[^\n]", &pid, &cmd, &arg) > 1) {

		if (arg[0])
		    info("command %c (%s) from pid %d", cmd, arg, pid);
		else
		    info("command %c from pid %d", cmd, pid);

		switch (cmd) {
		case GO:	/* change runlevel */
			    if (bit_for_runlevel(arg[0],1) > 0) {
				runlevel = arg[0];
				log("pid %d set runlevel to %c", pid, arg[0]);
				kill(pid, SIGUSR1);
				longjmp(goagain, cmd);
			    }
			    else
				kill(pid, SIGUSR2);
			    break;

		case REGISTER:
			    if (insert_to_registry(arg) == 0)
				kill(pid, SIGUSR1);
			    else
				kill(pid, SIGUSR2);
			    break;

		case LIST:  list_registry(pid, arg);
			    break;
			    
		case UNREGISTER:
			    unregister(pid);
			    break;

		default:    kill(pid, SIGHUP);
			    break;
		}
	    }
	}
	open_fifo();
    }
    status(0);
} /* wait_for_orders */


/*
 * start up all the respawn children for this runlevel
 */
void
respawn()
{
    struct cmd *p;

    int b = bit_for_runlevel(runlevel,0);

    for (p = commands; p; p = p->next)
	if (p->how == AGAIN && p->status == DEAD && (p->when & b) != 0) {
	    p->window = time(0) + SUSPTIME;
	    p->retries = 0;
	    run(p);
	}
} /* respawn */


/*
 * the dirty work of init
 */
void
go(char *arg)
{
    struct cmd * volatile p;
    int when;
    int timer;
    static char runenv[30];
    static char oldenv[30];
    volatile int lastrunlevel = 0;

    runlevel = BOOT;

    setjmp(goagain);

    while (1) {
	bornagain = 1;
	open_fifo();
	if (orders < 0 && runlevel == MULTI) {
	    log("cannot go into multiuser without " COMMAND);
	    runlevel = '?';
	}
	log_runlevel(runlevel, lastrunlevel);

	sprintf(runenv, "RUNLEVEL=%c", runlevel);
	putenv(runenv);
	if (lastrunlevel) {
	    sprintf(oldenv, "LASTRUNLEVEL=%c", lastrunlevel);
	    putenv(oldenv);
	}

	lastrunlevel = runlevel;

	/* kill off all the old processes
	 */
	status("kill old processes");

	killall(SIGTERM);
	for (timer = 20; (timer > 0) && (children > 0); --timer)
	    sleep(1);

	if (children > 0) {
	    killall(SIGKILL);
	    for (timer = 10; (timer > 0) && (children > 0); --timer)
		sleep(1);
	}

	/* and make certain that they are all dead
	 */
	for (p=commands; p; p = p->next)
	    if (p->status == ALIVE && kill(p->pid, 0) == 0) {
		say("process %d will not die -- trying drastic measures", p->pid);
		kill(p->pid, SIGTERM);
	    }

	/* now all the old processes are dead.  Start up all the processes for
	 * the new runlevel.
	 */
	when = bit_for_runlevel(runlevel,0);

	info("running ONCE jobs");
	for (p=commands; p; p = p->next) {
	    if (p->how != ONCE || (p->when & when) == 0)
		continue;
	    status("starting %s", p->argv[0]);
	    run(p);
	    while (p->status == ALIVE)
		wait_for_orders(IS_CAD);
	}
	info("finished with ONCE jobs");
#if INFO
	for (p=commands; p; p = p->next)
	    if (p->status == ALIVE)
		info("job [%s] is still alive", p->argv[0]);
#endif
	status(0);

	respawn();
	while (children > 0)
	    wait_for_orders(IS_CAD|IS_HUP);

	/* out of respawn children in this runlevel, progress to the next
	 * one
	 */
	info("no more children in runlevel %c", runlevel); 
	switch (runlevel) {
	case BOOT:  if (arg && bit_for_runlevel(arg[0],0) > 0)
			runlevel = arg[0];
		    else
			runlevel = default_runlevel();
		    break;
	case SINGLE:runlevel = ask_runlevel();
		    break;
	case HALT:  grind_to_a_stop();
		    break;
	case REBOOT:goodbye_cruel_world();
		    break;
	default:    runlevel = SINGLE;
		    break;
	}
	open_fifo();
    }
} /* go */


/* signal handlers for various important events */
#define Handler(b)	void c_ ## b(int sig) { sigmask |= b; }

Handler(IS_HUP)
Handler(IS_CAD)
Handler(IS_USR1)
Handler(IS_USR2)
Handler(IS_TSTP)


/*
 * catch and mark dying children
 */
void
reaper(int sig)
{
    pid_t corpse;
    int status;
    struct cmd *p;
    time_t now;

    time(&now);
    while ( (corpse = waitpid(-1, &status, WNOHANG)) > 0 ) {

	for (p = commands; p; p = p->next)
	    if (p->status == ALIVE && corpse == p->pid) {
		p->status = DEAD;
		children--;

		info("pid %d died - %d survivor%s",
				       corpse,
				     children,
				     (children==1) ? "" : "s");

		if (!bornagain)
		    break;

		if (now >= (p->started + RUNDELAY)) {
		    p->window = p->started + SUSPTIME;
		    p->retries = 0;
		}
		else if ( (++(p->retries) > RETRIES) && (now < p->window) ) {
		    log("process %s respawning too fast; disabled for %d minutes", p->argv[0], SUSPTIME/60);
		    p->status = SUSPENDED;
		    break;
		}

		if (p->how == AGAIN && (p->when & bit_for_runlevel(runlevel,0)))
		    run(p);
		break;
	    }
    }
} /* reaper */


/*
 * wake up SUSPENDED respawn children
 */
void
awaken(int sig)
{
    struct cmd *p;
    time_t now;

    time(&now);
    for (p = commands; p; p = p->next)
	if (p->how == AGAIN && p->status == SUSPENDED
			    && (p->when & bit_for_runlevel(runlevel,0))
			    && now >= p->window ) {
	    p->window = now + SUSPTIME;
	    p->retries = 0;
	    run(p);
	}
    alarm(WAKEUP_TIME);
} /* awaken */


/*
 * switch to a given runlevel based on a signal
 */
void
sigrunlevel(int sig)
{
    switch (sig) {
    case S_MULTI:  runlevel = MULTI;   break;
    case S_SINGLE: runlevel = SINGLE;  break;
    case S_HALT:   runlevel = HALT;    break;
    case S_REBOOT: runlevel = REBOOT;  break;
    default: log("sigrunlevel() from sig %s", signame(sig)); return;
    }
    log("runlevel set to %c by SIG%s", runlevel, signame(sig));
    longjmp(goagain, 1);
} /* sigrunlevel */


/*
 * stop respawning
 */
void
fall_from_grace()
{
    bornagain = 0;
    log("TSTP signal -- no longer respawning");
} /* fall_from_grace */


/*
 * set everything up
 */
void
initialize(int argc, char **argv)
{
    int x;
    struct sigaction sig;

#define	Catch(signal, f)	(sig.sa_handler = f), \
                                (sig.sa_flags=SA_RESTART), \
				sigaction(signal, &sig, 0)

    if (strlen(argv[0]) < 40 || strchr(argv[0], '@') == 0) {
	/* restart init but with a longer command line */
	argv[0] = "init @                                                                                 ";
#if DEBUG
	execv("init", argv);
#else
	execv("/sbin/init", argv);
#endif
	say("execl of %s failed: %s", argv[0], strerror(errno));
    }

    if ( (statusp = strchr(argv[0], '@')) == 0 )
	statusp = argv[0];
    statusl = strlen(statusp)-1;

    status("initialize");

    setsid();

    sigmask = 0;

    /* turn off all signals, then turn back on the ones we want
     */
    for (x=1; x < NSIG; x++)
	signal(x, SIG_IGN);

    memset(&sig, 0, sizeof sig);
    Catch(SIGHUP, c_IS_HUP);	/* we care about hup (of course) */
    Catch(SIGINT, c_IS_CAD);	/* software reboot requests */
    Catch(SIGTSTP,fall_from_grace);
    Catch(SIGCHLD,reaper);	/* dying children */
    Catch(SIGALRM,awaken);	/* and alarm clocks */
    Catch(S_MULTI,sigrunlevel);	/* => switch to runlevel `m' */
    Catch(S_SINGLE,sigrunlevel);/*     .    .    .       `s' */
    Catch(S_HALT,sigrunlevel);	/* halt */
    Catch(S_REBOOT,sigrunlevel);/* reboot */

    reboot(0xfee1dead, 672274793, 0);	/* make C-A-D cause a SIGINT */

    putenv("PATH=/bin:/usr/bin");

    alarm(WAKEUP_TIME);
} /* initialize */


/*
 * tell init to change runlevel
 */
void
telinit(int argc, char **argv)
{
    FILE* command;
    int status;
    int signo;
    int runlevel;
    char *arg = argv[0];

    if (argc <= 0) {
	fprintf(stderr, "usage: %s [cq%s]", pgm, RUNLEVELS);
	exit(1);
    }
    if (*arg == '-') ++arg;

    switch (*arg) {
    case 'Q': case 'q':
	    kill(1,SIGHUP);
	    exit(0);
    case 'C': case 'c':
	    kill(1,SIGTSTP);
	    exit(0);
    }
    if ((runlevel=bit_for_runlevel(*arg,1)) <= 0) {
	fprintf(stderr, "%s: can't change to runlevel %s", pgm, argv[0]);
	exit(1);
    }

    /* standard runlevels can be switched between via signal */
    switch (runlevel) {
    case AT_MULTI: signo = SIGUSR1;	break;
    case AT_SINGLE:signo = SIGUSR2;	break;
    case AT_HALT:  signo = SIGTERM;	break;
    case AT_REBOOT:signo = SIGQUIT;	break;
    default:       signo = 0;		break;
    }
    if (signo)
	exit( kill(1,signo) ? 1 : 0 );

    signal(SIGUSR1, c_IS_USR1);
    signal(SIGALRM, c_IS_USR2);
    signal(SIGUSR2, c_IS_USR2);

    alarm(1);
    if ((command = fopen(COMMAND, "w")) == 0) {
	fprintf(stderr, "%s: can't communicate with init", pgm);
	exit(1);
    }
    alarm(0);
    fprintf(command, "%d %c %s\n", getpid(), GO, argv[0]);
    fclose(command);
    alarm(10);
    wait(&status);
    if (sigmask & (IS_USR2|IS_CLK)) {
	fprintf(stderr, "Init refused our request\n");
	exit(1);
    }
    exit(0);
} /* telinit */


void
main(int argc, char **argv)
{
    pgm = basename(argv[0]);

    if (strcasecmp(pgm, "check") == 0)
	exit (readinittab((argc > 1) ? argv[1] : INITTAB) == 0) ;
    else if (strcasecmp(pgm, "telinit") == 0)
	telinit(argc-1, argv+1);
    else if (getpid() == 1) {
	initialize(argc, argv);
	if ( (commands = readinittab(INITTAB)) == 0) {
	    /* we're dead now -- allow c-a-d to work, then loop forever */
	    reboot(0xfee1dead, 672274793, 0xCDEF0123);
	    while (1)
		pause();
	}
	go(argv[1]);
    }
    else {
#ifdef DEBUG
	initialize(argc, argv);
	commands = readinittab(INITTAB);
#else
	telinit(argc-1, argv+1);
#endif
    }
    exit(0);
} /* main */
