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