/*	@(#)init.c	2.1		*/

/*	"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() */
/*     findslot() */
/*     getcmd() */
/*     endinittab() */
/*     mask() */
/*     level() */
/*     killproc() */
/*     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.						*/

#ifdef	ACCTDEBUG
#define	DEBUGGER
#endif

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

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

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

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

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

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

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

#ifndef	NPROC
#define	NPROC	100
#endif

/*	Legal action field values.					*/

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

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

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

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

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

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

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

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

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

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


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

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

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

#define	FULLTABLE	0
#define	BADLINE		1

#define	MAX_E_TYPES	2

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

/*	Useful file and device names.					*/

#ifdef UDEBUG

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

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

#else


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

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

#endif

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

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

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

#ifdef u3b
struct	termio	dflt_termio = {
	BRKINT|IGNPAR|ISTRIP|IXON|IXANY|ICRNL,
	OPOST|ONLCR|TAB3,
	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;

#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 */
	}	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[NPROC];	/* 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

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

main(argc,argv)
int argc;
char **argv;
{
	extern int own_pid;
	extern char *UTMP;
	extern struct PROC_TABLE proc_table[];
	extern struct PROC_TABLE dummy;
	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 long time();
	extern unsigned int spawncnt,syncnt,pausecnt;
	extern int rsflag;
	extern int time_up;
#ifdef	DEBUG
	extern char comment[];
#endif

#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

/* If there is no default level supplied, ask the user to supply */
/* one. */
	if (defaultlevel == 0) new_state = getlvl();
	else new_state = defaultlevel;
	if (new_state == SINGLE_USER) {
		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);
	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);
		account(BOOT_TIME,&dummy,NULL);	/* Put Boot Entry in "utmp" */
		account(RUN_LVL,&dummy,NULL);	/* Make the run level entry */
	}
	umask(0);	/* Allow later files to be created normally. */

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

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

/* If a change in run levels is the reason we awoke, now do */
/* the accounting to report the change in the utmp file.  Also */
/* report the change on the system console. */
		if (chg_lvl_flag) {
			chg_lvl_flag = FALSE;
			account(RUN_LVL,&dummy,NULL);
			console("New run level: %c\n",level(cur_state));
		}
/* Scan the inittab file and spawn and respawn processes that */
/* should be alive in the current state. */
		spawn();
		if (rsflag) {
			rsflag = 0;
			spawncnt++;
		}
		if (cur_state == SINGLE_USER) {
			single(0);
			if (cur_state != prev_state
			    && cur_state != LVLa && cur_state != LVLb
			    && cur_state != LVLc) {
				chg_lvl_flag = TRUE;
				continue;
			}
		}


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

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

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

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

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

/* At this point "init" is finished with all actions for the */
/* current wakeup.  Resync the disks and then pause until */
/* something new takes place. */
		} else {
			prev_state = cur_state;
			sync();	/* Update the disk */
			syncnt++;
			time_up = FALSE;
			while (wakeup.w_mask == 0) {

/* 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. */
				pause();
				pausecnt++;

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

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

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

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

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

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

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

	for (;;) {
		console("SINGLE USER MODE\n");
		signal(SIGCLD,SIG_DFL);
		while ((su_process = efork(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) {

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

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

			wakeup.w_mask = 0;
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

				break;
			}
		}
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

struct PROC_TABLE *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;

#ifdef	DEBUG1
	extern char *C();

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

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

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

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

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

/* Have we just reached the respawn limit? */
		else 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);

/* If process hasn't been respawning too often, reset the count */
/* and time to 0 and allow respawn. */
			} else {
				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);

/* If it is time to try respawning this command, clear the count */
/* and reset the time to now. */
			else {
				process->p_time = now;
				process->p_count = 0;
			}
		}
		rsflag = TRUE;
	}

/* Spawn a child process to execute this command. */
	signal(SIGCLD,SIG_DFL);
	oprocess = process;
	while ((process = efork(oprocess,modes)) == NO_ROOM) pause();

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

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

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

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

/* If the "exec" fails, print an error message. */
		console("Command\n\"%s\"\n failed to execute.  errno = %d\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 (process);
}

