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