[net.sources] preen, or How to Speed Up Reboots

chris@umcp-cs.UUCP (09/06/84)

(4.2BSD source for /etc/preen + "error" subroutine.)

One of the annoying things about ``fsck -p'' is that it doesn't really
understand what a disk is.  It just looks at the little pass numbers in
/etc/fstab.  Well, if you carefully arrange these, you can make the
delays while disk arms are idle fairly small, but it seemed that this
was the wrong way to go about doing things.  Why not simply look at the
actual disk drive and keep the arm busy always?

So, here's a program to do just that.  Preen reads /etc/fstab and
builds a list of file systems and disk drives.  If you stick to
standard conventions when naming your disks, preen will "know" that
/dev/hp3a and /dev/hp3b are the same disk while /dev/hp5c is a
different disk entirely.  Also, you don't have to worry about pass
numbers; preen just barges on ahead as fast as it can.

To use this thing, just install it in /etc/preen and change the line
in /etc/rc that says

	fsck -p >/dev/console

to one that says

	/etc/preen >/dev/console

The exit statuses (stati?) from preen match (as much as possible) those
from fsck.

Preen comes with the "error" routine I've grown so fond of lately.  I
normally just get this from the -lum (U of MD) library; you'll have to
use something different from what it says in the "Compilation" line.

: Run this shell script with "sh" not "csh"
PATH=:/bin:/usr/bin:/usr/ucb
export PATH
all=FALSE
if [ $1x = -ax ]; then
	all=TRUE
fi
/bin/echo 'Extracting preen.c'
sed 's/^X//' <<'//go.sysin dd *' >preen.c
X/*
 * Preen
 *
 * Run fsck in parallel over the normally mounted disks.
 *
 * This program differs from the -p option to fsck in that it
 * uses the base names of the disks to determine which are the
 * same physical drive, and actually tries to keep all drives
 * busy.  fsck -p merely runs groups of "passes" and with odd
 * configurations tends to leave the arms idle a lot.
 *
 * Compilation:
 %	cc -O -R -o preen preen.c -lum
 */

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <fstab.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#define FSCK "/etc/fsck"	/* where fsck lives */

struct fsck_info {
    struct fsck_info *fi_next;	/* linked list */
    struct fstab fi_fs;		/* copy of /etc/fstab entry */
    struct diskdev *fi_disk;	/* disk device info */
    int fi_isroot;		/* true iff this is the root file system */
    union wait fi_w;		/* info from wait3() sys call */
    int fi_pid;			/* PID of fsck checking this device */
    int fi_stat;		/* status flag; see below */
};
#define FI_NOTYET	1	/* not started yet */
#define FI_RUNNING	2	/* running now */
#define FI_NORMAL	3	/* exited normally */
#define FI_BAD		4	/* exited with error(s) */

struct diskdev {
    struct diskdev *dd_next;	/* linked list */
    char *dd_name;		/* name of drive */
    int dd_stat;		/* disk arm status */
};
#define DD_IDLE		0	/* arm is idle */
#define DD_BUSY		1	/* arm is busy */

int nfs;			/* number of fs entries still active */
struct fsck_info *fi_hd;	/* list head */
struct fsck_info *fi_tl;	/* list tail */
struct fsck_info *fi_root;	/* info for root file system */
struct diskdev *dd_hd;		/* disk head */
struct diskdev *dd_tl;		/* disk tail */

int ChildChanged;		/* set whenever a child exits */

X/* Exit stati */
#define E_OK	0		/* all OK */
#define E_RB	4		/* please reboot */
#define E_MISC	8		/* miscellaneous failure code */
#define E_INTR	12		/* interrupted from keyboard */
#define E_SIG   127		/* fsck(s) killed by signal(s) */

X/* imports */
extern char *ProgName;
extern int   errno;

char *malloc ();

X/* Bit masking for 4.2 signal stuff */
#define bit(s) (1 << ((s) - 1))

X/* Signal hander for keyboard interrupts */
sigdie (sig) {
    if (sig == SIGINT)
	exit (E_INTR);		/* interrupted */
    printf ("%s: help ... dying on signal %d\n", ProgName, sig);
    exit (E_SIG);
}

X/* Initialize signal handlers */
initsig () {
    int CatchChild ();

    signal (SIGHUP, sigdie);
    signal (SIGINT, sigdie);
    signal (SIGQUIT, sigdie);
    signal (SIGPIPE, sigdie);
    signal (SIGTERM, sigdie);
    signal (SIGCHLD, CatchChild);
}

X/* Save a string in managed memory */
char *
savestr (s)
register char *s;
{
    register char *t, *rv;

    t = s;
    while (*t++);
    if ((rv = malloc (t - s)) == 0)
	error (E_MISC, errno, "malloc");
    t = rv;
    while (*t++ = *s++);
    return rv;
}

X/* Convert a block device name to the equivalent raw name.  The new name
   is stuck into static storage. */