/************************/
/****    findslot    ****/
/************************/

/*	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;
{
	extern struct PROC_TABLE proc_table[];
	register struct PROC_TABLE *process,*empty;

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

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

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

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

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

static FILE *fp_inittab = NULL;

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

	if (fp_inittab == NULL) {

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

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

/* 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))
		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(level)
int level;
{
	register int answer;

	switch (level) {
	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(NULLPROC,0)) == NO_ROOM) pause();
	signal(SIGCLD,childeath);

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

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

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

int initialize()
{
	struct CMD_LINE cmd;
	char command[MAXCMDL];
	extern int cur_state,op_modes;
	extern int childeath();
	register int mask,i;
	static int states[] = {
		LVL0,LVL1,LVL2,LVL3,LVL4,LVL5,LVL6,SINGLE_USER
	};
	FILE *fp_systty,*fp;
	char device[sizeof("/dev/")+DIRSIZ];
	struct direct dirent;
	int fd_dev,initstate;
	struct stat statbuf,statcon;
	register struct PROC_TABLE *process,*oprocess;
	extern struct PROC_TABLE *efork(),*findpslot();
	extern char *ttyname();

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

#ifdef	UDEBUG
	save_ioctl();
#endif

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

/* Look for an "initdefault" entry in "/etc/inittab", which */
/* specifies the initial level to which "init" is to go at */
/* startup time. */
	while(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 (mask=MASKSU,i=(sizeof(states)/sizeof(int)) - 1 ; mask > 0; mask >>= 1 , i--) {
				if (mask & 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(oprocess,(NAMED|NOCLEANUP))) == NO_ROOM;);
				signal(SIGCLD,childeath);
				if (process == NULLPROC) {

/* Notice no bookkeeping is performed on these entries.  This is */
/* to avoid doing anything that would cause writes to the file */
/* system to take place.  No writing should be done until the */
/* operator has had the chance to decide whether the file system */
/* needs checking or not. */
					for (i=0,fp= stdin; i < _NFILE;i++,fp++) fclose(fp);
					execl(SH,"INITSH","-c",cmd.c_command,0);
					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;
			}
		}
	}
	if (initstate) return(initstate);

/* If the system console is remote, put a message on the */
/* system tty warning anyone there that syscon is elsewhere. */
	signal(SIGCLD,SIG_DFL);
	while ((process = efork(NULLPROC,NOCLEANUP)) == NO_ROOM);
	signal(SIGCLD,childeath);
	if (process == NULLPROC) {
		if ((fp_systty = fopen(SYSTTY,"r+")) != (FILE*)NULL) {
			if (fstat(fileno(fp_systty),&statbuf) != FAILURE
			   && stat(SYSCON,&statcon) != FAILURE
			   && statbuf.st_rdev != statcon.st_rdev) {
/* Since the devices of syscon and systty don't match, find the */
/* device that syscon is linked to and send the warning to the */
/* physical system tty. */

				if ((fd_dev = open("/dev",O_RDONLY)) != FAILURE) {
					zero(&device[0],sizeof(device));
					strcpy(&device[0],"/dev/");
					while (read(fd_dev,&dirent,sizeof(dirent)) == sizeof(dirent)) {

/* If this isn't the syscon entry itself.... */
						strncpy(&device[sizeof("/dev/")-1], &dirent.d_name[0],DIRSIZ);
						if (strcmp(SYSCON,&device[0])) {
							if (stat(&device[0],&statbuf) != FAILURE && statbuf.st_rdev == statcon.st_rdev) {
								break;
							}
						}
					}
				}
				if (statbuf.st_rdev != statcon.st_rdev)
					fprintf(fp_systty,
					    "\nInit: system console is remote.\
					    Type <DEL> to regain control.\n");
				else fprintf(fp_systty,
					    "\nInit: system console is remote:\
					    %s Type <DEL> to regain control.\n",
					    &device[0]);
				fflush(fp_systty);
				fclose(fp_systty);
			}
		}
		exit(0);
	}

/* Wait for the child to die. */
	while(waitproc(process) == FAILURE);

