/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */
/*	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.	*/

#ident	"$Header: init.c 1.29 90/09/07 $@(#)init:init.c	1.32"

/*	"init" is the general process spawning program.  It reads	*/
/*	/etc/inittab for a script.					*/
/*									*/
/*	Routines appear in the source code in the following order:	*/
/*									*/
/*     main() */
/*     single() */
/*     remove() */
/*     spawn() */
/*     respawn() */
/*     findpslot() */
/*     getcmd() */
/*     endinittab() */
/*     mask() */
/*     level() */
/*     killproc() */
/*     readenv() */
/*     initialize() */
/*     init_signals() */
/*     siglvl() */
/*     alarmclk() */
/*     childeath() */
/*     powerfail() */
/*     getlvl() */
/*     switchcon() */
/*     efork() */
/*     waitproc() */
/*     account() */
/*     prog_name() */
/*     opensyscon() */
/*     get_ioctl_syscon() */
/*     reset_syscon() */
/*     save_ioctl() */
/*     console() */
/*     error_time() */
/*     timer() */
/*     setimer() */
/*     zero() */
/*     userinit() */
/*     bcopy() */
/*     fdup() */
/*     drop_core() */
/*     debug() */
/*     C() */
/*									*/
/*	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.						*/
/*									*/
/*	PFDEBUG		Generate debug code for powerfail		*/

#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 */
#include	<signal.h>	/* Fix param.h not to duplicate */
#include	<sys/stropts.h>
#include	<stdio.h>
#include	"utmp.h"
#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/boot.h>

#include <pwd.h>		/* for login accounting */
#include <shadow.h>		/* for login accounting */

#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	512

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


/*	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
	/* Start and wait for complete on powerok */
#define	POWEROKWAIT	8
	/* Default level "init" should start at. */
#define	INITDEFAULT	9
	/* Actions performed before init speaks. */
#define	SYSINIT		10

#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_POK		0400
#define	M_INITDEFAULT	01000
#define M_SYSINIT	02000

#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)
#define	PO_MODES	(M_POK)
#define P_MODES		(PF_MODES | PO_MODES)

#define ALL	process = proc_table; process; process = process->p_next

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 */
	struct PROC_TABLE *p_next;	/* linked list modification */
};

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

char	*CONSOLE	=	"/dev/console";	/* Real system console */

#ifdef UDEBUG

char	*UTMP		=	"utmp";
char	*WTMP		=	"wtmp";
char	*INITTAB	=	"inittab";
#ifdef	USE_IOCTL_SYSCON
char	*SYSTTY		=	"/dev/systtyx";
#endif	/* USE_IOCTL_SYSCON */
char	*SYSCON		=	"/dev/sysconx";

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

#else


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

char	*ENVFILE	=	"/etc/TIMEZONE"; /* Change this name in 4.0! */
time_t	envtime;			/* keep the modify time */

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

#endif

char	*SU	=	"/bin/su";	/* Super-user program for single user
					 * mode.
					 */
char	*SH	=	"/bin/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" */
int	power_state = PWR_OK;	/* Current state of power supply */

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

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

#if u3b || u3b15 || 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
struct	termio	dflt_termio = {
	BRKINT|IGNPAR|ISTRIP|IXON|IXANY|ICRNL,
	OPOST|ONLCR|TAB3,
	CS8|CREAD|B300,
	ISIG|ICANON|ECHO|ECHOK,
	0,0177,control(\\),'#','@',control(D),0,0,0
};
#endif

struct	termio	termio, curterm;

#else

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

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 */
		unsigned w_powerok : 1;		/* The OS indicated the power was OK */
	}	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.
			*/
struct PROC_TABLE	*proc_table = NULLPROC; /* Table of active processes */
struct PROC_TABLE	dummy;	/* A zero table used when
				 * calling "account" for non-
				 * process type accounting.
				 */
#ifdef	DEBUG
char comment[120];
#endif

/**************************************************************/
/* array for default global environment                       */
/**************************************************************/
#define MAXENVENT 6  /* max number of default env variables + 1 */
char *glob_envp[MAXENVENT];  /* array of environment strings */

/******************************/
/* for login limit accounting */
/******************************/

static int l_u();
static void mks();

unsigned int systemid;
int numusers;
int initusers;
#define LEGAL_MAGIC	(0x12210000)	/* must not have top bit set */

/******************************/
/* signal masks               */
/******************************/

sigset_t	lvl_blk_set;
sigset_t	lvl_unblk_set;

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

