*BSD News Article 8825


Return to BSD News archive

Newsgroups: comp.unix.bsd
Path: sserve!manuel.anu.edu.au!munnari.oz.au!bunyip.cc.uq.oz.au!citec!sgccseh
From: sgccseh@citec.oz.au (Steve Hocking)
Subject: Top modules for 386bsd
Organization: CITEC
Date: Mon, 14 Dec 1992 01:33:53 GMT
Message-ID: <1992Dec14.013353.16568@citec.oz.au>
Summary: come & get it
Keywords: here - NOW
Lines: 761


	OK, here it is after a deluge of requests (well, 3 anyway) for it,
the all new sing & dancing top machine.c module for 386bsd. Get the top
sources from your local comp.sources.* archive.


	Cheers,


	Stephen

#!/bin/sh
# shar:	Shell Archiver  (v1.22)
#
#	Run the following text with /bin/sh to create:
#	  m_386bsd.c
#	  m_386bsd.desc
#	  m_386bsd.man
#	  386bsd-top.README
#
sed 's/^X//' << 'SHAR_EOF' > m_386bsd.c &&
X/*
X * top - a top users display for Unix
X *
X * SYNOPSIS:  For a 386BSD system
X *	      Note memory statistisc and process sizes could be wrong,
X *	      by ps gets them wrong too...
X *
X * DESCRIPTION:
X * This is the machine-dependent module for BSD4.3  NET/2 386bsd
X * Works for:
X *	i386 PC
X *
X * LIBS: -lutil
X *
X * AUTHOR:  Steve Hocking (adapted from Christos Zoulas <christos@ee.cornell.edu>)
X */
X
X#include <sys/types.h>
X#include <sys/signal.h>
X#include <sys/param.h>
X
X#include <stdio.h>
X#include <nlist.h>
X#include <math.h>
X#include <kvm.h>
X#include <sys/errno.h>
X#include <sys/kinfo.h>
X#include <sys/kinfo_proc.h>
X#ifdef notyet
X#define time __time
X#define hz __hz
X#include <sys/kernel.h>
X#undef time
X#undef hz
X#endif
X#include <sys/dir.h>
X#include <sys/dkstat.h>
X#include <sys/file.h>
X#include <sys/time.h>
X#include <sys/vmmeter.h>
X
X
X#define PATCHED_KVM		/* define this if you have a kvm.c */
X				/* that has been patched not to freshly */
X				/* alloc an array of procs each time */
X				/* kvm_getprocs is called, but to do a */
X				/* realloc when the no. of processes inc. */
X/* #define TOTAL_WORKING */	/* Uncomment when the total structure in */
X				/* the kernel actually works */
X#define DOSWAP
X
X#include "top.h"
X#include "machine.h"
X
X#define VMUNIX	"/386bsd"
X#define KMEM	"/dev/kmem"
X#define MEM	"/dev/mem"
X#ifdef DOSWAP
X#define SWAP	"/dev/drum"
X#endif
X
Xtypedef struct _kinfo {
X        struct proc ki_p;      /* proc structure */
X        struct eproc ki_e;     /* extra stuff */
X} KINFO;
X
X/* get_process_info passes back a handle.  This is what it looks like: */
X
Xstruct handle
X{
X    KINFO **next_proc;	/* points to next valid proc pointer */
X    int remaining;		/* number of pointers remaining */
X};
X
X/* declarations for load_avg */
X#include "loadavg.h"
X
X#define PP(pp, field) ((pp)->ki_p . field)
X#define EP(pp, field) ((pp)->ki_e . field)
X#define VP(pp, field) ((pp)->ki_e.e_vm . field)
X
X/* define what weighted cpu is.  */
X#define weighted_cpu(pct, pp) (PP((pp), p_time) == 0 ? 0.0 : \
X			 ((pct) / (1.0 - exp(PP((pp), p_time) * logcpu))))
X
X/* what we consider to be process size: */
X#define PROCSIZE(pp) (VP((pp), vm_tsize) + VP((pp), vm_dsize) + VP((pp), vm_ssize))
X
X/* definitions for indices in the nlist array */
X#define X_CCPU		0
X#define X_CP_TIME	1
X#define X_HZ		2
X#define X_AVENRUN	3
X#define X_TOTAL		4
X#define X_PG_FREE	5
X#define X_PG_ACTIVE	6
X#define X_PG_INACTIVE	7
X#define X_PG_WIRED	8
X
Xstatic struct nlist nlst[] = {
X    { "_ccpu" },		/* 0 */
X    { "_cp_time" },		/* 1 */
X    { "_hz" },			/* 2 */
X    { "_averunnable" },		/* 3 */
X    { "_total"},		/* 4 */
X    { "_vm_page_free_count" },	/* 5 */
X    { "_vm_page_active_count" },	/* 6 */
X    { "_vm_page_inactive_count" },/* 7 */
X    { "_vm_page_wire_count" },	/* 8 */
X    { 0 }
X};
X
X/*
X *  These definitions control the format of the per-process area
X */
X
Xstatic char header[] =
X  "  PID X        PRI NICE   SIZE   RES STATE   TIME   WCPU    CPU COMMAND";
X/* 0123456   -- field to fill in starts at header+6 */
X#define UNAME_START 6
X
X#define Proc_format \
X	"%5d %-8.8s %3d %4d%6dK %4dK %-5s%4d:%02d %5.2f%% %5.2f%% %.14s"
X
X
X/* process state names for the "STATE" column of the display */
X/* the extra nulls in the string "run" are for adding a slash and
X   the processor number when needed */
X
Xchar *state_abbrev[] =
X{
X    "", "sleep", "WAIT", "run\0\0\0", "start", "zomb", "stop"
X};
X
X
X/* values that we stash away in _init and use in later routines */
X
Xstatic double logcpu;
X
X/* these are retrieved from the kernel in _init */
X
Xstatic          long hz;
Xstatic load_avg  ccpu;
Xstatic          int  ncpu = 0;
X
X/* these are offsets obtained via nlist and used in the get_ functions */
X
Xstatic unsigned long cp_time_offset;
Xstatic unsigned long avenrun_offset;
X
X/* these are for calculating cpu state percentages */
X
Xstatic long cp_time[CPUSTATES];
Xstatic long cp_old[CPUSTATES];
Xstatic long cp_diff[CPUSTATES];
X
X/* these are for detailing the process states */
X
Xint process_states[7];
Xchar *procstatenames[] = {
X    "", " sleeping, ", " ABANDONED, ", " running, ", " starting, ",
X    " zombie, ", " stopped, ",
X    NULL
X};
X
X/* these are for detailing the cpu states */
X
Xint cpu_states[4];
Xchar *cpustatenames[] = {
X    "user", "nice", "system", "idle", NULL
X};
X
X/* these are for detailing the memory statistics */
X
Xint memory_stats[8];
Xchar *memorynames[] = {
X#ifdef	TOTAL_WORKING
X    "Real: ", "K/", "K ", "Virt: ", "K/",
X    "K ", "Free: ", "K", NULL
X#else
X    " Free: ", "K ", " Active: ", "K ", " Inactive: ",
X    "K ", " Wired: ", "K ", NULL
X#endif
X};
X
X/* these are for keeping track of the proc array */
X
Xstatic int bytes;
Xstatic int nproc;
Xstatic int onproc = -1;
Xstatic KINFO *pbase;
Xstatic KINFO **pref;
X
X/* these are for getting the memory statistics */
X
Xstatic int pageshift;		/* log base 2 of the pagesize */
X
X/* define pagetok in terms of pageshift */
X
X#define pagetok(size) ((size) << pageshift)
X
X/* useful externals */
Xlong percentages();
X
Xmachine_init(statics)
X
Xstruct statics *statics;
X
X{
X    register int i = 0;
X    register int pagesize;
X    char buf[1024];
X
X#if 0		/* some funny stuff going on here */
X    if (kvm_openfiles(NULL, NULL, NULL) == -1);
X	return -1;
X#else
X    kvm_openfiles(VMUNIX, NULL, NULL);
X#endif
X
X    /* get the list of symbols we want to access in the kernel */
X    (void) kvm_nlist(nlst);
X    if (nlst[0].n_type == 0)
X    {
X	fprintf(stderr, "top: nlist failed\n");
X	return(-1);
X    }
X
X    /* make sure they were all found */
X    if (i > 0 && check_nlist(nlst) > 0)
X    {
X	return(-1);
X    }
X
X    /* get the symbol values out of kmem */
X    (void) getkval(nlst[X_HZ].n_value,     (int *)(&hz),	sizeof(hz),
X	    nlst[X_HZ].n_name);
X    (void) getkval(nlst[X_CCPU].n_value,   (int *)(&ccpu),	sizeof(ccpu),
X	    nlst[X_CCPU].n_name);
X
X    /* stash away certain offsets for later use */
X    cp_time_offset = nlst[X_CP_TIME].n_value;
X    avenrun_offset = nlst[X_AVENRUN].n_value;
X
X    /* this is used in calculating WCPU -- calculate it ahead of time */
X    logcpu = log(loaddouble(ccpu));
X
X    pbase = NULL;
X    pref = NULL;
X    nproc = 0;
X    onproc = -1;
X    /* get the page size with "getpagesize" and calculate pageshift from it */
X    pagesize = getpagesize();
X    pageshift = 0;
X    while (pagesize > 1)
X    {
X	pageshift++;
X	pagesize >>= 1;
X    }
X
X    /* we only need the amount of log(2)1024 for our conversion */
X    pageshift -= LOG1024;
X
X    /* fill in the statics information */
X    statics->procstate_names = procstatenames;
X    statics->cpustate_names = cpustatenames;
X    statics->memory_names = memorynames;
X
X    /* all done! */
X    return(0);
X}
X
Xchar *format_header(uname_field)
X
Xregister char *uname_field;
X
X{
X    register char *ptr;
X
X    ptr = header + UNAME_START;
X    while (*uname_field != '\0')
X    {
X	*ptr++ = *uname_field++;
X    }
X
X    return(header);
X}
X
Xget_system_info(si)
X
Xstruct system_info *si;
X
X{
X    long total;
X    load_avg avenrun[3];
X
X    /* get the cp_time array */
X    (void) getkval(cp_time_offset, (int *)cp_time, sizeof(cp_time),
X		   "_cp_time");
X    (void) getkval(avenrun_offset, (int *)avenrun, sizeof(avenrun),
X		   "_avenrun");
X
X    /* convert load averages to doubles */
X    {
X	register int i;
X	register double *infoloadp;
X	load_avg *avenrunp;
X
X#ifdef KINFO_LOADAVG
X	struct loadavg sysload;
X	int size;
X	getkerninfo(KINFO_LOADAVG, &sysload, &size, 0);
X#endif
X
X	infoloadp = si->load_avg;
X	avenrunp = avenrun;
X	for (i = 0; i < 3; i++)
X	{
X#ifdef KINFO_LOADAVG
X	    *infoloadp++ = ((double) sysload.ldavg[i]) / sysload.fscale;
X#endif
X	    *infoloadp++ = loaddouble(*avenrunp++);
X	}
X    }
X
X    /* convert cp_time counts to percentages */
X    total = percentages(CPUSTATES, cpu_states, cp_time, cp_old, cp_diff);
X
X    /* sum memory statistics */
X    {
X#ifdef TOTAL_WORKING
X	static struct vmtotal total;
X	int size;
X
X	/* get total -- systemwide main memory usage structure */
X#ifdef KINFO_METER
X	getkerninfo(KINFO_METER, &total, &size, 0);
X#else
X	(void) getkval(nlst[X_TOTAL].n_value, (int *)(&total), sizeof(total),
X		nlst[X_TOTAL].n_name);
X#endif
X	/* convert memory stats to Kbytes */
X	memory_stats[0] = -1;
X	memory_stats[1] = pagetok(total.t_arm);
X	memory_stats[2] = pagetok(total.t_rm);
X	memory_stats[3] = -1;
X	memory_stats[4] = pagetok(total.t_avm);
X	memory_stats[5] = pagetok(total.t_vm);
X	memory_stats[6] = -1;
X	memory_stats[7] = pagetok(total.t_free);
X#else
X        static int free, active, inactive, wired;
X
X	(void) getkval(nlst[X_PG_FREE].n_value, (int *)(&free), sizeof(free),
X                nlst[X_PG_FREE].n_name);
X	(void) getkval(nlst[X_PG_ACTIVE].n_value, (int *)(&active), sizeof(active),
X                nlst[X_PG_ACTIVE].n_name);
X	(void) getkval(nlst[X_PG_INACTIVE].n_value, (int *)(&inactive), sizeof(inactive),
X                nlst[X_PG_INACTIVE].n_name);
X	(void) getkval(nlst[X_PG_WIRED].n_value, (int *)(&wired), sizeof(wired),
X                nlst[X_PG_WIRED].n_name);
X	memory_stats[0] = -1;
X	memory_stats[1] = pagetok(free);
X	memory_stats[2] = -1;
X	memory_stats[3] = pagetok(active);
X	memory_stats[4] = -1;
X	memory_stats[5] = pagetok(inactive);
X	memory_stats[6] = -1;
X	memory_stats[7] = pagetok(wired);
X#endif
X    }
X
X    /* set arrays and strings */
X    si->cpustates = cpu_states;
X    si->memory = memory_stats;
X    si->last_pid = -1;
X}
X
Xstatic struct handle handle;
X
Xcaddr_t get_process_info(si, sel, compare)
X
Xstruct system_info *si;
Xstruct process_select *sel;
Xint (*compare)();
X
X{
X    register int i;
X    register int total_procs;
X    register int active_procs;
X    register KINFO **prefp;
X    KINFO *pp;
X    struct proc *pr;
X    struct eproc *epr;
X
X    /* these are copied out of sel for speed */
X    int show_idle;
X    int show_system;
X    int show_uid;
X    int show_command;
X
X    
X    nproc = kvm_getprocs(KINFO_PROC_ALL, 0);
X    if (nproc > onproc)
X    {
X	pref = (KINFO **) realloc(pref, sizeof(KINFO *)
X		* nproc);
X        pbase = (KINFO *) realloc(pbase, sizeof(KINFO)
X                * (nproc + 2));
X        onproc = nproc;
X    }
X    if (pref == NULL || pbase == NULL) {
X	(void) fprintf(stderr, "top: Out of memory.\n");
X	quit(23);
X    }
X    /* get a pointer to the states summary array */
X    si->procstates = process_states;
X
X    /* set up flags which define what we are going to select */
X    show_idle = sel->idle;
X    show_system = sel->system;
X    show_uid = sel->uid != -1;
X    show_command = sel->command != NULL;
X
X    /* count up process states and get pointers to interesting procs */
X    total_procs = 0;
X    active_procs = 0;
X    memset((char *)process_states, 0, sizeof(process_states));
X    prefp = pref;
X    for (pp = pbase, i = 0; pr = kvm_nextproc(); pp++, i++)
X    {
X	/*
X	 *  Place pointers to each valid proc structure in pref[].
X	 *  Process slots that are actually in use have a non-zero
X	 *  status field.  Processes with SSYS set are system
X	 *  processes---these get ignored unless show_sysprocs is set.
X	 */
X        epr = kvm_geteproc(pr);
X        pp->ki_p = *pr;
X        pp->ki_e = *epr;
X	if (PP(pp, p_stat) != 0 &&
X	    (show_system || ((PP(pp, p_flag) & SSYS) == 0)))
X	{
X	    total_procs++;
X	    process_states[PP(pp, p_stat)]++;
X	    if ((PP(pp, p_stat) != SZOMB) &&
X		(show_idle || (PP(pp, p_pctcpu) != 0) || 
X		 (PP(pp, p_stat) == SRUN)) &&
X		(!show_uid || EP(pp, e_pcred.p_ruid) == (uid_t)sel->uid))
X	    {
X		*prefp++ = pp;
X		active_procs++;
X	    }
X	}
X    }
X
X    /* if requested, sort the "interesting" processes */
X    if (compare != NULL)
X    {
X	qsort((char *)pref, active_procs, sizeof(KINFO *), compare);
X    }
X
X    /* remember active and total counts */
X    si->p_total = total_procs;
X    si->p_active = active_procs;
X
X    /* pass back a handle */
X    handle.next_proc = pref;
X    handle.remaining = active_procs;
X#ifndef PATCHED_KVM
X    kvm_freeprocs();
X#endif
X    return((caddr_t)&handle);
X}
X
Xchar fmt[128];		/* static area where result is built */
X
Xchar *format_next_process(handle, get_userid)
X
Xcaddr_t handle;
Xchar *(*get_userid)();
X
X{
X    register KINFO *pp;
X    register long cputime;
X    register double pct;
X    int where;
X    struct handle *hp;
X
X    /* find and remember the next proc structure */
X    hp = (struct handle *)handle;
X    pp = *(hp->next_proc++);
X    hp->remaining--;
X    
X
X    /* get the process's user struct and set cputime */
X    if ((PP(pp, p_flag) & SLOAD) == 0) {
X	/*
X	 * Print swapped processes as <pname>
X	 */
X	char *comm = PP(pp, p_comm);
X#define COMSIZ sizeof(PP(pp, p_comm))
X	char buf[COMSIZ];
X	(void) strncpy(buf, comm, COMSIZ);
X	comm[0] = '<';
X	(void) strncpy(&comm[1], buf, COMSIZ - 2);
X	comm[COMSIZ - 2] = '\0';
X	(void) strncat(comm, ">", COMSIZ - 1);
X	comm[COMSIZ - 1] = '\0';
X    }
X
X    cputime = PP(pp, p_utime.tv_sec) + PP(pp, p_stime.tv_sec);
X
X    /* calculate the base for cpu percentages */
X    pct = pctdouble(PP(pp, p_pctcpu));
X
X    /* format this entry */
X    sprintf(fmt,
X	    Proc_format,
X	    PP(pp, p_pid),
X	    (*get_userid)(EP(pp, e_pcred.p_ruid)),
X	    PP(pp, p_pri) - PZERO,
X	    PP(pp, p_nice) - NZERO,
X	    pagetok(PROCSIZE(pp)),
X#ifdef notyet
X	    pagetok(VP(pp, vm_rssize)),
X#else
X            pagetok(pp->ki_e.e_vm.vm_pmap.pm_stats.resident_count),
X#endif
X	    state_abbrev[PP(pp, p_stat)],
X	    cputime / 60l,
X	    cputime % 60l,
X	    100.0 * weighted_cpu(pct, pp),
X	    100.0 * pct,
X	    printable(PP(pp, p_comm)));
X
X    /* return the result */
X    return(fmt);
X}
X
X
X/*
X * check_nlist(nlst) - checks the nlist to see if any symbols were not
X *		found.  For every symbol that was not found, a one-line
X *		message is printed to stderr.  The routine returns the
X *		number of symbols NOT found.
X */
X
Xint check_nlist(nlst)
X
Xregister struct nlist *nlst;
X
X{
X    register int i;
X
X    /* check to see if we got ALL the symbols we requested */
X    /* this will write one line to stderr for every symbol not found */
X
X    i = 0;
X    while (nlst->n_name != NULL)
X    {
X	if (nlst->n_type == 0)
X	{
X	    /* this one wasn't found */
X	    fprintf(stderr, "kernel: no symbol named `%s'\n", nlst->n_name);
X	    i = 1;
X	}
X	nlst++;
X    }
X
X    return(i);
X}
X
X
X/*
X *  getkval(offset, ptr, size, refstr) - get a value out of the kernel.
X *	"offset" is the byte offset into the kernel for the desired value,
X *  	"ptr" points to a buffer into which the value is retrieved,
X *  	"size" is the size of the buffer (and the object to retrieve),
X *  	"refstr" is a reference string used when printing error meessages,
X *	    if "refstr" starts with a '!', then a failure on read will not
X *  	    be fatal (this may seem like a silly way to do things, but I
X *  	    really didn't want the overhead of another argument).
X *  	
X */
X
Xgetkval(offset, ptr, size, refstr)
X
Xunsigned long offset;
Xint *ptr;
Xint size;
Xchar *refstr;
X
X{
X    if (kvm_read(offset, (char *) ptr, size) != size)
X    {
X	if (*refstr == '!')
X	{
X	    return(0);
X	}
X	else
X	{
X	    fprintf(stderr, "top: kvm_read for %s: %s\n",
X		refstr, strerror(errno));
X	    quit(23);
X	}
X    }
X    return(1);
X}
X    
X/* comparison routine for qsort */
X
X/*
X *  proc_compare - comparison function for "qsort"
X *	Compares the resource consumption of two processes using five
X *  	distinct keys.  The keys (in descending order of importance) are:
X *  	percent cpu, cpu ticks, state, resident set size, total virtual
X *  	memory usage.  The process states are ordered as follows (from least
X *  	to most important):  WAIT, zombie, sleep, stop, start, run.  The
X *  	array declaration below maps a process state index into a number
X *  	that reflects this ordering.
X */
X
Xstatic unsigned char sorted_state[] =
X{
X    0,	/* not used		*/
X    3,	/* sleep		*/
X    1,	/* ABANDONED (WAIT)	*/
X    6,	/* run			*/
X    5,	/* start		*/
X    2,	/* zombie		*/
X    4	/* stop			*/
X};
X 
Xproc_compare(pp1, pp2)
X
XKINFO **pp1;
XKINFO **pp2;
X
X{
X    register KINFO *p1;
X    register KINFO *p2;
X    register int result;
X    register pctcpu lresult;
X
X    /* remove one level of indirection */
X    p1 = *pp1;
X    p2 = *pp2;
X
X    /* compare percent cpu (pctcpu) */
X    if ((lresult = PP(p2, p_pctcpu) - PP(p1, p_pctcpu)) == 0)
X    {
X	/* use cpticks to break the tie */
X	if ((result = PP(p2, p_cpticks) - PP(p1, p_cpticks)) == 0)
X	{
X	    /* use process state to break the tie */
X	    if ((result = sorted_state[PP(p2, p_stat)] -
X			  sorted_state[PP(p1, p_stat)])  == 0)
X	    {
X		/* use priority to break the tie */
X		if ((result = PP(p2, p_pri) - PP(p1, p_pri)) == 0)
X		{
X		    /* use resident set size (rssize) to break the tie */
X		    if ((result = VP(p2, vm_rssize) - VP(p1, vm_rssize)) == 0)
X		    {
X			/* use total memory to break the tie */
X			result = PROCSIZE(p2) - PROCSIZE(p1);
X		    }
X		}
X	    }
X	}
X    }
X    else
X    {
X	result = lresult < 0 ? -1 : 1;
X    }
X
X    return(result);
X}
SHAR_EOF
chmod 0644 m_386bsd.c || echo "restore of m_386bsd.c fails"
sed 's/^X//' << 'SHAR_EOF' > m_386bsd.desc &&
X
Xtop - a top users display for Unix
X
XSYNOPSIS:  For a pre-release 4.4BSD system
X	      Note memory statistisc and process sizes could be wrong,
X	      by ps gets them wrong too...
X
XDESCRIPTION:
XThis is the machine-dependent module for BSD4.3  NET/2 386bsd
XWorks for:
X	i386 PC
X
XLIBS: -lkvm
X
XAUTHOR:  Steve Hocking (adapted from Christos Zoulas <christos@ee.cornell.edu>)
X
SHAR_EOF
chmod 0644 m_386bsd.desc || echo "restore of m_386bsd.desc fails"
sed 's/^X//' << 'SHAR_EOF' > m_386bsd.man &&
X.SH "386 BSD DIFFERENCES"
XThe 386 BSD port is said to work at least as well as ps does.
X
XThe 386 BSD port was written by Steve Hocking
SHAR_EOF
chmod 0644 m_386bsd.man || echo "restore of m_386bsd.man fails"
sed 's/^X//' << 'SHAR_EOF' > 386bsd-top.README &&
X
X	Find enclosed a module for 386bsd. A couple of notes -
XFirstly I recently posted a patch to kvm.c in the util library which proved
Xstrictly unneccessary, as there was a routine that allowed one to free up
Xthe memory used by the kvm_getprocs routine. (Humph, all the doco for the
Xkvm library was, as usual, in the source). There is a #define which should
Xbe set if you have not applied my patches to kvm.c,
X	Secondly there appears to be a subtle bug somewhere which was causing
Xthe kvm routines to not properly initialise. I suspect something was treading on
Xa stack frame early in the top code proper, but am unable to trace it owing
Xto lack of time. This caused me some problems in obtaining the resident set
Xsize of a process, even using a method which works for ps. I did find
X(largely owing to GDB, a debugger for whom I have an increasing amount of
Xaffection) another place where the relevant information was kept in the
Xstructure, and used it.
X	Thridly, the total structure that contains the information about the
Xsystem memory in traditional BSD form was not being fully updated by
Xvm_meter.c, probably because of all the changes to the VM subsystem. I opted
Xto report some of the stuff regarding free, active & wired  down pages.
X
X
X	(un)shar & enjoy
X
X	Stephen
SHAR_EOF
chmod 0644 386bsd-top.README || echo "restore of 386bsd-top.README fails"
exit 0
-- 
-----------------------------------------------------------------------------
          "Toddlers are the stormtroopers of the Lord of Entropy"

Stephen Hocking                                    sgccseh@citecuc.citec.oz.au