/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 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.	*/

#pragma ident	"@(#)init.c	1.49	99/07/26 SMI"

/*
 *
 *		PROPRIETARY NOTICE (Combined)
 *
 * This source code is unpublished proprietary information
 * constituting, or derived under license from AT&T's UNIX(r) System V.
 * In addition, portions of such source code were derived from Berkeley
 * 4.3 BSD under license from the Regents of the University of
 * California.
 *
 *
 *
 *		Copyright Notice
 *
 * Notice of copyright on this source code product does not indicate
 * publication.
 *
 *	Copyright (c) 1986-1999 by Sun Microsystems, Inc.
 *	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989  AT&T.
 *		    All rights reserved.
 */

/*
 * "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()
 *	remv()
 *	spawn()
 *	respawn()
 *	findpslot()
 *	getcmd()
 *	mask_to_highest_state()
 *	lvlname_to_state()
 *	lvlname_to_mask()
 *	state_to_mask()
 *	state_to_flags()
 *	state_to_lvlname()
 *	killproc()
 *	initialize()
 *	init_signals()
 *	setup_pipe()
 *	siglvl()
 *	alarmclk()
 *	childeath_single()
 *	childeath()
 *	powerfail()
 *	promptuserlvl()
 *	getlvl()
 *	switchcon()
 *	efork()
 *	waitproc()
 *	notify_pam_dead()
 *	account()
 *	prog_name()
 *	opensyscon()
 *	realcon()
 *	get_ioctl_syscon()
 *	reset_syscon()
 *	write_ioctl_syscon()
 *	reset_modes()
 *	console()
 *	error_time()
 *	timer()
 *	setimer()
 *	userinit()
 *	fdup()
 *	drop_core()
 *	debug()
 *	C()
 *	sigpoll()
 *	cleanaux()
 *	clearent()
 *
 * 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 utmpx, inittab
 *			and other administrative files.  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	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

#include	<sys/types.h>
#include	<signal.h>
#include	<stdio.h>
#include	<stdio_ext.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<string.h>
#include	<strings.h>
#include	<utmpx.h>
#include	<errno.h>
#include	<termio.h>
#include	<ulimit.h>
#include	<sys/termios.h>
#include	<sys/tty.h>
#include	<ctype.h>
#include	<sys/stat.h>
#include	<sys/statvfs.h>
#include	<fcntl.h>
#include	<time.h>
#include	<sys/stropts.h>
#include	<wait.h>
#include	<syslog.h>
#include	<bsm/libbsm.h>
#include 	<kstat.h>

#include	<security/pam_appl.h>

#undef	sleep

#define	fioctl(p, sptr, cmd)	ioctl(fileno(p), sptr, cmd)
#define	min(a, b)		(((a) < (b)) ? (a) : (b))


#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 inittab.
 */
#define	MAXCMDL	512

/*
 * EXEC		The length of the prefix string added to all comamnds
 *		found in 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 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

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

#else

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

#endif

/*
 * If allocating memory fails for SPECIALPID == 1, init will retry
 * allocating memory for a total of TRY_SPID_TIMES before giving up.
 */
#define	TRY_SPID_TIMES	10

/*
 * The kernel's default umask is 022 these days; since every process inherits
 * its umask from init, init will set it from CMASK in /etc/default/init.
 * Init gets the default umask from the kernel, it sets it to 022 whenever
 * it wants to create a file and reverts to CMASK afterwards.
 */

int cmask;

/*
 * init is responsible for the system's notion of run-level; when init is
 * run during boot-up, it is given pid 1, and is subsequently responsible
 * for spawning processes and changing run-levels.  To change run-levels,
 * the system administrator runs init from the command line, passing it a
 * level name.  Valid run-levels are [sS0123456].  Additionally, init can be
 * given directives [qQabc], which indicate actions to be taken pertaining
 * to /etc/inittab.  When an 'init' is invoked on the command line, it
 * directs init (pid 1) what to do by sending it a signal; these signal
 * numbers are commonly refered to in the code as 'states'.
 *
 * When init processes inittab entries, it finds processes that are to be
 * spawned at various run-levels.  init must build a mask of the levels for
 * which each inittab entry is valid.
 *
 * The following definitions, concluding with the 'lvls' array, provide a
 * common mapping between level-name (like 'S'), signal number (state),
 * run-level mask, and specific properties associated with a run-level.
 * This array should be accessed using the routines state_to_lvlname(),
 * lvlname_to_state(), lvlname_to_mask(), state_to_mask(), state_to_flags()
 * and mask_to_highest_state().
 */

/*
 * 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	0x0001
#define	MASK1	0x0002
#define	MASK2	0x0004
#define	MASK3	0x0008
#define	MASK4	0x0010
#define	MASK5	0x0020
#define	MASK6	0x0040
#define	MASKSU	0x0080
#define	MASKa	0x0100
#define	MASKb	0x0200
#define	MASKc	0x0400

#define	MASK_NUMERIC (MASK0 | MASK1 | MASK2 | MASK3 | MASK4 | MASK5 | MASK6)
#define	MASK_abc (MASKa | MASKb | MASKc)

/*
 * Flags to indicate properties of various states.
 */
#define	LSEL_RUNLEVEL	0x0001	/* runlevels you can transition to */
#define	LSEL_NOAUDIT	0x0002	/* levels with auditing disabled */

typedef struct lvl {
	int	lvl_state;
	int	lvl_mask;
	char	lvl_name;
	int	lvl_flags;
} lvl_t;

static lvl_t lvls[] = {
	{ LVLQ,		0,	'Q', 0					},
	{ LVLQ,		0,	'q', 0					},
	{ LVL0,		MASK0,	'0', LSEL_RUNLEVEL | LSEL_NOAUDIT	},
	{ LVL1, 	MASK1,	'1', LSEL_RUNLEVEL | LSEL_NOAUDIT	},
	{ LVL2, 	MASK2,	'2', LSEL_RUNLEVEL			},
	{ LVL3, 	MASK3,	'3', LSEL_RUNLEVEL			},
	{ LVL4, 	MASK4,	'4', LSEL_RUNLEVEL			},
	{ LVL5, 	MASK5,	'5', LSEL_RUNLEVEL | LSEL_NOAUDIT	},
	{ LVL6, 	MASK6, 	'6', LSEL_RUNLEVEL | LSEL_NOAUDIT	},
	{ SINGLE_USER, 	MASKSU, 'S', LSEL_RUNLEVEL | LSEL_NOAUDIT	},
	{ SINGLE_USER, 	MASKSU, 's', LSEL_RUNLEVEL | LSEL_NOAUDIT	},
	{ LVLa,		MASKa,	'a', 0					},
	{ LVLb,		MASKb,	'b', 0					},
	{ LVLc,		MASKc,	'c', 0					}
};

#define	LVL_NELEMS (sizeof (lvls) / sizeof (lvl_t))

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

#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 any 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 */
	pid_t	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 */
};

int 			num_proc = 20;  /* Initial number of proc tbl entries */
struct PROC_TABLE	*proc_table;	/* Table of active processes */
struct PROC_TABLE	dummy;	/* A zero table used when calling account() */
				/*   for non-process type accounting. */

/*
 * 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 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 remv() to determine whether it has looked
 *			at an entry while checking for processes to be killed.
 *
 *	WARNED		Flag used by remv() to mark processes that have been
 *			sent the SIGTERM signal.  If they don't die in 5
 *			seconds, they are sent the SIGKILL signal.
 *
 *	KILLED		Flag used by remv() to mark procs that have been sent
 *			the SIGTERM and SIGKILL signals.
 */
#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
 *			SPAWN_INTERVAL seconds before 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 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};

struct	pidrec {
	int	pd_type;	/* Command type */
	pid_t	pd_pid;		/* pid to add or remove */
};

/*
 * pd_type's
 */
#define	ADDPID	1
#define	REMPID	2

struct	pidlist {
	pid_t	pl_pid;		/* pid to watch for */
	int	pl_dflag;	/* Flag indicating SIGCLD from this pid */
	short	pl_exit;	/* Exit status of proc */
	struct	pidlist	*pl_next; /* Next in list */
} *Plhead, *Plfree;

/*
 * The following structures contain a set of modes for /dev/syscon
 * and should match the default contents of /etc/ioctl.syscon.
 */
struct termio	dflt_termio = {
	BRKINT|ICRNL|IXON|IMAXBEL,			/* iflag */
	OPOST|ONLCR|TAB3,				/* oflag */
	CS8|CREAD|B9600,				/* cflag */
	ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN, /* lflag */
	0,						/* ldisc */
	CINTR, CQUIT, CERASE, CKILL, CEOF, 0, 0, 0
};

struct termios	dflt_termios = {
	BRKINT|ICRNL|IXON|IMAXBEL,			/* iflag */
	OPOST|ONLCR|TAB3,				/* oflag */
	CS8|CREAD|B9600,				/* cflag */
	ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN, /* lflag */
	CINTR, CQUIT, CERASE, CKILL, CEOF, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0
};

struct termio	termio, curterm;
struct termios	termios, curterms;

int iscurterms;

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;	/* OS experienced powerfail */
	}	w_flags;
	int w_mask;
} wakeup;


/*
 * Useful file and device names.
 */
char	*CONSOLE	=	"/dev/console";	/* Real system console */
char	*INITPIPE_DIR	=	"/etc";
char	*INITPIPE	=	"/etc/initpipe";

#ifdef UDEBUG

char	*UTMPX		=	"utmpx";
char	*WTMPX		=	"wtmpx";
char	*INITTAB	=	"inittab";
char	*SYSTTY		=	"/dev/systtyx";
char	*SYSCON		=	"/dev/sysconx";
char	*CORE_RECORD	=	"core_record";
char	*DBG_FILE	=	"debug";
char	*IOCTLSYSCON	=	"ioctl.syscon";	/* Last syscon modes */
char	*ENVFILE	=	"default/init";	/* Default env. */

#else


char	*UTMPX		=	UTMPX_FILE;	/* Snapshot record file */
char	*WTMPX		=	WTMPX_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 */
char	*ENVFILE	=	"/etc/default/init";	/* Default env. */

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

#endif

char	*SU	=	"/etc/sulogin";	/* Super-user program for single user */
char	*SH	=	"/sbin/sh";	/* Standard shell */

/*
 * Default Path.  /sbin is included in path only during sysinit phase
 */
