/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: init.c,v 1.26.2.1 92/06/24 13:34:47 marker Exp $"

/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/


/*	"init" is the general process spawning program.  It reads	*/
/*	/etc/inittab for a script.					*/
/*									*/
/*	In case of bugs, there are four flavors of debug available.	*/
/*									*/
/*	UDEBUG		Will generate a version of "init" that can	*/
/*			be run as a user process.  In this form,	*/
/*			certain signals will cause core dumps and	*/
/*			and a file called "debug" is written in the	*/
/*			directory where "init" was started.  It also	*/
/*			reads the local directory for utmp, inittab	*/
/*			and the other files it usually gets from	*/
/*			/etc.  It also uses /dev/sysconx and		*/
/*			/dev/systtyx instead of /dev/syscon and		*/
/*			/dev/systty.					*/
/*									*/
/*	DEBUG		Generates an "init" which runs in the usual	*/
/*			way, but generates a file, /etc/debug, with	*/
/*			information about process removal, level	*/
/*			changes, and accounting.			*/
/*									*/
/*	DEBUG1		This symbol adds more debug to what would be	*/
/*			generated by DEBUG or UDEBUG.  It has		*/
/*			detailed information about each process		*/
/*			spawned from inittab.  DEBUG1 by itself is	*/
/*			equivalent to DEBUG and DEBUG1.  It can be	*/
/*			added to UDEBUG to get a user process version.	*/
/*									*/
/*	ACCTDEBUG	Generate debug from the accounting program	*/
/*			only.						*/

#ifdef _RISCOS
#define SVR4_CODE	/* additions made for SVR4 compatibility */
#endif

#ifdef	ACCTDEBUG
#define	DEBUGGER
#endif

#ifdef	DEBUG
#ifndef	DEBUGGER
#define	DEBUGGER
#endif
#endif

#ifdef	UDEBUG
#ifndef	DEBUG
#define	DEBUG
#endif
#ifndef	ACCTDEBUG
#define	ACCTDEBUG
#endif
#ifndef	DEBUGGER
#define	DEBUGGER
#endif
#endif

#ifdef	DEBUG1
#ifndef	DEBUG
#define	DEBUG
#endif
#ifndef	ACCTDEBUG
#define	ACCTDEBUG
#endif
#ifndef	DEBUGGER
#define	DEBUGGER
#endif
#endif

#define	NOSIGMASK		/* There is no sigmask system call yet. */

#ifndef		CBUNIX
#include	<sys/types.h>
#endif
#include	<sys/param.h>	/* included for NPROC & NOFILE */
#include	<signal.h>	/* Fix param.h not to duplicate */
#include	<stdio.h>
#ifdef SVR4_CODE
#include	"utmpx.h"
#else
#include	"utmp.h"
#endif
#include	<errno.h>
#ifndef		CBUNIX
#include	<termio.h>
#else
#include	<sys/ioctl.h>
#endif
#include	<ctype.h>
#include	<sys/stat.h>
#include	<fcntl.h>
#include	<sys/sysmips.h>

#if	_RISCOS
#include	<bsd/syslog.h>
#endif	/* _RISCOS */


#undef	sleep

#define	fioctl(p,sptr,cmd)	ioctl(fileno(p),sptr,cmd)

#define	TRUE	1
#define	FALSE	0
#define	FAILURE	-1

/*	SLEEPTIME	The number of seconds "init" sleeps between	*/
/*			wakeups if nothing else requires this "init"	*/
/*			wakeup.						*/

#define	SLEEPTIME	5*60

/*	MAXCMDL		The maximum length of a command string in	*/
/*			/etc/inittab.					*/

#define	MAXCMDL	4096

/*	EXEC		The length of the prefix string added to all	*/
/*			commands found in /etc/inittab.			*/

#define	EXEC	(sizeof("exec ") - 1)

/*	TWARN		The amount of time between warning signal,	*/
/*			SIGTERM, and the fatal kill signal, SIGKILL.	*/

#define	TWARN	5

/*	WARNFREQUENCY	The number of consecutive failures to find an	*/
/*			empty slot in "init's" internal "proc_table"	*/
/*			before another error message will be generated.	*/

#define	WARNFREQUENCY	25

#define	id_eq(x,y)	(( x[0] == y[0] && x[1] == y[1] && x[2] == y[2]\
			    && x[3] == y[3] ) ? TRUE : FALSE)

#ifdef UDEBUG

int SPECIALPID;	/* Any pid can be made special for debugging */

#else

/* Normally the special pid is process 1 */
#define	SPECIALPID	1

#endif

/*	Correspondence of signals to init actions.			*/

#define LVLQ	SIGHUP
#define	LVL0	SIGINT
#define	LVL1	SIGQUIT
#define	LVL2	SIGILL
#define	LVL3	SIGTRAP
#define	LVL4	SIGIOT
#define	LVL5	SIGEMT
#define	LVL6	SIGFPE
#define	SINGLE_USER	SIGBUS
#define	LVLa	SIGSEGV
#define	LVLb	SIGSYS
#define	LVLc	SIGPIPE

/*	Bit Mask for each level.  Used to determine legal levels.	*/

#define	MASK0	01
#define	MASK1	02
#define	MASK2	04
#define	MASK3	010
#define	MASK4	020
#define	MASK5	040
#define	MASK6	0100
#define	MASKSU	0200
#define	MASKa	0400
#define	MASKb	01000
#define	MASKc	02000

#ifdef _RISCOS

int	nproc;
#undef NPROC
#define NPROC	nproc
#ifndef DEFAULT_NPROC
#define DEFAULT_NPROC 300
#endif /* DEFAULT_NPROC */
#ifndef MIN_NPROC
#define MIN_NPROC 100
#endif /* MIN_NPROC */
#ifndef MAX_NPROC
#define MAX_NPROC 10000
#endif /* MAX_NPROC */
#define NPROC_NAME "_riscos_nproc"
#include <bsd43/sys/syscall.h>
#include <bsd43/sys/sysmips.h>
#include <sys/mipskopt.h>
#define mipskopt(x,y,z) syscall(BSD43_SYS_sysmips,BSD43_MIPS_KOPT,(x),(y),(z))
extern char *calloc();

#else /* _RISCOS */

#ifndef	NPROC
#define	NPROC	100
#endif

#endif /* _RISCOS */

/*	Legal action field values.					*/

	/* Kill process if on, otherwise ignore */
#define	OFF		0
	/* Continually restart process when it dies */
#define	RESPAWN		1
	/* Respawn for a,b,c type processes */
#define	ONDEMAND	RESPAWN
	/* Start process.  Do not respawn when dead */
#define	ONCE		2
	/* Perform once and wait to complete */
#define	WAIT		3
	/* Start at boot time only */
#define	BOOT		4
	/* Start at boot time and wait for complete */
#define	BOOTWAIT	5
	/* Start on powerfail */
#define	POWERFAIL	6
	/* Start and wait for complete on powerfail */
#define	POWERWAIT	7
	/* Default level "init" should start at. */
#define	INITDEFAULT	8
	/* Actions performed before init speaks. */
#define	SYSINIT		9

#define	M_OFF		0001
#define	M_RESPAWN	0002
#define	M_ONDEMAND	M_RESPAWN
#define	M_ONCE		0004
#define	M_WAIT		0010
#define	M_BOOT		0020
#define	M_BOOTWAIT	0040
#define	M_PF		0100
#define	M_PWAIT		0200
#define	M_INITDEFAULT	0400
#define M_SYSINIT	01000

#define	ID	1
#define	LEVELS	2
#define	ACTION	3
#define	COMMAND	4

/*	Init can be in either of three main states, "normal" mode	*/
/*	where it is processing entries for the lines file in a normal	*/
/*	fashion, "boot" mode, where it is only interested in the boot	*/
/*	actions, and "powerfail" mode, where it is only interested in	*/
/*	powerfail related actions.  The following masks declare the	*/
/*	legal actions for each mode.					*/

#define	NORMAL_MODES	(M_OFF | M_RESPAWN | M_ONCE | M_WAIT)
#define	BOOT_MODES	(M_BOOT | M_BOOTWAIT)
#define	PF_MODES	(M_PF | M_PWAIT)

struct PROC_TABLE {
	char p_id[4];	/* Four letter unique id of process */
	unsigned short p_pid;	/* Process id */
	short p_count;	/* How many respawns of this command in the
			 * current series.
				 */
	long p_time;	/* Start time for a series of respawns */
	short p_flags;
	short p_exit;	/* Exit status of a process which died */
};

/*	Flags for the "p_flags" word of a proc_table entry.		*/
/*									*/
/*	OCCUPIED		This slot in init's proc table is in	*/
/*					use.				*/
/*	LIVING			Process is alive.			*/
/*	NOCLEANUP		"efork()" is not allowed to cleanup	*/
/*					this entry even if process is	*/
/*					dead.				*/
/*	NAMED			This process has a name, i.e. came from	*/
/*					/etc/inittab.			*/
/*	DEMANDREQUEST		Process started by a "telinit [abc]"	*/
/*					command.  Processes formed this	*/
/*					way are respawnable and immune	*/
/*					to level changes as long as	*/
/*					their entry exists in inittab.	*/
/*	TOUCHED			Flag used by "remove" to determine	*/
/*					whether it has looked at an	*/
/*					entry while checking for	*/
/*					processes to be killed.		*/
/*	WARNED			Flag used by "remove" to mark processes	*/
/*					that have been sent the		*/
/*					SIGTERM signal.  If they don't	*/
/*					die in 5 seconds, they will	*/
/*					be sent the SIGKILL signal.	*/
/*	KILLED			Flag used by "remove" to say that a	*/
/*					process has been sent both	*/
/*					kill signals.  Such processes	*/
/*					should die immediately, but in	*/
/*					case they don't, this prevents	*/
/*					"init" from trying to kill it	*/
/*					again and again, and hogging	*/
/*					the process table of the	*/
/*					operating system.		*/

#define	OCCUPIED	01
#define	LIVING		02
#define	NOCLEANUP	04
#define	NAMED		010
#define	DEMANDREQUEST	020
#define	TOUCHED		040
#define	WARNED		0100
#define	KILLED		0200

/*	Respawn limits for processes that are to be respawned.		*/
/*									*/
/*	SPAWN_INTERVAL		The number of seconds over which	*/
/*				"init" will try to respawn a process	*/
/*				SPAWN_LIMIT times before it gets mad.	*/
/*									*/
/*	SPAWN_LIMIT		The number of respawns "init" will	*/
/*				attempt in SPANW_INTERVAL seconds	*/
/*				it generates an error message and	*/
/*				inhibits further tries for INHIBIT	*/
/*				seconds.				*/
/*									*/
/*	INHIBIT			The number of seconds "init" ignores	*/
/*				an entry it had trouble spawning	*/
/*				unless a "telinit Q" is received.	*/

#define	SPAWN_INTERVAL	(2*60)
#define	SPAWN_LIMIT	10
#define	INHIBIT		(5*60)


#define	NULLPROC	((struct PROC_TABLE *)(0))
#define	NO_ROOM		((struct PROC_TABLE *)(FAILURE))

struct CMD_LINE {
	char c_id[4];	/* Four letter unique id of process to be
			 * affected by action.
				 */
	short c_levels;	/* Mask of legal levels for process */
	short c_action;	/* Mask for type of action required */
	char *c_command;	/* Pointer to init command */
};

/*	Following are symbols for the various types of errors for	*/
/*	which "error_time" keeps timing entries.  MAX_E_TYPES is the	*/
/*	number of types currently being kept.				*/

#define	FULLTABLE	0
#define	BADLINE		1

#define	MAX_E_TYPES	2

static struct ERRORTIMES {
	long e_time;	/* Time of last message. */
	long e_max;	/* Amount of time to wait until next
			 * message.
				 */
} err_times[MAX_E_TYPES] = {
	0L,120L,0L,120L
};

/*	Useful file and device names.					*/


#ifdef SVR4_CODE
#ifndef UTMPX_FILE
#define UTMPX_FILE	"/var/adm/utmpx"
#define WTMPX_FILE	"/var/adm/wtmpx"
#endif
#endif /* SVR4_CODE */

#ifdef UDEBUG

char	*UTMP		=	"utmp";
char	*WTMP		=	"wtmp";
char	*UTMPX		=	"utmpx";
char	*WTMPX		=	"wtmpx";
char	*INITTAB	=	"inittab";
char	*SYSTTY		=	"/dev/systtyx";
char	*SYSCON		=	"/dev/sysconx";

char	*CORE_RECORD	=	"core_record";
char	*DBG_FILE	=	"debug";
char	*IOCTLSYSCON	=	"ioctl.syscon";	/* Last syscon modes */

#else /* !UDEBUG */

char	*UTMP		=	UTMP_FILE;	/* Snapshot record file */
char	*WTMP		=	WTMP_FILE;	/* Long term record file */
char	*UTMPX		=	UTMPX_FILE;	/* Snapshot record file */
char	*WTMPX		=	WTMPX_FILE;	/* Long term record file */
#ifdef DUMPTAB
char	*INITTAB 	=	"inittab";
#else
char	*INITTAB 	=	"/etc/inittab";	/* Script file for "init" */
#endif /* DUMPTAB */
char	*SYSTTY		=	"/dev/systty";	/* Real System Console */
char	*SYSCON		=	"/dev/syscon";	/* Virtual System console */
char	*IOCTLSYSCON	=	"/etc/ioctl.syscon";	/* Last syscon modes */

#ifdef	DEBUGGER
char	*DBG_FILE	=	"/etc/debug";
#endif

#endif /* UDEBUG */

char	*SU	=	"/sbin/su";	/* Super-user program for single user
					 * mode.
					 */
char	*SH	=	"/sbin/sh";	/* Standard Shell */

int	n_prev[NSIG];	/* Number of times previously in state */
int	cur_state = -1;	/* Current state of "init" */
int	prior_state;
int	prev_state;	/* State "init" was in last time it woke */
int	new_state;	/* State user wants "init" to go to. */
int	op_modes = BOOT_MODES;	/* Current state of "init" */

