/*
 * Init		A System-V Init Clone.
 *
 * Usage:	/etc/init
 *		     init [01234566SsQqAaBbCc]
 *		  telinit [01234566SsQqAaBbCc]
 *
 * Author:	Written by Miquel van Smoorenburg, Februari 1992.
 * 		Conforms to all standards I know of.
 *
 * Copyright:	(C) Miquel van Smoorenburg, miquels@drinkel.nl.mugnet.org
 *
 *		Permission for redistribution granted, if
 *		  1) You do not pretend you have written it
 *		  2) You keep this header intact.
 *		  3) You do not sell it or make any profit of it.
 *
 * Version:	1.0, 01-Feb-92
 *		- Initial version.
 *
 * 		1.1, 30-Apr-92
 *		- Read manual wrong: there is no 'action' field called
 *		  process, but all entries are of type process. Every
 *		  'process' get exec'ed by /bin/sh -c 'exec command'.
 *		- Rapidly respawning processes are caught in the act.
 *		- _SYSV support is really Linux support,
 *		  done by poe@daimi.aau.dk on 25-Mar-92.
 *
 *		1.2, 16-Jun-92
 *		- Bugreport from Michael Haardt ; removed deadlock
 *		  and added 'waitpid' instead of 'wait' for SYSV.
 *
 *		1.3, 05-Jul-92
 *		- Got a 386, so installed Linux. Added 'soft' reboot
 *		  to be default under linux. Fixed some typos.
 *
 *		2.0 08-Dec-92
 *		- Rewrote the code totally, so started with a new
 *		  version number.
 *		- Dropped Minix support, this code now is Linux - specific.
 *		- With TEST switch on, this init & telinit can
 *		  run standalone for test purposes.
 */

#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <termios.h>
#include <time.h>
#include <utmp.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>

#define DEBUG	   0				/* Debug code off */
#define TEST	   0				/* Test mode off */
#define SHELL	   "/bin/sh"			/* Shell to run commands */
#define INITLVL	   "/etc/initrunlvl"		/* Used with telinit */
#define INITPID	   1				/* pid of first process */
#define SOME_USER  0				/* 2 for password in SU mode */
#define CONSOLE    "/dev/console"		/* For diagnostics & input */
#define INITTAB    "/etc/inittab"		/* the inittab to use */

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

/* To keep this thing less system dependant, check some things */
#ifndef UTMP
#  ifdef UTMP_FILE
#    define UTMP UTMP_FILE		/* The real name */
#    define WTMP WTMP_FILE
#  else
#    define UTMP "/etc/utmp"
#    define WTMP "/usr/adm/wtmp"
#  endif
#endif
#ifndef NO_PROCESS
#  define NO_PROCESS 0
#endif
#ifndef _NSIG
#  define _NSIG NSIG
#endif

#if TEST
#  undef UTMP
#  undef WTMP
#  undef INITLVL
#  undef CONSOLE
#  undef INITTAB
#  undef INITPID
#  define UTMP		"/project/init/utmp"
#  define WTMP		"/project/init/wtmp"
#  define INITLVL 	"/project/init/initrunlvl"
#  define CONSOLE	"/dev/tty5"
#  define INITTAB	"/project/init/inittab.tst"
#  define INITPID	"/project/init/initpid"
#endif

/*
 * Linux does not have SIGPWR defined, but SIGINT means nearly the
 * same - the system must go down rapidly (SIGINT == CTRL-ALT-DEL)
 */
#ifndef SIGPWR
#  define SIGPWR SIGINT
#endif

/* Version information */
char *Version = "@(#) init 2.0 08-12-92 MvS";
char *bootmsg = "version 2.0 booting";

