/* Copyright (c) 1990, 1991 UNIX System Laboratories, Inc. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T */ /* All Rights Reserved */ /* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF */ /* UNIX System Laboratories, Inc. */ /* The copyright notice above does not evidence any */ /* actual or intended publication of such source code. */ /******************************************************************* 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. * * General Note:The has been implemented to run with or without the Trusted * Path driver configured on the system. * * 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. */ #ident "$Revision: 1.73 $" #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define fioctl(p, sptr, cmd) ioctl(fileno(p), sptr, cmd) #define TRUE 1 #define FALSE 0 #define FAILURE -1 #define SUCCESS 0 #define REBOOT 5 #define ERRBUFSZ (256) #define COPYBUF(srcbuf, dstbuf, dstbufsz) {\ size_t srcbufsz; \ size_t dstremainbufsz; \ size_t catsz; \ \ if ((srcbuf != (char *)NULL) && (dstbuf != (char *)NULL)){ \ srcbufsz = strlen(srcbuf); \ dstremainbufsz = strlen(dstbuf) + 1; \ if (dstremainbufsz < dstbufsz){ \ dstremainbufsz = dstbufsz - dstremainbufsz; \ strncpy(dstbuf, srcbuf, ((srcbufsz <= dstremainbufsz) ? srcbufsz : dstremainbufsz)); \ } \ } \ } /* * 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 #define NPROC_DEFAULT 200 /* * Legal action field values. */ #define OFF 0 /* Kill process if on, else ignore */ #define RESPAWN 1 /* Continuously restart process when it dies */ #define ONDEMAND RESPAWN /* Respawn for a, b, c type processes */ #define ONCE 2 /* Start process, do not respawn when dead */ #define WAIT 3 /* Perform once and wait to complete */ #define BOOT 4 /* Start at boot time only */ #define BOOTWAIT 5 /* Start at boot time and wait to complete */ #define POWERFAIL 6 /* Start on powerfail */ #define POWERWAIT 7 /* Start and wait for complete on powerfail */ #define INITDEFAULT 8 /* Default level "init" should start at */ #define SYSINIT 9 /* Actions performed before init speaks */ #define M_OFF 0001 #define M_RESPAWN 0002 #define M_ONDEMAND M_RESPAWN #define M_ONCE 0004 #define M_WAIT 0010 #define M_BOOT 0020 #define M_BOOTWAIT 0040 #define M_PF 0100 #define M_PWAIT 0200 #define M_INITDEFAULT 0400 #define M_SYSINIT 01000 #define ID 1 #define LEVELS 2 #define ACTION 3 #define COMMAND 4 /* * Init can be in any of three main states, "normal" mode where it is * processing entries for the lines file in a normal fashion, "boot" mode, * where it is only interested in the boot actions, and "powerfail" mode, * where it is only interested in powerfail related actions. The following * masks declare the legal actions for each mode. */ #define NORMAL_MODES (M_OFF | M_RESPAWN | M_ONCE | M_WAIT) #define BOOT_MODES (M_BOOT | M_BOOTWAIT) #define PF_MODES (M_PF | M_PWAIT) struct PROC_TABLE { char p_id[4]; /* Four letter unique id of process */ pid_t p_pid; /* Process id */ short p_count; /* How many respawns of this command in */ /* the current series */ long p_time; /* Start time for a series of respawns */ short p_flags; short p_exit; /* Exit status of a process which died */ }; int NPROC = NPROC_DEFAULT; /* size of active process table */ struct PROC_TABLE *proc_table; /* Table of active processes */ struct PROC_TABLE dummy; /* A zero table used when calling account() */ /* for non-process type accounting. */ /* * Flags for the "p_flags" word of a PROC_TABLE entry: * * OCCUPIED This slot in init's proc table is in use. * * LIVING Process is alive. * * NOCLEANUP efork() is not allowed to cleanup this entry even * if process is dead. * * NAMED This process has a name, i.e. came from inittab. * * DEMANDREQUEST Process started by a "telinit [abc]" command. Processes * formed this way are respawnable and immune to level * changes as long as their entry exists in inittab. * * TOUCHED Flag used by remv() to determine whether it has looked * at an entry while checking for processes to be killed. * * WARNED Flag used by remv() to mark processes that have been * sent the SIGTERM signal. If they don't die in 5 * seconds, they are sent the SIGKILL signal. * * KILLED Flag used by remv() to mark procs that have been sent * the SIGTERM and SIGKILL signals. */ #define OCCUPIED 01 #define LIVING 02 #define NOCLEANUP 04 #define NAMED 010 #define DEMANDREQUEST 020 #define TOUCHED 040 #define WARNED 0100 #define KILLED 0200 /* * Respawn limits for processes that are to be respawned: * * SPAWN_INTERVAL The number of seconds over which "init" will try to * respawn a process SPAWN_LIMIT times before it gets mad. * * SPAWN_LIMIT The number of respawns "init" will attempt in * SPAWN_INTERVAL seconds before it generates an * error message and inhibits further tries for * INHIBIT seconds. * * INHIBIT The number of seconds "init" ignores an entry it had * trouble spawning unless a "telinit Q" is received. */ #define SPAWN_INTERVAL (2*60) #define SPAWN_LIMIT 10 #define INHIBIT (5*60) #define NULLPROC ((struct PROC_TABLE *)(0)) #define NO_ROOM ((struct PROC_TABLE *)(FAILURE)) struct CMD_LINE { char c_id[4]; /* Four letter unique id of process to be */ /* affected by action */ short c_levels; /* Mask of legal levels for process */ short c_action; /* Mask for type of action required */ char *c_command; /* Pointer to init command */ }; /* * Following are symbols for the types of errors for which "error_time" keeps * timing entries. MAX_E_TYPES is the number of types currently being kept. */ #define FULLTABLE 0 #define BADLINE 1 #define MAX_E_TYPES 2 static struct ERRORTIMES { long e_time; /* Time of last message */ long e_max; /* Amount of time to wait until next message */ } err_times[MAX_E_TYPES] = {0L, 120L, 0L, 120L}; struct pidrec { int pd_type; /* Command type */ pid_t pd_pid; /* pid to add or remove */ }; /* * pd_type's */ #define ADDPID 1 #define REMPID 2 struct pidlist { pid_t pl_pid; /* pid to watch for */ int pl_dflag; /* Flag indicating SIGCLD from this pid */ short pl_exit; /* Exit status of proc */ struct pidlist *pl_next; /* Next in list */ } *Plhead, *Plfree; /* * The following structures contain a set of modes for consdevdsf. */ #define control(x) (x&037) struct termio dflt_termio = { BRKINT|IGNPAR|ISTRIP|IXON|IXANY|ICRNL, OPOST|ONLCR|TAB3, CLOCAL|CS8|CREAD, ISIG|ICANON|ECHO|ECHOK|ECHOE|ECHOKE|ECHOCTL #ifdef IEXTEN |IEXTEN #endif ,SSPEED,0,LDISC1, #if defined(_STYPES_LATER) {CINTR,CQUIT,CERASE,CKILL,CEOF,0,0,CNSWTCH, 0,0,0, 0,0,0,0, CLNEXT,CWERASE,CRPRNT,CFLUSH, CSTOP, CSTART}, #else /* !_STYPES_LATER */ {CINTR, /* 0 */ CQUIT, CERASE, CKILL, CEOF, 0, /* 5 */ 0, CNSWTCH, CSTART, CSTOP, CNSWTCH, /* 10 */ 0, CRPRNT, CFLUSH, CWERASE, CLNEXT, /* 15 */ 0, 0, 0, 0, 0, /* 20 */ 0, 0}, /* 22 */ #endif /* !_STYPES_LATER */ }; struct termio termio, curterm; 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; /* * flag definitions for getttyname() */ #define GETTTYNM_ONLYTP (0x01) #define REAL_CONSDEVMAJNUM (58) #define REAL_CONSDEVMINNUM (0) /* * Level definitions if MAC is not installed. */ #define SYS_PRIVATE_LVL ((level_t)2) #define SYS_PUBLIC_LVL ((level_t)1) /* * Useful file and device names. */ char *CONSOLE = "/dev/console"; /* Real system console */ char *INITPIPE = "/etc/.initpipe"; /* * When init s|S is entered, need to leave a "cookie" for the real init * (pid == 1) to set up a Trusted Path. "/dev/sysconreal" will be linked * to the real/physical tty device linked under a TP device. */ char *SYSCONREAL = "/dev/sysconreal"; #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 = "/etc/TIMEZONE"; #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/TIMEZONE"; #ifdef DEBUGGER char *DBG_FILE = "/etc/debug"; #endif #endif /* list of super-user programs to try for single user. * Try for sulogin first, but if it fails because it isn't there, * try the other standard shells as well. /sbin/sulogin must * be first in the list. */ char *singleuser[] = { "/sbin/sulogin", _PATH_CSHELL, _PATH_BSHELL, "/bin/ksh", 0 }; char *SH = _PATH_BSHELL; /* Standard Shell */ /* * Index tags for init's required Device Special Files defined in devtab */ #define DTTAG_SYSTTY (0) #define DTTAG_CONSOLE (1) #define DTTAG_SYSCON (2) #define DTTAG_SYSCONREAL (3) /* * Required Device Special Files for init * NOTE: Do not change order of entries with making cooresponding changes * to DTTAG_**** defines */ enum devtabstatus { dtsUNVERIFIED, /* initial status of devtab entry. */ dtsVERIFIED, /* state of devtab entry if verified to exist. */ dtsIGNORE, /* indicates to ingore devtab entry */ dtsENOENT, /* state of devtab entry if DSF does not exist. */ dtsWRONGDEV /* state of devtab entry if DSF Major and or Minor * device number does not match major and or minor * device number in devtab entry. */ }; enum devtabaction{ dtaNOACTION, /* indicates no action is to be performed on/for * devtab entry. */ dtaMKNOD, /* indicates that a DSF needs to be created for the * devtab entry. associated with dtsENOENT and * dtsWRONGDEV statuses. */ dtaLINK /* indicates that DSF needs to be linked to DSF * specified in dt_linkdsfname field in devtab entry. * associated with dtsENOENT and dtsWRONGDEV statuses. */ }; struct devicetable { char *dt_dsfname; char *dt_linkdsfname;/* if action is dtaLINK, * indicates the dsf name * dt_dfsname will link to. */ major_t dt_major; minor_t dt_minor; enum devtabstatus dt_status; enum devtabaction dt_action; short dt_tag; /* tag to identify devtab entry. * also indicates index of * devtab entry. */ }; struct devicetable devtab[] = { (char *)NULL, (char *)NULL, REAL_CONSDEVMAJNUM, REAL_CONSDEVMINNUM, dtsUNVERIFIED, dtaNOACTION, DTTAG_SYSTTY, (char *)NULL, (char *)NULL, (major_t)NODEV, (minor_t)NODEV, dtsUNVERIFIED, dtaNOACTION, DTTAG_CONSOLE, (char *)NULL, (char *)NULL, (major_t)NODEV, (minor_t)NODEV, dtsUNVERIFIED, dtaNOACTION, DTTAG_SYSCON, (char *)NULL, (char *)NULL, (major_t)NODEV, (minor_t)NODEV, dtsUNVERIFIED, dtaNOACTION, DTTAG_SYSCONREAL, "", (char *)NULL, (major_t)NODEV, (minor_t)NODEV, dtsUNVERIFIED, dtaNOACTION, -1 }; int n_prev[NSIG-1]; /* 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 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; #ifdef DEBUG char comment[120]; #endif /* * Array for default global environment. */ #define MAXENVENT 6 /* Max number of default env variables + 1 */ char *glob_envp[MAXENVENT]; /* Array of environment strings */ static char *consdevdsf = (char *)NULL; /* indicates where console message * output is to be displayed. */ static char *substitutedsf = (char *)NULL; /* indicates whether or not the device * name on /etc/inittab command line * sysinit entries should be * substituted. NULL means no. */ /* * Indicates whether or not the root file system has been verified to be * sane. */ static int rootfs_verified = FALSE; FILE *fdup(FILE *); char *prog_name(char *); char level(int); int error_time(int); int getcmd(struct CMD_LINE *); int getlvl(void); int initialize(void); int main(int, char **); int mask(int); int realcon(void); int setup_consdev(char *); int spawn(void); int substitutedev(struct CMD_LINE *, char *, char *); int update_devtab(void); int v_pgm(char *); long waitproc(struct PROC_TABLE *); struct PROC_TABLE *efork(int, struct PROC_TABLE *, int); struct PROC_TABLE *findpslot(struct CMD_LINE *); void account(short, struct PROC_TABLE *, char *); void alarmclk(void); void childeath(int); void cleanaux(void); void clearent(pid_t, short); void console(char *, ...); void firmware(void); void get_inittab(void); void get_ioctl_syscon(void); void handle_children(int); void init_signals(void); void initialize_devtab(void); void killproc(pid_t); void opensyscon(void); void powerfail(void); void remv(void); void reset_modes(void); void reset_syscon(void); void respawn(struct PROC_TABLE *, struct CMD_LINE *); void setimer(int); void sig_poll(int); void siglvl(int); void single(int); void switchcon(int); void timer(int); void userinit(int, char **); void verify_devtab(void); static void wr_ioctlsyscon(FILE *); static int rd_ioctlsyscon(FILE *); static int validsyscon(char *); int cap_enabled; cap_t cap; static int validsyscon(char*); static void wr_ioctlsyscon(FILE*); static int rd_ioctlsyscon(FILE*); #ifdef UDEBUG void drop_core(char *); #endif #ifdef DEBUGGER char *C(char *); void debug(char *, ...); #endif extern int doavailmon(char state); extern int argvtostr(char **); /* no prototype in headers.... */ int cap_enabled; cap_t cap; /* * Procedure: main * * Restrictions: unlink(2): None mknod(2): None open(2): None ioctl(2): None fopen: None fclose: None fcntl(2): None */ main(int argc, char **argv) { int defaultlevel; int utmpflag = 0; int chg_lvl_flag; FILE *fp, *fp2; mode_t old_umask; extern int errno; #ifdef DEBUG debug("%d:main():ENTER\n",getpid()); #endif if ((cap_enabled = sysconf(_SC_CAP)) > CAP_SYS_DISABLED) cap = cap_init(); #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(); chg_lvl_flag = FALSE; #ifdef DEBUG console("Debug version of init starting-pid = %ld\n", SPECIALPID); #endif /* * Set up pipe for "godchildren". */ (void)unlink(INITPIPE); (void)mknod(INITPIPE, S_IFIFO | 0600, 0); Pfd = open(INITPIPE, O_RDWR | O_NDELAY); if (Pfd >= 0) { (void)ioctl(Pfd, I_SETSIG, S_INPUT); /* * Read pipe in message discard mode. */ (void)ioctl(Pfd, I_SRDOPT, RMSGD); sigset(SIGPOLL, sig_poll); } /* * 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. */ old_umask = umask(022); if ((fp = fopen(UTMP,"w+")) == NULL || (fp2 = fopen(UTMPX, "w+")) == NULL) { console("Cannot create %s or %s\n", UTMP, UTMPX); cur_state = SINGLE_USER; defaultlevel = -1; } else { utmpflag = 1; fclose(fp); fclose(fp2); } umask(old_umask); /* put back original umask */ /* * 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); 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. */ old_umask = 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; /* put back original umask */ umask(old_umask); single(0); } else { utmpflag = 1; fclose(fp); fclose(fp2); /* put back original umask */ umask(old_umask); 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 (;;) { /* * 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)); } /* * If new level is 0, close the utmp file, otherwise * it's still open when the system halts and blocks * get lost owing to the fact that efs preallocates! */ if (cur_state == LVL0) endutent(); /* * 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; /* * "init" is finished with all actions for * the current wakeup. * The timer is here mainly for * paranoic defensiveness to force init to * wake up periodically - just in case. 5 * minutes is a long enough interval for the * cost to be very low. */ sighold(SIGCLD); if (wakeup.w_mask == 0) { setimer(SLEEPTIME); sigpause(SIGCLD); setimer(0); } else sigrelse(SIGCLD); if(wakeup.w_flags.w_childdeath) handle_children(0); if (wakeup.w_flags.w_usersignal) { wakeup.w_flags.w_usersignal = 0; /* * 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) { wakeup.w_flags.w_powerhit = 0; op_modes = PF_MODES; } } } } /* * Procedure: single * * Restrictions: kill(2): None open(2): None fcntl(2): None auditdmp(2): None access(2): None */ void single(int defaultlevel) { register struct PROC_TABLE *su_process; register struct pidlist *p; int state; int remainsingle = FALSE; int consfd = -1; char **theshell; char *arg; #ifdef DEBUG debug("%d:single():ENTER\n",getpid()); #endif sighold(SIGPOLL); /* * 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. */ if (Plhead) { int alive = 0; int t, s; for (s = SIGTERM, t = TWARN; t; t -= 1) { for (p = Plhead; p; p = p->pl_next) if (!kill(p->pl_pid, s)) alive = 1; if (alive) { s = 0; timer(1); } else break; } if(wakeup.w_flags.w_childdeath) handle_children(0); cleanaux(); /* Clean up utmp entries. */ /* * Any left? */ if (Plhead) { alive = 0; for (p = Plhead; p; p = p->pl_next) if (!kill(p->pl_pid, SIGKILL)) alive = 1; if (alive) timer(3);/* Give them a little time to die. */ } if(wakeup.w_flags.w_childdeath) handle_children(0); cleanaux(); /* Clean up utmp entries. */ } sigrelse(SIGPOLL); for (;;) { struct passwd *rootpass; struct stat shst; /* To handle the case where /bin/su can be * exec'ed, but fails because /etc/passwd is missing, or * has no valid entry for root, we have to try * to handle this by checking with getpwnam on "root", and * explictly testing for its shell. If that fails, we skip * the su attempt, and just try the shells. This somewhat * gross hack is needed because looking at the exit status * of 'su' doesn't work, because the exit status will usually * be the exit value of the last command run within the single * user shell (when su works). Try each time we start over * in case someone has fixed it up after getting a shell. */ if (rootpass = getpwnam("root")) { if (!rootpass->pw_shell[0]) /* do as su does */ rootpass->pw_shell = _PATH_BSHELL; /* fail if not there, or not executable */ if (stat(rootpass->pw_shell, &shst) || !(shst.st_mode & 01111)) rootpass = NULL; } if (!rootpass) { theshell = &singleuser[1]; arg = NULL; console("Warning: /etc/passwd damaged, or root shell bad\n"); } else { theshell = singleuser; arg = "-"; /* only for su, which must be first */ } console("SINGLE USER MODE\n"); while((su_process = efork(M_OFF,NULLPROC,NOCLEANUP)) == NO_ROOM) pause(); if (su_process == NULLPROC) { #ifdef DEBUG debug("%d:single():CHILD PROCESS consdevdsf = %s\n",getpid(), consdevdsf); #endif opensyscon(); sigset(SIGCLD, SIG_DFL); /* paranoia */ arg = "-"; if (cap_enabled && cap_set_proc(cap) < 0) { console("Can't clear capability set\n"); exit(1); } while(*theshell) { execlp(*theshell, *theshell, arg, 0); console("Can't start %s: %s\n",*theshell,strerror(errno)); arg = NULL; theshell++; } timer(5); (void)close(consfd); exit(1); } /* * If we are the parent, wait around for the child to die * or for "init" to be signaled to change levels. */ while (waitproc(su_process) == FAILURE) { /* * Did we waken because a change of levels? * If so, kill the child and then exit. */ if (wakeup.w_flags.w_usersignal) { wakeup.w_flags.w_usersignal = 0; #ifdef DEBUG debug("%d:single():waitproc() returned (CASE: NOT BY MY CHILD):A CHILD INIT SENT A SIGNAL\n",getpid()); #endif 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); } 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; su_process->p_flags&=~NOCLEANUP; #ifdef DEBUG debug("%d:single():waitproc() returned (CASE: NOT BY MY CHILD):RETURN \n",getpid()); #endif return; } } /* even in single user mode, reap orphans, etc. */ if(wakeup.w_flags.w_childdeath) handle_children(0); } #ifdef DEBUG debug("%d:single():waitproc() returned (CASE: MY CHILD )\n",getpid()); #endif if (remainsingle == TRUE){ state = SINGLE_USER; }else if (defaultlevel != 0){ 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) { #ifdef DEBUG debug("%d:single():state != SINGLE_USER\n",getpid()); #endif /* * Check to make sure UTMPX is accessible. * If not, remain in single user mode. */ if (access(UTMPX, F_OK) == -1) { #ifdef DEBUG debug("%d:single():ACCESS %s FAILED REMAIN SINGLE_USER\n",getpid(), UTMPX); #endif 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; #ifdef DEBUG debug("%d:single():RETURN\tconsdevdsf = %s\n", getpid(), consdevdsf); #endif return; } } } } /* * Procedure: remv * * Restrictions: kill(2): None */ /* * 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(void) { register struct PROC_TABLE *process; struct CMD_LINE cmd; 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) == 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. */ handle_children(1); 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)) sigpause(SIGCLD); if (time_up == TRUE) break; } if(wakeup.w_flags.w_childdeath) handle_children(0); /* * 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; } } if(wakeup.w_flags.w_childdeath) handle_children(0); } /* * Procedure: spawn * * Restrictions: open(2): None fcntl(2): None */ /* * spawn() scans inittab for entries which should be run at this mode. * Processes which should be running but are not, are started. */ int spawn(void) { register struct PROC_TABLE *pp; struct CMD_LINE cmd; char substituteline[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)) == 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; /* * If consdevdsf != CONSOLE, substitute all occurrences of * CONSOLE in the command line with consdevdsf. This will * redirect input and output to floating console device even * though /etc/inittab entries indicate CONSOLE as the device * to redirect input and output. */ if (strcmp(CONSOLE, consdevdsf) != 0){ if (substitutedev(&cmd, substituteline, consdevdsf) == FAILURE){ console("Substituting device %s failed for command\n\"%s\"\ncommand line length exceeds %d\n", substitutedsf, cmd.c_command, MAXCMDL); 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); } /* * Procedure: respawn * * Restrictions: fcntl(2): None */ /* * respawn() spawns a shell, inserts the information about the process * process into the proc_table, and does the startup accounting. */ void respawn(struct PROC_TABLE *process, 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. */ 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. * HOWEVER: don't do this if new level is 0: * we want utmp closed! */ if (cur_state != LVL0) { 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 = getdtablehi(); /* was ulimit(0,4)!!! */ for (i = 0; i < maxfiles; i++ ) fcntl(i, F_SETFD, FD_CLOEXEC); /* * Now exec a shell with the -c option and the command * from inittab. */ if (cap_enabled && cap_set_proc(cap) < 0) exit(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); /* * 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]; } } /************************/ /**** 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(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); } struct CMD_LINE *thetable; int thetablesize = -1; /* number of valid entries */ #define TABLE_INCR 100 /* number of entries to grow by */ /* * get_inittab() reads and parses the whole inittab file, if * it hasn't changed since last read. * * This costs some some small amount of memory, but saves us from * re-reading the inittab every time an inittab-started process * exits (or with my changes in signal handling for irix 6.4, every * time an orphaned process exits). Small memory systems will almost * never have a significant inittab. As shipped in 6.4, it's about 60 * entries, with about 2000 bytes of command strings, so less than a * 4KB page of memory. * Olson, 3/97 */ void get_inittab(void) { static time_t lastmodtime; static ino_t lastino; static int reportit = 1, lastsz; FILE *fp_inittab = NULL; struct stat itst; char lbuf[MAXCMDL]; char *ptr; struct CMD_LINE *cmd; int c, lastc, state; char *ptr1; int i, proceed, line; 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(thetablesize > 0) { /* we already have incore table; see if we should (and can) re-read the inittab file */ if(stat(INITTAB, &itst)) { if(reportit) console("%s can't be read, using old one, errno=%d\n", INITTAB, errno); reportit = 0; return; } else if(itst.st_size < 10) { if(reportit) console("%s truncated or corrupted, using old data\n", INITTAB); reportit = 0; return; } else if(itst.st_mtime == lastmodtime && itst.st_ino == lastino) { /* we don't check st_dev because we assume it's always going to * be on the root device; system doesn't work too well otherwise... */ return; /* no need to re-read */ } if(!(fp_inittab = fopen(INITTAB, "r"))) { if(reportit) console("%s can't be opened (errno=%d), using old data\n", INITTAB, errno); reportit = 0; return; } } else while(!fp_inittab) { /* try forever to open it first time, since otherwise there isn't * anything useful system can do... Basicly same sequence as in * checking sequence, but different error messages. We also report * each time, so user knows system isn't dead. The retries are only * going to help if this is a media or NFS error, but might as well... */ int tintvl = 5; if(stat(INITTAB, &itst) == 0 && itst.st_size < 10) console("%s truncated or corrupted, only %d bytes\n", INITTAB, itst.st_size); if(!(fp_inittab = fopen(INITTAB, "r"))) { console("%s can't be opened (errno=%d), retrying\n", INITTAB, errno); tintvl <<= 1; if(tintvl > 120) tintvl = 120; /* max out at 2 minutes */ timer(tintvl); continue; } /* start out by allocating room for TABLE_INCR entries, since that's * all that most people will need. If we need more, we allocate * more in chunks of TABLE_INCR; done first so we can bail with some useful * message; since this is the first process run, and it's during * initialization, this should never fail! */ if(!(thetable = calloc(sizeof(*thetable), TABLE_INCR))) { if(reportit) console("Cannot allocate memory for inittab table, errno=%d\n", errno); return; } lastsz = TABLE_INCR; } reportit = 1; /* report errors again */ thetablesize = 0; /* * Keep getting commands from inittab until you find a * good one or run out of file. */ for(line=1;;line++) { if(thetablesize >= lastsz) { /* because realloc can trash the original data, allocate * more memory and copy, even though that could waste some * time/memory */ void *ntbl = calloc(sizeof(*thetable), lastsz+TABLE_INCR); if(ntbl) { bcopy(thetable, ntbl, sizeof(*thetable) * lastsz); free(thetable); thetable = ntbl; lastsz += TABLE_INCR; } else { console("Can't grow inittab table, entries past %d are ignored\n", thetablesize); break; } } cmd = &thetable[thetablesize]; memset((void*)cmd, 0, sizeof(*cmd)); /* * 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) { break; } /* * Ignore blank or comment lines ('#' as first character) */ for (;;) { if ('\n' == c) { c = fgetc(fp_inittab); line++; continue; } if ('#' != c) break; do { c = fgetc(fp_inittab); } while (c != '\n' && c != EOF); } for (proceed = TRUE, ptr = lbuf, 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 - lbuf) < 1 || i > 4 ) { state = FAILURE; } else { bcopy(lbuf, &cmd->c_id[0], i); ptr = lbuf; 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 = lbuf; 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 = lbuf; /* Reset the buffer */ } break; case ACTION : /* * Null terminate the string in 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 == lbuf) { 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(lbuf,actions[i]) == 0) { /* * run process in /etc/inittab in * single user for all actions */ 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(lbuf,"exec "); } ptr = lbuf + 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 >= lbuf + 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) { if(cmd->c_command) free(cmd->c_command); if(!(cmd->c_command = strdup(lbuf))) { console("No memory for %s line %d, command=%s; skipped\n", INITTAB, line, lbuf); continue; } /* * 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 == lbuf + EXEC) *lbuf = '\0'; thetablesize++; /* number of entries, not index of last */ } /* else ignore the line, something about it is bad. */ } fclose(fp_inittab); /* no benefit in having it left open with table * kept in memory */ lastmodtime = itst.st_mtime; lastino = itst.st_ino; } /* * getcmd() returns lines from inittab. * It used to read the file every time, and reparse. That's clearly * a performance issue, so now we only re-read it (via get_inittab()) * if the modtime is different from the last time we did it. * * 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. * The assumption is that we are called to get the whole file on each use, * so when we get to the end and return FLASE, we reset our pointer back to * the start for the next time around. * Because callers can change part of the command line structure (in * particular, the string "CONSOLE" to the "real" console device), we * return a copy of the structure, with the command string a static * buffer... */ int getcmd(struct CMD_LINE *cmd) { static int cmdnum = 0; /* which one we are returning. If 0, we call get_inittab() to read the inittab file if necessary */ static char cmdstr[MAXCMDL]; if(!cmdnum) get_inittab(); if(cmdnum >= thetablesize) { /* reached the end, or failed to get table */ cmdnum = 0; /* for next time */ return FALSE; } *cmd = thetable[cmdnum]; cmd->c_command = cmdstr; strcpy(cmdstr, thetable[cmdnum].c_command); cmdnum++; return TRUE; } /********************/ /**** mask ****/ /********************/ int mask(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(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); } /* * Procedure: killproc * * Restrictions: kill(2): None * Notes: creates a child which kills the process specified by pid. */ void killproc(pid_t pid) { register struct PROC_TABLE *process; while ((process = efork(M_OFF, NULLPROC, 0)) == NO_ROOM) pause(); 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); } } /* * Procedure: initialize * * Restrictions: fopen: None fclose: None fcntl(2): None open(2): None */ static char rootpath[] = _PATH_ROOTPATH; /* * Perform the initial state setup and look for an * initdefault entry in the "inittab" file. */ int initialize(void) { 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]; char substituteline[MAXCMDL]; char lerrbuf[ERRBUFSZ]; FILE *fp; int initstate, inquotes, length, maxfiles; char *cp1; char initbuf[10]; static char *init_tbl[] = { "0", "1", "2", "3", "4", "5", "6", "s", 0, }; char **init_tbl_ptr; int gotlevel = 0; off_t v_addr; struct var v; int kmemfd; #ifdef DEBUG debug("%d:initialize():ENTER\n",getpid()); #endif /* * Initialize the proc table. */ v_addr = (off_t)sysmp(MP_KERNADDR, MPKA_VAR); if (v_addr != (off_t)-1) { kmemfd = open("/dev/kmem", O_RDONLY); if (kmemfd >= 0) { if (lseek(kmemfd, v_addr, SEEK_SET) == v_addr && read(kmemfd, (void *)&v, sizeof(v)) == sizeof(v)) NPROC = v.v_proc; close(kmemfd); } } proc_table = (struct PROC_TABLE *)calloc(NPROC, sizeof(*proc_table)); /* * -initialize entries in devtab * -verify the existence of the DSF listed in devtab. If any do not * exist, they will be made after the root file system has been * verified to be sane. * -setup the device to direct console message output. * If the CONSOLE DSF is not the DSF to direct console message * output, substitutedsf has been set to consdevdsf in setup_consdev. * This will cause all occurences of /dev/console in /etc/inittab * for the SYSINIT entries to be substituted with substitutedsf * before the SYSINIT command is executed. * If consdevdsf is NULL go into firmware mode since most * of the SYSINIT entries in the inittab will fail without a device * to read from and write to. * * NOTE: Since there is no device to direct console message output to, * we can not display an error message, that could assist the * administrator, before going into firmware mode. * Need to find a way to get a message out. (Perhaps modifying * uadmin(2) to accept a sting to be displayed.) */ initialize_devtab(); verify_devtab(); if (setup_consdev(lerrbuf) == FAILURE){ console("%s", lerrbuf); } if (consdevdsf == (char *)NULL) firmware(); /* * Initialize state to "SINGLE_USER" "BOOT_MODES". */ if(cur_state >= 0) { n_prev[cur_state]++; prior_state = cur_state; } cur_state = SINGLE_USER; op_modes = BOOT_MODES; /* * Set up all signals to be caught or ignored as appropriate. */ init_signals(); initstate = 0; #ifdef UDEBUG reset_modes(); #endif /* * Set up the default environment for all procs to be forked from init. * Read the values from the /etc/TIMEZONE file, except for PATH. If * there's not enough room in the environment array, the environment * lines that don't fit are silently discarded. */ glob_envp[0] = malloc(sizeof("PATH=") + sizeof(rootpath) - 1); strcpy( glob_envp[0], "PATH="); strcat( glob_envp[0], rootpath); if( (fp = fopen(ENVFILE, "r")) == NULL ) { console("Cannot open %s. Environment not initialized.\n", ENVFILE); } else { i = 1; while (fgets(line, MAXCMDL - 1, fp) != NULL && i < MAXENVENT - 1) { char *cp, *eq, *ep, *tp, quotechar; /* Make sure there's a newline at the end. */ length = strlen(line); if (line[length - 1] != '\n') { /* Too long, truncate it */ line[length - 1] = '\n'; } /* * First pass. Turn separators into newlines, * remove balanced quotes, and turn = into tabs. * Also handle \ and comments. */ cp = line; inquotes = 0; eq = NULL; while (cp < &line[length]) { if (inquotes) { if (*cp != quotechar) { cp++; continue; } inquotes = 0; for (cp1 = cp; cp1 < &line[length]; cp1++) *cp1 = cp1[1]; length--; } else if (*cp == '#') { *cp++ = '\n'; *cp = '\0'; length = cp - line; } else if (*cp == ';' || *cp == ' ' || *cp == '\t') { *cp++ = '\n'; eq = NULL; } else if (eq == NULL && *cp == '=') *(eq = cp++) = '\t'; else if (*cp == '\'' || *cp == '"') { quotechar = *cp; inquotes = 1; for (cp1 = cp; cp1 < &line[length]; cp1++) *cp1 = cp1[1]; length--; } else if (*cp == '\\') { for (cp1 = cp; cp1 < &line[length]; cp1++) *cp1 = cp1[1]; length--; cp++; } else cp++; } /* * Second pass. Use newlines as separators, after * turning tabs back into = put them in the environment. */ cp = line; while (*cp) { if (*cp == '\n') { do cp++; while (*cp == '\n'); } if (!(ep = strchr(cp, '\n'))) break; if (!(tp = strchr(cp, '\t')) || tp > ep) { cp = ep + 1; continue; } *tp = '='; *ep = '\0'; glob_envp[i] = malloc((unsigned)(ep - cp) + 1); strcpy(glob_envp[i], cp); cp = ep + 1; if (++i >= MAXENVENT - 1) break; } } /* * Append a null pointer to the environment array * to mark its end. */ glob_envp[i] = NULL; fclose(fp); } if (sgikopt("initstate", initbuf, sizeof(initbuf)) >= 0) { for (init_tbl_ptr = init_tbl; *init_tbl_ptr; init_tbl_ptr++) { if (strcmp(*init_tbl_ptr, initbuf) == 0) { initstate = states[init_tbl_ptr - init_tbl]; gotlevel = 1; break; } } } /* * Scan the "inittab" file and process "initdefault" * and "sysinit" entries. */ while (getcmd(&cmd) == TRUE) { if ((cmd.c_action == M_INITDEFAULT) && (!gotlevel)) { /* * 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)) { for (oprocess = process; (process = efork(M_OFF, oprocess, (NAMED|NOCLEANUP))) == NO_ROOM;); if (process == NULLPROC) { maxfiles = getdtablehi(); /* was ulimit(0,4)!!! */ for (i = 0; i < maxfiles; i++ ) fcntl(i, F_SETFD, FD_CLOEXEC); /* * If substitutedsf is defined, the DSF * CONSOLE either does not exist or has * an incorrect major and/or minor * device number. substitutedev() will * substitute all occurrences of the * DSF name CONSOLE in the command line * with the DSF name defined in * substitutedsf. */ if (substitutedsf != (char *)NULL){ if (substitutedev(&cmd, substituteline, substitutedsf) == FAILURE){ console("Substituting device %s failed for command\n\"%s\"\ncommand line length exceeds %d\n", substitutedsf, cmd.c_command, MAXCMDL); exit(1); } } if (cap_enabled && cap_set_proc(cap)< 0) exit(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; } } } /* One of the SYSINIT entries execute /sbin/bcheckrc which fsck the * root file system. Can assume the root file system is sane at * this point and can modify entries in the root file system. * Update any entries in devtab with action dtaMKNOD or dtaLINK. * Setup the console device (consdevdsf and TP) again. If any of the * devices in devtab were created, consdevdsf and TP (if TP is * configured) may need to be updated or allocated also. */ rootfs_verified = TRUE; (void)update_devtab(); if (setup_consdev(lerrbuf) == FAILURE){ console("%s", lerrbuf); } /* * Get the ioctl settings for /dev/syscon so that it can be * brought up in the state it was in when the system went down. */ get_ioctl_syscon(); #ifdef DEBUG debug("%d:initialize():RETURN\tinitstate = %d\n",getpid(),initstate); #endif 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(void) { int i; for(i=1; ip_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(void) { time_up = TRUE; } /* Used to a lot in the signal handler, which is bogus. * Now all we do is set a flag and return. * handler. Under heavy system stress, inst ould die, and it was * partly a side effect of this. * Was using signal; I've changed to sigset to * make it slightly safer. Also, we never set SIGCLD back to * default anywhere, so we don't lose any children. use hold/relse * instead. wakeup.w_flags.w_childdeath can only be set or cleared * while SIGCLD is blocked. It can be tested when it's not blocked, * because handle_children is a nop if there weren't really any childred. */ /* ARGSUSED */ void childeath(int signo) { wakeup.w_flags.w_childdeath = 1; } /* * use waitpid here, and if no children to wait for without * blocking, return immediately; keep looping while any children to * wait for. This all used to be part of childeath. * we hold sigcld for the duration. */ void handle_children(int leaveheld) { register struct PROC_TABLE *process; register struct pidlist *pp; register pid_t pid; int status, gchild = 0; sighold(SIGCLD); wakeup.w_flags.w_childdeath = 0; /* * get the process id of children that have exited, if any. * Don't block; if we get here with no children to wait for, * somebody sent us SIGCLD directly, we waited elsewhere, or * we have missed a timing race somewhere. * Scan the process table to see if we are interested in * this process. */ while((pid = waitpid(-1, &status, WNOHANG)) > 0) { #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; #ifdef UDEBUG if (process == &proc_table[NPROC]) debug("Didn't find process %ld.\n", pid); #endif break; } } if(process < &proc_table[NPROC]) continue; /* else 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; gchild = 1; break; } } sigrelse(SIGPOLL); } if(gchild) cleanaux(); if(!leaveheld) sigrelse(SIGCLD); } /*************************/ /**** powerfail ****/ /*************************/ void powerfail(void) { nice(-19); wakeup.w_flags.w_powerhit = 1; } /* * Procedure: getlvl * * Restrictions: fopen: None fcntl(2): None fclose: None fflush: None fscanf: None */ /* * Get the new run level from /dev/syscon. If someone at /dev/systty * types a while we are waiting for the user to start typing, * relink /dev/syscon to /dev/systty. */ int getlvl(void) { char c; int status, flag; FILE *fp_tmp; register int process; static char levels[] = { LVL0,LVL1,LVL2,LVL3,LVL4,LVL5,LVL6,SINGLE_USER }; #ifdef DEBUG debug("%d:getlvl():ENTER\n",getpid()); #endif /* * fork a child who will request the new run level from /dev/syscon. */ while ((process = fork()) == -1) ; if (process == 0) { sigset(SIGHUP, SIG_IGN); sigset(SIGCLD, SIG_DFL); /* * Open /dev/systty so that if someone types a , * we can be informed of the fact. */ if ((fp_tmp = fopen(SYSTTY,"r+")) != NULL) { /* * Make sure the file descriptor is greater than 2 so * that it won't interfere with the standard descriptors */ fd_systty = fcntl(fileno(fp_tmp), F_DUPFD, 3); (void)fdopen(fd_systty, "r+"); fclose(fp_tmp); /* * Prepare to catch the interupt signal if 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 consdevdsf. */ 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"); timer(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 . */ 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. */ (void)waitpid(process, &status, 0); #ifdef DEBUG debug("getlvl: status: %o exit: %o termination: %o\n", status, (status & 0xff00)>>8, (status & 0xff)); debug("%d:getlvl():RETURN\n",getpid()); #endif return((status & 0xff00) >> 8); } /*************************/ /**** switchcon ****/ /*************************/ /* ARGSUSED */ void switchcon(int sig) { /* * If this is the first time a has been typed on the * real/default system console device (the one associated with SYSTTY), * then reset console device to the device associated with SYSTTY. * Also re-establish file pointers. */ if (fd_systty != -1) { reset_syscon(); opensyscon(); /* * Set fd_systty to -1 so that we ignore any deletes from it in * the future as far as resetting console device to real/default * system console device (the one associated with 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(int action, struct PROC_TABLE *process, int modes) { register pid_t childpid; register struct PROC_TABLE *proc; int i; /* * 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); pause(); setimer(0); if(wakeup.w_flags.w_childdeath) handle_children(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 in child to the system defaults, * making sure that SIGXCPU and SIGXFSZ remain * ignored, for backward compatibility. */ for (i=1; i < NSIG; i++) if(i != SIGXCPU && i != SIGXFSZ) sigset(i, SIG_DFL); } 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(struct PROC_TABLE *process) { int answer; /* handle any exited children; then leave SIGCLD blocked */ handle_children(1); if (process->p_flags & LIVING) { sigpause(SIGCLD); if(wakeup.w_flags.w_childdeath) handle_children(0); if(process->p_flags & LIVING) return (FAILURE); /* some other signal or child */ } /* * 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 ****/ /***********************/ /* * Procedure: account * * Restrictions: getutxid: None pututxline: None updwtmpx: None */ /* * account() updates entries in utmp and utmpx and appends new entries * new entries to the end of wtmp and wtmpx (assuming they exist). * The program arg is the name of program if INIT_PROCESS, otherwise NULL. */ void account(short state, struct PROC_TABLE *process, char *program) { struct utmpx utmpbuf; register struct utmpx *u, *oldu; #ifdef ACCTDEBUG debug("** account ** state: %d id:%s\n", state, C(&process->p_id[0])); #endif /* * Audit dead process. */ if ((int)state == DEAD_PROCESS) { satvwrite(SAT_AE_IDENTITY, SAT_SUCCESS, "INIT|+|?|Logout pid %d id %c%c%c%c", process->p_pid, process->p_id[0], process->p_id[1], process->p_id[2], process->p_id[3]); } /* * Set up the prototype for the utmp structure we want to write. */ u = &utmpbuf; bzero(&u->ut_user[0], sizeof(u->ut_user)); bzero(&u->ut_line[0], sizeof(u->ut_line)); bzero(&u->ut_host[0], sizeof(u->ut_host)); /* * 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)); #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) && (cur_state != LVL0) && (cur_state != LVL6)) 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(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]); } /* * Procedure: opensyscon * * Restrictions: fclose: None fopen: None setbuf: None ioctl(2): None */ /* * opensyscon() opens stdin, stdout, and stderr, making sure * that their file descriptors are 0, 1, and 2, respectively. */ void opensyscon(void) { register FILE *fp; #ifdef DEBUG debug("%d:opensyscon():ENTER\n",getpid()); #endif fclose(stdin); fclose(stdout); fclose(stderr); close(0); close(1); close(2); #ifdef DEBUG debug("opensyscon():consdevdsf = %s\n",consdevdsf); #endif if ((fp = fopen(consdevdsf,"r+")) == NULL) { /* * If the open fails, switch back to real/default system * console device (associated with SYSTTY). */ reset_syscon(); fp = fopen(consdevdsf,"r+"); if(!fp) fp = stderr; } (void)fdup(fp); (void)fdup(fp); setbuf(fp, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); /* * Save the current consdevdsf 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. */ fioctl(fp, TCGETA, &curterm); /* * If the baud rate is B0, reset it to the default baud rate. */ if (curterm.c_ospeed == B0) curterm.c_ospeed = dflt_termio.c_ospeed; curterm.c_cflag &= ~HUPCL; /* Make sure hangup on close is off. */ if (realcon()) { /* * Don't overwrite cflag when init console is real console. */ termio.c_cflag = curterm.c_cflag; termio.c_ospeed = curterm.c_ospeed; } termio.c_cflag &= ~HUPCL; /* Make sure hangup on close is off. */ fioctl(fp, TCSETA, &termio); #ifdef DEBUG debug("%d:opensyscon():RETURN\n",getpid()); #endif return; } /* * Procedure: realcon * * Restrictions: stat(2): None open(2): None */ /* * realcon() returns a nonzero value if there is a character device * associated with consdevdsf that has the same device number as real, default, * console device. * * If TP is configure in the system and TP is on the console, have to call * tp_getinf to get the TP information structure which is associated with the * physical device linked under the TP console device. The stat(2) information * in the TP information is compared to the stat(2) of SYSTTY instead of CONSOLE * because CONSOLE is linked to TP_CONSDEV_DSF. */ int realcon(void) { struct stat consdevdsfbuf, consolebuf; int ret = 1; if ((stat(consdevdsf, &consdevdsfbuf) == -1) || (stat(SYSTTY, &consolebuf) == -1)){ ret = 0; } if ( !(consolebuf.st_mode & S_IFCHR) || !(consdevdsfbuf.st_mode & S_IFCHR) || (consolebuf.st_rdev != consdevdsfbuf.st_rdev)){ ret = 0; } return (ret); } /* * Procedure: get_ioctl_syscon * * Restrictions: fopen: None fscanf: None fclose: None */ /* * get_ioctl_syscon() retrieves the consdevdsf settings from the * IOCTLSYSCON file. */ void get_ioctl_syscon(void) { register FILE *fp; int valid_format = 0; /* * Read in the previous modes for SYSCON from IOCTLSYSCON. */ if ((fp = fopen(IOCTLSYSCON, "r")) == NULL) { console("warning:%s does not exist, default settings assumed\n", IOCTLSYSCON); reset_syscon(); } else { valid_format = rd_ioctlsyscon(fp); fclose(fp); /* * If the file is badly formatted, use the default settings. */ if (!valid_format) reset_syscon(); } } /* * Procedure: reset_syscon * * Restrictions: fopen: None lvlin: None lvlfile(2): None fclose: None */ /* * Switch the console back to the default system console device SYSTTY and * set the default ioctl settings back into IOCTLSYSCON and the incore arrays. * * If the root file system has not been verified, do not perform the switch * or set the ioctl settings. * NOTE: The code has been implemented such that this function should not be * called before the root file system has been verified. * * If TP is not configured on the system, SYSCON is relinked to SYSTTY. */ void reset_syscon(void) { register FILE *fp; mode_t old_umask; #ifdef DEBUG debug("%d:reset_syscon():ENTER\n",getpid()); #endif if (rootfs_verified != TRUE){ return; } if (devtab[DTTAG_SYSTTY].dt_status == dtsVERIFIED){ devtab[DTTAG_SYSCON].dt_major = devtab[DTTAG_SYSTTY].dt_major; devtab[DTTAG_SYSCON].dt_minor = devtab[DTTAG_SYSTTY].dt_minor; devtab[DTTAG_SYSCON].dt_linkdsfname = SYSTTY; devtab[DTTAG_SYSCON].dt_action = dtaLINK; devtab[DTTAG_CONSOLE].dt_action = dtaLINK; devtab[DTTAG_SYSTTY].dt_action = dtaMKNOD; } else { devtab[DTTAG_SYSCON].dt_major = devtab[DTTAG_CONSOLE].dt_major; devtab[DTTAG_SYSCON].dt_minor = devtab[DTTAG_CONSOLE].dt_minor; devtab[DTTAG_SYSCON].dt_linkdsfname = CONSOLE; devtab[DTTAG_SYSCON].dt_action = dtaLINK; devtab[DTTAG_CONSOLE].dt_action = dtaMKNOD; } (void)update_devtab(); consdevdsf = SYSCON; old_umask = umask(~0644); fp = fopen(IOCTLSYSCON, "w"); bcopy((char *)&dflt_termio, (char *)&termio, sizeof(struct termio)); wr_ioctlsyscon(fp); fclose(fp); /* put back original umask */ umask(old_umask); #ifdef DEBUG debug("%d:reset_syscon():RETURN\n",getpid()); #endif } /* * Procedure: reset_modes * * Restrictions: fopen: None ioctl(2): None fclose: None fscanf: None */ /* * reset_modes() makes sure the proper terminal modes are set so init can * continue talking to consdevdsf 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(void) { register FILE *fp; register struct PROC_TABLE *process; struct termio tio; ulong curcflag = 0; speed_t curospeed = B0; while ((process = efork(M_OFF, NULLPROC, NOCLEANUP)) == NO_ROOM) timer(2); if (process == NULLPROC) { if ((fp = fopen(consdevdsf,"w")) == NULL) { console("Unable to open %s\n", consdevdsf); } else { if (fioctl(fp, TCGETA, &tio) != FAILURE) { curcflag = tio.c_cflag; curospeed = tio.c_ospeed; /* * Clear HUPCL in the driver. */ tio.c_cflag &= ~HUPCL; fioctl(fp, TCSETA, &tio); } 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. */ (void)rd_ioctlsyscon(fp); 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. */ termio.c_cflag = curcflag; termio.c_ospeed = curospeed; } umask(~0644); if ((fp = fopen(IOCTLSYSCON, "w")) == NULL) { console("Can't open %s. errno: %d\n",IOCTLSYSCON,errno); } else { wr_ioctlsyscon(fp); fclose(fp); } termio.c_cflag &= ~HUPCL; exit(0); } else { /* * The parent waits for the child to die. */ while (waitproc(process) == FAILURE) ; } } /* * Procedure: console * * Restrictions: setbuf: None fflush: None ioctl(2): None */ /* * 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. * * NOTE: The following list of functions are directly or indirectly called * from this function. They should not call the function console() or any * other function that may directly or indirectly call console, since it may * cause an infinite call loop to console() if one of the code pathes from * console() is failing. * efork(), write_messge(), opensyscon(), reset_syscon(), realcon(), * realcon386(), tpswitchcons(), opentpdev(), update_devtab(), and * setup_consdev() */ /* PRINTFLIKE1 */ void console(char *format, ...) { register struct PROC_TABLE *process; char outbuf[BUFSIZ]; va_list args; #ifdef DEBUG debug("%d:console():ENTER\n",getpid()); #endif if (own_pid == SPECIALPID) { /* * We are the original "init" so we fork a child to do the * printing for us. */ while ((process = efork(M_OFF, NULLPROC, NOCLEANUP)) == NO_ROOM) timer(5); if (process == NULLPROC) { #ifdef UDEBUG sigset(SIGUSR1, abort); sigset(SIGUSR2, abort); #endif /* * Close the standard descriptors and open the console. */ opensyscon(); setbuf(stdout, &outbuf[0]); /* * Output the message to the console. */ fprintf(stdout, "\nINIT: "); va_start(args, format); vfprintf(stdout, format, args); va_end(args); fflush(stdout); /* * Restore the settings saved in opensyscon(). */ fioctl(stdout, TCSETAW, &curterm); exit(0); } else { while ( waitproc(process) == FAILURE) ; } } else { /* * We are some other "init" so print directly * to the standard output. */ opensyscon(); setbuf(stdout, &outbuf[0]); fprintf(stdout, "\nINIT: "); va_start(args, format); vfprintf(stdout, format, args); va_end(args); fflush(stdout); /* * Restore the settings saved in opensyscon(). */ fioctl(stdout, TCSETAW, &curterm); } #ifdef DEBUG debug("%d:console():RETURN\n",getpid()); #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(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(int waitime) { setimer(waitime); while (time_up == FALSE) pause(); } /***********************/ /**** setimer ****/ /***********************/ void setimer(int timelimit) { alarmclk(); alarm(timelimit); time_up = (timelimit ? FALSE : TRUE); } /* * Procedure: userinit * * Restrictions: open(2): None ttyname: None stat(2): None unlink(2): None link(2): None fopen: None fclose: None kill(2): None */ /* * Function to handle requests from users to main init running as process 1. */ void userinit(int argc, char **argv) { FILE *fp; char *userttydsf; int saverr; int init_signal; int fd; int statret = 0; struct stat consdevdsfbuf; struct stat userttydsfbuf; cap_t ocap; cap_value_t kill_caps[] = {CAP_KILL, CAP_MAC_WRITE}; #ifdef DEBUG debug("%d:userinit():ENTER\n",getpid()); #endif /* save command line arguments for audit record */ if (argvtostr(argv) == NULL) /* This should never happen! */ (void) fprintf(stderr,"failed argvtostr\n"); /* * We are a user invoked init. Is there an argument and is it * a single character? If not, print usage message and quit. */ if (argc != 2 || argv[1][1] != '\0') { fprintf(stderr,"Usage: init [0123456SsQqabc]\n"); exit(0); } doavailmon(argv[1][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': #ifdef DEBUG #endif /* * Initialize all global variables needed for init s. * * Initialize entries in devtab. * Verify existence of the DSFs listed in the devtab. */ initialize_devtab(); verify_devtab(); /* * Get the current console device, based on the devices that * have been verified in devtab. */ if ((fd = open(SYSCON, O_RDWR|O_NONBLOCK|O_NOCTTY)) == -1){ if (devtab[DTTAG_CONSOLE].dt_status == dtsVERIFIED){ consdevdsf = devtab[DTTAG_CONSOLE].dt_dsfname; }else{ /* * if SYSTTY does not exist, consdevdsf * is set to SYSTTY as a place holder. */ consdevdsf = devtab[DTTAG_SYSTTY].dt_dsfname; } }else{ consdevdsf = SYSCON; (void)close(fd); } /* * Make sure this process is talking to a legal tty line * and that consdevdsf is associated with this line. */ userttydsf = ttyname(0); if (userttydsf == NULL) { fprintf(stderr, "Standard input not a tty line\n"); exit(1); } if (stat(consdevdsf, &consdevdsfbuf) == -1){ statret = -1; } if ((statret != -1) && validsyscon(userttydsf) && (stat(userttydsf, &userttydsfbuf)) != -1 && (userttydsfbuf.st_rdev != consdevdsfbuf.st_rdev)){ /* * Unlink /dev/syscon and relink it to the * current line. */ if (unlink(SYSCON) == FAILURE) { perror("Can't unlink /dev/syscon"); exit(1); } if (link(userttydsf, SYSCON) == FAILURE && /* probably hwgraph link attempt so try a symlink instead */ (errno != EXDEV || symlink(userttydsf, SYSCON) == FAILURE)) { saverr = errno; fprintf(stderr, "Can't link /dev/syscon to %s", userttydsf); 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", userttydsf); 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. */ ocap = cap_acquire(2, kill_caps); if (kill(SPECIALPID, init_signal) == FAILURE) { cap_surrender(ocap); fprintf(stderr, "Must be privileged user\n"); #ifdef DEBUG debug("%d:userinit():RETURN\tkill FAILED\n",getpid()); #endif exit(1); } else { cap_surrender(ocap); #ifdef DEBUG debug("%d:userinit():RETURN\tkill SUCCEEDED\n",getpid()); #endif exit(0); } } /********************/ /**** fdup ****/ /********************/ FILE * fdup(FILE *fp) { register int newfd; register char *mode; if(!fp) return NULL; /* * 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(char *reason) { FILE *fp; if (efork(M_OFF, NULLPROC, 0) != NULLPROC) return; /* * 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(char *format, ...) { static FILE *fp; register int errnum; va_list args; #ifdef UDEBUG /* only open debug file once for cleaner debugging and tracing with par or strace */ if(!fp) #endif if ((fp = fopen(DBG_FILE, "a+")) == NULL) { errnum = errno; console("Can't open \"%s\". errno: %d\n", DBG_FILE, errnum); return; } va_start(args, format); vfprintf(fp, format, args); va_end(args); #ifndef UDEBUG fclose(fp); #endif } /*****************/ /**** C ****/ /*****************/ char * C(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 /* * Procedure: sig_poll * * Restrictions: read(2): None */ #define DELTA 25 /* Number of pidlist elements to allocate at a time */ /* ARGSUSED */ void sig_poll(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; } 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)) 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(void) { register struct pidlist *savep, *p; pid_t pid; short status; (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); } /* * Procedure: clearent * * Restrictions: getutxent: None pututxline: None updwtmpx: None */ void clearent(pid_t pid, short status) { register struct utmpx *up; setutxent(); while (up = getutxent()) { if (up->ut_pid == pid) { if (up->ut_type == DEAD_PROCESS) { /* * Cleaned up elsewhere. */ endutxent(); return; } up->ut_type = DEAD_PROCESS; up->ut_exit.e_termination = status & 0xff; up->ut_exit.e_exit = (status >> 8) & 0xff; time(&up->ut_tv.tv_sec); pututxline(up); /* * Now attempt to add to the end of the wtmp and wtmpx * files. Do not create if they don't already exist. */ updwtmpx(WTMPX, up); endutxent(); return; } } endutxent(); } /* * Procedure: v_pgm * * Restrictions: stat(2): none */ int v_pgm(char *pgmp) { struct stat statpgm; /* absolute pathnames required */ if (*pgmp != '/') return(1); /*Program must exist*/ /*If pathname is greater than 1024 characters, stat will return*/ /*ENAMETOOLONG. */ if (stat(pgmp, &statpgm)) return(1); else { /* pgm must be executable by owner */ if ((statpgm.st_mode & S_IFMT) == S_IFREG) { if (statpgm.st_mode & S_IXUSR) return(0); else return(1); }else return(1); } } /* * initialize_devtab() * * Perform any initialization needed for entries in devtab. * * If TP is not configured into system, set dt_major, dt_minor, * and dt_linkdsfname for CONSOLE, to REAL_CONSDEVMAJNUM, * REAL_CONSDEVMINNUM, and SYSTTY respectively. Set dt_status for * TP_CONSDEV_DSF, SYSCONREAL, TP_ADMDEV_DSF, TP_CTRLCLONE_DSF, * TP_DATACLONE_DSF, and SAD_ADMDEV_DSF to dtsIGNORE. * */ void initialize_devtab(void) { #ifdef DEBUG debug("%d:initialize_devtab():ENTER\n",getpid()); #endif devtab[DTTAG_SYSTTY].dt_dsfname = SYSTTY; devtab[DTTAG_CONSOLE].dt_dsfname = CONSOLE; devtab[DTTAG_SYSCON].dt_dsfname = SYSCON; devtab[DTTAG_SYSCONREAL].dt_dsfname = SYSCONREAL; devtab[DTTAG_SYSCONREAL].dt_linkdsfname = SYSTTY; devtab[DTTAG_CONSOLE].dt_major = REAL_CONSDEVMAJNUM; devtab[DTTAG_CONSOLE].dt_minor = REAL_CONSDEVMINNUM; devtab[DTTAG_CONSOLE].dt_linkdsfname = SYSTTY; devtab[DTTAG_SYSCON].dt_linkdsfname = SYSTTY; devtab[DTTAG_SYSCONREAL].dt_status = dtsIGNORE; #ifdef DEBUG debug("%d:initial_devtab():RETURN\n",getpid()); #endif } /* * Procedure: verify_devtab * * Restrictions: stat(2): None */ /* * verify_devtab() * * Verify the existence, and major and minor device numbers of all devices * in the device table devtab. * * If a device does not exist, set it status to dtsENOENT and action to * dtsMKNOD, if its dt_linkdsfname field is NULL, dtsLINK, if its * dt_linkdsfname field in not NULL. The DSF will be created or linked * after the root file system has been checked. (This will have occured * after all "sysinit" entries in /etc/inittab have run.) * * If the device's major and or minor device number stated in the devtab does * not match its cooresponding DSF entry in the file system, set it status to * dtsWRONGDEV. The action is set to dtsMKNOD or dtsLINK as described above. */ void verify_devtab(void) { struct devicetable *dtp; struct stat statbuf; #ifdef DEBUG debug("%d:verify_devtab():ENTER\n",getpid()); #endif for (dtp = devtab; dtp->dt_tag != -1; dtp++){ if (dtp->dt_status == dtsIGNORE){ continue; }else{ dtp->dt_status = dtsUNVERIFIED; } } for (dtp = devtab; dtp->dt_tag != -1; dtp++){ if ((dtp->dt_status == dtsVERIFIED) || (dtp->dt_status == dtsIGNORE)){ continue; } if (stat(dtp->dt_dsfname, &statbuf) == -1){ dtp->dt_status = dtsENOENT; } else if (dtp->dt_major != (major_t)(NODEV)){ if ((dtp->dt_major != major(statbuf.st_rdev)) || (dtp->dt_minor != minor(statbuf.st_rdev))){ dtp->dt_status = dtsWRONGDEV; }else{ dtp->dt_status = dtsVERIFIED; } }else{ dtp->dt_major = major(statbuf.st_rdev); dtp->dt_minor = minor(statbuf.st_rdev); dtp->dt_status = dtsVERIFIED; } if ((dtp->dt_status == dtsENOENT) || (dtp->dt_status == dtsWRONGDEV)){ /* * Special Case Handling: */ switch (dtp->dt_tag){ /* * If entry is missing, have SYSCONREAL_DSF Major * and Minor device number set to default system * console major and minor device number. * If entry indicates the wrong device, assume its * correct and that it was changed due to being * switched from 'init s|S' being called from a device * other then the real/physical console device. */ case DTTAG_SYSCONREAL: if (dtp->dt_status == dtsENOENT){ dtp->dt_major = REAL_CONSDEVMAJNUM; dtp->dt_minor = REAL_CONSDEVMINNUM; dtp->dt_linkdsfname = SYSTTY; }else{ dtp->dt_major = major(statbuf.st_rdev); dtp->dt_minor = minor(statbuf.st_rdev); dtp->dt_status = dtsVERIFIED; } break; /* * If TP is not configured, set dt_major, dt_minor, and * dt_linkdsfname to REAL_CONSDEVMAJNUM, * REAL_CONSDEVMINNUM, and SYSTTY respectively. */ case DTTAG_SYSCON: if (dtp->dt_status == dtsENOENT){ dtp->dt_major = REAL_CONSDEVMAJNUM; dtp->dt_minor = REAL_CONSDEVMINNUM; dtp->dt_linkdsfname = SYSTTY; }else{ dtp->dt_major = major(statbuf.st_rdev); dtp->dt_minor = minor(statbuf.st_rdev); dtp->dt_status = dtsVERIFIED; } break; default: break; } if (dtp->dt_status != dtsVERIFIED){ if (dtp->dt_linkdsfname == (char *)NULL){ dtp->dt_action = dtaMKNOD; }else{ dtp->dt_action = dtaLINK; } } } } /* * Special Case Handling: * * CASE: SYSTTY does not exist or has the wrong major/minor * device number. * If TP is not configured, set CONSOLE's devtab entry's action field * to dtaLINK. If SYSCONS's major and minor device number is the * same as SYSTTY, set SYSCONS's devtab entry's action field to dtaLINK. * The dt_status field is not changed since if the devices have * been verified, they may be used before they are relinked to SYSTTY. * */ if (devtab[DTTAG_SYSTTY].dt_status == dtsUNVERIFIED){ devtab[DTTAG_CONSOLE].dt_action = dtaLINK; if ((devtab[DTTAG_SYSCON].dt_major == devtab[DTTAG_SYSTTY].dt_major) && (devtab[DTTAG_SYSCON].dt_minor == devtab[DTTAG_SYSTTY].dt_minor)){ devtab[DTTAG_SYSCON].dt_action = dtaLINK; } } #ifdef DEBUG { char *status; for (dtp = devtab; dtp->dt_tag != -1; dtp++){ switch (dtp->dt_status){ case dtsUNVERIFIED: status = "UNVERIFIED"; break; case dtsVERIFIED: status = "VERIFIED"; break; case dtsIGNORE: status = "IGNORE"; break; case dtsENOENT: status = "ENOENT"; break; case dtsWRONGDEV: status = "WRONGDEV"; break; default: break; } debug("verify_devtab():DSF %s\tSTATUS = %s\n",dtp->dt_dsfname, status); } } debug("%d:verify_devtab():RETURN\n",getpid()); #endif } /* * Procedure: setup_consdev * * Restrictions: open(2): None * Setup consdevdsf, which is the dsf init uses internally to write * console messages. * Setup device to display console messages output. * **************************************************************************** * If TP is not configured on the system, set consdevdsf to SYSCON if it * exists and can be opened. If SYSCON does not exist or can not be opened * set consdevdsf to the first device that exists and has the correct * major/minor device number in the following order; * CONSOLE, and SYSTTY. **************************************************************************** * SIDE EFFECTS: * * Global consdevdsf gets set to DSF to send console messages. * Global substitutedsf gets set to consdevdsf if consdevdsf != * CONSOLE, otherwise it gets set to NULL. */ int setup_consdev(char *errbuf) { int fd; int ret = SUCCESS; #ifdef DEBUG debug("%d:setup_consdev():ENTER\n",getpid()); #endif if (errbuf != (char *)NULL){ *errbuf = '\0'; } if ((fd = open(SYSCON, O_RDWR|O_NONBLOCK|O_NOCTTY)) == -1){ if (devtab[DTTAG_CONSOLE].dt_status == dtsVERIFIED){ consdevdsf = devtab[DTTAG_CONSOLE].dt_dsfname; }else if (devtab[DTTAG_SYSTTY].dt_status == dtsVERIFIED){ consdevdsf = devtab[DTTAG_SYSTTY].dt_dsfname; }else{ ret = FAILURE; } devtab[DTTAG_SYSCON].dt_major = REAL_CONSDEVMAJNUM; devtab[DTTAG_SYSCON].dt_minor = REAL_CONSDEVMINNUM; devtab[DTTAG_SYSCON].dt_status = dtsWRONGDEV; devtab[DTTAG_SYSCON].dt_action = dtaLINK; devtab[DTTAG_SYSCON].dt_linkdsfname = SYSTTY; }else{ consdevdsf = SYSCON; (void)close(fd); } if (strcmp(CONSOLE, consdevdsf) != 0){ substitutedsf = consdevdsf; }else{ substitutedsf = (char *)NULL; } #ifdef DEBUG debug("%d:setup_consdev():RETURN %s\t consdevdsf = %s\n", getpid(), ((ret == SUCCESS)? "TRUE":"FALSE"), consdevdsf); #endif return (ret); } /* no reason to do fork here; console() does it already; no reason to * ever have done it, since we will just reboot anyway */ void firmware(void) { cap_t ocap; cap_value_t cap_shutdown = CAP_SHUTDOWN; /* console will probably fail to print, or we wouldn't be here ... */ console("Problems opening console, rebooting; use miniroot\n\n"); ocap = cap_acquire(1, &cap_shutdown); (void)uadmin(A_SHUTDOWN, AD_IBOOT, 0); cap_surrender(ocap); } /* * substitutedev() * * For the given command line from /etc/inittab, substitute all Device * Special File (ie. /dev/console entries) with the DSF specified in subdev. * * NOTE: There is an assumption that /etc/inittab entries always use * /dev/console when re-directing input and/or output from the console device. */ int substitutedev(struct CMD_LINE *cmd, char *line, char *subdev) { char *cmdlinesegp; /* pointer to beginning of a segment of the * command line to be searched for a DSF * entry. */ char *dsfp; /* pointer to beginning of a DSF located in * the segment of the command line. */ int linelen = 0; /* length of new command line being created */ int seglen; /* length of segment current segment of command * line. */ *line = '\0'; cmdlinesegp = cmd->c_command; while ((dsfp = strstr(cmdlinesegp, "/dev/console")) != (char *)NULL){ /* * Copy over segment from cmdlinesegp to dsfp to new command * line and copy the substitute DSF to new command line. */ seglen = dsfp - cmdlinesegp; linelen += seglen; if (linelen < MAXCMDL){ strncat(line, cmdlinesegp, seglen); #ifdef DEBUG debug("substitutedev():After strncat of cmdlinesegp line = \n%s\n",line); #endif }else{ return (FAILURE); } seglen = strlen(subdev); linelen += seglen; if (linelen < MAXCMDL){ strncat(line, subdev, seglen); #ifdef DEBUG printf("substitutedev():After strncat of subdev line = \n%s\n",line); #endif }else{ return (FAILURE); } cmdlinesegp = dsfp + strlen("/dev/console"); } /* end while */ /* * Copy rest of commnad line to new command line if any DSFs were * substituted. */ if (cmdlinesegp == cmd->c_command){ return (SUCCESS); }else{ seglen = strlen(cmdlinesegp); linelen += seglen; if (linelen < MAXCMDL){ strncat(line, cmdlinesegp, seglen); }else{ return (FAILURE); } cmd->c_command = line; } return (SUCCESS); } /* * Procedure: update_devtab * * Restrictions: unlink(2): None mknod(2): None lvlin: None lvlfile(2): None link(2): None */ /* * update_devtab() * * * Go through devtab and make all devices that have the action dtaMKNOD. * Go through devtab and make all devices that have the action dtaLINK. * NOTE: Function should not be called the root file system has been verified. */ int update_devtab(void) { struct devicetable *dtp; int ret = SUCCESS; #ifdef DEBUG debug("%d:update_devtab():ENTER\n",getpid()); #endif if (rootfs_verified != TRUE){ return (FAILURE); } /* * get the level to set the devices to, if any are created. */ for (dtp = &devtab[0]; dtp->dt_tag != (short)-1; dtp++){ if (dtp->dt_action == dtaMKNOD){ mode_t old_umask; #ifdef DEBUG debug("update_devtab:making DSF %s:major %d:minor %d:reason - %s\n", dtp->dt_dsfname, dtp->dt_major, dtp->dt_minor, (dtp->dt_status == dtsWRONGDEV? "incorrect major/minor number":"entry does not exit")); #endif (void)unlink(dtp->dt_dsfname); /* pick sane permissions: 622, not 720 */ old_umask = umask(0); #ifdef DEBUG debug("umask was: %o\n", old_umask); #endif if (mknod(dtp->dt_dsfname, S_IFCHR| S_IRUSR|S_IWUSR|S_IWGRP|S_IWOTH, makedev(dtp->dt_major, dtp->dt_minor)) == -1){ #ifdef DEBUG debug("update_devtab:mknod %s failed errno %d:%s\n", dtp->dt_dsfname, errno, strerror(errno)); #endif ret = FAILURE; }else{ dtp->dt_status = dtsVERIFIED; dtp->dt_action = dtaNOACTION; } umask(old_umask); } } for (dtp = devtab; dtp->dt_tag != (short)-1; dtp++){ if (dtp->dt_action == dtaLINK){ #ifdef DEBUG debug("update_devtab:linking DSF %s to %s:major %d:minor %d:reason - %s\n", dtp->dt_linkdsfname, dtp->dt_dsfname, dtp->dt_major, dtp->dt_minor, (dtp->dt_status == dtsWRONGDEV? "incorrect major/minor number":"entry does not exit")); #endif (void)unlink(dtp->dt_dsfname); if (link(dtp->dt_linkdsfname, dtp->dt_dsfname)){ #ifdef DEBUG debug("update_devtab:link %s to %s failed errno %d:%s\n", dtp->dt_linkdsfname, dtp->dt_dsfname, errno, strerror(errno)); #endif ret = FAILURE; }else{ dtp->dt_status = dtsVERIFIED; dtp->dt_action = dtaNOACTION; } } } #ifdef DEBUG debug("%d:update_devtab():RETURN\t%s\n",getpid(), ((ret == SUCCESS)?"EVERY DEVICE UPDATED SUCCESSFULLY":"ONE OR MORE DEVICE(S) FAILED TO BE UPDATED")); #endif return (ret); } /* * validsyscon(ttyname) -- check ttyname for validity as a new syscon * * If ttyname is on a list of acceptable names, return (1) else (0). * These are names to which syscon may be legitimately linked. * * Specifically excluded are network ttys, so that the console doesn't * disappear into a black hole when the network goes away while shutting * down into single-user mode. * * In the patterns, "?" is recognized, as is "*" (which is assumed to be * the last character in the pattern). */ char *validtty[] = { /* acceptable syscon name patterns */ "/dev/console", "/dev/systty", "/dev/ttyd*", }; static int validsyscon(char *ttyname) { int i; char *v, *t; for (i = 0; i < sizeof (validtty) / sizeof (*validtty); ++i) { for (t = ttyname, v = validtty[i]; *v != '\0'; ++t, ++v) { if (*v == '*') return(1); if (*t == '\0') break; if (*v == '?') continue; if (*t != *v) break; } if (*v == '\0' && *t == '\0') return(1); } return(0); } static int rd_ioctlsyscon(FILE *fp) { unsigned int iflags, oflags, cflags, lflags, ldisc, cc[NCCS]; speed_t ospeed, ispeed; int i; #if defined(_STYPES_LATER) 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:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x", &iflags,&oflags,&cflags,&lflags,&ospeed,&ispeed, &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],&cc[18],&cc[19], &cc[20],&cc[21],&cc[22],&cc[23], &cc[24],&cc[25],&cc[26],&cc[27], &cc[28],&cc[29],&cc[30],&ldisc); #if 31 != NCCS error error NCCS != 31 #endif #else /* notdef _STYPES_LATER */ 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:%x:%x:%x:%x:%x:%x:%x:%x", &iflags,&oflags,&cflags,&lflags,&ospeed,&ispeed, &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],&cc[18],&cc[19], &cc[20],&cc[21],&cc[22],&ldisc); #if 23 != NCCS error error NCCS != 23 #endif #endif /* notdef _STYPES_LATER */ if (i != 7+NCCS) return(0); /* * If the file is formatted properly, use the values to * initialize the console terminal condition. */ termio.c_iflag = iflags; termio.c_oflag = oflags; termio.c_cflag = cflags; termio.c_lflag = lflags; termio.c_ospeed = ospeed; termio.c_ispeed = ispeed; termio.c_line = ldisc; for (i=0; i