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