[mod.sources] v07i031: Top users display for 4.2BSD, Version 2.0, Part02/02

sources-request@mirror.UUCP (09/24/86)

Submitted by: William LeFebvre <phil@rice.edu>
Mod.sources: Volume 7, Issue 31
Archive-name: top2/Part02


#!/bin/sh
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
# If all goes well, you will see the message "No problems found."

# Exit status; set to 1 on "wc" errors or if would overwrite.
STATUS=0
# Contents:  top.c top.local.h top.man MANIFEST
 
echo x - top.c
if test -f top.c ; then
    echo top.c exists, putting output in $$top.c
    OUT=$$top.c
    STATUS=1
else
    OUT=top.c
fi
sed 's/^X//' > $OUT <<'@//E*O*F top.c//'
Xchar *copyright =
X    "Top, version 2.0, copyright (c) 1984, 1986, William LeFebvre";

X/*
X *  Top users display for Berkeley Unix
X *  Version 2.0
X *
X *  This program may be freely redistributed to other Unix sites, but this
X *  entire comment MUST remain intact.
X *
X *  Copyright (c) 1984, 1986, William LeFebvre, Rice University
X *
X *  This program is designed to run on either Berkeley 4.1 or 4.2 Unix.
X *  Compile with the preprocessor constant "FOUR_ONE" set to get an
X *  executable that will run on Berkeley 4.1 Unix.
X *
X *  The Sun kernel uses scaled integers instead of floating point.
X *  Compilation with the preprocessor variable "sun" gets an executable
X *  that will run on Sun Unix version 1.1 or later ("sun" is automatically
X *  set by the Sun C compiler).
X *
X *  The Pyramid splits the stack size (p_ssize) into control stack and user
X *  stack sizes.  Compilation with the preprocessor variable "pyr" gets an
X *  executable that will run on Pyramids ("pyr" is automatically set by the
X *  Pyramid C compiler).
X *
X *  See the file "Changes" for more information on version-to-version changes.
X */

X#include <stdio.h>
X#include <pwd.h>
X#include <nlist.h>
X#include <signal.h>
X#include <setjmp.h>
X#include <sys/param.h>
X#include <sys/dir.h>
X#include <sys/user.h>
X#include <sys/proc.h>
X#include <sys/dk.h>
X#include <sys/vm.h>

X/* includes specific to top */
X#include "layout.h"
X#include "screen.h"		/* interface to screen package */
X#include "top.h"
X#include "top.local.h"
X#include "boolean.h"

X/* Special atoi routine returns either a non-negative number or one of: */
X#define Infinity	-1
X#define Invalid		-2

X/* Size of the stdio buffer given to stdout */
X#define Buffersize	2048

X/* The buffer the stdio will use */
Xchar stdoutbuf[Buffersize];

X/* wish list for kernel symbols */

Xstruct nlist nlst[] = {
X    { "_avenrun" },
X#define X_AVENRUN	0
X    { "_ccpu" },
X#define X_CCPU		1
X    { "_cp_time" },
X#define X_CP_TIME	2
X    { "_hz" },
X#define X_HZ		3
X    { "_mpid" },
X#define X_MPID		4
X    { "_nproc" },
X#define X_NPROC		5
X    { "_proc" },
X#define X_PROC		6
X    { "_total" },
X#define X_TOTAL		7
X    { 0 },
X};

X/* build Signal masks */
X#define Smask(s)	(1 << ((s) - 1))

X/* for system errors */
Xextern int errno;

X/* for getopt: */
Xextern int  optind;
Xextern char *optarg;

X/* signal handling routines */
Xint leave();
Xint onalrm();
Xint tstop();

Xint nproc;
Xint mpid;

X/* kernel "hz" variable -- clock rate */
Xlong hz;

X/* All this is to calculate the cpu state percentages */

Xlong cp_time[CPUSTATES];
Xlong cp_old[CPUSTATES];
Xlong cp_change[CPUSTATES];
Xlong total_change;
Xlong mpid_offset;
Xlong avenrun_offset;
Xlong cp_time_offset;
Xlong total_offset;

X#ifdef sun
Xlong ccpu;
Xlong avenrun[3];
X#else
Xdouble ccpu;
Xdouble avenrun[3];
X#endif
Xdouble logcpu;

Xstruct vmtotal total;

Xstruct proc *proc;
Xstruct proc *pbase;
Xint bytes;
Xint initialized = No;
Xchar *myname = "top";
Xjmp_buf jmp_int;
Xchar input_waiting = No;

X/* routines that don't return int */

Xstruct passwd *getpwent();
Xchar *username();
Xchar *itoa();
Xchar *ctime();
Xchar *rindex();
Xchar *kill_procs();
Xchar *renice_procs();

Xint proc_compar();
Xlong time();

X/* different routines for displaying the user's identification */
X/* (values assigned to get_userid) */
Xchar *username();
Xchar *itoa7();

X/* display routines that need to be predeclared */
Xint i_loadave();
Xint u_loadave();
Xint i_procstates();
Xint u_procstates();
Xint i_cpustates();
Xint u_cpustates();
Xint i_memory();
Xint u_memory();
Xint i_header();
Xint u_header();
Xint i_process();
Xint u_process();

X/* pointers to display routines */
Xint (*d_loadave)() = i_loadave;
Xint (*d_procstates)() = i_procstates;
Xint (*d_cpustates)() = i_cpustates;
Xint (*d_memory)() = i_memory;
Xint (*d_header)() = i_header;
Xint (*d_process)() = i_process;

X/* buffer of proc information lines for display updating */
X/* unfortunate that this must be declared globally */
Xchar (* screenbuf)[Display_width];