char *
DiskRawName (name)
register char *name;
{
    register char  *p,
                   *t;
    static char newnm[200];

    p = name;
    while (*p++);
    while (p > name && *--p != '/');
    if (*p++ != '/') {
	error (0, 0, "malformed disk device name \"%s\"", name);
	return name;
    }
    t = newnm;
    while (name != p)
	*t++ = *name++;
    *t++ = 'r';
    while (*t++ = *name++);
    return newnm;
}

X/* Convert a disk name (either raw or block) to the base name of the disk
   (e.g., "/dev/rhp2a" -> "/dev/hp2"; "/dev/ra0g" -> "/dev/ra0"). */
char *
DiskBaseName (name, isblock)
char *name;
int isblock;
{
    register char  *nm,
                   *p,
                   *t;
    static char newnm[200];

    p = t = name;
    while (*p)
	if (*p++ == '/')
	    t = p;
    p = newnm;
    nm = name;
    while (nm < t)
	*p++ = *nm++;
    if (!isblock && *nm++ != 'r') {
	error (0, 0, "(DiskBaseName) claimed \"%s\" raw but no ``r''?",
		name);
	nm--;
    }
    while (*p = *nm++)
	p++;
    while (!isdigit (*--p));
    p[1] = 0;
    return newnm;
}
    
X/* Get a disk descriptor.  "name" is either a raw or block device name; it's
   the block device iff "rootdev" is set. */
struct diskdev *
GetDisk (name, rootdev)
char *name;
int rootdev;
{
    register struct diskdev *dd;
    register char  *p1,
                   *p2,
		   *dname;

 /* first, convert the name to the base name */
    dname = DiskBaseName (name, rootdev);

 /* now search the existing list */
    for (dd = dd_hd; dd; dd = dd -> dd_next) {
	p1 = dd -> dd_name;
	p2 = dname;
	while (*p1++ == *p2)
	    if (*p2++ == 0)
		return dd;	/* found it */
    }

 /* make a new entry */
    if ((dd = (struct diskdev  *) malloc (sizeof *dd)) == 0)
	error (E_MISC, errno, "malloc");
    dd -> dd_next = 0;
    dd -> dd_name = savestr (dname);
    dd -> dd_stat = DD_IDLE;
    if (dd_tl) {
	dd_tl -> dd_next = dd;
	dd_tl = dd;
    }
    else
	dd_hd = dd_tl = dd;
    return dd;
}

X/* The following macro determines which file systems are normally mounted.
   Since these are the ones labeled "ro", "rw", and "rq", we just check the
   first character, which is sufficient for now. */
#define NormallyMounted(fs) ((fs)->fs_type[0] == 'r')

X/* read in the entire fstab table */
readfs () {
    register struct fstab  *fs;
    register struct fsck_info  *fi;
    struct fstab   *getfsent ();

    setfsent ();		/* big deal. */

 /* Run through the entire file, saving info about any file systems that
    are normally mounted on. */
    while ((fs = getfsent ()) != 0) {
	if (!NormallyMounted (fs))
	    continue;
	if ((fi = (struct fsck_info *) malloc (sizeof *fi)) == 0)
	    error (E_MISC, errno, "malloc");
	fi -> fi_isroot = fs -> fs_file[0] == '/' && fs -> fs_file[1] == 0;
	if (fi -> fi_isroot) {
	    fs -> fs_spec = savestr (fs -> fs_spec);
	    if (fi_root)
		error (E_MISC, 0,
			"odd... you seem to have two root file systems!");
	    fi_root = fi;
	}
	else
	    fs -> fs_spec = savestr (DiskRawName (fs -> fs_spec));
	fs -> fs_file = savestr (fs -> fs_file);
	fs -> fs_type = savestr (fs -> fs_type);
	fi -> fi_next = 0;
	fi -> fi_fs = *fs;
	fi -> fi_pid = 0;
	fi -> fi_stat = FI_NOTYET;
	if (fi_tl) {
	    fi_tl -> fi_next = fi;
	    fi_tl = fi;
	}
	else
	    fi_hd = fi_tl = fi;
	fi -> fi_disk = GetDisk (fi -> fi_fs.fs_spec, fi -> fi_isroot);
	nfs++;
    }
    if (!fi_root)
	error (E_MISC, 0, "odd... you don't seem to have a root file system!");
    endfsent ();
}

main (argc, argv)
int argc;
char **argv;
{
    ProgName = *argv;

    initsig ();
    readfs ();
    StartOne (fi_root);		/* start the initial fsck on root */
    for (;;) {
	sigblock (bit (SIGCHLD));
	if (nfs == 0)		/* if done, exit as appropriate */
	    DoExit ();
	if (ChildChanged)	/* if some exited, start some more */
	    StartFscks ();
	sigpause (sigblock (0) & ~bit (SIGCHLD));
    }
}

X/* Catch SIGCHLD here.  Wait for children, then search for them in the list
   of filesystems.  If found (presumably this WILL be the case!), reset the
   state of the drive (arm) to DD_IDLE and note the exit code. */