/*	The following structures contain a set of modes for /dev/syscon	*/

#ifndef	CBUNIX
#define control(x)	('x'&037)

#if u3b || u3b5 || u3b2
struct	termio	dflt_termio = {
	BRKINT|IGNPAR|ISTRIP|IXON|IXANY|ICRNL,
	OPOST|ONLCR|TAB3,
	CS8|CREAD|B9600,
	ISIG|ICANON|ECHO|ECHOK,
	0,
	0177,control(\\),'#','@',control(D),0,0,0
};
#else /* u3b || u3b5 || u3b2 */
struct	termio	dflt_termio = {
	BRKINT|IGNPAR|ISTRIP|IXON|IXANY|ICRNL,		/* c_iflag */
	OPOST|ONLCR|TAB3,				/* c_oflag */
#if mips
	CS8|CREAD|B9600,				/* c_cflag */
	ISIG|ICANON|ECHO|ECHOK|ECHOE,			/* c_lflag */
	LDISC0,						/* c_line */
	{ CINTR,					/* c_cc[VINTR] */
	  CQUIT,					/* c_cc[VQUIT] */
	  CTRL(H),					/* c_cc[VERASE] */
	  CTRL(X),					/* c_cc[VKILL] */
	  CTRL(D),					/* c_cc[VEOF] */
	  CNUL,						/* c_cc[VEOL] */
	  CNUL,						/* c_cc[VEOL2] */
	  CNUL,						/* c_cc[VSWTCH] */
	  CSTART,					/* c_cc[V_START] */
	  CSTOP,					/* c_cc[V_STOP] */
	  CSUSP,					/* c_cc[V_SUSP] */
	  CDEL,						/* c_cc[V_DSUSP] */
	  CDEL,						/* c_cc[V_RPRNT] */
	  CFLUSH,					/* c_cc[V_FLUSH] */
	  CWERASE,					/* c_cc[V_WERAS] */
	  CESC,						/* c_cc[V_LNEXT] */
	  CDEL,						/* c_cc[V_STATUS] */
	  CDEL,						/* c_cc[V_SAVED_EOF] */
	  CDEL,						/* c_cc[V_SAVED_EOL] */
	  CDEL,						/* c_cc[19] */
	  CDEL,						/* c_cc[20] */
	  CDEL,						/* c_cc[21] */
	  CDEL }					/* c_cc[22] */
#else
	CS8|CREAD|B300,
	ISIG|ICANON|ECHO|ECHOK,
	0,
	0177,control(\\),'#','@',control(D),0,0,0
#endif /* mips */
};
#endif /* u3b || u3b5 || u3b2 */

struct	termio	termio;

#else /* CBUNIX */

struct	ttiocb	dflt_ttiocb = {
	B300,B300,'#','@',XTABS|ECHO|CRMOD|ODDP|EVENP
};
struct	ttiocb	ttiocb;

struct	sgldisc	dflt_sgldisc = {
	0
};
struct	sgldisc	sgldisc;

struct	ttiothcb dflt_other = {
	NOHUP
};
struct	ttiothcb ttiothcb;

#endif /* !CBUNIX */

struct	termcb	dflt_trmcb = {
	TM_NONE,TERM_NONE,0,0,0,0
};
struct	termcb	termcb;

union WAKEUP {
	struct WAKEFLAGS {
		unsigned w_usersignal : 1;	/* User sent signal to "init" */
		unsigned w_childdeath : 1;	/* An "init" child died */
		unsigned w_powerhit : 1;	/* The OS experienced powerfail */
	}	w_flags;
	int w_mask;
} wakeup;

unsigned int	spawncnt,syncnt,pausecnt;
int	rsflag;		/* Set if a respawn has taken place */

int	own_pid;	/* This is the value of our own pid.
			* If the value is SPECIALPID, then we have to fork
			* to interact with outside world.
			*/
#ifdef _RISCOS
struct PROC_TABLE	*proc_table;
#else /* _RISCOS */
struct PROC_TABLE	proc_table[NPROC];	/* Table of active processes */
#endif /* _RISCOS */
struct PROC_TABLE	dummy;	/* A zero table used when
				 * calling "account" for non-
				 * process type accounting.
				 */
#ifdef	DEBUG
char comment[120];
#endif


/********************/
/****    main    ****/
/********************/

main(argc,argv)
int argc;
char **argv;
{
	extern int own_pid;
	extern char *UTMP;
#ifndef _RISCOS
	extern struct PROC_TABLE proc_table[];
	extern struct PROC_TABLE dummy;
#endif /* _RISCOS */
	extern int prev_state,cur_state,new_state,op_modes;
	extern union WAKEUP wakeup;
	extern int errno;
	char utmplock[84];
	int override_level,defaultlevel,initialize();
	FILE *fp, *fp2;
	FILE *fdup();
	int chg_lvl_flag;
	extern char level();
	extern time_t time();
	extern unsigned int spawncnt,syncnt,pausecnt;
	extern int rsflag;
	extern int time_up;
	register struct PROC_TABLE *reset_proc;
	extern int childeath();
	struct PROC_TABLE *efork();
#ifdef	DEBUG
	extern char comment[];
#endif

#ifdef _RISCOS
#define TZ_LEN 20
	FILE *tz_fp = NULL;
	char buf[TZ_LEN+1], comm[TZ_LEN+4];
	char *getenv();

	/* Get timezone info so syslog will record correct time */
	if (getenv("TZ") == NULL) {
		if ((tz_fp=fopen("/etc/TZ","r")) == NULL) {
			/* Syslog() won't work when syslogd isn't up!
			 * I tried to log the stuff by opening up /dev/console
			 * but that didn't work either.
			 * syslog(LOG_ERR,"WARNING: Can't open '/etc/TZ'");
			 * syslog(LOG_ERR,"  Will not be able to set timezone");
			 */
		} else {
			int i;
			for (i=0; i<TZ_LEN; i++) {/* read until space or EOLN */
				if ((buf[i]=fgetc(tz_fp)) == EOF) break;
				if (isspace(buf[i]) || buf[i]=='\n') break;
			}
			buf[i] = '\0';
			if (i != 0) {
				buf[TZ_LEN] = '\0';
				strcpy(comm,"TZ=");
				strcat(comm, buf);
				putenv(comm);
			}
			fclose(tz_fp);
		}
	}
#undef TZ_LEN
#endif

#ifdef	UDEBUG
	if (argc == 1) SPECIALPID = getpid();
#endif

#ifdef  DUMPTAB
	dump_table();
	exit(0);
#endif

	/*
	 * If our pid is not SPECIALPID, then we are a user invoked init
	 * whose job it is to inform init to change levels.
	 */
	if ( (own_pid = getpid()) != SPECIALPID ) {
		userinit(argc,argv);
		exit(1);		/* userinit() shouldn't return ... */
	}

#ifdef _RISCOS
	errno = 0;
	nproc = mipskopt(NPROC_NAME,0,KOPT_GET);
	if (errno != 0)
		nproc = DEFAULT_NPROC;
	if (nproc < MIN_NPROC)
		nproc = MIN_NPROC;
	proc_table = (struct PROC_TABLE *) 
			calloc(nproc,sizeof(struct PROC_TABLE));
	if (proc_table == NULL) {
		own_pid++;
		real_console("cannot allocate %d proc_table entries\n",
				nproc,0,0,0);
		exit(1);
	};
#endif /* _RISCOS */
	/*
	 * Init takes on optional single argument specifying run level.
	 * The only legal form for such an arg is [1-6sS].
	 # Anything confusing triggers SINGLE_USER.
	 */
	switch (argc) {
	case 1:
		override_level = 0;	/* No over-ride */
		break;
	case 2: 
		if (strlen(argv[1]) == 1) {
			switch (argv[1][0]) {
			case '1':	override_level = LVL1; break;
			case '2':	override_level = LVL2; break;
			case '3':	override_level = LVL3; break;
			case '4':	override_level = LVL4; break;
			case '5':	override_level = LVL5; break;
			case '6':	override_level = LVL6; break;
			case 's':	override_level = SINGLE_USER; break;
			case 'S':	override_level = SINGLE_USER; break;
			/* confused */
			default:	override_level = SINGLE_USER; break;
			}
		}
		else {
			/* confused */
			override_level = SINGLE_USER;
		}
		break;
	default:
		/* confused */
		override_level = SINGLE_USER;
		break;
	}

	/*
	 * Reset syscon to systty at reboot in case it got hosed.
	 * (Fork child so init won't grab the tty as it's controlling tty.)
	 */
	openlog("INIT", LOG_CONS|LOG_NDELAY|LOG_NOWAIT, LOG_AUTH);
	signal(SIGCLD,SIG_DFL);
	reset_proc = efork(M_WAIT, NULLPROC, NOCLEANUP);
	signal(SIGCLD,childeath);
	if (reset_proc == NULLPROC) {
		reset_syscon();
		exit(1);
	}
	while (waitproc(reset_proc) == FAILURE);

	/*
	 * Scan /etc/inittab for a default level.
	 * (Also, execute any sysinit entries from inittab.)
	 */
	defaultlevel = initialize();
	chg_lvl_flag = FALSE;

#ifdef	DEBUG
	console("Debug version of init starting-pid = %d\n",SPECIALPID);
#endif

	/*
	 * The initial level is determined by the first of the following
	 * that applies.
	 *
	 *   (1) Can't open inittab.  The Only legal state is Single User.
	 *   (2) Argument passed to init.
	 *   (3) If no argument, then initdefault from /etc/inittab is used.
	 *   (4) And if neither of the above, then ask the operator.
	 */
	if (defaultlevel == -1) {
		new_state = SINGLE_USER;	/* Can't open /etc/inittab */
		defaultlevel = 0;
	}
	else if (override_level) {
		new_state = override_level;	/* Level from argument */
		defaultlevel = 0;
	}
	else if (defaultlevel > 0) {
		new_state = defaultlevel;	/* Level from inittab */
	}
	else {
		new_state = getlvl();		/* Ask the operator. */
	}

	if (new_state == SINGLE_USER) {
		account(RUN_LVL,&dummy,"S");
		single(defaultlevel);
		chg_lvl_flag = TRUE;
	} else {
		prev_state = cur_state;
		if(cur_state >= 0) {
			n_prev[cur_state]++;
			prior_state = cur_state;
		}
		cur_state = new_state;

	}

/* Initialize the "utmp" and "utmpx" files and put in the boot time. */
/* Set the umask so that the utmp file is created 644. */
	umask(022);
	sprintf(utmplock, "%s.lck", UTMP);
	unlink(utmplock);
	if ((fp = fopen(UTMP,"w+")) == NULL ||
	    (fp2 = fopen(UTMPX, "w+")) == NULL) {
		console("Cannot create %s or %s\n",UTMP,UTMPX,0);
		/* Without "utmp" file default to single user mode. */
		cur_state = SINGLE_USER;
	} else {
		fclose(fp);
		fclose(fp2);
		account(BOOT_TIME,&dummy,NULL);	/* Put Boot Entry in "utmp" */
		account(RUN_LVL,&dummy,NULL);	/* Make the run level entry */
	}
	umask(0);	/* Allow later files to be created normally. */

/* Here is the beginning of the main process loop. */
	for (;;) {

/* If in "normal" mode, check all living processes and initiate */
/* kill sequence on those that should not be there anymore. */
		if (op_modes == NORMAL_MODES && cur_state != LVLa
		   && cur_state != LVLb && cur_state != LVLc) remove();

/* If a change in run levels is the reason we awoke, now do */
/* the accounting to report the change in the utmp file.  Also */
/* report the change on the system console. */
		if (chg_lvl_flag) {
			chg_lvl_flag = FALSE;
			account(RUN_LVL,&dummy,NULL);
#if _RISCOS
			real_console("New run level: %c\n",level(cur_state));
#else /* !_RISCOS */
			console("New run level: %c\n",level(cur_state));
#endif /* !_RISCOS */
		}
/* Scan the inittab file and spawn and respawn processes that */
/* should be alive in the current state. If /etc/inittab does */
/* not exist default to "S" state. */
		if (spawn() == FAILURE) {
			prior_state = prev_state;
			cur_state = SINGLE_USER;
		}

		if (rsflag) {
			rsflag = 0;
			spawncnt++;
		}
		if (cur_state == SINGLE_USER) {
			account(RUN_LVL,&dummy,NULL);
			single(0);
			if (cur_state != prev_state
			    && cur_state != LVLa && cur_state != LVLb
			    && cur_state != LVLc) {
				chg_lvl_flag = TRUE;
				continue;
			}
		}


/* If a powerfail signal was received during the last sequence, */
/* set mode to powerfail.  When "spawn" is entered the first */
/* thing it does is to check "powerhit".  If it is in PF_MODES */
/* then it clears "powerhit" and does a powerfail sequence.  If */
/* it is not in PF_MODES, then it puts itself in PF_MODES and */
/* then clears "powerhit".  Should "powerhit" get set again while */
/* "spawn" is working on a powerfail sequence, the following code */
/* will see that "spawn" tries to execute the powerfail sequence */
/* again.  This guarentees	that the powerfail sequence will be */
/* successfully completed before further processing takes place. */
		if (wakeup.w_flags.w_powerhit) {
			op_modes = PF_MODES;

/* Make sure that cur_state != prev_state so that ONCE and WAIT types work. */
			prev_state = 0;

/* If "spawn" was not just called while in "normal" mode, then */
/* set the mode to "normal" and call it again to check normal */
/* states. */
		} else if (op_modes != NORMAL_MODES) {

/* If we have just finished a powerfail sequence(which had the */
/* prev_state == 0), set the prev_state = cur_state before the */
/* next pass through. */
			if (op_modes == PF_MODES) prev_state = cur_state;
			op_modes = NORMAL_MODES;

/* "spawn was last called with "normal" modes. */
/* If it was a change of levels that awakened us and the new */
/* level is one of the demand levels, LVL[a-c], then reset */
/* the cur_state to the previous state and do another scan to */
/* take care of the usual "respawn" actions. */
		} else if (cur_state == LVLa || cur_state == LVLb
			  || cur_state == LVLc) {
			if(cur_state >= 0) {
				n_prev[cur_state]++;
			}
			cur_state = prior_state;
			prior_state = prev_state;
			prev_state = cur_state;
			account(RUN_LVL,&dummy,NULL);

/* At this point "init" is finished with all actions for the */
/* current wakeup.  */
		} else {
			prev_state = cur_state;

/* Now pause until there is a signal of some sort.  Signals are */
/* disallowed until the pause system call actually is performed */
/* but then all signals are treated until we return from pause. */
			if (wakeup.w_mask == 0) {
				setimer(SLEEPTIME);
				pause();
				pausecnt++;
			}

/* If we woke up because the timer went off, then SLEEPTIME has */
/* expired.  Resync the disks. */
			if (time_up == TRUE) {
				sync();
				syncnt++;
			}
			setimer(0);

/*    Now install the new level, if a change in level happened and    */
/*    then allow signals again while we do our normal processing.     */
			if (wakeup.w_flags.w_usersignal) {
#ifdef	DEBUG
				debug("\nmain\tSignal-new: %c cur: %c prev: %c\n",
					level(new_state),level(cur_state),
					level(prev_state));
#endif
/* Set flag so that we know to change run level in utmp file */
/* all the old processes have been removed.  Do not set the flag */
/* if a "telinit {Q | a | b | c}" was done or a telinit to the */
/* same level at which init is already running (which is the */
/* same thing as a "telinit Q"). */
				if (new_state != cur_state)
					if(new_state == LVLa
					   || new_state == LVLb
					   || new_state == LVLc) {
						prev_state = prior_state;
						prior_state = cur_state;
						cur_state = new_state;
						account(RUN_LVL,&dummy,NULL);
					} else {
						prev_state = cur_state;
						if(cur_state >= 0) {
							n_prev[cur_state]++;
							prior_state = cur_state;
						}
						cur_state = new_state;
						chg_lvl_flag = TRUE;
					}

/* If the new level is SINGLE_USER, it is necessary to save */
/* the state of the terminal which is "syscon". These will be */
/* restored before the "su" is started up on the line. */
				if (new_state == SINGLE_USER) {
					save_ioctl();
					get_ioctl_syscon();
				}
				new_state = 0;
			}

/*    If we awoke because of a powerfail, change the operating mode   */
/*    to powerfail mode.                                              */
			if (wakeup.w_flags.w_powerhit)
				op_modes = PF_MODES;

/* Clear all wakeup reasons. */
			wakeup.w_mask = 0;
		}
	}
}

