1 /* $OpenBSD: commands.c,v 1.27 2007/02/23 13:31:45 millert 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 "boolean.h" 52 #include "utils.h" 53 #include "machine.h" 54 55 static char *next_field(char *); 56 static int scanint(char *, int *); 57 static char *err_string(void); 58 static size_t str_adderr(char *, size_t, int); 59 static size_t str_addarg(char *, size_t, char *, int); 60 static int err_compar(const void *, const void *); 61 62 /* 63 * show_help() - display the help screen; invoked in response to 64 * either 'h' or '?'. 65 */ 66 void 67 show_help(void) 68 { 69 printf("Top version %s, %s\n", version_string(), copyright); 70 puts("These single-character commands are available:\n" 71 "\n" 72 "h | ? - help; show this text\n" 73 "^L - redraw screen\n" 74 "q - quit"); 75 76 /* not all commands are available with overstrike terminals */ 77 if (overstrike) { 78 puts("\n" 79 "Other commands are also available, but this terminal is not\n" 80 "sophisticated enough to handle those commands gracefully.\n"); 81 } else { 82 puts( 83 "+ - reset any g, p, or u filters\n" 84 "C - toggle the display of command line arguments\n" 85 "d count - show `count' displays, then exit\n" 86 "e - list errors generated by last \"kill\" or \"renice\" command\n" 87 "g string - filter on command name (g+ selects all commands)\n" 88 "I | i - toggle the display of idle processes\n" 89 "k [-sig] pid - send signal `-sig' to process `pid'\n" 90 "n|# count - show `count' processes\n" 91 "o field - specify sort order (size, res, cpu, time, pri)\n" 92 "p pid - display process by `pid' (p+ selects all processes)\n" 93 "r count pid - renice process `pid' to nice value `count'\n" 94 "S - toggle the display of system processes\n" 95 "s time - change delay between displays to `time' seconds\n" 96 "T - toggle the display of threads\n" 97 "u user - display processes for `user' (u+ selects all users)\n" 98 "\n"); 99 } 100 } 101 102 /* 103 * Utility routines that help with some of the commands. 104 */ 105 static char * 106 next_field(char *str) 107 { 108 if ((str = strchr(str, ' ')) == NULL) 109 return (NULL); 110 111 *str = '\0'; 112 while (*++str == ' ') /* loop */ 113 ; 114 115 /* if there is nothing left of the string, return NULL */ 116 /* This fix is dedicated to Greg Earle */ 117 return (*str == '\0' ? NULL : str); 118 } 119 120 static int 121 scanint(char *str, int *intp) 122 { 123 int val = 0; 124 char ch; 125 126 /* if there is nothing left of the string, flag it as an error */ 127 /* This fix is dedicated to Greg Earle */ 128 if (*str == '\0') 129 return (-1); 130 131 while ((ch = *str++) != '\0') { 132 if (isdigit(ch)) 133 val = val * 10 + (ch - '0'); 134 else if (isspace(ch)) 135 break; 136 else 137 return (-1); 138 } 139 *intp = val; 140 return (0); 141 } 142 143 /* 144 * Some of the commands make system calls that could generate errors. 145 * These errors are collected up in an array of structures for later 146 * contemplation and display. Such routines return a string containing an 147 * error message, or NULL if no errors occurred. The next few routines are 148 * for manipulating and displaying these errors. We need an upper limit on 149 * the number of errors, so we arbitrarily choose 20. 150 */ 151 152 #define ERRMAX 20 153 154 struct errs { /* structure for a system-call error */ 155 int err; /* value of errno (that is, the actual error) */ 156 char *arg; /* argument that caused the error */ 157 }; 158 159 static struct errs errs[ERRMAX]; 160 static int errcnt; 161 static char *err_toomany = " too many errors occurred"; 162 static char *err_listem = 163 " Many errors occurred. Press `e' to display the list of errors."; 164 165 /* These macros get used to reset and log the errors */ 166 #define ERR_RESET errcnt = 0 167 #define ERROR(p, e) \ 168 if (errcnt >= ERRMAX) { \ 169 return(err_toomany); \ 170 } else { \ 171 errs[errcnt].arg = (p); \ 172 errs[errcnt++].err = (e); \ 173 } 174 175 #define STRMAX 80 176 177 /* 178 * err_string() - return an appropriate error string. This is what the 179 * command will return for displaying. If no errors were logged, then 180 * return NULL. The maximum length of the error string is defined by 181 * "STRMAX". 182 */ 183 static char * 184 err_string(void) 185 { 186 int cnt = 0, first = Yes, currerr = -1; 187 static char string[STRMAX]; 188 struct errs *errp; 189 190 /* if there are no errors, return NULL */ 191 if (errcnt == 0) 192 return (NULL); 193 194 /* sort the errors */ 195 qsort(errs, errcnt, sizeof(struct errs), err_compar); 196 197 /* need a space at the front of the error string */ 198 string[0] = ' '; 199 string[1] = '\0'; 200 201 /* loop thru the sorted list, building an error string */ 202 while (cnt < errcnt) { 203 errp = &(errs[cnt++]); 204 if (errp->err != currerr) { 205 if (currerr != -1) { 206 if (str_adderr(string, sizeof string, currerr) > 207 sizeof string - 2) 208 return (err_listem); 209 210 /* we know there's more */ 211 (void) strlcat(string, "; ", sizeof string); 212 } 213 currerr = errp->err; 214 first = Yes; 215 } 216 if (str_addarg(string, sizeof string, errp->arg, first) >= 217 sizeof string) 218 return (err_listem); 219 220 first = No; 221 } 222 223 /* add final message */ 224 if (str_adderr(string, sizeof string, currerr) >= sizeof string) 225 return (err_listem); 226 227 /* return the error string */ 228 return (string); 229 } 230 231 /* 232 * str_adderr(str, len, err) - add an explanation of error "err" to 233 * the string "str". 234 */ 235 static size_t 236 str_adderr(char *str, size_t len, int err) 237 { 238 size_t msglen; 239 char *msg; 240 241 msg = err == 0 ? "Not a number" : strerror(err); 242 243 if ((msglen = strlcat(str, ": ", len)) >= len) 244 return (msglen); 245 246 return (strlcat(str, msg, len)); 247 } 248 249 /* 250 * str_addarg(str, len, arg, first) - add the string argument "arg" to 251 * the string "str". This is the first in the group when "first" 252 * is set (indicating that a comma should NOT be added to the front). 253 */ 254 static size_t 255 str_addarg(char *str, size_t len, char *arg, int first) 256 { 257 size_t msglen; 258 259 if (!first) { 260 if ((msglen = strlcat(str, ", ", len)) >= len) 261 return (msglen); 262 } 263 return (strlcat(str, arg, len)); 264 } 265 266 /* 267 * err_compar(p1, p2) - comparison routine used by "qsort" 268 * for sorting errors. 269 */ 270 static int 271 err_compar(const void *e1, const void *e2) 272 { 273 const struct errs *p1 = (struct errs *) e1; 274 const struct errs *p2 = (struct errs *) e2; 275 int result; 276 277 if ((result = p1->err - p2->err) == 0) 278 return (strcmp(p1->arg, p2->arg)); 279 return (result); 280 } 281 282 /* 283 * error_count() - return the number of errors currently logged. 284 */ 285 int 286 error_count(void) 287 { 288 return (errcnt); 289 } 290 291 /* 292 * show_errors() - display on stdout the current log of errors. 293 */ 294 void 295 show_errors(void) 296 { 297 struct errs *errp = errs; 298 int cnt = 0; 299 300 printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); 301 while (cnt++ < errcnt) { 302 printf("%5s: %s\n", errp->arg, 303 errp->err == 0 ? "Not a number" : strerror(errp->err)); 304 errp++; 305 } 306 } 307 308 /* 309 * kill_procs(str) - send signals to processes, much like the "kill" 310 * command does; invoked in response to 'k'. 311 */ 312 char * 313 kill_procs(char *str) 314 { 315 int signum = SIGTERM, procnum; 316 uid_t uid, puid; 317 char *nptr; 318 319 /* reset error array */ 320 ERR_RESET; 321 322 /* remember our uid */ 323 uid = getuid(); 324 325 /* skip over leading white space */ 326 while (isspace(*str)) 327 str++; 328 329 if (str[0] == '-') { 330 /* explicit signal specified */ 331 if ((nptr = next_field(str)) == NULL) 332 return (" kill: no processes specified"); 333 334 if (isdigit(str[1])) { 335 (void) scanint(str + 1, &signum); 336 if (signum <= 0 || signum >= NSIG) 337 return (" invalid signal number"); 338 } else { 339 /* translate the name into a number */ 340 for (signum = 0; signum < NSIG; signum++) { 341 if (strcasecmp(sys_signame[signum], str + 1) == 0) 342 break; 343 } 344 345 /* was it ever found */ 346 if (signum == NSIG) 347 return (" bad signal name"); 348 } 349 /* put the new pointer in place */ 350 str = nptr; 351 } 352 /* loop thru the string, killing processes */ 353 do { 354 if (scanint(str, &procnum) == -1) { 355 ERROR(str, 0); 356 } else { 357 /* check process owner if we're not root */ 358 puid = proc_owner(procnum); 359 if (puid == (uid_t)(-1)) { 360 ERROR(str, ESRCH); 361 } else if (uid && (uid != puid)) { 362 ERROR(str, EACCES); 363 } else if (kill(procnum, signum) == -1) { 364 ERROR(str, errno); 365 } 366 } 367 } while ((str = next_field(str)) != NULL); 368 369 /* return appropriate error string */ 370 return (err_string()); 371 } 372 373 /* 374 * renice_procs(str) - change the "nice" of processes, much like the 375 * "renice" command does; invoked in response to 'r'. 376 */ 377 char * 378 renice_procs(char *str) 379 { 380 uid_t uid; 381 char negate; 382 int prio, procnum; 383 384 ERR_RESET; 385 uid = getuid(); 386 387 /* allow for negative priority values */ 388 if ((negate = (*str == '-')) != 0) { 389 /* move past the minus sign */ 390 str++; 391 } 392 /* use procnum as a temporary holding place and get the number */ 393 procnum = scanint(str, &prio); 394 395 /* negate if necessary */ 396 if (negate) 397 prio = -prio; 398 399 #if defined(PRIO_MIN) && defined(PRIO_MAX) 400 /* check for validity */ 401 if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 402 return (" bad priority value"); 403 #endif 404 405 /* move to the first process number */ 406 if ((str = next_field(str)) == NULL) 407 return (" no processes specified"); 408 409 /* loop thru the process numbers, renicing each one */ 410 do { 411 if (scanint(str, &procnum) == -1) { 412 ERROR(str, 0); 413 } 414 /* check process owner if we're not root */ 415 else if (uid && (uid != proc_owner(procnum))) { 416 ERROR(str, EACCES); 417 } else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) { 418 ERROR(str, errno); 419 } 420 } while ((str = next_field(str)) != NULL); 421 422 /* return appropriate error string */ 423 return (err_string()); 424 } 425