Xmain(argc, argv)

Xint  argc;
Xchar *argv[];

X{
X    register struct proc *pp;
X    register struct proc **prefp;
X    register int i;
X    register int active_procs;
X    register int change;

X    static struct proc **pref;
X    static char tempbuf1[50];
X    static char tempbuf2[50];
X    int total_procs;
X    int old_sigmask;
X    int proc_brkdn[7];
X    int topn = Default_TOPN;
X    int delay = Default_DELAY;
X    int displays = 0;		/* indicates unspecified */
X    long curr_time;
X    char *(*get_userid)() = username;
X    char *uname_field = "USERNAME";
X#ifndef FOUR_ONE
X    char ch;
X    char msg_showing = 0;
X    int readfds;
X    struct timeval timeout;
X#endif    
X    char dostates = No;
X    char do_unames = Yes;
X    char do_init = No;
X    char interactive = Maybe;
X    char show_sysprocs = No;
X    char topn_specified = No;
X    char warnings = 0;

X    /* set the buffer for stdout */
X    setbuffer(stdout, stdoutbuf, Buffersize);

X    /* get our name */
X    if (argc > 0)
X    {
X	if ((myname = rindex(argv[0], '/')) == 0)
X	{
X	    myname = argv[0];
X	}
X	else
X	{
X	    myname++;
X	}
X    }

X    /* process options */
X    while ((i = getopt(argc, argv, "Sbinus:d:")) != EOF)
X    {
X	switch(i)
X	{
X	    case 'u':			/* display uid instead of name */
X		do_unames = No;
X		uname_field = "   UID  ";
X		get_userid = itoa7;
X		break;

X	    case 'S':			/* show system processes */
X		show_sysprocs = Yes;
X		break;

X	    case 'i':			/* go interactive regardless */
X		interactive = Yes;
X		break;

X	    case 'n':			/* batch, or non-interactive */
X	    case 'b':
X		interactive = No;
X		break;

X	    case 'd':			/* number of displays to show */
X		if ((i = atoiwi(optarg)) == Invalid || i == 0)
X		{
X		    fprintf(stderr,
X			"%s: warning: display count should be positive -- option ignored\n",
X			myname);
X		    warnings++;
X		}
X		else
X		{
X		    displays = i;
X		}
X		break;

X	    case 's':
X		if ((delay = atoi(optarg)) < 0)
X		{
X		    fprintf(stderr,
X			"%s: warning: seconds delay should be non-negative -- using default\n",
X			myname);
X		    delay = Default_DELAY;
X		    warnings++;
X		}
X		break;

X	    default:
X		fprintf(stderr,
X		    "Usage: %s [-Sbinu] [-d x] [-s x] [number]\n",
X		    myname);
X		exit(1);
X	}
X    }

X    /* get count of top processes to display (if any) */
X    if (optind < argc)
X    {
X	if ((topn = atoiwi(argv[optind])) == Invalid)
X	{
X	    fprintf(stderr,
X		"%s: warning: process display count should be non-negative -- using default\n",
X		myname);
X	    topn = Default_TOPN;
X	    warnings++;
X	}
X	else
X	{
X	    topn_specified = Yes;
X	}
X    }

X    /* initialize the kernel memory interface */
X    init_kernel();

X    if (initialized != 1)
X    {
X	/* get the list of symbols we want to access in the kernel */
X	/* errno = 0; ??? */
X	nlist(VMUNIX, nlst);
X	if (nlst[0].n_type == 0)
X	{
X	    fprintf(stderr, "%s: can't nlist image\n", VMUNIX);
X	    exit(2);
X	}
X    
X	/* get the symbol values out of kmem */
X	getkval(nlst[X_PROC].n_value,  &proc,  sizeof(int),
X		nlst[X_PROC].n_name);
X	getkval(nlst[X_NPROC].n_value, &nproc, sizeof(int),
X		nlst[X_NPROC].n_name);
X	getkval(nlst[X_HZ].n_value,    &hz,    sizeof(int),
X		nlst[X_HZ].n_name);
X	getkval(nlst[X_CCPU].n_value,  &ccpu,  sizeof(int),
X		nlst[X_CCPU].n_name);
X    
X	/* some calculations we use later */
X    
X	mpid_offset = nlst[X_MPID].n_value;
X	avenrun_offset = nlst[X_AVENRUN].n_value;
X	cp_time_offset = nlst[X_CP_TIME].n_value;
X	total_offset = nlst[X_TOTAL].n_value;
X    
X	/* this is used in calculating WCPU -- calculate it ahead of time */
X#ifdef sun
X	logcpu = log((double)ccpu / FSCALE);
X#else
X	logcpu = log(ccpu);
X#endif
X    
X	/* allocate space for proc structure array and array of pointers */
X	bytes  = nproc * sizeof(struct proc);
X	pbase  = (struct proc *)sbrk(bytes);
X	pref   = (struct proc **)sbrk(nproc * sizeof(struct proc *));

X	/* Just in case ... */
X	if (pbase == (struct proc *)NULL || pref == (struct proc **)NULL)
X	{
X	    fprintf(stderr, "%s: can't allocate sufficient memory\n", myname);
X	    exit(3);
X	}
X    
X	/* initialize the hashing stuff */
X	if (do_unames)
X	{
X	    init_hash();
X	}
X	
X	if (do_init)
X	{
X	    initialized = 1;
X	    kill(0, SIGQUIT);
X	    exit(99);
X	}
X    }

X    /* initialize termcap */
X    init_termcap();

X    /*
X     *  Smart terminals can only display so many processes, precisely
X     *	"screen_length - Header_lines".  When run on dumb terminals, nothing
X     *	fancy is done anyway, so we can display as many processes as the
X     *	system can make.  But since we never need to remember what is on the
X     *	screen, we only allocate a buffer for one screen line.
X     */
X    if (smart_terminal)
X    {
X	/* can only display (screen_length - Header_lines) processes */
X	i = screen_length - Header_lines;
X	if (topn > i)		/* false even when topn == Infinity */
X	{
X	    fprintf(stderr,
X		"%s: warning: this terminal can only display %d processes.\n",
X		myname, screen_length - Header_lines);
X	    topn = i;
X	    warnings++;
X	}
X    }
X    else
X    {
X	i = 1;
X	screen_length = nproc + Header_lines;
X    }

X    /* allocate space for the screen buffer */
X    screenbuf = (char (*)[])sbrk(i * Display_width);
X    if (screenbuf == (char (*)[])NULL)
X    {
X	fprintf(stderr, "%s: can't allocate sufficient memory\n", myname);
X	exit(4);
X    }

X    /* adjust for topn == Infinity */
X    if (topn == Infinity)
X    {
X	/*
X	 *  For smart terminals, infinity really means everything that can
X	 *  be displayed (which just happens to be "i" at this point).
X	 *  On dumb terminals, infinity means every process in the system!
X	 *  We only really want to do that if it was explicitly specified.
X	 *  This is always the case when "Default_TOPN != Infinity".  But if
X	 *  topn wasn't explicitly specified and we are on a dumb terminal
X	 *  and the default is Infinity, then (and only then) we use
X	 *  "Nominal_TOPN" instead.
X	 */
X#if Default_TOPN == Infinity
X	topn = smart_terminal ? i :
X		    (topn_specified ? nproc : Nominal_TOPN);
X#else
X	topn = smart_terminal ? i : nproc;
X#endif
X    }

X    /* determine interactive state */
X    if (interactive == Maybe)
X    {
X	interactive = smart_terminal;
X    }

X    /* if # of displays not specified, fill it in */
X    if (displays == 0)
X    {
X	displays = smart_terminal ? Infinity : 1;
X    }

X    /* hold interrupt signals while setting up the screen and the handlers */
X#ifndef FOUR_ONE
X    old_sigmask = sigblock(Smask(SIGINT) | Smask(SIGQUIT) | Smask(SIGTSTP));
X#endif
X    init_screen();
X    signal(SIGINT, leave);
X    signal(SIGQUIT, leave);
X    signal(SIGTSTP, tstop);
X#ifndef FOUR_ONE
X    sigsetmask(old_sigmask);
X#endif
X    if (warnings)
X    {
X	fprintf(stderr, "....");
X	fflush(stderr);			/* why must I do this? */
X	sleep(3 * warnings);
X    }
X    clear();

X    /* setup the jump buffer for stops */
X    if (setjmp(jmp_int) != 0)
X    {
X	/* control ends up here after an interrupt */
X	clear();
X	reset_display();
X    }

X    /*
X     *  main loop -- repeat while display count is positive or while it
X     *		indicates infinity (by being -1)
X     */

X    while ((displays == -1) || (displays-- > 0))
X    {
X	/* read all the proc structures in one fell swoop */
X	getkval(proc, pbase, bytes, "proc array");

X	/* get the cp_time array */
X	getkval(cp_time_offset, cp_time, sizeof(cp_time), "_cp_time");

X	/* get load average array */
X	getkval(avenrun_offset, avenrun, sizeof(avenrun), "_avenrun");

X	/* get mpid -- process id of last process */
X	getkval(mpid_offset, &mpid, sizeof(mpid), "_mpid");

X	/* get total -- systemwide main memory usage structure */
X	getkval(total_offset, &total, sizeof(total), "_total");

X	/* count up process states and get pointers to interesting procs */
X	total_procs = 0;
X	active_procs = 0;
X	bzero(proc_brkdn, sizeof(proc_brkdn));
X	prefp = pref;
X	for (pp = pbase, i = 0; i < nproc; 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	    if (pp->p_stat != 0 &&
X		(show_sysprocs || ((pp->p_flag & SSYS) == 0)))
X	    {
X		total_procs++;
X		proc_brkdn[pp->p_stat]++;
X		if (pp->p_stat != SZOMB)
X		{
X		    *prefp++ = pp;
X		    active_procs++;
X		}
X	    }
X	}

X	/* display the load averages */
X	(*d_loadave)(mpid, avenrun);

X	/*
X	 *  Display the current time.
X	 *  "ctime" always returns a string that looks like this:
X	 *  
X	 *	Sun Sep 16 01:03:52 1973
X	 *      012345678901234567890123
X	 *	          1         2
X	 *
X	 *  We want indices 11 thru 18 (length 8).
X	 */

X	curr_time = time(0);
X	if (smart_terminal)
X	{
X	    Move_to(screen_width - 8, 0);
X	}
X	else
X	{
X	    fputs("    ", stdout);
X	}
X	printf("%-8.8s\n", &(ctime(&curr_time)[11]));

X	/* display process state breakdown */
X	(*d_procstates)(total_procs, proc_brkdn);

X	/* calculate percentage time in each cpu state */
X	if (dostates)	/* but not the first time */
X	{
X	    total_change = 0;
X	    for (i = 0; i < CPUSTATES; i++)
X	    {
X		/* calculate changes for each state and overall change */
X		if (cp_time[i] < cp_old[i])
X		{
X		    /* this only happens when the counter wraps */
X		    change = (int)
X			((unsigned long)cp_time[i]-(unsigned long)cp_old[i]);
X		}
X		else
X		{
X		    change = cp_time[i] - cp_old[i];
X		}
X		total_change += (cp_change[i] = change);
X		cp_old[i] = cp_time[i];
X	    }
X	    (*d_cpustates)(cp_change, total_change);
X	}
X	else
X	{
X	    /* we'll do it next time */
X	    if (smart_terminal)
X	    {
X		z_cpustates();
X	    }
X	    else
X	    {
X		putchar('\n');
X	    }
X	    dostates = Yes;
X	    bzero(cp_old, sizeof(cp_old));
X	}

X	/* display main memory statistics */
X	(*d_memory)(
X		pagetok(total.t_rm), pagetok(total.t_arm),
X		pagetok(total.t_vm), pagetok(total.t_avm),
X		pagetok(total.t_free));

X	i = 0;
X	if (topn > 0)
X	{
X	    /* update the header area */
X	    (*d_header)(uname_field);
X    
X	    /* sort by cpu percentage (pctcpu) */
X	    qsort(pref, active_procs, sizeof(struct proc *), proc_compar);
X    
X	    /* adjust for a lack of processes */
X	    if (active_procs > topn)
X	    {
X		active_procs = topn;
X	    }

X	    /*
X	     *  Now, show the top "n" processes.  The method is slightly
X	     *	different for dumb terminals, so we will just use two very
X	     *	similar loops; this increases speed but also code size.
X	     */
X	    if (smart_terminal)
X	    {
X		for (prefp = pref, i = 0; i < active_procs; prefp++, i++)
X		{
X		    pp = *prefp;
X		    (*d_process)(i, pp, get_userid);
X		}
X	    }
X	    else for (prefp = pref, i = 0; i < active_procs; prefp++, i++)
X	    {
X		pp = *prefp;
X		/* (only one buffer lien with dumb terminals) */
X		(*d_process)(0, pp, get_userid);
X	    }
X	}

X	/* do end-screen processing */
X	u_endscreen(i);

X	/* now, flush the output buffer */
X	fflush(stdout);

X	/* only do the rest if we have more displays to show */
X	if (displays)
X	{
X	    /* switch out for new display on smart terminals */
X	    if (smart_terminal)
X	    {
X		d_loadave = u_loadave;
X		d_procstates = u_procstates;
X		d_cpustates = u_cpustates;
X		d_memory = u_memory;
X		d_header = u_header;
X		d_process = u_process;
X	    }
X    
X#ifndef FOUR_ONE
X	    if (!interactive)
X#endif
X	    {
X		/* set up alarm */
X		signal(SIGALRM, onalrm);
X		alarm(delay);
X    
X		/* wait for the rest of it .... */
X		pause();
X	    }
X#ifndef FOUR_ONE
X	    else
X	    {
X		/* wait for either input or the end of the delay period */
X		readfds = 1;			/* for standard input */
X		timeout.tv_sec  = delay;
X		timeout.tv_usec = 0;
X		if (select(32, &readfds, 0, 0, &timeout) > 0)
X		{
X		    int newval;
X		    char *errmsg;
X    
X		    /* something to read -- clear the message area first */
X		    if (msg_showing)
X		    {
X			if (smart_terminal)
X			{
X			    putcap(clear_line);
X			}
X			msg_showing = No;
X		    }

X		    /* now read it and act on it */
X		    read(0, &ch, 1);
X		    switch(ch)
X		    {
X			case '\f':		/* redraw screen */
X			    reset_display();
X			    clear();
X			    break;

X			case ' ':		/* merely update display */
X			    break;
X	
X			case 'q':		/* quit */
X			    quit(0);
X			    break;
X	
X			case 'h':		/* help */
X			case '?':
X			    reset_display();
X			    clear();
X			    show_help();
X			    standout("Hit any key to continue: ");
X			    fflush(stdout);
X    			    read(0, &ch, 1);
X			    clear();
X			    break;
X    
X			case 'e':		/* show errors */
X			    if (error_count() == 0)
X			    {
X				standout(" Currently no errors to report.");
X				msg_showing = Yes;
X			    }
X			    else
X			    {
X				reset_display();
X				clear();
X				show_errors();
X				standout("Hit any key to continue: ");
X				fflush(stdout);
X				read(0, &ch, 1);
X				clear();
X			    }
X			    break;
X    
X			case 'n':		/* new number */
X			case '#':
X			    standout("Number of processes to show: ");
X			    newval = readline(tempbuf1, 8, Yes);
X			    putchar('\r');
X			    if (newval > -1)
X			    {
X				if (newval > (i = screen_length - Header_lines))
X				{
X				    standout(
X				      " This terminal can only display %d processes.",
X				      i);
X				    newval = i;
X				    msg_showing = Yes;
X				    break;
X				}
X	
X				if (newval > topn)
X				{
X				    /* zero fill appropriate part of screenbuf */
X				    bzero(screenbuf[topn],
X					(newval - topn) * Display_width);
X	
X				    /* redraw header if need be */
X				    if (topn == 0)
X				    {
X					d_header = i_header;
X				    }
X				}
X				topn = newval;
X			    }
X			    putcap(clear_line);
X			    break;
X	
X			case 's':		/* new seconds delay */
X			    standout("Seconds to delay: ");
X			    if ((i = readline(tempbuf1, 8, Yes)) > -1)
X			    {
X				delay = i;
X			    }
X			    putchar('\r');
X			    putcap(clear_line);
X			    break;
X    
X			case 'd':		/* change display count */
X			    standout("Displays to show (currently %s): ",
X				    displays == -1 ? "infinite" :
X						     itoa(displays));
X			    if ((i = readline(tempbuf1, 10, Yes)) > 0)
X			    {
X				displays = i;
X			    }
X			    else if (i == 0)
X			    {
X				quit(0);
X			    }
X			    putchar('\r');
X			    putcap(clear_line);
X			    break;

X			case 'k':		/* kill program */
X			    fputs("kill ", stdout);
X			    if (readline(tempbuf2, sizeof(tempbuf2), No) > 0)
X			    {
X				if ((errmsg = kill_procs(tempbuf2)) != NULL)
X				{
X				    putchar('\r');
X				    standout(errmsg);
X				}
X				msg_showing = Yes;
X			    }
X			    else
X			    {
X				putchar('\r');
X			    }
X			    putcap(clear_line);
X			    break;
X	
X			case 'r':		/* renice program */
X			    fputs("renice ", stdout);
X			    if (readline(tempbuf2, sizeof(tempbuf2), No) > 0)
X			    {
X				if ((errmsg = renice_procs(tempbuf2)) != NULL)
X				{
X				    putchar('\r');
X				    standout(errmsg);
X				    msg_showing = Yes;
X				}
X			    }
X			    else
X			    {
X				putchar('\r');
X			    }
X			    putcap(clear_line);
X			    break;
X	
X			default:
X			    standout(" Command not understood");
X			    msg_showing = Yes;
X		    }
X		}
X		else if (msg_showing)
X		{
X		    if (smart_terminal)
X		    {
X			putcap(clear_line);
X		    }
X		    msg_showing = No;
X		}
X	    }
X#endif
X	}
X    }

X    quit(0);
X}