main(argc,argv)
int argc;
char **argv;
{
	register int edi;	/* Boot flags, passed through from boot. */
	register int esi;	/* System ID */

	extern int own_pid;
	extern char *UTMP;
	extern int prev_state,cur_state,new_state,op_modes;
	extern union WAKEUP wakeup;
	extern int errno;
	char utmplock[84];
	int defaultlevel,initialize();
	FILE *fp;
	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;
	int fd;
	struct stat sbuf;
#ifdef	DEBUG
	extern char comment[];
#endif
	/* code for login accounting support */

	int howto;

	howto = edi;
	systemid = esi;

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

/* Determine if we are process 1, the main init, or a user invoked */
/* init, whose job it is to inform init to change levels or */
/* perform some other action. */
	if ((own_pid = getpid()) != SPECIALPID) userinit(argc,argv);

/* Set up the initial states and see if there is a default level */
/* supplied in the "/etc/inittab" file. */
	defaultlevel = initialize();
	chg_lvl_flag = FALSE;

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

/* do login limit accounting */
	
	if (own_pid == SPECIALPID) {
		initusers = l_u(systemid);
		if (initusers == 0)
			initusers = 1;
		kill(1, LEGAL_MAGIC|initusers);
	}

/* If the RB_SINGLE bit was turned on in the boot flag, set the run */
/* state to SINGLE_USER, else trust the default level supplied */
/* If there is no default level supplied, ask the user to supply */
/* one. */
	if (howto & RB_SINGLE) {
		new_state = SINGLE_USER;
		if (defaultlevel == -1)
			defaultlevel = 0;
	} else if (defaultlevel == 0) new_state = getlvl();
	else if (defaultlevel == -1) {
		new_state = SINGLE_USER;
		defaultlevel = 0;
	} else new_state = defaultlevel;
	if (new_state == SINGLE_USER) {
		/* zero data base files */
		umask(022);
		if ((fd = creat(UTMP, 0644)) != -1)
			(void) close(fd);
		if ((fd = creat(UTMP_DBM_PAG_FILE, 0644)) != -1)
			(void) close(fd);
		if ((fd = creat(UTMP_DBM_DIR_FILE, 0644)) != -1)
			(void) close(fd);
		account(RUN_LVL,&dummy,"S");
		endutent();
		umask(0);	/* Allow later files to be created normally. */
		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" file 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);
	endutent();		/* insure that accounting files closed */
	if ((fp = fopen(UTMP,"w+")) == NULL) {
		console("Cannot create %s\n",UTMP,0);
/* Without "utmp" file default to single user mode. */
		cur_state = SINGLE_USER;
	} else {
		fclose(fp);
		if ((fd = creat(UTMP_DBM_PAG_FILE, 0644)) != -1)
			(void) close(fd);
		if ((fd = creat(UTMP_DBM_DIR_FILE, 0644)) != -1)
			(void) close(fd);
		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. */
/* block level change signals  */
	sigprocmask(SIG_BLOCK, &lvl_blk_set, &lvl_unblk_set);
	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);
			console("New run level: %c\n",level(cur_state));
		}

/* Before starting new processes, make sure the global environment */
/* is up to date. */
		if (stat(ENVFILE, &sbuf) == 0 && sbuf.st_mtime > envtime) {
			int i;

			envtime = sbuf.st_mtime;
			/* free the old environment and get the new one */
			for (i=0; i<MAXENVENT-1 && glob_envp[i] != NULL; i++) {
				if (glob_envp[i] != NULL)
					free(glob_envp[i]);
			}
			readenv();
		}

/* 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);
			endutent();
			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 with powerfail code 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;
			power_state = PWR_FAIL;

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

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

/* 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 or powerok sequence(which had */
/* the prev_state == 0), set the prev_state = cur_state before the */
/* next pass through. */
			if (op_modes == PF_MODES || op_modes == PO_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);
				sigsuspend(&lvl_unblk_set);
				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;
					}

#ifdef	USE_IOCTL_SYSCON
/* 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();
				}
#endif	/* USE_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;
				power_state = PWR_FAIL;
/*    Else, if we awoke because of a powerok, change the operating    */
/*    mode to powerok mode.                                           */
			} else if (wakeup.w_flags.w_powerok) {
				op_modes = PO_MODES;
				power_state = PWR_OK;
			}