/* Information about a process in the in-core inittab */
typedef struct _child_ {
  int flags;			/* Status of this entry */
  int pid;			/* Pid of this process */
  time_t tm;			/* When respawned last */
  int count;			/* Times respawned in the last 2 minutes */
  char id[4];			/* 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_ *old;		/* Old entry (before inittab re-read) */
  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 */

char runlevel = 'S';		/* The current run level */
char lastlevel = 0;		/* The previous run level */
int got_hup = 0;		/* Set if we received the SIGHUP signal */
int got_alrm = 0;		/* Set if we received the SIGALRM signal */
int got_pwr = 0;		/* Set if we received the SIGPWR signal */
int got_chld = 0;		/* Set if we received the SIGCHLD signal */
int got_cont = 0;		/* Set if we received the SIGCONT signal */
int powerPanic = 0;		/* Set if the power is failing */
int sys_level;			/* Special levels */
int endSu = 0;			/* End single user mode? */

/* Two forward declarations */
void Wtmp(char *user, char *id, int pid, int type);
void WtmpOnly(char *user, char *id, int pid, int type, char *line);

/* 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 OFF			8
#define	ONDEMAND		9
#define	INITDEFAULT	       10
#define SYSINIT		       11

/* 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 */

/* Special runlevels */
#define SYS_LEVEL		1	/* The pre - boot stage */
#define BOOT_LEVEL		2	/* We're booting */
#define NORMAL_LEVEL		3	/* And running */

/* 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,
  "powerwait",	POWERWAIT,
  "off",	OFF,
  "ondemand",	ONDEMAND,
  "initdefault",INITDEFAULT,
  "sysinit",	SYSINIT,
  NULL,		0,
};

/*
 * The SIGHUP handler
 */
void hup_handler()
{
  signal(SIGHUP, hup_handler);
  got_hup = 1;
}

#ifdef SIGPWR
/*
 * The SIGPWR handler
 */
void pwr_handler()
{
  signal(SIGPWR, pwr_handler);
  got_pwr = 1;
}
#endif

/*
 * The SIGCHLD handler
 */
void chld_handler()
{
  signal(SIGCHLD, chld_handler);
  got_chld = 1;
}

/*
 * The SIGALRM handler
 */
void alrm_handler()
{
  signal(SIGALRM, alrm_handler);
  got_alrm = 1;
}

/*
 * 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()
{
  signal(SIGCONT, cont_handler);
  got_cont = 1;
}

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

/* Set terminal settings to reasonable defaults */
void SetTerm(void)
{
  struct termios tty;

  /* Get old settings */
  ioctl(0, TCGETS, &tty);

  /* 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;
  tty.c_cflag = HUPCL|CS8|B9600;
  /* FIXME: should we set C_LINE here? */

  /* 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;

  ioctl(0, TCSETS, &tty);
}

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

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

  /* Wait 'sec' seconds */
  if (verbose) printf("\r");
  for(f = 0; f < sec; f++) {
	if (verbose) {
		printf(".");
		fflush(stdout);
	}
	got_alrm = 0;
	alarm(1);
	while(got_alrm == 0) pause();
  }
  if (verbose) printf("\r\n");

  /* Reset old values of got_alrm flag and the timer */
  got_alrm = ga;
  if (oldAlrm) alarm(oldAlrm - sec > 0 ? oldAlrm - sec : 1);
}


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

  sprintf(buf, "\rINIT: %s\r\n", s);
  va_start(va_alist, s);
  vfprintf(stdout, buf, va_alist);
  va_end(va_alist);
}

/* Warn the user */
void Warning(char *s, ...)
{
  va_list va_alist;
  char buf[256];

  sprintf(buf, "\rINIT: Warning: %s\r\n", s);
  va_start(va_alist, s);
  vfprintf(stdout, buf, va_alist);
  va_end(va_alist);
}

#if DEBUG
/* Warn the user */
void Panic(char *s, ...)
{
  va_list va_alist;
  char buf[256];

  sprintf(buf, "\rINIT: PANIC: %s\r\n", s);
  va_start(va_alist, s);
  vfprintf(stdout, buf, va_alist);
  va_end(va_alist);
  exit(1);
}
#endif

