2 * SPDX-License-Identifier: BSD-3-Clause
4 * Copyright (c) 1990, 1993, 1994
5 * The Regents of the University of California. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * ------+---------+---------+-------- + --------+---------+---------+---------*
31 * Copyright (c) 2004 - Garance Alistair Drosehn <gad@FreeBSD.org>.
32 * All rights reserved.
34 * Significant modifications made to bring `ps' options somewhat closer
35 * to the standard for `ps' as described in SingleUnixSpec-v3.
36 * ------+---------+---------+-------- + --------+---------+---------+---------*
39 #include <sys/param.h>
44 #include <sys/ioctl.h>
45 #include <sys/sysctl.h>
46 #include <sys/mount.h>
66 #define _PATH_PTS "/dev/pts/"
68 #define W_SEP " \t" /* "Whitespace" list separators */
69 #define T_SEP "," /* "Terminate-element" list separators */
72 * isdigit takes an `int', but expects values in the range of unsigned char.
73 * This wrapper ensures that values from a 'char' end up in the correct range.
75 #define isdigitch(Anychar) isdigit((u_char)(Anychar))
78 int eval
; /* Exit value */
79 time_t now
; /* Current time(3) value */
81 int sumrusage
; /* -S */
82 int termwidth
; /* Width of the screen (0 == infinity). */
83 int showthreads
; /* will threads be shown? */
85 struct velisthead varlist
= STAILQ_HEAD_INITIALIZER(varlist
);
88 static int needcomm
; /* -o "command" */
89 static int needenv
; /* -e */
90 static int needuser
; /* -o "user" */
91 static int optfatal
; /* Fatal error parsing some list-option. */
92 static int pid_max
; /* kern.pid_max */
94 static enum sort
{ DEFAULT
, SORTMEM
, SORTCPU
} sortby
= DEFAULT
;
97 typedef int addelem_rtn(struct listinfo
*_inf
, const char *_elem
);
103 addelem_rtn
*addelem
;
115 static int addelem_gid(struct listinfo
*, const char *);
116 static int addelem_jid(struct listinfo
*, const char *);
117 static int addelem_pid(struct listinfo
*, const char *);
118 static int addelem_tty(struct listinfo
*, const char *);
119 static int addelem_uid(struct listinfo
*, const char *);
120 static void add_list(struct listinfo
*, const char *);
121 static void descendant_sort(KINFO
*, int);
122 static void format_output(KINFO
*);
123 static void *expand_list(struct listinfo
*);
125 fmt(char **(*)(kvm_t
*, const struct kinfo_proc
*, int),
126 KINFO
*, char *, char *, int);
127 static void free_list(struct listinfo
*);
128 static void init_list(struct listinfo
*, addelem_rtn
, int, const char *);
129 static char *kludge_oldps_options(const char *, char *, const char *);
130 static int pscomp(const void *, const void *);
131 static void saveuser(KINFO
*);
132 static void scanvars(void);
133 static void sizevars(void);
134 static void pidmax_init(void);
135 static void usage(void);
137 static char dfmt
[] = "pid,tt,state,time,command";
138 static char jfmt
[] = "user,pid,ppid,pgid,sid,jobc,state,tt,time,command";
139 static char lfmt
[] = "uid,pid,ppid,cpu,pri,nice,vsz,rss,mwchan,state,"
141 static char o1
[] = "pid";
142 static char o2
[] = "tt,state,time,command";
143 static char ufmt
[] = "user,pid,%cpu,%mem,vsz,rss,tt,state,start,time,command";
144 static char vfmt
[] = "pid,state,time,sl,re,pagein,vsz,rss,lim,tsiz,"
146 static char Zfmt
[] = "label";
148 #define PS_ARGS "AaCcD:defG:gHhjJ:LlM:mN:O:o:p:rSTt:U:uvwXxZ"
151 main(int argc
, char *argv
[])
153 struct listinfo gidlist
, jidlist
, pgrplist
, pidlist
;
154 struct listinfo ruidlist
, sesslist
, ttylist
, uidlist
;
155 struct kinfo_proc
*kp
;
156 KINFO
*kinfo
= NULL
, *next_KINFO
;
159 struct winsize ws
= { .ws_row
= 0 };
160 const char *nlistf
, *memf
, *str
;
162 int all
, ch
, elem
, flag
, _fmt
, i
, lineno
, linelen
, left
;
163 int descendancy
, nentries
, nkept
, nselectors
;
164 int prtheader
, wflag
, what
, xkeep
, xkeep_implied
;
165 int fwidthmin
, fwidthmax
;
166 char errbuf
[_POSIX2_LINE_MAX
];
167 char fmtbuf
[_POSIX2_LINE_MAX
];
168 enum { NONE
= 0, UP
= 1, DOWN
= 2, BOTH
= 1 | 2 } directions
= NONE
;
169 struct { int traversed
; int initial
; } pid_count
;
171 (void) setlocale(LC_ALL
, "");
172 time(&now
); /* Used by routines in print.c. */
175 * Compute default output line length before processing options.
176 * If COLUMNS is set, use it. Otherwise, if this is part of an
177 * interactive job (i.e. one associated with a terminal), use
178 * the terminal width. "Interactive" is determined by whether
179 * any of stdout, stderr, or stdin is a terminal. The intent
180 * is that "ps", "ps | more", and "ps | grep" all use the same
181 * default line length unless -w is specified.
183 * If not interactive, the default length was traditionally 79.
184 * It has been changed to unlimited. This is mostly for the
185 * benefit of non-interactive scripts, which arguably should
186 * use -ww, but is compatible with Linux.
188 if ((cols
= getenv("COLUMNS")) != NULL
&& *cols
!= '\0')
189 termwidth
= atoi(cols
);
190 else if ((ioctl(STDOUT_FILENO
, TIOCGWINSZ
, (char *)&ws
) == -1 &&
191 ioctl(STDERR_FILENO
, TIOCGWINSZ
, (char *)&ws
) == -1 &&
192 ioctl(STDIN_FILENO
, TIOCGWINSZ
, (char *)&ws
) == -1) ||
194 termwidth
= UNLIMITED
;
196 termwidth
= ws
.ws_col
- 1;
199 * Hide a number of option-processing kludges in a separate routine,
200 * to support some historical BSD behaviors, such as `ps axu'.
203 argv
[1] = kludge_oldps_options(PS_ARGS
, argv
[1], argv
[2]);
207 all
= descendancy
= _fmt
= nselectors
= optfatal
= 0;
208 prtheader
= showthreads
= wflag
= xkeep_implied
= 0;
209 xkeep
= -1; /* Neither -x nor -X. */
210 init_list(&gidlist
, addelem_gid
, sizeof(gid_t
), "group");
211 init_list(&jidlist
, addelem_jid
, sizeof(int), "jail id");
212 init_list(&pgrplist
, addelem_pid
, sizeof(pid_t
), "process group");
213 init_list(&pidlist
, addelem_pid
, sizeof(pid_t
), "process id");
214 init_list(&ruidlist
, addelem_uid
, sizeof(uid_t
), "ruser");
215 init_list(&sesslist
, addelem_pid
, sizeof(pid_t
), "session id");
216 init_list(&ttylist
, addelem_tty
, sizeof(dev_t
), "tty");
217 init_list(&uidlist
, addelem_uid
, sizeof(uid_t
), "user");
218 memf
= _PATH_DEVNULL
;
221 argc
= xo_parse_args(argc
, argv
);
225 while ((ch
= getopt(argc
, argv
, PS_ARGS
)) != -1)
229 * Exactly the same as `-ax'. This has been
230 * added for compatibility with SUSv3, but for
231 * now it will not be described in the man page.
245 size_t len
= strlen(optarg
);
248 strncasecmp(optarg
, "up", len
) == 0)
251 strncasecmp(optarg
, "down", len
) == 0)
254 strncasecmp(optarg
, "both", len
) == 0)
263 case 'e': /* XXX set ufmt */
270 add_list(&gidlist
, optarg
);
277 * XXX - This SUSv3 behavior is still under debate
278 * since it conflicts with the (undocumented)
279 * `-g' option. So we skip it for now.
281 add_list(&pgrplist
, optarg
);
286 /* The historical BSD-ish (from SunOS) behavior. */
290 showthreads
= KERN_PROC_INC_THREAD
;
293 prtheader
= ws
.ws_row
> 5 ? ws
.ws_row
: 22;
296 add_list(&jidlist
, optarg
);
326 o1
[0] = o2
[0] = '\0';
334 add_list(&pidlist
, optarg
);
336 * Note: `-p' does not *set* xkeep, but any values
337 * from pidlist are checked before xkeep is. That
338 * way they are always matched, even if the user
346 * XXX - This un-standard option is still under
347 * debate. This is what SUSv3 defines as
348 * the `-U' option, and while it would be
349 * nice to have, it could cause even more
350 * confusion to implement it as `-R'.
352 add_list(&ruidlist
, optarg
);
366 * XXX - This non-standard option is still under
367 * debate. This *is* supported on Solaris,
368 * Linux, and IRIX, but conflicts with `-s'
369 * on NetBSD and maybe some older BSD's.
371 add_list(&sesslist
, optarg
);
377 if ((optarg
= ttyname(STDIN_FILENO
)) == NULL
)
378 xo_errx(1, "stdin: not a terminal");
381 add_list(&ttylist
, optarg
);
386 /* This is what SUSv3 defines as the `-u' option. */
387 add_list(&uidlist
, optarg
);
405 termwidth
= UNLIMITED
;
406 else if (termwidth
< 131 && termwidth
!= UNLIMITED
)
412 * Note that `-X' and `-x' are not standard "selector"
413 * options. For most selector-options, we check *all*
414 * processes to see if any are matched by the given
415 * value(s). After we have a set of all the matched
416 * processes, then `-X' and `-x' govern whether we
417 * modify that *matched* set for processes which do
418 * not have a controlling terminal. `-X' causes
419 * those processes to be deleted from the matched
420 * set, while `-x' causes them to be kept.
439 * If there arguments after processing all the options, attempt
440 * to treat them as a list of process ids.
443 if (!isdigitch(**argv
))
445 add_list(&pidlist
, *argv
);
449 xo_warnx("illegal argument: %s\n", *argv
);
453 exit(1); /* Error messages already printed. */
454 if (xkeep
< 0) /* Neither -X nor -x was specified. */
455 xkeep
= xkeep_implied
;
457 kd
= kvm_openfiles(nlistf
, memf
, NULL
, O_RDONLY
, errbuf
);
459 xo_errx(1, "%s", errbuf
);
464 if (!all
&& nselectors
== 0) {
465 uidlist
.l
.ptr
= malloc(sizeof(uid_t
));
466 if (uidlist
.l
.ptr
== NULL
)
467 xo_errx(1, "malloc failed");
469 uidlist
.count
= uidlist
.maxcount
= 1;
470 *uidlist
.l
.uids
= getuid();
474 * scan requested variables, noting what structures are needed,
475 * and adjusting header widths as appropriate.
480 * Get process list. If the user requested just one selector-
481 * option, then kvm_getprocs can be asked to return just those
482 * processes. Otherwise, have it return all processes, and
483 * then this routine will search that full list and select the
484 * processes which match any of the user's selector-options.
486 what
= showthreads
!= 0 ? KERN_PROC_ALL
: KERN_PROC_PROC
;
488 if (nselectors
== 1) {
489 if (gidlist
.count
== 1) {
490 what
= KERN_PROC_RGID
| showthreads
;
491 flag
= *gidlist
.l
.gids
;
493 } else if (pgrplist
.count
== 1) {
494 what
= KERN_PROC_PGRP
| showthreads
;
495 flag
= *pgrplist
.l
.pids
;
497 } else if (pidlist
.count
== 1 && directions
== NONE
) {
498 what
= KERN_PROC_PID
| showthreads
;
499 flag
= *pidlist
.l
.pids
;
501 } else if (ruidlist
.count
== 1) {
502 what
= KERN_PROC_RUID
| showthreads
;
503 flag
= *ruidlist
.l
.uids
;
505 } else if (sesslist
.count
== 1) {
506 what
= KERN_PROC_SESSION
| showthreads
;
507 flag
= *sesslist
.l
.pids
;
509 } else if (ttylist
.count
== 1) {
510 what
= KERN_PROC_TTY
| showthreads
;
511 flag
= *ttylist
.l
.ttys
;
513 } else if (uidlist
.count
== 1) {
514 what
= KERN_PROC_UID
| showthreads
;
515 flag
= *uidlist
.l
.uids
;
524 kp
= kvm_getprocs(kd
, what
, flag
, &nentries
);
526 * Ignore ESRCH to preserve behaviour of "ps -p nonexistent-pid"
527 * not reporting an error.
529 if ((kp
== NULL
&& errno
!= ESRCH
) || (kp
!= NULL
&& nentries
< 0))
530 xo_errx(1, "%s", kvm_geterr(kd
));
532 pid_count
.initial
= pidlist
.count
;
533 if (directions
& DOWN
)
534 for (elem
= 0; elem
< pidlist
.count
; elem
++)
535 for (i
= 0; i
< nentries
; i
++) {
536 if (kp
[i
].ki_ppid
== kp
[i
].ki_pid
)
538 if (kp
[i
].ki_ppid
== pidlist
.l
.pids
[elem
]) {
539 if (pidlist
.count
>= pidlist
.maxcount
)
540 expand_list(&pidlist
);
541 pidlist
.l
.pids
[pidlist
.count
++] = kp
[i
].ki_pid
;
544 pid_count
.traversed
= pidlist
.count
;
546 for (elem
= 0; elem
< pidlist
.count
; elem
++) {
547 if (elem
>= pid_count
.initial
&& elem
< pid_count
.traversed
)
549 for (i
= 0; i
< nentries
; i
++) {
550 if (kp
[i
].ki_ppid
== kp
[i
].ki_pid
)
552 if (kp
[i
].ki_pid
== pidlist
.l
.pids
[elem
]) {
553 if (pidlist
.count
>= pidlist
.maxcount
)
554 expand_list(&pidlist
);
555 pidlist
.l
.pids
[pidlist
.count
++] = kp
[i
].ki_ppid
;
560 if ((kinfo
= malloc(nentries
* sizeof(*kinfo
))) == NULL
)
561 xo_errx(1, "malloc failed");
562 for (i
= nentries
; --i
>= 0; ++kp
) {
564 * If the user specified multiple selection-criteria,
565 * then keep any process matched by the inclusive OR
566 * of all the selection-criteria given.
568 if (pidlist
.count
> 0) {
569 for (elem
= 0; elem
< pidlist
.count
; elem
++)
570 if (kp
->ki_pid
== pidlist
.l
.pids
[elem
])
574 * Note that we had to process pidlist before
575 * filtering out processes which do not have
576 * a controlling terminal.
579 if ((kp
->ki_tdev
== NODEV
||
580 (kp
->ki_flag
& P_CONTROLT
) == 0))
585 if (gidlist
.count
> 0) {
586 for (elem
= 0; elem
< gidlist
.count
; elem
++)
587 if (kp
->ki_rgid
== gidlist
.l
.gids
[elem
])
590 if (jidlist
.count
> 0) {
591 for (elem
= 0; elem
< jidlist
.count
; elem
++)
592 if (kp
->ki_jid
== jidlist
.l
.jids
[elem
])
595 if (pgrplist
.count
> 0) {
596 for (elem
= 0; elem
< pgrplist
.count
; elem
++)
598 pgrplist
.l
.pids
[elem
])
601 if (ruidlist
.count
> 0) {
602 for (elem
= 0; elem
< ruidlist
.count
; elem
++)
604 ruidlist
.l
.uids
[elem
])
607 if (sesslist
.count
> 0) {
608 for (elem
= 0; elem
< sesslist
.count
; elem
++)
609 if (kp
->ki_sid
== sesslist
.l
.pids
[elem
])
612 if (ttylist
.count
> 0) {
613 for (elem
= 0; elem
< ttylist
.count
; elem
++)
614 if (kp
->ki_tdev
== ttylist
.l
.ttys
[elem
])
617 if (uidlist
.count
> 0) {
618 for (elem
= 0; elem
< uidlist
.count
; elem
++)
619 if (kp
->ki_uid
== uidlist
.l
.uids
[elem
])
623 * This process did not match any of the user's
624 * selector-options, so skip the process.
629 next_KINFO
= &kinfo
[nkept
];
630 next_KINFO
->ki_p
= kp
;
631 next_KINFO
->ki_d
.level
= 0;
632 next_KINFO
->ki_d
.prefix
= NULL
;
633 next_KINFO
->ki_pcpu
= getpcpu(next_KINFO
);
634 if (sortby
== SORTMEM
)
635 next_KINFO
->ki_memsize
= kp
->ki_tsize
+
636 kp
->ki_dsize
+ kp
->ki_ssize
;
638 saveuser(next_KINFO
);
655 qsort(kinfo
, nkept
, sizeof(KINFO
), pscomp
);
658 * We want things in descendant order
661 descendant_sort(kinfo
, nkept
);
665 * Prepare formatted output.
667 for (i
= 0; i
< nkept
; i
++)
668 format_output(&kinfo
[i
]);
673 xo_open_container("process-information");
675 if (xo_get_style(NULL
) != XO_STYLE_TEXT
)
676 termwidth
= UNLIMITED
;
679 * Output formatted lines.
681 xo_open_list("process");
682 for (i
= lineno
= 0; i
< nkept
; i
++) {
684 xo_open_instance("process");
685 STAILQ_FOREACH(vent
, &varlist
, next_ve
) {
686 ks
= STAILQ_FIRST(&kinfo
[i
].ki_ks
);
687 STAILQ_REMOVE_HEAD(&kinfo
[i
].ki_ks
, ks_next
);
688 /* Truncate rightmost column if necessary. */
689 fwidthmax
= _POSIX2_LINE_MAX
;
690 if (STAILQ_NEXT(vent
, next_ve
) == NULL
&&
691 termwidth
!= UNLIMITED
&& ks
->ks_str
!= NULL
) {
692 left
= termwidth
- linelen
;
693 if (left
> 0 && left
< (int)strlen(ks
->ks_str
))
700 /* No padding for the last column, if it's LJUST. */
701 fwidthmin
= (xo_get_style(NULL
) != XO_STYLE_TEXT
||
702 (STAILQ_NEXT(vent
, next_ve
) == NULL
&&
703 (vent
->var
->flag
& LJUST
))) ? 0 : vent
->var
->width
;
704 snprintf(fmtbuf
, sizeof(fmtbuf
), "{:%s/%%%s%d..%dhs}",
705 vent
->var
->field
? vent
->var
->field
: vent
->var
->name
,
706 (vent
->var
->flag
& LJUST
) ? "-" : "",
707 fwidthmin
, fwidthmax
);
708 xo_emit(fmtbuf
, str
);
709 linelen
+= fwidthmin
;
711 if (ks
->ks_str
!= NULL
) {
718 if (STAILQ_NEXT(vent
, next_ve
) != NULL
) {
724 xo_close_instance("process");
725 if (prtheader
&& lineno
++ == prtheader
- 4) {
731 xo_close_list("process");
732 xo_close_container("process-information");
739 free_list(&pgrplist
);
740 free_list(&ruidlist
);
741 free_list(&sesslist
);
744 for (i
= 0; i
< nkept
; i
++)
745 free(kinfo
[i
].ki_d
.prefix
);
752 addelem_gid(struct listinfo
*inf
, const char *elem
)
755 const char *nameorID
;
759 if (*elem
== '\0' || strlen(elem
) >= MAXLOGNAME
) {
761 xo_warnx("Invalid (zero-length) %s name", inf
->lname
);
763 xo_warnx("%s name too long: %s", inf
->lname
, elem
);
765 return (0); /* Do not add this value. */
769 * SUSv3 states that `ps -G grouplist' should match "real-group
770 * ID numbers", and does not mention group-names. I do want to
771 * also support group-names, so this tries for a group-id first,
772 * and only tries for a name if that doesn't work. This is the
773 * opposite order of what is done in addelem_uid(), but in
774 * practice the order would only matter for group-names which
780 bigtemp
= strtoul(elem
, &endp
, 10);
781 if (errno
== 0 && *endp
== '\0' && bigtemp
<= GID_MAX
) {
782 nameorID
= "name or ID matches";
783 grp
= getgrgid((gid_t
)bigtemp
);
786 grp
= getgrnam(elem
);
788 xo_warnx("No %s %s '%s'", inf
->lname
, nameorID
, elem
);
792 if (inf
->count
>= inf
->maxcount
)
794 inf
->l
.gids
[(inf
->count
)++] = grp
->gr_gid
;
799 addelem_jid(struct listinfo
*inf
, const char *elem
)
804 xo_warnx("Invalid (zero-length) jail id");
806 return (0); /* Do not add this value. */
809 tempid
= jail_getid(elem
);
811 xo_warnx("Invalid %s: %s", inf
->lname
, elem
);
816 if (inf
->count
>= inf
->maxcount
)
818 inf
->l
.jids
[(inf
->count
)++] = tempid
;
823 addelem_pid(struct listinfo
*inf
, const char *elem
)
829 xo_warnx("Invalid (zero-length) process id");
831 return (0); /* Do not add this value. */
835 tempid
= strtol(elem
, &endp
, 10);
836 if (*endp
!= '\0' || tempid
< 0 || elem
== endp
) {
837 xo_warnx("Invalid %s: %s", inf
->lname
, elem
);
839 } else if (errno
!= 0 || tempid
> pid_max
) {
840 xo_warnx("%s too large: %s", inf
->lname
, elem
);
843 if (errno
== ERANGE
) {
847 if (inf
->count
>= inf
->maxcount
)
849 inf
->l
.pids
[(inf
->count
)++] = tempid
;
854 * The user can specify a device via one of three formats:
855 * 1) fully qualified, e.g.: /dev/ttyp0 /dev/console /dev/pts/0
856 * 2) missing "/dev", e.g.: ttyp0 console pts/0
857 * 3) two-letters, e.g.: p0 co 0
858 * (matching letters that would be seen in the "TT" column)
861 addelem_tty(struct listinfo
*inf
, const char *elem
)
865 char pathbuf
[PATH_MAX
], pathbuf2
[PATH_MAX
], pathbuf3
[PATH_MAX
];
875 if (strcmp(elem
, "co") == 0) {
876 ttypath
= _PATH_CONSOLE
;
881 strlcpy(pathbuf
, _PATH_DEV
, sizeof(pathbuf
));
882 strlcat(pathbuf
, elem
, sizeof(pathbuf
));
884 if (strncmp(pathbuf
, _PATH_TTY
, strlen(_PATH_TTY
)) == 0)
886 if (strncmp(pathbuf
, _PATH_PTS
, strlen(_PATH_PTS
)) == 0)
888 if (strcmp(pathbuf
, _PATH_CONSOLE
) == 0)
890 /* Check to see if /dev/tty${elem} exists */
891 strlcpy(pathbuf2
, _PATH_TTY
, sizeof(pathbuf2
));
892 strlcat(pathbuf2
, elem
, sizeof(pathbuf2
));
893 if (stat(pathbuf2
, &sb
) == 0 && S_ISCHR(sb
.st_mode
)) {
894 /* No need to repeat stat() && S_ISCHR() checks */
898 /* Check to see if /dev/pts/${elem} exists */
899 strlcpy(pathbuf3
, _PATH_PTS
, sizeof(pathbuf3
));
900 strlcat(pathbuf3
, elem
, sizeof(pathbuf3
));
901 if (stat(pathbuf3
, &sb
) == 0 && S_ISCHR(sb
.st_mode
)) {
902 /* No need to repeat stat() && S_ISCHR() checks */
909 if (stat(ttypath
, &sb
) == -1) {
910 if (pathbuf3
[0] != '\0')
911 xo_warn("%s, %s, and %s", pathbuf3
, pathbuf2
,
914 xo_warn("%s", ttypath
);
918 if (!S_ISCHR(sb
.st_mode
)) {
919 if (pathbuf3
[0] != '\0')
920 xo_warnx("%s, %s, and %s: Not a terminal",
921 pathbuf3
, pathbuf2
, ttypath
);
923 xo_warnx("%s: Not a terminal", ttypath
);
928 if (inf
->count
>= inf
->maxcount
)
930 inf
->l
.ttys
[(inf
->count
)++] = sb
.st_rdev
;
935 addelem_uid(struct listinfo
*inf
, const char *elem
)
941 if (*elem
== '\0' || strlen(elem
) >= MAXLOGNAME
) {
943 xo_warnx("Invalid (zero-length) %s name", inf
->lname
);
945 xo_warnx("%s name too long: %s", inf
->lname
, elem
);
947 return (0); /* Do not add this value. */
950 pwd
= getpwnam(elem
);
953 bigtemp
= strtoul(elem
, &endp
, 10);
954 if (errno
!= 0 || *endp
!= '\0' || bigtemp
> UID_MAX
)
955 xo_warnx("No %s named '%s'", inf
->lname
, elem
);
957 /* The string is all digits, so it might be a userID. */
958 pwd
= getpwuid((uid_t
)bigtemp
);
960 xo_warnx("No %s name or ID matches '%s'",
966 * These used to be treated as minor warnings (and the
967 * option was simply ignored), but now they are fatal
968 * errors (and the command will be aborted).
973 if (inf
->count
>= inf
->maxcount
)
975 inf
->l
.uids
[(inf
->count
)++] = pwd
->pw_uid
;
980 add_list(struct listinfo
*inf
, const char *argp
)
985 char elemcopy
[PATH_MAX
];
988 inf
->addelem(inf
, argp
);
989 while (*argp
!= '\0') {
990 while (*argp
!= '\0' && strchr(W_SEP
, *argp
) != NULL
)
995 if (strchr(T_SEP
, *argp
) == NULL
) {
996 endp
= elemcopy
+ sizeof(elemcopy
) - 1;
997 while (*argp
!= '\0' && cp
<= endp
&&
998 strchr(W_SEP T_SEP
, *argp
) == NULL
)
1006 * Add this single element to the given list.
1008 inf
->addelem(inf
, elemcopy
);
1011 * The string is too long to copy. Find the end
1012 * of the string to print out the warning message.
1014 while (*argp
!= '\0' && strchr(W_SEP T_SEP
,
1017 xo_warnx("Value too long: %.*s", (int)(argp
- savep
),
1022 * Skip over any number of trailing whitespace characters,
1023 * but only one (at most) trailing element-terminating
1026 while (*argp
!= '\0' && strchr(W_SEP
, *argp
) != NULL
)
1028 if (*argp
!= '\0' && strchr(T_SEP
, *argp
) != NULL
) {
1030 /* Catch case where string ended with a comma. */
1032 inf
->addelem(inf
, argp
);
1038 descendant_sort(KINFO
*ki
, int items
)
1040 int dst
, lvl
, maxlvl
, n
, ndst
, nsrc
, siblings
, src
;
1041 unsigned char *path
;
1045 * First, sort the entries by descendancy, tracking the descendancy
1046 * depth in the ki_d.level field.
1050 while (src
< items
) {
1051 if (ki
[src
].ki_d
.level
) {
1055 for (nsrc
= 1; src
+ nsrc
< items
; nsrc
++)
1056 if (!ki
[src
+ nsrc
].ki_d
.level
)
1059 for (dst
= 0; dst
< items
; dst
++) {
1060 if (ki
[dst
].ki_p
->ki_pid
== ki
[src
].ki_p
->ki_pid
)
1062 if (ki
[dst
].ki_p
->ki_pid
== ki
[src
].ki_p
->ki_ppid
)
1071 for (ndst
= 1; dst
+ ndst
< items
; ndst
++)
1072 if (ki
[dst
+ ndst
].ki_d
.level
<= ki
[dst
].ki_d
.level
)
1075 for (n
= src
; n
< src
+ nsrc
; n
++) {
1076 ki
[n
].ki_d
.level
+= ki
[dst
].ki_d
.level
+ 1;
1077 if (maxlvl
< ki
[n
].ki_d
.level
)
1078 maxlvl
= ki
[n
].ki_d
.level
;
1084 memmove(ki
+ src
, ki
+ src
+ 1,
1085 (dst
- src
+ ndst
- 1) * sizeof *ki
);
1086 ki
[dst
+ ndst
- 1] = kn
;
1090 } else if (src
!= dst
+ ndst
) {
1092 memmove(ki
+ dst
+ ndst
+ 1, ki
+ dst
+ ndst
,
1093 (src
- dst
- ndst
) * sizeof *ki
);
1094 ki
[dst
+ ndst
] = kn
;
1107 * Now populate ki_d.prefix (instead of ki_d.level) with the command
1108 * prefix used to show descendancies.
1110 path
= calloc((maxlvl
+ 7) / 8, sizeof(unsigned char));
1111 for (src
= 0; src
< items
; src
++) {
1112 if ((lvl
= ki
[src
].ki_d
.level
) == 0) {
1113 ki
[src
].ki_d
.prefix
= NULL
;
1116 if ((ki
[src
].ki_d
.prefix
= malloc(lvl
* 2 + 1)) == NULL
)
1117 xo_errx(1, "malloc failed");
1118 for (n
= 0; n
< lvl
- 2; n
++) {
1119 ki
[src
].ki_d
.prefix
[n
* 2] =
1120 path
[n
/ 8] & 1 << (n
% 8) ? '|' : ' ';
1121 ki
[src
].ki_d
.prefix
[n
* 2 + 1] = ' ';
1124 /* Have I any more siblings? */
1125 for (siblings
= 0, dst
= src
+ 1; dst
< items
; dst
++) {
1126 if (ki
[dst
].ki_d
.level
> lvl
)
1128 if (ki
[dst
].ki_d
.level
== lvl
)
1133 path
[n
/ 8] |= 1 << (n
% 8);
1135 path
[n
/ 8] &= ~(1 << (n
% 8));
1136 ki
[src
].ki_d
.prefix
[n
* 2] = siblings
? '|' : '`';
1137 ki
[src
].ki_d
.prefix
[n
* 2 + 1] = '-';
1140 strcpy(ki
[src
].ki_d
.prefix
+ n
* 2, "- ");
1146 expand_list(struct listinfo
*inf
)
1151 newmax
= (inf
->maxcount
+ 1) << 1;
1152 newlist
= realloc(inf
->l
.ptr
, newmax
* inf
->elemsize
);
1153 if (newlist
== NULL
) {
1155 xo_errx(1, "realloc to %d %ss failed", newmax
, inf
->lname
);
1157 inf
->maxcount
= newmax
;
1158 inf
->l
.ptr
= newlist
;
1164 free_list(struct listinfo
*inf
)
1167 inf
->count
= inf
->elemsize
= inf
->maxcount
= 0;
1168 if (inf
->l
.ptr
!= NULL
)
1170 inf
->addelem
= NULL
;
1176 init_list(struct listinfo
*inf
, addelem_rtn artn
, int elemsize
,
1180 inf
->count
= inf
->maxcount
= 0;
1181 inf
->elemsize
= elemsize
;
1182 inf
->addelem
= artn
;
1188 find_varentry(VAR
*v
)
1190 struct varent
*vent
;
1192 STAILQ_FOREACH(vent
, &varlist
, next_ve
) {
1193 if (strcmp(vent
->var
->name
, v
->name
) == 0)
1202 struct varent
*vent
;
1205 STAILQ_FOREACH(vent
, &varlist
, next_ve
) {
1215 format_output(KINFO
*ki
)
1217 struct varent
*vent
;
1223 STAILQ_INIT(&ki
->ki_ks
);
1224 STAILQ_FOREACH(vent
, &varlist
, next_ve
) {
1226 str
= (v
->oproc
)(ki
, vent
);
1227 ks
= malloc(sizeof(*ks
));
1229 xo_errx(1, "malloc failed");
1231 STAILQ_INSERT_TAIL(&ki
->ki_ks
, ks
, ks_next
);
1244 struct varent
*vent
;
1248 STAILQ_FOREACH(vent
, &varlist
, next_ve
) {
1250 i
= strlen(vent
->header
);
1257 fmt(char **(*fn
)(kvm_t
*, const struct kinfo_proc
*, int), KINFO
*ki
,
1258 char *comm
, char *thread
, int maxlen
)
1262 s
= fmt_argv((*fn
)(kd
, ki
->ki_p
, termwidth
), comm
,
1263 showthreads
&& ki
->ki_p
->ki_numthreads
> 1 ? thread
: NULL
, maxlen
);
1270 char tdname
[COMMLEN
+ 1];
1275 * save arguments if needed
1278 if (ki
->ki_p
->ki_stat
== SZOMB
) {
1279 ki
->ki_args
= strdup("<defunct>");
1281 (void)snprintf(tdname
, sizeof(tdname
), "%s%s",
1282 ki
->ki_p
->ki_tdname
, ki
->ki_p
->ki_moretdname
);
1283 ki
->ki_args
= fmt(kvm_getargv
, ki
,
1284 ki
->ki_p
->ki_comm
, tdname
, COMMLEN
* 2 + 1);
1286 if (ki
->ki_args
== NULL
)
1287 xo_errx(1, "malloc failed");
1292 ki
->ki_env
= fmt(kvm_getenvv
, ki
, (char *)NULL
,
1294 if (ki
->ki_env
== NULL
)
1295 xo_errx(1, "malloc failed");
1301 /* A macro used to improve the readability of pscomp(). */
1302 #define DIFF_RETURN(a, b, field) do { \
1303 if ((a)->field != (b)->field) \
1304 return (((a)->field < (b)->field) ? -1 : 1); \
1308 pscomp(const void *a
, const void *b
)
1310 const KINFO
*ka
, *kb
;
1314 /* SORTCPU and SORTMEM are sorted in descending order. */
1315 if (sortby
== SORTCPU
)
1316 DIFF_RETURN(kb
, ka
, ki_pcpu
);
1317 if (sortby
== SORTMEM
)
1318 DIFF_RETURN(kb
, ka
, ki_memsize
);
1320 * TTY's are sorted in ascending order, except that all NODEV
1321 * processes come before all processes with a device.
1323 if (ka
->ki_p
->ki_tdev
!= kb
->ki_p
->ki_tdev
) {
1324 if (ka
->ki_p
->ki_tdev
== NODEV
)
1326 if (kb
->ki_p
->ki_tdev
== NODEV
)
1328 DIFF_RETURN(ka
, kb
, ki_p
->ki_tdev
);
1331 /* PID's and TID's (threads) are sorted in ascending order. */
1332 DIFF_RETURN(ka
, kb
, ki_p
->ki_pid
);
1333 DIFF_RETURN(ka
, kb
, ki_p
->ki_tid
);
1339 * ICK (all for getopt), would rather hide the ugliness
1340 * here than taint the main code.
1345 * The old convention that 't' with no trailing tty arg means the users
1346 * tty, is only supported if argv[1] doesn't begin with a '-'. This same
1347 * feature is available with the option 'T', which takes no argument.
1350 kludge_oldps_options(const char *optlist
, char *origval
, const char *nextarg
)
1353 char *argp
, *cp
, *newopts
, *ns
, *optp
, *pidp
;
1356 * See if the original value includes any option which takes an
1357 * argument (and will thus use up the remainder of the string).
1360 if (optlist
!= NULL
) {
1361 for (cp
= origval
; *cp
!= '\0'; cp
++) {
1362 optp
= strchr(optlist
, *cp
);
1363 if ((optp
!= NULL
) && *(optp
+ 1) == ':') {
1369 if (argp
!= NULL
&& *origval
== '-')
1373 * if last letter is a 't' flag with no argument (in the context
1374 * of the oldps options -- option string NOT starting with a '-' --
1375 * then convert to 'T' (meaning *this* terminal, i.e. ttyname(0)).
1377 * However, if a flag accepting a string argument is found earlier
1378 * in the option string (including a possible `t' flag), then the
1379 * remainder of the string must be the argument to that flag; so
1380 * do not modify that argument. Note that a trailing `t' would
1381 * cause argp to be set, if argp was not already set by some
1384 len
= strlen(origval
);
1385 cp
= origval
+ len
- 1;
1387 if (*cp
== 't' && *origval
!= '-' && cp
== argp
) {
1388 if (nextarg
== NULL
|| *nextarg
== '-' || isdigitch(*nextarg
))
1390 } else if (argp
== NULL
) {
1392 * The original value did not include any option which takes
1393 * an argument (and that would include `p' and `t'), so check
1394 * the value for trailing number, or comma-separated list of
1395 * numbers, which we will treat as a pid request.
1397 if (isdigitch(*cp
)) {
1398 while (cp
>= origval
&& (*cp
== ',' || isdigitch(*cp
)))
1405 * If nothing needs to be added to the string, then return
1406 * the "original" (although possibly modified) value.
1408 if (*origval
== '-' && pidp
== NULL
)
1412 * Create a copy of the string to add '-' and/or 'p' to the
1415 if ((newopts
= ns
= malloc(len
+ 3)) == NULL
)
1416 xo_errx(1, "malloc failed");
1418 if (*origval
!= '-')
1419 *ns
++ = '-'; /* add option flag */
1422 strcpy(ns
, origval
);
1425 * Copy everything before the pid string, add the `p',
1426 * and then copy the pid string.
1428 len
= pidp
- origval
;
1429 memcpy(ns
, origval
, len
);
1443 intsize
= sizeof(pid_max
);
1444 if (sysctlbyname("kern.pid_max", &pid_max
, &intsize
, NULL
, 0) < 0) {
1445 xo_warn("unable to read kern.pid_max");
1453 #define SINGLE_OPTS "[-aCcdeHhjlmrSTuvwXxZ]"
1455 xo_error("%s\n%s\n%s\n%s\n%s\n",
1456 "usage: ps [--libxo] " SINGLE_OPTS
" [-O fmt | -o fmt]",
1457 " [-G gid[,gid...]] [-J jid[,jid...]] [-M core] [-N system]",
1458 " [-p pid[,pid...]] [-t tty[,tty...]] [-U user[,user...]]",
1459 " [-D up | down | both]",
1460 " ps [--libxo] -L");