/* 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 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 (;;) {
		console("SINGLE USER MODE\n");
		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 || u3b15
	/* 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

#if i386
	/* did we waken because of a single-user powerfail signal? */
	/* if so, spawn /etc/powerfail from inittab */
			if (wakeup.w_flags.w_powerhit
			    || wakeup.w_flags.w_powerok) {
#ifdef	PFDEBUG
				console("Single user powerfail\n");
#endif
				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;
/* We need to protect powerhit and  powerok.  This code used to be */
/*	wakeup.w_mask = 0;	Now instead we clear all of the */ 
/* other bits indivdually */
					wakeup.w_flags.w_usersignal = 0;
					wakeup.w_flags.w_childdeath = 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". */

/* We need to protect powerhit and powerok.  This  code used to be */
/* 	wakeup.w_mask = 0;	 Now instead we clear all of the */
/* other bits indivdually */
			wakeup.w_flags.w_usersignal = 0;
			wakeup.w_flags.w_childdeath = 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()
{
	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 (ALL)
		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 (ALL) {

/* 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 c: %d f: %o e: %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 (ALL) {
/* 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)) sigsuspend(&lvl_unblk_set);
		}

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

/* 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 (ALL) {
		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()
{
	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" and "powerok" flags.  If one is set, */
/* make sure the modes are PF_MODES or PO_MODES and clear the */
/* corresponding flag.  Avoid the possible race on the flags by */
/* disallowing a new powerfail or powerok interupt between the test */
/* and the clearing of the flags. */

	if (wakeup.w_flags.w_powerhit) {
		wakeup.w_flags.w_powerhit = 0;
		op_modes = PF_MODES;
		power_state = PWR_FAIL;
	}
	else if (wakeup.w_flags.w_powerok) {
		wakeup.w_flags.w_powerok = 0;
		op_modes = PO_MODES;
		power_state = PWR_OK;
	}
	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:\tid: %s\tpid: %05d\n%s\tc: %d\tf: %o\te: %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:\tid: %s\t levels: %o\ti action: %o\ncommand: \"%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, POWERWAIT, or POWEROKWAIT, 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=/bin:/etc:/usr/bin",0
	};
	extern char *prog_name();
	extern int rsflag;
	int maxfiles;

#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 |  M_POK)) != 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. */
				console("Command is respawning\
 too rapidly.  Check for possible errors.\nid:%4s \"%s\"\n",
				    &cmd->c_id[0], &cmd->c_command[EXEC]);
				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) 
			sigsuspend(&lvl_unblk_set);
	else
		while ((process = efork(M_OFF,oprocess,modes)) == NO_ROOM) 
			sigsuspend(&lvl_unblk_set);

/* 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]));
		maxfiles = ulimit(4,0);
		for( i=0; i<maxfiles; i++ ) fcntl(i, F_SETFD, 1);

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

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

/* 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;
{
	register struct PROC_TABLE *process,*empty;
	extern char *malloc();

	empty = NULLPROC;

	for (ALL) {
		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. */

/* Modified to create new table entry if possible. */
	
	if (process != NULLPROC)
		return(process);	/* found slot with same id */
	
	if (empty != NULLPROC)
		return(empty);		/* found empty slot */

	/* Create a new entry, link it in table, and then return pointer to it. */

	empty = (struct PROC_TABLE *) malloc(sizeof(struct PROC_TABLE));
	if (empty == NULLPROC)
		return(NULLPROC);
	
	empty->p_next = proc_table;	/* link to existing */
	proc_table = empty;		/* insert at head of chain */

	empty->p_id[0] = '\0';
	empty->p_id[1] = '\0';
	empty->p_id[2] = '\0';
	empty->p_id[3] = '\0';
	empty->p_pid = 0;
	empty->p_time = 0L;
	empty->p_count = 0;
	empty->p_flags = 0;
	empty->p_exit = 0;

	return(empty);
}

/**********************/
/****    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","powerokwait",
		"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_POK,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 :

/* 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 | M_POK)))
		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)
		sigsuspend(&lvl_unblk_set);
	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);
	}
}

/***********************/
/****    readenv    ****/
/***********************/

/* Set up the default environment for all procs to be forked from init. */
/* Read the values from the /etc/TIMEZONE file, except for PATH.        */
/* If there's not room in the environment array, the environment lines  */
/* that don't fit are silently discarded.                               */

