/* $Id: init.c,v 4.1 1993/03/23 16:17:07 mike Exp $ */


#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef SYSLOG
#include <syslog.h>
#else
#define LOG_ERR		0
#define LOG_NOTICE	1
#define LOG_INFO	2
#endif
#include <fcntl.h>
#include <linux/unistd.h>
#include <unistd.h>
#include <utmp.h>

_syscall2(int, bdflush, int, func, int, data);


#define LOG_ADD		0
#define LOG_APPEND	1
#define LOG_UPDATE	2


#define KILLME		1
#define DEMAND		2
#define STUNNED		3


#define S_SYSINIT	0
#define S_SINGLE	1
#define S_BOOT 		2
#define S_START	 	3
#define S_RUNNING	4
#define S_WARN		5
#define S_NOTIFY	6
#define S_KILL		7
#define S_POWER		8
#define S_CTRLALTDEL	9
#define S_END		10
#define S_END2		11


char *actions[] = {
	"respawn",
	"wait",
	"once",
	"boot",
	"bootwait",
	"powerfail",
	"powerwait",
	"ondemand",
	"sysinit",
	"update",
	"runlevel",
	"initdefault",
	"ctrlaltdel",
	"endlevel",
	"bdflush"
};
#define A_RESPAWN	0
#define A_WAIT		1
#define A_ONCE		2
#define A_BOOT		3
#define A_BOOTWAIT	4
#define A_POWERFAIL	5
#define A_POWERWAIT	6
#define A_ONDEMAND	7
#define A_SYSINIT	8
#define A_UPDATE	9
#define A_RUNLEVEL	10
#define A_INITDEFAULT	11
#define A_CTRLALTDEL	12
#define A_ENDLEVEL	13
#define A_BDFLUSH	14


struct proc {
	char	id[5];
	char	*rstate;
	char	action;
	char	*process;
	char	count;
	char	pstate;
	pid_t	pid;
	time_t	wakeup;
	struct proc	*next;
	struct proc	*s_next;
};


struct proc suproc = {
	"~~", "S1", A_RESPAWN, SUPROGRAM,
	0, 0, 0, 0
};


int	ctrlaltdel_req = 0;


char	*curr_tab = 0;
char	*old_tab = 0;
struct proc	*p_list = 0;
struct proc	*p_old = 0;
struct proc	*p_stun = 0;
struct proc	*p_stail = 0;

char	default_level = 'S';
int	update_freq = 0;
int	bdflush_freq = 0;
int	bdflush_started = 0;
int	default_update_freq = 0;
int	default_bdflush_freq = 0;
int	sys_check = 0;

char	curr_run_level = ' ';
char	target_level = ' ';

char	sys_state = 0, pre_power_state = 0, pre_ctrlaltdel_state = 0;
struct proc	*pre_power_p = 0;
struct proc	*pre_ctrlaltdel_p = 0;
char	sys_is_booted = 0;
char	is_frozen = 0;
char	chlvl_pending = 0;
char	power_fail = 0;

char	*prog;


#ifdef NEED_STRERROR
char *
strerror(int i)
{
	return sys_errlist[i];
}
#endif


#ifndef SYSLOG
#define LOG_CONS	1
#define LOG_DAEMON	2
#define LOG_DEBUG	3
#include <varargs.h>
void
openlog(char *s, int i, int j)
{
	/* Noop... */
}

void
closelog(void)
{
	/* Noop... */
}

void
syslog(level, fmt, va_alist)
	int	level;
	char	*fmt;
	va_dcl
{
	va_list	args;
	static int	d = 0;
	char	buf[1024];

	if (d == 0)
		d = open(CONSOLE, O_WRONLY|O_NOCTTY|O_CREAT, 0666);
	va_start(args);
	vsprintf(buf, fmt, args);
	write(d, buf, strlen(buf));
	write(d, "\n", 1);
	va_end(args);
}
#endif


#ifdef DEBUG
void
DumpRunList(struct proc *it)
{
	struct proc	*p;

	for (p=it; p; p=p->next) {
		openlog("init", LOG_CONS, LOG_DAEMON);
		syslog(LOG_DEBUG, "ID: %s", p->id);
		syslog(LOG_DEBUG, "        Run levels: %s", p->rstate);
		syslog(LOG_DEBUG, "        Action    : %s", actions[(int)p->action]);
		syslog(LOG_DEBUG, "        Process   : %s", p->process);
		if (p->pid)
			syslog(LOG_DEBUG, "        State     : RUNNING (pid=%d)", p->pid);
		else
			syslog(LOG_DEBUG, "        State     : IDLE");
		if (p->pstate & STUNNED)
			syslog(LOG_DEBUG, "                    STUNNED");
		if (p->pstate & KILLME)
			syslog(LOG_DEBUG, "                    to be killed");
		closelog();
	}
}
#endif


