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