/* 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.
 */
int Spawn(CHILD *ch)
{
  char *args[16];		/* Argv array */
  char buf[136];		/* Line buffer */
  int f, pid;			/* Scratch variables */
  char *ptr;			/* Ditto */
  time_t t;			/* System time */
  int oldAlarm;			/* Previous alarm value */

  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) {

	  Warning("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 we need to fire off a shell for this command */
  if (any(ch->process, "~`!$^&*()=|\\{}[];\"'<>?")) {
  	/* Give command line to shell */
  	args[1] = SHELL;
  	args[2] = "-c";
  	strcpy(buf, "exec ");
  	strcat(buf, ch->process);
  	args[3] = buf;
  	args[4] = NULL;
  } else {
	/* Split up command line arguments */
  	strcpy(buf, ch->process);
  	ptr = buf;
  	for(f = 1; f < 15; f++) {
  		/* Skip white space */
  		while(*ptr == ' ' || *ptr == '\t') ptr++;
  		args[f] = ptr;
  		
  		/* 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) {
		/* Release from controlling tty and create new process group */
		setsid();
#if DEBUG
		pid = open("/dev/null", O_RDONLY);
		close(pid);
		if (pid != 3 && pid != 0)
			printf("Warning: some fd is still open (%d)\n", pid);
#endif
  		/* The single user entry needs to talk to the console */
  		if (strcmp(ch->id, "~~") == 0) {
			close(0);
			close(1);
			close(2);
			setsid();
  			f = open(CONSOLE, O_RDWR);
  			dup(f);
  			dup(f);
  			setuid(SOME_USER); /* Force su to ask for a password */
			/* Set ioctl settings to default ones */
			SetTerm();
  		}
  		/* Reset all the signals */
  		for(f = 1; f < _NSIG; f++) signal(f, SIG_DFL);
  		execvp(args[1], args + 1);
  		Warning("cannot execute \"%s\"", args[1]);
  		exit(1);
  	}
	if (pid == -1) {
		Warning("cannot fork, retry..", NULL, NULL);
		VerboseSleep(5, 1);
		continue;
	}
	return(pid);
  }
}

/*
 * Start a child running!
 */
void StartUp(ch)
CHILD *ch;
{
#if DEBUG
  printf("Starting id %s\n", ch->id);
#endif

  /* See if it's disabled */
  if (ch->flags & FAILING) return;

  if (sys_level < NORMAL_LEVEL) switch(ch->action) {

	/* Sysinit and boot levels.. */
  	case BOOTWAIT:
  	case BOOT:
  		if (sys_level != BOOT_LEVEL) break;
  	case SYSINIT:
  		if (ch->action == SYSINIT && sys_level != SYS_LEVEL) break;
  		ch->flags |= RUNNING;
  		if ((ch->pid = Spawn(ch)) < 0) break;
  		Wtmp("", ch->id, ch->pid, INIT_PROCESS);
		if (ch->action == BOOT) break;
  		/*
  		 * If we get interrupted by got_hup, that means a change of
  		 * runlevel. I don't know what to do: just wait and then
  		 * change runlevel, or stop waiting and return to the
  		 * main loop. The main loop will spawn all processes for
  		 * the new level anyway, and after that it will discover
  		 * that the runlevel has changed. This looks like the
  		 * thing to do, keeping it atomic.
  		 *
  		 * Anyway, at this stage (boot or sysinit) I don't think
  		 * telinit will be used.
  		 */
		errno = 0;
		while (waitpid(ch->pid, NULL, 0) < 0 && errno == EINTR)
			if (got_pwr) break;
  		if (errno != EINTR) {
			ch->flags &= ~RUNNING;
			Wtmp("", ch->id, ch->pid, DEAD_PROCESS);
		}
  		break;
	default:
		break;
  } else switch(ch->action) {
	/* Normal runtime levels */

  	case ONCE:
  		if (lastlevel == runlevel) break;
  	case POWERFAIL:
  		if (ch->action == POWERFAIL && !powerPanic) break;
  	case ONDEMAND:
		if (runlevel == 'S') break;
  	case RESPAWN:
  		ch->flags |= RUNNING;
  		if ((ch->pid = Spawn(ch)) < 0) break;
  		Wtmp("", ch->id, ch->pid, INIT_PROCESS);
  		break;
  	case POWERWAIT:
  		if (!powerPanic) break;
  	case WAIT:
  		if (ch->action == WAIT && runlevel == lastlevel &&
			strcmp(ch->id, "~~") != 0) break;
  		ch->flags |= RUNNING;
  		if ((ch->pid = Spawn(ch)) < 0) break;
  		Wtmp("", ch->id, ch->pid, INIT_PROCESS);
  		/*
  		 * If we get interrupted by got_hup, that means a change of
  		 * runlevel. I don't know what to do: just wait and then
  		 * change runlevel, or stop waiting and return to the
  		 * main loop. The main loop will spawn all processes for
  		 * the new level anyway, and after that it will discover
  		 * that the runlevel has changed. This looks like the
  		 * thing to do, keeping it atomic.
  		 */
		errno = 0;
		while (waitpid(ch->pid, NULL, 0) < 0 && errno == EINTR)
			if (got_hup || got_pwr) break;
  		if (errno != EINTR) {
  			ch->flags &= ~RUNNING;
			Wtmp("", ch->id, ch->pid, DEAD_PROCESS);
  		}
  		break;
  }
}

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

  if (str != NULL) s = str;
  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 *id, *rlevel, *action, *process; /* Fields of a line */
  CHILD *ch, *old;			/* Pointer to CHILD structure */
  CHILD *head;				/* Head of linked list */
  int lineNo = 0;			/* Line number in INITTAB file */
  int actionNo;				/* Decoded action field */
  int f;				/* Counter */

#if DEBUG
  if (newFamily != NULL) Panic("newFamily != NULL");
#endif

  /* Cheat, and initialize the first entry of the list */
  if ((ch = malloc(sizeof(CHILD))) != NULL) {
	memset(ch, 0, sizeof(CHILD));
	strcpy(ch->id, "~~");
	strcpy(ch->rlevel, "S");
	strcpy(ch->process, "/bin/su");
	ch->action = RESPAWN;
	newFamily = 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;
			ch->old  = old;
			break;
		}
  } else {
#if DEBUG
	Panic("Out of memory");
#else
	Warning("Out of memory, halted.."); while(1);
#endif
  }
  head = newFamily;

  /* Open INITTAB */
  if ((fp = fopen(INITTAB, "r")) == NULL) {
	Warning("No inittab file found");
	return;
  }

  /* Read INITTAB line by line */
  while(fgets(buf, 255, fp)) {
	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');
	if (!id || !rlevel || !action || !process ||
	    strlen(id) > 2 || strlen(rlevel) > 11 ||
	    strlen(process) > 127) {
		Warning("line %d of %s is incorrect", lineNo, INITTAB);
#if DEBUG
		printf("%s:%s:%s:%s\n", 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) {
		Warning("Invalid action field in line %d of %s",
			lineNo, INITTAB);
		continue;
	}

	/* See if the id field is unique */
	for(old = newFamily; old; old = old->next) {
		if(strcmp(old->id, id) == 0) {
			Warning("dupicate ID field on line %d in %s",
				lineNo, INITTAB);
			break;
		}
	}
	if (old) continue;

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

	/* And fill it in. */
	ch->action = actionNo;
	strncpy(ch->id, id, 3);
	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, "0123456");
		if (ch->action == POWERWAIT || ch->action == POWERFAIL)
			strcpy(ch->rlevel, "S0123456");
	}

	/* Now add it to the linked list */
	ch->next = NULL;
	head->next = 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;
			ch->old  = old;
			break;
		}
  }
  /* We're done. */
  fclose(fp);
}

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

  /* Reset terminal settings */
  SetTerm();

  while(!strchr("0123456S", lvl)) {
  	write(1, "\nEnter runlevel: ", 17);
	buf[0] = 0;
  	read(0, buf, 8);
  	if (buf[0] != 0 && (buf[1] == '\r' || buf[1] == '\n')) lvl = buf[0];
	if (islower(lvl)) lvl = toupper(lvl);
  }
  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;

  for(ch = family; ch; ch = ch->next)
	if (ch->action == INITDEFAULT) {
		p = ch->rlevel;
		while(*p) {
			if (*p > lvl) lvl = *p;
			p++;
		}
		break;
	}
  if (lvl > 0) {
	if (islower(lvl)) lvl = toupper(lvl);
	return(lvl);
  }
  return(AskRunLevel());
}