void
FreeProcList(struct proc **p)
{
	struct proc	*q;

	for (; *p && *p != &suproc; *p=q) {
		q = (*p)->next;
		free(*p);
	}
}


int
TellProcs(struct proc *it, int sig)
{
	struct proc	*p;
	int	count;

#if defined(DEBUG) && defined(SYSLOG)
	openlog("init", LOG_CONS, LOG_DAEMON);
#endif
	count = 0;
	for (p=it; p; p=p->next) {
		if (p->pid && (p->pstate & KILLME) == KILLME) {
#ifdef DEBUG
			syslog(LOG_DEBUG, "Signal %d (%s) with %d", p->pid, p->id, sig);
#endif
			kill(p->pid, sig);
			count++;
		}
	}

#if defined(DEBUG) && defined(SYSLOG)
	closelog();
#endif
	return count;
}


int
WarnOldProcs(void)
{
	struct proc	*p;

#if defined(DEBUG) && defined(SYSLOG)
	openlog("init", LOG_CONS, LOG_DAEMON);
#endif

	/* If we are going to a "real" run level (i.e. numeric
	 * or S) then check the list of processes and mark the
	 * ones that shouldn't be running in the new level.
	 */
	if (isdigit(target_level) || target_level == 'S' || target_level == 's') {
		for (p=p_list; p; p=p->next) {
			if (p->pid && !(p->pstate & DEMAND)
			&& strchr(p->rstate, target_level) == NULL) {
#ifdef DEBUG
				syslog(LOG_DEBUG, "ID: %s (pid=%d) to be killed",
					p->id, p->pid);
#endif
				p->pstate |= KILLME;
			} else {
				p->pstate &= (~KILLME);
			}
		}
	}

#if defined(DEBUG) && defined(SYSLOG)
	closelog();
#endif
 
	/* Notify process that they are required to terminate. If we
	 * have an old process list we notify them as well.
	 */
	return (p_old ? TellProcs(p_old, SIGTERM) : 0) | TellProcs(p_list, SIGTERM);
}


int
KillOldProcs(int sig)
{
	return (p_old ? TellProcs(p_old, sig) : 0) | TellProcs(p_list, sig);
}