/**********************/
/****    single    ****/
/**********************/

single(defaultlevel)
int defaultlevel;
{
	register struct PROC_TABLE *su_process;
	struct PROC_TABLE *efork();
	extern long waitproc();
	extern int errno;
	extern int new_state,cur_state,prev_state;
	extern struct PROC_TABLE dummy;
	extern union WAKEUP wakeup;
	int state;
#ifndef	CBUNIX
	extern struct termio termio;
#else
	extern struct ttiocb ttiocb;
	extern struct sgldisc sgldisc;
	extern struct ttiothcb ttiothcb;
#endif
	extern struct termcb termcb;
	extern int childeath();

	for (;;) {
#if	_RISCOS
		real_console("SINGLE USER MODE\n");
#else	/* !_RISCOS */
		console("SINGLE USER MODE\n");
#endif	/* _RISCOS */
		signal(SIGCLD,SIG_DFL);
		while ((su_process = efork(M_OFF,NULLPROC,NOCLEANUP)) == NO_ROOM)
			pause();
		signal(SIGCLD,childeath);
		if (su_process == NULLPROC) {
			opensyscon();

/* Execute the "su" program. */
			execlp(SU,SU,"-",0);
			console("execlp of %s failed; errno = %d\n",SU,errno);
			timer(5);
			exit(1);
		}

/* If we are the parent, wait around for the child to die or for */
/* "init" to be signaled to change levels. */
		while (waitproc(su_process) == FAILURE) {

#if u3b2 || u3b5
	/* did we waken because of a single-user 3B2/3B5 powerfail? */
	/* if so, spawn /etc/powerfail from inittab */
			if (wakeup.w_flags.w_powerhit) {
				spawn();
				op_modes = NORMAL_MODES;
			}
#endif

/* Did we waken because a change of levels?  If so, kill the  */
/* child and then exit. */
			if (wakeup.w_flags.w_usersignal) {
				if (new_state >= LVL0 && new_state <= LVL6) {
					kill(su_process->p_pid,SIGKILL);
					prev_state = cur_state;
					if(cur_state >= 0) {
						n_prev[cur_state]++;
						prior_state = cur_state;
					}
					cur_state = new_state;
					new_state = 0;
					wakeup.w_mask = 0;
					su_process->p_flags &= ~NOCLEANUP;
					return;
				}
			}

/* All other reasons for waking are ignored when in SINGLE_USER */
/* mode.  The only child we are interested in is being waited */
/* for explicitely by "waitproc". */

			wakeup.w_mask = 0;
		}

/* Since the su user process died and the level hasn't been */
/* changed by a signal, either request a new level from the user */
/* if default one wasn't supplied, or use the supplied default */
/* level. */
		if (defaultlevel != 0)
			state = defaultlevel;
		else
			state = getlvl();
		if (state != SINGLE_USER) {

/* If the new level is not SINGLE_USER, then exit, otherwise */
/* go back and make up a new "su" process. */
			prev_state = cur_state;
			if(cur_state >= 0) {
				n_prev[cur_state]++;
				prior_state = cur_state;
			}
			cur_state = state;
			return;
		}
	}
}

/**********************/
/****    remove    ****/
/**********************/

/*	"remove" scans through "proc_table" and performs cleanup.  If	*/
/*	there is a process in the table, which shouldn't be here at	*/
/*	the current runlevel, then "remove" kills the processes.	*/

remove()
{
#ifndef _RISCOS
	extern struct PROC_TABLE proc_table[];
#endif /* _RISCOS */
	register struct PROC_TABLE *process;
	extern int op_modes,prev_state,cur_state,new_state;
	struct CMD_LINE cmd;
	char cmd_string[MAXCMDL];
	int change_level;
	extern int time_up;
	extern char *C();

	change_level = (cur_state != prev_state ? TRUE : FALSE);

/* Clear the TOUCHED flag on all entries so that when we have */
/* finished scanning /etc/inittab, we will be able to tell if */
/* we have any processes for which there is no entry in */
/* /etc/inittab. */

	for (process= &proc_table[0]; process < &proc_table[NPROC]; process++)
		process->p_flags &= ~TOUCHED;

/* Scan all /etc/inittab entries. */
	while(getcmd(&cmd,&cmd_string[0]) == TRUE) {

/* Scan for process which goes with this entry in /etc/inittab. */
		for (process= &proc_table[0]; process < &proc_table[NPROC]; process++) {

/* Does this slot contain the process we are looking for? */
			if ((process->p_flags & OCCUPIED) && id_eq(process->p_id,cmd.c_id)) {
#ifdef	DEBUG
				debug("remove- id:%s pid: %d time: %lo %d %o %o\n",
					C(&process->p_id[0]),process->p_pid,
					process->p_time,process->p_count,
					process->p_flags,process->p_exit);
#endif

/* Is the cur_state SINGLE_USER or */
/* is this process marked as "off" or was this process was started */
/* by some other mechanism than the LVLa, LVLb, LVLc mechanism, */
/* and the current level does not support this process? */
				if ((cur_state == SINGLE_USER)
				    || (cmd.c_action == M_OFF)
				    || ((cmd.c_levels & mask(cur_state)) == 0
					&& (process->p_flags & DEMANDREQUEST) == 0)) {
					if (process->p_flags & LIVING) {

/* Touch this entry so that we will know that we've treated it. */
/* ****    NOTE    ****	Processes which are already dead at */
/* 			this point, but should not be restarted */
/* are left untouched.  This causes their slot to be freed later */
/* after dead accounting is performed. */
						process->p_flags |= TOUCHED;

/* If this process has already been killed before, but for some */
/* reason hasn't disappeared yet, don't kill it again.  Only kill */
/* it if the KILLED flag hasn't been set. */
						if ((process->p_flags & KILLED) == 0) {
/* If this is a change of levels call, then don't fork a killing */
/* process for each process that must die.  Send the first */
/* warning signal yourself and mark the process as warned.  If */
/* any of the warned processes fail to die in TWARN seconds, then */
/* kill them. */
							if (change_level) {
								process->p_flags |= WARNED;
								kill(process->p_pid,SIGTERM);

/* If this isn't a change of levels, then fork a killing process */
/* which will worry about the details of killing the specified */
/* process.  This allows "init" to continue work instead of */
/* pausing for TWARN seconds each pass through this routine. */
							} else killproc(process->p_pid);

/* Mark the process as having been sent it's kill signals.  It */
/* should show up as dead shortly, but just to be safe.... */
							process->p_flags |= KILLED;
						}
					}

/* This process can exist at the current level.  If it is also */
/* still`alive or a DEMANDREQUEST, TOUCH it so that will be left */
/* alone.  If it is dead and not a DEMANDREQUEST, leave it */
/* untouched so that it will be accounted and cleaned up later */
/* on in "remove".  Dead DEMANDREQUESTS will be accounted, but */
/* not freed. */
				} else if (process->p_flags & (LIVING | NOCLEANUP | DEMANDREQUEST))
					process->p_flags |= TOUCHED;

				break;
			}
		}
	}

/* If this was a change of levels call, scan through the process */
/* table for processes that were warned to die.  If any are found */
/* that haven't left yet, sleep for TWARN seconds and then send */
/* final terminations to any that haven't died yet. */
	if (change_level) {

/* Set the alarm for TWARN seconds on the assumption that there */
/* will be some that need to be waited for.  This won't harm */
/* anything except we are guarenteed to wakeup in TWARN seconds */
/* whether we need to or not. */
		setimer(TWARN);

/* Scan for processes which should be dying.  We hope they will */
/* die without having to be sent a SIGKILL signal. */
		for (process = &proc_table[0]; process < &proc_table[NPROC]; process++) {
/* If this process should die, hasn't yet, and the TWARN time */
/* hasn't expired yet, wait around for process to die or for */
/* timer to expire. */
			while ((time_up == FALSE)
				&& (process->p_flags & (WARNED | LIVING | OCCUPIED)) == (WARNED | LIVING | OCCUPIED)) pause();
		}

/* If we reached the end of the proc table without the timer */
/* expiring, then there are no processes which will have to be */
/* sent the SIGKILL signal.  If the timer has expired, then it is */
/* necessary to scan the table again and send signals to all */
/* processes which aren't going away nicely. */
		if (time_up == TRUE) for (process = &proc_table[0]; process < &proc_table[NPROC]; process++) {

/* Is this a WARNED process that hasn't died yet? */
			if ((process->p_flags & (WARNED | LIVING | OCCUPIED)) == (WARNED | LIVING | OCCUPIED))
				kill(process->p_pid,SIGKILL);
		}
		setimer(0);
	}

/* Rescan the proc_table for two kinds of entry, those marked */
/* as LIVING, NAMED, and which don't have an entry in */
/* /etc/inittab (haven't been TOUCHED by the above scanning), and */
/* haven't been sent kill signals, and those entries marked as */
/* not LIVING, NAMED.  The former processes should be killed. */
/* The latter entries should have DEAD_PROCESS accounting done */
/* on them and the slot cleared. */
	for (process= &proc_table[0]; process < &proc_table[NPROC]; process++) {
		if ((process->p_flags & (LIVING | NAMED | TOUCHED | KILLED | OCCUPIED)) == (LIVING | NAMED | OCCUPIED)) {
			killproc(process->p_pid);
			process->p_flags |= KILLED;
		} else if ((process->p_flags & (LIVING | NAMED | OCCUPIED)) == (NAMED | OCCUPIED)) {
			account(DEAD_PROCESS,process,NULL);

/* If this named process hasn't been TOUCHED, then free the space. */
/* It has either died of it's own accord, but isn't respawnable */
/* or was killed because it shouldn't exit at this level. */
			if ((process->p_flags & TOUCHED) == 0)
				process->p_flags = 0;
		}
	}
}

/*********************/
/****    spawn    ****/
/*********************/

/*	"spawn" scans /etc/inittab for entries which should be run at	*/
/*	this mode.  If a process which should be running is found not	*/
/*	to be running, then it is started.				*/

