/*
 * Init		A System-V Init Clone.
 *
 * Usage:	/sbin/init
 *		     init [0123456SsQqAaBbCc]
 *		  telinit [0123456SsQqAaBbCc]
 *
 * Version:	@(#)init.c  2.69  30-Nov-1996  MvS
 *
 *		This file is part of the sysvinit suite,
 *		Copyright 1991-1996 Miquel van Smoorenburg.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 */

/* Standard configuration */
#define SYSLOG	   1			/* Use syslog for messages */
#define CHANGE_WAIT 0			/* Do not change runlevel while
					   waiting for a process to exit */
#define NEWINIT     1			/* Enable use of /dev/initctl */

/* Debug and test modes */
#define DEBUG	   0			/* Debug code off */
#define INITDEBUG  0			/* Fork at startup so we can debug init. */
#define CLEAN_UTMP 1			/* Do garbage collect in utmp*/
#undef  ROOTFS	   "/dev/hda2"		/* Root fs to use */

/* Some constants */
#define INITPID	   1			/* pid of first process */
#define PIPE_FD    10			/* Fileno of initfifo. */

/* Failsafe configuration */
#define MAXSPAWN   10			/* Max times respawned in.. */
#define TESTTIME   120			/* this much seconds */
#define SLEEPTIME  300			/* Disable time */

/* Default path inherited by every child if it's not set. */
#define PATH_DFL   "PATH=/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/kd.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
#include <utmp.h>
#include <ctype.h>
#include <stdarg.h>
#if SYSLOG
#  include <sys/syslog.h>
#endif
#if NEWINIT
#  include <sys/time.h>
#  include "initreq.h"
#endif
#include "paths.h"
#include "reboot.h"

#ifndef NO_PROCESS
#  define NO_PROCESS 0
#endif
#ifndef SIGPWR
#  define SIGPWR SIGUSR1
#endif

/* Set a signal handler. */
#define SETSIG(sa, sig, fun)	sa.sa_handler = fun; \
				sa.sa_flags = SA_RESTART; \
				sigaction(sig, &sa, NULL);

/* Version information */
char *Version = "@(#) init 2.69 30-Nov-1996 MvS";
char *bootmsg = "version 2.69 booting";
#define E_VERSION "INIT_VERSION=sysvinit-2.69"

/* Information about a process in the in-core inittab */
typedef struct _child_ {
  int flags;			/* Status of this entry */
  int exstat;			/* Exit status of process */
  int pid;			/* Pid of this process */
  time_t tm;			/* When respawned last */
  int count;			/* Times respawned in the last 2 minutes */
  char id[8];			/* Inittab id (must be unique) */
  char rlevel[12];		/* run levels */
  int action;			/* what to do (see list below) */
  char process[128];		/* The command line */
  struct _child_ *new;		/* New entry (after inittab re-read) */
  struct _child_ *next;		/* For the linked list */
} CHILD;

CHILD *family = NULL;		/* The linked list of all entries */
CHILD *newFamily = NULL;	/* The list after inittab re-read */

CHILD ch_emerg = {		/* Emergency shell */
  0, 0, 0, 0, 0,
  "~~",
  "S",
  3,
  "/sbin/sulogin",
  NULL,
  NULL
};

char runlevel = 'S';		/* The current run level */
char thislevel = 'S';		/* The current runlevel */
char prevlevel = 'N';		/* Previous runlevel */
int dflLevel = 0;		/* Default runlevel */
int got_cont = 0;		/* Set if we received the SIGCONT signal */
int emerg_shell = 0;		/* Start emergency shell? */
sigset_t got_signals;		/* Set if we received a signal. */
int wroteReboot	= 1;		/* Set when we wrote the reboot record */
int sltime = 5;			/* Sleep time between TERM and KILL */
char *argv0;			/* First arguments; show up in ps listing */
int maxproclen;			/* Maximal length of argv[0] */
struct utmp utproto;		/* Only used for sizeof(utproto.ut_id) */
char *console_dev = CONSOLE;	/* Console device. */
int wroteiosave = 0;		/* Did we write ioctl.save yet? */

/* Prototypes. */
void Wtmp(char *user, char *id, int pid, int type, char *line);
void WtmpOnly(char *user, char *id, int pid, int type, char *line);
void Log(int loglevel, char *fmt, ...);
void SetTerm(int how);
void print(char *fmt);

/* Actions to be taken by init */
#define RESPAWN			1
#define WAIT			2
#define ONCE			3
#define	BOOT			4
#define BOOTWAIT		5
#define POWERFAIL		6
#define POWERWAIT		7
#define POWEROKWAIT		8
#define CTRLALTDEL		9
#define OFF		       10
#define	ONDEMAND	       11
#define	INITDEFAULT	       12
#define SYSINIT		       13
#define POWERFAILNOW           14
#define KBREQUEST               15

/* Macro to see if this is a special action */
#define ISPOWER(i) ((i) == POWERWAIT || (i) == POWERFAIL || \
		    (i) == POWEROKWAIT || (i) == POWERFAILNOW || \
		    (i) == CTRLALTDEL)

/* Values for the 'flags' field */
#define RUNNING			2	/* Process is still running */
#define KILLME			4	/* Kill this process */
#define DEMAND			8	/* "runlevels" a b c */
#define FAILING			16	/* process respawns rapidly */
#define WAITING			32	/* We're waiting for this process */
#define ZOMBIE			64	/* This process is already dead */
#define XECUTED		128	/* Set if spawned once or more times */

/* Log levels. */
#define L_CO	1		/* Log on the console. */
#define L_SY	2		/* Log with syslog() */
#define L_VB	(L_CO|L_SY)	/* Log with both. */

/* ascii values for the `action' field. */
struct actions {
  char *name;
  int act;
} actions[] = {
  { "respawn", 	   RESPAWN	},
  { "wait",	   WAIT		},
  { "once",	   ONCE		},
  { "boot",	   BOOT		},
  { "bootwait",	   BOOTWAIT	},
  { "powerfail",   POWERFAIL	},
  { "powerfailnow",POWERFAILNOW },
  { "powerwait",   POWERWAIT	},
  { "powerokwait", POWEROKWAIT	},
  { "ctrlaltdel",  CTRLALTDEL	},
  { "off",	   OFF		},
  { "ondemand",	   ONDEMAND	},
  { "initdefault", INITDEFAULT	},
  { "sysinit",	   SYSINIT	},
  { "kbrequest",   KBREQUEST    },
  { NULL,	   0		},
};

/*
 *	Set the process title. We do not check for overflow of
 *	the stack space since we know there is plenty for
 *	our needs and we'll never use more than 10 bytes anyway.
 */
int setproctitle(char *fmt, ...)
{
  va_list ap;
  int len;
  char buf[256];

  buf[0] = 0;

  va_start(ap, fmt);
  len = vsprintf(buf, fmt, ap);
  va_end(ap);

  memset(argv0, 0, maxproclen + 1);
  strncpy(argv0, buf, maxproclen);

  return len;
}

/* Open a device with retries. */
int serial_open(char *dev, int mode)
{
  int f, fd;
  int m;

  /* Open device in nonblocking mode. */
  m = mode | O_NONBLOCK;

  /* Retry the open five times. */
  for(f = 0;f < 5; f++)
	if ((fd = open(dev, m)) >= 0) break;
  if (fd < 0) return(fd);

  /* Set original flags. */
  if (m != mode)
  	fcntl(fd, F_SETFL, mode);
  return(fd);
}

/* We got a signal (HUP PWR WINCH ALRM INT) */
void signal_handler(int sig)
{
  sigaddset(&got_signals, sig);
}

/* SIGCHLD: one of our children has died. */
void chld_handler()
{
  CHILD *ch;
  int pid, st;

  /* Find out which process(es) this was (were) */
  while((pid = waitpid(-1, &st, WNOHANG)) != 0) {
	if (errno == ECHILD) break;
#ifdef __alpha__
	/* Some (old) Linux/Alpha version have a buggy library */
	if (pid == -ECHILD) break;
#endif
	for( ch = family; ch; ch = ch->next )
		if ( ch->pid == pid && (ch->flags & RUNNING) ) {
#if DEBUG
			Log(L_VB, "chld_handler: marked %d as zombie", ch->pid);
#endif
			sigaddset(&got_signals, SIGCHLD);
			ch->exstat = st;
			ch->flags |= ZOMBIE;
			if (ch->new) {
				ch->new->exstat = st;
				ch->new->flags |= ZOMBIE;
			}
			break;
		}
#if DEBUG
	if (ch == NULL)
		Log(L_VB, "chld_handler: unknown child %d exited.", pid);
#endif
  }
}