CatchChild () {
    register int    pid;
    register struct fsck_info  *fi;
    union wait status;

    while ((pid = wait3 (&status, WNOHANG, (struct rusage *) 0)) != 0) {
	if (pid == -1) {
	    if (errno == EINTR)
		continue;
	    else
		break;
	}
	for (fi = fi_hd; fi; fi = fi -> fi_next)
	    if (fi -> fi_pid == pid)
		break;
	if (fi == 0) {
	    error (0, 0, "pid %d exited but not in fsck info table", pid);
	    continue;
	}
	if (fi -> fi_stat != FI_RUNNING) {
	    error (0, 0, "pid %d exited but fi_stat = %d not %d", pid,
		    fi -> fi_stat, FI_RUNNING);
	    continue;
	}
	nfs--;			/* another one bites the dust */
	fi -> fi_w = status;
	if (WIFSIGNALED (status) || status.w_retcode != E_OK) {
	    fi -> fi_stat = FI_BAD;
	    if (fi == fi_root)
		nfs = 0;	/* eat flaming death */
	}
	else
	    fi -> fi_stat = FI_NORMAL;
	fi -> fi_disk -> dd_stat = DD_IDLE;
	ChildChanged++;
    }
}

X/* Start up fscks on the idle drives.  We'd like to just race down the list
   of drives, but there could be any number of file systems attached to any
   one drive, so we have to do things the hard way. */
StartFscks () {
    register struct fsck_info *fi;

    for (fi = fi_hd; fi; fi = fi -> fi_next)
	if (fi -> fi_stat == FI_NOTYET && fi -> fi_disk -> dd_stat == DD_IDLE)
	    StartOne (fi);
}

X/* Start up a single fsck on the indicated file system.  Also, mark the
   disk arm as busy. */
StartOne (fi)
register struct fsck_info *fi;
{
    register int pid;

    printf ("%s -p %s\n", FSCK, fi -> fi_fs.fs_spec);
    fflush (stdout);
    if ((pid = fork ()) == 0) {
	execl (FSCK, FSCK, "-p", fi -> fi_fs.fs_spec, (char *) 0);
	error (E_MISC, errno, "execl(%s) failed", FSCK);
    }
    if (pid == -1)
	error (E_MISC, errno, "fork");
    fi -> fi_pid = pid;
    fi -> fi_stat = FI_RUNNING;
    fi -> fi_disk -> dd_stat = DD_BUSY;
}

X/* Figure out what exit code to use to summarize the whole situation */
DoExit () {
    register struct fsck_info  *fi;
    int     e_intr = 0;		/* true => exit with interrupted status */
    int     e_misc = 0;		/* true => exit with misc error status */
    int     e_odd = 0;		/* true => exit with odd status e_odd */
    int     e_signal = 0;	/* true => exit with E_SIG status */

    for (fi = fi_hd; fi; fi = fi -> fi_next) {
	switch (fi -> fi_stat) {
	    case FI_NORMAL: 
		continue;
	    case FI_NOTYET: 
	    case FI_RUNNING: 
		e_intr++;
		continue;
	    case FI_BAD: 
		if (WIFSIGNALED (fi -> fi_w)) {
		    e_signal++;
		    continue;
		}
		if (fi -> fi_w.w_retcode == E_RB)
		    exit (E_RB);/* takes precedence over all others */
		e_odd = fi -> fi_w.w_retcode;
		continue;
	    default: 
		error (0, 0, "odd fi_stat %d", fi -> fi_stat);
		e_misc++;
	}
    }
    if (e_signal)
	exit (E_SIG);
    if (e_misc)
	exit (E_MISC);
    if (e_odd)
	exit (e_odd);
    if (e_intr)
	exit (E_INTR);
    exit (E_OK);
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 preen.c
	/bin/echo -n '	'; /bin/ls -ld preen.c
fi
/bin/echo 'Extracting error.c'
sed 's/^X//' <<'//go.sysin dd *' >error.c
#include <stdio.h>

char *ProgName;

X/*
 * error
 *
 * Useful for printing error messages.  Will print the program name
 * and (optionally) the system error associated with the values in
 * <errno.h>.
 *
 * Note that the type (and even the existence!) of ``arg'' is undefined.
 */
error (quit, e, fmt, arg)
int quit;
register int e;
char *fmt;
{
    extern   char *sys_errlist[];
    extern   int   sys_nerr;
    register char *p = ProgName;

    if (p == NULL)
	p = "tomb of the unknown program";
    fprintf (stderr, "%s: ", p);
    _doprnt (fmt, &arg, stderr);	/* magic */
    if (e > 0) {
	p = e < sys_nerr ? sys_errlist[e] : "unknown error";
	fprintf (stderr, ": %s", p);
    }
    putc ('\n', stderr);
    fflush (stderr);
    if (quit)
	exit (quit);
}
//go.sysin dd *
made=TRUE
if [ $all = TRUE ]; then
	/bin/echo '	Changing owner to "bin"'
	/etc/chown bin error.c
else
	/bin/echo '	Original owner was "bin"'
fi
if [ $made = TRUE ]; then
	/bin/chmod 644 error.c
	/bin/echo -n '	'; /bin/ls -ld error.c
fi
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci (301) 454-7690
UUCP:	{seismo,allegra,brl-bmd}!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@maryland