1/* $OpenBSD: ps.c,v 1.81 2024/05/18 13:08:09 sobrado 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 60extern char *__progname; 61 62struct varent *vhead; 63 64int eval; /* exit value */ 65int sumrusage; /* -S */ 66int termwidth; /* width of screen (0 == infinity) */ 67int totwidth; /* calculated width of requested variables */ 68 69int needcomm, needenv, neednlist, commandonly; 70 71enum sort { DEFAULT, SORTMEM, SORTCPU } sortby = DEFAULT; 72 73static char *kludge_oldps_options(char *); 74static int pscomp(const void *, const void *); 75static void scanvars(void); 76static void forest_sort(struct pinfo *, int); 77static void usage(void); 78 79char dfmt[] = "pid tt state time command"; 80char tfmt[] = "pid tid tt state time command"; 81char jfmt[] = "user pid ppid pgid sess jobc state tt time command"; 82char lfmt[] = "uid pid ppid cpu pri nice vsz rss wchan state tt time command"; 83char o1[] = "pid"; 84char o2[] = "tt state time command"; 85char ufmt[] = "user pid %cpu %mem vsz rss tt state start time command"; 86char vfmt[] = "pid state time sl re pagein vsz rss lim tsiz %cpu %mem command"; 87 88kvm_t *kd; 89int kvm_sysctl_only; 90 91int 92main(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 int found = 0; 231 232 if (uid_from_user(optarg, &uid) == 0) 233 found = 1; 234 else { 235 const char *errstr; 236 237 uid = strtonum(optarg, 0, UID_MAX, &errstr); 238 if (errstr == NULL && 239 user_from_uid(uid, 1) != NULL) 240 found = 1; 241 } 242 if (!found) 243 errx(1, "%s: unknown user", optarg); 244 Uflag = xflg = 1; 245 break; 246 } 247 case 'u': 248 parsefmt(ufmt); 249 sortby = SORTCPU; 250 fmt = 1; 251 ufmt[0] = '\0'; 252 break; 253 case 'v': 254 parsefmt(vfmt); 255 sortby = SORTMEM; 256 fmt = 1; 257 vfmt[0] = '\0'; 258 break; 259 case 'W': 260 swapf = optarg; 261 break; 262 case 'w': 263 if (wflag) 264 termwidth = UNLIMITED; 265 else if (termwidth < 131) 266 termwidth = 131; 267 wflag = 1; 268 break; 269 case 'x': 270 xflg = 1; 271 break; 272 default: 273 usage(); 274 } 275 argc -= optind; 276 argv += optind; 277 278#define BACKWARD_COMPATIBILITY 279#ifdef BACKWARD_COMPATIBILITY 280 if (*argv) { 281 nlistf = *argv; 282 if (*++argv) { 283 memf = *argv; 284 if (*++argv) 285 swapf = *argv; 286 } 287 } 288#endif 289 290 if (nlistf == NULL && memf == NULL && swapf == NULL) { 291 kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); 292 kvm_sysctl_only = 1; 293 } else { 294 kd = kvm_openfiles(nlistf, memf, swapf, O_RDONLY, errbuf); 295 } 296 if (kd == NULL) 297 errx(1, "%s", errbuf); 298 299 if (unveil(_PATH_DEVDB, "r") == -1 && errno != ENOENT) 300 err(1, "unveil %s", _PATH_DEVDB); 301 if (unveil(_PATH_DEV, "r") == -1 && errno != ENOENT) 302 err(1, "unveil %s", _PATH_DEV); 303 if (swapf) 304 if (unveil(swapf, "r") == -1) 305 err(1, "unveil %s", swapf); 306 if (nlistf) 307 if (unveil(nlistf, "r") == -1) 308 err(1, "unveil %s", nlistf); 309 if (memf) 310 if (unveil(memf, "r") == -1) 311 err(1, "unveil %s", memf); 312 if (pledge("stdio rpath getpw ps", NULL) == -1) 313 err(1, "pledge"); 314 315 if (!fmt) { 316 if (showthreads) 317 parsefmt(tfmt); 318 else 319 parsefmt(dfmt); 320 } 321 322 /* XXX - should be cleaner */ 323 if (!all && ttydev == NODEV && pid == -1 && !Uflag) { 324 uid = getuid(); 325 Uflag = 1; 326 } 327 328 /* 329 * scan requested variables, noting what structures are needed, 330 * and adjusting header widths as appropriate. 331 */ 332 scanvars(); 333 334 if (neednlist && !nlistread) 335 (void) donlist(); 336 337 /* 338 * get proc list 339 */ 340 if (Uflag) { 341 what = KERN_PROC_UID; 342 flag = uid; 343 } else if (ttydev != NODEV) { 344 what = KERN_PROC_TTY; 345 flag = ttydev; 346 } else if (pid != -1) { 347 what = KERN_PROC_PID; 348 flag = pid; 349 } else if (kflag) { 350 what = KERN_PROC_KTHREAD; 351 flag = 0; 352 } else { 353 what = KERN_PROC_ALL; 354 flag = 0; 355 } 356 if (showthreads) 357 what |= KERN_PROC_SHOW_THREADS; 358 359 /* 360 * select procs 361 */ 362 kp = kvm_getprocs(kd, what, flag, sizeof(*kp), &nentries); 363 if (kp == NULL) 364 errx(1, "%s", kvm_geterr(kd)); 365 366 /* 367 * print header 368 */ 369 printheader(); 370 if (nentries == 0) 371 exit(1); 372 373 if ((pinfo = calloc(nentries, sizeof(struct pinfo))) == NULL) 374 err(1, NULL); 375 for (i = 0; i < nentries; i++) 376 pinfo[i].ki = &kp[i]; 377 qsort(pinfo, nentries, sizeof(struct pinfo), pscomp); 378 379 if (forest) 380 forest_sort(pinfo, nentries); 381 382 /* 383 * for each proc, call each variable output function. 384 */ 385 for (i = lineno = 0; i < nentries; i++) { 386 if (xflg == 0 && ((int)pinfo[i].ki->p_tdev == NODEV || 387 (pinfo[i].ki->p_psflags & PS_CONTROLT ) == 0)) 388 continue; 389 if (showthreads && pinfo[i].ki->p_tid == -1) 390 continue; 391 for (vent = vhead; vent; vent = vent->next) { 392 (vent->var->oproc)(&pinfo[i], vent); 393 if (vent->next != NULL) 394 (void)putchar(' '); 395 } 396 (void)putchar('\n'); 397 if (prtheader && lineno++ == prtheader - 4) { 398 (void)putchar('\n'); 399 printheader(); 400 lineno = 0; 401 } 402 } 403 exit(eval); 404} 405 406static void 407scanvars(void) 408{ 409 struct varent *vent; 410 VAR *v; 411 int i; 412 413 for (vent = vhead; vent; vent = vent->next) { 414 v = vent->var; 415 i = strlen(v->header); 416 if (v->width < i) 417 v->width = i; 418 totwidth += v->width + 1; /* +1 for space */ 419 if (v->flag & COMM) 420 needcomm = 1; 421 if (v->flag & NLIST) 422 neednlist = 1; 423 } 424 totwidth--; 425} 426 427static int 428pscomp(const void *v1, const void *v2) 429{ 430 const struct pinfo *p1 = (const struct pinfo *)v1; 431 const struct pinfo *p2 = (const struct pinfo *)v2; 432 const struct kinfo_proc *kp1 = p1->ki; 433 const struct kinfo_proc *kp2 = p2->ki; 434 int i; 435#define VSIZE(k) ((k)->p_vm_dsize + (k)->p_vm_ssize + (k)->p_vm_tsize) 436 437 if (sortby == SORTCPU && (i = getpcpu(kp2) - getpcpu(kp1)) != 0) 438 return (i); 439 if (sortby == SORTMEM && (i = VSIZE(kp2) - VSIZE(kp1)) != 0) 440 return (i); 441 if ((i = kp1->p_tdev - kp2->p_tdev) == 0 && 442 (i = kp1->p_ustart_sec - kp2->p_ustart_sec) == 0) 443 i = kp1->p_ustart_usec - kp2->p_ustart_usec; 444 return (i); 445} 446 447/* 448 * ICK (all for getopt), would rather hide the ugliness 449 * here than taint the main code. 450 * 451 * ps foo -> ps -foo 452 * ps 34 -> ps -p34 453 * 454 * The old convention that 't' with no trailing tty arg means the users 455 * tty, is only supported if argv[1] doesn't begin with a '-'. This same 456 * feature is available with the option 'T', which takes no argument. 457 */ 458static char * 459kludge_oldps_options(char *s) 460{ 461 size_t len; 462 char *newopts, *ns, *cp; 463 464 len = strlen(s); 465 if ((newopts = ns = malloc(2 + len + 1)) == NULL) 466 err(1, NULL); 467 /* 468 * options begin with '-' 469 */ 470 if (*s != '-') 471 *ns++ = '-'; /* add option flag */ 472 473 /* 474 * gaze to end of argv[1] 475 */ 476 cp = s + len - 1; 477 /* 478 * if last letter is a 't' flag with no argument (in the context 479 * of the oldps options -- option string NOT starting with a '-' -- 480 * then convert to 'T' (meaning *this* terminal, i.e. ttyname(0)). 481 */ 482 if (*cp == 't' && *s != '-') 483 *cp = 'T'; 484 else { 485 /* 486 * otherwise check for trailing number, which *may* be a 487 * pid. 488 */ 489 while (cp >= s && isdigit((unsigned char)*cp)) 490 --cp; 491 } 492 cp++; 493 memmove(ns, s, (size_t)(cp - s)); /* copy up to trailing number */ 494 ns += cp - s; 495 /* 496 * if there's a trailing number, and not a preceding 'p' (pid), 497 * 't' (tty) or 'U' (user) flag, 498 * then assume it's a pid and insert a 'p' flag. 499 */ 500 if (isdigit((unsigned char)*cp) && 501 (cp == s || (cp[-1] != 't' && cp[-1] != 'p' && cp[-1] != 'U' && 502 (cp - 1 == s || cp[-2] != 't')))) 503 *ns++ = 'p'; 504 /* and append the number */ 505 (void)strlcpy(ns, cp, newopts + len + 3 - ns); 506 507 return (newopts); 508} 509 510static void 511forest_sort(struct pinfo *ki, int items) 512{ 513 int dst, lvl, maxlvl, n, ndst, nsrc, siblings, src; 514 unsigned char *path; 515 struct pinfo kn; 516 517 /* 518 * First, sort the entries by forest, tracking the forest 519 * depth in the level field. 520 */ 521 src = 0; 522 maxlvl = 0; 523 while (src < items) { 524 if (ki[src].level) { 525 src++; 526 continue; 527 } 528 for (nsrc = 1; src + nsrc < items; nsrc++) 529 if (!ki[src + nsrc].level) 530 break; 531 532 for (dst = 0; dst < items; dst++) { 533 if (ki[dst].ki->p_pid == ki[src].ki->p_pid) 534 continue; 535 if (ki[dst].ki->p_pid == ki[src].ki->p_ppid) 536 break; 537 } 538 539 if (dst == items) { 540 src += nsrc; 541 continue; 542 } 543 544 for (ndst = 1; dst + ndst < items; ndst++) 545 if (ki[dst + ndst].level <= ki[dst].level) 546 break; 547 548 for (n = src; n < src + nsrc; n++) { 549 ki[n].level += ki[dst].level + 1; 550 if (maxlvl < ki[n].level) 551 maxlvl = ki[n].level; 552 } 553 554 while (nsrc) { 555 if (src < dst) { 556 kn = ki[src]; 557 memmove(ki + src, ki + src + 1, 558 (dst - src + ndst - 1) * sizeof *ki); 559 ki[dst + ndst - 1] = kn; 560 nsrc--; 561 dst--; 562 ndst++; 563 } else if (src != dst + ndst) { 564 kn = ki[src]; 565 memmove(ki + dst + ndst + 1, ki + dst + ndst, 566 (src - dst - ndst) * sizeof *ki); 567 ki[dst + ndst] = kn; 568 ndst++; 569 nsrc--; 570 src++; 571 } else { 572 ndst += nsrc; 573 src += nsrc; 574 nsrc = 0; 575 } 576 } 577 } 578 /* 579 * Now populate prefix (instead of level) with the command 580 * prefix used to show descendancies. 581 */ 582 path = calloc(1, (maxlvl + 7) / 8); 583 if (path == NULL) 584 err(1, NULL); 585 586 for (src = 0; src < items; src++) { 587 if ((lvl = ki[src].level) == 0) { 588 ki[src].prefix = NULL; 589 continue; 590 } 591 592 if ((ki[src].prefix = malloc(lvl * 2 + 1)) == NULL) 593 err(1, NULL); 594 595 for (n = 0; n < lvl - 2; n++) { 596 ki[src].prefix[n * 2] = 597 path[n / 8] & 1 << (n % 8) ? '|' : ' '; 598 ki[src].prefix[n * 2 + 1] = ' '; 599 600 } 601 if (n == lvl - 2) { 602 /* Have I any more siblings? */ 603 for (siblings = 0, dst = src + 1; dst < items; dst++) { 604 if (ki[dst].level > lvl) 605 continue; 606 if (ki[dst].level == lvl) 607 siblings = 1; 608 break; 609 } 610 if (siblings) 611 path[n / 8] |= 1 << (n % 8); 612 else 613 path[n / 8] &= ~(1 << (n % 8)); 614 ki[src].prefix[n * 2] = siblings ? '|' : '`'; 615 ki[src].prefix[n * 2 + 1] = '-'; 616 n++; 617 } 618 strlcpy(ki[src].prefix + n * 2, "- ", (lvl - n) * 2 + 1); 619 } 620 free(path); 621} 622 623static void 624usage(void) 625{ 626 fprintf(stderr, "usage: %s [[-]AacefHhjkLlmrSTuvwx] [-M core]" 627 " [-N system] [-O fmt] [-o fmt]\n", __progname); 628 fprintf(stderr, "%-*s[-p pid] [-t tty] [-U user] [-W swap]\n", 629 (int)strlen(__progname) + 8, ""); 630 exit(1); 631} 632