ps.c revision 127149
1/*- 2 * Copyright (c) 1990, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#ifndef lint 35static const char copyright[] = 36"@(#) Copyright (c) 1990, 1993, 1994\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38#endif /* not lint */ 39 40#if 0 41#ifndef lint 42static char sccsid[] = "@(#)ps.c 8.4 (Berkeley) 4/2/94"; 43#endif /* not lint */ 44#endif 45 46#include <sys/cdefs.h> 47__FBSDID("$FreeBSD: head/bin/ps/ps.c 127149 2004-03-17 22:46:58Z gad $"); 48 49#include <sys/param.h> 50#include <sys/user.h> 51#include <sys/stat.h> 52#include <sys/ioctl.h> 53#include <sys/sysctl.h> 54 55#include <ctype.h> 56#include <err.h> 57#include <errno.h> 58#include <fcntl.h> 59#include <kvm.h> 60#include <limits.h> 61#include <locale.h> 62#include <paths.h> 63#include <pwd.h> 64#include <stdio.h> 65#include <stdlib.h> 66#include <string.h> 67#include <unistd.h> 68 69#include "ps.h" 70 71#define SEP ", \t" /* username separators */ 72 73static KINFO *kinfo; 74struct varent *vhead; 75 76int eval; /* exit value */ 77int cflag; /* -c */ 78int rawcpu; /* -C */ 79int sumrusage; /* -S */ 80int termwidth; /* width of screen (0 == infinity) */ 81int totwidth; /* calculated width of requested variables */ 82 83time_t now; /* current time(3) value */ 84 85static int needuser, needcomm, needenv; 86#if defined(LAZY_PS) 87static int forceuread=0; 88#else 89static int forceuread=1; 90#endif 91 92static enum sort { DEFAULT, SORTMEM, SORTCPU } sortby = DEFAULT; 93 94static const char *fmt(char **(*)(kvm_t *, const struct kinfo_proc *, int), 95 KINFO *, char *, int); 96static char *kludge_oldps_options(char *); 97static int pscomp(const void *, const void *); 98static void saveuser(KINFO *); 99static void scanvars(void); 100static void dynsizevars(KINFO *); 101static void sizevars(void); 102static void usage(void); 103static pid_t *getpids(const char *, int *); 104static uid_t *getuids(const char *, int *); 105 106static char dfmt[] = "pid,tt,state,time,command"; 107static char jfmt[] = "user,pid,ppid,pgid,jobc,state,tt,time,command"; 108static char lfmt[] = "uid,pid,ppid,cpu,pri,nice,vsz,rss,mwchan,state,tt,time,command"; 109static char o1[] = "pid"; 110static char o2[] = "tt,state,time,command"; 111static char ufmt[] = "user,pid,%cpu,%mem,vsz,rss,tt,state,start,time,command"; 112static char vfmt[] = "pid,state,time,sl,re,pagein,vsz,rss,lim,tsiz,%cpu,%mem,command"; 113static char Zfmt[] = "label"; 114 115static kvm_t *kd; 116 117#if defined(LAZY_PS) 118#define PS_ARGS "aCcefgHhjLlM:mN:O:o:p:rSTt:U:uvwxZ" 119#else 120#define PS_ARGS "aCcegHhjLlM:mN:O:o:p:rSTt:U:uvwxZ" 121#endif 122 123int 124main(int argc, char *argv[]) 125{ 126 struct kinfo_proc *kp; 127 struct varent *vent; 128 struct winsize ws; 129 dev_t ttydev; 130 pid_t *pids; 131 uid_t *uids; 132 int all, ch, flag, i, _fmt, lineno, nentries, nocludge, dropgid; 133 int prtheader, wflag, what, xflg, pid, uid, npids, nuids, showthreads; 134 char *cols; 135 char errbuf[_POSIX2_LINE_MAX]; 136 const char *cp, *nlistf, *memf; 137 138 (void) setlocale(LC_ALL, ""); 139 /* Set the time to what it is right now. */ 140 time(&now); 141 142 if ((cols = getenv("COLUMNS")) != NULL && *cols != '\0') 143 termwidth = atoi(cols); 144 else if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&ws) == -1 && 145 ioctl(STDERR_FILENO, TIOCGWINSZ, (char *)&ws) == -1 && 146 ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ws) == -1) || 147 ws.ws_col == 0) 148 termwidth = 79; 149 else 150 termwidth = ws.ws_col - 1; 151 152 /* 153 * Don't apply a kludge if the first argument is an option taking an 154 * argument 155 */ 156 if (argc > 1) { 157 nocludge = 0; 158 if (argv[1][0] == '-') { 159 for (cp = PS_ARGS; *cp != '\0'; cp++) { 160 if (*cp != ':') 161 continue; 162 if (*(cp - 1) == argv[1][1]) { 163 nocludge = 1; 164 break; 165 } 166 } 167 } 168 if (nocludge == 0) 169 argv[1] = kludge_oldps_options(argv[1]); 170 } 171 172 all = _fmt = prtheader = wflag = xflg = 0; 173 npids = nuids = 0; 174 pids = uids = NULL; 175 ttydev = NODEV; 176 dropgid = 0; 177 memf = nlistf = _PATH_DEVNULL; 178 showthreads = 0; 179 while ((ch = getopt(argc, argv, PS_ARGS)) != -1) 180 switch((char)ch) { 181 case 'a': 182 all = 1; 183 break; 184 case 'C': 185 rawcpu = 1; 186 break; 187 case 'c': 188 cflag = 1; 189 break; 190 case 'e': /* XXX set ufmt */ 191 needenv = 1; 192 break; 193 case 'g': 194 break; /* no-op */ 195 case 'H': 196 showthreads = KERN_PROC_INC_THREAD; 197 break; 198 case 'h': 199 prtheader = ws.ws_row > 5 ? ws.ws_row : 22; 200 break; 201 case 'j': 202 parsefmt(jfmt, 0); 203 _fmt = 1; 204 jfmt[0] = '\0'; 205 break; 206 case 'L': 207 showkey(); 208 exit(0); 209 case 'l': 210 parsefmt(lfmt, 0); 211 _fmt = 1; 212 lfmt[0] = '\0'; 213 break; 214 case 'M': 215 memf = optarg; 216 dropgid = 1; 217 break; 218 case 'm': 219 sortby = SORTMEM; 220 break; 221 case 'N': 222 nlistf = optarg; 223 dropgid = 1; 224 break; 225 case 'O': 226 parsefmt(o1, 1); 227 parsefmt(optarg, 1); 228 parsefmt(o2, 1); 229 o1[0] = o2[0] = '\0'; 230 _fmt = 1; 231 break; 232 case 'o': 233 parsefmt(optarg, 1); 234 _fmt = 1; 235 break; 236#if defined(LAZY_PS) 237 case 'f': 238 if (getuid() == 0 || getgid() == 0) 239 forceuread = 1; 240 break; 241#endif 242 case 'p': 243 pids = getpids(optarg, &npids); 244 xflg = 1; 245 break; 246 case 'r': 247 sortby = SORTCPU; 248 break; 249 case 'S': 250 sumrusage = 1; 251 break; 252 case 'T': 253 if ((optarg = ttyname(STDIN_FILENO)) == NULL) 254 errx(1, "stdin: not a terminal"); 255 /* FALLTHROUGH */ 256 case 't': { 257 struct stat sb; 258 char *ttypath, pathbuf[PATH_MAX]; 259 260 if (strcmp(optarg, "co") == 0) 261 ttypath = strdup(_PATH_CONSOLE); 262 else if (*optarg != '/') 263 (void)snprintf(ttypath = pathbuf, 264 sizeof(pathbuf), "%s%s", _PATH_TTY, optarg); 265 else 266 ttypath = optarg; 267 if (stat(ttypath, &sb) == -1) 268 err(1, "%s", ttypath); 269 if (!S_ISCHR(sb.st_mode)) 270 errx(1, "%s: not a terminal", ttypath); 271 ttydev = sb.st_rdev; 272 break; 273 } 274 case 'U': 275 uids = getuids(optarg, &nuids); 276 xflg++; /* XXX: intuitive? */ 277 break; 278 case 'u': 279 parsefmt(ufmt, 0); 280 sortby = SORTCPU; 281 _fmt = 1; 282 ufmt[0] = '\0'; 283 break; 284 case 'v': 285 parsefmt(vfmt, 0); 286 sortby = SORTMEM; 287 _fmt = 1; 288 vfmt[0] = '\0'; 289 break; 290 case 'w': 291 if (wflag) 292 termwidth = UNLIMITED; 293 else if (termwidth < 131) 294 termwidth = 131; 295 wflag++; 296 break; 297 case 'x': 298 xflg = 1; 299 break; 300 case 'Z': 301 parsefmt(Zfmt, 0); 302 Zfmt[0] = '\0'; 303 break; 304 case '?': 305 default: 306 usage(); 307 } 308 argc -= optind; 309 argv += optind; 310 311#define BACKWARD_COMPATIBILITY 312#ifdef BACKWARD_COMPATIBILITY 313 if (*argv) { 314 nlistf = *argv; 315 if (*++argv) { 316 memf = *argv; 317 } 318 } 319#endif 320 /* 321 * Discard setgid privileges if not the running kernel so that bad 322 * guys can't print interesting stuff from kernel memory. 323 */ 324 if (dropgid) { 325 setgid(getgid()); 326 setuid(getuid()); 327 } 328 329 kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf); 330 if (kd == 0) 331 errx(1, "%s", errbuf); 332 333 if (!_fmt) 334 parsefmt(dfmt, 0); 335 336 /* XXX - should be cleaner */ 337 if (!all && ttydev == NODEV && !npids && !nuids) { 338 if ((uids = malloc(sizeof (*uids))) == NULL) 339 errx(1, "malloc failed"); 340 nuids = 1; 341 *uids = getuid(); 342 } 343 344 /* 345 * scan requested variables, noting what structures are needed, 346 * and adjusting header widths as appropriate. 347 */ 348 scanvars(); 349 /* 350 * get proc list 351 */ 352 if (nuids == 1) { 353 what = KERN_PROC_UID | showthreads; 354 flag = *uids; 355 } else if (ttydev != NODEV) { 356 what = KERN_PROC_TTY | showthreads; 357 flag = ttydev; 358 } else if (npids == 1) { 359 what = KERN_PROC_PID | showthreads; 360 flag = *pids; 361 } else { 362 what = showthreads != 0 ? KERN_PROC_ALL : KERN_PROC_PROC; 363 flag = 0; 364 } 365 366 /* 367 * select procs 368 */ 369 kp = kvm_getprocs(kd, what, flag, &nentries); 370 if ((kp == 0 && nentries != 0) || nentries < 0) 371 errx(1, "%s", kvm_geterr(kd)); 372 if (nentries > 0) { 373 if ((kinfo = malloc(nentries * sizeof(*kinfo))) == NULL) 374 errx(1, "malloc failed"); 375 for (i = nentries; --i >= 0; ++kp) { 376 kinfo[i].ki_p = kp; 377 if (needuser) 378 saveuser(&kinfo[i]); 379 dynsizevars(&kinfo[i]); 380 } 381 } 382 383 sizevars(); 384 385 /* 386 * print header 387 */ 388 printheader(); 389 if (nentries == 0) 390 exit(1); 391 /* 392 * sort proc list 393 */ 394 qsort(kinfo, nentries, sizeof(KINFO), pscomp); 395 /* 396 * for each proc, call each variable output function. 397 */ 398 for (i = lineno = 0; i < nentries; i++) { 399 if (xflg == 0 && ((&kinfo[i])->ki_p->ki_tdev == NODEV || 400 ((&kinfo[i])->ki_p->ki_flag & P_CONTROLT ) == 0)) 401 continue; 402 if (npids > 1) { 403 for (pid = 0; pid < npids; pid++) 404 if ((&kinfo[i])->ki_p->ki_pid == pids[pid]) 405 break; 406 if (pid == npids) 407 continue; 408 } 409 if (nuids > 1) { 410 for (uid = 0; uid < nuids; uid++) 411 if ((&kinfo[i])->ki_p->ki_uid == uids[uid]) 412 break; 413 if (uid == nuids) 414 continue; 415 } 416 for (vent = vhead; vent; vent = vent->next) { 417 (vent->var->oproc)(&kinfo[i], vent); 418 if (vent->next != NULL) 419 (void)putchar(' '); 420 } 421 (void)putchar('\n'); 422 if (prtheader && lineno++ == prtheader - 4) { 423 (void)putchar('\n'); 424 printheader(); 425 lineno = 0; 426 } 427 } 428 free(uids); 429 430 exit(eval); 431} 432 433#define BSD_PID_MAX 99999 /* Copy of PID_MAX from sys/proc.h */ 434pid_t * 435getpids(const char *arg, int *npids) 436{ 437 char copyarg[32]; 438 char *copyp, *endp; 439 pid_t *pids, *morepids; 440 int alloc; 441 long tempid; 442 443 alloc = 0; 444 *npids = 0; 445 pids = NULL; 446 while (*arg != '\0') { 447 while (*arg != '\0' && strchr(SEP, *arg) != NULL) 448 arg++; 449 if (*arg == '\0' || strchr(SEP, *arg) != NULL) 450 tempid = 0; 451 else { 452 copyp = copyarg; 453 endp = copyarg + sizeof(copyarg) - 1; 454 while (*arg != '\0' && strchr(SEP, *arg) == NULL && 455 copyp <= endp) 456 *copyp++ = *arg++; 457 if (copyp > endp) { 458 *endp = '\0'; 459 tempid = -1; 460 while (*arg != '\0' && 461 strchr(SEP, *arg) == NULL) 462 arg++; 463 } else { 464 *copyp = '\0'; 465 errno = 0; 466 tempid = strtol(copyarg, &endp, 10); 467 } 468 /* 469 * Write warning messages for any values which 470 * would never be a valid process number. 471 */ 472 if (*endp != '\0' || tempid < 0 || copyarg == endp) { 473 warnx("invalid process number: %s", copyarg); 474 errno = ERANGE; 475 } else if (errno != 0 || tempid > BSD_PID_MAX) { 476 warnx("process number too large: %s", copyarg); 477 errno = ERANGE; 478 } 479 if (errno == ERANGE) { 480 /* Ignore this value from the given list. */ 481 continue; 482 } 483 } 484 if (*npids >= alloc) { 485 alloc = (alloc + 1) << 1; 486 morepids = realloc(pids, alloc * sizeof (*pids)); 487 if (morepids == NULL) { 488 free(pids); 489 errx(1, "realloc failed"); 490 } 491 pids = morepids; 492 } 493 pids[(*npids)++] = tempid; 494 } 495 496 if (!*npids) 497 errx(1, "No valid process numbers specified"); 498 499 return (pids); 500} 501 502uid_t * 503getuids(const char *arg, int *nuids) 504{ 505 char name[MAXLOGNAME]; 506 struct passwd *pwd; 507 uid_t *uids, *moreuids; 508 int alloc; 509 size_t l; 510 511 512 alloc = 0; 513 *nuids = 0; 514 uids = NULL; 515 for (; (l = strcspn(arg, SEP)) > 0; arg += l + strspn(arg + l, SEP)) { 516 if (l >= sizeof name) { 517 warnx("%.*s: name too long", (int)l, arg); 518 continue; 519 } 520 strncpy(name, arg, l); 521 name[l] = '\0'; 522 if ((pwd = getpwnam(name)) == NULL) { 523 warnx("%s: no such user", name); 524 continue; 525 } 526 if (*nuids >= alloc) { 527 alloc = (alloc + 1) << 1; 528 moreuids = realloc(uids, alloc * sizeof (*uids)); 529 if (moreuids == NULL) { 530 free(uids); 531 errx(1, "realloc failed"); 532 } 533 uids = moreuids; 534 } 535 uids[(*nuids)++] = pwd->pw_uid; 536 } 537 endpwent(); 538 539 if (!*nuids) 540 errx(1, "No users specified"); 541 542 return uids; 543} 544 545VARENT * 546find_varentry(VAR *v) 547{ 548 struct varent *vent; 549 550 for (vent = vhead; vent; vent = vent->next) { 551 if (strcmp(vent->var->name, v->name) == 0) 552 return vent; 553 } 554 return NULL; 555} 556 557static void 558scanvars(void) 559{ 560 struct varent *vent; 561 VAR *v; 562 563 for (vent = vhead; vent; vent = vent->next) { 564 v = vent->var; 565 if (v->flag & DSIZ) { 566 v->dwidth = v->width; 567 v->width = 0; 568 } 569 if (v->flag & USER) 570 needuser = 1; 571 if (v->flag & COMM) 572 needcomm = 1; 573 } 574} 575 576static void 577dynsizevars(KINFO *ki) 578{ 579 struct varent *vent; 580 VAR *v; 581 int i; 582 583 for (vent = vhead; vent; vent = vent->next) { 584 v = vent->var; 585 if (!(v->flag & DSIZ)) 586 continue; 587 i = (v->sproc)( ki); 588 if (v->width < i) 589 v->width = i; 590 if (v->width > v->dwidth) 591 v->width = v->dwidth; 592 } 593} 594 595static void 596sizevars(void) 597{ 598 struct varent *vent; 599 VAR *v; 600 int i; 601 602 for (vent = vhead; vent; vent = vent->next) { 603 v = vent->var; 604 i = strlen(vent->header); 605 if (v->width < i) 606 v->width = i; 607 totwidth += v->width + 1; /* +1 for space */ 608 } 609 totwidth--; 610} 611 612static const char * 613fmt(char **(*fn)(kvm_t *, const struct kinfo_proc *, int), KINFO *ki, 614 char *comm, int maxlen) 615{ 616 const char *s; 617 618 s = fmt_argv((*fn)(kd, ki->ki_p, termwidth), comm, maxlen); 619 return (s); 620} 621 622#define UREADOK(ki) (forceuread || (ki->ki_p->ki_sflag & PS_INMEM)) 623 624static void 625saveuser(KINFO *ki) 626{ 627 628 if (ki->ki_p->ki_sflag & PS_INMEM) { 629 /* 630 * The u-area might be swapped out, and we can't get 631 * at it because we have a crashdump and no swap. 632 * If it's here fill in these fields, otherwise, just 633 * leave them 0. 634 */ 635 ki->ki_valid = 1; 636 } else 637 ki->ki_valid = 0; 638 /* 639 * save arguments if needed 640 */ 641 if (needcomm && (UREADOK(ki) || (ki->ki_p->ki_args != NULL))) { 642 ki->ki_args = strdup(fmt(kvm_getargv, ki, ki->ki_p->ki_comm, 643 MAXCOMLEN)); 644 } else if (needcomm) { 645 asprintf(&ki->ki_args, "(%s)", ki->ki_p->ki_comm); 646 } else { 647 ki->ki_args = NULL; 648 } 649 if (needenv && UREADOK(ki)) { 650 ki->ki_env = strdup(fmt(kvm_getenvv, ki, (char *)NULL, 0)); 651 } else if (needenv) { 652 ki->ki_env = malloc(3); 653 strcpy(ki->ki_env, "()"); 654 } else { 655 ki->ki_env = NULL; 656 } 657} 658 659static int 660pscomp(const void *a, const void *b) 661{ 662 int i; 663#define VSIZE(k) ((k)->ki_p->ki_dsize + (k)->ki_p->ki_ssize + \ 664 (k)->ki_p->ki_tsize) 665 666 if (sortby == SORTCPU) 667 return (getpcpu((const KINFO *)b) - getpcpu((const KINFO *)a)); 668 if (sortby == SORTMEM) 669 return (VSIZE((const KINFO *)b) - VSIZE((const KINFO *)a)); 670 i = (int)((const KINFO *)a)->ki_p->ki_tdev - (int)((const KINFO *)b)->ki_p->ki_tdev; 671 if (i == 0) 672 i = ((const KINFO *)a)->ki_p->ki_pid - ((const KINFO *)b)->ki_p->ki_pid; 673 return (i); 674} 675 676/* 677 * ICK (all for getopt), would rather hide the ugliness 678 * here than taint the main code. 679 * 680 * ps foo -> ps -foo 681 * ps 34 -> ps -p34 682 * 683 * The old convention that 't' with no trailing tty arg means the users 684 * tty, is only supported if argv[1] doesn't begin with a '-'. This same 685 * feature is available with the option 'T', which takes no argument. 686 */ 687static char * 688kludge_oldps_options(char *s) 689{ 690 int have_fmt; 691 size_t len; 692 char *newopts, *ns, *cp; 693 694 /* 695 * If we have an 'o' option, then note it, since we don't want to do 696 * some types of munging. 697 */ 698 have_fmt = index(s, 'o') != NULL; 699 700 len = strlen(s); 701 if ((newopts = ns = malloc(len + 2)) == NULL) 702 errx(1, "malloc failed"); 703 /* 704 * options begin with '-' 705 */ 706 if (*s != '-') 707 *ns++ = '-'; /* add option flag */ 708 /* 709 * gaze to end of argv[1] 710 */ 711 cp = s + len - 1; 712 /* 713 * if last letter is a 't' flag with no argument (in the context 714 * of the oldps options -- option string NOT starting with a '-' -- 715 * then convert to 'T' (meaning *this* terminal, i.e. ttyname(0)). 716 * 717 * However, if a flag accepting a string argument is found in the 718 * option string, the remainder of the string is the argument to 719 * that flag; do not modify that argument. 720 */ 721 if (strcspn(s, "MNOoU") == len && *cp == 't' && *s != '-') 722 *cp = 'T'; 723 else { 724 /* 725 * otherwise check for trailing number, which *may* be a 726 * pid. 727 */ 728 while (cp >= s && isdigit(*cp)) 729 --cp; 730 } 731 cp++; 732 memmove(ns, s, (size_t)(cp - s)); /* copy up to trailing number */ 733 ns += cp - s; 734 /* 735 * if there's a trailing number, and not a preceding 'p' (pid) or 736 * 't' (tty) flag, then assume it's a pid and insert a 'p' flag. 737 */ 738 if (isdigit(*cp) && 739 (cp == s || (cp[-1] != 't' && cp[-1] != 'p')) && 740 (cp - 1 == s || cp[-2] != 't') && !have_fmt) 741 *ns++ = 'p'; 742 (void)strcpy(ns, cp); /* and append the number */ 743 744 return (newopts); 745} 746 747static void 748usage(void) 749{ 750 751 (void)fprintf(stderr, "%s\n%s\n%s\n", 752 "usage: ps [-aCHhjlmrSTuvwxZ] [-O|o fmt] [-p pid[,pid]] [-t tty]", 753 " [-U user[,user]] [-M core] [-N system]", 754 " ps [-L]"); 755 exit(1); 756} 757