spawn()
{
#ifndef _RISCOS
	extern struct PROC_TABLE proc_table[];
#endif /* _RISCOS */
	extern struct PROC_TABLE *findpslot();
	register struct PROC_TABLE *process;
	struct CMD_LINE cmd;
	char cmd_string[MAXCMDL];
	short lvl_mask, status;
	extern int cur_state,prev_state,op_modes;
	extern union WAKEUP wakeup;
	extern long waitproc();
#ifdef	DEBUG
	extern char level();
	extern char *ctime(),*C();
#endif

/* First check the "powerhit" flag.  If it is set, make sure */
/* the modes are PF_MODES and clear the "powerhit" flag. */
/* Avoid the possible race on the "powerhit" flag by disallowing */
/* a new powerfail interupt between the test of the powerhit */
/* flag and the clearing of it. */
	if (wakeup.w_flags.w_powerhit) {
		wakeup.w_flags.w_powerhit = 0;
		op_modes = PF_MODES;
	}
	lvl_mask = mask(cur_state);

#ifdef	DEBUG1
	debug("spawn\tSignal-new: %c cur: %c prev: %c\n",level(new_state), level(cur_state),level(prev_state));
	debug("spawn- lvl_mask: %o op_modes: %o\n",lvl_mask,op_modes);
#endif

/* Scan through all the entries in /etc/inittab. */
	while ((status = getcmd(&cmd,&cmd_string[0])) == TRUE) {

/* Find out if there is a process slot for this entry already. */
		if ((process = findpslot(&cmd)) == NULLPROC) {

/* Only generate an error message once every WARNFREQUENCY seconds */
/* when the internal process table is full. */
			if (error_time(FULLTABLE))
				console("Internal process table is full.\n");
			continue;
		}

/* If there is an entry, and it is marked as DEMANDREQUEST, one */
/* of the levels a,b, or c is in its levels mask, and the action */
/* field is ONDEMAND and ONDEMAND is a permissable mode, and */
/* the process is dead, then respawn it. */
		if (((process->p_flags & (LIVING | DEMANDREQUEST)) == DEMANDREQUEST)
		    && (cmd.c_levels & (MASKa | MASKb | MASKc))
		    && (cmd.c_action & op_modes) == M_ONDEMAND) {
			respawn(process,&cmd);
			continue;	/* Now finished with this entry. */
		}

#ifdef	DEBUG1
		debug("process:\t%s\t%05d\n%s\t%d\t%o\t%o\n",
			C(&process->p_id[0]),process->p_pid,
			ctime(&process->p_time),process->p_count,
			process->p_flags,process->p_exit);
		debug("cmd:\t%s\t%o\t%o\n\"%s\"\n",C(&cmd.c_id[0]),
			cmd.c_levels,cmd.c_action,cmd.c_command);
#endif

/* If the action is not an action we are interested in, skip */
/* the entry. */
		if ((cmd.c_action & op_modes) == 0) continue;
		if (process->p_flags & LIVING) continue;
		if ((cmd.c_levels & lvl_mask) == 0) continue;

/* If the modes are the normal modes (ONCE, WAIT, RESPAWN, OFF, */
/* ONDEMAND) and the action field is either OFF or the action */
/* field is ONCE or WAIT and the current level is the same as the */
/* last level, then skip this entry.  ONCE and WAIT only get run */
/* when the level changes. */
		if ((op_modes == NORMAL_MODES)
		    && (cmd.c_action == M_OFF || (cmd.c_action & (M_ONCE | M_WAIT))
		       && cur_state == prev_state)) continue;

/* At this point we are interested in performing the action for */
/* this entry.  Actions fall into two catagories, spinning off */
/* a process and not waiting, and spinning off a process and */
/* waiting for it to die. */
/* If the action is ONCE, RESPAWN, ONDEMAND, POWERFAIL, or BOOT */
/* then spin off a process, but don't wait. */
		if (cmd.c_action & (M_ONCE | M_RESPAWN | M_PF | M_BOOT))
			respawn(process,&cmd);

/* The action must be WAIT, BOOTWAIT, or POWERWAIT, therefore */
/* spin off the process, but wait for it to die before continuing. */
		else {
			respawn(process,&cmd);
			while (waitproc(process) == FAILURE);
			account(DEAD_PROCESS,process,NULL);
			process->p_flags = 0;
		}
	}
	return(status);
}

/***********************/
/****    respawn    ****/
/***********************/

/*	"respawn" spawns a shell, inserts the information about the	*/
/*	process into the proc_table, and does the startup accounting.	*/

respawn(process,cmd)
register struct PROC_TABLE *process;
register struct CMD_LINE *cmd;
{
	register int i;
	FILE *fp;
	int modes;
	extern int childeath();
	extern int cur_state,errno;
	extern struct PROC_TABLE *efork();
	struct PROC_TABLE tmproc,*oprocess;
	long now;
	static char *envp[] = { 
		"PATH=/sbin:/usr/sbin:/etc:/usr/bin",0
	};
	extern char *prog_name();
	extern int rsflag;

#ifdef	DEBUG1
	extern char *C();

	debug("**  respawn  **  id:%s\n",C(&process->p_id[0]));
#endif

/* The modes to be sent to "efork" are 0 unless we are spawning */
/* a LVLa, LVLb, or LVLc entry or we will be waiting for the */
/* death of the child before continuing. */
	modes = NAMED;
	if ((process->p_flags & DEMANDREQUEST) || cur_state == LVLa
	    || cur_state == LVLb || cur_state == LVLc)
		modes |= DEMANDREQUEST;
	if ((cmd->c_action & (M_SYSINIT | M_WAIT | M_BOOTWAIT | M_PWAIT)) != 0)
		modes |= NOCLEANUP;

/* If this is a respawnable process, check the threshold */
/* information to avoid excessive respawns. */
	if (cmd->c_action & M_RESPAWN) {

/* Add the NOCLEANUP to all respawnable commands so that the  */
/* information about the frequency of respawns isn't lost. */
		modes |= NOCLEANUP;
		time(&now);

/* If no time is assigned, then this is the first time this */
/* command is being processed in this series.  Assign the current */
/* time. */
		if (process->p_time == 0L)
			process->p_time = now;

/* Have we just reached the respawn limit? */
		if (process->p_count++ == SPAWN_LIMIT) {

/* If so, have we been respawning it too rapidly? */
			if ((now - process->p_time) < SPAWN_INTERVAL) {

/* If so, generate an error message and refuse to respawn the */
/* process for now. */
#ifdef _RISCOS
				console("Command is respawning\
 too rapidly.  Check for possible errors.\n");
				console("id:%4s \"%s\"\n",
				    &cmd->c_id[0], &cmd->c_command[EXEC]);
#else
				console("Command is respawning\
 too rapidly.  Check for possible errors.\nid:%4s \"%s\"\n",
				    &cmd->c_id[0], &cmd->c_command[EXEC]);
#endif /* _RISCOS */
				return;
			}
			process->p_time = now;
			process->p_count = 0;

/* If this process has been respawning too rapidly and the */
/* inhibit time limit hasn't expired yet, refuse to respawn. */
		} else if (process->p_count > SPAWN_LIMIT) {
			if ((now - process->p_time) <
			    (SPAWN_INTERVAL + INHIBIT))
				return;
			process->p_time = now;
			process->p_count = 0;
		}
		rsflag = TRUE;
	}

/* Spawn a child process to execute this command. */
	signal(SIGCLD,SIG_DFL);
	oprocess = process;
	if (cmd->c_action & (M_WAIT | M_BOOTWAIT))
		while ((process = efork(M_WAIT,oprocess,modes)) == NO_ROOM) pause();
	else
		while ((process = efork(M_OFF,oprocess,modes)) == NO_ROOM) pause();

/* If we are the child, close up all the open files and set up the */
/* default standard input and standard outputs. */
	if (process == NULLPROC) {

/* Make sure the child uses a different file pointer in the OS */
/* for its references to /etc/utmp.  If this isn't done, the */
/* seeks and reads of the child and parent will compete with each */
/* other. */
		endutent();

/* Perform the accounting for the beginning of a process. */
/* Note that all processes are initially "INIT_PROCESS"es.  Getty */
/* will change the type to "LOGIN_PROCESS" and login will change */
/* it to "USER_PROCESS" when they run. */
		tmproc.p_id[0] = cmd->c_id[0];
		tmproc.p_id[1] = cmd->c_id[1];
		tmproc.p_id[2] = cmd->c_id[2];
		tmproc.p_id[3] = cmd->c_id[3];
		tmproc.p_pid = getpid();
		tmproc.p_exit = 0;
		account(INIT_PROCESS,&tmproc,prog_name(&cmd->c_command[EXEC]));
		for (i=0,fp= stdin; i < _NFILE;i++,fp++) fclose(fp);

/* Now "exec" a shell with the -c option and the command from */
/* /etc/inittab. */
		execle(SH,"INITSH","-c",cmd->c_command,0,&envp[0]);

/* If the "exec" fails, print an error message. */
#ifdef _RISCOS
		console("Command\n");
		console("\"%s\"\n", cmd->c_command);
		console(" failed to execute.  errno = %d (exec of shell failed)\n", errno);
#else
		console("Command\n\"%s\"\n failed to execute.  errno = %d (exec of shell failed)\n", cmd->c_command,errno);
#endif /* _RISCOS */

/* Don't come back so quickly that "init" hasn't had a chance to */
/* complete putting this child in "proc_table". */
		timer(20);
		exit(1);

/* We are the parent, therefore insert the necessary information */
/* in the proc_table. */
	} else {
		process->p_id[0] = cmd->c_id[0];
		process->p_id[1] = cmd->c_id[1];
		process->p_id[2] = cmd->c_id[2];
		process->p_id[3] = cmd->c_id[3];
	}
	signal(SIGCLD,childeath);
	return;
}

/************************/
/****    findpslot    ****/
/************************/

/*	findpslot() finds the old slot in the process table for the	*/
/*	command with the same id, or it finds an empty slot.		*/

struct PROC_TABLE *findpslot(cmd)
register struct CMD_LINE *cmd;
{
#ifndef _RISCOS
	extern struct PROC_TABLE proc_table[];
#endif /* _RISCOS */
	register struct PROC_TABLE *process,*empty;

	for(empty= NULLPROC,process= &proc_table[0] ; process < &proc_table[NPROC];process++) {
		if ((process->p_flags & OCCUPIED) && id_eq(process->p_id,cmd->c_id)) break;

/* If the entry is totally empty and "empty" is still 0, remember */
/* where this hole is and make sure the slot is zeroed out. */
		if (empty == NULLPROC && (process->p_flags & OCCUPIED) == 0) {
			empty = process;
			process->p_id[0] = '\0';
			process->p_id[1] = '\0';
			process->p_id[2] = '\0';
			process->p_id[3] = '\0';
			process->p_pid = 0;
			process->p_time = 0L;
			process->p_count = 0;
			process->p_flags = 0;
			process->p_exit = 0;
		}
	}

/* If there is no entry for this slot, then there should be */
/* an empty slot.  If there is no empty slot, then we've run out */
/* of proc_table space.  If the latter is true, empty will be NULL */
/* and the caller will have to complain. */
	if (process == &proc_table[NPROC]) {
		process = empty;
	}
	return(process);
}

/**********************/
/****    getcmd    ****/
/**********************/

/*	"getcmd" parses lines from /etc/inittab.  Each time it finds	*/
/*	a command line it will return TRUE as well as fill the passed	*/
/*	CMD_LINE structure and the shell command string.  When the end	*/
/*	of /etc/inittab is reached, FALSE is returned.			*/
/*									*/
/*	/etc/inittab is automatically opened if it is not currently	*/
/*	open and is closed when the end of the file is reached.		*/

static FILE *fp_inittab = NULL;

