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