1 /* $OpenBSD: commands.c,v 1.14 2004/05/09 22:14:15 deraadt Exp $ */ 2 3 /* 4 * Top users/processes display for Unix 5 * Version 3 6 * 7 * Copyright (c) 1984, 1989, William LeFebvre, Rice University 8 * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE AUTHOR OR HIS EMPLOYER BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /* 32 * This file contains the routines that implement some of the interactive 33 * mode commands. Note that some of the commands are implemented in-line 34 * in "main". This is necessary because they change the global state of 35 * "top" (i.e.: changing the number of processes to display). 36 */ 37 38 #include <sys/types.h> 39 #include <stdio.h> 40 #include <ctype.h> 41 #include <errno.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <signal.h> 45 #include <unistd.h> 46 #include <sys/time.h> 47 #include <sys/resource.h> 48 49 #include "top.h" 50 51 #include "sigdesc.h" /* generated automatically */ 52 #include "boolean.h" 53 #include "utils.h" 54 #include "machine.h" 55 56 static char *next_field(char *); 57 static int scanint(char *, int *); 58 static char *err_string(void); 59 static size_t str_adderr(char *, size_t, int); 60 static size_t str_addarg(char *, size_t, char *, int); 61 static int err_compar(const void *, const void *); 62 63 /* 64 * show_help() - display the help screen; invoked in response to 65 * either 'h' or '?'. 66 */ 67 void 68 show_help(void) 69 { 70 printf("Top version %s, %s\n", version_string(), copyright); 71 fputs("\n\n" 72 "A top users display for Unix\n" 73 "\n" 74 "These single-character commands are available:\n" 75 "\n" 76 "^L - redraw screen\n" 77 "h or ? - help; show this text\n" 78 "q - quit\n", stdout); 79 80 /* not all commands are available with overstrike terminals */ 81 if (overstrike) { 82 fputs("\n" 83 "Other commands are also available, but this terminal is not\n" 84 "sophisticated enough to handle those commands gracefully.\n\n", 85 stdout); 86 } else { 87 fputs( 88 "d - change number of displays to show\n" 89 "e - list errors generated by last \"kill\" or \"renice\" command\n" 90 "i - toggle the displaying of idle processes\n" 91 "I - same as 'i'\n" 92 "k - kill processes; send a signal to a list of processes\n" 93 "n or # - change number of processes to display\n", stdout); 94 fputs( 95 "o - specify sort order (size, res, cpu, time)\n", 96 stdout); 97 fputs( 98 "r - renice a process\n" 99 "s - change number of seconds to delay between updates\n" 100 "S - toggle the display of system processes\n" 101 "u - display processes for only one user (+ selects all users)\n" 102 "\n\n", stdout); 103 } 104 } 105 106 /* 107 * Utility routines that help with some of the commands. 108 */ 109 static char * 110 next_field(char *str) 111 { 112 if ((str = strchr(str, ' ')) == NULL) 113 return (NULL); 114 115 *str = '\0'; 116 while (*++str == ' ') /* loop */ 117 ; 118 119 /* if there is nothing left of the string, return NULL */ 120 /* This fix is dedicated to Greg Earle */ 121 return (*str == '\0' ? NULL : str); 122 } 123 124 static int 125 scanint(char *str, int *intp) 126 { 127 int val = 0; 128 char ch; 129 130 /* if there is nothing left of the string, flag it as an error */ 131 /* This fix is dedicated to Greg Earle */ 132 if (*str == '\0') 133 return (-1); 134 135 while ((ch = *str++) != '\0') { 136 if (isdigit(ch)) 137 val = val * 10 + (ch - '0'); 138 else if (isspace(ch)) 139 break; 140 else 141 return (-1); 142 } 143 *intp = val; 144 return (0); 145 } 146 147 /* 148 * Some of the commands make system calls that could generate errors. 149 * These errors are collected up in an array of structures for later 150 * contemplation and display. Such routines return a string containing an 151 * error message, or NULL if no errors occurred. The next few routines are 152 * for manipulating and displaying these errors. We need an upper limit on 153 * the number of errors, so we arbitrarily choose 20. 154 */ 155 156 #define ERRMAX 20 157 158 struct errs { /* structure for a system-call error */ 159 int errno; /* value of errno (that is, the actual error) */ 160 char *arg; /* argument that caused the error */ 161 }; 162 163 static struct errs errs[ERRMAX]; 164 static int errcnt; 165 static char *err_toomany = " too many errors occurred"; 166 static char *err_listem = 167 " Many errors occurred. Press `e' to display the list of errors."; 168 169 /* These macros get used to reset and log the errors */ 170 #define ERR_RESET errcnt = 0 171 #define ERROR(p, e) \ 172 if (errcnt >= ERRMAX) { \ 173 return(err_toomany); \ 174 } else { \ 175 errs[errcnt].arg = (p); \ 176 errs[errcnt++].errno = (e); \ 177 } 178 179 #define STRMAX 80 180 181 /* 182 * err_string() - return an appropriate error string. This is what the 183 * command will return for displaying. If no errors were logged, then 184 * return NULL. The maximum length of the error string is defined by 185 * "STRMAX". 186 */ 187 static char * 188 err_string(void) 189 { 190 int cnt = 0, first = Yes, currerr = -1; 191 static char string[STRMAX]; 192 struct errs *errp; 193 194 /* if there are no errors, return NULL */ 195 if (errcnt == 0) 196 return (NULL); 197 198 /* sort the errors */ 199 qsort(errs, errcnt, sizeof(struct errs), err_compar); 200 201 /* need a space at the front of the error string */ 202 string[0] = ' '; 203 string[1] = '\0'; 204 205 /* loop thru the sorted list, building an error string */ 206 while (cnt < errcnt) { 207 errp = &(errs[cnt++]); 208 if (errp->errno != currerr) { 209 if (currerr != -1) { 210 if (str_adderr(string, sizeof string, currerr) > 211 sizeof string - 2) 212 return (err_listem); 213 214 /* we know there's more */ 215 (void) strlcat(string, "; ", sizeof string); 216 } 217 currerr = errp->errno; 218 first = Yes; 219 } 220 if (str_addarg(string, sizeof string, errp->arg, first) >= 221 sizeof string) 222 return (err_listem); 223 224 first = No; 225 } 226 227 /* add final message */ 228 if (str_adderr(string, sizeof string, currerr) >= sizeof string) 229 return (err_listem); 230 231 /* return the error string */ 232 return (string); 233 } 234 235 /* 236 * str_adderr(str, len, err) - add an explanation of error "err" to 237 * the string "str". 238 */ 239 static size_t 240 str_adderr(char *str, size_t len, int err) 241 { 242 size_t msglen; 243 char *msg; 244 245 msg = err == 0 ? "Not a number" : strerror(err); 246 247 if ((msglen = strlcat(str, ": ", len)) >= len) 248 return (msglen); 249 250 return (strlcat(str, msg, len)); 251 } 252 253 /* 254 * str_addarg(str, len, arg, first) - add the string argument "arg" to 255 * the string "str". This is the first in the group when "first" 256 * is set (indicating that a comma should NOT be added to the front). 257 */ 258 static size_t 259 str_addarg(char *str, size_t len, char *arg, int first) 260 { 261 size_t msglen; 262 263 if (!first) { 264 if ((msglen = strlcat(str, ", ", len)) >= len) 265 return (msglen); 266 } 267 return (strlcat(str, arg, len)); 268 } 269 270 /* 271 * err_compar(p1, p2) - comparison routine used by "qsort" 272 * for sorting errors. 273 */ 274 static int 275 err_compar(const void *e1, const void *e2) 276 { 277 const struct errs *p1 = (struct errs *) e1; 278 const struct errs *p2 = (struct errs *) e2; 279 int result; 280 281 if ((result = p1->errno - p2->errno) == 0) 282 return (strcmp(p1->arg, p2->arg)); 283 return (result); 284 } 285 286 /* 287 * error_count() - return the number of errors currently logged. 288 */ 289 int 290 error_count(void) 291 { 292 return (errcnt); 293 } 294 295 /* 296 * show_errors() - display on stdout the current log of errors. 297 */ 298 void 299 show_errors(void) 300 { 301 struct errs *errp = errs; 302 int cnt = 0; 303 304 printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); 305 while (cnt++ < errcnt) { 306 printf("%5s: %s\n", errp->arg, 307 errp->errno == 0 ? "Not a number" : strerror(errp->errno)); 308 errp++; 309 } 310 } 311 312 /* 313 * kill_procs(str) - send signals to processes, much like the "kill" 314 * command does; invoked in response to 'k'. 315 */ 316 char * 317 kill_procs(char *str) 318 { 319 int signum = SIGTERM, procnum; 320 struct sigdesc *sigp; 321 uid_t uid, puid; 322 char *nptr; 323 324 /* reset error array */ 325 ERR_RESET; 326 327 /* remember our uid */ 328 uid = getuid(); 329 330 /* skip over leading white space */ 331 while (isspace(*str)) 332 str++; 333 334 if (str[0] == '-') { 335 /* explicit signal specified */ 336 if ((nptr = next_field(str)) == NULL) 337 return (" kill: no processes specified"); 338 339 if (isdigit(str[1])) { 340 (void) scanint(str + 1, &signum); 341 if (signum <= 0 || signum >= NSIG) 342 return (" invalid signal number"); 343 } else { 344 /* translate the name into a number */ 345 for (sigp = sigdesc; sigp->name != NULL; sigp++) { 346 if (strcmp(sigp->name, str + 1) == 0) { 347 signum = sigp->number; 348 break; 349 } 350 } 351 352 /* was it ever found */ 353 if (sigp->name == NULL) 354 return (" bad signal name"); 355 } 356 /* put the new pointer in place */ 357 str = nptr; 358 } 359 /* loop thru the string, killing processes */ 360 do { 361 if (scanint(str, &procnum) == -1) { 362 ERROR(str, 0); 363 } else { 364 /* check process owner if we're not root */ 365 puid = proc_owner(procnum); 366 if (puid == (uid_t)(-1)) { 367 ERROR(str, ESRCH); 368 } else if (uid && (uid != puid)) { 369 ERROR(str, EACCES); 370 } else if (kill(procnum, signum) == -1) { 371 ERROR(str, errno); 372 } 373 } 374 } while ((str = next_field(str)) != NULL); 375 376 /* return appropriate error string */ 377 return (err_string()); 378 } 379 380 /* 381 * renice_procs(str) - change the "nice" of processes, much like the 382 * "renice" command does; invoked in response to 'r'. 383 */ 384 char * 385 renice_procs(char *str) 386 { 387 uid_t uid; 388 char negate; 389 int prio, procnum; 390 391 ERR_RESET; 392 uid = getuid(); 393 394 /* allow for negative priority values */ 395 if ((negate = (*str == '-')) != 0) { 396 /* move past the minus sign */ 397 str++; 398 } 399 /* use procnum as a temporary holding place and get the number */ 400 procnum = scanint(str, &prio); 401 402 /* negate if necessary */ 403 if (negate) 404 prio = -prio; 405 406 #if defined(PRIO_MIN) && defined(PRIO_MAX) 407 /* check for validity */ 408 if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 409 return (" bad priority value"); 410 #endif 411 412 /* move to the first process number */ 413 if ((str = next_field(str)) == NULL) 414 return (" no processes specified"); 415 416 /* loop thru the process numbers, renicing each one */ 417 do { 418 if (scanint(str, &procnum) == -1) { 419 ERROR(str, 0); 420 } 421 /* check process owner if we're not root */ 422 else if (uid && (uid != proc_owner(procnum))) { 423 ERROR(str, EACCES); 424 } else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) { 425 ERROR(str, errno); 426 } 427 } while ((str = next_field(str)) != NULL); 428 429 /* return appropriate error string */ 430 return (err_string()); 431 } 432