398 lines
9.6 KiB
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());
|
|
}
|