/*
 * Linux ignores all signals sent to init when the
 * SIG_DFL handler is installed. Therefore we must catch SIGTSTP
 * and SIGCONT, or else they won't work....
 *
 * The SIGCONT handler
 */
void cont_handler()
{
  got_cont = 1;
}

/*
 * OOPS: segmentation violation!
 */
void segv_handler()
{
  Log(L_VB, "PANIC: segmentation violation! giving up..");
  while(1) pause();
}

/*
 * The SIGSTOP & SIGTSTP handler
 */
void stop_handler()
{
  got_cont = 0;
  while(!got_cont) pause();
  got_cont = 0;
}

/* Set terminal settings to reasonable defaults */
void SetTerm(int how)
{
  struct termios tty;
  int fd, fd2;
  int restore_ok = 0;

  if ((fd = serial_open(console_dev, O_RDWR|O_NOCTTY)) < 0) {
	Log(L_VB, "can't open %s", console_dev);
	return;
  }

  /* Do we want to save or restore modes. */
  if (how == 0) {
	/* Restore. */
	if (wroteiosave && (fd2 = open(IOSAVE, O_RDONLY)) >= 0) {
		if (read(fd2, &tty, sizeof(tty)) == sizeof(tty))
			restore_ok = 1;
		close(fd2);
	}
	if (restore_ok == 0) {
		/* Get old settings */
		(void) ioctl(fd, TCGETS, &tty);

		/* Set speed etc. */
		tty.c_cflag &= CBAUD|CBAUDEX|CSIZE|CSTOPB|PARENB|PARODD;
		tty.c_cflag |= HUPCL|CLOCAL;

		/* Set the most important characters */
		tty.c_cc[VINTR]  = 3;
		tty.c_cc[VQUIT]  = 28;
		tty.c_cc[VERASE] = 127;
		tty.c_cc[VKILL]  = 24;
		tty.c_cc[VEOF]   = 4;
		tty.c_cc[VTIME]  = 0;
		tty.c_cc[VMIN]   = 1;
		tty.c_cc[VSTART] = 17;
		tty.c_cc[VSTOP]  = 19;
		tty.c_cc[VSUSP]  = 26;
	}

	/* Set pre and post processing */
	tty.c_iflag = IGNPAR|ICRNL|IXON|IXANY;
	tty.c_oflag = OPOST|ONLCR;
	tty.c_lflag = ISIG|ICANON|ECHO|ECHOCTL|ECHOPRT|ECHOKE;

	/* Now set the terminal line. */
	(void) ioctl(fd, TCSETS, &tty);
	(void) close(fd);
	return;
  }

  /* Save the terminal settings. */
  (void) ioctl(fd, TCGETS, &tty);
  if ((fd2 = open(IOSAVE, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
	if (errno != EROFS)
		Log(L_VB, "can't open(%s, O_WRONLY): %s", IOSAVE, sys_errlist[errno]);
	(void) close(fd);
	(void) close(fd2);
	return;
  }
  (void) write(fd2, &tty, sizeof(struct termios));
  wroteiosave = 1;
  (void) close(fd);
  (void) close(fd2);
}

/* Print to the system console */
void print(char *s)
{
  int fd;

  if ((fd = serial_open(console_dev, O_WRONLY|O_NOCTTY|O_NDELAY)) >= 0) {
	write(fd, s, strlen(s));
	close(fd);
  }
}

/* Sleep a number of seconds */
void Sleep(int sec)
{
  int oldAlrm;			/* Previous value of timer */
  int f;			/* Counter */
  int ga = sigismember(&got_signals, SIGALRM);	/* Remember got_alrm flag */

  /* Save old alarm time */
  oldAlrm = alarm(0);

  /* Wait 'sec' seconds */
  for(f = 0; f < sec; f++) {
	sigdelset(&got_signals, SIGALRM);
	alarm(1);
	while(sigismember(&got_signals, SIGALRM) == 0) pause();
  }

  /* Reset old values of got_alrm flag and the timer */
  ga ? sigaddset(&got_signals, SIGALRM) : sigdelset(&got_signals, SIGALRM);
  if (oldAlrm) alarm(oldAlrm - sec > 0 ? oldAlrm - sec : 1);
}

/* Log something to the user */
void Log(int loglevel, char *s, ...)
{
  va_list va_alist;
  char buf[256];

  va_start(va_alist, s);
  vsprintf(buf, s, va_alist);
  va_end(va_alist);

#if SYSLOG
  if (loglevel & L_SY) {
	/* Re-etablish connection with syslogd every time. */
	openlog("init", 0, LOG_DAEMON);
	syslog(LOG_INFO, buf);
	/* closelog(); NOT needed with recent libc's. */
  }
#endif

  /* And log to the console. */
  if (loglevel & L_CO) {
	print("\rINIT: ");
	print(buf);
	print("\r\n");
  }
}

/* See if one character of s2 is in s1 */
int any(char *s1, char *s2)
{
  while(*s2)
	if (strchr(s1, *s2++) != NULL) return(1);
  return(0);
}


/*
 *	Fork and execute.
 *
 *	This function is too long and indents too deep.
 *
 */
int Spawn(CHILD *ch)
{
  char *args[16];		/* Argv array */
  char buf[136];		/* Line buffer */
  int f, st;			/* Scratch variables */
  char *ptr;			/* Ditto */
  time_t t;			/* System time */
  int oldAlarm;			/* Previous alarm value */
  char *proc = ch->process;	/* Command line */
  char i_lvl[] = "RUNLEVEL=x";	/* Runlevel in environment. */
  char i_prev[] = "PREVLEVEL=x";/* Previous runlevel. */
  char i_cons[32];		/* console device. */
  pid_t pid, pgrp;		/* child, console process group. */

  /* Skip '+' if it's there */
  if (proc[0] == '+') proc++;

  ch->flags |= XECUTED;

  if (ch->action == RESPAWN || ch->action == ONDEMAND) {
	/* Is the date stamp from less than 2 minutes ago? */
	time(&t);
	if (ch->tm + TESTTIME > t)
		ch->count++;
	else
		ch->count = 0;
	ch->tm = t;

	/* Do we try to respawn too fast? */
	if (ch->count >= MAXSPAWN) {

	  Log(L_VB, "Id \"%s\" respawning too fast: disabled for %d minutes",
			ch->id, SLEEPTIME / 60);
	  ch->flags &= ~RUNNING;
	  ch->flags |= FAILING;

	  /* Remember the time we stopped */
	  ch->tm = t;

	  /* Try again in 5 minutes */
	  oldAlarm = alarm(0);
	  if (oldAlarm > SLEEPTIME || oldAlarm <= 0) oldAlarm = SLEEPTIME;
	  alarm(oldAlarm);
	  return(-1);
	}
  }

  /* See if there is an "initscript" (except in single user mode). */
  if (access(INITSCRIPT, R_OK) == 0 && runlevel != 'S') {
	/* Build command line using "initscript" */
	args[1] = SHELL;
	args[2] = INITSCRIPT;
	args[3] = ch->id;
	args[4] = ch->rlevel;
	args[5] = "unknown";
	for(f = 0; actions[f].name; f++) {
		if (ch->action == actions[f].act) {
			args[5] = actions[f].name;
			break;
		}
	}
	args[6] = proc;
	args[7] = NULL;
  } else if (any(proc, "~`!$^&*()=|\\{}[];\"'<>?")) {
  /* See if we need to fire off a shell for this command */
  	/* Give command line to shell */
  	args[1] = SHELL;
  	args[2] = "-c";
  	strcpy(buf, "exec ");
  	strcat(buf, proc);
  	args[3] = buf;
  	args[4] = NULL;
  } else {
	/* Split up command line arguments */
  	strcpy(buf, proc);
  	ptr = buf;
  	for(f = 1; f < 15; f++) {
  		/* Skip white space */
  		while(*ptr == ' ' || *ptr == '\t') ptr++;
  		args[f] = ptr;
  		
		/* May be trailing space.. */
		if (*ptr == 0) break;

  		/* Skip this `word' */
  		while(*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '#')
  			ptr++;
  		
  		/* If end-of-line, break */	
  		if (*ptr == '#' || *ptr == 0) {
  			f++;
  			*ptr = 0;
  			break;
  		}
  		/* End word with \0 and continue */
  		*ptr++ = 0;
  	}
  	args[f] = NULL;
  }
  args[0] = args[1];
  while(1) {
	if ((pid = fork()) == 0) {
		close(0);
		close(1);
		close(2);
		close(PIPE_FD);
		/* Now set RUNLEVEL and PREVLEVEL */
		sprintf(i_cons, "CONSOLE=%s", console_dev);
		i_lvl[9]   = thislevel;
		i_prev[10] = prevlevel;
		putenv(i_lvl);
		putenv(i_prev);
		putenv(i_cons);
		putenv(E_VERSION);

		/*
		 *	In sysinit, boot, bootwait or single user mode:
		 *	any wait-type subprocess gets the console
		 *	as its controlling tty (if not in use yet).
		 */
  		if (strchr("*#sS", runlevel) && ch->flags & WAITING) {
			/*
			 *	We fork once extra. This is so that we can
			 *	wait and change the process group and session
			 *	of the console after exit of the leader.
			 */
			setsid();
			f = serial_open(console_dev, O_RDWR|O_NOCTTY);
			(void)ioctl(f, TIOCSCTTY, 0);
  			dup(f);
  			dup(f);
			if ((pid = fork()) < 0) {
  				Log(L_VB, "cannot fork");
				exit(1);
			}
			if (pid > 0) {
				/*
				 *	Ignore keyboard signals etc.
				 */
				signal(SIGINT, SIG_IGN);
				signal(SIGQUIT, SIG_IGN);
				signal(SIGTSTP, SIG_IGN);
				signal(SIGCHLD, SIG_DFL);

				while(waitpid(pid, &st, 0) != pid)
					;
				pgrp = 0;
				(void)ioctl(f, TIOCGPGRP, &pgrp);
				if (pgrp != getpid())
					exit(0);
				/*
				 *	De-attach session from controlling tty.
				 *	Or actually, the other way around.
				 *	This _without_ SIGHUP etc.
				 */
				if ((pid = fork()) < 0) {
  					Log(L_VB, "cannot fork");
					exit(1);
				}
				if (pid == 0) {
					setsid();
					(void)ioctl(f, TIOCSCTTY, 1);
					exit(0);
				}
				while(waitpid(pid, &st, 0) != pid)
					;
				exit(0);
			}
			/* Set ioctl settings to default ones */
			SetTerm(0);
  		} else {
			if ((f = serial_open(console_dev, O_RDWR|O_NOCTTY)) < 0) {
				Log(L_VB, "open(%s): %s", console_dev,
					sys_errlist[errno]);
				f = open("/dev/null", O_RDWR);
			}
			dup(f);
			dup(f);
			setsid();
		}
  		/* Reset all the signals */
  		for(f = 1; f < _NSIG; f++) signal(f, SIG_DFL);
  		execvp(args[1], args + 1);
		/* Is this a bug in execvp? It does _not_ execute shell
		 * scripts (/etc/rc !), so we try again with 'sh -c exec ...'
		 */
		if (errno == ENOEXEC) {
  			args[1] = SHELL;
  			args[2] = "-c";
  			strcpy(buf, "exec ");
  			strcat(buf, proc);
  			args[3] = buf;
  			args[4] = NULL;
			execvp(args[1], args + 1);
		}
  		Log(L_VB, "cannot execute \"%s\"", args[1]);
  		exit(1);
  	}
#if DEBUG
	Log(L_VB, "Started id %s (pid %d)", ch->id, pid);
#endif

	if (pid == -1) {
		Log(L_VB, "cannot fork, retry..", NULL, NULL);
		Sleep(5);
		continue;
	}
	return(pid);
  }
}

/* Start a child running! */
void StartUp(CHILD *ch)
{
  /* See if it's disabled */
  if (ch->flags & FAILING) return;

  switch(ch->action) {

	case SYSINIT:
  	case BOOTWAIT:
	case WAIT:
	case POWERWAIT:
	case POWERFAILNOW:
	case POWEROKWAIT:
	case CTRLALTDEL:
		if (!(ch->flags & XECUTED)) ch->flags |= WAITING;
	case KBREQUEST:
	case BOOT:
	case POWERFAIL:
	case ONCE:
		if (ch->flags & XECUTED) break;
	case ONDEMAND:
	case RESPAWN:
  		ch->flags |= RUNNING;
  		if ((ch->pid = Spawn(ch)) < 0) break;
		/* Do NOT log if process field starts with '+' */
  		if (ch->process[0] != '+')
			Wtmp("", ch->id, ch->pid, INIT_PROCESS, "");
  		break;
  }
}

/* My version of strtok(3) */
char *getPart(char *str, int tok)
{
  static char *s;
  char *p, *q;

  if (str != NULL) s = str;
  if (s == NULL || *s == 0) return(NULL);
  q = p = s;
  while(*p != tok && *p) p++;
  if (*p == tok) *p++ = 0;
  s = p;
  return(q);
}

/* Read the inittab file. */
void ReadItab(void)
{
  FILE *fp;			/* The INITTAB file */
  char buf[256];		/* Line buffer */
  char err[64];			/* Error message. */
  char *id, *rlevel,
       *action, *process;	/* Fields of a line */
  CHILD *ch, *old, *i;		/* Pointers to CHILD structure */
  CHILD *head = NULL;		/* Head of linked list */
  int lineNo = 0;		/* Line number in INITTAB file */
  int actionNo;			/* Decoded action field */
  int f;			/* Counter */
  int round;			/* round 0 for SIGTERM, round 1 for SIGKILL */
  int foundOne = 0;		/* No killing no sleep */
  int talk;			/* Talk to the user */
  int done = 0;			/* Ready yet? */
  sigset_t omask;		/* For blocking SIGCHLD. */
#ifdef INITLVL
  struct stat st;		/* To stat INITLVL */
#endif

#if DEBUG
  if (newFamily != NULL) {
	Log(L_VB, "PANIC newFamily != NULL");
	exit(1);
  }
  Log(L_VB, "Reading inittab");
#endif

  /* Open INITTAB */
  if ((fp = fopen(INITTAB, "r")) == NULL)
	Log(L_VB, "No inittab file found");

  /* Read INITTAB line by line */
  while(!done) {
	/* Add single user shell entry as last one. */
	if (fp == NULL || fgets(buf, 255, fp) == NULL) {
		done = 1;
		/* See if we have a single user entry. */
		for(old = newFamily; old; old = old->next)
			if (strcmp(old->rlevel, "S") == 0) break;
		if (old == NULL)
			sprintf(buf, "~~:S:wait:%s\n", SHELL);
		else
			continue;
	}
	lineNo++;
	/* Skip comments and empty lines */
	if (buf[0] == '#' || buf[0] == '\n') continue;

	/* Decode the fields */
	id =      getPart(buf,  ':');
	rlevel =  getPart(NULL, ':');
	action =  getPart(NULL, ':');
	process = getPart(NULL, '\n');

	/* Check if syntax is OK. Be very verbose here, to
	 * avoid newbie postings on comp.os.linux.setup :)
	 */
	err[0] = 0;
	if (!id || !*id) strcpy(err, "missing id field");
	if (!rlevel)     strcpy(err, "missing runlevel field");
	if (!process)    strcpy(err, "missing process field");
	if (!action || !*action)
			strcpy(err, "missing action field");
	if (id && strlen(id) > sizeof(utproto.ut_id))
			sprintf(err, "id field too long (max %d characters)",
				(int)sizeof(utproto.ut_id));
	if (rlevel && strlen(rlevel) > 11)
			strcpy(err, "rlevel field too long (max 11 characters)");
	if (process && strlen(process) > 127) strcpy(err, "process field too long");
	if (err[0] != 0) {
		Log(L_VB, "%s[%d]: %s", INITTAB, lineNo, err);
#if DEBUG
		Log(L_VB, "%s:%s:%s:%s", id, rlevel, action, process);
#endif
		continue;
	}
  
	/* Decode the "action" field */
	actionNo = -1;
	for(f = 0; actions[f].name; f++)
		if (strcasecmp(action, actions[f].name) == 0) {
			actionNo = actions[f].act;
			break;
		}
	if (actionNo == -1) {
		Log(L_VB, "%s[%d]: %s: unknown action field",
			INITTAB, lineNo, action);
		continue;
	}

	/* See if the id field is unique */
	for(old = newFamily; old; old = old->next) {
		if(strcmp(old->id, id) == 0 && strcmp(id, "~~")) {
			Log(L_VB, "%s[%d]: duplicate ID field \"%s\"",
				INITTAB, lineNo, id);
			break;
		}
	}
	if (old) continue;

	/* Allocate a CHILD structure */
	if ((ch = malloc(sizeof(CHILD))) == NULL) {
		Log(L_VB, "out of memory");
		continue;
	}
	memset(ch, 0, sizeof(CHILD));

	/* And fill it in. */
	ch->action = actionNo;
	strncpy(ch->id, id, sizeof(utproto.ut_id) + 1); /* Hack for different libs. */
	strncpy(ch->process, process, 127);
	if (rlevel[0]) {
		for(f = 0; f < 11 && rlevel[f]; f++) {
			ch->rlevel[f] = rlevel[f];
			if (ch->rlevel[f] == 's') ch->rlevel[f] = 'S';
		}
		strncpy(ch->rlevel, rlevel, 11);
	} else {
		strcpy(ch->rlevel, "0123456789");
		if (ISPOWER(ch->action))
			strcpy(ch->rlevel, "S0123456789");
	}
	/*
	 * We have the fake runlevel '#' for SYSINIT  and
	 * '*' for BOOT and BOOTWAIT.
	 */
	if (ch->action == SYSINIT) strcpy(ch->rlevel, "#");
	if (ch->action == BOOT || ch->action == BOOTWAIT)
		strcpy(ch->rlevel, "*");

	/* Now add it to the linked list. Special for powerfail. */
	if (ISPOWER(ch->action)) {

		/* Disable by default */
		ch->flags |= XECUTED;

		/* Tricky: insert at the front of the list.. */
		old = NULL;
		for(i = newFamily; i; i = i->next) {
			if (!ISPOWER(i->action)) break;
			old = i;
		}
		/* Now add after entry "old" */
		if (old) {
			ch->next = i;
			old->next = ch;
			if (i == NULL) head = ch;
		} else {
			ch->next = newFamily;
			newFamily = ch;
			if (ch->next == NULL) head = ch;
		}
	} else {
		/* Just add at end of the list */
		if (ch->action == KBREQUEST) ch->flags |= XECUTED;
		ch->next = NULL;
		if (head)
			head->next = ch;
		else
			newFamily = ch;
		head = ch;
	}

	/* Walk through the old list comparing id fields */
	for(old = family; old; old = old->next)
		if (strcmp(old->id, ch->id) == 0) {
			old->new = ch;
			break;
		}
  }
  /* We're done. */
  if (fp) fclose(fp);

  /*
   * Loop through the list of children, and see if they need to
   * be killed. 
   */

#if DEBUG
  Log(L_VB, "Checking for children to kill");
#endif
  for(round = 0; round < 2; round++) {
    talk = 1;
    for(ch = family; ch; ch = ch->next) {
	ch->flags &= ~KILLME;

	/* Is this line deleted? */
	if (ch->new == NULL) ch->flags |= KILLME;

#if 0 /* XXX - This is more compatible with real sysv. */
	/* See if the entry changed. Yes: kill anyway */
	if (ch->new && (strcmp(ch->process, ch->new->process) ||
		ch->action != ch->new->action)) ch->flags |= KILLME;
#endif

	/* Only BOOT processes may live in all levels */
	if (ch->action != BOOT &&
	    strchr(ch->rlevel, runlevel) == NULL) {
		/* Ondemand procedures live always except in single user */
		if (runlevel == 'S' || !(ch->flags & DEMAND))
			ch->flags |= KILLME;
	}

	/* Now, if this process may live note so in the new list */
	if ((ch->flags & KILLME) == 0) {
		ch->new->flags  = ch->flags;
		ch->new->pid    = ch->pid;
		ch->new->exstat = ch->exstat;
		continue;
	}


	/* Is this process still around? */
	if ((ch->flags & RUNNING) == 0) {
		ch->flags &= ~KILLME;
		continue;
	}
#if DEBUG
	Log(L_VB, "Killing \"%s\"", ch->process);
#endif
	switch(round) {
		case 0: /* Send TERM signal */
			if (talk)
				Log(L_CO, "Sending processes the TERM signal");
			kill(-(ch->pid), SIGTERM);
			foundOne = 1;
			break;
		case 1: /* Send KILL signal and collect status */
			if (talk)
				Log(L_CO, "Sending processes the KILL signal");
			kill(-(ch->pid), SIGKILL);
			break;
	}
	talk = 0;
	
	/* And process the next member of our family */
    }
    /* See if we have to wait 5 seconds */
    if (foundOne && round == 0) {
	/* Yup, but check every second if we still have children. */
	for(f = 0; f < sltime; f++) {
		for(ch = family; ch; ch = ch->next) {
			if (!(ch->flags & KILLME)) continue;
			if ((ch->flags & RUNNING) && !(ch->flags & ZOMBIE))
				break;
		}
		if (ch == NULL) {
			/* No running children, skip SIGKILL */
			round = 1;
			foundOne = 0; /* Skip the sleep below. */
			break;
		}
		Sleep(1);
	}
    }
  }

  /*
   *	Now give all processes the chance to die and collect exit statuses.
   */
  if (foundOne) Sleep(1);
  for(ch = family; ch; ch = ch->next)
	if (ch->flags & KILLME) {
		if (!(ch->flags & ZOMBIE))
		    Log(L_CO, "Pid %d [id %s] seems to hang", ch->pid,
				ch->id);
		else {
#if DEBUG
		    Log(L_VB, "Updating utmp for pid %d [id %s]", ch->pid, ch->id);
#endif
		    ch->flags &= ~RUNNING;
		    if (ch->process[0] != '+')
		    	Wtmp("", ch->id, ch->pid, DEAD_PROCESS, NULL);
		}
	}

  /* Both rounds done; clean up the list. */
  omask = sigblock(sigmask(SIGCHLD));
  for(ch = family; ch; ch = old) {
	old = ch->next;
	free(ch);
  }
  family = newFamily;
  for(ch = family; ch; ch = ch->next) ch->new = NULL;
  newFamily = NULL;
  sigsetmask(omask);

#ifdef INITLVL
  /* Dispose of INITLVL file. */
  if (lstat(INITLVL, &st) >= 0 && S_ISLNK(st.st_mode)) {
	/* INITLVL is a symbolic link, so just truncate the file. */
	close(open(INITLVL, O_WRONLY|O_TRUNC));
  } else {
	/* Delete INITLVL file. */
  	unlink(INITLVL);
  }
#endif
#ifdef INITLVL2
  /* Dispose of INITLVL2 file. */
  if (lstat(INITLVL2, &st) >= 0 && S_ISLNK(st.st_mode)) {
	/* INITLVL2 is a symbolic link, so just truncate the file. */
	close(open(INITLVL2, O_WRONLY|O_TRUNC));
  } else {
	/* Delete INITLVL2 file. */
  	unlink(INITLVL2);
  }
#endif
}

/*
 * Walk through the family list and start up children.
 * The entries that do not belong here at all are removed
 * from the list.
 */
void StartEmIfNeeded(void)
{
  CHILD *ch;		/* Pointer to child */
  int delete;		/* Delete this entry from list? */

#if DEBUG
  Log(L_VB, "Checking for children to start");
#endif

  for(ch = family; ch; ch = ch->next) {

#if DEBUG
	if (ch->rlevel[0] == 'C') {
		Log(L_VB, "%s: flags %d", ch->process, ch->flags);
	}
#endif

	/* Are we waiting for this process? Then quit here. */
	if (ch->flags & WAITING) break;

	/* Already running? OK, don't touch it */
	if (ch->flags & RUNNING) continue;

	/* See if we have to start it up */
	delete = 1;
	if (strchr(ch->rlevel, runlevel) ||
	    ((ch->flags & DEMAND) && !strchr("#*Ss", runlevel))) {
		StartUp(ch);
		delete = 0;
	}

	if (delete) {
		/* FIXME: is this OK? */
		ch->flags &= ~(RUNNING|WAITING);
		if (!ISPOWER(ch->action) && ch->action != KBREQUEST)
			ch->flags &= ~XECUTED;
		ch->pid = 0;
	} else
		/* Do we have to wait for this process? */
		if (ch->flags & WAITING) break;
  }
  /* Done. */
}

/*
 * Ask the user on the console for a runlevel
 */
int AskRunLevel()
{
  int lvl = -1;
  char buf[8];
  int fd;


  SetTerm(0);
  /*
   *	FIXME: should we open the tty O_NONBLOCK and CLOCAL mode?
   */
  fd = serial_open(console_dev, O_RDWR|O_NOCTTY);

  if (fd < 0) return('S');

  while(!strchr("0123456789S", lvl)) {
  	write(fd, "\nEnter runlevel: ", 17);
	buf[0] = 0;
  	read(fd, buf, 8);
  	if (buf[0] != 0 && (buf[1] == '\r' || buf[1] == '\n')) lvl = buf[0];
	if (islower(lvl)) lvl = toupper(lvl);
  }
  close(fd);
  return(lvl);
}

/*
 * Search the INITTAB file for the 'initdefault' field, with the default
 * runlevel. If this fails, ask the user to supply a runlevel.
 */
int GetInitDefault(void)
{
  CHILD *ch;
  int lvl = -1;
  char *p;

  /* Look for initdefault. */
  for(ch = family; ch; ch = ch->next)
	if (ch->action == INITDEFAULT) {
		p = ch->rlevel;
		while(*p) {
			if (*p > lvl) lvl = *p;
			p++;
		}
		break;
	}
  /* See if level is valid */
  if (lvl > 0) {
	if (islower(lvl)) lvl = toupper(lvl);
	if (strchr("0123456789S", lvl) == NULL) {
		Log(L_VB, "Initdefault level '%c' is invalid", lvl);
		lvl = 0;
	}
  }
  /* Ask for runlevel on console if needed. */
  if (lvl <= 0) lvl = AskRunLevel();

  /* Log the fact that we have a runlevel now. */
  return(lvl);
}


/*
 *	We got signaled.
 *
 *	Do actions for the new level. If we are compatible with
 *	the "old" INITLVL and arg == 0, try to read the new
 *	runlevel from that file first.
 */
int ReadLevel(int arg)
{
  unsigned char foo = 'X';		/* Contents of INITLVL */
  int st;				/* Sleep time */
  CHILD *ch;				/* Walk through list */
  FILE *fp;				/* File pointer for INITLVL */
  int ok = 1;
  struct stat stt;

#if CLEAN_UTMP
  /* Clean up utmp file first. */
  Wtmp(NULL, "", -2, 0, NULL);
#endif

#ifdef INITLVL
  ok = 0;

  if (arg == 0) {
	fp = NULL;
	if (stat(INITLVL, &stt) != 0 || stt.st_size != 0L)
		fp = fopen(INITLVL, "r");
#ifdef INITLVL2
	if (fp == NULL && (stat(INITLVL2, &stt) != 0 || stt.st_size != 0L))
		fp = fopen(INITLVL2, "r");
#endif
	if (fp == NULL) {
		/* INITLVL file is empty or not there - act as 'init q' */
		Log(L_SY, "Re-reading inittab");
  		return(runlevel);
	}
	ok = fscanf(fp, "%c %d", &foo, &st);
	fclose(fp);
  } else {
	/* We go to the new runlevel passed as an argument. */
	foo = arg;
	ok = 1;
  }
  if (islower(foo)) foo = toupper(foo);
  if (ok == 2) sltime = st;

#endif /* INITLVL */

  if (ok < 1 || ok > 2 || strchr("QS0123456789ABC", foo) == NULL) {
 	Log(L_VB, "bad runlevel: %c", foo);
  	return(runlevel);
  }

  /* Be verbose 'bout it all */
  switch(foo) {
	case 'S':
  		Log(L_VB, "Going single user");
		break;
	case 'Q':
		Log(L_SY, "Re-reading inittab");
		break;
	case 'A':
	case 'B':
	case 'C':
		Log(L_SY, "Activating demand-procedures for '%c'", foo);
		break;
	default:
	  	Log(L_VB, "Switching to runlevel: %c", foo);
  }

  if (foo == 'Q') return(runlevel);

  /* Check if this is a runlevel a, b or c */
  if (strchr("ABC", foo)) {
	if (runlevel == 'S') return(runlevel);

	/* Read inittab again first! */
	ReadItab();

  	/* Mark those special tasks */
	for(ch = family; ch; ch = ch->next)
		if (strchr(ch->rlevel, foo) != NULL ||
		    strchr(ch->rlevel, tolower(foo)) != NULL) {
			ch->flags |= DEMAND;
			ch->flags &= ~XECUTED;
#if DEBUG
			Log(L_VB, "Marking (%s) as ondemand, flags %d",
				ch->id, ch->flags);
#endif
		}
  	return(runlevel);
  }

  /* Store both the old and the new runlevel. */
  Wtmp("runlevel", "~~", foo + 256*runlevel, RUN_LVL, "~");
  thislevel = foo;
  prevlevel = runlevel;
  return(foo);
}


/*
 * Zero out an utmp struct but copy the id field.
 */
void EmptyStruct(struct utmp *oldu, struct utmp *newu)
{
  memset(newu, 0, sizeof(struct utmp));
  strncpy(newu->ut_id, oldu->ut_id, sizeof(newu->ut_id));
  strncpy(newu->ut_line, oldu->ut_line, sizeof(newu->ut_line));
  newu->ut_type = DEAD_PROCESS;
}

/*
 * Log an event ONLY in the wtmp file (reboot, runlevel)
 */
void WtmpOnly(
char *user,			/* name of user */
char *id,			/* inittab ID */
int pid,			/* PID of process */
int type,			/* TYPE of entry */
char *line)			/* Which line is this */
{
  int fd;
  struct utmp utmp;

  if ((fd = open(WTMP_FILE, O_WRONLY|O_APPEND)) < 0) return;

  /* Note if we are going to write a boot record. */
  if (type == BOOT_TIME) wroteReboot++;

  /*
   * See if we need to write a reboot record. The reason that
   * we are being so paranoid is that when we first tried to
   * write the reboot record, /var was possibly not mounted
   * yet. As soon as we can open WTMP we write a delayed boot record.
   */
  if (wroteReboot == 0 && type != BOOT_TIME)
  	WtmpOnly("reboot", "~~", 0, BOOT_TIME, "~");

  /* Zero the fields */
  memset(&utmp, 0, sizeof(utmp));

  /* Enter new fields */
#if defined(__GNU_LIBRARY__) && __GNU_LIBRARY__ >= 6
  gettimeofday(&utmp.ut_tv, NULL);
#else
  time(&utmp.ut_time);
#endif
  utmp.ut_pid  = pid;
  utmp.ut_type = type;
  strncpy(utmp.ut_name, user, sizeof(utmp.ut_name));
  strncpy(utmp.ut_id  , id  , sizeof(utmp.ut_id  ));
  strncpy(utmp.ut_line, line, sizeof(utmp.ut_line));

  write(fd, (char *)&utmp, sizeof(utmp));
  close(fd);
}

/*
 * Log an event into the WTMP and UTMP files.
 *
 * FIXME: we shouldn't zero out other processes' utmp entries. What we
 *        should do to get/update an utmp entry:
 *        o see if an entry with matching ut_id field is present
 *        o if so, re-use it.
 *        o if not, re-open utmp in O_APPEND mode and add new entry.
 *
 * Thanks to Michael Haardt for pointing this out.
 *
 * Also, check if the RUNLEVEL entry is still present and rewrite if
 * it is missing. (People tend to mess up their utmp files).
 *
 */
void Wtmp(
char *user,			/* name of user */
char *id,			/* inittab ID */
int pid,			/* PID of process */
int type,			/* TYPE of entry */
char *line)			/* LINE if used. */
{
  struct utmp utmp;		/* UTMP/WTMP User Accounting */
  struct utmp tmp;		/* Scratch */
  struct utmp empty;		/* Empty utmp struct */
  int fd = -1;			/* File Descriptor for UTMP */
  int fd2;			/* File Descriptor for WTMP */
  int found = 0;		/* Was the record found in UTMP */
  int freeEntry = -1;		/* Was a free entry found during UTMP scan? */
  int matchEntry = -1;		/* Was a matching entry found ? */
  int lineno;			/* Offset into UTMP file */
  static int opened = 0;	/* Already opened /var/run/utmp? */
#if CLEAN_UTMP
  CHILD *ch;			/* Scratch */
#endif

  /* Clear utmp if not yet opened. */
  if (!opened) close(open(UTMP_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0644));

  /* First read the utmp entry for this process */
  if ((fd = open(UTMP_FILE, O_RDWR)) >= 0) {
	lineno = 0;
	opened = 1;
        while (read(fd, (char *) &tmp, sizeof(tmp)) == sizeof(tmp)) {
#if DEBUG
		Log(L_VB, "utmp #%d: pid %d, type %d", lineno / sizeof(tmp),
			tmp.ut_pid, tmp.ut_type);
#endif
		if (user != NULL &&
		    ((tmp.ut_pid == pid && tmp.ut_type != NO_PROCESS) ||
		     (tmp.ut_type == RUN_LVL && type == RUN_LVL))) {
			found = 1;
			memcpy(&utmp, &tmp, sizeof(utmp));
			if (type == DEAD_PROCESS) {
				/* Zero all entries with this pid */
				(void) lseek(fd, (long) lineno, SEEK_SET);
				EmptyStruct(&tmp, &empty);
				write(fd, (char *)&empty, sizeof(struct utmp));
			} else {
				(void) lseek(fd, (long) lineno, SEEK_SET);
				break;
			}
  		}
#if CLEAN_UTMP
		/* Let's check if this process still exists. If it
		 * doesn't, clear it's utmp entry.
		 */
		else if (tmp.ut_type != NO_PROCESS &&
			 tmp.ut_type != RUN_LVL && tmp.ut_pid > 0) {
			/* One of our children? */
			for(ch = family; ch; ch = ch->next)
				if (ch->pid == tmp.ut_pid) break;
			if (ch == NULL && kill(tmp.ut_pid, 0) < 0) {
#if DEBUG
				Log(L_VB, "utmp: cleaning up stale entry (pid %d)",
					tmp.ut_pid);
#endif
				/* Zero the entry with this pid */
				(void) lseek(fd, (long) lineno, SEEK_SET);
				EmptyStruct(&tmp, &empty);
				if (write(fd, (char *)&empty, sizeof(struct utmp))
					!= sizeof(struct utmp)) {
						Log(L_VB, "error writing utmp struct");
						break;
				}
			}
		}
#endif
		/* See if this is a unused entry, save it for later */
		if ((tmp.ut_pid == 0 && tmp.ut_id[0] == 0
			&& tmp.ut_user[0] == 0 && tmp.ut_type != RUN_LVL) || tmp.ut_type == 0)
			if (freeEntry < 0) freeEntry = lineno;

		/* See if this entry is dead and matches our "id", save it for later. */
		if (tmp.ut_type == DEAD_PROCESS && strncmp(tmp.ut_id, id, sizeof(tmp.ut_id)) == 0
			&& tmp.ut_user[0] == 0)
			if (matchEntry < 0) matchEntry = lineno;

		lineno += sizeof(tmp);
	}
#if CLEAN_UTMP
	/* Only a call to clean the utmp file? */
	if (user == NULL) {
		(void) close(fd);
		return;
	}
#endif
  }

#if DEBUG
  Log(L_VB, "Wtmp(): found=%d, match=%d, free=%d", found, matchEntry, freeEntry);
#endif

  if (!found) { /* Enter some defaults */
	/* Zero the fields */
	memset(&utmp, 0, sizeof(utmp));

	/* Enter new fields */
	strncpy(utmp.ut_name, user, sizeof(utmp.ut_name));
	strncpy(utmp.ut_id  , id  , sizeof(utmp.ut_id  ));
	strcpy (utmp.ut_line, "");

	/* Where to write new utmp record */
	if (matchEntry >= 0) freeEntry = matchEntry;
	if (freeEntry >= 0)
		lseek(fd, (long) freeEntry, SEEK_SET);
  }

  /* Change the values of some fields */
#if defined(__GNU_LIBRARY__) && __GNU_LIBRARY__ >= 6
  gettimeofday(&utmp.ut_tv, NULL);
#else
  time(&utmp.ut_time);
#endif
  utmp.ut_type = type;
  utmp.ut_pid = pid;
  if (line) strncpy(utmp.ut_line, line, sizeof(utmp.ut_line));

  /* Write the utmp record, if needed */
  if (fd >= 0)  {
  	/* DEAD_PROCESS has already been zeroed */
  	if (utmp.ut_type != DEAD_PROCESS)
		(void) write(fd, (char *) &utmp, sizeof(struct utmp));
	(void) close(fd);
  }

  /* Write the wtmp record */
  if ((fd2 = open(WTMP_FILE, O_WRONLY|O_APPEND)) >= 0) {
  	/* See if we need to write a boot record */
	if (wroteReboot == 0 && type != BOOT_TIME) {
  		WtmpOnly("reboot", "~~", 0, BOOT_TIME, "~");
		wroteReboot++;
	}
	/* Set ut_user to 0 if this is a logout record */
	if (utmp.ut_type == DEAD_PROCESS) utmp.ut_name[0] = 0;
	(void) write(fd2, (char *) &utmp, sizeof(struct utmp));
	(void) close(fd2);
  }
}

/*
 * This procedure is called after every signal (SIGHUP, SIGALRM..)
 *
 * Only clear the 'failing' flag if the process is sleeping
 * longer than 5 minutes, or inittab was read again due
 * to user interaction.
 */
void FailCheck(void)
{
  time_t t;		/* System time */
  CHILD *ch;		/* Pointer to child structure */
  time_t nxtAlrm = 0;	/* When to set next alarm */

  time(&t);

  for(ch = family; ch; ch = ch->next) {

	if (ch->flags & FAILING) {
		/* Can we free this sucker? */
		if (ch->tm + SLEEPTIME < t) {
			ch->flags &= ~FAILING;
			ch->count = 0;
			ch->tm = 0;
		} else {
			/* No, we'll look again later */
			if (nxtAlrm == 0 || ch->tm + SLEEPTIME > nxtAlrm)
				nxtAlrm = ch->tm + SLEEPTIME;
		}
	}
  }
  if (nxtAlrm) {
	nxtAlrm -= t;
	if (nxtAlrm < 1) nxtAlrm = 1;
	alarm(nxtAlrm);
  }
}

/* Set all 'Fail' timers to 0 */
void FailCancel(void)
{
  CHILD *ch;

  for(ch = family; ch; ch = ch->next) {
	ch->count = 0;
	ch->tm = 0;
	ch->flags &= ~FAILING;
  }
}

/* Start up powerfail entries. */
void DoPowerFail(int pwrstat)
{
  CHILD *ch;

  /* Tell powerwait & powerfail entries to start up */
  for(ch = family; ch; ch = ch->next) {
	if (pwrstat == 'O') {
		/* The power is good again. */
		if (ch->action == POWEROKWAIT)
			ch->flags &= ~XECUTED;
	} else if (pwrstat == 'L') {
		/* Low battery, shut down now. */
		if (ch->action == POWERFAILNOW)
			ch->flags &= ~XECUTED;
	} else {
		/* Power is failing */
		if (ch->action == POWERFAIL || ch->action == POWERWAIT)
			ch->flags &= ~XECUTED;
	}
  }
}

#if NEWINIT

/*
 * We got a change runlevel request through the
 * init.fifo. Process it.
 */
void FifoNewLevel(int level)
{
  int oldlevel;
#if CHANGE_WAIT
  CHILD *ch;
#endif

  if (level == runlevel) return;

#if CHANGE_WAIT
  /* Are we waiting for a child? */
  for(ch = family; ch; ch = ch->next)
	if (ch->flags & WAITING) break;
  if (ch == NULL)
#endif
  {
	/* We need to go into a new runlevel */
	oldlevel = runlevel;
	runlevel = ReadLevel(level);
	if (oldlevel != 'S' && runlevel == 'S') SetTerm(0);
	if (runlevel == '6' || runlevel == '0' || runlevel == '1') SetTerm(0);
	ReadItab();
	FailCancel();
	setproctitle("init [%c]", runlevel);
  }
}

/*
 * Read from the init FIFO. Processes like telnetd and rlogind can
 * ask us to create login processes on their behalf.
 *
 * FIXME: this needs to be finished. NOT that it is buggy, but we need
 *	  to add the telnetd/rlogind stuff so people can start using it.
 */
void CheckInitFifo(void)
{
  static int pipe_fd = -1;
  struct init_request request;
  int n;
  fd_set fds;
  int quit = 0;

  /* Try to open /dev/initctl */
  if (pipe_fd < 0) {
	if ((pipe_fd = open(INIT_FIFO, O_RDWR|O_NONBLOCK)) >= 0) {
		/* Don't use fd's 0, 1 or 2. */
		(void) dup2(pipe_fd, PIPE_FD);
		close(pipe_fd);
		pipe_fd = PIPE_FD;
	}
  }

  /* Wait for data to appear, _if_ the pipe was opened. */
  if (pipe_fd >= 0) while(!quit) {

	/* Do select, return on EINTR. */
	FD_ZERO(&fds);
	FD_SET(pipe_fd, &fds);
	n = select(pipe_fd + 1, &fds, NULL, NULL, NULL);
	if (n <= 0) {
		if (errno == EINTR) return;
		continue;
	}

	/* Read the data, return on EINTR. */
	n = read(pipe_fd, &request, sizeof(request));
	if (n == 0) {
		/*
		 *	End of file. This can't happen under Linux (because
		 *	the pipe is opened O_RDWR - see select() in the
		 *	kernel) but you never know...
		 */
		close(pipe_fd);
		pipe_fd = -1;
		return;
	}
	if (n <= 0) {
		if (errno == EINTR) return;
		Log(L_VB, "error reading initrequest");
		continue;
	}

	/* Process request. */
	if (request.magic != INIT_MAGIC || n != sizeof(request)) {
		Log(L_VB, "got bogus initrequest");
		continue;
	}
	switch(request.cmd) {
		case INIT_CMD_RUNLVL:
			sltime = request.sleeptime;
			FifoNewLevel(request.runlevel);
			quit = 1;
			break;
		case INIT_CMD_POWERFAIL:
			sltime = request.sleeptime;
			DoPowerFail('F');
			quit = 1;
			break;
		case INIT_CMD_POWERFAILNOW:
			sltime = request.sleeptime;
			DoPowerFail('L');
			quit = 1;
			break;
		case INIT_CMD_POWEROK:
			sltime = request.sleeptime;
			DoPowerFail('O');
			quit = 1;
			break;
		default:
			Log(L_VB, "got unimplemented initrequest.");
			break;
	}
  }

  /* We come here if the pipe couldn't be opened. */
  if (pipe_fd < 0) pause();

}
#endif


/*
 * This function is used in the transition
 * sysinit (-> single user) boot -> multi-user.
 */
void BootTransitions()
{
  CHILD *ch;
  static int did_boot = 0;
  static int newlevel = 0;
  static int loglevel;
  static int oldlevel;

  /* Check if there is something to wait for! */
  for( ch = family; ch; ch = ch->next )
	if ((ch->flags & RUNNING) && ch->action != BOOT) break;
     
  if (ch == NULL) {
	/* No processes left in this level, proceed to next level. */
	loglevel = -1;
	oldlevel = 'N';
	switch(runlevel) {
		case '#': /* SYSINIT -> BOOT */
#if DEBUG
			Log(L_VB, "SYSINIT -> BOOT");
#endif
			/* Save tty modes. */
			SetTerm(1);

			/* Write a boot record. */
			wroteReboot = 0;
			WtmpOnly("reboot", "~~", 0, BOOT_TIME, "~");

  			/* Get our run level */
  			newlevel = dflLevel ? dflLevel : GetInitDefault();
			if (newlevel == 'S') {
				runlevel = newlevel;
				/* Not really 'S' but show anyway. */
				setproctitle("init [S]");
			} else
				runlevel = '*';
			break;
		case '*': /* BOOT -> NORMAL */
#if DEBUG
			Log(L_VB, "BOOT -> NORMAL");
#endif
			/* Save tty modes. */
			SetTerm(1);

			if (runlevel != newlevel)
				loglevel = newlevel;
			runlevel = newlevel;
			did_boot = 1;
			break;
		case 'S': /* Ended SU mode */
		case 's':
#if DEBUG
			Log(L_VB, "END SU MODE");
#endif
			/* Save tty modes. */
			SetTerm(1);

			newlevel = GetInitDefault();
			if (!did_boot && newlevel != 'S')
				runlevel = '*';
			else {
				if (runlevel != newlevel)
					loglevel = newlevel;
				runlevel = newlevel;
				oldlevel = 'S';
			}
			for(ch = family; ch; ch = ch->next)
			    if (strcmp(ch->rlevel, "S") == 0)
				ch->flags &= ~(FAILING|WAITING|XECUTED);
			break;
		default:
			loglevel = -1;
			Log(L_VB, "no more processes left in this runlevel");
			if (got_signals == 0) {
#if NEWINIT
				CheckInitFifo();
#else
				pause();
#endif
			}
			break;
	}
	if (loglevel > 0) {
		Log(L_VB, "Entering runlevel: %c", runlevel);
		Wtmp("runlevel", "~~", runlevel + 256 * oldlevel, RUN_LVL, "~");
		thislevel = runlevel;
		prevlevel = oldlevel;
		setproctitle("init [%c]", runlevel);
	}
  }
}

/*
 * Init got hit by a signal. See which signal it is,
 * and act accordingly.
 */
void ProcessSignals()
{
  int pwrstat;
  int oldlevel;
  int fd;
  CHILD *ch;
  char c;

  if (sigismember(&got_signals, SIGPWR)) {
#if DEBUG
	Log(L_VB, "got SIGPWR");
#endif
	/* See _what_ kind of SIGPWR this is. */
	pwrstat = 0;
	if ((fd = open(PWRSTAT, O_RDONLY)) >= 0) {
		c = 0;
		read(fd, &c, 1);
		pwrstat = c;
		close(fd);
		unlink(PWRSTAT);
		DoPowerFail(pwrstat);
	}
	sigdelset(&got_signals, SIGPWR);
  }

  if (sigismember(&got_signals, SIGINT)) {
#if DEBUG
	Log(L_VB, "got SIGINT");
#endif
	/* Tell ctrlaltdel entry to start up */
	for(ch = family; ch; ch = ch->next)
		if (ch->action == CTRLALTDEL)
			ch->flags &= ~XECUTED;
	sigdelset(&got_signals, SIGINT);
  }

  if (sigismember(&got_signals, SIGWINCH)) {
#if DEBUG
	Log(L_VB, "got SIGWINCH");
#endif
	/* Tell kbrequest entry to start up */
	for(ch = family; ch; ch = ch->next)
		if (ch->action == KBREQUEST)
			ch->flags &= ~XECUTED;
	sigdelset(&got_signals, SIGWINCH);
  }

  if (sigismember(&got_signals, SIGALRM)) {
#if DEBUG
	Log(L_VB, "got SIGALRM");
#endif
	/* The timer went off: check it out */
	sigdelset(&got_signals, SIGALRM);
  }

  if (sigismember(&got_signals, SIGCHLD)) {
#if DEBUG
	Log(L_VB, "got SIGCHLD");
#endif
	/* First set flag to 0 */
	sigdelset(&got_signals, SIGCHLD);

	/* See which child this was */
	for(ch = family; ch; ch = ch->next)
	    if (ch->flags & ZOMBIE) {
#if DEBUG
		Log(L_VB, "Child died, PID= %d", ch->pid);
#endif
		ch->flags &= ~(RUNNING|ZOMBIE|WAITING);
		if (ch->process[0] != '+')
			Wtmp("", ch->id, ch->pid, DEAD_PROCESS, NULL);
	    }

  }

  if (sigismember(&got_signals, SIGHUP)) {
#if DEBUG
	Log(L_VB, "got SIGHUP");
#endif
#if CHANGE_WAIT
	/* Are we waiting for a child? */
	for(ch = family; ch; ch = ch->next)
		if (ch->flags & WAITING) break;
	if (ch == NULL)
#endif
	{
		/* We need to go into a new runlevel */
		oldlevel = runlevel;
#ifdef INITLVL
		runlevel = ReadLevel(0);
#endif
		if (oldlevel != 'S' && runlevel == 'S') SetTerm(0);
		if (runlevel == '6' || runlevel == '0' || runlevel == '1') SetTerm(0);
		ReadItab();
		FailCancel();
		setproctitle("init [%c]", runlevel);
		sigdelset(&got_signals, SIGHUP);
	}
  }
}

/*
 * The main loop
 */ 
int InitMain()
{
  int f, st;
  CHILD *ch;
  struct sigaction sa;
  char *s;

#if INITDEBUG
  /*
   * Fork so we can debug the init process.
   */
  if ((f = fork()) > 0) {
	while(wait(&st) != f)
		;
	write(1, "PRNT: init killed.\r\n", 20);
	while(1) pause();
  }
#endif

#ifdef ROOTFS
  /* Close all file descriptors. */
  close(0);
  close(1);
  close(2);

  /* Mount new root file system on /root */
  if (mount(ROOTFS, "/root", "ext2", 0, 0) < 0) {
	Log(L_VB, "mount(%s, /root): %s", ROOTFS, sys_errlist[errno]);
	while(1) pause();
  }

  /* Now do the chroot (needs kernel patch). */
  if (chroot("/root") < 0) {
	Log(L_VB, "chroot(/root): %s", sys_errlist[errno]);
	while(1) pause();
  }
#endif

  /* Tell the kernel to send us SIGINT when CTRL-ALT-DEL is pressed */
  init_reboot(BMAGIC_SOFT);

  /* Tell the kernel that we want to handle keyboard signals. */
#ifdef KDSIGACCEPT
  if ((f = open(VT_MASTER, O_RDWR | O_NOCTTY)) >= 0) {
  	(void) ioctl(f, KDSIGACCEPT, SIGWINCH);
	close(f);
  } else
  	(void) ioctl(0, KDSIGACCEPT, SIGWINCH);
#endif

  /* Set up signals */
  for(f = 1; f <= _NSIG; f++)
  	signal(f, SIG_IGN);

  SETSIG(sa, SIGALRM,  signal_handler);
  SETSIG(sa, SIGHUP,   signal_handler);
  SETSIG(sa, SIGINT,   signal_handler);
  SETSIG(sa, SIGPWR,   signal_handler);
  SETSIG(sa, SIGWINCH, signal_handler);
  SETSIG(sa, SIGSTOP,  stop_handler);
  SETSIG(sa, SIGTSTP,  stop_handler);
  SETSIG(sa, SIGCONT,  cont_handler);
  SETSIG(sa, SIGSEGV,  segv_handler);

  /* Close whatever files are open, and reset the console. */
  close(0);
  close(1);
  close(2);
  if ((s = getenv("CONSOLE")) != NULL)
	console_dev = s;
  SetTerm(0);
  setsid();

  /* Set default PATH variable (for ksh) */
  if (getenv("PATH") == NULL) putenv(PATH_DFL);

  /* Initialize /var/run/utmp (only works if /var is on root and mounted rw) */
  (void) close(open(UTMP_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0644));

  /* Say hello to the world */
  Log(L_CO, bootmsg);

  /* See if we have to start an emergency shell. */
  if (emerg_shell) {
	SETSIG(sa, SIGCHLD, SIG_DFL);
	if ((f = Spawn(&ch_emerg)) > 0) {
		while(wait(&st) != f)
			;
	}
  }
  SETSIG(sa, SIGCHLD,  chld_handler);

  /* Start normal boot procedure. */
  runlevel = '#';
  ReadItab();
  StartEmIfNeeded();

  while(1) {

     /* See if we need to make the boot transitions. */
     BootTransitions();
#if DEBUG
     Log(L_VB, "InitMain: waiting..");
#endif

     /* Check if there are processes to be waited on. */
     for(ch = family; ch; ch = ch->next)
	if ((ch->flags & RUNNING) && ch->action != BOOT) break;
#if 0
     if (ch == NULL && got_signals == 0)
	Log(L_VB, "no more processes in this runlevel");
#endif

#if CHANGE_WAIT
     /* Wait until we get hit by some signal. */
     while (ch != NULL && got_signals == 0) {
	if (sigismember(&got_signals, SIGHUP)) {
		/* See if there are processes to be waited on. */
		for(ch = family; ch; ch = ch->next)
			if (ch->flags & WAITING) break;
	}
#  if NEWINIT
	if (ch != NULL) CheckInitFifo();
#  else
	if (ch != NULL) pause();
#  endif
     }
#else /* CHANGE_WAIT */
#  if NEWINIT
     if (ch != NULL && got_signals == 0) CheckInitFifo();
#  else
     if (ch != NULL && got_signals == 0) pause();
#  endif
#endif /* CHANGE_WAIT */

     /* Check the 'failing' flags */
     FailCheck();

     /* Process any signals. */
     ProcessSignals();

     /* See what we need to start up (again) */
     StartEmIfNeeded();
     sync();
  }
  /*NOTREACHED*/
}

/*
 * Tell the user about the syntax we expect.
 */
void Usage(char *s)
{
  fprintf(stderr, "Usage: %s 0123456SsQqAaBbCc\n", s);
  exit(1);
}

/*
 * Main entry for init and telinit.
 */
int main(int argc, char *argv[])
{
#if NEWINIT
  struct init_request request;
#endif
  char *p;
  FILE *fp;
  int f, fd;

  /* Get my own name */
  if ((p = strrchr(argv[0], '/')) != NULL)
  	p++;
  else
  	p = argv[0];
  	

  /* See if I want to become "parental guide of all processes" */
#ifndef TEST
  if (getpid() == INITPID || strcmp(p, "init.new") == 0 || strcmp(p, "sh") == 0) {
#else
			   {
#endif
  	/* Check command line arguments */
	maxproclen = strlen(argv[0]) + 1;
  	for(f = 1; f < argc; f++) {
		if (!strcmp(argv[f], "single"))
			dflLevel = 'S';
		else if (!strcmp(argv[f], "-a") || !strcmp(argv[f], "auto"))
			putenv("AUTOBOOT=YES");
		else if (!strcmp(argv[f], "-b") || !strcmp(argv[f], "emergency"))
			emerg_shell = 1;
		else if (strchr("0123456789sS", argv[f][0])
			&& strlen(argv[f]) == 1)
			dflLevel = argv[f][0];
		maxproclen += strlen(argv[f]) + 1;
	}
	maxproclen--;

	/* Start booting. */
	argv0 = argv[0];
	argv[1] = NULL;
	setproctitle("init boot");
	InitMain(dflLevel);
  }

  /* Nope, this is a change-run-level init */
  while((f = getopt(argc, argv, "t:")) != EOF) {
	switch(f) {
		case 't':
			sltime = atoi(optarg);
			break;
		default:
			Usage(p);
			break;
	}
  }

  if (geteuid() != 0) {
	fprintf(stderr, "init: must be superuser.\n");
	exit(1);
  }

  /* Check syntax. */
  if (argc - optind != 1 || strlen(argv[optind]) != 1) Usage(p);
  if (!strchr("0123456789SsQqAaBbCc", argv[optind][0])) Usage(p);

  umask(022);

#if NEWINIT
  /* Open the fifo and write a command. */
  memset(&request, 0, sizeof(request));
  request.magic     = INIT_MAGIC;
  request.cmd       = INIT_CMD_RUNLVL;
  request.runlevel  = argv[optind][0];
  request.sleeptime = sltime;

  /* Make sure we don't hang on opening /etc/init.fifo */
  signal(SIGALRM, signal_handler);
  alarm(3);
  if ((fd = open(INIT_FIFO, O_WRONLY)) >= 0 &&
	    write(fd, &request, sizeof(request)) == sizeof(request)) {
	close(fd);
	alarm(0);
	return 0;
  }
#ifndef INITLVL
  perror(INIT_FIFO);
  exit(1);
#endif
  /* Fallthrough to the old method. */
#endif

#ifdef INITLVL
  /* Now write the new runlevel. */
  if ((fp = fopen(INITLVL, "w")) == NULL) {
	fprintf(stderr, "%s: cannot create %s\n", p, INITLVL);
	exit(1);
  }
  fprintf(fp, "%s %d", argv[optind], sltime);
  fclose(fp);

  /* And tell init about the pending runlevel change. */
  if (kill(INITPID, SIGHUP) < 0) perror(p);

  exit(0);
#endif
}