void
ReadInittab(int forced)
{
	static time_t	last_read = 0;
	struct proc *p_tail, *p, *q;
	struct stat	sb;
	int	i, j;

	/* If the inittab doesn't exist its size is, naturally, zero.
	 * We'll carry on anyway so we clear out any existing processes
	 * (inittab may have been deleted) and fall back to the built
	 * in single user process.
	 */
	if (stat(INITTAB, &sb) < 0) {
		openlog("init", LOG_CONS, LOG_DAEMON);
		syslog(LOG_ERR, "%s: %s", INITTAB, strerror(errno));
		closelog();
		sb.st_size = 0;
	/* If the modification time is earlier than the last time we
	 * read the inittab we don't need to bother doing anything unless
	 * forced to.
	 */
	} else if (!forced && sb.st_mtime < last_read) {
		return;
	}

	/* We're going to read the inittab so note the time and set
	 * the state to S_WARN or S_END;
	 */
#ifdef DEBUG
	openlog("init", LOG_CONS, LOG_DAEMON);
	syslog(LOG_DEBUG, "Rereading inittab...");
	closelog();
#endif
	last_read = time(0);
	if (target_level == curr_run_level)
		sys_state = S_WARN;
	else
		sys_state = S_END;

	/* If we are holding an old proc list and inittab free it so
	 * we can use the space. If any of these processes are still
	 * running (or pending status collection) it's just tough...
	 */
	if (p_old)
		FreeProcList(&p_old);
	if (old_tab) {
		free(old_tab);
		old_tab = NULL;
	}

	/* Mark all processes for killing. */
	for (p=p_list; p; p=p->next)
		p->pstate |= KILLME;

	/* Slurp in the whole of /etc/inittab in one go. */
	if (sb.st_size && (i = open(INITTAB, O_RDONLY)) < 0) {
		openlog("init", LOG_CONS, LOG_DAEMON);
		syslog(LOG_ERR, "%s: %s", INITTAB, strerror(errno));
		closelog();
		sb.st_size = 0;
	}
	old_tab = curr_tab;
	if ((curr_tab = (char *)malloc(sb.st_size+1)) == NULL) {
		curr_tab = old_tab;
		close(i);
		openlog("init", LOG_CONS, LOG_DAEMON);
		syslog(LOG_ERR, "%s: %s", INITTAB, strerror(errno));
		closelog();
		return;
	}
	if (sb.st_size) {
		read(i, curr_tab, sb.st_size);
		close(i);
	}

	/* Scan the new table extracting the important data. */
	p_old = p_list;
	p_list = 0;
	p_tail = 0;
	p_stun = 0;
	p_stail = 0;
	i = 0;
	while (i < sb.st_size) {
		/* Skip blank lines and white space. */
		while (curr_tab[i] == '\n' || curr_tab[i] == ' '
		|| curr_tab[i] == '\t')
			i++;

		/* If this is a comment line zip over it. */
		if (curr_tab[i] == '#') {
			while (curr_tab[i++] != '\n') /* Nothing */ ;
			continue;
		}

		/* Get a new proc entry and fill it. */
		if ((p = (struct proc *)malloc(sizeof(struct proc))) == NULL) {
			openlog("init", LOG_CONS, LOG_DAEMON);
			syslog(LOG_ERR, "%s", strerror(errno));
			free(curr_tab);
			curr_tab = old_tab;
			FreeProcList(&p_list);
			p_list = p_old;
			closelog();
			return;
		}

		p->process = &curr_tab[i];
		while (i < sb.st_size && curr_tab[i] != ':' && curr_tab[i] != '\n') {
			if (i < sb.st_size-1 && curr_tab[i] == '\\' && curr_tab[i+1] == '\n') {
				curr_tab[i] = ' ';
				curr_tab[i+1] = ' ';
			}
			i++;
		}
		if (curr_tab[i] == ':')
			curr_tab[i++] = '\0';

		strncpy(p->id, p->process, 4);
		p->id[4] = '\0';

		p->rstate = &curr_tab[i];
		while (i < sb.st_size && curr_tab[i] != ':' && curr_tab[i] != '\n') {
			if (i < sb.st_size-1 && curr_tab[i] == '\\' && curr_tab[i+1] == '\n') {
				curr_tab[i] = ' ';
				curr_tab[i+1] = ' ';
			}
			i++;
		}
		if (curr_tab[i] == ':')
			curr_tab[i++] = '\0';

		p->process = &curr_tab[i];
		while (i < sb.st_size && curr_tab[i] != ':' && curr_tab[i] != '\n') {
			if (i < sb.st_size-1 && curr_tab[i] == '\\' && curr_tab[i+1] == '\n') {
				curr_tab[i] = ' ';
				curr_tab[i+1] = ' ';
			}
			i++;
		}
		if (curr_tab[i] == ':')
			curr_tab[i++] = '\0';

		for (j=0; j<sizeof(actions)/sizeof(char *); j++)
			if (strcmp(p->process, actions[j]) == 0)
				break;
		p->action = j;

		p->process = &curr_tab[i];
		while (i < sb.st_size && curr_tab[i] != '\n') {
			if (i < sb.st_size-1 && curr_tab[i] == '\\' && curr_tab[i+1] == '\n') {
				curr_tab[i] = ' ';
				curr_tab[i+1] = ' ';
			}
			i++;
		}
		while (curr_tab[i] == '\n')
			curr_tab[i++] = '\0';

		/* Initdefault, update and bdflush entries are treated
		 * specially.
		 */
		if (p->action == A_INITDEFAULT) {
			default_level = p->rstate[0];
			free(p);
			continue;
		} else if (p->action == A_UPDATE && p->rstate[0] == '\0') {
			default_update_freq = atol(p->process);
		} else if (p->action == A_BDFLUSH && p->rstate[0] == '\0') {
			default_bdflush_freq = atol(p->process);
			if (!bdflush_started && !fork()) {
				strncpy(prog, "bdflush", strlen(prog));
				bdflush(0, 0);
				exit(1);
			}
			bdflush_started = 1;
		}

		/* Add the process record to the new list. */
		p->count = 0;
		p->pstate = 0;
		p->pid = 0;
		p->next = 0;
		if (p_tail)
			p_tail->next = p;
		else
			p_list = p;
		p_tail = p;

		/* If this process is identical to one we already have
		 * from the previous process list then move the pid
		 * across and clear the kill and stun flags.
		 */
		for (q=p_old; q; q=q->next) {
			if (strcmp(p->id, q->id) == 0
			&& p->action == q->action
			&& strcmp(p->process, q->process) == 0) {
				p->pstate = (q->pstate & (~(KILLME|STUNNED)));
				p->pid = q->pid;
				q->pid = 0;
				break;
			}
		}
	}
	curr_tab[i] = '\0';

	/* Add the single user process entry to the end of the process
	 * list built from inittab.
	 */
	suproc.count = 0;
	if (p_tail)
		p_tail->next = &suproc;
	else
		p_list = &suproc;

#ifdef DEBUG
	openlog("init", LOG_CONS, LOG_DAEMON);
	syslog(LOG_DEBUG, "New inittab structure is...");
	DumpRunList(p_list);
	closelog();
#endif
}


