/* @(#)init.c 1.7 */ /* "init" is the general process spawning program. It reads */ /* /etc/inittab for a script. */ /* */ /* Routines appear in the source code in the following order: */ /* */ /* main() */ /* single() */ /* remove() */ /* spawn() */ /* respawn() */ /* findslot() */ /* getcmd() */ /* endinittab() */ /* mask() */ /* level() */ /* killproc() */ /* initialize() */ /* init_signals() */ /* siglvl() */ /* alarmclk() */ /* childeath() */ /* powerfail() */ /* getlvl() */ /* switchcon() */ /* efork() */ /* waitproc() */ /* account() */ /* prog_name() */ /* opensyscon() */ /* get_ioctl_syscon() */ /* reset_syscon() */ /* save_ioctl() */ /* console() */ /* error_time() */ /* timer() */ /* setimer() */ /* zero() */ /* userinit() */ /* bcopy() */ /* fdup() */ /* drop_core() */ /* debug() */ /* C() */ /* */ /* In case of bugs, there are four flavors of debug available. */ /* */ /* UDEBUG Will generate a version of "init" that can */ /* be run as a user process. In this form, */ /* certain signals will cause core dumps and */ /* and a file called "debug" is written in the */ /* directory where "init" was started. It also */ /* reads the local directory for utmp, inittab */ /* and the other files it usually gets from */ /* /etc. It also uses /dev/sysconx and */ /* /dev/systtyx instead of /dev/syscon and */ /* /dev/systty. */ /* */ /* DEBUG Generates an "init" which runs in the usual */ /* way, but generates a file, /etc/debug, with */ /* information about process removal, level */ /* changes, and accounting. */ /* */ /* DEBUG1 This symbol adds more debug to what would be */ /* generated by DEBUG or UDEBUG. It has */ /* detailed information about each process */ /* spawned from inittab. DEBUG1 by itself is */ /* equivalent to DEBUG and DEBUG1. It can be */ /* added to UDEBUG to get a user process version. */ /* */ /* ACCTDEBUG Generate debug from the accounting program */ /* only. */ #ifdef ACCTDEBUG #define DEBUGGER #endif #ifdef DEBUG #ifndef DEBUGGER #define DEBUGBER #endif #endif #ifdef UDEBUG #ifndef DEBUG #define DEBUG #endif #ifndef ACCTDEBUG #define ACCTDEBUG #endif #ifndef DEBUGGER #define DEBUGGER #endif #endif #ifdef DEBUG1 #ifndef DEBUG #define DEBUG #endif #ifndef ACCTDEBUG #define ACCTDEBUG #endif #ifndef DEBUGGER #define DEBUGGER #endif #endif #define NOSIGMASK/* There is no sigmask system call yet. */ #ifndef CBUNIX #include #endif #include /* included for NPROC & NOFILE */ #include /* Fix param.h not to duplicate */ #include #include "utmp.h" #include #ifndef CBUNIX #include #else #include #endif #include #include #include #include #include #undef sleep #define fioctl(p,sptr,cmd) ioctl(fileno(p),sptr,cmd) #define TRUE 1 #define FALSE 0 #define FAILURE -1 /* SLEEPTIME The number of seconds "init" sleeps between */ /* wakeups if nothing else requires this "init" */ /* wakeup. */ #define SLEEPTIME 5*60 /* MAXCMDL The maximum length of a command string in */ /* /etc/inittab. */ #define MAXCMDL 512 /* EXEC The length of the prefix string added to all */ /* commands found in /etc/inittab. */ #define EXEC (sizeof("exec ") - 1) /* TWARN The amount of time between warning signal, */ /* SIGTERM, and the fatal kill signal, SIGKILL. */ #define TWARN 20 /* WARNFREQUENCY The number of consecutive failures to find an */ /* empty slot in "init's" internal "proc_table" */ /* before another error message will be generated. */ #define WARNFREQUENCY 25 #define id_eq(x,y) (( x[0] == y[0] && x[1] == y[1] && x[2] == y[2]\ && x[3] == y[3] ) ? TRUE : FALSE) #ifdef UDEBUG int SPECIALPID; /* Any pid can be made special for debugging */ #else /* Normally the special pid is process 1 */ #define SPECIALPID 1 #endif /* Correspondence of signals to init actions. */ #define LVLQ SIGHUP #define LVL0 SIGINT #define LVL1 SIGQUIT #define LVL2 SIGILL #define LVL3 SIGTRAP #define LVL4 SIGIOT #define LVL5 SIGEMT #define LVL6 SIGFPE #define SINGLE_USER SIGBUS #define LVLa SIGSEGV #define LVLb SIGSYS #define LVLc SIGPIPE /* Bit Mask for each level. Used to determine legal levels. */ #define MASK0 01 #define MASK1 02 #define MASK2 04 #define MASK3 010 #define MASK4 020 #define MASK5 040 #define MASK6 0100 #define MASKSU 0200 #define MASKa 0400 #define MASKb 01000 #define MASKc 02000 #ifndef NPROC #define NPROC 100 #endif /* Legal action field values. */ /* Kill process if on, otherwise ignore */ #define OFF 0 /* Continually restart process when it dies */ #define RESPAWN 1 /* Respawn for a,b,c type processes */ #define ONDEMAND RESPAWN /* Start process. Do not respawn when dead */ #define ONCE 2 /* Perform once and wait to complete */ #define WAIT 3 /* Start at boot time only */ #define BOOT 4 /* Start at boot time and wait for complete */ #define BOOTWAIT 5 /* Start on powerfail */ #define POWERFAIL 6 /* Start and wait for complete on powerfail */ #define POWERWAIT 7 /* Default level "init" should start at. */ #define INITDEFAULT 8 /* Actions performed before init speaks. */ #define SYSINIT 9 #define M_OFF 0001 #define M_RESPAWN 0002 #define M_ONDEMAND M_RESPAWN #define M_ONCE 0004 #define M_WAIT 0010 #define M_BOOT 0020 #define M_BOOTWAIT 0040 #define M_PF 0100 #define M_PWAIT 0200 #define M_INITDEFAULT 0400 #define M_SYSINIT 01000 #define ID 1 #define LEVELS 2 #define ACTION 3 #define COMMAND 4 /* Init can be in either of three main states, "normal" mode */ /* where it is processing entries for the lines file in a normal */ /* fashion, "boot" mode, where it is only interested in the boot */ /* actions, and "powerfail" mode, where it is only interested in */ /* powerfail related actions. The following masks declare the */ /* legal actions for each mode. */ #define NORMAL_MODES (M_OFF | M_RESPAWN | M_ONCE | M_WAIT) #define BOOT_MODES (M_BOOT | M_BOOTWAIT) #define PF_MODES (M_PF | M_PWAIT) struct PROC_TABLE { char p_id[4]; /* Four letter unique id of process */ unsigned short p_pid; /* Process id */ short p_count; /* How many respawns of this command in the * current series. */ long p_time; /* Start time for a series of respawns */ short p_flags; short p_exit; /* Exit status of a process which died */ }; /* Flags for the "p_flags" word of a proc_table entry. */ /* */ /* OCCUPIED This slot in init's proc table is in */ /* use. */ /* LIVING Process is alive. */ /* NOCLEANUP "efork()" is not allowed to cleanup */ /* this entry even if process is */ /* dead. */ /* NAMED This process has a name, i.e. came from */ /* /etc/inittab. */ /* DEMANDREQUEST Process started by a "telinit [abc]" */ /* command. Processes formed this */ /* way are respawnable and immune */ /* to level changes as long as */ /* their entry exists in inittab. */ /* TOUCHED Flag used by "remove" to determine */ /* whether it has looked at an */ /* entry while checking for */ /* processes to be killed. */ /* WARNED Flag used by "remove" to mark processes */ /* that have been sent the */ /* SIGTERM signal. If they don't */ /* die in 20 seconds, they will */ /* be sent the SIGKILL signal. */ /* KILLED Flag used by "remove" to say that a */ /* process has been sent both */ /* kill signals. Such processes */ /* should die immediately, but in */ /* case they don't, this prevents */ /* "init" from trying to kill it */ /* again and again, and hogging */ /* the process table of the */ /* operating system. */ #define OCCUPIED 01 #define LIVING 02 #define NOCLEANUP 04 #define NAMED 010 #define DEMANDREQUEST 020 #define TOUCHED 040 #define WARNED 0100 #define KILLED 0200 /* Respawn limits for processes that are to be respawned. */ /* */ /* SPAWN_INTERVAL The number of seconds over which */ /* "init" will try to respawn a process */ /* SPAWN_LIMIT times before it gets mad. */ /* */ /* SPAWN_LIMIT The number of respawns "init" will */ /* attempt in SPANW_INTERVAL seconds */ /* it generates an error message and */ /* inhibits further tries for INHIBIT */ /* seconds. */ /* */ /* INHIBIT The number of seconds "init" ignores */ /* an entry it had trouble spawning */ /* unless a "telinit Q" is received. */ #define SPAWN_INTERVAL (2*60) #define SPAWN_LIMIT 10 #define INHIBIT (5*60) #define NULLPROC ((struct PROC_TABLE *)(0)) #define NO_ROOM ((struct PROC_TABLE *)(FAILURE)) struct CMD_LINE { char c_id[4]; /* Four letter unique id of process to be * affected by action. */ short c_levels; /* Mask of legal levels for process */ short c_action; /* Mask for type of action required */ char *c_command; /* Pointer to init command */ }; /* Following are symbols for the various types of errors for */ /* which "error_time" keeps timing entries. MAX_E_TYPES is the */ /* number of types currently being kept. */ #define FULLTABLE 0 #define BADLINE 1 #define MAX_E_TYPES 2 static struct ERRORTIMES { long e_time; /* Time of last message. */ long e_max; /* Amount of time to wait until next * message. */ } err_times[MAX_E_TYPES] = { 0L,120L,0L,120L }; /* Useful file and device names. */ #ifdef UDEBUG char *UTMP = "utmp"; char *WTMP = "wtmp"; char *INITTAB = "inittab"; char *SYSTTY = "/dev/systtyx"; char *SYSCON = "/dev/sysconx"; char *CORE_RECORD = "core_record"; char *DBG_FILE = "debug"; char *IOCTLSYSCON = "ioctl.syscon"; /* Last syscon modes */ #else char *UTMP = UTMP_FILE; /* Snapshot record file */ char *WTMP = WTMP_FILE; /* Long term record file */ char *INITTAB = "/etc/inittab"; /* Script file for "init" */ char *SYSTTY = "/dev/systty"; /* System Console */ char *SYSCON = "/dev/syscon"; /* Virtual System console */ char *IOCTLSYSCON = "/etc/ioctl.syscon"; /* Last syscon modes */ #ifdef DEBUGGER char *DBG_FILE = "/etc/debug"; #endif #endif char *SU = "/bin/su"; /* Super-user program for single user * mode. */ char *SH = "/bin/sh"; /* Standard Shell */ int n_prev[NSIG]; /* Number of times previously in state */ int cur_state = -1; /* Current state of "init" */ int prior_state; int prev_state; /* State "init" was in last time it woke */ int new_state; /* State user wants "init" to go to. */ int op_modes = BOOT_MODES; /* Current state of "init" */ /* The following structures contain a set of modes for /dev/syscon */ #ifndef CBUNIX #define control(x) ('x'&037) #ifdef u3b struct termio dflt_termio = { BRKINT|IGNPAR|ISTRIP|IXON|IXANY|ICRNL, OPOST|ONLCR|TAB3, B9600, ISIG|ICANON|ECHO|ECHOK, 0, 0177,control(\\),'#','@',control(D),0,0,0 }; #else struct termio dflt_termio = { BRKINT|IGNPAR|ISTRIP|IXON|IXANY|ICRNL, OPOST|ONLCR|TAB3, CS8|CREAD|B300, ISIG|ICANON|ECHO|ECHOK, 0,0177,control(\\),'#','@',control(D),0,0,0 }; #endif struct termio termio; #else struct ttiocb dflt_ttiocb = { B300,B300,'#','@',XTABS|ECHO|CRMOD|ODDP|EVENP }; struct ttiocb ttiocb; struct sgldisc dflt_sgldisc = { 0 }; struct sgldisc sgldisc; struct ttiothcb dflt_other = { NOHUP }; struct ttiothcb ttiothcb; #endif struct termcb dflt_trmcb = { TM_NONE,TERM_NONE,0,0,0,0 }; struct termcb termcb; union WAKEUP { struct WAKEFLAGS { unsigned w_usersignal : 1; /* User sent signal to "init" */ unsigned w_childdeath : 1; /* An "init" child died */ unsigned w_powerhit : 1; /* The OS experienced powerfail */ } w_flags; int w_mask; } wakeup; unsigned int spawncnt,syncnt,pausecnt; int rsflag; /* Set if a respawn has taken place */ int own_pid; /* This is the value of our own pid. * If the value is SPECIALPID, then we have to fork * to interact with outside world. */ struct PROC_TABLE proc_table[NPROC]; /* Table of active processes */ struct PROC_TABLE dummy; /* A zero table used when * calling "account" for non- * process type accounting. */ #ifdef DEBUG char comment[120]; #endif /********************/ /**** main ****/ /********************/ main(argc,argv) int argc; char **argv; { extern int own_pid; extern char *UTMP; extern struct PROC_TABLE proc_table[]; extern struct PROC_TABLE dummy; extern int prev_state,cur_state,new_state,op_modes; extern union WAKEUP wakeup; extern int errno; char utmplock[84]; int defaultlevel,initialize(); FILE *fp; FILE *fdup(); int chg_lvl_flag; extern char level(); extern long time(); extern unsigned int spawncnt,syncnt,pausecnt; extern int rsflag; extern int time_up; #ifdef DEBUG extern char comment[]; #endif #ifdef UDEBUG if (argc == 1) SPECIALPID = getpid(); #endif /* Determine if we are process 1, the main init, or a user invoked */ /* init, whose job it is to inform init to change levels or */ /* perform some other action. */ if ((own_pid = getpid()) != SPECIALPID) userinit(argc,argv); /* Set up the initial states and see if there is a default level */ /* supplied in the "/etc/inittab" file. */ defaultlevel = initialize(); chg_lvl_flag = FALSE; #ifdef DEBUG console("Debug version of init starting-pid = %d\n",SPECIALPID); #endif /* If there is no default level supplied, ask the user to supply */ /* one. */ if (defaultlevel == 0) new_state = getlvl(); else new_state = defaultlevel; if (new_state == SINGLE_USER) { single(defaultlevel); chg_lvl_flag = TRUE; } else { prev_state = cur_state; if(cur_state >= 0) { n_prev[cur_state]++; prior_state = cur_state; } cur_state = new_state; } /* Initialize the "utmp" file and put in the boot time. */ /* Set the umask so that the utmp file is created 644. */ umask(022); sprintf(utmplock, "%s.lck", UTMP); unlink(utmplock); if ((fp = fopen(UTMP,"w+")) == NULL) { console("Cannot create %s\n",UTMP,0); /* Without "utmp" file default to single user mode. */ cur_state = SINGLE_USER; } else { fclose(fp); account(BOOT_TIME,&dummy,NULL); /* Put Boot Entry in "utmp" */ account(RUN_LVL,&dummy,NULL); /* Make the run level entry */ } umask(0); /* Allow later files to be created normally. */ /* Here is the beginning of the main process loop. */ for (;;) { /* If in "normal" mode, check all living processes and initiate */ /* kill sequence on those that should not be there anymore. */ if (op_modes == NORMAL_MODES && cur_state != LVLa && cur_state != LVLb && cur_state != LVLc) remove(); /* If a change in run levels is the reason we awoke, now do */ /* the accounting to report the change in the utmp file. Also */ /* report the change on the system console. */ if (chg_lvl_flag) { chg_lvl_flag = FALSE; account(RUN_LVL,&dummy,NULL); console("New run level: %c\n",level(cur_state)); } /* Scan the inittab file and spawn and respawn processes that */ /* should be alive in the current state. */ spawn(); if (rsflag) { rsflag = 0; spawncnt++; } if (cur_state == SINGLE_USER) { single(0); if (cur_state != prev_state && cur_state != LVLa && cur_state != LVLb && cur_state != LVLc) { chg_lvl_flag = TRUE; continue; } } /* If a powerfail signal was received during the last sequence, */ /* set mode to powerfail. When "spawn" is entered the first */ /* thing it does is to check "powerhit". If it is in PF_MODES */ /* then it clears "powerhit" and does a powerfail sequence. If */ /* it is not in PF_MODES, then it puts itself in PF_MODES and */ /* then clears "powerhit". Should "powerhit" get set again while */ /* "spawn" is working on a powerfail sequence, the following code */ /* will see that "spawn" tries to execute the powerfail sequence */ /* again. This guarentees that the powerfail sequence will be */ /* successfully completed before further processing takes place. */ if (wakeup.w_flags.w_powerhit) { op_modes = PF_MODES; /* Make sure that cur_state != prev_state so that ONCE and WAIT types work. */ prev_state = 0; /* If "spawn" was not just called while in "normal" mode, then */ /* set the mode to "normal" and call it again to check normal */ /* states. */ } else if (op_modes != NORMAL_MODES) { /* If we have just finished a powerfail sequence(which had the */ /* prev_state == 0), set the prev_state = cur_state before the */ /* next pass through. */ if (op_modes == PF_MODES) prev_state = cur_state; op_modes = NORMAL_MODES; /* "spawn was last called with "normal" modes. */ /* If it was a change of levels that awakened us and the new */ /* level is one of the demand levels, LVL[a-c], then reset */ /* the cur_state to the previous state and do another scan to */ /* take care of the usual "respawn" actions. */ } else if (cur_state == LVLa || cur_state == LVLb || cur_state == LVLc) { if(cur_state >= 0) { n_prev[cur_state]++; } cur_state = prior_state; prior_state = prev_state; prev_state = cur_state; account(RUN_LVL,&dummy,NULL); /* At this point "init" is finished with all actions for the */ /* current wakeup. Resync the disks and then pause until */ /* something new takes place. */ } else { prev_state = cur_state; sync(); /* Update the disk */ syncnt++; time_up = FALSE; while (wakeup.w_mask == 0) { /* Now pause until there is a signal of some sort. Signals are */ /* disallowed until the pause system call actually is performed */ /* but then all signals are treated until we return from pause. */ pause(); pausecnt++; /* If we woke up because the timer went off, then SLEEPTIME has */ /* expired. Resync the disks. */ if (time_up == TRUE) { sync(); syncnt++; time_up = FALSE; } } /* Now install the new level, if a change in level happened and */ /* then allow signals again while we do our normal processing. */ if (wakeup.w_flags.w_usersignal) { #ifdef DEBUG debug("\nmain\tSignal-new: %c cur: %c prev: %c\n", level(new_state),level(cur_state), level(prev_state)); #endif /* Set flag so that we know to change run level in utmp file */ /* all the old processes have been removed. Do not set the flag */ /* if a "telinit {Q | a | b | c}" was done or a telinit to the */ /* same level at which init is already running (which is the */ /* same thing as a "telinit Q"). */ if (new_state != cur_state) if(new_state == LVLa || new_state == LVLb || new_state == LVLc) { prev_state = prior_state; prior_state = cur_state; cur_state = new_state; account(RUN_LVL,&dummy,NULL); } else { prev_state = cur_state; if(cur_state >= 0) { n_prev[cur_state]++; prior_state = cur_state; } cur_state = new_state; chg_lvl_flag = TRUE; } /* If the new level is SINGLE_USER, it is necessary to save */ /* the state of the terminal which is "syscon". These will be */ /* restored before the "su" is started up on the line. */ if (new_state == SINGLE_USER) { save_ioctl(); get_ioctl_syscon(); } new_state = 0; } /* If we awoke because of a powerfail, change the operating mode */ /* to powerfail mode. */ if (wakeup.w_flags.w_powerhit) op_modes = PF_MODES; /* Clear all wakeup reasons. */ wakeup.w_mask = 0; } } } /**********************/ /**** single ****/ /**********************/ single(defaultlevel) int defaultlevel; { register struct PROC_TABLE *su_process; struct PROC_TABLE *efork(); extern long waitproc(); extern int errno; extern int new_state,cur_state,prev_state; extern struct PROC_TABLE dummy; extern union WAKEUP wakeup; int state; #ifndef CBUNIX extern struct termio termio; #else extern struct ttiocb ttiocb; extern struct sgldisc sgldisc; extern struct ttiothcb ttiothcb; #endif extern struct termcb termcb; extern int childeath(); for (;;) { console("SINGLE USER MODE\n"); signal(SIGCLD,SIG_DFL); while ((su_process = efork(NULLPROC,NOCLEANUP)) == NO_ROOM) pause(); signal(SIGCLD,childeath); if (su_process == NULLPROC) { opensyscon(); /* Execute the "su" program. */ execlp(SU,SU,"-",0); console("execlp of %s failed; errno = %d\n",SU,errno); timer(5); exit(1); } /* If we are the parent, wait around for the child to die or for */ /* "init" to be signaled to change levels. */ while (waitproc(su_process) == FAILURE) { /* Did we waken because a change of levels? If so, kill the */ /* child and then exit. */ if (wakeup.w_flags.w_usersignal) { if (new_state >= LVL0 && new_state <= LVL6) { kill(su_process->p_pid,SIGKILL); prev_state = cur_state; if(cur_state >= 0) { n_prev[cur_state]++; prior_state = cur_state; } cur_state = new_state; new_state = 0; wakeup.w_mask = 0; return; } } /* All other reasons for waking are ignored when in SINGLE_USER */ /* mode. The only child we are interested in is being waited */ /* for explicitely by "waitproc". */ wakeup.w_mask = 0; } /* Since the su user process died and the level hasn't been */ /* changed by a signal, either request a new level from the user */ /* if default one wasn't supplied, or use the supplied default */ /* level. */ if (defaultlevel != 0) state = defaultlevel; else state = getlvl(); if (state != SINGLE_USER) { /* If the new level is not SINGLE_USER, then exit, otherwise */ /* go back and make up a new "su" process. */ prev_state = cur_state; if(cur_state >= 0) { n_prev[cur_state]++; prior_state = cur_state; } cur_state = state; return; } } } /**********************/ /**** remove ****/ /**********************/ /* "remove" scans through "proc_table" and performs cleanup. If */ /* there is a process in the table, which shouldn't be here at */ /* the current runlevel, then "remove" kills the processes. */ remove() { extern struct PROC_TABLE proc_table[]; register struct PROC_TABLE *process; extern int op_modes,prev_state,cur_state,new_state; struct CMD_LINE cmd; char cmd_string[MAXCMDL]; int change_level; extern int time_up; extern char *C(); change_level = (cur_state != prev_state ? TRUE : FALSE); /* Clear the TOUCHED flag on all entries so that when we have */ /* finished scanning /etc/inittab, we will be able to tell if */ /* we have any processes for which there is no entry in */ /* /etc/inittab. */ for (process= &proc_table[0]; process < &proc_table[NPROC]; process++) process->p_flags &= ~TOUCHED; /* Scan all /etc/inittab entries. */ while(getcmd(&cmd,&cmd_string[0]) == TRUE) { /* Scan for process which goes with this entry in /etc/inittab. */ for (process= &proc_table[0]; process < &proc_table[NPROC]; process++) { /* Does this slot contain the process we are looking for? */ if ((process->p_flags & OCCUPIED) && id_eq(process->p_id,cmd.c_id)) { #ifdef DEBUG debug("remove- id:%s pid: %d time: %lo %d %o %o\n", C(&process->p_id[0]),process->p_pid, process->p_time,process->p_count, process->p_flags,process->p_exit); #endif /* Is the cur_state SINGLE_USER or */ /* is this process marked as "off" or was this process was started */ /* by some other mechanism than the LVLa, LVLb, LVLc mechanism, */ /* and the current level does not support this process? */ if ((cur_state == SINGLE_USER) || (cmd.c_action == M_OFF) || ((cmd.c_levels & mask(cur_state)) == 0 && (process->p_flags & DEMANDREQUEST) == 0)) { if (process->p_flags & LIVING) { /* Touch this entry so that we will know that we've treated it. */ /* **** NOTE **** Processes which are already dead at */ /* this point, but should not be restarted */ /* are left untouched. This causes their slot to be freed later */ /* after dead accounting is performed. */ process->p_flags |= TOUCHED; /* If this process has already been killed before, but for some */ /* reason hasn't disappeared yet, don't kill it again. Only kill */ /* it if the KILLED flag hasn't been set. */ if ((process->p_flags & KILLED) == 0) { /* If this is a change of levels call, then don't fork a killing */ /* process for each process that must die. Send the first */ /* warning signal yourself and mark the process as warned. If */ /* any of the warned processes fail to die in TWARN seconds, then */ /* kill them. */ if (change_level) { process->p_flags |= WARNED; kill(process->p_pid,SIGTERM); /* If this isn't a change of levels, then fork a killing process */ /* which will worry about the details of killing the specified */ /* process. This allows "init" to continue work instead of */ /* pausing for TWARN seconds each pass through this routine. */ } else killproc(process->p_pid); /* Mark the process as having been sent it's kill signals. It */ /* should show up as dead shortly, but just to be safe.... */ process->p_flags |= KILLED; } } /* This process can exist at the current level. If it is also */ /* still`alive or a DEMANDREQUEST, TOUCH it so that will be left */ /* alone. If it is dead and not a DEMANDREQUEST, leave it */ /* untouched so that it will be accounted and cleaned up later */ /* on in "remove". Dead DEMANDREQUESTS will be accounted, but */ /* not freed. */ } else if (process->p_flags & (LIVING | DEMANDREQUEST)) process->p_flags |= TOUCHED; break; } } } /* If this was a change of levels call, scan through the process */ /* table for processes that were warned to die. If any are found */ /* that haven't left yet, sleep for TWARN seconds and then send */ /* final terminations to any that haven't died yet. */ if (change_level) { /* Set the alarm for TWARN seconds on the assumption that there */ /* will be some that need to be waited for. This won't harm */ /* anything except we are guarenteed to wakeup in TWARN seconds */ /* whether we need to or not. */ setimer(TWARN); /* Scan for processes which should be dying. We hope they will */ /* die without having to be sent a SIGKILL signal. */ for (process = &proc_table[0]; process < &proc_table[NPROC]; process++) { /* If this process should die, hasn't yet, and the TWARN time */ /* hasn't expired yet, wait around for process to die or for */ /* timer to expire. */ while ((time_up == FALSE) && (process->p_flags & (WARNED | LIVING | OCCUPIED)) == (WARNED | LIVING | OCCUPIED)) pause(); } /* If we reached the end of the proc table without the timer */ /* expiring, then there are no processes which will have to be */ /* sent the SIGKILL signal. If the timer has expired, then it is */ /* necessary to scan the table again and send signals to all */ /* processes which aren't going away nicely. */ if (time_up == TRUE) for (process = &proc_table[0]; process < &proc_table[NPROC]; process++) { /* Is this a WARNED process that hasn't died yet? */ if ((process->p_flags & (WARNED | LIVING | OCCUPIED)) == (WARNED | LIVING | OCCUPIED)) kill(process->p_pid,SIGKILL); } } /* Rescan the proc_table for two kinds of entry, those marked */ /* as LIVING, NAMED, and which don't have an entry in */ /* /etc/inittab (haven't been TOUCHED by the above scanning), and */ /* haven't been sent kill signals, and those entries marked as */ /* not LIVING, NAMED. The former processes should be killed. */ /* The latter entries should have DEAD_PROCESS accounting done */ /* on them and the slot cleared. */ for (process= &proc_table[0]; process < &proc_table[NPROC]; process++) { if ((process->p_flags & (LIVING | NAMED | TOUCHED | KILLED | OCCUPIED)) == (LIVING | NAMED | OCCUPIED)) { killproc(process->p_pid); process->p_flags |= KILLED; } else if ((process->p_flags & (LIVING | NAMED | OCCUPIED)) == (NAMED | OCCUPIED)) { account(DEAD_PROCESS,process,NULL); /* If this named process hasn't been TOUCHED, then free the space. */ /* It has either died of it's own accord, but isn't respawnable */ /* or was killed because it shouldn't exit at this level. */ if ((process->p_flags & TOUCHED) == 0) process->p_flags = 0; } } } /*********************/ /**** spawn ****/ /*********************/ /* "spawn" scans /etc/inittab for entries which should be run at */ /* this mode. If a process which should be running is found not */ /* to be running, then it is started. */ spawn() { extern struct PROC_TABLE proc_table[]; extern struct PROC_TABLE *findpslot(); register struct PROC_TABLE *process; struct PROC_TABLE *respawn(); struct CMD_LINE cmd; char cmd_string[MAXCMDL]; short lvl_mask; extern int cur_state,prev_state,op_modes; extern union WAKEUP wakeup; extern long waitproc(); #ifdef DEBUG extern char level(); extern char *ctime(),*C(); #endif /* First check the "powerhit" flag. If it is set, make sure */ /* the modes are PF_MODES and clear the "powerhit" flag. */ /* Avoid the possible race on the "powerhit" flag by disallowing */ /* a new powerfail interupt between the test of the powerhit */ /* flag and the clearing of it. */ if (wakeup.w_flags.w_powerhit) { wakeup.w_flags.w_powerhit = 0; op_modes = PF_MODES; } lvl_mask = mask(cur_state); #ifdef DEBUG1 debug("spawn\tSignal-new: %c cur: %c prev: %c\n",level(new_state), level(cur_state),level(prev_state)); debug("spawn- lvl_mask: %o op_modes: %o\n",lvl_mask,op_modes); #endif /* Scan through all the entries in /etc/inittab. */ while (getcmd(&cmd,&cmd_string[0]) == TRUE) { /* Find out if there is a process slot for this entry already. */ if ((process = findpslot(&cmd)) == NULLPROC) { /* Only generate an error message once every WARNFREQUENCY seconds */ /* when the internal process table is full. */ if (error_time(FULLTABLE)) console("Internal process table is full.\n"); continue; } /* If there is an entry, and it is marked as DEMANDREQUEST, one */ /* of the levels a,b, or c is in its levels mask, and the action */ /* field is ONDEMAND and ONDEMAND is a permissable mode, and */ /* the process is dead, then respawn it. */ if (((process->p_flags & (LIVING | DEMANDREQUEST)) == DEMANDREQUEST) && (cmd.c_levels & (MASKa | MASKb | MASKc)) && (cmd.c_action & op_modes) == M_ONDEMAND) { respawn(process,&cmd); continue; /* Now finished with this entry. */ } #ifdef DEBUG1 debug("process:\t%s\t%05d\n%s\t%d\t%o\t%o\n", C(&process->p_id[0]),process->p_pid, ctime(&process->p_time),process->p_count, process->p_flags,process->p_exit); debug("cmd:\t%s\t%o\t%o\n\"%s\"\n",C(&cmd.c_id[0]), cmd.c_levels,cmd.c_action,cmd.c_command); #endif /* If the action is not an action we are interested in, skip */ /* the entry. */ if ((cmd.c_action & op_modes) == 0) continue; if (process->p_flags & LIVING) continue; if ((cmd.c_levels & lvl_mask) == 0) continue; /* If the modes are the normal modes (ONCE, WAIT, RESPAWN, OFF, */ /* ONDEMAND) and the action field is either OFF or the action */ /* field is ONCE or WAIT and the current level is the same as the */ /* last level, then skip this entry. ONCE and WAIT only get run */ /* when the level changes. */ if ((op_modes == NORMAL_MODES) && (cmd.c_action == M_OFF || (cmd.c_action & (M_ONCE | M_WAIT)) && cur_state == prev_state)) continue; /* At this point we are interested in performing the action for */ /* this entry. Actions fall into two catagories, spinning off */ /* a process and not waiting, and spinning off a process and */ /* waiting for it to die. */ /* If the action is ONCE, RESPAWN, ONDEMAND, POWERFAIL, or BOOT */ /* then spin off a process, but don't wait. */ if (cmd.c_action & (M_ONCE | M_RESPAWN | M_PF | M_BOOT)) respawn(process,&cmd); /* The action must be WAIT, BOOTWAIT, or POWERWAIT, therefore */ /* spin off the process, but wait for it to die before continuing. */ else { respawn(process,&cmd); while (waitproc(process) == FAILURE); account(DEAD_PROCESS,process,NULL); process->p_flags = 0; } } } /***********************/ /**** respawn ****/ /***********************/ /* "respawn" spawns a shell, inserts the information about the */ /* process into the proc_table, and does the startup accounting. */ struct PROC_TABLE *respawn(process,cmd) register struct PROC_TABLE *process; register struct CMD_LINE *cmd; { register int i; FILE *fp; int modes; extern int childeath(); extern int cur_state,errno; extern struct PROC_TABLE *efork(); struct PROC_TABLE tmproc,*oprocess; long now; static char *envp[] = { "PATH=/bin:/etc:/usr/bin",0 }; extern char *prog_name(); extern int rsflag; #ifdef DEBUG1 extern char *C(); debug("** respawn ** id:%s\n",C(&process->p_id[0])); #endif /* The modes to be sent to "efork" are 0 unless we are spawning */ /* a LVLa, LVLb, or LVLc entry or we will be waiting for the */ /* death of the child before continuing. */ modes = NAMED; if ((process->p_flags & DEMANDREQUEST) || cur_state == LVLa || cur_state == LVLb || cur_state == LVLc) modes |= DEMANDREQUEST; if ((cmd->c_action & (M_SYSINIT | M_WAIT | M_BOOTWAIT | M_PWAIT)) != 0) modes |= NOCLEANUP; /* If this is a respawnable process, check the threshold */ /* information to avoid excessive respawns. */ if (cmd->c_action & M_RESPAWN) { /* Add the NOCLEANUP to all respawnable commands so that the */ /* information about the frequency of respawns isn't lost. */ modes |= NOCLEANUP; time(&now); /* If no time is assigned, then this is the first time this */ /* command is being processed in this series. Assign the current */ /* time. */ if (process->p_time == 0L) process->p_time = now; /* Have we just reached the respawn limit? */ else if (process->p_count++ == SPAWN_LIMIT) { /* If so, have we been respawning it too rapidly? */ if ((now - process->p_time) < SPAWN_INTERVAL) { /* If so, generate an error message and refuse to respawn the */ /* process for now. */ console("Command is respawning\ too rapidly. Check for possible errors.\nid:%4s \"%s\"\n", &cmd->c_id[0], &cmd->c_command[EXEC]); return(process); /* If process hasn't been respawning too often, reset the count */ /* and time to 0 and allow respawn. */ } else { process->p_time = now; process->p_count = 0; } /* If this process has been respawning too rapidly and the */ /* inhibit time limit hasn't expired yet, refuse to respawn. */ } else if (process->p_count > SPAWN_LIMIT) { if ((now - process->p_time) < (SPAWN_INTERVAL + INHIBIT)) return(process); /* If it is time to try respawning this command, clear the count */ /* and reset the time to now. */ else { process->p_time = now; process->p_count = 0; } } rsflag = TRUE; } /* Spawn a child process to execute this command. */ signal(SIGCLD,SIG_DFL); oprocess = process; while ((process = efork(oprocess,modes)) == NO_ROOM) pause(); /* If we are the child, close up all the open files and set up the */ /* default standard input and standard outputs. */ if (process == NULLPROC) { /* Make sure the child uses a different file pointer in the OS */ /* for its references to /etc/utmp. If this isn't done, the */ /* seeks and reads of the child and parent will compete with each */ /* other. */ endutent(); /* Perform the accounting for the beginning of a process. */ /* Note that all processes are initially "INIT_PROCESS"es. Getty */ /* will change the type to "LOGIN_PROCESS" and login will change */ /* it to "USER_PROCESS" when they run. */ tmproc.p_id[0] = cmd->c_id[0]; tmproc.p_id[1] = cmd->c_id[1]; tmproc.p_id[2] = cmd->c_id[2]; tmproc.p_id[3] = cmd->c_id[3]; tmproc.p_pid = getpid(); tmproc.p_exit = 0; account(INIT_PROCESS,&tmproc,prog_name(&cmd->c_command[EXEC])); for (i=0,fp= stdin; i < _NFILE;i++,fp++) fclose(fp); /* Now "exec" a shell with the -c option and the command from */ /* /etc/inittab. */ execle(SH,"INITSH","-c",cmd->c_command,0,&envp[0]); /* If the "exec" fails, print an error message. */ console("Command\n\"%s\"\n failed to execute. errno = %d\n", cmd->c_command,errno); /* Don't come back so quickly that "init" hasn't had a chance to */ /* complete putting this child in "proc_table". */ timer(20); exit(1); /* We are the parent, therefore insert the necessary information */ /* in the proc_table. */ } else { process->p_id[0] = cmd->c_id[0]; process->p_id[1] = cmd->c_id[1]; process->p_id[2] = cmd->c_id[2]; process->p_id[3] = cmd->c_id[3]; } signal(SIGCLD,childeath); return (process); } /************************/ /**** findslot ****/ /************************/ /* findpslot() finds the old slot in the process table for the */ /* command with the same id, or it finds an empty slot. */ struct PROC_TABLE *findpslot(cmd) register struct CMD_LINE *cmd; { extern struct PROC_TABLE proc_table[]; register struct PROC_TABLE *process,*empty; for(empty= NULLPROC,process= &proc_table[0] ; process < &proc_table[NPROC];process++) { if ((process->p_flags & OCCUPIED) && id_eq(process->p_id,cmd->c_id)) break; /* If the entry is totally empty and "empty" is still 0, remember */ /* where this hole is and make sure the slot is zeroed out. */ if (empty == NULLPROC && (process->p_flags & OCCUPIED) == 0) { empty = process; process->p_id[0] = '\0'; process->p_id[1] = '\0'; process->p_id[2] = '\0'; process->p_id[3] = '\0'; process->p_pid = 0; process->p_time = 0L; process->p_count = 0; process->p_flags = 0; process->p_exit = 0; } } /* If there is no entry for this slot, then there should be */ /* an empty slot. If there is no empty slot, then we've run out */ /* of proc_table space. If the latter is true, empty will be NULL */ /* and the caller will have to complain. */ if (process == &proc_table[NPROC]) { process = empty; } return(process); } /**********************/ /**** getcmd ****/ /**********************/ /* "getcmd" parses lines from /etc/inittab. Each time it finds */ /* a command line it will return TRUE as well as fill the passed */ /* CMD_LINE structure and the shell command string. When the end */ /* of /etc/inittab is reached, FALSE is returned. */ /* */ /* /etc/inittab is automatically opened if it is not currently */ /* open and is closed when the end of the file is reached. */ static FILE *fp_inittab = NULL; getcmd(cmd,shcmd) register struct CMD_LINE *cmd; char *shcmd; { extern FILE *fp_inittab; int i; int answer,proceed; register char *ptr; register int c; register int state; char lastc,*ptr1; int errnum; extern int errno; static char *actions[] = { "off","respawn","ondemand","once","wait","boot", "bootwait","powerfail","powerwait","initdefault", "sysinit", }; static short act_masks[] = { M_OFF,M_RESPAWN,M_ONDEMAND,M_ONCE,M_WAIT,M_BOOT,M_BOOTWAIT, M_PF,M_PWAIT,M_INITDEFAULT,M_SYSINIT, }; if (fp_inittab == NULL) { /* Be very persistent in trying to open /etc/inittab. */ for (i=0; i < 3;i++) { if ((fp_inittab = fopen(INITTAB,"r")) != NULL) break; else { errnum = errno; /* Remember for error message */ timer(3); /* Wait 3 seconds to see if file appears. */ } } /* If unable to open /etc/inittab, print error message and return */ /* FALSE to caller. */ if (fp_inittab == NULL) { console("Cannot open %s. errno: %d\n",INITTAB,errnum); return (FALSE); } } /* Keep getting commands from /etc/inittab until you find a good */ /* one or run out of file. */ for (answer= FALSE; answer == FALSE;) { /* Zero out the cmd itself before trying next line. */ zero((char *)cmd,sizeof(struct CMD_LINE)); /* Read in lines of /etc/inittab, parsing at colons, until a line */ /* is read in which doesn't end with a backslash. Do not start */ /* if the first character read is an EOF. Note that this means */ /* that should a line fail to end in a newline, it will still */ /* be processed, since the "for" will terminate normally once */ /* started, regardless of whether line terminates with a newline */ /* or an EOF. */ state = FAILURE; if ((c = fgetc(fp_inittab)) != EOF) for (proceed= TRUE, ptr= shcmd,state=ID, lastc= '\0'; proceed && c != EOF; lastc = c , c = fgetc(fp_inittab)) { /* If we are not in the FAILURE state and haven't yet reached */ /* the shell command field, process the line, otherwise just */ /* look for a real end of line. */ if (state != FAILURE && state != COMMAND) { /* Squeeze out spaces and tabs. */ if (c == ' ' || c == '\t') continue; /* If the character is a ':', then check the previous field for */ /* correctness and advance to the next field. */ if ( c == ':' ) { switch (state) { case ID : /* Check to see that there are only 1 to 4 characters for the id. */ if ((i = ptr - shcmd) < 1 || i > 4 ) { state = FAILURE; } else { bcopy(shcmd,&cmd->c_id[0],i); ptr = shcmd; /* Reset pointer */ state = LEVELS; } break; case LEVELS : /* Build a mask for all the levels that this command will be */ /* legal */ for (cmd->c_levels= 0,ptr1= shcmd ; ptr1 < ptr; ptr1++) { if (*ptr1 >= '0' && *ptr1 <= '6') cmd->c_levels |= (MASK0 << (*ptr1 - '0')); else if (*ptr1 >= 'a' && *ptr1 <= 'c') cmd->c_levels |= (MASKa << (*ptr1 - 'a')); else if(*ptr1 == 's' || *ptr1 == 'S') cmd->c_levels |= MASKSU; else { state = FAILURE; break; } } if (state != FAILURE) { state = ACTION; ptr = shcmd; /* Reset the buffer */ } break; case ACTION : /* Null terminate the string in shcmd buffer and then try to match */ /* against legal actions. If the field is of length 0, then the */ /* default of "RESPAWN" is used if the id is numeric, otherwise */ /* the default is "OFF". */ if (ptr == shcmd) { if (isdigit(cmd->c_id[0]) && (cmd->c_id[1] == '\0' || isdigit(cmd->c_id[1])) && (cmd->c_id[2] == '\0' || isdigit(cmd->c_id[2])) && (cmd->c_id[3] == '\0' || isdigit(cmd->c_id[3])) ) cmd->c_action = M_RESPAWN; else cmd->c_action = M_OFF; } else { for (cmd->c_action=0,i= 0,*ptr = '\0'; i < sizeof(actions)/sizeof(char *);i++) { if (strcmp(shcmd,actions[i]) == 0) { /* code folded from here */ if((cmd->c_levels & MASKSU) && (act_masks[i] != M_INITDEFAULT)) cmd->c_action = 0; else cmd->c_action = act_masks[i]; break; /* unfolding */ } } } /* If the action didn't match any legal action, set state to */ /* FAILURE. */ if (cmd->c_action == 0) state = FAILURE; else { state = COMMAND; /* Insert the prefix string of "exec " into the command buffer */ /* before inserting any characters. */ strcpy(shcmd,"exec "); } ptr = shcmd + EXEC; break; } continue; } } /* If the character is a '\n', then this is the end of a line. */ /* If the '\n' wasn't preceded by a backslash, it is also the end */ /* of an /etc/inittab command. If it was preceded by a backslash */ /* then the next line is a continuation. Note that the */ /* continuation '\n' falls through and is treated like other */ /* characters and is stored in the shell command line. */ if (c == '\n') { if (lastc != '\\') { proceed = FALSE; *ptr = '\0'; break; } } /* For all other characters just stuff them into the command */ /* as long as there aren't too many of them. Make sure there is */ /* room for a terminating '\0' also. */ if (ptr >= (shcmd + MAXCMDL-1)) state = FAILURE; else *ptr++ = c; /* If the character we just stored was a quoted backslash, then */ /* change "c" to '\0', so that this backslash will not cause a */ /* subsequent '\n' to appear quoted. In otherwords '\' '\' '\n' */ /* is the real end of a command, while '\' '\n' is a continuation. */ if ( c == '\\' && lastc == '\\') c = '\0'; } /* Make sure all the fields are properly specified for a good */ /* command line. */ if (state == COMMAND) { answer = TRUE; cmd->c_command = shcmd; /* If no default level was supplied, insert all numerical levels. */ if (cmd->c_levels == 0) cmd->c_levels = MASK0 | MASK1 | MASK2 | MASK3 | MASK4 | MASK5 | MASK6; /* If no action has been supplied, declare this entry to be */ /* OFF. */ if (cmd->c_action == 0) cmd->c_action = M_OFF; /* If no shell command has been supplied, make sure there is */ /* a null string in the command field. */ if (ptr == (shcmd + EXEC)) { /* EXEC is the length of the string "exec " minus the null at the end. */ *shcmd = '\0'; } } else answer = FALSE; /* If we have reached the end of /etc/inittab, then close it and */ /* quit trying to find a good command line. */ if (c == EOF) { endinittab(); /* Close "inittab" */ break; } } return(answer); } /**************************/ /**** endinittab ****/ /**************************/ endinittab() { extern FILE *fp_inittab; fclose(fp_inittab); fp_inittab = NULL; } /********************/ /**** mask ****/ /********************/ int mask(level) int level; { register int answer; switch (level) { case LVLQ : answer = 0; break; case LVL0 : answer = MASK0; break; case LVL1 : answer = MASK1; break; case LVL2 : answer = MASK2; break; case LVL3 : answer = MASK3; break; case LVL4 : answer = MASK4; break; case LVL5 : answer = MASK5; break; case LVL6 : answer = MASK6; break; case SINGLE_USER : answer = MASKSU; break; case LVLa : answer = MASKa; break; case LVLb : answer = MASKb; break; case LVLc : answer = MASKc; break; default : answer = FAILURE; break; } return (answer); } /*********************/ /**** level ****/ /*********************/ char level(state) int state; { register char answer; switch(state) { case LVL0 : answer = '0'; break; case LVL1 : answer = '1'; break; case LVL2 : answer = '2'; break; case LVL3 : answer = '3'; break; case LVL4 : answer = '4'; break; case LVL5 : answer = '5'; break; case LVL6 : answer = '6'; break; case SINGLE_USER : answer = 'S'; break; case LVLa : answer = 'a'; break; case LVLb : answer = 'b'; break; case LVLc : answer = 'c'; break; default : answer = '?'; break; } return(answer); } /************************/ /**** killproc ****/ /************************/ /* "killproc" sends the SIGTERM signal to the specified process */ /* and then after TWARN seconds, the SIGKILL signal. */ killproc(pid) register int pid; { extern int childeath(); struct PROC_TABLE *efork(); register struct PROC_TABLE *process; signal(SIGCLD,SIG_DFL); while ((process = efork(NULLPROC,0)) == NO_ROOM) pause(); signal(SIGCLD,childeath); /* If we are the child, send the signals to the process we are */ /* to kill. */ if (process == NULLPROC) { kill(pid,SIGTERM); /* Warn the process to quit. */ timer(TWARN); /* Sleep TWARN seconds */ kill(pid,SIGKILL); /* Kill the process if still alive. */ exit(0); } } /**************************/ /**** initialize ****/ /**************************/ /* Perform the initial state setup and look for an initdefault */ /* entry in the "inittab" file. */ int initialize() { struct CMD_LINE cmd; char command[MAXCMDL]; extern int cur_state,op_modes; extern int childeath(); register int mask,i; static int states[] = { LVL0,LVL1,LVL2,LVL3,LVL4,LVL5,LVL6,SINGLE_USER }; FILE *fp_systty,*fp; char device[sizeof("/dev/")+DIRSIZ]; struct direct dirent; int fd_dev,initstate; struct stat statbuf,statcon; register struct PROC_TABLE *process,*oprocess; extern struct PROC_TABLE *efork(),*findpslot(); extern char *ttyname(); /* Initialize state to "SINGLE_USER" "BOOT_MODES" */ if(cur_state >= 0) { n_prev[cur_state]++; prior_state = cur_state; } cur_state = SINGLE_USER; op_modes = BOOT_MODES; /* Set up all signals to be caught or ignored as is appropriate. */ init_signals(); #ifdef UDEBUG save_ioctl(); #endif /* Get the ioctl settings for /dev/syscon so that it can be */ /* brought up in the state it was in when the system went down. */ get_ioctl_syscon(); /* Look for an "initdefault" entry in "/etc/inittab", which */ /* specifies the initial level to which "init" is to go at */ /* startup time. */ while(getcmd(&cmd,&command[0]) == TRUE) { if (cmd.c_action == M_INITDEFAULT) { /* Look through the "c_levels" word, starting at the highest */ /* level. The assumption is that there will only be one level */ /* specified, but if there is more than one, the system will */ /* come up at the highest possible level. */ for (mask=MASKSU,i=(sizeof(states)/sizeof(int)) - 1 ; mask > 0; mask >>= 1 , i--) { if (mask & cmd.c_levels) { initstate = states[i]; } } /* If the entry is for a system initialization command, execute */ /* it at once, and wait for it to complete. */ } else if (cmd.c_action == M_SYSINIT) { if (process = findpslot(&cmd)) { signal(SIGCLD,SIG_DFL); for (oprocess=process; (process = efork(oprocess,(NAMED|NOCLEANUP))) == NO_ROOM;); signal(SIGCLD,childeath); if (process == NULLPROC) { /* Notice no bookkeeping is performed on these entries. This is */ /* to avoid doing anything that would cause writes to the file */ /* system to take place. No writing should be done until the */ /* operator has had the chance to decide whether the file system */ /* needs checking or not. */ for (i=0,fp= stdin; i < _NFILE;i++,fp++) fclose(fp); execl(SH,"INITSH","-c",cmd.c_command,0); exit(1); } else while (waitproc(process) == FAILURE); #ifdef ACCTDEBUG debug("SYSINIT- id: %.4s term: %o exit: %o\n", &cmd.c_id[0],(process->p_exit&0xff), (process->p_exit&0xff00)>>8); #endif process->p_flags = 0; } } } if (initstate) return(initstate); /* If the system console is remote, put a message on the */ /* system tty warning anyone there that syscon is elsewhere. */ signal(SIGCLD,SIG_DFL); while ((process = efork(NULLPROC,NOCLEANUP)) == NO_ROOM); signal(SIGCLD,childeath); if (process == NULLPROC) { if ((fp_systty = fopen(SYSTTY,"r+")) != (FILE*)NULL) { if (fstat(fileno(fp_systty),&statbuf) != FAILURE && stat(SYSCON,&statcon) != FAILURE && statbuf.st_rdev != statcon.st_rdev) { /* Since the devices of syscon and systty don't match, find the */ /* device that syscon is linked to and send the warning to the */ /* physical system tty. */ if ((fd_dev = open("/dev",O_RDONLY)) != FAILURE) { zero(&device[0],sizeof(device)); strcpy(&device[0],"/dev/"); while (read(fd_dev,&dirent,sizeof(dirent)) == sizeof(dirent)) { /* If this isn't the syscon entry itself.... */ strncpy(&device[sizeof("/dev/")-1], &dirent.d_name[0],DIRSIZ); if (strcmp(SYSCON,&device[0])) { if (stat(&device[0],&statbuf) != FAILURE && statbuf.st_rdev == statcon.st_rdev) { break; } } } } if (statbuf.st_rdev != statcon.st_rdev) fprintf(fp_systty, "\nInit: system console is remote.\ Type to regain control.\n"); else fprintf(fp_systty, "\nInit: system console is remote:\ %s Type to regain control.\n", &device[0]); fflush(fp_systty); fclose(fp_systty); } } exit(0); } /* Wait for the child to die. */ while(waitproc(process) == FAILURE); /* Since no "initdefault" entry was found, return 0. This will */ /* have "init" ask the user at /dev/syscon to supply a level. */ return(0); } /****************************/ /**** init_signals ****/ /****************************/ /* Initialize all signals to either be caught or ignored. */ init_signals() { extern int siglvl(),alarmclk(),childeath(),powerfail(); #ifdef UDEBUG extern int abort(); #endif signal(LVLQ,siglvl); signal(LVL0,siglvl); signal(LVL1,siglvl); #ifdef UDEBUG signal(LVL2,SIG_DFL); signal(LVL3,SIG_DFL); signal(LVL4,SIG_DFL); #else signal(LVL2,siglvl); signal(LVL3,siglvl); signal(LVL4,siglvl); #endif signal(LVL5,siglvl); signal(LVL6,siglvl); signal(SINGLE_USER,siglvl); signal(LVLa,siglvl); signal(LVLb,siglvl); signal(LVLc,siglvl); alarmclk(); #ifdef UDEBUG signal(SIGTERM,SIG_DFL); signal(SIGUSR1,abort); signal(SIGUSR2,abort); #else signal(SIGTERM,SIG_IGN); signal(SIGUSR1,SIG_IGN); signal(SIGUSR2,SIG_IGN); #endif signal(SIGCLD,childeath); signal(SIGPWR,powerfail); } /**********************/ /**** siglvl ****/ /**********************/ siglvl(sig) int sig; { extern int new_state,cur_state; extern union WAKEUP wakeup; extern struct PROC_TABLE proc_table[]; register struct PROC_TABLE *process; /* If the signal received is a "LVLQ" signal, do not really */ /* change levels, just restate the current level. */ if (sig == LVLQ) new_state = cur_state; /* If the signal received is something other than "LVLQ", set */ /* the new level to the value of the signal received. */ else new_state = sig; /* Clear all times and repeat counts in the process table */ /* since either the level is changing or the user has editted */ /* the "/etc/inittab" file and wants us to look at it again. If */ /* the user has fixed a typo, we don't want residual timing data */ /* preventing the fixed command line from executing. */ for (process= &proc_table[0]; process < &proc_table[NPROC] ; process++) { process->p_time = 0L; process->p_count = 0; } /* Set the flag saying that a "user signal" was received. */ wakeup.w_flags.w_usersignal = 1; signal(sig,siglvl); } /************************/ /**** alarmclk ****/ /************************/ alarmclk() { extern int time_up; signal(SIGALRM,alarmclk); time_up = TRUE; alarm(SLEEPTIME); } /*************************/ /**** childeath ****/ /*************************/ childeath() { extern union WAKEUP wakeup; extern struct PROC_TABLE proc_table[]; register struct PROC_TABLE *process; register int pid; int status; /* Perform wait to get the process id of the child who died and */ /* then scan the process table to see if we are interested in */ /* this process. **Note** if a super-user sends the SIGCLD signal */ /* to "init", the following wait will not immediately return */ /* and "init" will be inoperative until one of its child really */ /* does die. */ pid = wait(&status); #ifdef UDEBUG debug("childeath: pid-%d status-%x\n",pid,status); #endif #ifdef UDEBUG debug("childeath: pid- %d status- %x\n",pid,status); #endif for (process= &proc_table[0]; process < &proc_table[NPROC];process++) { if ((process->p_flags & (OCCUPIED)) == OCCUPIED && process->p_pid == pid) { /* Mark this process as having died and store the exit status. */ /* Also set the wakeup flag for a dead child and break out of */ /* loop. */ process->p_flags &= ~LIVING; process->p_exit = status; wakeup.w_flags.w_childdeath = 1; break; } } #ifdef UDEBUG if (process == &proc_table[NPROC]) debug("Didn't find process %d.\n", pid); #endif /* Reset the child death signal routine. */ signal(SIGCLD,childeath); } /*************************/ /**** powerfail ****/ /*************************/ powerfail() { extern union WAKEUP wakeup; wakeup.w_flags.w_powerhit = 1; signal(SIGPWR,powerfail); } /**********************/ /**** getlvl ****/ /**********************/ /* Get the new run level from /dev/syscon. If someone at */ /* /dev/systty types a while we are waiting for the user */ /* to start typing, relink /dev/syscon to /dev/systty. */ int fd_systty; /* Due to problems with , getlvl doesn't use it. This */ /* define makes for simple printing of strings. */ #define WRTSTR(x,y) write(x,y,sizeof(y)-1) getlvl() { int c; int status; extern int switchcon(); extern union WAKEUP wakeup; #ifdef UDEBUG extern int abort(); #endif int fd_tmp; register struct PROC_TABLE *process; extern struct PROC_TABLE *efork(); extern int childeath(); extern int fd_systty; static char levels[] = { LVL0,LVL1,LVL2,LVL3,LVL4,LVL5,LVL6,SINGLE_USER }; extern long waitproc(); #ifndef CBUNIX extern struct termio termio,dflt_termio; #else extern struct ttiocb ttiocb,dflt_ttiocb; extern struct sgldisc sgldisc; extern struct ttiothcb ttiothcb; #endif extern struct termcb termcb; /* Fork a child who will request the new run level from */ /* /dev/syscon. */ signal(SIGCLD,SIG_DFL); while ((process = efork(NULLPROC,NOCLEANUP)) == NO_ROOM); signal(SIGCLD,childeath); if (process == NULLPROC) { /* Open /dev/systty so that if someone types a , we can be */ /* informed of the fact. */ if ((fd_tmp = open(SYSTTY,2)) != FAILURE) { /* Make sure the system tty is not RAW. */ #ifndef CBUNIX ioctl(fd_tmp,TCSETA,&dflt_termio); #else ioctl(fd_tmp,TIOCSETP,&dflt_ttiocb); #endif /* Make sure the file descriptor is greater than 2 so that it */ /* won't interfere with the standard descriptors. */ fd_systty = fcntl(fd_tmp,0,3); close(fd_tmp); /* Prepare to catch the interupt signal if typed at */ /* /dev/systty. */ signal(SIGINT,switchcon); signal(SIGQUIT,switchcon); } #ifdef UDEBUG signal(SIGUSR1,abort); signal(SIGUSR2,abort); #endif for (;;) { /* Close the current descriptors and open ones to /dev/syscon. */ opensyscon(); /* Print something unimportant and pause, since reboot may be */ /* taking place over a line coming in over the dataswitch. */ /* The dataswitch sometimes gets the carrier up before the */ /* connection is complete and the first write gets lost. */ WRTSTR(1,"\n"); sleep(2); /* Now read in the user response. */ for (c='\0'; c != EOF;) { WRTSTR(1,"ENTER RUN LEVEL (0-6,s, or S): "); /* Get a character from the user which isn't a space or tab. */ for (read(0,&c,1); c == ' ' || c == '\t' ; read(0,&c,1) ); c &= 0x7f; /* If the character is a digit between 0 and 6 or the letter S, */ /* fine, exit with the level equal to the new desired state. */ if ( c == 'S' || c == 's' ) c = '7'; if ( c >= '0' && c <= '7') exit(levels[c - '0']); else if (c != EOF) WRTSTR(1,"\nUsage: 0123456sS\n"); } } } /* Wait for the child to die and return it's status. */ while((status = waitproc(process)) == FAILURE); /* Ignore any signals such as powerfail when in "getlvl". */ wakeup.w_mask = 0; /* Return the new run level to the caller. */ #ifdef DEBUG debug("getlvl: status: %o exit: %o termination: %o\n", status,(status&0xff00)>>8,(status&0xff)); #endif return((status&0xff00)>>8); } /*************************/ /**** switchcon ****/ /*************************/ switchcon(sig) int sig; { extern int own_pid; extern int fd_systty; signal(sig,SIG_IGN); /* If this is the first time a has been typed on the */ /* /dev/systty, then unlink /dev/syscon and relink it to */ /* /dev/systty. Also reestablish file pointers. */ if (fd_systty != -1) { reset_syscon(); opensyscon(); /* Set fd_systty to -1 so that we ignore any deletes from it in */ /* the future as far as relinking /dev/syscon to /dev/systty. */ fd_systty = -1; } signal(sig,switchcon); } /*********************/ /**** efork ****/ /*********************/ /* "efork" forks a child and the parent inserts the process in */ /* its table of processes that are directly a result of forks */ /* that it has performed. The child just changes the "global" */ /* with the process id for this process to it's new value. */ /* */ /* If "efork" is called with a pointer into the proc_table */ /* it uses that slot, otherwise it searches for a free slot. */ /* Whichever way it is called, it returns the pointer to the */ /* proc_table entry. */ struct PROC_TABLE *efork(process,modes) register struct PROC_TABLE *process; int modes; { register int childpid; extern int own_pid,errno; extern struct PROC_TABLE proc_table[]; register struct PROC_TABLE *proc; int i; extern int childeath(); int (*oldroutine)(); #ifdef UDEBUG static int (*oldsigs[NPROC])(); #endif /* Freshen up the proc_table, removing any entries for dead */ /* processes that don't have the NOCLEANUP set. Perform the */ /* necessary accounting. */ for (proc= &proc_table[0]; proc < &proc_table[NPROC]; proc++) { if ((proc->p_flags & (OCCUPIED | LIVING | NOCLEANUP)) == (OCCUPIED)) { #ifdef DEBUG debug("efork- id:%s pid: %d time: %lo %d %o %o\n", C(&proc->p_id[0]),proc->p_pid, proc->p_time, proc->p_count, proc->p_flags,proc->p_exit); #endif /* Is this a named process? If so, do the necessary bookkeeping. */ if (proc->p_flags & NAMED) account(DEAD_PROCESS,proc,NULL); /* Free this entry for new usage. */ proc->p_flags = 0; } } while((childpid = fork()) == FAILURE) { /* Shorten the alarm timer in case someone else's child dies and */ /* free up a slot in the process table. */ alarm(5); /* Wait for some children to die. Since efork() is normally */ /* called with SIGCLD in the default state, reset it to catch */ /* so that child death signals can come in. */ oldroutine = signal(SIGCLD,childeath); pause(); signal(SIGCLD,oldroutine); } if (childpid != 0) { /* If a pointer into the process table was not specified, then */ /* search for one. */ if (process == NULLPROC) { for (process= &proc_table[0]; process->p_flags != 0 && process < &proc_table[NPROC];process++ ); if (process == &proc_table[NPROC]) { if (error_time(FULLTABLE)) console("Internal process table is full.\n"); return(NO_ROOM); } process->p_time = 0L; process->p_count = 0; } process->p_id[0] = '\0'; process->p_id[1] = '\0'; process->p_id[2] = '\0'; process->p_id[3] = '\0'; process->p_pid = childpid; process->p_flags = (LIVING | OCCUPIED | modes); process->p_exit = 0; } else { own_pid = getpid(); /* Reset child's concept of its * own process id. */ #ifndef CBUNIX setpgrp(own_pid); /* Temporary fix for VAX */ #endif process = NULLPROC; /* Reset all signals to the system defaults. */ #ifdef UDEBUG for (i=SIGHUP; i <= SIGPWR;i++) oldsigs[i] = signal(i,SIG_DFL); #else for (i=SIGHUP; i <= SIGPWR;i++) signal(i,SIG_DFL); #endif } return(process); } /************************/ /**** waitproc ****/ /************************/ /* "waitproc" waits for a specified process to die. For this */ /* routine to work, the specified process must already in */ /* the proc_table. "waitproc" returns the exit status of the */ /* specified process when it dies. */ /* */ long waitproc(process) register struct PROC_TABLE *process; { extern struct PROC_TABLE proc_table[]; int answer; /* Wait around until the process dies. */ if (process->p_flags & LIVING) pause(); if (process->p_flags & LIVING) return (FAILURE); /* Make sure to only return 16 bits so that answer will always */ /* be positive whenever the process of interest really died. */ answer = (process->p_exit & 0xffff); /* Free the slot in the proc_table. */ process->p_flags = 0; return(answer); } /***********************/ /**** account ****/ /***********************/ /* "account" updates entries in /etc/utmp and appends new entries */ /* to the end of /etc/wtmp (assuming /etc/wtmp exists). */ account(state,process,program) int state; register struct PROC_TABLE *process; char *program; /* Name of program in the case of INIT_PROCESSes * otherwise NULL. */ { extern cur_state; struct utmp utmpbuf; register struct utmp *u,*oldu; extern struct utmp *getutid(),*pututline(); extern char *WTMP; FILE *fp; char level(); #ifdef ACCTDEBUG extern char *C(); debug("** account ** state: %d id:%s\n",state,C(&process->p_id[0])); #endif /* Set up the prototype for the utmp structure we want to write. */ u = &utmpbuf; zero(&u->ut_user[0],sizeof(u->ut_user)); zero(&u->ut_line[0],sizeof(u->ut_line)); /* Fill in the various fields of the utmp structure. */ u->ut_id[0] = process->p_id[0]; u->ut_id[1] = process->p_id[1]; u->ut_id[2] = process->p_id[2]; u->ut_id[3] = process->p_id[3]; u->ut_pid = process->p_pid; /* Fill the "ut_exit" structure. */ u->ut_exit.e_termination = (process->p_exit & 0xff); u->ut_exit.e_exit = ((process->p_exit >> 8) & 0xff); u->ut_type = state; time(&u->ut_time); /* See if there already is such an entry in the "utmp" file. */ setutent(); /* Start at beginning of utmp file. */ if ((oldu = getutid(u)) != NULL) { /* Copy in the old "user" and "line" fields to our new structure. */ bcopy(&oldu->ut_user[0],&u->ut_user[0],sizeof(u->ut_user)); bcopy(&oldu->ut_line[0],&u->ut_line[0],sizeof(u->ut_line)); #ifdef ACCTDEBUG debug("New entry in utmp file.\n"); #endif } #ifdef ACCTDEBUG else debug("Replacing old entry in utmp file.\n"); #endif /* Preform special accounting. Insert the special string into the */ /* ut_line array. For INIT_PROCESSes put in the name of the */ /* program in the "ut_user" field. */ switch(state) { case RUN_LVL : u->ut_exit.e_termination = level(cur_state); u->ut_exit.e_exit = level(prior_state); u->ut_pid = n_prev[cur_state]; sprintf(&u->ut_line[0],RUNLVL_MSG,level(cur_state)); break; case BOOT_TIME : sprintf(&u->ut_line[0],"%.12s",BOOT_MSG); break; case INIT_PROCESS : strncpy(&u->ut_user[0],program,sizeof(u->ut_user)); break; default : break; } /* Write out the updated entry to utmp file. */ if (pututline(u) == (struct utmp *)NULL) console("failed write of utmp entry: \"%2.2s\"\n",&u->ut_id[0]); /* Now attempt to add to the end of the wtmp file. Do not create */ /* if it doesn't already exist. ** Note ** This is the reason */ /* "r+" is used instead of "a+". "r+" won't create a file, while */ /* "a+" will. */ if ((fp = fopen(WTMP,"r+")) != NULL) { fseek(fp,0L,2); /* Seek to end of file */ fwrite(u,sizeof(*u),1,fp); fclose(fp); } } /*************************/ /**** prog_name ****/ /*************************/ /* "prog_name" searches for the word or unix path name and */ /* returns a pointer to the last element of the pathname. */ char *prog_name(string) register char *string; { register char *ptr,*ptr2; struct utmp *dummy; /* Used only to get size of ut_user */ static char word[sizeof(dummy->ut_user)+1]; /* Search for the first word skipping leading spaces and tabs. */ while (*string == ' ' || *string == '\t') string++; /* If the first non-space non-tab character is not one allowed in */ /* a word, return a pointer to a null string, otherwise parse the */ /* pathname. */ if (*string != '.' && *string != '/' && *string != '_' && (*string < 'a' || *string > 'z') && (*string < 'A' || * string > 'Z') && (*string < '0' || *string > '9')) return(""); /* Parse the pathname looking forward for '/', ' ', '\t', '\n' or */ /* '\0'. Each time a '/' is found, move "ptr" to one past the */ /* '/', thus when a ' ', '\t', '\n', or '\0' is found, "ptr" will */ /* point to the last element of the pathname. */ for (ptr=string; *string != ' ' && *string != '\t' && *string != '\n' && *string != '\0'; string++) { if (*string == '/') ptr = string+1; } /* Copy out up to the size of the "ut_user" array into "word", */ /* null terminate it and return a pointer to it. */ for (ptr2= &word[0]; ptr2 < &word[sizeof(dummy->ut_user)] && ptr < string;) *ptr2++ = *ptr++; /* Add null to end of string. */ *ptr2 = '\0'; return(&word[0]); } /**************************/ /**** opensyscon ****/ /**************************/ /* "opensyscon" opens stdin, stdout, and stderr, making sure */ /* that their file descriptors are 0, 1, and 2, respectively. */ opensyscon() { register FILE *fp; register int state; #ifndef CBUNIX extern struct termio termio; #else extern struct ttiocb ttiocb; extern struct sgldisc sgldisc; extern struct ttiothcb ttiothcb; #endif extern struct termcb termcb; fclose(stdin); fclose(stdout); fclose(stderr); close(0); close(1); close(2); if ((fp = fopen(SYSCON,"r+")) == NULL) { /* If the open fails, switch back to /dev/systty. */ reset_syscon(); fp = fopen(SYSCON,"r+"); } fdup(fp); fdup(fp); setbuf(fp,NULL); setbuf(stdout,NULL); setbuf(stderr,NULL); /* Make sure the hangup on last close is off. Then restore */ /* the modes that were on syscon when the signal was sent. */ #ifndef CBUNIX termio.c_cflag &= ~HUPCL; fioctl(fp,TCSETA,&termio); fioctl(fp,LDSETT,&termcb); #else ttiocb.ioc_flags &= ~HUPCL; fioctl(fp,TIOCSETP,&ttiocb); fioctl(fp,TIOCSETD,&sgldisc); ttiothcb.ioth_flags |= NOHUP; fioctl(fp,TIOCSETO,&ttiothcb); fioctl(fp,DIOCSETT,&termcb); #endif return; } /********************************/ /**** get_ioctl_syscon ****/ /********************************/ /* "get_ioctl_syscon" retrieves the /dev/syscon settings from */ /* the file "/etc/ioctl.syscon". */ get_ioctl_syscon() { register FILE *fp; #ifndef CBUNIX int iflags,oflags,cflags,lflags,ldisc,cc[8],i; extern struct termio dflt_termio,termio; #else int ispeed,ospeed,erase,kill,flags,ldisc,oflags,i; extern struct ttiocb dflt_ttiocb,ttiocb; extern struct sgldisc dflt_sgldisc,sgldisc; extern struct ttiothcb dflt_other,ttiothcb; #endif int termt,tflgs,vrow; extern struct termcb dflt_trmcb,termcb; /* Read in the previous modes for /dev/syscon from ioctl.syscon. */ if ((fp = fopen(IOCTLSYSCON,"r")) == NULL) reset_syscon(); else { #ifndef CBUNIX i = fscanf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x", &iflags,&oflags,&cflags,&lflags,&ldisc, &cc[0],&cc[1],&cc[2],&cc[3],&cc[4],&cc[5],&cc[6],&cc[7], &termt,&tflgs,&vrow); /* If the file is formatted properly, use the values to initialize */ /* the console terminal condition. */ if (i == 16) { termio.c_iflag = (ushort) iflags; termio.c_oflag = (ushort) oflags; termio.c_cflag = (ushort) cflags; termio.c_lflag = (ushort) lflags; termio.c_line = (char) ldisc; for(i=0; i<8; i++) termio.c_cc[i] = (char) cc[i]; termcb.st_termt = termt; termcb.st_flgs = tflgs; termcb.st_vrow = vrow; if (termcb.st_termt != TERM_NONE && termcb.st_flgs) termcb.st_flgs |= TM_SET; /* If the file is badly formatted, use the default settings. */ } else { bcopy(&dflt_termio,&termio,sizeof(dflt_termio)); bcopy(&dflt_trmcb,&termcb,sizeof(dflt_trmcb)); } #else i = fscanf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x", &ispeed,&ospeed, &erase,&kill,&flags,&ldisc,&oflags,&termt,&tflgs,&vrow); /* If there are are the proper number of arguments, install them. */ if (i == 10) { ttiocb.ioc_ispeed = ispeed; ttiocb.ioc_ospeed = ospeed; ttiocb.ioc_erase = erase; ttiocb.ioc_kill = kill; ttiocb.ioc_flags = flags; sgldisc.sgl_type = ldisc; ttiothcb.ioth_flags = oflags; termcb.st_termt = termt; termcb.st_flgs = tflgs; termcb.st_vrow = vrow; if (termcb.st_termt != TERM_NONE && termcb.st_flgs) termcb.st_flgs |= TM_SET; /* Otherwise use the defaults. */ } else { bcopy(&dflt_ttiocb,&ttiocb,sizeof(struct ttiocb)); bcopy(&dflt_sgldisc,&sgldisc,sizeof(struct sgldisc)); bcopy(&dflt_other,&ttiothcb,sizeof(struct ttiothcb)); bcopy(&dflt_trmcb,&termcb,sizeof(dflt_trmcb)); } #endif fclose(fp); } } /****************************/ /**** reset_syscon ****/ /****************************/ /* "reset_syscon" relinks /dev/syscon to /dev/systty and puts */ /* the default ioctl setting back into /etc/ioctl.syscon and */ /* the incore arrays. */ reset_syscon() { register FILE *fp; #ifndef CBUNIX extern struct termio dflt_termio,termio; #else extern struct ttiocb dflt_ttiocb,ttiocb; extern struct sgldisc dflt_sgldisc,sgldisc; extern struct ttiothcb dflt_other,ttiothcb; #endif extern struct termcb dflt_trmcb,termcb; unlink(SYSCON); link(SYSTTY,SYSCON); umask(~0644); fp = fopen(IOCTLSYSCON,"w"); #ifndef CBUNIX bcopy(&dflt_termio,&termio,sizeof(struct termio)); bcopy(&dflt_trmcb,&termcb,sizeof(struct termcb)); fprintf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n", termio.c_iflag,termio.c_oflag,termio.c_cflag,termio.c_lflag, termio.c_line, termio.c_cc[0],termio.c_cc[1],termio.c_cc[2], termio.c_cc[3], termio.c_cc[4],termio.c_cc[5], termio.c_cc[6], termio.c_cc[7], termcb.st_termt,termcb.st_flgs,termcb.st_vrow); #else bcopy(&dflt_ttiocb,&ttiocb,sizeof(struct ttiocb)); bcopy(&dflt_sgldisc,&sgldisc,sizeof(struct sgldisc)); bcopy(&dflt_other,&ttiothcb,sizeof(struct ttiothcb)); bcopy(&dflt_trmcb,&termcb,sizeof(struct termcb)); fprintf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n", ttiocb.ioc_ispeed, ttiocb.ioc_ospeed, ttiocb.ioc_erase, ttiocb.ioc_kill, ttiocb.ioc_flags, sgldisc.sgl_type, ttiothcb.ioth_flags, termcb.st_termt, termcb.st_flgs, termcb.st_vrow); #endif fclose(fp); sync(); umask(0); } /**************************/ /**** save_ioctl ****/ /**************************/ /* Get the ioctl state of SYSCON and write it into IOCTLSYSCON. */ save_ioctl() { register FILE *fp; register struct PROC_TABLE *process; #ifndef CBUNIX extern struct termio termio,dflt_termio; #else extern struct ttiocb ttiocb,dflt_ttiocb; extern struct sgldisc sgldisc,dflt_sgldisc; extern struct ttiothcb ttiothcb,dflt_other; #endif extern struct termcb termcb,dflt_trmcb; extern struct PROC_TABLE *efork(); extern int errno,childeath(); signal(SIGCLD,SIG_DFL); while ((process = efork(NULLPROC,NOCLEANUP)) == NO_ROOM) timer(2); signal(SIGCLD,childeath); /* If we are the child, open /dev/syscon, do an ioctl to get the */ /* modes, and write them out to "/etc/ioctl.syscon". */ if (process == NULLPROC) { if ((fp = fopen(SYSCON,"w")) == NULL) console("Unable to open %s\n",SYSCON); else { /* Turn off the HUPCL bit, so that carrier stays up after the */ /* shell is killed. */ #ifndef CBUNIX if (fioctl(fp,TCGETA,&termio) != FAILURE) { termio.c_cflag &= ~HUPCL; fioctl(fp,TCSETA,&termio); } fioctl(fp,LDGETT,&termcb); #else if (fioctl(fp,TIOCGETP,&ttiocb) != FAILURE) { ttiocb.ioc_flags &= ~HUPCL; fioctl(fp,TIOCSETP,&ttiocb); } fioctl(fp,TIOCGETD,&sgldisc); fioctl(fp,TIOCGETO,&ttiothcb); fioctl(fp,DIOCSETT,&termcb); #endif } fclose(fp); /* Write the state of "/dev/syscon" into "/etc/ioctl.syscon" so */ /* that it will be remembered across reboots. */ umask(~0644); if ((fp = fopen(IOCTLSYSCON,"w")) == NULL) console("Can't open %s. errno: %d\n",IOCTLSYSCON,errno); else { #ifndef CBUNIX fprintf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n", termio.c_iflag,termio.c_oflag,termio.c_cflag, termio.c_lflag,termio.c_line,termio.c_cc[0], termio.c_cc[1],termio.c_cc[2],termio.c_cc[3], termio.c_cc[4],termio.c_cc[5],termio.c_cc[6], termio.c_cc[7],termcb.st_termt,termcb.st_flgs, termcb.st_vrow); #else fprintf(fp,"%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n", ttiocb.ioc_ispeed,ttiocb.ioc_ospeed, ttiocb.ioc_erase,ttiocb.ioc_kill, ttiocb.ioc_flags,sgldisc.sgl_type, ttiothcb.ioth_flags,termcb.st_termt, termcb.st_flgs,termcb.st_vrow); #endif fclose(fp); sync(); } exit(0); } /* If we are the parent, wait for the child to die. */ else while (waitproc(process) == FAILURE); } /***********************/ /**** console ****/ /***********************/ /* "console" forks a child if it finds that it is the main "init" */ /* and outputs the requested message to the system console. */ /* Note that the number of arguments passed to "console" is */ /* determined by the print format. */ console(format,arg1,arg2,arg3,arg4) char *format; int arg1,arg2,arg3,arg4; { register struct PROC_TABLE *process; extern int own_pid; extern long waitproc(); char outbuf[BUFSIZ]; extern int childeath(); #ifdef UDEBUG extern int abort(); #endif /* Are with the original "init"? If so, fork a child to do the */ /* printing for us. */ if (own_pid == SPECIALPID) { signal(SIGCLD,SIG_DFL); while ((process = efork(NULLPROC,NOCLEANUP)) == NO_ROOM) timer(5); signal(SIGCLD,childeath); if (process == NULLPROC) { #ifdef UDEBUG signal(SIGUSR1,abort); signal(SIGUSR2,abort); #endif /* Close the standard descriptors and open the system console. */ opensyscon(); setbuf(stdout,&outbuf[0]); /* Output the message to the console. */ fprintf(stdout,"\nINIT: "); fprintf(stdout,format,arg1,arg2,arg3,arg4); fflush(stdout); exit(0); /* The parent waits for the message to complete. */ } else while(waitproc(process) == FAILURE); /* If we are some other "init", then just print directly to the */ /* standard output. */ } else { opensyscon(); setbuf(stdout,&outbuf[0]); fprintf(stdout,"\nINIT: "); fprintf(stdout,format,arg1,arg2,arg3,arg4); fflush(stdout); } #ifdef ACCTDEBUG debug(format,arg1,arg2,arg3,arg4); #endif } /**************************/ /**** error_time ****/ /**************************/ /* "error_time" keeps a table of times, one for each time of */ /* error that it handles. If the current entry is 0 or the */ /* elapsed time since the last error message is large enough, */ /* "error_time" returns TRUE, otherwise it returns FALSE. */ error_time(type) register int type; { long curtime; extern struct ERRORTIMES err_times[]; time(&curtime); if (err_times[type].e_time == 0 || (curtime - err_times[type].e_time >= err_times[type].e_max)) { err_times[type].e_time = curtime; return(TRUE); } else return(FALSE); } /*********************/ /**** timer ****/ /*********************/ /* "timer" is a substitute for "sleep" which uses "alarm" and */ /* "pause". */ timer(waitime) register int waitime; { extern union WAKEUP wakeup; extern int time_up; setimer(waitime); while (time_up == FALSE) pause(); } /***********************/ /**** setimer ****/ /***********************/ int time_up; /* Flag set to TRUE by alarm interupt routine * each time an alarm interupt takes place. */ setimer(timelimit) int timelimit; { alarm(timelimit); time_up = FALSE; } /********************/ /**** zero ****/ /********************/ zero(adr,size) register char *adr; register int size; { while (size--) *adr++ = '\0'; } /************************/ /**** userinit ****/ /************************/ /* Routine to handle requests from users to main init running as */ /* process 1. */ userinit(argc,argv) int argc; char **argv; { FILE *fp; char *ln; extern char *ttyname(); int init_signal; int saverr; extern int errno; extern char *SYSTTY,*SYSCON; extern int own_pid; /* We are a user invoked init. Is there an argument and is it */ /* a single character? If not, print usage message and quit. */ if (argc != 2 || *(*++argv +1) != '\0') { fprintf(stderr,"Usage: init [0123456SsQqabc]\n"); exit(0); } else switch (**argv) { case 'Q' : case 'q' : init_signal = LVLQ; break; case '0' : init_signal = LVL0; break; case '1' : init_signal = LVL1; break; case '2' : init_signal = LVL2; break; case '3' : init_signal = LVL3; break; case '4' : init_signal = LVL4; break; case '5' : init_signal = LVL5; break; case '6' : init_signal = LVL6; break; /* If the request is to switch to single user mode, make sure */ /* that this process is talking to a legal teletype line and that */ /* /dev/syscon is linked to this line. */ case 'S' : case 's' : ln = ttyname(0); /* Get the name of tty */ if (*ln == '\0') { fprintf(stderr,"Standard input not a tty line\n"); exit(1); } if (strcmp(ln,SYSCON) != 0) { /* Leave a message if possible on system console saying where */ /* /dev/syscon is currently connected. */ if ((fp = fopen(SYSTTY,"r+")) != NULL) { fprintf(fp,"\n**** SYSCON CHANGED TO %s ****\n", ln); fclose(fp); } /* Unlink /dev/syscon and then relink it to the current line. */ if (unlink(SYSCON) == FAILURE) { perror("Can't unlink /dev/syscon\n"); exit(1); } if (link(ln,SYSCON) == FAILURE) { saverr = errno; fprintf(stderr,"Can't link /dev/syscon to %s",ln); errno = saverr; perror(": "); link(SYSTTY,SYSCON); /* Try to leave a syscon */ exit(1); } } init_signal = SINGLE_USER; break; case 'a' : init_signal = LVLa; break; case 'b' : init_signal = LVLb; break; case 'c' : init_signal = LVLc; break; /* If the argument was invalid, print the usage message. */ default : fprintf(stderr,"Usage: init [01234567SQabc]\n"); exit(1); } /* Now send signal to main init and then exit. */ if (kill(SPECIALPID,init_signal) == FAILURE) { fprintf(stderr,"Could not send signal to \"init\".\n"); exit(1); } else exit(0); } /*********************/ /**** bcopy ****/ /*********************/ #ifdef vax bcopy(from,to,size) { asm(" movc3 12(ap),*4(ap),*8(ap) "); } #else bcopy(from,to,size) register char *from,*to; register int size; { while(size--) *to++ = *from++; } #endif /********************/ /**** fdup ****/ /********************/ /* @(#)fdup.c 3.1 */ FILE *fdup(fp) register FILE *fp; { register int newfd; register char *mode; /* Dup the file descriptor for the specified stream and then */ /* convert it to a stream pointer with the modes of the original */ /* stream pointer. */ if ((newfd = dup(fileno(fp))) != FAILURE) { /* Determine the proper mode. If the old file was _IORW, then */ /* use the "r+" option, if _IOREAD, the "r" option, or if _IOWRT */ /* the "w" option. Note that since none of these force an lseek */ /* by "fdopen", the dupped file pointer will be at the same spot */ /* as the original. */ if (fp->_flag & _IORW) mode = "r+"; else if (fp->_flag & _IOREAD) mode = "r"; else if (fp->_flag & _IOWRT) mode = "w"; /* Something is wrong, close dupped descriptor and return NULL. */ else { close(newfd); return(NULL); } /* Now have fdopen finish the job of establishing a new file */ /* pointer. */ return(fdopen(newfd,mode)); } else return(NULL); } #ifdef UDEBUG /*************************/ /**** drop_core ****/ /*************************/ drop_core(reason) char *reason; { struct PROC_TABLE *efork(); FILE *fp; extern char *CORE_RECORD; extern int childeath(); signal(SIGCLD,SIG_DFL); if (efork(NULLPROC,0) != NULLPROC) return; signal(SIGCLD,childeath); /* Tell user where core is going to be. */ if ((fp = fopen(CORE_RECORD,"a+")) == NULL) { console("Couldn't open \"%s\".\n",CORE_RECORD); } else { fprintf(fp,"core.%05d: \"%s\"\n",getpid(),reason); fclose(fp); } signal(SIGIOT,SIG_DFL); abort(); } #endif #ifdef DEBUGGER /*********************/ /**** debug ****/ /*********************/ debug(format,arg1,arg2,arg3,arg4,arg5,arg6) char *format; int arg1,arg2,arg3,arg4,arg5,arg6; { register FILE *fp; register int errnum; extern int errno; if ((fp = fopen(DBG_FILE,"a+")) == NULL) { errnum = errno; console("Can't open \"%s\". errno: %d\n",DBG_FILE,errnum); return; } fprintf(fp,format,arg1,arg2,arg3,arg4,arg5,arg6); fclose(fp); } /*****************/ /**** C ****/ /*****************/ char *C(id) register char *id; { static char answer[12]; register char *ptr; register int i; for (i=4,ptr = &answer[0]; --i >= 0;id++) { if ( isprint(*id) == 0 ) { *ptr++ = '^'; *ptr++ = *id + 0100; } else *ptr++ = *id; } *ptr++ = '\0'; return(&answer[0]); } #endif /* LIBRARY ROUTINES ** with no `/etc/utmp.lck' */ /* @(#)getut.c 1.1 */ /* Routines to read and write the /etc/utmp file. */ /* */ #define MAXFILE 79 /* Maximum pathname length for "utmp" file */ #ifdef DEBUG #undef UTMP_FILE #define UTMP_FILE "utmp" #endif static int fd = -1; /* File descriptor for the utmp file. */ static char utmpfile[MAXFILE+1] = UTMP_FILE; /* Name of the current * "utmp" like file. */ static long loc_utmp; /* Where in "utmp" the current "ubuf" was * found. */ static struct utmp ubuf; /* Copy of last entry read in. */ /* "getutent" gets the next entry in the utmp file. */ struct utmp *getutent() { extern int fd; extern char utmpfile[]; extern struct utmp ubuf; extern long loc_utmp,lseek(); extern int errno; register char *u; register int i; struct stat stbuf; /* If the "utmp" file is not open, attempt to open it for * reading. If there is no file, attempt to create one. If * both attempts fail, return NULL. If the file exists, but * isn't readable and writeable, do not attempt to create. */ if (fd < 0) { /* Make sure file is a multiple of 'utmp' entries long */ if (stat(utmpfile,&stbuf) == 0) { if((stbuf.st_size % sizeof(struct utmp)) != 0) { unlink(utmpfile); } } if ((fd = open(utmpfile, O_RDWR|O_CREAT, 0644)) < 0) { /* If the open failed for permissions, try opening it only for * reading. All "pututline()" later will fail the writes. */ if (errno == EACCES && (fd = open(utmpfile, O_RDONLY)) < 0) return(NULL); } } /* Try to read in the next entry from the utmp file. */ if (read(fd,&ubuf,sizeof(ubuf)) != sizeof(ubuf)) { /* Make sure ubuf is zeroed. */ for (i=0,u=(char *)(&ubuf); iut_type) { /* Do not look for an entry if the user sent us an EMPTY entry. */ case EMPTY: return(NULL); /* For RUN_LVL, BOOT_TIME, OLD_TIME, and NEW_TIME entries, only */ /* the types have to match. If they do, return the address of */ /* internal buffer. */ case RUN_LVL: case BOOT_TIME: case OLD_TIME: case NEW_TIME: if (entry->ut_type == ubuf.ut_type) return(&ubuf); break; /* For INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, and DEAD_PROCESS */ /* the type of the entry in "ubuf", must be one of the above and */ /* id's must match. */ case INIT_PROCESS: case LOGIN_PROCESS: case USER_PROCESS: case DEAD_PROCESS: if (((type = ubuf.ut_type) == INIT_PROCESS || type == LOGIN_PROCESS || type == USER_PROCESS || type == DEAD_PROCESS) && ubuf.ut_id[0] == entry->ut_id[0] && ubuf.ut_id[1] == entry->ut_id[1] && ubuf.ut_id[2] == entry->ut_id[2] && ubuf.ut_id[3] == entry->ut_id[3]) return(&ubuf); break; /* Do not search for illegal types of entry. */ default: return(NULL); } } } while (getutent() != NULL); /* Return NULL since the proper entry wasn't found. */ return(NULL); } /* "getutline" searches the "utmp" file for a LOGIN_PROCESS or * USER_PROCESS with the same "line" as the specified "entry". */ struct utmp *getutline(entry) register struct utmp *entry; { extern struct utmp ubuf,*getutent(); register struct utmp *cur; /* Start by using the entry currently incore. This prevents */ /* doing reads that aren't necessary. */ cur = &ubuf; do { /* If the current entry is the one we are interested in, return */ /* a pointer to it. */ if (cur->ut_type != EMPTY && (cur->ut_type == LOGIN_PROCESS || cur->ut_type == USER_PROCESS) && strncmp(&entry->ut_line[0], &cur->ut_line[0],sizeof(cur->ut_line)) == 0) return(cur); } while ((cur = getutent()) != NULL); /* Since entry wasn't found, return NULL. */ return(NULL); } /* "pututline" writes the structure sent into the utmp file. */ /* If there is already an entry with the same id, then it is */ /* overwritten, otherwise a new entry is made at the end of the */ /* utmp file. */ struct utmp *pututline(entry) struct utmp *entry; { register int i,type; int fc; struct utmp *answer; struct stat statbuf; extern long time(); extern struct utmp ubuf; extern long loc_utmp,lseek(); extern struct utmp *getutid(); extern int fd,errno; struct utmp tmpbuf; /* Copy the user supplied entry into our temporary buffer to */ /* avoid the possibility that the user is actually passing us */ /* the address of "ubuf". */ tmpbuf = *entry; getutent(); if (fd < 0) { #ifdef ERRDEBUG gdebug("pututline: Unable to create utmp file.\n"); #endif return((struct utmp *)NULL); } /* Make sure file is writable */ if ((fc=fcntl(fd, F_GETFL, NULL)) == -1 || (fc & O_RDWR) != O_RDWR) { return((struct utmp *)NULL); } /* Find the proper entry in the utmp file. Start at the current */ /* location. If it isn't found from here to the end of the */ /* file, then reset to the beginning of the file and try again. */ /* If it still isn't found, then write a new entry at the end of */ /* the file. (Making sure the location is an integral number of */ /* utmp structures into the file incase the file is scribbled.) */ if (getutid(&tmpbuf) == NULL) { #ifdef ERRDEBUG gdebug("First getutid() failed. fd: %d",fd); #endif setutent(); if (getutid(&tmpbuf) == NULL) { #ifdef ERRDEBUG loc_utmp = lseek(fd, 0L, 1); gdebug("Second getutid() failed. fd: %d loc_utmp: %ld\n",fd,loc_utmp); #endif fcntl(fd, F_SETFL, fc | O_APPEND); } else { lseek(fd, -(long)sizeof(struct utmp), 1); } } else { lseek(fd, -(long)sizeof(struct utmp), 1); } /* Write out the user supplied structure. If the write fails, */ /* then the user probably doesn't have permission to write the */ /* utmp file. */ if (write(fd,&tmpbuf,sizeof(tmpbuf)) != sizeof(tmpbuf)) { #ifdef ERRDEBUG gdebug("pututline failed: write-%d\n",errno); #endif answer = (struct utmp *)NULL; } else { /* Copy the user structure into ubuf so that it will be up to */ /* date in the future. */ ubuf = tmpbuf; answer = &ubuf; #ifdef ERRDEBUG gdebug("id: %c%c loc: %x\n",ubuf.ut_id[0],ubuf.ut_id[1], ubuf.ut_id[2],ubuf.ut_id[3],loc_utmp); #endif } fcntl(fd, F_SETFL, fc); return(answer); } /* "setutent" just resets the utmp file back to the beginning. */ setutent() { register char *ptr; register int i; extern int fd; extern struct utmp ubuf; extern long loc_utmp; if (fd != -1) lseek(fd,0L,0); /* Zero the stored copy of the last entry read, since we are */ /* resetting to the beginning of the file. */ for (i=0,ptr=(char*)&ubuf; i < sizeof(ubuf);i++) *ptr++ = '\0'; loc_utmp = 0L; } /* "endutent" closes the utmp file. */ endutent() { extern int fd; extern long loc_utmp; extern struct utmp ubuf; register char *ptr; register int i; if (fd != -1) close(fd); fd = -1; loc_utmp = 0; for (i=0,ptr= (char *)(&ubuf); i < sizeof(ubuf);i++) *ptr++ = '\0'; } /* "utmpname" allows the user to read a file other than the */ /* normal "utmp" file. */ utmpname(newfile) char *newfile; { extern char utmpfile[]; /* Determine if the new filename will fit. If not, return 0. */ if (strlen(newfile) > MAXFILE) return (0); /* Otherwise copy in the new file name. */ else strcpy(&utmpfile[0],newfile); /* Make sure everything is reset to the beginning state. */ endutent(); return(1); } #ifdef ERRDEBUG #include gdebug(format,arg1,arg2,arg3,arg4,arg5,arg6) char *format; int arg1,arg2,arg3,arg4,arg5,arg6; { register FILE *fp; register int errnum; extern int errno; if ((fp = fopen("/etc/dbg.getut","a+")) == NULL) return; fprintf(fp,format,arg1,arg2,arg3,arg4,arg5,arg6); fclose(fp); } #endif