1 /* $OpenBSD: ps.c,v 1.79 2022/09/01 21:15:54 job Exp $ */ 2 /* $NetBSD: ps.c,v 1.15 1995/05/18 20:33:25 mycroft Exp $ */ 3 4 /*- 5 * Copyright (c) 1990, 1993, 1994 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #include <sys/param.h> /* NODEV */ 34 #include <sys/types.h> 35 #include <sys/signal.h> 36 #include <sys/sysctl.h> 37 #include <sys/time.h> 38 #include <sys/resource.h> 39 #include <sys/proc.h> 40 #include <sys/stat.h> 41 #include <sys/ioctl.h> 42 43 #include <ctype.h> 44 #include <err.h> 45 #include <errno.h> 46 #include <fcntl.h> 47 #include <kvm.h> 48 #include <locale.h> 49 #include <nlist.h> 50 #include <paths.h> 51 #include <pwd.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 #include <unistd.h> 56 #include <limits.h> 57 58 #include "ps.h" 59 60 extern char *__progname; 61 62 struct varent *vhead; 63 64 int eval; /* exit value */ 65 int sumrusage; /* -S */ 66 int termwidth; /* width of screen (0 == infinity) */ 67 int totwidth; /* calculated width of requested variables */ 68 69 int needcomm, needenv, neednlist, commandonly; 70 71 enum sort { DEFAULT, SORTMEM, SORTCPU } sortby = DEFAULT; 72 73 static char *kludge_oldps_options(char *); 74 static int pscomp(const void *, const void *); 75 static void scanvars(void); 76 static void forest_sort(struct pinfo *, int); 77 static void usage(void); 78 79 char dfmt[] = "pid tt state time command"; 80 char tfmt[] = "pid tid tt state time command"; 81 char jfmt[] = "user pid ppid pgid sess jobc state tt time command"; 82 char lfmt[] = "uid pid ppid cpu pri nice vsz rss wchan state tt time command"; 83 char o1[] = "pid"; 84 char o2[] = "tt state time command"; 85 char ufmt[] = "user pid %cpu %mem vsz rss tt state start time command"; 86 char vfmt[] = "pid state time sl re pagein vsz rss lim tsiz %cpu %mem command"; 87 88 kvm_t *kd; 89 int kvm_sysctl_only; 90 91 int 92 main(int argc, char *argv[]) 93 { 94 struct kinfo_proc *kp; 95 struct pinfo *pinfo; 96 struct varent *vent; 97 struct winsize ws; 98 dev_t ttydev; 99 pid_t pid; 100 uid_t uid; 101 int all, ch, flag, i, fmt, lineno, nentries; 102 int prtheader, showthreads, wflag, kflag, what, Uflag, xflg; 103 int forest; 104 char *nlistf, *memf, *swapf, *cols, errbuf[_POSIX2_LINE_MAX]; 105 106 setlocale(LC_CTYPE, ""); 107 108 termwidth = 0; 109 if ((cols = getenv("COLUMNS")) != NULL) 110 termwidth = strtonum(cols, 1, INT_MAX, NULL); 111 if (termwidth == 0 && 112 (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 || 113 ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == 0 || 114 ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) && 115 ws.ws_col > 0) 116 termwidth = ws.ws_col - 1; 117 if (termwidth == 0) 118 termwidth = 79; 119 120 if (argc > 1) 121 argv[1] = kludge_oldps_options(argv[1]); 122 123 all = fmt = prtheader = showthreads = wflag = kflag = Uflag = xflg = 0; 124 pid = -1; 125 uid = 0; 126 forest = 0; 127 ttydev = NODEV; 128 memf = nlistf = swapf = NULL; 129 while ((ch = getopt(argc, argv, 130 "AaCcefgHhjkLlM:mN:O:o:p:rSTt:U:uvW:wx")) != -1) 131 switch (ch) { 132 case 'A': 133 all = 1; 134 xflg = 1; 135 break; 136 case 'a': 137 all = 1; 138 break; 139 case 'C': 140 break; /* no-op */ 141 case 'c': 142 commandonly = 1; 143 break; 144 case 'e': /* XXX set ufmt */ 145 needenv = 1; 146 break; 147 case 'f': 148 forest = 1; 149 break; 150 case 'g': 151 break; /* no-op */ 152 case 'H': 153 showthreads = 1; 154 break; 155 case 'h': 156 prtheader = ws.ws_row > 5 ? ws.ws_row : 22; 157 break; 158 case 'j': 159 parsefmt(jfmt); 160 fmt = 1; 161 jfmt[0] = '\0'; 162 break; 163 case 'k': 164 kflag = 1; 165 break; 166 case 'L': 167 showkey(); 168 exit(0); 169 case 'l': 170 parsefmt(lfmt); 171 fmt = 1; 172 lfmt[0] = '\0'; 173 break; 174 case 'M': 175 memf = optarg; 176 break; 177 case 'm': 178 sortby = SORTMEM; 179 break; 180 case 'N': 181 nlistf = optarg; 182 break; 183 case 'O': 184 parsefmt(o1); 185 parsefmt(optarg); 186 parsefmt(o2); 187 o1[0] = o2[0] = '\0'; 188 fmt = 1; 189 break; 190 case 'o': 191 parsefmt(optarg); 192 fmt = 1; 193 break; 194 case 'p': 195 pid = atol(optarg); 196 xflg = 1; 197 break; 198 case 'r': 199 sortby = SORTCPU; 200 break; 201 case 'S': 202 sumrusage = 1; 203 break; 204 case 'T': 205 if ((optarg = ttyname(STDIN_FILENO)) == NULL) 206 errx(1, "stdin: not a terminal"); 207 /* FALLTHROUGH */ 208 case 't': { 209 struct stat sb; 210 char *ttypath, pathbuf[PATH_MAX]; 211 212 if (strcmp(optarg, "co") == 0) 213 ttypath = _PATH_CONSOLE; 214 else if (*optarg != '/') { 215 int r = snprintf(pathbuf, sizeof(pathbuf), "%s%s", 216 _PATH_TTY, optarg); 217 if (r < 0 || r > sizeof(pathbuf)) 218 errx(1, "%s: too long\n", optarg); 219 ttypath = pathbuf; 220 } else 221 ttypath = optarg; 222 if (stat(ttypath, &sb) == -1) 223 err(1, "%s", ttypath); 224 if (!S_ISCHR(sb.st_mode)) 225 errx(1, "%s: not a terminal", ttypath); 226 ttydev = sb.st_rdev; 227 break; 228 } 229 case 'U': 230 if (uid_from_user(optarg, &uid) == -1) 231 errx(1, "%s: no such user", optarg); 232 Uflag = xflg = 1; 233 break; 234 case 'u': 235 parsefmt(ufmt); 236 sortby = SORTCPU; 237 fmt = 1; 238 ufmt[0] = '\0'; 239 break; 240 case 'v': 241 parsefmt(vfmt); 242 sortby = SORTMEM; 243 fmt = 1; 244 vfmt[0] = '\0'; 245 break; 246 case 'W': 247 swapf = optarg; 248 break; 249 case 'w': 250 if (wflag) 251 termwidth = UNLIMITED; 252 else if (termwidth < 131) 253 termwidth = 131; 254 wflag = 1; 255 break; 256 case 'x': 257 xflg = 1; 258 break; 259 default: 260 usage(); 261 } 262 argc -= optind; 263 argv += optind; 264 265 #define BACKWARD_COMPATIBILITY 266 #ifdef BACKWARD_COMPATIBILITY 267 if (*argv) { 268 nlistf = *argv; 269 if (*++argv) { 270 memf = *argv; 271 if (*++argv) 272 swapf = *argv; 273 } 274 } 275 #endif 276 277 if (nlistf == NULL && memf == NULL && swapf == NULL) { 278 kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); 279 kvm_sysctl_only = 1; 280 } else { 281 kd = kvm_openfiles(nlistf, memf, swapf, O_RDONLY, errbuf); 282 } 283 if (kd == NULL) 284 errx(1, "%s", errbuf); 285 286 if (unveil(_PATH_DEVDB, "r") == -1 && errno != ENOENT) 287 err(1, "unveil %s", _PATH_DEVDB); 288 if (unveil(_PATH_DEV, "r") == -1 && errno != ENOENT) 289 err(1, "unveil %s", _PATH_DEV); 290 if (swapf) 291 if (unveil(swapf, "r") == -1) 292 err(1, "unveil %s", swapf); 293 if (nlistf) 294 if (unveil(nlistf, "r") == -1) 295 err(1, "unveil %s", nlistf); 296 if (memf) 297 if (unveil(memf, "r") == -1) 298 err(1, "unveil %s", memf); 299 if (pledge("stdio rpath getpw ps", NULL) == -1) 300 err(1, "pledge"); 301 302 if (!fmt) { 303 if (showthreads) 304 parsefmt(tfmt); 305 else 306 parsefmt(dfmt); 307 } 308 309 /* XXX - should be cleaner */ 310 if (!all && ttydev == NODEV && pid == -1 && !Uflag) { 311 uid = getuid(); 312 Uflag = 1; 313 } 314 315 /* 316 * scan requested variables, noting what structures are needed, 317 * and adjusting header widths as appropriate. 318 */ 319 scanvars(); 320 321 if (neednlist && !nlistread) 322 (void) donlist(); 323 324 /* 325 * get proc list 326 */ 327 if (Uflag) { 328 what = KERN_PROC_UID; 329 flag = uid; 330 } else if (ttydev != NODEV) { 331 what = KERN_PROC_TTY; 332 flag = ttydev; 333 } else if (pid != -1) { 334 what = KERN_PROC_PID; 335 flag = pid; 336 } else if (kflag) { 337 what = KERN_PROC_KTHREAD; 338 flag = 0; 339 } else { 340 what = KERN_PROC_ALL; 341 flag = 0; 342 } 343 if (showthreads) 344 what |= KERN_PROC_SHOW_THREADS; 345 346 /* 347 * select procs 348 */ 349 kp = kvm_getprocs(kd, what, flag, sizeof(*kp), &nentries); 350 if (kp == NULL) 351 errx(1, "%s", kvm_geterr(kd)); 352 353 /* 354 * print header 355 */ 356 printheader(); 357 if (nentries == 0) 358 exit(1); 359 360 if ((pinfo = calloc(nentries, sizeof(struct pinfo))) == NULL) 361 err(1, NULL); 362 for (i = 0; i < nentries; i++) 363 pinfo[i].ki = &kp[i]; 364 qsort(pinfo, nentries, sizeof(struct pinfo), pscomp); 365 366 if (forest) 367 forest_sort(pinfo, nentries); 368 369 /* 370 * for each proc, call each variable output function. 371 */ 372 for (i = lineno = 0; i < nentries; i++) { 373 if (xflg == 0 && ((int)pinfo[i].ki->p_tdev == NODEV || 374 (pinfo[i].ki->p_psflags & PS_CONTROLT ) == 0)) 375 continue; 376 if (showthreads && pinfo[i].ki->p_tid == -1) 377 continue; 378 for (vent = vhead; vent; vent = vent->next) { 379 (vent->var->oproc)(&pinfo[i], vent); 380 if (vent->next != NULL) 381 (void)putchar(' '); 382 } 383 (void)putchar('\n'); 384 if (prtheader && lineno++ == prtheader - 4) { 385 (void)putchar('\n'); 386 printheader(); 387 lineno = 0; 388 } 389 } 390 exit(eval); 391 } 392 393 static void 394 scanvars(void) 395 { 396 struct varent *vent; 397 VAR *v; 398 int i; 399 400 for (vent = vhead; vent; vent = vent->next) { 401 v = vent->var; 402 i = strlen(v->header); 403 if (v->width < i) 404 v->width = i; 405 totwidth += v->width + 1; /* +1 for space */ 406 if (v->flag & COMM) 407 needcomm = 1; 408 if (v->flag & NLIST) 409 neednlist = 1; 410 } 411 totwidth--; 412 } 413 414 static int 415 pscomp(const void *v1, const void *v2) 416 { 417 const struct pinfo *p1 = (const struct pinfo *)v1; 418 const struct pinfo *p2 = (const struct pinfo *)v2; 419 const struct kinfo_proc *kp1 = p1->ki; 420 const struct kinfo_proc *kp2 = p2->ki; 421 int i; 422 #define VSIZE(k) ((k)->p_vm_dsize + (k)->p_vm_ssize + (k)->p_vm_tsize) 423 424 if (sortby == SORTCPU && (i = getpcpu(kp2) - getpcpu(kp1)) != 0) 425 return (i); 426 if (sortby == SORTMEM && (i = VSIZE(kp2) - VSIZE(kp1)) != 0) 427 return (i); 428 if ((i = kp1->p_tdev - kp2->p_tdev) == 0 && 429 (i = kp1->p_ustart_sec - kp2->p_ustart_sec) == 0) 430 i = kp1->p_ustart_usec - kp2->p_ustart_usec; 431 return (i); 432 } 433 434 /* 435 * ICK (all for getopt), would rather hide the ugliness 436 * here than taint the main code. 437 * 438 * ps foo -> ps -foo 439 * ps 34 -> ps -p34 440 * 441 * The old convention that 't' with no trailing tty arg means the users 442 * tty, is only supported if argv[1] doesn't begin with a '-'. This same 443 * feature is available with the option 'T', which takes no argument. 444 */ 445 static char * 446 kludge_oldps_options(char *s) 447 { 448 size_t len; 449 char *newopts, *ns, *cp; 450 451 len = strlen(s); 452 if ((newopts = ns = malloc(2 + len + 1)) == NULL) 453 err(1, NULL); 454 /* 455 * options begin with '-' 456 */ 457 if (*s != '-') 458 *ns++ = '-'; /* add option flag */ 459 460 /* 461 * gaze to end of argv[1] 462 */ 463 cp = s + len - 1; 464 /* 465 * if last letter is a 't' flag with no argument (in the context 466 * of the oldps options -- option string NOT starting with a '-' -- 467 * then convert to 'T' (meaning *this* terminal, i.e. ttyname(0)). 468 */ 469 if (*cp == 't' && *s != '-') 470 *cp = 'T'; 471 else { 472 /* 473 * otherwise check for trailing number, which *may* be a 474 * pid. 475 */ 476 while (cp >= s && isdigit((unsigned char)*cp)) 477 --cp; 478 } 479 cp++; 480 memmove(ns, s, (size_t)(cp - s)); /* copy up to trailing number */ 481 ns += cp - s; 482 /* 483 * if there's a trailing number, and not a preceding 'p' (pid) or 484 * 't' (tty) flag, then assume it's a pid and insert a 'p' flag. 485 */ 486 if (isdigit((unsigned char)*cp) && 487 (cp == s || (cp[-1] != 't' && cp[-1] != 'p' && 488 (cp - 1 == s || cp[-2] != 't')))) 489 *ns++ = 'p'; 490 /* and append the number */ 491 (void)strlcpy(ns, cp, newopts + len + 3 - ns); 492 493 return (newopts); 494 } 495 496 static void 497 forest_sort(struct pinfo *ki, int items) 498 { 499 int dst, lvl, maxlvl, n, ndst, nsrc, siblings, src; 500 unsigned char *path; 501 struct pinfo kn; 502 503 /* 504 * First, sort the entries by forest, tracking the forest 505 * depth in the level field. 506 */ 507 src = 0; 508 maxlvl = 0; 509 while (src < items) { 510 if (ki[src].level) { 511 src++; 512 continue; 513 } 514 for (nsrc = 1; src + nsrc < items; nsrc++) 515 if (!ki[src + nsrc].level) 516 break; 517 518 for (dst = 0; dst < items; dst++) { 519 if (ki[dst].ki->p_pid == ki[src].ki->p_pid) 520 continue; 521 if (ki[dst].ki->p_pid == ki[src].ki->p_ppid) 522 break; 523 } 524 525 if (dst == items) { 526 src += nsrc; 527 continue; 528 } 529 530 for (ndst = 1; dst + ndst < items; ndst++) 531 if (ki[dst + ndst].level <= ki[dst].level) 532 break; 533 534 for (n = src; n < src + nsrc; n++) { 535 ki[n].level += ki[dst].level + 1; 536 if (maxlvl < ki[n].level) 537 maxlvl = ki[n].level; 538 } 539 540 while (nsrc) { 541 if (src < dst) { 542 kn = ki[src]; 543 memmove(ki + src, ki + src + 1, 544 (dst - src + ndst - 1) * sizeof *ki); 545 ki[dst + ndst - 1] = kn; 546 nsrc--; 547 dst--; 548 ndst++; 549 } else if (src != dst + ndst) { 550 kn = ki[src]; 551 memmove(ki + dst + ndst + 1, ki + dst + ndst, 552 (src - dst - ndst) * sizeof *ki); 553 ki[dst + ndst] = kn; 554 ndst++; 555 nsrc--; 556 src++; 557 } else { 558 ndst += nsrc; 559 src += nsrc; 560 nsrc = 0; 561 } 562 } 563 } 564 /* 565 * Now populate prefix (instead of level) with the command 566 * prefix used to show descendancies. 567 */ 568 path = calloc(1, (maxlvl + 7) / 8); 569 if (path == NULL) 570 err(1, NULL); 571 572 for (src = 0; src < items; src++) { 573 if ((lvl = ki[src].level) == 0) { 574 ki[src].prefix = NULL; 575 continue; 576 } 577 578 if ((ki[src].prefix = malloc(lvl * 2 + 1)) == NULL) 579 err(1, NULL); 580 581 for (n = 0; n < lvl - 2; n++) { 582 ki[src].prefix[n * 2] = 583 path[n / 8] & 1 << (n % 8) ? '|' : ' '; 584 ki[src].prefix[n * 2 + 1] = ' '; 585 586 } 587 if (n == lvl - 2) { 588 /* Have I any more siblings? */ 589 for (siblings = 0, dst = src + 1; dst < items; dst++) { 590 if (ki[dst].level > lvl) 591 continue; 592 if (ki[dst].level == lvl) 593 siblings = 1; 594 break; 595 } 596 if (siblings) 597 path[n / 8] |= 1 << (n % 8); 598 else 599 path[n / 8] &= ~(1 << (n % 8)); 600 ki[src].prefix[n * 2] = siblings ? '|' : '`'; 601 ki[src].prefix[n * 2 + 1] = '-'; 602 n++; 603 } 604 strlcpy(ki[src].prefix + n * 2, "- ", (lvl - n) * 2 + 1); 605 } 606 free(path); 607 } 608 609 static void 610 usage(void) 611 { 612 fprintf(stderr, "usage: %s [-AacefHhjkLlmrSTuvwx] [-M core] [-N system]" 613 " [-O fmt] [-o fmt] [-p pid]\n", __progname); 614 fprintf(stderr, "%-*s[-t tty] [-U username] [-W swap]\n", 615 (int)strlen(__progname) + 8, ""); 616 exit(1); 617 } 618