/*
 * We got signaled: read the new level from INITLVL
 */
void ReadLevel(void)
{
  int fd;				/* Filedescriptor for INITLVL */
  unsigned char foo;			/* Contents of INITLVL */
  CHILD *ch;				/* Walk through list */

  if ((fd = open(INITLVL, O_RDONLY)) < 0) {
  	Warning("cannot open %s", INITLVL);
  	return;
  }

  read(fd, &foo, 1);
  close(fd);
  if (islower(foo)) foo = toupper(foo);

  if (strchr("QS0123456ABC", foo) == NULL) {
  	Warning("bad runlevel: %c", foo);
  	return;
  }

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

  if (foo == 'Q') return;

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

  	/* Start up 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;
#if DEBUG
			printf("Marking (%s) as ondemand, flags %d\n",
				ch->id, ch->flags);
#endif
		}
  	return;
  }

  runlevel = foo;
  WtmpOnly("runlevel", "~~", runlevel, RUN_LVL, "~");
}

/*
 * Loop through the list of children, and see if they need to
 * be killed. 
 */
void KillEmIfNeeded(void)
{
  CHILD *ch;		/* Pointer to this child */
  int round;		/* round 0 for SIGTERM, round 1 for SIGKILL */
  int foundOne = 0;	/* No killing no sleep */
  int status;		/* Status returned by waitpid */
  int talk;		/* Talk to the user */

  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;

	/* 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;

	/* Now see if this entry belongs in this level */
	switch(sys_level) {
		case SYS_LEVEL:
			if (ch->action != SYSINIT) ch->flags |= KILLME;
			break;
		case BOOT_LEVEL:
			if (ch->action != BOOT && ch->action != BOOTWAIT)
				ch->flags |= KILLME;
			break;
		case NORMAL_LEVEL:
			if (runlevel != 'S' && (ch->flags & DEMAND)) break;
			/* Only BOOT processes may live in all levels */
			if (ch->action != BOOT &&
			    strchr(ch->rlevel, runlevel) == NULL)
				ch->flags |= KILLME;
			break;
	}

	/* 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;

		/* Reset respawn count yes or no? */
		if (lastlevel == runlevel && got_hup == 0) {
			ch->new->tm = ch->tm;
			ch->new->count = ch->count;
		}
		continue;
	}

	/* Is this process still around? */
	if ((ch->flags & RUNNING) == 0) {
		ch->flags &= ~KILLME;
		continue;
	}
#if DEBUG
	printf("Killing \"%s\"\n", ch->process);
#endif
	switch(round) {
		case 0: /* Send TERM signal */
			if (talk)
				Say("Sending processes the TERM signal");
			kill(-(ch->pid), SIGTERM);
			foundOne = 1;
			break;
		case 1: /* Send KILL signal and collect status */
			if (talk)
				Say("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 20 seconds */
    if (foundOne && round == 0) VerboseSleep(20, 1);
  }

  /* Now give all processes the chance to die and collect exit statuses */
  /* FIXME: how to give away time slice?? */
  if (foundOne) VerboseSleep(1, 0);
  for(ch = family; ch; ch = ch->next)
	if (ch->flags & KILLME) {
		if (waitpid(ch->pid, &status, WNOHANG) != ch->pid)
		    Warning("Pid %d [id %s] seems to hang", ch->pid,
				ch->id);
		else {
		    Wtmp("", ch->id, ch->pid, DEAD_PROCESS);
		    ch->flags &= ~RUNNING;
		}
	}

  /* Both rounds done; clean up the list. */
  for(ch = family; ch; ch = ch->next) free(ch);
  family = newFamily;
  for(ch = family; ch; ch = ch->next) ch->old = ch->new = NULL;
  newFamily = NULL;

  /* Delete the INITLVL file (if it exists) */
  unlink(INITLVL);
}

/*
 * 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? */

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

	/* See if we have to start it up */
	delete = 1;
	switch(sys_level) {
		case SYS_LEVEL:
			if (ch->action == SYSINIT) {
				StartUp(ch);
				delete = 0;
			}
			break;
		case BOOT_LEVEL:
			if (ch->action == BOOT || ch->action == BOOTWAIT) {
				StartUp(ch);
				delete = 0;
			}
			break;
		case NORMAL_LEVEL:
			if (strchr(ch->rlevel, runlevel) ||
			   (ch->flags & DEMAND)) {
				StartUp(ch);
				delete = 0;
			}
			break;
	}
	if (delete) {
		/* FIXME: is this OK? */
		ch->flags &= ~RUNNING;
		ch->pid = 0;
	}
  }
  /* Done. */
  lastlevel = runlevel;;
}


