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 "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 const struct command all_commands[] = 54 { 55 {'C', "toggle the displaying of weighted CPU percentage", false, CMD_wcputog}, 56 {'d', "change number of displays to show", false, CMD_displays}, 57 {'e', "list errors generated by last \"kill\" or \"renice\" command", false, CMD_errors}, 58 {'H', "toggle the displaying of threads", false, CMD_thrtog}, 59 {'h', "show this help text", true, CMD_help}, 60 {'?', "show this help text", true, CMD_help}, 61 {'i', "toggle the displaying of idle processes", false, CMD_idletog}, 62 {'I', "toggle the displaying of idle processes", false, CMD_idletog}, 63 {'j', "toggle the displaying of jail ID", false, CMD_jidtog}, 64 {'J', "display processes for only one jail (+ selects all jails)", false, CMD_jail}, 65 {'k', "kill processes; send a signal to a list of processes", false, CMD_kill}, 66 {'q', "quit" , true, CMD_quit}, 67 {'m', "toggle the display between 'cpu' and 'io' modes", false, CMD_viewtog}, 68 {'n', "change number of processes to display", false, CMD_number}, 69 {'#', "change number of processes to display", false, CMD_number}, 70 {'o', "specify the sort order", false, CMD_order}, 71 {'p', "display one process (+ selects all processes)", false, CMD_pid}, 72 {'P', "toggle the displaying of per-CPU statistics", false, CMD_pcputog}, 73 {'r', "renice a process", false, CMD_renice}, 74 {'s', "change number of seconds to delay between updates", false, CMD_delay}, 75 {'S', "toggle the displaying of system processes", false, CMD_viewsys}, 76 {'a', "toggle the displaying of process titles", false, CMD_showargs}, 77 {'T', "toggle the displaying of thread IDs", false, CMD_toggletid}, 78 {'t', "toggle the display of this process", false, CMD_selftog}, 79 {'u', "display processes for only one user (+ selects all users)", false, CMD_user}, 80 {'w', "toggle the display of swap use for each process", false, CMD_swaptog}, 81 {'z', "toggle the displaying of the system idle process", false, CMD_kidletog}, 82 {' ', "update the display", false, CMD_update}, 83 {0, NULL, true, CMD_NONE} 84 }; 85 86 void 87 show_help(void) 88 { 89 const struct command *curcmd; 90 91 printf("Top version FreeBSD, %s\n", copyright); 92 curcmd = all_commands; 93 while (curcmd->c != 0) { 94 if (overstrike && !curcmd->available_to_dumb) { 95 ++curcmd; 96 continue; 97 } 98 printf("%c\t- %s\n", curcmd->c, curcmd->desc); 99 ++curcmd; 100 } 101 if (overstrike) 102 { 103 fputs("\ 104 Other commands are also available, but this terminal is not\n\ 105 sophisticated enough to handle those commands gracefully.\n", stdout); 106 } 107 } 108 109 /* 110 * Utility routines that help with some of the commands. 111 */ 112 113 static char * 114 next_field(char *str) 115 { 116 if ((str = strchr(str, ' ')) == NULL) 117 { 118 return(NULL); 119 } 120 *str = '\0'; 121 while (*++str == ' ') /* loop */; 122 123 /* if there is nothing left of the string, return NULL */ 124 /* This fix is dedicated to Greg Earle */ 125 return(*str == '\0' ? NULL : str); 126 } 127 128 static int 129 scanint(char *str, int *intp) 130 { 131 int val = 0; 132 char ch; 133 134 /* if there is nothing left of the string, flag it as an error */ 135 /* This fix is dedicated to Greg Earle */ 136 if (*str == '\0') 137 { 138 return(-1); 139 } 140 141 while ((ch = *str++) != '\0') 142 { 143 if (isdigit(ch)) 144 { 145 val = val * 10 + (ch - '0'); 146 } 147 else if (isspace(ch)) 148 { 149 break; 150 } 151 else 152 { 153 return(-1); 154 } 155 } 156 *intp = val; 157 return(0); 158 } 159 160 /* 161 * Some of the commands make system calls that could generate errors. 162 * These errors are collected up in an array of structures for later 163 * contemplation and display. Such routines return a string containing an 164 * error message, or NULL if no errors occurred. The next few routines are 165 * for manipulating and displaying these errors. We need an upper limit on 166 * the number of errors, so we arbitrarily choose 20. 167 */ 168 169 #define ERRMAX 20 170 171 static struct errs errs[ERRMAX]; 172 static int errcnt; 173 static char err_toomany[] = " too many errors occurred"; 174 static char err_listem[] = 175 " Many errors occurred. Press `e' to display the list of errors."; 176 177 /* These macros get used to reset and log the errors */ 178 #define ERR_RESET errcnt = 0 179 #define ERROR(p, e) if (errcnt >= ERRMAX) \ 180 { \ 181 return(err_toomany); \ 182 } \ 183 else \ 184 { \ 185 errs[errcnt].arg = (p); \ 186 errs[errcnt++].errnum = (e); \ 187 } 188 189 /* 190 * err_string() - return an appropriate error string. This is what the 191 * command will return for displaying. If no errors were logged, then 192 * return NULL. The maximum length of the error string is defined by 193 * "STRMAX". 194 */ 195 196 #define STRMAX 80 197 198 char * 199 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 const char no_proc_specified[] = " no processes specified"; 354 static const char invalid_signal_number[] = " invalid_signal_number"; 355 static const char bad_signal_name[] = " bad signal name"; 356 static const char bad_pri_value[] = " bad priority value"; 357 358 static int 359 signame_to_signum(const char * sig) 360 { 361 int n; 362 363 if (strncasecmp(sig, "SIG", 3) == 0) 364 sig += 3; 365 for (n = 1; n < sys_nsig; n++) { 366 if (!strcasecmp(sys_signame[n], sig)) 367 return (n); 368 } 369 return (-1); 370 } 371 372 /* 373 * kill_procs(str) - send signals to processes, much like the "kill" 374 * command does; invoked in response to 'k'. 375 */ 376 377 const char * 378 kill_procs(char *str) 379 { 380 char *nptr; 381 int signum = SIGTERM; /* default */ 382 int procnum; 383 384 /* reset error array */ 385 ERR_RESET; 386 387 /* skip over leading white space */ 388 while (isspace(*str)) str++; 389 390 if (str[0] == '-') 391 { 392 /* explicit signal specified */ 393 if ((nptr = next_field(str)) == NULL) 394 { 395 return(no_proc_specified); 396 } 397 398 if (isdigit(str[1])) 399 { 400 scanint(str + 1, &signum); 401 if (signum <= 0 || signum >= NSIG) 402 { 403 return(invalid_signal_number); 404 } 405 } 406 else 407 { 408 signum = signame_to_signum(str + 1); 409 410 /* was it ever found */ 411 if (signum == -1 ) 412 { 413 return(bad_signal_name); 414 } 415 } 416 /* put the new pointer in place */ 417 str = nptr; 418 } 419 420 /* loop thru the string, killing processes */ 421 do 422 { 423 if (scanint(str, &procnum) == -1) 424 { 425 ERROR(str, 0); 426 } 427 else 428 { 429 /* go in for the kill */ 430 if (kill(procnum, signum) == -1) 431 { 432 /* chalk up an error */ 433 ERROR(str, errno); 434 } 435 } 436 } while ((str = next_field(str)) != NULL); 437 438 /* return appropriate error string */ 439 return(err_string()); 440 } 441 442 /* 443 * renice_procs(str) - change the "nice" of processes, much like the 444 * "renice" command does; invoked in response to 'r'. 445 */ 446 447 const char * 448 renice_procs(char *str) 449 { 450 char negate; 451 int prio; 452 int procnum; 453 454 ERR_RESET; 455 456 /* allow for negative priority values */ 457 if ((negate = (*str == '-')) != 0) 458 { 459 /* move past the minus sign */ 460 str++; 461 } 462 463 /* use procnum as a temporary holding place and get the number */ 464 procnum = scanint(str, &prio); 465 466 /* negate if necessary */ 467 if (negate) 468 { 469 prio = -prio; 470 } 471 472 /* check for validity */ 473 if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 474 { 475 return(bad_pri_value); 476 } 477 478 /* move to the first process number */ 479 if ((str = next_field(str)) == NULL) 480 { 481 return(no_proc_specified); 482 } 483 484 /* loop thru the process numbers, renicing each one */ 485 do 486 { 487 if (scanint(str, &procnum) == -1) 488 { 489 ERROR(str, 0); 490 } 491 492 if (setpriority(PRIO_PROCESS, procnum, prio) == -1) 493 { 494 ERROR(str, errno); 495 } 496 } while ((str = next_field(str)) != NULL); 497 498 /* return appropriate error string */ 499 return(err_string()); 500 } 501 502