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