/*
 * Log an event ONLY in the wtmp file (reboot, runlevel)
 */
void WtmpOnly(user, id, pid, type, line)
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;
  time_t t;

  if ((fd = open(WTMP, O_WRONLY)) < 0) return;
  /* Zero the fields */
  memset(&utmp, 0, sizeof(utmp));

  /* Enter new fields */
  time(&t);
  utmp.ut_time = t;
  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));

  lseek(fd, 0L, SEEK_END);
  write(fd, (char *)&utmp, sizeof(utmp));
  close(fd);
}

/*
 * Log an event into the WTMP and UTMP files.
 */
void Wtmp(user, id, pid, type)
char *user;			/* name of user */
char *id;			/* inittab ID */
int pid;			/* PID of process */
int type;			/* TYPE of entry */
{
  struct utmp utmp;		/* UTMP/WTMP User Accounting */
  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 lineno;			/* Offset into UTMP file */
  time_t t;			/* What's the time? */

  /* First read the utmp entry for this process */
  if ((fd = open(UTMP, O_RDWR)) >= 0) {
	lineno = 0;
        while (read(fd, (char *) &utmp, sizeof(utmp)) == sizeof(utmp)) {
		if (strncmp(utmp.ut_id, id, sizeof(utmp.ut_id)) == 0 &&
		    utmp.ut_type != NO_PROCESS) {
			(void) lseek(fd, (long) lineno, SEEK_SET);
			found++;
			break;
		}
		/* See if this is a free entry, save it for later */
		if (utmp.ut_pid == 0 || utmp.ut_type == 0)
			if (freeEntry < 0) freeEntry = lineno;
		lineno += sizeof(utmp);
	}
  }
  if (!found) { /* Enter some defaults */
	/* Zero the fields */
	memset(&utmp, 0, sizeof(utmp));

	/* Enter new fields */
	utmp.ut_pid  = pid;
	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 (freeEntry >= 0)
		lseek(fd, (long) freeEntry, SEEK_SET);
  }

  /* Change the values of some fields */
  time(&t);
  utmp.ut_type = type;
  utmp.ut_time = t;

  /* Write the wtmp record */
  if ((fd2 = open(WTMP, O_WRONLY)) >= 0) {
	if (lseek(fd2, 0L, SEEK_END) >= 0L)
		(void) write(fd2, (char *) &utmp, sizeof(struct utmp));
	(void) close(fd2);
  }

  /* And write the utmp record, if needed */
  if (fd >= 0)  {
  	/* DEAD_PROCESS makes no sense in /etc/utmp */
  	if (utmp.ut_type == DEAD_PROCESS) {
  		utmp.ut_type = NO_PROCESS;
  		utmp.ut_pid  = 0;
  	}
	(void) write(fd, (char *) &utmp, sizeof(struct utmp));
	(void) close(fd);
  }
}

