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