4035 lines
96 KiB
C
Executable File
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 = ≺
|
|
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);
|
|
}
|