#define	DEF_PATH "PATH=/usr/sbin:/usr/bin"
#define	INIT_PATH "PATH=/sbin:/usr/sbin:/usr/bin"

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	Gchild = 0;	/* Flag to indicate "godchild" died, set in */
			/*   childeath() and cleared in cleanaux() */
int	Pfd = -1;	/* fd to receive pids thru */
unsigned int	spawncnt, pausecnt;
int	rsflag;		/* Set if a respawn has taken place */
pid_t	own_pid;	/* This is the value of our own pid.  If the value */
			/*   is SPECIALPID, then we have to fork to interact */
			/*   with the outside world. */
volatile int time_up;	/* Flag set to TRUE by alarm interupt routine */
			/*   each time an alarm interupt takes place. */
int	fd_systty;
int	savelevel;	/* Set to defaultlevel if sflg is set */
int	promptlevel;	/* Set to defaultlevel for [] default in getuserlvl */
int	sflg = 0;	/* Set if we were booted -s to single user */
int	rflg = 0;	/* Set if we were booted -r, reconfigure devices */
int	bflg = 0;	/* Set if we were booted -b, don't run rc scripts */
int	fflg = 0;	/* Set if we were booted -f, flush cachefs cache */

#ifdef	DEBUG
char	comment[120];
#endif

/*
 * Array for default global environment.
 */
#define	MAXENVENT	23	/* Max number of default env variables + 1 */
				/* Since init can use two itself, this leaves */
				/* 20 for the administrator in ENVFILE. */
char	*glob_envp[MAXENVENT];	/* Array of environment strings */

char	*prog_name();
char	state_to_lvlname();
int	lvlname_to_mask(), state_to_flags(), state_to_mask();
int	lvlname_to_state(), mask_to_highest_state();
int	error_time(), getcmd(), getlvl(), initialize(), realcon();
int	spawn();
int	opensyscon(), get_ioctl_syscon();
void	account(), alarmclk(), childeath(), cleanaux(), clearent();
void	console(), init_signals(), setup_pipe(), killproc();
void	powerfail(), remv(), reset_syscon(), write_ioctl_syscon(), respawn();
void	reset_modes(), setimer(), siglvl(), sigpoll(), single();
void	switchcon(), timer(), userinit(), notify_pam_dead();
long	waitproc();
FILE	*fdup();
struct PROC_TABLE	*efork(), *findpslot();
/* private: from bsm/audit_init.c */
extern int audit_init_setup(), audit_init_fail(), audit_init_success();

#ifdef UDEBUG
void	drop_core();
#endif

#ifdef DEBUGGER
char	*C();
void	debug();
#endif


/*
 * main
 */
