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 display of this process\n\ 103 u - display processes for only one user (+ selects all users)\n\ 104 w - toggle the display of swap use for each process\n\ 105 z - toggle the displaying of the system idle process\n\ 106 \n\ 107 \n", stdout); 108 } 109 } 110 111 /* 112 * Utility routines that help with some of the commands. 113 */ 114 115 static char * 116 next_field(char *str) 117 { 118 if ((str = strchr(str, ' ')) == NULL) 119 { 120 return(NULL); 121 } 122 *str = '\0'; 123 while (*++str == ' ') /* loop */; 124 125 /* if there is nothing left of the string, return NULL */ 126 /* This fix is dedicated to Greg Earle */ 127 return(*str == '\0' ? NULL : str); 128 } 129 130 static int 131 scanint(char *str, int *intp) 132 { 133 int val = 0; 134 char ch; 135 136 /* if there is nothing left of the string, flag it as an error */ 137 /* This fix is dedicated to Greg Earle */ 138 if (*str == '\0') 139 { 140 return(-1); 141 } 142 143 while ((ch = *str++) != '\0') 144 { 145 if (isdigit(ch)) 146 { 147 val = val * 10 + (ch - '0'); 148 } 149 else if (isspace(ch)) 150 { 151 break; 152 } 153 else 154 { 155 return(-1); 156 } 157 } 158 *intp = val; 159 return(0); 160 } 161 162 /* 163 * Some of the commands make system calls that could generate errors. 164 * These errors are collected up in an array of structures for later 165 * contemplation and display. Such routines return a string containing an 166 * error message, or NULL if no errors occurred. The next few routines are 167 * for manipulating and displaying these errors. We need an upper limit on 168 * the number of errors, so we arbitrarily choose 20. 169 */ 170 171 #define ERRMAX 20 172 173 static struct errs errs[ERRMAX]; 174 static int errcnt; 175 static char err_toomany[] = " too many errors occurred"; 176 static char err_listem[] = 177 " Many errors occurred. Press `e' to display the list of errors."; 178 179 /* These macros get used to reset and log the errors */ 180 #define ERR_RESET errcnt = 0 181 #define ERROR(p, e) if (errcnt >= ERRMAX) \ 182 { \ 183 return(err_toomany); \ 184 } \ 185 else \ 186 { \ 187 errs[errcnt].arg = (p); \ 188 errs[errcnt++].errnum = (e); \ 189 } 190 191 /* 192 * err_string() - return an appropriate error string. This is what the 193 * command will return for displaying. If no errors were logged, then 194 * return NULL. The maximum length of the error string is defined by 195 * "STRMAX". 196 */ 197 198 #define STRMAX 80 199 200 char *err_string(void) 201 { 202 struct errs *errp; 203 int cnt = 0; 204 bool first = true; 205 int currerr = -1; 206 int stringlen; /* characters still available in "string" */ 207 static char string[STRMAX]; 208 209 /* if there are no errors, return NULL */ 210 if (errcnt == 0) 211 { 212 return(NULL); 213 } 214 215 /* sort the errors */ 216 qsort((char *)errs, errcnt, sizeof(struct errs), err_compar); 217 218 /* need a space at the front of the error string */ 219 string[0] = ' '; 220 string[1] = '\0'; 221 stringlen = STRMAX - 2; 222 223 /* loop thru the sorted list, building an error string */ 224 while (cnt < errcnt) 225 { 226 errp = &(errs[cnt++]); 227 if (errp->errnum != currerr) 228 { 229 if (currerr != -1) 230 { 231 if ((stringlen = str_adderr(string, stringlen, currerr)) < 2) 232 { 233 return(err_listem); 234 } 235 strcat(string, "; "); /* we know there's more */ 236 } 237 currerr = errp->errnum; 238 first = true; 239 } 240 if ((stringlen = str_addarg(string, stringlen, errp->arg, first)) ==0) 241 { 242 return(err_listem); 243 } 244 first = false; 245 } 246 247 /* add final message */ 248 stringlen = str_adderr(string, stringlen, currerr); 249 250 /* return the error string */ 251 return(stringlen == 0 ? err_listem : string); 252 } 253 254 /* 255 * str_adderr(str, len, err) - add an explanation of error "err" to 256 * the string "str". 257 */ 258 259 static int 260 str_adderr(char *str, int len, int err) 261 { 262 const char *msg; 263 int msglen; 264 265 msg = err == 0 ? "Not a number" : strerror(err); 266 msglen = strlen(msg) + 2; 267 if (len <= msglen) 268 { 269 return(0); 270 } 271 strcat(str, ": "); 272 strcat(str, msg); 273 return(len - msglen); 274 } 275 276 /* 277 * str_addarg(str, len, arg, first) - add the string argument "arg" to 278 * the string "str". This is the first in the group when "first" 279 * is set (indicating that a comma should NOT be added to the front). 280 */ 281 282 static int 283 str_addarg(char str[], int len, char arg[], bool first) 284 { 285 int arglen; 286 287 arglen = strlen(arg); 288 if (!first) 289 { 290 arglen += 2; 291 } 292 if (len <= arglen) 293 { 294 return(0); 295 } 296 if (!first) 297 { 298 strcat(str, ", "); 299 } 300 strcat(str, arg); 301 return(len - arglen); 302 } 303 304 /* 305 * err_compar(p1, p2) - comparison routine used by "qsort" 306 * for sorting errors. 307 */ 308 309 static int 310 err_compar(const void *p1, const void *p2) 311 { 312 int result; 313 const struct errs * const g1 = (const struct errs * const)p1; 314 const struct errs * const g2 = (const struct errs * const)p2; 315 316 317 318 if ((result = g1->errnum - g2->errnum) == 0) 319 { 320 return(strcmp(g1->arg, g2->arg)); 321 } 322 return(result); 323 } 324 325 /* 326 * error_count() - return the number of errors currently logged. 327 */ 328 329 int 330 error_count(void) 331 { 332 return(errcnt); 333 } 334 335 /* 336 * show_errors() - display on stdout the current log of errors. 337 */ 338 339 void 340 show_errors(void) 341 { 342 int cnt = 0; 343 struct errs *errp = errs; 344 345 printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); 346 while (cnt++ < errcnt) 347 { 348 printf("%5s: %s\n", errp->arg, 349 errp->errnum == 0 ? "Not a number" : strerror(errp->errnum)); 350 errp++; 351 } 352 } 353 354 static char no_proc_specified[] = " no processes specified"; 355 static char invalid_signal_number[] = " invalid_signal_number"; 356 static char bad_signal_name[] = " bad signal name"; 357 static char bad_pri_value[] = " bad priority value"; 358 359 /* 360 * kill_procs(str) - send signals to processes, much like the "kill" 361 * command does; invoked in response to 'k'. 362 */ 363 364 char * 365 kill_procs(char *str) 366 { 367 char *nptr; 368 int signum = SIGTERM; /* default */ 369 int procnum; 370 struct sigdesc *sigp; 371 int uid; 372 373 /* reset error array */ 374 ERR_RESET; 375 376 /* remember our uid */ 377 uid = getuid(); 378 379 /* skip over leading white space */ 380 while (isspace(*str)) str++; 381 382 if (str[0] == '-') 383 { 384 /* explicit signal specified */ 385 if ((nptr = next_field(str)) == NULL) 386 { 387 return(no_proc_specified); 388 } 389 390 if (isdigit(str[1])) 391 { 392 scanint(str + 1, &signum); 393 if (signum <= 0 || signum >= NSIG) 394 { 395 return(invalid_signal_number); 396 } 397 } 398 else 399 { 400 /* translate the name into a number */ 401 for (sigp = sigdesc; sigp->name != NULL; sigp++) 402 { 403 if (strcmp(sigp->name, str + 1) == 0) 404 { 405 signum = sigp->number; 406 break; 407 } 408 } 409 410 /* was it ever found */ 411 if (sigp->name == NULL) 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 /* check process owner if we're not root */ 430 if (uid && (uid != proc_owner(procnum))) 431 { 432 ERROR(str, EACCES); 433 } 434 /* go in for the kill */ 435 else if (kill(procnum, signum) == -1) 436 { 437 /* chalk up an error */ 438 ERROR(str, errno); 439 } 440 } 441 } while ((str = next_field(str)) != NULL); 442 443 /* return appropriate error string */ 444 return(err_string()); 445 } 446 447 /* 448 * renice_procs(str) - change the "nice" of processes, much like the 449 * "renice" command does; invoked in response to 'r'. 450 */ 451 452 char * 453 renice_procs(char *str) 454 { 455 char negate; 456 int prio; 457 int procnum; 458 int uid; 459 460 ERR_RESET; 461 uid = getuid(); 462 463 /* allow for negative priority values */ 464 if ((negate = (*str == '-')) != 0) 465 { 466 /* move past the minus sign */ 467 str++; 468 } 469 470 /* use procnum as a temporary holding place and get the number */ 471 procnum = scanint(str, &prio); 472 473 /* negate if necessary */ 474 if (negate) 475 { 476 prio = -prio; 477 } 478 479 /* check for validity */ 480 if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 481 { 482 return(bad_pri_value); 483 } 484 485 /* move to the first process number */ 486 if ((str = next_field(str)) == NULL) 487 { 488 return(no_proc_specified); 489 } 490 491 /* loop thru the process numbers, renicing each one */ 492 do 493 { 494 if (scanint(str, &procnum) == -1) 495 { 496 ERROR(str, 0); 497 } 498 499 /* check process owner if we're not root */ 500 else if (uid && (uid != proc_owner(procnum))) 501 { 502 ERROR(str, EACCES); 503 } 504 else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) 505 { 506 ERROR(str, errno); 507 } 508 } while ((str = next_field(str)) != NULL); 509 510 /* return appropriate error string */ 511 return(err_string()); 512 } 513 514