readenv()
{
	char command[MAXCMDL];
	FILE *fp;
	int i, len;
	char *cmdptr;
	char *c;
 	char *cptr1, *cptr2;
 	extern char *strpbrk() ;

	glob_envp[0] = malloc((unsigned)(strlen("PATH=/bin:/etc:/usr/bin")+2));
	strcpy( glob_envp[0],"PATH=/bin:/etc:/usr/bin");

	if( (fp = fopen(ENVFILE,"r")) != NULL )
	{
		i = 1;
	envloop:
		while( i < MAXENVENT-1 && fgets(command, MAXCMDL-1, fp) )
		{
			cmdptr = command;
		cmdloop:
			for( c=cmdptr; *c != '\0'; c++ )
			{
				if( *c == '#' ) {
					goto envloop;
				}
				if( *c == '=' )
				{
					char	*tmpptr, *nxtptr;

					while( (cmdptr[0] == '\t') ||
						(cmdptr[0] == ' ') ) {
						cmdptr++;
					}
					tmpptr = cmdptr;
					while( (tmpptr[0] != '\0') &&
						(tmpptr[0] != '\n') &&
						(tmpptr[0] != ';') &&
						(tmpptr[0] != '#') ) {
						tmpptr++;
					}
					if ( (tmpptr[0] == '#') && (
						(tmpptr[-1] != '\t') &&
						(tmpptr[-1] != ' '))) {
						console("init: BAD /etc/TIMEZONE format, line: %s\n", cmdptr);
						goto envloop;
					}
					nxtptr = NULL;
					if (tmpptr[0] == ';')
						nxtptr = &tmpptr[1];
					while( (tmpptr[-1] == '\t') ||
						(tmpptr[-1] == ' ') ) {
						tmpptr--;
					}
					tmpptr[0] = '\0';
					len = strlen(cmdptr);
					/* while loop strips double and single quotes */
					while ( (cptr1 = strpbrk(cmdptr,"\"\'")) != NULL )
					{
						for (cptr2=cptr1; cptr2 < &cmdptr[len-1] ; cptr2++ ) {
							*cptr2 = *(cptr2+1) ;
						}

						len-- ;
					}
					glob_envp[i]= malloc((unsigned)(len+1));
					strcpy(glob_envp[i], cmdptr);
					i++;
					if (nxtptr && i < MAXENVENT-1) {
						cmdptr = nxtptr;
						goto cmdloop;
					}
					goto envloop;
				}
			}
		}

/* Append a null pointer to the environment array to mark its end.  */
/* If the array's full, write over the last entry so the system     */
/* doesn't panic.                                                   */

		if( i < MAXENVENT )
			glob_envp[i] = NULL;
		else
			glob_envp[MAXENVENT-1] = NULL;
		fclose(fp);
	}
	else
		printf("Cannot open %s. Environment not initialized.\n", ENVFILE);
}

/**************************/
/****    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();
	extern char *malloc();
	register int msk,i;
	static int states[] = { LVL0,LVL1,LVL2,LVL3,LVL4,LVL5,LVL6,SINGLE_USER };
	int initstate,flag;
	register struct PROC_TABLE *process,*oprocess;
	extern struct PROC_TABLE *efork(),*findpslot();
	int maxfiles;
	struct stat sbuf;

/* 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	USE_IOCTL_SYSCON
#ifdef	UDEBUG
	save_ioctl();
#endif
#endif	/* USE_IOCTL_SYSCON */

/* Initialize the environment for children of init.  Remember the */
/* modification time of ENVFILE for future state changes. */
	(void) stat(ENVFILE, &sbuf);
	envtime = sbuf.st_mtime;
	readenv();

/* 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. */
					maxfiles = ulimit(4,0);
					for( i=0; i<maxfiles; i++ ) fcntl(i,F_SETFD,1);
					execle(SH,"INITSH","-c",cmd.c_command,0,&glob_envp[0]);
/* If the "exec" fails, print an error message. */
					console("Command\n\"%s\"\n failed to execute.  errno = %d (exec of shell failed)\n", cmd.c_command,errno);
					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;
			}
		}
	}