X/*
X *  reset_display() - reset all the display routine pointers so that entire
X *	screen will get redrawn.
X */

Xreset_display()

X{
X    d_loadave    = i_loadave;
X    d_procstates = i_procstates;
X    d_cpustates  = i_cpustates;
X    d_memory     = i_memory;
X    d_header	 = i_header;
X    d_process	 = i_process;
X}

Xreadline(buffer, size, numeric)

Xchar *buffer;
Xint  size;
Xint  numeric;

X{
X    register char *ptr = buffer;
X    register char ch;
X    register char cnt = 0;

X    size -= 1;
X    while ((fflush(stdout), read(0, ptr, 1) > 0))
X    {
X	if ((ch = *ptr) == '\n')
X	{
X	    break;
X	}

X	if (ch == ch_kill)
X	{
X	    *buffer = '\0';
X	    return(-1);
X	}
X	else if (ch == ch_erase)
X	{
X	    if (cnt <= 0)
X	    {
X		putchar('\7');
X	    }
X	    else
X	    {
X		fputs("\b \b", stdout);
X		ptr--;
X		cnt--;
X	    }
X	}
X	else if (cnt == size || (numeric && (ch < '0' || ch > '9')))
X	{
X	    putchar('\7');
X	}
X	else
X	{
X	    putchar(ch);
X	    ptr++;
X	    cnt++;
X	}
X    }
X    *ptr = '\0';
X    return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
X}