/*
 * 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()
{
  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;
  }
}

/*
 * (Re-) read the INITTAB file.
 */
void ScanItab(void)
{
  CHILD *ch;

  /* Read the inittab file. If this fails: go single user */
  ReadItab();
  
  if (newFamily->next == NULL && runlevel != 'S') {
	Warning("No inittab file: going single user..");
	runlevel = 'S';
	WtmpOnly("runlevel", "~~", 'S', RUN_LVL, "~");
  }
  KillEmIfNeeded();
  StartEmIfNeeded();

  /* See if there are processes running. If not: go single user */
  for(ch = family; ch; ch = ch->next) if (ch->flags & RUNNING) break;
  if (ch == NULL && endSu == 0 && sys_level == NORMAL_LEVEL) {
	Warning("No processes in this level: going single user..");
	runlevel = 'S';
	WtmpOnly("runlevel", "~~", 'S', RUN_LVL, "~");
	ReadItab();
	KillEmIfNeeded();
	StartEmIfNeeded();
  }
}

/*
 * The main loop
 */ 
int InitMain()
{
  int f, pid, st;
  int did_boot = 0;
  CHILD *ch;

  /* Tell the kernel to send us SIGINT when CTRL-ALT-DEL is pressed */
  reboot(0xfee1dead, 672274793, 0);

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

  signal(SIGALRM, alrm_handler);
  signal(SIGHUP,  hup_handler);
  signal(SIGCHLD, chld_handler);
  signal(SIGSTOP, stop_handler);
  signal(SIGTSTP, stop_handler);
  signal(SIGCONT, cont_handler);
#ifdef SIGPWR
  signal(SIGPWR, pwr_handler);
#endif  

  /* Close whatever files are open, and open 0 1 & 2 */
  close(0);
  close(1);
  close(2);
  open(CONSOLE, O_RDWR);
  dup(0);
  dup(0);
  SetTerm();
  setsid();

  /* Initialize /etc/utmp */
  close(creat(UTMP, 0644));

  Say(bootmsg);

  /* First process entries of type 'sysinit' */
  sys_level = SYS_LEVEL;
  ReadItab();
  KillEmIfNeeded();
  StartEmIfNeeded();

  /* Write a boot record (AFTER sysinit, 'cause sysinit sets the date) */
  WtmpOnly("reboot", "~~", 0, BOOT_TIME, "~");

  /* Get our run level */
  runlevel = GetInitDefault();

  /* Now process boot and bootwait */
  if (runlevel != 'S') {
  	sys_level = BOOT_LEVEL;
	ScanItab();
  	did_boot = 1;
  }
  sys_level = NORMAL_LEVEL;

  /* And go into default run level */
  WtmpOnly("runlevel", "~~", runlevel, RUN_LVL, "~");
  ScanItab();
  Say("Entering runlevel: %c", runlevel);

  /* Now wait for a signal, either:
   * - SIGPWR    Something awful happened
   * - SIGCHLD   A Child died.
   * - SIGALRM   The timer rang.
   * - SIGHUP    New runlevel
   *
   * processed in this order.
   */
  while(1) {
#if DEBUG
     printf("InitMain: waiting..\n");
#endif
     if ((got_pwr | got_chld | got_alrm | got_hup | endSu) == 0) pause();

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

     if (got_pwr) {
#if DEBUG
	printf("got_pwr\n");
#endif
	powerPanic = 1;
	ScanItab();
	powerPanic = 0;
	got_pwr = 0;
	continue;
     }

     if (got_chld) {
#if DEBUG
	printf("got_chld\n");
#endif
	/* The SIGCHLD signal: one of our children died! */
	while((pid = wait4(WAIT_ANY, &st, WNOHANG, NULL)) > 0) {
#if DEBUG
		printf("Child died, PID= %d\n", pid);
#endif
		/* See which child this was */
		for(ch = family; ch; ch = ch->next)
		    if (ch->pid == pid) {
			ch->flags &= ~RUNNING;
			Wtmp("", ch->id, ch->pid, DEAD_PROCESS);
			if (strcmp(ch->id, "~~") == 0) endSu = 1;
			break;
		    }
	}
	got_chld = 0;
     }

     if (got_alrm) {
#if DEBUG
	printf("got_alrm\n");
#endif
	/* The timer went off: check it out */
	got_alrm = 0;
     }

     if (got_hup) {
#if DEBUG
	printf("got_hup\n");
#endif
	/* Cancel all 'Fail' timers */
	FailCancel();

	/* We need to go into a new runlevel */
	ReadLevel();
	got_hup = 0;
     }
     /* See if this was the single user entry */
     if (endSu && runlevel == 'S') {
#if DEBUG
	printf("endSu\n");
#endif
	runlevel = AskRunLevel();
	if (runlevel != 'S' && did_boot == 0) {
		sys_level = BOOT_LEVEL;
		ScanItab();
		sys_level = NORMAL_LEVEL;
		did_boot++;
	}
	Say("New runlevel: %c", runlevel);
	WtmpOnly("runlevel", "~~", runlevel, RUN_LVL, "~");
     }
     endSu = 0;

     /* Re read INITTAB and start / stop what is needed */
     ScanItab();
     sync();
  }

}

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