getcmd(cmd,shcmd)
register struct CMD_LINE *cmd;
char *shcmd;
{
	extern FILE *fp_inittab;
	int i;
	int answer,proceed;
	register char *ptr;
	register int c;
	register int state;
	char lastc,*ptr1;
	int errnum;
	extern int errno;
	static char *actions[] = {
		"off","respawn","ondemand","once","wait","boot",
		"bootwait","powerfail","powerwait","initdefault",
		"sysinit",
	};
	static short act_masks[] = {
		M_OFF,M_RESPAWN,M_ONDEMAND,M_ONCE,M_WAIT,M_BOOT,M_BOOTWAIT,
		M_PF,M_PWAIT,M_INITDEFAULT,M_SYSINIT,
	};

	if (fp_inittab == NULL) {

/* Be very persistent in trying to open /etc/inittab.   */
		for (i=0; i < 3;i++) {
			if ((fp_inittab = fopen(INITTAB,"r")) != NULL) break;
			else {
				errnum = errno;	/* Remember for error message */
				timer(3);	/* Wait 3 seconds to see if file appears. */
			}
		}

/* If unable to open /etc/inittab, print error message and return */
/* FALSE to caller. */
		if (fp_inittab == NULL) {
			console("Cannot open %s  errno: %d\n",INITTAB,errnum);
			return (FAILURE);
		}
	}

/* Keep getting commands from /etc/inittab until you find a good */
/* one or run out of file. */
	for (answer= FALSE; answer == FALSE;) {

/* Zero out the cmd itself before trying next line. */
		zero((char *)cmd,sizeof(struct CMD_LINE));

/* Read in lines of /etc/inittab, parsing at colons, until a line */
/* is read in which doesn't end with a backslash.  Do not start */
/* if the first character read is an EOF.  Note that this means */
/* that should a line fail to end in a newline, it will still */
/* be processed, since the "for" will terminate normally once */
/* started, regardless of whether line terminates with a newline */
/* or an EOF. */
		state = FAILURE;
		if ((c = fgetc(fp_inittab)) != EOF) for (proceed= TRUE, ptr= shcmd,state=ID, lastc= '\0'; proceed && c !=
		    EOF; lastc = c , c = fgetc(fp_inittab)) {

/* If we are not in the FAILURE state and haven't yet reached */
/* the shell command field, process the line, otherwise just */
/* look for a real end of line. */
			if (state != FAILURE && state != COMMAND) {

/* Squeeze out spaces and tabs. */
				if (c == ' ' || c == '\t') continue;

/* If the character is a ':', then check the previous field for */
/* correctness and advance to the next field. */
				if ( c == ':' ) {
					switch (state) {
					case ID :

/* If the first character of the id was a #, it's a comment. */
						if (shcmd[0] == '#') {
#ifdef DUMPTAB
							printf("Comment line: %s\n", shcmd);
#endif
							state = FAILURE;
							break;
						}

/* Check to see that there are only 1 to 4 characters for the id. */
						if ((i = ptr - shcmd) < 1 || i > 4 ) {
							state = FAILURE;
						} else {
							bcopy(shcmd,&cmd->c_id[0],i);
							ptr = shcmd;	/* Reset pointer */
							state = LEVELS;
						}
						break;

					case LEVELS :

/* Build a mask for all the levels that this command will be */
/* legal */
						for (cmd->c_levels= 0,ptr1= shcmd ; ptr1 < ptr; ptr1++) {
							if (*ptr1 >= '0' && *ptr1 <= '6')
								cmd->c_levels |= (MASK0 << (*ptr1 - '0'));
							else if (*ptr1 >= 'a' && *ptr1 <= 'c')
								cmd->c_levels |= (MASKa << (*ptr1 - 'a'));
							else if(*ptr1 == 's' || *ptr1 == 'S')
								cmd->c_levels |= MASKSU;
							else {
								state = FAILURE;
								break;
							}
						}
						if (state != FAILURE) {
							state = ACTION;
							ptr = shcmd;	/* Reset the buffer */
						}
						break;

					case ACTION :

/* Null terminate the string in shcmd buffer and then try to match */
/* against legal actions.  If the field is of length 0, then the */
/* default of "RESPAWN" is used if the id is numeric, otherwise */
/* the default is "OFF". */
						if (ptr == shcmd) {
							if (isdigit(cmd->c_id[0])
							   && (cmd->c_id[1] == '\0' || isdigit(cmd->c_id[1]))
							   && (cmd->c_id[2] == '\0' || isdigit(cmd->c_id[2]))
							   && (cmd->c_id[3] == '\0' || isdigit(cmd->c_id[3])) )
								cmd->c_action = M_RESPAWN;
							else cmd->c_action = M_OFF;
						} else {
							for (cmd->c_action=0,i= 0,*ptr = '\0'; i < sizeof(actions)/sizeof(char *);i++) {
								if (strcmp(shcmd,actions[i]) == 0) {
/* code folded from here */
	if((cmd->c_levels & MASKSU) && !(act_masks[i] & (M_INITDEFAULT | M_PF | M_PWAIT)))
		cmd->c_action = 0;
	else cmd->c_action = act_masks[i];
	break;
/* unfolding */
								}
							}
						}

/* If the action didn't match any legal action, set state to */
/* FAILURE. */
						if (cmd->c_action == 0) state = FAILURE;
						else {
							state = COMMAND;

/* Insert the prefix string of "exec " into the command buffer */
/* before inserting any characters. */
							strcpy(shcmd,"exec ");
						}
						ptr = shcmd + EXEC;
						break;
					}
					continue;
				}
			}

/* If the character is a '\n', then this is the end of a line. */
/* If the '\n' wasn't preceded by a backslash, it is also the end */
/* of an /etc/inittab command.  If it was preceded by a backslash */
/* then the next line is a continuation.  Note that the */
/* continuation '\n' falls through and is treated like other */
/* characters and is stored in the shell command line. */
			if (c == '\n') {
				if (lastc != '\\') {
					proceed = FALSE;
					*ptr = '\0';
					break;
				}
			}

/* For all other characters just stuff them into the command */
/* as long as there aren't too many of them. Make sure there is */
/* room for a terminating '\0' also. */
			if (ptr >= (shcmd + MAXCMDL-1)) state = FAILURE;
			else *ptr++ = c;

/* If the character we just stored was a quoted backslash, then */
/* change "c" to '\0', so that this backslash will not cause a */
/* subsequent '\n' to appear quoted.  In otherwords '\' '\' '\n' */
/* is the real end of a command, while '\' '\n' is a continuation. */
			if ( c == '\\' && lastc == '\\') c = '\0';
		}

/* Make sure all the fields are properly specified for a good */
/* command line. */
		if (state == COMMAND) {
			answer = TRUE;
			cmd->c_command = shcmd;

/* If no default level was supplied, insert all numerical levels. */
			if (cmd->c_levels == 0)
				cmd->c_levels = MASK0 | MASK1 | MASK2 |  MASK3 | MASK4 | MASK5 | MASK6;

/* If no action has been supplied, declare this entry to be */
/* OFF. */
			if (cmd->c_action == 0) cmd->c_action = M_OFF;

/* If no shell command has been supplied, make sure there is */
/* a null string in the command field. */
			if (ptr == (shcmd + EXEC)) {
/* EXEC is the length of the string "exec " minus the null at the end. */
				*shcmd = '\0';
			}
		}
		else answer = FALSE;

/* If we have reached the end of /etc/inittab, then close it and */
/* quit trying to find a good command line. */
		if (c == EOF) {
			endinittab();	/* Close "inittab" */
			break;
		}
	}
	return(answer);
}

/**************************/
/****    endinittab    ****/
/**************************/

endinittab()
{
	extern FILE *fp_inittab;

	fclose(fp_inittab);
	fp_inittab = NULL;
}

/********************/
/****    mask    ****/
/********************/

int mask(lvl)
int lvl;
{
	register int answer;

	switch (lvl) {
	case LVLQ :
		answer = 0;
		break;
	case LVL0 :
		answer = MASK0;
		break;
	case LVL1 :
		answer = MASK1;
		break;
	case LVL2 :
		answer = MASK2;
		break;
	case LVL3 :
		answer = MASK3;
		break;
	case LVL4 :
		answer = MASK4;
		break;
	case LVL5 :
		answer = MASK5;
		break;
	case LVL6 :
		answer = MASK6;
		break;
	case SINGLE_USER :
		answer = MASKSU;
		break;
	case LVLa :
		answer = MASKa;
		break;
	case LVLb :
		answer = MASKb;
		break;
	case LVLc :
		answer = MASKc;
		break;
	default :
		answer = FAILURE;
		break;
	}
	return (answer);
}

/*********************/
/****    level    ****/
/*********************/

char level(state)
int state;
{
	register char answer;

	switch(state) {
	case LVL0 :
		answer = '0';
		break;
	case LVL1 :
		answer = '1';
		break;
	case LVL2 :
		answer = '2';
		break;
	case LVL3 :
		answer = '3';
		break;
	case LVL4 :
		answer = '4';
		break;
	case LVL5 :
		answer = '5';
		break;
	case LVL6 :
		answer = '6';
		break;
	case SINGLE_USER :
		answer = 'S';
		break;
	case LVLa :
		answer = 'a';
		break;
	case LVLb :
		answer = 'b';
		break;
	case LVLc :
		answer = 'c';
		break;
	default :
		answer = '?';
		break;
	}
	return(answer);
}

/************************/
/****    killproc    ****/
/************************/

/*	"killproc" sends the SIGTERM signal to the specified process	*/
/*	and then after TWARN seconds, the SIGKILL signal.		*/

killproc(pid)
register int pid;
{
	extern int childeath();
	struct PROC_TABLE *efork();
	register struct PROC_TABLE *process;

	signal(SIGCLD,SIG_DFL);
	while ((process = efork(M_OFF,NULLPROC,0)) == NO_ROOM) pause();
	signal(SIGCLD,childeath);

/* If we are the child, send the signals to the process we are */
/* to kill. */
	if (process == NULLPROC) {
		kill(pid,SIGTERM);	/* Warn the process to quit.    */
		timer(TWARN);	/* Sleep TWARN seconds */
		kill(pid,SIGKILL);	/* Kill the process if still alive. */
		exit(0);
	}
}

/**************************/
/****    initialize    ****/
/**************************/

/*	Perform the initial state setup and look for an initdefault	*/
/*	entry in the "inittab" file.					*/

int initialize()
{
	struct CMD_LINE cmd;
	char command[MAXCMDL];
	extern int cur_state,op_modes;
	extern int childeath();
	register int msk,i;
	static int states[] = {
		LVL0,LVL1,LVL2,LVL3,LVL4,LVL5,LVL6,SINGLE_USER
	};
	static char *envp[] = { 
		"PATH=/sbin:/usr/sbin:/etc:/usr/bin",0
	};
	FILE *fp;
	int initstate,flag;
	register struct PROC_TABLE *process,*oprocess;
	extern struct PROC_TABLE *efork(),*findpslot();

/* Initialize state to "SINGLE_USER" "BOOT_MODES" */
	if(cur_state >= 0) {
		n_prev[cur_state]++;
		prior_state = cur_state;
	}
	cur_state = SINGLE_USER;
	op_modes = BOOT_MODES;

/* Set up all signals to be caught or ignored as is appropriate. */
	init_signals();
	initstate = 0;

#ifdef	UDEBUG
	save_ioctl();
#endif

/* Look for an "initdefault" entry in "/etc/inittab", which */
/* specifies the initial level to which "init" is to go at */
/* startup time. */
	while((flag = getcmd(&cmd,&command[0])) == TRUE) {
		if (cmd.c_action == M_INITDEFAULT) {

/* Look through the "c_levels" word, starting at the highest */
/* level.  The assumption is that there will only be one level */
/* specified, but if there is more than one, the system will */
/* come up at the highest possible level. */
			for (msk=MASKSU,i=(sizeof(states)/sizeof(int)) - 1 ; msk > 0; msk >>= 1 , i--) {
				if (msk & cmd.c_levels) {
					initstate = states[i];
				}
			}

/* If the entry is for a system initialization command, execute */
/* it at once, and wait for it to complete. */
		} else if (cmd.c_action == M_SYSINIT) {
			if (process = findpslot(&cmd)) {
				signal(SIGCLD,SIG_DFL);
				for (oprocess=process; (process = efork(M_OFF,oprocess,(NAMED|NOCLEANUP))) == NO_ROOM;);
				signal(SIGCLD,childeath);
				if (process == NULLPROC) {

/* Notice no bookkeeping is performed on these entries.  This is */
/* to avoid doing anything that would cause writes to the file */
/* system to take place.  No writing should be done until the */
/* operator has had the chance to decide whether the file system */
/* needs checking or not. */
					for (i=0,fp= stdin; i < _NFILE;i++,fp++) fclose(fp);
					execle(SH,"INITSH","-c",cmd.c_command,0,&envp[0]);
/* If the "exec" fails, print an error message. */
#ifdef _RISCOS
					console("Command\n");
					console("\"%s\"\n", cmd.c_command);
					console(" failed to execute.  errno = %d (exec of shell failed)\n", errno);
#else
					console("Command\n\"%s\"\n failed to execute.  errno = %d (exec of shell failed)\n", cmd.c_command,errno);
#endif /* _RISCOS */
					exit(1);
				} else while (waitproc(process) == FAILURE);
#ifdef	ACCTDEBUG
				debug("SYSINIT- id: %.4s term: %o exit: %o\n",
					&cmd.c_id[0],(process->p_exit&0xff),
					(process->p_exit&0xff00)>>8);
#endif
				process->p_flags = 0;
			}
		}
	}

/* Get the ioctl settings for /dev/syscon so that it can be */
/* brought up in the state it was in when the system went down. */
	get_ioctl_syscon();

/* If no "initdefault" entry is found, return 0.  This will */
/* have "init" ask the user at /dev/syscon to supply a level. */
	if (initstate) 
		return(initstate);
	else
		return(flag);
}

/****************************/
/****    init_signals    ****/
/****************************/

/*	Initialize all signals to either be caught or ignored.		*/

init_signals()
{
	extern int siglvl(),alarmclk(),childeath(),powerfail();
#ifdef	UDEBUG
	extern int abort();
#endif

	signal(LVLQ,siglvl);
	signal(LVL0,siglvl);
	signal(LVL1,siglvl);
#ifdef	UDEBUG
	signal(LVL2,SIG_DFL);
	signal(LVL3,SIG_DFL);
	signal(LVL4,SIG_DFL);
#else
	signal(LVL2,siglvl);
	signal(LVL3,siglvl);
	signal(LVL4,siglvl);
#endif
	signal(LVL5,siglvl);
	signal(LVL6,siglvl);
	signal(SINGLE_USER,siglvl);
	signal(LVLa,siglvl);
	signal(LVLb,siglvl);
	signal(LVLc,siglvl);
	alarmclk();
#ifdef	UDEBUG
	signal(SIGTERM,SIG_DFL);
	signal(SIGUSR1,abort);
	signal(SIGUSR2,abort);
#else
	signal(SIGTERM,SIG_IGN);
	signal(SIGUSR1,SIG_IGN);
	signal(SIGUSR2,SIG_IGN);
#endif
	signal(SIGCLD,childeath);
	signal(SIGPWR,powerfail);
}

/**********************/
/****    siglvl    ****/
/**********************/

siglvl(sig)
int sig;
{
	extern int new_state,cur_state;
	extern union WAKEUP wakeup;
#ifndef _RISCOS
	extern struct PROC_TABLE proc_table[];
#endif /* _RISCOS */
	register struct PROC_TABLE *process;

	signal(sig,SIG_IGN);
/* If the signal received is a "LVLQ" signal, do not really */
/* change levels, just restate the current level. */
	if (sig == LVLQ) new_state = cur_state;

/* If the signal received is something other than "LVLQ", set */
/* the new level to the value of the signal received. */
	else new_state = sig;

/* Clear all times and repeat counts in the process table */
/* since either the level is changing or the user has editted */
/* the "/etc/inittab" file and wants us to look at it again.  If */
/* the user has fixed a typo, we don't want residual timing data */
/* preventing the fixed command line from executing. */
	for (process= &proc_table[0]; process < &proc_table[NPROC] ; process++) {
		process->p_time = 0L;
		process->p_count = 0;
	}

/* Set the flag saying that a "user signal" was received. */
	wakeup.w_flags.w_usersignal = 1;
	signal(sig,siglvl);
}

/************************/
/****    alarmclk    ****/
/************************/

alarmclk()
{
	extern int time_up;

	signal(SIGALRM,alarmclk);
	time_up = TRUE;
}

/*************************/
/****    childeath    ****/
/*************************/