X/*
X *  signal handlers
X */

Xleave()			/* exit under normal conditions -- INT handler */

X{
X    end_screen();
X    exit(0);
X}

Xtstop()

X{
X    /* move to the lower left */
X    end_screen();
X    fflush(stdout);

X#ifdef FOUR_ONE		/* a 4.1 system */

X    /* send a STOP (uncatchable) to everyone in the process group */
X    kill(0, SIGSTOP);

X    /* reset the signal handler */
X    signal(SIGTSTP, tstop);

X#else			/* assume it is a 4.2 system */

X    /* default the signal handler action */
X    signal(SIGTSTP, SIG_DFL);

X    /* unblock the signal and send ourselves one */
X    sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
X    kill(0, SIGTSTP);

X    /* reset the signal handler */
X    signal(SIGTSTP, tstop);

X#endif
X    /* reinit screen */
X    reinit_screen();

X    /* jump to appropriate place */
X    longjmp(jmp_int, 1);

X    /*NOTREACHED*/
X}

Xquit(status)		/* exit under duress */

Xint status;

X{
X    end_screen();
X    exit(status);
X}

Xonalrm()

X{
X    return(0);
X}

X/*
X *  proc_compar - 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 */

Xunsigned 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_compar(pp1, pp2)

Xstruct proc **pp1;
Xstruct proc **pp2;