/*
 * Main entry for init and telinit.
 */
int main(int argc, char **argv)
{
  char *p;
  int fd;
  struct stat st;
#if TEST
  int pid;
  FILE *fp;
#endif

  /* Get my own name */
  if (p = strrchr(argv[0], '/'))
  	p++;
  else
  	p = argv[0];
  	
  /* See if I want to become "father of all processes" */
#if TEST
  if (strcmp(p, "init") == 0 && argc == 1) {

	if ((fp = fopen(INITPID, "w")) != NULL) {
		fprintf(fp, "%d", getpid());
		fclose(fp);
	} else {
		fprintf(stderr, "%s: cannot open %s\n", p, INITPID);
		exit(1);
	}
	InitMain();
  }
#else
  if (getpid() == INITPID) InitMain();
#endif

  /* Nope, this is a change-run-level init */
  if (argc != 2 || strlen(argv[1]) != 1) Usage(p);
  if (!strchr("0123456SsQqAaBbCc", argv[1][0])) Usage(p);

#if TEST
  if ((fp = fopen(INITPID, "r")) == NULL) {
	fprintf(stderr, "%s: cannot open %s\n", p, INITPID);
	exit(1);
  }
  fscanf(fp, "%d", &pid);
  fclose(fp);
#endif
  if ((fd = open(INITLVL, O_WRONLY | O_CREAT, 0644)) < 0) {
	fprintf(stderr, "%s: cannot create %s\n", p, INITLVL);
	exit(1);
  }
  write(fd, argv[1], 1);
  close(fd);
#if TEST
  if (kill(pid, SIGHUP) < 0) perror(p);
#else
  if (kill(INITPID, SIGHUP) < 0) perror(p);
#endif
  /* Now wait until the INITLVL file is gone */
  while(stat(INITLVL, &st) == 0) sleep(1);

  exit(0);
}