void
LogProcess(int mode, char *file,
	short t, pid_t pid, char *id, char *line, char *user)
{
	struct utmp	utmp1, *utmp2;
	char	*p, *q;
	int	d;

	/* Build a model. */
	utmp1.ut_type = t;
	strncpy(utmp1.ut_id, id, sizeof(utmp1.ut_id));

	utmpname(file);
	if (mode == LOG_UPDATE) {
		if ((utmp2 = getutid(&utmp1)) == NULL) {
			endutent();
			return;
		}
	} else if (mode == LOG_ADD ) {
		while ((utmp2 = getutent()) != NULL) {
			if (utmp2->ut_type == UT_UNKNOWN
			|| (utmp2->ut_type != RUN_LVL && utmp2->ut_type != BOOT_TIME
			&& utmp2->ut_type != NEW_TIME && utmp2->ut_type != OLD_TIME
			&& kill(utmp2->ut_pid, 0) < 0))
				break;
		}
		if (!utmp2) {
			utmp2 = &utmp1;
		} else {
			utmp2->ut_type = t;
			strncpy(utmp2->ut_id, id, sizeof(utmp2->ut_id));
			mode = LOG_UPDATE;
		}
	} else {
		utmp2 = &utmp1;
	}

	utmp2->ut_pid = pid;
	strncpy(utmp2->ut_line, line, sizeof(utmp1.ut_line));
	utmp2->ut_line[sizeof(utmp1.ut_line)-1] = '\0';
	utmp2->ut_time = time(0);
	for (p=user; *p && *p != ' '; p++);
	for (; p != user && *p != '/'; p--);
	if (*p == '/')
		p++;
	for (d=0,q=utmp2->ut_user;
		d < sizeof(utmp1.ut_user) && *p && *p != ' ';
		*(q++) = *(p++),d++);
	for (; d < sizeof(utmp1.ut_user); *(q++) = '\0',d++);
	utmp2->ut_host[0] = '\0';
	utmp2->ut_addr = 0;

	if (mode == LOG_UPDATE) {
		pututline(utmp2);
		endutent();
		return;
	}

	if ((d = open(file, O_WRONLY|O_APPEND)) >= 0) {
		write(d, (char *)utmp2, sizeof(struct utmp));
		close(d);
	}
}


pid_t
StartProc(struct proc *it)
{
	int	i;
	char	buf[1024];
	char	*args[5];

	/* If the respawn count rises to RESPAWN_COUNT we mark
	 * the process as STUNNED and don't spawn it. It can be
	 * woken by forcing a reread of inittab.
	 */
	if ((it->action == A_RESPAWN || it->action == A_ONDEMAND)
	&& ++it->count == RESPAWN_COUNT) {
		openlog("init", LOG_CONS, LOG_DAEMON);
		syslog(LOG_ERR, "%s: command respawning too rapidly - disabled for 5 minutes", it->id);
		closelog();
		it->pstate |= STUNNED;
		it->wakeup = time(0) + STUN_TIME;
		it->s_next = 0;
		if (p_stail)
			p_stail->s_next = it;
		else
			p_stun = it;
		p_stail = it;
		return (0);
	}

	switch (it->pid = fork()) {
		case 0: /* Child */
			/* Reset all signals to the default. */
#ifdef NSIG
			for (i=0; i<NSIG; i++)
#else
			for (i=0; i<_NSIG; i++)
#endif
				signal(i, SIG_DFL);

			LogProcess(LOG_ADD, UTMP_FILE, INIT_PROCESS, getpid(),
				it->id, "", it->process);
			LogProcess(LOG_APPEND, WTMP_FILE, INIT_PROCESS, getpid(),
				it->id, "", it->process);

			/* Close any open files we may have. */
#ifdef _NFILE
			for (i=0; i<_NFILE; i++)
#else
			for (i=0; i<getdtablesize(); i++)
#endif
				close(i);

			strcpy(buf, "exec ");
			strncat(buf, it->process, sizeof(buf)-6);
			buf[sizeof(buf)-1] = '\0';
			args[0] = SHELL;
			args[1] = args[0];
			args[2] = "-c";
			args[3] = buf;
			args[4] = NULL;

			setsid();
			execv(args[0], args+1);

			/* Not reached. */
			exit(1);
			break;

		case -1: /* Error */
			it->pid = 0;
			openlog("init", LOG_CONS, LOG_DAEMON);
			syslog(LOG_ERR, "%s (id %s)", strerror(errno), it->id);
			closelog();
			break;
	}
#ifdef DEBUG
	openlog("init", LOG_CONS, LOG_DAEMON);
	syslog(LOG_DEBUG, "Started process: [pid=%d] %s", it->pid, it->process);
	closelog();
#endif
	return it->pid;
}


