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