1 /* $OpenBSD: commands.c,v 1.28 2007/05/29 00:56:56 otto 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 * Utility routines that help with some of the commands. 64 */ 65 static char * 66 next_field(char *str) 67 { 68 if ((str = strchr(str, ' ')) == NULL) 69 return (NULL); 70 71 *str = '\0'; 72 while (*++str == ' ') /* loop */ 73 ; 74 75 /* if there is nothing left of the string, return NULL */ 76 /* This fix is dedicated to Greg Earle */ 77 return (*str == '\0' ? NULL : str); 78 } 79 80 static int 81 scanint(char *str, int *intp) 82 { 83 int val = 0; 84 char ch; 85 86 /* if there is nothing left of the string, flag it as an error */ 87 /* This fix is dedicated to Greg Earle */ 88 if (*str == '\0') 89 return (-1); 90 91 while ((ch = *str++) != '\0') { 92 if (isdigit(ch)) 93 val = val * 10 + (ch - '0'); 94 else if (isspace(ch)) 95 break; 96 else 97 return (-1); 98 } 99 *intp = val; 100 return (0); 101 } 102 103 /* 104 * Some of the commands make system calls that could generate errors. 105 * These errors are collected up in an array of structures for later 106 * contemplation and display. Such routines return a string containing an 107 * error message, or NULL if no errors occurred. The next few routines are 108 * for manipulating and displaying these errors. We need an upper limit on 109 * the number of errors, so we arbitrarily choose 20. 110 */ 111 112 #define ERRMAX 20 113 114 struct errs errs[ERRMAX]; 115 int errcnt; 116 static char *err_toomany = " too many errors occurred"; 117 static char *err_listem = 118 " Many errors occurred. Press `e' to display the list of errors."; 119 120 /* These macros get used to reset and log the errors */ 121 #define ERR_RESET errcnt = 0 122 #define ERROR(p, e) \ 123 if (errcnt >= ERRMAX) { \ 124 return(err_toomany); \ 125 } else { \ 126 errs[errcnt].arg = (p); \ 127 errs[errcnt++].err = (e); \ 128 } 129 130 #define STRMAX 80 131 132 /* 133 * err_string() - return an appropriate error string. This is what the 134 * command will return for displaying. If no errors were logged, then 135 * return NULL. The maximum length of the error string is defined by 136 * "STRMAX". 137 */ 138 static char * 139 err_string(void) 140 { 141 int cnt = 0, first = Yes, currerr = -1; 142 static char string[STRMAX]; 143 struct errs *errp; 144 145 /* if there are no errors, return NULL */ 146 if (errcnt == 0) 147 return (NULL); 148 149 /* sort the errors */ 150 qsort(errs, errcnt, sizeof(struct errs), err_compar); 151 152 /* need a space at the front of the error string */ 153 string[0] = ' '; 154 string[1] = '\0'; 155 156 /* loop thru the sorted list, building an error string */ 157 while (cnt < errcnt) { 158 errp = &(errs[cnt++]); 159 if (errp->err != currerr) { 160 if (currerr != -1) { 161 if (str_adderr(string, sizeof string, currerr) > 162 sizeof string - 2) 163 return (err_listem); 164 165 /* we know there's more */ 166 (void) strlcat(string, "; ", sizeof string); 167 } 168 currerr = errp->err; 169 first = Yes; 170 } 171 if (str_addarg(string, sizeof string, errp->arg, first) >= 172 sizeof string) 173 return (err_listem); 174 175 first = No; 176 } 177 178 /* add final message */ 179 if (str_adderr(string, sizeof string, currerr) >= sizeof string) 180 return (err_listem); 181 182 /* return the error string */ 183 return (string); 184 } 185 186 /* 187 * str_adderr(str, len, err) - add an explanation of error "err" to 188 * the string "str". 189 */ 190 static size_t 191 str_adderr(char *str, size_t len, int err) 192 { 193 size_t msglen; 194 char *msg; 195 196 msg = err == 0 ? "Not a number" : strerror(err); 197 198 if ((msglen = strlcat(str, ": ", len)) >= len) 199 return (msglen); 200 201 return (strlcat(str, msg, len)); 202 } 203 204 /* 205 * str_addarg(str, len, arg, first) - add the string argument "arg" to 206 * the string "str". This is the first in the group when "first" 207 * is set (indicating that a comma should NOT be added to the front). 208 */ 209 static size_t 210 str_addarg(char *str, size_t len, char *arg, int first) 211 { 212 size_t msglen; 213 214 if (!first) { 215 if ((msglen = strlcat(str, ", ", len)) >= len) 216 return (msglen); 217 } 218 return (strlcat(str, arg, len)); 219 } 220 221 /* 222 * err_compar(p1, p2) - comparison routine used by "qsort" 223 * for sorting errors. 224 */ 225 static int 226 err_compar(const void *e1, const void *e2) 227 { 228 const struct errs *p1 = (const struct errs *) e1; 229 const struct errs *p2 = (const struct errs *) e2; 230 int result; 231 232 if ((result = p1->err - p2->err) == 0) 233 return (strcmp(p1->arg, p2->arg)); 234 return (result); 235 } 236 237 /* 238 * error_count() - return the number of errors currently logged. 239 */ 240 int 241 error_count(void) 242 { 243 return (errcnt); 244 } 245 246 /* 247 * kill_procs(str) - send signals to processes, much like the "kill" 248 * command does; invoked in response to 'k'. 249 */ 250 char * 251 kill_procs(char *str) 252 { 253 int signum = SIGTERM, procnum; 254 uid_t uid, puid; 255 char *nptr; 256 257 /* reset error array */ 258 ERR_RESET; 259 260 /* remember our uid */ 261 uid = getuid(); 262 263 /* skip over leading white space */ 264 while (isspace(*str)) 265 str++; 266 267 if (str[0] == '-') { 268 /* explicit signal specified */ 269 if ((nptr = next_field(str)) == NULL) 270 return (" kill: no processes specified"); 271 272 if (isdigit(str[1])) { 273 (void) scanint(str + 1, &signum); 274 if (signum <= 0 || signum >= NSIG) 275 return (" invalid signal number"); 276 } else { 277 /* translate the name into a number */ 278 for (signum = 0; signum < NSIG; signum++) { 279 if (strcasecmp(sys_signame[signum], str + 1) == 0) 280 break; 281 } 282 283 /* was it ever found */ 284 if (signum == NSIG) 285 return (" bad signal name"); 286 } 287 /* put the new pointer in place */ 288 str = nptr; 289 } 290 /* loop thru the string, killing processes */ 291 do { 292 if (scanint(str, &procnum) == -1) { 293 ERROR(str, 0); 294 } else { 295 /* check process owner if we're not root */ 296 puid = proc_owner(procnum); 297 if (puid == (uid_t)(-1)) { 298 ERROR(str, ESRCH); 299 } else if (uid && (uid != puid)) { 300 ERROR(str, EACCES); 301 } else if (kill(procnum, signum) == -1) { 302 ERROR(str, errno); 303 } 304 } 305 } while ((str = next_field(str)) != NULL); 306 307 /* return appropriate error string */ 308 return (err_string()); 309 } 310 311 /* 312 * renice_procs(str) - change the "nice" of processes, much like the 313 * "renice" command does; invoked in response to 'r'. 314 */ 315 char * 316 renice_procs(char *str) 317 { 318 uid_t uid; 319 char negate; 320 int prio, procnum; 321 322 ERR_RESET; 323 uid = getuid(); 324 325 /* allow for negative priority values */ 326 if ((negate = (*str == '-')) != 0) { 327 /* move past the minus sign */ 328 str++; 329 } 330 /* use procnum as a temporary holding place and get the number */ 331 procnum = scanint(str, &prio); 332 333 /* negate if necessary */ 334 if (negate) 335 prio = -prio; 336 337 #if defined(PRIO_MIN) && defined(PRIO_MAX) 338 /* check for validity */ 339 if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 340 return (" bad priority value"); 341 #endif 342 343 /* move to the first process number */ 344 if ((str = next_field(str)) == NULL) 345 return (" no processes specified"); 346 347 /* loop thru the process numbers, renicing each one */ 348 do { 349 if (scanint(str, &procnum) == -1) { 350 ERROR(str, 0); 351 } 352 /* check process owner if we're not root */ 353 else if (uid && (uid != proc_owner(procnum))) { 354 ERROR(str, EACCES); 355 } else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) { 356 ERROR(str, errno); 357 } 358 } while ((str = next_field(str)) != NULL); 359 360 /* return appropriate error string */ 361 return (err_string()); 362 } 363