X{
X    register struct proc *p1;
X    register struct proc *p2;
X    register int result;
X#ifndef sun
X    register double dresult;
X#endif

X    /* remove one level of indirection */
X    p1 = *pp1;
X    p2 = *pp2;

X    /* compare percent cpu (pctcpu) */
X#ifdef sun
X    if ((result = p2->p_pctcpu - p1->p_pctcpu) == 0)
X#else
X    if ((dresult = p2->p_pctcpu - p1->p_pctcpu) == 0)
X#endif
X    {
X	/* use cpticks to break the tie */
X	if ((result = p2->p_cpticks - p1->p_cpticks) == 0)
X	{
X	    /* use process state to break the tie */
X	    if ((result = sorted_state[p2->p_stat] -
X			  sorted_state[p1->p_stat])  == 0)
X	    {
X		/* use priority to break the tie */
X		if ((result = p2->p_pri - p1->p_pri) == 0)
X		{
X		    /* use resident set size (rssize) to break the tie */
X		    if ((result = p2->p_rssize - p1->p_rssize) == 0)
X		    {
X			/* use total memory to break the tie */
X#ifdef pyr
X			result = (p2->p_tsize + p2->p_dsize + p2->p_ussize) -
X				 (p1->p_tsize + p1->p_dsize + p1->p_ussize);
X#else
X			result = (p2->p_tsize + p2->p_dsize + p2->p_ssize) -
X				 (p1->p_tsize + p1->p_dsize + p1->p_ssize);
X#endif
X		    }
X		}
X	    }
X	}
X    }
X#ifndef sun
X    else
X    {
X	result = dresult < 0.0 ? -1 : 1;
X    }
X#endif

X    return(result);
X}

