1 /* $OpenBSD: commands.c,v 1.12 2003/06/20 16:53:15 deraadt Exp $ */ 2 3 /* 4 * Top users/processes display for Unix 5 * Version 3 6 * 7 * Copyright (c) 1984, 1989, William LeFebvre, Rice University 8 * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE AUTHOR OR HIS EMPLOYER BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /* 32 * This file contains the routines that implement some of the interactive 33 * mode commands. Note that some of the commands are implemented in-line 34 * in "main". This is necessary because they change the global state of 35 * "top" (i.e.: changing the number of processes to display). 36 */ 37 38 #include <sys/types.h> 39 #include <stdio.h> 40 #include <ctype.h> 41 #include <errno.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <signal.h> 45 #include <unistd.h> 46 #include <sys/time.h> 47 #include <sys/resource.h> 48 49 #include "top.h" 50 51 #include "sigdesc.h" /* generated automatically */ 52 #include "boolean.h" 53 #include "utils.h" 54 #include "machine.h" 55 56 static char *next_field(char *); 57 static int scanint(char *, int *); 58 static char *err_string(void); 59 static size_t str_adderr(char *, size_t, int); 60 static size_t str_addarg(char *, size_t, char *, int); 61 static int err_compar(const void *, const void *); 62 63 /* 64 * show_help() - display the help screen; invoked in response to 65 * either 'h' or '?'. 66 */ 67 void 68 show_help(void) 69 { 70 printf("Top version %s, %s\n", version_string(), copyright); 71 fputs("\n\n" 72 "A top users display for Unix\n" 73 "\n" 74 "These single-character commands are available:\n" 75 "\n" 76 "^L - redraw screen\n" 77 "q - quit\n" 78 "h or ? - help; show this text\n", stdout); 79 80 /* not all commands are available with overstrike terminals */ 81 if (overstrike) { 82 fputs("\n" 83 "Other commands are also available, but this terminal is not\n" 84 "sophisticated enough to handle those commands gracefully.\n\n", 85 stdout); 86 } else { 87 fputs( 88 "d - change number of displays to show\n" 89 "e - list errors generated by last \"kill\" or \"renice\" command\n" 90 "i - toggle the displaying of idle processes\n" 91 "I - same as 'i'\n" 92 "k - kill processes; send a signal to a list of processes\n" 93 "n or # - change number of processes to display\n", stdout); 94 #ifdef ORDER 95 fputs( 96 "o - specify sort order (size, res, cpu, time)\n", 97 stdout); 98 #endif 99 fputs( 100 "r - renice a process\n" 101 "s - change number of seconds to delay between updates\n" 102 "u - display processes for only one user (+ selects all users)\n" 103 "\n\n", stdout); 104 } 105 } 106 107 /* 108 * Utility routines that help with some of the commands. 109 */ 110 static char * 111 next_field(char *str) 112 { 113 if ((str = strchr(str, ' ')) == NULL) 114 return (NULL); 115 116 *str = '\0'; 117 while (*++str == ' ') /* loop */ 118 ; 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 125 static int 126 scanint(char *str, int *intp) 127 { 128 int val = 0; 129 char ch; 130 131 /* if there is nothing left of the string, flag it as an error */ 132 /* This fix is dedicated to Greg Earle */ 133 if (*str == '\0') 134 return (-1); 135 136 while ((ch = *str++) != '\0') { 137 if (isdigit(ch)) 138 val = val * 10 + (ch - '0'); 139 else if (isspace(ch)) 140 break; 141 else 142 return (-1); 143 } 144 *intp = val; 145 return (0); 146 } 147 148 /* 149 * Some of the commands make system calls that could generate errors. 150 * These errors are collected up in an array of structures for later 151 * contemplation and display. Such routines return a string containing an 152 * error message, or NULL if no errors occurred. The next few routines are 153 * for manipulating and displaying these errors. We need an upper limit on 154 * the number of errors, so we arbitrarily choose 20. 155 */ 156 157 #define ERRMAX 20 158 159 struct errs { /* structure for a system-call error */ 160 int errno; /* value of errno (that is, the actual error) */ 161 char *arg; /* argument that caused the error */ 162 }; 163 164 static struct errs errs[ERRMAX]; 165 static int errcnt; 166 static char *err_toomany = " too many errors occurred"; 167 static char *err_listem = 168 " Many errors occurred. Press `e' to display the list of errors."; 169 170 /* These macros get used to reset and log the errors */ 171 #define ERR_RESET errcnt = 0 172 #define ERROR(p, e) \ 173 if (errcnt >= ERRMAX) { \ 174 return(err_toomany); \ 175 } else { \ 176 errs[errcnt].arg = (p); \ 177 errs[errcnt++].errno = (e); \ 178 } 179 180 #define STRMAX 80 181 182 /* 183 * err_string() - return an appropriate error string. This is what the 184 * command will return for displaying. If no errors were logged, then 185 * return NULL. The maximum length of the error string is defined by 186 * "STRMAX". 187 */ 188 static char * 189 err_string(void) 190 { 191 int cnt = 0, first = Yes, currerr = -1; 192 static char string[STRMAX]; 193 struct errs *errp; 194 195 /* if there are no errors, return NULL */ 196 if (errcnt == 0) 197 return (NULL); 198 199 /* sort the errors */ 200 qsort(errs, errcnt, sizeof(struct errs), err_compar); 201 202 /* need a space at the front of the error string */ 203 string[0] = ' '; 204 string[1] = '\0'; 205 206 /* loop thru the sorted list, building an error string */ 207 while (cnt < errcnt) { 208 errp = &(errs[cnt++]); 209 if (errp->errno != currerr) { 210 if (currerr != -1) { 211 if (str_adderr(string, sizeof string, currerr) > 212 sizeof string - 2) 213 return (err_listem); 214 215 /* we know there's more */ 216 (void) strlcat(string, "; ", sizeof string); 217 } 218 currerr = errp->errno; 219 first = Yes; 220 } 221 if (str_addarg(string, sizeof string, errp->arg, first) >= 222 sizeof string) 223 return (err_listem); 224 225 first = No; 226 } 227 228 /* add final message */ 229 if (str_adderr(string, sizeof string, currerr) >= sizeof string) 230 return (err_listem); 231 232 /* return the error string */ 233 return (string); 234 } 235 236 /* 237 * str_adderr(str, len, err) - add an explanation of error "err" to 238 * the string "str". 239 */ 240 static size_t 241 str_adderr(char *str, size_t len, int err) 242 { 243 size_t msglen; 244 char *msg; 245 246 msg = err == 0 ? "Not a number" : strerror(err); 247 248 if ((msglen = strlcat(str, ": ", len)) >= len) 249 return (msglen); 250 251 return (strlcat(str, msg, len)); 252 } 253 254 /* 255 * str_addarg(str, len, arg, first) - add the string argument "arg" to 256 * the string "str". This is the first in the group when "first" 257 * is set (indicating that a comma should NOT be added to the front). 258 */ 259 static size_t 260 str_addarg(char *str, size_t len, char *arg, int first) 261 { 262 size_t msglen; 263 264 if (!first) { 265 if ((msglen = strlcat(str, ", ", len)) >= len) 266 return (msglen); 267 } 268 return (strlcat(str, arg, len)); 269 } 270 271 /* 272 * err_compar(p1, p2) - comparison routine used by "qsort" 273 * for sorting errors. 274 */ 275 static int 276 err_compar(const void *e1, const void *e2) 277 { 278 const struct errs *p1 = (struct errs *) e1; 279 const struct errs *p2 = (struct errs *) e2; 280 int result; 281 282 if ((result = p1->errno - p2->errno) == 0) 283 return (strcmp(p1->arg, p2->arg)); 284 return (result); 285 } 286 287 /* 288 * error_count() - return the number of errors currently logged. 289 */ 290 int 291 error_count(void) 292 { 293 return (errcnt); 294 } 295 296 /* 297 * show_errors() - display on stdout the current log of errors. 298 */ 299 void 300 show_errors(void) 301 { 302 struct errs *errp = errs; 303 int cnt = 0; 304 305 printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); 306 while (cnt++ < errcnt) { 307 printf("%5s: %s\n", errp->arg, 308 errp->errno == 0 ? "Not a number" : strerror(errp->errno)); 309 errp++; 310 } 311 } 312 313 /* 314 * kill_procs(str) - send signals to processes, much like the "kill" 315 * command does; invoked in response to 'k'. 316 */ 317 char * 318 kill_procs(char *str) 319 { 320 int signum = SIGTERM, procnum; 321 struct sigdesc *sigp; 322 uid_t uid, puid; 323 char *nptr; 324 325 /* reset error array */ 326 ERR_RESET; 327 328 /* remember our uid */ 329 uid = getuid(); 330 331 /* skip over leading white space */ 332 while (isspace(*str)) 333 str++; 334 335 if (str[0] == '-') { 336 /* explicit signal specified */ 337 if ((nptr = next_field(str)) == NULL) 338 return (" kill: no processes specified"); 339 340 if (isdigit(str[1])) { 341 (void) scanint(str + 1, &signum); 342 if (signum <= 0 || signum >= NSIG) 343 return (" invalid signal number"); 344 } else { 345 /* translate the name into a number */ 346 for (sigp = sigdesc; sigp->name != NULL; sigp++) { 347 if (strcmp(sigp->name, str + 1) == 0) { 348 signum = sigp->number; 349 break; 350 } 351 } 352 353 /* was it ever found */ 354 if (sigp->name == NULL) 355 return (" bad signal name"); 356 } 357 /* put the new pointer in place */ 358 str = nptr; 359 } 360 /* loop thru the string, killing processes */ 361 do { 362 if (scanint(str, &procnum) == -1) { 363 ERROR(str, 0); 364 } else { 365 /* check process owner if we're not root */ 366 puid = proc_owner(procnum); 367 if (puid == (uid_t)(-1)) { 368 ERROR(str, ESRCH); 369 } else if (uid && (uid != puid)) { 370 ERROR(str, EACCES); 371 } else if (kill(procnum, signum) == -1) { 372 ERROR(str, errno); 373 } 374 } 375 } while ((str = next_field(str)) != NULL); 376 377 /* return appropriate error string */ 378 return (err_string()); 379 } 380 381 /* 382 * renice_procs(str) - change the "nice" of processes, much like the 383 * "renice" command does; invoked in response to 'r'. 384 */ 385 char * 386 renice_procs(char *str) 387 { 388 uid_t uid; 389 char negate; 390 int prio, procnum; 391 392 ERR_RESET; 393 uid = getuid(); 394 395 /* allow for negative priority values */ 396 if ((negate = (*str == '-')) != 0) { 397 /* move past the minus sign */ 398 str++; 399 } 400 /* use procnum as a temporary holding place and get the number */ 401 procnum = scanint(str, &prio); 402 403 /* negate if necessary */ 404 if (negate) 405 prio = -prio; 406 407 #if defined(PRIO_MIN) && defined(PRIO_MAX) 408 /* check for validity */ 409 if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 410 return (" bad priority value"); 411 #endif 412 413 /* move to the first process number */ 414 if ((str = next_field(str)) == NULL) 415 return (" no processes specified"); 416 417 /* loop thru the process numbers, renicing each one */ 418 do { 419 if (scanint(str, &procnum) == -1) { 420 ERROR(str, 0); 421 } 422 /* check process owner if we're not root */ 423 else if (uid && (uid != proc_owner(procnum))) { 424 ERROR(str, EACCES); 425 } else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) { 426 ERROR(str, errno); 427 } 428 } while ((str = next_field(str)) != NULL); 429 430 /* return appropriate error string */ 431 return (err_string()); 432 } 433