childeath()
{
	extern union WAKEUP wakeup;
#ifndef _RISCOS
	extern struct PROC_TABLE proc_table[];
#endif /* _RISCOS */
	register struct PROC_TABLE *process;
	register int pid;
	int status;

/* Perform wait to get the process id of the child who died and */
/* then scan the process table to see if we are interested in */
/* this process. **Note** if a super-user sends the SIGCLD signal */
/* to "init", the following wait will not immediately return */
/* and "init" will be inoperative until one of its child really */
/* does die. */
	pid = wait(&status);
#ifdef	UDEBUG
	debug("childeath: pid- %d status- %x\n",pid,status);
#endif

	for (process= &proc_table[0]; process < &proc_table[NPROC];process++) {
		if ((process->p_flags & (OCCUPIED)) == OCCUPIED && process->p_pid == pid) {

/* Mark this process as having died and store the exit status. */
/* Also set the wakeup flag for a dead child and break out of */
/* loop. */
			process->p_flags &= ~LIVING;
			process->p_exit = status;
			wakeup.w_flags.w_childdeath = 1;
			break;
		}
	}
#ifdef	UDEBUG
	if (process == &proc_table[NPROC]) debug("Didn't find process %d.\n", pid);
#endif

/* Reset the child death signal routine. */
	signal(SIGCLD,childeath);
}

/*************************/
/****    powerfail    ****/
/*************************/

powerfail()
{
	extern union WAKEUP wakeup;

#if u3b2 || u3b5
	nice(-19);
#endif
	wakeup.w_flags.w_powerhit = 1;
	signal(SIGPWR,powerfail);
}

/**********************/
/****    getlvl    ****/
/**********************/

/*	Get the new run level from /dev/syscon.  If someone at		*/
/*	/dev/systty types a <del> while we are waiting for the user	*/
/*	to start typing, relink /dev/syscon to /dev/systty.		*/

int fd_systty;

getlvl()
{
	char c;
	int status, flag;
	extern int switchcon();
#ifdef	UDEBUG
	extern int abort();
#endif
	FILE *fp_tmp, *fp_systty, *fopen(), *fdopen();
	register process;
	extern int childeath();
	extern int fd_systty;
	static char levels[] = {
		LVL0,LVL1,LVL2,LVL3,LVL4,LVL5,LVL6,SINGLE_USER
	};
	extern long waitproc();
#ifndef	CBUNIX
	extern struct termio termio,dflt_termio;
#else
	extern struct ttiocb ttiocb,dflt_ttiocb;
	extern struct sgldisc sgldisc;
	extern struct ttiothcb ttiothcb;
#endif
	extern struct termcb termcb;

/* Fork a child who will request the new run level from */
/* /dev/syscon. */

	signal(SIGCLD,SIG_DFL);
	while ((process = fork()) == -1);
	if (process == 0) {
		signal(SIGHUP,SIG_IGN);

/*
 * Open /dev/systty so that if someone types a <del>, we can be 
 * informed of the fact.  (Must be a process group leader in order for
 * SYSTTY to become our controlling tty so that we get signals.)
 */
		setpgrp();
		if ((fp_tmp = fopen(SYSTTY,"r+")) != NULL) {

/* Make sure the file descriptor is greater than 2 so that it */
/* won't interfere with the standard descriptors. */
			fd_systty = fcntl(fileno(fp_tmp),F_DUPFD,3);
			fp_systty = fdopen(fd_systty, "r+");
			fclose(fp_tmp);

/* Prepare to catch the interupt signal if <del> typed at */
/* /dev/systty. */
			signal(SIGINT,switchcon);
			signal(SIGQUIT,switchcon);
		}
#ifdef	UDEBUG
		signal(SIGUSR1,abort);
		signal(SIGUSR2,abort);
#endif
		for (;;) {

/* Close the current descriptors and open ones to /dev/syscon. */
			opensyscon();

/* Print something unimportant and pause, since reboot may be */
/* taking place over a line coming in over the dataswitch. */
/* The dataswitch sometimes gets the carrier up before the */
/* connection is complete and the first write gets lost. */
			fprintf(stdout,"\n");
			sleep(2);
	
           		flag = TRUE;
	   		while(flag) {

/* Now read in the user response. */

				fprintf(stdout,"ENTER RUN LEVEL (0-6,s or S): ");
				fflush(stdout);

/* Get a character from the user which isn't a space , tab or a <cr>. */
				while((fscanf(stdin,"%c",&c) != 1)
                                	|| (c == '\n') || (c == '\t') || (c == ' '));
				c &= 0x7f;

/* If the character is a digit between 0 and 6 or the letter S, */
/* fine, exit with the level equal to the new desired state. */
				if (c >= '0' && c <= '6') { 
					fprintf(stdout, "will change to state %c\n", c);
					exit(levels[c - '0']);
                		}
				else if (c == 'S' || c == 's') {
					fprintf(stdout, "will change to state %c\n", c);
					exit(levels['7' - '0']);
				}
				else if (c > '6' && c <= '9') {
					fprintf(stdout,"\nUsage: 0123456sS\n");
					fprintf(stdout, " %c is not a valid state\n\n",c);
                       			while ((fscanf(stdin,"%c",&c) != 1) || (c != '\n'));
                		} 
                		else { 
					fprintf(stdout,"\nbad character <%3.3o>\n\n",c);
                       			while ((fscanf(stdin,"%c",&c) != 1) || (c != '\n'));
                		}
	   		}
		}
	}

/* Wait for the child to die and return it's status. */
	while (wait(&status) != process);

#ifdef DEBUG
/* Return the new run level to the caller. */
	debug("getlvl: status: %o exit: %o termination: %o\n", status,(status&0xff00)>>8,(status&0xff));
#endif
	return((status&0xff00)>>8);
}

/*************************/
/****    switchcon    ****/
/*************************/

switchcon(sig)
int sig;
{
	extern int own_pid;
	extern int fd_systty;

	signal(sig,SIG_IGN);

/* If this is the first time a <del> has been typed on the */
/* /dev/systty, then unlink /dev/syscon and relink it to */
/* /dev/systty.  Also reestablish file pointers. */
	if (fd_systty != -1) {
		reset_syscon();
		opensyscon();

/* Set fd_systty to -1 so that we ignore any deletes from it in */
/* the future as far as relinking /dev/syscon to /dev/systty. */
		fd_systty = -1;
	}
	signal(sig,switchcon);
}

/*********************/
/****    efork    ****/
/*********************/

/*	"efork" forks a child and the parent inserts the process in	*/
/*	its table of processes that are directly a result of forks	*/
/*	that it has performed.  The child just changes the "global"	*/
/*	with the process id for this process to it's new value.		*/
/*									*/
/*	If "efork" is called with a pointer into the proc_table		*/
/*	it uses that slot, otherwise it searches for a free slot.	*/
/*	Whichever way it is called, it returns the pointer to the	*/
/*	proc_table entry.						*/

struct PROC_TABLE *efork(action,process,modes)
int action;
register struct PROC_TABLE *process;
int modes;
{
	register int childpid;
	extern int own_pid,errno;
#ifndef _RISCOS
	extern struct PROC_TABLE proc_table[];
#endif /* _RISCOS */
	register struct PROC_TABLE *proc;
	int i;
	extern int childeath();
	void (*oldroutine)();
#ifdef	UDEBUG
	static void (*oldsigs[NPROC])();
#endif

/* Freshen up the proc_table, removing any entries for dead */
/* processes that don't have the NOCLEANUP set.  Perform the */
/* necessary accounting. */
	for (proc= &proc_table[0]; proc < &proc_table[NPROC]; proc++) {
		if ((proc->p_flags & (OCCUPIED | LIVING | NOCLEANUP)) == (OCCUPIED)) {
#ifdef	DEBUG
			debug("efork- id:%s pid: %d time: %lo %d %o %o\n",
				C(&proc->p_id[0]),proc->p_pid, proc->p_time,
				proc->p_count, proc->p_flags,proc->p_exit);
#endif

/* Is this a named process?  If so, do the necessary bookkeeping. */
			if (proc->p_flags & NAMED) account(DEAD_PROCESS,proc,NULL);

/* Free this entry for new usage. */
			proc->p_flags = 0;
		}
	}

	while((childpid = fork()) == FAILURE) {
/* Shorten the alarm timer in case someone else's child dies and */
/* free up a slot in the process table. */
		setimer(5);

/* Wait for some children to die.  Since efork() is normally */
/* called with SIGCLD in the default state, reset it to catch */
/* so that child death signals can come in. */

		oldroutine = signal(SIGCLD,childeath);
		pause();
		signal(SIGCLD,oldroutine);
		setimer(0);
	}

	if (childpid != 0) {
/* If a pointer into the process table was not specified, then */
/* search for one. */
		if (process == NULLPROC) {
			for (process= &proc_table[0]; process->p_flags != 0 && process < &proc_table[NPROC];process++ );
			if (process == &proc_table[NPROC]) {
				if (error_time(FULLTABLE))
					console("Internal process table is full.\n");
				return(NO_ROOM);
			}
			process->p_time = 0L;
			process->p_count = 0;
		}
		process->p_id[0] = '\0';
		process->p_id[1] = '\0';
		process->p_id[2] = '\0';
		process->p_id[3] = '\0';
		process->p_pid = childpid;
		process->p_flags = (LIVING | OCCUPIED | modes);
		process->p_exit = 0;
	} else {
		own_pid = getpid();	/* Reset child's concept of its
					 * own process id.
							 */
		if (action != M_WAIT) {
#ifndef	CBUNIX
			setpgrp(own_pid);	/* Temporary fix for VAX */
#endif
		}
		process = NULLPROC;

/* Reset all signals to the system defaults. */
#ifdef	UDEBUG
		for (i=SIGHUP; i <= SIGPWR;i++)
			oldsigs[i] = signal(i,SIG_DFL);
#else
		for (i=SIGHUP; i <= SIGPWR;i++) signal(i,SIG_DFL);
#endif
	}
	return(process);
}

/************************/
/****    waitproc    ****/
/************************/

/*	"waitproc" waits for a specified process to die.  For this	*/
/*	routine to work, the specified process must already in		*/
/*	the proc_table.  "waitproc" returns the exit status of the	*/
/*	specified process when it dies.					*/
/*									*/

long waitproc(process)
register struct PROC_TABLE *process;
{
#ifndef _RISCOS
	extern struct PROC_TABLE proc_table[];
#endif /* _RISCOS */
	int answer;

/* Wait around until the process dies. */
	if (process->p_flags & LIVING) pause();
	if (process->p_flags & LIVING) return (FAILURE);

/* Make sure to only return 16 bits so that answer will always */
/* be positive whenever the process of interest really died. */
	answer = (process->p_exit & 0xffff);

/* Free the slot in the proc_table. */
	process->p_flags = 0;
	return(answer);
}

/***********************/
/****    account    ****/
/***********************/

#ifdef SVR4_CODE
/*
 * account() updates entries in utmp and utmpx and appends new entries
 * new entries to the end of wtmp and wtmpx (assuming they exist).
 */
account(state, process, program)
int	state;
register struct PROC_TABLE *process;
char	*program;	/* Name of program if INIT_PROCESS, otherwise NULL */
{
	struct utmpx	utmpbuf;
	register struct utmpx *u, *oldu;

#ifdef	ACCTDEBUG
	extern char *C();

	debug("** account ** state: %d id:%s\n", state, C(&process->p_id[0]));
#endif

	/*
	 * Set up the prototype for the utmp structure we want to write.
	 */
	u = &utmpbuf;
	zero(&u->ut_user[0], sizeof(u->ut_user));
	zero(&u->ut_line[0], sizeof(u->ut_line));
	zero(&u->ut_host[0], sizeof(u->ut_host));

	/*
	 * Fill in the various fields of the utmp structure.
	 */
	u->ut_id[0] = process->p_id[0];
	u->ut_id[1] = process->p_id[1];
	u->ut_id[2] = process->p_id[2];
	u->ut_id[3] = process->p_id[3];
	u->ut_pid = process->p_pid;

	/*
	 * Fill the "ut_exit" structure.
	 */
	u->ut_exit.e_termination = process->p_exit & 0xff;
	u->ut_exit.e_exit = process->p_exit >> 8 & 0xff;
	u->ut_type = state;
	time(&u->ut_tv.tv_sec);

	/*
	 * See if there already is such an entry in the "utmp" file.
	 */
	setutxent();	/* Start at beginning of utmp file. */

	if ((oldu = getutxid(u)) != NULL) {
		/*
		 * Copy in the old "user", "line" and "host" fields
		 * to our new structure.
		 */
		bcopy(&oldu->ut_user[0], &u->ut_user[0], sizeof(u->ut_user));
		bcopy(&oldu->ut_line[0], &u->ut_line[0], sizeof(u->ut_line));
		bcopy(&oldu->ut_host[0], &u->ut_host[0], sizeof(u->ut_host));
#ifdef	ACCTDEBUG
		debug("New entry in utmp file.\n");
#endif
	}
#ifdef	ACCTDEBUG
	else debug("Replacing old entry in utmp file.\n");
#endif

	/*
	 * Perform special accounting. Insert the special string into the
	 * ut_line array. For INIT_PROCESSes put in the name of the
	 * program in the "ut_user" field.
	 */
	switch(state) {

	case RUN_LVL:
		u->ut_exit.e_termination = level(cur_state);
		if (program != NULL && oldu != NULL && *program == 'S')
			u->ut_exit.e_exit = oldu->ut_exit.e_termination;
		else
			u->ut_exit.e_exit = level(prior_state);

		u->ut_pid = n_prev[cur_state];
		sprintf(&u->ut_line[0], RUNLVL_MSG, level(cur_state));
		break;

	case BOOT_TIME:
		sprintf(&u->ut_line[0], "%.12s",BOOT_MSG);
		break;

	case INIT_PROCESS:
		strncpy(&u->ut_user[0], program, sizeof(u->ut_user));
		break;

	default:
		break;
	}

	/*
	 * Write out the updated entry to utmp file.
	 */
	if (pututxline(u) == (struct utmpx *)NULL)
		console("failed write of utmpx entry:\"%2.2s\"\n",&u->ut_id[0]);
	endutxent();

	/*
	 * Now attempt to add to the end of the wtmp and wtmpx files.
	 */
	updwtmpx(WTMPX, u);
}
#else /* !SVR4_CODE */
/*	"account" updates entries in /etc/utmp and appends new entries	*/
/*	to the end of /etc/wtmp (assuming /etc/wtmp exists).		*/