X/* routines to translate uids into a string */

Xchar *user_name(euid, ruid)

Xint euid, ruid;

X{
X    return(username(euid));
X}

Xchar *user_uid(euid, ruid)

Xint euid, ruid;

X{
X    return(itoa7(euid));
X}

X/*
X *  These routines handle uid to username mapping.
X *  They use a hashing table scheme to reduce reading overhead.
X */

Xstruct hash_el {
X    int  uid;
X    char name[8];
X};

X#define    H_empty	-1

X/* simple minded hashing function */
X#define    hashit(i)	((i) % Table_size)

Xstruct hash_el hash_table[Table_size];

Xinit_hash()

X{
X    register int i;
X    register struct hash_el *h;

X    for (h = hash_table, i = 0; i < Table_size; h++, i++)
X    {
X	h->uid = H_empty;
X    }
X}

Xchar *username(uid)

Xregister int uid;

X{
X    register int index;
X    register int found;
X    register char *name;

X    /* This is incredibly naive, but it'll probably get changed anyway */
X    index = hashit(uid);
X    while ((found = hash_table[index].uid) != uid)
X    {
X	if (found == H_empty)
X	{
X	    /* not here -- get it out of passwd */
X	    index = get_user(uid);
X	    break;		/* out of while */
X	}
X	index = (index + 1) % Table_size;
X    }
X    return(hash_table[index].name);
X}

Xenter_user(uid, name)

Xregister int  uid;
Xregister char *name;

X{
X    register int length;
X    register int index;
X    register int try;
X    static int uid_count = 0;

X    /* avoid table overflow -- insure at least one empty slot */
X    if (++uid_count >= Table_size)
X    {
X	fprintf(stderr, "table overflow: too many users\n");
X	quit(10);
X    }

X    index = hashit(uid);
X    while ((try = hash_table[index].uid) != H_empty)
X    {
X	if (try == uid)
X	{
X	    return(index);
X	}
X	index = (index + 1) % Table_size;
X    }
X    hash_table[index].uid = uid;
X    strncpy(hash_table[index].name, name, 8);
X    return(index);
X}

Xget_user(uid)

Xregister int uid;

X{
X    struct passwd *pwd;
X    register int last_index;

X    while ((pwd = getpwent()) != NULL)
X    {
X	last_index = enter_user(pwd->pw_uid, pwd->pw_name);
X	if (pwd->pw_uid == uid)
X	{
X	    return(last_index);
X	}
X    }
X    return(enter_user(uid, itoa7(uid)));
X}

Xatoiwi(str)

Xchar *str;

X{
X    register int len;

X    len = strlen(str);
X    if (len != 0)
X    {
X	if (strncmp(str, "infinity", len) == 0 ||
X	    strncmp(str, "all",      len) == 0 ||
X	    strncmp(str, "maximum",  len) == 0)
X	{
X	    return(Infinity);
X	}
X	else if (str[0] == '-')
X	{
X	    return(Invalid);
X	}
X	else
X	{
X	    return(atoi(str));
X	}
X    }
X    return(0);
X}

X/*
X *  itoa - convert integer (decimal) to ascii string for positive numbers
X *  	   only (we don't bother with negative numbers since we know we
X *	   don't use them).
X */

Xstatic char buffer[16];		/* shared by the next two routines */

Xchar *itoa(val)

Xregister int val;

X{
X    register char *ptr;

X    ptr = buffer + sizeof(buffer);
X    *--ptr = '\0';
X    if (val == 0)
X    {
X	*--ptr = '0';
X    }
X    else while (val != 0)
X    {
X	*--ptr = (val % 10) + '0';
X	val /= 10;
X    }
X    return(ptr);
X}

X/*
X *  itoa7(val) - like itoa, except the number is right justified in a 7
X *	character field.  This code is a duplication of itoa instead of
X *	a front end to a more general routine for efficiency.
X */

Xchar *itoa7(val)

Xregister int val;

X{
X    register char *ptr;

X    ptr = buffer + sizeof(buffer);
X    *--ptr = '\0';
X    if (val == 0)
X    {
X	*--ptr = '0';
X    }
X    else while (val != 0)
X    {
X	*--ptr = (val % 10) + '0';
X	val /= 10;
X    }
X    while (ptr > buffer + sizeof(buffer) - 7)
X    {
X	*--ptr = ' ';
X    }
X    return(ptr);
X}

@//E*O*F top.c//
chmod u=rw,g=rw,o=rw $OUT
 
echo x - top.local.h
if test -f top.local.h ; then
    echo top.local.h exists, putting output in $$top.local.h
    OUT=$$top.local.h
    STATUS=1
else
    OUT=top.local.h
fi
sed 's/^X//' > $OUT <<'@//E*O*F top.local.h//'
X/*
X *  Top - a top users display for Berkeley Unix
X *
X *  Definitions for things that might vary between installations.
X */

X/*
X *  "Table_size" defines the size of the hash tables used to map uid to
X *  username.  The number of users in /etc/passwd CANNOT be greater than
X *  this number.  If the error message "table overflow: too many users"
X *  is printed by top, then "Table_size" needs to be increased.  Things will
X *  work best if the number is a prime number that is about twice the number
X *  of lines in /etc/passwd.
X */
X#ifndef Table_size
X#define Table_size	421
X#endif