void
ProcStopped(pid_t it)
{
	struct proc	*p;

#ifdef DEBUG
	openlog("init", LOG_CONS, LOG_DAEMON);
	syslog(LOG_DEBUG, "Process %d stopped", it);
	closelog();
#endif

	/* Search the current and old lists for this process. */
	for (p=p_list; p && p->pid != it; p=p->next);
	if (p == 0 && p_old != 0)
		for (p=p_old; p && p->pid != it; p=p->next);

	if (p) {
		LogProcess(LOG_UPDATE, UTMP_FILE, UT_UNKNOWN, p->pid,
			p->id, "", "");
		LogProcess(LOG_APPEND, WTMP_FILE, DEAD_PROCESS, p->pid,
			p->id, "", "");

		/* Reread the inittab and then search for this processes
		 * entry in the current list. If we find it then it's still
		 * a candidate to be run...
		 */
		ReadInittab(0);
		for (p=p_list; p && p->pid != it; p=p->next);
		if (p) {
			p->pid = 0;
			if (!is_frozen && (p->pstate & (KILLME|STUNNED)) == 0
			&& (p->action == A_RESPAWN
			|| p->action == A_ONDEMAND))
				StartProc(p);
		}
	}
}


void
int_handler(int i)
{
	if (!ctrlaltdel_req)
		ctrlaltdel_req = 1;
	signal(SIGINT, int_handler);
}


void
alarm_handler(int i)
{
	/* The work is done elsewhere. We just get ready to
	 * catch the next alarm.
	 */
	signal(SIGALRM, alarm_handler);
}


void
pwr_handler(int i)
{
	power_fail = 1;
#ifdef SIGPWR
	signal(SIGPWR, pwr_handler);
#endif
#ifdef SIGSTOP
	signal(SIGSTOP, pwr_handler);
#endif
}


void
stop_handler(int i)
{
	is_frozen = 1;
	signal(SIGUSR1, stop_handler);
#ifdef SIGTSTP
	signal(SIGTSTP, stop_handler);
#endif
#ifdef SIGSTOP
	signal(SIGSTOP, stop_handler);
#endif
}


void
start_handler(int i)
{
	if (is_frozen)
		sys_state = S_START;
	is_frozen = 0;
	signal(SIGUSR2, start_handler);
#ifdef SIGCONT
	signal(SIGCONT, start_handler);
#endif
}


void
hup_handler(int i)
{
	chlvl_pending = 1;
	signal(SIGHUP, hup_handler);
}


char
GetLevel()
{
	int	d;
	char	new_level;

	if ((d = open(INITRUNLVL, O_RDONLY)) >= 0) {
		read(d, &new_level, 1);
		close(d);
		return (new_level);
	} else {
		return curr_run_level;
	}
}


int
telinit(char *s)
{
	int	d;

	if ((d = open(INITRUNLVL, O_WRONLY|O_CREAT, 0600)) >= 0) {
		write(d, s, 1);
		close(d);
		kill(INITPID, SIGHUP);
		return 0;
	} else {
		return 1;
	}
}


char
MapName(char *s)
{
	struct proc	*p;

	/* If the name matches a runlevel entry in inittab we return the
	 * first character of the rstate field. Other run level letters
	 * or digits in the rstate field are silently ignored.
	 */
	for (p=p_list; p; p=p->next) {
		if (p->action == A_RUNLEVEL && strcmp(s, p->process) == 0)
			return p->rstate[0];
	}

	/* If the given name is only a single character we assume it is
	 * a runlevel in its own right and return it as is.
	 */
	if (s[1] == '\0')
		return s[0];

	/* Doesn't make sense as a run level. We just quietly ignore it,
	 * we'll stick with whatever the default is.
	 */
	return ' ';
}


