USG_PG3/usr/source/cmd2/init.c
#define CSPEED (07<<8)|07
#define XTABS 02
#define LCASE 04
#define ECHO 010
#define CRMOD 020
#define ANYP 0300
#define CR1 010000
#define NPROC 50
#define NOFILE 15
#define SINGLE 7
#define REBOOT ~7
#define BUFSIZ 512
#define NTOKEN 5
#define NARGS 20
#define IPROCSZ 3
#define SIGHUP 1
#define LVL0 2
#define LVL1 3
#define LVL2 4
#define LVL3 5
#define LVL4 6
#define LVL5 7
#define LVL6 8
#define KILL 9
#define SINLVL 10
#define SIGSEG 11
#define SIGSYS 12
#define SIGPIPE 13
#define SIGCLK 14
#define SIGWRN 15
#define ARESPAWN 21
#define AONCE 22
#define AWAIT 23
#define AOFF 24
#define ABOOT 25
#define ABOOTWAIT 26
#define ADEMAND 27
struct entryln {
char *lid;
char *rlevel;
char *action;
char *shellcm;
char **initcm;
}l;
struct iproc {
int id;
char flip;
char flop;
int ipid;
} iproc[NPROC];
struct stimulus {
char *keystr;
int numb;
};
struct utmp {
char name[8];
int line;
int time[2];
int upid;
}utmp;
int catchit,hboot,il,iq,rb,status;
int curlvl,lvltag,wrncnt;
char lbuf[BUFSIZ+1];
char *inarg[NARGS+1];
char *CTTY "/dev/ln00";
char *SUSER "/bin/sh";
char *LINES "/etc/lines";
char *GETTY "/etc/getty";
char *SHELL "/bin/sh";
char *INIT "/etc/init";
char *UTMPF "/etc/utmp";
char *WTMPF "/etc/wtmp";
char *XINIT "/etc/xinit";
char set 1;
struct stimulus siggrp[] {
"0",LVL0,
"1",LVL1,
"2",LVL2,
"3",LVL3,
"4",LVL4,
"5",LVL5,
"6",LVL6,
"a",SIGSEG,
"b",SIGSYS,
"c",SIGPIPE,
0
};
struct stimulus action[] {
"respawn",ARESPAWN,
"once",AONCE,
"wait",AWAIT,
"off",AOFF,
"boot",ABOOT,
"bootwait",ABOOTWAIT,
"ondemand",ADEMAND,
0
};
char *tabu[]{
"xx",
"RL",
"!B",
"OT",
"NT",
0
};
int cpackit[]{
CSPEED,
0,
ANYP+XTABS+LCASE+CRMOD+ECHO+CR1
};
main()
{
register i,runlvl,pid;
setsig();
lvltag = 0;
i = creat(UTMPF,0644);
copy("RL",&utmp.line); /*initialize "RL" slot in UTMPF*/
write(i,&utmp,16);
close(i);
acctg(-10,"**BOOT**"); /*record boot time*/
if((i=getcsw()) == SINGLE) single();
if(lvltag == 0){
i = getcsw() + 2; /* run level = signal-2 */
if(i>=LVL0 && i<=LVL6) lvltag = i;
else lvltag = LVL0;
}
/*
* Main process path---steady state condition.
*/
for(hboot=0;;) {
setexit();
sync();
runlvl = 0;
if(lvltag){
runlvl = lvltag;
lvltag = 0;
}
switch(runlvl){
case 0: /*death of a child -rescan lines file*/
case SIGHUP: /*immediate scan of lines file*/
runlvl = curlvl;
break;
default:
if(runlvl>=LVL0 && runlvl<=LVL6)
if(runlvl != curlvl)
if(cleanup(runlvl)) continue; /*err in cleanup*/
}
bringup(runlvl);
sync();
if(lvltag && lvltag != SINLVL) continue;
for(;;){
pid = wait(&status);
if(pid == -1){ /*a signal was caught*/
if(lvltag == SINLVL) {
lvltag = 0;
continue;
}
break;
}
if((i=find(pid,0))<0)continue; /*not in proc table*/
rmproc(i);
break; /* iproc[i].flip == 0 ---rescan lines file and
start up entry line process */
}
}
}
/*
* If the run level is between LVL0 and LVL6,
* cleanup will post a 20 second warning to all processes
* running at a run level that is no longer current. If
* the warned process does not gracefully exit after 20 seconds
* have elapsed,the errant process is forcibly terminated.
* If runlvl is null then all processes in INIT's process
* table will be posted a 20 second warning and subsequently will
* terminate or be terminated.
* No other runlvls are possible.
*/
cleanup(runlvl)
{
register int *wptr;
register char *sptr;
register pid;
int i,j;
int warnid[NPROC];
if(openlns()) {
if(runlvl) single();
return(1); /*error return*/
}
wrncnt = 0;
wptr = &warnid;
zero(wptr,NPROC);
loop:
while(j=getline()){
if(j<0)continue;
zmap();
if((il=find(l.lid,1)) < 0) continue;
if((l.rlevel == 0) && runlvl) continue; /*null field case*/
if(runlvl){ /*check run level*/
for(sptr=l.rlevel; *sptr; sptr++) {
i = *sptr;
i =& 0177;
if((i=stoi(&i,&siggrp)) == 0) {
console("Illegal rlevel at %s\n",l.lid);
goto loop;
}
if(i == runlvl) goto loop;
}
}
if(kill(iproc[il].ipid,SIGWRN) < 0)
zero(&iproc[il].id,IPROCSZ);
else{
wrncnt++;
*wptr++ = iproc[il].ipid;
iproc[il].flip = 20;
}
}
alarm(20);
while(wrncnt) {
pid = wait(&status);
if((pid != -1) && ((j=find(pid,0)) != -1)) {
if(iproc[j].flip == 20 && wrncnt) --wrncnt;
rmproc(j);
}
}
alarm(0);
for(wptr = &warnid; (*wptr && (wptr <= wptr[NPROC])); wptr++) {
if((j=find(*wptr,0)) != -1) killproc(j);
}
close(0);
return(0); /*successful cleanup*/
}
/*
* Legal runlvls honored by bringup are those falling
* between LVL0 and LVL6 and between SIGSEG and SIGPIPE.
* All others are invalid.
*/
bringup(runlvl)
{
int i;
register j;
register char *sptr;
if(openlns()) {
single();
return(1);
}
set =* -1;
while(j=getline()){
if(j<0)continue; /* bad entry - ignore */
if((iq = find(0,0)) < 0) { /*index for free slot*/
console("INIT process table filled(> %d)\n",NPROC);
close(0);
sleep(20);
return(0);
}
zmap(); /* zero out pointers pointing to nulls */
il = find(l.lid,1); /* index for this process */
if(il >= 0){ /*process exists*/
iproc[il].flop = set;
if(nullchk()) continue;
}
else{ /*process not in iproc table*/
if(nullchk()){
l.action = "respawn";
copy(GETTY,&lbuf[4]); /*save l.lid ref*/
for(j=4;lbuf[j];j++); /*find end of str*/
lbuf[j++] = ' '; /*pad w/blank*/
copy("ln",&lbuf[j]); j =+ 2;
copy(l.lid,&lbuf[j]); /*append line id*/
l.initcm = &lbuf[4];
dfork(iq);
continue;
}
}
if(l.rlevel){ /*run level dependent entry*/
for(sptr = l.rlevel; *sptr; sptr++) {
i = *sptr;
i =& 0177;
if((i=stoi(&i,&siggrp)) == 0) {
console("Illegal rlevel at %s\n",l.lid);
break;
}
if(i == runlvl) goto ground;
}
if(il>=0 && runlvl>=LVL0 && runlvl<=LVL6) wrnkill(il);
continue;
}
ground:
if((i=stoi(l.action,&action)) == 0) {
console("Illegal action at %s\n",l.lid);
continue;
}
aloop(i,runlvl);
}
close(0);
for(j=0;j<NPROC;j++){ /*remove processes deleted from lines file*/
if(iproc[j].flop && (iproc[j].flop != set))
wrnkill(j);
}
if(runlvl>=LVL0 && runlvl<=LVL6 && curlvl != runlvl){
acctg(-runlvl,"RUNLEVEL"); /*record run level*/
curlvl = runlvl; /* reset the current level */
}
if(hboot == 0) hboot = 1;
if(catchit) {/* catches secondary child deaths */
catchit = 0;
reset();
}
return(0);
}
/*
* Interpret action field and perform the predefined
* actions.
*/
aloop(actnum,runlvl)
{
register i;
switch(actnum) {
case ADEMAND:
case ARESPAWN: /* start the process and do not wait for
its completion */
if(il<0) dfork(iq); /* create a process */
return; /* ignore--process is alive */
case AONCE: /* start the process once and clear it from
init's process table when it dies */
if(curlvl == runlvl) return;
dfork(iq);
return;
case AWAIT: /* start the process and wait for it! */
if(curlvl == runlvl) return;
waitfor(iq);
return;
case AOFF: /* Ignore this process and continue on.
if process is active then terminate it
and clear the entry from the proc table.
A 20 second warning is posted before it
is forcibly terminated. */
if(il < 0) return;
wrnkill(il);
return;
case ABOOT: /* start this process only at boot time
ignore on any subsequent scan of this */
if(hboot == 0) dfork(iq);
return;
case ABOOTWAIT: /*same as ABOOT but wait for it*/
if(hboot == 0) waitfor(iq);
return;
}
}
/*
* Wait for the process to complete
*/
waitfor(enum)
{
dfork(enum);
if(waitproc(iproc[enum].ipid)) rmproc(enum);
}
/*
* Create/re-create processes via a fork.
* Parent process stores in the proc table the process id of the
* child and the entry tag(lid).
* The parent then returns without waiting for the child to complete.
*/
dfork(enum)
{
register char **reserve;
register char *atty;
register type;
type = -1;
if(enum < 0) {
console("%d is an invalid iproc table entry\n",enum);
return;
}
if(l.shellcm) type = 0;
if(l.initcm){
type = 2;
getarg();
if(cmp(*l.initcm,GETTY)) {
for(reserve=tabu;*reserve;reserve++){
if(cmp(l.lid,*reserve) == 0) continue;
console("line id %s reserved\n",l.lid);
return;
}
type = 1;
}
}
copy(l.lid,&iproc[enum].id);
iproc[enum].flip = 0;
iproc[enum].flop = set;
if((iproc[enum].ipid = fork()) == 0) {
close(0);close(1);close(2);
if(l.shellcm) shcmnd();
if(l.initcm) incmnd();
exit(1);
}
switch(type){
case 0:
acctg(enum,l.shellcm);
break;
case 1:
acctg(enum,1);
break;
case 2:
acctg(enum,*l.initcm);
}
}
/*
* The shell command interpreter is invoked with the
* '-c' option.
*/
shcmnd()
{
register i;
if((i=fork()) == 0) {
open("/dev/null",0);
open(CTTY,1);
stty(1,cpackit);
execl("/bin/sh","-","-c",l.shellcm,0);
console("%s not executed\n",l.shellcm);
sleep(20);
exit(1);
}
waitproc(i);
}
/*
* A check is performed on the first character of an argument field
* to see if the standard input and output is to be changed.
* If it is then a change is initiated and the process is
* executed. Incmnd assumes that the process and its arguments
* are in a vector beginning at inarg.
*/
incmnd()
{
register char **k,*infile,*outfile;
k = l.initcm;
while(*k) {
switch(**k) {
case '<' : /* change standard input */
if(k[0][1] == 0){
*k++ = 0;
infile = *k;
}
else infile = ++(*k);
*k++ = 0;
close(0);
if((open(infile,2)) < 0){
console("bad input file %s:%s\n",
l.lid,infile);
sleep(20);
exit(1);
}
break;
case '>' : /* change standard output */
if(k[0][1] == 0){
*k++ = 0;
outfile = *k;
}
else outfile = ++(*k);
*k++ = 0;
close(1);
switch(open(outfile,2)) {
case 0:
dup(0);
close(0);
break;
case -1:
console("bad out file %s:%s\n",
l.lid,outfile);
sleep(20);
exit(1);
}
break;
default :
k++;
}
}
execv(*l.initcm,l.initcm);
console("%s not executed\n",*l.initcm);
sleep(20);
}
/*
* Invoke single user mode on the console tty.
*/
single()
{
register i,j;
curlvl = rb = 0;
if(lvltag) clrsys();
acctg(-1,"RUNLEVEL"); /*single user recording*/
for(;;) {
console("SINGLE USER MODE\n");
if((rb=fork()) == 0) {
close(0);close(1);close(2);
open(CTTY,2);
dup(0);
stty(1,cpackit);
execl(SUSER,"-",0);
console("Exec of %s failed\n",SUSER);
exit(1);
}
while(rb != (j=wait(&status))) {
if(j == -1){
if(lvltag>=LVL0 && lvltag <= LVL6){
kill(rb,KILL);
if(hboot) reset();
return;
}
lvltag = 0;
}
}
if((i=bus()) != SINGLE && i != REBOOT){
i =+ 2;
if(i>=LVL0 && i<=LVL6) lvltag = i;
else lvltag = LVL0;
if(hboot) reset();
return; /*valid only at boot time*/
}
}
}
/*
* The bus signal (10) is trapped to this function.
* The console switches are checked and either the
* single user or reboot sequence is started up.
*/
bus()
{
register j;
register struct iproc *p;
signal(lvltag=SINLVL,bus);
switch(j=getcsw()){
case SINGLE:
if(curlvl) single();
break; /*if already in single user*/
case REBOOT:
if(curlvl){
console("Not single user-reboot disallowed\n");
break;
}
clrsys();
if(rb)kill(rb,KILL); /*kill SU console tty*/
console("Overlay with %s\n",XINIT);
sleep(5); /*delay for switch setting chng*/
execl(XINIT,XINIT,0);
console("Overlay with %s failed\n",XINIT);
if(rb == 0) single();
break;
}
return(j);
}
/*
* Record the accounting information on all processes
* when they spawn and die. the utmpf file will always have
* an instantaneous record of process activities; while the wtmpf
* file(if it is turned on) will contain the running history of
* the process activities from the time WTMPF file was turned on.
* ENUM MARKER REASON
* >=0 0 dead process
* >=0 1 line process
* >=0 string non-line process
* -1 RUNLEVEL single user
* -(2->8) RUNLEVEL run levels
* -10 **BOOT** hardware boot
*/
acctg(enum,marker)
char *marker;
{
register rw,k;
register struct utmp *p;
int cbuf[256];
char *cp;
int key;
zero(&utmp,(sizeof utmp)/2);
switch(marker){
default: /*non-line process accounting*/
cp = marker;
while(*cp) /*strip off leading pathnames*/
if(*cp++ == '/') marker = cp;
copy(marker,utmp.name);
if(enum < 0){
if(enum == -10){
copy("!B",&key);
utmp.upid = 0;
}
else{ /*(-1,-8)*/
copy("RL",&key);
if(enum != -1)
enum = -(enum) - 2;
utmp.upid = enum;
}
break;
}
case 1: /*line process accounting*/
utmp.upid = iproc[enum].ipid;
case 0: /*dead process accounting*/
key = iproc[enum].id;
}
utmp.line = key;
time(utmp.time);
if((rw=open(UTMPF,2)) < 0) goto out;
while((k=read(rw,&cbuf,512)) > 0){
cp = k; /*save original count*/
for(p = &cbuf;(k =- 16) >= 0; p++){ /*does it exist?*/
if(p->line != key) continue;
seek(rw,-(k+16),1);
write(rw,&utmp,16);
goto out;
}
if(cp < 512) break; /*entire utmpf file examined*/
}
seek(rw,0,0);
while((k=read(rw,&cbuf,512)) > 0){
cp = k; /*save original count*/
for(p = &cbuf;(k =- 16)>=0; p++){ /*look for empty slot*/
if(p->line != 0) continue;
seek(rw,-(k+16),1);
write(rw,&utmp,16);
goto out;
}
if(cp < 512) break;
}
if(k <= 0) write(rw,&utmp,16); /* boundary case conditions
* where '<' implies the end
* of the file but < 512
*/
out:
close(rw);
if((rw=open(WTMPF,1)) >= 0) {
seek(rw,0,2);
write(rw,&utmp,16);
close(rw);
}
sync();
}
/*
* Lines file opening strategy
*/
openlns()
{
register i;
switch(i=open(LINES,0)){
case 0:
break;
default:
close(i);
sleep(1);
switch(i=open(LINES,0)){
case 0:
break;
default:
close(i);
sleep(3);
switch(i=open(LINES,0)){
case 0:
break;
default:
close(i);
console("open error on %s\n",LINES);
return(1);
}
break;
}
break;
}
return(0); /*successful open*/
}
/*
* Getline filters out comments,maps multiple blanks to one
* blank and stores into a previously nulled-out 5 element array
* pointers to non-null fields(defined as tokens between colons on
* the entry line). Getline parses an entry until a line feed is
* encountered and subsequently returns a 1. A -1 is returned on
* a syntax infraction and a 0 on the end of file for /etc/lines.
*/
getline()
{
register char c,*q;
register int *p;
char c0;
int fl,keepc;
fl = 1;
zero(&lbuf,BUFSIZ/2); /* zero out lbuf char array(512) */
zero(&l,NTOKEN); /* zero out array of pointers -struct "l" */
p = &l;
q = &lbuf;
keepc = ' ';
*p = q;
while((c = getchar()) > 0) {
if(q > &lbuf[BUFSIZ]) {
console("%d buffer size exceeded\n",BUFSIZ);
while((c=getchar()) != '\n'); /*goto end of line*/
return(-1);
}
switch(c) {
case'/': /* comments */
c0 = comments();
if(c0 == 0)return(-1);
if(c0 == '\n'){
*q++ = c;
goto err;
}
if(c0 != '*'){
*q++ = c;
*q++ = c0;
}
continue;
case ':':
if(fl == 1 && q>(&lbuf[2])) goto err;
*q = '\0';
*(++p) = ++q;
keepc = ' ';
if(fl++ > NTOKEN) {
console("over %d token limit\n%s\n",
NTOKEN,&lbuf);
goto err;
}
continue;
case '\n': /* end of line or continuation */
if(keepc == '\\') {
q--;
continue;
}
*q = '\0';
if(fl == 1) goto err;
return(1);
case '\t': /* suppress horiz tabs */
continue;
case ' ': /* all excess blanks reduced to one*/
if(keepc == ' ') continue;
default:
*q++=c;
keepc = c;
}
}
return(0);
err:
console("syntax violation in LINES file entry at %s\n",l.lid);
return(-1);
}
/*
* supress comments in lines files
*/
comments()
{
register char c;
if((c=getchar())!='*')return(c);
while((c=getchar())>0)
if(c=='*' && (c=getchar())=='/')return('*');
console("syntax violation at %s\n",l.lid);
return(0);
}
/*
* Parsing a given token with delimiters '\0',' '.
* Pointers to the various arguments in the initcm token
* fields are stored in an array of pointers(inarg).
* Because of getline's parsing no more than one
* blank between characters exist.
*/
getarg()
{
register char *s;
register int *d,ctr;
ctr = 1;
d = &inarg; /*ptr to array of argument ptrs*/
s = l.initcm; /* ptr to lbuf[] element */
l.initcm = d;
zero(inarg,NARGS); /* zero out array of pointers */
*d++ = s;
while(*s) {
switch(*s) {
case ' ':
*s++ = '\0';
*d++ = s++;
if(ctr++ > NARGS) {
console("%d args exceeded\n",NARGS);
console("%s not executed\n",*l.initcm);
return(-1);
}
break;
default:
s++;
}
}
}
/*
* Close all files and clear the system with due warning
*/
clrsys()
{
register i;
for(i=0;i<NOFILE;i++) close(i);
if(cleanup(0)) for(i=0;i<NPROC;i++) killproc(i);
}
/*
* warn the process of impending doom then terminate the
* process if it is still alive after 20 seconds.
*/
wrnkill(enum)
{
register i;
if(fork() == 0){ /*shadow process*/
kill(iproc[enum].ipid,SIGWRN); /*object process*/
sleep(20);
kill(iproc[enum].ipid,KILL);
exit(1);
}
alarm(2); /* 2 second timer */
if(waitproc(iproc[enum].ipid)){
alarm(0);
rmproc(enum);
}
}
/*
* clear a process from the init proc table
*/
killproc(enum)
{
if(iproc[enum].ipid)
if(kill(iproc[enum].ipid,KILL)>=0) rmproc(enum);
}
/*
* The process id is passed to waitproc and a wait is effected
* until a process id or a signal is caught. If the object
* process dies a 1 is returned, if a signal is caught a 0 is returned.
* if a secondary process(other than the desired process in question)
* dies waitproc checks if it is one of INIT's child and if it is
* removes it, sets the catchit flag and continues to wait.
* If the process is not a child of INIT then it just continues
* to wait.
*/
waitproc(pnum)
{
register i;
while((i=wait(&status)) != pnum){
if(i<0) return(0); /*a signal was caught*/
if((i=find(i,0)) < 0) continue;
++catchit; /*secondary process died*/
rmproc(i);
}
return(1); /*successful wait on primary process*/
}
/*
* process is removed from init's internal process table
*/
rmproc(enum)
{
if(iproc[enum].ipid){
acctg(enum,0);
zero(&iproc[enum].id,IPROCSZ);
}
}
/* If a pointer addresses a value of zero(\0) then that
* pointer is zeroed out.
*/
zmap()
{
register i;
register char **k;
k = &l;
k++; /* begin at l.rlevel (offset 1) */
for(i=0; i<NTOKEN-1; i++) {
if(k[0][0] == '\0') *k = 0;
k++;
}
}
/*
* Searches proc table(element id or ipid) for value and
* returns the index of entry on a successful match. Otherwise
* a -1 is returned.
*/
find(value,flag)
{
register i,*iptr;
register struct iproc *p;
if(flag){ /*search for id*/
p = &iproc[0].id;
iptr = value;
value = *iptr;
}
else /*search for ipid*/
p = &iproc[0].ipid;
for(i=0;i<NPROC;i++)if((p++)->id == value)return(i);
return(-1);
}
/*
* Check the l.rlevel,l.action,l.shellcm and l.initcm fields
* for null values. If they are all null then return 1.
*/
nullchk()
{
register i,j,*k;
k = &l;
j = 0;
k++; /* begin at offset 1 */
for(i=0;i<NTOKEN-1; i++) j =+ *k++;
if(j == 0) return(1);
return(0);
}
/*
* a = object string
* b = address of structure "a" is to be searched against
*
* Stoi will return a number associated with a predefined string
* upon a successful scan; otherwise a -1 is returned,
* the entry ignored and INIT continues scanning.
*/
stoi(a,b)
char *a;
struct stimulus *b;
{
for(b;b->numb;b++)
if(cmp(a,b->keystr)) return(b->numb);
return(0);
}
/*
* 1 = successful string compare
* 0 = failure on string compare
*/
cmp(a,b)
char *a,*b;
{
while(*a++ == *b) {
if (*b++ == '\0') return(1);
}
return(0);
}
/*
* copy string s to string t
*/
copy(s,t)
char *s,*t;
{
while(*t++ = *s++);
}
/*
* Zeroes out the given array. Note where character arrays
* are involved the limit passed must be one-half the size
* of the target array.
*/
zero(array,limit)
int *array;
{
register i;
for(i=0; i<limit; i++) *array++ = 0;
}
/*
* print message to console tty
*/
console(p,arg1,arg2,arg3,arg4,arg5)
char *p;
{
extern fout;
register i;
if((i=fork()) == 0) {
for(i=0;i<3;i++) close(i);
open(CTTY,2);
dup(0);
stty(1,cpackit);
write(1,"INIT: ",6);
printf(p,arg1,arg2,arg3,arg4,arg5);
flush(); /* purge the print buffer */
exit(1);
}
waitproc(i);
}
setsig()
{
extern bus();
hangup();
intr();
quit();
ilgins();
trace();
iot();
emt();
fpt();
signal(SINLVL,bus);
seg();
sys();
pipe();
clk();
}
/*
* Signal catching and flagging.
*/
hangup()
{ signal(lvltag = SIGHUP,hangup);}
intr()
{ signal(lvltag = LVL0,intr);}
quit()
{ signal(lvltag = LVL1,quit);}
ilgins()
{ signal(lvltag = LVL2,ilgins);}
trace()
{ signal(lvltag = LVL3,trace);}
iot()
{ signal(lvltag = LVL4,iot);}
emt()
{ signal(lvltag = LVL5,emt);}
fpt()
{ signal(lvltag = LVL6,fpt);}
seg()
{ signal(lvltag = SIGSEG,seg);}
sys()
{ signal(lvltag = SIGSYS,sys);}
pipe()
{ signal(lvltag = SIGPIPE,pipe);}
clk()
{ signal(SIGCLK,clk);wrncnt=0;}