aboutsummaryrefslogtreecommitdiff
path: root/bin/ps
diff options
context:
space:
mode:
Diffstat (limited to 'bin/ps')
-rw-r--r--bin/ps/Makefile10
-rw-r--r--bin/ps/Makefile.depend19
-rw-r--r--bin/ps/extern.h95
-rw-r--r--bin/ps/fmt.c130
-rw-r--r--bin/ps/keyword.c483
-rw-r--r--bin/ps/nlist.c60
-rw-r--r--bin/ps/print.c867
-rw-r--r--bin/ps/ps.11019
-rw-r--r--bin/ps/ps.c1548
-rw-r--r--bin/ps/ps.h100
10 files changed, 4331 insertions, 0 deletions
diff --git a/bin/ps/Makefile b/bin/ps/Makefile
new file mode 100644
index 000000000000..94a6b07757f0
--- /dev/null
+++ b/bin/ps/Makefile
@@ -0,0 +1,10 @@
+PACKAGE=runtime
+PROG= ps
+SRCS= fmt.c keyword.c nlist.c print.c ps.c
+
+LIBADD= m kvm jail xo
+.ifdef PS_CHECK_KEYWORDS
+CFLAGS+=-DPS_CHECK_KEYWORDS
+.endif
+
+.include <bsd.prog.mk>
diff --git a/bin/ps/Makefile.depend b/bin/ps/Makefile.depend
new file mode 100644
index 000000000000..521210d8ba8e
--- /dev/null
+++ b/bin/ps/Makefile.depend
@@ -0,0 +1,19 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libjail \
+ lib/libkvm \
+ lib/libxo/libxo \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/ps/extern.h b/bin/ps/extern.h
new file mode 100644
index 000000000000..bb7a21bbc8be
--- /dev/null
+++ b/bin/ps/extern.h
@@ -0,0 +1,95 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+struct kinfo;
+struct nlist;
+struct var;
+struct varent;
+
+extern fixpt_t ccpu;
+extern int cflag, eval, fscale, nlistread, rawcpu;
+extern unsigned long mempages;
+extern time_t now;
+extern int showthreads, sumrusage, termwidth;
+extern struct velisthead varlist;
+extern const size_t known_keywords_nb;
+
+__BEGIN_DECLS
+char *arguments(KINFO *, VARENT *);
+void check_keywords(void);
+char *command(KINFO *, VARENT *);
+char *cputime(KINFO *, VARENT *);
+char *cpunum(KINFO *, VARENT *);
+int donlist(void);
+char *elapsed(KINFO *, VARENT *);
+char *elapseds(KINFO *, VARENT *);
+char *emulname(KINFO *, VARENT *);
+VARENT *find_varentry(const char *);
+const char *fmt_argv(char **, char *, char *, size_t);
+double getpcpu(const KINFO *);
+char *jailname(KINFO *, VARENT *);
+size_t aliased_keyword_index(const VAR *);
+char *kvar(KINFO *, VARENT *);
+char *label(KINFO *, VARENT *);
+char *loginclass(KINFO *, VARENT *);
+char *logname(KINFO *, VARENT *);
+char *longtname(KINFO *, VARENT *);
+char *lstarted(KINFO *, VARENT *);
+char *maxrss(KINFO *, VARENT *);
+char *lockname(KINFO *, VARENT *);
+char *mwchan(KINFO *, VARENT *);
+char *nwchan(KINFO *, VARENT *);
+char *pagein(KINFO *, VARENT *);
+void parsefmt(const char *, struct velisthead *, int);
+char *pcpu(KINFO *, VARENT *);
+char *pmem(KINFO *, VARENT *);
+char *pri(KINFO *, VARENT *);
+void printheader(void);
+char *priorityr(KINFO *, VARENT *);
+char *egroupname(KINFO *, VARENT *);
+char *rgroupname(KINFO *, VARENT *);
+void resolve_aliases(void);
+char *runame(KINFO *, VARENT *);
+char *rvar(KINFO *, VARENT *);
+void showkey(void);
+char *started(KINFO *, VARENT *);
+char *state(KINFO *, VARENT *);
+char *systime(KINFO *, VARENT *);
+char *tdev(KINFO *, VARENT *);
+char *tdnam(KINFO *, VARENT *);
+char *tname(KINFO *, VARENT *);
+char *ucomm(KINFO *, VARENT *);
+char *username(KINFO *, VARENT *);
+char *upr(KINFO *, VARENT *);
+char *usertime(KINFO *, VARENT *);
+char *vsize(KINFO *, VARENT *);
+char *wchan(KINFO *, VARENT *);
+__END_DECLS
diff --git a/bin/ps/fmt.c b/bin/ps/fmt.c
new file mode 100644
index 000000000000..87e2e0f49a34
--- /dev/null
+++ b/bin/ps/fmt.c
@@ -0,0 +1,130 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "ps.h"
+
+static char *cmdpart(char *);
+static char *shquote(char **);
+
+static char *
+shquote(char **argv)
+{
+ long arg_max;
+ static size_t buf_size;
+ size_t len;
+ char **p, *dst, *src;
+ static char *buf = NULL;
+
+ if (buf == NULL) {
+ if ((arg_max = sysconf(_SC_ARG_MAX)) == -1)
+ errx(1, "sysconf _SC_ARG_MAX failed");
+ if (arg_max >= LONG_MAX / 4 || arg_max >= (long)(SIZE_MAX / 4))
+ errx(1, "sysconf _SC_ARG_MAX preposterously large");
+ buf_size = 4 * arg_max + 1;
+ if ((buf = malloc(buf_size)) == NULL)
+ errx(1, "malloc failed");
+ }
+
+ if (*argv == NULL) {
+ buf[0] = '\0';
+ return (buf);
+ }
+ dst = buf;
+ for (p = argv; (src = *p++) != NULL; ) {
+ if (*src == '\0')
+ continue;
+ len = (buf_size - 1 - (dst - buf)) / 4;
+ strvisx(dst, src, strlen(src) < len ? strlen(src) : len,
+ VIS_NL | VIS_CSTYLE);
+ while (*dst != '\0')
+ dst++;
+ if ((buf_size - 1 - (dst - buf)) / 4 > 0)
+ *dst++ = ' ';
+ }
+ /* Chop off trailing space */
+ if (dst != buf && dst[-1] == ' ')
+ dst--;
+ *dst = '\0';
+ return (buf);
+}
+
+static char *
+cmdpart(char *arg0)
+{
+ char *cp;
+
+ return ((cp = strrchr(arg0, '/')) != NULL ? cp + 1 : arg0);
+}
+
+const char *
+fmt_argv(char **argv, char *cmd, char *thread, size_t maxlen)
+{
+ size_t len;
+ char *ap, *cp;
+
+ if (argv == NULL || argv[0] == NULL) {
+ if (cmd == NULL)
+ return ("");
+ ap = NULL;
+ len = maxlen + 3;
+ } else {
+ ap = shquote(argv);
+ len = strlen(ap) + maxlen + 4;
+ }
+ cp = malloc(len);
+ if (cp == NULL)
+ errx(1, "malloc failed");
+ if (ap == NULL) {
+ if (thread != NULL) {
+ asprintf(&ap, "%s/%s", cmd, thread);
+ sprintf(cp, "[%.*s]", (int)maxlen, ap);
+ free(ap);
+ } else
+ sprintf(cp, "[%.*s]", (int)maxlen, cmd);
+ } else if (strncmp(cmdpart(argv[0]), cmd, maxlen) != 0)
+ sprintf(cp, "%s (%.*s)", ap, (int)maxlen, cmd);
+ else
+ strcpy(cp, ap);
+ return (cp);
+}
diff --git a/bin/ps/keyword.c b/bin/ps/keyword.c
new file mode 100644
index 000000000000..f05e5245f695
--- /dev/null
+++ b/bin/ps/keyword.c
@@ -0,0 +1,483 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2025 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Olivier Certner
+ * <olce@FreeBSD.org> at Kumacom SARL under sponsorship from the FreeBSD
+ * Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/proc.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libxo/xo.h>
+
+#include "ps.h"
+
+static int vcmp(const void *, const void *);
+
+/* Compute offset in common structures. */
+#define KOFF(x) offsetof(struct kinfo_proc, x)
+#define ROFF(x) offsetof(struct rusage, x)
+
+#define LWPFMT "d"
+#define NLWPFMT "d"
+#define UIDFMT "u"
+#define PIDFMT "d"
+
+/* PLEASE KEEP THE TABLE BELOW SORTED ALPHABETICALLY!!! */
+static VAR keywords[] = {
+ {"%cpu", {NULL}, "%CPU", "percent-cpu", 0, pcpu, 0, UNSPEC, NULL},
+ {"%mem", {NULL}, "%MEM", "percent-memory", 0, pmem, 0, UNSPEC, NULL},
+ {"acflag", {NULL}, "ACFLG", "accounting-flag", 0, kvar, KOFF(ki_acflag),
+ USHORT, "x"},
+ {"acflg", {"acflag"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"args", {NULL}, "COMMAND", "arguments", COMM|LJUST|USER, arguments, 0,
+ UNSPEC, NULL},
+ {"blocked", {"sigmask"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"caught", {"sigcatch"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"class", {NULL}, "CLASS", "login-class", LJUST, loginclass, 0,
+ UNSPEC, NULL},
+ {"comm", {NULL}, "COMMAND", "command", LJUST, ucomm, 0, UNSPEC, NULL},
+ {"command", {NULL}, "COMMAND", "command", COMM|LJUST|USER, command, 0,
+ UNSPEC, NULL},
+ {"cow", {NULL}, "COW", "copy-on-write-faults", 0, kvar, KOFF(ki_cow),
+ UINT, "u"},
+ {"cpu", {NULL}, "C", "on-cpu", 0, cpunum, 0, UNSPEC, NULL},
+ {"cputime", {"time"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"dsiz", {NULL}, "DSIZ", "data-size", 0, kvar, KOFF(ki_dsize),
+ PGTOK, "ld"},
+ {"egid", {"gid"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"egroup", {"group"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"emul", {NULL}, "EMUL", "emulation-envirnment", LJUST, emulname, 0,
+ UNSPEC, NULL},
+ {"etime", {NULL}, "ELAPSED", "elapsed-time", USER, elapsed, 0,
+ UNSPEC, NULL},
+ {"etimes", {NULL}, "ELAPSED", "elapsed-times", USER, elapseds, 0,
+ UNSPEC, NULL},
+ {"euid", {"uid"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"f", {NULL}, "F", "flags", 0, kvar, KOFF(ki_flag), LONG, "lx"},
+ {"f2", {NULL}, "F2", "flags2", 0, kvar, KOFF(ki_flag2), INT, "08x"},
+ {"fib", {NULL}, "FIB", "fib", 0, kvar, KOFF(ki_fibnum), INT, "d"},
+ {"flags", {"f"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"flags2", {"f2"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"gid", {NULL}, "GID", "gid", 0, kvar, KOFF(ki_groups), UINT, UIDFMT},
+ {"group", {NULL}, "GROUP", "group", LJUST, egroupname, 0, UNSPEC, NULL},
+ {"ignored", {"sigignore"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"inblk", {NULL}, "INBLK", "read-blocks", USER, rvar, ROFF(ru_inblock),
+ LONG, "ld"},
+ {"inblock", {"inblk"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"jail", {NULL}, "JAIL", "jail-name", LJUST, jailname, 0, UNSPEC, NULL},
+ {"jid", {NULL}, "JID", "jail-id", 0, kvar, KOFF(ki_jid), INT, "d"},
+ {"jobc", {NULL}, "JOBC", "job-control-count", 0, kvar, KOFF(ki_jobc),
+ SHORT, "d"},
+ {"ktrace", {NULL}, "KTRACE", "ktrace", 0, kvar, KOFF(ki_traceflag),
+ INT, "x"},
+ {"label", {NULL}, "LABEL", "label", LJUST, label, 0, UNSPEC, NULL},
+ {"lim", {NULL}, "LIM", "memory-limit", 0, maxrss, 0, UNSPEC, NULL},
+ {"lockname", {NULL}, "LOCK", "lock-name", LJUST, lockname, 0,
+ UNSPEC, NULL},
+ {"login", {NULL}, "LOGIN", "login-name", LJUST, logname, 0,
+ UNSPEC, NULL},
+ {"logname", {"login"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"lstart", {NULL}, "STARTED", "start-time", LJUST|USER, lstarted, 0,
+ UNSPEC, NULL},
+ {"lwp", {NULL}, "LWP", "thread-id", 0, kvar, KOFF(ki_tid),
+ UINT, LWPFMT},
+ {"majflt", {NULL}, "MAJFLT", "major-faults", USER, rvar, ROFF(ru_majflt),
+ LONG, "ld"},
+ {"minflt", {NULL}, "MINFLT", "minor-faults", USER, rvar, ROFF(ru_minflt),
+ LONG, "ld"},
+ {"msgrcv", {NULL}, "MSGRCV", "received-messages", USER, rvar,
+ ROFF(ru_msgrcv), LONG, "ld"},
+ {"msgsnd", {NULL}, "MSGSND", "sent-messages", USER, rvar,
+ ROFF(ru_msgsnd), LONG, "ld"},
+ {"mwchan", {NULL}, "MWCHAN", "wait-channel", LJUST, mwchan, 0,
+ UNSPEC, NULL},
+ {"ni", {"nice"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"nice", {NULL}, "NI", "nice", 0, kvar, KOFF(ki_nice), CHAR, "d"},
+ {"nivcsw", {NULL}, "NIVCSW", "involuntary-context-switches", USER, rvar,
+ ROFF(ru_nivcsw), LONG, "ld"},
+ {"nlwp", {NULL}, "NLWP", "threads", 0, kvar, KOFF(ki_numthreads),
+ UINT, NLWPFMT},
+ {"nsignals", {"nsigs"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"nsigs", {NULL}, "NSIGS", "signals-taken", USER, rvar,
+ ROFF(ru_nsignals), LONG, "ld"},
+ {"nswap", {NULL}, "NSWAP", "swaps", USER, rvar, ROFF(ru_nswap),
+ LONG, "ld"},
+ {"nvcsw", {NULL}, "NVCSW", "voluntary-context-switches", USER, rvar,
+ ROFF(ru_nvcsw), LONG, "ld"},
+ {"nwchan", {NULL}, "NWCHAN", "wait-channel-address", LJUST, nwchan, 0,
+ UNSPEC, NULL},
+ {"oublk", {NULL}, "OUBLK", "written-blocks", USER, rvar,
+ ROFF(ru_oublock), LONG, "ld"},
+ {"oublock", {"oublk"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"paddr", {NULL}, "PADDR", "process-address", 0, kvar, KOFF(ki_paddr),
+ KPTR, "lx"},
+ {"pagein", {NULL}, "PAGEIN", "pageins", USER, pagein, 0, UNSPEC, NULL},
+ {"pcpu", {"%cpu"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"pending", {"sig"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"pgid", {NULL}, "PGID", "process-group", 0, kvar, KOFF(ki_pgid),
+ UINT, PIDFMT},
+ {"pid", {NULL}, "PID", "pid", 0, kvar, KOFF(ki_pid), UINT, PIDFMT},
+ {"pmem", {"%mem"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"ppid", {NULL}, "PPID", "ppid", 0, kvar, KOFF(ki_ppid), UINT, PIDFMT},
+ {"pri", {NULL}, "PRI", "priority", 0, pri, 0, UNSPEC, NULL},
+ {"re", {NULL}, "RE", "residency-time", INF127, kvar, KOFF(ki_swtime),
+ UINT, "d"},
+ {"rgid", {NULL}, "RGID", "real-gid", 0, kvar, KOFF(ki_rgid),
+ UINT, UIDFMT},
+ {"rgroup", {NULL}, "RGROUP", "real-group", LJUST, rgroupname, 0,
+ UNSPEC, NULL},
+ {"rss", {NULL}, "RSS", "rss", 0, kvar, KOFF(ki_rssize), PGTOK, "ld"},
+ {"rtprio", {NULL}, "RTPRIO", "realtime-priority", 0, priorityr,
+ KOFF(ki_pri), UNSPEC, NULL},
+ {"ruid", {NULL}, "RUID", "real-uid", 0, kvar, KOFF(ki_ruid),
+ UINT, UIDFMT},
+ {"ruser", {NULL}, "RUSER", "real-user", LJUST, runame, 0, UNSPEC, NULL},
+ {"sid", {NULL}, "SID", "sid", 0, kvar, KOFF(ki_sid), UINT, PIDFMT},
+ {"sig", {NULL}, "PENDING", "signals-pending", 0, kvar, KOFF(ki_siglist),
+ INT, "x"},
+ {"sigcatch", {NULL}, "CAUGHT", "signals-caught", 0, kvar,
+ KOFF(ki_sigcatch), UINT, "x"},
+ {"sigignore", {NULL}, "IGNORED", "signals-ignored", 0, kvar,
+ KOFF(ki_sigignore), UINT, "x"},
+ {"sigmask", {NULL}, "BLOCKED", "signal-mask", 0, kvar, KOFF(ki_sigmask),
+ UINT, "x"},
+ {"sl", {NULL}, "SL", "sleep-time", INF127, kvar, KOFF(ki_slptime),
+ UINT, "d"},
+ {"ssiz", {NULL}, "SSIZ", "stack-size", 0, kvar, KOFF(ki_ssize),
+ PGTOK, "ld"},
+ {"start", {NULL}, "STARTED", "start-time", LJUST|USER, started, 0,
+ UNSPEC, NULL},
+ {"stat", {"state"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"state", {NULL}, "STAT", "state", LJUST, state, 0, UNSPEC, NULL},
+ {"svgid", {NULL}, "SVGID", "saved-gid", 0, kvar, KOFF(ki_svgid),
+ UINT, UIDFMT},
+ {"svuid", {NULL}, "SVUID", "saved-uid", 0, kvar, KOFF(ki_svuid),
+ UINT, UIDFMT},
+ {"systime", {NULL}, "SYSTIME", "system-time", USER, systime, 0,
+ UNSPEC, NULL},
+ {"tdaddr", {NULL}, "TDADDR", "thread-address", 0, kvar, KOFF(ki_tdaddr),
+ KPTR, "lx"},
+ {"tdev", {NULL}, "TDEV", "terminal-device", 0, tdev, 0, UNSPEC, NULL},
+ {"tdnam", {"tdname"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"tdname", {NULL}, "TDNAME", "thread-name", LJUST, tdnam, 0,
+ UNSPEC, NULL},
+ {"tid", {"lwp"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"time", {NULL}, "TIME", "cpu-time", USER, cputime, 0, UNSPEC, NULL},
+ {"tpgid", {NULL}, "TPGID", "terminal-process-gid", 0, kvar,
+ KOFF(ki_tpgid), UINT, PIDFMT},
+ {"tracer", {NULL}, "TRACER", "tracer", 0, kvar, KOFF(ki_tracer),
+ UINT, PIDFMT},
+ {"tsid", {NULL}, "TSID", "terminal-sid", 0, kvar, KOFF(ki_tsid),
+ UINT, PIDFMT},
+ {"tsiz", {NULL}, "TSIZ", "text-size", 0, kvar, KOFF(ki_tsize),
+ PGTOK, "ld"},
+ {"tt", {NULL}, "TT ", "terminal-name", 0, tname, 0, UNSPEC, NULL},
+ {"tty", {NULL}, "TTY", "tty", LJUST, longtname, 0, UNSPEC, NULL},
+ {"ucomm", {NULL}, "UCOMM", "accounting-name", LJUST, ucomm, 0,
+ UNSPEC, NULL},
+ {"uid", {NULL}, "UID", "uid", 0, kvar, KOFF(ki_uid), UINT, UIDFMT},
+ {"upr", {NULL}, "UPR", "user-priority", 0, upr, 0, UNSPEC, NULL},
+ {"uprocp", {NULL}, "UPROCP", "process-address", 0, kvar, KOFF(ki_paddr),
+ KPTR, "lx"},
+ {"user", {NULL}, "USER", "user", LJUST, username, 0, UNSPEC, NULL},
+ {"usertime", {NULL}, "USERTIME", "user-time", USER, usertime, 0,
+ UNSPEC, NULL},
+ {"usrpri", {"upr"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"vmaddr", {NULL}, "VMADDR", "vmspace-address", 0, kvar,
+ KOFF(ki_vmspace), KPTR, "lx"},
+ {"vsize", {"vsz"}, NULL, NULL, 0, NULL, 0, UNSPEC, NULL},
+ {"vsz", {NULL}, "VSZ", "virtual-size", 0, vsize, 0, UNSPEC, NULL},
+ {"wchan", {NULL}, "WCHAN", "wait-channel", LJUST, wchan, 0,
+ UNSPEC, NULL},
+ {"xstat", {NULL}, "XSTAT", "exit-status", 0, kvar, KOFF(ki_xstat),
+ USHORT, "x"},
+};
+
+const size_t known_keywords_nb = nitems(keywords);
+
+size_t
+aliased_keyword_index(const VAR *const v)
+{
+ const VAR *const fv = (v->flag & RESOLVED_ALIAS) == 0 ?
+ v : v->final_kw;
+ const size_t idx = fv - keywords;
+
+ assert(idx < known_keywords_nb);
+ return (idx);
+}
+
+/*
+ * Sanity checks on declared keywords.
+ *
+ * Checks specific to aliases are done in resolve_alias() instead.
+ *
+ * Currently, only checks that keywords are alphabetically ordered by their
+ * names. More checks could be added, such as the absence of type (UNSPEC),
+ * 'fmt' (NULL) when the output routine is not kval()/rval().
+ *
+ * Called from main() on PS_CHECK_KEYWORDS, else available when debugging.
+ */
+void
+check_keywords(void)
+{
+ const VAR *k, *next_k;
+ bool order_violated = false;
+
+ k = &keywords[0];
+ for (size_t i = 1; i < known_keywords_nb; ++i) {
+ next_k = &keywords[i];
+ if (vcmp(k, next_k) >= 0) {
+ xo_warnx("keywords bad order: '%s' followed by '%s'",
+ k->name, next_k->name);
+ order_violated = true;
+ }
+ k = next_k;
+ }
+ if (order_violated)
+ /* Must be the case as we rely on bsearch() + vcmp(). */
+ xo_errx(2, "keywords are not in ascending order "
+ "(internal error)");
+}
+
+static void
+alias_errx(const char *const name, const char *const what)
+{
+ xo_errx(2, "alias keyword '%s' specifies %s (internal error)",
+ name, what);
+}
+
+static void
+merge_alias(VAR *const k, VAR *const tgt)
+{
+ if ((tgt->flag & RESOLVED_ALIAS) != 0)
+ k->final_kw = tgt->final_kw;
+ else {
+ k->final_kw = tgt;
+ assert(tgt->aliased == NULL);
+ }
+
+#define MERGE_IF_SENTINEL(field, sentinel) do { \
+ if (k->field == sentinel) \
+ k->field = tgt->field; \
+} while (0)
+
+ MERGE_IF_SENTINEL(header, NULL);
+ MERGE_IF_SENTINEL(field, NULL);
+ /* If NOINHERIT is present, no merge occurs. */
+ MERGE_IF_SENTINEL(flag, 0);
+
+#undef MERGE_IF_SENTINEL
+
+ /* We also check that aliases don't specify things they should not. */
+#define MERGE_CHECK_SENTINEL(field, sentinel, field_descr) do { \
+ if (k->field != sentinel) \
+ alias_errx(k->name, field_descr); \
+ k->field = tgt->field; \
+} while (0);
+
+ MERGE_CHECK_SENTINEL(oproc, NULL, "an output routine");
+ MERGE_CHECK_SENTINEL(off, 0, "a structure offset");
+ MERGE_CHECK_SENTINEL(type, UNSPEC, "a different type than UNSPEC");
+ MERGE_CHECK_SENTINEL(fmt, NULL, "a printf format");
+
+#undef MERGE_CHECK_SENTINEL
+}
+
+static void
+resolve_alias(VAR *const k)
+{
+ VAR *t, key;
+
+ if ((k->flag & RESOLVED_ALIAS) != 0 || k->aliased == NULL)
+ return;
+
+ if ((k->flag & RESOLVING_ALIAS) != 0)
+ xo_errx(2, "cycle when resolving alias keyword '%s'", k->name);
+ k->flag |= RESOLVING_ALIAS;
+
+ key.name = k->aliased;
+ t = bsearch(&key, keywords, known_keywords_nb, sizeof(VAR), vcmp);
+ if (t == NULL)
+ xo_errx(2, "unknown target '%s' for keyword alias '%s'",
+ k->aliased, k->name);
+
+ resolve_alias(t);
+ merge_alias(k, t);
+
+ k->flag &= ~RESOLVING_ALIAS;
+ k->flag |= RESOLVED_ALIAS;
+}
+
+/*
+ * Resolve all aliases immediately.
+ *
+ * Called from main() on PS_CHECK_KEYWORDS, else available when debugging.
+ */
+void
+resolve_aliases(void)
+{
+ for (size_t i = 0; i < known_keywords_nb; ++i)
+ resolve_alias(&keywords[i]);
+}
+
+void
+showkey(void)
+{
+ const VAR *v;
+ const VAR *const end = keywords + known_keywords_nb;
+ const char *sep;
+ int i;
+
+ i = 0;
+ sep = "";
+ xo_open_list("key");
+ for (v = keywords; v < end; ++v) {
+ const char *const p = v->name;
+ const int len = strlen(p);
+
+ if (termwidth && (i += len + 1) > termwidth) {
+ i = len;
+ sep = "\n";
+ }
+ xo_emit("{P:/%hs}{l:key/%hs}", sep, p);
+ sep = " ";
+ }
+ xo_emit("\n");
+ xo_close_list("key");
+ if (xo_finish() < 0)
+ xo_err(1, "stdout");
+}
+
+void
+parsefmt(const char *p, struct velisthead *const var_list,
+ const int user)
+{
+ char *copy, *cp;
+ char *hdr_p, sep;
+ size_t sep_idx;
+ VAR *v, key;
+ struct varent *vent;
+
+ cp = copy = strdup(p);
+ if (copy == NULL)
+ xo_err(1, "strdup");
+
+ sep = cp[0]; /* We only care if it's 0 or not here. */
+ sep_idx = -1;
+ while (sep != '\0') {
+ cp += sep_idx + 1;
+
+ /*
+ * If an item contains an equals sign, it specifies a column
+ * header, may contain embedded separator characters and
+ * is always the last item.
+ */
+ sep_idx = strcspn(cp, "= \t,\n");
+ sep = cp[sep_idx];
+ cp[sep_idx] = 0;
+ if (sep == '=') {
+ hdr_p = cp + sep_idx + 1;
+ sep = '\0'; /* No more keywords. */
+ } else
+ hdr_p = NULL;
+
+ /* At this point, '*cp' is '\0' iff 'sep_idx' is 0. */
+ if (*cp == '\0') {
+ /*
+ * Empty keyword. Skip it, and silently unless some
+ * header has been specified.
+ */
+ if (hdr_p != NULL)
+ xo_warnx("empty keyword with header '%s'",
+ hdr_p);
+ continue;
+ }
+
+ /* Find the keyword. */
+ key.name = cp;
+ v = bsearch(&key, keywords,
+ known_keywords_nb, sizeof(VAR), vcmp);
+ if (v == NULL) {
+ xo_warnx("%s: keyword not found", cp);
+ eval = 1;
+ continue;
+ }
+
+#ifndef PS_CHECK_KEYWORDS
+ /*
+ * On PS_CHECK_KEYWORDS, this is not necessary as all aliases
+ * are resolved at startup in main() by calling
+ * resolve_aliases().
+ */
+ resolve_alias(v);
+#endif
+
+ if ((vent = malloc(sizeof(struct varent))) == NULL)
+ xo_errx(1, "malloc failed");
+ vent->header = v->header;
+ if (hdr_p) {
+ hdr_p = strdup(hdr_p);
+ if (hdr_p)
+ vent->header = hdr_p;
+ }
+ vent->width = strlen(vent->header);
+ vent->var = v;
+ vent->flags = user ? VE_KEEP : 0;
+ STAILQ_INSERT_TAIL(var_list, vent, next_ve);
+ }
+
+ free(copy);
+
+ if (STAILQ_EMPTY(var_list)) {
+ xo_warnx("no valid keywords; valid keywords:");
+ showkey();
+ exit(1);
+ }
+}
+
+static int
+vcmp(const void *a, const void *b)
+{
+ return (strcmp(((const VAR *)a)->name, ((const VAR *)b)->name));
+}
diff --git a/bin/ps/nlist.c b/bin/ps/nlist.c
new file mode 100644
index 000000000000..8c6a4814e4d5
--- /dev/null
+++ b/bin/ps/nlist.c
@@ -0,0 +1,60 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <stddef.h>
+
+#include "ps.h"
+
+fixpt_t ccpu; /* kernel _ccpu variable */
+int nlistread; /* if nlist already read. */
+unsigned long mempages; /* number of pages of phys. memory */
+int fscale; /* kernel _fscale variable */
+
+int
+donlist(void)
+{
+ size_t oldlen;
+
+ oldlen = sizeof(ccpu);
+ if (sysctlbyname("kern.ccpu", &ccpu, &oldlen, NULL, 0) == -1)
+ return (1);
+ oldlen = sizeof(fscale);
+ if (sysctlbyname("kern.fscale", &fscale, &oldlen, NULL, 0) == -1)
+ return (1);
+ oldlen = sizeof(mempages);
+ if (sysctlbyname("hw.availpages", &mempages, &oldlen, NULL, 0) == -1)
+ return (1);
+ nlistread = 1;
+ return (0);
+}
diff --git a/bin/ps/print.c b/bin/ps/print.c
new file mode 100644
index 000000000000..f59e843effc1
--- /dev/null
+++ b/bin/ps/print.c
@@ -0,0 +1,867 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/proc.h>
+#include <sys/stat.h>
+
+#include <sys/mac.h>
+#include <sys/user.h>
+#include <sys/sysctl.h>
+#include <sys/vmmeter.h>
+
+#include <grp.h>
+#include <jail.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <math.h>
+#include <nlist.h>
+#include <pwd.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+#include <libxo/xo.h>
+
+#include "ps.h"
+
+#define COMMAND_WIDTH 16
+#define ARGUMENTS_WIDTH 16
+
+#define ps_pgtok(a) (((a) * getpagesize()) / 1024)
+
+void
+printheader(void)
+{
+ const VAR *v;
+ struct varent *vent;
+
+ STAILQ_FOREACH(vent, &varlist, next_ve)
+ if (*vent->header != '\0')
+ break;
+ if (!vent)
+ return;
+
+ STAILQ_FOREACH(vent, &varlist, next_ve) {
+ v = vent->var;
+ if (v->flag & LJUST) {
+ if (STAILQ_NEXT(vent, next_ve) == NULL) /* last one */
+ xo_emit("{T:/%hs}", vent->header);
+ else
+ xo_emit("{T:/%-*hs}", vent->width, vent->header);
+ } else
+ xo_emit("{T:/%*hs}", vent->width, vent->header);
+ if (STAILQ_NEXT(vent, next_ve) != NULL)
+ xo_emit("{P: }");
+ }
+ xo_emit("\n");
+}
+
+char *
+arguments(KINFO *k, VARENT *ve)
+{
+ char *vis_args;
+
+ if ((vis_args = malloc(strlen(k->ki_args) * 4 + 1)) == NULL)
+ xo_errx(1, "malloc failed");
+ strvis(vis_args, k->ki_args, VIS_TAB | VIS_NL | VIS_NOSLASH);
+
+ if (STAILQ_NEXT(ve, next_ve) != NULL && strlen(vis_args) > ARGUMENTS_WIDTH)
+ vis_args[ARGUMENTS_WIDTH] = '\0';
+
+ return (vis_args);
+}
+
+char *
+command(KINFO *k, VARENT *ve)
+{
+ char *vis_args, *vis_env, *str;
+
+ if (cflag) {
+ /* If it is the last field, then don't pad */
+ if (STAILQ_NEXT(ve, next_ve) == NULL) {
+ asprintf(&str, "%s%s%s%s%s",
+ k->ki_d.prefix ? k->ki_d.prefix : "",
+ k->ki_p->ki_comm,
+ (showthreads && k->ki_p->ki_numthreads > 1) ? "/" : "",
+ (showthreads && k->ki_p->ki_numthreads > 1) ? k->ki_p->ki_tdname : "",
+ (showthreads && k->ki_p->ki_numthreads > 1) ? k->ki_p->ki_moretdname : "");
+ } else
+ str = strdup(k->ki_p->ki_comm);
+
+ return (str);
+ }
+ if ((vis_args = malloc(strlen(k->ki_args) * 4 + 1)) == NULL)
+ xo_errx(1, "malloc failed");
+ strvis(vis_args, k->ki_args, VIS_TAB | VIS_NL | VIS_NOSLASH);
+
+ if (STAILQ_NEXT(ve, next_ve) == NULL) {
+ /* last field */
+
+ if (k->ki_env) {
+ if ((vis_env = malloc(strlen(k->ki_env) * 4 + 1))
+ == NULL)
+ xo_errx(1, "malloc failed");
+ strvis(vis_env, k->ki_env,
+ VIS_TAB | VIS_NL | VIS_NOSLASH);
+ } else
+ vis_env = NULL;
+
+ asprintf(&str, "%s%s%s%s",
+ k->ki_d.prefix ? k->ki_d.prefix : "",
+ vis_env ? vis_env : "",
+ vis_env ? " " : "",
+ vis_args);
+
+ if (vis_env != NULL)
+ free(vis_env);
+ free(vis_args);
+ } else {
+ /* ki_d.prefix & ki_env aren't shown for interim fields */
+ str = vis_args;
+
+ if (strlen(str) > COMMAND_WIDTH)
+ str[COMMAND_WIDTH] = '\0';
+ }
+
+ return (str);
+}
+
+char *
+ucomm(KINFO *k, VARENT *ve)
+{
+ char *str;
+
+ if (STAILQ_NEXT(ve, next_ve) == NULL) { /* last field, don't pad */
+ asprintf(&str, "%s%s%s%s%s",
+ k->ki_d.prefix ? k->ki_d.prefix : "",
+ k->ki_p->ki_comm,
+ (showthreads && k->ki_p->ki_numthreads > 1) ? "/" : "",
+ (showthreads && k->ki_p->ki_numthreads > 1) ? k->ki_p->ki_tdname : "",
+ (showthreads && k->ki_p->ki_numthreads > 1) ? k->ki_p->ki_moretdname : "");
+ } else {
+ if (showthreads && k->ki_p->ki_numthreads > 1)
+ asprintf(&str, "%s/%s%s", k->ki_p->ki_comm,
+ k->ki_p->ki_tdname, k->ki_p->ki_moretdname);
+ else
+ str = strdup(k->ki_p->ki_comm);
+ }
+ return (str);
+}
+
+char *
+tdnam(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ if (showthreads && k->ki_p->ki_numthreads > 1)
+ asprintf(&str, "%s%s", k->ki_p->ki_tdname,
+ k->ki_p->ki_moretdname);
+ else
+ str = strdup(" ");
+
+ return (str);
+}
+
+char *
+logname(KINFO *k, VARENT *ve __unused)
+{
+
+ if (*k->ki_p->ki_login == '\0')
+ return (NULL);
+ return (strdup(k->ki_p->ki_login));
+}
+
+char *
+state(KINFO *k, VARENT *ve __unused)
+{
+ long flag, tdflags;
+ char *cp, *buf;
+
+ buf = malloc(16);
+ if (buf == NULL)
+ xo_errx(1, "malloc failed");
+
+ flag = k->ki_p->ki_flag;
+ tdflags = k->ki_p->ki_tdflags; /* XXXKSE */
+ cp = buf;
+
+ switch (k->ki_p->ki_stat) {
+
+ case SSTOP:
+ *cp = 'T';
+ break;
+
+ case SSLEEP:
+ if (tdflags & TDF_SINTR) /* interruptible (long) */
+ *cp = k->ki_p->ki_slptime >= MAXSLP ? 'I' : 'S';
+ else
+ *cp = 'D';
+ break;
+
+ case SRUN:
+ case SIDL:
+ *cp = 'R';
+ break;
+
+ case SWAIT:
+ *cp = 'W';
+ break;
+
+ case SLOCK:
+ *cp = 'L';
+ break;
+
+ case SZOMB:
+ *cp = 'Z';
+ break;
+
+ default:
+ *cp = '?';
+ }
+ cp++;
+ if (k->ki_p->ki_nice < NZERO || k->ki_p->ki_pri.pri_class == PRI_REALTIME)
+ *cp++ = '<';
+ else if (k->ki_p->ki_nice > NZERO || k->ki_p->ki_pri.pri_class == PRI_IDLE)
+ *cp++ = 'N';
+ if (flag & P_TRACED)
+ *cp++ = 'X';
+ if (flag & P_WEXIT && k->ki_p->ki_stat != SZOMB)
+ *cp++ = 'E';
+ if (flag & P_PPWAIT)
+ *cp++ = 'V';
+ if ((flag & P_SYSTEM) || k->ki_p->ki_lock > 0)
+ *cp++ = 'L';
+ if ((k->ki_p->ki_cr_flags & KI_CRF_CAPABILITY_MODE) != 0)
+ *cp++ = 'C';
+ if (k->ki_p->ki_kiflag & KI_SLEADER)
+ *cp++ = 's';
+ if ((flag & P_CONTROLT) && k->ki_p->ki_pgid == k->ki_p->ki_tpgid)
+ *cp++ = '+';
+ if (flag & P_JAILED)
+ *cp++ = 'J';
+ *cp = '\0';
+ return (buf);
+}
+
+#define scalepri(x) ((x) - PUSER)
+
+char *
+pri(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ asprintf(&str, "%d", scalepri(k->ki_p->ki_pri.pri_level));
+ return (str);
+}
+
+char *
+upr(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ asprintf(&str, "%d", scalepri(k->ki_p->ki_pri.pri_user));
+ return (str);
+}
+#undef scalepri
+
+char *
+username(KINFO *k, VARENT *ve __unused)
+{
+
+ return (strdup(user_from_uid(k->ki_p->ki_uid, 0)));
+}
+
+char *
+egroupname(KINFO *k, VARENT *ve __unused)
+{
+
+ return (strdup(group_from_gid(k->ki_p->ki_groups[0], 0)));
+}
+
+char *
+rgroupname(KINFO *k, VARENT *ve __unused)
+{
+
+ return (strdup(group_from_gid(k->ki_p->ki_rgid, 0)));
+}
+
+char *
+runame(KINFO *k, VARENT *ve __unused)
+{
+
+ return (strdup(user_from_uid(k->ki_p->ki_ruid, 0)));
+}
+
+char *
+tdev(KINFO *k, VARENT *ve __unused)
+{
+ dev_t dev;
+ char *str;
+
+ dev = k->ki_p->ki_tdev;
+ if (dev == NODEV)
+ str = strdup("-");
+ else
+ asprintf(&str, "%#jx", (uintmax_t)dev);
+
+ return (str);
+}
+
+char *
+tname(KINFO *k, VARENT *ve __unused)
+{
+ dev_t dev;
+ char *ttname, *str;
+
+ dev = k->ki_p->ki_tdev;
+ if (dev == NODEV || (ttname = devname(dev, S_IFCHR)) == NULL)
+ str = strdup("- ");
+ else {
+ if (strncmp(ttname, "tty", 3) == 0 ||
+ strncmp(ttname, "cua", 3) == 0)
+ ttname += 3;
+ if (strncmp(ttname, "pts/", 4) == 0)
+ ttname += 4;
+ asprintf(&str, "%s%c", ttname,
+ k->ki_p->ki_kiflag & KI_CTTY ? ' ' : '-');
+ }
+
+ return (str);
+}
+
+char *
+longtname(KINFO *k, VARENT *ve __unused)
+{
+ dev_t dev;
+ const char *ttname;
+
+ dev = k->ki_p->ki_tdev;
+ if (dev == NODEV || (ttname = devname(dev, S_IFCHR)) == NULL)
+ ttname = "-";
+
+ return (strdup(ttname));
+}
+
+char *
+started(KINFO *k, VARENT *ve __unused)
+{
+ time_t then;
+ struct tm *tp;
+ size_t buflen = 100;
+ char *buf;
+
+ if (!k->ki_valid)
+ return (NULL);
+
+ buf = malloc(buflen);
+ if (buf == NULL)
+ xo_errx(1, "malloc failed");
+
+ then = k->ki_p->ki_start.tv_sec;
+ tp = localtime(&then);
+ if (now - k->ki_p->ki_start.tv_sec < 24 * 3600) {
+ (void)strftime(buf, buflen, "%H:%M ", tp);
+ } else if (now - k->ki_p->ki_start.tv_sec < 7 * 86400) {
+ (void)strftime(buf, buflen, "%a%H ", tp);
+ } else
+ (void)strftime(buf, buflen, "%e%b%y", tp);
+ return (buf);
+}
+
+char *
+lstarted(KINFO *k, VARENT *ve __unused)
+{
+ time_t then;
+ char *buf;
+ size_t buflen = 100;
+
+ if (!k->ki_valid)
+ return (NULL);
+
+ buf = malloc(buflen);
+ if (buf == NULL)
+ xo_errx(1, "malloc failed");
+
+ then = k->ki_p->ki_start.tv_sec;
+ (void)strftime(buf, buflen, "%c", localtime(&then));
+ return (buf);
+}
+
+char *
+lockname(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ if (k->ki_p->ki_kiflag & KI_LOCKBLOCK) {
+ if (k->ki_p->ki_lockname[0] != 0)
+ str = strdup(k->ki_p->ki_lockname);
+ else
+ str = strdup("???");
+ } else
+ str = NULL;
+
+ return (str);
+}
+
+char *
+wchan(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ if (k->ki_p->ki_wchan) {
+ if (k->ki_p->ki_wmesg[0] != 0)
+ str = strdup(k->ki_p->ki_wmesg);
+ else
+ asprintf(&str, "%lx", (long)k->ki_p->ki_wchan);
+ } else
+ str = NULL;
+
+ return (str);
+}
+
+char *
+nwchan(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ if (k->ki_p->ki_wchan)
+ asprintf(&str, "%0lx", (long)k->ki_p->ki_wchan);
+ else
+ str = NULL;
+
+ return (str);
+}
+
+char *
+mwchan(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ if (k->ki_p->ki_wchan) {
+ if (k->ki_p->ki_wmesg[0] != 0)
+ str = strdup(k->ki_p->ki_wmesg);
+ else
+ asprintf(&str, "%lx", (long)k->ki_p->ki_wchan);
+ } else if (k->ki_p->ki_kiflag & KI_LOCKBLOCK) {
+ if (k->ki_p->ki_lockname[0]) {
+ str = strdup(k->ki_p->ki_lockname);
+ } else
+ str = strdup("???");
+ } else
+ str = NULL;
+
+ return (str);
+}
+
+char *
+vsize(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ asprintf(&str, "%lu", (u_long)(k->ki_p->ki_size / 1024));
+ return (str);
+}
+
+static char *
+printtime(KINFO *k, VARENT *ve __unused, long secs, long psecs)
+/* psecs is "parts" of a second. first micro, then centi */
+{
+ static char decimal_point;
+ char *str;
+
+ if (decimal_point == '\0')
+ decimal_point = localeconv()->decimal_point[0];
+ if (!k->ki_valid) {
+ secs = 0;
+ psecs = 0;
+ } else {
+ /* round and scale to 100's */
+ psecs = (psecs + 5000) / 10000;
+ secs += psecs / 100;
+ psecs = psecs % 100;
+ }
+ asprintf(&str, "%ld:%02ld%c%02ld",
+ secs / 60, secs % 60, decimal_point, psecs);
+ return (str);
+}
+
+char *
+cputime(KINFO *k, VARENT *ve)
+{
+ long secs, psecs;
+
+ /*
+ * This counts time spent handling interrupts. We could
+ * fix this, but it is not 100% trivial (and interrupt
+ * time fractions only work on the sparc anyway). XXX
+ */
+ secs = k->ki_p->ki_runtime / 1000000;
+ psecs = k->ki_p->ki_runtime % 1000000;
+ if (sumrusage) {
+ secs += k->ki_p->ki_childtime.tv_sec;
+ psecs += k->ki_p->ki_childtime.tv_usec;
+ }
+ return (printtime(k, ve, secs, psecs));
+}
+
+char *
+cpunum(KINFO *k, VARENT *ve __unused)
+{
+ char *cpu;
+
+ if (k->ki_p->ki_stat == SRUN && k->ki_p->ki_oncpu != NOCPU) {
+ asprintf(&cpu, "%d", k->ki_p->ki_oncpu);
+ } else {
+ asprintf(&cpu, "%d", k->ki_p->ki_lastcpu);
+ }
+ return (cpu);
+}
+
+char *
+systime(KINFO *k, VARENT *ve)
+{
+ long secs, psecs;
+
+ secs = k->ki_p->ki_rusage.ru_stime.tv_sec;
+ psecs = k->ki_p->ki_rusage.ru_stime.tv_usec;
+ if (sumrusage) {
+ secs += k->ki_p->ki_childstime.tv_sec;
+ psecs += k->ki_p->ki_childstime.tv_usec;
+ }
+ return (printtime(k, ve, secs, psecs));
+}
+
+char *
+usertime(KINFO *k, VARENT *ve)
+{
+ long secs, psecs;
+
+ secs = k->ki_p->ki_rusage.ru_utime.tv_sec;
+ psecs = k->ki_p->ki_rusage.ru_utime.tv_usec;
+ if (sumrusage) {
+ secs += k->ki_p->ki_childutime.tv_sec;
+ psecs += k->ki_p->ki_childutime.tv_usec;
+ }
+ return (printtime(k, ve, secs, psecs));
+}
+
+char *
+elapsed(KINFO *k, VARENT *ve __unused)
+{
+ time_t val;
+ int days, hours, mins, secs;
+ char *str;
+
+ if (!k->ki_valid)
+ return (NULL);
+ val = now - k->ki_p->ki_start.tv_sec;
+ days = val / (24 * 60 * 60);
+ val %= 24 * 60 * 60;
+ hours = val / (60 * 60);
+ val %= 60 * 60;
+ mins = val / 60;
+ secs = val % 60;
+ if (days != 0)
+ asprintf(&str, "%3d-%02d:%02d:%02d", days, hours, mins, secs);
+ else if (hours != 0)
+ asprintf(&str, "%02d:%02d:%02d", hours, mins, secs);
+ else
+ asprintf(&str, "%02d:%02d", mins, secs);
+
+ return (str);
+}
+
+char *
+elapseds(KINFO *k, VARENT *ve __unused)
+{
+ time_t val;
+ char *str;
+
+ if (!k->ki_valid)
+ return (NULL);
+ val = now - k->ki_p->ki_start.tv_sec;
+ asprintf(&str, "%jd", (intmax_t)val);
+ return (str);
+}
+
+double
+getpcpu(const KINFO *k)
+{
+ static int failure;
+
+ if (!nlistread)
+ failure = donlist();
+ if (failure)
+ return (0.0);
+
+#define fxtofl(fixpt) ((double)(fixpt) / fscale)
+
+ /* XXX - I don't like this */
+ if (k->ki_p->ki_swtime == 0)
+ return (0.0);
+ if (rawcpu)
+ return (100.0 * fxtofl(k->ki_p->ki_pctcpu));
+ return (100.0 * fxtofl(k->ki_p->ki_pctcpu) /
+ (1.0 - exp(k->ki_p->ki_swtime * log(fxtofl(ccpu)))));
+}
+
+char *
+pcpu(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ asprintf(&str, "%.1f", getpcpu(k));
+ return (str);
+}
+
+static double
+getpmem(KINFO *k)
+{
+ static int failure;
+ double fracmem;
+
+ if (!nlistread)
+ failure = donlist();
+ if (failure)
+ return (0.0);
+
+ /* XXX want pmap ptpages, segtab, etc. (per architecture) */
+ /* XXX don't have info about shared */
+ fracmem = ((double)k->ki_p->ki_rssize) / mempages;
+ return (100.0 * fracmem);
+}
+
+char *
+pmem(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ asprintf(&str, "%.1f", getpmem(k));
+ return (str);
+}
+
+char *
+pagein(KINFO *k, VARENT *ve __unused)
+{
+ char *str;
+
+ asprintf(&str, "%ld", k->ki_valid ? k->ki_p->ki_rusage.ru_majflt : 0);
+ return (str);
+}
+
+/* ARGSUSED */
+char *
+maxrss(KINFO *k __unused, VARENT *ve __unused)
+{
+
+ /* XXX not yet */
+ return (NULL);
+}
+
+char *
+priorityr(KINFO *k, VARENT *ve __unused)
+{
+ struct priority *lpri;
+ char *str;
+ unsigned class, level;
+
+ lpri = &k->ki_p->ki_pri;
+ class = lpri->pri_class;
+ level = lpri->pri_level;
+ switch (class) {
+ case RTP_PRIO_REALTIME:
+ /* alias for PRI_REALTIME */
+ asprintf(&str, "real:%u", level - PRI_MIN_REALTIME);
+ break;
+ case RTP_PRIO_NORMAL:
+ /* alias for PRI_TIMESHARE */
+ if (level >= PRI_MIN_TIMESHARE)
+ asprintf(&str, "normal:%u", level - PRI_MIN_TIMESHARE);
+ else
+ asprintf(&str, "kernel:%u", level - PRI_MIN_KERN);
+ break;
+ case RTP_PRIO_IDLE:
+ /* alias for PRI_IDLE */
+ asprintf(&str, "idle:%u", level - PRI_MIN_IDLE);
+ break;
+ case RTP_PRIO_ITHD:
+ /* alias for PRI_ITHD */
+ asprintf(&str, "intr:%u", level - PRI_MIN_ITHD);
+ break;
+ default:
+ asprintf(&str, "%u:%u", class, level);
+ break;
+ }
+ return (str);
+}
+
+/*
+ * Generic output routines. Print fields from various prototype
+ * structures.
+ */
+static char *
+printval(void *bp, const VAR *v)
+{
+ static char ofmt[32] = "%";
+ const char *fcp;
+ char *cp, *str;
+
+ cp = ofmt + 1;
+ fcp = v->fmt;
+ while ((*cp++ = *fcp++));
+
+#define CHKINF127(n) (((n) > 127) && (v->flag & INF127) ? 127 : (n))
+
+ switch (v->type) {
+ case UNSPEC:
+ xo_errx(1, "cannot print value of unspecified type "
+ "(internal error)");
+ break;
+ case CHAR:
+ (void)asprintf(&str, ofmt, *(char *)bp);
+ break;
+ case UCHAR:
+ (void)asprintf(&str, ofmt, *(u_char *)bp);
+ break;
+ case SHORT:
+ (void)asprintf(&str, ofmt, *(short *)bp);
+ break;
+ case USHORT:
+ (void)asprintf(&str, ofmt, *(u_short *)bp);
+ break;
+ case INT:
+ (void)asprintf(&str, ofmt, *(int *)bp);
+ break;
+ case UINT:
+ (void)asprintf(&str, ofmt, CHKINF127(*(u_int *)bp));
+ break;
+ case LONG:
+ (void)asprintf(&str, ofmt, *(long *)bp);
+ break;
+ case ULONG:
+ (void)asprintf(&str, ofmt, *(u_long *)bp);
+ break;
+ case KPTR:
+ (void)asprintf(&str, ofmt, *(u_long *)bp);
+ break;
+ case PGTOK:
+ (void)asprintf(&str, ofmt, ps_pgtok(*(u_long *)bp));
+ break;
+ default:
+ xo_errx(1, "unknown type (internal error)");
+ break;
+ }
+
+ return (str);
+}
+
+char *
+kvar(KINFO *k, VARENT *ve)
+{
+ const VAR *v;
+
+ v = ve->var;
+ return (printval((char *)((char *)k->ki_p + v->off), v));
+}
+
+char *
+rvar(KINFO *k, VARENT *ve)
+{
+ const VAR *v;
+
+ v = ve->var;
+ if (!k->ki_valid)
+ return (NULL);
+ return (printval((char *)((char *)(&k->ki_p->ki_rusage) + v->off), v));
+}
+
+char *
+emulname(KINFO *k, VARENT *ve __unused)
+{
+
+ return (strdup(k->ki_p->ki_emul));
+}
+
+char *
+label(KINFO *k, VARENT *ve __unused)
+{
+ char *string;
+ mac_t proclabel;
+ int error;
+
+ string = NULL;
+ if (mac_prepare_process_label(&proclabel) == -1) {
+ xo_warn("mac_prepare_process_label");
+ goto out;
+ }
+ error = mac_get_pid(k->ki_p->ki_pid, proclabel);
+ if (error == 0) {
+ if (mac_to_text(proclabel, &string) == -1)
+ string = NULL;
+ }
+ mac_free(proclabel);
+out:
+ return (string);
+}
+
+char *
+loginclass(KINFO *k, VARENT *ve __unused)
+{
+
+ /*
+ * Don't display login class for system processes;
+ * login classes are used for resource limits,
+ * and limits don't apply to system processes.
+ */
+ if (k->ki_p->ki_flag & P_SYSTEM) {
+ return (strdup("-"));
+ }
+ return (strdup(k->ki_p->ki_loginclass));
+}
+
+char *
+jailname(KINFO *k, VARENT *ve __unused)
+{
+ char *name;
+
+ if (k->ki_p->ki_jid == 0)
+ return (strdup("-"));
+ name = jail_getname(k->ki_p->ki_jid);
+ if (name == NULL)
+ return (strdup("-"));
+ return (name);
+}
diff --git a/bin/ps/ps.1 b/bin/ps/ps.1
new file mode 100644
index 000000000000..542004453658
--- /dev/null
+++ b/bin/ps/ps.1
@@ -0,0 +1,1019 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-3-Clause
+.\"
+.\" Copyright (c) 1980, 1990, 1991, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\" Copyright (c) 2025 The FreeBSD Foundation
+.\"
+.\" Portions of this documentation were written by Olivier Certner
+.\" <olce@FreeBSD.org> at Kumacom SARL under sponsorship from the FreeBSD
+ \" Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd July 16, 2025
+.Dt PS 1
+.Os
+.Sh NAME
+.Nm ps
+.Nd process status
+.Sh SYNOPSIS
+.Nm
+.Op Fl -libxo
+.Op Fl AaCcdefHhjlmrSTuvwXxZ
+.Op Fl O Ar fmt
+.Op Fl o Ar fmt
+.Op Fl D Ar up | down | both
+.Op Fl G Ar gid Ns Op , Ns Ar gid Ns Ar ...
+.Op Fl J Ar jid Ns Op , Ns Ar jid Ns Ar ...
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl p Ar pid Ns Op , Ns Ar pid Ns Ar ...
+.Op Fl t Ar tty Ns Op , Ns Ar tty Ns Ar ...
+.Op Fl U Ar user Ns Op , Ns Ar user Ns Ar ...
+.Nm
+.Op Fl -libxo
+.Fl L
+.Sh DESCRIPTION
+The
+.Nm
+utility displays information about a selection of processes.
+Its traditional text style output consists of a header line followed by one line
+of information per selected process, or possibly multiple ones if using
+.Fl H
+.Pq one per lightweight-process .
+Other output styles can be requested via
+.Fl -libxo .
+.Pp
+By default, only the processes of the calling user, determined by matching their
+effective user ID with that of the
+.Nm
+process, that have controlling terminals are shown.
+A different set of processes can be selected for display by using combinations
+of the
+.Fl A , a , D , G , J , p , T , t , U , X ,
+and
+.Fl x
+options.
+Except for options
+.Fl X
+and
+.Fl x ,
+as soon as one of them appears, it inhibits the default process selection, i.e.,
+the calling user's processes are shown only on request.
+If more than one of these
+.Pq with same exceptions
+appear,
+.Nm
+will select processes as soon as they are matched by at least one of them
+.Pq inclusive OR .
+The
+.Fl X
+option can be independently used to further filter the listed processes to only
+those that have a controlling terminal
+.Po
+except for those selected by
+.Fl p
+.Pc .
+Its opposite,
+.Fl x ,
+forcefully removes that filter.
+If none of
+.Fl X
+and
+.Fl x
+is specified, the implied default behavior is that of
+.Fl X
+unless using another option whose description explicitly says that
+.Fl x
+is implied.
+.Pp
+For each selected process, the default displayed information consists of the
+process' ID, controlling terminal, state, CPU time
+.Pq including both user and system time
+and associated command
+.Po
+see the documentation for the
+.Cm command
+keyword below
+.Pc .
+This information can be tweaked using two groups of options which can be
+combined as needed.
+First, options
+.Fl o
+and
+.Fl O
+add columns with data corresponding to the explicitly passed keywords.
+Available keywords are documented in the
+.Sx KEYWORDS
+section below.
+They can be listed using option
+.Fl L .
+Second, options
+.Fl j , l , u ,
+and
+.Fl v
+designate specific predefined groups of columns, also called canned displays.
+Appearance of any of these options inhibits the default display, replacing it
+all with the requested columns, and in the order options are passed.
+The individual columns requested via a canned display option that have the same
+keyword or an alias to that of some column added by an earlier canned display
+option, or by an explicit
+.Fl O
+or
+.Fl o
+option anywhere on the command line, are suppressed.
+This automatic removal of duplicate data in canned displays is useful for
+slightly tweaking these displays and/or combining multiple ones without having
+to rebuild variants from scratch, e.g., using only
+.Fl o
+options.
+.Pp
+Output information lines are by default sorted first by controlling terminal,
+then by process ID, and then, if
+.Fl H
+has been specified, by lightweight-process (thread) ID.
+The
+.Fl m , r , u ,
+and
+.Fl v
+options will change the sort order.
+If more than one sorting option was given, then the selected processes
+will be sorted by the last sorting option which was specified.
+.Pp
+If the traditional text output (the default) is used, the default output width is that requested by the
+.Ev COLUMNS
+environment variable if present, else the line width of the terminal associated
+to the
+.Nm
+process, if any.
+In all other situations, the output width is unlimited.
+See also the
+.Fl w
+option and the
+.Sx BUGS
+section.
+.Pp
+For backwards compatibility,
+.Nm
+attempts to interpret any positional argument as a process ID, as if specified
+by the
+.Fl p
+option.
+Failure to do so will trigger an error.
+.Nm
+also accepts the old-style BSD options, whose format and effect are left
+undocumented on purpose.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl -libxo
+Generate output via
+.Xr libxo 3
+in a selection of different human and machine readable formats.
+See
+.Xr xo_options 7
+for details on command line arguments.
+The default is the traditional text style output.
+.It Fl A
+Display information about all processes in the system.
+Using this option is strictly equivalent to specifying both
+.Fl a
+and
+.Fl x .
+Please see their description for more information.
+.It Fl a
+Display information about all users' processes.
+It does not, however, list all processes
+.Po
+see
+.Fl A
+and
+.Fl x
+.Pc .
+If the
+.Va security.bsd.see_other_uids
+sysctl is set to zero, this option is honored only if the real user ID of the
+.Nm
+process is 0.
+.It Fl C
+Change the way the CPU percentage is calculated by using a
+.Dq raw
+CPU calculation that ignores
+.Dq resident
+time (this normally has
+no effect).
+.It Fl c
+Change the
+.Dq command
+column output to just contain the executable name,
+rather than the full command line.
+.It Fl D
+Expand the list of selected processes based on the process tree.
+.Dq UP
+will add the ancestor processes,
+.Dq DOWN
+will add the descendant processes, and
+.Dq BOTH
+will add both the ancestor and the descendant processes.
+.Fl D
+does not imply
+.Fl d ,
+but works well with it.
+.It Fl d
+Arrange processes into descendancy order and prefix each command with
+indentation text showing sibling and parent/child relationships as a tree.
+If either of the
+.Fl m
+and
+.Fl r
+options are also used, they control how sibling processes are sorted
+relative to each other.
+Note that this option has no effect if the last column does not have
+.Cm comm ,
+.Cm command
+or
+.Cm ucomm
+as its keyword.
+.It Fl e
+Display the environment as well.
+.It Fl f
+Indicates to print the full command and arguments in
+.Cm command
+columns.
+This is the default behavior on
+.Fx .
+See
+.Fl c
+to turn it off.
+.It Fl G
+Display information about processes whose real group ID matches the specified
+group IDs or names.
+Implies
+.Fl x
+by default.
+.It Fl H
+Show all of the threads associated with each process.
+.It Fl h
+Repeat the information header as often as necessary to guarantee one
+header per page of information.
+.It Fl J
+Display information about processes which match the specified jail IDs.
+This may be either the
+.Cm jid
+or
+.Cm name
+of the jail.
+Use
+.Fl J
+.Sy 0
+to request display of host processes.
+Implies
+.Fl x
+by default.
+.It Fl j
+Print information associated with the following keywords:
+.Cm user , pid , ppid , pgid , sid , jobc , state , tt , time ,
+and
+.Cm command .
+.It Fl L
+List the set of keywords available for the
+.Fl O
+and
+.Fl o
+options.
+.It Fl l
+Display information associated with the following keywords:
+.Cm uid , pid , ppid , cpu , pri , nice , vsz , rss , mwchan , state ,
+.Cm tt , time ,
+and
+.Cm command .
+.It Fl M
+Extract values associated with the name list from the specified core
+instead of the currently running system.
+.It Fl m
+Sort by memory usage, instead of the combination of controlling
+terminal and process ID.
+.It Fl N
+Extract the name list from the specified system instead of the default,
+which is the kernel image the system has booted from.
+.It Fl O
+Save passed columns in a separate list that in the end is grafted just after the
+display's first occurence of the process ID column as specified by other
+options, or the default display if there is none.
+If the display prepared by other options does not include a process ID column,
+the list is inserted at start of the display.
+Further occurences of
+.Fl O
+append to the to-be-grafted list of columns.
+This option takes a space- or comma-separated list of keywords.
+The last keyword in the list may be appended with an equals sign
+.Pq Ql =
+as explained for option
+.Fl o
+and with the same effect.
+.It Fl o
+Display information associated with the space- or comma-separated list of
+keywords specified.
+The last keyword in the list may be appended with an equals sign
+.Pq Ql =
+and a string that spans the rest of the argument, and can contain
+space and comma characters.
+This causes the printed header to use the specified string instead of
+the standard header.
+Multiple keywords may also be given in the form of more than one
+.Fl o
+option.
+So the header texts for multiple keywords can be changed.
+If all keywords have empty header texts, no header line is written.
+.It Fl p
+Display information about processes which match the specified process IDs.
+Processes selected by this option are not subject to being filtered by
+.Fl X .
+.It Fl r
+Sort by current CPU usage, instead of the combination of controlling
+terminal and process ID.
+.It Fl S
+Change the way the process times, namely cputime, systime, and usertime,
+are calculated by summing all exited children to their parent process.
+.It Fl T
+Display information about processes attached to the device associated
+with the standard input.
+.It Fl t
+Display information about processes attached to the specified terminal
+devices.
+Full pathnames, as well as abbreviations (see explanation of the
+.Cm tt
+keyword) can be specified.
+Implies
+.Fl x
+by default.
+.It Fl U
+Display information about processes whose real user ID matches the specified
+user IDs or names.
+Implies
+.Fl x
+by default.
+.It Fl u
+Display information associated with the following keywords:
+.Cm user , pid , %cpu , %mem , vsz , rss , tt , state , start , time ,
+and
+.Cm command .
+The
+.Fl u
+option implies the
+.Fl r
+option.
+.It Fl v
+Display information associated with the following keywords:
+.Cm pid , state , time , sl , re , pagein , vsz , rss , lim , tsiz ,
+.Cm %cpu , %mem ,
+and
+.Cm command .
+The
+.Fl v
+option implies the
+.Fl m
+option.
+.It Fl w
+Use at least 131 columns to display information.
+If
+.Fl w
+is specified more than once,
+.Nm
+will use as many columns as necessary.
+Please see the preamble of this manual page for how the output width is
+initially determined.
+In particular, if the initial output width is unlimited, specifying
+.Fl w
+has no effect.
+Please also consult the
+.Sx BUGS
+section.
+.It Fl X
+When displaying processes selected by other options, skip any processes which do
+not have a controlling terminal, except for those selected through
+.Fl p .
+This is the default behaviour, unless using another option whose description
+explicitly says that
+.Fl x
+is implied.
+.It Fl x
+When displaying processes selected by other options, include processes which do
+not have a controlling terminal.
+This option has the opposite behavior to that of
+.Fl X .
+If both
+.Fl X
+and
+.Fl x
+are specified,
+.Nm
+will obey the last occurence.
+.It Fl Z
+Add
+.Xr mac 4
+label to the list of keywords for which
+.Nm
+will display information.
+.El
+.Sh KEYWORDS
+The following is a complete list of the available keywords and their meanings.
+Several of them have aliases (keywords which are synonyms).
+Detailed descriptions for some of them can be found after this list.
+.Pp
+.Bl -tag -width ".Cm sigignore" -compact
+.It Cm %cpu
+percentage CPU usage (alias
+.Cm pcpu )
+.It Cm %mem
+percentage memory usage (alias
+.Cm pmem )
+.It Cm acflag
+accounting flag (alias
+.Cm acflg )
+.It Cm args
+command and arguments
+.It Cm class
+login class
+.It Cm comm
+command
+.It Cm command
+command and arguments
+.It Cm cow
+number of copy-on-write faults
+.It Cm cpu
+The processor number on which the process is executing (visible only on SMP
+systems).
+.It Cm dsiz
+data size in KiB
+.It Cm emul
+system-call emulation environment (ABI)
+.It Cm etime
+elapsed running time, format
+.Do
+.Op days- Ns
+.Op hours\&: Ns
+minutes:seconds
+.Dc
+.It Cm etimes
+elapsed running time, in decimal integer seconds
+.It Cm fib
+default FIB number, see
+.Xr setfib 1
+.It Cm flags
+the process flags, in hexadecimal (alias
+.Cm f )
+.It Cm flags2
+the additional set of process flags, in hexadecimal (alias
+.Cm f2 )
+.It Cm gid
+effective group ID (alias
+.Cm egid )
+.It Cm group
+group name (from egid) (alias
+.Cm egroup )
+.It Cm inblk
+total blocks read (alias
+.Cm inblock )
+.It Cm jail
+jail name
+.It Cm jid
+jail ID
+.It Cm jobc
+job control count
+.It Cm ktrace
+tracing flags
+.It Cm label
+MAC label
+.It Cm lim
+memoryuse limit
+.It Cm lockname
+lock currently blocked on (as a symbolic name)
+.It Cm logname
+login name of user who started the session
+.It Cm lstart
+time started
+.It Cm lwp
+thread (light-weight process) ID (alias
+.Cm tid )
+.It Cm majflt
+total page faults
+.It Cm minflt
+total page reclaims
+.It Cm msgrcv
+total messages received (reads from pipes/sockets)
+.It Cm msgsnd
+total messages sent (writes on pipes/sockets)
+.It Cm mwchan
+wait channel or lock currently blocked on
+.It Cm nice
+nice value (alias
+.Cm ni )
+.It Cm nivcsw
+total involuntary context switches
+.It Cm nlwp
+number of threads (light-weight processes) tied to a process
+.It Cm nsigs
+total signals taken (alias
+.Cm nsignals )
+.It Cm nswap
+total swaps in/out
+.It Cm nvcsw
+total voluntary context switches
+.It Cm nwchan
+wait channel (as an address)
+.It Cm oublk
+total blocks written (alias
+.Cm oublock )
+.It Cm paddr
+process pointer
+.It Cm pagein
+pageins (same as majflt)
+.It Cm pgid
+process group number
+.It Cm pid
+process ID
+.It Cm ppid
+parent process ID
+.It Cm pri
+scheduling priority
+.It Cm re
+core residency time (in seconds; 127 = infinity)
+.It Cm rgid
+real group ID
+.It Cm rgroup
+group name (from rgid)
+.It Cm rss
+resident set size in KiB
+.It Cm rtprio
+realtime priority (see
+.Xr rtprio 1)
+.It Cm ruid
+real user ID
+.It Cm ruser
+user name (from ruid)
+.It Cm sid
+session ID
+.It Cm sig
+pending signals (alias
+.Cm pending )
+.It Cm sigcatch
+caught signals (alias
+.Cm caught )
+.It Cm sigignore
+ignored signals (alias
+.Cm ignored )
+.It Cm sigmask
+blocked signals (alias
+.Cm blocked )
+.It Cm sl
+sleep time (in seconds; 127 = infinity)
+.It Cm ssiz
+stack size in KiB
+.It Cm start
+time started
+.It Cm state
+symbolic process state (alias
+.Cm stat )
+.It Cm svgid
+saved gid from a setgid executable
+.It Cm svuid
+saved UID from a setuid executable
+.It Cm systime
+accumulated system CPU time
+.It Cm tdaddr
+thread address
+.It Cm tdname
+thread name
+.It Cm tdev
+control terminal device number
+.It Cm time
+accumulated CPU time, user + system (alias
+.Cm cputime )
+.It Cm tpgid
+control terminal process group ID
+.It Cm tracer
+tracer process ID
+.\".It Cm trss
+.\"text resident set size in KiB
+.It Cm tsid
+control terminal session ID
+.It Cm tsiz
+text size in KiB
+.It Cm tt
+control terminal name (two letter abbreviation)
+.It Cm tty
+full name of control terminal
+.It Cm ucomm
+process name used for accounting
+.It Cm uid
+effective user ID (alias
+.Cm euid )
+.It Cm upr
+scheduling priority on return from system call (alias
+.Cm usrpri )
+.It Cm uprocp
+process pointer
+.It Cm user
+user name (from UID)
+.It Cm usertime
+accumulated user CPU time
+.It Cm vmaddr
+vmspace pointer
+.It Cm vsz
+virtual size in KiB (alias
+.Cm vsize )
+.It Cm wchan
+wait channel (as a symbolic name)
+.It Cm xstat
+exit or stop status (valid only for stopped or zombie process)
+.El
+.Pp
+Some of these keywords are further specified as follows:
+.Bl -tag -width lockname
+.It Cm %cpu
+The CPU utilization of the process; this is a decaying average over up to
+a minute of previous (real) time.
+Since the time base over which this is computed varies (since processes may
+be very young) it is possible for the sum of all
+.Cm %cpu
+fields to exceed 100%.
+.It Cm %mem
+The percentage of real memory used by this process.
+.It Cm class
+Login class associated with the process.
+.It Cm command
+The printed command and arguments are determined as follows.
+A process that has exited and has a parent that has not yet waited for the
+process (in other words, a zombie) is listed as
+.Dq Li <defunct>.
+If the arguments cannot be located
+.Po
+usually because they have not been set, as is the case for system processes
+and/or kernel threads
+.Pc ,
+the command name is printed within square brackets.
+The
+.Nm
+utility first tries to obtain the arguments cached by the kernel
+.Po
+if they were shorter than the value of the
+.Va kern.ps_arg_cache_limit
+sysctl
+.Pc .
+The process can change the arguments shown with
+.Xr setproctitle 3 .
+Otherwise,
+.Nm
+makes an educated guess as to the file name and arguments given when the
+process was created by examining memory or the swap area.
+The method is inherently somewhat unreliable and in any event a process
+is entitled to destroy this information.
+The
+.Cm ucomm
+keyword
+.Pq accounting
+can, however, be depended on.
+If the arguments are unavailable or do not agree with the
+.Cm ucomm
+keyword, the value for the
+.Cm ucomm
+keyword is appended to the arguments in parentheses.
+.It Cm flags
+The flags associated with the process as in
+the include file
+.In sys/proc.h :
+.Bl -column P_SINGLE_BOUNDARY 0x40000000
+.It Dv "P_ADVLOCK" Ta No "0x00000001" Ta "Process may hold a POSIX advisory lock"
+.It Dv "P_CONTROLT" Ta No "0x00000002" Ta "Has a controlling terminal"
+.It Dv "P_KPROC" Ta No "0x00000004" Ta "Kernel process"
+.It Dv "P_PPWAIT" Ta No "0x00000010" Ta "Parent is waiting for child to exec/exit"
+.It Dv "P_PROFIL" Ta No "0x00000020" Ta "Has started profiling"
+.It Dv "P_STOPPROF" Ta No "0x00000040" Ta "Has thread in requesting to stop prof"
+.It Dv "P_HADTHREADS" Ta No "0x00000080" Ta "Has had threads (no cleanup shortcuts)"
+.It Dv "P_SUGID" Ta No "0x00000100" Ta "Had set id privileges since last exec"
+.It Dv "P_SYSTEM" Ta No "0x00000200" Ta "System proc: no sigs, stats or swapping"
+.It Dv "P_SINGLE_EXIT" Ta No "0x00000400" Ta "Threads suspending should exit, not wait"
+.It Dv "P_TRACED" Ta No "0x00000800" Ta "Debugged process being traced"
+.It Dv "P_WAITED" Ta No "0x00001000" Ta "Someone is waiting for us"
+.It Dv "P_WEXIT" Ta No "0x00002000" Ta "Working on exiting"
+.It Dv "P_EXEC" Ta No "0x00004000" Ta "Process called exec"
+.It Dv "P_WKILLED" Ta No "0x00008000" Ta "Killed, shall go to kernel/user boundary ASAP"
+.It Dv "P_CONTINUED" Ta No "0x00010000" Ta "Proc has continued from a stopped state"
+.It Dv "P_STOPPED_SIG" Ta No "0x00020000" Ta "Stopped due to SIGSTOP/SIGTSTP"
+.It Dv "P_STOPPED_TRACE" Ta No "0x00040000" Ta "Stopped because of tracing"
+.It Dv "P_STOPPED_SINGLE" Ta No "0x00080000" Ta "Only one thread can continue"
+.It Dv "P_PROTECTED" Ta No "0x00100000" Ta "Do not kill on memory overcommit"
+.It Dv "P_SIGEVENT" Ta No "0x00200000" Ta "Process pending signals changed"
+.It Dv "P_SINGLE_BOUNDARY" Ta No "0x00400000" Ta "Threads should suspend at user boundary"
+.It Dv "P_HWPMC" Ta No "0x00800000" Ta "Process is using HWPMCs"
+.It Dv "P_JAILED" Ta No "0x01000000" Ta "Process is in jail"
+.It Dv "P_TOTAL_STOP" Ta No "0x02000000" Ta "Stopped for system suspend"
+.It Dv "P_INEXEC" Ta No "0x04000000" Ta Process is in Xr execve 2
+.It Dv "P_STATCHILD" Ta No "0x08000000" Ta "Child process stopped or exited"
+.It Dv "P_INMEM" Ta No "0x10000000" Ta "Always set, unused"
+.It Dv "P_PPTRACE" Ta No "0x80000000" Ta "Vforked child issued ptrace(PT_TRACEME)"
+.El
+.It Cm flags2
+The flags kept in
+.Va p_flag2
+associated with the process as in
+the include file
+.In sys/proc.h :
+.Bl -column P2_INHERIT_PROTECTED 0x00000001
+.It Dv "P2_INHERIT_PROTECTED" Ta No "0x00000001" Ta "New children get P_PROTECTED"
+.It Dv "P2_NOTRACE" Ta No "0x00000002" Ta "No" Xr ptrace 2 attach or coredumps
+.It Dv "P2_NOTRACE_EXEC" Ta No "0x00000004" Ta Keep P2_NOPTRACE on Xr execve 2
+.It Dv "P2_AST_SU" Ta No "0x00000008" Ta "Handles SU ast for kthreads"
+.It Dv "P2_PTRACE_FSTP" Ta No "0x00000010" Ta "SIGSTOP from PT_ATTACH not yet handled"
+.It Dv "P2_TRAPCAP" Ta No "0x00000020" Ta "SIGTRAP on ENOTCAPABLE"
+.It Dv "P2_ASLR_ENABLE" Ta No "0x00000040" Ta "Force enable ASLR"
+.It Dv "P2_ASLR_DISABLE" Ta No "0x00000080" Ta "Force disable ASLR"
+.It Dv "P2_ASLR_IGNSTART" Ta No "0x00000100" Ta "Enable ASLR to consume sbrk area"
+.It Dv "P2_PROTMAX_ENABLE" Ta No "0x00000200" Ta "Force enable implied PROT_MAX"
+.It Dv "P2_PROTMAX_DISABLE" Ta No "0x00000400" Ta "Force disable implied PROT_MAX"
+.It Dv "P2_STKGAP_DISABLE" Ta No "0x00000800" Ta "Disable stack gap for MAP_STACK"
+.It Dv "P2_STKGAP_DISABLE_EXEC" Ta No "0x00001000" Ta "Stack gap disabled after exec"
+.It Dv "P2_ITSTOPPED" Ta No "0x00002000" Ta "itimers stopped (as part of process stop)"
+.It Dv "P2_PTRACEREQ" Ta No "0x00004000" Ta "Active ptrace req"
+.It Dv "P2_NO_NEW_PRIVS" Ta No "0x00008000" Ta "Ignore setuid on exec"
+.It Dv "P2_WXORX_DISABLE" Ta No "0x00010000" Ta "WX mappings enabled"
+.It Dv "P2_WXORX_ENABLE_EXEC" Ta No "0x00020000" Ta "WxorX enabled after exec"
+.It Dv "P2_WEXIT" Ta No "0x00040000" Ta "Internal exit early state"
+.It Dv "P2_REAPKILLED" Ta No "0x00080000" Ta "REAP_KILL pass handled the process"
+.It Dv "P2_MEMBAR_PRIVE" Ta No "0x00100000" Ta "membarrier private expedited registered"
+.It Dv "P2_MEMBAR_PRIVE_SYNCORE" Ta No "0x00200000" Ta "membarrier private expedited sync core registered"
+.It Dv "P2_MEMBAR_GLOBE" Ta No "0x00400000" Ta "membar global expedited registered"
+.El
+.It Cm label
+The MAC label of the process.
+.It Cm lim
+The soft limit on memory used, specified via a call to
+.Xr setrlimit 2 .
+.It Cm lstart
+The exact time the command started, using the
+.Ql %c
+format described in
+.Xr strftime 3 .
+.It Cm lockname
+The name of the lock that the process is currently blocked on.
+If the name is invalid or unknown, then
+.Dq ???\&
+is displayed.
+.It Cm logname
+The login name associated with the session the process is in (see
+.Xr getlogin 2 ) .
+.It Cm mwchan
+The event name if the process is blocked normally, or the lock name if
+the process is blocked on a lock.
+See the wchan and lockname keywords
+for details.
+.It Cm nice
+The process scheduling increment (see
+.Xr setpriority 2 ) .
+.It Cm rss
+the real memory (resident set) size of the process in KiB.
+.It Cm start
+The time the command started.
+If the command started less than 24 hours ago, the start time is
+displayed using the
+.Dq Li %H:%M
+format described in
+.Xr strftime 3 .
+If the command started less than 7 days ago, the start time is
+displayed using the
+.Dq Li %a%H
+format.
+Otherwise, the start time is displayed using the
+.Dq Li %e%b%y
+format.
+.It Cm sig
+The bitmask of signals pending in the process queue if the
+.Fl H
+option has not been specified, else the per-thread queue of pending signals.
+.It Cm state
+The state is given by a sequence of characters, for example,
+.Dq Li RWNA .
+The first character indicates the run state of the process:
+.Pp
+.Bl -tag -width indent -compact
+.It Li D
+Marks a process in disk (or other short term, uninterruptible) wait.
+.It Li I
+Marks a process that is idle (sleeping for longer than about 20 seconds).
+.It Li L
+Marks a process that is waiting to acquire a lock.
+.It Li R
+Marks a runnable process.
+.It Li S
+Marks a process that is sleeping for less than about 20 seconds.
+.It Li T
+Marks a stopped process.
+.It Li W
+Marks an idle interrupt thread.
+.It Li Z
+Marks a dead process (a
+.Dq zombie ) .
+.El
+.Pp
+Additional characters after these, if any, indicate additional state
+information:
+.Pp
+.Bl -tag -width indent -compact
+.It Li +
+The process is in the foreground process group of its control terminal.
+.It Li <
+The process has raised CPU scheduling priority.
+.It Li C
+The process is in
+.Xr capsicum 4
+capability mode.
+.It Li E
+The process is trying to exit.
+.It Li J
+Marks a process which is in
+.Xr jail 2 .
+The hostname of the prison can be found in
+.Pa /proc/ Ns Ao Ar pid Ac Ns Pa /status .
+.It Li L
+The process has pages locked in core (for example, for raw I/O).
+.It Li N
+The process has reduced CPU scheduling priority (see
+.Xr setpriority 2 ) .
+.It Li s
+The process is a session leader.
+.It Li V
+The process' parent is suspended during a
+.Xr vfork 2 ,
+waiting for the process to exec or exit.
+.It Li X
+The process is being traced or debugged.
+.El
+.It Cm tt
+An abbreviation for the pathname of the controlling terminal, if any.
+The abbreviation consists of the three letters following
+.Pa /dev/tty ,
+or, for pseudo-terminals, the corresponding entry in
+.Pa /dev/pts .
+This is followed by a
+.Ql -
+if the process can no longer reach that
+controlling terminal (i.e., it has been revoked).
+A
+.Ql -
+without a preceding two letter abbreviation or pseudo-terminal device number
+indicates a process which never had a controlling terminal.
+The full pathname of the controlling terminal is available via the
+.Cm tty
+keyword.
+.It Cm wchan
+The event (an address in the system) on which a process waits.
+When printed numerically, the initial part of the address is
+trimmed off and the result is printed in hex, for example, 0x80324000 prints
+as 324000.
+.El
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev COLUMNS"
+.It Ev COLUMNS
+If set, specifies the user's preferred output width in column positions.
+Only affects the traditional text style output.
+Please see the preamble of this manual page on how the final output width is
+determined.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /boot/kernel/kernel" -compact
+.It Pa /boot/kernel/kernel
+default system namelist
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Display information on all system processes:
+.Pp
+.Dl $ ps -auxw
+.Sh SEE ALSO
+.Xr kill 1 ,
+.Xr pgrep 1 ,
+.Xr pkill 1 ,
+.Xr procstat 1 ,
+.Xr w 1 ,
+.Xr kvm 3 ,
+.Xr libxo 3 ,
+.Xr strftime 3 ,
+.Xr xo_options 7 ,
+.Xr mac 4 ,
+.Xr procfs 4 ,
+.Xr pstat 8 ,
+.Xr sysctl 8 ,
+.Xr mutex 9
+.Sh STANDARDS
+For historical reasons, the
+.Nm
+utility under
+.Fx
+supports a different set of options from what is described by
+.St -p1003.1-2024
+and what is supported on
+.No non- Ns Bx
+operating systems.
+.Pp
+In particular, and contrary to this implementation, POSIX specifies that option
+.Fl d
+should serve to select all processes except session leaders, option
+.Fl e
+to select all processes
+.Po
+equivalently to
+.Fl A
+.Pc ,
+and option
+.Fl u
+to select processes by effective user ID.
+.Pp
+However, options
+.Fl A , a , G , l , o , p , U ,
+and
+.Fl t
+behave as prescribed by
+.St -p1003.1-2024 .
+Options
+.Fl f
+and
+.Fl w
+currently do not, but may be changed to in the future.
+.Pp
+POSIX's option
+.Fl g ,
+to select processes having the specified processes as their session leader, is
+not implemented.
+However, other UNIX systems that provide this functionality do so via option
+.Fl s
+instead, reserving
+.Fl g
+to query by group leaders.
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.At v3
+in section 8 of the manual.
+.Sh BUGS
+Since
+.Nm
+cannot run faster than the system and is run as any other scheduled
+process, the information it displays can never be exact.
+.Pp
+.Nm ps
+currently does not correctly limit the ouput width, and in most cases does not
+limit it at all when it should.
+Regardless of the target width, requested columns are always all printed and
+with widths allowing to entirely print their longest values, except for columns
+with keyword
+.Cm command
+or
+.Cm args
+that are not last in the display
+.Pq they are truncated to 16 bytes ,
+and for the last column in the display if its keyword requests textual
+information of variable length, such as the
+.Cm command , jail ,
+and
+.Cm user
+keywords do.
+This considerably limits the effects and usefulness of the terminal width on the
+output, and consequently that of the
+.Ev COLUMNS
+environment variable and the
+.Fl w
+option
+.Pq if specified only once .
+.Pp
+The
+.Nm
+utility does not correctly display argument lists containing multibyte
+characters.
diff --git a/bin/ps/ps.c b/bin/ps/ps.c
new file mode 100644
index 000000000000..bb5102729957
--- /dev/null
+++ b/bin/ps/ps.c
@@ -0,0 +1,1548 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2025 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Olivier Certner
+ * <olce@FreeBSD.org> at Kumacom SARL under sponsorship from the FreeBSD
+ * Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ------+---------+---------+-------- + --------+---------+---------+---------*
+ * Copyright (c) 2004 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * Significant modifications made to bring `ps' options somewhat closer
+ * to the standard for `ps' as described in SingleUnixSpec-v3.
+ * ------+---------+---------+-------- + --------+---------+---------+---------*
+ */
+
+#include <sys/param.h>
+#include <sys/jail.h>
+#include <sys/proc.h>
+#include <sys/user.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <sys/mount.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <jail.h>
+#include <kvm.h>
+#include <limits.h>
+#include <locale.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+
+#include "ps.h"
+
+#define _PATH_PTS "/dev/pts/"
+
+#define W_SEP " \t" /* "Whitespace" list separators */
+#define T_SEP "," /* "Terminate-element" list separators */
+
+/*
+ * isdigit takes an `int', but expects values in the range of unsigned char.
+ * This wrapper ensures that values from a 'char' end up in the correct range.
+ */
+#define isdigitch(Anychar) isdigit((u_char)(Anychar))
+
+int cflag; /* -c */
+int eval; /* Exit value */
+time_t now; /* Current time(3) value */
+int rawcpu; /* -C */
+int sumrusage; /* -S */
+int termwidth; /* Width of the screen (0 == infinity). */
+int showthreads; /* will threads be shown? */
+
+struct keyword_info {
+ /*
+ * Whether there is (at least) one column referencing this keyword that
+ * must be kept.
+ */
+#define KWI_HAS_MUST_KEEP_COLUMN (1 << 0)
+ /*
+ * Whether a column with such a keyword has been seen.
+ */
+#define KWI_SEEN (1 << 1)
+ u_int flags;
+};
+
+struct velisthead varlist = STAILQ_HEAD_INITIALIZER(varlist);
+static struct velisthead Ovarlist = STAILQ_HEAD_INITIALIZER(Ovarlist);
+
+static kvm_t *kd;
+static int needcomm; /* -o "command" */
+static int needenv; /* -e */
+static int needuser; /* -o "user" */
+static int optfatal; /* Fatal error parsing some list-option. */
+static int pid_max; /* kern.pid_max */
+
+static enum sort { DEFAULT, SORTMEM, SORTCPU } sortby = DEFAULT;
+
+struct listinfo;
+typedef int addelem_rtn(struct listinfo *_inf, const char *_elem);
+
+struct listinfo {
+ int count;
+ int maxcount;
+ int elemsize;
+ addelem_rtn *addelem;
+ const char *lname;
+ union {
+ gid_t *gids;
+ int *jids;
+ pid_t *pids;
+ dev_t *ttys;
+ uid_t *uids;
+ void *ptr;
+ } l;
+};
+
+static int addelem_gid(struct listinfo *, const char *);
+static int addelem_jid(struct listinfo *, const char *);
+static int addelem_pid(struct listinfo *, const char *);
+static int addelem_tty(struct listinfo *, const char *);
+static int addelem_uid(struct listinfo *, const char *);
+static void add_list(struct listinfo *, const char *);
+static void descendant_sort(KINFO *, int);
+static void format_output(KINFO *);
+static void *expand_list(struct listinfo *);
+static const char *
+ fmt(char **(*)(kvm_t *, const struct kinfo_proc *, int),
+ KINFO *, char *, char *, int);
+static void free_list(struct listinfo *);
+static void init_list(struct listinfo *, addelem_rtn, int, const char *);
+static char *kludge_oldps_options(const char *, char *, const char *);
+static int pscomp(const void *, const void *);
+static void saveuser(KINFO *);
+static void scan_vars(struct keyword_info *);
+static void remove_redundant_columns(struct keyword_info *);
+static void pidmax_init(void);
+static void usage(void);
+
+static const char dfmt[] = "pid,tt,state,time,command";
+static const char jfmt[] = "user,pid,ppid,pgid,sid,jobc,state,tt,time,command";
+static const char lfmt[] = "uid,pid,ppid,cpu,pri,nice,vsz,rss,mwchan,state,"
+ "tt,time,command";
+static const char ufmt[] = "user,pid,%cpu,%mem,vsz,rss,tt,state,start,time,command";
+static const char vfmt[] = "pid,state,time,sl,re,pagein,vsz,rss,lim,tsiz,"
+ "%cpu,%mem,command";
+static const char Zfmt[] = "label";
+
+#define PS_ARGS "AaCcD:defG:gHhjJ:LlM:mN:O:o:p:rSTt:U:uvwXxZ"
+
+int
+main(int argc, char *argv[])
+{
+ struct listinfo gidlist, jidlist, pgrplist, pidlist;
+ struct listinfo ruidlist, sesslist, ttylist, uidlist;
+ struct kinfo_proc *kp;
+ KINFO *kinfo = NULL, *next_KINFO;
+ KINFO_STR *ks;
+ struct varent *vent;
+ struct winsize ws = { .ws_row = 0 };
+ const char *nlistf, *memf, *str;
+ char *cols;
+ int all, ch, elem, flag, _fmt, i, lineno, linelen, left;
+ int descendancy, nentries, nkept, nselectors;
+ int prtheader, wflag, what, xkeep, xkeep_implied;
+ int fwidthmin, fwidthmax;
+ char errbuf[_POSIX2_LINE_MAX];
+ char fmtbuf[_POSIX2_LINE_MAX];
+ enum { NONE = 0, UP = 1, DOWN = 2, BOTH = 1 | 2 } directions = NONE;
+ struct { int traversed; int initial; } pid_count;
+ struct keyword_info *keywords_info;
+
+ (void) setlocale(LC_ALL, "");
+ time(&now); /* Used by routines in print.c. */
+
+ /*
+ * Compute default output line length before processing options.
+ * If COLUMNS is set, use it. Otherwise, if this is part of an
+ * interactive job (i.e. one associated with a terminal), use
+ * the terminal width. "Interactive" is determined by whether
+ * any of stdout, stderr, or stdin is a terminal. The intent
+ * is that "ps", "ps | more", and "ps | grep" all use the same
+ * default line length unless -w is specified.
+ *
+ * If not interactive, the default length was traditionally 79.
+ * It has been changed to unlimited. This is mostly for the
+ * benefit of non-interactive scripts, which arguably should
+ * use -ww, but is compatible with Linux.
+ */
+ if ((cols = getenv("COLUMNS")) != NULL && *cols != '\0')
+ termwidth = atoi(cols);
+ else if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&ws) == -1 &&
+ ioctl(STDERR_FILENO, TIOCGWINSZ, (char *)&ws) == -1 &&
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ws) == -1) ||
+ ws.ws_col == 0)
+ termwidth = UNLIMITED;
+ else
+ termwidth = ws.ws_col - 1;
+
+ /*
+ * Hide a number of option-processing kludges in a separate routine,
+ * to support some historical BSD behaviors, such as `ps axu'.
+ */
+ if (argc > 1)
+ argv[1] = kludge_oldps_options(PS_ARGS, argv[1], argv[2]);
+
+ pidmax_init();
+
+#ifdef PS_CHECK_KEYWORDS
+ /* Check for obvious problems in the keywords array. */
+ check_keywords();
+ /* Resolve all aliases at start to spot errors. */
+ resolve_aliases();
+#endif
+
+ all = descendancy = _fmt = nselectors = optfatal = 0;
+ prtheader = showthreads = wflag = xkeep_implied = 0;
+ xkeep = -1; /* Neither -x nor -X. */
+ init_list(&gidlist, addelem_gid, sizeof(gid_t), "group");
+ init_list(&jidlist, addelem_jid, sizeof(int), "jail id");
+ init_list(&pgrplist, addelem_pid, sizeof(pid_t), "process group");
+ init_list(&pidlist, addelem_pid, sizeof(pid_t), "process id");
+ init_list(&ruidlist, addelem_uid, sizeof(uid_t), "ruser");
+ init_list(&sesslist, addelem_pid, sizeof(pid_t), "session id");
+ init_list(&ttylist, addelem_tty, sizeof(dev_t), "tty");
+ init_list(&uidlist, addelem_uid, sizeof(uid_t), "user");
+ memf = _PATH_DEVNULL;
+ nlistf = NULL;
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ exit(1);
+
+ while ((ch = getopt(argc, argv, PS_ARGS)) != -1)
+ switch (ch) {
+ case 'A':
+ all = xkeep = 1;
+ break;
+ case 'a':
+ all = 1;
+ break;
+ case 'C':
+ rawcpu = 1;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'D': {
+ size_t len = strlen(optarg);
+
+ if (len <= 2 &&
+ strncasecmp(optarg, "up", len) == 0)
+ directions |= UP;
+ else if (len <= 4 &&
+ strncasecmp(optarg, "down", len) == 0)
+ directions |= DOWN;
+ else if (len <= 4 &&
+ strncasecmp(optarg, "both", len) == 0)
+ directions |= BOTH;
+ else
+ usage();
+ break;
+ }
+ case 'd':
+ descendancy = 1;
+ break;
+ case 'e': /* XXX set ufmt */
+ needenv = 1;
+ break;
+ case 'f':
+ /* compat */
+ break;
+ case 'G':
+ add_list(&gidlist, optarg);
+ xkeep_implied = 1;
+ nselectors++;
+ break;
+ case 'g':
+#if 0
+ /*
+ * XXX - This behavior is still under debate since it
+ * conflicts with the (undocumented) `-g' option
+ * and is non-standard. However, it is the
+ * behavior of most UNIX systems except
+ * SunOS/Solaris/illumos (see next comment; see
+ * also comment for '-s' below).
+ */
+ add_list(&pgrplist, optarg);
+ xkeep_implied = 1;
+ nselectors++;
+ break;
+#else
+ /*
+ * The historical BSD-ish (from SunOS) behavior: Also
+ * display process group leaders (but we do not filter
+ * them out).
+ */
+ break; /* no-op */
+#endif
+ case 'H':
+ showthreads = KERN_PROC_INC_THREAD;
+ break;
+ case 'h':
+ prtheader = ws.ws_row > 5 ? ws.ws_row : 22;
+ break;
+ case 'J':
+ add_list(&jidlist, optarg);
+ xkeep_implied = 1;
+ nselectors++;
+ break;
+ case 'j':
+ parsefmt(jfmt, &varlist, 0);
+ _fmt = 1;
+ break;
+ case 'L':
+ showkey();
+ exit(0);
+ case 'l':
+ parsefmt(lfmt, &varlist, 0);
+ _fmt = 1;
+ break;
+ case 'M':
+ memf = optarg;
+ break;
+ case 'm':
+ sortby = SORTMEM;
+ break;
+ case 'N':
+ nlistf = optarg;
+ break;
+ case 'O':
+ parsefmt(optarg, &Ovarlist, 1);
+ break;
+ case 'o':
+ parsefmt(optarg, &varlist, 1);
+ _fmt = 1;
+ break;
+ case 'p':
+ add_list(&pidlist, optarg);
+ /*
+ * Note: `-p' does not *set* xkeep, but any values
+ * from pidlist are checked before xkeep is. That
+ * way they are always matched, even if the user
+ * specifies `-X'.
+ */
+ nselectors++;
+ break;
+ case 'r':
+ sortby = SORTCPU;
+ break;
+ case 'S':
+ sumrusage = 1;
+ break;
+#if 0
+ case 's':
+ /*
+ * XXX - This non-standard option is still under debate.
+ * It is supported on Solaris, Linux, IRIX, and
+ * OpenBSD but conflicts with '-s' on NetBSD. This
+ * is the same functionality as POSIX option '-g',
+ * but the cited systems do not provide it under
+ * '-g', only under '-s'.
+ */
+ add_list(&sesslist, optarg);
+ xkeep_implied = 1;
+ nselectors++;
+ break;
+#endif
+ case 'T':
+ if ((optarg = ttyname(STDIN_FILENO)) == NULL)
+ xo_errx(1, "stdin: not a terminal");
+ /* FALLTHROUGH */
+ case 't':
+ add_list(&ttylist, optarg);
+ xkeep_implied = 1;
+ nselectors++;
+ break;
+ case 'U':
+ add_list(&ruidlist, optarg);
+ xkeep_implied = 1;
+ nselectors++;
+ break;
+ case 'u':
+#if 0
+ /*
+ * POSIX's '-u' behavior.
+ *
+ * This has not been activated because:
+ * 1. Option '-U' is a substitute for most users, and
+ * those that care seem more likely to want to match
+ * on the real user ID to display all processes
+ * launched by some users.
+ * 2. '-u' has been a canned display on the BSDs for
+ * a very long time (POLA).
+ */
+ add_list(&uidlist, optarg);
+ xkeep_implied = 1;
+ nselectors++;
+ break;
+#else
+ /* Historical BSD's '-u'. */
+ parsefmt(ufmt, &varlist, 0);
+ sortby = SORTCPU;
+ _fmt = 1;
+ break;
+#endif
+ case 'v':
+ parsefmt(vfmt, &varlist, 0);
+ sortby = SORTMEM;
+ _fmt = 1;
+ break;
+ case 'w':
+ if (wflag)
+ termwidth = UNLIMITED;
+ else if (termwidth < 131 && termwidth != UNLIMITED)
+ termwidth = 131;
+ wflag++;
+ break;
+ case 'X':
+ /*
+ * Note that `-X' and `-x' are not standard "selector"
+ * options. For most selector-options, we check *all*
+ * processes to see if any are matched by the given
+ * value(s). After we have a set of all the matched
+ * processes, then `-X' and `-x' govern whether we
+ * modify that *matched* set for processes which do
+ * not have a controlling terminal. `-X' causes
+ * those processes to be deleted from the matched
+ * set, while `-x' causes them to be kept.
+ */
+ xkeep = 0;
+ break;
+ case 'x':
+ xkeep = 1;
+ break;
+ case 'Z':
+ parsefmt(Zfmt, &varlist, 0);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * If there arguments after processing all the options, attempt
+ * to treat them as a list of process ids.
+ */
+ while (*argv) {
+ if (!isdigitch(**argv))
+ break;
+ add_list(&pidlist, *argv);
+ argv++;
+ }
+ if (*argv) {
+ xo_warnx("illegal argument: %s\n", *argv);
+ usage();
+ }
+ if (optfatal)
+ exit(1); /* Error messages already printed. */
+ if (xkeep < 0) /* Neither -X nor -x was specified. */
+ xkeep = xkeep_implied;
+
+ kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);
+ if (kd == NULL)
+ xo_errx(1, "%s", errbuf);
+
+ if (!_fmt)
+ parsefmt(dfmt, &varlist, 0);
+
+ if (!STAILQ_EMPTY(&Ovarlist)) {
+ VARENT *const pid_entry = find_varentry("pid");
+
+ /*
+ * We insert the keywords passed by '-O' after the process ID if
+ * specified, else at start.
+ */
+ if (pid_entry != NULL) {
+ struct velisthead rest;
+
+ STAILQ_SPLIT_AFTER(&varlist, pid_entry, &rest, next_ve);
+ STAILQ_CONCAT(&varlist, &Ovarlist);
+ STAILQ_CONCAT(&varlist, &rest);
+ }
+ else {
+ STAILQ_SWAP(&varlist, &Ovarlist, varent);
+ STAILQ_CONCAT(&varlist, &Ovarlist);
+ }
+ }
+
+ keywords_info = calloc(known_keywords_nb, sizeof(struct keyword_info));
+ if (keywords_info == NULL)
+ xo_errx(1, "malloc failed");
+ /*
+ * Scan requested variables, noting which structures are needed and
+ * which keywords are specified.
+ */
+ scan_vars(keywords_info);
+ /*
+ * Remove redundant columns from "canned" displays (see the callee's
+ * herald comment for more details).
+ */
+ remove_redundant_columns(keywords_info);
+ free(keywords_info);
+ keywords_info = NULL;
+
+ if (all)
+ /*
+ * We have to display all processes, regardless of other
+ * options.
+ */
+ nselectors = 0;
+ else if (nselectors == 0) {
+ /*
+ * Default is to request our processes only. As per POSIX, we
+ * match processes by their effective user IDs and we use our
+ * effective user ID as our own identity.
+ */
+ expand_list(&uidlist);
+ uidlist.l.uids[uidlist.count++] = geteuid();
+ nselectors = 1;
+ }
+
+ /*
+ * Get process list. If the user requested just one selector-
+ * option, then kvm_getprocs can be asked to return just those
+ * processes. Otherwise, have it return all processes, and
+ * then this routine will search that full list and select the
+ * processes which match any of the user's selector-options.
+ */
+ what = showthreads != 0 ? KERN_PROC_ALL : KERN_PROC_PROC;
+ flag = 0;
+ if (nselectors == 1) {
+ if (gidlist.count == 1) {
+ what = KERN_PROC_RGID | showthreads;
+ flag = *gidlist.l.gids;
+ nselectors = 0;
+ } else if (pgrplist.count == 1) {
+ what = KERN_PROC_PGRP | showthreads;
+ flag = *pgrplist.l.pids;
+ nselectors = 0;
+ } else if (pidlist.count == 1 && directions == NONE) {
+ what = KERN_PROC_PID | showthreads;
+ flag = *pidlist.l.pids;
+ nselectors = 0;
+ } else if (ruidlist.count == 1) {
+ what = KERN_PROC_RUID | showthreads;
+ flag = *ruidlist.l.uids;
+ nselectors = 0;
+ } else if (sesslist.count == 1) {
+ what = KERN_PROC_SESSION | showthreads;
+ flag = *sesslist.l.pids;
+ nselectors = 0;
+ } else if (ttylist.count == 1) {
+ what = KERN_PROC_TTY | showthreads;
+ flag = *ttylist.l.ttys;
+ nselectors = 0;
+ } else if (uidlist.count == 1) {
+ what = KERN_PROC_UID | showthreads;
+ flag = *uidlist.l.uids;
+ nselectors = 0;
+ }
+ }
+
+ /*
+ * select procs
+ */
+ nentries = -1;
+ kp = kvm_getprocs(kd, what, flag, &nentries);
+ /*
+ * Ignore ESRCH to preserve behaviour of "ps -p nonexistent-pid"
+ * not reporting an error.
+ */
+ if ((kp == NULL && errno != ESRCH) || (kp != NULL && nentries < 0))
+ xo_errx(1, "%s", kvm_geterr(kd));
+ nkept = 0;
+ pid_count.initial = pidlist.count;
+ if (directions & DOWN)
+ for (elem = 0; elem < pidlist.count; elem++)
+ for (i = 0; i < nentries; i++) {
+ if (kp[i].ki_ppid == kp[i].ki_pid)
+ continue;
+ if (kp[i].ki_ppid == pidlist.l.pids[elem]) {
+ if (pidlist.count >= pidlist.maxcount)
+ expand_list(&pidlist);
+ pidlist.l.pids[pidlist.count++] = kp[i].ki_pid;
+ }
+ }
+ pid_count.traversed = pidlist.count;
+ if (directions & UP)
+ for (elem = 0; elem < pidlist.count; elem++) {
+ if (elem >= pid_count.initial && elem < pid_count.traversed)
+ continue;
+ for (i = 0; i < nentries; i++) {
+ if (kp[i].ki_ppid == kp[i].ki_pid)
+ continue;
+ if (kp[i].ki_pid == pidlist.l.pids[elem]) {
+ if (pidlist.count >= pidlist.maxcount)
+ expand_list(&pidlist);
+ pidlist.l.pids[pidlist.count++] = kp[i].ki_ppid;
+ }
+ }
+ }
+ if (nentries > 0) {
+ if ((kinfo = malloc(nentries * sizeof(*kinfo))) == NULL)
+ xo_errx(1, "malloc failed");
+ for (i = nentries; --i >= 0; ++kp) {
+ /*
+ * If the user specified multiple selection-criteria,
+ * then keep any process matched by the inclusive OR
+ * of all the selection-criteria given.
+ */
+ if (pidlist.count > 0) {
+ for (elem = 0; elem < pidlist.count; elem++)
+ if (kp->ki_pid == pidlist.l.pids[elem])
+ goto keepit;
+ }
+ /*
+ * Note that we had to process pidlist before
+ * filtering out processes which do not have
+ * a controlling terminal.
+ */
+ if (xkeep == 0) {
+ if ((kp->ki_tdev == NODEV ||
+ (kp->ki_flag & P_CONTROLT) == 0))
+ continue;
+ }
+ if (nselectors == 0)
+ goto keepit;
+ if (gidlist.count > 0) {
+ for (elem = 0; elem < gidlist.count; elem++)
+ if (kp->ki_rgid == gidlist.l.gids[elem])
+ goto keepit;
+ }
+ if (jidlist.count > 0) {
+ for (elem = 0; elem < jidlist.count; elem++)
+ if (kp->ki_jid == jidlist.l.jids[elem])
+ goto keepit;
+ }
+ if (pgrplist.count > 0) {
+ for (elem = 0; elem < pgrplist.count; elem++)
+ if (kp->ki_pgid ==
+ pgrplist.l.pids[elem])
+ goto keepit;
+ }
+ if (ruidlist.count > 0) {
+ for (elem = 0; elem < ruidlist.count; elem++)
+ if (kp->ki_ruid ==
+ ruidlist.l.uids[elem])
+ goto keepit;
+ }
+ if (sesslist.count > 0) {
+ for (elem = 0; elem < sesslist.count; elem++)
+ if (kp->ki_sid == sesslist.l.pids[elem])
+ goto keepit;
+ }
+ if (ttylist.count > 0) {
+ for (elem = 0; elem < ttylist.count; elem++)
+ if (kp->ki_tdev == ttylist.l.ttys[elem])
+ goto keepit;
+ }
+ if (uidlist.count > 0) {
+ for (elem = 0; elem < uidlist.count; elem++)
+ if (kp->ki_uid == uidlist.l.uids[elem])
+ goto keepit;
+ }
+ /*
+ * This process did not match any of the user's
+ * selector-options, so skip the process.
+ */
+ continue;
+
+ keepit:
+ next_KINFO = &kinfo[nkept];
+ next_KINFO->ki_p = kp;
+ next_KINFO->ki_d.level = 0;
+ next_KINFO->ki_d.prefix = NULL;
+ next_KINFO->ki_pcpu = getpcpu(next_KINFO);
+ if (sortby == SORTMEM)
+ next_KINFO->ki_memsize = kp->ki_tsize +
+ kp->ki_dsize + kp->ki_ssize;
+ if (needuser)
+ saveuser(next_KINFO);
+ nkept++;
+ }
+ }
+
+ if (nkept == 0) {
+ printheader();
+ if (xo_finish() < 0)
+ xo_err(1, "stdout");
+ exit(1);
+ }
+
+ /*
+ * sort proc list
+ */
+ qsort(kinfo, nkept, sizeof(KINFO), pscomp);
+
+ /*
+ * We want things in descendant order
+ */
+ if (descendancy)
+ descendant_sort(kinfo, nkept);
+
+
+ /*
+ * Prepare formatted output.
+ */
+ for (i = 0; i < nkept; i++)
+ format_output(&kinfo[i]);
+
+ /*
+ * Print header.
+ */
+ xo_open_container("process-information");
+ printheader();
+ if (xo_get_style(NULL) != XO_STYLE_TEXT)
+ termwidth = UNLIMITED;
+
+ /*
+ * Output formatted lines.
+ */
+ xo_open_list("process");
+ for (i = lineno = 0; i < nkept; i++) {
+ linelen = 0;
+ xo_open_instance("process");
+ STAILQ_FOREACH(vent, &varlist, next_ve) {
+ ks = STAILQ_FIRST(&kinfo[i].ki_ks);
+ STAILQ_REMOVE_HEAD(&kinfo[i].ki_ks, ks_next);
+ /* Truncate rightmost column if necessary. */
+ fwidthmax = _POSIX2_LINE_MAX;
+ if (STAILQ_NEXT(vent, next_ve) == NULL &&
+ termwidth != UNLIMITED && ks->ks_str != NULL) {
+ left = termwidth - linelen;
+ if (left > 0 && left < (int)strlen(ks->ks_str))
+ fwidthmax = left;
+ }
+
+ str = ks->ks_str;
+ if (str == NULL)
+ str = "-";
+ /* No padding for the last column, if it's LJUST. */
+ fwidthmin = (xo_get_style(NULL) != XO_STYLE_TEXT ||
+ (STAILQ_NEXT(vent, next_ve) == NULL &&
+ (vent->var->flag & LJUST))) ? 0 : vent->width;
+ snprintf(fmtbuf, sizeof(fmtbuf), "{:%s/%%%s%d..%dhs}",
+ vent->var->field ? vent->var->field : vent->var->name,
+ (vent->var->flag & LJUST) ? "-" : "",
+ fwidthmin, fwidthmax);
+ xo_emit(fmtbuf, str);
+ linelen += fwidthmin;
+
+ if (ks->ks_str != NULL) {
+ free(ks->ks_str);
+ ks->ks_str = NULL;
+ }
+ free(ks);
+ ks = NULL;
+
+ if (STAILQ_NEXT(vent, next_ve) != NULL) {
+ xo_emit("{P: }");
+ linelen++;
+ }
+ }
+ xo_emit("\n");
+ xo_close_instance("process");
+ if (prtheader && lineno++ == prtheader - 4) {
+ xo_emit("\n");
+ printheader();
+ lineno = 0;
+ }
+ }
+ xo_close_list("process");
+ xo_close_container("process-information");
+ if (xo_finish() < 0)
+ xo_err(1, "stdout");
+
+ free_list(&gidlist);
+ free_list(&jidlist);
+ free_list(&pidlist);
+ free_list(&pgrplist);
+ free_list(&ruidlist);
+ free_list(&sesslist);
+ free_list(&ttylist);
+ free_list(&uidlist);
+ for (i = 0; i < nkept; i++)
+ free(kinfo[i].ki_d.prefix);
+ free(kinfo);
+
+ exit(eval);
+}
+
+static int
+addelem_gid(struct listinfo *inf, const char *elem)
+{
+ struct group *grp;
+ const char *nameorID;
+ char *endp;
+ u_long bigtemp;
+
+ if (*elem == '\0' || strlen(elem) >= MAXLOGNAME) {
+ if (*elem == '\0')
+ xo_warnx("Invalid (zero-length) %s name", inf->lname);
+ else
+ xo_warnx("%s name too long: %s", inf->lname, elem);
+ optfatal = 1;
+ return (0); /* Do not add this value. */
+ }
+
+ /*
+ * SUSv3 states that `ps -G grouplist' should match "real-group
+ * ID numbers", and does not mention group-names. I do want to
+ * also support group-names, so this tries for a group-id first,
+ * and only tries for a name if that doesn't work. This is the
+ * opposite order of what is done in addelem_uid(), but in
+ * practice the order would only matter for group-names which
+ * are all-numeric.
+ */
+ grp = NULL;
+ nameorID = "named";
+ errno = 0;
+ bigtemp = strtoul(elem, &endp, 10);
+ if (errno == 0 && *endp == '\0' && bigtemp <= GID_MAX) {
+ nameorID = "name or ID matches";
+ grp = getgrgid((gid_t)bigtemp);
+ }
+ if (grp == NULL)
+ grp = getgrnam(elem);
+ if (grp == NULL) {
+ xo_warnx("No %s %s '%s'", inf->lname, nameorID, elem);
+ optfatal = 1;
+ return (0);
+ }
+ if (inf->count >= inf->maxcount)
+ expand_list(inf);
+ inf->l.gids[(inf->count)++] = grp->gr_gid;
+ return (1);
+}
+
+static int
+addelem_jid(struct listinfo *inf, const char *elem)
+{
+ int tempid;
+
+ if (*elem == '\0') {
+ xo_warnx("Invalid (zero-length) jail id");
+ optfatal = 1;
+ return (0); /* Do not add this value. */
+ }
+
+ tempid = jail_getid(elem);
+ if (tempid < 0) {
+ xo_warnx("Invalid %s: %s", inf->lname, elem);
+ optfatal = 1;
+ return (0);
+ }
+
+ if (inf->count >= inf->maxcount)
+ expand_list(inf);
+ inf->l.jids[(inf->count)++] = tempid;
+ return (1);
+}
+
+static int
+addelem_pid(struct listinfo *inf, const char *elem)
+{
+ char *endp;
+ long tempid;
+
+ if (*elem == '\0') {
+ xo_warnx("Invalid (zero-length) process id");
+ optfatal = 1;
+ return (0); /* Do not add this value. */
+ }
+
+ errno = 0;
+ tempid = strtol(elem, &endp, 10);
+ if (*endp != '\0' || tempid < 0 || elem == endp) {
+ xo_warnx("Invalid %s: %s", inf->lname, elem);
+ errno = ERANGE;
+ } else if (errno != 0 || tempid > pid_max) {
+ xo_warnx("%s too large: %s", inf->lname, elem);
+ errno = ERANGE;
+ }
+ if (errno == ERANGE) {
+ optfatal = 1;
+ return (0);
+ }
+ if (inf->count >= inf->maxcount)
+ expand_list(inf);
+ inf->l.pids[(inf->count)++] = tempid;
+ return (1);
+}
+
+/*
+ * The user can specify a device via one of three formats:
+ * 1) fully qualified, e.g.: /dev/ttyp0 /dev/console /dev/pts/0
+ * 2) missing "/dev", e.g.: ttyp0 console pts/0
+ * 3) two-letters, e.g.: p0 co 0
+ * (matching letters that would be seen in the "TT" column)
+ */
+static int
+addelem_tty(struct listinfo *inf, const char *elem)
+{
+ const char *ttypath;
+ struct stat sb;
+ char pathbuf[PATH_MAX], pathbuf2[PATH_MAX], pathbuf3[PATH_MAX];
+
+ ttypath = NULL;
+ pathbuf2[0] = '\0';
+ pathbuf3[0] = '\0';
+ switch (*elem) {
+ case '/':
+ ttypath = elem;
+ break;
+ case 'c':
+ if (strcmp(elem, "co") == 0) {
+ ttypath = _PATH_CONSOLE;
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ strlcpy(pathbuf, _PATH_DEV, sizeof(pathbuf));
+ strlcat(pathbuf, elem, sizeof(pathbuf));
+ ttypath = pathbuf;
+ if (strncmp(pathbuf, _PATH_TTY, strlen(_PATH_TTY)) == 0)
+ break;
+ if (strncmp(pathbuf, _PATH_PTS, strlen(_PATH_PTS)) == 0)
+ break;
+ if (strcmp(pathbuf, _PATH_CONSOLE) == 0)
+ break;
+ /* Check to see if /dev/tty${elem} exists */
+ strlcpy(pathbuf2, _PATH_TTY, sizeof(pathbuf2));
+ strlcat(pathbuf2, elem, sizeof(pathbuf2));
+ if (stat(pathbuf2, &sb) == 0 && S_ISCHR(sb.st_mode)) {
+ /* No need to repeat stat() && S_ISCHR() checks */
+ ttypath = NULL;
+ break;
+ }
+ /* Check to see if /dev/pts/${elem} exists */
+ strlcpy(pathbuf3, _PATH_PTS, sizeof(pathbuf3));
+ strlcat(pathbuf3, elem, sizeof(pathbuf3));
+ if (stat(pathbuf3, &sb) == 0 && S_ISCHR(sb.st_mode)) {
+ /* No need to repeat stat() && S_ISCHR() checks */
+ ttypath = NULL;
+ break;
+ }
+ break;
+ }
+ if (ttypath) {
+ if (stat(ttypath, &sb) == -1) {
+ if (pathbuf3[0] != '\0')
+ xo_warn("%s, %s, and %s", pathbuf3, pathbuf2,
+ ttypath);
+ else
+ xo_warn("%s", ttypath);
+ optfatal = 1;
+ return (0);
+ }
+ if (!S_ISCHR(sb.st_mode)) {
+ if (pathbuf3[0] != '\0')
+ xo_warnx("%s, %s, and %s: Not a terminal",
+ pathbuf3, pathbuf2, ttypath);
+ else
+ xo_warnx("%s: Not a terminal", ttypath);
+ optfatal = 1;
+ return (0);
+ }
+ }
+ if (inf->count >= inf->maxcount)
+ expand_list(inf);
+ inf->l.ttys[(inf->count)++] = sb.st_rdev;
+ return (1);
+}
+
+static int
+addelem_uid(struct listinfo *inf, const char *elem)
+{
+ struct passwd *pwd;
+ char *endp;
+ u_long bigtemp;
+
+ if (*elem == '\0' || strlen(elem) >= MAXLOGNAME) {
+ if (*elem == '\0')
+ xo_warnx("Invalid (zero-length) %s name", inf->lname);
+ else
+ xo_warnx("%s name too long: %s", inf->lname, elem);
+ optfatal = 1;
+ return (0); /* Do not add this value. */
+ }
+
+ pwd = getpwnam(elem);
+ if (pwd == NULL) {
+ errno = 0;
+ bigtemp = strtoul(elem, &endp, 10);
+ if (errno != 0 || *endp != '\0' || bigtemp > UID_MAX)
+ xo_warnx("No %s named '%s'", inf->lname, elem);
+ else {
+ /* The string is all digits, so it might be a userID. */
+ pwd = getpwuid((uid_t)bigtemp);
+ if (pwd == NULL)
+ xo_warnx("No %s name or ID matches '%s'",
+ inf->lname, elem);
+ }
+ }
+ if (pwd == NULL) {
+ /*
+ * These used to be treated as minor warnings (and the
+ * option was simply ignored), but now they are fatal
+ * errors (and the command will be aborted).
+ */
+ optfatal = 1;
+ return (0);
+ }
+ if (inf->count >= inf->maxcount)
+ expand_list(inf);
+ inf->l.uids[(inf->count)++] = pwd->pw_uid;
+ return (1);
+}
+
+static void
+add_list(struct listinfo *inf, const char *argp)
+{
+ const char *savep;
+ char *cp, *endp;
+ int toolong;
+ char elemcopy[PATH_MAX];
+
+ if (*argp == '\0')
+ inf->addelem(inf, argp);
+ while (*argp != '\0') {
+ while (*argp != '\0' && strchr(W_SEP, *argp) != NULL)
+ argp++;
+ savep = argp;
+ toolong = 0;
+ cp = elemcopy;
+ if (strchr(T_SEP, *argp) == NULL) {
+ endp = elemcopy + sizeof(elemcopy) - 1;
+ while (*argp != '\0' && cp <= endp &&
+ strchr(W_SEP T_SEP, *argp) == NULL)
+ *cp++ = *argp++;
+ if (cp > endp)
+ toolong = 1;
+ }
+ if (!toolong) {
+ *cp = '\0';
+ /*
+ * Add this single element to the given list.
+ */
+ inf->addelem(inf, elemcopy);
+ } else {
+ /*
+ * The string is too long to copy. Find the end
+ * of the string to print out the warning message.
+ */
+ while (*argp != '\0' && strchr(W_SEP T_SEP,
+ *argp) == NULL)
+ argp++;
+ xo_warnx("Value too long: %.*s", (int)(argp - savep),
+ savep);
+ optfatal = 1;
+ }
+ /*
+ * Skip over any number of trailing whitespace characters,
+ * but only one (at most) trailing element-terminating
+ * character.
+ */
+ while (*argp != '\0' && strchr(W_SEP, *argp) != NULL)
+ argp++;
+ if (*argp != '\0' && strchr(T_SEP, *argp) != NULL) {
+ argp++;
+ /* Catch case where string ended with a comma. */
+ if (*argp == '\0')
+ inf->addelem(inf, argp);
+ }
+ }
+}
+
+static void
+descendant_sort(KINFO *ki, int items)
+{
+ int dst, lvl, maxlvl, n, ndst, nsrc, siblings, src;
+ unsigned char *path;
+ KINFO kn;
+
+ /*
+ * First, sort the entries by descendancy, tracking the descendancy
+ * depth in the ki_d.level field.
+ */
+ src = 0;
+ maxlvl = 0;
+ while (src < items) {
+ if (ki[src].ki_d.level) {
+ src++;
+ continue;
+ }
+ for (nsrc = 1; src + nsrc < items; nsrc++)
+ if (!ki[src + nsrc].ki_d.level)
+ break;
+
+ for (dst = 0; dst < items; dst++) {
+ if (ki[dst].ki_p->ki_pid == ki[src].ki_p->ki_pid)
+ continue;
+ if (ki[dst].ki_p->ki_pid == ki[src].ki_p->ki_ppid)
+ break;
+ }
+
+ if (dst == items) {
+ src += nsrc;
+ continue;
+ }
+
+ for (ndst = 1; dst + ndst < items; ndst++)
+ if (ki[dst + ndst].ki_d.level <= ki[dst].ki_d.level)
+ break;
+
+ for (n = src; n < src + nsrc; n++) {
+ ki[n].ki_d.level += ki[dst].ki_d.level + 1;
+ if (maxlvl < ki[n].ki_d.level)
+ maxlvl = ki[n].ki_d.level;
+ }
+
+ while (nsrc) {
+ if (src < dst) {
+ kn = ki[src];
+ memmove(ki + src, ki + src + 1,
+ (dst - src + ndst - 1) * sizeof *ki);
+ ki[dst + ndst - 1] = kn;
+ nsrc--;
+ dst--;
+ ndst++;
+ } else if (src != dst + ndst) {
+ kn = ki[src];
+ memmove(ki + dst + ndst + 1, ki + dst + ndst,
+ (src - dst - ndst) * sizeof *ki);
+ ki[dst + ndst] = kn;
+ ndst++;
+ nsrc--;
+ src++;
+ } else {
+ ndst += nsrc;
+ src += nsrc;
+ nsrc = 0;
+ }
+ }
+ }
+
+ /*
+ * Now populate ki_d.prefix (instead of ki_d.level) with the command
+ * prefix used to show descendancies.
+ */
+ path = calloc((maxlvl + 7) / 8, sizeof(unsigned char));
+ for (src = 0; src < items; src++) {
+ if ((lvl = ki[src].ki_d.level) == 0) {
+ ki[src].ki_d.prefix = NULL;
+ continue;
+ }
+ if ((ki[src].ki_d.prefix = malloc(lvl * 2 + 1)) == NULL)
+ xo_errx(1, "malloc failed");
+ for (n = 0; n < lvl - 2; n++) {
+ ki[src].ki_d.prefix[n * 2] =
+ path[n / 8] & 1 << (n % 8) ? '|' : ' ';
+ ki[src].ki_d.prefix[n * 2 + 1] = ' ';
+ }
+ if (n == lvl - 2) {
+ /* Have I any more siblings? */
+ for (siblings = 0, dst = src + 1; dst < items; dst++) {
+ if (ki[dst].ki_d.level > lvl)
+ continue;
+ if (ki[dst].ki_d.level == lvl)
+ siblings = 1;
+ break;
+ }
+ if (siblings)
+ path[n / 8] |= 1 << (n % 8);
+ else
+ path[n / 8] &= ~(1 << (n % 8));
+ ki[src].ki_d.prefix[n * 2] = siblings ? '|' : '`';
+ ki[src].ki_d.prefix[n * 2 + 1] = '-';
+ n++;
+ }
+ strcpy(ki[src].ki_d.prefix + n * 2, "- ");
+ }
+ free(path);
+}
+
+static void *
+expand_list(struct listinfo *inf)
+{
+ void *newlist;
+ int newmax;
+
+ newmax = (inf->maxcount + 1) << 1;
+ newlist = realloc(inf->l.ptr, newmax * inf->elemsize);
+ if (newlist == NULL) {
+ free(inf->l.ptr);
+ xo_errx(1, "realloc to %d %ss failed", newmax, inf->lname);
+ }
+ inf->maxcount = newmax;
+ inf->l.ptr = newlist;
+
+ return (newlist);
+}
+
+static void
+free_list(struct listinfo *inf)
+{
+
+ inf->count = inf->elemsize = inf->maxcount = 0;
+ if (inf->l.ptr != NULL)
+ free(inf->l.ptr);
+ inf->addelem = NULL;
+ inf->lname = NULL;
+ inf->l.ptr = NULL;
+}
+
+static void
+init_list(struct listinfo *inf, addelem_rtn artn, int elemsize,
+ const char *lname)
+{
+
+ inf->count = inf->maxcount = 0;
+ inf->elemsize = elemsize;
+ inf->addelem = artn;
+ inf->lname = lname;
+ inf->l.ptr = NULL;
+}
+
+VARENT *
+find_varentry(const char *name)
+{
+ struct varent *vent;
+
+ STAILQ_FOREACH(vent, &varlist, next_ve) {
+ if (strcmp(vent->var->name, name) == 0)
+ return vent;
+ }
+ return NULL;
+}
+
+static void
+scan_vars(struct keyword_info *const keywords_info)
+{
+ struct varent *vent;
+ const VAR *v;
+
+ STAILQ_FOREACH(vent, &varlist, next_ve) {
+ v = vent->var;
+ if (v->flag & USER)
+ needuser = 1;
+ if (v->flag & COMM)
+ needcomm = 1;
+ if ((vent->flags & VE_KEEP) != 0)
+ keywords_info[aliased_keyword_index(v)].flags |=
+ KWI_HAS_MUST_KEEP_COLUMN;
+ }
+}
+
+/*
+ * For each explicitly requested keyword, remove all the same keywords
+ * from "canned" displays. If the same keyword appears multiple times
+ * only in "canned displays", then keep the first (leftmost) occurence
+ * only (with the reasoning that columns requested first are the most
+ * important as their positions catch the eye more).
+ */
+static void
+remove_redundant_columns(struct keyword_info *const keywords_info)
+{
+ struct varent *prev_vent, *vent, *next_vent;
+
+ prev_vent = NULL;
+ STAILQ_FOREACH_SAFE(vent, &varlist, next_ve, next_vent) {
+ const VAR *const v = vent->var;
+ struct keyword_info *const kwi =
+ &keywords_info[aliased_keyword_index(v)];
+
+ /*
+ * If the current column is not marked as to absolutely keep,
+ * and we have either already output one with the same keyword
+ * or know we will output one later, remove it.
+ */
+ if ((vent->flags & VE_KEEP) == 0 &&
+ (kwi->flags & (KWI_HAS_MUST_KEEP_COLUMN | KWI_SEEN)) != 0) {
+ if (prev_vent == NULL)
+ STAILQ_REMOVE_HEAD(&varlist, next_ve);
+ else
+ STAILQ_REMOVE_AFTER(&varlist, prev_vent,
+ next_ve);
+ } else
+ prev_vent = vent;
+
+
+ kwi->flags |= KWI_SEEN;
+ }
+}
+
+static void
+format_output(KINFO *ki)
+{
+ struct varent *vent;
+ const VAR *v;
+ KINFO_STR *ks;
+ char *str;
+ u_int len;
+
+ STAILQ_INIT(&ki->ki_ks);
+ STAILQ_FOREACH(vent, &varlist, next_ve) {
+ v = vent->var;
+ str = (v->oproc)(ki, vent);
+ ks = malloc(sizeof(*ks));
+ if (ks == NULL)
+ xo_errx(1, "malloc failed");
+ ks->ks_str = str;
+ STAILQ_INSERT_TAIL(&ki->ki_ks, ks, ks_next);
+ if (str != NULL) {
+ len = strlen(str);
+ } else
+ len = 1; /* "-" */
+ if (vent->width < len)
+ vent->width = len;
+ }
+}
+
+static const char *
+fmt(char **(*fn)(kvm_t *, const struct kinfo_proc *, int), KINFO *ki,
+ char *comm, char *thread, int maxlen)
+{
+ const char *s;
+
+ s = fmt_argv((*fn)(kd, ki->ki_p, termwidth), comm,
+ showthreads && ki->ki_p->ki_numthreads > 1 ? thread : NULL, maxlen);
+ return (s);
+}
+
+static void
+saveuser(KINFO *ki)
+{
+ char tdname[COMMLEN + 1];
+
+ ki->ki_valid = 1;
+
+ /*
+ * save arguments if needed
+ */
+ if (needcomm) {
+ if (ki->ki_p->ki_stat == SZOMB) {
+ ki->ki_args = strdup("<defunct>");
+ } else {
+ (void)snprintf(tdname, sizeof(tdname), "%s%s",
+ ki->ki_p->ki_tdname, ki->ki_p->ki_moretdname);
+ ki->ki_args = fmt(kvm_getargv, ki,
+ ki->ki_p->ki_comm, tdname, COMMLEN * 2 + 1);
+ }
+ if (ki->ki_args == NULL)
+ xo_errx(1, "malloc failed");
+ } else {
+ ki->ki_args = NULL;
+ }
+ if (needenv) {
+ ki->ki_env = fmt(kvm_getenvv, ki, (char *)NULL,
+ (char *)NULL, 0);
+ if (ki->ki_env == NULL)
+ xo_errx(1, "malloc failed");
+ } else {
+ ki->ki_env = NULL;
+ }
+}
+
+/* A macro used to improve the readability of pscomp(). */
+#define DIFF_RETURN(a, b, field) do { \
+ if ((a)->field != (b)->field) \
+ return (((a)->field < (b)->field) ? -1 : 1); \
+} while (0)
+
+static int
+pscomp(const void *a, const void *b)
+{
+ const KINFO *ka, *kb;
+
+ ka = a;
+ kb = b;
+ /* SORTCPU and SORTMEM are sorted in descending order. */
+ if (sortby == SORTCPU)
+ DIFF_RETURN(kb, ka, ki_pcpu);
+ if (sortby == SORTMEM)
+ DIFF_RETURN(kb, ka, ki_memsize);
+ /*
+ * TTY's are sorted in ascending order, except that all NODEV
+ * processes come before all processes with a device.
+ */
+ if (ka->ki_p->ki_tdev != kb->ki_p->ki_tdev) {
+ if (ka->ki_p->ki_tdev == NODEV)
+ return (-1);
+ if (kb->ki_p->ki_tdev == NODEV)
+ return (1);
+ DIFF_RETURN(ka, kb, ki_p->ki_tdev);
+ }
+
+ /* PID's and TID's (threads) are sorted in ascending order. */
+ DIFF_RETURN(ka, kb, ki_p->ki_pid);
+ DIFF_RETURN(ka, kb, ki_p->ki_tid);
+ return (0);
+}
+#undef DIFF_RETURN
+
+/*
+ * ICK (all for getopt), would rather hide the ugliness
+ * here than taint the main code.
+ *
+ * ps foo -> ps -foo
+ * ps 34 -> ps -p34
+ *
+ * The old convention that 't' with no trailing tty arg means the users
+ * tty, is only supported if argv[1] doesn't begin with a '-'. This same
+ * feature is available with the option 'T', which takes no argument.
+ */
+static char *
+kludge_oldps_options(const char *optlist, char *origval, const char *nextarg)
+{
+ size_t len;
+ char *argp, *cp, *newopts, *ns, *optp, *pidp;
+
+ /*
+ * See if the original value includes any option which takes an
+ * argument (and will thus use up the remainder of the string).
+ */
+ argp = NULL;
+ if (optlist != NULL) {
+ for (cp = origval; *cp != '\0'; cp++) {
+ optp = strchr(optlist, *cp);
+ if ((optp != NULL) && *(optp + 1) == ':') {
+ argp = cp;
+ break;
+ }
+ }
+ }
+ if (argp != NULL && *origval == '-')
+ return (origval);
+
+ /*
+ * if last letter is a 't' flag with no argument (in the context
+ * of the oldps options -- option string NOT starting with a '-' --
+ * then convert to 'T' (meaning *this* terminal, i.e. ttyname(0)).
+ *
+ * However, if a flag accepting a string argument is found earlier
+ * in the option string (including a possible `t' flag), then the
+ * remainder of the string must be the argument to that flag; so
+ * do not modify that argument. Note that a trailing `t' would
+ * cause argp to be set, if argp was not already set by some
+ * earlier option.
+ */
+ len = strlen(origval);
+ cp = origval + len - 1;
+ pidp = NULL;
+ if (*cp == 't' && *origval != '-' && cp == argp) {
+ if (nextarg == NULL || *nextarg == '-' || isdigitch(*nextarg))
+ *cp = 'T';
+ } else if (argp == NULL) {
+ /*
+ * The original value did not include any option which takes
+ * an argument (and that would include `p' and `t'), so check
+ * the value for trailing number, or comma-separated list of
+ * numbers, which we will treat as a pid request.
+ */
+ if (isdigitch(*cp)) {
+ while (cp >= origval && (*cp == ',' || isdigitch(*cp)))
+ --cp;
+ pidp = cp + 1;
+ }
+ }
+
+ /*
+ * If nothing needs to be added to the string, then return
+ * the "original" (although possibly modified) value.
+ */
+ if (*origval == '-' && pidp == NULL)
+ return (origval);
+
+ /*
+ * Create a copy of the string to add '-' and/or 'p' to the
+ * original value.
+ */
+ if ((newopts = ns = malloc(len + 3)) == NULL)
+ xo_errx(1, "malloc failed");
+
+ if (*origval != '-')
+ *ns++ = '-'; /* add option flag */
+
+ if (pidp == NULL)
+ strcpy(ns, origval);
+ else {
+ /*
+ * Copy everything before the pid string, add the `p',
+ * and then copy the pid string.
+ */
+ len = pidp - origval;
+ memcpy(ns, origval, len);
+ ns += len;
+ *ns++ = 'p';
+ strcpy(ns, pidp);
+ }
+
+ return (newopts);
+}
+
+static void
+pidmax_init(void)
+{
+ size_t intsize;
+
+ intsize = sizeof(pid_max);
+ if (sysctlbyname("kern.pid_max", &pid_max, &intsize, NULL, 0) < 0) {
+ xo_warn("unable to read kern.pid_max");
+ pid_max = 99999;
+ }
+}
+
+static void __dead2
+usage(void)
+{
+#define SINGLE_OPTS "[-aCcdeHhjlmrSTuvwXxZ]"
+
+ xo_error("%s\n%s\n%s\n%s\n%s\n",
+ "usage: ps [--libxo] " SINGLE_OPTS " [-O fmt | -o fmt]",
+ " [-G gid[,gid...]] [-J jid[,jid...]] [-M core] [-N system]",
+ " [-p pid[,pid...]] [-t tty[,tty...]] [-U user[,user...]]",
+ " [-D up | down | both]",
+ " ps [--libxo] -L");
+ exit(1);
+}
diff --git a/bin/ps/ps.h b/bin/ps/ps.h
new file mode 100644
index 000000000000..065a4c1f1c54
--- /dev/null
+++ b/bin/ps/ps.h
@@ -0,0 +1,100 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/queue.h>
+
+#define UNLIMITED 0 /* unlimited terminal width */
+enum type { UNSPEC, /* For output routines that don't care and aliases. */
+ CHAR, UCHAR, SHORT, USHORT, INT, UINT, LONG, ULONG, KPTR, PGTOK };
+
+typedef struct kinfo_str {
+ STAILQ_ENTRY(kinfo_str) ks_next;
+ char *ks_str; /* formatted string */
+} KINFO_STR;
+
+typedef struct kinfo {
+ struct kinfo_proc *ki_p; /* kinfo_proc structure */
+ const char *ki_args; /* exec args */
+ const char *ki_env; /* environment */
+ int ki_valid; /* 1 => uarea stuff valid */
+ double ki_pcpu; /* calculated in main() */
+ segsz_t ki_memsize; /* calculated in main() */
+ union {
+ int level; /* used in decendant_sort() */
+ char *prefix; /* calculated in decendant_sort() */
+ } ki_d;
+ STAILQ_HEAD(, kinfo_str) ki_ks;
+} KINFO;
+
+/* Keywords/variables to be printed. */
+typedef struct varent {
+ STAILQ_ENTRY(varent) next_ve;
+ const char *header;
+ const struct var *var;
+ u_int width;
+#define VE_KEEP (1 << 0)
+ uint16_t flags;
+} VARENT;
+STAILQ_HEAD(velisthead, varent);
+
+struct var;
+typedef struct var VAR;
+/* Structure representing one available keyword. */
+struct var {
+ const char *name; /* name(s) of variable */
+ union {
+ /* Valid field depends on RESOLVED_ALIAS' presence. */
+ const char *aliased; /* keyword this one is an alias to */
+ const VAR *final_kw; /* final aliased keyword */
+ };
+ const char *header; /* default header */
+ const char *field; /* xo field name */
+#define COMM 0x01 /* needs exec arguments and environment (XXX) */
+#define LJUST 0x02 /* left adjust on output (trailing blanks) */
+#define USER 0x04 /* needs user structure */
+#define INF127 0x10 /* values >127 displayed as 127 */
+#define NOINHERIT 0x1000 /* Don't inherit flags from aliased keyword. */
+#define RESOLVING_ALIAS 0x10000 /* Used transiently to resolve aliases. */
+#define RESOLVED_ALIAS 0x20000 /* Mark that an alias has been resolved. */
+ u_int flag;
+ /* output routine */
+ char *(*oproc)(struct kinfo *, struct varent *);
+ /*
+ * The following (optional) elements are hooks for passing information
+ * to the generic output routine pvar (which prints simple elements
+ * from the well known kinfo_proc structure).
+ */
+ size_t off; /* offset in structure */
+ enum type type; /* type of element */
+ const char *fmt; /* printf format (depends on output routine) */
+};
+
+#include "extern.h"