/* Since no "initdefault" entry was found, return 0.  This will */
/* have "init" ask the user at /dev/syscon to supply a level. */
	return(0);
}

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

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

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

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

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

siglvl(sig)
int sig;
{
	extern int new_state,cur_state;
	extern union WAKEUP wakeup;
	extern struct PROC_TABLE proc_table[];
	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 (process= &proc_table[0]; process < &proc_table[NPROC] ; process++) {
		process->p_time = 0L;
		process->p_count = 0;
	}

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

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

alarmclk()
{
	extern int time_up;

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

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

childeath()
{
	extern union WAKEUP wakeup;
	extern struct PROC_TABLE proc_table[];
	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
#ifdef	UDEBUG
	debug("childeath: pid- %d status- %x\n",pid,status);
#endif

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

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

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

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

powerfail()
{
	extern union WAKEUP wakeup;

	wakeup.w_flags.w_powerhit = 1;
	signal(SIGPWR,powerfail);
}

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

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

int fd_systty;

/*	Due to problems with <stdio>, getlvl doesn't use it.  This	*/
/*	define makes for simple printing of strings.			*/

#define		WRTSTR(x,y)	write(x,y,sizeof(y)-1)

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

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

	signal(SIGCLD,SIG_DFL);
	while ((process = efork(NULLPROC,NOCLEANUP)) == NO_ROOM);
	signal(SIGCLD,childeath);
	if (process == NULLPROC) {

/* Open /dev/systty so that if someone types a <del>, we can be */
/* informed of the fact. */
		if ((fd_tmp = open(SYSTTY,2)) != FAILURE) {

/* Make sure the system tty is not RAW. */
#ifndef	CBUNIX
			ioctl(fd_tmp,TCSETA,&dflt_termio);
#else
			ioctl(fd_tmp,TIOCSETP,&dflt_ttiocb);
#endif

/* Make sure the file descriptor is greater than 2 so that it */
/* won't interfere with the standard descriptors. */
			fd_systty = fcntl(fd_tmp,0,3);
			close(fd_tmp);

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

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

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

/* Now read in the user response. */
			for (c='\0'; c != EOF;) {
				WRTSTR(1,"ENTER RUN LEVEL (0-6,s, or S): ");

/* Get a character from the user which isn't a space or tab. */
				for (read(0,&c,1); c == ' ' || c == '\t' ; read(0,&c,1) );
				c &= 0x7f;

/* If the character is a digit between 0 and 6 or the letter S, */
/* fine, exit with the level equal to the new desired state. */
				if ( c == 'S' || c == 's' ) c = '7';
				if ( c >= '0' && c <= '7') exit(levels[c - '0']);
				else if (c != EOF) WRTSTR(1,"\nUsage: 0123456sS\n");
			}
		}
	}

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

/* Ignore any signals such as powerfail when in "getlvl". */
	wakeup.w_mask = 0;

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

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

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

	signal(sig,SIG_IGN);

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

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

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

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

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

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

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

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

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

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

		oldroutine = signal(SIGCLD,childeath);
		pause();
		signal(SIGCLD,oldroutine);
	}
	if (childpid != 0) {

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

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

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

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

long waitproc(process)
register struct PROC_TABLE *process;
{
	extern struct PROC_TABLE proc_table[];
	int answer;

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

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

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

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

/*	"account" updates entries in /etc/utmp and appends new entries	*/
/*	to the end of /etc/wtmp (assuming /etc/wtmp exists).		*/

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

#ifdef	ACCTDEBUG
	extern char *C();

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

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

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

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

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

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

/* 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);
		u->ut_exit.e_exit = level(prior_state);
		u->ut_pid = n_prev[cur_state];
		sprintf(&u->ut_line[0],RUNLVL_MSG,level(cur_state));
		break;
	case BOOT_TIME :
		sprintf(&u->ut_line[0],"%.12s",BOOT_MSG);
		break;
	case INIT_PROCESS :
		strncpy(&u->ut_user[0],program,sizeof(u->ut_user));
		break;
	default :
		break;
	}

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

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

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

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

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

/* If the open fails, switch back to /dev/systty. */
		reset_syscon();
		fp = fopen(SYSCON,"r+");
	}
	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
	termio.c_cflag &= ~HUPCL;
	fioctl(fp,TCSETA,&termio);
	fioctl(fp,LDSETT,&termcb);
#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;
}

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

/* If the file is badly formatted, use the default settings. */
		} else {
			bcopy(&dflt_termio,&termio,sizeof(dflt_termio));
			bcopy(&dflt_trmcb,&termcb,sizeof(dflt_trmcb));
		}
#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 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;

/* Otherwise use the defaults. */
		} 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(dflt_trmcb));
		}