account(state,process,program)
int state;
register struct PROC_TABLE *process;
char *program;	/* Name of program in the case of INIT_PROCESSes
		 * otherwise NULL.
		 */
{
	extern cur_state;
	struct utmp utmpbuf;
	register struct utmp *u,*oldu;
	extern struct utmp *getutid(),*pututline();
	extern char *WTMP;
	FILE *fp;
	char level();

#ifdef	ACCTDEBUG
	extern char *C();

	debug("** account ** state: %d id:%s\n",state,C(&process->p_id[0]));
#endif

/* Set up the prototype for the utmp structure we want to write. */
	u = &utmpbuf;
	zero(&u->ut_user[0], sizeof(u->ut_user));
	zero(&u->ut_line[0], sizeof(u->ut_line));

/* Fill in the various fields of the utmp structure. */
	u->ut_id[0] = process->p_id[0];
	u->ut_id[1] = process->p_id[1];
	u->ut_id[2] = process->p_id[2];
	u->ut_id[3] = process->p_id[3];
	u->ut_pid = process->p_pid;

/* Fill the "ut_exit" structure. */
	u->ut_exit.e_termination = (process->p_exit & 0xff);
	u->ut_exit.e_exit = ((process->p_exit >> 8) & 0xff);
	u->ut_type = state;
	time(&u->ut_time);

/* See if there already is such an entry in the "utmp" file. */
	setutent();	/* Start at beginning of utmp file. */
	if ((oldu = getutid(u)) != NULL) {

/* Copy in the old "user", "line", & "host" fields to our new structure. */
		bcopy(&oldu->ut_user[0],&u->ut_user[0],sizeof(u->ut_user));
		bcopy(&oldu->ut_line[0],&u->ut_line[0],sizeof(u->ut_line));
#ifdef	ACCTDEBUG
		debug("New entry in utmp file.\n");
#endif
	}
#ifdef	ACCTDEBUG
	else debug("Replacing old entry in utmp file.\n");
#endif

/* Perform special accounting. Insert the special string into the */
/* ut_line array. For INIT_PROCESSes put in the name of the */
/* program in the "ut_user" field. */
	switch(state) {
	case RUN_LVL :
		u->ut_exit.e_termination = level(cur_state);
		if ( oldu != NULL && program != NULL && *program == 'S')
			u->ut_exit.e_exit = oldu->ut_exit.e_termination;
		else
			u->ut_exit.e_exit = level(prior_state);
		u->ut_pid = n_prev[cur_state];
		sprintf(&u->ut_line[0],RUNLVL_MSG,level(cur_state));
		break;
	case BOOT_TIME :
		sprintf(&u->ut_line[0],"%.12s",BOOT_MSG);
		break;
	case INIT_PROCESS :
		strncpy(&u->ut_user[0],program,sizeof(u->ut_user));
		break;
	default :
		break;
	}

/* Write out the updated entry to utmp file. */
	if (pututline(u) == (struct utmp *)NULL)
		console("failed write of utmp entry: \"%2.2s\"\n",&u->ut_id[0]);

/* Now attempt to add to the end of the wtmp file.  Do not create */
/* if it doesn't already exist.  **  Note  ** This is the reason */
/* "r+" is used instead of "a+".  "r+" won't create a file, while */
/* "a+" will. */
	if ((fp = fopen(WTMP,"r+")) != NULL) {
		fseek(fp,0L,2);	/* Seek to end of file */
		fwrite(u,sizeof(*u),1,fp);
		fclose(fp);
	}
}
#endif /* SVR4_CODE */


/*************************/
/****    prog_name    ****/
/*************************/

/*	"prog_name" searches for the word or unix path name and		*/
/*	returns a pointer to the last element of the pathname.		*/

char *prog_name(string)
register char *string;
{
	register char *ptr,*ptr2;
	struct utmp *dummy;	/* Used only to get size of ut_user */
	static char word[sizeof(dummy->ut_user)+1];

/* Search for the first word skipping leading spaces and tabs. */
	while (*string == ' ' || *string == '\t') string++;

/* If the first non-space non-tab character is not one allowed in */
/* a word, return a pointer to a null string, otherwise parse the */
/* pathname. */
	if (*string != '.' && *string != '/' && *string != '_'
	   && (*string < 'a' || *string > 'z')
	   && (*string < 'A' || * string > 'Z')
	   && (*string < '0' || *string > '9'))
		return("");

/* Parse the pathname looking forward for '/', ' ', '\t', '\n' or */
/* '\0'.  Each time a '/' is found, move "ptr" to one past the */
/* '/', thus when a ' ', '\t', '\n', or '\0' is found, "ptr" will */
/* point to the last element of the pathname. */
	for (ptr=string; *string != ' ' && *string != '\t' && *string != '\n' && *string != '\0'; string++) {
		if (*string == '/') ptr = string+1;
	}

/* Copy out up to the size of the "ut_user" array into "word", */
/* null terminate it and return a pointer to it. */
	for (ptr2= &word[0]; ptr2 < &word[sizeof(dummy->ut_user)] && ptr < string;)
		*ptr2++ = *ptr++;

/* Add null to end of string. */
	*ptr2 = '\0';
	return(&word[0]);
}

/**************************/
/****    opensyscon    ****/
/**************************/

/*	"opensyscon" opens stdin, stdout, and stderr, making sure	*/
/*	that their file descriptors are 0, 1, and 2, respectively.	*/

opensyscon()
{
	register FILE *fp;
	register int state;
#ifndef	CBUNIX
	extern struct termio termio;
#else
	extern struct ttiocb ttiocb;
	extern struct sgldisc sgldisc;
	extern struct ttiothcb ttiothcb;
#endif
	extern struct termcb termcb;
	struct	termio	curterm;	/* current terminal state */
	static in_opensyscon = 0;

	if (in_opensyscon) 
		return;
	in_opensyscon = 1;

	fclose(stdin);
	fclose(stdout);
	fclose(stderr);
	close(0);
	close(1);
	close(2);

	if ((fp = fopen(SYSCON,"r+")) == NULL) {

/* If the open fails, switch back to /dev/systty. */
		reset_syscon();
		fp = fopen(SYSCON,"r+");
		if (fp == NULL) {
			console("Unable to open %s\n",SYSCON);
			in_opensyscon = 0;
			return;
		}
	}
	fdup(fp);
	fdup(fp);
	setbuf(fp,NULL);
	setbuf(stdout,NULL);
	setbuf(stderr,NULL);

/* Make sure the hangup on last close is off.  Then restore */
/* the modes that were on syscon when the signal was sent. */
#ifndef	CBUNIX
	fioctl(fp,TCGETA,&curterm);
	/* Don't overwrite cflag when init console is real system console */
	if (realcon())
		termio.c_cflag = curterm.c_cflag;
	termio.c_cflag &= ~HUPCL;
	fioctl(fp,TCSETA,&termio);
#else
	ttiocb.ioc_flags &= ~HUPCL;
	fioctl(fp,TIOCSETP,&ttiocb);
	fioctl(fp,TIOCSETD,&sgldisc);
	ttiothcb.ioth_flags |= NOHUP;
	fioctl(fp,TIOCSETO,&ttiothcb);
	fioctl(fp,DIOCSETT,&termcb);
#endif
	in_opensyscon = 0;
	return;
}

/********************************/
/****        realcon         ****/
/********************************/

/* realcon returns a nonzero value (1) if there is a
 * character device associated with SYSCON 
 * that has the same device number as SYSTTY
 */

int
realcon()
{
	struct	stat	sconbuf, conbuf;

	if ( (stat(SYSCON, &sconbuf) != -1)
	   && (stat(SYSTTY, &conbuf) != -1)
	   && ((sconbuf.st_mode & S_IFMT) == S_IFCHR)
	   && ((conbuf.st_mode & S_IFMT) == S_IFCHR)
	   && (sconbuf.st_ino == conbuf.st_ino)
	   && (sconbuf.st_rdev == conbuf.st_rdev) )
		return 1;
	else
		return 0;
}

/********************************/
/****    get_ioctl_syscon    ****/
/********************************/

/*	"get_ioctl_syscon" retrieves the /dev/syscon settings from	*/
/*	the file "/etc/ioctl.syscon".					*/

get_ioctl_syscon()
{
	register FILE *fp;
#ifndef	CBUNIX
	int iflags,oflags,cflags,lflags,ldisc,cc[8],i;
	extern struct termio dflt_termio,termio;
#else
	int ispeed,ospeed,erase,kill,flags,ldisc,oflags,i;
	extern struct ttiocb dflt_ttiocb,ttiocb;
	extern struct sgldisc dflt_sgldisc,sgldisc;
	extern struct ttiothcb dflt_other,ttiothcb;
#endif
	int valid_format = 0;
	int termt,tflgs,vrow;
	extern struct termcb dflt_trmcb,termcb;

/* Read in the previous modes for /dev/syscon from ioctl.syscon. */
	if ((fp = fopen(IOCTLSYSCON,"r")) == NULL) {
		console("warning: %s does not exist, default settings assumed\n",IOCTLSYSCON);
		reset_syscon();
	} else {

#ifndef	CBUNIX
		i = fscanf(fp,
			"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x",
			&iflags,&oflags,&cflags,&lflags,&ldisc,
			&cc[0],&cc[1],&cc[2],&cc[3],&cc[4],&cc[5],&cc[6],&cc[7],
			&termt,&tflgs,&vrow);

/* If the file is formatted properly, use the values to initialize */
/* the console terminal condition. */
		if (i == 16) {
			termio = dflt_termio;
			termio.c_iflag = (ushort) iflags;
			termio.c_oflag = (ushort) oflags;
			termio.c_cflag = (ushort) cflags;
			termio.c_lflag = (ushort) lflags;
			termio.c_line = (char) ldisc;
			for(i=0; i<8; i++) termio.c_cc[i] = (char) cc[i];
			termcb = dflt_trmcb;
			termcb.st_termt = termt;
			termcb.st_flgs = tflgs;
			termcb.st_vrow = vrow;
			if (termcb.st_termt != TERM_NONE && termcb.st_flgs)
				termcb.st_flgs |= TM_SET;
			valid_format = 1;
		} 
#else
		i = fscanf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x", &ispeed,&ospeed,
			&erase,&kill,&flags,&ldisc,&oflags,&termt,&tflgs,&vrow);

/* If there are the proper number of arguments, install them. */
		if (i == 10) {
			ttiocb.ioc_ispeed = ispeed;
			ttiocb.ioc_ospeed = ospeed;
			ttiocb.ioc_erase = erase;
			ttiocb.ioc_kill = kill;
			ttiocb.ioc_flags = flags;
			sgldisc.sgl_type = ldisc;
			ttiothcb.ioth_flags = oflags;
			termcb.st_termt = termt;
			termcb.st_flgs = tflgs;
			termcb.st_vrow = vrow;
			if (termcb.st_termt != TERM_NONE && termcb.st_flgs)
				termcb.st_flgs |= TM_SET;
			valid_format = 1;
		}
#endif
		fclose(fp);
/* If the file is badly formatted, use the default settings. */
		if (!valid_format)
			reset_syscon();
	}
}

/****************************/
/****    reset_syscon    ****/
/****************************/

/*	"reset_syscon" relinks /dev/syscon to /dev/systty and puts	*/
/*	the default ioctl setting back into /etc/ioctl.syscon and	*/
/*	the incore arrays.						*/

reset_syscon()
{
	register FILE *fp;
#ifndef	CBUNIX
	extern struct termio dflt_termio,termio;
#else
	extern struct ttiocb dflt_ttiocb,ttiocb;
	extern struct sgldisc dflt_sgldisc,sgldisc;
	extern struct ttiothcb dflt_other,ttiothcb;
#endif
	extern struct termcb dflt_trmcb,termcb;

	unlink(SYSCON);
	link(SYSTTY,SYSCON);
	umask(~0644);
	fp = fopen(IOCTLSYSCON,"w");

#ifndef	CBUNIX
	bcopy(&dflt_termio,&termio,sizeof(struct termio));
	bcopy(&dflt_trmcb,&termcb,sizeof(struct termcb));
	fprintf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n",
		termio.c_iflag,termio.c_oflag,termio.c_cflag,termio.c_lflag,
		termio.c_line, termio.c_cc[0],termio.c_cc[1],termio.c_cc[2],
		termio.c_cc[3], termio.c_cc[4],termio.c_cc[5], termio.c_cc[6],
		termio.c_cc[7], termcb.st_termt,termcb.st_flgs,termcb.st_vrow);
#else
	bcopy(&dflt_ttiocb,&ttiocb,sizeof(struct ttiocb));
	bcopy(&dflt_sgldisc,&sgldisc,sizeof(struct sgldisc));
	bcopy(&dflt_other,&ttiothcb,sizeof(struct ttiothcb));
	bcopy(&dflt_trmcb,&termcb,sizeof(struct termcb));
	fprintf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n",
		ttiocb.ioc_ispeed, ttiocb.ioc_ospeed, ttiocb.ioc_erase,
		ttiocb.ioc_kill, ttiocb.ioc_flags, sgldisc.sgl_type,
		ttiothcb.ioth_flags, termcb.st_termt, termcb.st_flgs,
		termcb.st_vrow);
#endif
	fclose(fp);
	sync();
	umask(0);
}

/**************************/
/****    save_ioctl    ****/
/**************************/

/*	Get the ioctl state of SYSCON and write it into IOCTLSYSCON.	*/