#ifdef	USE_IOCTL_SYSCON
/* 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();
#endif	/* USE_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

	sigemptyset(&lvl_blk_set);
	sigset(LVLQ,siglvl);
	sigaddset(&lvl_blk_set, LVLQ);
	sigset(LVL0,siglvl);
	sigaddset(&lvl_blk_set, LVL0);
	sigset(LVL1,siglvl);
	sigaddset(&lvl_blk_set, LVL1);
#ifdef	UDEBUG
	sigset(LVL2,SIG_DFL);
	sigaddset(&lvl_blk_set, LVL2);
	sigset(LVL3,SIG_DFL);
	sigaddset(&lvl_blk_set, LVL3);
	sigset(LVL4,SIG_DFL);
	sigaddset(&lvl_blk_set, LVL4);
#else
	sigset(LVL2,siglvl);
	sigaddset(&lvl_blk_set, LVL2);
	sigset(LVL3,siglvl);
	sigaddset(&lvl_blk_set, LVL3);
	sigset(LVL4,siglvl);
	sigaddset(&lvl_blk_set, LVL4);
#endif
	sigset(LVL5,siglvl);
	sigaddset(&lvl_blk_set, LVL5);
	sigset(LVL6,siglvl);
	sigaddset(&lvl_blk_set, LVL6);
	sigset(SINGLE_USER,siglvl);
	sigaddset(&lvl_blk_set, SINGLE_USER);
	sigset(LVLa,siglvl);
	sigaddset(&lvl_blk_set, LVLa);
	sigset(LVLb,siglvl);
	sigaddset(&lvl_blk_set, LVLb);
	sigset(LVLc,siglvl);
	sigaddset(&lvl_blk_set, LVLc);
	sigset(SIGALRM,alarmclk);
	alarmclk();
#ifdef	UDEBUG
	sigset(SIGTERM,SIG_DFL);
	sigset(SIGUSR1,abort);
	sigset(SIGUSR2,abort);
#else
	sigset(SIGTERM,SIG_IGN);
	sigset(SIGUSR1,SIG_IGN);
	sigset(SIGUSR2,SIG_IGN);
	sigset(SIGTSTP,SIG_IGN);	/* ignore job control signals */
	sigset(SIGSTOP,SIG_IGN);	/* ignore job control signals */
	sigset(SIGTTIN,SIG_IGN);	/* ignore job control signals */
	sigset(SIGTTOU,SIG_IGN);	/* ignore job control signals */
#endif
	signal(SIGCLD,childeath);
	/*
	 * don't mask SIGCLD - there is no need to and it breaks wait.
	 */
	sigset(SIGPWR,powerfail);
	sigaddset(&lvl_blk_set, SIGPWR);
}

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

siglvl(sig)
int sig;
{
	extern int new_state,cur_state;
	extern union WAKEUP wakeup;
	register struct PROC_TABLE *process;

/* 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 (ALL) {
		process->p_time = 0L;
		process->p_count = 0;
	}

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

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

alarmclk()
{
	extern int time_up;

	time_up = TRUE;
}

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

childeath()
{
	extern union WAKEUP wakeup;
	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 (ALL) {
		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 == NULLPROC) debug("Didn't find process %d.\n", pid);
#endif

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

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

powerfail()
{
	extern union WAKEUP wakeup;
	extern sigcontex();
	int code;
#ifdef PFDEBUG
	int rc;
#endif

#if u3b2 || u3b15
	nice(-19);
#endif

#ifdef PFDEBUG
	if (rc = sigcontext(SF_CODE, &code)) {
		console("powerfail %d\n", rc);
		return;
	}
#else
	sigcontext(SF_CODE, &code);
#endif
	if (code == PWR_OK) {
		wakeup.w_flags.w_powerok = 1;
	}
/* We do not want to kick off anoter powerfail if we have already indicated */
/* We are on the UPS without having gone off it */
	else if (code == PWR_FAIL && power_state == PWR_OK) {
		wakeup.w_flags.w_powerhit = 1;
	}
#ifdef PFDEBUG
	else {
		/* Since kill can not send a code to init, for debugging */
		/* Alternately indicate powerhit and powerok */
		console("powerfail: Invalid code 0x%x\n", code);
		if (power_state == PWR_OK) {
			power_state = PWR_FAIL;
			wakeup.w_flags.w_powerhit = 1;
			console("powerfail: set power fail\n");
		} else {
			power_state = PWR_OK;
			wakeup.w_flags.w_powerok = 1;
			console("powerfail: set power ok\n");
		}
	}
#endif

	/* If they are both on then their is no change of state, so clear */
	/* both of them.  This assumes the flags are cleared just before */
	/* action is taken on them. */
	if (wakeup.w_flags.w_powerok == 1 && wakeup.w_flags.w_powerhit == 1) {
		wakeup.w_flags.w_powerok = 0;
		wakeup.w_flags.w_powerhit = 0;
	}		
#ifdef PFDEBUG
	if (wakeup.w_flags.w_powerok == 1) {
		console("powerfail:  power ok on\n");
	}
	if (wakeup.w_flags.w_powerhit == 1) {
		console("powerfail:  power fail on\n");
	}
#endif
}

