summaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
authorJason Downs <downsj@cvs.openbsd.org>1997-08-14 14:00:29 +0000
committerJason Downs <downsj@cvs.openbsd.org>1997-08-14 14:00:29 +0000
commit6e46887b7158d427eebc23ab135222ed4df2bdf0 (patch)
tree3c75f1ac16fc0820700961b0f91b18496054cb6d /usr.bin
parent44dbc9719c1c86a71535213a3f43c4cee4a4b770 (diff)
top 3.4, with a few changes. Still needs more work.
Diffstat (limited to 'usr.bin')
-rw-r--r--usr.bin/top/DISCLAIMER30
-rw-r--r--usr.bin/top/Makefile22
-rw-r--r--usr.bin/top/boolean.h7
-rw-r--r--usr.bin/top/commands.c511
-rw-r--r--usr.bin/top/display.c1131
-rw-r--r--usr.bin/top/display.h9
-rw-r--r--usr.bin/top/layout.h29
-rw-r--r--usr.bin/top/loadavg.h59
-rw-r--r--usr.bin/top/machine.c818
-rw-r--r--usr.bin/top/machine.h60
-rw-r--r--usr.bin/top/os.h31
-rw-r--r--usr.bin/top/patchlevel.h3
-rw-r--r--usr.bin/top/screen.c496
-rw-r--r--usr.bin/top/screen.h33
-rw-r--r--usr.bin/top/sigconv.awk53
-rw-r--r--usr.bin/top/top.1321
-rw-r--r--usr.bin/top/top.c998
-rw-r--r--usr.bin/top/top.h38
-rw-r--r--usr.bin/top/top.local.h70
-rw-r--r--usr.bin/top/username.c187
-rw-r--r--usr.bin/top/utils.c457
-rw-r--r--usr.bin/top/utils.h25
-rw-r--r--usr.bin/top/version.c27
23 files changed, 5415 insertions, 0 deletions
diff --git a/usr.bin/top/DISCLAIMER b/usr.bin/top/DISCLAIMER
new file mode 100644
index 00000000000..eabbe044847
--- /dev/null
+++ b/usr.bin/top/DISCLAIMER
@@ -0,0 +1,30 @@
+DISCLAIMER
+
+"top" is distributed free of charge. It should not be considered an
+official product of Argonne National Laboratory. William LeFebvre
+supports "top" in his spare time and as time permits.
+
+NO WARRANTY:
+
+BECAUSE "top" IS DISTRIBUTED FREE OF CHARGE, THERE IS ABSOLUTELY NO
+WARRANTY PROVIDED, TO THE EXTENT PERMITTED BY APPLICABLE STATE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING, ARGONNE NATIONAL LABORATORY,
+NORTHWESTERN UNIVERSITY, WILLIAM N. LeFEBVRE AND/OR OTHER PARTIES
+PROVIDE "top" "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
+AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD
+THE "top" PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+IN NO EVENT WILL ARGONNE NATIONAL LABORATORY, NORTHWESTERN UNIVERSITY,
+WILLIAM N. LeFEBVRE, AND/OR ANY OTHER PARTY WHO MAY MODIFY AND
+REDISTRIBUTE "top", BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY LOST
+PROFITS, LOST MONIES, OR OTHER SPECIAL, INCIDENTAL OR CONSEQUENTIAL
+DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE (INCLUDING BUT NOT
+LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES
+SUSTAINED BY THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH
+OTHER PROGRAMS) THE PROGRAM, EVEN IF YOU HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES, OR FOR ANY CLAIM BY ANY OTHER PARTY.
+
+So there!
diff --git a/usr.bin/top/Makefile b/usr.bin/top/Makefile
new file mode 100644
index 00000000000..aa428c726f2
--- /dev/null
+++ b/usr.bin/top/Makefile
@@ -0,0 +1,22 @@
+# $OpenBSD: Makefile,v 1.1 1997/08/14 14:00:20 downsj Exp $
+#
+# Makefile for OpenBSD top-3.4.
+
+PROG= top
+
+CFLAGS+=-I. -I${.CURDIR}
+SRCS= commands.c display.c machine.c screen.c top.c username.c utils.c \
+ version.c
+DPADD= ${LIBTERMLIB} ${LIBM} ${LIBKVM}
+LDADD= -ltermlib -lm -lkvm
+BINGRP= kmem
+BINMODE=2555
+
+CLEANFILES+= sigdesc.h
+
+.depend commands.o: sigdesc.h
+
+sigdesc.h:
+ awk -f ${.CURDIR}/sigconv.awk /usr/include/sys/signal.h > sigdesc.h
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/top/boolean.h b/usr.bin/top/boolean.h
new file mode 100644
index 00000000000..fd3861cab1c
--- /dev/null
+++ b/usr.bin/top/boolean.h
@@ -0,0 +1,7 @@
+/* $OpenBSD: boolean.h,v 1.1 1997/08/14 14:00:20 downsj Exp $ */
+
+/* My favorite names for boolean values */
+#define No 0
+#define Yes 1
+#define Maybe 2 /* tri-state boolean, actually */
+
diff --git a/usr.bin/top/commands.c b/usr.bin/top/commands.c
new file mode 100644
index 00000000000..a03c4d7ac89
--- /dev/null
+++ b/usr.bin/top/commands.c
@@ -0,0 +1,511 @@
+/* $OpenBSD: commands.c,v 1.1 1997/08/14 14:00:20 downsj Exp $ */
+
+/*
+ * Top users/processes display for Unix
+ * Version 3
+ *
+ * This program may be freely redistributed,
+ * but this entire comment MUST remain intact.
+ *
+ * Copyright (c) 1984, 1989, William LeFebvre, Rice University
+ * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
+ */
+
+/*
+ * This file contains the routines that implement some of the interactive
+ * mode commands. Note that some of the commands are implemented in-line
+ * in "main". This is necessary because they change the global state of
+ * "top" (i.e.: changing the number of processes to display).
+ */
+
+#include "os.h"
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include "sigdesc.h" /* generated automatically */
+#include "boolean.h"
+#include "utils.h"
+
+extern int errno;
+
+extern char *copyright;
+
+/* imported from screen.c */
+extern int overstrike;
+
+int err_compar();
+char *err_string();
+
+/*
+ * show_help() - display the help screen; invoked in response to
+ * either 'h' or '?'.
+ */
+
+show_help()
+
+{
+ printf("Top version %s, %s\n", version_string(), copyright);
+ fputs("\n\n\
+A top users display for Unix\n\
+\n\
+These single-character commands are available:\n\
+\n\
+^L - redraw screen\n\
+q - quit\n\
+h or ? - help; show this text\n", stdout);
+
+ /* not all commands are availalbe with overstrike terminals */
+ if (overstrike)
+ {
+ fputs("\n\
+Other commands are also available, but this terminal is not\n\
+sophisticated enough to handle those commands gracefully.\n\n", stdout);
+ }
+ else
+ {
+ fputs("\
+d - change number of displays to show\n\
+e - list errors generated by last \"kill\" or \"renice\" command\n\
+i - toggle the displaying of idle processes\n\
+I - same as 'i'\n\
+k - kill processes; send a signal to a list of processes\n\
+n or # - change number of processes to display\n", stdout);
+#ifdef ORDER
+ fputs("\
+o - specify sort order (size, res, cpu, time)\n", stdout);
+#endif
+ fputs("\
+r - renice a process\n\
+s - change number of seconds to delay between updates\n\
+u - display processes for only one user (+ selects all users)\n\
+\n\
+\n", stdout);
+ }
+}
+
+/*
+ * Utility routines that help with some of the commands.
+ */
+
+char *next_field(str)
+
+register char *str;
+
+{
+ if ((str = strchr(str, ' ')) == NULL)
+ {
+ return(NULL);
+ }
+ *str = '\0';
+ while (*++str == ' ') /* loop */;
+
+ /* if there is nothing left of the string, return NULL */
+ /* This fix is dedicated to Greg Earle */
+ return(*str == '\0' ? NULL : str);
+}
+
+scanint(str, intp)
+
+char *str;
+int *intp;
+
+{
+ register int val = 0;
+ register char ch;
+
+ /* if there is nothing left of the string, flag it as an error */
+ /* This fix is dedicated to Greg Earle */
+ if (*str == '\0')
+ {
+ return(-1);
+ }
+
+ while ((ch = *str++) != '\0')
+ {
+ if (isdigit(ch))
+ {
+ val = val * 10 + (ch - '0');
+ }
+ else if (isspace(ch))
+ {
+ break;
+ }
+ else
+ {
+ return(-1);
+ }
+ }
+ *intp = val;
+ return(0);
+}
+
+/*
+ * Some of the commands make system calls that could generate errors.
+ * These errors are collected up in an array of structures for later
+ * contemplation and display. Such routines return a string containing an
+ * error message, or NULL if no errors occurred. The next few routines are
+ * for manipulating and displaying these errors. We need an upper limit on
+ * the number of errors, so we arbitrarily choose 20.
+ */
+
+#define ERRMAX 20
+
+struct errs /* structure for a system-call error */
+{
+ int errno; /* value of errno (that is, the actual error) */
+ char *arg; /* argument that caused the error */
+};
+
+static struct errs errs[ERRMAX];
+static int errcnt;
+static char *err_toomany = " too many errors occurred";
+static char *err_listem =
+ " Many errors occurred. Press `e' to display the list of errors.";
+
+/* These macros get used to reset and log the errors */
+#define ERR_RESET errcnt = 0
+#define ERROR(p, e) if (errcnt >= ERRMAX) \
+ { \
+ return(err_toomany); \
+ } \
+ else \
+ { \
+ errs[errcnt].arg = (p); \
+ errs[errcnt++].errno = (e); \
+ }
+
+/*
+ * err_string() - return an appropriate error string. This is what the
+ * command will return for displaying. If no errors were logged, then
+ * return NULL. The maximum length of the error string is defined by
+ * "STRMAX".
+ */
+
+#define STRMAX 80
+
+char *err_string()
+
+{
+ register struct errs *errp;
+ register int cnt = 0;
+ register int first = Yes;
+ register int currerr = -1;
+ int stringlen; /* characters still available in "string" */
+ static char string[STRMAX];
+
+ /* if there are no errors, return NULL */
+ if (errcnt == 0)
+ {
+ return(NULL);
+ }
+
+ /* sort the errors */
+ qsort((char *)errs, errcnt, sizeof(struct errs), err_compar);
+
+ /* need a space at the front of the error string */
+ string[0] = ' ';
+ string[1] = '\0';
+ stringlen = STRMAX - 2;
+
+ /* loop thru the sorted list, building an error string */
+ while (cnt < errcnt)
+ {
+ errp = &(errs[cnt++]);
+ if (errp->errno != currerr)
+ {
+ if (currerr != -1)
+ {
+ if ((stringlen = str_adderr(string, stringlen, currerr)) < 2)
+ {
+ return(err_listem);
+ }
+ (void) strcat(string, "; "); /* we know there's more */
+ }
+ currerr = errp->errno;
+ first = Yes;
+ }
+ if ((stringlen = str_addarg(string, stringlen, errp->arg, first)) ==0)
+ {
+ return(err_listem);
+ }
+ first = No;
+ }
+
+ /* add final message */
+ stringlen = str_adderr(string, stringlen, currerr);
+
+ /* return the error string */
+ return(stringlen == 0 ? err_listem : string);
+}
+
+/*
+ * str_adderr(str, len, err) - add an explanation of error "err" to
+ * the string "str".
+ */
+
+str_adderr(str, len, err)
+
+char *str;
+int len;
+int err;
+
+{
+ register char *msg;
+ register int msglen;
+
+ msg = err == 0 ? "Not a number" : errmsg(err);
+ msglen = strlen(msg) + 2;
+ if (len <= msglen)
+ {
+ return(0);
+ }
+ (void) strcat(str, ": ");
+ (void) strcat(str, msg);
+ return(len - msglen);
+}
+
+/*
+ * str_addarg(str, len, arg, first) - add the string argument "arg" to
+ * the string "str". This is the first in the group when "first"
+ * is set (indicating that a comma should NOT be added to the front).
+ */
+
+str_addarg(str, len, arg, first)
+
+char *str;
+int len;
+char *arg;
+int first;
+
+{
+ register int arglen;
+
+ arglen = strlen(arg);
+ if (!first)
+ {
+ arglen += 2;
+ }
+ if (len <= arglen)
+ {
+ return(0);
+ }
+ if (!first)
+ {
+ (void) strcat(str, ", ");
+ }
+ (void) strcat(str, arg);
+ return(len - arglen);
+}
+
+/*
+ * err_compar(p1, p2) - comparison routine used by "qsort"
+ * for sorting errors.
+ */
+
+err_compar(p1, p2)
+
+register struct errs *p1, *p2;
+
+{
+ register int result;
+
+ if ((result = p1->errno - p2->errno) == 0)
+ {
+ return(strcmp(p1->arg, p2->arg));
+ }
+ return(result);
+}
+
+/*
+ * error_count() - return the number of errors currently logged.
+ */
+
+error_count()
+
+{
+ return(errcnt);
+}
+
+/*
+ * show_errors() - display on stdout the current log of errors.
+ */
+
+show_errors()
+
+{
+ register int cnt = 0;
+ register struct errs *errp = errs;
+
+ printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
+ while (cnt++ < errcnt)
+ {
+ printf("%5s: %s\n", errp->arg,
+ errp->errno == 0 ? "Not a number" : errmsg(errp->errno));
+ errp++;
+ }
+}
+
+/*
+ * kill_procs(str) - send signals to processes, much like the "kill"
+ * command does; invoked in response to 'k'.
+ */
+
+char *kill_procs(str)
+
+char *str;
+
+{
+ register char *nptr;
+ int signum = SIGTERM; /* default */
+ int procnum;
+ struct sigdesc *sigp;
+ int uid;
+
+ /* reset error array */
+ ERR_RESET;
+
+ /* remember our uid */
+ uid = getuid();
+
+ /* skip over leading white space */
+ while (isspace(*str)) str++;
+
+ if (str[0] == '-')
+ {
+ /* explicit signal specified */
+ if ((nptr = next_field(str)) == NULL)
+ {
+ return(" kill: no processes specified");
+ }
+
+ if (isdigit(str[1]))
+ {
+ (void) scanint(str + 1, &signum);
+ if (signum <= 0 || signum >= NSIG)
+ {
+ return(" invalid signal number");
+ }
+ }
+ else
+ {
+ /* translate the name into a number */
+ for (sigp = sigdesc; sigp->name != NULL; sigp++)
+ {
+ if (strcmp(sigp->name, str + 1) == 0)
+ {
+ signum = sigp->number;
+ break;
+ }
+ }
+
+ /* was it ever found */
+ if (sigp->name == NULL)
+ {
+ return(" bad signal name");
+ }
+ }
+ /* put the new pointer in place */
+ str = nptr;
+ }
+
+ /* loop thru the string, killing processes */
+ do
+ {
+ if (scanint(str, &procnum) == -1)
+ {
+ ERROR(str, 0);
+ }
+ else
+ {
+ /* check process owner if we're not root */
+ if (uid && (uid != proc_owner(procnum)))
+ {
+ ERROR(str, EACCES);
+ }
+ /* go in for the kill */
+ else if (kill(procnum, signum) == -1)
+ {
+ /* chalk up an error */
+ ERROR(str, errno);
+ }
+ }
+ } while ((str = next_field(str)) != NULL);
+
+ /* return appropriate error string */
+ return(err_string());
+}
+
+/*
+ * renice_procs(str) - change the "nice" of processes, much like the
+ * "renice" command does; invoked in response to 'r'.
+ */
+
+char *renice_procs(str)
+
+char *str;
+
+{
+ register char negate;
+ int prio;
+ int procnum;
+ int uid;
+
+ ERR_RESET;
+ uid = getuid();
+
+ /* allow for negative priority values */
+ if ((negate = (*str == '-')) != 0)
+ {
+ /* move past the minus sign */
+ str++;
+ }
+
+ /* use procnum as a temporary holding place and get the number */
+ procnum = scanint(str, &prio);
+
+ /* negate if necessary */
+ if (negate)
+ {
+ prio = -prio;
+ }
+
+#if defined(PRIO_MIN) && defined(PRIO_MAX)
+ /* check for validity */
+ if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX)
+ {
+ return(" bad priority value");
+ }
+#endif
+
+ /* move to the first process number */
+ if ((str = next_field(str)) == NULL)
+ {
+ return(" no processes specified");
+ }
+
+ /* loop thru the process numbers, renicing each one */
+ do
+ {
+ if (scanint(str, &procnum) == -1)
+ {
+ ERROR(str, 0);
+ }
+
+ /* check process owner if we're not root */
+ else if (uid && (uid != proc_owner(procnum)))
+ {
+ ERROR(str, EACCES);
+ }
+ else if (setpriority(PRIO_PROCESS, procnum, prio) == -1)
+ {
+ ERROR(str, errno);
+ }
+ } while ((str = next_field(str)) != NULL);
+
+ /* return appropriate error string */
+ return(err_string());
+}
+
diff --git a/usr.bin/top/display.c b/usr.bin/top/display.c
new file mode 100644
index 00000000000..8511187a6f1
--- /dev/null
+++ b/usr.bin/top/display.c
@@ -0,0 +1,1131 @@
+/* $OpenBSD: display.c,v 1.1 1997/08/14 14:00:21 downsj Exp $ */
+
+/*
+ * Top users/processes display for Unix
+ * Version 3
+ *
+ * This program may be freely redistributed,
+ * but this entire comment MUST remain intact.
+ *
+ * Copyright (c) 1984, 1989, William LeFebvre, Rice University
+ * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
+ */
+
+/*
+ * This file contains the routines that display information on the screen.
+ * Each section of the screen has two routines: one for initially writing
+ * all constant and dynamic text, and one for only updating the text that
+ * changes. The prefix "i_" is used on all the "initial" routines and the
+ * prefix "u_" is used for all the "updating" routines.
+ *
+ * ASSUMPTIONS:
+ * None of the "i_" routines use any of the termcap capabilities.
+ * In this way, those routines can be safely used on terminals that
+ * have minimal (or nonexistant) terminal capabilities.
+ *
+ * The routines are called in this order: *_loadave, i_timeofday,
+ * *_procstates, *_cpustates, *_memory, *_message, *_header,
+ * *_process, u_endscreen.
+ */
+
+#include "os.h"
+#include <ctype.h>
+#include <time.h>
+
+#include "screen.h" /* interface to screen package */
+#include "layout.h" /* defines for screen position layout */
+#include "display.h"
+#include "top.h"
+#include "top.local.h"
+#include "boolean.h"
+#include "machine.h" /* we should eliminate this!!! */
+#include "utils.h"
+
+#ifdef DEBUG
+FILE *debug;
+#endif
+
+/* imported from screen.c */
+extern int overstrike;
+
+static int lmpid = 0;
+static int last_hi = 0; /* used in u_process and u_endscreen */
+static int lastline = 0;
+static int display_width = MAX_COLS;
+
+#define lineindex(l) ((l)*display_width)
+
+char *printable();
+
+/* things initialized by display_init and used thruout */
+
+/* buffer of proc information lines for display updating */
+char *screenbuf = NULL;
+
+static char **procstate_names;
+static char **cpustate_names;
+static char **memory_names;
+
+static int num_procstates;
+static int num_cpustates;
+static int num_memory;
+
+static int *lprocstates;
+static int *lcpustates;
+static int *lmemory;
+
+static int *cpustate_columns;
+static int cpustate_total_length;
+
+static enum { OFF, ON, ERASE } header_status = ON;
+
+static int string_count();
+static void summary_format();
+static void line_update();
+
+int display_resize()
+
+{
+ register int lines;
+
+ /* first, deallocate any previous buffer that may have been there */
+ if (screenbuf != NULL)
+ {
+ free(screenbuf);
+ }
+
+ /* calculate the current dimensions */
+ /* if operating in "dumb" mode, we only need one line */
+ lines = smart_terminal ? screen_length - Header_lines : 1;
+
+ /* we don't want more than MAX_COLS columns, since the machine-dependent
+ modules make static allocations based on MAX_COLS and we don't want
+ to run off the end of their buffers */
+ display_width = screen_width;
+ if (display_width >= MAX_COLS)
+ {
+ display_width = MAX_COLS - 1;
+ }
+
+ /* now, allocate space for the screen buffer */
+ screenbuf = (char *)malloc(lines * display_width);
+ if (screenbuf == (char *)NULL)
+ {
+ /* oops! */
+ return(-1);
+ }
+
+ /* return number of lines available */
+ /* for dumb terminals, pretend like we can show any amount */
+ return(smart_terminal ? lines : Largest);
+}
+
+int display_init(statics)
+
+struct statics *statics;
+
+{
+ register int lines;
+ register char **pp;
+ register int *ip;
+ register int i;
+
+ /* call resize to do the dirty work */
+ lines = display_resize();
+
+ /* only do the rest if we need to */
+ if (lines > -1)
+ {
+ /* save pointers and allocate space for names */
+ procstate_names = statics->procstate_names;
+ num_procstates = string_count(procstate_names);
+ lprocstates = (int *)malloc(num_procstates * sizeof(int));
+
+ cpustate_names = statics->cpustate_names;
+ num_cpustates = string_count(cpustate_names);
+ lcpustates = (int *)malloc(num_cpustates * sizeof(int));
+ cpustate_columns = (int *)malloc(num_cpustates * sizeof(int));
+
+ memory_names = statics->memory_names;
+ num_memory = string_count(memory_names);
+ lmemory = (int *)malloc(num_memory * sizeof(int));
+
+ /* calculate starting columns where needed */
+ cpustate_total_length = 0;
+ pp = cpustate_names;
+ ip = cpustate_columns;
+ while (*pp != NULL)
+ {
+ if ((i = strlen(*pp++)) > 0)
+ {
+ *ip++ = cpustate_total_length;
+ cpustate_total_length += i + 8;
+ }
+ }
+ }
+
+ /* return number of lines available */
+ return(lines);
+}
+
+i_loadave(mpid, avenrun)
+
+int mpid;
+double *avenrun;
+
+{
+ register int i;
+
+ /* i_loadave also clears the screen, since it is first */
+ clear();
+
+ /* mpid == -1 implies this system doesn't have an _mpid */
+ if (mpid != -1)
+ {
+ printf("last pid: %5d; ", mpid);
+ }
+
+ printf("load averages");
+
+ for (i = 0; i < 3; i++)
+ {
+ printf("%c %5.2f",
+ i == 0 ? ':' : ',',
+ avenrun[i]);
+ }
+ lmpid = mpid;
+}
+
+u_loadave(mpid, avenrun)
+
+int mpid;
+double *avenrun;
+
+{
+ register int i;
+
+ if (mpid != -1)
+ {
+ /* change screen only when value has really changed */
+ if (mpid != lmpid)
+ {
+ Move_to(x_lastpid, y_lastpid);
+ printf("%5d", mpid);
+ lmpid = mpid;
+ }
+
+ /* i remembers x coordinate to move to */
+ i = x_loadave;
+ }
+ else
+ {
+ i = x_loadave_nompid;
+ }
+
+ /* move into position for load averages */
+ Move_to(i, y_loadave);
+
+ /* display new load averages */
+ /* we should optimize this and only display changes */
+ for (i = 0; i < 3; i++)
+ {
+ printf("%s%5.2f",
+ i == 0 ? "" : ", ",
+ avenrun[i]);
+ }
+}
+
+i_timeofday(tod)
+
+time_t *tod;
+
+{
+ /*
+ * Display the current time.
+ * "ctime" always returns a string that looks like this:
+ *
+ * Sun Sep 16 01:03:52 1973
+ * 012345678901234567890123
+ * 1 2
+ *
+ * We want indices 11 thru 18 (length 8).
+ */
+
+ if (smart_terminal)
+ {
+ Move_to(screen_width - 8, 0);
+ }
+ else
+ {
+ fputs(" ", stdout);
+ }
+#ifdef DEBUG
+ {
+ char *foo;
+ foo = ctime(tod);
+ fputs(foo, stdout);
+ }
+#endif
+ printf("%-8.8s\n", &(ctime(tod)[11]));
+ lastline = 1;
+}
+
+static int ltotal = 0;
+static char procstates_buffer[128];
+
+/*
+ * *_procstates(total, brkdn, names) - print the process summary line
+ *
+ * Assumptions: cursor is at the beginning of the line on entry
+ * lastline is valid
+ */
+
+i_procstates(total, brkdn)
+
+int total;
+int *brkdn;
+
+{
+ register int i;
+
+ /* write current number of processes and remember the value */
+ printf("%d processes:", total);
+ ltotal = total;
+
+ /* put out enough spaces to get to column 15 */
+ i = digits(total);
+ while (i++ < 4)
+ {
+ putchar(' ');
+ }
+
+ /* format and print the process state summary */
+ summary_format(procstates_buffer, brkdn, procstate_names);
+ fputs(procstates_buffer, stdout);
+
+ /* save the numbers for next time */
+ memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
+}
+
+u_procstates(total, brkdn)
+
+int total;
+int *brkdn;
+
+{
+ static char new[128];
+ register int i;
+
+ /* update number of processes only if it has changed */
+ if (ltotal != total)
+ {
+ /* move and overwrite */
+#if (x_procstate == 0)
+ Move_to(x_procstate, y_procstate);
+#else
+ /* cursor is already there...no motion needed */
+ /* assert(lastline == 1); */
+#endif
+ printf("%d", total);
+
+ /* if number of digits differs, rewrite the label */
+ if (digits(total) != digits(ltotal))
+ {
+ fputs(" processes:", stdout);
+ /* put out enough spaces to get to column 15 */
+ i = digits(total);
+ while (i++ < 4)
+ {
+ putchar(' ');
+ }
+ /* cursor may end up right where we want it!!! */
+ }
+
+ /* save new total */
+ ltotal = total;
+ }
+
+ /* see if any of the state numbers has changed */
+ if (memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0)
+ {
+ /* format and update the line */
+ summary_format(new, brkdn, procstate_names);
+ line_update(procstates_buffer, new, x_brkdn, y_brkdn);
+ memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
+ }
+}
+
+/*
+ * *_cpustates(states, names) - print the cpu state percentages
+ *
+ * Assumptions: cursor is on the PREVIOUS line
+ */
+
+static int cpustates_column;
+
+/* cpustates_tag() calculates the correct tag to use to label the line */
+
+char *cpustates_tag()
+
+{
+ register char *use;
+
+ static char *short_tag = "CPU: ";
+ static char *long_tag = "CPU states: ";
+
+ /* if length + strlen(long_tag) >= screen_width, then we have to
+ use the shorter tag (we subtract 2 to account for ": ") */
+ if (cpustate_total_length + (int)strlen(long_tag) - 2 >= screen_width)
+ {
+ use = short_tag;
+ }
+ else
+ {
+ use = long_tag;
+ }
+
+ /* set cpustates_column accordingly then return result */
+ cpustates_column = strlen(use);
+ return(use);
+}
+
+i_cpustates(states)
+
+register int *states;
+
+{
+ register int i = 0;
+ register int value;
+ register char **names = cpustate_names;
+ register char *thisname;
+
+ /* print tag and bump lastline */
+ printf("\n%s", cpustates_tag());
+ lastline++;
+
+ /* now walk thru the names and print the line */
+ while ((thisname = *names++) != NULL)
+ {
+ if (*thisname != '\0')
+ {
+ /* retrieve the value and remember it */
+ value = *states++;
+
+ /* if percentage is >= 1000, print it as 100% */
+ printf((value >= 1000 ? "%s%4.0f%% %s" : "%s%4.1f%% %s"),
+ i++ == 0 ? "" : ", ",
+ ((float)value)/10.,
+ thisname);
+ }
+ }
+
+ /* copy over values into "last" array */
+ memcpy(lcpustates, states, num_cpustates * sizeof(int));
+}
+
+u_cpustates(states)
+
+register int *states;
+
+{
+ register int value;
+ register char **names = cpustate_names;
+ register char *thisname;
+ register int *lp;
+ register int *colp;
+
+ Move_to(cpustates_column, y_cpustates);
+ lastline = y_cpustates;
+ lp = lcpustates;
+ colp = cpustate_columns;
+
+ /* we could be much more optimal about this */
+ while ((thisname = *names++) != NULL)
+ {
+ if (*thisname != '\0')
+ {
+ /* did the value change since last time? */
+ if (*lp != *states)
+ {
+ /* yes, move and change */
+ Move_to(cpustates_column + *colp, y_cpustates);
+ lastline = y_cpustates;
+
+ /* retrieve value and remember it */
+ value = *states;
+
+ /* if percentage is >= 1000, print it as 100% */
+ printf((value >= 1000 ? "%4.0f" : "%4.1f"),
+ ((double)value)/10.);
+
+ /* remember it for next time */
+ *lp = *states;
+ }
+ }
+
+ /* increment and move on */
+ lp++;
+ states++;
+ colp++;
+ }
+}
+
+z_cpustates()
+
+{
+ register int i = 0;
+ register char **names = cpustate_names;
+ register char *thisname;
+ register int *lp;
+
+ /* show tag and bump lastline */
+ printf("\n%s", cpustates_tag());
+ lastline++;
+
+ while ((thisname = *names++) != NULL)
+ {
+ if (*thisname != '\0')
+ {
+ printf("%s %% %s", i++ == 0 ? "" : ", ", thisname);
+ }
+ }
+
+ /* fill the "last" array with all -1s, to insure correct updating */
+ lp = lcpustates;
+ i = num_cpustates;
+ while (--i >= 0)
+ {
+ *lp++ = -1;
+ }
+}
+
+/*
+ * *_memory(stats) - print "Memory: " followed by the memory summary string
+ *
+ * Assumptions: cursor is on "lastline"
+ * for i_memory ONLY: cursor is on the previous line
+ */
+
+char memory_buffer[MAX_COLS];
+
+i_memory(stats)
+
+int *stats;
+
+{
+ fputs("\nMemory: ", stdout);
+ lastline++;
+
+ /* format and print the memory summary */
+ summary_format(memory_buffer, stats, memory_names);
+ fputs(memory_buffer, stdout);
+}
+
+u_memory(stats)
+
+int *stats;
+
+{
+ static char new[MAX_COLS];
+
+ /* format the new line */
+ summary_format(new, stats, memory_names);
+ line_update(memory_buffer, new, x_mem, y_mem);
+}
+
+/*
+ * *_message() - print the next pending message line, or erase the one
+ * that is there.
+ *
+ * Note that u_message is (currently) the same as i_message.
+ *
+ * Assumptions: lastline is consistent
+ */
+
+/*
+ * i_message is funny because it gets its message asynchronously (with
+ * respect to screen updates).
+ */
+
+static char next_msg[MAX_COLS + 5];
+static int msglen = 0;
+/* Invariant: msglen is always the length of the message currently displayed
+ on the screen (even when next_msg doesn't contain that message). */
+
+i_message()
+
+{
+ while (lastline < y_message)
+ {
+ fputc('\n', stdout);
+ lastline++;
+ }
+ if (next_msg[0] != '\0')
+ {
+ standout(next_msg);
+ msglen = strlen(next_msg);
+ next_msg[0] = '\0';
+ }
+ else if (msglen > 0)
+ {
+ (void) clear_eol(msglen);
+ msglen = 0;
+ }
+}
+
+u_message()
+
+{
+ i_message();
+}
+
+static int header_length;
+
+/*
+ * *_header(text) - print the header for the process area
+ *
+ * Assumptions: cursor is on the previous line and lastline is consistent
+ */
+
+i_header(text)
+
+char *text;
+
+{
+ header_length = strlen(text);
+ if (header_status == ON)
+ {
+ putchar('\n');
+ fputs(text, stdout);
+ lastline++;
+ }
+ else if (header_status == ERASE)
+ {
+ header_status = OFF;
+ }
+}
+
+/*ARGSUSED*/
+u_header(text)
+
+char *text; /* ignored */
+
+{
+ if (header_status == ERASE)
+ {
+ putchar('\n');
+ lastline++;
+ clear_eol(header_length);
+ header_status = OFF;
+ }
+}
+
+/*
+ * *_process(line, thisline) - print one process line
+ *
+ * Assumptions: lastline is consistent
+ */
+
+i_process(line, thisline)
+
+int line;
+char *thisline;
+
+{
+ register char *p;
+ register char *base;
+
+ /* make sure we are on the correct line */
+ while (lastline < y_procs + line)
+ {
+ putchar('\n');
+ lastline++;
+ }
+
+ /* truncate the line to conform to our current screen width */
+ thisline[display_width] = '\0';
+
+ /* write the line out */
+ fputs(thisline, stdout);
+
+ /* copy it in to our buffer */
+ base = smart_terminal ? screenbuf + lineindex(line) : screenbuf;
+ p = strecpy(base, thisline);
+
+ /* zero fill the rest of it */
+ memzero(p, display_width - (p - base));
+}
+
+u_process(line, newline)
+
+int line;
+char *newline;
+
+{
+ register char *optr;
+ register int screen_line = line + Header_lines;
+ register char *bufferline;
+
+ /* remember a pointer to the current line in the screen buffer */
+ bufferline = &screenbuf[lineindex(line)];
+
+ /* truncate the line to conform to our current screen width */
+ newline[display_width] = '\0';
+
+ /* is line higher than we went on the last display? */
+ if (line >= last_hi)
+ {
+ /* yes, just ignore screenbuf and write it out directly */
+ /* get positioned on the correct line */
+ if (screen_line - lastline == 1)
+ {
+ putchar('\n');
+ lastline++;
+ }
+ else
+ {
+ Move_to(0, screen_line);
+ lastline = screen_line;
+ }
+
+ /* now write the line */
+ fputs(newline, stdout);
+
+ /* copy it in to the buffer */
+ optr = strecpy(bufferline, newline);
+
+ /* zero fill the rest of it */
+ memzero(optr, display_width - (optr - bufferline));
+ }
+ else
+ {
+ line_update(bufferline, newline, 0, line + Header_lines);
+ }
+}
+
+u_endscreen(hi)
+
+register int hi;
+
+{
+ register int screen_line = hi + Header_lines;
+ register int i;
+
+ if (smart_terminal)
+ {
+ if (hi < last_hi)
+ {
+ /* need to blank the remainder of the screen */
+ /* but only if there is any screen left below this line */
+ if (lastline + 1 < screen_length)
+ {
+ /* efficiently move to the end of currently displayed info */
+ if (screen_line - lastline < 5)
+ {
+ while (lastline < screen_line)
+ {
+ putchar('\n');
+ lastline++;
+ }
+ }
+ else
+ {
+ Move_to(0, screen_line);
+ lastline = screen_line;
+ }
+
+ if (clear_to_end)
+ {
+ /* we can do this the easy way */
+ putcap(clear_to_end);
+ }
+ else
+ {
+ /* use clear_eol on each line */
+ i = hi;
+ while ((void) clear_eol(strlen(&screenbuf[lineindex(i++)])), i < last_hi)
+ {
+ putchar('\n');
+ }
+ }
+ }
+ }
+ last_hi = hi;
+
+ /* move the cursor to a pleasant place */
+ Move_to(x_idlecursor, y_idlecursor);
+ lastline = y_idlecursor;
+ }
+ else
+ {
+ /* separate this display from the next with some vertical room */
+ fputs("\n\n", stdout);
+ }
+}
+
+display_header(t)
+
+int t;
+
+{
+ if (t)
+ {
+ header_status = ON;
+ }
+ else if (header_status == ON)
+ {
+ header_status = ERASE;
+ }
+}
+
+/*VARARGS2*/
+new_message(type, msgfmt, a1, a2, a3)
+
+int type;
+char *msgfmt;
+caddr_t a1, a2, a3;
+
+{
+ register int i;
+
+ /* first, format the message */
+ (void) snprintf(next_msg, sizeof(next_msg), msgfmt, a1, a2, a3);
+
+ if (msglen > 0)
+ {
+ /* message there already -- can we clear it? */
+ if (!overstrike)
+ {
+ /* yes -- write it and clear to end */
+ i = strlen(next_msg);
+ if ((type & MT_delayed) == 0)
+ {
+ type & MT_standout ? standout(next_msg) :
+ fputs(next_msg, stdout);
+ (void) clear_eol(msglen - i);
+ msglen = i;
+ next_msg[0] = '\0';
+ }
+ }
+ }
+ else
+ {
+ if ((type & MT_delayed) == 0)
+ {
+ type & MT_standout ? standout(next_msg) : fputs(next_msg, stdout);
+ msglen = strlen(next_msg);
+ next_msg[0] = '\0';
+ }
+ }
+}
+
+clear_message()
+
+{
+ if (clear_eol(msglen) == 1)
+ {
+ putchar('\r');
+ }
+}
+
+readline(buffer, size, numeric)
+
+char *buffer;
+int size;
+int numeric;
+
+{
+ register char *ptr = buffer;
+ register char ch;
+ register char cnt = 0;
+ register char maxcnt = 0;
+
+ /* allow room for null terminator */
+ size -= 1;
+
+ /* read loop */
+ while ((fflush(stdout), read(0, ptr, 1) > 0))
+ {
+ /* newline means we are done */
+ if ((ch = *ptr) == '\n')
+ {
+ break;
+ }
+
+ /* handle special editing characters */
+ if (ch == ch_kill)
+ {
+ /* kill line -- account for overstriking */
+ if (overstrike)
+ {
+ msglen += maxcnt;
+ }
+
+ /* return null string */
+ *buffer = '\0';
+ putchar('\r');
+ return(-1);
+ }
+ else if (ch == ch_erase)
+ {
+ /* erase previous character */
+ if (cnt <= 0)
+ {
+ /* none to erase! */
+ putchar('\7');
+ }
+ else
+ {
+ fputs("\b \b", stdout);
+ ptr--;
+ cnt--;
+ }
+ }
+ /* check for character validity and buffer overflow */
+ else if (cnt == size || (numeric && !isdigit(ch)) ||
+ !isprint(ch))
+ {
+ /* not legal */
+ putchar('\7');
+ }
+ else
+ {
+ /* echo it and store it in the buffer */
+ putchar(ch);
+ ptr++;
+ cnt++;
+ if (cnt > maxcnt)
+ {
+ maxcnt = cnt;
+ }
+ }
+ }
+
+ /* all done -- null terminate the string */
+ *ptr = '\0';
+
+ /* account for the extra characters in the message area */
+ /* (if terminal overstrikes, remember the furthest they went) */
+ msglen += overstrike ? maxcnt : cnt;
+
+ /* return either inputted number or string length */
+ putchar('\r');
+ return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
+}
+
+/* internal support routines */
+
+static int string_count(pp)
+
+register char **pp;
+
+{
+ register int cnt;
+
+ cnt = 0;
+ while (*pp++ != NULL)
+ {
+ cnt++;
+ }
+ return(cnt);
+}
+
+static void summary_format(str, numbers, names)
+
+char *str;
+int *numbers;
+register char **names;
+
+{
+ register char *p;
+ register int num;
+ register char *thisname;
+ register int useM = No;
+
+ /* format each number followed by its string */
+ p = str;
+ while ((thisname = *names++) != NULL)
+ {
+ /* get the number to format */
+ num = *numbers++;
+
+ /* display only non-zero numbers */
+ if (num > 0)
+ {
+ /* is this number in kilobytes? */
+ if (thisname[0] == 'K')
+ {
+ /* yes: format it as a memory value */
+ p = strecpy(p, format_k(num));
+
+ /* skip over the K, since it was included by format_k */
+ p = strecpy(p, thisname+1);
+ }
+ else
+ {
+ p = strecpy(p, itoa(num));
+ p = strecpy(p, thisname);
+ }
+ }
+
+ /* ignore negative numbers, but display corresponding string */
+ else if (num < 0)
+ {
+ p = strecpy(p, thisname);
+ }
+ }
+
+ /* if the last two characters in the string are ", ", delete them */
+ p -= 2;
+ if (p >= str && p[0] == ',' && p[1] == ' ')
+ {
+ *p = '\0';
+ }
+}
+
+static void line_update(old, new, start, line)
+
+register char *old;
+register char *new;
+int start;
+int line;
+
+{
+ register int ch;
+ register int diff;
+ register int newcol = start + 1;
+ register int lastcol = start;
+ char cursor_on_line = No;
+ char *current;
+
+ /* compare the two strings and only rewrite what has changed */
+ current = old;
+#ifdef DEBUG
+ fprintf(debug, "line_update, starting at %d\n", start);
+ fputs(old, debug);
+ fputc('\n', debug);
+ fputs(new, debug);
+ fputs("\n-\n", debug);
+#endif
+
+ /* start things off on the right foot */
+ /* this is to make sure the invariants get set up right */
+ if ((ch = *new++) != *old)
+ {
+ if (line - lastline == 1 && start == 0)
+ {
+ putchar('\n');
+ }
+ else
+ {
+ Move_to(start, line);
+ }
+ cursor_on_line = Yes;
+ putchar(ch);
+ *old = ch;
+ lastcol = 1;
+ }
+ old++;
+
+ /*
+ * main loop -- check each character. If the old and new aren't the
+ * same, then update the display. When the distance from the
+ * current cursor position to the new change is small enough,
+ * the characters that belong there are written to move the
+ * cursor over.
+ *
+ * Invariants:
+ * lastcol is the column where the cursor currently is sitting
+ * (always one beyond the end of the last mismatch).
+ */
+ do /* yes, a do...while */
+ {
+ if ((ch = *new++) != *old)
+ {
+ /* new character is different from old */
+ /* make sure the cursor is on top of this character */
+ diff = newcol - lastcol;
+ if (diff > 0)
+ {
+ /* some motion is required--figure out which is shorter */
+ if (diff < 6 && cursor_on_line)
+ {
+ /* overwrite old stuff--get it out of the old buffer */
+ printf("%.*s", diff, &current[lastcol-start]);
+ }
+ else
+ {
+ /* use cursor addressing */
+ Move_to(newcol, line);
+ cursor_on_line = Yes;
+ }
+ /* remember where the cursor is */
+ lastcol = newcol + 1;
+ }
+ else
+ {
+ /* already there, update position */
+ lastcol++;
+ }
+
+ /* write what we need to */
+ if (ch == '\0')
+ {
+ /* at the end--terminate with a clear-to-end-of-line */
+ (void) clear_eol(strlen(old));
+ }
+ else
+ {
+ /* write the new character */
+ putchar(ch);
+ }
+ /* put the new character in the screen buffer */
+ *old = ch;
+ }
+
+ /* update working column and screen buffer pointer */
+ newcol++;
+ old++;
+
+ } while (ch != '\0');
+
+ /* zero out the rest of the line buffer -- MUST BE DONE! */
+ diff = display_width - newcol;
+ if (diff > 0)
+ {
+ memzero(old, diff);
+ }
+
+ /* remember where the current line is */
+ if (cursor_on_line)
+ {
+ lastline = line;
+ }
+}
+
+/*
+ * printable(str) - make the string pointed to by "str" into one that is
+ * printable (i.e.: all ascii), by converting all non-printable
+ * characters into '?'. Replacements are done in place and a pointer
+ * to the original buffer is returned.
+ */
+
+char *printable(str)
+
+char *str;
+
+{
+ register char *ptr;
+ register char ch;
+
+ ptr = str;
+ while ((ch = *ptr) != '\0')
+ {
+ if (!isprint(ch))
+ {
+ *ptr = '?';
+ }
+ ptr++;
+ }
+ return(str);
+}
diff --git a/usr.bin/top/display.h b/usr.bin/top/display.h
new file mode 100644
index 00000000000..12d602e069f
--- /dev/null
+++ b/usr.bin/top/display.h
@@ -0,0 +1,9 @@
+/* $OpenBSD: display.h,v 1.1 1997/08/14 14:00:21 downsj Exp $ */
+
+/* constants needed for display.c */
+
+/* "type" argument for new_message function */
+
+#define MT_standout 1
+#define MT_delayed 2
+
diff --git a/usr.bin/top/layout.h b/usr.bin/top/layout.h
new file mode 100644
index 00000000000..3fe86f8a11a
--- /dev/null
+++ b/usr.bin/top/layout.h
@@ -0,0 +1,29 @@
+/* $OpenBSD: layout.h,v 1.1 1997/08/14 14:00:22 downsj Exp $ */
+
+/*
+ * Top - a top users display for Berkeley Unix
+ *
+ * This file defines the locations on tne screen for various parts of the
+ * display. These definitions are used by the routines in "display.c" for
+ * cursor addressing.
+ */
+
+#define x_lastpid 10
+#define y_lastpid 0
+#define x_loadave 33
+#define x_loadave_nompid 15
+#define y_loadave 0
+#define x_procstate 0
+#define y_procstate 1
+#define x_brkdn 15
+#define y_brkdn 1
+#define x_mem 8
+#define y_mem 3
+#define y_message 4
+#define x_header 0
+#define y_header 5
+#define x_idlecursor 0
+#define y_idlecursor 4
+#define y_procs 6
+
+#define y_cpustates 2
diff --git a/usr.bin/top/loadavg.h b/usr.bin/top/loadavg.h
new file mode 100644
index 00000000000..fee726e7307
--- /dev/null
+++ b/usr.bin/top/loadavg.h
@@ -0,0 +1,59 @@
+/* $OpenBSD: loadavg.h,v 1.1 1997/08/14 14:00:22 downsj Exp $ */
+
+/*
+ * Top - a top users display for Berkeley Unix
+ *
+ * Defines required to access load average figures.
+ *
+ * This include file sets up everything we need to access the load average
+ * values in the kernel in a machine independent way. First, it sets the
+ * typedef "load_avg" to be either double or long (depending on what is
+ * needed), then it defines these macros appropriately:
+ *
+ * loaddouble(la) - convert load_avg to double.
+ * intload(i) - convert integer to load_avg.
+ */
+
+/*
+ * We assume that if FSCALE is defined, then avenrun and ccpu are type long.
+ * If your machine is an exception (mips, perhaps?) then make adjustments
+ * here.
+ *
+ * Defined types: load_avg for load averages, pctcpu for cpu percentages.
+ */
+#if defined(mips) && !defined(NetBSD)
+# include <sys/fixpoint.h>
+# if defined(FBITS) && !defined(FSCALE)
+# define FSCALE (1 << FBITS) /* mips */
+# endif
+#endif
+
+#ifdef FSCALE
+# define FIXED_LOADAVG FSCALE
+# define FIXED_PCTCPU FSCALE
+#endif
+
+#ifdef ibm032
+# undef FIXED_LOADAVG
+# undef FIXED_PCTCPU
+# define FIXED_PCTCPU PCT_SCALE
+#endif
+
+
+#ifdef FIXED_PCTCPU
+ typedef long pctcpu;
+# define pctdouble(p) ((double)(p) / FIXED_PCTCPU)
+#else
+typedef double pctcpu;
+# define pctdouble(p) (p)
+#endif
+
+#ifdef FIXED_LOADAVG
+ typedef long load_avg;
+# define loaddouble(la) ((double)(la) / FIXED_LOADAVG)
+# define intload(i) ((int)((i) * FIXED_LOADAVG))
+#else
+ typedef double load_avg;
+# define loaddouble(la) (la)
+# define intload(i) ((double)(i))
+#endif
diff --git a/usr.bin/top/machine.c b/usr.bin/top/machine.c
new file mode 100644
index 00000000000..b8def959ae0
--- /dev/null
+++ b/usr.bin/top/machine.c
@@ -0,0 +1,818 @@
+/* $OpenBSD: machine.c,v 1.1 1997/08/14 14:00:22 downsj Exp $ */
+
+/*
+ * top - a top users display for Unix
+ *
+ * SYNOPSIS: For an OpenBSD system
+ *
+ * DESCRIPTION:
+ * This is the machine-dependent module for OpenBSD
+ * Tested on:
+ * i386
+ *
+ * LIBS: -lkvm
+ *
+ * TERMCAP: -ltermlib
+ *
+ * CFLAGS: -DHAVE_GETOPT
+ *
+ * AUTHOR: Thorsten Lockert <tholo@sigmasoft.com>
+ * Adapted from BSD4.4 by Christos Zoulas <christos@ee.cornell.edu>
+ * Patch for process wait display by Jarl F. Greipsland <jarle@idt.unit.no>
+ */
+
+#include <sys/types.h>
+#include <sys/signal.h>
+#include <sys/param.h>
+
+#define LASTPID
+#define DOSWAP
+
+#include "os.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <nlist.h>
+#include <math.h>
+#include <kvm.h>
+#include <unistd.h>
+#include <sys/errno.h>
+#include <sys/sysctl.h>
+#include <sys/dir.h>
+#include <sys/dkstat.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#ifdef DOSWAP
+#include <err.h>
+#include <sys/map.h>
+#include <sys/conf.h>
+#endif
+
+static int check_nlist __P((struct nlist *));
+static int getkval __P((unsigned long, int *, int, char *));
+static int swapmode __P((int *, int *));
+extern char* printable __P((char *));
+
+#include "top.h"
+#include "machine.h"
+#include "utils.h"
+
+/* get_process_info passes back a handle. This is what it looks like: */
+
+struct handle
+{
+ struct kinfo_proc **next_proc; /* points to next valid proc pointer */
+ int remaining; /* number of pointers remaining */
+};
+
+/* declarations for load_avg */
+#include "loadavg.h"
+
+#define PP(pp, field) ((pp)->kp_proc . field)
+#define EP(pp, field) ((pp)->kp_eproc . field)
+#define VP(pp, field) ((pp)->kp_eproc.e_vm . field)
+
+/* what we consider to be process size: */
+#define PROCSIZE(pp) (VP((pp), vm_tsize) + VP((pp), vm_dsize) + VP((pp), vm_ssize))
+
+/* definitions for indices in the nlist array */
+#define X_CP_TIME 0
+#define X_HZ 1
+
+#ifdef DOSWAP
+#define VM_SWAPMAP 2
+#define VM_NSWAPMAP 3
+#define VM_SWDEVT 4
+#define VM_NSWAP 5
+#define VM_NSWDEV 6
+#define VM_DMMAX 7
+#define VM_NISWAP 8
+#define VM_NISWDEV 9
+
+#define X_LASTPID 10
+#elif defined(LASTPID)
+#define X_LASTPID 2
+#endif
+
+static struct nlist nlst[] = {
+ { "_cp_time" }, /* 0 */
+ { "_hz" }, /* 1 */
+#ifdef DOSWAP
+ { "_swapmap" }, /* 2 */
+ { "_nswapmap" }, /* 3 */
+ { "_swdevt" }, /* 4 */
+ { "_nswap" }, /* 5 */
+ { "_nswdev" }, /* 6 */
+ { "_dmmax" }, /* 7 */
+ { "_niswap" }, /* 8 */
+ { "_niswdev" }, /* 9 */
+#endif
+#ifdef LASTPID
+ { "_lastpid" }, /* 2 / 10 */
+#endif
+ { 0 }
+};
+
+/*
+ * These definitions control the format of the per-process area
+ */
+
+static char header[] =
+ " PID X PRI NICE SIZE RES STATE WAIT TIME CPU COMMAND";
+/* 0123456 -- field to fill in starts at header+6 */
+#define UNAME_START 6
+
+#define Proc_format \
+ "%5d %-8.8s %3d %4d %5s %5s %-5s %-6.6s %6s %5.2f%% %.14s"
+
+
+/* process state names for the "STATE" column of the display */
+/* the extra nulls in the string "run" are for adding a slash and
+ the processor number when needed */
+
+char *state_abbrev[] =
+{
+ "", "start", "run\0\0\0", "sleep", "stop", "zomb",
+};
+
+
+static kvm_t *kd;
+
+/* these are retrieved from the kernel in _init */
+
+static long hz;
+
+/* these are offsets obtained via nlist and used in the get_ functions */
+
+static unsigned long cp_time_offset;
+#ifdef LASTPID
+static unsigned long lastpid_offset;
+static pid_t lastpid;
+#endif
+
+/* these are for calculating cpu state percentages */
+static long cp_time[CPUSTATES];
+static long cp_old[CPUSTATES];
+static long cp_diff[CPUSTATES];
+
+/* these are for detailing the process states */
+
+int process_states[7];
+char *procstatenames[] = {
+ "", " starting, ", " running, ", " idle, ", " stopped, ", " zombie, ",
+ NULL
+};
+
+/* these are for detailing the cpu states */
+
+int cpu_states[CPUSTATES];
+char *cpustatenames[] = {
+ "user", "nice", "system", "interrupt", "idle", NULL
+};
+
+/* these are for detailing the memory statistics */
+
+int memory_stats[8];
+char *memorynames[] = {
+ "Real: ", "K/", "K act/tot ", "Free: ", "K ",
+#ifdef DOSWAP
+ "Swap: ", "K/", "K used/tot",
+#endif
+ NULL
+};
+
+/* these are for keeping track of the proc array */
+
+static int nproc;
+static int onproc = -1;
+static int pref_len;
+static struct kinfo_proc *pbase;
+static struct kinfo_proc **pref;
+
+/* these are for getting the memory statistics */
+
+static int pageshift; /* log base 2 of the pagesize */
+
+/* define pagetok in terms of pageshift */
+
+#define pagetok(size) ((size) << pageshift)
+
+int
+machine_init(statics)
+
+struct statics *statics;
+
+{
+ register int i = 0;
+ register int pagesize;
+
+ if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm_open")) == NULL)
+ return -1;
+
+
+ /* get the list of symbols we want to access in the kernel */
+ (void) kvm_nlist(kd, nlst);
+ if (nlst[0].n_type == 0)
+ {
+ fprintf(stderr, "top: nlist failed\n");
+ return(-1);
+ }
+
+ /* make sure they were all found */
+ if (i > 0 && check_nlist(nlst) > 0)
+ {
+ return(-1);
+ }
+
+ /* get the symbol values out of kmem */
+ (void) getkval(nlst[X_HZ].n_value, (int *)(&hz), sizeof(hz),
+ nlst[X_HZ].n_name);
+
+ /* stash away certain offsets for later use */
+ cp_time_offset = nlst[X_CP_TIME].n_value;
+#ifdef LASTPID
+ lastpid_offset = nlst[X_LASTPID].n_value;
+#endif
+
+ pbase = NULL;
+ pref = NULL;
+ onproc = -1;
+ nproc = 0;
+
+ /* get the page size with "getpagesize" and calculate pageshift from it */
+ pagesize = getpagesize();
+ pageshift = 0;
+ while (pagesize > 1)
+ {
+ pageshift++;
+ pagesize >>= 1;
+ }
+
+ /* we only need the amount of log(2)1024 for our conversion */
+ pageshift -= LOG1024;
+
+ /* fill in the statics information */
+ statics->procstate_names = procstatenames;
+ statics->cpustate_names = cpustatenames;
+ statics->memory_names = memorynames;
+
+ /* all done! */
+ return(0);
+}
+
+char *format_header(uname_field)
+
+register char *uname_field;
+
+{
+ register char *ptr;
+
+ ptr = header + UNAME_START;
+ while (*uname_field != '\0')
+ {
+ *ptr++ = *uname_field++;
+ }
+
+ return(header);
+}
+
+void
+get_system_info(si)
+
+struct system_info *si;
+
+{
+ long total;
+
+ /* get the cp_time array */
+ (void) getkval(cp_time_offset, (int *)cp_time, sizeof(cp_time),
+ "_cp_time");
+#ifdef LASTPID
+ (void) getkval(lastpid_offset, (int *)&lastpid, sizeof(lastpid),
+ "!");
+#endif
+
+ /* convert load averages to doubles */
+ {
+ register int i;
+ register double *infoloadp;
+ struct loadavg sysload;
+ int size = sizeof(sysload);
+ static int mib[] = { CTL_VM, VM_LOADAVG };
+
+ if (sysctl(mib, 2, &sysload, &size, NULL, 0) < 0) {
+ (void) fprintf(stderr, "top: sysctl failed: %s\n", strerror(errno));
+ bzero(&total, sizeof(total));
+ }
+
+ infoloadp = si->load_avg;
+ for (i = 0; i < 3; i++)
+ *infoloadp++ = ((double) sysload.ldavg[i]) / sysload.fscale;
+ }
+
+ /* convert cp_time counts to percentages */
+ total = percentages(CPUSTATES, cpu_states, cp_time, cp_old, cp_diff);
+
+ /* sum memory statistics */
+ {
+ struct vmtotal total;
+ int size = sizeof(total);
+ static int mib[] = { CTL_VM, VM_METER };
+
+ /* get total -- systemwide main memory usage structure */
+ if (sysctl(mib, 2, &total, &size, NULL, 0) < 0) {
+ (void) fprintf(stderr, "top: sysctl failed: %s\n", strerror(errno));
+ bzero(&total, sizeof(total));
+ }
+ /* convert memory stats to Kbytes */
+ memory_stats[0] = -1;
+ memory_stats[1] = pagetok(total.t_arm);
+ memory_stats[2] = pagetok(total.t_rm);
+ memory_stats[3] = -1;
+ memory_stats[4] = pagetok(total.t_free);
+ memory_stats[5] = -1;
+#ifdef DOSWAP
+ if (!swapmode(&memory_stats[6], &memory_stats[7])) {
+ memory_stats[6] = 0;
+ memory_stats[7] = 0;
+ }
+#endif
+ }
+
+ /* set arrays and strings */
+ si->cpustates = cpu_states;
+ si->memory = memory_stats;
+#ifdef LASTPID
+ if (lastpid > 0)
+ si->last_pid = lastpid;
+ else
+#endif
+ si->last_pid = -1;
+}
+
+static struct handle handle;
+
+caddr_t get_process_info(si, sel, compare)
+
+struct system_info *si;
+struct process_select *sel;
+int (*compare)();
+
+{
+ register int i;
+ register int total_procs;
+ register int active_procs;
+ register struct kinfo_proc **prefp;
+ register struct kinfo_proc *pp;
+
+ /* these are copied out of sel for speed */
+ int show_idle;
+ int show_system;
+ int show_uid;
+ int show_command;
+
+
+ pbase = kvm_getprocs(kd, KERN_PROC_ALL, 0, &nproc);
+ if (nproc > onproc)
+ pref = (struct kinfo_proc **) realloc(pref, sizeof(struct kinfo_proc *)
+ * (onproc = nproc));
+ if (pref == NULL || pbase == NULL) {
+ (void) fprintf(stderr, "top: Out of memory.\n");
+ quit(23);
+ }
+ /* get a pointer to the states summary array */
+ si->procstates = process_states;
+
+ /* set up flags which define what we are going to select */
+ show_idle = sel->idle;
+ show_system = sel->system;
+ show_uid = sel->uid != -1;
+ show_command = sel->command != NULL;
+
+ /* count up process states and get pointers to interesting procs */
+ total_procs = 0;
+ active_procs = 0;
+ memset((char *)process_states, 0, sizeof(process_states));
+ prefp = pref;
+ for (pp = pbase, i = 0; i < nproc; pp++, i++)
+ {
+ /*
+ * Place pointers to each valid proc structure in pref[].
+ * Process slots that are actually in use have a non-zero
+ * status field. Processes with SSYS set are system
+ * processes---these get ignored unless show_sysprocs is set.
+ */
+ if (PP(pp, p_stat) != 0 &&
+ (show_system || ((PP(pp, p_flag) & P_SYSTEM) == 0)))
+ {
+ total_procs++;
+ process_states[(unsigned char) PP(pp, p_stat)]++;
+ if ((PP(pp, p_stat) != SZOMB) &&
+ (show_idle || (PP(pp, p_pctcpu) != 0) ||
+ (PP(pp, p_stat) == SRUN)) &&
+ (!show_uid || EP(pp, e_pcred.p_ruid) == (uid_t)sel->uid))
+ {
+ *prefp++ = pp;
+ active_procs++;
+ }
+ }
+ }
+
+ /* if requested, sort the "interesting" processes */
+ if (compare != NULL)
+ {
+ qsort((char *)pref, active_procs, sizeof(struct kinfo_proc *), compare);
+ }
+
+ /* remember active and total counts */
+ si->p_total = total_procs;
+ si->p_active = pref_len = active_procs;
+
+ /* pass back a handle */
+ handle.next_proc = pref;
+ handle.remaining = active_procs;
+ return((caddr_t)&handle);
+}
+
+char fmt[MAX_COLS]; /* static area where result is built */
+
+char *format_next_process(handle, get_userid)
+
+caddr_t handle;
+char *(*get_userid)();
+
+{
+ register struct kinfo_proc *pp;
+ register long cputime;
+ register double pct;
+ struct handle *hp;
+ char waddr[sizeof(void *) * 2 + 3]; /* Hexify void pointer */
+ char *p_wait;
+
+ /* find and remember the next proc structure */
+ hp = (struct handle *)handle;
+ pp = *(hp->next_proc++);
+ hp->remaining--;
+
+
+ /* get the process's user struct and set cputime */
+ if ((PP(pp, p_flag) & P_INMEM) == 0) {
+ /*
+ * Print swapped processes as <pname>
+ */
+ char *comm = PP(pp, p_comm);
+#define COMSIZ sizeof(PP(pp, p_comm))
+ char buf[COMSIZ];
+ (void) strncpy(buf, comm, COMSIZ);
+ comm[0] = '<';
+ (void) strncpy(&comm[1], buf, COMSIZ - 2);
+ comm[COMSIZ - 2] = '\0';
+ (void) strncat(comm, ">", COMSIZ - 1);
+ comm[COMSIZ - 1] = '\0';
+ }
+
+ cputime = (PP(pp, p_uticks) + PP(pp, p_sticks) + PP(pp, p_iticks)) / hz;
+
+ /* calculate the base for cpu percentages */
+ pct = pctdouble(PP(pp, p_pctcpu));
+
+ if (PP(pp, p_wchan))
+ if (PP(pp, p_wmesg))
+ p_wait = EP(pp, e_wmesg);
+ else {
+ snprintf(waddr, sizeof(waddr), "%x",
+ (unsigned long)(PP(pp, p_wchan)) & ~KERNBASE);
+ p_wait = waddr;
+ }
+ else
+ p_wait = "-";
+
+ /* format this entry */
+ snprintf(fmt, MAX_COLS,
+ Proc_format,
+ PP(pp, p_pid),
+ (*get_userid)(EP(pp, e_pcred.p_ruid)),
+ PP(pp, p_priority) - PZERO,
+ PP(pp, p_nice) - NZERO,
+ format_k(pagetok(PROCSIZE(pp))),
+ format_k(pagetok(VP(pp, vm_rssize))),
+ state_abbrev[(unsigned char) PP(pp, p_stat)],
+ p_wait,
+ format_time(cputime),
+ 100.0 * pct,
+ printable(PP(pp, p_comm)));
+
+ /* return the result */
+ return(fmt);
+}
+
+
+/*
+ * check_nlist(nlst) - checks the nlist to see if any symbols were not
+ * found. For every symbol that was not found, a one-line
+ * message is printed to stderr. The routine returns the
+ * number of symbols NOT found.
+ */
+
+static int check_nlist(nlst)
+
+register struct nlist *nlst;
+
+{
+ register int i;
+
+ /* check to see if we got ALL the symbols we requested */
+ /* this will write one line to stderr for every symbol not found */
+
+ i = 0;
+ while (nlst->n_name != NULL)
+ {
+ if (nlst->n_type == 0)
+ {
+ /* this one wasn't found */
+ (void) fprintf(stderr, "kernel: no symbol named `%s'\n",
+ nlst->n_name);
+ i = 1;
+ }
+ nlst++;
+ }
+
+ return(i);
+}
+
+
+/*
+ * getkval(offset, ptr, size, refstr) - get a value out of the kernel.
+ * "offset" is the byte offset into the kernel for the desired value,
+ * "ptr" points to a buffer into which the value is retrieved,
+ * "size" is the size of the buffer (and the object to retrieve),
+ * "refstr" is a reference string used when printing error meessages,
+ * if "refstr" starts with a '!', then a failure on read will not
+ * be fatal (this may seem like a silly way to do things, but I
+ * really didn't want the overhead of another argument).
+ *
+ */
+
+static int getkval(offset, ptr, size, refstr)
+
+unsigned long offset;
+int *ptr;
+int size;
+char *refstr;
+
+{
+ if (kvm_read(kd, offset, (char *) ptr, size) != size)
+ {
+ if (*refstr == '!')
+ {
+ return(0);
+ }
+ else
+ {
+ fprintf(stderr, "top: kvm_read for %s: %s\n",
+ refstr, strerror(errno));
+ quit(23);
+ }
+ }
+ return(1);
+}
+
+/* comparison routine for qsort */
+
+/*
+ * proc_compare - comparison function for "qsort"
+ * Compares the resource consumption of two processes using five
+ * distinct keys. The keys (in descending order of importance) are:
+ * percent cpu, cpu ticks, state, resident set size, total virtual
+ * memory usage. The process states are ordered as follows (from least
+ * to most important): zombie, sleep, stop, start, run. The array
+ * declaration below maps a process state index into a number that
+ * reflects this ordering.
+ */
+
+static unsigned char sorted_state[] =
+{
+ 0, /* not used */
+ 4, /* start */
+ 5, /* run */
+ 2, /* sleep */
+ 3, /* stop */
+ 1 /* zombie */
+};
+
+int
+proc_compare(pp1, pp2)
+
+struct proc **pp1;
+struct proc **pp2;
+
+{
+ register struct kinfo_proc *p1;
+ register struct kinfo_proc *p2;
+ register int result;
+ register pctcpu lresult;
+
+ /* remove one level of indirection */
+ p1 = *(struct kinfo_proc **) pp1;
+ p2 = *(struct kinfo_proc **) pp2;
+
+ /* compare percent cpu (pctcpu) */
+ if ((lresult = PP(p2, p_pctcpu) - PP(p1, p_pctcpu)) == 0)
+ {
+ /* use cpticks to break the tie */
+ if ((result = PP(p2, p_cpticks) - PP(p1, p_cpticks)) == 0)
+ {
+ /* use process state to break the tie */
+ if ((result = sorted_state[(unsigned char) PP(p2, p_stat)] -
+ sorted_state[(unsigned char) PP(p1, p_stat)]) == 0)
+ {
+ /* use priority to break the tie */
+ if ((result = PP(p2, p_priority) - PP(p1, p_priority)) == 0)
+ {
+ /* use resident set size (rssize) to break the tie */
+ if ((result = VP(p2, vm_rssize) - VP(p1, vm_rssize)) == 0)
+ {
+ /* use total memory to break the tie */
+ result = PROCSIZE(p2) - PROCSIZE(p1);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ result = lresult < 0 ? -1 : 1;
+ }
+
+ return(result);
+}
+
+
+/*
+ * proc_owner(pid) - returns the uid that owns process "pid", or -1 if
+ * the process does not exist.
+ * It is EXTREMLY IMPORTANT that this function work correctly.
+ * If top runs setuid root (as in SVR4), then this function
+ * is the only thing that stands in the way of a serious
+ * security problem. It validates requests for the "kill"
+ * and "renice" commands.
+ */
+
+int proc_owner(pid)
+
+int pid;
+
+{
+ register int cnt;
+ register struct kinfo_proc **prefp;
+ register struct kinfo_proc *pp;
+
+ prefp = pref;
+ cnt = pref_len;
+ while (--cnt >= 0)
+ {
+ pp = *prefp++;
+ if (PP(pp, p_pid) == (pid_t)pid)
+ {
+ return((int)EP(pp, e_pcred.p_ruid));
+ }
+ }
+ return(-1);
+}
+
+#ifdef DOSWAP
+/*
+ * swapmode is based on a program called swapinfo written
+ * by Kevin Lahey <kml@rokkaku.atl.ga.us>.
+ */
+
+#define SVAR(var) __STRING(var) /* to force expansion */
+#define KGET(idx, var) \
+ KGET1(idx, &var, sizeof(var), SVAR(var))
+#define KGET1(idx, p, s, msg) \
+ KGET2(nlst[idx].n_value, p, s, msg)
+#define KGET2(addr, p, s, msg) \
+ if (kvm_read(kd, (u_long)(addr), p, s) != s) \
+ warnx("cannot read %s: %s", msg, kvm_geterr(kd))
+
+static int
+swapmode(used, total)
+int *used;
+int *total;
+{
+ int nswap, nswdev, dmmax, nswapmap, niswap, niswdev;
+ int s, e, i, l, nfree;
+ struct swdevt *sw;
+ long *perdev;
+ struct map *swapmap, *kswapmap;
+ struct mapent *mp, *freemp;
+
+ KGET(VM_NSWAP, nswap);
+ KGET(VM_NSWDEV, nswdev);
+ KGET(VM_DMMAX, dmmax);
+ KGET(VM_NSWAPMAP, nswapmap);
+ KGET(VM_SWAPMAP, kswapmap); /* kernel `swapmap' is a pointer */
+ if ((sw = malloc(nswdev * sizeof(*sw))) == NULL ||
+ (perdev = malloc(nswdev * sizeof(*perdev))) == NULL ||
+ (freemp = mp = malloc(nswapmap * sizeof(*mp))) == NULL)
+ err(1, "malloc");
+ KGET1(VM_SWDEVT, sw, nswdev * sizeof(*sw), "swdevt");
+ KGET2((long)kswapmap, mp, nswapmap * sizeof(*mp), "swapmap");
+
+ /* Supports sequential swap */
+ if (nlst[VM_NISWAP].n_value != 0) {
+ KGET(VM_NISWAP, niswap);
+ KGET(VM_NISWDEV, niswdev);
+ } else {
+ niswap = nswap;
+ niswdev = nswdev;
+ }
+
+ /* First entry in map is `struct map'; rest are mapent's. */
+ swapmap = (struct map *)mp;
+ if (nswapmap != swapmap->m_limit - (struct mapent *)kswapmap)
+ errx(1, "panic: nswapmap goof");
+
+ /* Count up swap space. */
+ nfree = 0;
+ memset(perdev, 0, nswdev * sizeof(*perdev));
+ for (mp++; mp->m_addr != 0; mp++) {
+ s = mp->m_addr; /* start of swap region */
+ e = mp->m_addr + mp->m_size; /* end of region */
+ nfree += mp->m_size;
+
+ /*
+ * Swap space is split up among the configured disks.
+ *
+ * For interleaved swap devices, the first dmmax blocks
+ * of swap space some from the first disk, the next dmmax
+ * blocks from the next, and so on up to niswap blocks.
+ *
+ * Sequential swap devices follow the interleaved devices
+ * (i.e. blocks starting at niswap) in the order in which
+ * they appear in the swdev table. The size of each device
+ * will be a multiple of dmmax.
+ *
+ * The list of free space joins adjacent free blocks,
+ * ignoring device boundries. If we want to keep track
+ * of this information per device, we'll just have to
+ * extract it ourselves. We know that dmmax-sized chunks
+ * cannot span device boundaries (interleaved or sequential)
+ * so we loop over such chunks assigning them to devices.
+ */
+ i = -1;
+ while (s < e) { /* XXX this is inefficient */
+ int bound = roundup(s+1, dmmax);
+
+ if (bound > e)
+ bound = e;
+ if (bound <= niswap) {
+ /* Interleaved swap chunk. */
+ if (i == -1)
+ i = (s / dmmax) % niswdev;
+ perdev[i] += bound - s;
+ if (++i >= niswdev)
+ i = 0;
+ } else {
+ /* Sequential swap chunk. */
+ if (i < niswdev) {
+ i = niswdev;
+ l = niswap + sw[i].sw_nblks;
+ }
+ while (s >= l) {
+ /* XXX don't die on bogus blocks */
+ if (i == nswdev-1)
+ break;
+ l += sw[++i].sw_nblks;
+ }
+ perdev[i] += bound - s;
+ }
+ s = bound;
+ }
+ }
+
+ *total = 0;
+ for (i = 0; i < nswdev; i++) {
+ int xsize, xfree;
+
+ xsize = sw[i].sw_nblks;
+ xfree = perdev[i];
+ *total += xsize;
+ }
+
+ /*
+ * If only one partition has been set up via swapon(8), we don't
+ * need to bother with totals.
+ */
+#if DEV_BSHIFT < 10
+ *used = (*total - nfree) >> (10 - DEV_BSHIFT);
+ *total >>= 10 - DEV_BSHIFT;
+#elif DEV_BSHIFT > 10
+ *used = (*total - nfree) >> (DEV_BSHIFT - 10);
+ *total >>= DEV_BSHIFT - 10;
+#endif
+ free (sw); free (freemp); free (perdev);
+ return 1;
+}
+#endif
diff --git a/usr.bin/top/machine.h b/usr.bin/top/machine.h
new file mode 100644
index 00000000000..f50cb67d30e
--- /dev/null
+++ b/usr.bin/top/machine.h
@@ -0,0 +1,60 @@
+/* $OpenBSD: machine.h,v 1.1 1997/08/14 14:00:23 downsj Exp $ */
+
+/*
+ * This file defines the interface between top and the machine-dependent
+ * module. It is NOT machine dependent and should not need to be changed
+ * for any specific machine.
+ */
+
+/*
+ * the statics struct is filled in by machine_init
+ */
+struct statics
+{
+ char **procstate_names;
+ char **cpustate_names;
+ char **memory_names;
+#ifdef ORDER
+ char **order_names;
+#endif
+};
+
+/*
+ * the system_info struct is filled in by a machine dependent routine.
+ */
+
+struct system_info
+{
+ int last_pid;
+ double load_avg[NUM_AVERAGES];
+ int p_total;
+ int p_active; /* number of procs considered "active" */
+ int *procstates;
+ int *cpustates;
+ int *memory;
+};
+
+/* cpu_states is an array of percentages * 10. For example,
+ the (integer) value 105 is 10.5% (or .105).
+ */
+
+/*
+ * the process_select struct tells get_process_info what processes we
+ * are interested in seeing
+ */
+
+struct process_select
+{
+ int idle; /* show idle processes */
+ int system; /* show system processes */
+ int uid; /* only this uid (unless uid == -1) */
+ char *command; /* only this command (unless == NULL) */
+};
+
+/* routines defined by the machine dependent module */
+
+char *format_header();
+char *format_next_process();
+
+/* non-int routines typically used by the machine dependent module */
+char *printable();
diff --git a/usr.bin/top/os.h b/usr.bin/top/os.h
new file mode 100644
index 00000000000..48ea0d99d04
--- /dev/null
+++ b/usr.bin/top/os.h
@@ -0,0 +1,31 @@
+/* $OpenBSD: os.h,v 1.1 1997/08/14 14:00:23 downsj Exp $ */
+
+#include <sys/types.h>
+#include <sys/param.h> /* This defines BSD */
+#if defined(BSD) && !defined(BSD4_4) && !defined(__osf__)
+# include <stdio.h>
+# include <strings.h>
+# define strchr(a, b) index((a), (b))
+# define strrchr(a, b) rindex((a), (b))
+# define memcpy(a, b, c) bcopy((b), (a), (c))
+# define memzero(a, b) bzero((a), (b))
+# define memcmp(a, b, c) bcmp((a), (b), (c))
+#if defined(NeXT)
+ typedef void sigret_t;
+#else
+ typedef int sigret_t;
+#endif
+
+/* system routines that don't return int */
+char *getenv();
+caddr_t malloc();
+
+#else
+# include <stdio.h>
+# define setbuffer(f, b, s) setvbuf((f), (b), (b) ? _IOFBF : _IONBF, (s))
+# include <string.h>
+# include <memory.h>
+# include <stdlib.h>
+# define memzero(a, b) memset((a), 0, (b))
+ typedef void sigret_t;
+#endif
diff --git a/usr.bin/top/patchlevel.h b/usr.bin/top/patchlevel.h
new file mode 100644
index 00000000000..9fc4a61610c
--- /dev/null
+++ b/usr.bin/top/patchlevel.h
@@ -0,0 +1,3 @@
+/* $OpenBSD: patchlevel.h,v 1.1 1997/08/14 14:00:24 downsj Exp $ */
+
+#define PATCHLEVEL 4
diff --git a/usr.bin/top/screen.c b/usr.bin/top/screen.c
new file mode 100644
index 00000000000..c52c0298c13
--- /dev/null
+++ b/usr.bin/top/screen.c
@@ -0,0 +1,496 @@
+/* $OpenBSD: screen.c,v 1.1 1997/08/14 14:00:24 downsj Exp $ */
+
+/*
+ * Top users/processes display for Unix
+ * Version 3
+ *
+ * This program may be freely redistributed,
+ * but this entire comment MUST remain intact.
+ *
+ * Copyright (c) 1984, 1989, William LeFebvre, Rice University
+ * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
+ */
+
+/* This file contains the routines that interface to termcap and stty/gtty.
+ *
+ * Paul Vixie, February 1987: converted to use ioctl() instead of stty/gtty.
+ *
+ * I put in code to turn on the TOSTOP bit while top was running, but I
+ * didn't really like the results. If you desire it, turn on the
+ * preprocessor variable "TOStop". --wnl
+ */
+
+#include "os.h"
+#include "top.h"
+
+#include <sys/ioctl.h>
+#ifdef CBREAK
+# include <sgtty.h>
+# define SGTTY
+#else
+# ifdef TCGETA
+# define TERMIO
+# include <termio.h>
+# else
+# define TERMIOS
+# include <termios.h>
+# endif
+#endif
+#if defined(TERMIO) || defined(TERMIOS)
+# ifndef TAB3
+# ifdef OXTABS
+# define TAB3 OXTABS
+# else
+# define TAB3 0
+# endif
+# endif
+#endif
+#include "screen.h"
+#include "boolean.h"
+
+extern char *myname;
+
+int putstdout();
+
+int overstrike;
+int screen_length;
+int screen_width;
+char ch_erase;
+char ch_kill;
+char smart_terminal;
+char PC;
+char *tgetstr();
+char *tgoto();
+char termcap_buf[1024];
+char string_buffer[1024];
+char home[15];
+char lower_left[15];
+char *clear_line;
+char *clear_screen;
+char *clear_to_end;
+char *cursor_motion;
+char *start_standout;
+char *end_standout;
+char *terminal_init;
+char *terminal_end;
+short ospeed;
+
+#ifdef SGTTY
+static struct sgttyb old_settings;
+static struct sgttyb new_settings;
+#endif
+#ifdef TERMIO
+static struct termio old_settings;
+static struct termio new_settings;
+#endif
+#ifdef TERMIOS
+static struct termios old_settings;
+static struct termios new_settings;
+#endif
+static char is_a_terminal = No;
+#ifdef TOStop
+static int old_lword;
+static int new_lword;
+#endif
+
+#define STDIN 0
+#define STDOUT 1
+#define STDERR 2
+
+init_termcap(interactive)
+
+int interactive;
+
+{
+ char *bufptr;
+ char *PCptr;
+ char *term_name;
+ char *getenv();
+ int status;
+
+ /* set defaults in case we aren't smart */
+ screen_width = MAX_COLS;
+ screen_length = 0;
+
+ if (!interactive)
+ {
+ /* pretend we have a dumb terminal */
+ smart_terminal = No;
+ return;
+ }
+
+ /* assume we have a smart terminal until proven otherwise */
+ smart_terminal = Yes;
+
+ /* get the terminal name */
+ term_name = getenv("TERM");
+
+ /* if there is no TERM, assume it's a dumb terminal */
+ /* patch courtesy of Sam Horrocks at telegraph.ics.uci.edu */
+ if (term_name == NULL)
+ {
+ smart_terminal = No;
+ return;
+ }
+
+ /* now get the termcap entry */
+ if ((status = tgetent(termcap_buf, term_name)) != 1)
+ {
+ if (status == -1)
+ {
+ fprintf(stderr, "%s: can't open termcap file\n", myname);
+ }
+ else
+ {
+ fprintf(stderr, "%s: no termcap entry for a `%s' terminal\n",
+ myname, term_name);
+ }
+
+ /* pretend it's dumb and proceed */
+ smart_terminal = No;
+ return;
+ }
+
+ /* "hardcopy" immediately indicates a very stupid terminal */
+ if (tgetflag("hc"))
+ {
+ smart_terminal = No;
+ return;
+ }
+
+ /* set up common terminal capabilities */
+ if ((screen_length = tgetnum("li")) <= 0)
+ {
+ screen_length = smart_terminal = 0;
+ return;
+ }
+
+ /* screen_width is a little different */
+ if ((screen_width = tgetnum("co")) == -1)
+ {
+ screen_width = 79;
+ }
+ else
+ {
+ screen_width -= 1;
+ }
+
+ /* terminals that overstrike need special attention */
+ overstrike = tgetflag("os");
+
+ /* initialize the pointer into the termcap string buffer */
+ bufptr = string_buffer;
+
+ /* get "ce", clear to end */
+ if (!overstrike)
+ {
+ clear_line = tgetstr("ce", &bufptr);
+ }
+
+ /* get necessary capabilities */
+ if ((clear_screen = tgetstr("cl", &bufptr)) == NULL ||
+ (cursor_motion = tgetstr("cm", &bufptr)) == NULL)
+ {
+ smart_terminal = No;
+ return;
+ }
+
+ /* get some more sophisticated stuff -- these are optional */
+ clear_to_end = tgetstr("cd", &bufptr);
+ terminal_init = tgetstr("ti", &bufptr);
+ terminal_end = tgetstr("te", &bufptr);
+ start_standout = tgetstr("so", &bufptr);
+ end_standout = tgetstr("se", &bufptr);
+
+ /* pad character */
+ PC = (PCptr = tgetstr("pc", &bufptr)) ? *PCptr : 0;
+
+ /* set convenience strings */
+ (void) strcpy(home, tgoto(cursor_motion, 0, 0));
+ /* (lower_left is set in get_screensize) */
+
+ /* get the actual screen size with an ioctl, if needed */
+ /* This may change screen_width and screen_length, and it always
+ sets lower_left. */
+ get_screensize();
+
+ /* if stdout is not a terminal, pretend we are a dumb terminal */
+#ifdef SGTTY
+ if (ioctl(STDOUT, TIOCGETP, &old_settings) == -1)
+ {
+ smart_terminal = No;
+ }
+#endif
+#ifdef TERMIO
+ if (ioctl(STDOUT, TCGETA, &old_settings) == -1)
+ {
+ smart_terminal = No;
+ }
+#endif
+#ifdef TERMIOS
+ if (tcgetattr(STDOUT, &old_settings) == -1)
+ {
+ smart_terminal = No;
+ }
+#endif
+}
+
+init_screen()
+
+{
+ /* get the old settings for safe keeping */
+#ifdef SGTTY
+ if (ioctl(STDOUT, TIOCGETP, &old_settings) != -1)
+ {
+ /* copy the settings so we can modify them */
+ new_settings = old_settings;
+
+ /* turn on CBREAK and turn off character echo and tab expansion */
+ new_settings.sg_flags |= CBREAK;
+ new_settings.sg_flags &= ~(ECHO|XTABS);
+ (void) ioctl(STDOUT, TIOCSETP, &new_settings);
+
+ /* remember the erase and kill characters */
+ ch_erase = old_settings.sg_erase;
+ ch_kill = old_settings.sg_kill;
+
+#ifdef TOStop
+ /* get the local mode word */
+ (void) ioctl(STDOUT, TIOCLGET, &old_lword);
+
+ /* modify it */
+ new_lword = old_lword | LTOSTOP;
+ (void) ioctl(STDOUT, TIOCLSET, &new_lword);
+#endif
+ /* remember that it really is a terminal */
+ is_a_terminal = Yes;
+
+ /* send the termcap initialization string */
+ putcap(terminal_init);
+ }
+#endif
+#ifdef TERMIO
+ if (ioctl(STDOUT, TCGETA, &old_settings) != -1)
+ {
+ /* copy the settings so we can modify them */
+ new_settings = old_settings;
+
+ /* turn off ICANON, character echo and tab expansion */
+ new_settings.c_lflag &= ~(ICANON|ECHO);
+ new_settings.c_oflag &= ~(TAB3);
+ new_settings.c_cc[VMIN] = 1;
+ new_settings.c_cc[VTIME] = 0;
+ (void) ioctl(STDOUT, TCSETA, &new_settings);
+
+ /* remember the erase and kill characters */
+ ch_erase = old_settings.c_cc[VERASE];
+ ch_kill = old_settings.c_cc[VKILL];
+
+ /* remember that it really is a terminal */
+ is_a_terminal = Yes;
+
+ /* send the termcap initialization string */
+ putcap(terminal_init);
+ }
+#endif
+#ifdef TERMIOS
+ if (tcgetattr(STDOUT, &old_settings) != -1)
+ {
+ /* copy the settings so we can modify them */
+ new_settings = old_settings;
+
+ /* turn off ICANON, character echo and tab expansion */
+ new_settings.c_lflag &= ~(ICANON|ECHO);
+ new_settings.c_oflag &= ~(TAB3);
+ new_settings.c_cc[VMIN] = 1;
+ new_settings.c_cc[VTIME] = 0;
+ (void) tcsetattr(STDOUT, TCSADRAIN, &new_settings);
+
+ /* remember the erase and kill characters */
+ ch_erase = old_settings.c_cc[VERASE];
+ ch_kill = old_settings.c_cc[VKILL];
+
+ /* remember that it really is a terminal */
+ is_a_terminal = Yes;
+
+ /* send the termcap initialization string */
+ putcap(terminal_init);
+ }
+#endif
+
+ if (!is_a_terminal)
+ {
+ /* not a terminal at all---consider it dumb */
+ smart_terminal = No;
+ }
+}
+
+end_screen()
+
+{
+ /* move to the lower left, clear the line and send "te" */
+ if (smart_terminal)
+ {
+ putcap(lower_left);
+ putcap(clear_line);
+ fflush(stdout);
+ putcap(terminal_end);
+ }
+
+ /* if we have settings to reset, then do so */
+ if (is_a_terminal)
+ {
+#ifdef SGTTY
+ (void) ioctl(STDOUT, TIOCSETP, &old_settings);
+#ifdef TOStop
+ (void) ioctl(STDOUT, TIOCLSET, &old_lword);
+#endif
+#endif
+#ifdef TERMIO
+ (void) ioctl(STDOUT, TCSETA, &old_settings);
+#endif
+#ifdef TERMIOS
+ (void) tcsetattr(STDOUT, TCSADRAIN, &old_settings);
+#endif
+ }
+}
+
+reinit_screen()
+
+{
+ /* install our settings if it is a terminal */
+ if (is_a_terminal)
+ {
+#ifdef SGTTY
+ (void) ioctl(STDOUT, TIOCSETP, &new_settings);
+#ifdef TOStop
+ (void) ioctl(STDOUT, TIOCLSET, &new_lword);
+#endif
+#endif
+#ifdef TERMIO
+ (void) ioctl(STDOUT, TCSETA, &new_settings);
+#endif
+#ifdef TERMIOS
+ (void) tcsetattr(STDOUT, TCSADRAIN, &new_settings);
+#endif
+ }
+
+ /* send init string */
+ if (smart_terminal)
+ {
+ putcap(terminal_init);
+ }
+}
+
+get_screensize()
+
+{
+
+#ifdef TIOCGWINSZ
+
+ struct winsize ws;
+
+ if (ioctl (1, TIOCGWINSZ, &ws) != -1)
+ {
+ if (ws.ws_row != 0)
+ {
+ screen_length = ws.ws_row;
+ }
+ if (ws.ws_col != 0)
+ {
+ screen_width = ws.ws_col - 1;
+ }
+ }
+
+#else
+#ifdef TIOCGSIZE
+
+ struct ttysize ts;
+
+ if (ioctl (1, TIOCGSIZE, &ts) != -1)
+ {
+ if (ts.ts_lines != 0)
+ {
+ screen_length = ts.ts_lines;
+ }
+ if (ts.ts_cols != 0)
+ {
+ screen_width = ts.ts_cols - 1;
+ }
+ }
+
+#endif /* TIOCGSIZE */
+#endif /* TIOCGWINSZ */
+
+ (void) strcpy(lower_left, tgoto(cursor_motion, 0, screen_length - 1));
+}
+
+standout(msg)
+
+char *msg;
+
+{
+ if (smart_terminal)
+ {
+ putcap(start_standout);
+ fputs(msg, stdout);
+ putcap(end_standout);
+ }
+ else
+ {
+ fputs(msg, stdout);
+ }
+}
+
+clear()
+
+{
+ if (smart_terminal)
+ {
+ putcap(clear_screen);
+ }
+}
+
+clear_eol(len)
+
+int len;
+
+{
+ if (smart_terminal && !overstrike && len > 0)
+ {
+ if (clear_line)
+ {
+ putcap(clear_line);
+ return(0);
+ }
+ else
+ {
+ while (len-- > 0)
+ {
+ putchar(' ');
+ }
+ return(1);
+ }
+ }
+ return(-1);
+}
+
+go_home()
+
+{
+ if (smart_terminal)
+ {
+ putcap(home);
+ }
+}
+
+/* This has to be defined as a subroutine for tputs (instead of a macro) */
+
+putstdout(ch)
+
+char ch;
+
+{
+ putchar(ch);
+}
+
diff --git a/usr.bin/top/screen.h b/usr.bin/top/screen.h
new file mode 100644
index 00000000000..1d8fedfb573
--- /dev/null
+++ b/usr.bin/top/screen.h
@@ -0,0 +1,33 @@
+/* $OpenBSD: screen.h,v 1.1 1997/08/14 14:00:24 downsj Exp $ */
+
+/*
+ * top - a top users display for Unix 4.2
+ *
+ * This file contains all the definitions necessary to use the hand-written
+ * screen package in "screen.c"
+ */
+
+#define TCputs(str) tputs(str, 1, putstdout)
+#define putcap(str) (void)((str) != NULL ? TCputs(str) : 0)
+#define Move_to(x, y) TCputs(tgoto(cursor_motion, x, y))
+
+/* declare return values for termcap functions */
+char *tgetstr();
+char *tgoto();
+
+extern char ch_erase; /* set to the user's erase character */
+extern char ch_kill; /* set to the user's kill character */
+extern char smart_terminal; /* set if the terminal has sufficient termcap
+ capabilities for normal operation */
+
+/* These are some termcap strings for use outside of "screen.c" */
+extern char *cursor_motion;
+extern char *clear_line;
+extern char *clear_to_end;
+
+/* rows and columns on the screen according to termcap */
+extern int screen_length;
+extern int screen_width;
+
+/* a function that puts a single character on stdout */
+int putstdout();
diff --git a/usr.bin/top/sigconv.awk b/usr.bin/top/sigconv.awk
new file mode 100644
index 00000000000..8c90d8dc749
--- /dev/null
+++ b/usr.bin/top/sigconv.awk
@@ -0,0 +1,53 @@
+BEGIN {
+ nsig = 0;
+ j = 0;
+ print "/* This file was automatically generated */"
+ print "/* by the awk script \"sigconv.awk\". */\n"
+ print "struct sigdesc {"
+ print " char *name;"
+ print " int number;"
+ print "};\n"
+ print "struct sigdesc sigdesc[] = {"
+ }
+
+/^#define[ \t][ \t]*SIG[A-Z]/ {
+
+ j = sprintf("%d", $3);
+ str = $2;
+
+ if (nsig < j)
+ nsig = j;
+
+ siglist[j] = sprintf("\"%s\",\t%2d,", \
+ substr(str, 4), j);
+ }
+/^#[ \t]*define[ \t][ \t]*SIG[A-Z]/ {
+
+ j = sprintf("%d", $4);
+ str = $3;
+
+ if (nsig < j)
+ nsig = j;
+
+ siglist[j] = sprintf("\"%s\",\t%2d,", \
+ substr(str, 4), j);
+ }
+/^#[ \t]*define[ \t][ \t]*_SIG[A-Z]/ {
+
+ j = sprintf("%d", $4);
+ str = $3;
+
+ if (nsig < j)
+ nsig = j;
+
+ siglist[j] = sprintf("\"%s\",\t%2d,", \
+ substr(str, 5), j);
+ }
+
+END {
+ for (n = 1; n <= nsig; n++)
+ if (siglist[n] != "")
+ printf(" %s\n", siglist[n]);
+
+ printf(" NULL,\t 0\n};\n");
+ }
diff --git a/usr.bin/top/top.1 b/usr.bin/top/top.1
new file mode 100644
index 00000000000..3e04fe9bede
--- /dev/null
+++ b/usr.bin/top/top.1
@@ -0,0 +1,321 @@
+.\" $OpenBSD: top.1,v 1.1 1997/08/14 14:00:25 downsj Exp $
+.nr N 15
+.nr D 5
+.TH TOP 1 Local
+.UC 4
+.SH NAME
+top \- display and update information about the top cpu processes
+.SH SYNOPSIS
+.B top
+[
+.B \-SbiInqu
+] [
+.BI \-d count
+] [
+.BI \-s time
+] [
+.BI \-o field
+] [
+.BI \-U username
+] [
+.I number
+]
+.SH DESCRIPTION
+.\" This defines appropriate quote strings for nroff and troff
+.ds lq \&"
+.ds rq \&"
+.if t .ds lq ``
+.if t .ds rq ''
+.\" Just in case these number registers aren't set yet...
+.if \nN==0 .nr N 10
+.if \nD==0 .nr D 5
+.I Top
+displays the top
+.if !\nN==-1 \nN
+processes on the system and periodically updates this information.
+.if \nN==-1 \
+\{\
+If standard output is an intelligent terminal (see below) then
+as many processes as will fit on the terminal screen are displayed
+by default. Otherwise, a good number of them are shown (around 20).
+.\}
+Raw cpu percentage is used to rank the processes. If
+.I number
+is given, then the top
+.I number
+processes will be displayed instead of the default.
+.PP
+.I Top
+makes a distinction between terminals that support advanced capabilities
+and those that do not. This
+distinction affects the choice of defaults for certain options. In the
+remainder of this document, an \*(lqintelligent\*(rq terminal is one that
+supports cursor addressing, clear screen, and clear to end of line.
+Conversely, a \*(lqdumb\*(rq terminal is one that does not support such
+features. If the output of
+.I top
+is redirected to a file, it acts as if it were being run on a dumb
+terminal.
+.SH OPTIONS
+.TP
+.B \-S
+Show system processes in the display. Normally, system processes such as
+the pager and the swapper are not shown. This option makes them visible.
+.TP
+.B \-b
+Use \*(lqbatch\*(rq mode. In this mode, all input from the terminal is
+ignored. Interrupt characters (such as ^C and ^\e) still have an effect.
+This is the default on a dumb terminal, or when the output is not a terminal.
+.TP
+.B \-i
+Use \*(lqinteractive\*(rq mode. In this mode, any input is immediately
+read for processing. See the section on \*(lqInteractive Mode\*(rq
+for an explanation of
+which keys perform what functions. After the command is processed, the
+screen will immediately be updated, even if the command was not
+understood. This mode is the default when standard output is an
+intelligent terminal.
+.TP
+.B \-I
+Do not display idle processes.
+By default, top displays both active and idle processes.
+.TP
+.B \-n
+Use \*(lqnon-interactive\*(rq mode. This is indentical to \*(lqbatch\*(rq
+mode.
+.TP
+.B \-q
+Renice
+.I top
+to -20 so that it will run faster. This can be used when the system is
+being very sluggish to improve the possibility of discovering the problem.
+This option can only be used by root.
+.TP
+.B \-u
+Do not take the time to map uid numbers to usernames. Normally,
+.I top
+will read as much of the file \*(lq/etc/passwd\*(rq as is necessary to map
+all the user id numbers it encounters into login names. This option
+disables all that, while possibly decreasing execution time. The uid
+numbers are displayed instead of the names.
+.TP
+.BI \-d count
+Show only
+.I count
+displays, then exit. A display is considered to be one update of the
+screen. This option allows the user to select the number of displays he
+wants to see before
+.I top
+automatically exits. For intelligent terminals, no upper limit
+is set. The default is 1 for dumb terminals.
+.TP
+.BI \-s time
+Set the delay between screen updates to
+.I time
+seconds. The default delay between updates is \nD seconds.
+.TP
+.BI \-o field
+Sort the process display area on the specified field. The field name is
+the name of the column as seen in the output, but in lower case. Likely
+values are \*(lqcpu\*(rq, \*(lqsize\*(rq, \*(lqres\*(rq, and \*(lqtime\*(rq,
+but may vary on different operating systems. Note that
+not all operating systems support this option.
+.TP
+.BI \-U username
+Show only those processes owned by
+.IR username .
+This option currently only accepts usernames and will not understand
+uid numbers.
+.PP
+Both
+.I count
+and
+.I number
+fields can be specified as \*(lqinfinite\*(rq, indicating that they can
+stretch as far as possible. This is accomplished by using any proper
+prefix of the keywords
+\*(lqinfinity\*(rq,
+\*(lqmaximum\*(rq,
+or
+\*(lqall\*(rq.
+The default for
+.I count
+on an intelligent terminal is, in fact,
+.BI infinity .
+.PP
+The environment variable
+.B TOP
+is examined for options before the command line is scanned. This enables
+a user to set his or her own defaults. The number of processes to display
+can also be specified in the environment variable
+.BR TOP .
+The options
+.BR \-I ,
+.BR \-S ,
+and
+.B \-u
+are actually toggles. A second specification of any of these options
+will negate the first. Thus a user who has the environment variable
+.B TOP
+set to \*(lq\-I\*(rq may use the command \*(lqtop \-I\*(rq to see idle processes.
+.SH "INTERACTIVE MODE"
+When
+.I top
+is running in \*(lqinteractive mode\*(rq, it reads commands from the
+terminal and acts upon them accordingly. In this mode, the terminal is
+put in \*(lqCBREAK\*(rq, so that a character will be
+processed as soon as it is typed. Almost always, a key will be
+pressed when
+.I top
+is between displays; that is, while it is waiting for
+.I time
+seconds to elapse. If this is the case, the command will be
+processed and the display will be updated immediately thereafter
+(reflecting any changes that the command may have specified). This
+happens even if the command was incorrect. If a key is pressed while
+.I top
+is in the middle of updating the display, it will finish the update and
+then process the command. Some commands require additional information,
+and the user will be prompted accordingly. While typing this information
+in, the user's erase and kill keys (as set up by the command
+.IR stty )
+are recognized, and a newline terminates the input.
+.PP
+These commands are currently recognized (^L refers to control-L):
+.TP
+.B ^L
+Redraw the screen.
+.IP "\fBh\fP\ or\ \fB?\fP"
+Display a summary of the commands (help screen).
+.TP
+.B q
+Quit
+.IR top.
+.TP
+.B d
+Change the number of displays to show (prompt for new number).
+Remember that the next display counts as one, so typing
+.B d1
+will make
+.I top
+show one final display and then immediately exit.
+.TP
+.B n or #
+Change the number of processes to display (prompt for new number).
+.TP
+.B s
+Change the number of seconds to delay between displays
+(prompt for new number).
+.TP
+.B k
+Send a signal (\*(lqkill\*(rq by default) to a list of processes. This
+acts similarly to the command
+.IR kill (1)).
+.TP
+.B r
+Change the priority (the \*(lqnice\*(rq) of a list of processes.
+This acts similarly to the command
+.IR renice (8)).
+.TP
+.B u
+Display only processes owned by a specific username (prompt for username).
+If the username specified is simply \*(lq+\*(rq, then processes belonging
+to all users will be displayed.
+.TP
+.B e
+Display a list of system errors (if any) generated by the last
+.BR k ill
+or
+.BR r enice
+command.
+.TP
+.B i
+(or
+.BR I)
+Toggle the display of idle processes.
+.SH "THE DISPLAY"
+The actual display varies depending on the specific variant of Unix
+that the machine is running. This description may not exactly match
+what is seen by top running on this particular machine. Differences
+are listed at the end of this manual entry.
+.PP
+The top few lines of the display show general information
+about the state of the system, including
+the last process id assigned to a process (on most systems),
+the three load averages,
+the current time,
+the number of existing processes,
+the number of processes in each state
+(sleeping, running, starting, zombies, and stopped),
+and a percentage of time spent in each of the processor states
+(user, nice, system, and idle).
+It also includes information about physial and virtual memory allocation.
+.PP
+The remainder of the screen displays information about individual
+processes. This display is similar in spirit to
+.IR ps (1)
+but it is not exactly the same. PID is the process id, USERNAME is the name
+of the process's owner (if
+.B \-u
+is specified, a UID column will be substituted for USERNAME),
+PRI is the current priority of the process,
+NICE is the nice amount (in the range \-20 to 20),
+SIZE is the total size of the process (text, data, and stack),
+RES is the current amount of resident memory (both SIZE and RES are
+given in kilobytes),
+STATE is the current state (one of \*(lqsleep\*(rq, \*(lqWAIT\*(rq,
+\*(lqrun\*(rq, \*(lqidl\*(rq, \*(lqzomb\*(rq, or \*(lqstop\*(rq),
+TIME is the number of system and user cpu seconds that the process has used,
+WCPU, when displayed, is the weighted cpu percentage (this is the same
+value that
+.IR ps (1)
+displays as CPU),
+CPU is the raw percentage and is the field that is sorted to determine
+the order of the processes, and
+COMMAND is the name of the command that the process is currently running
+(if the process is swapped out, this column is marked \*(lq<swapped>\*(rq).
+.SH NOTES
+The \*(lqABANDONED\*(rq state (known in the kernel as \*(lqSWAIT\*(rq) was
+abandoned, thus the name. A process should never end up in this state.
+.SH AUTHOR
+William LeFebvre, EECS Department, Northwestern University
+.SH ENVIRONMENT
+.DT
+TOP user-configurable defaults for options.
+.SH FILES
+.DT
+/dev/kmem kernel memory
+.br
+/dev/mem physical memory
+.br
+/bsd system image
+.SH BUGS
+Don't shoot me, but the default for
+.B \-I
+has changed once again. So many people were confused by the fact that
+.I top
+wasn't showing them all the processes that I have decided to make the
+default behavior show idle processes, just like it did in version 2.
+But to appease folks who can't stand that behavior, I have added the
+ability to set \*(lqdefault\*(rq options in the environment variable
+.B TOP
+(see the OPTIONS section). Those who want the behavior that version
+3.0 had need only set the environment variable
+.B TOP
+to \*(lq\-I\*(rq.
+.PP
+The command name for swapped processes should be tracked down, but this
+would make the program run slower.
+.PP
+As with
+.IR ps (1),
+things can change while
+.I top
+is collecting information for an update. The picture it gives is only a
+close approximation to reality.
+.SH "SEE ALSO"
+kill(1),
+ps(1),
+stty(1),
+mem(4),
+renice(8)
diff --git a/usr.bin/top/top.c b/usr.bin/top/top.c
new file mode 100644
index 00000000000..a4368dc906d
--- /dev/null
+++ b/usr.bin/top/top.c
@@ -0,0 +1,998 @@
+/* $OpenBSD: top.c,v 1.1 1997/08/14 14:00:26 downsj Exp $ */
+
+char *copyright =
+ "Copyright (c) 1984 through 1996, William LeFebvre";
+
+/*
+ * Top users/processes display for Unix
+ * Version 3
+ *
+ * This program may be freely redistributed,
+ * but this entire comment MUST remain intact.
+ *
+ * Copyright (c) 1984, 1989, William LeFebvre, Rice University
+ * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
+ */
+
+/*
+ * See the file "Changes" for information on version-to-version changes.
+ */
+
+/*
+ * This file contains "main" and other high-level routines.
+ */
+
+/*
+ * The following preprocessor variables, when defined, are used to
+ * distinguish between different Unix implementations:
+ *
+ * SIGHOLD - use SVR4 sighold function when defined
+ * SIGRELSE - use SVR4 sigrelse function when defined
+ * FD_SET - macros FD_SET and FD_ZERO are used when defined
+ */
+
+#include "os.h"
+#include <signal.h>
+#include <setjmp.h>
+#include <ctype.h>
+#include <sys/time.h>
+
+/* includes specific to top */
+#include "display.h" /* interface to display package */
+#include "screen.h" /* interface to screen package */
+#include "top.h"
+#include "top.local.h"
+#include "boolean.h"
+#include "machine.h"
+#include "utils.h"
+
+/* Size of the stdio buffer given to stdout */
+#define Buffersize 2048
+
+/* The buffer that stdio will use */
+char stdoutbuf[Buffersize];
+
+/* build Signal masks */
+#define Smask(s) (1 << ((s) - 1))
+
+/* for system errors */
+extern int errno;
+
+/* for getopt: */
+extern int optind;
+extern char *optarg;
+
+/* imported from screen.c */
+extern int overstrike;
+
+/* signal handling routines */
+sigret_t leave();
+sigret_t onalrm();
+sigret_t tstop();
+#ifdef SIGWINCH
+sigret_t winch();
+#endif
+
+/* internal routines */
+void quit();
+
+/* values which need to be accessed by signal handlers */
+static int max_topn; /* maximum displayable processes */
+
+/* miscellaneous things */
+char *myname = "top";
+jmp_buf jmp_int;
+
+/* routines that don't return int */
+
+char *username();
+char *ctime();
+char *kill_procs();
+char *renice_procs();
+
+#ifdef ORDER
+extern int (*proc_compares[])();
+#else
+extern int proc_compare();
+#endif
+time_t time();
+
+caddr_t get_process_info();
+
+/* different routines for displaying the user's identification */
+/* (values assigned to get_userid) */
+char *username();
+char *itoa7();
+
+/* display routines that need to be predeclared */
+int i_loadave();
+int u_loadave();
+int i_procstates();
+int u_procstates();
+int i_cpustates();
+int u_cpustates();
+int i_memory();
+int u_memory();
+int i_message();
+int u_message();
+int i_header();
+int u_header();
+int i_process();
+int u_process();
+
+/* pointers to display routines */
+int (*d_loadave)() = i_loadave;
+int (*d_procstates)() = i_procstates;
+int (*d_cpustates)() = i_cpustates;
+int (*d_memory)() = i_memory;
+int (*d_message)() = i_message;
+int (*d_header)() = i_header;
+int (*d_process)() = i_process;
+
+
+main(argc, argv)
+
+int argc;
+char *argv[];
+
+{
+ register int i;
+ register int active_procs;
+ register int change;
+
+ struct system_info system_info;
+ struct statics statics;
+ caddr_t processes;
+
+ static char tempbuf1[50];
+ static char tempbuf2[50];
+ int old_sigmask; /* only used for BSD-style signals */
+ int topn = Default_TOPN;
+ int delay = Default_DELAY;
+ int displays = 0; /* indicates unspecified */
+ time_t curr_time;
+ char *(*get_userid)() = username;
+ char *uname_field = "USERNAME";
+ char *header_text;
+ char *env_top;
+ char **preset_argv;
+ int preset_argc = 0;
+ char **av;
+ int ac;
+ char dostates = No;
+ char do_unames = Yes;
+ char interactive = Maybe;
+ char warnings = 0;
+#if Default_TOPN == Infinity
+ char topn_specified = No;
+#endif
+ char ch;
+ char *iptr;
+ char no_command = 1;
+ struct timeval timeout;
+ struct process_select ps;
+#ifdef ORDER
+ char *order_name = NULL;
+ int order_index = 0;
+#endif
+#ifndef FD_SET
+ /* FD_SET and friends are not present: fake it */
+ typedef int fd_set;
+#define FD_ZERO(x) (*(x) = 0)
+#define FD_SET(f, x) (*(x) = f)
+#endif
+ fd_set readfds;
+
+#ifdef ORDER
+ static char command_chars[] = "\f qh?en#sdkriIuo";
+#else
+ static char command_chars[] = "\f qh?en#sdkriIu";
+#endif
+/* these defines enumerate the "strchr"s of the commands in command_chars */
+#define CMD_redraw 0
+#define CMD_update 1
+#define CMD_quit 2
+#define CMD_help1 3
+#define CMD_help2 4
+#define CMD_OSLIMIT 4 /* terminals with OS can only handle commands */
+#define CMD_errors 5 /* less than or equal to CMD_OSLIMIT */
+#define CMD_number1 6
+#define CMD_number2 7
+#define CMD_delay 8
+#define CMD_displays 9
+#define CMD_kill 10
+#define CMD_renice 11
+#define CMD_idletog 12
+#define CMD_idletog2 13
+#define CMD_user 14
+#ifdef ORDER
+#define CMD_order 15
+#endif
+
+ /* set the buffer for stdout */
+#ifdef DEBUG
+ setbuffer(stdout, NULL, 0);
+#else
+ setbuffer(stdout, stdoutbuf, Buffersize);
+#endif
+
+ /* get our name */
+ if (argc > 0)
+ {
+ if ((myname = strrchr(argv[0], '/')) == 0)
+ {
+ myname = argv[0];
+ }
+ else
+ {
+ myname++;
+ }
+ }
+
+ /* initialize some selection options */
+ ps.idle = Yes;
+ ps.system = No;
+ ps.uid = -1;
+ ps.command = NULL;
+
+ /* get preset options from the environment */
+ if ((env_top = getenv("TOP")) != NULL)
+ {
+ av = preset_argv = argparse(env_top, &preset_argc);
+ ac = preset_argc;
+
+ /* set the dummy argument to an explanatory message, in case
+ getopt encounters a bad argument */
+ preset_argv[0] = "while processing environment";
+ }
+
+ /* process options */
+ do {
+ /* if we're done doing the presets, then process the real arguments */
+ if (preset_argc == 0)
+ {
+ ac = argc;
+ av = argv;
+
+ /* this should keep getopt happy... */
+ optind = 1;
+ }
+
+ while ((i = getopt(ac, av, "SIbinqus:d:U:o:")) != EOF)
+ {
+ switch(i)
+ {
+ case 'u': /* toggle uid/username display */
+ do_unames = !do_unames;
+ break;
+
+ case 'U': /* display only username's processes */
+ if ((ps.uid = userid(optarg)) == -1)
+ {
+ fprintf(stderr, "%s: unknown user\n", optarg);
+ exit(1);
+ }
+ break;
+
+ case 'S': /* show system processes */
+ ps.system = !ps.system;
+ break;
+
+ case 'I': /* show idle processes */
+ ps.idle = !ps.idle;
+ break;
+
+ case 'i': /* go interactive regardless */
+ interactive = Yes;
+ break;
+
+ case 'n': /* batch, or non-interactive */
+ case 'b':
+ interactive = No;
+ break;
+
+ case 'd': /* number of displays to show */
+ if ((i = atoiwi(optarg)) == Invalid || i == 0)
+ {
+ fprintf(stderr,
+ "%s: warning: display count should be positive -- option ignored\n",
+ myname);
+ warnings++;
+ }
+ else
+ {
+ displays = i;
+ }
+ break;
+
+ case 's':
+ if ((delay = atoi(optarg)) < 0)
+ {
+ fprintf(stderr,
+ "%s: warning: seconds delay should be non-negative -- using default\n",
+ myname);
+ delay = Default_DELAY;
+ warnings++;
+ }
+ break;
+
+ case 'q': /* be quick about it */
+ /* only allow this if user is really root */
+ if (getuid() == 0)
+ {
+ /* be very un-nice! */
+ (void) nice(-20);
+ }
+ else
+ {
+ fprintf(stderr,
+ "%s: warning: `-q' option can only be used by root\n",
+ myname);
+ warnings++;
+ }
+ break;
+
+ case 'o': /* select sort order */
+#ifdef ORDER
+ order_name = optarg;
+#else
+ fprintf(stderr,
+ "%s: this platform does not support arbitrary ordering. Sorry.\n",
+ myname);
+ warnings++;
+#endif
+ break;
+
+ default:
+ fprintf(stderr, "\
+Top version %s\n\
+Usage: %s [-ISbinqu] [-d x] [-s x] [-o field] [-U username] [number]\n",
+ version_string(), myname);
+ exit(1);
+ }
+ }
+
+ /* get count of top processes to display (if any) */
+ if (optind < ac)
+ {
+ if ((topn = atoiwi(av[optind])) == Invalid)
+ {
+ fprintf(stderr,
+ "%s: warning: process display count should be non-negative -- using default\n",
+ myname);
+ warnings++;
+ }
+#if Default_TOPN == Infinity
+ else
+ {
+ topn_specified = Yes;
+ }
+#endif
+ }
+
+ /* tricky: remember old value of preset_argc & set preset_argc = 0 */
+ i = preset_argc;
+ preset_argc = 0;
+
+ /* repeat only if we really did the preset arguments */
+ } while (i != 0);
+
+ /* set constants for username/uid display correctly */
+ if (!do_unames)
+ {
+ uname_field = " UID ";
+ get_userid = itoa7;
+ }
+
+ /* initialize the kernel memory interface */
+ if (machine_init(&statics) == -1)
+ {
+ exit(1);
+ }
+
+#ifdef ORDER
+ /* determine sorting order index, if necessary */
+ if (order_name != NULL)
+ {
+ if ((order_index = string_index(order_name, statics.order_names)) == -1)
+ {
+ char **pp;
+
+ fprintf(stderr, "%s: '%s' is not a recognized sorting order.\n",
+ myname, order_name);
+ fprintf(stderr, "\tTry one of these:");
+ pp = statics.order_names;
+ while (*pp != NULL)
+ {
+ fprintf(stderr, " %s", *pp++);
+ }
+ fputc('\n', stderr);
+ exit(1);
+ }
+ }
+#endif
+
+#ifdef no_initialization_needed
+ /* initialize the hashing stuff */
+ if (do_unames)
+ {
+ init_hash();
+ }
+#endif
+
+ /* initialize termcap */
+ init_termcap(interactive);
+
+ /* get the string to use for the process area header */
+ header_text = format_header(uname_field);
+
+ /* initialize display interface */
+ if ((max_topn = display_init(&statics)) == -1)
+ {
+ fprintf(stderr, "%s: can't allocate sufficient memory\n", myname);
+ exit(4);
+ }
+
+ /* print warning if user requested more processes than we can display */
+ if (topn > max_topn)
+ {
+ fprintf(stderr,
+ "%s: warning: this terminal can only display %d processes.\n",
+ myname, max_topn);
+ warnings++;
+ }
+
+ /* adjust for topn == Infinity */
+ if (topn == Infinity)
+ {
+ /*
+ * For smart terminals, infinity really means everything that can
+ * be displayed, or Largest.
+ * On dumb terminals, infinity means every process in the system!
+ * We only really want to do that if it was explicitly specified.
+ * This is always the case when "Default_TOPN != Infinity". But if
+ * topn wasn't explicitly specified and we are on a dumb terminal
+ * and the default is Infinity, then (and only then) we use
+ * "Nominal_TOPN" instead.
+ */
+#if Default_TOPN == Infinity
+ topn = smart_terminal ? Largest :
+ (topn_specified ? Largest : Nominal_TOPN);
+#else
+ topn = Largest;
+#endif
+ }
+
+ /* set header display accordingly */
+ display_header(topn > 0);
+
+ /* determine interactive state */
+ if (interactive == Maybe)
+ {
+ interactive = smart_terminal;
+ }
+
+ /* if # of displays not specified, fill it in */
+ if (displays == 0)
+ {
+ displays = smart_terminal ? Infinity : 1;
+ }
+
+ /* hold interrupt signals while setting up the screen and the handlers */
+#ifdef SIGHOLD
+ sighold(SIGINT);
+ sighold(SIGQUIT);
+ sighold(SIGTSTP);
+#else
+ old_sigmask = sigblock(Smask(SIGINT) | Smask(SIGQUIT) | Smask(SIGTSTP));
+#endif
+ init_screen();
+ (void) signal(SIGINT, leave);
+ (void) signal(SIGQUIT, leave);
+ (void) signal(SIGTSTP, tstop);
+#ifdef SIGWINCH
+ (void) signal(SIGWINCH, winch);
+#endif
+#ifdef SIGRELSE
+ sigrelse(SIGINT);
+ sigrelse(SIGQUIT);
+ sigrelse(SIGTSTP);
+#else
+ (void) sigsetmask(old_sigmask);
+#endif
+ if (warnings)
+ {
+ fputs("....", stderr);
+ fflush(stderr); /* why must I do this? */
+ sleep((unsigned)(3 * warnings));
+ fputc('\n', stderr);
+ }
+
+ /* setup the jump buffer for stops */
+ if (setjmp(jmp_int) != 0)
+ {
+ /* control ends up here after an interrupt */
+ reset_display();
+ }
+
+ /*
+ * main loop -- repeat while display count is positive or while it
+ * indicates infinity (by being -1)
+ */
+
+ while ((displays == -1) || (displays-- > 0))
+ {
+ /* get the current stats */
+ get_system_info(&system_info);
+
+ /* get the current set of processes */
+ processes =
+ get_process_info(&system_info,
+ &ps,
+#ifdef ORDER
+ proc_compares[order_index]);
+#else
+ proc_compare);
+#endif
+
+ /* display the load averages */
+ (*d_loadave)(system_info.last_pid,
+ system_info.load_avg);
+
+ /* display the current time */
+ /* this method of getting the time SHOULD be fairly portable */
+ time(&curr_time);
+ i_timeofday(&curr_time);
+
+ /* display process state breakdown */
+ (*d_procstates)(system_info.p_total,
+ system_info.procstates);
+
+ /* display the cpu state percentage breakdown */
+ if (dostates) /* but not the first time */
+ {
+ (*d_cpustates)(system_info.cpustates);
+ }
+ else
+ {
+ /* we'll do it next time */
+ if (smart_terminal)
+ {
+ z_cpustates();
+ }
+ else
+ {
+ putchar('\n');
+ }
+ dostates = Yes;
+ }
+
+ /* display memory stats */
+ (*d_memory)(system_info.memory);
+
+ /* handle message area */
+ (*d_message)();
+
+ /* update the header area */
+ (*d_header)(header_text);
+
+ if (topn > 0)
+ {
+ /* determine number of processes to actually display */
+ /* this number will be the smallest of: active processes,
+ number user requested, number current screen accomodates */
+ active_procs = system_info.p_active;
+ if (active_procs > topn)
+ {
+ active_procs = topn;
+ }
+ if (active_procs > max_topn)
+ {
+ active_procs = max_topn;
+ }
+
+ /* now show the top "n" processes. */
+ for (i = 0; i < active_procs; i++)
+ {
+ (*d_process)(i, format_next_process(processes, get_userid));
+ }
+ }
+ else
+ {
+ i = 0;
+ }
+
+ /* do end-screen processing */
+ u_endscreen(i);
+
+ /* now, flush the output buffer */
+ fflush(stdout);
+
+ /* only do the rest if we have more displays to show */
+ if (displays)
+ {
+ /* switch out for new display on smart terminals */
+ if (smart_terminal)
+ {
+ if (overstrike)
+ {
+ reset_display();
+ }
+ else
+ {
+ d_loadave = u_loadave;
+ d_procstates = u_procstates;
+ d_cpustates = u_cpustates;
+ d_memory = u_memory;
+ d_message = u_message;
+ d_header = u_header;
+ d_process = u_process;
+ }
+ }
+
+ no_command = Yes;
+ if (!interactive)
+ {
+ /* set up alarm */
+ (void) signal(SIGALRM, onalrm);
+ (void) alarm((unsigned)delay);
+
+ /* wait for the rest of it .... */
+ pause();
+ }
+ else while (no_command)
+ {
+ /* assume valid command unless told otherwise */
+ no_command = No;
+
+ /* set up arguments for select with timeout */
+ FD_ZERO(&readfds);
+ FD_SET(1, &readfds); /* for standard input */
+ timeout.tv_sec = delay;
+ timeout.tv_usec = 0;
+
+ /* wait for either input or the end of the delay period */
+ if (select(32, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timeout) > 0)
+ {
+ int newval;
+ char *errmsg;
+
+ /* something to read -- clear the message area first */
+ clear_message();
+
+ /* now read it and convert to command strchr */
+ /* (use "change" as a temporary to hold strchr) */
+ (void) read(0, &ch, 1);
+ if ((iptr = strchr(command_chars, ch)) == NULL)
+ {
+ /* illegal command */
+ new_message(MT_standout, " Command not understood");
+ putchar('\r');
+ no_command = Yes;
+ }
+ else
+ {
+ change = iptr - command_chars;
+ if (overstrike && change > CMD_OSLIMIT)
+ {
+ /* error */
+ new_message(MT_standout,
+ " Command cannot be handled by this terminal");
+ putchar('\r');
+ no_command = Yes;
+ }
+ else switch(change)
+ {
+ case CMD_redraw: /* redraw screen */
+ reset_display();
+ break;
+
+ case CMD_update: /* merely update display */
+ /* is the load average high? */
+ if (system_info.load_avg[0] > LoadMax)
+ {
+ /* yes, go home for visual feedback */
+ go_home();
+ fflush(stdout);
+ }
+ break;
+
+ case CMD_quit: /* quit */
+ quit(0);
+ /*NOTREACHED*/
+ break;
+
+ case CMD_help1: /* help */
+ case CMD_help2:
+ reset_display();
+ clear();
+ show_help();
+ standout("Hit any key to continue: ");
+ fflush(stdout);
+ (void) read(0, &ch, 1);
+ break;
+
+ case CMD_errors: /* show errors */
+ if (error_count() == 0)
+ {
+ new_message(MT_standout,
+ " Currently no errors to report.");
+ putchar('\r');
+ no_command = Yes;
+ }
+ else
+ {
+ reset_display();
+ clear();
+ show_errors();
+ standout("Hit any key to continue: ");
+ fflush(stdout);
+ (void) read(0, &ch, 1);
+ }
+ break;
+
+ case CMD_number1: /* new number */
+ case CMD_number2:
+ new_message(MT_standout,
+ "Number of processes to show: ");
+ newval = readline(tempbuf1, 8, Yes);
+ if (newval > -1)
+ {
+ if (newval > max_topn)
+ {
+ new_message(MT_standout | MT_delayed,
+ " This terminal can only display %d processes.",
+ max_topn);
+ putchar('\r');
+ }
+
+ if (newval == 0)
+ {
+ /* inhibit the header */
+ display_header(No);
+ }
+ else if (newval > topn && topn == 0)
+ {
+ /* redraw the header */
+ display_header(Yes);
+ d_header = i_header;
+ }
+ topn = newval;
+ }
+ break;
+
+ case CMD_delay: /* new seconds delay */
+ new_message(MT_standout, "Seconds to delay: ");
+ if ((i = readline(tempbuf1, 8, Yes)) > -1)
+ {
+ delay = i;
+ }
+ clear_message();
+ break;
+
+ case CMD_displays: /* change display count */
+ new_message(MT_standout,
+ "Displays to show (currently %s): ",
+ displays == -1 ? "infinite" :
+ itoa(displays));
+ if ((i = readline(tempbuf1, 10, Yes)) > 0)
+ {
+ displays = i;
+ }
+ else if (i == 0)
+ {
+ quit(0);
+ }
+ clear_message();
+ break;
+
+ case CMD_kill: /* kill program */
+ new_message(0, "kill ");
+ if (readline(tempbuf2, sizeof(tempbuf2), No) > 0)
+ {
+ if ((errmsg = kill_procs(tempbuf2)) != NULL)
+ {
+ new_message(MT_standout, errmsg);
+ putchar('\r');
+ no_command = Yes;
+ }
+ }
+ else
+ {
+ clear_message();
+ }
+ break;
+
+ case CMD_renice: /* renice program */
+ new_message(0, "renice ");
+ if (readline(tempbuf2, sizeof(tempbuf2), No) > 0)
+ {
+ if ((errmsg = renice_procs(tempbuf2)) != NULL)
+ {
+ new_message(MT_standout, errmsg);
+ putchar('\r');
+ no_command = Yes;
+ }
+ }
+ else
+ {
+ clear_message();
+ }
+ break;
+
+ case CMD_idletog:
+ case CMD_idletog2:
+ ps.idle = !ps.idle;
+ new_message(MT_standout | MT_delayed,
+ " %sisplaying idle processes.",
+ ps.idle ? "D" : "Not d");
+ putchar('\r');
+ break;
+
+ case CMD_user:
+ new_message(MT_standout,
+ "Username to show: ");
+ if (readline(tempbuf2, sizeof(tempbuf2), No) > 0)
+ {
+ if (tempbuf2[0] == '+' &&
+ tempbuf2[1] == '\0')
+ {
+ ps.uid = -1;
+ }
+ else if ((i = userid(tempbuf2)) == -1)
+ {
+ new_message(MT_standout,
+ " %s: unknown user", tempbuf2);
+ no_command = Yes;
+ }
+ else
+ {
+ ps.uid = i;
+ }
+ putchar('\r');
+ }
+ else
+ {
+ clear_message();
+ }
+ break;
+
+#ifdef ORDER
+ case CMD_order:
+ new_message(MT_standout,
+ "Order to sort: ");
+ if (readline(tempbuf2, sizeof(tempbuf2), No) > 0)
+ {
+ if ((i = string_index(tempbuf2, statics.order_names)) == -1)
+ {
+ new_message(MT_standout,
+ " %s: unrecognized sorting order", tempbuf2);
+ no_command = Yes;
+ }
+ else
+ {
+ order_index = i;
+ }
+ putchar('\r');
+ }
+ else
+ {
+ clear_message();
+ }
+ break;
+#endif
+
+ default:
+ new_message(MT_standout, " BAD CASE IN SWITCH!");
+ putchar('\r');
+ }
+ }
+
+ /* flush out stuff that may have been written */
+ fflush(stdout);
+ }
+ }
+ }
+ }
+
+ quit(0);
+ /*NOTREACHED*/
+}
+
+/*
+ * reset_display() - reset all the display routine pointers so that entire
+ * screen will get redrawn.
+ */
+
+reset_display()
+
+{
+ d_loadave = i_loadave;
+ d_procstates = i_procstates;
+ d_cpustates = i_cpustates;
+ d_memory = i_memory;
+ d_message = i_message;
+ d_header = i_header;
+ d_process = i_process;
+}
+
+/*
+ * signal handlers
+ */
+
+sigret_t leave() /* exit under normal conditions -- INT handler */
+
+{
+ end_screen();
+ exit(0);
+}
+
+sigret_t tstop(i) /* SIGTSTP handler */
+
+int i;
+
+{
+ /* move to the lower left */
+ end_screen();
+ fflush(stdout);
+
+ /* default the signal handler action */
+ (void) signal(SIGTSTP, SIG_DFL);
+
+ /* unblock the signal and send ourselves one */
+#ifdef SIGRELSE
+ sigrelse(SIGTSTP);
+#else
+ (void) sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
+#endif
+ (void) kill(0, SIGTSTP);
+
+ /* reset the signal handler */
+ (void) signal(SIGTSTP, tstop);
+
+ /* reinit screen */
+ reinit_screen();
+
+ /* jump to appropriate place */
+ longjmp(jmp_int, 1);
+
+ /*NOTREACHED*/
+}
+
+#ifdef SIGWINCH
+sigret_t winch(i) /* SIGWINCH handler */
+
+int i;
+
+{
+ /* reascertain the screen dimensions */
+ get_screensize();
+
+ /* tell display to resize */
+ max_topn = display_resize();
+
+ /* reset the signal handler */
+ (void) signal(SIGWINCH, winch);
+
+ /* jump to appropriate place */
+ longjmp(jmp_int, 1);
+}
+#endif
+
+void quit(status) /* exit under duress */
+
+int status;
+
+{
+ end_screen();
+ exit(status);
+ /*NOTREACHED*/
+}
+
+sigret_t onalrm() /* SIGALRM handler */
+
+{
+ /* this is only used in batch mode to break out of the pause() */
+ /* return; */
+}
+
diff --git a/usr.bin/top/top.h b/usr.bin/top/top.h
new file mode 100644
index 00000000000..ad65c41aa24
--- /dev/null
+++ b/usr.bin/top/top.h
@@ -0,0 +1,38 @@
+/* $OpenBSD: top.h,v 1.1 1997/08/14 14:00:26 downsj Exp $ */
+
+/*
+ * Top - a top users display for Berkeley Unix
+ *
+ * General (global) definitions
+ */
+
+/* Current major version number */
+#define VERSION 3
+
+/* Number of lines of header information on the standard screen */
+#define Header_lines 6
+
+/* Maximum number of columns allowed for display */
+#define MAX_COLS 128
+
+/* Log base 2 of 1024 is 10 (2^10 == 1024) */
+#define LOG1024 10
+
+char *itoa();
+char *itoa7();
+
+char *version_string();
+
+/* Special atoi routine returns either a non-negative number or one of: */
+#define Infinity -1
+#define Invalid -2
+
+/* maximum number we can have */
+#define Largest 0x7fffffff
+
+/*
+ * The entire display is based on these next numbers being defined as is.
+ */
+
+#define NUM_AVERAGES 3
+
diff --git a/usr.bin/top/top.local.h b/usr.bin/top/top.local.h
new file mode 100644
index 00000000000..618e7b4c2f8
--- /dev/null
+++ b/usr.bin/top/top.local.h
@@ -0,0 +1,70 @@
+/* $OpenBSD: top.local.h,v 1.1 1997/08/14 14:00:26 downsj Exp $ */
+
+/*
+ * Top - a top users display for Berkeley Unix
+ *
+ * Definitions for things that might vary between installations.
+ */
+
+/*
+ * The space command forces an immediate update. Sometimes, on loaded
+ * systems, this update will take a significant period of time (because all
+ * the output is buffered). So, if the short-term load average is above
+ * "LoadMax", then top will put the cursor home immediately after the space
+ * is pressed before the next update is attempted. This serves as a visual
+ * acknowledgement of the command. On Suns, "LoadMax" will get multiplied by
+ * "FSCALE" before being compared to avenrun[0]. Therefore, "LoadMax"
+ * should always be specified as a floating point number.
+ */
+#ifndef LoadMax
+#define LoadMax 5.0
+#endif
+
+/*
+ * "Table_size" defines the size of the hash tables used to map uid to
+ * username. The number of users in /etc/passwd CANNOT be greater than
+ * this number. If the error message "table overflow: too many users"
+ * is printed by top, then "Table_size" needs to be increased. Things will
+ * work best if the number is a prime number that is about twice the number
+ * of lines in /etc/passwd.
+ */
+#ifndef Table_size
+#define Table_size 503
+#endif
+
+/*
+ * "Nominal_TOPN" is used as the default TOPN when Default_TOPN is Infinity
+ * and the output is a dumb terminal. If we didn't do this, then
+ * installations who use a default TOPN of Infinity will get every
+ * process in the system when running top on a dumb terminal (or redirected
+ * to a file). Note that Nominal_TOPN is a default: it can still be
+ * overridden on the command line, even with the value "infinity".
+ */
+#ifndef Nominal_TOPN
+#define Nominal_TOPN 18
+#endif
+
+#ifndef Default_TOPN
+#define Default_TOPN 15
+#endif
+
+#ifndef Default_DELAY
+#define Default_DELAY 5
+#endif
+
+/*
+ * If the local system's getpwnam interface uses random access to retrieve
+ * a record (i.e.: 4.3 systems, Sun "yellow pages"), then defining
+ * RANDOM_PW will take advantage of that fact. If RANDOM_PW is defined,
+ * then getpwnam is used and the result is cached. If not, then getpwent
+ * is used to read and cache the password entries sequentially until the
+ * desired one is found.
+ *
+ * We initially set RANDOM_PW to something which is controllable by the
+ * Configure script. Then if its value is 0, we undef it.
+ */
+
+#define RANDOM_PW 1
+#if RANDOM_PW == 0
+#undef RANDOM_PW
+#endif
diff --git a/usr.bin/top/username.c b/usr.bin/top/username.c
new file mode 100644
index 00000000000..e7e88c72f57
--- /dev/null
+++ b/usr.bin/top/username.c
@@ -0,0 +1,187 @@
+/* $OpenBSD: username.c,v 1.1 1997/08/14 14:00:27 downsj Exp $ */
+
+/*
+ * Top users/processes display for Unix
+ * Version 3
+ *
+ * This program may be freely redistributed,
+ * but this entire comment MUST remain intact.
+ *
+ * Copyright (c) 1984, 1989, William LeFebvre, Rice University
+ * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
+ */
+
+/*
+ * Username translation code for top.
+ *
+ * These routines handle uid to username mapping.
+ * They use a hashing table scheme to reduce reading overhead.
+ * For the time being, these are very straightforward hashing routines.
+ * Maybe someday I'll put in something better. But with the advent of
+ * "random access" password files, it might not be worth the effort.
+ *
+ * Changes to these have been provided by John Gilmore (gnu@toad.com).
+ *
+ * The hash has been simplified in this release, to avoid the
+ * table overflow problems of previous releases. If the value
+ * at the initial hash location is not right, it is replaced
+ * by the right value. Collisions will cause us to call getpw*
+ * but hey, this is a cache, not the Library of Congress.
+ * This makes the table size independent of the passwd file size.
+ */
+
+#include <stdio.h>
+#include <pwd.h>
+
+#include "top.local.h"
+#include "utils.h"
+
+struct hash_el {
+ int uid;
+ char name[9];
+};
+
+#define is_empty_hash(x) (hash_table[x].name[0] == 0)
+
+/* simple minded hashing function */
+/* Uid "nobody" is -2 results in hashit(-2) = -2 which is out of bounds for
+ the hash_table. Applied abs() function to fix. 2/16/96 tpugh
+*/
+#define hashit(i) (abs(i) % Table_size)
+
+/* K&R requires that statically declared tables be initialized to zero. */
+/* We depend on that for hash_table and YOUR compiler had BETTER do it! */
+struct hash_el hash_table[Table_size];
+
+init_hash()
+
+{
+ /*
+ * There used to be some steps we had to take to initialize things.
+ * We don't need to do that anymore, but we will leave this stub in
+ * just in case future changes require initialization steps.
+ */
+}
+
+char *username(uid)
+
+register int uid;
+
+{
+ register int hashindex;
+
+ hashindex = hashit(uid);
+ if (is_empty_hash(hashindex) || (hash_table[hashindex].uid != uid))
+ {
+ /* not here or not right -- get it out of passwd */
+ hashindex = get_user(uid);
+ }
+ return(hash_table[hashindex].name);
+}
+
+int userid(username)
+
+char *username;
+
+{
+ struct passwd *pwd;
+
+ /* Eventually we want this to enter everything in the hash table,
+ but for now we just do it simply and remember just the result.
+ */
+
+ if ((pwd = getpwnam(username)) == NULL)
+ {
+ return(-1);
+ }
+
+ /* enter the result in the hash table */
+ enter_user(pwd->pw_uid, username, 1);
+
+ /* return our result */
+ return(pwd->pw_uid);
+}
+
+int enter_user(uid, name, wecare)
+
+register int uid;
+register char *name;
+int wecare; /* 1 = enter it always, 0 = nice to have */
+
+{
+ register int hashindex;
+
+#ifdef DEBUG
+ fprintf(stderr, "enter_hash(%d, %s, %d)\n", uid, name, wecare);
+#endif
+
+ hashindex = hashit(uid);
+
+ if (!is_empty_hash(hashindex))
+ {
+ if (!wecare)
+ return 0; /* Don't clobber a slot for trash */
+ if (hash_table[hashindex].uid == uid)
+ return(hashindex); /* Fortuitous find */
+ }
+
+ /* empty or wrong slot -- fill it with new value */
+ hash_table[hashindex].uid = uid;
+ (void) strncpy(hash_table[hashindex].name, name, 8);
+ return(hashindex);
+}
+
+/*
+ * Get a userid->name mapping from the system.
+ * If the passwd database is hashed (#define RANDOM_PW), we
+ * just handle this uid. Otherwise we scan the passwd file
+ * and cache any entries we pass over while looking.
+ */
+
+int get_user(uid)
+
+register int uid;
+
+{
+ struct passwd *pwd;
+
+#ifdef RANDOM_PW
+ /* no performance penalty for using getpwuid makes it easy */
+ if ((pwd = getpwuid(uid)) != NULL)
+ {
+ return(enter_user(pwd->pw_uid, pwd->pw_name, 1));
+ }
+#else
+
+ int from_start = 0;
+
+ /*
+ * If we just called getpwuid each time, things would be very slow
+ * since that just iterates through the passwd file each time. So,
+ * we walk through the file instead (using getpwent) and cache each
+ * entry as we go. Once the right record is found, we cache it and
+ * return immediately. The next time we come in, getpwent will get
+ * the next record. In theory, we never have to read the passwd file
+ * a second time (because we cache everything we read). But in
+ * practice, the cache may not be large enough, so if we don't find
+ * it the first time we have to scan the file a second time. This
+ * is not very efficient, but it will do for now.
+ */
+
+ while (from_start++ < 2)
+ {
+ while ((pwd = getpwent()) != NULL)
+ {
+ if (pwd->pw_uid == uid)
+ {
+ return(enter_user(pwd->pw_uid, pwd->pw_name, 1));
+ }
+ (void) enter_user(pwd->pw_uid, pwd->pw_name, 0);
+ }
+ /* try again */
+ setpwent();
+ }
+#endif
+ /* if we can't find the name at all, then use the uid as the name */
+ return(enter_user(uid, itoa7(uid), 1));
+}
diff --git a/usr.bin/top/utils.c b/usr.bin/top/utils.c
new file mode 100644
index 00000000000..14206747614
--- /dev/null
+++ b/usr.bin/top/utils.c
@@ -0,0 +1,457 @@
+/* $OpenBSD: utils.c,v 1.1 1997/08/14 14:00:27 downsj Exp $ */
+
+/*
+ * Top users/processes display for Unix
+ * Version 3
+ *
+ * This program may be freely redistributed,
+ * but this entire comment MUST remain intact.
+ *
+ * Copyright (c) 1984, 1989, William LeFebvre, Rice University
+ * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
+ */
+
+/*
+ * This file contains various handy utilities used by top.
+ */
+
+#include "top.h"
+#include "os.h"
+
+int atoiwi(str)
+
+char *str;
+
+{
+ register int len;
+
+ len = strlen(str);
+ if (len != 0)
+ {
+ if (strncmp(str, "infinity", len) == 0 ||
+ strncmp(str, "all", len) == 0 ||
+ strncmp(str, "maximum", len) == 0)
+ {
+ return(Infinity);
+ }
+ else if (str[0] == '-')
+ {
+ return(Invalid);
+ }
+ else
+ {
+ return(atoi(str));
+ }
+ }
+ return(0);
+}
+
+/*
+ * itoa - convert integer (decimal) to ascii string for positive numbers
+ * only (we don't bother with negative numbers since we know we
+ * don't use them).
+ */
+
+ /*
+ * How do we know that 16 will suffice?
+ * Because the biggest number that we will
+ * ever convert will be 2^32-1, which is 10
+ * digits.
+ */
+
+char *itoa(val)
+
+register int val;
+
+{
+ register char *ptr;
+ static char buffer[16]; /* result is built here */
+ /* 16 is sufficient since the largest number
+ we will ever convert will be 2^32-1,
+ which is 10 digits. */
+
+ ptr = buffer + sizeof(buffer);
+ *--ptr = '\0';
+ if (val == 0)
+ {
+ *--ptr = '0';
+ }
+ else while (val != 0)
+ {
+ *--ptr = (val % 10) + '0';
+ val /= 10;
+ }
+ return(ptr);
+}
+
+/*
+ * itoa7(val) - like itoa, except the number is right justified in a 7
+ * character field. This code is a duplication of itoa instead of
+ * a front end to a more general routine for efficiency.
+ */
+
+char *itoa7(val)
+
+register int val;
+
+{
+ register char *ptr;
+ static char buffer[16]; /* result is built here */
+ /* 16 is sufficient since the largest number
+ we will ever convert will be 2^32-1,
+ which is 10 digits. */
+
+ ptr = buffer + sizeof(buffer);
+ *--ptr = '\0';
+ if (val == 0)
+ {
+ *--ptr = '0';
+ }
+ else while (val != 0)
+ {
+ *--ptr = (val % 10) + '0';
+ val /= 10;
+ }
+ while (ptr > buffer + sizeof(buffer) - 7)
+ {
+ *--ptr = ' ';
+ }
+ return(ptr);
+}
+
+/*
+ * digits(val) - return number of decimal digits in val. Only works for
+ * positive numbers. If val <= 0 then digits(val) == 0.
+ */
+
+int digits(val)
+
+int val;
+
+{
+ register int cnt = 0;
+
+ while (val > 0)
+ {
+ cnt++;
+ val /= 10;
+ }
+ return(cnt);
+}
+
+/*
+ * strecpy(to, from) - copy string "from" into "to" and return a pointer
+ * to the END of the string "to".
+ */
+
+char *strecpy(to, from)
+
+register char *to;
+register char *from;
+
+{
+ while ((*to++ = *from++) != '\0');
+ return(--to);
+}
+
+/*
+ * string_index(string, array) - find string in array and return index
+ */
+
+int string_index(string, array)
+
+char *string;
+char **array;
+
+{
+ register int i = 0;
+
+ while (*array != NULL)
+ {
+ if (strcmp(string, *array) == 0)
+ {
+ return(i);
+ }
+ array++;
+ i++;
+ }
+ return(-1);
+}
+
+/*
+ * argparse(line, cntp) - parse arguments in string "line", separating them
+ * out into an argv-like array, and setting *cntp to the number of
+ * arguments encountered. This is a simple parser that doesn't understand
+ * squat about quotes.
+ */
+
+char **argparse(line, cntp)
+
+char *line;
+int *cntp;
+
+{
+ register char *from;
+ register char *to;
+ register int cnt;
+ register int ch;
+ int length;
+ int lastch;
+ register char **argv;
+ char **argarray;
+ char *args;
+
+ /* unfortunately, the only real way to do this is to go thru the
+ input string twice. */
+
+ /* step thru the string counting the white space sections */
+ from = line;
+ lastch = cnt = length = 0;
+ while ((ch = *from++) != '\0')
+ {
+ length++;
+ if (ch == ' ' && lastch != ' ')
+ {
+ cnt++;
+ }
+ lastch = ch;
+ }
+
+ /* add three to the count: one for the initial "dummy" argument,
+ one for the last argument and one for NULL */
+ cnt += 3;
+
+ /* allocate a char * array to hold the pointers */
+ argarray = (char **)malloc(cnt * sizeof(char *));
+
+ /* allocate another array to hold the strings themselves */
+ args = (char *)malloc(length+2);
+
+ /* initialization for main loop */
+ from = line;
+ to = args;
+ argv = argarray;
+ lastch = '\0';
+
+ /* create a dummy argument to keep getopt happy */
+ *argv++ = to;
+ *to++ = '\0';
+ cnt = 2;
+
+ /* now build argv while copying characters */
+ *argv++ = to;
+ while ((ch = *from++) != '\0')
+ {
+ if (ch != ' ')
+ {
+ if (lastch == ' ')
+ {
+ *to++ = '\0';
+ *argv++ = to;
+ cnt++;
+ }
+ *to++ = ch;
+ }
+ lastch = ch;
+ }
+ *to++ = '\0';
+
+ /* set cntp and return the allocated array */
+ *cntp = cnt;
+ return(argarray);
+}
+
+/*
+ * percentages(cnt, out, new, old, diffs) - calculate percentage change
+ * between array "old" and "new", putting the percentages i "out".
+ * "cnt" is size of each array and "diffs" is used for scratch space.
+ * The array "old" is updated on each call.
+ * The routine assumes modulo arithmetic. This function is especially
+ * useful on BSD mchines for calculating cpu state percentages.
+ */
+
+long percentages(cnt, out, new, old, diffs)
+
+int cnt;
+int *out;
+register long *new;
+register long *old;
+long *diffs;
+
+{
+ register int i;
+ register long change;
+ register long total_change;
+ register long *dp;
+ long half_total;
+
+ /* initialization */
+ total_change = 0;
+ dp = diffs;
+
+ /* calculate changes for each state and the overall change */
+ for (i = 0; i < cnt; i++)
+ {
+ if ((change = *new - *old) < 0)
+ {
+ /* this only happens when the counter wraps */
+ change = (int)
+ ((unsigned long)*new-(unsigned long)*old);
+ }
+ total_change += (*dp++ = change);
+ *old++ = *new++;
+ }
+
+ /* avoid divide by zero potential */
+ if (total_change == 0)
+ {
+ total_change = 1;
+ }
+
+ /* calculate percentages based on overall change, rounding up */
+ half_total = total_change / 2l;
+ for (i = 0; i < cnt; i++)
+ {
+ *out++ = (int)((*diffs++ * 1000 + half_total) / total_change);
+ }
+
+ /* return the total in case the caller wants to use it */
+ return(total_change);
+}
+
+/*
+ * errmsg(errnum) - return an error message string appropriate to the
+ * error number "errnum". This is a substitute for the System V
+ * function "strerror" with one important difference: the string
+ * returned by this function does NOT end in a newline!
+ * N.B.: there appears to be no reliable way to determine if
+ * "strerror" exists at compile time, so I make do by providing
+ * something of similar functionality.
+ */
+
+/* externs referenced by errmsg */
+
+extern char *sys_errlist[];
+extern int sys_nerr;
+
+char *errmsg(errnum)
+
+int errnum;
+
+{
+ if (errnum > 0 && errnum < sys_nerr)
+ {
+ return(sys_errlist[errnum]);
+ }
+ return("No error");
+}
+
+/* format_time(seconds) - format number of seconds into a suitable
+ * display that will fit within 6 characters. Note that this
+ * routine builds its string in a static area. If it needs
+ * to be called more than once without overwriting previous data,
+ * then we will need to adopt a technique similar to the
+ * one used for format_k.
+ */
+
+/* Explanation:
+ We want to keep the output within 6 characters. For low values we use
+ the format mm:ss. For values that exceed 999:59, we switch to a format
+ that displays hours and fractions: hhh.tH. For values that exceed
+ 999.9, we use hhhh.t and drop the "H" designator. For values that
+ exceed 9999.9, we use "???".
+ */
+
+char *format_time(seconds)
+
+long seconds;
+
+{
+ register int value;
+ register int digit;
+ register char *ptr;
+ static char result[10];
+
+ /* sanity protection */
+ if (seconds < 0 || seconds > (99999l * 360l))
+ {
+ strcpy(result, " ???");
+ }
+ else if (seconds >= (1000l * 60l))
+ {
+ /* alternate (slow) method displaying hours and tenths */
+ snprintf(result, sizeof(result), "%5.1fH",
+ (double)seconds / (double)(60l * 60l));
+
+ /* It is possible that the sprintf took more than 6 characters.
+ If so, then the "H" appears as result[6]. If not, then there
+ is a \0 in result[6]. Either way, it is safe to step on.
+ */
+ result[6] = '\0';
+ }
+ else
+ {
+ /* standard method produces MMM:SS */
+ /* we avoid printf as must as possible to make this quick */
+ snprintf(result, sizeof(result), "%3d:%02d", seconds / 60l,
+ seconds % 60l);
+ }
+ return(result);
+}
+
+/*
+ * format_k(amt) - format a kilobyte memory value, returning a string
+ * suitable for display. Returns a pointer to a static
+ * area that changes each call. "amt" is converted to a
+ * string with a trailing "K". If "amt" is 10000 or greater,
+ * then it is formatted as megabytes (rounded) with a
+ * trailing "M".
+ */
+
+/*
+ * Compromise time. We need to return a string, but we don't want the
+ * caller to have to worry about freeing a dynamically allocated string.
+ * Unfortunately, we can't just return a pointer to a static area as one
+ * of the common uses of this function is in a large call to sprintf where
+ * it might get invoked several times. Our compromise is to maintain an
+ * array of strings and cycle thru them with each invocation. We make the
+ * array large enough to handle the above mentioned case. The constant
+ * NUM_STRINGS defines the number of strings in this array: we can tolerate
+ * up to NUM_STRINGS calls before we start overwriting old information.
+ * Keeping NUM_STRINGS a power of two will allow an intelligent optimizer
+ * to convert the modulo operation into something quicker. What a hack!
+ */
+
+#define NUM_STRINGS 8
+
+char *format_k(amt)
+
+int amt;
+
+{
+ static char retarray[NUM_STRINGS][16];
+ static int index = 0;
+ register char *p;
+ register char *ret;
+ register char tag = 'K';
+
+ p = ret = retarray[index];
+ index = (index + 1) % NUM_STRINGS;
+
+ if (amt >= 10000)
+ {
+ amt = (amt + 512) / 1024;
+ tag = 'M';
+ if (amt >= 10000)
+ {
+ amt = (amt + 512) / 1024;
+ tag = 'G';
+ }
+ }
+
+ p = strecpy(p, itoa(amt));
+ *p++ = tag;
+ *p = '\0';
+
+ return(ret);
+}
diff --git a/usr.bin/top/utils.h b/usr.bin/top/utils.h
new file mode 100644
index 00000000000..2b12c4b6ab2
--- /dev/null
+++ b/usr.bin/top/utils.h
@@ -0,0 +1,25 @@
+/* $OpenBSD: utils.h,v 1.1 1997/08/14 14:00:28 downsj Exp $ */
+
+/*
+ * Top users/processes display for Unix
+ * Version 3
+ *
+ * This program may be freely redistributed,
+ * but this entire comment MUST remain intact.
+ *
+ * Copyright (c) 1984, 1989, William LeFebvre, Rice University
+ * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
+ */
+
+/* prototypes for functions found in utils.c */
+
+int atoiwi();
+char *itoa();
+char *itoa7();
+int digits();
+char *strecpy();
+char **argparse();
+long percentages();
+char *errmsg();
+char *format_time();
+char *format_k();
diff --git a/usr.bin/top/version.c b/usr.bin/top/version.c
new file mode 100644
index 00000000000..281cb59bf99
--- /dev/null
+++ b/usr.bin/top/version.c
@@ -0,0 +1,27 @@
+/* $OpenBSD: version.c,v 1.1 1997/08/14 14:00:28 downsj Exp $ */
+
+/*
+ * Top users/processes display for Unix
+ * Version 3
+ *
+ * This program may be freely redistributed,
+ * but this entire comment MUST remain intact.
+ *
+ * Copyright (c) 1984, 1989, William LeFebvre, Rice University
+ * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
+ */
+
+#include "top.h"
+#include "patchlevel.h"
+
+static char version[16];
+
+char *version_string()
+
+{
+ snprintf(version, sizeof(version), "%d.%d", VERSION, PATCHLEVEL);
+#ifdef BETA
+ strcat(version, BETA);
+#endif
+ return(version);
+}