save_ioctl()
{
	register FILE *fp;
	register struct PROC_TABLE *process;
#ifndef	CBUNIX
	extern struct termio termio,dflt_termio;
#else
	extern struct ttiocb ttiocb,dflt_ttiocb;
	extern struct sgldisc sgldisc,dflt_sgldisc;
	extern struct ttiothcb ttiothcb,dflt_other;
#endif
	extern struct termcb termcb,dflt_trmcb;
	extern struct PROC_TABLE *efork();
	extern int errno,childeath();

	signal(SIGCLD,SIG_DFL);
	while ((process = efork(M_OFF,NULLPROC,NOCLEANUP)) == NO_ROOM) timer(2);
	signal(SIGCLD,childeath);

/* If we are the child, open /dev/syscon, do an ioctl to get the */
/* modes, and write them out to "/etc/ioctl.syscon". */
	if (process == NULLPROC) {
		if ((fp = fopen(SYSCON,"w")) == NULL)
			console("Unable to open %s\n",SYSCON);
		else {

/* Turn off the HUPCL bit, so that carrier stays up after the */
/* shell is killed. */

#ifndef	CBUNIX
			if (fioctl(fp,TCGETA,&termio) != FAILURE) {
				termio.c_cflag &= ~HUPCL;
				fioctl(fp,TCSETA,&termio);
			}
#else
			if (fioctl(fp,TIOCGETP,&ttiocb) != FAILURE) {
				ttiocb.ioc_flags &= ~HUPCL;
				fioctl(fp,TIOCSETP,&ttiocb);
			}
			fioctl(fp,TIOCGETD,&sgldisc);
			fioctl(fp,TIOCGETO,&ttiothcb);
			fioctl(fp,DIOCSETT,&termcb);
#endif
		}
		fclose(fp);

/* Write the state of "/dev/syscon" into "/etc/ioctl.syscon" so */
/* that it will be remembered across reboots. */
		umask(~0644);
		if ((fp = fopen(IOCTLSYSCON,"w")) == NULL)
			console("Can't open %s. errno: %d\n",IOCTLSYSCON,errno);
		else {
#ifndef	CBUNIX
			fprintf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n",
				termio.c_iflag,termio.c_oflag,termio.c_cflag,
		 		termio.c_lflag,termio.c_line,termio.c_cc[0],
				termio.c_cc[1],termio.c_cc[2],termio.c_cc[3],
				termio.c_cc[4],termio.c_cc[5],termio.c_cc[6],
				termio.c_cc[7],termcb.st_termt,termcb.st_flgs,
				termcb.st_vrow);
#else
			fprintf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n",
				ttiocb.ioc_ispeed,ttiocb.ioc_ospeed,
				ttiocb.ioc_erase,ttiocb.ioc_kill,
				ttiocb.ioc_flags,sgldisc.sgl_type,
				ttiothcb.ioth_flags,termcb.st_termt,
				termcb.st_flgs,termcb.st_vrow);
#endif
			fclose(fp);
			sync();
		}
		exit(0);
	}

/* If we are the parent, wait for the child to die. */
	else while (waitproc(process) == FAILURE);
}

/***********************/
/****    console    ****/
/***********************/

/*	"console" forks a child if it finds that it is the main "init"	*/
/*	and outputs the requested message to the system console.	*/
/*	Note that the number of arguments passed to "console" is	*/
/*	determined by the print format.					*/

#if	_RISCOS
console(format,arg1,arg2,arg3,arg4)
char *format;
int arg1,arg2,arg3,arg4;
{
	syslog(LOG_ERR,format,arg1,arg2,arg3,arg4);
}

real_console(format,arg1,arg2,arg3,arg4)
#else	/* !_RISCOS */
console(format,arg1,arg2,arg3,arg4)
#endif	/*  !_RISCOS */
char *format;
int arg1,arg2,arg3,arg4;
{
	register struct PROC_TABLE *process;
	extern int own_pid;
	extern long waitproc();
	char outbuf[BUFSIZ];
	extern int childeath();
	extern struct PROC_TABLE *efork();
#ifdef	UDEBUG
	extern int abort();
#endif

/* Are with the original "init"?  If so, fork a child to do the */
/* printing for us. */
	if (own_pid == SPECIALPID) {
		signal(SIGCLD,SIG_DFL);
		while ((process = efork(M_OFF,NULLPROC,NOCLEANUP)) == NO_ROOM) timer(5);
		signal(SIGCLD,childeath);
		if (process == NULLPROC) {
#ifdef	UDEBUG
			signal(SIGUSR1,abort);
			signal(SIGUSR2,abort);
#endif

/* Close the standard descriptors and open the system console. */
			opensyscon();
			setbuf(stdout,&outbuf[0]);

/* Output the message to the console. */
			fprintf(stdout,"\nINIT: ");
			fprintf(stdout,format,arg1,arg2,arg3,arg4);
			fflush(stdout);
			exit(0);

/* The parent waits for the message to complete. */
		} else while(waitproc(process) == FAILURE);

/* If we are some other "init", then just print directly to the */
/* standard output. */
	} else {
		opensyscon();
		setbuf(stdout,&outbuf[0]);
		fprintf(stdout,"\nINIT: ");
		fprintf(stdout,format,arg1,arg2,arg3,arg4);
		fflush(stdout);
	}

#ifdef	ACCTDEBUG
	debug(format,arg1,arg2,arg3,arg4);
#endif
}

/**************************/
/****    error_time    ****/
/**************************/

/*	"error_time" keeps a table of times, one for each time of	*/
/*	error that it handles.  If the current entry is 0 or the	*/
/*	elapsed time since the last error message is large enough,	*/
/*	"error_time" returns TRUE, otherwise it returns FALSE.		*/

error_time(type)
register int type;
{
	long curtime;
	extern struct ERRORTIMES err_times[];

	time(&curtime);
	if (err_times[type].e_time == 0
	   || (curtime - err_times[type].e_time >= err_times[type].e_max)) {
		err_times[type].e_time = curtime;
		return(TRUE);
	} else return(FALSE);
}

/*********************/
/****    timer    ****/
/*********************/

/*	"timer" is a substitute for "sleep" which uses "alarm" and	*/
/*	"pause".							*/

timer(waitime)
register int waitime;
{
	extern union WAKEUP wakeup;
	extern int time_up;

	setimer(waitime);
	while (time_up == FALSE) pause();
}

/***********************/
/****    setimer    ****/
/***********************/

int time_up;	/* Flag set to TRUE by alarm interupt routine
		 * each time an alarm interupt takes place.
		 */

setimer(timelimit)
int timelimit;
{
	alarmclk();
	alarm(timelimit);
	time_up = (timelimit ? FALSE : TRUE);
}

/********************/
/****    zero    ****/
/********************/

zero(adr,size)
register char *adr;
register int size;
{
	while (size--) *adr++ = '\0';
}

/************************/
/****    userinit    ****/
/************************/

/*	Routine to handle requests from users to main init running as	*/
/*	process 1.							*/

userinit(argc,argv)
int argc;
char **argv;
{
	FILE *fp;
	char *ln;
	int saverr;
	int init_signal;
	struct stat sconbuf, conbuf;
	extern int errno;
	extern int own_pid;
	extern char *ttyname();
	extern char *SYSTTY,*SYSCON;

/* We are a user invoked init.  Is there an argument and is it */
/* a single character?  If not, print usage message and quit. */
	if (argc != 2 || argv[1][1] != '\0') {
		fprintf(stderr,"Usage: init [0123456SsQqabc]\n");
		exit(0);
	} else switch (argv[1][0]) {
	case 'Q' :
	case 'q' :
		init_signal = LVLQ;
		break;
	case '0' :
		check_key();
		init_signal = LVL0;
		break;
	case '1' :
		check_key();
		init_signal = LVL1;
		break;
	case '2' :
		check_key();
		init_signal = LVL2;
		break;
	case '3' :
		check_key();
		init_signal = LVL3;
		break;
	case '4' :
		check_key();
		init_signal = LVL4;
		break;
	case '5' :
		check_key();
		init_signal = LVL5;
		break;
	case '6' :
		check_key();
		init_signal = LVL6;
		break;

/* If the request is to switch to single user mode, make sure */
/* that this process is talking to a legal teletype line and that */
/* /dev/syscon is linked to this line. */

	case 'S' :
	case 's' :
		check_key();
		ln = ttyname(0);	/* Get the name of tty */
		if (*ln == '\0') {
			fprintf(stderr,"Standard input not a tty line\n");
			exit(1);
		}
		if ((stat(ln,&sconbuf) != -1)
			&& (stat(SYSCON,&conbuf) != -1)
			&& (sconbuf.st_rdev != conbuf.st_rdev)
			&& (sconbuf.st_ino != conbuf.st_ino)) {

/* Unlink /dev/syscon and then relink it to the current line. */
			if (unlink(SYSCON) == FAILURE) {
				perror("Can't unlink /dev/syscon");
				exit(1);
			}
			if (link(ln,SYSCON) == FAILURE) {
				saverr = errno;
				fprintf(stderr,"Can't link /dev/syscon to %s",ln);
				errno = saverr;
				perror(" ");
				link(SYSTTY,SYSCON);	/* Try to leave a syscon */
				exit(1);
			}
/* Leave a message if possible on system console saying where */
/* /dev/syscon is currently connected. */
			if ((fp = fopen(SYSTTY,"r+")) != NULL) {
				fprintf(fp,"\r\n****	SYSCON CHANGED TO %s	****\r\n", ln);
				fclose(fp);
			}
		}
		init_signal = SINGLE_USER;
		break;

	case 'a' :
		init_signal = LVLa;
		break;
	case 'b' :
		init_signal = LVLb;
		break;
	case 'c' :
		init_signal = LVLc;
		break;

/* If the argument was invalid, print the usage message. */
	default :
		fprintf(stderr,"Usage: init [0123456SsQqabc]\n");
		exit(1);
	}

/* Now send signal to main init and then exit. */
	if (kill(SPECIALPID,init_signal) == FAILURE) {
		fprintf(stderr,"Must be super-user\n");
		exit(1);
	} else exit(0);
}

/*********************/
/****  check_key  ****/
/*********************/

check_key()
{
	if (sysmips(MIPS_KEYLOCKED) == 1) {
		fprintf(stderr, "Machine key locked - can not change state\n");
		exit(1);
	}
}

/*********************/
/****    bcopy    ****/
/*********************/

#ifdef	vax

bcopy(from,to,size)
{
	asm("	movc3	12(ap),*4(ap),*8(ap) ");
}

#else

bcopy(from,to,size)
register char *from,*to;
register int size;
{
	while(size--) *to++ = *from++;
}
#endif

/********************/
/****    fdup    ****/
/********************/

FILE *fdup(fp)
register FILE *fp;
{
	register int newfd;
	register char *mode;

/* Dup the file descriptor for the specified stream and then */
/* convert it to a stream pointer with the modes of the original */
/* stream pointer. */
	if ((newfd = dup(fileno(fp))) != FAILURE) {

/* Determine the proper mode.  If the old file was _IORW, then */
/* use the "r+" option, if _IOREAD, the "r" option, or if _IOWRT */
/* the "w" option.  Note that since none of these force an lseek */
/* by "fdopen", the dupped file pointer will be at the same spot */
/* as the original. */
		if (fp->_flag & _IORW) mode = "r+";
		else if (fp->_flag & _IOREAD) mode = "r";
		else if (fp->_flag & _IOWRT) mode = "w";

/* Something is wrong, close dupped descriptor and return NULL. */
		else {
			close(newfd);
			return(NULL);
		}

/* Now have fdopen finish the job of establishing a new file */
/* pointer. */
		return(fdopen(newfd,mode));
	} else return(NULL);
}

#ifdef	UDEBUG

/*************************/
/****    drop_core    ****/
/*************************/

drop_core(reason)
char *reason;
{
	struct PROC_TABLE *efork();
	FILE *fp;
	extern char *CORE_RECORD;
	extern int childeath();

	signal(SIGCLD,SIG_DFL);
	if (efork(M_OFF,NULLPROC,0) != NULLPROC) return;
	signal(SIGCLD,childeath);

/* Tell user where core is going to be. */
	if ((fp = fopen(CORE_RECORD,"a+")) == NULL) {
		console("Couldn't open \"%s\".\n",CORE_RECORD);
	} else {
		fprintf(fp,"core.%05d: \"%s\"\n",getpid(),reason);
		fclose(fp);
	}
	signal(SIGIOT,SIG_DFL);
	abort();
}
#endif
#ifdef DEBUGGER

/*********************/
/****    debug    ****/
/*********************/

debug(format,arg1,arg2,arg3,arg4,arg5,arg6)
char *format;
int arg1,arg2,arg3,arg4,arg5,arg6;
{
	register FILE *fp;
	register int errnum;
	extern int errno;

#if	_RISCOS
	syslog(LOG_DEBUG,format,arg1,arg2,arg3,arg4,arg5,arg6);
#else	/* !_RISCOS */
	if ((fp = fopen(DBG_FILE,"a+")) == NULL) {
		errnum = errno;
		console("Can't open \"%s\".  errno: %d\n",DBG_FILE,errnum);
		return;
	}
	fprintf(fp,format,arg1,arg2,arg3,arg4,arg5,arg6);
	fclose(fp);
#endif	/* !_RISCOS */
}

/*****************/
/****    C    ****/
/*****************/

char *C(id)
register char *id;
{
	static char answer[12];
	register char *ptr;
	register int i;

	for (i=4,ptr = &answer[0]; --i >= 0;id++) {
		if ( isprint(*id) == 0 ) {
			*ptr++ = '^';
			*ptr++ = *id + 0100;
		} else *ptr++ = *id;
	}
	*ptr++ = '\0';
	return(&answer[0]);
}
#endif

#ifdef DUMPTAB
dump_table()
{
	struct CMD_LINE cmd;
	char cmd_string[MAXCMDL];

	printf("Inittab dump\n");

	while(getcmd(&cmd,&cmd_string[0]) == TRUE) {
		printf("\nInittab entry id %.4s\n", cmd.c_id);
		printf("\tLevel  mask: 0x%x\n", cmd.c_levels);
		printf("\tAction mask: 0x%x\n", cmd.c_action);
		printf("\tCommand    : %s\n", cmd.c_command ? cmd.c_command : "(none)");
	}
}
#endif