X/*
X *  "Nominal_TOPN" is used as the default TOPN when Default_TOPN is Infinity
X *  and the output is a dumb terminal.  If we didn't do this, then
X *  installations who use a default TOPN of Infinity will get every
X *  process in the system when running top on a dumb terminal (or redirected
X *  to a file).  Note that Nominal_TOPN is a default:  it can still be
X *  overridden on the command line, even with the value "infinity".
X */
X#ifndef Nominal_TOPN
X#define Nominal_TOPN	18
X#endif

X/*
X *  File name for the system image and the memory devices.
X */
X#define VMUNIX	"/vmunix"
X#define KMEM	"/dev/kmem"
X#define MEM	"/dev/mem"
@//E*O*F top.local.h//
chmod u=rw,g=rw,o=rw $OUT
 
echo x - top.man
if test -f top.man ; then
    echo top.man exists, putting output in $$top.man
    OUT=$$top.man
    STATUS=1
else
    OUT=top.man
fi
sed 's/^X//' > $OUT <<'@//E*O*F top.man//'
X.\" NOTE:  changes to the manual page for "top" should be made in the
X.\"        file "top.man" and NOT in the file "top.1".
X.TH TOP 1 Local
X.UC 4
X.SH NAME
Xtop \- display and update information about the top cpu processes
X.SH SYNOPSIS
X.B top
X[
X.B \-Sbinu
X] [
X.BI \-d count
X] [
X.BI \-s time
X] [
X.I number
X]
X.SH DESCRIPTION
X.\" This defines appropriate quote strings for nroff and troff
X.ds lq \&"
X.ds rq \&"
X.if t .ds lq ``
X.if t .ds rq ''
X.\" Just in case these number registers aren't set yet...
X.if \nN==0 .nr N 10
X.if \nD==0 .nr D 5
X.I Top
Xdisplays the top
X.if !\nN==-1 \nN
Xprocesses on the system and periodically updates this information.
X.if \nN==-1 \
X\{\
XIf standard output is an intelligent terminal (see below) then
Xas many processes as will fit on the terminal screen are displayed
Xby default.  Otherwise, a good number of them are shown (around 20).
X.\}
XRaw cpu percentage is used to rank the processes.  If
X.I number
Xis given, then the top
X.I number
Xprocesses will be displayed instead of the default.
X.PP
X.I Top
Xmakes a distinction between terminals that support advanced capabilities
Xand those that do not.  This
Xdistinction affects the choice of defaults for certain options.  In the
Xremainder of this document, an \*(lqintelligent\*(rq terminal is one that
Xsupports cursor addressing, clear screen, and clear to end of line.
XConversely, a \*(lqdumb\*(rq terminal is one that does not support such
Xfeatures.  If the output of
X.I top
Xis redirected to a file, it acts as if it were being run on a dumb
Xterminal.
X.SH OPTIONS
X.TP
X.B \-S
XShow system processes in the display.  Normally, system processes such as
Xthe pager and the swapper are not shown.  This option makes them visible.
X.TP
X.B \-b
XUse \*(lqbatch\*(rq mode.  In this mode, all input from the terminal is
Xignored.  Interrupt characters (such as ^C and ^\e) still have an effect.
XThis is the default on a dumb terminal, or when the output is not a terminal.
X.TP
X.B \-i
XUse \*(lqinteractive\*(rq mode.  In this mode, any input is immediately
Xread for processing.  See the section on \*(lqInteractive Mode\*(rq
Xfor an explanation of
Xwhich keys perform what functions.  After the command is processed, the
Xscreen will immediately be updated, even if the command was not
Xunderstood.  This mode is the default when standard output is an
Xintelligent terminal.
X.TP
X.B \-n
XUse \*(lqnon-interactive\*(rq mode.  This is indentical to \*(lqbatch\*(rq
Xmode.
X.TP
X.B \-u
XDo not take the time to map uid numbers to usernames.  Normally,
X.I top
Xwill read as much of the file \*(lq/etc/passwd\*(rq as is necessary to map
Xall the user id numbers it encounters into login names.  This option
Xdisables all that, while possibly decreasing execution time.  The uid
Xnumbers are displayed instead of the names.
X.TP
X.BI \-d count
XShow only
X.I count
Xdisplays, then exit.  A display is considered to be one update of the
Xscreen.  This option allows the user to select the number of displays he
Xwants to see before
X.I top
Xautomatically exits.  For intelligent terminals, no upper limit
Xis set.  The default is 1 for dumb terminals.
X.TP
X.BI \-s time
XSet the delay between screen updates to
X.I time
Xseconds.  The default delay between updates is \nD seconds.
X.PP
XBoth
X.I count
Xand
X.I number
Xfields can be specified as \*(lqinfinite\*(rq, indicating that they can
Xstretch as far as possible.  This is accomplished by using any proper
Xprefix of the keywords
X\*(lqinfinity\*(rq,
X\*(lqmaximum\*(rq,
Xor
X\*(lqall\*(rq.
XThe default for
X.I count
Xon an intelligent terminal is, in fact,
X.BI infinity .
X.SH "INTERACTIVE MODE"
XWhen
X.I top
Xis running in \*(lqinteractive mode\*(rq, it reads commands from the
Xterminal and acts upon them accordingly.  In this mode, the terminal is
Xput in \*(lqCBREAK\*(rq, so that a character will be
Xprocessed as soon as it is typed.  Almost always, a key will be
Xpressed when
X.I top
Xis between displays; that is, while it is waiting for
X.I time
Xseconds to elapse.  If this is the case, the command will be
Xprocessed and the display will be updated immediately thereafter
X(reflecting any changes that the command may have specified).  This
Xhappens even if the command was incorrect.  If a key is pressed while 
X.I top
Xis in the middle of updating the display, it will finish the update and
Xthen process the command.  Some commands require additional information,
Xand the user will be prompted accordingly.  While typing this information
Xin, the user's erase and kill keys (as set up by the command
X.IR stty )
Xare recognized, and a newline terminates the input.
X.PP
XThese commands are currently recognized (^L refers to control-L):
X.TP
X.B ^L
XRedraw the screen.
X.IP "\fBh\fP\ or\ \fB?\fP"
XDisplay a summary of the commands (help screen).
X.TP
X.B q
XQuit
X.IR top.
X.TP
X.B d
XChange the number of displays to show (prompt for new number).
XRemember that the next display counts as one, so typing
X.B d1
Xwill make
X.I top
Xshow one final display and then immediately exit.
X.TP
X.B n or #
XChange the number of processes to display (prompt for new number).
X.TP
X.B s
XChange the number of seconds to delay between displays
X(prompt for new number).
X.TP
X.B k
XSend a signal (\*(lqkill\*(rq by default) to a list of processes.  This
Xacts similarly to the command
X.IR kill (1)).
X.TP
X.B r
XChange the priority (the \*(lqnice\*(rq) of a list of processes.
XThis acts similarly to the command
X.IR renice (8)).
X.TP
X.B e
XDisplay a list of system errors (if any) generated by the last
X.BR k ill
Xor
X.BR r enice
Xcommand.
X.SH "THE DISPLAY"
XThe top few lines of the display show general information
Xabout the state of the system, including
Xthe last process id assigned to a process,
Xthe three load averages,
Xthe current time,
Xthe number of existing processes,
Xthe number of processes in each state
X(sleeping, ABANDONED, running, starting, zombies, and stopped),
Xand a percentage of time spent in each of the processor states
X(user, nice, system, and idle).
XIt also includes the amount of virtual and real memory in use
X(with the amount of memory considered \*(lqactive\*(rq in parentheses) and
Xthe amount of free memory.
X.PP
XThe remainder of the screen displays information about individual
Xprocesses.  This display is similar in spirit to
X.IR ps (1)
Xbut it is not exactly the same.  PID is the process id, USERNAME is the name
Xof the process's owner (if
X.B \-u
Xis specified, a UID column will be substituted for USERNAME),
XPRI is the current priority of the process,
XNICE is the nice amount (in the range \-20 to 20),
XSIZE is the total size of the process (text, data, and stack),
XRES is the current amount of resident memory (both SIZE and RES are
Xgiven in kilobytes),
XSTATE is the current state (one of \*(lqsleep\*(rq, \*(lqWAIT\*(rq,
X\*(lqrun\*(rq, \*(lqidl\*(rq, \*(lqzomb\*(rq, or \*(lqstop\*(rq),
XTIME is the number of system and user cpu seconds that the process has used,
XWCPU is the weighted cpu percentage (this is the same value that
X.IR ps (1)
Xdisplays as CPU),
XCPU is the raw percentage and is the field that is sorted to determine
Xthe order of the processes, and
XCOMMAND is the name of the command that the process is currently running
X(if the process is swapped out, this column is marked \*(lq<swapped>\*(rq).
X.SH NOTES
XThe \*(lqABANDONED\*(rq state (known in the kernel as \*(lqSWAIT\*(rq) was
Xabandoned, thus the name.  A process should never end up in this state.
X.SH AUTHOR
XWilliam LeFebvre, Rice University graduate student
X.SH FILES
X.DT
X/dev/kmem		kernel memory
X.br
X/dev/mem		physical memory
X.br
X/etc/passwd		used to map uid numbers to user names
X.br
X/vmunix		system image
X.SH BUGS
XThe command name for swapped processes should be tracked down, but this
Xwould make the program run slower.
X.PP
XAs with
X.IR ps (1),
Xthings can change while
X.I top
Xis collecting information for an update.  The picture it gives is only a
Xclose approximation to reality.
X.SH "SEE ALSO"
Xkill(1),
Xps(1),
Xstty(1),
Xmem(4),
Xrenice(8)
@//E*O*F top.man//
chmod u=rw,g=rw,o=rw $OUT
 
echo x - MANIFEST
if test -f MANIFEST ; then
    echo MANIFEST exists, putting output in $$MANIFEST
    OUT=$$MANIFEST
    STATUS=1
else
    OUT=MANIFEST
fi
sed 's/^X//' > $OUT <<'@//E*O*F MANIFEST//'
XChanges                      1
XMANIFEST                     2
XMakefile                     1
XManifest                     1
XREADME                       1
Xboolean.h                    1
Xbzero.c                      1
Xcommands.c                   1
Xdisplay.c                    1
Xgetopt.c                     1
Xkernel.c                     1
Xlayout.h                     1
Xscreen.c                     1
Xscreen.h                     1
Xsigconv.awk                  1
Xtop.c                        2
Xtop.h                        1
Xtop.local.h                  2
Xtop.man                      2
@//E*O*F MANIFEST//
chmod u=rw,g=rw,o=rw $OUT
 
echo Inspecting for damage in transit...
temp=/tmp/sharin$$; dtemp=/tmp/sharout$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
    1257    3700   26262 top.c
      36     209    1209 top.local.h
     247    1361    7861 top.man
      19      38     589 MANIFEST
    1559    5308   35921 total
!!!
wc  top.c top.local.h top.man MANIFEST | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if test -s $dtemp ; then
    echo "Ouch [diff of wc output]:"
    cat $dtemp
    STATUS=1
elif test $STATUS = 0 ; then
    echo "No problems found."
else
    echo "WARNING -- PROBLEMS WERE FOUND..."
fi
exit $STATUS