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 static const struct command all_commands[] = 54 { 55 {'C', "toggle the displaying of weighted CPU percentage", false }, 56 {'d', "change number of displays to show", false}, 57 {'e', "list errors generated by last \"kill\" or \"renice\" command", false}, 58 {'H', "toggle the displaying of threads", false}, 59 {'h', "show this help text", true}, 60 {'?', "show this help text", true}, 61 {'i', "toggle the displaying of idle processes", false}, 62 {'I', "toggle the displaying of idle processes", false}, 63 {'j', "toggle the displaying of jail ID", false}, 64 {'J', "display processes for only one jail (+ selects all jails)", false}, 65 {'k', "kill processes; send a signal to a list of processes", false}, 66 {'q', "quit" , true}, 67 {'m', "toggle the display between 'cpu' and 'io' modes", false}, 68 {'n', "change number of processes to display", false}, 69 {'#', "change number of processes to display", false}, 70 {'o', "specify the sort order", false}, 71 {'p', "display one process (+ selects all processes)", false}, 72 {'P', "toggle the displaying of per-CPU statistics", false}, 73 {'r', "renice a process", false}, 74 {'s', "change number of seconds to delay between updates", false}, 75 {'S', "toggle the displaying of system processes", false}, 76 {'a', "toggle the displaying of process titles", false}, 77 {'T', "toggle the displaying of thread IDs", false}, 78 {'t', "toggle the display of this process", false}, 79 {'u', "display processes for only one user (+ selects all users)", false}, 80 {'w', "toggle the display of swap use for each process", false}, 81 {'z', "toggle the displaying of the system idle process", false }, 82 {0, NULL, true} 83 }; 84 /* XXX: eventually remove command_chars, but assert they are the same for now */ 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 *err_string(void) 199 { 200 struct errs *errp; 201 int cnt = 0; 202 bool first = true; 203 int currerr = -1; 204 int stringlen; /* characters still available in "string" */ 205 static char string[STRMAX]; 206 207 /* if there are no errors, return NULL */ 208 if (errcnt == 0) 209 { 210 return(NULL); 211 } 212 213 /* sort the errors */ 214 qsort((char *)errs, errcnt, sizeof(struct errs), err_compar); 215 216 /* need a space at the front of the error string */ 217 string[0] = ' '; 218 string[1] = '\0'; 219 stringlen = STRMAX - 2; 220 221 /* loop thru the sorted list, building an error string */ 222 while (cnt < errcnt) 223 { 224 errp = &(errs[cnt++]); 225 if (errp->errnum != currerr) 226 { 227 if (currerr != -1) 228 { 229 if ((stringlen = str_adderr(string, stringlen, currerr)) < 2) 230 { 231 return(err_listem); 232 } 233 strcat(string, "; "); /* we know there's more */ 234 } 235 currerr = errp->errnum; 236 first = true; 237 } 238 if ((stringlen = str_addarg(string, stringlen, errp->arg, first)) ==0) 239 { 240 return(err_listem); 241 } 242 first = false; 243 } 244 245 /* add final message */ 246 stringlen = str_adderr(string, stringlen, currerr); 247 248 /* return the error string */ 249 return(stringlen == 0 ? err_listem : string); 250 } 251 252 /* 253 * str_adderr(str, len, err) - add an explanation of error "err" to 254 * the string "str". 255 */ 256 257 static int 258 str_adderr(char *str, int len, int err) 259 { 260 const char *msg; 261 int msglen; 262 263 msg = err == 0 ? "Not a number" : strerror(err); 264 msglen = strlen(msg) + 2; 265 if (len <= msglen) 266 { 267 return(0); 268 } 269 strcat(str, ": "); 270 strcat(str, msg); 271 return(len - msglen); 272 } 273 274 /* 275 * str_addarg(str, len, arg, first) - add the string argument "arg" to 276 * the string "str". This is the first in the group when "first" 277 * is set (indicating that a comma should NOT be added to the front). 278 */ 279 280 static int 281 str_addarg(char str[], int len, char arg[], bool first) 282 { 283 int arglen; 284 285 arglen = strlen(arg); 286 if (!first) 287 { 288 arglen += 2; 289 } 290 if (len <= arglen) 291 { 292 return(0); 293 } 294 if (!first) 295 { 296 strcat(str, ", "); 297 } 298 strcat(str, arg); 299 return(len - arglen); 300 } 301 302 /* 303 * err_compar(p1, p2) - comparison routine used by "qsort" 304 * for sorting errors. 305 */ 306 307 static int 308 err_compar(const void *p1, const void *p2) 309 { 310 int result; 311 const struct errs * const g1 = (const struct errs * const)p1; 312 const struct errs * const g2 = (const struct errs * const)p2; 313 314 315 316 if ((result = g1->errnum - g2->errnum) == 0) 317 { 318 return(strcmp(g1->arg, g2->arg)); 319 } 320 return(result); 321 } 322 323 /* 324 * error_count() - return the number of errors currently logged. 325 */ 326 327 int 328 error_count(void) 329 { 330 return(errcnt); 331 } 332 333 /* 334 * show_errors() - display on stdout the current log of errors. 335 */ 336 337 void 338 show_errors(void) 339 { 340 int cnt = 0; 341 struct errs *errp = errs; 342 343 printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); 344 while (cnt++ < errcnt) 345 { 346 printf("%5s: %s\n", errp->arg, 347 errp->errnum == 0 ? "Not a number" : strerror(errp->errnum)); 348 errp++; 349 } 350 } 351 352 static const char no_proc_specified[] = " no processes specified"; 353 static const char invalid_signal_number[] = " invalid_signal_number"; 354 static const char bad_signal_name[] = " bad signal name"; 355 static const char bad_pri_value[] = " bad priority value"; 356 357 static int 358 signame_to_signum(const char * sig) 359 { 360 int n; 361 362 if (strncasecmp(sig, "SIG", 3) == 0) 363 sig += 3; 364 for (n = 1; n < sys_nsig; n++) { 365 if (!strcasecmp(sys_signame[n], sig)) 366 return (n); 367 } 368 return (-1); 369 } 370 371 /* 372 * kill_procs(str) - send signals to processes, much like the "kill" 373 * command does; invoked in response to 'k'. 374 */ 375 376 const char * 377 kill_procs(char *str) 378 { 379 char *nptr; 380 int signum = SIGTERM; /* default */ 381 int procnum; 382 383 /* reset error array */ 384 ERR_RESET; 385 386 /* skip over leading white space */ 387 while (isspace(*str)) str++; 388 389 if (str[0] == '-') 390 { 391 /* explicit signal specified */ 392 if ((nptr = next_field(str)) == NULL) 393 { 394 return(no_proc_specified); 395 } 396 397 if (isdigit(str[1])) 398 { 399 scanint(str + 1, &signum); 400 if (signum <= 0 || signum >= NSIG) 401 { 402 return(invalid_signal_number); 403 } 404 } 405 else 406 { 407 signum = signame_to_signum(str + 1); 408 409 /* was it ever found */ 410 if (signum == -1 ) 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 /* go in for the kill */ 429 if (kill(procnum, signum) == -1) 430 { 431 /* chalk up an error */ 432 ERROR(str, errno); 433 } 434 } 435 } while ((str = next_field(str)) != NULL); 436 437 /* return appropriate error string */ 438 return(err_string()); 439 } 440 441 /* 442 * renice_procs(str) - change the "nice" of processes, much like the 443 * "renice" command does; invoked in response to 'r'. 444 */ 445 446 const char * 447 renice_procs(char *str) 448 { 449 char negate; 450 int prio; 451 int procnum; 452 453 ERR_RESET; 454 455 /* allow for negative priority values */ 456 if ((negate = (*str == '-')) != 0) 457 { 458 /* move past the minus sign */ 459 str++; 460 } 461 462 /* use procnum as a temporary holding place and get the number */ 463 procnum = scanint(str, &prio); 464 465 /* negate if necessary */ 466 if (negate) 467 { 468 prio = -prio; 469 } 470 471 /* check for validity */ 472 if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 473 { 474 return(bad_pri_value); 475 } 476 477 /* move to the first process number */ 478 if ((str = next_field(str)) == NULL) 479 { 480 return(no_proc_specified); 481 } 482 483 /* loop thru the process numbers, renicing each one */ 484 do 485 { 486 if (scanint(str, &procnum) == -1) 487 { 488 ERROR(str, 0); 489 } 490 491 if (setpriority(PRIO_PROCESS, procnum, prio) == -1) 492 { 493 ERROR(str, errno); 494 } 495 } while ((str = next_field(str)) != NULL); 496 497 /* return appropriate error string */ 498 return(err_string()); 499 } 500 501