Files
Arquivotheca.Solaris-2.5/cmd/init/init.c
seta75D 7c4988eac0 Init
2021-10-11 19:38:01 -03:00

4035 lines
96 KiB
C
Executable File

/* 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. */
#ident "@(#)init.c 1.33 95/04/29 SMI" /* SVr4.0 1.32.3.27 */
/*******************************************************************
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.
(c) 1986,1987,1988,1989 Sun Microsystems, Inc
(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()
* level()
* killproc()
* initialize()
* init_signals()
* siglvl()
* alarmclk()
* childeath_single()
* childeath()
* powerfail()
* getlvl()
* switchcon()
* efork()
* waitproc()
* account()
* prog_name()
* opensyscon()
* realcon()
* get_ioctl_syscon()
* reset_syscon()
* write_ioctl_syscon()
* reset_modes()
* console()
* error_time()
* timer()
* setimer()
* zero()
* userinit()
* bcopy()
* 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 utmp, 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 <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <utmpx.h>
#include <errno.h>
#include <termio.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 <security/ia_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
/*
* Correspondence of signals to init actions.
*/
#define LVLQ SIGHUP
#define LVL0 SIGINT
#define LVL1 SIGQUIT
#define LVL2 SIGILL
#define LVL3 SIGTRAP
#define LVL4 SIGIOT
#define LVL5 SIGEMT
#define LVL6 SIGFPE
#define SINGLE_USER SIGBUS
#define LVLa SIGSEGV
#define LVLb SIGSYS
#define LVLc SIGPIPE
/*
* Bit Mask for each level. Used to determine legal levels.
*/
#define MASK0 01
#define MASK1 02
#define MASK2 04
#define MASK3 010
#define MASK4 020
#define MASK5 040
#define MASK6 0100
#define MASKSU 0200
#define MASKa 0400
#define MASKb 01000
#define MASKc 02000
#ifndef NPROC
#define NPROC 500
#endif
/*
* 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 */
};
struct PROC_TABLE proc_table[NPROC]; /* Table of active processes */
struct PROC_TABLE dummy; /* A zero table used when calling account() */
/* for non-process type accounting. */
/*
* 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 *UTMP = "utmp";
char *WTMP = "wtmp";
char *UTMPX = "utmpx";
char *WTMPX = "wtmpx";
char *INITTAB = "inittab";
char *SYSTTY = "/dev/systtyx";
char *SYSCON = "/dev/sysconx";
char *CORE_RECORD = "core_record";
char *DBG_FILE = "debug";
char *IOCTLSYSCON = "ioctl.syscon"; /* Last syscon modes */
char *ENVFILE = "default/init"; /* Default env. */
#else
char *UTMP = UTMP_FILE; /* Snapshot record file */
char *WTMP = WTMP_FILE; /* Long term record file */
char *UTMPX = UTMPX_FILE; /* Snapshot record file */
char *WTMPX = WTMPX_FILE; /* Long term record file */
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. */
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 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 level();
char *prog_name();
int error_time(), getcmd(), getlvl(), initialize(), mask(), realcon();
int spawn();
int opensyscon(), get_ioctl_syscon();
void account(), alarmclk(), bcopy(), childeath(), cleanaux(), clearent();
void console(), init_signals(), killproc();
void powerfail(), remv(), reset_syscon(), write_ioctl_syscon(), respawn();
void reset_modes(), setimer(), siglvl(), sigpoll(), single();
void switchcon(), timer(), userinit(), zero();
long waitproc();
FILE *fdup();
extern char *sys_errlist[];
struct PROC_TABLE *efork(), *findpslot();
#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, *fp2;
extern char *optarg;
extern int optind, opterr;
int c;
/* 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
/*
* 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.
*/
defaultlevel = initialize();
/*
* 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 "utmp" and "utmpx" files. Set the umask so
* that the utmp and utmpx files are created 644.
* If "utmp" can't be opened we default to single user mode.
*/
umask(022);
if ((fp = fopen(UTMP, "w+")) == NULL ||
(fp2 = fopen(UTMPX, "w+")) == NULL) {
console("Cannot create %s or %s\n", UTMP, UTMPX);
if (bflg == 0) {
cur_state = SINGLE_USER;
defaultlevel = -1;
}
} else {
utmpflag = 1;
fclose(fp);
fclose(fp2);
}
umask(0); /* Allow later files to be created normally. */
/*
* If there is no default level supplied, ask the user to supply one.
*/
if (defaultlevel == 0) {
new_state = getlvl();
} else if (defaultlevel == -1) {
new_state = SINGLE_USER;
defaultlevel = 0;
} else {
new_state = defaultlevel;
}
if (new_state == SINGLE_USER) {
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.
*/
umask(022);
if ((fp = fopen(UTMP, "w+")) == NULL ||
(fp2 = fopen(UTMPX, "w+")) == NULL) {
console("Cannot create %s or %s\n",UTMP,UTMPX);
new_state = cur_state = SINGLE_USER;
umask(0);
single(0);
} else {
utmpflag = 1;
fclose(fp);
fclose(fp2);
umask(0);
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", level(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",
level(new_state), level(cur_state),
level(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 ****/
/**********************/
void
single(defaultlevel)
int defaultlevel;
{
register struct PROC_TABLE *su_process;
register 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 utmp 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 utmp 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) {
#if u3b2 || u3b15
/*
* Did we waken because of a single-user 3B2/3B5
* powerfail? If so, spawn powerfail entries.
*/
if (wakeup.w_flags.w_powerhit) {
(void) spawn();
op_modes = NORMAL_MODES;
}
#endif
/*
* Did we waken because a change of levels?
* If so, kill the child and then exit.
*/
if (wakeup.w_flags.w_usersignal) {
if (new_state >= LVL0 && new_state <= LVL6) {
/*
* 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 ****/
/**********************/
/*
* 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()
{
register 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[0]; process < &proc_table[NPROC]; 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[0]; process < &proc_table[NPROC];
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 & 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[0]; process < &proc_table[NPROC];
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[0];
process < &proc_table[NPROC]; 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[0]; process < &proc_table[NPROC]; process++) {
if ((process->p_flags & (LIVING|NAMED|TOUCHED|KILLED|OCCUPIED))
== (LIVING|NAMED|OCCUPIED)) {
killproc(process->p_pid);
process->p_flags |= KILLED;
} else if ((process->p_flags & (LIVING|NAMED|OCCUPIED)) ==
(NAMED|OCCUPIED)) {
account(DEAD_PROCESS, process, NULL);
/*
* If this named 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 ****/
/*********************/
/*
* spawn() scans inittab for entries which should be run at this mode.
* Processes which should be running but are not, are started.
*/
int
spawn()
{
register 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 = mask(cur_state);
#ifdef DEBUG1
debug("spawn\tSignal-new: %c cur: %c prev: %c\n", level(new_state),
level(cur_state),level(prev_state));
debug("spawn- lvl_mask: %o op_modes: %o\n",lvl_mask,op_modes);
#endif
/*
* Scan through all the entries in 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 & (MASKa|MASKb|MASKc)) &&
(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 ****/
/***********************/
/*
* respawn() spawns a shell, inserts the information about the process
* process into the proc_table, and does the startup accounting.
*/
void
respawn(process, cmd)
register struct PROC_TABLE *process;
register struct CMD_LINE *cmd;
{
register int i;
int modes, maxfiles;
long 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.\nid:%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 utmp. 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(4,0);
for (i = 0; i < maxfiles; i++ )
fcntl(i, F_SETFD, 1);
/*
* Now exec a shell with the -c option and the command
* from 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);
return;
}
/************************/
/**** findpslot ****/
/************************/
/*
* findpslot() finds the old slot in the process table for the
* command with the same id, or it finds an empty slot.
*/
struct PROC_TABLE *
findpslot(cmd)
register struct CMD_LINE *cmd;
{
register struct PROC_TABLE *process;
register struct PROC_TABLE *empty = NULLPROC;
for (process = &proc_table[0]; process < &proc_table[NPROC]; process++){
if (process->p_flags & OCCUPIED &&
id_eq(process->p_id, cmd->c_id))
break;
/*
* If the entry is totally empty and "empty" is still 0,remember
* where this hole is and make sure the slot is zeroed out.
*/
if (empty == NULLPROC && (process->p_flags & OCCUPIED) == 0) {
empty = process;
process->p_id[0] = '\0';
process->p_id[1] = '\0';
process->p_id[2] = '\0';
process->p_id[3] = '\0';
process->p_pid = 0;
process->p_time = 0L;
process->p_count = 0;
process->p_flags = 0;
process->p_exit = 0;
}
}
/*
* If there is no entry for this slot, then there should be an
* empty slot. If there is no empty slot, then we've run out
* of proc_table space. If the latter is true, empty will be
* NULL and the caller will have to complain.
*/
if (process == &proc_table[NPROC])
process = empty;
return(process);
}
/**********************/
/**** getcmd ****/
/**********************/
/*
* getcmd() parses lines from 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,
};
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.
*/
zero((char *)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++) {
if (*ptr1 >= '0' && *ptr1 <= '6') {
cmd->c_levels |=
(MASK0 << (*ptr1 - '0'));
} else if(*ptr1 >= 'a' && *ptr1 <= 'c'){
cmd->c_levels |=
(MASKa << (*ptr1 - 'a'));
} else if(*ptr1 == 's' || *ptr1 == 'S'){
cmd->c_levels |= MASKSU;
} else {
state = FAILURE;
break;
}
}
if (state != FAILURE) {
state = ACTION;
ptr = shcmd; /* Reset the buffer */
}
break;
case ACTION :
/*
* Null terminate the string in shcmd buffer and
* then try to match against legal actions. If
* the field is of length 0, then the default of
* "RESPAWN" is used if the id is numeric,
* otherwise the default is "OFF".
*/
if (ptr == shcmd) {
if (isdigit(cmd->c_id[0])
&& (cmd->c_id[1] == '\0' ||
isdigit(cmd->c_id[1]))
&& (cmd->c_id[2] == '\0' ||
isdigit(cmd->c_id[2]))
&& (cmd->c_id[3] == '\0' ||
isdigit(cmd->c_id[3])) )
cmd->c_action = M_RESPAWN;
else
cmd->c_action = M_OFF;
} else {
for (cmd->c_action=0, i= 0,*ptr = '\0';
i < sizeof(actions)/sizeof(char *);
i++) {
if (strcmp(shcmd,actions[i]) == 0) {
if ((cmd->c_levels & MASKSU) &&
!(act_masks[i] &
(M_INITDEFAULT|M_PF|M_PWAIT)))
cmd->c_action = 0;
else
cmd->c_action= act_masks[i];
break;
}
}
}
/*
* 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 =
MASK0|MASK1|MASK2| MASK3|MASK4|MASK5|MASK6;
/*
* If no action has been supplied, declare this
* entry to be OFF.
*/
if (cmd->c_action == 0)
cmd->c_action = M_OFF;
/*
* If no shell command has been supplied, make sure
* there is a null string in the command field.
*/
if (ptr == shcmd + EXEC)
*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 ****/
/********************/
int
mask(lvl)
int lvl;
{
register int answer;
switch (lvl) {
case LVLQ:
answer = 0;
break;
case LVL0:
answer = MASK0;
break;
case LVL1:
answer = MASK1;
break;
case LVL2:
answer = MASK2;
break;
case LVL3:
answer = MASK3;
break;
case LVL4:
answer = MASK4;
break;
case LVL5:
answer = MASK5;
break;
case LVL6:
answer = MASK6;
break;
case SINGLE_USER:
answer = MASKSU;
break;
case LVLa:
answer = MASKa;
break;
case LVLb:
answer = MASKb;
break;
case LVLc:
answer = MASKc;
break;
default:
answer = FAILURE;
break;
}
return(answer);
}
/*********************/
/**** level ****/
/*********************/
char
level(state)
int state;
{
register char answer;
switch(state) {
case LVL0:
answer = '0';
break;
case LVL1:
answer = '1';
break;
case LVL2:
answer = '2';
break;
case LVL3:
answer = '3';
break;
case LVL4:
answer = '4';
break;
case LVL5:
answer = '5';
break;
case LVL6:
answer = '6';
break;
case SINGLE_USER:
answer = 'S';
break;
case LVLa:
answer = 'a';
break;
case LVLb:
answer = 'b';
break;
case LVLc:
answer = 'c';
break;
default:
answer = '?';
break;
}
return(answer);
}
/************************/
/**** killproc ****/
/************************/
/*
* killproc() creates a child which kills the process specified by pid.
*/
void
killproc(pid)
register pid_t pid;
{
register struct PROC_TABLE *process;
sigset(SIGCLD, SIG_DFL);
while ((process = efork(M_OFF, NULLPROC, 0)) == NO_ROOM)
pause();
sigset(SIGCLD, childeath);
if (process == NULLPROC) {
/*
* 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()
{
register int msk, i;
register struct PROC_TABLE *process, *oprocess;
static int states[] = {LVL0,LVL1,LVL2,LVL3,LVL4,LVL5,LVL6,SINGLE_USER};
struct CMD_LINE cmd;
char line[MAXCMDL];
FILE *fp;
int initstate, inquotes, length, maxfiles, wslength;
char *tokp, *cp1, *cp2;
struct statvfs statvfs_buf;
struct stat stat_buf;
int write_ioctl;
/*
* 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.
*/
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);
}
/*
* 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();
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--;
}
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.
*/
for (msk = MASKSU, i = sizeof(states)/sizeof(int) - 1;
msk > 0; msk >>= 1 , i--) {
if (msk & cmd.c_levels) {
initstate = states[i];
}
}
} 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;);
sigset(SIGCLD, childeath);
if (process == NULLPROC) {
maxfiles = ulimit(4, 0);
for (i = 0; i < maxfiles; i++)
fcntl(i, F_SETFD, 1);
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) {
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)
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);
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;
{
register 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[0]; process < &proc_table[NPROC]; 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 ****/
/*************************/
/*
* 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()
{
register struct PROC_TABLE *process;
register struct pidlist *pp;
register 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[0]; process < &proc_table[NPROC]; process++){
if ((process->p_flags & OCCUPIED) == OCCUPIED &&
process->p_pid == pid) {
/*
* Mark this process as having died and store the exit
* status. Also set the wakeup flag for a dead child
* and break out of the loop.
*/
process->p_flags &= ~LIVING;
process->p_exit = (short)status;
wakeup.w_flags.w_childdeath = 1;
#ifdef UDEBUG
if (process == &proc_table[NPROC])
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();
}
/*************************/
/**** powerfail ****/
/*************************/
void
powerfail()
{
nice(-19);
wakeup.w_flags.w_powerhit = 1;
}
/**********************/
/**** getlvl ****/
/**********************/
/*
* Get the new run level from /dev/syscon. If someone at /dev/systty
* types a <del> while we are waiting for the user to start typing,
* relink /dev/syscon to /dev/systty.
*/
int
getlvl()
{
char c;
int status, flag;
FILE *fp_tmp, *fp_systty;
register int process;
static char levels[] = {
LVL0,LVL1,LVL2,LVL3,LVL4,LVL5,LVL6,SINGLE_USER
};
/*
* 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);
fp_systty = fdopen(fd_systty, "r+");
fclose(fp_tmp);
/*
* Prepare to catch the interupt signal if <del> typed
* at /dev/systty.
*/
sigset(SIGINT, switchcon);
sigset(SIGQUIT, switchcon);
}
#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);
flag = TRUE;
while(flag) {
/*
* Now read in the user response.
*/
fprintf(stdout,
"ENTER RUN LEVEL (0-6,s or S): ");
fflush(stdout);
/*
* Get a character from the user which isn't a
* space, tab or a <cr>.
*/
while (fscanf(stdin, "%c", &c) != 1 ||
c == '\n' || c == '\t' || c == ' ')
;
c &= 0x7f;
/*
* If the character is a digit between 0 and 6
* or the letter S, exit with the level equal to
* the new desired state.
*/
if (c >= '0' && c <= '6') {
fprintf(stdout,
"will change to state %c\n", c);
exit(levels[c - '0']);
} else if (c == 'S' || c == 's') {
fprintf(stdout,
"will change to state %c\n", c);
exit(levels['7' - '0']);
} else if (c > '6' && c <= '9') {
fprintf(stdout,"\nUsage: 0123456sS\n");
fprintf(stdout,
" %c is not a valid state\n\n",c);
while ((fscanf(stdin, "%c", &c) != 1) ||
(c != '\n'))
;
} else {
fprintf(stdout,
"\nbad character <%3.3o>\n\n",c);
while ((fscanf(stdin,"%c",&c) != 1) ||
(c != '\n'))
;
}
}
}
}
/*
* Wait for the child to die and return it's status.
*/
while (wait(&status) != process)
;
#ifdef DEBUG
debug("getlvl: status: %o exit: %o termination: %o\n",
status, (status & 0xff00)>>8, (status & 0xff));
#endif
return((status & 0xff00) >> 8);
}
/*************************/
/**** switchcon ****/
/*************************/
/* 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 ****/
/*********************/
/*
* 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;
register struct PROC_TABLE *process;
int modes;
{
register pid_t childpid;
register struct PROC_TABLE *proc;
int i;
void (*oldroutine)();
#ifdef UDEBUG
static void (*oldsigs[NPROC])();
#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[0]; proc < &proc_table[NPROC]; 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[0]; process->p_flags != 0 &&
process < &proc_table[NPROC]; process++)
;
if (process == &proc_table[NPROC]) {
if (error_time(FULLTABLE))
console("Internal process table is full\n");
return(NO_ROOM);
}
process->p_time = 0L;
process->p_count = 0;
}
process->p_id[0] = '\0';
process->p_id[1] = '\0';
process->p_id[2] = '\0';
process->p_id[3] = '\0';
process->p_pid = childpid;
process->p_flags = (LIVING | OCCUPIED | modes);
process->p_exit = 0;
} else {
/*
* 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 ****/
/************************/
/*
* 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);
}
/***********************/
/**** account ****/
/***********************/
/*
* account() updates entries in utmp and utmpx and appends new entries
* new entries to the end of wtmp and wtmpx (assuming they exist).
*/
void
account(state, process, program)
short state;
register struct PROC_TABLE *process;
char *program; /* Name of program if INIT_PROCESS, otherwise NULL */
{
struct utmpx utmpbuf;
register struct utmpx *u, *oldu;
int tmplen;
void *iah;
struct ia_status out;
#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 = process->p_exit & 0xff;
u->ut_exit.e_exit = process->p_exit >> 8 & 0xff;
u->ut_type = state;
time(&u->ut_tv.tv_sec);
/*
* See if there already is such an entry in the "utmp" file.
*/
setutxent(); /* Start at beginning of utmp file. */
if ((oldu = getutxid(u)) != NULL) {
/*
* Copy in the old "user", "line" and "host" fields
* to our new structure.
*/
bcopy(&oldu->ut_user[0], &u->ut_user[0], sizeof (u->ut_user));
bcopy(&oldu->ut_line[0], &u->ut_line[0], sizeof (u->ut_line));
bcopy(&oldu->ut_host[0], &u->ut_host[0], sizeof (u->ut_host));
u->ut_syslen = (tmplen = strlen(u->ut_host)) ?
min(tmplen + 1, sizeof (u->ut_host)) : 0;
/*
* We have left the processing of utmp/wtmp as part of init
* for the time being. The flag IS_NOOP is used
* ia_close_session to short circuit the utmp/wtmp processing
* there.
*/
if (oldu->ut_user &&
(oldu->ut_type == USER_PROCESS) &&
(state == DEAD_PROCESS) &&
(ia_start("init", oldu->ut_user, oldu->ut_line,
oldu->ut_host, NULL, &iah) == IA_SUCCESS)) {
(void) ia_close_session(iah, IS_NOOP, process->p_pid,
0, NULL, &out);
(void) ia_end(iah);
}
#ifdef ACCTDEBUG
debug("New entry in utmp file.\n");
#endif
}
#ifdef ACCTDEBUG
else debug("Replacing old entry in utmp file.\n");
#endif
/*
* Perform special accounting. Insert the special string into the
* ut_line array. For INIT_PROCESSes put in the name of the
* program in the "ut_user" field.
*/
switch (state) {
case RUN_LVL:
u->ut_exit.e_termination = level(cur_state);
if (program != NULL && oldu != NULL && *program == 'S')
u->ut_exit.e_exit = oldu->ut_exit.e_termination;
else
u->ut_exit.e_exit = level(prior_state);
u->ut_pid = n_prev[cur_state];
sprintf(&u->ut_line[0], RUNLVL_MSG, level(cur_state));
break;
case BOOT_TIME:
sprintf(&u->ut_line[0], "%.12s", BOOT_MSG);
break;
case INIT_PROCESS:
strncpy(&u->ut_user[0], program, sizeof(u->ut_user));
break;
default:
break;
}
/*
* Write out the updated entry to utmp file.
*/
if (pututxline(u) == (struct utmpx *)NULL && bflg == 0)
console("failed write of utmpx entry:\"%2.2s\"\n",&u->ut_id[0]);
endutxent();
/*
* Now attempt to add to the end of the wtmp and wtmpx files.
*/
updwtmpx(WTMPX, u);
}
/*************************/
/**** prog_name ****/
/*************************/
/*
* prog_name() searches for the word or unix path name and
* returns a pointer to the last element of the pathname.
*/
char *
prog_name(string)
register char *string;
{
register char *ptr, *ptr2;
struct utmp *dummy; /* Used only to get size of ut_user */
static char word[sizeof(dummy->ut_user) + 1];
/*
* Search for the first word skipping leading spaces and tabs.
*/
while (*string == ' ' || *string == '\t')
string++;
/*
* If the first non-space non-tab character is not one allowed in
* a word, return a pointer to a null string, otherwise parse the
* pathname.
*/
if (*string != '.' && *string != '/' && *string != '_' &&
(*string < 'a' || *string > 'z') &&
(*string < 'A' || * string > 'Z') &&
(*string < '0' || *string > '9'))
return("");
/*
* Parse the pathname looking forward for '/', ' ', '\t', '\n' or
* '\0'. Each time a '/' is found, move "ptr" to one past the
* '/', thus when a ' ', '\t', '\n', or '\0' is found, "ptr" will
* point to the last element of the pathname.
*/
for (ptr = string;
*string !=' ' && *string !='\t' && *string !='\n' && *string !='\0';
string++) {
if (*string == '/')
ptr = string+1;
}
/*
* Copy out up to the size of the "ut_user" array into "word",
* null terminate it and return a pointer to it.
*/
for (ptr2 = &word[0];
ptr2 < &word[sizeof(dummy->ut_user)] && ptr < string;)
*ptr2++ = *ptr++;
*ptr2 = '\0';
return(&word[0]);
}
/**************************/
/**** opensyscon ****/
/**************************/
/*
* 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, sys_errlist[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 ****/
/********************************/
/*
* 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 ****/
/********************************/
/*
* 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) iflags;
termios.c_oflag = termio.c_oflag = (ushort) oflags;
termios.c_cflag = termio.c_cflag = (ushort) cflags;
termios.c_lflag = termio.c_lflag = (ushort) 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 ****/
/****************************/
/*
* 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);
umask(~0644);
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);
umask(0);
}
/***************************/
/**** reset_modes ****/
/***************************/
/*
* 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()
{
register FILE *fp;
register struct PROC_TABLE *process;
struct termio tio;
struct termios tios;
ulong 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, sys_errlist[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) iflags;
termios.c_oflag = termio.c_oflag = (ushort) oflags;
termios.c_cflag = termio.c_cflag = (ushort) cflags;
termios.c_lflag = termio.c_lflag = (ushort) 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;
}
umask(~0644);
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);
}
termios.c_cflag &= ~HUPCL;
exit(0);
} else {
/*
* The parent waits for the child to die.
*/
while (waitproc(process) == FAILURE)
;
}
}
/***********************/
/**** console ****/
/***********************/
/*
* console() forks a child if it finds that it is the main "init" and outputs
* the requested message to the system console. Note that the number of
* arguments passed to console() is determined by the print format.
*/
/* 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 ****/
/**************************/
/*
* 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;
{
long 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 ****/
/*********************/
/*
* timer() is a substitute for sleep() which uses alarm() and pause().
*/
void
timer(waitime)
register int waitime;
{
setimer(waitime);
while (time_up == FALSE)
pause();
}
/***********************/
/**** setimer ****/
/***********************/
void
setimer(timelimit)
int timelimit;
{
alarmclk();
alarm(timelimit);
time_up = (timelimit ? FALSE : TRUE);
}
/********************/
/**** zero ****/
/********************/
void
zero(adr, size)
register char *adr;
register int size;
{
while (size--)
*adr++ = '\0';
}
/************************/
/**** userinit ****/
/************************/
/*
* 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 saverr, init_signal;
struct stat sconbuf, conbuf;
/*
* We are a user invoked init. Is there an argument and is it
* a single character? If not, print usage message and quit.
*/
if (argc != 2 || argv[1][1] != '\0') {
fprintf(stderr,"Usage: init [0123456SsQqabc]\n");
exit(0);
}
switch (argv[1][0]) {
case 'Q':
case 'q':
init_signal = LVLQ;
break;
case '0':
init_signal = LVL0;
break;
case '1':
init_signal = LVL1;
break;
case '2':
init_signal = LVL2;
break;
case '3':
init_signal = LVL3;
break;
case '4':
init_signal = LVL4;
break;
case '5':
init_signal = LVL5;
break;
case '6':
init_signal = LVL6;
break;
case 'S':
case 's':
/*
* 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) {
fprintf(stderr, "Standard input not a tty line\n");
exit(1);
}
if (stat(ln, &sconbuf) != -1 &&
stat(SYSCON, &conbuf) != -1 &&
sconbuf.st_rdev != conbuf.st_rdev &&
sconbuf.st_ino != conbuf.st_ino) {
/*
* Unlink /dev/syscon and relink it to the current line.
*/
if (unlink(SYSCON) == FAILURE) {
perror("Can't unlink /dev/syscon");
exit(1);
}
if (link(ln, SYSCON) == FAILURE) {
saverr = errno;
fprintf(stderr,
"Can't link /dev/syscon to %s", ln);
errno = saverr;
perror(" ");
link(SYSTTY,SYSCON); /* Try to leave a syscon */
exit(1);
}
/*
* 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);
}
}
init_signal = SINGLE_USER;
break;
case 'a':
init_signal = LVLa;
break;
case 'b':
init_signal = LVLb;
break;
case 'c':
init_signal = LVLc;
break;
default:
fprintf(stderr, "Usage: init [0123456SsQqabc]\n");
exit(1);
}
/*
* Now send signal to main init and then exit.
*/
if (kill(SPECIALPID, init_signal) == FAILURE) {
fprintf(stderr, "Must be super-user\n");
exit(1);
} else {
exit(0);
}
}
/*********************/
/**** bcopy ****/
/*********************/
void
bcopy(from, to, size)
register char *from, *to;
register int size;
{
while (size--)
*to++ = *from++;
}
/********************/
/**** fdup ****/
/********************/
FILE *
fdup(fp)
register FILE *fp;
{
register int newfd;
register char *mode;
/*
* Dup the file descriptor for the specified stream and then convert
* it to a stream pointer with the modes of the original stream pointer.
*/
if ((newfd = dup(fileno(fp))) != FAILURE) {
/*
* Determine the proper mode. If the old file was _IORW, then
* use the "r+" option, if _IOREAD, the "r" option, or if _IOWRT
* the "w" option. Note that since none of these force an lseek
* by fdopen(), the duped file pointer will be at the same spot
* as the original.
*/
if (fp->_flag & _IORW) {
mode = "r+";
} else if (fp->_flag & _IOREAD) {
mode = "r";
} else if (fp->_flag & _IOWRT) {
mode = "w";
} 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
/*************************/
/**** drop_core ****/
/*************************/
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
/*********************/
/**** debug ****/
/*********************/
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);
}
/*****************/
/**** C ****/
/*****************/
char *
C(id)
register char *id;
{
static char answer[12];
register char *ptr;
register int i;
for (i = 4, ptr = &answer[0]; --i >= 0; id++) {
if (isprint(*id) == 0 ) {
*ptr++ = '^';
*ptr++ = *id + 0100;
} else {
*ptr++ = *id;
}
}
*ptr++ = '\0';
return(&answer[0]);
}
#endif
/************************/
/******* sigpoll ******/
/************************/
#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;
}
}
}
/************************/
/******* cleanaux *****/
/************************/
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);
}
/************************/
/******* clearent *****/
/************************/
void
clearent(pid, status)
pid_t pid;
short status;
{
void *iah;
struct ia_status out;
if ( ia_start ("init","root", NULL,NULL,NULL,&iah) != IA_SUCCESS)
return;
/*
* No error checking for now.
*/
(void) ia_close_session(iah, 0, pid, status, NULL, &out);
ia_end(iah);
}