1 /* $OpenBSD: commands.c,v 1.6 2002/07/15 17:20:36 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 int str_adderr(char *, int, int); 60 static int str_addarg(char *, int, 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 68 void 69 show_help() 70 71 { 72 printf("Top version %s, %s\n", version_string(), copyright); 73 fputs("\n\n\ 74 A top users display for Unix\n\ 75 \n\ 76 These single-character commands are available:\n\ 77 \n\ 78 ^L - redraw screen\n\ 79 q - quit\n\ 80 h or ? - help; show this text\n", stdout); 81 82 /* not all commands are availalbe with overstrike terminals */ 83 if (overstrike) 84 { 85 fputs("\n\ 86 Other commands are also available, but this terminal is not\n\ 87 sophisticated enough to handle those commands gracefully.\n\n", stdout); 88 } 89 else 90 { 91 fputs("\ 92 d - change number of displays to show\n\ 93 e - list errors generated by last \"kill\" or \"renice\" command\n\ 94 i - toggle the displaying of idle processes\n\ 95 I - same as 'i'\n\ 96 k - kill processes; send a signal to a list of processes\n\ 97 n or # - change number of processes to display\n", stdout); 98 #ifdef ORDER 99 fputs("\ 100 o - specify sort order (size, res, cpu, time)\n", stdout); 101 #endif 102 fputs("\ 103 r - renice a process\n\ 104 s - change number of seconds to delay between updates\n\ 105 u - display processes for only one user (+ selects all users)\n\ 106 \n\ 107 \n", stdout); 108 } 109 } 110 111 /* 112 * Utility routines that help with some of the commands. 113 */ 114 115 static char *next_field(str) 116 117 char *str; 118 119 { 120 if ((str = strchr(str, ' ')) == NULL) 121 { 122 return(NULL); 123 } 124 *str = '\0'; 125 while (*++str == ' ') /* loop */; 126 127 /* if there is nothing left of the string, return NULL */ 128 /* This fix is dedicated to Greg Earle */ 129 return(*str == '\0' ? NULL : str); 130 } 131 132 static int scanint(str, intp) 133 134 char *str; 135 int *intp; 136 137 { 138 int val = 0; 139 char ch; 140 141 /* if there is nothing left of the string, flag it as an error */ 142 /* This fix is dedicated to Greg Earle */ 143 if (*str == '\0') 144 { 145 return(-1); 146 } 147 148 while ((ch = *str++) != '\0') 149 { 150 if (isdigit(ch)) 151 { 152 val = val * 10 + (ch - '0'); 153 } 154 else if (isspace(ch)) 155 { 156 break; 157 } 158 else 159 { 160 return(-1); 161 } 162 } 163 *intp = val; 164 return(0); 165 } 166 167 /* 168 * Some of the commands make system calls that could generate errors. 169 * These errors are collected up in an array of structures for later 170 * contemplation and display. Such routines return a string containing an 171 * error message, or NULL if no errors occurred. The next few routines are 172 * for manipulating and displaying these errors. We need an upper limit on 173 * the number of errors, so we arbitrarily choose 20. 174 */ 175 176 #define ERRMAX 20 177 178 struct errs /* structure for a system-call error */ 179 { 180 int errno; /* value of errno (that is, the actual error) */ 181 char *arg; /* argument that caused the error */ 182 }; 183 184 static struct errs errs[ERRMAX]; 185 static int errcnt; 186 static char *err_toomany = " too many errors occurred"; 187 static char *err_listem = 188 " Many errors occurred. Press `e' to display the list of errors."; 189 190 /* These macros get used to reset and log the errors */ 191 #define ERR_RESET errcnt = 0 192 #define ERROR(p, e) if (errcnt >= ERRMAX) \ 193 { \ 194 return(err_toomany); \ 195 } \ 196 else \ 197 { \ 198 errs[errcnt].arg = (p); \ 199 errs[errcnt++].errno = (e); \ 200 } 201 202 /* 203 * err_string() - return an appropriate error string. This is what the 204 * command will return for displaying. If no errors were logged, then 205 * return NULL. The maximum length of the error string is defined by 206 * "STRMAX". 207 */ 208 209 #define STRMAX 80 210 211 static char *err_string() 212 213 { 214 struct errs *errp; 215 int cnt = 0; 216 int first = Yes; 217 int currerr = -1; 218 int stringlen; /* characters still available in "string" */ 219 static char string[STRMAX]; 220 221 /* if there are no errors, return NULL */ 222 if (errcnt == 0) 223 { 224 return(NULL); 225 } 226 227 /* sort the errors */ 228 qsort((char *)errs, errcnt, sizeof(struct errs), err_compar); 229 230 /* need a space at the front of the error string */ 231 string[0] = ' '; 232 string[1] = '\0'; 233 stringlen = STRMAX - 2; 234 235 /* loop thru the sorted list, building an error string */ 236 while (cnt < errcnt) 237 { 238 errp = &(errs[cnt++]); 239 if (errp->errno != currerr) 240 { 241 if (currerr != -1) 242 { 243 if ((stringlen = str_adderr(string, stringlen, currerr)) < 2) 244 { 245 return(err_listem); 246 } 247 /* we know there's more */ 248 (void) strlcat(string, "; ", sizeof string); 249 } 250 currerr = errp->errno; 251 first = Yes; 252 } 253 if ((stringlen = str_addarg(string, stringlen, errp->arg, first)) ==0) 254 { 255 return(err_listem); 256 } 257 first = No; 258 } 259 260 /* add final message */ 261 stringlen = str_adderr(string, stringlen, currerr); 262 263 /* return the error string */ 264 return(stringlen == 0 ? err_listem : string); 265 } 266 267 /* 268 * str_adderr(str, len, err) - add an explanation of error "err" to 269 * the string "str". 270 */ 271 272 static int str_adderr(str, len, err) 273 274 char *str; 275 int len; 276 int err; 277 278 { 279 char *msg; 280 int msglen; 281 282 msg = err == 0 ? "Not a number" : strerror(err); 283 msglen = strlen(msg) + 2; 284 if (len <= msglen) 285 { 286 return(0); 287 } 288 (void) strcat(str, ": "); 289 (void) strcat(str, msg); 290 return(len - msglen); 291 } 292 293 /* 294 * str_addarg(str, len, arg, first) - add the string argument "arg" to 295 * the string "str". This is the first in the group when "first" 296 * is set (indicating that a comma should NOT be added to the front). 297 */ 298 299 static int str_addarg(str, len, arg, first) 300 301 char *str; 302 int len; 303 char *arg; 304 int first; 305 306 { 307 int arglen; 308 309 arglen = strlen(arg); 310 if (!first) 311 { 312 arglen += 2; 313 } 314 if (len <= arglen) 315 { 316 return(0); 317 } 318 if (!first) 319 { 320 (void) strcat(str, ", "); 321 } 322 (void) strcat(str, arg); 323 return(len - arglen); 324 } 325 326 /* 327 * err_compar(p1, p2) - comparison routine used by "qsort" 328 * for sorting errors. 329 */ 330 331 static int err_compar(e1, e2) 332 333 const void *e1, *e2; 334 335 { 336 const struct errs *p1 = (struct errs *)e1; 337 const struct errs *p2 = (struct errs *)e2; 338 int result; 339 340 if ((result = p1->errno - p2->errno) == 0) 341 { 342 return(strcmp(p1->arg, p2->arg)); 343 } 344 return(result); 345 } 346 347 /* 348 * error_count() - return the number of errors currently logged. 349 */ 350 351 int error_count() 352 353 { 354 return(errcnt); 355 } 356 357 /* 358 * show_errors() - display on stdout the current log of errors. 359 */ 360 361 void show_errors() 362 363 { 364 int cnt = 0; 365 struct errs *errp = errs; 366 367 printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); 368 while (cnt++ < errcnt) 369 { 370 printf("%5s: %s\n", errp->arg, 371 errp->errno == 0 ? "Not a number" : strerror(errp->errno)); 372 errp++; 373 } 374 } 375 376 /* 377 * kill_procs(str) - send signals to processes, much like the "kill" 378 * command does; invoked in response to 'k'. 379 */ 380 381 char *kill_procs(str) 382 383 char *str; 384 385 { 386 char *nptr; 387 int signum = SIGTERM; /* default */ 388 int procnum; 389 struct sigdesc *sigp; 390 int uid; 391 392 /* reset error array */ 393 ERR_RESET; 394 395 /* remember our uid */ 396 uid = getuid(); 397 398 /* skip over leading white space */ 399 while (isspace(*str)) str++; 400 401 if (str[0] == '-') 402 { 403 /* explicit signal specified */ 404 if ((nptr = next_field(str)) == NULL) 405 { 406 return(" kill: no processes specified"); 407 } 408 409 if (isdigit(str[1])) 410 { 411 (void) scanint(str + 1, &signum); 412 if (signum <= 0 || signum >= NSIG) 413 { 414 return(" invalid signal number"); 415 } 416 } 417 else 418 { 419 /* translate the name into a number */ 420 for (sigp = sigdesc; sigp->name != NULL; sigp++) 421 { 422 if (strcmp(sigp->name, str + 1) == 0) 423 { 424 signum = sigp->number; 425 break; 426 } 427 } 428 429 /* was it ever found */ 430 if (sigp->name == NULL) 431 { 432 return(" bad signal name"); 433 } 434 } 435 /* put the new pointer in place */ 436 str = nptr; 437 } 438 439 /* loop thru the string, killing processes */ 440 do 441 { 442 if (scanint(str, &procnum) == -1) 443 { 444 ERROR(str, 0); 445 } 446 else 447 { 448 /* check process owner if we're not root */ 449 if (uid && (uid != proc_owner(procnum))) 450 { 451 ERROR(str, EACCES); 452 } 453 /* go in for the kill */ 454 else if (kill(procnum, signum) == -1) 455 { 456 /* chalk up an error */ 457 ERROR(str, errno); 458 } 459 } 460 } while ((str = next_field(str)) != NULL); 461 462 /* return appropriate error string */ 463 return(err_string()); 464 } 465 466 /* 467 * renice_procs(str) - change the "nice" of processes, much like the 468 * "renice" command does; invoked in response to 'r'. 469 */ 470 471 char *renice_procs(str) 472 473 char *str; 474 475 { 476 char negate; 477 int prio; 478 int procnum; 479 int uid; 480 481 ERR_RESET; 482 uid = getuid(); 483 484 /* allow for negative priority values */ 485 if ((negate = (*str == '-')) != 0) 486 { 487 /* move past the minus sign */ 488 str++; 489 } 490 491 /* use procnum as a temporary holding place and get the number */ 492 procnum = scanint(str, &prio); 493 494 /* negate if necessary */ 495 if (negate) 496 { 497 prio = -prio; 498 } 499 500 #if defined(PRIO_MIN) && defined(PRIO_MAX) 501 /* check for validity */ 502 if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 503 { 504 return(" bad priority value"); 505 } 506 #endif 507 508 /* move to the first process number */ 509 if ((str = next_field(str)) == NULL) 510 { 511 return(" no processes specified"); 512 } 513 514 /* loop thru the process numbers, renicing each one */ 515 do 516 { 517 if (scanint(str, &procnum) == -1) 518 { 519 ERROR(str, 0); 520 } 521 522 /* check process owner if we're not root */ 523 else if (uid && (uid != proc_owner(procnum))) 524 { 525 ERROR(str, EACCES); 526 } 527 else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) 528 { 529 ERROR(str, errno); 530 } 531 } while ((str = next_field(str)) != NULL); 532 533 /* return appropriate error string */ 534 return(err_string()); 535 } 536 537