/**********************/
/****    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;
#ifdef	USE_IOCTL_SYSCON
	extern int switchcon();
#else
	struct stat sbuf;
#endif	/* USE_IOCTL_SYSCON */
#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);
	endutent();			/* insure accounting files closed */
	while ((process = fork()) == -1);
	if (process == 0) {
		sigset(SIGHUP,SIG_IGN);

#ifdef	USE_IOCTL_SYSCON
/* Open /dev/systty so that if someone types a <del>, we can be */
/* informed of the fact. */
		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. */
			sigset(SIGINT,switchcon);
			sigset(SIGQUIT,switchcon);
		}
#endif	/* USE_IOCTL_SYSCON */
#ifdef	UDEBUG
		sigset(SIGUSR1,abort);
		sigset(SIGUSR2,abort);
#endif
		for (;;) {

#ifndef	USE_IOCTL_SYSCON
/* Try to read from the current stdin.  If that fails use SYSCON. */
			if (fstat(stdin, &sbuf) < 0 || fstat(stdout, &sbuf) < 0)
#endif	/* USE_IOCTL_SYSCON */
/* 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>. */
				switch (c = getc(stdin)) {
				    case '\n':
				    case '\t':
				    case ' ':
					break;
/* 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. */
				    case '0': case '1': case '2':
				    case '3': case '4': case '5':
				    case '6': case 's': case 'S':
					fprintf(stdout, "will change to state %c\n", c);
					if (c == 'S' || c == 's')
					    exit(levels['7' - '0']);
					else
					    exit(levels[c - '0']);
				    default:
					fprintf(stdout,"\nreply with one of 0123456sS\n");
					if (c>037 && c != 0177)
						fprintf(stdout, " %c is not a valid state\n\n",c);
					else
						fprintf(stdout, " <%3.3o> is not a valid character\n\n",c);
					if feof(stdin) clearerr(stdin);
                       			break;
                		} 
				if (c != '\n')
					/* user had to type return, so
					   eat until end of line */
					while ((c = getc(stdin)) != '\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);
}

#ifdef	USE_IOCTL_SYSCON
/*************************/
/****    switchcon    ****/
/*************************/

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


/* 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;
	}
}
#endif	/* USE_IOCTL_SYSCON */

/*********************/
/****    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;
	register struct PROC_TABLE *proc;
	struct PROC_TABLE *p_new;
	int i;
	extern int childeath();
	void (*oldroutine)();
	extern char *malloc();
#ifdef	UDEBUG
	static void (*oldsigs[MAXSIG])();
#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; proc ; proc = proc->p_next) {
		if ((proc->p_flags & (OCCUPIED | LIVING | NOCLEANUP)) == (OCCUPIED)) {
#ifdef	DEBUG
			debug("efork- id:%s pid: %d time: %lo c: %d f: %o e: %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;
		}
	}

	endutent();			/* insure accounting files closed */
	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);
		sigsuspend(&lvl_unblk_set);
		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; process && process->p_flags != 0 ;process = process->p_next );
			if (process == NULLPROC) {
				process = (struct PROC_TABLE *) malloc(sizeof(struct PROC_TABLE));
				if (process == NULLPROC) {
					if (error_time(FULLTABLE))
						console("Internal process table is full.\n");
					return(NO_ROOM);
				} else {
					process->p_next = proc_table;
					proc_table = process;   /* link in */
				}
			}
			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
			setsid();
#endif
		}
		process = NULLPROC;
		sigprocmask(SIG_UNBLOCK, &lvl_blk_set, 0);

/* Reset all signals to the system defaults. */
#ifdef	UDEBUG
		for (i=SIGHUP; i <= SIGPWR;i++)
			oldsigs[i] = sigset(i,SIG_DFL);
