dissertation/usr.bin/top/commands.c

398 lines
9.6 KiB
C

/* $OpenBSD: commands.c,v 1.33 2019/10/08 07:26:59 kn Exp $ */
/*
* Top users/processes display for Unix
* Version 3
*
* Copyright (c) 1984, 1989, William LeFebvre, Rice University
* Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR OR HIS EMPLOYER BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* 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 <sys/types.h>
#include <stdio.h>
#include <err.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdbool.h>
#include "top.h"
#include "utils.h"
#include "machine.h"
static char *next_field(char *);
static int scan_arg(char *, int *, char *);
static char *err_string(void);
static size_t str_adderr(char *, size_t, int);
static size_t str_addarg(char *, size_t, char *, int);
static int err_compar(const void *, const void *);
/*
* Utility routines that help with some of the commands.
*/
static char *
next_field(char *str)
{
char *spaces = " \t";
size_t span;
span = strcspn(str, spaces);
if (span == strlen(str))
return (NULL);
str += span;
*str++ = '\0';
while (strcspn(str, spaces) == 0)
str++;
if (*str == '\0')
return (NULL);
return(str);
}
/*
* Scan the renice or kill interactive arguments for data and/or errors.
*/
static int
scan_arg(char *str, int *intp, char *nptr)
{
int val = 0, bad_flag = 0;
char ch;
*nptr = '\0';
if (*str == '\0')
return (-1);
while ((ch = *str++) != '\0') {
if (isspace((unsigned char)ch))
break;
else if (!isdigit((unsigned char)ch))
bad_flag = 1;
else
val = val * 10 + (ch - '0');
*(nptr++) = ch;
}
*nptr = '\0';
if (bad_flag == 1)
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 errs[ERRMAX];
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 { \
free(errs[errcnt].arg); \
if ((errs[errcnt].arg = strdup(p)) == NULL) \
err(1, "strdup"); \
errs[errcnt++].err = (e); \
}
#define STRMAX 80
/*
* 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".
*/
static char *
err_string(void)
{
int cnt = 0, first = true, currerr = -1;
static char string[STRMAX];
struct errs *errp;
/* if there are no errors, return NULL */
if (errcnt == 0)
return (NULL);
/* sort the errors */
qsort(errs, errcnt, sizeof(struct errs), err_compar);
/* need a space at the front of the error string */
string[0] = ' ';
string[1] = '\0';
/* loop thru the sorted list, building an error string */
while (cnt < errcnt) {
errp = &(errs[cnt++]);
if (errp->err != currerr) {
if (currerr != -1) {
if (str_adderr(string, sizeof string, currerr) >
sizeof string - 2)
return (err_listem);
/* we know there's more */
(void) strlcat(string, "; ", sizeof string);
}
currerr = errp->err;
first = true;
}
if (str_addarg(string, sizeof string, errp->arg, first) >=
sizeof string)
return (err_listem);
first = false;
}
/* add final message */
if (str_adderr(string, sizeof string, currerr) >= sizeof string)
return (err_listem);
/* return the error string */
return (string);
}
/*
* str_adderr(str, len, err) - add an explanation of error "err" to
* the string "str".
*/
static size_t
str_adderr(char *str, size_t len, int err)
{
size_t msglen;
char *msg;
msg = err == 0 ? "Not a number" : strerror(err);
if ((msglen = strlcat(str, ": ", len)) >= len)
return (msglen);
return (strlcat(str, msg, len));
}
/*
* 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 size_t
str_addarg(char *str, size_t len, char *arg, int first)
{
size_t msglen;
if (!first) {
if ((msglen = strlcat(str, ", ", len)) >= len)
return (msglen);
}
return (strlcat(str, arg, len));
}
/*
* err_compar(p1, p2) - comparison routine used by "qsort"
* for sorting errors.
*/
static int
err_compar(const void *e1, const void *e2)
{
const struct errs *p1 = (const struct errs *) e1;
const struct errs *p2 = (const struct errs *) e2;
int result;
if ((result = p1->err - p2->err) == 0)
return (strcmp(p1->arg, p2->arg));
return (result);
}
/*
* error_count() - return the number of errors currently logged.
*/
int
error_count(void)
{
return (errcnt);
}
/*
* kill_procs(str) - send signals to processes, much like the "kill"
* command does; invoked in response to 'k'.
*/
char *
kill_procs(char *str)
{
int signum = SIGTERM, procnum;
uid_t uid, puid;
char tempbuf[TEMPBUFSIZE];
char *nptr, *tmp;
tmp = tempbuf;
/* reset error array */
ERR_RESET;
/* remember our uid */
uid = getuid();
/* skip over leading white space */
while (isspace((unsigned char)*str))
str++;
if (*str == '-') {
str++;
/* explicit signal specified */
if ((nptr = next_field(str)) == NULL)
return (" kill: no processes specified");
if (isdigit((unsigned char)*str)) {
(void) scan_arg(str, &signum, tmp);
if (signum <= 0 || signum >= NSIG)
return (" invalid signal number");
} else {
/* translate the name into a number */
for (signum = 0; signum < NSIG; signum++) {
if (strcasecmp(sys_signame[signum],
str) == 0)
break;
}
/* was it ever found */
if (signum == NSIG)
return (" bad signal name");
}
/* put the new pointer in place */
str = nptr;
}
nptr = tempbuf;
/* loop thru the string, killing processes */
do {
if (scan_arg(str, &procnum, nptr) == -1) {
ERROR(nptr, 0);
} else {
/* check process owner if we're not root */
puid = proc_owner(procnum);
if (puid == (uid_t)(-1)) {
ERROR(nptr, ESRCH);
} else if (uid && (uid != puid)) {
ERROR(nptr, EACCES);
} else if (kill(procnum, signum) == -1) {
ERROR(nptr, 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(char *str)
{
uid_t uid;
char negate;
int prio, procnum;
char tempbuf[TEMPBUFSIZE];
char *nptr;
ERR_RESET;
uid = getuid();
/* skip over leading white space */
while (isspace((unsigned char)*str))
str++;
/* allow for negative priority values */
if ((negate = (*str == '-')) != 0) {
/* move past the minus sign */
str++;
}
nptr = tempbuf;
/* use procnum as a temporary holding place and get the number */
procnum = scan_arg(str, &prio, nptr);
/* 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 (scan_arg(str, &procnum, nptr) == -1) {
ERROR(nptr, 0);
}
/* check process owner if we're not root */
else if (uid && (uid != proc_owner(procnum))) {
ERROR(nptr, EACCES);
} else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) {
ERROR(nptr, errno);
}
} while ((str = next_field(str)) != NULL);
/* return appropriate error string */
return (err_string());
}