main(argc, argv)
int argc;
char **argv;
{
	int	defaultlevel;
	int	utmpflag = 0;
	int	chg_lvl_flag;
	FILE	*fp;
	extern char *optarg;
	extern int optind, opterr;
	int	c;

	/* Get the default umask */
	cmask = umask(022);
	(void) umask(cmask);

	/* Parse the arguments to init. Check for single user */

	opterr = 0;
	while ((c = getopt(argc, argv, "brsf")) != EOF)
		switch (c) {
		case 'b':
			rflg = 0;
			bflg = 1;
			if (!sflg)
				sflg++;
			break;
		case 'r':
			bflg = 0;
			rflg++;
			break;
		case 's':
			if (!bflg)
				sflg++;
			break;
		case 'f':
			fflg++;
			break;
		}

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

	/*
	 * Allocate space for the proc table
	 */
	proc_table = (struct PROC_TABLE *)
			calloc(num_proc, sizeof (struct PROC_TABLE));
	if ((proc_table == NULL) && (SPECIALPID == 1)) {
		int i;

		for (i = 0; i < TRY_SPID_TIMES; i++) {
			proc_table = (struct PROC_TABLE *)
				calloc(num_proc, sizeof (struct PROC_TABLE));
			if (proc_table != NULL)
				break;
			sleep(1);
		}
		console("Cannot run init: out of memory\n");
		return (ENOMEM);
	} else if ((proc_table == NULL) && (SPECIALPID != 1)) {
		fprintf(stderr, "init: %s\n", strerror(errno));
		return (ENOMEM);
	}

	/*
	 * 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 inittab file.
	 */
	promptlevel = defaultlevel = initialize();
	if (promptlevel == -1) {
		promptlevel = SINGLE_USER;
	}

	/*
	 * If the system was booted -s then save the real default level.
	 * This gets restored just before going to the real default level,
	 * the restore is done in single()
	 */

	if (sflg || bflg) {
		savelevel = defaultlevel;
		defaultlevel = SINGLE_USER;
	}
	chg_lvl_flag = FALSE;

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

	/*
	 * Initialize the "utmpx" file.  Set the umask so
	 * that the utmpx file is created 644.
	 * If "utmpx" can't be opened we default to single user mode.
	 */
	(void) umask(022);
	if ((fp = fopen(UTMPX, "w+")) == NULL) {
		console("Cannot create %s\n", UTMPX);
		if (bflg == 0) {
			cur_state = SINGLE_USER;
			defaultlevel = -1;
		}
	} else {
		utmpflag = 1;
		fclose(fp);
	}
	(void) umask(cmask);	/* Allow later files to be created normally. */

	/*
	 * If there is no default level supplied, ask the user to supply one.
	 */
	if (defaultlevel == 0) {
		promptlevel = new_state = getlvl();
	} else if (defaultlevel == -1) {
		new_state = SINGLE_USER;
		defaultlevel = 0;
	} else {
		new_state = defaultlevel;
	}
	if (new_state == SINGLE_USER) {
		account(BOOT_TIME, &dummy, NULL); /* Put Boot Entry in utmpx */
		account(RUN_LVL, &dummy, "S"); /* Make the run level entry */
		single(defaultlevel);
		if (bflg) {
			bflg = 2;
			initialize();
		}
		while (utmpflag == 0) {
			/*
			 * We went to single user because we couldn't get at
			 * "utmpx" earlier. Since we have returned from single()
			 * we should be able to get there now.  Truncate the
			 * file now.  The boot time we will write may be off
			 * since we may have been up for a while but this is the
			 * best we can do under the circumstances.  If we still
			 * can't get at "utmpx" we keep going back to single
			 * user until we can.
			 */
			(void) umask(022);
			if ((fp = fopen(UTMPX, "w+")) == NULL) {
			console("Cannot create %s\n", UTMPX);
				new_state = cur_state = SINGLE_USER;
				(void) umask(cmask);
				single(0);
			} else {
				utmpflag = 1;
				fclose(fp);
				(void) umask(cmask);
				account(BOOT_TIME, &dummy, NULL);
			}
		}
		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;
		account(BOOT_TIME, &dummy, NULL); /* Put Boot Entry in "utmp" */
		account(RUN_LVL, &dummy, NULL);	/* Make the run level entry */
	}

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

		/*
		 * Clean up any accounting records for dead "godchildren".
		 */
		if (Gchild)
			cleanaux();

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

		/*
		 * 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",
			    state_to_lvlname(cur_state));
		}

		/*
		 * Scan the inittab file and spawn and respawn processes that
		 * should be alive in the current state. If inittab does not
		 * exist default to  single user mode.
		 */
		if (spawn() == FAILURE) {
			prior_state = prev_state;
			cur_state = SINGLE_USER;
		}

		if (rsflag) {
			rsflag = 0;
			spawncnt++;
		}

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

		/*
		 * If a powerfail signal was received during the last
		 * sequence, set mode to powerfail.  When spawn() is entered
		 * the first thing it does is to check "powerhit".  If it is
		 * in PF_MODES then it clears "powerhit" and does a powerfail
		 * sequence.  If it is not in PF_MODES, then it puts itself
		 * in PF_MODES and then clears "powerhit".  Should "powerhit"
		 * get set again while spawn() is working on a powerfail
		 * sequence, the following code  will see that spawn() tries to
		 * execute the powerfail sequence again.  This guarantees 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;
		} else if (op_modes != NORMAL_MODES) {
			/*
			 * If spawn() was not just called while in normal mode,
			 * we set the mode to normal and it will be called again
			 * to check normal modes.  If we have just finished
			 * a powerfail sequence with prev_state equal to zero,
			 * we set prev_state equal to cur_state before the next
			 * pass through.
			 */
			if (op_modes == PF_MODES)
				prev_state = cur_state;
			op_modes = NORMAL_MODES;
		} else if (cur_state == LVLa || cur_state == LVLb ||
		    cur_state == LVLc) {
			/*
			 * If it was a change of levels that awakened us and the
			 * new level is one of the demand levels then reset
			 * cur_state to the previous state and do another scan
			 * to take care of the usual respawn actions.
			 */
			n_prev[cur_state]++;
			cur_state = prior_state;
			prior_state = prev_state;
			prev_state = cur_state;
			account(RUN_LVL, &dummy, NULL);
		} else {
			prev_state = cur_state;

			if (wakeup.w_mask == 0) {
				/*
				 * "init" is finished with all actions for
				 * the current wakeup.
				 */
				setimer(SLEEPTIME);
				pause();
				pausecnt++;
			}

			setimer(0);

			if (wakeup.w_flags.w_usersignal) {
				/*
				 * Install the new level.  This could be a real
				 * change in levels  or a telinit [Q|a|b|c] or
				 * just a telinit to the same level at which
				 * we are running.
				 */
#ifdef	DEBUG
				debug("\nmain\tSignal-new:%c cur:%c prev:%c\n",
				    state_to_lvlname(new_state),
				    state_to_lvlname(cur_state),
				    state_to_lvlname(prev_state));
#endif
				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 (new_state == SINGLE_USER)
					reset_modes();
				new_state = 0;
			}

			if (wakeup.w_flags.w_powerhit)
				op_modes = PF_MODES;

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

/*
 * single(): handle transition to single-user mode
 */
void
single(defaultlevel)
int defaultlevel;
{
	struct PROC_TABLE	*su_process;
	struct pidlist		*p;
	int			state;

	/*
	 * Go through the godchild list and send SIGTERM, then wait for
	 * TWARN seconds and send SIGKILL to anything that is left.
	 * Also wait a little bit before doing this so SIGCLDs hopefully
	 * go to the right process.
	 */
	timer(3);
	for (p = Plhead; p; p = p->pl_next)
		kill(p->pl_pid, SIGTERM);
	timer(TWARN);
	cleanaux();	/* Clean up utmpx entries. */

	/*
	 * Any left?
	 */
	if (Plhead) {
		for (p = Plhead; p; p = p->pl_next)
			kill(p->pl_pid, SIGKILL);
		timer(3);	/* Give them a little time to die. */
		cleanaux();	/* Clean up utmpx entries. */
	}

	for (;;) {
		console("SINGLE USER MODE\n");
		sigset(SIGCLD, SIG_DFL);
		while ((su_process = efork(M_OFF, NULLPROC, NOCLEANUP))
							== NO_ROOM)
			pause();
		sigset(SIGCLD, childeath);
		if (su_process == NULLPROC) {
			(void) opensyscon();

			/*
			 * Execute the "su" program.
			 */
			execle(SU, SU, "-", (char *)0, glob_envp);
			console("execle 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) {
			/*
			 * Check to see if powerfail notification was
			 * received.
			 */
			if (wakeup.w_flags.w_powerhit) {
				(void) spawn();
				op_modes = NORMAL_MODES;
			}

			/*
			 * Did we waken because a change of levels?
			 * If so, kill the child and then exit.
			 */
			if ((wakeup.w_flags.w_usersignal) &&
			    (state_to_flags(new_state) & LSEL_RUNLEVEL) &&
			    (new_state != SINGLE_USER)) {
				/*
				 * Check to make sure UTMPX is
				 * accessible.  If not, remain in
				 * single user mode.
				 */
				if (access(UTMPX, F_OK) == -1) {
					console("Cannot access %s, remaining "
					    "in single user mode.\n", UTMPX);
				} else {
					kill(su_process->p_pid, SIGKILL);
					prev_state = cur_state;
					if (cur_state >= 0) {
						n_prev[cur_state]++;
						prior_state = cur_state;
					}
					cur_state = new_state;
					new_state = 0;
					wakeup.w_mask = 0;
					su_process->p_flags &= ~NOCLEANUP;
					return;
				}
			}

			/*
			 * All other reasons for waking are ignored when in
			 * single-user mode.  The only child we are interested
			 * in is being waited for explicitly 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)
			if (sflg--)
				state = savelevel;
			else
				state = defaultlevel;
		else
			state = getlvl();

		/*
		 * If the new level is not SINGLE_USER, then return,
		 * otherwise go back and make up a new "su" process.
		 */
		if (state != SINGLE_USER) {
			/*
			 * Check to make sure UTMPX is accessible.
			 * If not, remain in single user mode.
			 */
			if (access(UTMPX, F_OK) == -1) {
				console("Cannot access %s, remaining in "
				    "single user mode.\n", UTMPX);
			} else {
				prev_state = cur_state;
				if (cur_state >= 0) {
					n_prev[cur_state]++;
					prior_state = cur_state;
				}
				cur_state = state;
				return;
			}
		}
	}
}

/*
 * remv() scans through "proc_table" and performs cleanup.  If
 * there is a process in the table, which shouldn't be here at
 * the current run level, then remv() kills the process.
 */
void
remv()
{
	struct PROC_TABLE	*process;
	struct CMD_LINE		cmd;
	char			cmd_string[MAXCMDL];
	int			change_level;

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

	/*
	 * Clear the TOUCHED flag on all entries so that when we have
	 * finished scanning inittab, we will be able to tell if we
	 * have any processes for which there is no entry in inittab.
	 */
	for (process = proc_table;
		(process < proc_table + num_proc); process++) {
		process->p_flags &= ~TOUCHED;
	}

	/*
	 * Scan all inittab entries.
	 */
	while (getcmd(&cmd, &cmd_string[0]) == TRUE) {
	    /* Scan for process which goes with this entry in inittab. */
	    for (process = proc_table;
		(process < proc_table + num_proc); process++) {
		/* Does this slot contain the proc we are looking for? */
		if ((process->p_flags & OCCUPIED) &&
		    id_eq(process->p_id, cmd.c_id)) {
#ifdef	DEBUG
		    debug("remv- id:%s pid:%ld 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 proc	*/
		    /* started by some mechanism other than	*/
		    /* LVL{a|b|c} and the current level does	*/
		    /* not support this process?		*/
		    if (cur_state == SINGLE_USER ||
		    cmd.c_action == M_OFF ||
		    ((cmd.c_levels & state_to_mask(cur_state)) == 0 &&
		    (process->p_flags & DEMANDREQUEST) == 0)) {

			if (process->p_flags & LIVING) {
				/*
				 * Touch this entry so we know we have
				 * treated it.  Note that procs which are
				 * already dead at this point and should
				 * not be restarted are left untouched.
				 * This causes their slot to be freed later
				 * after dead accounting is done.
				 */
				process->p_flags |= TOUCHED;

				if ((process->p_flags & KILLED) == 0) {
					if (change_level) {
						process->p_flags |= WARNED;
						kill(process->p_pid, SIGTERM);
					} else {
						/*
						 * fork a killing proc so "init"
						 * can continue without having
						 * to pause for TWARN seconds.
						 */
						killproc(process->p_pid);
					}
					process->p_flags |= KILLED;
				}
			}

		    } else {
			/*
			 * Process can exist at current level.  If it is still
			 * alive or a DEMANDREQUEST we touch it so it will be
			 * left alone.  Otherwise we leave it untouched so it
			 * will be accounted for and cleaned up later in remv().
			 * Dead DEMANDREQUESTs will be accounted but not freed.
			 */
			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 guaranteed 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;
			(process < proc_table + num_proc); process++) {
			/*
			 * If this process should die, hasn't yet, and the
			 * TWARN time hasn't expired yet, wait for process
			 * to die or for timer to expire.
			 */
			while (time_up == FALSE &&
			    (process->p_flags & (WARNED|LIVING|OCCUPIED)) ==
			    (WARNED|LIVING|OCCUPIED))
				pause();

			if (time_up == TRUE)
				break;
		}

		/*
		 * If we reached the end of the table without the timer
		 * expiring, then there are no procs 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;
			    (process < proc_table + num_proc); process++) {
			    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 LIVING,
	 * NAMED, which don't have an entry in inittab (haven't been TOUCHED
	 * by the above scanning), and haven't been sent kill signals, and
	 * those entries marked not LIVING, NAMED.  The former procs are killed.
	 * The latter have DEAD_PROCESS accounting done and the slot cleared.
	 */
	for (process = proc_table;
		(process < proc_table + num_proc); 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 proc hasn't been TOUCHED, then free the
			 * space. It has either died of it's own accord, but
			 * isn't respawnable or it was killed because it
			 * shouldn't exist at this level.
			 */
			if ((process->p_flags & TOUCHED) == 0)
				process->p_flags = 0;
		}
	}
}

/*
 * spawn() scans inittab for entries which should be run at this mode.
 * Processes which should be running but are not, are started.
 */
int
spawn()
{
	struct PROC_TABLE		*pp;
	struct CMD_LINE			cmd;
	char				cmd_string[MAXCMDL];
	short				lvl_mask;
	int				status;

	/*
	 * 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 interrupt
	 * 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 = state_to_mask(cur_state);

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

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

		/*
		 * Find out if there is a process slot for this entry already.
		 */
		if ((pp = findpslot(&cmd)) == NULLPROC) {
			/*
			 * Only generate an error message 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 (((pp->p_flags & (LIVING|DEMANDREQUEST)) == DEMANDREQUEST) &&
		    (cmd.c_levels & MASK_abc) &&
		    (cmd.c_action & op_modes) == M_ONDEMAND) {
			respawn(pp, &cmd);
			continue;
		}

#ifdef	DEBUG1
		debug("process:\t%s\t%05d\n%s\t%d\t%o\t%o\n",
			C(&pp->p_id[0]), pp->p_pid, ctime(&pp->p_time),
			pp->p_count, pp->p_flags, pp->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 || pp->p_flags & LIVING ||
		    (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 categories, 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 we don't wait for the process
		 * to die, for all other actions we do wait.
		 */
		if (cmd.c_action & (M_ONCE | M_RESPAWN | M_PF | M_BOOT)) {
			respawn(pp, &cmd);

		} else {
			respawn(pp, &cmd);
			while (waitproc(pp) == FAILURE);
			account(DEAD_PROCESS, pp, NULL);
			pp->p_flags = 0;
		}
	}
	return (status);
}

/*
 * respawn() spawns a shell, inserts the information about the process
 * process into the proc_table, and does the startup accounting.
 */
void
respawn(process, cmd)
struct PROC_TABLE	*process;
struct CMD_LINE		*cmd;
{
	int		i;
	int		modes, maxfiles;
	time_t		now;
	struct PROC_TABLE tmproc, *oprocess;

#ifdef	DEBUG1
	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 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;

		if (process->p_count++ == SPAWN_LIMIT) {

			if ((now - process->p_time) < SPAWN_INTERVAL) {
				/*
				 * Process is respawning too rapidly.  Print
				 * message and refuse to respawn it for now.
				 */
				console("Command is respawning too rapidly. "
				    "Check for possible errors.\n"
				    "id:%4s \"%s\"\n",
				    &cmd->c_id[0], &cmd->c_command[EXEC]);
				return;
			}
			process->p_time = now;
			process->p_count = 0;

		} else if (process->p_count > SPAWN_LIMIT) {
			/*
			 * If process has been respawning too rapidly and
			 * the inhibit time limit hasn't expired yet, we
			 * refuse to respawn.
			 */
			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.
	 */
	sigset(SIGCLD, SIG_DFL);
	oprocess = process;
	if (cmd->c_action & (M_WAIT | M_BOOTWAIT))
		while ((process = efork(M_WAIT, oprocess, modes)) == NO_ROOM)
			pause();
	else
		while ((process = efork(M_OFF, oprocess, modes)) == NO_ROOM)
			pause();

	if (process == NULLPROC) {

		/*
		 * We are the child.  We must make sure we get a different
		 * file pointer for our references to utmpx.  Otherwise our
		 * seeks and reads will compete with those of the parent.
		 */
		endutxent();

		/*
		 * Perform the accounting for the beginning of a process.
		 * Note that all processes are initially "INIT_PROCESS"es.
		 */
		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(UL_GDESLIM, 0);
		for (i = 0; i < maxfiles; i++)
			fcntl(i, F_SETFD, FD_CLOEXEC);

		/*
		 * Now exec a shell with the -c option and the command
		 * from inittab.
		 */
		execle(SH, "INITSH", "-c", cmd->c_command, (char *)0,
								glob_envp);
		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" doesn't have a
		 * chance to finish putting this child in "proc_table".
		 */
		timer(20);
		exit(1);

	} else {
		/*
		 * We are the parent.  Insert the necessary
		 * information in the proc_table.
		 */
		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];
	}
	sigset(SIGCLD, childeath);
}

/*
 * 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)
struct CMD_LINE	*cmd;
{
	struct PROC_TABLE	*process;
	struct PROC_TABLE	*empty = NULLPROC;

	for (process = proc_table;
		(process < proc_table + num_proc); 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 + num_proc))
		process = empty;

	return (process);
}

/*
 * getcmd() parses lines from 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 inittab is reached, FALSE
 * is returned 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;

int
getcmd(cmd, shcmd)
register struct CMD_LINE	*cmd;
char				*shcmd;
{
	register char	*ptr;
	register int	c, lastc, state;
	char 		*ptr1;
	int		answer, i, proceed;
	struct	stat	sbuf;
	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,
	};
	/*
	 * Only these actions will be allowed for entries which
	 * are specified for single-user mode.
	 */
	short su_acts = M_INITDEFAULT | M_PF | M_PWAIT | M_WAIT;

	if (fp_inittab == NULL) {
		/*
		 * Before attempting to open inittab we stat it to make
		 * sure it currently exists and is not empty.  We try
		 * several times because someone may have temporarily
		 * unlinked or truncated the file.
		 */
		for (i = 0; i < 3; i++) {
			if (stat(INITTAB, &sbuf) == -1) {
				if (i == 2) {
					console("Cannot stat %s, errno: %d\n",
					    INITTAB, errno);
					return (FAILURE);
				} else {
					timer(3);
				}
			} else if (sbuf.st_size < 10) {
				if (i == 2) {
					console("%s truncated or corrupted\n",
					    INITTAB);
					return (FAILURE);
				} else {
					timer(3);
				}
			} else {
				break;
			}
		}

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

	/*
	 * Keep getting commands from 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.
		 */
		bzero(cmd, sizeof (struct CMD_LINE));

		/*
		 * Read in lines of 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 lines which don't end in a newline are still processed,
		 * since the "for" will terminate normally once started,
		 * regardless of whether line terminates with a newline or EOF.
		 */
		state = FAILURE;
		if ((c = fgetc(fp_inittab)) == EOF) {
			answer = FALSE;
			fclose(fp_inittab);
			fp_inittab = NULL;
			break;
		}

		for (proceed = TRUE, ptr = shcmd, state = ID, lastc = '\0';
		    proceed && c != EOF;
		    lastc = c, c = fgetc(fp_inittab)) {
		    /* If we're 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;
					state = LEVELS;
				}
				break;

			    case LEVELS :
				/*
				 * Build a mask for all the levels for
				 * which this command will be legal.
				 */
				for (cmd->c_levels = 0, ptr1 = shcmd;
				    ptr1 < ptr; ptr1++) {
					int mask;
					if (lvlname_to_mask(*ptr1,
					    &mask) == -1) {
						state = FAILURE;
						break;
					}
					cmd->c_levels |= mask;
				}
				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) {
					    if ((cmd->c_levels & MASKSU) &&
						!(act_masks[i] & su_acts))
						    cmd->c_action = 0;
					    else
						cmd->c_action = act_masks[i];
					    break;
					}
				    }
				}

				/*
				 * If the action didn't match any legal action,
				 * set state to FAILURE.
				 */
				if (cmd->c_action == 0) {
					state = FAILURE;
				} else {
					state = COMMAND;
					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 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' && 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++ = (char)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 = MASK_NUMERIC;

			/*
			 * 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)
				*shcmd = '\0';
		} else
			answer = FALSE;

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

/*
 * mask_to_highest_state(): return the highest state represented in a level
 * mask.
 */
int
mask_to_highest_state(mask)
int	mask;
{
	int i, highest_mask = 0, highest_state = 0;
	for (i = 0; i < LVL_NELEMS; i++) {
		if ((lvls[i].lvl_mask & mask) == lvls[i].lvl_mask) {
			if (lvls[i].lvl_mask > highest_mask) {
				highest_mask = lvls[i].lvl_mask;
				highest_state = lvls[i].lvl_state;
			}
		}
	}
	return (highest_state);
}

/*
 * lvlname_to_state(): convert the character name of a state to its level
 * (its corresponding signal number).
 */
int
lvlname_to_state(name)
char	name;
{
	int i;
	for (i = 0; i < LVL_NELEMS; i++) {
		if (lvls[i].lvl_name == name)
			return (lvls[i].lvl_state);
	}
	return (-1);
}

/*
 * state_to_mask(): return the mask corresponding to a signal number
 */
int
state_to_mask(state)
int	state;
{
	int i;
	for (i = 0; i < LVL_NELEMS; i++) {
		if (lvls[i].lvl_state == state)
			return (lvls[i].lvl_mask);
	}
	return (0);	/* return 0, since that represents an empty mask */
}

/*
 * lvlname_to_mask(): return the mask corresponding to a levels character name
 */
int
lvlname_to_mask(name, mask)
char	name;
int	*mask;
{
	int i;
	for (i = 0; i < LVL_NELEMS; i++) {
		if (lvls[i].lvl_name == name) {
			*mask = lvls[i].lvl_mask;
			return (0);
		}
	}
	return (-1);
}

/*
 * state_to_flags(): return the flags corresponding to a runlevel.  These
 * indicate properties of that runlevel.
 */
int
state_to_flags(state)
int	state;
{
	int i;
	for (i = 0; i < LVL_NELEMS; i++) {
		if (lvls[i].lvl_state == state)
			return (lvls[i].lvl_flags);
	}
	return (0);
}

/*
 * state_to_lvlname(): return the character name of a runlevel given a
 * signal number.
 */
char
state_to_lvlname(state)
int	state;
{
	int i;
	for (i = 0; i < LVL_NELEMS; i++) {
		if (lvls[i].lvl_state == state)
			return (lvls[i].lvl_name);
	}
	return ('?');
}

/*
 * killproc() creates a child which kills the process specified by pid.
 */
void
killproc(pid)
pid_t	pid;
{
	struct PROC_TABLE	*process;

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

	if (process == NULLPROC) {
		/*
		 * efork() sets all signal handlers to the default, so reset
		 * the ALRM handler to make timer() work as expected.
		 */
		sigset(SIGALRM, alarmclk);

		/*
		 * We are the child.  Try to terminate the process nicely
		 * first using SIGTERM and if it refuses to die in TWARN
		 * seconds kill it with SIGKILL.
		 */
		kill(pid, SIGTERM);
		timer(TWARN);
		kill(pid, SIGKILL);
		exit(0);
	}
}

/*
 * initialize(): Perform the initial state setup and look for an
 * initdefault entry in the "inittab" file.
 */
int
initialize()
{
	int i;
	struct PROC_TABLE *process, *oprocess;
	struct CMD_LINE	cmd;
	char 	line[MAXCMDL];
	FILE	*fp;
	int 	initstate, inquotes, length, maxfiles, wslength;
	char	*tokp, *cp1, *cp2;
	int write_ioctl;

	/*
	 * Initialize state to "SINGLE_USER" "BOOT_MODES".
	 */
	if (bflg != 2) {
		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 appropriate.
	 */
	init_signals();

	/*
	 * Set up pipe for "godchildren". If the file exists and is a pipe
	 * just open it. Else, if the file system is r/w create it now.
	 * Otherwise, defer its creation and open until after the sysinit
	 * functions have had a chance to make the root read/write.
	 */

	setup_pipe();

	initstate = 0;

#ifdef	UDEBUG
	reset_modes();
#endif

	/*
	 * Get the ioctl settings for /dev/syscon from /etc/ioctl.syscon
	 * so that it can be brought up in the state it was in when the
	 * system went down; or set to defaults if ioctl.syscon isn't
	 * valid.
	 */
	write_ioctl = get_ioctl_syscon();

	/*
	 * Set up the default environment for all procs to be forked from init.
	 * Read the values from the /etc/default/init file, except for PATH.  If
	 * there's not enough room in the environment array, the environment
	 * lines that don't fit are silently discarded.
	 */
	glob_envp[0] = malloc((unsigned)(strlen(INIT_PATH)+2));
	strcpy(glob_envp[0], INIT_PATH);  /* For sysinit cmds */

	if (rflg) {
		glob_envp[1] =
			malloc((unsigned)(strlen("_DVFS_RECONFIG=YES")+2));
		strcpy(glob_envp[1], "_DVFS_RECONFIG=YES");
		i = 2;
	} else if (bflg == 1) {
		glob_envp[1] =
			malloc((unsigned)(strlen("RB_NOBOOTRC=YES")+2));
		strcpy(glob_envp[1], "RB_NOBOOTRC=YES");
		i = 2;
	} else
		i = 1;
	if (fflg) {
		glob_envp[i] =
			malloc((unsigned)(strlen("_CACHEFS_FLUSHCACHE=YES")+2));
		strcpy(glob_envp[i], "_CACHEFS_FLUSHCACHE=YES");
		i++;
	}
	if ((fp = fopen(ENVFILE, "r")) == NULL) {
		console("Cannot open %s. Environment not initialized.\n",
		    ENVFILE);
	} else {
		while (fgets(line, MAXCMDL - 1, fp) != NULL &&
		    i < MAXENVENT - 1) {
			/*
			 * Toss newline
			 */
			length = strlen(line);
			if (line[length - 1] == '\n')
				line[length - 1] = '\0';
			/*
			 * Ignore blank or comment lines.
			 */
			if (line[0] == '#' || line[0] == '\0' ||
			    (wslength = strspn(line, " \t\n")) ==
							strlen(line) ||
			    strchr(line, '#') == line + wslength)
				continue;
			/*
			 * First make a pass through the line and change
			 * any non-quoted semi-colons to blanks so they
			 * will be treated as token separators below.
			 */
			inquotes = 0;
			for (cp1 = line; *cp1 != '\0'; cp1++) {
				if (*cp1 == '"') {
					if (inquotes == 0)
						inquotes = 1;
					else
						inquotes = 0;
				} else if (*cp1 == ';') {
					if (inquotes == 0)
						*cp1 = ' ';
				}
			}

			/*
			 * Tokens within the line are separated by blanks
			 *  and tabs.  For each token in the line which
			 * contains a '=' we strip out any quotes and then
			 * stick the token in the environment array.
			 */
			if ((tokp = strtok(line, " \t")) == NULL)
				continue;
			do {
				if (strchr(tokp, '=') == NULL)
					continue;
				length = strlen(tokp);
				while ((cp1 = strpbrk(tokp, "\"\'")) != NULL) {
					for (cp2 = cp1;
					    cp2 < &tokp[length - 1]; cp2++)
						*cp2 = *(cp2 + 1);
					length--;
				}
				if (strncmp(tokp, "CMASK=",
				    sizeof ("CMASK=") - 1) == 0) {
					long t;

					/* We know there's an = */
					t = strtol(strchr(tokp, '=') + 1, NULL,
					    8);

					/* Sanity */
					if (t <= 077 && t >= 0)
						cmask = (int)t;
					(void) umask(cmask);
					continue;
				}
				glob_envp[i] = malloc((unsigned)(length + 1));
				strcpy(glob_envp[i], tokp);
				if (++i >= MAXENVENT - 1)
					break;
			} while ((tokp = strtok(NULL, " \t")) != NULL);
		}

		/*
		 * Append a null pointer to the environment array
		 * to mark its end.
		 */
		glob_envp[i] = NULL;
		fclose(fp);
	}

	/*
	 * Scan the "inittab" file and process "initdefault"
	 * and "sysinit" entries.
	 */
	while (getcmd(&cmd, &line[0]) == TRUE) {
		if (cmd.c_action == M_INITDEFAULT) {
			/*
			 * Look through the "c_levels" word, starting at
			 * the highest level.  If there is more than one
			 * level specified, the system will come up at
			 * the highest of the specified levels.
			 */
			initstate = mask_to_highest_state(cmd.c_levels);
		} else if (cmd.c_action == M_SYSINIT) {
			/*
			 * Execute the "sysinit" entry and wait for it to
			 * complete.  No bookkeeping is performed on these
			 * entries because we avoid writing to the file system
			 * until after there has been an chance to check it.
			 */
			if (process = findpslot(&cmd)) {
				sigset(SIGCLD, SIG_DFL);

				for (oprocess = process;
				    (process = efork(M_OFF, oprocess,
				    (NAMED|NOCLEANUP))) == NO_ROOM;
				    /* CSTYLED */)
					;
				sigset(SIGCLD, childeath);

				if (process == NULLPROC) {
					maxfiles = ulimit(UL_GDESLIM, 0);

					for (i = 0; i < maxfiles; i++)
						fcntl(i, F_SETFD, FD_CLOEXEC);
					execle(SH, "INITSH", "-c",
					    cmd.c_command,
					    (char *)0, glob_envp);
					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;
			}
		}
	}
	/*
	 * We're done processing sysinit cmds so set proper default path
	 */
	glob_envp[0] = malloc((unsigned)(strlen(DEF_PATH)+2));
	strcpy(glob_envp[0], DEF_PATH);

	/*
	 * The sysinit entries have been completed, so if the root partition
	 * is to ever become r/w, it should have done so by now. Remember,
	 * that in the case of read-only media, it may never become r/w.
	 */
	if (Pfd < 0)
		setup_pipe();

	if (Pfd < 0)
		console("warning: cannot open %s.\n", INITPIPE);

	/*
	 * If the /etc/ioctl.syscon didn't exist or had invalid
	 * contents write out a correct version.
	 */
	if (write_ioctl)
		write_ioctl_syscon();

	if (initstate == SINGLE_USER)
		return (-1);

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

/*
 * init_signals(): Initialize all signals to either be caught or ignored.
 */
void
init_signals()
{
	sigset(LVLQ, siglvl);
	sigset(LVL0, siglvl);
	sigset(LVL1, siglvl);
#ifdef	UDEBUG
	sigset(LVL2, SIG_DFL);
	sigset(LVL3, SIG_DFL);
	sigset(LVL4, SIG_DFL);
#else
	sigset(LVL2, siglvl);
	sigset(LVL3, siglvl);
	sigset(LVL4, siglvl);
#endif
	sigset(LVL5, siglvl);
	sigset(LVL6, siglvl);
	sigset(SINGLE_USER, siglvl);

	sigset(LVLa, siglvl);
	sigset(LVLb, siglvl);
	sigset(LVLc, siglvl);
	sigset(SIGALRM, alarmclk);
	alarmclk();
#ifdef	UDEBUG
	sigset(SIGTERM, SIG_DFL);
	sigset(SIGUSR1, abort);
	sigset(SIGUSR2, abort);
	sigset(SIGXCPU, abort);
	sigset(SIGXFSZ, abort);
#else
	sigset(SIGTERM, SIG_IGN);
	sigset(SIGUSR1, SIG_IGN);
	sigset(SIGUSR2, SIG_IGN);
	sigset(SIGXCPU, SIG_IGN);
	sigset(SIGXFSZ, SIG_IGN);
#endif
	sigset(SIGCLD, childeath);
	sigset(SIGPWR, powerfail);

}

/*
 * Set up pipe for "godchildren". If the file exists and is a pipe
 * just open it. Else, if the file system is r/w create it.
 */
void
setup_pipe()
{
	struct stat stat_buf;
	struct statvfs statvfs_buf;

	if ((stat(INITPIPE, &stat_buf) == 0) &&
	    ((stat_buf.st_mode & (S_IFMT|S_IRUSR)) == (S_IFIFO|S_IRUSR)))
		Pfd = open(INITPIPE, O_RDWR | O_NDELAY);
	else
		if ((statvfs(INITPIPE_DIR, &statvfs_buf) == 0) &&
		    ((statvfs_buf.f_flag & ST_RDONLY) == 0)) {
			(void) unlink(INITPIPE);
			(void) mknod(INITPIPE, S_IFIFO | 0600, 0);
			Pfd = open(INITPIPE, O_RDWR | O_NDELAY);
		}

	if (Pfd >= 0) {
		(void) ioctl(Pfd, I_SETSIG, S_INPUT);
		/*
		 * Read pipe in message discard mode.
		 */
		(void) ioctl(Pfd, I_SRDOPT, RMSGD);
		sigset(SIGPOLL, sigpoll);
	}
}


/*
 * siglvl
 */
void
siglvl(sig)
int sig;
{
	struct PROC_TABLE	*process;

	/*
	 * If the signal received is a LVLQ signal, do not really
	 * change levels, just restate the current level.  If the
	 * signal is not a LVLQ, set the new level to the signal
	 * received.
	 */
	if (sig == LVLQ)
		new_state = cur_state;
	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 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;
		(process < proc_table + num_proc); process++) {
		process->p_time = 0L;
		process->p_count = 0;
	}

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


/*
 * alarmclk
 */
void
alarmclk()
{
	time_up = TRUE;
}

/*
 * childeath_single():
 *
 * This used to be the SIGCLD handler and it was set with signal()
 * (as opposed to sigset()).  When a child exited we'd come to the
 * handler, wait for the child, and reenable the handler with
 * signal() just before returning.  The implementation of signal()
 * checks with waitid() for waitable children and sends a SIGCLD
 * if there are some.  If children are exiting faster than the
 * handler can run we keep sending signals and the handler never
 * gets to return and eventually the stack runs out and init dies.
 * To prevent that we set the handler with sigset() so the handler
 * doesn't need to be reset, and in childeath() (see below) we
 * call childeath_single() as long as there are children to be
 * waited for.  If a child exits while init is in the handler a
 * SIGCLD will be pending and delivered on return from the handler.
 * If the child was already waited for the handler will have nothing
 * to do and return, otherwise the child will be waited for.
 */
void
childeath_single()
{
	struct PROC_TABLE	*process;
	struct pidlist		*pp;
	pid_t			pid;
	int			status;

	/*
	 * Perform wait to get the process id of the child that 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- %ld status- %x\n", pid, status);
#endif

	for (process = proc_table;
		(process < proc_table + num_proc); 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 the loop.
			 */
			process->p_flags &= ~LIVING;
			process->p_exit = (short)status;
			wakeup.w_flags.w_childdeath = 1;

#ifdef	UDEBUG
			if (process == (proc_table + num_proc))
				debug("Didn't find process %ld.\n", pid);
#endif

			return;
		}
	}

	/*
	 * No process was found above, look through auxiliary list.
	 */
	(void) sighold(SIGPOLL);
	pp = Plhead;
	while (pp) {
		if (pid > pp->pl_pid) {
			/*
			 * Keep on looking.
			 */
			pp = pp->pl_next;
			continue;
		} else if (pid < pp->pl_pid) {
			/*
			 * Not in the list.
			 */
			break;
		} else {
			/*
			 * This is a dead "godchild".
			 */
			pp->pl_dflag = 1;
			pp->pl_exit = (short)status;
			wakeup.w_flags.w_childdeath = 1;
			Gchild = 1;	/* Notice to call cleanaux(). */
			break;
		}
	}

	(void) sigrelse(SIGPOLL);
}

/* ARGSUSED */
void
childeath(signo)
int signo;
{
	siginfo_t info;

	/*
	 * We have to check for si_pid != 0 because of the current
	 * kernel implementation, but that seems to contradict the SVID
	 * and the kernel may change (see bug 1151732).
	 */
	while ((waitid(P_ALL, (id_t)0, &info, WEXITED|WNOHANG|WNOWAIT) == 0) &&
	    info.si_pid != 0)
		childeath_single();
}

void
powerfail()
{
	nice(-19);
	wakeup.w_flags.w_powerhit = 1;
}

/*
 * promptuserlvl(): ask user what runlevel they would like to transition to.
 */
int
promptuserlvl()
{
	char s[2], c, plvl;
	int st;

	if ((plvl = state_to_lvlname(promptlevel)) == '?') {
		plvl = '3'; /* 3 as default in case there is truly no default */
	}

	for (;;) {
		(void) fprintf(stdout, "ENTER RUN LEVEL (0-6, s or S) [%c]:",
		    plvl);
		(void) fflush(stdout);

		if (fgets(s, sizeof (s), stdin) == NULL) {
			continue;
		}

		/*
		 * If there was no newline in s, the user pressed a bunch of
		 * keys, and we need to flush out the input stream.
		 */
		if (strchr(s, '\n') == NULL) {
			while (fgetc(stdin) != '\n')
				;
		}

		c = s[0];
		if (c == '\n') {	/* Handle default of <Return> */
			c = plvl;
		}

		/*
		 * If we got a valid state, and that state is a valid run level,
		 * break out; else, take another lap.
		 */
		if ((st = lvlname_to_state(c)) != -1) {
			if ((state_to_flags(st) & LSEL_RUNLEVEL) != 0)
				break;
		}

		(void) fprintf(stdout, "'%c' is not a valid state\n", c);
		(void) fprintf(stdout, "Usage: 0123456sS or <Return>\n");
	}
	(void) fflush(stdout);
	return (st);
}

/*
 * 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
getlvl()
{
	int	status;
	FILE	*fp_tmp;
	int	process;

	/*
	 * fork a child who will request the new run level from  /dev/syscon.
	 */
	sigset(SIGCLD, SIG_DFL);
	while ((process = fork()) == -1)
		;
	if (process == 0) {
		sigset(SIGHUP, SIG_IGN);

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

#ifdef	UDEBUG
		sigset(SIGUSR1, abort);
		sigset(SIGUSR2, abort);
#endif

		for (;;) {
			/*
			 * Close the current descriptors and open
			 * ones to /dev/syscon.
			 */
			(void) 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);

			exit(promptuserlvl());
		}
	}

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

#ifdef DEBUG
	debug("getlvl: status: %o exit: %o termination: %o\n",
	    status, (status & 0xff00)>>8, (status & 0xff));
#endif

	return ((status & 0xff00) >> 8);
}


/* ARGSUSED */
void
switchcon(sig)
int sig;
{
	/*
	 * 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 re-establish file pointers.
	 */
	if (fd_systty != -1) {
		reset_syscon();
		(void) 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;
	}
}


/*
 * 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.  Regardless of how it was called,
 * it returns the pointer to the proc_table entry
 */
struct PROC_TABLE *
efork(action, process, modes)
int	action;
struct PROC_TABLE *process;
int	modes;
{
	pid_t	childpid;
	struct PROC_TABLE *proc;
	int		i;
	void (*oldroutine)();
#ifdef	UDEBUG
	static void (*oldsigs[SIGPWR + 1])();
#endif

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

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

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

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

		/*
		 * Wait for some children to die.  Since efork() is normally
		 * called with SIGCLD in the default state, reset it to catch
		 * so that child death signals can come in.
		 */
		oldroutine = sigset(SIGCLD, childeath);
		pause();
		sigset(SIGCLD, oldroutine);
		setimer(0);
	}

	if (childpid != 0) {

		if (process == NULLPROC) {
			/*
			 * No proc table pointer specified so search
			 * for a free slot.
			 */
			for (process = proc_table;  process->p_flags != 0 &&
				(process < proc_table + num_proc); process++)
					;

			if (process == (proc_table + num_proc)) {
				sigset_t block, unblock;
				int old_num_proc = num_proc;
				struct PROC_TABLE *new_proc = NULLPROC;

				/*
				 * Block signals for realloc.
				 */
				(void) sigfillset(&block);
				(void) sigprocmask(SIG_BLOCK, &block, &unblock);

				/*
				 * /etc/inittab has more entries and we have
				 * run out of proc_table array. Increase the
				 * size of the proc_table array by doubling
				 * it to accomodate the extra /etc/inittab
				 * entries.
				 */
				num_proc *= 2;

				new_proc = (struct PROC_TABLE *)
					realloc(proc_table,
						sizeof (struct PROC_TABLE) *
						num_proc);

				if ((new_proc == NULL) && (SPECIALPID == 1)) {
					int i;

					for (i = 0; i < TRY_SPID_TIMES; i++) {
						new_proc = (struct PROC_TABLE *)
							realloc(proc_table,
								num_proc *
						sizeof (struct PROC_TABLE));

						if (new_proc != NULL)
							break;

						sleep(1);
					}
				}


				/*
				 * If realloc fails, return NO_ROOM. Otherwise
				 * return a pointer to the next proc_table
				 * entry.
				 */
				if (new_proc != NULLPROC) {
					proc_table = new_proc;
					process = proc_table + old_num_proc;
				} else {
					if (error_time(FULLTABLE))
						console("Internal process "
							"table cannot grow\n");

					/* also decrement nproc as we failed */
					num_proc /= 2;

					/* unblock signals before returning */
					(void) sigprocmask(SIG_SETMASK,
							&unblock, NULL);
					return (NO_ROOM);
				}

				/* unblock our signals before returning */
				(void) sigprocmask(SIG_SETMASK, &unblock, NULL);
			}
			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 {
		/*
		 * Reset child's concept of its own process id.
		 */
		own_pid = getpid();

		if (action != M_WAIT)
			setpgrp();

		process = NULLPROC;

		/*
		 * Reset all signals to the system defaults,
		 * making sure that SIGXCPU and SIGXFSZ remain
		 * ignored, for backward compatibility.
		 */
#ifdef	UDEBUG
		for (i = SIGHUP; i <= SIGPWR; i++)
			oldsigs[i] = sigset(i, SIG_DFL);
#else
		for (i = SIGHUP; i <= SIGPWR; i++)
			sigset(i, SIG_DFL);
#endif
	}
	return (process);
}


/*
 * waitproc() waits for a specified process to die.  For this function 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)
		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);
}

/*
 * notify_pam_dead(): calls into the PAM framework to close the given session.
 */
void
notify_pam_dead(up)
struct utmpx 	*up;
{
	pam_handle_t *pamh;
	char user[sizeof (up->ut_user) + 1];
	char ttyn[sizeof (up->ut_line) + 1];
	char host[sizeof (up->ut_host) + 1];

	/*
	 * PAM does not take care of updatng utmpx/wtmpx
	 */
	(void) snprintf(user, sizeof (user), "%s", up->ut_user);
	(void) snprintf(ttyn, sizeof (ttyn), "%s", up->ut_line);
	(void) snprintf(host, sizeof (host), "%s", up->ut_host);

	if (pam_start("init", user, NULL, &pamh) == PAM_SUCCESS)  {
		(void) pam_set_item(pamh, PAM_TTY, ttyn);
		(void) pam_set_item(pamh, PAM_RHOST, host);
		(void) pam_close_session(pamh, 0);
		(void) pam_end(pamh, PAM_SUCCESS);
	}
}

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

#ifdef	ACCTDEBUG
	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;
	memset(u, 0, sizeof (struct utmpx));

	/*
	 * 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 = WTERMSIG(process->p_exit);
	u->ut_exit.e_exit = WEXITSTATUS(process->p_exit);
	u->ut_type = state;

	/*
	 * We need to special-case the BOOT_TIME utmp entry, and drag
	 * that value out of the kernel if it's there.
	 */
	if (state == BOOT_TIME) {
		kstat_ctl_t *kc;
		kstat_t *ks;
		kstat_named_t *boot;

		if (((kc = kstat_open()) != 0) && ((ks =
		    kstat_lookup(kc, "unix", 0, "system_misc")) != NULL) &&
		    (kstat_read(kc, ks, NULL) != -1) &&
		    ((boot = kstat_data_lookup(ks, "boot_time")) != NULL)) {
			/*
			 * If we're here, then we've successfully found
			 * the boot_time kstat...use its value.
			 */
			u->ut_tv.tv_sec = boot->value.ul;
		} else
			time(&u->ut_tv.tv_sec);

		if (kc)
			kstat_close(kc);
	} else
		time(&u->ut_tv.tv_sec);

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

	if ((oldu = getutxid(u)) != NULL) {
		/*
		 * Copy in the old "user", "line" and "host" fields
		 * to our new structure.
		 */
		bcopy(oldu->ut_user, u->ut_user, sizeof (u->ut_user));
		bcopy(oldu->ut_line, u->ut_line, sizeof (u->ut_line));
		bcopy(oldu->ut_host, u->ut_host, sizeof (u->ut_host));
		u->ut_syslen = (tmplen = strlen(u->ut_host)) ?
			min(tmplen + 1, sizeof (u->ut_host)) : 0;

		if (oldu->ut_type == USER_PROCESS && state == DEAD_PROCESS) {
			notify_pam_dead(oldu);
		}
#ifdef	ACCTDEBUG
		debug("New entry in utmpx file.\n");
#endif
	}
#ifdef	ACCTDEBUG
	else debug("Replacing old entry in utmpx file.\n");
#endif

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

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

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

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

	case INIT_PROCESS:
		strncpy(u->ut_user, program, sizeof (u->ut_user));
		break;

	default:
		break;
	}

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

	/*
	 * Now attempt to add to the end of the wtmpx file.
	 */
	updwtmpx(WTMPX, u);
}

/*
 * 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;
	/* XXX - utmp - fix name length */
	static char word[_POSIX_LOGIN_NAME_MAX];

	/*
	 * 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.
	 */
	/* XXX - utmp - fix name length */
	for (ptr2 = &word[0]; ptr2 < &word[_POSIX_LOGIN_NAME_MAX - 1] &&
	    ptr < string; /* CSTYLED */)
		*ptr2++ = *ptr++;

	*ptr2 = '\0';
	return (&word[0]);
}


/*
 * opensyscon() opens stdin, stdout, and stderr, making sure
 * that their file descriptors are 0, 1, and 2, respectively.
 * Returns -1 if it cannot open SYSCON, 0 otherwise.
 */
int
opensyscon()
{
	register FILE *fp;

	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();
		if ((fp = fopen(SYSCON, "r+")) == NULL) {
			/*
			 * Need to issue an error message somewhere.
			 */
			syslog(LOG_CRIT,
				"init[%d]: cannot open %s; %s\n",
				getpid(), SYSCON, strerror(errno));
			return (-1);
		}
	}

	(void) fdup(fp);
	(void) fdup(fp);
	setbuf(fp, NULL);
	setbuf(stdout, NULL);
	setbuf(stderr, NULL);

	/*
	 * Save the current SYSCON modes and restore the modes stored in
	 * termio (modes from the IOCTL.SYSCON file or default modes).
	 * The current modes will be restored by console() after the
	 * message is printed.
	 */
	if ((iscurterms = fioctl(fp, TCGETS, &curterms)) < 0) {
		int i;

		fioctl(fp, TCGETA, &curterm);
		curterms.c_oflag = curterm.c_oflag;
		curterms.c_iflag = curterm.c_iflag;
		curterms.c_cflag = curterm.c_cflag;
		curterms.c_lflag = curterm.c_lflag;
		for (i = 0; i < NCC; i++)
			curterms.c_cc[i] = curterm.c_cc[i];
	}
	curterms.c_cflag &= ~HUPCL;	/* Make sure hangup on close is off. */
	if (realcon()) {
		/*
		 * Don't overwrite cflag when init console is real console.
		 */
		termios.c_cflag = curterms.c_cflag;
	}

	termios.c_cflag &= ~HUPCL;	/* Make sure hangup on close is off. */
	if (iscurterms < 0) {
		int i;

		termio.c_iflag = termios.c_iflag;
		termio.c_oflag = termios.c_oflag;
		termio.c_cflag = termios.c_cflag;
		termio.c_lflag = termios.c_lflag;
		for (i = 0; i < NCC; i++)
			termio.c_cc[i] = termios.c_cc[i];
		fioctl(fp, TCSETA, &termio);
	} else
		fioctl(fp, TCSETS, &termios);
	return (0);
}


/*
 * realcon() returns a nonzero value 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() retrieves the SYSCON settings from the IOCTLSYSCON file.
 */
int
get_ioctl_syscon()
{
	register FILE	*fp;
	unsigned int	iflags, oflags, cflags, lflags, ldisc, cc[18];
	int		i, valid_format = 0;

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

	    i = fscanf(fp,
	    "%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x",
		&iflags, &oflags, &cflags, &lflags,
		&cc[0], &cc[1], &cc[2], &cc[3], &cc[4], &cc[5], &cc[6],
		&cc[7], &cc[8], &cc[9], &cc[10], &cc[11], &cc[12], &cc[13],
		&cc[14], &cc[15], &cc[16], &cc[17]);

	    if (i == 22) {
		termios.c_iflag = iflags;
		termios.c_oflag = oflags;
		termios.c_cflag = cflags;
		termios.c_lflag = lflags;
		for (i = 0; i < 18; i++)
			termios.c_cc[i] = (char)cc[i];
		valid_format = 1;
	    } else if (i == 13) {
		rewind(fp);
		i = fscanf(fp, "%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]);

		/*
		 * If the file is formatted properly, use the values to
		 * initialize the console terminal condition.
		 */
		termios.c_iflag = termio.c_iflag = (ushort_t)iflags;
		termios.c_oflag = termio.c_oflag = (ushort_t)oflags;
		termios.c_cflag = termio.c_cflag = (ushort_t)cflags;
		termios.c_lflag = termio.c_lflag = (ushort_t)lflags;
		termio.c_line = (char)ldisc;
		for (i = 0; i < 8; i++)
			termios.c_cc[i] = termio.c_cc[i] = (char)cc[i];
		valid_format = 1;
	    }
	    fclose(fp);

	    /* If the file is badly formatted, use the default settings. */
	    if (!valid_format) {
		termio = dflt_termio;
		termios = dflt_termios;
	    }
	}
	return (!valid_format);
}


/*
 * reset_syscon() relinks /dev/syscon to /dev/systty and puts the default
 * ioctl setting back into /etc/ioctl.syscon and the incore arrays.
 */
void
reset_syscon()
{
	bcopy((char *)&dflt_termio, (char *)&termio, sizeof (struct termio));
	bcopy((char *)&dflt_termios, (char *)&termios, sizeof (struct termios));
	write_ioctl_syscon();
}

void
write_ioctl_syscon()
{
	register FILE *fp;
	int i;

	unlink(SYSCON);
	link(SYSTTY, SYSCON);
	(void) umask(022);
	fp = fopen(IOCTLSYSCON, "w");

	termio.c_iflag = termios.c_iflag;
	termio.c_oflag = termios.c_oflag;
	termio.c_cflag = termios.c_cflag;
	termio.c_lflag = termios.c_lflag;
	for (i = 0; i < 8; i++)
		termio.c_cc[i] = termios.c_cc[i];

	fprintf(fp, "%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]);
	fflush(fp);
	fsync(fileno(fp));
	fclose(fp);
	(void) umask(cmask);
}


/*
 * reset_modes() makes sure the proper terminal modes are set so init can
 * continue talking to SYSCON after coming down to single user and after
 * rebooting.  It must see that the proper modes are set in the driver,
 * init's in-core termio structure, and the ioctl.syscon file.
 */
void
reset_modes()
{
	FILE		*fp;
	struct PROC_TABLE *process;
	struct termio	tio;
	struct termios	tios;
	ulong_t		curcflag = 0;
	int		valid_termios = 0;
	int		i;
	unsigned int	iflags, oflags, cflags, lflags, ldisc, cc[18];

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

	if (process == NULLPROC) {
		if ((fp = fopen(SYSCON, "w")) == NULL) {
			syslog(LOG_CRIT,
				"init[%d]: cannot open %s; %s\n",
				getpid(), SYSCON, strerror(errno));
		} else {
			if (fioctl(fp, TCGETS, &tios) < 0) {
			    if (fioctl(fp, TCGETA, &tio) != FAILURE) {
				curcflag = tio.c_cflag;
				/*
				 * Clear HUPCL in the driver.
				 */
				tio.c_cflag &= ~HUPCL;
				fioctl(fp, TCSETA, &tio);
			    }
			} else {
				curcflag = tios.c_cflag;
				/*
				 * Clear HUPCL in the driver.
				 */
				tios.c_cflag &= ~HUPCL;
				fioctl(fp, TCSETS, &tios);
			}
			fclose(fp);
		}

		if ((fp = fopen(IOCTLSYSCON, "r")) != NULL) {
			/*
			 * Update the in-core termio structure so it agrees
			 * with the ioctl.syscon file.  Better sanity checking
			 * should probably be done here on the ioctl.syscon
			 * data.
			 */
		    i = fscanf(fp,
	"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x",
			    &iflags, &oflags, &cflags, &lflags,
			    &cc[0], &cc[1], &cc[2], &cc[3], &cc[4], &cc[5],
			    &cc[6], &cc[7], &cc[8], &cc[9], &cc[10], &cc[11],
			    &cc[12], &cc[13], &cc[14], &cc[15], &cc[16],
			    &cc[17]);
		    if (i == 22) {
			    termios.c_iflag = iflags;
			    termios.c_oflag = oflags;
			    termios.c_cflag = cflags;
			    termios.c_lflag = lflags;
			    for (i = 0; i < 18; i++)
				termios.c_cc[i] = (char)cc[i];
			    valid_termios = 1;
		    } else if (i == 13) {
			rewind(fp);
			fscanf(fp, "%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]);
			termios.c_iflag = termio.c_iflag = (ushort_t)iflags;
			termios.c_oflag = termio.c_oflag = (ushort_t)oflags;
			termios.c_cflag = termio.c_cflag = (ushort_t)cflags;
			termios.c_lflag = termio.c_lflag = (ushort_t)lflags;
			termio.c_line = (char)ldisc;
			for (i = 0; i < 8; i++)
			    termios.c_cc[i] = termio.c_cc[i] = (char)cc[i];
		    }
		    fclose(fp);
		}

		if (!realcon() && curcflag != 0) {
			/*
			 * The virtual console is different from the
			 * physical console so we set the cflag in the
			 * in-core termio and in ioctl.syscon to the current
			 * cflag setting.  This ensures that the settings for
			 * this device will be correct when we reach single
			 * user and after reboot.  We don't reset the other
			 * (non-cflag) fields because the current settings
			 * may be inappropriate for the single user shell.
			 */
			termios.c_cflag = curcflag;
		}

		(void) umask(022);
		if ((fp = fopen(IOCTLSYSCON, "w")) == NULL) {
			console("Can't open %s. errno: %d\n", IOCTLSYSCON,
								errno);
		} else {
		    if (valid_termios) {
			fprintf(fp,
	"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n",
			    termios.c_iflag, termios.c_oflag, termios.c_cflag,
			    termios.c_lflag, termios.c_cc[0], termios.c_cc[1],
			    termios.c_cc[2], termios.c_cc[3], termios.c_cc[4],
			    termios.c_cc[5], termios.c_cc[6], termios.c_cc[7],
			    termios.c_cc[8], termios.c_cc[9], termios.c_cc[10],
			    termios.c_cc[11], termios.c_cc[12],
			    termios.c_cc[13], termios.c_cc[14],
			    termios.c_cc[15], termios.c_cc[16],
			    termios.c_cc[17]);
		    } else {
			termio.c_iflag = termios.c_iflag;
			termio.c_oflag = termios.c_oflag;
			termio.c_cflag = termios.c_cflag;
			termio.c_lflag = termios.c_lflag;
			for (i = 0; i < 8; i++)
				termio.c_cc[i] = termios.c_cc[i];
			fprintf(fp,
	    "%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]);
		    }
		    fflush(fp);
		    fsync(fileno(fp));
		    fclose(fp);
		}
		(void) umask(cmask);
		termios.c_cflag &= ~HUPCL;
		exit(0);
	} else {
		/*
		 * The parent waits for the child to die.
		 */
		while (waitproc(process) == FAILURE)
			;
	}
}


/*
 * 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.
 */
/* PRINTFLIKE1 */
void
console(format, arg1, arg2, arg3, arg4)
char	*format;
int	arg1, arg2, arg3, arg4;
{
	register struct PROC_TABLE *process;
	char	outbuf[BUFSIZ];

	if (own_pid == SPECIALPID) {
		/*
		 * We are the original "init" so we fork a child to do the
		 * printing for us.
		 */
		sigset(SIGCLD, SIG_DFL);
		while ((process = efork(M_OFF, NULLPROC, NOCLEANUP)) == NO_ROOM)
			timer(5);
		sigset(SIGCLD, childeath);
		if (process == NULLPROC) {
#ifdef	UDEBUG
			sigset(SIGUSR1, abort);
			sigset(SIGUSR2, abort);
#endif
			/*
			 * Close the standard descriptors and open the console.
			 */
			if (opensyscon() == -1) {
				syslog(LOG_WARNING, "INIT: ");
				syslog(LOG_WARNING, format, arg1, arg2,
							arg3, arg4);
				exit(1);
			}
			setbuf(stdout, &outbuf[0]);

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

			/*
			 * Restore the settings saved in opensyscon().
			 */
			if (iscurterms < 0) {
				int i;

				curterm.c_oflag = curterms.c_oflag;
				curterm.c_iflag = curterms.c_iflag;
				curterm.c_cflag = curterms.c_cflag;
				curterm.c_lflag = curterms.c_lflag;
				for (i = 0; i < NCC; i++)
					curterm.c_cc[i] = curterms.c_cc[i];
				fioctl(stdout, TCSETAW, &curterm);
			} else
				fioctl(stdout, TCSETSW, &curterms);
			exit(0);

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

	} else {
		/*
		 * We are some other "init" so print directly
		 * to the standard output.
		 */
		if (opensyscon() == -1) {
			syslog(LOG_WARNING, "INIT: ");
			syslog(LOG_WARNING, format, arg1, arg2, arg3, arg4);
			return;
		}
		setbuf(stdout, &outbuf[0]);
		fprintf(stdout, "\nINIT: ");
		fprintf(stdout, format, arg1, arg2, arg3, arg4);
		fflush(stdout);

		/*
		 * Restore the settings saved in opensyscon().
		 */
		if (iscurterms < 0) {
			int i;

			curterm.c_oflag = curterms.c_oflag;
			curterm.c_iflag = curterms.c_iflag;
			curterm.c_cflag = curterms.c_cflag;
			curterm.c_lflag = curterms.c_lflag;
			for (i = 0; i < NCC; i++)
				curterm.c_cc[i] = curterms.c_cc[i];
			fioctl(stdout, TCSETAW, &curterm);
		} else
			fioctl(stdout, TCSETSW, &curterms);
	}

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

}


/*
 * error_time() keeps a table of times, one for each type 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, else it returns FALSE.
 */
int
error_time(type)
register int	type;
{
	time_t	curtime;

	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() is a substitute for sleep() which uses alarm() and pause().
 */
void
timer(waitime)
register int	waitime;
{
	setimer(waitime);
	while (time_up == FALSE)
		pause();
}


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

/*
 * Function to handle requests from users to main init running as process 1.
 */
void
userinit(argc, argv)
int	argc;
char	**argv;
{
	FILE	*fp;
	char	*ln;
	int	init_signal;
	struct stat	sconbuf, conbuf;
	int turnoff = 0;
	const char *usage_msg = "Usage: init [0123456SsQqabc]\n";

	/*
	 * 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') {
		(void) fprintf(stderr, usage_msg);
		exit(0);
	}

	(void) audit_init_setup(argc, argv);

	if ((init_signal = lvlname_to_state(argv[1][0])) == -1) {
		(void) fprintf(stderr, usage_msg);
		(void) audit_init_fail();
		exit(1);
	}

	turnoff = LSEL_NOAUDIT & state_to_flags(init_signal);

	if (init_signal == SINGLE_USER) {
		/*
		 * Make sure this process is talking to a legal tty line
		 * and that /dev/syscon is linked to this line.
		 */
		ln = ttyname(0);	/* Get the name of tty */
		if (ln == NULL) {
			(void) fprintf(stderr,
			    "Standard input not a tty line\n");
			(void) audit_init_fail();
			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 relink it to the current line.
			 */
			if (unlink(SYSCON) == FAILURE) {
				perror("Can't unlink /dev/syscon");
				(void) audit_init_fail();
				exit(1);
			}
			if (link(ln, SYSCON) == FAILURE) {
				(void) fprintf(stderr,
				    "Can't link /dev/syscon to %s: %s", ln,
				    strerror(errno));

				/* Try to leave a syscon */
				(void) link(SYSTTY, SYSCON);
				(void) audit_init_fail();
				exit(1);
			}

			/*
			 * Try to leave a message 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);
			}
		}
	}

	if (geteuid() == 0) {
		int cond = AUC_NOAUDIT;
		int canaudit;
		(void) audit_init_success();

		/* find out if audit daemon is running */
		(void) auditon(A_GETCOND, (caddr_t)&cond, sizeof (cond));
		canaudit = (cond == AUC_AUDITING);

		/* turn off audit daemon and try to flush audit queue */
		if (turnoff && canaudit && system("/usr/sbin/audit -t")) {
			(void) fprintf(stderr, "%s: can't turn off auditd\n",
				argv[0]);
		}
		if (turnoff)
			sleep(5);
	}
	/*
	 * Now send signal to main init and then exit.
	 */
	if (kill(SPECIALPID, init_signal) == FAILURE) {
		(void) fprintf(stderr, "Must be super-user\n");
		(void) audit_init_fail();
		exit(1);
	}
	exit(0);
}


FILE *
fdup(fp)
FILE	*fp;
{
	int	newfd;
	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 stream was open for
		 * read and write, then use the "r+" option; if open read-only,
		 * the "r" option; if write-only, the "w" option.  Note that
		 * since none of these force an lseek by fdopen(), the dup()'d
		 * file pointer will be at the same spot as the original.
		 */
		if (__freadable(fp) && __fwritable(fp)) {
			mode = "r+";
		} else if (__freading(fp)) {
			mode = "r";
		} else if (__fwriting(fp)) {
			mode = "w";
		} else {
			/*
			 * Something is wrong.
			 */
			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

void
drop_core(reason)
char	*reason;
{
	FILE	*fp;

	sigset(SIGCLD, SIG_DFL);
	if (efork(M_OFF, NULLPROC, 0) != NULLPROC)
		return;
	sigset(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

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

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


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


#define	DELTA	25	/* Number of pidlist elements to allocate at a time */

/* ARGSUSED */
void
sigpoll(n)
int	n;
{
	struct pidrec prec;
	register struct pidrec *p = &prec;
	register struct pidlist *plp;
	register struct pidlist *tp, *savetp;
	register int i;

	if (Pfd < 0) {
		return;
	}
	sigset(SIGCLD, SIG_DFL);
	for (;;) {
		/*
		 * Important Note: Either read will really fail (in which case
		 * return is all we can do) or will get EAGAIN (Pfd was opened
		 * O_NDELAY), in which case we also want to return.
		 * Always return from here!
		 */
		if (read(Pfd, p, sizeof (struct pidrec)) !=
						sizeof (struct pidrec)) {
			sigset(SIGCLD, childeath);
			return;
		}
		switch (p->pd_type) {

		case ADDPID:
			/*
			 * New "godchild", add to list.
			 */
			if (Plfree == NULL) {
				plp = (struct pidlist *)calloc(DELTA,
				    sizeof (struct pidlist));
				if (plp == NULL) {
					/* Can't save pid */
					break;
				}
				/*
				 * Point at 2nd record allocated, we'll use plp.
				 */
				tp = plp + 1;
				/*
				 * Link them into a chain.
				 */
				Plfree = tp;
				for (i = 0; i < DELTA - 2; i++) {
					tp->pl_next = tp + 1;
					tp++;
				}
			} else {
				plp = Plfree;
				Plfree = plp->pl_next;
			}
			plp->pl_pid = p->pd_pid;
			plp->pl_dflag = 0;
			plp->pl_next = NULL;
			/*
			 * Note - pid list is kept in increasing order of pids.
			 */
			if (Plhead == NULL) {
				Plhead = plp;
				/* Back up to read next record */
				break;
			} else {
				savetp = tp = Plhead;
				while (tp) {
					if (plp->pl_pid > tp->pl_pid) {
						savetp = tp;
						tp = tp->pl_next;
						continue;
					} else if (plp->pl_pid < tp->pl_pid) {
						if (tp == Plhead) {
							plp->pl_next = Plhead;
							Plhead = plp;
						} else {
							plp->pl_next =
							    savetp->pl_next;
							savetp->pl_next = plp;
						}
						break;
					} else {
						/* Already in list! */
						plp->pl_next = Plfree;
						Plfree = plp;
						break;
					}
				}
				if (tp == NULL) {
					/* Add to end of list */
					savetp->pl_next = plp;
				}
			}
			/* Back up to read next record. */
			break;

		case REMPID:
			/*
			 * This one was handled by someone else,
			 * purge it from the list.
			 */
			if (Plhead == NULL) {
				/* Back up to read next record. */
				break;
			}
			savetp = tp = Plhead;
			while (tp) {
				if (p->pd_pid > tp->pl_pid) {
					/* Keep on looking. */
					savetp = tp;
					tp = tp->pl_next;
					continue;
				} else if (p->pd_pid < tp->pl_pid) {
					/* Not in list. */
					break;
				} else {
					/* Found it. */
					if (tp == Plhead)
						Plhead = tp->pl_next;
					else
						savetp->pl_next = tp->pl_next;
					tp->pl_next = Plfree;
					Plfree = tp;
					break;
				}
			}
			/* Back up to read next record. */
			break;
		default:
			console("Bad message on initpipe\n");
			break;
		}
	}
}


void
cleanaux()
{
	register struct pidlist *savep, *p;
	pid_t	pid;
	short	status;

	sigset(SIGCLD, SIG_DFL);
	Gchild = 0;	/* Note - Safe to do this here since no SIGCLDs */
	(void) sighold(SIGPOLL);
	savep = p = Plhead;
	while (p) {
		if (p->pl_dflag) {
			/*
			 * Found an entry to delete,
			 * remove it from list first.
			 */
			pid = p->pl_pid;
			status = p->pl_exit;
			if (p == Plhead) {
				Plhead = p->pl_next;
				p->pl_next = Plfree;
				Plfree = p;
				savep = p = Plhead;
			} else {
				savep->pl_next = p->pl_next;
				p->pl_next = Plfree;
				Plfree = p;
				p = savep->pl_next;
			}
			clearent(pid, status);
			continue;
		}
		savep = p;
		p = p->pl_next;
	}
	(void) sigrelse(SIGPOLL);
	sigset(SIGCLD, childeath);
}


void
clearent(pid, status)
pid_t   pid;
short   status;
{
	struct utmpx *up;

	/*
	 * No error checking for now.
	 */

	setutxent();
	while (up = getutxent()) {
		if (up->ut_pid == pid) {
			if (up->ut_type == DEAD_PROCESS) {
				/*
				 * Cleaned up elsewhere.
				 */
				continue;
			}

			notify_pam_dead(up);

			up->ut_type = DEAD_PROCESS;
			up->ut_exit.e_termination = WTERMSIG(status);
			up->ut_exit.e_exit = WEXITSTATUS(status);
			(void) time(&up->ut_tv.tv_sec);

			(void) pututxline(up);
			/*
			 * Now attempt to add to the end of the
			 * wtmp and wtmpx files.  Do not create
			 * if they don't already exist.
			 */
			updwtmpx(WTMPX_FILE, up);

			break;
		}
	}

	endutxent();
}