#else
		(void)sigset(SIGHUP, SIG_DFL);
		(void)sigset(SIGINT, SIG_DFL);
		(void)sigset(SIGQUIT, SIG_DFL);
		(void)sigset(SIGILL, SIG_DFL);
		(void)sigset(SIGTRAP, SIG_DFL);
		(void)sigset(SIGIOT, SIG_DFL);
		(void)sigset(SIGABRT, SIG_DFL);
		(void)sigset(SIGEMT, SIG_DFL);
		(void)sigset(SIGFPE, SIG_DFL);
		(void)sigset(SIGKILL, SIG_DFL);
		(void)sigset(SIGBUS, SIG_DFL);
		(void)sigset(SIGSEGV, SIG_DFL);
		(void)sigset(SIGSYS, SIG_DFL);
		(void)sigset(SIGPIPE, SIG_DFL);
		(void)sigset(SIGALRM, SIG_DFL);
		(void)sigset(SIGTERM, SIG_DFL);
		(void)sigset(SIGUSR1, SIG_DFL);
		(void)sigset(SIGUSR2, SIG_DFL);
		(void)sigset(SIGCLD, SIG_DFL);
		(void)sigset(SIGPWR, SIG_IGN);
		(void)sigset(SIGWINCH, SIG_IGN);
		(void)sigset(SIGPOLL, SIG_IGN);
		(void)sigset(SIGSTOP, SIG_IGN);
		(void)sigset(SIGTSTP, SIG_IGN);
		(void)sigset(SIGCONT, SIG_IGN);
		(void)sigset(SIGTTIN, SIG_IGN);
		(void)sigset(SIGTTOU, SIG_IGN);
#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;
{
	int answer;

/* Wait around until the process dies. */
	if (process->p_flags & LIVING) sigsuspend(&lvl_unblk_set);
	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    ****/
/***********************/

/*	"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;
	extern struct utmp *getutid(),*pututline();
	extern char *WTMP;
	FILE *fp;
	char level();
	char buf[80];

#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);

	setutent();	/* Start at beginning of utmp file. */

/* Preform 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 && (*program == 'S'))
			u->ut_exit.e_exit = level(SINGLE_USER);
		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]);
#ifdef ACCTDEBUG
		console("  utmp_id= [%d][%d][%d][%d]\n", u->ut_id[0],
		  u->ut_id[1], u->ut_id[2], u->ut_id[3]);
		strncpy(buf, u->ut_user, sizeof(u->ut_user));
		console("  utmp ut_user= %s\n", buf);
		strncpy(buf, u->ut_line, sizeof(u->ut_line));
		console("  utmp ut_line= %s\n", buf);
		console("  utmp ut_type= %d\n", u->ut_type);
		console("  my pid is %d\n", getpid());
#endif
	}

	/* Now attempt to add to the end of the wtmp file.
	 * Do not create the file if it doesn't exist.
	 * Old code used to open mode "r+" and seek to end.
	 * Did this because "a+" will create if it does not exist.
	 * This is why the access() call is made.  Don't want to
	 * create if the file does not exist.
	 */

	if (access(WTMP, 02) == 0) {
		if ((fp = fopen(WTMP,"a")) != NULL) {
			fwrite(u,sizeof(*u),1,fp);
			fclose(fp);
		}
	}
}

/*************************/
/****    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;
	extern struct termio curterm;	/* current terminal state */
	char	name[20];

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

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

#ifdef	USE_IOCTL_SYSCON
/* If the open fails, switch back to /dev/systty. */
		reset_syscon();
#endif	/* USE_IOCTL_SYSCON */
		fp = fopen(SYSCON,"r+");
	}
	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
	/*Get current SYSCON settings, console() restores them */
#ifdef	USE_IOCTL_SYSCON
	fioctl(fp,TCGETA,&curterm);
	curterm.c_cflag &= ~HUPCL;
	/* 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);
	fioctl(fp,LDSETT,&termcb);
#else
	fioctl(fp,TCGETA,&curterm);
	curterm.c_cflag &= ~HUPCL;
	fioctl(fp,TCSETA,&curterm);
#endif	/* USE_IOCTL_SYSCON */
#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
	return;
}

#ifdef	USE_IOCTL_SYSCON
/********************************/
/****        realcon         ****/
/********************************/

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

int
realcon()
{
	struct	stat	sconbuf, conbuf;

	if ( (stat(SYSCON, &sconbuf) != -1)
	   && (stat(CONSOLE, &conbuf) != -1)
	   && (sconbuf.st_mode & S_IFCHR)
	   && (conbuf.st_mode & 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.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.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);
			}
			fioctl(fp,LDGETT,&termcb);
#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);
}
#endif	/* USE_IOCTL_SYSCON */

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

console(format,arg1,arg2,arg3,arg4)
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();
	FILE *fp;
#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
			sigset(SIGUSR1,abort);
			sigset(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);
			/*Restore the setting saved in opensyscon()*/
			fioctl (stdout, TCSETAW, &curterm) ;
			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);
		/*Restore the setting saved in opensyscon()*/
		fioctl (stdout, TCSETAW, &curterm) ;
	}
#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 int time_up;

	setimer(waitime);
	while (time_up == FALSE) sigsuspend(&lvl_unblk_set);
}