#endif
		fclose(fp);
	}
}

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

/***********************/
/****    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();
#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(NULLPROC,NOCLEANUP)) == NO_ROOM) timer(5);
		signal(SIGCLD,childeath);
		if (process == NULLPROC) {
#ifdef	UDEBUG
			signal(SIGUSR1,abort);
			signal(SIGUSR2,abort);
#endif

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

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

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

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

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

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

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

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

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

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

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

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

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

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

setimer(timelimit)
int timelimit;
{
	alarm(timelimit);
	time_up = FALSE;
}

/********************/
/****    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;
	extern char *ttyname();
	int init_signal;
	int saverr;
	extern int errno;
	extern char *SYSTTY,*SYSCON;
	extern int own_pid;

/* 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) != '\0') {
		fprintf(stderr,"Usage: init [0123456SsQqabc]\n");
		exit(0);
	} else switch (**argv) {
	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' :
		ln = ttyname(0);	/* Get the name of tty */
		if (*ln == '\0') {
			fprintf(stderr,"Standard input not a tty line\n");
			exit(1);
		}
		if (strcmp(ln,SYSCON) != 0) {

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

/* Unlink /dev/syscon and then relink it to the current line. */
			if (unlink(SYSCON) == FAILURE) {
				perror("Can't unlink /dev/syscon\n");
				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);
			}
		}
		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 [01234567SQabc]\n");
		exit(1);
	}

