We've been maintaining top(1) for a long time, and the upstream hasn't existed/been used in similarly as long. Make it clear that we own top(1) Tested with 'make universe'. Everything passed except MIPS which failed for unrelated reasons. Install also tested for amd64. Reviewed by: sbruno No objections: imp, mmacy Differential Revision: https://reviews.freebsd.org/D15387
543 lines
11 KiB
C
543 lines
11 KiB
C
/*
|
|
* 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
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
/*
|
|
* 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 <sys/time.h>
|
|
#include <sys/resource.h>
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
|
|
#include "commands.h"
|
|
#include "sigdesc.h" /* generated automatically */
|
|
#include "top.h"
|
|
#include "boolean.h"
|
|
#include "utils.h"
|
|
#include "machine.h"
|
|
|
|
extern int errno;
|
|
|
|
extern char *copyright;
|
|
|
|
/* imported from screen.c */
|
|
extern int overstrike;
|
|
|
|
int err_compar();
|
|
char *err_string();
|
|
static int str_adderr(char *str, int len, int err);
|
|
static int str_addarg(char *str, int len, char *arg, int first);
|
|
|
|
/*
|
|
* show_help() - display the help screen; invoked in response to
|
|
* either 'h' or '?'.
|
|
*/
|
|
|
|
void
|
|
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("\
|
|
C - toggle the displaying of weighted CPU percentage\n\
|
|
d - change number of displays to show\n\
|
|
e - list errors generated by last \"kill\" or \"renice\" command\n\
|
|
H - toggle the displaying of threads\n\
|
|
i or I - toggle the displaying of idle processes\n\
|
|
j - toggle the displaying of jail ID\n\
|
|
J - display processes for only one jail (+ selects all jails)\n\
|
|
k - kill processes; send a signal to a list of processes\n\
|
|
m - toggle the display between 'cpu' and 'io' modes\n\
|
|
n or # - change number of processes to display\n", stdout);
|
|
#ifdef ORDER
|
|
if (displaymode == DISP_CPU)
|
|
fputs("\
|
|
o - specify sort order (pri, size, res, cpu, time, threads, jid, pid)\n",
|
|
stdout);
|
|
else
|
|
fputs("\
|
|
o - specify sort order (vcsw, ivcsw, read, write, fault, total, jid, pid)\n",
|
|
stdout);
|
|
#endif
|
|
fputs("\
|
|
P - toggle the displaying of per-CPU statistics\n\
|
|
r - renice a process\n\
|
|
s - change number of seconds to delay between updates\n\
|
|
S - toggle the displaying of system processes\n\
|
|
a - toggle the displaying of process titles\n\
|
|
t - toggle the display of this process\n\
|
|
u - display processes for only one user (+ selects all users)\n\
|
|
w - toggle the display of swap use for each process\n\
|
|
z - toggle the displaying of the system idle process\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);
|
|
}
|
|
|
|
int
|
|
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 errnum; /* 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++].errnum = (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->errnum != 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->errnum;
|
|
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".
|
|
*/
|
|
|
|
static int
|
|
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).
|
|
*/
|
|
|
|
static int
|
|
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.
|
|
*/
|
|
|
|
int
|
|
err_compar(p1, p2)
|
|
|
|
register struct errs *p1, *p2;
|
|
|
|
{
|
|
register int result;
|
|
|
|
if ((result = p1->errnum - p2->errnum) == 0)
|
|
{
|
|
return(strcmp(p1->arg, p2->arg));
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
/*
|
|
* error_count() - return the number of errors currently logged.
|
|
*/
|
|
|
|
int
|
|
error_count()
|
|
|
|
{
|
|
return(errcnt);
|
|
}
|
|
|
|
/*
|
|
* show_errors() - display on stdout the current log of errors.
|
|
*/
|
|
|
|
void
|
|
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->errnum == 0 ? "Not a number" : errmsg(errp->errnum));
|
|
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());
|
|
}
|
|
|