/***********************/
/****    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;
#ifdef	USE_IOCTL_SYSCON
	extern char *ttyname();
	extern char *SYSTTY,*SYSCON;
#endif	/* USE_IOCTL_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' :
		init_signal = LVL0;
		break;
	case '1' :
		init_signal = LVL1;
		break;
	case '2' :
		init_signal = LVL2;
		break;
	case '3' :
		init_signal = LVL3;
		break;
	case '4' :
		init_signal = LVL4;
		break;
	case '5' :
		init_signal = LVL5;
		break;
	case '6' :
		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' :
#ifdef	USE_IOCTL_SYSCON
		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,"\n****	SYSCON CHANGED TO %s	****\n", ln);
				fclose(fp);
			}
		}
#endif	/* USE_IOCTL_SYSCON */
		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);
}

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

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

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

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

/*
 * Code for login limit accounting.  A password entry "usrlimit" contains
 * the number of simultanious logins allowded.  This is in the "gecos" field.
 * It is picked up as the first string of digits in the field.
 *
 * The system id and this number of users are permuted and then compared
 * with the password entry for "usrlimit".  If they match, then this is then
 * number of users we will allow.  Otherwise allow 1.
 */

extern struct passwd *getpwnam();
extern struct spwd *getspnam();

#define NKEYS	64

/*
 * WARNING -- The following copyright string is not used for printing.
 *            It is used in user limit generation.  If the string is
 *            altered a new userlimit will be required.
 */

static char saltc[2+1];
static char copyright[] =
 "DYNIX/psx(TM) Copyright (C) 1986, 1987, 1988, 1989 Sequent Computer Systems";
static char key1[2*NKEYS];

static int
l_u(id)
	int id;
{
	register int n, i, j;
	struct passwd *p_ptr;
	struct spwd   *s_ptr;
	char *pw;
	char *p;
	int count;

	id &= 0xFFFFFF;

	p_ptr = getpwnam("usrlimit");
	if (p_ptr == NULL) {
		console("No password entry for 'usrlimit', setting logins to 1\n");
		return(0);
	}
	endpwent();

	if (access(SHADOW,0))
		pw = p_ptr->pw_passwd;
	else {
		s_ptr = getspnam("usrlimit");
		if (s_ptr == NULL) {
			console("No shadow entry for 'usrlimit', setting logins to 1\n");
			return(0);
		}
		pw = s_ptr->sp_pwdp;
	}
	p = p_ptr->pw_gecos;
	if (!p) {
		console("No user count in 'usrlimit' password, setting logins to 1\n");
		return(0);
	}
	while (*p && (*p < '0' || *p > '9'))
		p++;
	
	if (!p || !*p) {
		console("No user count in 'usrlimit' password, setting logins to 1\n");
		return 0;
	}
	count = atoi(p);
	if (count <= 0)
		return(0);

	/* make salt */
	mks(id, count);

	/* now permute and genearate string */

	for (i=0; i < sizeof(key1); i++)
		key1[i] = '\0';
	p = copyright;
	for (i=0, p = copyright; (i < sizeof(key1) && *p); i++)
		key1[i] = *p++;

	for (i=0; i < NKEYS; i++) {
		for (j=0; j < NKEYS-3; j++) {
			n = *(int *)&key1[j];
#define	FUNC(a, b, c) (((a) * (c) * 1103515245) + 12345 + (b)) & 0x7fffffff;
			n = FUNC(id, count, n);
			*(int *)&key1[j] = n;
		}
	}

	if (strcmp(pw, crypt(key1, saltc)) == 0)
		return(count);
	
	console("Bad 'usrlimit' password, setting number of logins to 1\n");
#ifdef DEBUG1
	debug("Bad usrlimit: id= 0x%x, count= %d\n", id, count);
	debug("Found usrlimt pw '%s', expected pw '%s'\n", pw, crypt(key1, saltc));
#endif
	return(0);
}

#define USRLIM_VER	2

static void
mks(id, users)
{
	register salt, i, c;

	/* 24 bits of ether address or other value for system id */
	salt = ((((id & 0xFFF) + users + USRLIM_VER) + 
		(id >> 12)) * 1103515245) + 12345;
	saltc[0] = (salt & 077);
	saltc[1] = ((salt>>6) & 077);
	for (i = 0; i < 2; i++) {
		c = saltc[i] + '.';
		if (c > '9') c += 7;
		if (c > 'Z') c += 6;
		saltc[i] = c;
	}
}