/* Now send signal to main init and then exit. */
	if (kill(SPECIALPID,init_signal) == FAILURE) {
		fprintf(stderr,"Could not send signal to \"init\".\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    ****/
/********************/

/*	@(#)fdup.c	3.1	*/

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(NULLPROC,0) != NULLPROC) return;
	signal(SIGCLD,childeath);

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

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

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

	if ((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[12];
	register char *ptr;
	register int i;

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


/* LIBRARY ROUTINES ** with no `/etc/utmp.lck' */

/*	@(#)getut.c	1.1	*/

/*	Routines to read and write the /etc/utmp file.			*/
/*									*/
#define	MAXFILE	79	/* Maximum pathname length for "utmp" file */

#ifdef	DEBUG
#undef	UTMP_FILE
#define	UTMP_FILE "utmp"
#endif

static int fd = -1;	/* File descriptor for the utmp file. */
static char utmpfile[MAXFILE+1] = UTMP_FILE;	/* Name of the current
						 * "utmp" like file.
						 */
static long loc_utmp;	/* Where in "utmp" the current "ubuf" was
			 * found.
			 */
static struct utmp ubuf;	/* Copy of last entry read in. */


/* "getutent" gets the next entry in the utmp file.
 */

struct utmp *getutent()
{
	extern int fd;
	extern char utmpfile[];
	extern struct utmp ubuf;
	extern long loc_utmp,lseek();
	extern int errno;
	register char *u;
	register int i;
	struct stat stbuf;

/* If the "utmp" file is not open, attempt to open it for
 * reading.  If there is no file, attempt to create one.  If
 * both attempts fail, return NULL.  If the file exists, but
 * isn't readable and writeable, do not attempt to create.
 */

	if (fd < 0) {

/* Make sure file is a multiple of 'utmp' entries long */
		if (stat(utmpfile,&stbuf) == 0) {
			if((stbuf.st_size % sizeof(struct utmp)) != 0) {
				unlink(utmpfile);
			}
		}
		if ((fd = open(utmpfile, O_RDWR|O_CREAT, 0644)) < 0) {

/* If the open failed for permissions, try opening it only for
 * reading.  All "pututline()" later will fail the writes.
 */
			if (errno == EACCES
			    && (fd = open(utmpfile, O_RDONLY)) < 0)
				return(NULL);
		}
	}

/* Try to read in the next entry from the utmp file.  */
	if (read(fd,&ubuf,sizeof(ubuf)) != sizeof(ubuf)) {

/* Make sure ubuf is zeroed. */
		for (i=0,u=(char *)(&ubuf); i<sizeof(ubuf); i++) *u++ = '\0';
		loc_utmp = 0;
		return(NULL);
	}

/* Save the location in the file where this entry was found. */
	loc_utmp = lseek(fd,0L,1) - (long)(sizeof(struct utmp));
	return(&ubuf);
}

/*	"getutid" finds the specified entry in the utmp file.  If	*/
/*	it can't find it, it returns NULL.				*/

struct utmp *getutid(entry)
register struct utmp *entry;
{
	extern struct utmp ubuf;
	struct utmp *getutent();
	register short type;

/* Start looking for entry.  Look in our current buffer before */
/* reading in new entries. */
	do {

/* If there is no entry in "ubuf", skip to the read. */
		if (ubuf.ut_type != EMPTY) {
			switch(entry->ut_type) {

/* Do not look for an entry if the user sent us an EMPTY entry. */
			case EMPTY:
				return(NULL);

/* For RUN_LVL, BOOT_TIME, OLD_TIME, and NEW_TIME entries, only */
/* the types have to match.  If they do, return the address of */
/* internal buffer. */
			case RUN_LVL:
			case BOOT_TIME:
			case OLD_TIME:
			case NEW_TIME:
				if (entry->ut_type == ubuf.ut_type) return(&ubuf);
				break;

/* For INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, and DEAD_PROCESS */
/* the type of the entry in "ubuf", must be one of the above and */
/* id's must match. */
			case INIT_PROCESS:
			case LOGIN_PROCESS:
			case USER_PROCESS:
			case DEAD_PROCESS:
				if (((type = ubuf.ut_type) == INIT_PROCESS
					|| type == LOGIN_PROCESS
					|| type == USER_PROCESS
					|| type == DEAD_PROCESS)
				    && ubuf.ut_id[0] == entry->ut_id[0]
				    && ubuf.ut_id[1] == entry->ut_id[1]
				    && ubuf.ut_id[2] == entry->ut_id[2]
				    && ubuf.ut_id[3] == entry->ut_id[3])
					return(&ubuf);
				break;

/* Do not search for illegal types of entry. */
			default:
				return(NULL);
			}
		}
	} while (getutent() != NULL);

/* Return NULL since the proper entry wasn't found. */
	return(NULL);
}

/* "getutline" searches the "utmp" file for a LOGIN_PROCESS or
 * USER_PROCESS with the same "line" as the specified "entry".
 */

struct utmp *getutline(entry)
register struct utmp *entry;
{
	extern struct utmp ubuf,*getutent();
	register struct utmp *cur;

/* Start by using the entry currently incore.  This prevents */
/* doing reads that aren't necessary. */
	cur = &ubuf;
	do {
/* If the current entry is the one we are interested in, return */
/* a pointer to it. */
		if (cur->ut_type != EMPTY && (cur->ut_type == LOGIN_PROCESS
		    || cur->ut_type == USER_PROCESS) && strncmp(&entry->ut_line[0],
		    &cur->ut_line[0],sizeof(cur->ut_line)) == 0) return(cur);
	} while ((cur = getutent()) != NULL);

/* Since entry wasn't found, return NULL. */
	return(NULL);
}

/*	"pututline" writes the structure sent into the utmp file.	*/
/*	If there is already an entry with the same id, then it is	*/
/*	overwritten, otherwise a new entry is made at the end of the	*/
/*	utmp file.							*/

struct utmp *pututline(entry)
struct utmp *entry;
{
	register int i,type;
	int fc;
	struct utmp *answer;
	struct stat statbuf;
	extern long time();
	extern struct utmp ubuf;
	extern long loc_utmp,lseek();
	extern struct utmp *getutid();
	extern int fd,errno;
	struct utmp tmpbuf;

/* Copy the user supplied entry into our temporary buffer to */
/* avoid the possibility that the user is actually passing us */
/* the address of "ubuf". */
	tmpbuf = *entry;
	getutent();
	if (fd < 0) {
#ifdef	ERRDEBUG
		gdebug("pututline: Unable to create utmp file.\n");
#endif
		return((struct utmp *)NULL);
	}
/* Make sure file is writable */
	if ((fc=fcntl(fd, F_GETFL, NULL)) == -1
	    || (fc & O_RDWR) != O_RDWR) {
		return((struct utmp *)NULL);
	}

/* Find the proper entry in the utmp file.  Start at the current */
/* location.  If it isn't found from here to the end of the */
/* file, then reset to the beginning of the file and try again. */
/* If it still isn't found, then write a new entry at the end of */
/* the file.  (Making sure the location is an integral number of */
/* utmp structures into the file incase the file is scribbled.) */

	if (getutid(&tmpbuf) == NULL) {
#ifdef	ERRDEBUG
		gdebug("First getutid() failed.  fd: %d",fd);
#endif
		setutent();
		if (getutid(&tmpbuf) == NULL) {
#ifdef	ERRDEBUG
			loc_utmp = lseek(fd, 0L, 1);
			gdebug("Second getutid() failed.  fd: %d loc_utmp: %ld\n",fd,loc_utmp);
#endif
			fcntl(fd, F_SETFL, fc | O_APPEND);
		} else {
			lseek(fd, -(long)sizeof(struct utmp), 1);
		}
	} else {
		lseek(fd, -(long)sizeof(struct utmp), 1);
	}

/* Write out the user supplied structure.  If the write fails, */
/* then the user probably doesn't have permission to write the */
/* utmp file. */
	if (write(fd,&tmpbuf,sizeof(tmpbuf)) != sizeof(tmpbuf)) {
#ifdef	ERRDEBUG
		gdebug("pututline failed: write-%d\n",errno);
#endif
		answer = (struct utmp *)NULL;
	} else {
/* Copy the user structure into ubuf so that it will be up to */
/* date in the future. */
		ubuf = tmpbuf;
		answer = &ubuf;

#ifdef	ERRDEBUG
		gdebug("id: %c%c loc: %x\n",ubuf.ut_id[0],ubuf.ut_id[1],
		    ubuf.ut_id[2],ubuf.ut_id[3],loc_utmp);
#endif
	}
	fcntl(fd, F_SETFL, fc);
	return(answer);
}

/*	"setutent" just resets the utmp file back to the beginning.	*/

setutent()
{
	register char *ptr;
	register int i;
	extern int fd;
	extern struct utmp ubuf;
	extern long loc_utmp;

	if (fd != -1) lseek(fd,0L,0);

/* Zero the stored copy of the last entry read, since we are */
/* resetting to the beginning of the file. */

	for (i=0,ptr=(char*)&ubuf; i < sizeof(ubuf);i++) *ptr++ = '\0';
	loc_utmp = 0L;
}

/*	"endutent" closes the utmp file.				*/

endutent()
{
	extern int fd;
	extern long loc_utmp;
	extern struct utmp ubuf;
	register char *ptr;
	register int i;

	if (fd != -1) close(fd);
	fd = -1;
	loc_utmp = 0;
	for (i=0,ptr= (char *)(&ubuf); i < sizeof(ubuf);i++) *ptr++ = '\0';
}

/*	"utmpname" allows the user to read a file other than the	*/
/*	normal "utmp" file.						*/

utmpname(newfile)
char *newfile;
{
	extern char utmpfile[];

/* Determine if the new filename will fit.  If not, return 0. */
	if (strlen(newfile) > MAXFILE) return (0);

/* Otherwise copy in the new file name. */
	else strcpy(&utmpfile[0],newfile);

/* Make sure everything is reset to the beginning state. */
	endutent();
	return(1);
}

#ifdef	ERRDEBUG
#include	<stdio.h>

gdebug(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("/etc/dbg.getut","a+")) == NULL) return;
	fprintf(fp,format,arg1,arg2,arg3,arg4,arg5,arg6);
	fclose(fp);
}
#endif
