1 1.1 christos /* 2 1.1 christos * Copyright (c) 1984 through 2008, William LeFebvre 3 1.1 christos * All rights reserved. 4 1.1 christos * 5 1.1 christos * Redistribution and use in source and binary forms, with or without 6 1.1 christos * modification, are permitted provided that the following conditions are met: 7 1.1 christos * 8 1.1 christos * * Redistributions of source code must retain the above copyright 9 1.1 christos * notice, this list of conditions and the following disclaimer. 10 1.1 christos * 11 1.1 christos * * Redistributions in binary form must reproduce the above 12 1.1 christos * copyright notice, this list of conditions and the following disclaimer 13 1.1 christos * in the documentation and/or other materials provided with the 14 1.1 christos * distribution. 15 1.1 christos * 16 1.1 christos * * Neither the name of William LeFebvre nor the names of other 17 1.1 christos * contributors may be used to endorse or promote products derived from 18 1.1 christos * this software without specific prior written permission. 19 1.1 christos * 20 1.1 christos * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 1.1 christos * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 1.1 christos * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 1.1 christos * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 1.1 christos * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 1.1 christos * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 1.1 christos * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 1.1 christos * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 1.1 christos * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 1.1 christos * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 1.1 christos * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 1.1 christos */ 32 1.1 christos 33 1.1 christos /* 34 1.1 christos * Top users/processes display for Unix 35 1.1 christos * Version 3 36 1.1 christos */ 37 1.1 christos 38 1.1 christos /* 39 1.1 christos * This file contains the routines that implement some of the interactive 40 1.1 christos * mode commands. Note that some of the commands are implemented in-line 41 1.1 christos * in "main". This is necessary because they change the global state of 42 1.1 christos * "top" (i.e.: changing the number of processes to display). 43 1.1 christos */ 44 1.1 christos 45 1.1 christos #include "os.h" 46 1.1 christos #include <ctype.h> 47 1.1 christos #include <signal.h> 48 1.1 christos #include <stdarg.h> 49 1.1 christos #include <unistd.h> 50 1.1 christos #include <color.h> 51 1.1 christos #include <errno.h> 52 1.1 christos #ifdef HAVE_SYS_RESOURCE_H 53 1.1 christos #include <sys/resource.h> 54 1.1 christos #endif 55 1.1 christos 56 1.1 christos #if defined(HAVE_DECL_SYS_SIGLIST) & defined(HAVE_STRCASECMP) 57 1.1 christos #define USE_SYS_SIGLIST 58 1.1 christos #endif 59 1.1 christos 60 1.1 christos #ifdef USE_SYS_SIGLIST 61 1.1 christos extern const char * const sys_siglist[]; 62 1.1 christos extern const char * const sys_signame[]; 63 1.1 christos #else 64 1.1 christos #include "sigdesc.h" /* generated automatically */ 65 1.1 christos #endif 66 1.1 christos #include "top.h" 67 1.1 christos #include "machine.h" 68 1.1 christos #include "globalstate.h" 69 1.1 christos #include "boolean.h" 70 1.1 christos #include "color.h" 71 1.1 christos #include "commands.h" 72 1.1 christos #include "display.h" 73 1.1 christos #include "screen.h" 74 1.1 christos #include "username.h" 75 1.1 christos #include "utils.h" 76 1.1 christos #include "version.h" 77 1.1 christos 78 1.1 christos extern char *copyright; 79 1.1 christos 80 1.1 christos typedef struct command { 81 1.1 christos int ch; 82 1.1 christos int (*cmd_func)(globalstate *); 83 1.6 christos const char *help; 84 1.1 christos } command; 85 1.1 christos 86 1.1 christos /* 87 1.1 christos * Some of the commands make system calls that could generate errors. 88 1.1 christos * These errors are collected up in an array of structures for later 89 1.1 christos * contemplation and display. Such routines return a string containing an 90 1.1 christos * error message, or NULL if no errors occurred. We need an upper limit on 91 1.1 christos * the number of errors, so we arbitrarily choose 20. 92 1.1 christos */ 93 1.1 christos 94 1.1 christos #define ERRMAX 20 95 1.1 christos 96 1.1 christos struct errs /* structure for a system-call error */ 97 1.1 christos { 98 1.1 christos int errnum; /* value of errno (that is, the actual error) */ 99 1.1 christos char *arg; /* argument that caused the error */ 100 1.1 christos }; 101 1.1 christos 102 1.1 christos static struct errs errs[ERRMAX]; 103 1.1 christos static int errcnt; 104 1.1 christos 105 1.1 christos /* These macros get used to reset and log the errors */ 106 1.1 christos #define ERR_RESET errcnt = 0 107 1.1 christos #define ERROR(p, e) if (errcnt < ERRMAX) \ 108 1.1 christos { \ 109 1.1 christos errs[errcnt].arg = (p); \ 110 1.1 christos errs[errcnt++].errnum = (e); \ 111 1.1 christos } 112 1.1 christos 113 1.1 christos /* 114 1.1 christos * err_compar(p1, p2) - comparison routine used by "qsort" 115 1.1 christos * for sorting errors. 116 1.1 christos */ 117 1.1 christos 118 1.6 christos static int 119 1.1 christos err_compar(const void *p1, const void *p2) 120 1.1 christos 121 1.1 christos { 122 1.1 christos register int result; 123 1.1 christos 124 1.6 christos if ((result = ((const struct errs *)p1)->errnum - 125 1.6 christos ((const struct errs *)p2)->errnum) == 0) 126 1.1 christos { 127 1.6 christos return(strcmp(((const struct errs *)p1)->arg, 128 1.6 christos ((const struct errs *)p2)->arg)); 129 1.1 christos } 130 1.1 christos return(result); 131 1.1 christos } 132 1.1 christos 133 1.1 christos /* 134 1.1 christos * str_adderr(str, len, err) - add an explanation of error "err" to 135 1.1 christos * the string "str" without overflowing length "len". return 136 1.1 christos * number of characters remaining in str, or 0 if overflowed. 137 1.1 christos */ 138 1.1 christos 139 1.6 christos static int 140 1.1 christos str_adderr(char *str, int len, int err) 141 1.1 christos 142 1.1 christos { 143 1.6 christos register const char *msg; 144 1.1 christos register int msglen; 145 1.1 christos 146 1.1 christos msg = err == 0 ? "Not a number" : errmsg(err); 147 1.1 christos msglen = strlen(msg) + 2; 148 1.1 christos if (len <= msglen) 149 1.1 christos { 150 1.1 christos return(0); 151 1.1 christos } 152 1.1 christos (void) strcat(str, ": "); 153 1.1 christos (void) strcat(str, msg); 154 1.1 christos return(len - msglen); 155 1.1 christos } 156 1.1 christos 157 1.1 christos /* 158 1.1 christos * str_addarg(str, len, arg, first) - add the string argument "arg" to 159 1.1 christos * the string "str" without overflowing length "len". This is the 160 1.1 christos * first in the group when "first" is set (indicating that a comma 161 1.1 christos * should NOT be added to the front). Return number of characters 162 1.1 christos * remaining in str, or 0 if overflowed. 163 1.1 christos */ 164 1.1 christos 165 1.6 christos static int 166 1.1 christos str_addarg(char *str, int len, char *arg, int first) 167 1.1 christos 168 1.1 christos { 169 1.1 christos register int arglen; 170 1.1 christos 171 1.1 christos arglen = strlen(arg); 172 1.1 christos if (!first) 173 1.1 christos { 174 1.1 christos arglen += 2; 175 1.1 christos } 176 1.1 christos if (len <= arglen) 177 1.1 christos { 178 1.1 christos return(0); 179 1.1 christos } 180 1.1 christos if (!first) 181 1.1 christos { 182 1.1 christos (void) strcat(str, ", "); 183 1.1 christos } 184 1.1 christos (void) strcat(str, arg); 185 1.1 christos return(len - arglen); 186 1.1 christos } 187 1.1 christos 188 1.1 christos /* 189 1.1 christos * void err_string() 190 1.1 christos * 191 1.1 christos * Use message_error to log errors in the errs array. This function 192 1.1 christos * will combine identical errors to make the message short, but if 193 1.1 christos * there is more than one type of error it will call message_error 194 1.1 christos * for each one. 195 1.1 christos */ 196 1.1 christos 197 1.1 christos #define STRMAX 80 198 1.1 christos 199 1.6 christos static void 200 1.6 christos err_string(void) 201 1.1 christos 202 1.1 christos { 203 1.1 christos register struct errs *errp; 204 1.1 christos register int cnt = 0; 205 1.1 christos register int first = Yes; 206 1.1 christos register int currerr = -1; 207 1.1 christos int stringlen = 0; /* characters still available in "string" */ 208 1.1 christos char string[STRMAX]; 209 1.1 christos 210 1.1 christos /* if there are no errors, our job is easy */ 211 1.1 christos if (errcnt == 0) 212 1.1 christos { 213 1.1 christos return; 214 1.1 christos } 215 1.1 christos 216 1.1 christos /* sort the errors */ 217 1.1 christos qsort((char *)errs, errcnt, sizeof(struct errs), err_compar); 218 1.1 christos 219 1.1 christos /* initialize the buffer (probably not necessary) */ 220 1.1 christos string[0] = '\0'; 221 1.1 christos stringlen = STRMAX - 1; 222 1.1 christos 223 1.1 christos /* loop thru the sorted list, logging errors */ 224 1.1 christos while (cnt < errcnt) 225 1.1 christos { 226 1.1 christos /* point to the current error */ 227 1.1 christos errp = &(errs[cnt++]); 228 1.1 christos 229 1.1 christos /* note that on overflow "stringlen" will become 0 and all 230 1.1 christos subsequent calls to str_addarg or str_adderr will return 0 */ 231 1.1 christos 232 1.1 christos /* if the error number is different then add the error string */ 233 1.1 christos if (errp->errnum != currerr) 234 1.1 christos { 235 1.1 christos if (currerr != -1) 236 1.1 christos { 237 1.1 christos /* add error string and log the error */ 238 1.1 christos stringlen = str_adderr(string, stringlen, currerr); 239 1.1 christos message_error(" %s", string); 240 1.1 christos 241 1.1 christos } 242 1.1 christos /* reset the buffer */ 243 1.1 christos string[0] = '\0'; 244 1.1 christos stringlen = STRMAX - 1; 245 1.1 christos 246 1.1 christos /* move to next error num */ 247 1.1 christos currerr = errp->errnum; 248 1.1 christos first = Yes; 249 1.1 christos } 250 1.1 christos 251 1.1 christos /* add this arg */ 252 1.1 christos stringlen = str_addarg(string, stringlen, errp->arg, first); 253 1.1 christos first = No; 254 1.1 christos } 255 1.1 christos 256 1.1 christos /* add final message */ 257 1.1 christos stringlen = str_adderr(string, stringlen, currerr); 258 1.1 christos 259 1.1 christos /* write the error string */ 260 1.1 christos message_error(" %s", string); 261 1.1 christos } 262 1.1 christos 263 1.1 christos /* 264 1.1 christos * Utility routines that help with some of the commands. 265 1.1 christos */ 266 1.1 christos 267 1.6 christos static char * 268 1.1 christos next_field(char *str) 269 1.1 christos 270 1.1 christos 271 1.1 christos { 272 1.1 christos if ((str = strchr(str, ' ')) == NULL) 273 1.1 christos { 274 1.1 christos return(NULL); 275 1.1 christos } 276 1.1 christos *str = '\0'; 277 1.1 christos while (*++str == ' ') /* loop */; 278 1.1 christos 279 1.1 christos /* if there is nothing left of the string, return NULL */ 280 1.1 christos /* This fix is dedicated to Greg Earle */ 281 1.1 christos return(*str == '\0' ? NULL : str); 282 1.1 christos } 283 1.1 christos 284 1.6 christos static int 285 1.1 christos scanint(char *str, int *intp) 286 1.1 christos 287 1.1 christos { 288 1.1 christos register int val = 0; 289 1.1 christos register int ch; 290 1.1 christos 291 1.1 christos /* if there is nothing left of the string, flag it as an error */ 292 1.1 christos /* This fix is dedicated to Greg Earle */ 293 1.1 christos if (*str == '\0') 294 1.1 christos { 295 1.1 christos return(-1); 296 1.1 christos } 297 1.1 christos 298 1.1 christos while ((ch = *str++) != '\0') 299 1.1 christos { 300 1.1 christos if (isdigit(ch)) 301 1.1 christos { 302 1.1 christos val = val * 10 + (ch - '0'); 303 1.1 christos } 304 1.1 christos else if (isspace(ch)) 305 1.1 christos { 306 1.1 christos break; 307 1.1 christos } 308 1.1 christos else 309 1.1 christos { 310 1.1 christos return(-1); 311 1.1 christos } 312 1.1 christos } 313 1.1 christos *intp = val; 314 1.1 christos return(0); 315 1.1 christos } 316 1.1 christos 317 1.6 christos #ifdef notdef 318 1.1 christos /* 319 1.1 christos * error_count() - return the number of errors currently logged. 320 1.1 christos */ 321 1.1 christos 322 1.6 christos static int 323 1.6 christos error_count(void) 324 1.1 christos 325 1.1 christos { 326 1.1 christos return(errcnt); 327 1.1 christos } 328 1.1 christos 329 1.1 christos /* 330 1.1 christos * show_errors() - display on stdout the current log of errors. 331 1.1 christos */ 332 1.1 christos 333 1.6 christos static void 334 1.6 christos show_errors(void) 335 1.1 christos 336 1.1 christos { 337 1.1 christos register int cnt = 0; 338 1.1 christos register struct errs *errp = errs; 339 1.1 christos 340 1.1 christos printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); 341 1.1 christos while (cnt++ < errcnt) 342 1.1 christos { 343 1.1 christos printf("%5s: %s\n", errp->arg, 344 1.1 christos errp->errnum == 0 ? "Not a number" : errmsg(errp->errnum)); 345 1.1 christos errp++; 346 1.1 christos } 347 1.1 christos } 348 1.6 christos #endif 349 1.1 christos 350 1.1 christos /* 351 1.1 christos * kill_procs(str) - send signals to processes, much like the "kill" 352 1.1 christos * command does; invoked in response to 'k'. 353 1.1 christos */ 354 1.1 christos 355 1.6 christos static void 356 1.1 christos kill_procs(char *str) 357 1.1 christos 358 1.1 christos { 359 1.1 christos register char *nptr; 360 1.1 christos int signum = SIGTERM; /* default */ 361 1.1 christos int procnum; 362 1.1 christos int uid; 363 1.1 christos int owner; 364 1.1 christos #ifndef USE_SYS_SIGLIST 365 1.1 christos struct sigdesc *sigp; 366 1.1 christos #endif 367 1.1 christos 368 1.1 christos /* reset error array */ 369 1.1 christos ERR_RESET; 370 1.1 christos 371 1.1 christos /* remember our uid */ 372 1.1 christos uid = getuid(); 373 1.1 christos 374 1.1 christos /* skip over leading white space */ 375 1.1 christos while (isspace((int)*str)) str++; 376 1.1 christos 377 1.1 christos if (str[0] == '-') 378 1.1 christos { 379 1.1 christos /* explicit signal specified */ 380 1.1 christos if ((nptr = next_field(str)) == NULL) 381 1.1 christos { 382 1.1 christos message_error(" kill: no processes specified"); 383 1.1 christos return; 384 1.1 christos } 385 1.1 christos 386 1.1 christos str++; 387 1.1 christos if (isdigit((int)str[0])) 388 1.1 christos { 389 1.1 christos (void) scanint(str, &signum); 390 1.1 christos if (signum <= 0 || signum >= NSIG) 391 1.1 christos { 392 1.1 christos message_error(" kill: invalid signal number"); 393 1.1 christos return; 394 1.1 christos } 395 1.1 christos } 396 1.1 christos else 397 1.1 christos { 398 1.1 christos /* translate the name into a number */ 399 1.1 christos #ifdef USE_SYS_SIGLIST 400 1.1 christos for (signum = 1; signum < NSIG; signum++) 401 1.1 christos { 402 1.1 christos if (strcasecmp(sys_signame[signum], str) == 0) 403 1.1 christos { 404 1.1 christos break; 405 1.1 christos } 406 1.1 christos } 407 1.1 christos if (signum == NSIG) 408 1.1 christos { 409 1.1 christos message_error(" kill: bad signal name"); 410 1.1 christos return; 411 1.1 christos } 412 1.1 christos #else 413 1.1 christos for (sigp = sigdesc; sigp->name != NULL; sigp++) 414 1.1 christos { 415 1.1 christos #ifdef HAVE_STRCASECMP 416 1.1 christos if (strcasecmp(sigp->name, str) == 0) 417 1.1 christos #else 418 1.1 christos if (strcmp(sigp->name, str) == 0) 419 1.1 christos #endif 420 1.1 christos { 421 1.1 christos signum = sigp->number; 422 1.1 christos break; 423 1.1 christos } 424 1.1 christos } 425 1.1 christos 426 1.1 christos /* was it ever found */ 427 1.1 christos if (sigp->name == NULL) 428 1.1 christos { 429 1.1 christos message_error(" kill: bad signal name"); 430 1.1 christos return; 431 1.1 christos } 432 1.1 christos #endif 433 1.1 christos } 434 1.1 christos /* put the new pointer in place */ 435 1.1 christos str = nptr; 436 1.1 christos } 437 1.1 christos 438 1.1 christos /* loop thru the string, killing processes */ 439 1.1 christos do 440 1.1 christos { 441 1.1 christos if (scanint(str, &procnum) == -1) 442 1.1 christos { 443 1.1 christos ERROR(str, 0); 444 1.1 christos } 445 1.1 christos else 446 1.1 christos { 447 1.1 christos /* check process owner if we're not root */ 448 1.1 christos owner = proc_owner(procnum); 449 1.1 christos if (uid && (uid != owner)) 450 1.1 christos { 451 1.1 christos ERROR(str, owner == -1 ? ESRCH : EACCES); 452 1.1 christos } 453 1.1 christos /* go in for the kill */ 454 1.1 christos else if (kill(procnum, signum) == -1) 455 1.1 christos { 456 1.1 christos /* chalk up an error */ 457 1.1 christos ERROR(str, errno); 458 1.1 christos } 459 1.1 christos } 460 1.1 christos } while ((str = next_field(str)) != NULL); 461 1.1 christos 462 1.1 christos /* process errors */ 463 1.1 christos err_string(); 464 1.1 christos } 465 1.1 christos 466 1.1 christos /* 467 1.1 christos * renice_procs(str) - change the "nice" of processes, much like the 468 1.1 christos * "renice" command does; invoked in response to 'r'. 469 1.1 christos */ 470 1.1 christos 471 1.6 christos static void 472 1.1 christos renice_procs(char *str) 473 1.1 christos 474 1.1 christos { 475 1.1 christos register char negate; 476 1.1 christos int prio; 477 1.1 christos int procnum; 478 1.1 christos int uid; 479 1.1 christos 480 1.1 christos ERR_RESET; 481 1.1 christos uid = getuid(); 482 1.1 christos 483 1.1 christos /* allow for negative priority values */ 484 1.1 christos if ((negate = (*str == '-')) != 0) 485 1.1 christos { 486 1.1 christos /* move past the minus sign */ 487 1.1 christos str++; 488 1.1 christos } 489 1.1 christos 490 1.1 christos /* use procnum as a temporary holding place and get the number */ 491 1.1 christos procnum = scanint(str, &prio); 492 1.1 christos 493 1.1 christos /* negate if necessary */ 494 1.1 christos if (negate) 495 1.1 christos { 496 1.1 christos prio = -prio; 497 1.1 christos } 498 1.1 christos 499 1.1 christos #if defined(PRIO_MIN) && defined(PRIO_MAX) 500 1.1 christos /* check for validity */ 501 1.1 christos if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 502 1.1 christos { 503 1.1 christos message_error(" renice: bad priority value"); 504 1.2 christos return; 505 1.1 christos } 506 1.1 christos #endif 507 1.1 christos 508 1.1 christos /* move to the first process number */ 509 1.1 christos if ((str = next_field(str)) == NULL) 510 1.1 christos { 511 1.8 snj message_error(" renice: no processes specified"); 512 1.2 christos return; 513 1.1 christos } 514 1.1 christos 515 1.1 christos #ifdef HAVE_SETPRIORITY 516 1.1 christos /* loop thru the process numbers, renicing each one */ 517 1.1 christos do 518 1.1 christos { 519 1.1 christos if (scanint(str, &procnum) == -1) 520 1.1 christos { 521 1.1 christos ERROR(str, 0); 522 1.1 christos } 523 1.1 christos 524 1.1 christos /* check process owner if we're not root */ 525 1.1 christos else if (uid && (uid != proc_owner(procnum))) 526 1.1 christos { 527 1.1 christos ERROR(str, EACCES); 528 1.1 christos } 529 1.1 christos else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) 530 1.1 christos { 531 1.1 christos ERROR(str, errno); 532 1.1 christos } 533 1.1 christos } while ((str = next_field(str)) != NULL); 534 1.1 christos err_string(); 535 1.1 christos #else 536 1.1 christos message_error(" renice operation not supported"); 537 1.1 christos #endif 538 1.1 christos } 539 1.1 christos 540 1.1 christos /* COMMAND ROUTINES */ 541 1.1 christos 542 1.1 christos /* 543 1.1 christos * Each command routine is called by command_process and is passed a 544 1.1 christos * pointer to the current global state. Command routines are free 545 1.1 christos * to change anything in the global state, although changes to the 546 1.1 christos * statics structure are discouraged. Whatever a command routine 547 1.1 christos * returns will be returned by command_process. 548 1.1 christos */ 549 1.1 christos 550 1.6 christos static void 551 1.1 christos cmd_quit(globalstate *gstate) 552 1.1 christos 553 1.1 christos { 554 1.1 christos quit(EX_OK); 555 1.1 christos /*NOTREACHED*/ 556 1.1 christos } 557 1.1 christos 558 1.6 christos static int 559 1.1 christos cmd_update(globalstate *gstate) 560 1.1 christos 561 1.1 christos { 562 1.1 christos /* go home for visual feedback */ 563 1.1 christos screen_home(); 564 1.1 christos fflush(stdout); 565 1.1 christos message_expire(); 566 1.1 christos return CMD_REFRESH; 567 1.1 christos } 568 1.1 christos 569 1.6 christos static int 570 1.1 christos cmd_redraw(globalstate *gstate) 571 1.1 christos 572 1.1 christos { 573 1.1 christos gstate->fulldraw = Yes; 574 1.1 christos return CMD_REFRESH; 575 1.1 christos } 576 1.1 christos 577 1.6 christos static int 578 1.1 christos cmd_color(globalstate *gstate) 579 1.1 christos 580 1.1 christos { 581 1.1 christos gstate->use_color = color_activate(-1); 582 1.1 christos gstate->fulldraw = Yes; 583 1.1 christos return CMD_REFRESH; 584 1.1 christos } 585 1.1 christos 586 1.6 christos static int 587 1.1 christos cmd_number(globalstate *gstate) 588 1.1 christos 589 1.1 christos { 590 1.1 christos int newval; 591 1.1 christos char tmpbuf[20]; 592 1.1 christos 593 1.1 christos message_prompt("Number of processes to show: "); 594 1.1 christos newval = readline(tmpbuf, 8, Yes); 595 1.1 christos if (newval > -1) 596 1.1 christos { 597 1.1 christos if (newval > gstate->max_topn) 598 1.1 christos { 599 1.1 christos message_error(" This terminal can only display %d processes", 600 1.1 christos gstate->max_topn); 601 1.1 christos } 602 1.1 christos 603 1.1 christos if (newval == 0) 604 1.1 christos { 605 1.1 christos /* inhibit the header */ 606 1.1 christos display_header(No); 607 1.1 christos } 608 1.1 christos 609 1.1 christos else if (gstate->topn == 0) 610 1.1 christos { 611 1.1 christos display_header(Yes); 612 1.1 christos } 613 1.1 christos 614 1.1 christos gstate->topn = newval; 615 1.1 christos } 616 1.1 christos return CMD_REFRESH; 617 1.1 christos } 618 1.1 christos 619 1.6 christos static int 620 1.1 christos cmd_delay(globalstate *gstate) 621 1.1 christos 622 1.1 christos { 623 1.3 christos double newval; 624 1.1 christos char tmpbuf[20]; 625 1.1 christos 626 1.1 christos message_prompt("Seconds to delay: "); 627 1.3 christos if (readline(tmpbuf, 8, No) > 0) 628 1.1 christos { 629 1.3 christos newval = atof(tmpbuf); 630 1.3 christos if (newval == 0 && getuid() != 0) 631 1.1 christos { 632 1.1 christos gstate->delay = 1; 633 1.1 christos } 634 1.3 christos else 635 1.3 christos { 636 1.3 christos gstate->delay = newval; 637 1.3 christos } 638 1.1 christos } 639 1.1 christos return CMD_REFRESH; 640 1.1 christos } 641 1.1 christos 642 1.6 christos static int 643 1.1 christos cmd_idle(globalstate *gstate) 644 1.1 christos 645 1.1 christos { 646 1.1 christos gstate->pselect.idle = !gstate->pselect.idle; 647 1.1 christos message_error(" %sisplaying idle processes.", 648 1.1 christos gstate->pselect.idle ? "D" : "Not d"); 649 1.1 christos return CMD_REFRESH; 650 1.1 christos } 651 1.1 christos 652 1.6 christos static int 653 1.1 christos cmd_displays(globalstate *gstate) 654 1.1 christos 655 1.1 christos { 656 1.1 christos int i; 657 1.1 christos char tmpbuf[20]; 658 1.1 christos 659 1.1 christos message_prompt("Displays to show (currently %s): ", 660 1.1 christos gstate->displays == -1 ? "infinite" : 661 1.1 christos itoa(gstate->displays)); 662 1.1 christos 663 1.1 christos if ((i = readline(tmpbuf, 10, Yes)) > 0) 664 1.1 christos { 665 1.1 christos gstate->displays = i; 666 1.1 christos } 667 1.1 christos else if (i == 0) 668 1.1 christos { 669 1.1 christos quit(EX_OK); 670 1.1 christos /*NOTREACHED*/ 671 1.1 christos } 672 1.1 christos return CMD_OK; 673 1.1 christos } 674 1.1 christos 675 1.6 christos static int 676 1.1 christos cmd_cmdline(globalstate *gstate) 677 1.1 christos 678 1.1 christos { 679 1.1 christos if (gstate->statics->flags.fullcmds) 680 1.1 christos { 681 1.1 christos gstate->pselect.fullcmd = !gstate->pselect.fullcmd; 682 1.1 christos message_error(" %sisplaying full command lines.", 683 1.1 christos gstate->pselect.fullcmd ? "D" : "Not d"); 684 1.1 christos return CMD_REFRESH; 685 1.1 christos } 686 1.1 christos message_error(" Full command display not supported."); 687 1.1 christos return CMD_OK; 688 1.1 christos } 689 1.1 christos 690 1.6 christos static int 691 1.1 christos cmd_order(globalstate *gstate) 692 1.1 christos 693 1.1 christos { 694 1.1 christos char tmpbuf[MAX_COLS]; 695 1.1 christos int i; 696 1.1 christos 697 1.1 christos if (gstate->statics->order_names != NULL) 698 1.1 christos { 699 1.1 christos message_prompt("Column to sort: "); 700 1.1 christos if (readline(tmpbuf, sizeof(tmpbuf), No) > 0) 701 1.1 christos { 702 1.1 christos if ((i = string_index(tmpbuf, gstate->statics->order_names)) == -1) 703 1.1 christos { 704 1.1 christos message_error(" Sort order \"%s\" not recognized", tmpbuf); 705 1.1 christos } 706 1.1 christos else 707 1.1 christos { 708 1.1 christos gstate->order_index = i; 709 1.1 christos return CMD_REFRESH; 710 1.1 christos } 711 1.1 christos } 712 1.1 christos } 713 1.1 christos return CMD_OK; 714 1.1 christos } 715 1.1 christos 716 1.6 christos static int 717 1.6 christos cmd_order_x(globalstate *gstate, const char *name, ...) 718 1.1 christos 719 1.1 christos { 720 1.1 christos va_list ap; 721 1.1 christos char *p; 722 1.6 christos const char **names; 723 1.1 christos int i; 724 1.1 christos 725 1.1 christos names = gstate->statics->order_names; 726 1.1 christos if (names != NULL) 727 1.1 christos { 728 1.1 christos if ((i = string_index(name, names)) == -1) 729 1.1 christos { 730 1.1 christos /* check the alternate list */ 731 1.1 christos va_start(ap, name); 732 1.1 christos p = va_arg(ap, char *); 733 1.1 christos while (p != NULL) 734 1.1 christos { 735 1.1 christos if ((i = string_index(p, names)) != -1) 736 1.1 christos { 737 1.1 christos gstate->order_index = i; 738 1.1 christos return CMD_REFRESH; 739 1.1 christos } 740 1.1 christos p = va_arg(ap, char *); 741 1.1 christos } 742 1.1 christos message_error(" Sort order not recognized"); 743 1.1 christos } 744 1.1 christos else 745 1.1 christos { 746 1.1 christos gstate->order_index = i; 747 1.1 christos return CMD_REFRESH; 748 1.1 christos } 749 1.1 christos } 750 1.1 christos return CMD_OK; 751 1.1 christos } 752 1.1 christos 753 1.6 christos static int 754 1.1 christos cmd_order_cpu(globalstate *gstate) 755 1.1 christos 756 1.1 christos { 757 1.1 christos return cmd_order_x(gstate, "cpu", NULL); 758 1.1 christos } 759 1.1 christos 760 1.6 christos static int 761 1.1 christos cmd_order_pid(globalstate *gstate) 762 1.1 christos 763 1.1 christos { 764 1.1 christos return cmd_order_x(gstate, "pid", NULL); 765 1.1 christos } 766 1.1 christos 767 1.6 christos static int 768 1.1 christos cmd_order_mem(globalstate *gstate) 769 1.1 christos 770 1.1 christos { 771 1.1 christos return cmd_order_x(gstate, "mem", "size", NULL); 772 1.1 christos } 773 1.1 christos 774 1.6 christos static int 775 1.1 christos cmd_order_time(globalstate *gstate) 776 1.1 christos 777 1.1 christos { 778 1.1 christos return cmd_order_x(gstate, "time"); 779 1.1 christos } 780 1.1 christos 781 1.1 christos #ifdef ENABLE_KILL 782 1.1 christos 783 1.6 christos static int 784 1.1 christos cmd_kill(globalstate *gstate) 785 1.1 christos 786 1.1 christos { 787 1.1 christos char tmpbuf[MAX_COLS]; 788 1.1 christos 789 1.1 christos message_prompt_plain("kill "); 790 1.1 christos if (readline(tmpbuf, sizeof(tmpbuf), No) > 0) 791 1.1 christos { 792 1.1 christos kill_procs(tmpbuf); 793 1.1 christos } 794 1.1 christos return CMD_OK; 795 1.1 christos } 796 1.1 christos 797 1.6 christos static int 798 1.1 christos cmd_renice(globalstate *gstate) 799 1.1 christos 800 1.1 christos { 801 1.1 christos char tmpbuf[MAX_COLS]; 802 1.1 christos 803 1.1 christos message_prompt_plain("renice "); 804 1.1 christos if (readline(tmpbuf, sizeof(tmpbuf), No) > 0) 805 1.1 christos { 806 1.1 christos renice_procs(tmpbuf); 807 1.1 christos } 808 1.1 christos return CMD_OK; 809 1.1 christos } 810 1.1 christos 811 1.1 christos #endif 812 1.1 christos 813 1.6 christos static int 814 1.4 christos cmd_pid(globalstate *gstate) 815 1.4 christos 816 1.4 christos { 817 1.4 christos char tmpbuf[MAX_COLS]; 818 1.4 christos 819 1.4 christos message_prompt_plain("select pid "); 820 1.4 christos gstate->pselect.pid = -1; 821 1.4 christos if (readline(tmpbuf, sizeof(tmpbuf), No) > 0) 822 1.4 christos { 823 1.4 christos int pid; 824 1.4 christos if (scanint(tmpbuf, &pid) == 0) 825 1.4 christos gstate->pselect.pid = pid; 826 1.4 christos } 827 1.4 christos return CMD_OK; 828 1.4 christos } 829 1.4 christos 830 1.6 christos static int 831 1.1 christos cmd_user(globalstate *gstate) 832 1.1 christos 833 1.1 christos { 834 1.1 christos char linebuf[MAX_COLS]; 835 1.1 christos int i; 836 1.1 christos int ret = CMD_OK; 837 1.1 christos 838 1.1 christos message_prompt("Username to show: "); 839 1.1 christos if (readline(linebuf, sizeof(linebuf), No) > 0) 840 1.1 christos { 841 1.1 christos if (linebuf[0] == '+' && 842 1.1 christos linebuf[1] == '\0') 843 1.1 christos { 844 1.1 christos gstate->pselect.uid = -1; 845 1.1 christos ret = CMD_REFRESH; 846 1.1 christos } 847 1.1 christos else if ((i = userid(linebuf)) == -1) 848 1.1 christos { 849 1.1 christos message_error(" %s: unknown user", linebuf); 850 1.1 christos } 851 1.1 christos else 852 1.1 christos { 853 1.1 christos gstate->pselect.uid = i; 854 1.1 christos ret = CMD_REFRESH; 855 1.1 christos } 856 1.1 christos } 857 1.1 christos return ret; 858 1.1 christos } 859 1.1 christos 860 1.6 christos static int 861 1.1 christos cmd_command(globalstate *gstate) 862 1.1 christos 863 1.1 christos { 864 1.1 christos char linebuf[MAX_COLS]; 865 1.1 christos 866 1.1 christos if (gstate->pselect.command != NULL) 867 1.1 christos { 868 1.1 christos free(gstate->pselect.command); 869 1.1 christos gstate->pselect.command = NULL; 870 1.1 christos } 871 1.1 christos 872 1.1 christos message_prompt("Command to show: "); 873 1.1 christos if (readline(linebuf, sizeof(linebuf), No) > 0) 874 1.1 christos { 875 1.1 christos if (linebuf[0] != '\0') 876 1.1 christos { 877 1.5 christos gstate->pselect.command = estrdup(linebuf); 878 1.1 christos } 879 1.1 christos } 880 1.1 christos return CMD_REFRESH; 881 1.1 christos } 882 1.1 christos 883 1.6 christos static int 884 1.1 christos cmd_useruid(globalstate *gstate) 885 1.1 christos 886 1.1 christos { 887 1.1 christos gstate->pselect.usernames = !gstate->pselect.usernames; 888 1.1 christos display_header(2); 889 1.1 christos return CMD_REFRESH; 890 1.1 christos } 891 1.1 christos 892 1.6 christos static int 893 1.1 christos cmd_mode(globalstate *gstate) 894 1.1 christos 895 1.1 christos { 896 1.1 christos if (gstate->statics->modemax <= 1) 897 1.1 christos { 898 1.1 christos return CMD_NA; 899 1.1 christos } 900 1.1 christos gstate->pselect.mode = (gstate->pselect.mode + 1) % gstate->statics->modemax; 901 1.1 christos display_header(2); 902 1.1 christos return CMD_REFRESH; 903 1.1 christos } 904 1.1 christos 905 1.6 christos static int 906 1.1 christos cmd_system(globalstate *gstate) 907 1.1 christos 908 1.1 christos { 909 1.1 christos gstate->pselect.system = !gstate->pselect.system; 910 1.1 christos display_header(2); 911 1.1 christos return CMD_REFRESH; 912 1.1 christos } 913 1.1 christos 914 1.6 christos static int 915 1.1 christos cmd_threads(globalstate *gstate) 916 1.1 christos 917 1.1 christos { 918 1.1 christos if (gstate->statics->flags.threads) 919 1.1 christos { 920 1.1 christos gstate->pselect.threads = !gstate->pselect.threads; 921 1.1 christos display_header(2); 922 1.1 christos return CMD_REFRESH; 923 1.1 christos } 924 1.1 christos return CMD_NA; 925 1.1 christos } 926 1.1 christos 927 1.6 christos static int 928 1.2 christos cmd_percpustates(globalstate *gstate) 929 1.2 christos { 930 1.2 christos gstate->percpustates = !gstate->percpustates; 931 1.2 christos gstate->fulldraw = Yes; 932 1.2 christos gstate->max_topn += display_setmulti(gstate->percpustates); 933 1.2 christos return CMD_REFRESH; 934 1.2 christos } 935 1.2 christos 936 1.2 christos 937 1.1 christos /* forward reference for cmd_help, as it needs to see the command_table */ 938 1.1 christos int cmd_help(globalstate *gstate); 939 1.1 christos 940 1.1 christos /* command table */ 941 1.1 christos command command_table[] = { 942 1.1 christos { '\014', cmd_redraw, "redraw screen" }, 943 1.1 christos { ' ', cmd_update, "update screen" }, 944 1.1 christos { '?', cmd_help, "help; show this text" }, 945 1.1 christos { 'h', cmd_help, NULL }, 946 1.7 sborrill { '1', cmd_percpustates, "toggle the display of cpu states per cpu" }, 947 1.1 christos { 'C', cmd_color, "toggle the use of color" }, 948 1.1 christos { 'H', cmd_threads, "toggle the display of individual threads" }, 949 1.1 christos { 't', cmd_threads, NULL }, 950 1.1 christos { 'M', cmd_order_mem, "sort by memory usage" }, 951 1.1 christos { 'N', cmd_order_pid, "sort by process id" }, 952 1.1 christos { 'P', cmd_order_cpu, "sort by CPU usage" }, 953 1.1 christos { 'S', cmd_system, "toggle the display of system processes" }, 954 1.1 christos { 'T', cmd_order_time, "sort by CPU time" }, 955 1.1 christos { 'U', cmd_useruid, "toggle the display of usernames or uids" }, 956 1.1 christos { 'c', cmd_command, "display processes by command name" }, 957 1.1 christos { 'd', cmd_displays, "change number of displays to show" }, 958 1.1 christos { 'f', cmd_cmdline, "toggle the display of full command paths" }, 959 1.1 christos { 'i', cmd_idle, "toggle the displaying of idle processes" }, 960 1.1 christos { 'I', cmd_idle, NULL }, 961 1.1 christos #ifdef ENABLE_KILL 962 1.1 christos { 'k', cmd_kill, "kill processes; send a signal to a list of processes" }, 963 1.1 christos #endif 964 1.1 christos { 'm', cmd_mode, "toggle between display modes" }, 965 1.1 christos { 'n', cmd_number, "change number of processes to display" }, 966 1.1 christos { '#', cmd_number, NULL }, 967 1.1 christos { 'o', cmd_order, "specify sort order (see below)" }, 968 1.4 christos { 'p', cmd_pid, "select a single pid" }, 969 1.1 christos { 'q', (int (*)(globalstate *))cmd_quit, "quit" }, 970 1.1 christos #ifdef ENABLE_KILL 971 1.1 christos { 'r', cmd_renice, "renice a process" }, 972 1.1 christos #endif 973 1.1 christos { 's', cmd_delay, "change number of seconds to delay between updates" }, 974 1.1 christos { 'u', cmd_user, "display processes for only one user (+ selects all users)" }, 975 1.1 christos { '\0', NULL, NULL }, 976 1.1 christos }; 977 1.1 christos 978 1.1 christos int 979 1.1 christos cmd_help(globalstate *gstate) 980 1.1 christos 981 1.1 christos { 982 1.1 christos command *c; 983 1.1 christos char buf[12]; 984 1.1 christos char *p; 985 1.6 christos const char *help; 986 1.1 christos 987 1.1 christos display_pagerstart(); 988 1.1 christos 989 1.1 christos display_pager("Top version %s, %s\n", version_string(), copyright); 990 1.1 christos display_pager("Platform module: %s\n\n", MODULE); 991 1.1 christos display_pager("A top users display for Unix\n\n"); 992 1.1 christos display_pager("These single-character commands are available:\n\n"); 993 1.1 christos 994 1.1 christos c = command_table; 995 1.1 christos while (c->cmd_func != NULL) 996 1.1 christos { 997 1.1 christos /* skip null help strings */ 998 1.1 christos if ((help = c->help) == NULL) 999 1.1 christos { 1000 1.1 christos continue; 1001 1.1 christos } 1002 1.1 christos 1003 1.1 christos /* translate character in to something readable */ 1004 1.1 christos if (c->ch < ' ') 1005 1.1 christos { 1006 1.1 christos buf[0] = '^'; 1007 1.1 christos buf[1] = c->ch + '@'; 1008 1.1 christos buf[2] = '\0'; 1009 1.1 christos } 1010 1.1 christos else if (c->ch == ' ') 1011 1.1 christos { 1012 1.1 christos strcpy(buf, "<sp>"); 1013 1.1 christos } 1014 1.1 christos else 1015 1.1 christos { 1016 1.1 christos buf[0] = c->ch; 1017 1.1 christos buf[1] = '\0'; 1018 1.1 christos } 1019 1.1 christos 1020 1.1 christos /* if the next command is the same, fold them onto one line */ 1021 1.1 christos if ((c+1)->cmd_func == c->cmd_func) 1022 1.1 christos { 1023 1.1 christos strcat(buf, " or "); 1024 1.1 christos p = buf + strlen(buf); 1025 1.1 christos *p++ = (c+1)->ch; 1026 1.1 christos *p = '\0'; 1027 1.1 christos c++; 1028 1.1 christos } 1029 1.1 christos 1030 1.1 christos display_pager("%-7s - %s\n", buf, help); 1031 1.1 christos c++; 1032 1.1 christos } 1033 1.1 christos 1034 1.1 christos display_pager("\nNot all commands are available on all systems.\n\n"); 1035 1.1 christos display_pager("Available sort orders: %s\n", gstate->order_namelist); 1036 1.1 christos display_pagerend(); 1037 1.1 christos gstate->fulldraw = Yes; 1038 1.1 christos return CMD_REFRESH; 1039 1.1 christos } 1040 1.1 christos 1041 1.1 christos /* 1042 1.1 christos * int command_process(globalstate *gstate, int cmd) 1043 1.1 christos * 1044 1.1 christos * Process the single-character command "cmd". The global state may 1045 1.1 christos * be modified by the command to alter the output. Returns CMD_ERROR 1046 1.1 christos * if there was a serious error that requires an immediate exit, CMD_OK 1047 1.1 christos * to indicate success, CMD_REFRESH to indicate that the screen needs 1048 1.1 christos * to be refreshed immediately, CMD_UNKNOWN when the command is not known, 1049 1.1 christos * and CMD_NA when the command is not available. Error messages for 1050 1.1 christos * CMD_NA and CMD_UNKNOWN must be handled by the caller. 1051 1.1 christos */ 1052 1.1 christos 1053 1.1 christos int 1054 1.1 christos command_process(globalstate *gstate, int cmd) 1055 1.1 christos 1056 1.1 christos { 1057 1.1 christos command *c; 1058 1.1 christos 1059 1.1 christos c = command_table; 1060 1.1 christos while (c->cmd_func != NULL) 1061 1.1 christos { 1062 1.1 christos if (c->ch == cmd) 1063 1.1 christos { 1064 1.1 christos return (c->cmd_func)(gstate); 1065 1.1 christos } 1066 1.1 christos c++; 1067 1.1 christos } 1068 1.1 christos 1069 1.1 christos return CMD_UNKNOWN; 1070 1.1 christos } 1071