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