int
main(int argc, char *argv[])
{
	struct proc	*p, *q;
	char	*ptr;
	int	sys_timeout, next_sync, to_wait;
	pid_t	wait_for_pid, this_pid;
	int	status;
	int	i;

	/* Close any open files we may have. Linux sets us up connected
	 * to /dev/tty1. This is fine if it is falling back to a shell
	 * but init must *not* hold files open because this would prevent
	 * the root filesystem from being remounted read only. Init
	 * *must* allow the root filesystem to be pulled from under it
	 * or the system cannot be shutdown cleanly.
	 */
#ifdef _NFILE
	for (i=0; i<_NFILE; i++)
#else
	for (i=0; i<getdtablesize(); i++)
#endif
		close(i);

	/* Grab the inittab early. We may need it while parsing command
	 * line options.
	 */
	ReadInittab(1);

	/* Find out if we are a master init or not. A master init is
	 * one which is invoked by a name other than telinit and whose
	 * pid is equal to the expected pid of the master init.
	 */
	prog = argv[0];
	if ((ptr = strrchr(argv[0], '/')) != NULL)
		prog = ptr + 1;
	if (strcmp(prog, "telinit") == 0 || getpid() != INITPID) {
		if (argc != 2) {
			fprintf(stderr, "usage: %s [0-9A-Z...]\n", prog);
			exit(1);
		} else {
			if (argv[1][1] == '\0'
			&& (isdigit(argv[1][0]) || argv[1][0] == 'S' || argv[1][0] == 's')) {
				exit(telinit(argv[1]));
			} else if ((target_level = MapName(argv[1])) == ' ') {
				fprintf(stderr, "usage: %s [0-9A-Z...]\n", prog);
				exit(1);
			}
			exit(telinit(&target_level));
		}
	}

	/* Parse any command line options. */
	while (--argc) {
		/* Options starting with -
		 *   -a system is autobooting
		 */
		if (argv[argc][0] == '-') {
			ptr = argv[argc];
			while (*ptr) {
				switch (*ptr) {
					case 'a':
						/* Auto boot. */
						putenv("AUTOBOOT=YES");
						break;

					case 'c':
						/* Force check of filesystems. */
						putenv("CHECKFS=YES");
						break;

					case 'f':
						/* Fast boot. */
						putenv("FASTBOOT=YES");
						break;

					case 's':
						/* Single user */
						target_level = 'S';
						break;
				}
				ptr++;
			}

		/* A single digit or S is taken to be an explicit
		 * run level. If specified on the command line it
		 * overrides the initdefault entry in inittab.
		 */
		} else if (argv[argc][1] == '\0'
		&& (isdigit(argv[argc][0]) || argv[argc][0] == 'S' || argv[argc][0] == 's')) {
			target_level = argv[argc][0];

		/* Anything else is taken to be a run level name and
		 * the corresponding run level looked up from the inittab
		 * information. If we can't find a corresponding run
		 * level the option is simply ignored.
		 * Special for Lilo9+: if the system is autobooting lilo
		 * passes us the word 'auto' on the command line. We trap
		 * this and treat it in the same way as a -a argument.
		 */
		} else if (strcmp(argv[argc], "auto") == 0) {
			putenv("AUTOBOOT=YES");
		} else {
			target_level = MapName(argv[argc]);
		}
	}

	/* Set the signal handling. */
	signal(SIGINT, int_handler);
	signal(SIGHUP, hup_handler);
	signal(SIGALRM, alarm_handler);
	signal(SIGUSR1, stop_handler);
#ifdef SIGTSTP
	signal(SIGTSTP, stop_handler);
#endif
#ifdef SIGPWR
	signal(SIGPWR, pwr_handler);
#endif
#ifdef SIGSTOP
	signal(SIGSTOP, pwr_handler);
#endif
	signal(SIGUSR2, start_handler);
#ifdef SIGCONT
	signal(SIGCONT, start_handler);
#endif

	/* If the target level is still blank we use the default level
	 * which may have been overridden by an initdefault in the
	 * inittab. If the target level is not blank we have been
	 * given a level on the command line.
	 */
	if (target_level == ' ')
		target_level = default_level;

	sys_state = S_SYSINIT;
	sys_timeout = 0;
	sys_check = time(0) + RESPAWN_TIME;
	next_sync = 0;
	wait_for_pid = 0;
	p = p_list;

	do {
		switch (sys_state) {
			/* WARNING: Note carefully where the breaks are
			 * and where we simply drop through to the next
			 * case!
			 */
			case S_SYSINIT:
				/* Look for the next sysinit entry. */
				while (p && p->action != A_SYSINIT)
					p = p->next;
				if (p) {
					/* Start a sysinit process. */
					wait_for_pid = StartProc(p);
					p = p->next;
					break;
				} else {
					/* Sysinit completed. Start syncing
					 * if required and move to the boot
					 * stage.
					 */
					bdflush_freq = default_bdflush_freq;
					update_freq = default_update_freq;
					if (bdflush_freq) {
						next_sync = time(0) + bdflush_freq;
						openlog("init", LOG_CONS, LOG_DAEMON);
						syslog(LOG_NOTICE, "Bdflush frequency now %d", bdflush_freq);
						closelog();
					} else if (update_freq) {
						next_sync = time(0) + update_freq;
						openlog("init", LOG_CONS, LOG_DAEMON);
						syslog(LOG_NOTICE, "Sync frequency now %d", update_freq);
						closelog();
					}
					p = p_list;
					sys_state = S_START;
				}

			case S_START:
start_level:
				/* If we haven't booted the system yet do
				 * do it now.
				 */
				if (target_level != 'S' && target_level != 's'
				&& sys_is_booted == 0) {
					/* Look for the next bootwait entry
					 * starting boot processes as we go.
					 */
					while (p && p->action != A_BOOTWAIT) {
						if (p->action == A_BOOT) {
							p->pstate |= DEMAND;
							StartProc(p);
						}
						p = p->next;
					}
					if (p) {
						/* Start a bootwait process. */
						wait_for_pid = StartProc(p);
						p = p->next;
						break;
					} else {
						/* Boot completed. Start up
						 * the initial run level.
						 */
						LogProcess(LOG_APPEND, WTMP_FILE, BOOT_TIME,
							0, "~~", "~", "reboot");
						sys_is_booted = 1;
						p = p_list;
					}
				}
				
#ifdef DEBUG
				openlog("init", LOG_CONS, LOG_DAEMON);
				syslog(LOG_DEBUG, "Starting level %c", target_level);
				closelog();
#endif
				/* If we are changing to a new run level
				 * or triggering a non-numeric level we
				 * start all processes that aren't already
				 * running. Otherwise we just start any
				 * respawn/ondemand processes that aren't
				 * already running.
				 */
				while (p && (curr_run_level == target_level
				|| strchr(p->rstate, target_level) == NULL
				|| p->action != A_WAIT)) {
					if (p->pid == 0 && strchr(p->rstate, target_level)) {
						if (curr_run_level != target_level
						&& p->action == A_ONCE) {
							p->pstate |= DEMAND;
							StartProc(p);
						}
						if (p->action == A_RESPAWN
						|| p->action == A_ONDEMAND) {
							StartProc(p);
						}
					}
					p = p->next;
				}
				if (p) {
					/* Start a wait process. */
					wait_for_pid = StartProc(p);
					p = p->next;
				} else {
					/* Startup completed. Set the run
					 * level depending whether we started
					 * a "real" run level or not. Handling
					 * of special run states is done here.
					 */
					switch (target_level) {
						case '0': /* System shutdown. */
							is_frozen = 1;
							curr_run_level = target_level;
							break;

						case '1': case '2': case '3':
						case '4': case '5': case '6':
						case '7': case '8': case '9':
						case 'S': case 's':
							LogProcess(LOG_APPEND, WTMP_FILE, RUN_LVL,
							0, "~~", "~", "runlevel");
							openlog("init", LOG_CONS, LOG_DAEMON);
							syslog(LOG_INFO, "Change to %c complete", target_level);
							closelog();
							curr_run_level = target_level;
							break;

						default:
							target_level = curr_run_level;
							break;
					}
					sys_state = S_RUNNING;
				}
				break;

			case S_RUNNING:
				/* Currently nothing to do here... */
				break;

			case S_WARN:
				syslog(LOG_INFO, "Changing to level %c", target_level);
				/* Send SIGTERM to the processes which
				 * are no longer required.
				 */
				sys_state = S_NOTIFY;
				if (WarnOldProcs()) {
					sys_timeout = time(0) + NOTIFY_TIME;
					break;
				}

			case S_NOTIFY:
				/* Send SIGINT to the processes which
				 * are no longer required.
				 */
				sys_state = S_KILL;
				if (KillOldProcs(SIGINT)) {
					sys_timeout = time(0) + WARN_TIME;
					break;
				}

			case S_KILL:
				sys_state = S_START;
				p = p_list;
				KillOldProcs(SIGKILL);
				goto start_level;

			case S_POWER:
				/* Look for the next powerwait entry
				 * starting powerfail processes as we go.
				 */
				while (p && p->action != A_POWERWAIT) {
					if (p->action == A_POWERFAIL) {
						p->pstate |= DEMAND;
						StartProc(p);
					}
					p = p->next;
				}
				if (p) {
					/* Start a powerwait process. */
					wait_for_pid = StartProc(p);
					p = p->next;
				} else {
					/* Power failure done. Resume
					 * the previous state. Normally
					 * we would expect to have been
					 * shutdown by now...
					 */
					sys_state = pre_power_state;
					p = pre_power_p;
				}
				break;

			case S_CTRLALTDEL:
				/* Look for the next ctrlaltdel entry.
				 */
				while (p && p->action != A_CTRLALTDEL) {
					p = p->next;
				}
				if (p) {
					/* Start a ctrlaltdel process. */
					wait_for_pid = StartProc(p);
					p = p->next;
				} else {
					/* Ctrl-alt-del done. Resume
					 * the previous state.
					 */
					sys_state = pre_ctrlaltdel_state;
					p = pre_ctrlaltdel_p;
					ctrlaltdel_req = 0;
				}
				break;

			case S_END:
				p = p_list;
				sys_state = S_END2;
				/* Fall through... */
			case S_END2:
				/* Look for the next end level entry.
				 */
				while (p && (p->action != A_ENDLEVEL
				|| strchr(p->rstate, target_level) == NULL)) {
					p = p->next;
				}
				if (p) {
					/* Start an end level process. */
					wait_for_pid = StartProc(p);
					p = p->next;
				} else {
					sys_state = S_WARN;
				}
				break;
		}

		do {
			/* Set an alarm to wake us up next time
			 * we need to do something. This could be
			 * to do a sync or to kill off previously
			 * warned processes.
			 */
			if (sys_timeout && next_sync)
				to_wait = ((sys_timeout < next_sync)
						? sys_timeout
						: next_sync);
			else if (next_sync)
				to_wait = next_sync;
			else if (sys_timeout)
				to_wait = sys_timeout;
			else
				to_wait = sys_check;
			if (p_stun && to_wait > p_stun->wakeup)
				to_wait = p_stun->wakeup;
			to_wait = ((to_wait < sys_check)
					? to_wait
					: sys_check)
				-time(0);

#ifdef DEBUG
			openlog("init", LOG_CONS, LOG_DAEMON);
			syslog(LOG_DEBUG, "[%c:%d] Waiting for %d", curr_run_level, sys_state, wait_for_pid);
			syslog(LOG_DEBUG, "    - alarm in %d seconds", to_wait);
			closelog();
#endif
			if ((sys_state == S_RUNNING && chlvl_pending) || to_wait <= 0) {
				this_pid = -1;
				errno = EINTR;
			} else {
				alarm(to_wait);
				this_pid = wait(&status);
				alarm(0);
			}

			if (this_pid > 0) {
				ProcStopped(this_pid);
				if (this_pid == wait_for_pid)
					wait_for_pid = 0;
			} else if (errno == ECHILD && sys_state == S_RUNNING) {
				/* No children to wait for.
				 * If we are frozen just wait for a
				 * signal to tell us what to do next.
				 * Otherwise start a single user shell.
				 */
				if (is_frozen) {
					if (ctrlaltdel_req == -1) {
						sync();
						reboot(0xfee1dead, 672274793, 0x1234567);
						pause();
					} else {
#ifdef DEBUG
						openlog("init", LOG_CONS, LOG_DAEMON);
						syslog(LOG_DEBUG, "No children. Waiting for signal");
						closelog();
#endif
						sync();
#if 0
						pause();
#endif
					}
				} else {
#ifdef DEBUG
					openlog("init", LOG_CONS, LOG_DAEMON);
					syslog(LOG_DEBUG, "Going single user");
					closelog();
#endif
					target_level = 'S';
					sys_state = S_WARN;
				}
			}

			if (next_sync && time(0) >= next_sync) {
#ifdef DEBUG
				openlog("init", LOG_CONS, LOG_DAEMON);
				syslog(LOG_DEBUG, "SYNC");
				closelog();
#endif
				if (bdflush_freq) {
					if (!bdflush(1, 0)) {
						next_sync = time(0) + bdflush_freq;
					} else {
						openlog("init", LOG_CONS, LOG_DAEMON);
						syslog(LOG_NOTICE, "kernel does not support bdflush - will use sync");
						closelog();
						bdflush_freq = 0;
						default_bdflush_freq = 0;
						if (!update_freq) {
							update_freq = 30;
							default_update_freq = 30;
						}
					}
				}
				if (!bdflush_freq) {
					sync();
					if (update_freq)
						next_sync = time(0) + update_freq;
					else
						next_sync = 0;
				}
			}
			if (sys_timeout && time(0) >= sys_timeout) {
#ifdef DEBUG
				openlog("init", LOG_CONS, LOG_DAEMON);
				syslog(LOG_DEBUG, "TIMEOUT REACHED");
				closelog();
#endif
				sys_timeout = 0;
			}
			if (time(0) >= sys_check) {
				for (q=p_list; q; q=q->next) {
					q->count = 0;
				}
				sys_check = time(0) + RESPAWN_TIME;
			}

			if (p_stun && time(0) >= p_stun->wakeup) {
				q = p_stun;
				q->pstate &= (~STUNNED);
				if ((p_stun = q->s_next) == 0)
					p_stail = 0;
				if (!is_frozen && (q->pstate & KILLME) == 0)
					StartProc(q);
			}

			/* If we have been notified of a power failure
			 * switch state to handle power failure processes.
			 */
			if (power_fail) {
				LogProcess(LOG_APPEND, WTMP_FILE, INIT_PROCESS,
					0, "~~", "~", "power");
				ReadInittab(0);
				pre_power_state = sys_state;
				pre_power_p = p;
				sys_state = S_POWER;
				p = p_list;
			}

			/* If we have been notified of a ctrl-alt-del
			 * switch state to handle ctrlaltdel process.
			 */
			if (ctrlaltdel_req == 1) {
				ctrlaltdel_req = -1;
				LogProcess(LOG_APPEND, WTMP_FILE, INIT_PROCESS,
					0, "~~", "~", "c-a-d");
				ReadInittab(0);
				pre_ctrlaltdel_state = sys_state;
				pre_ctrlaltdel_p = p;
				sys_state = S_CTRLALTDEL;
				p = p_list;
			}

			/* If we are between run levels and we have
			 * completed the initial boot we hold off
			 * changing the run level until this change
			 * is complete.
			 */
			if (chlvl_pending && sys_state == S_RUNNING) {
				chlvl_pending = 0;
				target_level = GetLevel();
				is_frozen = 0;
				if (target_level == 'Q' || target_level == 'q')
					target_level = curr_run_level;
				ReadInittab(1);
			}
		} while (wait_for_pid || is_frozen || sys_timeout);
	} while (1);
}
