summaryrefslogtreecommitdiffstats
path: root/src/ps
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/ps/HACKING46
-rw-r--r--src/ps/common.h512
-rw-r--r--src/ps/display.c681
-rw-r--r--src/ps/global.c651
-rw-r--r--src/ps/help.c223
-rw-r--r--src/ps/output.c2370
-rw-r--r--src/ps/parser.c1268
-rw-r--r--src/ps/regression26
-rw-r--r--src/ps/select.c162
-rw-r--r--src/ps/signames.c170
-rw-r--r--src/ps/sortformat.c949
-rw-r--r--src/ps/stacktrace.c191
12 files changed, 7249 insertions, 0 deletions
diff --git a/src/ps/HACKING b/src/ps/HACKING
new file mode 100644
index 0000000..e7a6b4c
--- /dev/null
+++ b/src/ps/HACKING
@@ -0,0 +1,46 @@
+Warning:
+
+This code must corrctly handle lots of picky little details to meet
+the Unix98 standard while simultaneously being as compatible as
+possible with the original Linux ps. Don't "fix" something without
+considering the impact on all the special-case code. For example,
+the "tty" format _must_ use "TT" as the header, even though the SysV
+output formats _must_ use "TTY".
+
+File overview:
+
+display.c main(), debug code, iterates over processes
+escape.c Does stuff like \202 and < to command and environment.
+global.c Data + code to init it.
+help.c Help message.
+output.c Giant tables and lots of output functions.
+parser.c Initial command parsing.
+select.c want_this_proc() checks a process against flags & lists
+sortformat.c Parses sort & format specifier lists. Picks output format.
+stacktrace.c Debug code, not normally used.
+../proc/* Library used to gather data.
+regression Regression tests that ought to be run.
+common.h Lots of interesting stuff.
+Makefile Makefile
+p Script used to test ps when the library is not installed.
+utf Empty file used to test "ps ut?" unmangling behavior.
+ps.1 Man page.
+
+Operation:
+
+Unless the personality forces BSD parsing, parser.c tries to parse the
+command line as a mixed BSD+SysV+Gnu mess. On failure, BSD parsing is
+attempted. If BSD parsing fails _after_ SysV parsing has been attempted,
+the error message comes from the original SysV parse.
+
+Control goes to sortformat.c, which must pick apart ambiguous options
+like "O". Failure can reset the whole program and set PER_FORCE_BSD,
+which means a second trip through parser.c and sortformat.c.
+
+The choice of output format happens in sortformat.c. There is a switch()
+with all the valid format_flags combinations. The SysV and default
+options are NULL (unless overridden by personality), which causes a
+trip through SysV output format generation hackery. Note that the
+default format always goes through there, even if it is for BSD.
+Formats that came from the switch() (generally BSD, plus overrides)
+get mangled a bit to support various SysV output modifiers.
diff --git a/src/ps/common.h b/src/ps/common.h
new file mode 100644
index 0000000..ea49a03
--- /dev/null
+++ b/src/ps/common.h
@@ -0,0 +1,512 @@
+/*
+ * common.h - shared header file
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net
+ * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 1998-2002 Albert Cahalan
+ *
+ * This file may be used subject to the terms and conditions of the
+ * GNU Library General Public License Version 2, or any later version
+ * at your option, as published by the Free Software Foundation.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ */
+
+#ifndef PROCPS_PS_H
+#define PROCPS_PS_H
+
+#include <stdbool.h>
+
+#include "nls.h"
+#include "meminfo.h"
+#include "misc.h"
+#include "pids.h"
+#include "stat.h"
+
+// --- <pids> interface begin ||||||||||||||||||||||||||||||||||||||||||||
+// -----------------------------------------------------------------------
+
+// hack to minimize code impact
+#undef proc_t
+#define proc_t struct pids_stack
+
+/* this is for allocation of the Pids_items and represents a compromise.
+ we can't predict how many fields will actually be requested yet there
+ are numerous duplicate format_array entries. here are the statistics:
+ 252 entries in the format_array
+ 82 of those entries are unique
+ 60 equals a former proc_t size
+ in reality, only a small portion of the stack depth will be occupied,
+ and the excess represents storage cost only, not a run-time cpu cost! */
+#define PIDSITEMS 70
+
+/* a 'results stack value' extractor macro
+ where: E=rel enum, T=data type, S=stack */
+#define rSv(E,T,S) PIDS_VAL(rel_ ## E, T, S, Pids_info)
+
+#define namREL(e) rel_ ## e
+#define makEXT(e) extern int namREL(e);
+#define makREL(e) int namREL(e) = -1;
+#define chkREL(e) if (namREL(e) < 0) { \
+ Pids_items[Pids_index] = PIDS_ ## e; \
+ namREL(e) = (Pids_index < PIDSITEMS) ? Pids_index++ : rel_noop; }
+
+#define setREL1(e) { \
+ if (!outbuf) { \
+ chkREL(e) \
+ return 0; \
+ } }
+#define setREL2(e1,e2) { \
+ if (!outbuf) { \
+ chkREL(e1) chkREL(e2) \
+ return 0; \
+ } }
+#define setREL3(e1,e2,e3) { \
+ if (!outbuf) { \
+ chkREL(e1) chkREL(e2) chkREL(e3) \
+ return 0; \
+ } }
+#define setREL4(e1,e2,e3,e4) { \
+ if (!outbuf) { \
+ chkREL(e1) chkREL(e2) chkREL(e3) chkREL(e4) \
+ return 0; \
+ } }
+
+extern struct pids_info *Pids_info;
+extern enum pids_item *Pids_items;
+extern int Pids_index;
+
+// most of these need not be extern, they're unique to output.c
+// (but for future flexibility the easiest path has been taken)
+makEXT(ADDR_CODE_END)
+makEXT(ADDR_CODE_START)
+makEXT(ADDR_CURR_EIP)
+makEXT(ADDR_CURR_ESP)
+makEXT(ADDR_STACK_START)
+makEXT(AUTOGRP_ID)
+makEXT(AUTOGRP_NICE)
+makEXT(CGNAME)
+makEXT(CGROUP)
+makEXT(CMD)
+makEXT(CMDLINE)
+makEXT(ENVIRON)
+makEXT(EXE)
+makEXT(FLAGS)
+makEXT(FLT_MAJ)
+makEXT(FLT_MAJ_C)
+makEXT(FLT_MIN)
+makEXT(FLT_MIN_C)
+makEXT(ID_EGID)
+makEXT(ID_EGROUP)
+makEXT(ID_EUID)
+makEXT(ID_EUSER)
+makEXT(ID_FGID)
+makEXT(ID_FGROUP)
+makEXT(ID_FUID)
+makEXT(ID_FUSER)
+makEXT(ID_LOGIN)
+makEXT(ID_PGRP)
+makEXT(ID_PID)
+makEXT(ID_PPID)
+makEXT(ID_RGID)
+makEXT(ID_RGROUP)
+makEXT(ID_RUID)
+makEXT(ID_RUSER)
+makEXT(ID_SESSION)
+makEXT(ID_SGID)
+makEXT(ID_SGROUP)
+makEXT(ID_SUID)
+makEXT(ID_SUSER)
+makEXT(ID_TGID)
+makEXT(ID_TPGID)
+makEXT(IO_READ_BYTES)
+makEXT(IO_READ_CHARS)
+makEXT(IO_READ_OPS)
+makEXT(IO_WRITE_BYTES)
+makEXT(IO_WRITE_CBYTES)
+makEXT(IO_WRITE_CHARS)
+makEXT(IO_WRITE_OPS)
+makEXT(LXCNAME)
+makEXT(NICE)
+makEXT(NLWP)
+makEXT(NS_CGROUP)
+makEXT(NS_IPC)
+makEXT(NS_MNT)
+makEXT(NS_NET)
+makEXT(NS_PID)
+makEXT(NS_TIME)
+makEXT(NS_USER)
+makEXT(NS_UTS)
+makEXT(OOM_ADJ)
+makEXT(OOM_SCORE)
+makEXT(PRIORITY)
+makEXT(PRIORITY_RT)
+makEXT(PROCESSOR)
+makEXT(PROCESSOR_NODE)
+makEXT(RSS)
+makEXT(RSS_RLIM)
+makEXT(SCHED_CLASS)
+makEXT(SD_MACH)
+makEXT(SD_OUID)
+makEXT(SD_SEAT)
+makEXT(SD_SESS)
+makEXT(SD_SLICE)
+makEXT(SD_UNIT)
+makEXT(SD_UUNIT)
+makEXT(SIGBLOCKED)
+makEXT(SIGCATCH)
+makEXT(SIGIGNORE)
+makEXT(SIGNALS)
+makEXT(SIGPENDING)
+makEXT(SMAP_PRV_TOTAL)
+makEXT(SMAP_PSS)
+makEXT(STATE)
+makEXT(SUPGIDS)
+makEXT(SUPGROUPS)
+makEXT(TICS_ALL)
+makEXT(TICS_ALL_C)
+makEXT(TIME_ALL)
+makEXT(TIME_ELAPSED)
+makEXT(TICS_BEGAN)
+makEXT(TTY)
+makEXT(TTY_NAME)
+makEXT(TTY_NUMBER)
+makEXT(UTILIZATION)
+makEXT(UTILIZATION_C)
+makEXT(VM_DATA)
+makEXT(VM_RSS_LOCKED)
+makEXT(VM_RSS)
+makEXT(VM_SIZE)
+makEXT(VM_STACK)
+makEXT(VSIZE_BYTES)
+makEXT(WCHAN_NAME)
+makEXT(extra)
+makEXT(noop)
+// -----------------------------------------------------------------------
+// --- <pids> interface end ||||||||||||||||||||||||||||||||||||||||||||||
+
+
+#if TRACE
+#define trace(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define trace(...)
+#endif
+
+
+/***************** GENERAL DEFINE ********************/
+
+/* selection list */
+#define SEL_RUID 1
+#define SEL_EUID 2
+#define SEL_SUID 3
+#define SEL_FUID 4
+#define SEL_RGID 5
+#define SEL_EGID 6
+#define SEL_SGID 7
+#define SEL_FGID 8
+#define SEL_PGRP 9
+#define SEL_PID 10
+#define SEL_TTY 11
+#define SEL_SESS 12
+#define SEL_COMM 13
+#define SEL_PPID 14
+#define SEL_PID_QUICK 15
+
+/* Since an enum could be smashed by a #define, it would be bad. */
+#define U98 0 /* Unix98 standard */ /* This must be 0 */
+#define XXX 1 /* Common extension */
+#define DEC 2 /* Digital Unix */
+#define AIX 3 /* AIX */
+#define SCO 4 /* SCO */
+#define LNX 5 /* Linux original :-) */
+#define BSD 6 /* FreeBSD and OpenBSD */
+#define SUN 7 /* SunOS 5 (Solaris) */
+#define HPU 8 /* HP-UX */
+#define SGI 9 /* Irix */
+#define SOE 10 /* IBM's S/390 OpenEdition */
+#define TST 11 /* test code */
+
+/*
+ * Try not to overflow the output buffer:
+ * 32 pages for env+cmd
+ * 64 kB pages on IA-64
+ * plus some slack for other stuff
+ * That is about 8.5 MB on IA-64, or 0.6 MB on i386
+ *
+ * Sadly, current kernels only supply one page of env/command data.
+ * The buffer is now protected with a guard page, and via other means
+ * to avoid hitting the guard page.
+ */
+
+/* output buffer size */
+#define OUTBUF_SIZE (2 * 64*1024)
+
+/******************* PS DEFINE *******************/
+
+// Column flags
+// Justification control for flags field comes first.
+#define CF_JUST_MASK 0x0f
+// CF_AIXHACK 0
+#define CF_USER 1 // left if text, right if numeric
+#define CF_LEFT 2
+#define CF_RIGHT 3
+#define CF_UNLIMITED 4
+#define CF_WCHAN 5 // left if text, right if numeric
+#define CF_SIGNAL 6 // right in 9, or 16 if screen_cols>107
+// Then the other flags
+#define CF_PIDMAX 0x00000010 // react to pid_max
+// Only one allowed; use separate bits to catch errors.
+#define CF_PRINT_THREAD_ONLY 0x10000000
+#define CF_PRINT_PROCESS_ONLY 0x20000000
+#define CF_PRINT_EVERY_TIME 0x40000000
+#define CF_PRINT_AS_NEEDED 0x80000000 // means we have no clue, so assume EVERY TIME
+#define CF_PRINT_MASK 0xf0000000
+
+/* thread_flags */
+#define TF_B_H 0x0001
+#define TF_B_m 0x0002
+#define TF_U_m 0x0004
+#define TF_U_T 0x0008
+#define TF_U_L 0x0010
+#define TF_show_proc 0x0100 // show the summary line
+#define TF_show_task 0x0200 // show the per-thread lines
+#define TF_show_both 0x0400 // distinct proc/task format lists
+#define TF_loose_tasks 0x0800 // let sorting break up task groups (BSDish)
+#define TF_no_sort 0x1000 // don't know if thread-grouping should survive a sort
+#define TF_no_forest 0x2000 // don't see how to do threads w/ forest option
+#define TF_must_use 0x4000 // options only make sense if LWP/SPID column added
+
+/* personality control flags */
+#define PER_BROKEN_o 0x0001
+#define PER_BSD_h 0x0002
+#define PER_BSD_m 0x0004
+#define PER_IRIX_l 0x0008
+#define PER_FORCE_BSD 0x0010
+#define PER_GOOD_o 0x0020
+#define PER_OLD_m 0x0040
+#define PER_NO_DEFAULT_g 0x0080
+#define PER_ZAP_ADDR 0x0100
+#define PER_SANE_USER 0x0200
+#define PER_HPUX_x 0x0400
+#define PER_SVR4_x 0x0800
+#define PER_BSD_COLS 0x1000
+#define PER_UNIX_COLS 0x2000
+
+/* Simple selections by bit mask */
+#define SS_B_x 0x01
+#define SS_B_g 0x02
+#define SS_U_d 0x04
+#define SS_U_a 0x08
+#define SS_B_a 0x10
+
+/* predefined format flags such as: -l -f l u s -j */
+#define FF_Uf 0x0001 /* -f */
+#define FF_Uj 0x0002 /* -j */
+#define FF_Ul 0x0004 /* -l */
+#define FF_Bj 0x0008 /* j */
+#define FF_Bl 0x0010 /* l */
+#define FF_Bs 0x0020 /* s */
+#define FF_Bu 0x0040 /* u */
+#define FF_Bv 0x0080 /* v */
+#define FF_LX 0x0100 /* X */
+#define FF_Lm 0x0200 /* m */ /* overloaded: threads, sort, format */
+#define FF_Fc 0x0400 /* --context */ /* Flask security context format */
+
+/* predefined format modifier flags such as: -l -f l u s -j */
+#define FM_c 0x0001 /* -c */
+#define FM_j 0x0002 /* -j */ /* only set when !sysv_j_format */
+#define FM_y 0x0004 /* -y */
+//#define FM_L 0x0008 /* -L */
+#define FM_P 0x0010 /* -P */
+#define FM_M 0x0020 /* -M */
+//#define FM_T 0x0040 /* -T */
+#define FM_F 0x0080 /* -F */ /* -F also sets the regular -f flags */
+
+/* sorting & formatting */
+/* U,B,G is Unix,BSD,Gnu and then there is the option itself */
+#define SF_U_O 1
+#define SF_U_o 2
+#define SF_B_O 3
+#define SF_B_o 4
+#define SF_B_m 5 /* overloaded: threads, sort, format */
+#define SF_G_sort 6
+#define SF_G_format 7
+
+/* headers */
+#define HEAD_SINGLE 0 /* default, must be 0 */
+#define HEAD_NONE 1
+#define HEAD_MULTI 2
+
+
+/********************** GENERAL TYPEDEF *******************/
+
+/* Other fields that might be useful:
+ *
+ * char *name; user-defined column name (format specification)
+ * int reverse; sorting in reverse (sort specification)
+ *
+ * name in place of u
+ * reverse in place of n
+ */
+
+typedef union sel_union {
+ pid_t pid;
+ pid_t ppid;
+ uid_t uid;
+ gid_t gid;
+ dev_t tty;
+ char cmd[64]; /* this is _not_ \0 terminated */
+} sel_union;
+
+typedef struct selection_node {
+ struct selection_node *next;
+ sel_union *u; /* used if selection type has a list of values */
+ int n; /* used if selection type has a list of values */
+ int typecode;
+} selection_node;
+
+typedef struct sort_node {
+ struct sort_node *next;
+ enum pids_item sr;
+ int (*xe)(char *, proc_t *); // special format_node 'pr' guy
+ enum pids_sort_order reverse;
+ int typecode;
+} sort_node;
+
+typedef struct format_node {
+ struct format_node *next;
+ char *name; /* user can override default name */
+ int (*pr)(char *restrict const outbuf, const proc_t *restrict const pp); // print function
+ int width;
+ int vendor; /* Vendor that invented this */
+ int flags;
+ int typecode;
+} format_node;
+
+typedef struct format_struct {
+ const char *spec; /* format specifier */
+ const char *head; /* default header in the POSIX locale */
+ int (* const pr)(char *restrict const outbuf, const proc_t *restrict const pp); // print function
+ enum pids_item sr;
+ const int width;
+ const int vendor; /* Where does this come from? */
+ const int flags;
+} format_struct;
+
+/* though ps-specific, needed by general file */
+typedef struct macro_struct {
+ const char *spec; /* format specifier */
+ const char *head; /* default header in the POSIX locale */
+} macro_struct;
+
+/**************** PS TYPEDEF ***********************/
+
+typedef struct aix_struct {
+ const int desc; /* 1-character format code */
+ const char *spec; /* format specifier */
+ const char *head; /* default header in the POSIX locale */
+} aix_struct;
+
+typedef struct shortsort_struct {
+ const int desc; /* 1-character format code */
+ const char *spec; /* format specifier */
+} shortsort_struct;
+
+/* Save these options for later: -o o -O O --format --sort */
+typedef struct sf_node {
+ struct sf_node *next; /* next arg */
+ format_node *f_cooked; /* convert each arg alone, then merge */
+ sort_node *s_cooked; /* convert each arg alone, then merge */
+ char *sf;
+ int sf_code;
+} sf_node;
+
+/********************* UNDECIDED GLOBALS **************/
+
+/* output.c */
+extern void show_one_proc(const proc_t *restrict const p, const format_node *restrict fmt);
+extern void print_format_specifiers(void);
+extern const aix_struct *search_aix_array(const int findme);
+extern const shortsort_struct *search_shortsort_array(const int findme);
+extern const format_struct *search_format_array(const char *findme);
+extern const macro_struct *search_macro_array(const char *findme);
+extern void init_output(void);
+extern int pr_nop(char *restrict const outbuf, const proc_t *restrict const pp);
+
+/* global.c */
+extern void reset_global(void);
+
+/* global.c */
+extern int all_processes;
+extern const char *bsd_j_format;
+extern const char *bsd_l_format;
+extern const char *bsd_s_format;
+extern const char *bsd_u_format;
+extern const char *bsd_v_format;
+extern int bsd_c_option;
+extern int bsd_e_option;
+extern uid_t cached_euid;
+extern int cached_tty;
+extern char forest_prefix[4 * 32*1024 + 100];
+extern int forest_type;
+extern unsigned format_flags; /* -l -f l u s -j... */
+extern format_node *format_list; /* digested formatting options */
+extern unsigned format_modifiers; /* -c -j -y -P -L... */
+extern int header_gap;
+extern int header_type; /* none, single, multi... */
+extern int include_dead_children;
+extern int lines_to_next_header;
+extern char *lstart_format;
+extern int max_line_width;
+extern int negate_selection;
+extern int page_size; // "int" for math reasons?
+extern unsigned personality;
+extern int prefer_bsd_defaults;
+extern int running_only;
+extern int screen_cols;
+extern int screen_rows;
+extern selection_node *selection_list;
+extern unsigned simple_select;
+extern sort_node *sort_list;
+extern const char *sysv_f_format;
+extern const char *sysv_fl_format;
+extern const char *sysv_j_format;
+extern const char *sysv_l_format;
+extern unsigned thread_flags;
+extern int unix_f_option;
+extern int user_is_number;
+extern int wchan_is_number;
+extern const char *the_word_help;
+extern bool signal_names;
+
+/************************* PS GLOBALS *********************/
+
+/* display.c */
+extern char *myname;
+
+/* sortformat.c */
+extern int defer_sf_option(const char *arg, int source);
+extern const char *process_sf_options();
+extern void reset_sortformat(void);
+
+/* select.c */
+extern int want_this_proc(proc_t *buf);
+extern const char *select_bits_setup(void);
+
+/* signames.c */
+int print_signame(char *restrict const outbuf, const char *restrict const sig, const size_t len);
+
+/* help.c */
+extern void __attribute__ ((__noreturn__)) do_help(const char *opt, int rc);
+
+/* global.c */
+extern void self_info(void);
+extern void catastrophic_failure(const char *filename, unsigned int linenum,
+ const char *message);
+
+/* parser.c */
+extern int arg_parse(int argc, char *argv[]);
+
+#endif
diff --git a/src/ps/display.c b/src/ps/display.c
new file mode 100644
index 0000000..e1a3b0d
--- /dev/null
+++ b/src/ps/display.c
@@ -0,0 +1,681 @@
+/*
+ * display.c - display ps output
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net
+ * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2012-2014 Jaromir Capik <jcapik@redhat.com
+ * Copyright © 1998-2003 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <grp.h>
+#include <locale.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+#include "c.h"
+#include "fileutils.h"
+#include "signals.h"
+#include "xalloc.h"
+
+#include "common.h"
+
+#ifndef SIGCHLD
+#define SIGCHLD SIGCLD
+#endif
+
+#define SIG_IS_TERM_OR_HUP(signo) (((signo) == SIGTERM) || (signo) == SIGHUP)
+
+char *myname;
+long Hertz;
+
+/* just reports a crash */
+static void signal_handler(int signo){
+ sigset_t ss;
+
+ sigfillset(&ss);
+ sigprocmask(SIG_BLOCK, &ss, NULL);
+ if(signo==SIGPIPE) _exit(0); /* "ps | head" will cause this */
+ /* fprintf() is not reentrant, but we _exit() anyway */
+ if (!SIG_IS_TERM_OR_HUP(signo)) {
+ fprintf(stderr,
+ _("Signal %d (%s) caught by %s (%s).\n"),
+ signo,
+ signal_number_to_name(signo),
+ myname,
+ PACKAGE_VERSION
+ );
+ }
+ switch (signo) {
+ case SIGHUP:
+ case SIGUSR1:
+ case SIGUSR2:
+ exit(EXIT_FAILURE);
+ default:
+ if (!SIG_IS_TERM_OR_HUP(signo))
+ error_at_line(0, 0, __FILE__, __LINE__, "%s", _("please report this bug"));
+ signal(signo, SIG_DFL); /* allow core file creation */
+ sigemptyset(&ss);
+ sigaddset(&ss, signo);
+ sigprocmask(SIG_UNBLOCK, &ss, NULL);
+ kill(getpid(), signo);
+ _exit(EXIT_FAILURE);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+#undef DEBUG
+#ifdef DEBUG
+void init_stack_trace(char *prog_name);
+
+#include <ctype.h>
+
+void hex_dump(void *vp){
+ char *charlist;
+ int i = 0;
+ int line = 45;
+ char *cp = (char *)vp;
+
+ while(line--){
+ printf("%8lx ", (unsigned long)cp);
+ charlist = cp;
+ cp += 16;
+ for(i=0; i<16; i++){
+ if((charlist[i]>31) && (charlist[i]<127)){
+ printf("%c", charlist[i]);
+ }else{
+ printf(".");
+ }
+ }
+ printf(" ");
+ for(i=0; i<16; i++) printf(" %2x",(unsigned int)((unsigned char)(charlist[i])));
+ printf("\n");
+ i=0;
+ }
+}
+
+static void show_tgid(char *s, int n, sel_union *data){
+ printf("%s ", s);
+ while(--n){
+ printf("%d,", data[n].tgid);
+ }
+ printf("%d\n", data[0].tgid);
+}
+
+static void show_uid(char *s, int n, sel_union *data){
+ struct passwd *pw_data;
+ printf("%s ", s);
+ while(--n){
+ pw_data = getpwuid(data[n].uid);
+ if(pw_data) printf("%s,", pw_data->pw_name);
+ else printf("%d,", data[n].uid);
+ }
+ pw_data = getpwuid(data[n].uid);
+ if(pw_data) printf("%s\n", pw_data->pw_name);
+ else printf("%d\n", data[n].uid);
+}
+
+static void show_gid(char *s, int n, sel_union *data){
+ struct group *gr_data;
+ printf("%s ", s);
+ while(--n){
+ gr_data = getgrgid(data[n].gid);
+ if(gr_data) printf("%s,", gr_data->gr_name);
+ else printf("%d,", data[n].gid);
+ }
+ gr_data = getgrgid(data[n].gid);
+ if(gr_data) printf("%s\n", gr_data->gr_name);
+ else printf("%d\n", data[n].gid);
+}
+
+static void show_tty(char *s, int n, sel_union *data){
+ printf("%s ", s);
+ while(--n){
+ printf("%d:%d,", (int)major(data[n].tty), (int)minor(data[n].tty));
+ }
+ printf("%d:%d\n", (int)major(data[n].tty), (int)minor(data[n].tty));
+}
+
+static void show_cmd(char *s, int n, sel_union *data){
+ printf("%s ", s);
+ while(--n){
+ printf("%.8s,", data[n].cmd);
+ }
+ printf("%.8s\n", data[0].cmd);
+}
+
+static void arg_show(void){
+ selection_node *walk = selection_list;
+ while(walk){
+ switch(walk->typecode){
+ case SEL_RUID: show_uid("RUID", walk->n, walk->u); break;
+ case SEL_EUID: show_uid("EUID", walk->n, walk->u); break;
+ case SEL_SUID: show_uid("SUID", walk->n, walk->u); break;
+ case SEL_FUID: show_uid("FUID", walk->n, walk->u); break;
+ case SEL_RGID: show_gid("RGID", walk->n, walk->u); break;
+ case SEL_EGID: show_gid("EGID", walk->n, walk->u); break;
+ case SEL_SGID: show_gid("SGID", walk->n, walk->u); break;
+ case SEL_FGID: show_gid("FGID", walk->n, walk->u); break;
+ case SEL_PGRP: show_pid("PGRP", walk->n, walk->u); break;
+ case SEL_PID : show_pid("PID ", walk->n, walk->u); break;
+ case SEL_PID_QUICK : show_pid("PID_QUICK ", walk->n, walk->u); break;
+ case SEL_PPID: show_pid("PPID", walk->n, walk->u); break;
+ case SEL_TTY : show_tty("TTY ", walk->n, walk->u); break;
+ case SEL_SESS: show_pid("SESS", walk->n, walk->u); break;
+ case SEL_COMM: show_cmd("COMM", walk->n, walk->u); break;
+ default: printf("Garbage typecode value!\n");
+ }
+ walk = walk->next;
+ }
+}
+
+#endif
+//////////////////////////////////////////////////////////////////////////
+
+
+/***** check the header */
+/* Unix98: must not print empty header */
+static void check_headers(void){
+ format_node *walk = format_list;
+ int head_normal = 0;
+ if(header_type==HEAD_MULTI){
+ header_gap = screen_rows-1; /* true BSD */
+ return;
+ }
+ if(header_type==HEAD_NONE){
+ lines_to_next_header = -1; /* old Linux */
+ return;
+ }
+ while(walk){
+ if(!*(walk->name)){
+ walk = walk->next;
+ continue;
+ }
+ if(walk->pr){
+ head_normal++;
+ walk = walk->next;
+ continue;
+ }
+ walk = walk->next;
+ }
+ if(!head_normal) lines_to_next_header = -1; /* how UNIX does --noheader */
+}
+
+static format_node *proc_format_list;
+static format_node *task_format_list;
+
+
+/***** munge lists and determine final needs */
+static void lists_and_needs(void){
+ check_headers();
+
+ // only care about the difference when showing both
+ if(thread_flags & TF_show_both){
+ format_node pfn, tfn; // junk, to handle special case at begin of list
+ format_node *walk = format_list;
+ format_node *p_end = &pfn;
+ format_node *t_end = &tfn;
+ while(walk){
+ format_node *new = xmalloc(sizeof(format_node));
+ memcpy(new,walk,sizeof(format_node));
+ p_end->next = walk;
+ t_end->next = new;
+ p_end = walk;
+ t_end = new;
+ switch(walk->flags & CF_PRINT_MASK){
+ case CF_PRINT_THREAD_ONLY:
+ p_end->pr = pr_nop;
+ break;
+ case CF_PRINT_PROCESS_ONLY:
+ t_end->pr = pr_nop;
+ break;
+ default:
+ catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
+ // FALL THROUGH
+ case CF_PRINT_AS_NEEDED:
+ case CF_PRINT_EVERY_TIME:
+ break;
+ }
+ walk = walk->next;
+ }
+ t_end->next = NULL;
+ p_end->next = NULL;
+ proc_format_list = pfn.next;
+ task_format_list = tfn.next;
+ }else{
+ proc_format_list = format_list;
+ task_format_list = format_list;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+/***** fill in %CPU; not in libproc because of include_dead_children */
+/* Note: for sorting, not display, so 0..0x7fffffff would be OK */
+static void value_this_proc_pcpu(proc_t *buf){
+ unsigned long long used_jiffies;
+ unsigned long pcpu = 0;
+ unsigned long long seconds;
+
+ if(want_this_proc(buf)) {
+
+ if(include_dead_children) used_jiffies = rSv(TICS_ALL_C, ull_int, buf);
+ else used_jiffies = rSv(TICS_ALL, ull_int, buf);
+
+ seconds = rSv(TIME_ELAPSED, real, buf);
+ if(seconds) pcpu = (used_jiffies * 1000ULL / Hertz) / seconds;
+
+ // if xtra-procps-debug.h active, can't use PIDS_VAL as base due to assignment
+ buf->head[rel_extra].result.ul_int = pcpu;
+ }
+}
+
+/***** just display */
+static void simple_spew(void){
+ struct pids_fetch *pidread;
+ proc_t *buf;
+ int i;
+
+ // -q option (only single SEL_PID_QUICK typecode entry expected in the list, if present)
+ if (selection_list && selection_list->typecode == SEL_PID_QUICK) {
+ unsigned *pidlist = xcalloc(selection_list->n, sizeof(unsigned));
+ enum pids_select_type which;
+ for (i = 0; i < selection_list->n; i++)
+ pidlist[i] = selection_list->u[selection_list->n-i-1].pid;
+ which = (thread_flags & (TF_loose_tasks|TF_show_task))
+ ? PIDS_SELECT_PID_THREADS : PIDS_SELECT_PID;
+ pidread = procps_pids_select(Pids_info, pidlist, selection_list->n, which);
+ free(pidlist);
+ } else {
+ enum pids_fetch_type which;
+ which = (thread_flags & (TF_loose_tasks|TF_show_task))
+ ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY;
+ pidread = procps_pids_reap(Pids_info, which);
+ }
+ if (!pidread) {
+ fprintf(stderr, _("fatal library error, reap\n"));
+ exit(EXIT_FAILURE);
+ }
+
+ switch(thread_flags & (TF_show_proc|TF_loose_tasks|TF_show_task)){
+ case TF_show_proc: // normal non-thread output
+ for (i = 0; i < pidread->counts->total; i++) {
+ buf = pidread->stacks[i];
+ if (want_this_proc(buf))
+ show_one_proc(buf, proc_format_list);
+ }
+ break;
+ case TF_show_task: // -L and -T options
+ case TF_show_proc|TF_loose_tasks: // H option
+ for (i = 0; i < pidread->counts->total; i++) {
+ buf = pidread->stacks[i];
+ if (want_this_proc(buf))
+ show_one_proc(buf, task_format_list);
+ }
+ break;
+ case TF_show_proc|TF_show_task: // m and -m options
+ procps_pids_sort(Pids_info, pidread->stacks
+ , pidread->counts->total, PIDS_TICS_BEGAN, PIDS_SORT_ASCEND);
+ procps_pids_sort(Pids_info, pidread->stacks
+ , pidread->counts->total, PIDS_ID_TGID, PIDS_SORT_ASCEND);
+ for (i = 0; i < pidread->counts->total; i++) {
+ buf = pidread->stacks[i];
+next_proc:
+ if (want_this_proc(buf)) {
+ int self = rSv(ID_PID, s_int, buf);
+ show_one_proc(buf, proc_format_list);
+ for (; i < pidread->counts->total; i++) {
+ buf = pidread->stacks[i];
+ if (rSv(ID_TGID, s_int, buf) != self) goto next_proc;
+ show_one_proc(buf, task_format_list);
+ }
+ }
+ }
+ break;
+ }
+}
+
+
+/***** forest output requires sorting by ppid; add start_time by default */
+static void prep_forest_sort(void){
+ sort_node *tmp_list = sort_list;
+ const format_struct *incoming;
+
+ if(!sort_list) { /* assume start time order */
+ incoming = search_format_array("ppid");
+ if(!incoming) { fprintf(stderr, _("could not find ppid\n")); exit(1); }
+ tmp_list = xmalloc(sizeof(sort_node));
+ tmp_list->reverse = PIDS_SORT_ASCEND;
+ tmp_list->typecode = '?'; /* what was this for? */
+ tmp_list->sr = incoming->sr;
+ tmp_list->next = sort_list;
+ sort_list = tmp_list;
+ }
+ /* this is required for the forest option */
+ incoming = search_format_array("start_time");
+ if(!incoming) { fprintf(stderr, _("could not find start_time\n")); exit(1); }
+ tmp_list = xmalloc(sizeof(sort_node));
+ tmp_list->reverse = PIDS_SORT_ASCEND;
+ tmp_list->typecode = '?'; /* what was this for? */
+ tmp_list->sr = incoming->sr;
+ tmp_list->next = sort_list;
+ sort_list = tmp_list;
+}
+
+/* we rely on the POSIX requirement for zeroed memory */
+static proc_t **processes;
+
+/***** show pre-sorted array of process pointers */
+static void show_proc_array(int n){
+ proc_t **p = processes;
+ while(n--){
+ show_one_proc(*p, proc_format_list);
+ p++;
+ }
+}
+
+/***** show tree */
+/* this needs some optimization work */
+#define ADOPTED(x) 1
+
+#define IS_LEVEL_SAFE(level) \
+ ((level) >= 0 && (size_t)(level) < sizeof(forest_prefix))
+
+static void show_tree(const int self, const int n, const int level, const int have_sibling){
+ int i = 0;
+
+ if(!IS_LEVEL_SAFE(level))
+ catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
+
+ if(level){
+ /* add prefix of "+" or "L" */
+ if(have_sibling) forest_prefix[level-1] = '+';
+ else forest_prefix[level-1] = 'L';
+ }
+ forest_prefix[level] = '\0';
+ show_one_proc(processes[self],format_list); /* first show self */
+ for(;;){ /* look for children */
+ if(i >= n) return; /* no children */
+ if(rSv(ID_PPID, s_int, processes[i]) == rSv(ID_PID, s_int, processes[self])) break;
+ i++;
+ }
+ if(level){
+ /* change our prefix to "|" or " " for the children */
+ if(have_sibling) forest_prefix[level-1] = '|';
+ else forest_prefix[level-1] = ' ';
+ }
+ forest_prefix[level] = '\0';
+ for(;;){
+ int self_pid;
+ int more_children = 1;
+ if(i >= n) break; /* over the edge */
+ self_pid=rSv(ID_PID, s_int, processes[self]);
+ if(i+1 >= n)
+ more_children = 0;
+ else
+ if(rSv(ID_PPID, s_int, processes[i+1]) != self_pid) more_children = 0;
+ if(self_pid==1 && ADOPTED(processes[i]) && forest_type!='u')
+ show_tree(i++, n, level, more_children);
+ else
+ show_tree(i++, n, IS_LEVEL_SAFE(level+1) ? level+1 : level, more_children);
+ if(!more_children) break;
+ }
+ /* chop prefix that children added -- do we need this? */
+ /* chop prefix that children added */
+ forest_prefix[level] = '\0';
+}
+
+#undef IS_LEVEL_SAFE
+
+/***** show forest */
+static void show_forest(const int n){
+ int i = n;
+ int j;
+ while(i--){ /* cover whole array looking for trees */
+ j = n;
+ while(j--){ /* search for parent: if none, i is a tree! */
+ if(rSv(ID_PID, s_int, processes[j]) == rSv(ID_PPID, s_int, processes[i])) goto not_root;
+ }
+ show_tree(i,n,0,0);
+not_root:
+ ;
+ }
+ /* don't free the array because it takes time and ps will exit anyway */
+}
+
+#if 0
+static int want_this_proc_nop(proc_t *dummy){
+ (void)dummy;
+ return 1;
+}
+#endif
+
+/***** sorted or forest */
+static void fancy_spew(void){
+ struct pids_fetch *pidread;
+ enum pids_fetch_type which;
+ proc_t *buf;
+ int i, n = 0;
+
+ which = (thread_flags & TF_loose_tasks)
+ ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY;
+
+ pidread = procps_pids_reap(Pids_info, which);
+ if (!pidread || !pidread->counts->total) {
+ fprintf(stderr, _("fatal library error, reap\n"));
+ exit(EXIT_FAILURE);
+ }
+ processes = xcalloc(pidread->counts->total, sizeof(void*));
+ for (i = 0; i < pidread->counts->total; i++) {
+ buf = pidread->stacks[i];
+ value_this_proc_pcpu(buf);
+ if (want_this_proc(buf))
+ processes[n++] = buf;
+ }
+ if (n) {
+ if(forest_type) prep_forest_sort();
+ while(sort_list) {
+ procps_pids_sort(Pids_info, processes, n, sort_list->sr, sort_list->reverse);
+ sort_list = sort_list->next;
+ }
+ if(forest_type) show_forest(n);
+ else show_proc_array(n);
+ }
+ free(processes);
+}
+
+static void arg_check_conflicts(void)
+{
+ int selection_list_len;
+ int has_quick_pid;
+ selection_node *walk = selection_list;
+ has_quick_pid = 0;
+ selection_list_len = 0;
+
+ while (walk) {
+ if (walk->typecode == SEL_PID_QUICK) has_quick_pid++;
+ walk = walk->next;
+ selection_list_len++;
+ }
+
+ /* -q doesn't allow multiple occurrences */
+ if (has_quick_pid > 1) {
+ fprintf(stderr, "q/-q/--quick-pid can only be used once.\n");
+ exit(1);
+ }
+
+ /* -q doesn't allow combinations with other selection switches */
+ if (has_quick_pid && selection_list_len > has_quick_pid) {
+ fprintf(stderr, "q/-q/--quick-pid cannot be combined with other selection options.\n");
+ exit(1);
+ }
+
+ /* -q cannot be used with forest type listings */
+ if (has_quick_pid && forest_type) {
+ fprintf(stderr, "q/-q/--quick-pid cannot be used together with forest type listings.\n");
+ exit(1);
+ }
+
+ /* -q cannot be used with sort */
+ if (has_quick_pid && sort_list) {
+ fprintf(stderr, "q/-q,--quick-pid cannot be used together with sort options.\n");
+ exit(1);
+ }
+
+ /* -q cannot be used with -N */
+ if (has_quick_pid && negate_selection) {
+ fprintf(stderr, "q/-q/--quick-pid cannot be used together with negation switches.\n");
+ exit(1);
+ }
+
+}
+
+static void finalize_stacks (void)
+{
+ format_node *f_node;
+ sort_node *s_node;
+
+#if (PIDSITEMS < 60)
+ # error PIDSITEMS (common.h) should be at least 60!
+#endif
+
+ /* first, ensure minimum result structures for items
+ which may or may not actually be displayable ... */
+ Pids_index = 0;
+
+ // needed by for selections
+ chkREL(CMD)
+ chkREL(ID_EGID)
+ chkREL(ID_EUID)
+ chkREL(ID_FGID)
+ chkREL(ID_FUID)
+ chkREL(ID_PID)
+ chkREL(ID_PPID)
+ chkREL(ID_RGID)
+ chkREL(ID_RUID)
+ chkREL(ID_SESSION)
+ chkREL(ID_SGID)
+ chkREL(ID_SUID)
+ chkREL(ID_TGID)
+ chkREL(STATE)
+ chkREL(TTY)
+ // needed to creata an enhanced 'stat/state'
+ chkREL(ID_PGRP)
+ chkREL(ID_TPGID)
+ chkREL(NICE)
+ chkREL(NLWP)
+ chkREL(RSS)
+ chkREL(VM_RSS_LOCKED)
+ // needed with 's' switch, previously assured
+ chkREL(SIGBLOCKED)
+ chkREL(SIGCATCH)
+ chkREL(SIGIGNORE)
+ chkREL(SIGNALS)
+ chkREL(SIGPENDING)
+ // needed with loss of defunct 'cook_time' macros
+ chkREL(TICS_ALL)
+ chkREL(TICS_ALL_C)
+ chkREL(TIME_ALL)
+ chkREL(TIME_ELAPSED)
+ chkREL(TICS_BEGAN)
+ // special items with 'extra' used as former pcpu
+ chkREL(extra)
+ chkREL(noop)
+
+ // now accommodate any results not yet satisfied
+ f_node = format_list;
+ while (f_node) {
+ if (*f_node->pr) (*f_node->pr)(NULL, NULL);
+ f_node = f_node->next;
+ }
+ s_node = sort_list;
+ while (s_node) {
+ if (s_node->xe) (*s_node->xe)(NULL, NULL);
+ s_node = s_node->next;
+ }
+
+ procps_pids_reset(Pids_info, Pids_items, Pids_index);
+}
+
+/***** no comment */
+int main(int argc, char *argv[]){
+ atexit(close_stdout);
+ myname = strrchr(*argv, '/');
+ if (myname) ++myname; else myname = *argv;
+ Hertz = procps_hertz_get();
+
+ setlocale (LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ setenv("TZ", ":/etc/localtime", 0);
+
+#ifdef DEBUG
+ init_stack_trace(argv[0]);
+#else
+ do {
+ struct sigaction sa;
+ int i = 32;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = signal_handler;
+ sigfillset(&sa.sa_mask);
+ while(i--) switch(i){
+ default:
+ sigaction(i,&sa,NULL);
+ case 0:
+ case SIGCONT:
+ case SIGINT: /* ^C */
+ case SIGTSTP: /* ^Z */
+ case SIGTTOU: /* see stty(1) man page */
+ case SIGQUIT: /* ^\ */
+ case SIGPROF: /* profiling */
+ case SIGKILL: /* can not catch */
+ case SIGSTOP: /* can not catch */
+ case SIGWINCH: /* don't care if window size changes */
+ case SIGURG: /* Urgent condition on socket (4.2BSD) */
+ ;
+ }
+ } while (0);
+#endif
+
+ reset_global(); /* must be before parser */
+ arg_parse(argc,argv);
+
+ /* check for invalid combination of arguments */
+ arg_check_conflicts();
+
+/* arg_show(); */
+ trace("screen is %ux%u\n",screen_cols,screen_rows);
+/* printf("sizeof(proc_t) is %d.\n", sizeof(proc_t)); */
+ trace("======= ps output follows =======\n");
+
+ init_output(); /* must be between parser and output */
+
+ finalize_stacks();
+ lists_and_needs();
+
+ if(forest_type || sort_list) fancy_spew(); /* sort or forest */
+ else simple_spew(); /* no sort, no forest */
+ show_one_proc((proc_t *)-1,format_list); /* no output yet? */
+
+ procps_pids_unref(&Pids_info);
+ return 0;
+}
diff --git a/src/ps/global.c b/src/ps/global.c
new file mode 100644
index 0000000..cc4fa24
--- /dev/null
+++ b/src/ps/global.c
@@ -0,0 +1,651 @@
+/*
+ * global.c - generic ps symbols and functions
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net
+ * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 1998-2002 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdbool.h>
+
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+#include "c.h"
+#include "xalloc.h"
+
+#include "common.h"
+
+#ifndef __GNU_LIBRARY__
+#define __GNU_LIBRARY__ -1
+#endif
+#ifndef __GLIBC__
+#define __GLIBC__ -1
+#endif
+#ifndef __GLIBC_MINOR__
+#define __GLIBC_MINOR__ -1
+#endif
+
+// --- <pids> interface begin ||||||||||||||||||||||||||||||||||||||||||||
+// -----------------------------------------------------------------------
+struct pids_info *Pids_info = NULL; // our required <pids> context
+enum pids_item *Pids_items; // allocated as PIDSITEMS
+int Pids_index; // actual number of active enums
+
+// most of these could be defined as static in the output.c module
+// (but for future flexibility, the easiest route has been chosen)
+makREL(ADDR_CODE_END)
+makREL(ADDR_CODE_START)
+makREL(ADDR_CURR_EIP)
+makREL(ADDR_CURR_ESP)
+makREL(ADDR_STACK_START)
+makREL(AUTOGRP_ID)
+makREL(AUTOGRP_NICE)
+makREL(CGNAME)
+makREL(CGROUP)
+makREL(CMD)
+makREL(CMDLINE)
+makREL(ENVIRON)
+makREL(EXE)
+makREL(FLAGS)
+makREL(FLT_MAJ)
+makREL(FLT_MAJ_C)
+makREL(FLT_MIN)
+makREL(FLT_MIN_C)
+makREL(ID_EGID)
+makREL(ID_EGROUP)
+makREL(ID_EUID)
+makREL(ID_EUSER)
+makREL(ID_FGID)
+makREL(ID_FGROUP)
+makREL(ID_FUID)
+makREL(ID_FUSER)
+makREL(ID_LOGIN)
+makREL(ID_PGRP)
+makREL(ID_PID)
+makREL(ID_PPID)
+makREL(ID_RGID)
+makREL(ID_RGROUP)
+makREL(ID_RUID)
+makREL(ID_RUSER)
+makREL(ID_SESSION)
+makREL(ID_SGID)
+makREL(ID_SGROUP)
+makREL(ID_SUID)
+makREL(ID_SUSER)
+makREL(ID_TGID)
+makREL(ID_TPGID)
+makREL(IO_READ_BYTES)
+makREL(IO_READ_CHARS)
+makREL(IO_READ_OPS)
+makREL(IO_WRITE_BYTES)
+makREL(IO_WRITE_CBYTES)
+makREL(IO_WRITE_CHARS)
+makREL(IO_WRITE_OPS)
+makREL(LXCNAME)
+makREL(NICE)
+makREL(NLWP)
+makREL(NS_CGROUP)
+makREL(NS_IPC)
+makREL(NS_MNT)
+makREL(NS_NET)
+makREL(NS_PID)
+makREL(NS_TIME)
+makREL(NS_USER)
+makREL(NS_UTS)
+makREL(OOM_ADJ)
+makREL(OOM_SCORE)
+makREL(PRIORITY)
+makREL(PRIORITY_RT)
+makREL(PROCESSOR)
+makREL(PROCESSOR_NODE)
+makREL(RSS)
+makREL(RSS_RLIM)
+makREL(SCHED_CLASS)
+makREL(SD_MACH)
+makREL(SD_OUID)
+makREL(SD_SEAT)
+makREL(SD_SESS)
+makREL(SD_SLICE)
+makREL(SD_UNIT)
+makREL(SD_UUNIT)
+makREL(SIGBLOCKED)
+makREL(SIGCATCH)
+makREL(SIGIGNORE)
+makREL(SIGNALS)
+makREL(SIGPENDING)
+makREL(SMAP_PRV_TOTAL)
+makREL(SMAP_PSS)
+makREL(STATE)
+makREL(SUPGIDS)
+makREL(SUPGROUPS)
+makREL(TICS_ALL)
+makREL(TICS_ALL_C)
+makREL(TIME_ALL)
+makREL(TIME_ELAPSED)
+makREL(TICS_BEGAN)
+makREL(TTY)
+makREL(TTY_NAME)
+makREL(TTY_NUMBER)
+makREL(UTILIZATION)
+makREL(UTILIZATION_C)
+makREL(VM_DATA)
+makREL(VM_RSS_LOCKED)
+makREL(VM_RSS)
+makREL(VM_SIZE)
+makREL(VM_STACK)
+makREL(VSIZE_BYTES)
+makREL(WCHAN_NAME)
+makREL(extra)
+makREL(noop)
+// -----------------------------------------------------------------------
+// --- <pids> interface end ||||||||||||||||||||||||||||||||||||||||||||||
+
+
+static const char * saved_personality_text = "You found a bug!";
+
+int all_processes = -1;
+const char *bsd_j_format = (const char *)0xdeadbeef;
+const char *bsd_l_format = (const char *)0xdeadbeef;
+const char *bsd_s_format = (const char *)0xdeadbeef;
+const char *bsd_u_format = (const char *)0xdeadbeef;
+const char *bsd_v_format = (const char *)0xdeadbeef;
+int bsd_c_option = -1;
+int bsd_e_option = -1;
+unsigned cached_euid = 0xffffffff;
+int cached_tty = -1;
+char forest_prefix[4 * 32*1024 + 100]; // FIXME
+int forest_type = -1;
+unsigned format_flags = 0xffffffff; /* -l -f l u s -j... */
+format_node *format_list = (format_node *)0xdeadbeef; /* digested formatting options */
+unsigned format_modifiers = 0xffffffff; /* -c -j -y -P -L... */
+int header_gap = -1;
+int header_type = -1;
+int include_dead_children = -1;
+int lines_to_next_header = -1;
+char *lstart_format = NULL;
+int negate_selection = -1;
+int running_only = -1;
+int page_size = -1; // "int" for math reasons?
+unsigned personality = 0xffffffff;
+int prefer_bsd_defaults = -1;
+int screen_cols = -1;
+int screen_rows = -1;
+selection_node *selection_list = (selection_node *)0xdeadbeef;
+unsigned simple_select = 0xffffffff;
+sort_node *sort_list = (sort_node *)0xdeadbeef; /* ready-to-use sort list */
+const char *sysv_f_format = (const char *)0xdeadbeef;
+const char *sysv_fl_format = (const char *)0xdeadbeef;
+const char *sysv_j_format = (const char *)0xdeadbeef;
+const char *sysv_l_format = (const char *)0xdeadbeef;
+unsigned thread_flags = 0xffffffff;
+int unix_f_option = -1;
+int user_is_number = -1;
+int wchan_is_number = -1;
+const char *the_word_help;
+bool signal_names = FALSE;
+
+static void reset_selection_list(void){
+ selection_node *old;
+ selection_node *walk = selection_list;
+ if(selection_list == (selection_node *)0xdeadbeef){
+ selection_list = NULL;
+ return;
+ }
+ while(walk){
+ old = walk;
+ walk = old->next;
+ free(old->u);
+ free(old);
+ }
+ selection_list = NULL;
+}
+
+// The rules:
+// 1. Defaults are implementation-specific. (ioctl,termcap,guess)
+// 2. COLUMNS and LINES override the defaults. (standards compliance)
+// 3. Command line options override everything else.
+// 4. Actual output may be more if the above is too narrow.
+//
+// SysV tends to spew semi-wide output in all cases. The args
+// will be limited to 64 or 80 characters, without regard to
+// screen size. So lines of 120 to 160 chars are normal.
+// Tough luck if you want more or less than that! HP-UX has a
+// new "-x" option for 1024-char args in place of comm that
+// we'll implement at some point.
+//
+// BSD tends to make a good effort, then fall back to 80 cols.
+// Use "ww" to get infinity. This is nicer for "ps | less"
+// and "watch ps". It can run faster too.
+static void set_screen_size(void){
+ struct winsize ws;
+ char *columns; /* Unix98 environment variable */
+ char *lines; /* Unix98 environment variable */
+
+ do{
+ int fd;
+ if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0) break;
+ if(ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0) break;
+ if(ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0) break;
+ fd = open("/dev/tty", O_NOCTTY|O_NONBLOCK|O_RDONLY);
+ if(fd != -1){
+ int ret = ioctl(fd, TIOCGWINSZ, &ws);
+ close(fd);
+ if(ret != -1 && ws.ws_col>0 && ws.ws_row>0) break;
+ }
+ // TODO: ought to do tgetnum("co") and tgetnum("li") here
+ ws.ws_col = 80;
+ ws.ws_row = 24;
+ }while(0);
+ screen_cols = ws.ws_col; // hmmm, NetBSD subtracts 1
+ screen_rows = ws.ws_row;
+
+ // TODO: delete this line
+ if(!isatty(STDOUT_FILENO)) screen_cols = OUTBUF_SIZE;
+
+ columns = getenv("COLUMNS");
+ if(columns && *columns){
+ long t;
+ char *endptr;
+ t = strtol(columns, &endptr, 0);
+ if(!*endptr && (t>0) && (t<(long)OUTBUF_SIZE)) screen_cols = (int)t;
+ }
+
+ lines = getenv("LINES");
+ if(lines && *lines){
+ long t;
+ char *endptr;
+ t = strtol(lines, &endptr, 0);
+ if(!*endptr && (t>0) && (t<(long)OUTBUF_SIZE)) screen_rows = (int)t;
+ }
+
+ if((screen_cols<9) || (screen_rows<2))
+ fprintf(stderr,_("your %dx%d screen size is bogus. expect trouble\n"),
+ screen_cols, screen_rows
+ );
+}
+
+/**************** personality control **************/
+
+typedef struct personality_table_struct {
+ const char *name; /* personality name */
+ const void *jump; /* See gcc extension info. :-) */
+} personality_table_struct;
+
+static int compare_personality_table_structs(const void *a, const void *b){
+ return strcasecmp(((const personality_table_struct*)a)->name,((const personality_table_struct*)b)->name);
+}
+
+static const char *set_personality(void){
+ const char *s;
+ size_t sl;
+ char buf[16];
+ personality_table_struct findme = { buf, NULL};
+ personality_table_struct *found;
+ static const personality_table_struct personality_table[] = {
+ {"390", &&case_390},
+ {"aix", &&case_aix},
+ {"bsd", &&case_bsd},
+ {"compaq", &&case_compaq},
+ {"debian", &&case_debian},
+ {"default", &&case_default},
+ {"digital", &&case_digital},
+ {"gnu", &&case_gnu},
+ {"hp", &&case_hp},
+ {"hpux", &&case_hpux},
+ {"irix", &&case_irix},
+ {"linux", &&case_linux},
+ {"old", &&case_old},
+ {"os390", &&case_os390},
+ {"posix", &&case_posix},
+ {"s390", &&case_s390},
+ {"sco", &&case_sco},
+ {"sgi", &&case_sgi},
+ {"solaris2", &&case_solaris2},
+ {"sunos4", &&case_sunos4},
+ {"svr4", &&case_svr4},
+ {"sysv", &&case_sysv},
+ {"tru64", &&case_tru64},
+ {"unix", &&case_unix},
+ {"unix95", &&case_unix95},
+ {"unix98", &&case_unix98},
+ {"unknown", &&case_unknown}
+ };
+ const int personality_table_count = sizeof(personality_table)/sizeof(personality_table_struct);
+
+ personality = 0;
+ prefer_bsd_defaults = 0;
+
+ bsd_j_format = "OL_j";
+ bsd_l_format = "OL_l";
+ bsd_s_format = "OL_s";
+ bsd_u_format = "OL_u";
+ bsd_v_format = "OL_v";
+
+ /* When these are NULL, the code does SysV output modifier logic */
+ sysv_f_format = NULL;
+ sysv_fl_format = NULL;
+ sysv_j_format = NULL;
+ sysv_l_format = NULL;
+
+ s = getenv("PS_PERSONALITY");
+ if(!s || !*s) s = getenv("CMD_ENV");
+ if(!s || !*s) s="unknown"; /* "Do The Right Thing[tm]" */
+ if(getenv("I_WANT_A_BROKEN_PS")) s="old";
+ sl = strlen(s);
+ if(sl > 15) return _("environment specified an unknown personality");
+ strncpy(buf, s, sl);
+ buf[sl] = '\0';
+ if ((saved_personality_text = strdup(buf))==NULL) {
+ fprintf(stderr, _("cannot strdup() personality text\n"));
+ exit(EXIT_FAILURE);
+ }
+
+ found = bsearch(&findme, personality_table, personality_table_count,
+ sizeof(personality_table_struct), compare_personality_table_structs
+ );
+
+ if(!found) return _("environment specified an unknown personality");
+
+ goto *(found->jump); /* See gcc extension info. :-) */
+
+ case_bsd:
+ personality = PER_FORCE_BSD | PER_BSD_h | PER_BSD_m;
+ prefer_bsd_defaults = 1;
+ bsd_j_format = "FB_j";
+ bsd_l_format = "FB_l";
+ /* bsd_s_format not used */
+ bsd_u_format = "FB_u";
+ bsd_v_format = "FB_v";
+ return NULL;
+
+ case_old:
+ personality = PER_FORCE_BSD | PER_OLD_m;
+ prefer_bsd_defaults = 1;
+ return NULL;
+
+ case_debian: /* Toss this? They don't seem to care much. */
+ case_gnu:
+ personality = PER_GOOD_o | PER_OLD_m;
+ prefer_bsd_defaults = 1;
+ sysv_f_format = "RD_f";
+ /* sysv_fl_format = "RD_fl"; */ /* old Debian ps can't do this! */
+ sysv_j_format = "RD_j";
+ sysv_l_format = "RD_l";
+ return NULL;
+
+ case_linux:
+ personality = PER_GOOD_o | PER_ZAP_ADDR | PER_SANE_USER;
+ return NULL;
+
+ case_default: /* use defaults for ps, ignoring other environment variables */
+ case_unknown: /* defaults, but also check inferior environment variables */
+ return NULL;
+
+ case_aix:
+ bsd_j_format = "FB_j";
+ bsd_l_format = "FB_l";
+ /* bsd_s_format not used */
+ bsd_u_format = "FB_u";
+ bsd_v_format = "FB_v";
+ return NULL;
+
+ case_tru64:
+ case_compaq:
+ case_digital:
+ // no PER_NO_DEFAULT_g even though man page claims it
+ // Reality: the g is a NOP
+ personality = PER_GOOD_o | PER_BSD_h;
+ prefer_bsd_defaults = 1;
+ sysv_f_format = "F5FMT";
+ sysv_fl_format = "FL5FMT";
+ sysv_j_format = "JFMT";
+ sysv_l_format = "L5FMT";
+ bsd_j_format = "JFMT";
+ bsd_l_format = "LFMT";
+ bsd_s_format = "SFMT";
+ bsd_u_format = "UFMT";
+ bsd_v_format = "VFMT";
+ return NULL;
+
+ case_sunos4:
+ personality = PER_NO_DEFAULT_g;
+ prefer_bsd_defaults = 1;
+ bsd_j_format = "FB_j";
+ bsd_l_format = "FB_l";
+ /* bsd_s_format not used */
+ bsd_u_format = "FB_u";
+ bsd_v_format = "FB_v";
+ return NULL;
+
+ case_irix:
+ case_sgi:
+ s = getenv("_XPG");
+ if(s && s[0]>'0' && s[0]<='9')
+ return NULL;
+ personality = PER_IRIX_l;
+ return NULL;
+
+ case_os390: /* IBM's OS/390 OpenEdition on the S/390 mainframe */
+ case_s390:
+ case_390:
+ sysv_j_format = "J390"; /* don't know what -jl and -jf do */
+ return NULL;
+
+ case_hp:
+ case_hpux:
+ personality = PER_HPUX_x;
+ return NULL;
+
+ case_svr4:
+ case_sysv:
+ case_sco:
+ personality = PER_SVR4_x;
+ return NULL;
+
+ case_posix:
+ case_solaris2:
+ case_unix95:
+ case_unix98:
+ case_unix:
+ return NULL;
+}
+
+
+/************ Call this to reinitialize everything ***************/
+void reset_global(void){
+ proc_t *p;
+ int i;
+
+ reset_selection_list();
+
+// --- <pids> interface --------------------------------------------------
+ if (!Pids_items)
+ Pids_items = xcalloc(PIDSITEMS, sizeof(enum pids_item));
+
+ for (i = 0; i < PIDSITEMS; i++)
+ Pids_items[i] = PIDS_noop;
+
+ if (!Pids_info) {
+ if (procps_pids_new(&Pids_info, Pids_items, i)) {
+ fprintf(stderr, _("fatal library error, context\n"));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ Pids_items[0] = PIDS_TTY;
+ procps_pids_reset(Pids_info, Pids_items, 1);
+ if (!(p = fatal_proc_unmounted(Pids_info, 1))) {
+ fprintf(stderr, _("fatal library error, lookup self\n"));
+ exit(EXIT_FAILURE);
+ }
+// --- <pids> interface --------------------------------------------------
+
+ set_screen_size();
+ set_personality();
+
+ all_processes = 0;
+ bsd_c_option = 0;
+ bsd_e_option = 0;
+ cached_euid = geteuid();
+ cached_tty = PIDS_VAL(0, s_int, p, Pids_info);
+/* forest_prefix must be all zero because of POSIX */
+ forest_type = 0;
+ format_flags = 0; /* -l -f l u s -j... */
+ format_list = NULL; /* digested formatting options */
+ format_modifiers = 0; /* -c -j -y -P -L... */
+ header_gap = -1; /* send lines_to_next_header to -infinity */
+ header_type = HEAD_SINGLE;
+ include_dead_children = 0;
+ lines_to_next_header = 1;
+ negate_selection = 0;
+ page_size = getpagesize();
+ running_only = 0;
+ selection_list = NULL;
+ simple_select = 0;
+ sort_list = NULL;
+ thread_flags = 0;
+ unix_f_option = 0;
+ user_is_number = 0;
+ wchan_is_number = 0;
+/* Translation Note:
+ . The following translatable word will be used to recognize the
+ . user's request for help text. In other words, the translation
+ . you provide will alter program behavior.
+ .
+ . It must be limited to 15 characters or less.
+ */
+ the_word_help = _("help");
+}
+
+static const char archdefs[] =
+#ifdef __alpha__
+" alpha"
+#endif
+#ifdef __arm__
+" arm"
+#endif
+#ifdef __hppa__
+" hppa"
+#endif
+#ifdef __i386__
+" i386"
+#endif
+#ifdef __ia64__
+" ia64"
+#endif
+#ifdef __mc68000__
+" mc68000"
+#endif
+#ifdef __mips64__
+" mips64"
+#endif
+#ifdef __mips__
+" mips"
+#endif
+#ifdef __powerpc__
+" powerpc"
+#endif
+#ifdef __sh3__
+" sh3"
+#endif
+#ifdef __sh__
+" sh"
+#endif
+#ifdef __sparc__
+" sparc"
+#endif
+#ifdef __sparc_v9__
+" sparc_v9"
+#endif
+#ifdef __x86_64__
+" x86_64"
+#endif
+"";
+
+/*********** spew variables ***********/
+void self_info(void){
+ fprintf(stderr,
+ "BSD j %s\n"
+ "BSD l %s\n"
+ "BSD s %s\n"
+ "BSD u %s\n"
+ "BSD v %s\n"
+ "SysV -f %s\n"
+ "SysV -fl %s\n"
+ "SysV -j %s\n"
+ "SysV -l %s\n"
+ "\n",
+ bsd_j_format ? bsd_j_format : "(none)",
+ bsd_l_format ? bsd_l_format : "(none)",
+ bsd_s_format ? bsd_s_format : "(none)",
+ bsd_u_format ? bsd_u_format : "(none)",
+ bsd_v_format ? bsd_v_format : "(none)",
+ sysv_f_format ? sysv_f_format : "(none)",
+ sysv_fl_format ? sysv_fl_format : "(none)",
+ sysv_j_format ? sysv_j_format : "(none)",
+ sysv_l_format ? sysv_l_format : "(none)"
+ );
+
+ fprintf(stderr, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION);
+ /* __libc_print_version(); */ /* how can we get the run-time version? */
+ fprintf(stderr, "Compiled with: glibc %d.%d, gcc %d.%d\n\n",
+ __GLIBC__, __GLIBC_MINOR__, __GNUC__, __GNUC_MINOR__
+ );
+
+ fprintf(stderr,
+ "header_gap=%d lines_to_next_header=%d\n"
+ "screen_cols=%d screen_rows=%d\n"
+ "\n",
+ header_gap, lines_to_next_header,
+ screen_cols, screen_rows
+ );
+
+ fprintf(stderr,
+ "personality=0x%08x (from \"%s\")\n"
+ "EUID=%d TTY=%d,%d page_size=%d\n",
+ personality, saved_personality_text,
+ cached_euid, (int)major(cached_tty), (int)minor(cached_tty),
+ (int)(page_size)
+ );
+
+ fprintf(stderr,
+ "sizeof(proc_t)=%d sizeof(long)=%d sizeof(long)=%d\n",
+ (int)sizeof(proc_t), (int)sizeof(long), (int)sizeof(long)
+ );
+
+ fprintf(stderr, "archdefs:%s\n", archdefs);
+}
+
+void __attribute__ ((__noreturn__))
+catastrophic_failure(const char *filename,
+ unsigned int linenum,
+ const char *message)
+{
+ error_at_line(0, 0, filename, linenum, "%s", message);
+ exit(EXIT_FAILURE);
+}
diff --git a/src/ps/help.c b/src/ps/help.c
new file mode 100644
index 0000000..61e6b7c
--- /dev/null
+++ b/src/ps/help.c
@@ -0,0 +1,223 @@
+/*
+ * help.c - ps help output
+ *
+ * Copyright © 2012-2023 Jim Warner <james.warner@comcast.net
+ * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2012-2014 Jaromir Capik <jcapik@redhat.com
+ * Copyright © 1998-2004 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+
+enum {
+ HELP_SMP, HELP_LST, HELP_OUT,
+ HELP_THD, HELP_MSC, HELP_ALL,
+ HELP_default
+};
+
+static struct {
+ const char *word;
+ const char *abrv;
+} help_tab[HELP_default];
+
+
+static int parse_help_opt (const char *opt) {
+/* Translation Notes for ps Help #1 ---------------------------------
+ . This next group of lines represents 6 pairs of words + abbreviations
+ . which are the basis of the 'ps' program help text.
+ .
+ . The words and abbreviations you provide will alter program behavior.
+ . They will also appear in the help usage summary associated with the
+ . "Notes for ps Help #2" below.
+ .
+ . In their English form, help text would look like this:
+ . Try 'ps --help <simple|list|output|threads|misc|all>'
+ . or 'ps --help <s|l|o|t|m|a>'
+ . for additional help text.
+ .
+ . When translating these 6 pairs you may choose any appropriate
+ . language equivalents and the only requirement is the abbreviated
+ . representations must be unique.
+ .
+ . By default, those abbreviations are single characters. However,
+ . they are not limited to only one character after translation.
+ . */
+
+/* Translation Hint, Pair #1 */
+ help_tab[HELP_SMP].word = _("simple"); help_tab[HELP_SMP].abrv = _("s");
+/* Translation Hint, Pair #2 */
+ help_tab[HELP_LST].word = _("list"); help_tab[HELP_LST].abrv = _("l");
+/* Translation Hint, Pair #3 */
+ help_tab[HELP_OUT].word = _("output"); help_tab[HELP_OUT].abrv = _("o");
+/* Translation Hint, Pair #4 */
+ help_tab[HELP_THD].word = _("threads"); help_tab[HELP_THD].abrv = _("t");
+/* Translation Hint, Pair #5 */
+ help_tab[HELP_MSC].word = _("misc"); help_tab[HELP_MSC].abrv = _("m");
+/* Translation Hint, Pair #6 */
+ help_tab[HELP_ALL].word = _("all"); help_tab[HELP_ALL].abrv = _("a");
+/*
+ * the above are doubled on each line so they carry the same .pot
+ * line # reference and thus appear more like true "pairs" even
+ * though xgettext will produce separate msgid/msgstr groups */
+
+ if(opt) {
+ int i;
+ for (i = HELP_SMP; i < HELP_default; i++)
+ if (!strcmp(opt, help_tab[i].word) || !strcmp(opt, help_tab[i].abrv))
+ return i;
+ }
+ return HELP_default;
+}
+
+
+void do_help (const char *opt, int rc);
+void do_help (const char *opt, int rc) {
+ FILE *out = (rc == EXIT_SUCCESS) ? stdout : stderr;
+ int section = parse_help_opt(opt);
+
+ fprintf(out, _("\n"
+ "Usage:\n"
+ " %s [options]\n"), myname);
+
+ if (section == HELP_SMP || section == HELP_ALL) {
+ fputs(_("\nBasic options:\n"), out);
+ fputs(_(" -A, -e all processes\n"), out);
+ fputs(_(" -a all with tty, except session leaders\n"), out);
+ fputs(_(" a all with tty, including other users\n"), out);
+ fputs(_(" -d all except session leaders\n"), out);
+ fputs(_(" -N, --deselect negate selection\n"), out);
+ fputs(_(" r only running processes\n"), out);
+ fputs(_(" T all processes on this terminal\n"), out);
+ fputs(_(" x processes without controlling ttys\n"), out);
+ }
+ if (section == HELP_LST || section == HELP_ALL) {
+ fputs(_("\nSelection by list:\n"), out);
+ fputs(_(" -C <command> command name\n"), out);
+ fputs(_(" -G, --Group <GID> real group id or name\n"), out);
+ fputs(_(" -g, --group <group> session or effective group name\n"), out);
+ fputs(_(" -p, p, --pid <PID> process id\n"), out);
+ fputs(_(" --ppid <PID> parent process id\n"), out);
+ fputs(_(" -q, q, --quick-pid <PID>\n"
+ " process id (quick mode)\n"), out);
+ fputs(_(" -s, --sid <session> session id\n"), out);
+ fputs(_(" -t, t, --tty <tty> terminal\n"), out);
+ fputs(_(" -u, U, --user <UID> effective user id or name\n"), out);
+ fputs(_(" -U, --User <UID> real user id or name\n"), out);
+ fputs(_("\n"
+ " The selection options take as their argument either:\n"
+ " a comma-separated list e.g. '-u root,nobody' or\n"
+ " a blank-separated list e.g. '-p 123 4567'\n"), out);
+ }
+ if (section == HELP_OUT || section == HELP_ALL) {
+ fputs(_("\nOutput formats:\n"), out);
+ fputs(_(" -D <format> date format for lstart\n"), out);
+ fputs(_(" -F extra full\n"), out);
+ fputs(_(" -f full-format, including command lines\n"), out);
+ fputs(_(" f, --forest ascii art process tree\n"), out);
+ fputs(_(" -H show process hierarchy\n"), out);
+ fputs(_(" -j jobs format\n"), out);
+ fputs(_(" j BSD job control format\n"), out);
+ fputs(_(" -l long format\n"), out);
+ fputs(_(" l BSD long format\n"), out);
+ fputs(_(" -M, Z add security data (for SELinux)\n"), out);
+ fputs(_(" -O <format> preloaded with default columns\n"), out);
+ fputs(_(" O <format> as -O, with BSD personality\n"), out);
+ fputs(_(" -o, o, --format <format>\n"
+ " user-defined format\n"), out);
+ fputs(_(" -P add psr column\n"), out);
+ fputs(_(" s signal format\n"), out);
+ fputs(_(" u user-oriented format\n"), out);
+ fputs(_(" v virtual memory format\n"), out);
+ fputs(_(" X register format\n"), out);
+ fputs(_(" -y do not show flags, show rss vs. addr (used with -l)\n"), out);
+ fputs(_(" --context display security context (for SELinux)\n"), out);
+ fputs(_(" --headers repeat header lines, one per page\n"), out);
+ fputs(_(" --no-headers do not print header at all\n"), out);
+ fputs(_(" --cols, --columns, --width <num>\n"
+ " set screen width\n"), out);
+ fputs(_(" --rows, --lines <num>\n"
+ " set screen height\n"), out);
+ fputs(_(" --signames display signal masks using signal names\n"), out);
+ }
+ if (section == HELP_THD || section == HELP_ALL) {
+ fputs(_("\nShow threads:\n"), out);
+ fputs(_(" H as if they were processes\n"), out);
+ fputs(_(" -L possibly with LWP and NLWP columns\n"), out);
+ fputs(_(" -m, m after processes\n"), out);
+ fputs(_(" -T possibly with SPID column\n"), out);
+ }
+ if (section == HELP_MSC || section == HELP_ALL) {
+ fputs(_("\nMiscellaneous options:\n"), out);
+ fputs(_(" -c show scheduling class with -l option\n"), out);
+ fputs(_(" c show true command name\n"), out);
+ fputs(_(" e show the environment after command\n"), out);
+ fputs(_(" k, --sort specify sort order as: [+|-]key[,[+|-]key[,...]]\n"), out);
+ fputs(_(" L show format specifiers\n"), out);
+ fputs(_(" n display numeric uid and wchan\n"), out);
+ fputs(_(" S, --cumulative include some dead child process data\n"), out);
+ fputs(_(" -y do not show flags, show rss (only with -l)\n"), out);
+ fputs(_(" -V, V, --version display version information and exit\n"), out);
+ fputs(_(" -w, w unlimited output width\n"), out);
+ fprintf(out, _("\n"
+ " --%s <%s|%s|%s|%s|%s|%s>\n"
+ " display help and exit\n")
+ , the_word_help
+ , help_tab[HELP_SMP].word, help_tab[HELP_LST].word
+ , help_tab[HELP_OUT].word, help_tab[HELP_THD].word
+ , help_tab[HELP_MSC].word, help_tab[HELP_ALL].word);
+ }
+ if (section == HELP_default) {
+/* Translation Notes for ps Help #2 ---------------------------------
+ . Most of the following c-format string is derived from the 6
+ . pairs of words + chars mentioned above in "Notes for ps Help #1".
+ .
+ . In its full English form, help text would look like this:
+ . Try 'ps --help <simple|list|output|threads|misc|all>'
+ . or 'ps --help <s|l|o|t|m|a>'
+ . for additional help text.
+ .
+ . The word for "help" will be translated elsewhere. Thus, the only
+ . translations below will be: "Try", "or" and "for additional...".
+ . */
+ fprintf(out, _("\n"
+ " Try '%s --%s <%s|%s|%s|%s|%s|%s>'\n"
+ " or '%s --%s <%s|%s|%s|%s|%s|%s>'\n"
+ " for additional help text.\n")
+ , myname, the_word_help
+ , help_tab[HELP_SMP].word, help_tab[HELP_LST].word
+ , help_tab[HELP_OUT].word, help_tab[HELP_THD].word
+ , help_tab[HELP_MSC].word, help_tab[HELP_ALL].word
+ , myname, the_word_help
+ , help_tab[HELP_SMP].abrv, help_tab[HELP_LST].abrv
+ , help_tab[HELP_OUT].abrv, help_tab[HELP_THD].abrv
+ , help_tab[HELP_MSC].abrv, help_tab[HELP_ALL].abrv);
+ }
+ fprintf(out, _("\nFor more details see ps(1).\n"));
+ exit(rc);
+}
+
+/* Missing:
+ *
+ * -P e k
+ *
+ */
diff --git a/src/ps/output.c b/src/ps/output.c
new file mode 100644
index 0000000..a4b3833
--- /dev/null
+++ b/src/ps/output.c
@@ -0,0 +1,2370 @@
+/*
+ * output.c - ps output definitions
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net
+ * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2011 Lukas Nykryn <lnykryn@redhat.com>
+ * Copyright © 1999-2004 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This file is really gross, and I know it. I looked into several
+ * alternate ways to deal with the mess, and they were all ugly.
+ *
+ * FreeBSD has a fancy hack using offsets into a struct -- that
+ * saves code but it is _really_ gross. See the PO macro below.
+ *
+ * We could have a second column width for wide output format.
+ * For example, Digital prints the real-time signals.
+ */
+
+/*
+ * Data table idea:
+ *
+ * table 1 maps aix to specifier
+ * table 2 maps shortsort to specifier
+ * table 3 maps macro to specifiers
+ * table 4 maps specifier to title,datatype,offset,vendor,helptext
+ * table 5 maps datatype to justification,width,widewidth,sorting,printing
+ *
+ * Here, "datatype" could be user,uid,u16,pages,deltaT,signals,tty,longtty...
+ * It must be enough to determine printing and sorting.
+ *
+ * After the tables, increase width as needed to fit the header.
+ *
+ * Table 5 could go in a file with the output functions.
+ */
+
+#include <ctype.h>
+#if ENABLE_LIBSELINUX
+#include <dlfcn.h>
+#endif
+#include <ctype.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <langinfo.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+
+#include "c.h"
+
+#include "common.h"
+
+/* TODO:
+ * Stop assuming system time is local time.
+ */
+
+#define COLWID 240 /* satisfy snprintf, which is faster than sprintf */
+#define SIGNAL_NAME_WIDTH 27
+
+static unsigned max_rightward = OUTBUF_SIZE-1; /* space for RIGHT stuff */
+static unsigned max_leftward = OUTBUF_SIZE-1; /* space for LEFT stuff */
+
+
+static int wide_signals; /* true if we have room */
+
+static time_t seconds_since_1970;
+
+
+extern long Hertz;
+
+
+static unsigned int boot_time(void)
+{
+ static unsigned int boot_time = 0;
+ struct stat_info *stat_info = NULL;
+ if (boot_time == 0) {
+ if (procps_stat_new(&stat_info) < 0)
+ xerrx(EXIT_FAILURE, _("Unable to get system boot time"));
+ boot_time = STAT_GET(stat_info, STAT_SYS_TIME_OF_BOOT, ul_int);
+ procps_stat_unref(&stat_info);
+ }
+ return boot_time;
+}
+
+static unsigned long memory_total()
+{
+ static unsigned long memory_total = 0;
+ struct meminfo_info *mem_info = NULL;
+
+ if (memory_total == 0) {
+ if (procps_meminfo_new(&mem_info) < 0)
+ xerrx(EXIT_FAILURE,
+ _("Unable to get total memory"));
+ memory_total = MEMINFO_GET(mem_info, MEMINFO_MEM_TOTAL, ul_int);
+ procps_meminfo_unref(&mem_info);
+ }
+ return memory_total;
+}
+
+#define SECURE_ESCAPE_ARGS(dst, bytes, cells) do { \
+ if ((bytes) <= 0) return 0; \
+ *(dst) = '\0'; \
+ if ((bytes) >= INT_MAX) return 0; \
+ if ((cells) >= INT_MAX) return 0; \
+ if ((cells) <= 0) return 0; \
+} while (0)
+
+// copy a string that doesn't need to be 'escaped'
+static int escaped_copy(char *restrict dst, const char *restrict src, int bufsize, int *maxroom){
+ int n;
+
+ SECURE_ESCAPE_ARGS(dst, bufsize, *maxroom);
+ if (bufsize > *maxroom+1)
+ bufsize = *maxroom+1;
+ n = snprintf(dst, bufsize, "%s", src);
+ if (n < 0) {
+ *dst = '\0';
+ return 0;
+ }
+ if (n >= bufsize)
+ n = bufsize-1;
+ *maxroom -= n;
+ return n;
+}
+
+// duplicated from proc/escape.c so both can be made private
+static int escape_str_utf8 (char *dst, const char *src, int bufsize, int *maxcells) {
+ int my_cells = 0;
+ int my_bytes = 0;
+ mbstate_t s;
+
+ SECURE_ESCAPE_ARGS(dst, bufsize, *maxcells);
+
+ memset(&s, 0, sizeof (s));
+
+ for(;;) {
+ wchar_t wc;
+ int len = 0;
+
+ if(my_cells >= *maxcells || my_bytes+1 >= bufsize)
+ break;
+
+ if (!(len = mbrtowc (&wc, src, MB_CUR_MAX, &s)))
+ /* 'str' contains \0 */
+ break;
+
+ if (len < 0) {
+ /* invalid multibyte sequence -- zeroize state */
+ memset (&s, 0, sizeof (s));
+ *(dst++) = '?';
+ src++;
+ my_cells++;
+ my_bytes++;
+
+ } else if (len==1) {
+ /* non-multibyte */
+ *(dst++) = isprint(*src) ? *src : '?';
+ src++;
+ my_cells++;
+ my_bytes++;
+
+ } else if (!iswprint(wc)) {
+ /* multibyte - no printable */
+ *(dst++) = '?';
+ src+=len;
+ my_cells++;
+ my_bytes++;
+
+ } else {
+ /* multibyte - maybe, kinda "printable" */
+ int wlen = wcwidth(wc);
+ // Got space?
+ if (wlen > *maxcells-my_cells || len >= bufsize-(my_bytes+1)) break;
+ // safe multibyte
+ memcpy(dst, src, len);
+ dst += len;
+ src += len;
+ my_bytes += len;
+ if (wlen > 0) my_cells += wlen;
+ }
+ //fprintf(stdout, "cells: %d\n", my_cells);
+ }
+ *dst = '\0';
+
+ // fprintf(stderr, "maxcells: %d, my_cells; %d\n", *maxcells, my_cells);
+
+ *maxcells -= my_cells;
+ return my_bytes; // bytes of text, excluding the NUL
+}
+
+// duplicated from proc/escape.c so both can be made private
+static int escape_str (char *dst, const char *src, int bufsize, int *maxcells) {
+ unsigned char c;
+ int my_cells = 0;
+ int my_bytes = 0;
+ const char codes[] =
+ "Z..............................."
+ "||||||||||||||||||||||||||||||||"
+ "||||||||||||||||||||||||||||||||"
+ "|||||||||||||||||||||||||||||||."
+ "????????????????????????????????"
+ "????????????????????????????????"
+ "????????????????????????????????"
+ "????????????????????????????????";
+ static int utf_init=0;
+
+ if(utf_init==0){
+ /* first call -- check if UTF stuff is usable */
+ char *enc = nl_langinfo(CODESET);
+ utf_init = enc && strcasecmp(enc, "UTF-8")==0 ? 1 : -1;
+ }
+ if (utf_init==1 && MB_CUR_MAX>1) {
+ /* UTF8 locales */
+ return escape_str_utf8(dst, src, bufsize, maxcells);
+ }
+
+ SECURE_ESCAPE_ARGS(dst, bufsize, *maxcells);
+
+ if(bufsize > *maxcells+1) bufsize=*maxcells+1; // FIXME: assumes 8-bit locale
+
+ for(;;){
+ if(my_cells >= *maxcells || my_bytes+1 >= bufsize)
+ break;
+ c = (unsigned char) *(src++);
+ if(!c) break;
+ if(codes[c]!='|') c=codes[c];
+ my_cells++;
+ my_bytes++;
+ *(dst++) = c;
+ }
+ *dst = '\0';
+
+ *maxcells -= my_cells;
+ return my_bytes; // bytes of text, excluding the NUL
+}
+
+/***************************************************************************/
+/************ Lots of format functions, starting with the NOP **************/
+
+// so popular it can't be "static"
+int pr_nop(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(noop)
+ (void)pp;
+ return snprintf(outbuf, COLWID, "%c", '-');
+}
+
+/********* Unix 98 ************/
+/***
+
+Only comm and args are allowed to contain blank characters; all others are
+not. Any implementation-dependent variables will be specified in the system
+documentation along with the default header and indicating if the field
+may contain blank characters.
+
+Some headers do not have a standardized specifier!
+
+%CPU pcpu The % of cpu time used recently, with unspecified "recently".
+ADDR The address of the process.
+C Processor utilisation for scheduling.
+CMD The command name, or everything with -f.
+COMMAND args Command + args. May chop as desired. May use either version.
+COMMAND comm argv[0]
+ELAPSED etime Elapsed time since the process was started. [[dd-]hh:]mm:ss
+F Flags (octal and additive)
+GROUP group Effective group ID, prefer text over decimal.
+NI nice Decimal system scheduling priority, see nice(1).
+PGID pgid The decimal value of the process group ID.
+PID pid Decimal PID.
+PPID ppid Decimal PID.
+PRI Priority. Higher numbers mean lower priority.
+RGROUP rgroup Real group ID, prefer text over decimal.
+RUSER ruser Real user ID, prefer text over decimal.
+S The state of the process.
+STIME Starting time of the process.
+SZ The size in blocks of the core image of the process.
+TIME time Cumulative CPU time. [dd-]hh:mm:ss
+TT tty Name of tty in format used by who(1).
+TTY The controlling terminal for the process.
+UID UID, or name when -f
+USER user Effective user ID, prefer text over decimal.
+VSZ vsz Virtual memory size in decimal kB.
+WCHAN Where waiting/sleeping or blank if running.
+
+The nice value is used to compute the priority.
+
+For some undefined ones, Digital does:
+
+F flag Process flags -- but in hex!
+PRI pri Process priority
+S state Symbolic process status
+TTY tt,tty,tname,longtname -- all do "ttyp1", "console", "??"
+UID uid Process user ID (effective UID)
+WCHAN wchan Address of event on which a
+
+For some undefined ones, Sun does:
+
+ADDR addr memory address of the process
+C c Processor utilization for scheduling (obsolete).
+CMD
+F f
+S s state: OSRZT
+STIME start time, printed w/o blanks. If 24h old, months & days
+SZ size (in pages) of the swappable process's image in main memory
+TTY
+UID uid
+WCHAN wchan
+
+For some undefined ones, SCO does:
+ADDR addr Virtual address of the process' entry in the process table.
+SZ swappable size in kB of the virtual data and stack
+STIME stime hms or md time format
+***/
+
+/* Source & destination are known. Return bytes or screen characters? */
+//
+// OldLinux FreeBSD HPUX
+// ' ' ' ' ' ' ' '
+// 'L' ' \_ ' '`-' ' '
+// '+' ' \_ ' '|-' ' '
+// '|' ' | ' '| ' ' '
+//
+static int forest_helper(char *restrict const outbuf){
+ char *p = forest_prefix;
+ char *q = outbuf;
+ int rightward = max_rightward < OUTBUF_SIZE ? max_rightward : OUTBUF_SIZE-1;
+ *q = '\0';
+ if(!*p) return 0;
+ /* Arrrgh! somebody defined unix as 1 */
+ if(forest_type == 'u') goto unixy;
+ while(*p){
+ if (rightward < 4) break;
+ switch(*p){
+ case ' ': strcpy(q, " "); break;
+ case 'L': strcpy(q, " \\_ "); break;
+ case '+': strcpy(q, " \\_ "); break;
+ case '|': strcpy(q, " | "); break;
+ case '\0': return q-outbuf; /* redundant & not used */
+ }
+ q += 4;
+ rightward -= 4;
+ p++;
+ }
+ return q-outbuf; /* gcc likes this here */
+unixy:
+ while(*p){
+ if (rightward < 2) break;
+ switch(*p){
+ case ' ': strcpy(q, " "); break;
+ case 'L': strcpy(q, " "); break;
+ case '+': strcpy(q, " "); break;
+ case '|': strcpy(q, " "); break;
+ case '\0': return q-outbuf; /* redundant & not used */
+ }
+ q += 2;
+ rightward -= 2;
+ p++;
+ }
+ return q-outbuf; /* gcc likes this here */
+}
+
+
+/* XPG4-UNIX, according to Digital:
+The "args" and "command" specifiers show what was passed to the command.
+Modifications to the arguments are not shown.
+*/
+
+/*
+ * pp->cmd short accounting name (comm & ucomm)
+ * pp->cmdline long name with args (args & command)
+ * pp->environ environment
+ */
+
+// FIXME: some of these may hit the guard page in forest mode
+
+#define OUTBUF_SIZE_AT(endp) \
+ (((endp) >= outbuf && (endp) < outbuf + OUTBUF_SIZE) ? (outbuf + OUTBUF_SIZE) - (endp) : 0)
+
+/*
+ * "args", "cmd", "command" are all the same: long unless c
+ * "comm", "ucmd", "ucomm" are all the same: short unless -f
+ * ( determinations are made in display.c, we mostly deal with results ) */
+static int pr_args(char *restrict const outbuf, const proc_t *restrict const pp){
+ char *endp;
+ int rightward, fh;
+setREL3(CMDLINE,CMD,ENVIRON)
+ endp = outbuf;
+ rightward = max_rightward;
+ fh = forest_helper(outbuf);
+ endp += fh;
+ rightward -= fh;
+ if (!bsd_c_option)
+ endp += escape_str(endp, rSv(CMDLINE, str, pp), OUTBUF_SIZE_AT(endp), &rightward);
+ else
+ endp += escape_str(endp, rSv(CMD, str, pp), OUTBUF_SIZE_AT(endp), &rightward);
+ if(bsd_e_option && rightward>1) {
+ char *e = rSv(ENVIRON, str, pp);
+ if(*e != '-' || *(e+1) != '\0') {
+ *endp++ = ' ';
+ rightward--;
+ escape_str(endp, e, OUTBUF_SIZE_AT(endp), &rightward);
+ }
+ }
+ return max_rightward-rightward;
+}
+
+/*
+ * "args", "cmd", "command" are all the same: long unless c
+ * "comm", "ucmd", "ucomm" are all the same: short unless -f
+ * ( determinations are made in display.c, we mostly deal with results ) */
+static int pr_comm(char *restrict const outbuf, const proc_t *restrict const pp){
+ char *endp;
+ int rightward, fh;
+setREL3(CMD,CMDLINE,ENVIRON)
+ endp = outbuf;
+ rightward = max_rightward;
+ fh = forest_helper(outbuf);
+ endp += fh;
+ rightward -= fh;
+ if(unix_f_option)
+ endp += escape_str(endp, rSv(CMDLINE, str, pp), OUTBUF_SIZE_AT(endp), &rightward);
+ else
+ endp += escape_str(endp, rSv(CMD, str, pp), OUTBUF_SIZE_AT(endp), &rightward);
+ if(bsd_e_option && rightward>1) {
+ char *e = rSv(ENVIRON, str, pp);
+ if(*e != '-' || *(e+1) != '\0') {
+ *endp++ = ' ';
+ rightward--;
+ escape_str(endp, e, OUTBUF_SIZE_AT(endp), &rightward);
+ }
+ }
+ return max_rightward-rightward;
+}
+
+static int pr_cgname(char *restrict const outbuf,const proc_t *restrict const pp) {
+ int rightward;
+setREL1(CGNAME)
+ rightward = max_rightward;
+ escape_str(outbuf, rSv(CGNAME, str, pp), OUTBUF_SIZE, &rightward);
+ return max_rightward-rightward;
+}
+
+static int pr_cgroup(char *restrict const outbuf,const proc_t *restrict const pp) {
+ int rightward;
+setREL1(CGROUP)
+ rightward = max_rightward;
+ escape_str(outbuf, rSv(CGROUP, str, pp), OUTBUF_SIZE, &rightward);
+ return max_rightward-rightward;
+}
+
+/* Non-standard, from SunOS 5 */
+static int pr_fname(char *restrict const outbuf, const proc_t *restrict const pp){
+ char *endp;
+ int rightward, fh;
+setREL1(CMD)
+ endp = outbuf;
+ rightward = max_rightward;
+ fh = forest_helper(outbuf);
+ endp += fh;
+ rightward -= fh;
+ if (rightward>8) /* 8=default, but forest maybe feeds more */
+ rightward = 8;
+ endp += escape_str(endp, rSv(CMD, str, pp), OUTBUF_SIZE_AT(endp), &rightward);
+ //return endp - outbuf;
+ return max_rightward-rightward;
+}
+
+#undef OUTBUF_SIZE_AT
+
+/* elapsed wall clock time, [[dd-]hh:]mm:ss format (not same as "time") */
+static int pr_etime(char *restrict const outbuf, const proc_t *restrict const pp){
+ unsigned long t;
+ unsigned dd,hh,mm,ss;
+ char *cp;
+setREL1(TIME_ELAPSED)
+ cp = outbuf;
+ t = rSv(TIME_ELAPSED, real, pp);
+ ss = t%60;
+ t /= 60;
+ mm = t%60;
+ t /= 60;
+ hh = t%24;
+ t /= 24;
+ dd = t;
+ cp +=( dd ? snprintf(cp, COLWID, "%u-", dd) : 0 );
+ cp +=( (dd || hh) ? snprintf(cp, COLWID, "%02u:", hh) : 0 );
+ cp += snprintf(cp, COLWID, "%02u:%02u", mm, ss) ;
+ return (int)(cp-outbuf);
+}
+
+/* elapsed wall clock time in seconds */
+static int pr_etimes(char *restrict const outbuf, const proc_t *restrict const pp){
+ unsigned t;
+setREL1(TIME_ELAPSED)
+ t = rSv(TIME_ELAPSED, real, pp);
+ return snprintf(outbuf, COLWID, "%u", t);
+}
+
+/* "Processor utilisation for scheduling." --- we use %cpu w/o fraction */
+static int pr_c(char *restrict const outbuf, const proc_t *restrict const pp){
+ unsigned long long total_time; /* jiffies used by this process */
+ unsigned pcpu; /* scaled %cpu, 99 means 99% */
+ unsigned long long jiffies; /* jiffies of process life */
+setREL4(TICS_ALL,TICS_ALL_C,TIME_ELAPSED,UTILIZATION)
+ pcpu = 0;
+ if(include_dead_children) total_time = rSv(TICS_ALL_C, ull_int, pp);
+ else total_time = rSv(TICS_ALL, ull_int, pp);
+ jiffies = rSv(TIME_ELAPSED, real, pp) * Hertz;
+ if(jiffies) pcpu = (total_time * 100ULL) / jiffies;
+ if (pcpu > 99U) pcpu = 99U;
+ return snprintf(outbuf, COLWID, "%2u", pcpu);
+}
+
+/* normal %CPU in ##.# format. */
+static int pr_pcpu(char *restrict const outbuf, const proc_t *restrict const pp){
+ unsigned long long total_time; /* jiffies used by this process */
+ unsigned pcpu; /* scaled %cpu, 999 means 99.9% */
+ unsigned long long jiffies; /* jiffies of process life */
+setREL4(TICS_ALL,TICS_ALL_C,TIME_ELAPSED,UTILIZATION)
+ pcpu = 0;
+ if(include_dead_children) total_time = rSv(TICS_ALL_C, ull_int, pp);
+ else total_time = rSv(TICS_ALL, ull_int, pp);
+ jiffies = rSv(TIME_ELAPSED, real, pp) * Hertz;
+ if(jiffies) pcpu = (total_time * 1000ULL) / jiffies;
+ if (pcpu > 999U)
+ return snprintf(outbuf, COLWID, "%u", pcpu/10U);
+ return snprintf(outbuf, COLWID, "%u.%u", pcpu/10U, pcpu%10U);
+}
+
+/* this is a "per-mill" format, like %cpu with no decimal point */
+static int pr_cp(char *restrict const outbuf, const proc_t *restrict const pp){
+ unsigned long long total_time; /* jiffies used by this process */
+ unsigned pcpu; /* scaled %cpu, 999 means 99.9% */
+ unsigned long long jiffies; /* jiffies of process life */
+setREL4(TICS_ALL,TICS_ALL_C,TIME_ELAPSED,UTILIZATION)
+ pcpu = 0;
+ if(include_dead_children) total_time = rSv(TICS_ALL_C, ull_int, pp);
+ else total_time = rSv(TICS_ALL, ull_int, pp);
+ jiffies = rSv(TIME_ELAPSED, real, pp) * Hertz;
+ if(jiffies) pcpu = (total_time * 1000ULL) / jiffies;
+ if (pcpu > 999U) pcpu = 999U;
+ return snprintf(outbuf, COLWID, "%3u", pcpu);
+}
+
+static int pr_pgid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_PGRP)
+ return snprintf(outbuf, COLWID, "%u", rSv(ID_PGRP, s_int, pp));
+}
+static int pr_ppid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_PPID)
+ return snprintf(outbuf, COLWID, "%u", rSv(ID_PPID, s_int, pp));
+}
+
+/* cumulative CPU time, [dd-]hh:mm:ss format (not same as "etime") */
+static int pr_time(char *restrict const outbuf, const proc_t *restrict const pp){
+ unsigned long t;
+ unsigned dd,hh,mm,ss;
+ int c;
+setREL1(TIME_ALL)
+ t = rSv(TIME_ALL, real, pp);
+ ss = t%60;
+ t /= 60;
+ mm = t%60;
+ t /= 60;
+ hh = t%24;
+ t /= 24;
+ dd = t;
+ c =( dd ? snprintf(outbuf, COLWID, "%u-", dd) : 0 );
+ c +=( snprintf(outbuf+c, COLWID, "%02u:%02u:%02u", hh, mm, ss) );
+ return c;
+}
+
+/* cumulative CPU time in seconds (not same as "etimes") */
+static int pr_times(char *restrict const outbuf, const proc_t *restrict const pp){
+ unsigned long t;
+setREL1(TIME_ALL)
+ t = rSv(TIME_ALL, real, pp);
+ return snprintf(outbuf, COLWID, "%lu", t);
+}
+
+/* HP-UX puts this (I forget, vsz or vsize?) in kB and uses "sz" for pages.
+ * Unix98 requires "vsz" to be kB.
+ * Tru64 does both vsize and vsz like "1.23M"
+ *
+ * Our pp->vm_size is kB and our pp->vsize is pages.
+ *
+ * TODO: add flag for "1.23M" behavior, on this and other columns.
+ */
+static int pr_vsz(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(VM_SIZE)
+ return snprintf(outbuf, COLWID, "%lu", rSv(VM_SIZE, ul_int, pp));
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+// "PRI" is created by "opri", or by "pri" when -c is used.
+//
+// Unix98 only specifies that a high "PRI" is low priority.
+// Sun and SCO add the -c behavior. Sun defines "pri" and "opri".
+// Linux may use "priority" for historical purposes.
+//
+// According to the kernel's fs/proc/array.c and kernel/sched.c source,
+// the kernel reports it in /proc via this:
+// p->prio - MAX_RT_PRIO
+// such that "RT tasks are offset by -200. Normal tasks are centered
+// around 0, value goes from -16 to +15" but who knows if that is
+// before or after the conversion...
+//
+// <linux/sched.h> says:
+// MAX_RT_PRIO is currently 100. (so we see 0 in /proc)
+// RT tasks have a p->prio of 0 to 99. (so we see -100 to -1)
+// non-RT tasks are from 100 to 139. (so we see 0 to 39)
+// Lower values have higher priority, as in the UNIX standard.
+//
+// In any case, pp->priority+100 should get us back to what the kernel
+// has for p->prio.
+//
+// Test results with the "yes" program on a 2.6.x kernel:
+//
+// # ps -C19,_20 -o pri,opri,intpri,priority,ni,pcpu,pid,comm
+// PRI PRI PRI PRI NI %CPU PID COMMAND
+// 0 99 99 39 19 10.6 8686 19
+// 34 65 65 5 -20 94.7 8687 _20
+//
+// Grrr. So the UNIX standard "PRI" must NOT be from "pri".
+// Either of the others will do. We use "opri" for this.
+// (and use "pri" when the "-c" option is used)
+// Probably we should have Linux-specific "pri_for_l" and "pri_for_lc"
+//
+// sched_get_priority_min.2 says the Linux static priority is
+// 1..99 for RT and 0 for other... maybe 100 is kernel-only?
+//
+// A nice range would be -99..0 for RT and 1..40 for normal,
+// which is pp->priority+1. (3-digit max, positive is normal,
+// negative or 0 is RT, and meets the standard for PRI)
+//
+
+// legal as UNIX "PRI"
+// "priority" (was -20..20, now -100..39)
+static int pr_priority(char *restrict const outbuf, const proc_t *restrict const pp){ /* -20..20 */
+setREL1(PRIORITY)
+ return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY, s_int, pp));
+}
+
+// legal as UNIX "PRI"
+// "intpri" and "opri" (was 39..79, now -40..99)
+static int pr_opri(char *restrict const outbuf, const proc_t *restrict const pp){ /* 39..79 */
+setREL1(PRIORITY)
+ return snprintf(outbuf, COLWID, "%d", 60 + rSv(PRIORITY, s_int, pp));
+}
+
+// legal as UNIX "PRI"
+// "pri_foo" -- match up w/ nice values of sleeping processes (-120..19)
+static int pr_pri_foo(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(PRIORITY)
+ return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY, s_int, pp) - 20);
+}
+
+// legal as UNIX "PRI"
+// "pri_bar" -- makes RT pri show as negative (-99..40)
+static int pr_pri_bar(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(PRIORITY)
+ return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY, s_int, pp) + 1);
+}
+
+// legal as UNIX "PRI"
+// "pri_baz" -- the kernel's ->prio value, as of Linux 2.6.8 (1..140)
+static int pr_pri_baz(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(PRIORITY)
+ return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY, s_int, pp) + 100);
+}
+
+// not legal as UNIX "PRI"
+// "pri" (was 20..60, now 0..139)
+static int pr_pri(char *restrict const outbuf, const proc_t *restrict const pp){ /* 20..60 */
+setREL1(PRIORITY)
+ return snprintf(outbuf, COLWID, "%d", 39 - rSv(PRIORITY, s_int, pp));
+}
+
+// not legal as UNIX "PRI"
+// "pri_api" -- match up w/ RT API (-40..99)
+static int pr_pri_api(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(PRIORITY)
+ return snprintf(outbuf, COLWID, "%d", -1 - rSv(PRIORITY, s_int, pp));
+}
+
+// Linux applies nice value in the scheduling policies (classes)
+// SCHED_OTHER(0) and SCHED_BATCH(3). Ref: sched_setscheduler(2).
+// Also print nice value for old kernels which didn't use scheduling
+// policies (-1).
+static int pr_nice(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(NICE,SCHED_CLASS)
+ if(rSv(SCHED_CLASS, s_int, pp)!=0 && rSv(SCHED_CLASS, s_int, pp)!=3 && rSv(SCHED_CLASS, s_int, pp)!=-1) return snprintf(outbuf, COLWID, "-");
+ return snprintf(outbuf, COLWID, "%d", rSv(NICE, s_int, pp));
+}
+
+static int pr_oom_adj(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(OOM_ADJ)
+ return snprintf(outbuf, COLWID, "%d", rSv(OOM_ADJ, s_int, pp));
+}
+
+static int pr_oom(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(OOM_SCORE)
+ return snprintf(outbuf, COLWID, "%d", rSv(OOM_SCORE, s_int, pp));
+}
+// HP-UX "cls": RT RR RR2 ???? HPUX FIFO KERN
+// Solaris "class": SYS TS FX IA RT FSS (FIFO is RR w/ Inf quant)
+// FIFO+RR share RT; FIFO has Inf quant
+// IA=interactive; FX=fixed; TS=timeshare; SYS=system
+// FSS=fairshare; INTS=interrupts
+// Tru64 "policy": FF RR TS
+// IRIX "class": RT TS B BC WL GN
+// RT=real-time; TS=time-share; B=batch; BC=batch-critical
+// WL=weightless; GN=gang-scheduled
+// see miser(1) for this; PRI has some letter codes too
+static int pr_class(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SCHED_CLASS)
+ switch(rSv(SCHED_CLASS, s_int, pp)){
+ case -1: return snprintf(outbuf, COLWID, "-"); // not reported
+ case 0: return snprintf(outbuf, COLWID, "TS"); // SCHED_OTHER SCHED_NORMAL
+ case 1: return snprintf(outbuf, COLWID, "FF"); // SCHED_FIFO
+ case 2: return snprintf(outbuf, COLWID, "RR"); // SCHED_RR
+ case 3: return snprintf(outbuf, COLWID, "B"); // SCHED_BATCH
+ case 4: return snprintf(outbuf, COLWID, "ISO"); // reserved for SCHED_ISO (Con Kolivas)
+ case 5: return snprintf(outbuf, COLWID, "IDL"); // SCHED_IDLE
+ case 6: return snprintf(outbuf, COLWID, "DLN"); // SCHED_DEADLINE
+ case 7: return snprintf(outbuf, COLWID, "#7"); //
+ case 8: return snprintf(outbuf, COLWID, "#8"); //
+ case 9: return snprintf(outbuf, COLWID, "#9"); //
+ default: return snprintf(outbuf, COLWID, "?"); // unknown value
+ }
+}
+
+// Based on "type", FreeBSD would do:
+// REALTIME "real:%u", prio
+// NORMAL "normal"
+// IDLE "idle:%u", prio
+// default "%u:%u", type, prio
+// We just print the priority, and have other keywords for type.
+static int pr_rtprio(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(SCHED_CLASS,PRIORITY_RT)
+ if(rSv(SCHED_CLASS, s_int, pp)==0 || rSv(SCHED_CLASS, s_int, pp)==-1) return snprintf(outbuf, COLWID, "-");
+ return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY_RT, s_int, pp));
+}
+
+static int pr_sched(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SCHED_CLASS)
+ if(rSv(SCHED_CLASS, s_int, pp)==-1) return snprintf(outbuf, COLWID, "-");
+ return snprintf(outbuf, COLWID, "%d", rSv(SCHED_CLASS, s_int, pp));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static int pr_wchan(char *restrict const outbuf, const proc_t *restrict const pp){
+/*
+ * Unix98 says "blank if running" and also "no blanks"! :-(
+ * Unix98 also says to use '-' if something is meaningless.
+ * Digital uses both '*' and '-', with undocumented differences.
+ * (the '*' for -1 (rare) and the '-' for 0)
+ * Sun claims to use a blank AND use '-', in the same man page.
+ * Perhaps "blank" should mean '-'.
+ *
+ * AIX uses '-' for running processes, the location when there is
+ * only one thread waiting in the kernel, and '*' when there is
+ * more than one thread waiting in the kernel.
+ *
+ * The output should be truncated to maximal columns width -- overflow
+ * is not supported for the "wchan".
+ */
+ const char *w;
+ size_t len;
+setREL1(WCHAN_NAME)
+ w = rSv(WCHAN_NAME, str, pp);
+ len = strlen(w);
+ if(len>max_rightward) len=max_rightward;
+ memcpy(outbuf, w, len);
+ outbuf[len] = '\0';
+ return len;
+}
+
+/* Terrible trunctuation, like BSD crap uses: I999 J999 K999 */
+/* FIXME: disambiguate /dev/tty69 and /dev/pts/69. */
+static int pr_tty4(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(TTY_NUMBER)
+ return snprintf(outbuf, COLWID, "%s", rSv(TTY_NUMBER, str, pp));
+}
+
+/* Unix98: format is unspecified, but must match that used by who(1). */
+static int pr_tty8(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(TTY_NAME)
+ return snprintf(outbuf, COLWID, "%s", rSv(TTY_NAME, str, pp));
+}
+
+#if 0
+/* This BSD state display may contain spaces, which is illegal. */
+static int pr_oldstate(char *restrict const outbuf, const proc_t *restrict const pp){
+ return snprintf(outbuf, COLWID, "%s", status(pp));
+}
+#endif
+
+// This state display is Unix98 compliant and has lots of info like BSD.
+static int pr_stat(char *restrict const outbuf, const proc_t *restrict const pp){
+ int end;
+ if (!outbuf) {
+ chkREL(STATE)
+ chkREL(NICE)
+ chkREL(VM_RSS_LOCKED)
+ chkREL(ID_SESSION)
+ chkREL(ID_TGID)
+ chkREL(NLWP)
+ chkREL(ID_PGRP)
+ chkREL(ID_TPGID)
+ return 0;
+ }
+ end = 0;
+ outbuf[end++] = rSv(STATE, s_ch, pp);
+// if(rSv(RSS, ul_int, pp)==0 && rSv(STATE, s_ch, pp)!='Z') outbuf[end++] = 'W'; // useless "swapped out"
+ if(rSv(NICE, s_int, pp) < 0) outbuf[end++] = '<';
+ if(rSv(NICE, s_int, pp) > 0) outbuf[end++] = 'N';
+// In this order, NetBSD would add:
+// traced 'X'
+// systrace 'x'
+// exiting 'E' (not printed for zombies)
+// vforked 'V'
+// system 'K' (and do not print 'L' too)
+ if(rSv(VM_RSS_LOCKED, ul_int, pp)) outbuf[end++] = 'L';
+ if(rSv(ID_SESSION, s_int, pp) == rSv(ID_TGID, s_int, pp)) outbuf[end++] = 's'; // session leader
+ if(rSv(NLWP, s_int, pp) > 1) outbuf[end++] = 'l'; // multi-threaded
+ if(rSv(ID_PGRP, s_int, pp) == rSv(ID_TPGID, s_int, pp)) outbuf[end++] = '+'; // in foreground process group
+ outbuf[end] = '\0';
+ return end;
+}
+
+/* This minimal state display is Unix98 compliant, like SCO and SunOS 5 */
+static int pr_s(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(STATE)
+ outbuf[0] = rSv(STATE, s_ch, pp);
+ outbuf[1] = '\0';
+ return 1;
+}
+
+static int pr_flag(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(FLAGS)
+ /* Unix98 requires octal flags */
+ /* this user-hostile and volatile junk gets 1 character */
+ return snprintf(outbuf, COLWID, "%o", (unsigned)(rSv(FLAGS, ul_int, pp)>>6U)&0x7U);
+}
+
+// plus these: euid,ruid,egroup,rgroup (elsewhere in this file)
+
+/*********** non-standard ***********/
+
+/*** BSD
+sess session pointer
+(SCO has:Process session leader ID as a decimal value. (SESSION))
+jobc job control count
+cpu short-term cpu usage factor (for scheduling)
+sl sleep time (in seconds; 127 = infinity)
+re core residency time (in seconds; 127 = infinity)
+pagein pageins (same as majflt)
+lim soft memory limit
+tsiz text size (in Kbytes)
+***/
+
+static int pr_stackp(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ADDR_STACK_START)
+ return snprintf(outbuf, COLWID, "%0*lx", (int)(2*sizeof(long)), rSv(ADDR_STACK_START, ul_int, pp));
+}
+
+static int pr_esp(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ADDR_CURR_ESP)
+ return snprintf(outbuf, COLWID, "%0*lx", (int)(2*sizeof(long)), rSv(ADDR_CURR_ESP, ul_int, pp));
+}
+
+static int pr_eip(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ADDR_CURR_EIP)
+ return snprintf(outbuf, COLWID, "%0*lx", (int)(2*sizeof(long)), rSv(ADDR_CURR_EIP, ul_int, pp));
+}
+
+static int pr_bsdtime(char *restrict const outbuf, const proc_t *restrict const pp){
+ unsigned long long t;
+ unsigned u;
+setREL2(TICS_ALL,TICS_ALL_C)
+ if(include_dead_children) t = rSv(TICS_ALL_C, ull_int, pp);
+ else t = rSv(TICS_ALL, ull_int, pp);
+ u = t / Hertz;
+ return snprintf(outbuf, COLWID, "%3u:%02u", u/60U, u%60U);
+}
+
+static int pr_bsdstart(char *restrict const outbuf, const proc_t *restrict const pp){
+ time_t start;
+ time_t seconds_ago;
+setREL1(TICS_BEGAN)
+ start = boot_time() + rSv(TICS_BEGAN, ull_int, pp) / Hertz;
+ seconds_ago = seconds_since_1970 - start;
+ if(seconds_ago < 0) seconds_ago=0;
+ if(seconds_ago > 3600*24) snprintf(outbuf, COLWID, "%s", ctime(&start)+4);
+ else snprintf(outbuf, COLWID, "%s", ctime(&start)+10);
+ outbuf[6] = '\0';
+ return 6;
+}
+
+/* HP-UX puts this in pages and uses "vsz" for kB */
+static int pr_sz(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(VM_SIZE)
+ return snprintf(outbuf, COLWID, "%lu", rSv(VM_SIZE, ul_int, pp)/(page_size/1024));
+}
+
+/*
+ * FIXME: trs,drs,tsiz,dsiz,m_trs,m_drs,vm_exe,vm_data,trss
+ * I suspect some/all of those are broken. They seem to have been
+ * inherited by Linux and AIX from early BSD systems. FreeBSD only
+ * retains tsiz. The prefixed versions come from Debian.
+ * Sun and Digital have none of this crap. The code here comes
+ * from an old Linux ps, and might not be correct for ELF executables.
+ *
+ * AIX TRS size of resident-set (real memory) of text
+ * AIX TSIZ size of text (shared-program) image
+ * FreeBSD tsiz text size (in Kbytes)
+ * 4.3BSD NET/2 trss text resident set size (in Kbytes)
+ * 4.3BSD NET/2 tsiz text size (in Kbytes)
+ */
+
+/* kB data size. See drs, tsiz & trs. */
+static int pr_dsiz(char *restrict const outbuf, const proc_t *restrict const pp){
+ long dsiz;
+setREL3(VSIZE_BYTES,ADDR_CODE_END,ADDR_CODE_START)
+ dsiz = 0;
+ if(rSv(VSIZE_BYTES, ul_int, pp)) dsiz += (rSv(VSIZE_BYTES, ul_int, pp) - rSv(ADDR_CODE_END, ul_int, pp) + rSv(ADDR_CODE_START, ul_int, pp)) >> 10;
+ return snprintf(outbuf, COLWID, "%ld", dsiz);
+}
+
+/* kB text (code) size. See trs, dsiz & drs. */
+static int pr_tsiz(char *restrict const outbuf, const proc_t *restrict const pp){
+ long tsiz;
+setREL3(VSIZE_BYTES,ADDR_CODE_END,ADDR_CODE_START)
+ tsiz = 0;
+ if(rSv(VSIZE_BYTES, ul_int, pp)) tsiz += (rSv(ADDR_CODE_END, ul_int, pp) - rSv(ADDR_CODE_START, ul_int, pp)) >> 10;
+ return snprintf(outbuf, COLWID, "%ld", tsiz);
+}
+
+/* kB _resident_ data size. See dsiz, tsiz & trs. */
+static int pr_drs(char *restrict const outbuf, const proc_t *restrict const pp){
+ long drs;
+setREL3(VSIZE_BYTES,ADDR_CODE_END,ADDR_CODE_START)
+ drs = 0;
+ if(rSv(VSIZE_BYTES, ul_int, pp)) drs += (rSv(VSIZE_BYTES, ul_int, pp) - rSv(ADDR_CODE_END, ul_int, pp) + rSv(ADDR_CODE_START, ul_int, pp)) >> 10;
+ return snprintf(outbuf, COLWID, "%ld", drs);
+}
+
+/* kB text _resident_ (code) size. See tsiz, dsiz & drs. */
+static int pr_trs(char *restrict const outbuf, const proc_t *restrict const pp){
+ long trs;
+setREL3(VSIZE_BYTES,ADDR_CODE_END,ADDR_CODE_START)
+ trs = 0;
+ if(rSv(VSIZE_BYTES, ul_int, pp)) trs += (rSv(ADDR_CODE_END, ul_int, pp) - rSv(ADDR_CODE_START, ul_int, pp)) >> 10;
+ return snprintf(outbuf, COLWID, "%ld", trs);
+}
+
+static int pr_swapable(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL3(VM_DATA,VM_STACK,VSIZE_BYTES) // that last enum will approximate sort needs
+ return snprintf(outbuf, COLWID, "%lu", rSv(VM_DATA, ul_int, pp) + rSv(VM_STACK, ul_int, pp));
+}
+
+/* nasty old Debian thing */
+static int pr_size(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(VSIZE_BYTES)
+ return snprintf(outbuf, COLWID, "%lu", rSv(VSIZE_BYTES, ul_int, pp));
+}
+
+static int pr_minflt(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(FLT_MIN,FLT_MIN_C)
+ unsigned long flt = rSv(FLT_MIN, ul_int, pp);
+ if(include_dead_children) flt = rSv(FLT_MIN_C, ul_int, pp);
+ return snprintf(outbuf, COLWID, "%lu", flt);
+}
+
+static int pr_majflt(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(FLT_MAJ,FLT_MAJ_C)
+ unsigned long flt = rSv(FLT_MAJ, ul_int, pp);
+ if(include_dead_children) flt = rSv(FLT_MAJ_C, ul_int, pp);
+ return snprintf(outbuf, COLWID, "%lu", flt);
+}
+
+static int pr_lim(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(RSS_RLIM)
+ if(rSv(RSS_RLIM, ul_int, pp) == RLIM_INFINITY){
+ outbuf[0] = 'x';
+ outbuf[1] = 'x';
+ outbuf[2] = '\0';
+ return 2;
+ }
+ return snprintf(outbuf, COLWID, "%5lu", rSv(RSS_RLIM, ul_int, pp) >> 10L);
+}
+
+/* should print leading tilde ('~') if process is bound to the CPU */
+static int pr_psr(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(PROCESSOR)
+ return snprintf(outbuf, COLWID, "%d", rSv(PROCESSOR, s_int, pp));
+}
+
+static int pr_pss(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SMAP_PSS)
+ return snprintf(outbuf, COLWID, "%lu", rSv(SMAP_PSS, ul_int, pp));
+}
+
+static int pr_numa(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(PROCESSOR_NODE)
+ return snprintf(outbuf, COLWID, "%d", rSv(PROCESSOR_NODE, s_int, pp));
+}
+
+static int pr_rss(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(VM_RSS)
+ return snprintf(outbuf, COLWID, "%lu", rSv(VM_RSS, ul_int, pp));
+}
+
+/* pp->vm_rss * 1000 would overflow on 32-bit systems with 64 GB memory */
+static int pr_pmem(char *restrict const outbuf, const proc_t *restrict const pp){
+ unsigned long pmem;
+setREL1(VM_RSS)
+ pmem = 0;
+ pmem = rSv(VM_RSS, ul_int, pp) * 1000ULL / memory_total();
+ if (pmem > 999) pmem = 999;
+ return snprintf(outbuf, COLWID, "%2u.%u", (unsigned)(pmem/10), (unsigned)(pmem%10));
+}
+
+// Format cannot be %c as the length changes depending on locale
+#define DEFAULT_LSTART_FORMAT "%a %b %e %H:%M:%S %Y"
+static int pr_lstart(char *restrict const outbuf, const proc_t *restrict const pp){
+ time_t t;
+ struct tm start_time;
+ size_t len;
+setREL1(TICS_BEGAN)
+ t = boot_time() + rSv(TICS_BEGAN, ull_int, pp) / Hertz;
+ if (localtime_r(&t, &start_time) == NULL)
+ return 0;
+ len = strftime(outbuf, COLWID,
+ (lstart_format?lstart_format:DEFAULT_LSTART_FORMAT), &start_time);
+ if (len <= 0 || len >= COLWID)
+ outbuf[len = 0] = '\0';
+ return len;
+}
+
+/* Unix98 specifies a STIME header for a column that shows the start
+ * time of the process, but does not specify a format or format specifier.
+ * From the general Unix98 rules, we know there must not be any spaces.
+ * Most systems violate that rule, though the Solaris documentation
+ * claims to print the column without spaces. (NOT!)
+ *
+ * So this isn't broken, but could be renamed to u98_std_stime,
+ * as long as it still shows as STIME when using the -f option.
+ */
+static int pr_stime(char *restrict const outbuf, const proc_t *restrict const pp){
+ struct tm proc_time;
+ struct tm our_time;
+ time_t t;
+ const char *fmt;
+ int tm_year;
+ int tm_yday;
+ size_t len;
+setREL1(TICS_BEGAN)
+ if (localtime_r(&seconds_since_1970, &our_time) == NULL)
+ return 0;
+ tm_year = our_time.tm_year;
+ tm_yday = our_time.tm_yday;
+ t = boot_time() + rSv(TICS_BEGAN, ull_int, pp) / Hertz;
+ if (localtime_r(&t, &proc_time) == NULL)
+ return 0;
+ fmt = "%H:%M"; /* 03:02 23:59 */
+ if(tm_yday != proc_time.tm_yday) fmt = "%b%d"; /* Jun06 Aug27 */
+ if(tm_year != proc_time.tm_year) fmt = "%Y"; /* 1991 2001 */
+ len = strftime(outbuf, COLWID, fmt, &proc_time);
+ if(len <= 0 || len >= COLWID) outbuf[len = 0] = '\0';
+ return len;
+}
+
+static int pr_start(char *restrict const outbuf, const proc_t *restrict const pp){
+ time_t t;
+ char *str;
+setREL1(TICS_BEGAN)
+ t = boot_time() + rSv(TICS_BEGAN, ull_int, pp) / Hertz;
+ str = ctime(&t);
+ if(str[8]==' ') str[8]='0';
+ if(str[11]==' ') str[11]='0';
+ if((unsigned long)t+60*60*24 > (unsigned long)seconds_since_1970)
+ return snprintf(outbuf, COLWID, "%8.8s", str+11);
+ return snprintf(outbuf, COLWID, " %6.6s", str+4);
+}
+
+static int help_pr_sig(char *restrict const outbuf, const char *restrict const sig){
+ int ret;
+ const size_t len = strlen(sig);
+
+ if (signal_names) {
+ int rightward;
+ rightward = max_rightward;
+ if ( (ret = print_signame(outbuf, sig, rightward)) > 0)
+ return ret;
+ }
+
+ if(wide_signals){
+ if(len>8) return snprintf(outbuf, COLWID, "%s", sig);
+ return snprintf(outbuf, COLWID, "00000000%s", sig);
+ }
+ if(len-strspn(sig,"0") > 8)
+ return snprintf(outbuf, COLWID, "<%s", sig+len-8);
+ if(len < 8)
+ return snprintf(outbuf, COLWID, "%s%s", "00000000"+len, sig);
+ return snprintf(outbuf, COLWID, "%s", sig+len-8);
+}
+
+// This one is always thread-specific pending. (from Dragonfly BSD)
+static int pr_tsig(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SIGPENDING)
+ return help_pr_sig(outbuf, rSv(SIGPENDING, str, pp));
+}
+// This one is (wrongly?) thread-specific when printing thread lines,
+// but process-pending otherwise.
+static int pr_sig(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SIGNALS)
+ return help_pr_sig(outbuf, rSv(SIGNALS, str, pp));
+}
+static int pr_sigmask(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SIGBLOCKED)
+ return help_pr_sig(outbuf, rSv(SIGBLOCKED, str, pp));
+}
+static int pr_sigignore(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SIGIGNORE)
+ return help_pr_sig(outbuf, rSv(SIGIGNORE, str, pp));
+}
+static int pr_sigcatch(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SIGCATCH)
+ return help_pr_sig(outbuf, rSv(SIGCATCH, str, pp));
+}
+
+static int pr_uss(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SMAP_PRV_TOTAL)
+ return snprintf(outbuf, COLWID, "%lu", rSv(SMAP_PRV_TOTAL, ul_int, pp));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/*
+ * internal terms: ruid euid suid fuid
+ * kernel vars: uid euid suid fsuid
+ * command args: ruid uid svuid n/a
+ */
+
+static int pr_egid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_EGID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_EGID, u_int, pp));
+}
+static int pr_rgid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_RGID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_RGID, u_int, pp));
+}
+static int pr_sgid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_SGID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_SGID, u_int, pp));
+}
+static int pr_fgid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_FGID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_FGID, u_int, pp));
+}
+
+static int pr_euid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_EUID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_EUID, u_int, pp));
+}
+static int pr_ruid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_RUID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_RUID, u_int, pp));
+}
+static int pr_suid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_SUID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_SUID, u_int, pp));
+}
+static int pr_fuid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_FUID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_FUID, u_int, pp));
+}
+static int pr_luid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_LOGIN)
+ if(rSv(ID_LOGIN, s_int, pp)==-1) return snprintf(outbuf, COLWID, "-");
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_LOGIN, s_int, pp));
+}
+
+// The Open Group Base Specifications Issue 6 (IEEE Std 1003.1, 2004 Edition)
+// requires that user and group names print as decimal numbers if there is
+// not enough room in the column. However, we will now truncate such names
+// and provide a visual hint of such truncation. Hopefully, this will reduce
+// the volume of bug reports regarding that former 'feature'.
+//
+// The UNIX and POSIX way to change column width is to rename it:
+// ps -o pid,user=CumbersomeUserNames -o comm
+// The easy way is to directly specify the desired width:
+// ps -o pid,user:19,comm
+//
+static int do_pr_name(char *restrict const outbuf, const char *restrict const name, unsigned u){
+ if(!user_is_number){
+ int rightward = OUTBUF_SIZE; /* max cells */
+ int len; /* real cells */
+
+ escape_str(outbuf, name, OUTBUF_SIZE, &rightward);
+ len = OUTBUF_SIZE-rightward;
+
+ if(len <= (int)max_rightward)
+ return len; /* returns number of cells */
+
+ // only use '+' when not on a multi-byte char, else show uid
+ if (max_rightward >= 1 && (unsigned)outbuf[max_rightward-1] < 127) {
+ len = max_rightward-1;
+ outbuf[len++] = '+';
+ outbuf[len] = 0;
+ return len;
+ }
+ }
+ return snprintf(outbuf, COLWID, "%u", u);
+}
+
+static int pr_ruser(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(ID_RUSER,ID_RUID)
+ return do_pr_name(outbuf, rSv(ID_RUSER, str, pp), rSv(ID_RUID, u_int, pp));
+}
+static int pr_euser(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(ID_EUSER,ID_EUID)
+ return do_pr_name(outbuf, rSv(ID_EUSER, str, pp), rSv(ID_EUID, u_int, pp));
+}
+static int pr_fuser(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(ID_FUSER,ID_FUID)
+ return do_pr_name(outbuf, rSv(ID_FUSER, str, pp), rSv(ID_FUID, u_int, pp));
+}
+static int pr_suser(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(ID_SUSER,ID_SUID)
+ return do_pr_name(outbuf, rSv(ID_SUSER, str, pp), rSv(ID_SUID, u_int, pp));
+}
+static int pr_egroup(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(ID_EGROUP,ID_EGID)
+ return do_pr_name(outbuf, rSv(ID_EGROUP, str, pp), rSv(ID_EGID, u_int, pp));
+}
+static int pr_rgroup(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(ID_RGROUP,ID_RGID)
+ return do_pr_name(outbuf, rSv(ID_RGROUP, str, pp), rSv(ID_RGID, u_int, pp));
+}
+static int pr_fgroup(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(ID_FGROUP,ID_FGID)
+ return do_pr_name(outbuf, rSv(ID_FGROUP, str, pp), rSv(ID_FGID, u_int, pp));
+}
+static int pr_sgroup(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL2(ID_SGROUP,ID_SGID)
+ return do_pr_name(outbuf, rSv(ID_SGROUP, str, pp), rSv(ID_SGID, u_int, pp));
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+
+// IO stats
+static int pr_rbytes(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(IO_READ_BYTES)
+ return snprintf(outbuf, COLWID, "%lu", rSv(IO_READ_BYTES, ul_int, pp));
+}
+static int pr_rchars(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(IO_READ_CHARS)
+ return snprintf(outbuf, COLWID, "%lu", rSv(IO_READ_CHARS, ul_int, pp));
+}
+static int pr_rops(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(IO_READ_OPS)
+ return snprintf(outbuf, COLWID, "%lu", rSv(IO_READ_OPS, ul_int, pp));
+}
+static int pr_wbytes(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(IO_WRITE_BYTES)
+ return snprintf(outbuf, COLWID, "%lu", rSv(IO_WRITE_BYTES, ul_int, pp));
+}
+static int pr_wcbytes(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(IO_WRITE_CBYTES)
+ return snprintf(outbuf, COLWID, "%lu", rSv(IO_WRITE_CBYTES, ul_int, pp));
+}
+static int pr_wchars(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(IO_WRITE_CHARS)
+ return snprintf(outbuf, COLWID, "%lu", rSv(IO_WRITE_CHARS, ul_int, pp));
+}
+static int pr_wops(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(IO_WRITE_OPS)
+ return snprintf(outbuf, COLWID, "%lu", rSv(IO_WRITE_OPS, ul_int, pp));
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+
+// PID pid, TGID tgid
+static int pr_procs(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_TGID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_TGID, s_int, pp));
+}
+// LWP lwp, SPID spid, TID tid
+static int pr_tasks(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_PID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_PID, s_int, pp));
+}
+// thcount THCNT
+static int pr_nlwp(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(NLWP)
+ return snprintf(outbuf, COLWID, "%d", rSv(NLWP, s_int, pp));
+}
+
+static int pr_sess(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_SESSION)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_SESSION, s_int, pp));
+}
+
+static int pr_supgid(char *restrict const outbuf, const proc_t *restrict const pp){
+ int rightward;
+setREL1(SUPGIDS)
+ rightward = max_rightward;
+ escaped_copy(outbuf, rSv(SUPGIDS, str, pp), OUTBUF_SIZE, &rightward);
+ return max_rightward-rightward;
+}
+
+static int pr_supgrp(char *restrict const outbuf, const proc_t *restrict const pp){
+ int rightward;
+setREL1(SUPGROUPS)
+ rightward = max_rightward;
+ escape_str(outbuf, rSv(SUPGROUPS, str, pp), OUTBUF_SIZE, &rightward);
+ return max_rightward-rightward;
+}
+
+static int pr_tpgid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(ID_TPGID)
+ return snprintf(outbuf, COLWID, "%d", rSv(ID_TPGID, s_int, pp));
+}
+
+/* SGI uses "cpu" to print the processor ID with header "P" */
+static int pr_sgi_p(char *restrict const outbuf, const proc_t *restrict const pp){ /* FIXME */
+setREL2(STATE,PROCESSOR)
+ if(rSv(STATE, s_ch, pp) == 'R') return snprintf(outbuf, COLWID, "%u", rSv(PROCESSOR, u_int, pp));
+ return snprintf(outbuf, COLWID, "*");
+}
+
+/* full path to executable */
+static int pr_exe(char *restrict const outbuf, const proc_t *restrict const pp){
+ int rightward;
+setREL1(EXE)
+ rightward = max_rightward;
+ escape_str(outbuf, rSv(EXE, str, pp), OUTBUF_SIZE, &rightward);
+ return max_rightward-rightward;
+}
+
+/* %cpu utilization over task lifetime, as ##.### format */
+static int pr_utilization(char *restrict const outbuf, const proc_t *restrict const pp){
+double cu;
+setREL1(UTILIZATION)
+ cu = rSv(UTILIZATION, real, pp);
+ /* this check is really just for us (the ps program) since we will be very
+ short lived and the library might reflect 100% or even more utilization */
+ if (cu > 99.0) cu = 99.999;
+ return snprintf(outbuf, COLWID, "%#.3f", cu);
+}
+
+/* %cpu utilization (plus dead children) over task lifetime, as ##.### format */
+static int pr_utilization_c(char *restrict const outbuf, const proc_t *restrict const pp){
+double cu;
+setREL1(UTILIZATION_C)
+ cu = rSv(UTILIZATION_C, real, pp);
+ /* this check is really just for us (the ps program) since we will be very
+ short lived and the library might reflect 100% or even more utilization */
+ if (cu > 99.0) cu = 99.999;
+ return snprintf(outbuf, COLWID, "%#.3f", cu);
+}
+
+/************************* Systemd stuff ********************************/
+static int pr_sd_unit(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SD_UNIT)
+ return snprintf(outbuf, COLWID, "%s", rSv(SD_UNIT, str, pp));
+}
+
+static int pr_sd_session(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SD_SESS)
+ return snprintf(outbuf, COLWID, "%s", rSv(SD_SESS, str, pp));
+}
+
+static int pr_sd_ouid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SD_OUID)
+ return snprintf(outbuf, COLWID, "%s", rSv(SD_OUID, str, pp));
+}
+
+static int pr_sd_machine(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SD_MACH)
+ return snprintf(outbuf, COLWID, "%s", rSv(SD_MACH, str, pp));
+}
+
+static int pr_sd_uunit(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SD_UUNIT)
+ return snprintf(outbuf, COLWID, "%s", rSv(SD_UUNIT, str, pp));
+}
+
+static int pr_sd_seat(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SD_SEAT)
+ return snprintf(outbuf, COLWID, "%s", rSv(SD_SEAT, str, pp));
+}
+
+static int pr_sd_slice(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(SD_SLICE)
+ return snprintf(outbuf, COLWID, "%s", rSv(SD_SLICE, str, pp));
+}
+/************************ Linux namespaces ******************************/
+
+#define _pr_ns(NAME, ID)\
+static int pr_##NAME(char *restrict const outbuf, const proc_t *restrict const pp) {\
+ setREL1(NS_ ## ID) \
+ if (rSv(NS_ ## ID, ul_int, pp)) \
+ return snprintf(outbuf, COLWID, "%lu", rSv(NS_ ## ID, ul_int, pp)); \
+ else \
+ return snprintf(outbuf, COLWID, "-"); \
+}
+_pr_ns(cgroupns, CGROUP);
+_pr_ns(ipcns, IPC);
+_pr_ns(mntns, MNT);
+_pr_ns(netns, NET);
+_pr_ns(pidns, PID);
+_pr_ns(timens, TIME);
+_pr_ns(userns, USER);
+_pr_ns(utsns, UTS);
+#undef _pr_ns
+
+/************************ Linux containers ******************************/
+static int pr_lxcname(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(LXCNAME)
+ return snprintf(outbuf, COLWID, "%s", rSv(LXCNAME, str, pp));
+}
+
+/****************** FLASK & seLinux security stuff **********************/
+// move the bulk of this to libproc sometime
+// This needs more study, considering:
+// 1. the static linking option (maybe disable this in that case)
+// 2. the -z and -Z option issue
+// 3. width of output
+static int pr_context(char *restrict const outbuf, const proc_t *restrict const pp){
+ static void (*ps_freecon)(char*);
+ static int (*ps_getpidcon)(pid_t pid, char **context);
+#if ENABLE_LIBSELINUX
+ static int (*ps_is_selinux_enabled)(void);
+ static int tried_load;
+#endif
+ static int selinux_enabled;
+ size_t len;
+ char *context;
+setREL1(ID_TGID)
+
+#if ENABLE_LIBSELINUX
+ if(!ps_getpidcon && !tried_load){
+ void *handle = dlopen("libselinux.so.1", RTLD_NOW);
+ if(handle){
+ ps_freecon = dlsym(handle, "freecon");
+ if(dlerror())
+ ps_freecon = 0;
+ dlerror();
+ ps_getpidcon = dlsym(handle, "getpidcon");
+ if(dlerror())
+ ps_getpidcon = 0;
+ ps_is_selinux_enabled = dlsym(handle, "is_selinux_enabled");
+ if(dlerror())
+ ps_is_selinux_enabled = 0;
+ else
+ selinux_enabled = ps_is_selinux_enabled();
+ }
+ tried_load++;
+ }
+#endif
+ if(ps_getpidcon && selinux_enabled && !ps_getpidcon(rSv(ID_TGID, s_int, pp), &context)){
+ size_t max_len = OUTBUF_SIZE-1;
+ len = strlen(context);
+ if(len > max_len) len = max_len;
+ memcpy(outbuf, context, len);
+ if (len >= 1 && outbuf[len-1] == '\n') --len;
+ outbuf[len] = '\0';
+ ps_freecon(context);
+ }else{
+ char filename[48];
+ ssize_t num_read;
+ int fd;
+
+ snprintf(filename, sizeof filename, "/proc/%d/attr/current", rSv(ID_TGID, s_int, pp));
+
+ if ((fd = open(filename, O_RDONLY, 0)) != -1) {
+ num_read = read(fd, outbuf, OUTBUF_SIZE-1);
+ close(fd);
+ if (num_read > 0) {
+ outbuf[num_read] = '\0';
+ len = 0;
+ while(isprint(outbuf[len]))
+ len++;
+ outbuf[len] = '\0';
+ if(len)
+ return len;
+ }
+ }
+ outbuf[0] = '-';
+ outbuf[1] = '\0';
+ len = 1;
+ }
+ return len;
+}
+
+/************************ Linux autogroups ******************************/
+static int pr_agid(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(AUTOGRP_ID)
+ return snprintf(outbuf, COLWID, "%d", rSv(AUTOGRP_ID, s_int, pp));
+}
+static int pr_agnice(char *restrict const outbuf, const proc_t *restrict const pp){
+setREL1(AUTOGRP_NICE)
+ return snprintf(outbuf, COLWID, "%d", rSv(AUTOGRP_NICE, s_int, pp));
+}
+
+////////////////////////////// Test code /////////////////////////////////
+
+// like "args"
+static int pr_t_unlimited(char *restrict const outbuf, const proc_t *restrict const pp){
+ static const char *const vals[] = {"[123456789-12345] <defunct>","ps","123456789-123456"};
+ if (!outbuf) return 0;
+ (void)pp;
+ snprintf(outbuf, max_rightward+1, "%s", vals[lines_to_next_header%3u]);
+ return strlen(outbuf);
+}
+static int pr_t_unlimited2(char *restrict const outbuf, const proc_t *restrict const pp){
+ static const char *const vals[] = {"unlimited", "[123456789-12345] <defunct>","ps","123456789-123456"};
+ if (!outbuf) return 0;
+ (void)pp;
+ snprintf(outbuf, max_rightward+1, "%s", vals[lines_to_next_header%4u]);
+ return strlen(outbuf);
+}
+
+// like "etime"
+static int pr_t_right(char *restrict const outbuf, const proc_t *restrict const pp){
+ static const char *const vals[] = {"999-23:59:59","99-23:59:59","9-23:59:59","59:59"};
+ if (!outbuf) return 0;
+ (void)pp;
+ return snprintf(outbuf, COLWID, "%s", vals[lines_to_next_header%4u]);
+}
+static int pr_t_right2(char *restrict const outbuf, const proc_t *restrict const pp){
+ static const char *const vals[] = {"999-23:59:59","99-23:59:59","9-23:59:59"};
+ if (!outbuf) return 0;
+ (void)pp;
+ return snprintf(outbuf, COLWID, "%s", vals[lines_to_next_header%3u]);
+}
+
+// like "tty"
+static int pr_t_left(char *restrict const outbuf, const proc_t *restrict const pp){
+ static const char *const vals[] = {"tty7","pts/9999","iseries/vtty42","ttySMX0","3270/tty4"};
+ if (!outbuf) return 0;
+ (void)pp;
+ return snprintf(outbuf, COLWID, "%s", vals[lines_to_next_header%5u]);
+}
+static int pr_t_left2(char *restrict const outbuf, const proc_t *restrict const pp){
+ static const char *const vals[] = {"tty7","pts/9999","ttySMX0","3270/tty4"};
+ if (!outbuf) return 0;
+ (void)pp;
+ return snprintf(outbuf, COLWID, "%s", vals[lines_to_next_header%4u]);
+}
+
+/***************************************************************************/
+/*************************** other stuff ***********************************/
+
+/*
+ * Old header specifications.
+ *
+ * short Up " PID TTY STAT TIME COMMAND"
+ * long l Pp " FLAGS UID PID PPID PRI NI SIZE RSS WCHAN STA TTY TIME COMMAND
+ * user u up "USER PID %CPU %MEM SIZE RSS TTY STAT START TIME COMMAND
+ * jobs j gPp " PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
+ * sig s p " UID PID SIGNAL BLOCKED IGNORED CATCHED STAT TTY TIME COMMAND
+ * vm v r " PID TTY STAT TIME PAGEIN TSIZ DSIZ RSS LIM %MEM COMMAND
+ * m m r " PID TTY MAJFLT MINFLT TRS DRS SIZE SWAP RSS SHRD LIB DT COMMAND
+ * regs X p "NR PID STACK ESP EIP TMOUT ALARM STAT TTY TIME COMMAND
+ */
+
+/*
+ * Unix98 requires that the heading for tty is TT, though XPG4, Digital,
+ * and BSD use TTY. The Unix98 headers are:
+ * args,comm,etime,group,nice,pcpu,pgid
+ * pid,ppid,rgroup,ruser,time,tty,user,vsz
+ *
+ * BSD c: "command" becomes accounting name ("comm" or "ucomm")
+ * BSD n: "user" becomes "uid" and "wchan" becomes "nwchan" (number)
+ */
+
+/* Justification control for flags field. */
+#define USER CF_USER // left if text, right if numeric
+#define LEFT CF_LEFT
+#define RIGHT CF_RIGHT
+#define UNLIMITED CF_UNLIMITED
+#define WCHAN CF_WCHAN // left if text, right if numeric
+#define SIGNAL CF_SIGNAL // right in 9, or 16 if room
+#define PIDMAX CF_PIDMAX
+#define TO CF_PRINT_THREAD_ONLY
+#define PO CF_PRINT_PROCESS_ONLY
+#define ET CF_PRINT_EVERY_TIME
+#define AN CF_PRINT_AS_NEEDED // no idea
+
+
+/* TODO
+ * pull out annoying BSD aliases into another table (to macro table?)
+ * add sorting functions here (to unify names)
+ */
+
+/* temporary hack -- mark new stuff grabbed from Debian ps */
+#define LNx LNX
+
+/* Note: upon conversion to the <pids> API the numerous former sort provisions
+ for otherwise non-printable fields (pr_nop) have been retained. And,
+ since the new library can sort on any item, many previously printable
+ but unsortable fields have now been made sortable. */
+/* there are about 211 listed */
+/* Many of these are placeholders for unsupported options. */
+static const format_struct format_array[] = { /*
+ .spec .head .pr .sr .width .vendor .flags */
+{"%cpu", "%CPU", pr_pcpu, PIDS_UTILIZATION, 4, BSD, ET|RIGHT}, /*pcpu*/
+{"%mem", "%MEM", pr_pmem, PIDS_VM_RSS, 4, BSD, PO|RIGHT}, /*pmem*/
+{"_left", "LLLLLLLL", pr_t_left, PIDS_noop, 8, TST, ET|LEFT},
+{"_left2", "L2L2L2L2", pr_t_left2, PIDS_noop, 8, TST, ET|LEFT},
+{"_right", "RRRRRRRRRRR", pr_t_right, PIDS_noop, 11, TST, ET|RIGHT},
+{"_right2", "R2R2R2R2R2R", pr_t_right2, PIDS_noop, 11, TST, ET|RIGHT},
+{"_unlimited","U", pr_t_unlimited, PIDS_noop, 16, TST, ET|UNLIMITED},
+{"_unlimited2","U2", pr_t_unlimited2, PIDS_noop, 16, TST, ET|UNLIMITED},
+{"acflag", "ACFLG", pr_nop, PIDS_noop, 5, XXX, AN|RIGHT}, /*acflg*/
+{"acflg", "ACFLG", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT}, /*acflag*/
+{"addr", "ADDR", pr_nop, PIDS_noop, 4, XXX, AN|RIGHT},
+{"addr_1", "ADDR", pr_nop, PIDS_noop, 1, LNX, AN|LEFT},
+{"ag_id", "AGID", pr_agid, PIDS_AUTOGRP_ID, 5, LNX, AN|RIGHT},
+{"ag_nice", "AGNI", pr_agnice, PIDS_AUTOGRP_NICE, 4, LNX, AN|RIGHT},
+{"alarm", "ALARM", pr_nop, PIDS_noop, 5, LNX, AN|RIGHT},
+{"argc", "ARGC", pr_nop, PIDS_noop, 4, LNX, PO|RIGHT},
+{"args", "COMMAND", pr_args, PIDS_CMDLINE, 27, U98, PO|UNLIMITED}, /*command*/
+{"atime", "TIME", pr_time, PIDS_TIME_ALL, 8, SOE, ET|RIGHT}, /*cputime*/ /* was 6 wide */
+{"blocked", "BLOCKED", pr_sigmask, PIDS_SIGBLOCKED, 9, BSD, TO|SIGNAL},/*sigmask*/
+{"bnd", "BND", pr_nop, PIDS_noop, 1, AIX, TO|RIGHT},
+{"bsdstart", "START", pr_bsdstart, PIDS_TICS_BEGAN, 6, LNX, ET|RIGHT},
+{"bsdtime", "TIME", pr_bsdtime, PIDS_TICS_ALL, 6, LNX, ET|RIGHT},
+{"c", "C", pr_c, PIDS_UTILIZATION, 2, SUN, ET|RIGHT},
+{"caught", "CAUGHT", pr_sigcatch, PIDS_SIGCATCH, 9, BSD, TO|SIGNAL}, /*sigcatch*/
+{"cgname", "CGNAME", pr_cgname, PIDS_CGNAME, 27, LNX, PO|UNLIMITED},
+{"cgroup", "CGROUP", pr_cgroup, PIDS_CGROUP, 27, LNX, PO|UNLIMITED},
+{"cgroupns", "CGROUPNS",pr_cgroupns, PIDS_NS_CGROUP, 10, LNX, ET|RIGHT},
+{"class", "CLS", pr_class, PIDS_SCHED_CLASS, 3, XXX, TO|LEFT},
+{"cls", "CLS", pr_class, PIDS_SCHED_CLASS, 3, HPU, TO|RIGHT}, /*says HPUX or RT*/
+{"cmaj_flt", "-", pr_nop, PIDS_noop, 1, LNX, AN|RIGHT},
+{"cmd", "CMD", pr_args, PIDS_CMDLINE, 27, DEC, PO|UNLIMITED}, /*ucomm*/
+{"cmin_flt", "-", pr_nop, PIDS_noop, 1, LNX, AN|RIGHT},
+{"cnswap", "-", pr_nop, PIDS_noop, 1, LNX, AN|RIGHT},
+{"comm", "COMMAND", pr_comm, PIDS_CMD, 15, U98, PO|UNLIMITED}, /*ucomm*/
+{"command", "COMMAND", pr_args, PIDS_CMDLINE, 27, XXX, PO|UNLIMITED}, /*args*/
+{"context", "CONTEXT", pr_context, PIDS_ID_TGID, 31, LNX, ET|LEFT},
+{"cp", "CP", pr_cp, PIDS_UTILIZATION, 3, DEC, ET|RIGHT}, /*cpu*/
+{"cpu", "CPU", pr_nop, PIDS_noop, 3, BSD, AN|RIGHT}, /* FIXME ... HP-UX wants this as the CPU number for SMP? */
+{"cpuid", "CPUID", pr_psr, PIDS_PROCESSOR, 5, BSD, TO|RIGHT}, // OpenBSD: 8 wide!
+{"cputime", "TIME", pr_time, PIDS_TIME_ALL, 8, DEC, ET|RIGHT}, /*time*/
+{"cputimes", "TIME", pr_times, PIDS_TIME_ALL, 8, LNX, ET|RIGHT}, /*time*/
+{"ctid", "CTID", pr_nop, PIDS_noop, 5, SUN, ET|RIGHT}, // resource contracts?
+{"cuc", "%CUC", pr_utilization_c, PIDS_UTILIZATION_C, 7, XXX, AN|RIGHT},
+{"cursig", "CURSIG", pr_nop, PIDS_noop, 6, DEC, AN|RIGHT},
+{"cutime", "-", pr_nop, PIDS_TICS_USER_C, 1, LNX, AN|RIGHT},
+{"cuu", "%CUU", pr_utilization, PIDS_UTILIZATION, 6, XXX, AN|RIGHT},
+{"cwd", "CWD", pr_nop, PIDS_noop, 3, LNX, AN|LEFT},
+{"drs", "DRS", pr_drs, PIDS_VSIZE_BYTES, 5, LNX, PO|RIGHT},
+{"dsiz", "DSIZ", pr_dsiz, PIDS_VSIZE_BYTES, 4, LNX, PO|RIGHT},
+{"egid", "EGID", pr_egid, PIDS_ID_EGID, 5, LNX, ET|RIGHT},
+{"egroup", "EGROUP", pr_egroup, PIDS_ID_EGROUP, 8, LNX, ET|USER},
+{"eip", "EIP", pr_eip, PIDS_ADDR_CURR_EIP, (int)(2*sizeof(long)), LNX, TO|RIGHT},
+{"emul", "EMUL", pr_nop, PIDS_noop, 13, BSD, PO|LEFT}, /* "FreeBSD ELF32" and such */
+{"end_code", "E_CODE", pr_nop, PIDS_ADDR_CODE_END, (int)(2*sizeof(long)), LNx, PO|RIGHT}, // sortable, but unprintable ??
+{"environ","ENVIRONMENT",pr_nop, PIDS_noop, 11, LNx, PO|UNLIMITED},
+{"esp", "ESP", pr_esp, PIDS_ADDR_CURR_ESP, (int)(2*sizeof(long)), LNX, TO|RIGHT},
+{"etime", "ELAPSED", pr_etime, PIDS_TIME_ELAPSED, 11, U98, ET|RIGHT}, /* was 7 wide */
+{"etimes", "ELAPSED", pr_etimes, PIDS_TIME_ELAPSED, 7, BSD, ET|RIGHT}, /* FreeBSD */
+{"euid", "EUID", pr_euid, PIDS_ID_EUID, 5, LNX, ET|RIGHT},
+{"euser", "EUSER", pr_euser, PIDS_ID_EUSER, 8, LNX, ET|USER},
+{"exe", "EXE", pr_exe, PIDS_EXE, 27, LNX, PO|UNLIMITED},
+{"f", "F", pr_flag, PIDS_FLAGS, 1, XXX, ET|RIGHT}, /*flags*/
+{"fgid", "FGID", pr_fgid, PIDS_FLAGS, 5, LNX, ET|RIGHT},
+{"fgroup", "FGROUP", pr_fgroup, PIDS_ID_FGROUP, 8, LNX, ET|USER},
+{"flag", "F", pr_flag, PIDS_FLAGS, 1, DEC, ET|RIGHT},
+{"flags", "F", pr_flag, PIDS_FLAGS, 1, BSD, ET|RIGHT}, /*f*/ /* was FLAGS, 8 wide */
+{"fname", "COMMAND", pr_fname, PIDS_CMD, 8, SUN, PO|LEFT},
+{"fsgid", "FSGID", pr_fgid, PIDS_ID_FGID, 5, LNX, ET|RIGHT},
+{"fsgroup", "FSGROUP", pr_fgroup, PIDS_ID_FGROUP, 8, LNX, ET|USER},
+{"fsuid", "FSUID", pr_fuid, PIDS_ID_FUID, 5, LNX, ET|RIGHT},
+{"fsuser", "FSUSER", pr_fuser, PIDS_ID_FUSER, 8, LNX, ET|USER},
+{"fuid", "FUID", pr_fuid, PIDS_ID_FUID, 5, LNX, ET|RIGHT},
+{"fuser", "FUSER", pr_fuser, PIDS_ID_FUSER, 8, LNX, ET|USER},
+{"gid", "GID", pr_egid, PIDS_ID_EGID, 5, SUN, ET|RIGHT},
+{"group", "GROUP", pr_egroup, PIDS_ID_EGROUP, 8, U98, ET|USER},
+{"ignored", "IGNORED", pr_sigignore, PIDS_SIGIGNORE, 9, BSD, TO|SIGNAL},/*sigignore*/
+{"inblk", "INBLK", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT}, /*inblock*/
+{"inblock", "INBLK", pr_nop, PIDS_noop, 5, DEC, AN|RIGHT}, /*inblk*/
+{"intpri", "PRI", pr_opri, PIDS_PRIORITY, 3, HPU, TO|RIGHT},
+{"ipcns", "IPCNS", pr_ipcns, PIDS_NS_IPC, 10, LNX, ET|RIGHT},
+{"jid", "JID", pr_nop, PIDS_noop, 1, SGI, PO|RIGHT},
+{"jobc", "JOBC", pr_nop, PIDS_noop, 4, XXX, AN|RIGHT},
+{"ktrace", "KTRACE", pr_nop, PIDS_noop, 8, BSD, AN|RIGHT},
+{"ktracep", "KTRACEP", pr_nop, PIDS_noop, 8, BSD, AN|RIGHT},
+{"label", "LABEL", pr_context, PIDS_ID_TGID, 31, SGI, ET|LEFT},
+{"lastcpu", "C", pr_psr, PIDS_PROCESSOR, 3, BSD, TO|RIGHT}, // DragonFly
+{"lim", "LIM", pr_lim, PIDS_RSS_RLIM, 5, BSD, AN|RIGHT},
+{"login", "LOGNAME", pr_nop, PIDS_noop, 8, BSD, AN|LEFT}, /*logname*/ /* double check */
+{"logname", "LOGNAME", pr_nop, PIDS_noop, 8, XXX, AN|LEFT}, /*login*/
+{"longtname", "TTY", pr_tty8, PIDS_TTY_NAME, 8, DEC, PO|LEFT},
+{"lsession", "SESSION", pr_sd_session, PIDS_SD_SESS, 11, LNX, ET|LEFT},
+{"lstart", "STARTED", pr_lstart, PIDS_TICS_BEGAN, 24, XXX, ET|RIGHT},
+{"luid", "LUID", pr_luid, PIDS_ID_LOGIN, 5, LNX, ET|RIGHT}, /* login ID */
+{"luser", "LUSER", pr_nop, PIDS_noop, 8, LNX, ET|USER}, /* login USER */
+{"lwp", "LWP", pr_tasks, PIDS_ID_PID, 5, SUN, TO|PIDMAX|RIGHT},
+{"lxc", "LXC", pr_lxcname, PIDS_LXCNAME, 8, LNX, ET|LEFT},
+{"m_drs", "DRS", pr_drs, PIDS_VSIZE_BYTES, 5, LNx, PO|RIGHT},
+{"m_dt", "DT", pr_nop, PIDS_noop, 4, LNx, PO|RIGHT},
+{"m_lrs", "LRS", pr_nop, PIDS_noop, 5, LNx, PO|RIGHT},
+{"m_resident", "RES", pr_nop, PIDS_MEM_RES_PGS, 5, LNx, PO|RIGHT},
+{"m_share", "SHRD", pr_nop, PIDS_MEM_SHR_PGS, 5, LNx, PO|RIGHT},
+{"m_size", "SIZE", pr_size, PIDS_VSIZE_BYTES, 5, LNX, PO|RIGHT},
+{"m_swap", "SWAP", pr_nop, PIDS_noop, 5, LNx, PO|RIGHT},
+{"m_trs", "TRS", pr_trs, PIDS_VSIZE_BYTES, 5, LNx, PO|RIGHT},
+{"machine", "MACHINE", pr_sd_machine, PIDS_SD_MACH, 31, LNX, ET|LEFT},
+{"maj_flt", "MAJFL", pr_majflt, PIDS_FLT_MAJ, 6, LNX, AN|RIGHT},
+{"majflt", "MAJFLT", pr_majflt, PIDS_FLT_MAJ, 6, XXX, AN|RIGHT},
+{"min_flt", "MINFL", pr_minflt, PIDS_FLT_MIN, 6, LNX, AN|RIGHT},
+{"minflt", "MINFLT", pr_minflt, PIDS_FLT_MIN, 6, XXX, AN|RIGHT},
+{"mntns", "MNTNS", pr_mntns, PIDS_NS_MNT, 10, LNX, ET|RIGHT},
+{"msgrcv", "MSGRCV", pr_nop, PIDS_noop, 6, XXX, AN|RIGHT},
+{"msgsnd", "MSGSND", pr_nop, PIDS_noop, 6, XXX, AN|RIGHT},
+{"mwchan", "MWCHAN", pr_nop, PIDS_noop, 6, BSD, TO|WCHAN}, /* mutex (FreeBSD) */
+{"netns", "NETNS", pr_netns, PIDS_NS_NET, 10, LNX, ET|RIGHT},
+{"ni", "NI", pr_nice, PIDS_NICE, 3, BSD, TO|RIGHT}, /*nice*/
+{"nice", "NI", pr_nice, PIDS_NICE, 3, U98, TO|RIGHT}, /*ni*/
+{"nivcsw", "IVCSW", pr_nop, PIDS_noop, 5, XXX, AN|RIGHT},
+{"nlwp", "NLWP", pr_nlwp, PIDS_NLWP, 4, SUN, PO|RIGHT},
+{"nsignals", "NSIGS", pr_nop, PIDS_noop, 5, DEC, AN|RIGHT}, /*nsigs*/
+{"nsigs", "NSIGS", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT}, /*nsignals*/
+{"nswap", "NSWAP", pr_nop, PIDS_noop, 5, XXX, AN|RIGHT},
+{"numa", "NUMA", pr_numa, PIDS_PROCESSOR_NODE, 4, XXX, AN|RIGHT},
+{"nvcsw", "VCSW", pr_nop, PIDS_noop, 5, XXX, AN|RIGHT},
+{"nwchan", "WCHAN", pr_nop, PIDS_noop, 6, XXX, TO|RIGHT},
+{"oom", "OOM", pr_oom, PIDS_OOM_SCORE, 4, XXX, TO|RIGHT},
+{"oomadj", "OOMADJ", pr_oom_adj, PIDS_OOM_ADJ, 5, XXX, TO|RIGHT},
+{"opri", "PRI", pr_opri, PIDS_PRIORITY, 3, SUN, TO|RIGHT},
+{"osz", "SZ", pr_nop, PIDS_noop, 2, SUN, PO|RIGHT},
+{"oublk", "OUBLK", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT}, /*oublock*/
+{"oublock", "OUBLK", pr_nop, PIDS_noop, 5, DEC, AN|RIGHT}, /*oublk*/
+{"ouid", "OWNER", pr_sd_ouid, PIDS_SD_OUID, 5, LNX, ET|LEFT},
+{"p_ru", "P_RU", pr_nop, PIDS_noop, 6, BSD, AN|RIGHT},
+{"paddr", "PADDR", pr_nop, PIDS_noop, 6, BSD, AN|RIGHT},
+{"pagein", "PAGEIN", pr_majflt, PIDS_FLT_MAJ, 6, XXX, AN|RIGHT},
+{"pcpu", "%CPU", pr_pcpu, PIDS_UTILIZATION, 4, U98, ET|RIGHT}, /*%cpu*/
+{"pending", "PENDING", pr_sig, PIDS_SIGNALS, 9, BSD, ET|SIGNAL}, /*sig*/
+{"pgid", "PGID", pr_pgid, PIDS_ID_PGRP, 5, U98, PO|PIDMAX|RIGHT},
+{"pgrp", "PGRP", pr_pgid, PIDS_ID_PGRP, 5, LNX, PO|PIDMAX|RIGHT},
+{"pid", "PID", pr_procs, PIDS_ID_TGID, 5, U98, PO|PIDMAX|RIGHT},
+{"pidns", "PIDNS", pr_pidns, PIDS_NS_PID, 10, LNX, ET|RIGHT},
+{"pmem", "%MEM", pr_pmem, PIDS_VM_RSS, 4, XXX, PO|RIGHT}, /* %mem */
+{"poip", "-", pr_nop, PIDS_noop, 1, BSD, AN|RIGHT},
+{"policy", "POL", pr_class, PIDS_SCHED_CLASS, 3, DEC, TO|LEFT},
+{"ppid", "PPID", pr_ppid, PIDS_ID_PPID, 5, U98, PO|PIDMAX|RIGHT},
+{"pri", "PRI", pr_pri, PIDS_PRIORITY, 3, XXX, TO|RIGHT},
+{"pri_api", "API", pr_pri_api, PIDS_PRIORITY, 3, LNX, TO|RIGHT},
+{"pri_bar", "BAR", pr_pri_bar, PIDS_PRIORITY, 3, LNX, TO|RIGHT},
+{"pri_baz", "BAZ", pr_pri_baz, PIDS_PRIORITY, 3, LNX, TO|RIGHT},
+{"pri_foo", "FOO", pr_pri_foo, PIDS_PRIORITY, 3, LNX, TO|RIGHT},
+{"priority", "PRI", pr_priority, PIDS_PRIORITY, 3, LNX, TO|RIGHT},
+{"prmgrp", "PRMGRP", pr_nop, PIDS_noop, 12, HPU, PO|RIGHT},
+{"prmid", "PRMID", pr_nop, PIDS_noop, 12, HPU, PO|RIGHT},
+{"project", "PROJECT", pr_nop, PIDS_noop, 12, SUN, PO|LEFT}, // see prm* andctid
+{"projid", "PROJID", pr_nop, PIDS_noop, 5, SUN, PO|RIGHT},
+{"pset", "PSET", pr_nop, PIDS_noop, 4, DEC, TO|RIGHT},
+{"psr", "PSR", pr_psr, PIDS_PROCESSOR, 3, DEC, TO|RIGHT},
+{"pss", "PSS", pr_pss, PIDS_SMAP_PSS, 5, XXX, PO|RIGHT},
+{"psxpri", "PPR", pr_nop, PIDS_noop, 3, DEC, TO|RIGHT},
+{"rbytes", "RBYTES", pr_rbytes, PIDS_IO_READ_BYTES, 5, LNX, TO|RIGHT},
+{"rchars", "RCHARS", pr_rchars, PIDS_IO_READ_CHARS, 5, LNX, TO|RIGHT},
+{"re", "RE", pr_nop, PIDS_noop, 3, BSD, AN|RIGHT},
+{"resident", "RES", pr_nop, PIDS_MEM_RES_PGS, 5, LNX, PO|RIGHT},
+{"rgid", "RGID", pr_rgid, PIDS_ID_RGID, 5, XXX, ET|RIGHT},
+{"rgroup", "RGROUP", pr_rgroup, PIDS_ID_RGROUP, 8, U98, ET|USER}, /* was 8 wide */
+{"rlink", "RLINK", pr_nop, PIDS_noop, 8, BSD, AN|RIGHT},
+{"rops", "ROPS", pr_rops, PIDS_IO_READ_OPS, 5, LNX, TO|RIGHT},
+{"rss", "RSS", pr_rss, PIDS_VM_RSS, 5, XXX, PO|RIGHT}, /* was 5 wide */
+{"rssize", "RSS", pr_rss, PIDS_VM_RSS, 5, DEC, PO|RIGHT}, /*rsz*/
+{"rsz", "RSZ", pr_rss, PIDS_VM_RSS, 5, BSD, PO|RIGHT}, /*rssize*/
+{"rtprio", "RTPRIO", pr_rtprio, PIDS_PRIORITY_RT, 6, BSD, TO|RIGHT},
+{"ruid", "RUID", pr_ruid, PIDS_ID_RUID, 5, XXX, ET|RIGHT},
+{"ruser", "RUSER", pr_ruser, PIDS_ID_RUSER, 8, U98, ET|USER},
+{"s", "S", pr_s, PIDS_STATE, 1, SUN, TO|LEFT}, /*stat,state*/
+{"sched", "SCH", pr_sched, PIDS_SCHED_CLASS, 3, AIX, TO|RIGHT},
+{"scnt", "SCNT", pr_nop, PIDS_noop, 4, DEC, AN|RIGHT}, /* man page misspelling of scount? */
+{"scount", "SC", pr_nop, PIDS_noop, 4, AIX, AN|RIGHT}, /* scnt==scount, DEC claims both */
+{"seat", "SEAT", pr_sd_seat, PIDS_SD_SEAT, 11, LNX, ET|LEFT},
+{"sess", "SESS", pr_sess, PIDS_ID_SESSION, 5, XXX, PO|PIDMAX|RIGHT},
+{"session", "SESS", pr_sess, PIDS_ID_SESSION, 5, LNX, PO|PIDMAX|RIGHT},
+{"sgi_p", "P", pr_sgi_p, PIDS_STATE, 1, LNX, TO|RIGHT}, /* "cpu" number */
+{"sgi_rss", "RSS", pr_rss, PIDS_VM_RSS, 4, LNX, PO|LEFT}, /* SZ:RSS */
+{"sgid", "SGID", pr_sgid, PIDS_ID_SGID, 5, LNX, ET|RIGHT},
+{"sgroup", "SGROUP", pr_sgroup, PIDS_ID_SGROUP, 8, LNX, ET|USER},
+{"share", "-", pr_nop, PIDS_noop, 1, LNX, PO|RIGHT},
+{"sid", "SID", pr_sess, PIDS_ID_SESSION, 5, XXX, PO|PIDMAX|RIGHT}, /* Sun & HP */
+{"sig", "PENDING", pr_sig, PIDS_SIGNALS, 9, XXX, ET|SIGNAL}, /*pending -- Dragonfly uses this for whole-proc and "tsig" for thread */
+{"sig_block", "BLOCKED", pr_sigmask, PIDS_SIGBLOCKED, 9, LNX, TO|SIGNAL},
+{"sig_catch", "CATCHED", pr_sigcatch, PIDS_SIGCATCH, 9, LNX, TO|SIGNAL},
+{"sig_ignore", "IGNORED",pr_sigignore, PIDS_SIGIGNORE, 9, LNX, TO|SIGNAL},
+{"sig_pend", "SIGNAL", pr_sig, PIDS_SIGNALS, 9, LNX, ET|SIGNAL},
+{"sigcatch", "CAUGHT", pr_sigcatch, PIDS_SIGCATCH, 9, XXX, TO|SIGNAL}, /*caught*/
+{"sigignore", "IGNORED", pr_sigignore, PIDS_SIGIGNORE, 9, XXX, TO|SIGNAL}, /*ignored*/
+{"sigmask", "BLOCKED", pr_sigmask, PIDS_SIGBLOCKED, 9, XXX, TO|SIGNAL}, /*blocked*/
+{"size", "SIZE", pr_swapable, PIDS_VSIZE_BYTES, 5, SCO, PO|RIGHT},
+{"sl", "SL", pr_nop, PIDS_noop, 3, XXX, AN|RIGHT},
+{"slice", "SLICE", pr_sd_slice, PIDS_SD_SLICE, 31, LNX, ET|LEFT},
+{"spid", "SPID", pr_tasks, PIDS_ID_PID, 5, SGI, TO|PIDMAX|RIGHT},
+{"stackp", "STACKP", pr_stackp, PIDS_ADDR_STACK_START, (int)(2*sizeof(long)), LNX, PO|RIGHT}, /*start_stack*/
+{"start", "STARTED", pr_start, PIDS_TICS_BEGAN, 8, XXX, ET|RIGHT},
+{"start_code", "S_CODE", pr_nop, PIDS_ADDR_CODE_START, (int)(2*sizeof(long)), LNx, PO|RIGHT}, // sortable, but unprintable ??
+{"start_stack", "STACKP",pr_stackp, PIDS_ADDR_STACK_START, (int)(2*sizeof(long)), LNX, PO|RIGHT}, /*stackp*/
+{"start_time", "START", pr_stime, PIDS_TICS_BEGAN, 5, LNx, ET|RIGHT},
+{"stat", "STAT", pr_stat, PIDS_STATE, 4, BSD, TO|LEFT}, /*state,s*/
+{"state", "S", pr_s, PIDS_STATE, 1, XXX, TO|LEFT}, /*stat,s*/ /* was STAT */
+{"status", "STATUS", pr_nop, PIDS_noop, 6, DEC, AN|RIGHT},
+{"stime", "STIME", pr_stime, PIDS_TICS_BEGAN, 5, XXX, ET|RIGHT}, /* was 6 wide */
+{"suid", "SUID", pr_suid, PIDS_ID_SUID, 5, LNx, ET|RIGHT},
+{"supgid", "SUPGID", pr_supgid, PIDS_SUPGIDS, 20, LNX, PO|UNLIMITED},
+{"supgrp", "SUPGRP", pr_supgrp, PIDS_SUPGROUPS, 40, LNX, PO|UNLIMITED},
+{"suser", "SUSER", pr_suser, PIDS_ID_SUSER, 8, LNx, ET|USER},
+{"svgid", "SVGID", pr_sgid, PIDS_ID_SGID, 5, XXX, ET|RIGHT},
+{"svgroup", "SVGROUP", pr_sgroup, PIDS_ID_SGROUP, 8, LNX, ET|USER},
+{"svuid", "SVUID", pr_suid, PIDS_ID_SUID, 5, XXX, ET|RIGHT},
+{"svuser", "SVUSER", pr_suser, PIDS_ID_SUSER, 8, LNX, ET|USER},
+{"systime", "SYSTEM", pr_nop, PIDS_noop, 6, DEC, ET|RIGHT},
+{"sz", "SZ", pr_sz, PIDS_VM_SIZE, 5, HPU, PO|RIGHT},
+{"taskid", "TASKID", pr_nop, PIDS_noop, 5, SUN, TO|PIDMAX|RIGHT}, // is this a thread ID?
+{"tdev", "TDEV", pr_nop, PIDS_noop, 4, XXX, AN|RIGHT},
+{"tgid", "TGID", pr_procs, PIDS_ID_TGID, 5, LNX, PO|PIDMAX|RIGHT},
+{"thcount", "THCNT", pr_nlwp, PIDS_NLWP, 5, AIX, PO|RIGHT},
+{"tid", "TID", pr_tasks, PIDS_ID_PID, 5, AIX, TO|PIDMAX|RIGHT},
+{"time", "TIME", pr_time, PIDS_TIME_ALL, 8, U98, ET|RIGHT}, /*cputime*/ /* was 6 wide */
+{"timens", "TIMENS", pr_timens, PIDS_NS_TIME, 10, LNX, ET|RIGHT},
+{"timeout", "TMOUT", pr_nop, PIDS_noop, 5, LNX, AN|RIGHT}, // 2.0.xx era
+{"times", "TIME", pr_times, PIDS_TIME_ALL, 8, LNX, ET|RIGHT},
+{"tmout", "TMOUT", pr_nop, PIDS_noop, 5, LNX, AN|RIGHT}, // 2.0.xx era
+{"tname", "TTY", pr_tty8, PIDS_TTY_NAME, 8, DEC, PO|LEFT},
+{"tpgid", "TPGID", pr_tpgid, PIDS_ID_TPGID, 5, XXX, PO|PIDMAX|RIGHT},
+{"trs", "TRS", pr_trs, PIDS_VSIZE_BYTES, 4, AIX, PO|RIGHT},
+{"trss", "TRSS", pr_trs, PIDS_VSIZE_BYTES, 4, BSD, PO|RIGHT}, /* 4.3BSD NET/2 */
+{"tsess", "TSESS", pr_nop, PIDS_noop, 5, BSD, PO|PIDMAX|RIGHT},
+{"tsession", "TSESS", pr_nop, PIDS_noop, 5, DEC, PO|PIDMAX|RIGHT},
+{"tsid", "TSID", pr_nop, PIDS_noop, 5, BSD, PO|PIDMAX|RIGHT},
+{"tsig", "PENDING", pr_tsig, PIDS_SIGPENDING, 9, BSD, ET|SIGNAL}, /* Dragonfly used this for thread-specific, and "sig" for whole-proc */
+{"tsiz", "TSIZ", pr_tsiz, PIDS_VSIZE_BYTES, 4, BSD, PO|RIGHT},
+{"tt", "TT", pr_tty8, PIDS_TTY_NAME, 8, BSD, PO|LEFT},
+{"tty", "TT", pr_tty8, PIDS_TTY_NAME, 8, U98, PO|LEFT}, /* Unix98 requires "TT" but has "TTY" too. :-( */ /* was 3 wide */
+{"tty4", "TTY", pr_tty4, PIDS_TTY_NAME, 4, LNX, PO|LEFT},
+{"tty8", "TTY", pr_tty8, PIDS_TTY_NAME, 8, LNX, PO|LEFT},
+{"u_procp", "UPROCP", pr_nop, PIDS_noop, 6, DEC, AN|RIGHT},
+{"ucmd", "CMD", pr_comm, PIDS_CMD, 15, DEC, PO|UNLIMITED}, /*ucomm*/
+{"ucomm", "COMMAND", pr_comm, PIDS_CMD, 15, XXX, PO|UNLIMITED}, /*comm*/
+{"uid", "UID", pr_euid, PIDS_ID_EUID, 5, XXX, ET|RIGHT},
+{"uid_hack", "UID", pr_euser, PIDS_ID_EUSER, 8, XXX, ET|USER},
+{"umask", "UMASK", pr_nop, PIDS_noop, 5, DEC, AN|RIGHT},
+{"uname", "USER", pr_euser, PIDS_ID_EUSER, 8, DEC, ET|USER}, /* man page misspelling of user? */
+{"unit", "UNIT", pr_sd_unit, PIDS_SD_UNIT, 31, LNX, ET|LEFT},
+{"upr", "UPR", pr_nop, PIDS_noop, 3, BSD, TO|RIGHT}, /*usrpri*/
+{"uprocp", "UPROCP", pr_nop, PIDS_noop, 8, BSD, AN|RIGHT},
+{"user", "USER", pr_euser, PIDS_ID_EUSER, 8, U98, ET|USER}, /* BSD n forces this to UID */
+{"userns", "USERNS", pr_userns, PIDS_NS_USER, 10, LNX, ET|RIGHT},
+{"usertime", "USER", pr_nop, PIDS_noop, 4, DEC, ET|RIGHT},
+{"usrpri", "UPR", pr_nop, PIDS_noop, 3, DEC, TO|RIGHT}, /*upr*/
+{"uss", "USS", pr_uss, PIDS_SMAP_PRV_TOTAL, 5, XXX, PO|RIGHT},
+{"util", "C", pr_c, PIDS_UTILIZATION, 2, SGI, ET|RIGHT}, // not sure about "C"
+{"utime", "UTIME", pr_nop, PIDS_TICS_USER, 6, LNx, ET|RIGHT},
+{"utsns", "UTSNS", pr_utsns, PIDS_NS_UTS, 10, LNX, ET|RIGHT},
+{"uunit", "UUNIT", pr_sd_uunit, PIDS_SD_UUNIT, 31, LNX, ET|LEFT},
+{"vm_data", "DATA", pr_nop, PIDS_VM_DATA, 5, LNx, PO|RIGHT},
+{"vm_exe", "EXE", pr_nop, PIDS_VM_EXE, 5, LNx, PO|RIGHT},
+{"vm_lib", "LIB", pr_nop, PIDS_VM_LIB, 5, LNx, PO|RIGHT},
+{"vm_lock", "LCK", pr_nop, PIDS_VM_RSS_LOCKED, 3, LNx, PO|RIGHT},
+{"vm_stack", "STACK", pr_nop, PIDS_VM_STACK, 5, LNx, PO|RIGHT},
+{"vsize", "VSZ", pr_vsz, PIDS_VSIZE_BYTES, 6, DEC, PO|RIGHT}, /*vsz*/
+{"vsz", "VSZ", pr_vsz, PIDS_VM_SIZE, 6, U98, PO|RIGHT}, /*vsize*/
+{"wbytes", "WBYTES", pr_wbytes, PIDS_IO_WRITE_BYTES, 5, LNX, TO|RIGHT},
+{"wcbytes", "WCBYTES", pr_wcbytes, PIDS_IO_WRITE_CBYTES, 5, LNX, TO|RIGHT},
+{"wchan", "WCHAN", pr_wchan, PIDS_WCHAN_NAME, 6, XXX, TO|WCHAN}, /* BSD n forces this to nwchan */ /* was 10 wide */
+{"wchars", "WCHARS", pr_wchars, PIDS_IO_WRITE_CHARS, 5, LNX, TO|RIGHT},
+{"wname", "WCHAN", pr_wchan, PIDS_WCHAN_NAME, 6, SGI, TO|WCHAN}, /* opposite of nwchan */
+{"wops", "WOPS", pr_wops, PIDS_IO_WRITE_OPS, 5, LNX, TO|RIGHT},
+{"xstat", "XSTAT", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT},
+{"zone", "ZONE", pr_context, PIDS_ID_TGID, 31, SUN, ET|LEFT}, // Solaris zone == Linux context?
+{"zoneid", "ZONEID", pr_nop, PIDS_noop, 31, SUN, ET|RIGHT}, // Linux only offers context names
+{"~", "-", pr_nop, PIDS_noop, 1, LNX, AN|RIGHT} /* NULL would ruin alphabetical order */
+};
+
+#undef USER
+#undef LEFT
+#undef RIGHT
+#undef UNLIMITED
+#undef WCHAN
+#undef SIGNAL
+#undef PIDMAX
+#undef PO
+#undef TO
+#undef AN
+#undef ET
+
+static const int format_array_count = sizeof(format_array)/sizeof(format_struct);
+
+
+/****************************** Macro formats *******************************/
+/* First X field may be NR, which is p->start_code>>26 printed with %2ld */
+/* That seems useless though, and Debian already killed it. */
+/* The ones marked "Digital" have the name defined, not just the data. */
+static const macro_struct macro_array[] = {
+{"DFMT", "pid,tname,state,cputime,cmd"}, /* Digital's default */
+{"DefBSD", "pid,tname,stat,bsdtime,args"}, /* Our BSD default */
+{"DefSysV", "pid,tname,time,cmd"}, /* Our SysV default */
+{"END_BSD", "state,tname,cputime,comm"}, /* trailer for O */
+{"END_SYS5", "state,tname,time,command"}, /* trailer for -O */
+{"F5FMT", "uname,pid,ppid,c,start,tname,time,cmd"}, /* Digital -f */
+
+{"FB_", "pid,tt,stat,time,command"}, /* FreeBSD default */
+{"FB_j", "user,pid,ppid,pgid,sess,jobc,stat,tt,time,command"}, /* FreeBSD j */
+{"FB_l", "uid,pid,ppid,cpu,pri,nice,vsz,rss,wchan,stat,tt,time,command"}, /* FreeBSD l */
+{"FB_u", "user,pid,pcpu,pmem,vsz,rss,tt,stat,start,time,command"}, /* FreeBSD u */
+{"FB_v", "pid,stat,time,sl,re,pagein,vsz,rss,lim,tsiz,pcpu,pmem,command"}, /* FreeBSD v */
+
+{"FD_", "pid,tty,time,comm"}, /* Fictional Debian SysV default */
+{"FD_f", "user,pid,ppid,start_time,tty,time,comm"}, /* Fictional Debian -f */
+{"FD_fj", "user,pid,ppid,start_time,tty,time,pgid,sid,comm"}, /* Fictional Debian -jf */
+{"FD_j", "pid,tty,time,pgid,sid,comm"}, /* Fictional Debian -j */
+{"FD_l", "flags,state,uid,pid,ppid,priority,nice,vsz,wchan,tty,time,comm"}, /* Fictional Debian -l */
+{"FD_lj", "flags,state,uid,pid,ppid,priority,nice,vsz,wchan,tty,time,pgid,sid,comm"}, /* Fictional Debian -jl */
+
+{"FL5FMT", "f,state,uid,pid,ppid,pcpu,pri,nice,rss,wchan,start,time,command"}, /* Digital -fl */
+
+{"FLASK_context", "pid,context,command"}, /* Flask Linux context, --context */
+
+{"HP_", "pid,tty,time,comm"}, /* HP default */
+{"HP_f", "user,pid,ppid,cpu,stime,tty,time,args"}, /* HP -f */
+{"HP_fl", "flags,state,user,pid,ppid,cpu,intpri,nice,addr,sz,wchan,stime,tty,time,args"}, /* HP -fl */
+{"HP_l", "flags,state,uid,pid,ppid,cpu,intpri,nice,addr,sz,wchan,tty,time,comm"}, /* HP -l */
+
+{"J390", "pid,sid,pgrp,tname,atime,args"}, /* OS/390 -j */
+{"JFMT", "user,pid,ppid,pgid,sess,jobc,state,tname,cputime,command"}, /* Digital j and -j */
+{"L5FMT", "f,state,uid,pid,ppid,c,pri,nice,addr,sz,wchan,tt,time,ucmd"}, /* Digital -l */
+{"LFMT", "uid,pid,ppid,cp,pri,nice,vsz,rss,wchan,state,tname,cputime,command"}, /* Digital l */
+
+{"OL_X", "pid,start_stack,esp,eip,timeout,alarm,stat,tname,bsdtime,args"}, /* Old i386 Linux X */
+{"OL_j", "ppid,pid,pgid,sid,tname,tpgid,stat,uid,bsdtime,args"}, /* Old Linux j */
+{"OL_l", "flags,uid,pid,ppid,priority,nice,vsz,rss,wchan,stat,tname,bsdtime,args"}, /* Old Linux l */
+{"OL_m", "pid,tname,majflt,minflt,m_trs,m_drs,m_size,m_swap,rss,m_share,vm_lib,m_dt,args"}, /* Old Linux m */
+{"OL_s", "uid,pid,pending,sig_block,sig_ignore,caught,stat,tname,bsdtime,args"}, /* Old Linux s */
+{"OL_u", "user,pid,pcpu,pmem,vsz,rss,tname,stat,start_time,bsdtime,args"}, /* Old Linux u */
+{"OL_v", "pid,tname,stat,bsdtime,maj_flt,m_trs,m_drs,rss,pmem,args"}, /* Old Linux v */
+
+{"RD_", "pid,tname,state,bsdtime,comm"}, /* Real Debian default */
+{"RD_f", "uid,pid,ppid,start_time,tname,bsdtime,args"}, /* Real Debian -f */
+{"RD_fj", "uid,pid,ppid,start_time,tname,bsdtime,pgid,sid,args"}, /* Real Debian -jf */
+{"RD_j", "pid,tname,state,bsdtime,pgid,sid,comm"}, /* Real Debian -j */
+{"RD_l", "flags,state,uid,pid,ppid,priority,nice,wchan,tname,bsdtime,comm"}, /* Real Debian -l */
+{"RD_lj", "flags,state,uid,pid,ppid,priority,nice,wchan,tname,bsdtime,pgid,sid,comm"}, /* Real Debian -jl */
+
+{"RUSAGE", "minflt,majflt,nswap,inblock,oublock,msgsnd,msgrcv,nsigs,nvcsw,nivcsw"}, /* Digital -o "RUSAGE" */
+{"SCHED", "user,pcpu,pri,usrpri,nice,psxpri,psr,policy,pset"}, /* Digital -o "SCHED" */
+{"SFMT", "uid,pid,cursig,sig,sigmask,sigignore,sigcatch,stat,tname,command"}, /* Digital s */
+
+{"Std_f", "uid_hack,pid,ppid,c,stime,tname,time,cmd"}, /* new -f */
+{"Std_fl", "f,s,uid_hack,pid,ppid,c,opri,ni,addr,sz,wchan,stime,tname,time,cmd"}, /* -fl */
+{"Std_l", "f,s,uid,pid,ppid,c,opri,ni,addr,sz,wchan,tname,time,ucmd"}, /* new -l */
+
+{"THREAD", "user,pcpu,pri,scnt,wchan,usertime,systime"}, /* Digital -o "THREAD" */
+{"UFMT", "uname,pid,pcpu,pmem,vsz,rss,tt,state,start,time,command"}, /* Digital u */
+{"VFMT", "pid,tt,state,time,sl,pagein,vsz,rss,pcpu,pmem,command"}, /* Digital v */
+{"~", "~"} /* NULL would ruin alphabetical order */
+};
+
+static const int macro_array_count = sizeof(macro_array)/sizeof(macro_struct);
+
+
+/*************************** AIX formats ********************/
+/* Convert AIX format codes to normal format specifiers. */
+static const aix_struct aix_array[] = {
+{'C', "pcpu", "%CPU"},
+{'G', "group", "GROUP"},
+{'P', "ppid", "PPID"},
+{'U', "user", "USER"},
+{'a', "args", "COMMAND"},
+{'c', "comm", "COMMAND"},
+{'g', "rgroup", "RGROUP"},
+{'n', "nice", "NI"},
+{'p', "pid", "PID"},
+{'r', "pgid", "PGID"},
+{'t', "etime", "ELAPSED"},
+{'u', "ruser", "RUSER"},
+{'x', "time", "TIME"},
+{'y', "tty", "TTY"},
+{'z', "vsz", "VSZ"},
+{'~', "~", "~"} /* NULL would ruin alphabetical order */
+};
+
+
+/********************* sorting ***************************/
+/* Convert short sorting codes to normal format specifiers. */
+static const shortsort_struct shortsort_array[] = {
+{'C', "pcpu" },
+{'G', "tpgid" },
+{'J', "cstime" },
+/* {'K', "stime" }, */ /* conflict, system vs. start time */
+{'M', "maj_flt" },
+{'N', "cmaj_flt" },
+{'P', "ppid" },
+{'R', "resident" },
+{'S', "share" },
+{'T', "start_time" },
+{'U', "uid" }, /* euid */
+{'c', "cmd" },
+{'f', "flags" },
+{'g', "pgrp" },
+{'j', "cutime" },
+{'k', "utime" },
+{'m', "min_flt" },
+{'n', "cmin_flt" },
+{'o', "session" },
+{'p', "pid" },
+{'r', "rss" },
+{'s', "size" },
+{'t', "tty" },
+{'u', "user" },
+{'v', "vsize" },
+{'y', "priority" }, /* nice */
+{'~', "~" } /* NULL would ruin alphabetical order */
+};
+
+
+/*********** print format_array **********/
+/* called by the parser in another file */
+void print_format_specifiers(void){
+ const format_struct *walk = format_array;
+ while(*(walk->spec) != '~'){
+ if(walk->pr != pr_nop) printf("%-12.12s %-8.8s\n", walk->spec, walk->head);
+ walk++;
+ }
+}
+
+/************ comparison functions for bsearch *************/
+
+static int compare_format_structs(const void *a, const void *b){
+ return strcmp(((const format_struct*)a)->spec,((const format_struct*)b)->spec);
+}
+
+static int compare_macro_structs(const void *a, const void *b){
+ return strcmp(((const macro_struct*)a)->spec,((const macro_struct*)b)->spec);
+}
+
+/******** look up structs as needed by the sort & format parsers ******/
+
+const shortsort_struct *search_shortsort_array(const int findme){
+ const shortsort_struct *walk = shortsort_array;
+ while(walk->desc != '~'){
+ if(walk->desc == findme) return walk;
+ walk++;
+ }
+ return NULL;
+}
+
+const aix_struct *search_aix_array(const int findme){
+ const aix_struct *walk = aix_array;
+ while(walk->desc != '~'){
+ if(walk->desc == findme) return walk;
+ walk++;
+ }
+ return NULL;
+}
+
+const format_struct *search_format_array(const char *findme){
+ format_struct key;
+ key.spec = findme;
+ return bsearch(&key, format_array, format_array_count,
+ sizeof(format_struct), compare_format_structs
+ );
+}
+
+const macro_struct *search_macro_array(const char *findme){
+ macro_struct key;
+ key.spec = findme;
+ return bsearch(&key, macro_array, macro_array_count,
+ sizeof(macro_struct), compare_macro_structs
+ );
+}
+
+static unsigned int active_cols; /* some multiple of screen_cols */
+
+/***** Last chance, avoid needless trunctuation. */
+static void check_header_width(void){
+ format_node *walk = format_list;
+ unsigned int total = 0;
+ int was_normal = 0;
+ unsigned int i = 0;
+ unsigned int sigs = 0;
+ while(walk){
+ switch((walk->flags) & CF_JUST_MASK){
+ default:
+ total += walk->width;
+ total += was_normal;
+ was_normal = 1;
+ break;
+ case CF_SIGNAL:
+ sigs++;
+ if (signal_names) {
+ if (walk->width < SIGNAL_NAME_WIDTH)
+ walk->width = SIGNAL_NAME_WIDTH;
+ walk->flags = CF_UNLIMITED;
+ if (walk->next)
+ total += walk->width;
+ else
+ total += 3;
+ } else {
+ total += walk->width;
+ }
+ total += was_normal;
+ was_normal = 1;
+ break;
+ case CF_UNLIMITED: /* could chop this a bit */
+ if(walk->next) total += walk->width;
+ else total += 3; /* not strlen(walk->name) */
+ total += was_normal;
+ was_normal = 1;
+ break;
+ case 0: /* AIX */
+ total += walk->width;
+ was_normal = 0;
+ break;
+ }
+ walk = walk->next;
+ }
+ for(;;){
+ i++;
+ active_cols = screen_cols * i;
+ if(active_cols>=total) break;
+ if(screen_cols*i >= OUTBUF_SIZE/2) break; /* can't go over */
+ }
+ wide_signals = (total+sigs*7 <= active_cols);
+}
+
+
+/********** show one process (NULL proc prints header) **********/
+
+//#define SPACE_AMOUNT page_size
+#define SPACE_AMOUNT 144
+
+static char *saved_outbuf;
+
+void show_one_proc(const proc_t *restrict const p, const format_node *restrict fmt){
+ /* unknown: maybe set correct & actual to 1, remove +/- 1 below */
+ int correct = 0; /* screen position we should be at */
+ int actual = 0; /* screen position we are at */
+ int amount = 0; /* amount of text that this data is */
+ int leftpad = 0; /* amount of space this column _could_ need */
+ int space = 0; /* amount of space we actually need to print */
+ int dospace = 0; /* previous column determined that we need a space */
+ int legit = 0; /* legitimately stolen extra space */
+ int sz = 0; /* real size of data in outbuffer */
+ int tmpspace = 0;
+ char *restrict const outbuf = saved_outbuf;
+ static int did_stuff = 0; /* have we ever printed anything? */
+
+ if(-1==(long)p){ /* true only once, at the end */
+ if(did_stuff) return;
+ /* have _never_ printed anything, but might need a header */
+ if(!--lines_to_next_header){
+ lines_to_next_header = header_gap;
+ show_one_proc(NULL,fmt);
+ }
+ /* fprintf(stderr, "No processes available.\n"); */ /* legal? */
+ exit(1);
+ }
+ if(p){ /* not header, maybe we should call ourselves for it */
+ if(!--lines_to_next_header){
+ lines_to_next_header = header_gap;
+ show_one_proc(NULL,fmt);
+ }
+ }
+ did_stuff = 1;
+ if(active_cols>(int)OUTBUF_SIZE) fprintf(stderr,_("fix bigness error\n"));
+
+ /* print row start sequence */
+ for(;;){
+ legit = 0;
+ /* set width suggestion which might be ignored */
+// if(likely(fmt->next)) max_rightward = fmt->width;
+// else max_rightward = active_cols-((correct>actual) ? correct : actual);
+
+ if(fmt->next){
+ max_rightward = fmt->width;
+ tmpspace = 0;
+ }else{
+ tmpspace = correct-actual;
+ if (tmpspace<1){
+ tmpspace = dospace;
+ max_rightward = active_cols-actual-tmpspace;
+ }else{
+ max_rightward = active_cols - ( (correct>actual) ? correct : actual );
+ }
+ }
+ if(max_rightward <= 0) max_rightward = 0;
+ else if(max_rightward >= OUTBUF_SIZE) max_rightward = OUTBUF_SIZE-1;
+
+ max_leftward = fmt->width + actual - correct; /* TODO check this */
+ if(max_leftward <= 0) max_leftward = 0;
+ else if(max_leftward >= OUTBUF_SIZE) max_leftward = OUTBUF_SIZE-1;
+
+// fprintf(stderr, "cols: %d, max_rightward: %d, max_leftward: %d, actual: %d, correct: %d\n",
+// active_cols, max_rightward, max_leftward, actual, correct);
+
+ /* prepare data and calculate leftpad */
+ if(p && fmt->pr) amount = (*fmt->pr)(outbuf,p);
+ else amount = snprintf(outbuf, OUTBUF_SIZE, "%s", fmt->name); /* AIX or headers */
+
+ if(amount < 0) outbuf[amount = 0] = '\0';
+ else if(amount >= OUTBUF_SIZE) outbuf[amount = OUTBUF_SIZE-1] = '\0';
+
+ switch((fmt->flags) & CF_JUST_MASK){
+ case 0: /* for AIX, assigned outside this file */
+ leftpad = 0;
+ break;
+ case CF_LEFT: /* bad */
+ leftpad = 0;
+ break;
+ case CF_RIGHT: /* OK */
+ leftpad = fmt->width - amount;
+ if(leftpad < 0) leftpad = 0;
+ break;
+ case CF_SIGNAL:
+ /* if the screen is wide enough, use full 16-character output */
+ if(wide_signals){
+ leftpad = 16 - amount;
+ legit = 7;
+ }else{
+ leftpad = 9 - amount;
+ }
+ if(leftpad < 0) leftpad = 0;
+ break;
+ case CF_USER: /* bad */
+ leftpad = fmt->width - amount;
+ if(leftpad < 0) leftpad = 0;
+ if(!user_is_number) leftpad = 0;
+ break;
+ case CF_WCHAN: /* bad */
+ if(wchan_is_number){
+ leftpad = fmt->width - amount;
+ if(leftpad < 0) leftpad = 0;
+ break;
+ }else{
+ if ((active_cols-actual-tmpspace)<1)
+ outbuf[1] = '\0'; /* oops, we (mostly) lose this column... */
+ leftpad = 0;
+ break;
+ }
+ case CF_UNLIMITED:
+ {
+ if(active_cols-actual-tmpspace < 1)
+ outbuf[1] = '\0'; /* oops, we (mostly) lose this column... */
+ leftpad = 0;
+ break;
+ }
+ default:
+ fprintf(stderr, _("bad alignment code\n"));
+ break;
+ }
+ /* At this point:
+ *
+ * correct from previous column
+ * actual from previous column
+ * amount not needed (garbage due to chopping)
+ * leftpad left padding for this column alone (not make-up or gap)
+ * space not needed (will recalculate now)
+ * dospace if we require space between this and the prior column
+ * legit space we were allowed to steal, and thus did steal
+ */
+ space = correct - actual + leftpad;
+ if(space<1) space=dospace;
+ if(space>SPACE_AMOUNT) space=SPACE_AMOUNT; // only so much available
+
+ /* real size -- don't forget in 'amount' is number of cells */
+ outbuf[OUTBUF_SIZE-1] = '\0';
+ sz = strlen(outbuf);
+
+ /* print data, set x position stuff */
+ if(!fmt->next){
+ /* Last column. Write padding + data + newline all together. */
+ outbuf[sz] = '\n';
+ fwrite(outbuf-space, space+sz+1, 1, stdout);
+ break;
+ }
+ /* Not the last column. Write padding + data together. */
+ fwrite(outbuf-space, space+sz, 1, stdout);
+ actual += space+amount;
+ correct += fmt->width;
+ correct += legit; /* adjust for SIGNAL expansion */
+ if(fmt->pr && fmt->next->pr){ /* neither is AIX filler */
+ correct++;
+ dospace = 1;
+ }else{
+ dospace = 0;
+ }
+ fmt = fmt->next;
+ /* At this point:
+ *
+ * correct screen position we should be at
+ * actual screen position we are at
+ * amount not needed
+ * leftpad not needed
+ * space not needed
+ * dospace if have determined that we need a space next time
+ * legit not needed
+ */
+ }
+}
+
+
+void init_output(void)
+{
+ int outbuf_pages;
+ char *outbuf;
+
+ // add page_size-1 to round up
+ outbuf_pages = (OUTBUF_SIZE+SPACE_AMOUNT+page_size-1)/page_size;
+ outbuf = mmap(
+ 0,
+ page_size * (outbuf_pages+1), // 1 more, for guard page at high addresses
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS,
+ -1,
+ 0);
+
+ if(outbuf == MAP_FAILED)
+ catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
+
+ memset(outbuf, ' ', SPACE_AMOUNT);
+ if(SPACE_AMOUNT==page_size)
+ mprotect(outbuf, page_size, PROT_READ);
+ mprotect(outbuf + page_size*outbuf_pages, page_size, PROT_NONE); // guard page
+ saved_outbuf = outbuf + SPACE_AMOUNT;
+ // available space: page_size*outbuf_pages-SPACE_AMOUNT
+ seconds_since_1970 = time(NULL);
+
+ check_header_width();
+}
diff --git a/src/ps/parser.c b/src/ps/parser.c
new file mode 100644
index 0000000..1f50a7a
--- /dev/null
+++ b/src/ps/parser.c
@@ -0,0 +1,1268 @@
+/*
+ * parser.c - ps command options parser
+ *
+ * Copyright © 2012-2023 Jim Warner <james.warner@comcast.net
+ * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2012-2014 Jaromir Capik <jcapik@redhat.com>
+ * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi>
+ * Copyright © 1998-2003 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Ought to have debug print stuff like this:
+ * #define Print(fmt, args...) printf("Debug: " fmt, ## args)
+ */
+
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "c.h"
+#include "xalloc.h"
+
+#include "common.h"
+
+#define ARG_GNU 0
+#define ARG_END 1
+#define ARG_PGRP 2
+#define ARG_SYSV 3
+#define ARG_PID 4
+#define ARG_BSD 5
+#define ARG_FAIL 6
+#define ARG_SESS 7
+
+static int w_count = 0;
+
+static int ps_argc; /* global argc */
+static char **ps_argv; /* global argv */
+static int thisarg; /* index into ps_argv */
+static char *flagptr; /* current location in ps_argv[thisarg] */
+static int force_bsd = 0; /* set when normal parsing fails */
+
+#define exclusive(x) if((ps_argc != 2) || strcmp(ps_argv[1],x)) \
+ return _("the option is exclusive: " x)
+
+/********** utility functions **********/
+static void display_ps_version(void)
+{
+ fprintf(stdout, PROCPS_NG_VERSION);
+}
+
+
+/*
+ * Both "-Oppid" and "-O ppid" should be legal, though Unix98
+ * does not require it. BSD and Digital Unix allow both.
+ * Return the argument or NULL;
+ */
+static const char *get_opt_arg(void){
+ if(*(flagptr+1)){ /* argument is part of ps_argv[thisarg] */
+ return flagptr+1;
+ }
+ if(thisarg+2 > ps_argc) return NULL; /* there is nothing left */
+ /* argument follows ps_argv[thisarg] */
+ if(*(ps_argv[thisarg+1]) == '\0') return NULL;
+ return ps_argv[++thisarg];
+}
+
+/********** parse lists (of UID, tty, GID, PID...) **********/
+
+static const char *parse_pid(char *str, sel_union *ret){
+ char *endp;
+ unsigned long num;
+ num = strtoul(str, &endp, 0);
+ if(*endp != '\0') return _("process ID list syntax error");
+ if(num<1) return _("process ID out of range");
+ if(num > 0x7fffffffUL) return _("process ID out of range");
+ ret->pid = num;
+ return 0;
+}
+
+static const char *parse_uid(char *str, sel_union *ret){
+ struct passwd *passwd_data;
+ char *endp;
+ unsigned long num;
+ num = strtoul(str, &endp, 0);
+ if(*endp != '\0'){ /* hmmm, try as login name */
+ passwd_data = getpwnam(str);
+ if(!passwd_data){
+ if(!negate_selection) return _("user name does not exist");
+ num = -1;
+ }
+ else
+ num = passwd_data->pw_uid;
+ }
+ if(!negate_selection && (num > 0xfffffffeUL)) return _("user ID out of range");
+ ret->uid = num;
+ return 0;
+}
+
+static const char *parse_gid(char *str, sel_union *ret){
+ struct group *group_data;
+ char *endp;
+ unsigned long num;
+ num = strtoul(str, &endp, 0);
+ if(*endp != '\0'){ /* hmmm, try as login name */
+ group_data = getgrnam(str);
+ if(!group_data){
+ if(!negate_selection) return _("group name does not exist");
+ num = -1;
+ }
+ else
+ num = group_data->gr_gid;
+ }
+ if(!negate_selection && (num > 0xfffffffeUL)) return _("group ID out of range");
+ ret->gid = num;
+ return 0;
+}
+
+static const char *parse_cmd(char *str, sel_union *ret){
+ strncpy(ret->cmd, str, sizeof ret->cmd); // strncpy pads to end
+ ret->cmd[sizeof(ret->cmd)-1] = '\0'; // but let's be safe
+ return 0;
+}
+
+static const char *parse_tty(char *str, sel_union *ret){
+ struct stat sbuf;
+ char path[4096];
+ if(str[0]=='/'){
+ if(stat(str, &sbuf) >= 0) goto found_it;
+ return _("TTY could not be found");
+ }
+#define lookup(p) \
+ snprintf(path,4096,p,str); \
+ if(stat(path, &sbuf) >= 0) goto found_it
+
+ lookup("/dev/pts/%s"); /* New Unix98 ptys go first */
+ lookup("/dev/%s");
+ lookup("/dev/tty%s");
+ lookup("/dev/pty%s");
+ lookup("/dev/%snsole"); /* "co" means "console", maybe do all VCs too? */
+ if(!strcmp(str,"-")){ /* "-" means no tty (from AIX) */
+ ret->tty = 0; /* processes w/o tty */
+ return 0;
+ }
+ if(!strcmp(str,"?")){ /* "?" means no tty, which bash eats (Reno BSD?) */
+ ret->tty = 0; /* processes w/o tty */
+ return 0;
+ }
+ if(!*(str+1) && (stat(str,&sbuf)>=0)){ /* Kludge! Assume bash ate '?'. */
+ ret->tty = 0; /* processes w/o tty */
+ return 0;
+ }
+#undef lookup
+ return _("TTY could not be found");
+found_it:
+ if(!S_ISCHR(sbuf.st_mode)) return _("list member was not a TTY");
+ ret->tty = sbuf.st_rdev;
+ return 0;
+}
+
+/*
+ * Used to parse lists in a generic way. (function pointers)
+ */
+static const char *parse_list(const char *arg, const char *(*parse_fn)(char *, sel_union *) ){
+ selection_node *node;
+ char *buf; /* temp copy of arg to hack on */
+ char *sep_loc; /* separator location: " \t," */
+ char *walk;
+ int items;
+ int need_item;
+ const char *err; /* error code that could or did happen */
+ /*** prepare to operate ***/
+ node = xmalloc(sizeof(selection_node));
+ node->n = 0;
+ node->u = NULL;
+ buf = strdup(arg);
+ /*** sanity check and count items ***/
+ need_item = 1; /* true */
+ items = 0;
+ walk = buf;
+ err = _("improper list");
+ do{
+ switch(*walk){
+ case ' ': case ',': case '\t': case '\0':
+ if(need_item) goto parse_error;
+ need_item=1;
+ break;
+ default:
+ if(need_item && items<INT_MAX) items++;
+ need_item=0;
+ }
+ } while (*++walk);
+ if(need_item) goto parse_error;
+ node->n = items;
+ node->u = xcalloc(items, sizeof(sel_union));
+ /*** actually parse the list ***/
+ walk = buf;
+ while(items--){
+ sep_loc = strpbrk(walk," ,\t");
+ if(sep_loc) *sep_loc = '\0';
+ if(( err=(parse_fn)(walk, node->u+items) )) goto parse_error;
+ walk = sep_loc + 1; /* point to next item, if any */
+ }
+ free(buf);
+ node->next = selection_list;
+ selection_list = node;
+ return NULL;
+parse_error:
+ free(buf);
+ free(node->u);
+ free(node);
+ return err;
+}
+
+/***************** parse SysV options, including Unix98 *****************/
+static const char *parse_sysv_option(void){
+ const char *arg;
+ const char *err;
+
+ flagptr = ps_argv[thisarg];
+ while(*++flagptr){
+ switch(*flagptr){
+ case 'A':
+ trace("-A selects all processes\n");
+ all_processes = 1;
+ break;
+ case 'C': /* end */
+ trace("-C select by process name\n"); /* Why only HP/UX and us? */
+ arg=get_opt_arg();
+ if(!arg) return _("list of command names must follow -C");
+ err=parse_list(arg, parse_cmd);
+ if(err) return err;
+ selection_list->typecode = SEL_COMM;
+ return NULL; /* can't have any more options */
+ case 'D':
+ trace("-D sets lstart date format\n");
+ arg = get_opt_arg();
+ if (!arg) return _("date format must follow -D");
+ if (lstart_format) free(lstart_format);
+ lstart_format = strdup(arg);
+ break;
+ case 'F': /* DYNIX/ptx -f plus sz,rss,psr=ENG between c and stime */
+ trace("-F does fuller listing\n");
+ format_modifiers |= FM_F;
+ format_flags |= FF_Uf;
+ unix_f_option = 1; /* does this matter? */
+ break;
+ case 'G': /* end */
+ trace("-G select by RGID (supports names)\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of real groups must follow -G");
+ err=parse_list(arg, parse_gid);
+ if(err) return err;
+ selection_list->typecode = SEL_RGID;
+ return NULL; /* can't have any more options */
+ case 'H': /* another nice HP/UX feature */
+ trace("-H process hierarchy (like ASCII art forest option)\n");
+ forest_type = 'u';
+ break;
+#if 0
+ case 'J': // specify list of job IDs in hex (IRIX) -- like HP "-R" maybe?
+ trace("-J select by job ID\n"); // want a JID ("jid") for "-j" too
+ arg=get_opt_arg();
+ if(!arg) return _("list of jobs must follow -J");
+ err=parse_list(arg, parse_jid);
+ if(err) return err;
+ selection_list->typecode = SEL_JID;
+ return NULL; /* can't have any more options */
+#endif
+ case 'L': /* */
+ /* In spite of the insane 2-level thread system, Sun appears to
+ * have made this option Linux-compatible. If a process has N
+ * threads, ps will produce N lines of output. (not N+1 lines)
+ * Zombies are the only exception, with NLWP==0 and 1 output line.
+ * SCO UnixWare uses -L too.
+ */
+ trace("-L print LWP (thread) info\n");
+ thread_flags |= TF_U_L;
+// format_modifiers |= FM_L;
+ break;
+ case 'M': // typically the SELinux context
+ trace("-M print security label for Mandatory Access Control\n");
+ format_modifiers |= FM_M;
+ break;
+ case 'N':
+ trace("-N negates\n");
+ negate_selection = 1;
+ break;
+ case 'O': /* end */
+ trace("-O is preloaded -o\n");
+ arg=get_opt_arg();
+ if(!arg) return _("format or sort specification must follow -O");
+ defer_sf_option(arg, SF_U_O);
+ return NULL; /* can't have any more options */
+ case 'P': /* SunOS 5 "psr" or unknown HP/UX feature */
+ trace("-P adds columns of PRM info (HP-UX), PSR (SunOS), or capabilities (IRIX)\n");
+ format_modifiers |= FM_P;
+ break;
+#if 0
+ case 'R': // unknown HP/UX feature, like IRIX "-J" maybe?
+ trace("-R select by PRM group\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of PRM groups must follow -R");
+ err=parse_list(arg, parse_prm);
+ if(err) return err;
+ selection_list->typecode = SEL_PRM;
+ return NULL; /* can't have any more options */
+#endif
+ case 'T':
+ /* IRIX 6.5 docs suggest POSIX threads get shown individually.
+ * This would make -T be like -L, -m, and m. (but an extra column)
+ * Testing (w/ normal processes) shows 1 line/process, not 2.
+ * Also, testing shows PID==SPID for all normal processes.
+ */
+ trace("-T adds strange SPID column (old sproc() threads?)\n");
+ thread_flags |= TF_U_T;
+// format_modifiers |= FM_T;
+ break;
+ case 'U': /* end */
+ trace("-U select by RUID (supports names)\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of real users must follow -U");
+ err=parse_list(arg, parse_uid);
+ if(err) return err;
+ selection_list->typecode = SEL_RUID;
+ return NULL; /* can't have any more options */
+ case 'V': /* single */
+ trace("-V prints version\n");
+ exclusive("-V");
+ display_ps_version();
+ exit(0);
+ // This must be verified against SVR4-MP. (UnixWare or Powermax)
+ // Leave it undocumented until that problem is solved.
+ case 'Z': /* full Mandatory Access Control level info */
+ trace("-Z shows full MAC info\n");
+ format_modifiers |= FM_M;
+ break;
+ case 'a':
+ trace("-a select all with a tty, but omit session leaders\n");
+ simple_select |= SS_U_a;
+ break;
+ case 'c':
+ /* HP-UX and SunOS 5 scheduling info modifier */
+ trace("-c changes scheduling info\n");
+ format_modifiers |= FM_c;
+ break;
+ case 'd':
+ trace("-d select all, but omit session leaders\n");
+ simple_select |= SS_U_d;
+ break;
+ case 'e':
+ trace("-e selects all processes\n");
+ all_processes = 1;
+ break;
+ case 'f':
+ trace("-f does full listing\n");
+ format_flags |= FF_Uf;
+ unix_f_option = 1; /* does this matter? */
+ break;
+ case 'g': /* end */
+ trace("-g selects by session leader OR by group name\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of session leaders OR effective group names must follow -g");
+ err=parse_list(arg, parse_pid);
+ if(!err){
+ selection_list->typecode = SEL_SESS;
+ return NULL; /* can't have any more options */
+ }
+ err=parse_list(arg, parse_gid);
+ if(!err){
+ selection_list->typecode = SEL_EGID;
+ return NULL; /* can't have any more options */
+ }
+ return _("list of session leaders OR effective group IDs was invalid");
+ case 'j':
+ trace("-j jobs format\n");
+ /* old Debian used RD_j and Digital uses JFMT */
+ if(sysv_j_format) format_flags |= FF_Uj;
+ else format_modifiers |= FM_j;
+ break;
+ case 'l':
+ trace("-l long format\n");
+ format_flags |= FF_Ul;
+ break;
+ case 'm':
+ trace("-m shows threads\n");
+ /* note that AIX shows 2 lines for a normal process */
+ thread_flags |= TF_U_m;
+ break;
+ case 'o': /* end */
+ /* Unix98 has gross behavior regarding this. From the following: */
+ /* ps -o pid,nice=NICE,tty=TERMINAL,comm */
+ /* The result must be 2 columns: "PID NICE,tty=TERMINAL,comm" */
+ /* Yes, the second column has the name "NICE,tty=TERMINAL,comm" */
+ /* This parser looks for any excuse to ignore that braindamage. */
+ trace("-o user-defined format\n");
+ arg=get_opt_arg();
+ if(!arg) return _("format specification must follow -o");
+ defer_sf_option(arg, SF_U_o);
+ return NULL; /* can't have any more options */
+ case 'p': /* end */
+ trace("-p select by PID\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of process IDs must follow -p");
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_PID;
+ return NULL; /* can't have any more options */
+ case 'q': /* end */
+ trace("-q quick select by PID.\n");
+ arg=get_opt_arg();
+ if(!arg) return "List of process IDs must follow -q.";
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_PID_QUICK;
+ return NULL; /* can't have any more options */
+#if 0
+ case 'r':
+ trace("-r some Digital Unix thing about warnings...\n");
+ trace(" or SCO's option to chroot() for new /proc and /dev\n");
+ return _("the -r option is reserved");
+ break;
+#endif
+ case 's': /* end */
+ trace("-s select processes belonging to the sessions given\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of session IDs must follow -s");
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_SESS;
+ return NULL; /* can't have any more options */
+ case 't': /* end */
+ trace("-t select by tty\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of terminals (pty, tty...) must follow -t");
+ err=parse_list(arg, parse_tty);
+ if(err) return err;
+ selection_list->typecode = SEL_TTY;
+ return NULL; /* can't have any more options */
+ case 'u': /* end */
+ trace("-u select by user effective ID (supports names)\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of users must follow -u");
+ err=parse_list(arg, parse_uid);
+ if(err) return err;
+ selection_list->typecode = SEL_EUID;
+ return NULL; /* can't have any more options */
+ case 'w':
+ trace("-w wide output\n");
+ w_count++;
+ break;
+ case 'x': /* behind personality until "ps -ax" habit is uncommon */
+ if(personality & PER_SVR4_x){
+ // Same as -y, but for System V Release 4 MP
+ trace("-x works like Sun Solaris & SCO Unixware -y option\n");
+ format_modifiers |= FM_y;
+ break;
+ }
+ if(personality & PER_HPUX_x){
+ trace("-x extends the command line\n");
+ w_count += 2;
+ unix_f_option = 1;
+ break;
+ }
+ return _("must set personality to get -x option");
+ case 'y': /* Sun's -l hack (also: Irix "lnode" resource control info) */
+ trace("-y print lnone info in UID/USER column or do Sun -l hack\n");
+ format_modifiers |= FM_y;
+ break;
+#if 0
+ // This must be verified against SVR4-MP (UnixWare or Powermax)
+ case 'z': /* alias of Mandatory Access Control level info */
+ trace("-z shows aliased MAC info\n");
+ format_modifiers |= FM_M;
+ break;
+ // Solaris 10 does this
+ case 'z': /* select by zone */
+ trace("-z secects by zone\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of zones (contexts, labels, whatever?) must follow -z");
+ err=parse_list(arg, parse_zone);
+ if(err) return err;
+ selection_list->typecode = SEL_ZONE;
+ return NULL; /* can't have any more options */
+#endif
+ case '-':
+ return _("embedded '-' among SysV options makes no sense");
+ break;
+ case '\0':
+ catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
+ break;
+ default:
+ return _("unsupported SysV option");
+ } /* switch */
+ } /* while */
+ return NULL;
+}
+
+/************************* parse BSD options **********************/
+static const char *parse_bsd_option(void){
+ const char *arg;
+ const char *err;
+
+ flagptr = ps_argv[thisarg]; /* assume we _have_ a '-' */
+ if(flagptr[0]=='-'){
+ if(!force_bsd) return _("cannot happen - problem #1");
+ }else{
+ flagptr--; /* off beginning, will increment before use */
+ if(personality & PER_FORCE_BSD){
+ if(!force_bsd) return _("cannot happen - problem #2");
+ }else{
+ if(force_bsd) return _("second chance parse failed, not BSD or SysV");
+ }
+ }
+
+ while(*++flagptr){
+ switch(*flagptr){
+ case '0' ... '9': /* end */
+ trace("0..9 pld BSD-style select by process ID\n");
+ arg=flagptr;
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_PID;
+ return NULL; /* can't have any more options */
+#if 0
+ case 'A':
+ /* maybe this just does a larger malloc() ? */
+ trace("A increases the argument space (Digital Unix)\n");
+ return _("option A is reserved");
+ break;
+ case 'C':
+ /* should divide result by 1-(e**(foo*log(bar))) */
+ trace("C use raw CPU time for %%CPU instead of decaying ave\n");
+ return _("option C is reserved");
+ break;
+#endif
+ case 'H': // The FreeBSD way (NetBSD:s OpenBSD:k FreeBSD:H -- NIH???)
+ trace("H print LWP (thread) info\n"); // was: Use /vmcore as c-dumpfile\n");
+ thread_flags |= TF_B_H;
+ //format_modifiers |= FM_L; // FIXME: determine if we need something like this
+ break;
+ case 'L': /* single */
+ trace("L list all format specifiers\n");
+ exclusive("L");
+ print_format_specifiers();
+ exit(0);
+ case 'M': // undocumented for now: these are proliferating!
+ trace("M MacOS X thread display, like AIX/Tru64\n");
+ thread_flags |= TF_B_m;
+ break;
+ case 'O': /* end */
+ trace("O like o + defaults, add new columns after PID, also sort\n");
+ arg=get_opt_arg();
+ if(!arg) return _("format or sort specification must follow O");
+ defer_sf_option(arg, SF_B_O);
+ return NULL; /* can't have any more options */
+ break;
+ case 'S':
+ trace("S include dead kids in sum\n");
+ include_dead_children = 1;
+ break;
+ case 'T':
+ trace("T select all processes on this terminal\n");
+ /* put our tty on a tiny list */
+ {
+ selection_node *node;
+ node = xmalloc(sizeof(selection_node));
+ node->u = xmalloc(sizeof(sel_union));
+ node->u[0].tty = cached_tty;
+ node->typecode = SEL_TTY;
+ node->n = 1;
+ node->next = selection_list;
+ selection_list = node;
+ }
+ break;
+ case 'U': /* end */
+ trace("U select processes for specified users\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of users must follow U");
+ err=parse_list(arg, parse_uid);
+ if(err) return err;
+ selection_list->typecode = SEL_EUID;
+ return NULL; /* can't have any more options */
+ case 'V': /* single */
+ trace("V show version info\n");
+ exclusive("V");
+ display_ps_version();
+ exit(0);
+ case 'W':
+ trace("W N/A get swap info from ... not /dev/drum.\n");
+ return _("obsolete W option not supported (you have a /dev/drum?)");
+ break;
+ case 'X':
+ trace("X old Linux i386 register format\n");
+ format_flags |= FF_LX;
+ break;
+ case 'Z': /* FreeBSD does MAC like SGI's Irix does it */
+ trace("Z print security label for Mandatory Access Control.\n");
+ format_modifiers |= FM_M;
+ break;
+ case 'a':
+ trace("a select all w/tty, including other users\n");
+ simple_select |= SS_B_a;
+ break;
+ case 'c':
+ trace("c true command name\n");
+ bsd_c_option = 1;
+ break;
+// case 'd':
+// trace("d FreeBSD-style tree\n");
+// forest_type = 'f';
+// break;
+ case 'e':
+ trace("e environment\n");
+ bsd_e_option = 1;
+ break;
+ case 'f':
+ trace("f ASCII art forest\n");
+ forest_type = 'b';
+ break;
+ case 'g':
+ trace("g _all_, even group leaders\n");
+ simple_select |= SS_B_g;
+ break;
+ case 'h':
+ trace("h repeat header\n");
+ if(header_type) return _("only one heading option may be specified");
+ if(personality & PER_BSD_h) header_type = HEAD_MULTI;
+ else header_type = HEAD_NONE;
+ break;
+ case 'j':
+ trace("j job control format\n");
+ format_flags |= FF_Bj;
+ break;
+ case 'k':
+ // OpenBSD: don't hide "kernel threads" -- like the swapper?
+ // trace("k Print LWP (thread) info.\n"); // was: Use /vmcore as c-dumpfile\n");
+
+ // NetBSD, and soon (?) FreeBSD: sort-by-keyword
+ trace("k specify sorting keywords\n");
+ arg=get_opt_arg();
+ if(!arg) return _("long sort specification must follow 'k'");
+ defer_sf_option(arg, SF_G_sort);
+ return NULL; /* can't have any more options */
+ case 'l':
+ trace("l display long format\n");
+ format_flags |= FF_Bl;
+ break;
+ case 'm':
+ trace("m all threads, sort on mem use, show mem info\n");
+ if(personality & PER_OLD_m){
+ format_flags |= FF_Lm;
+ break;
+ }
+ if(personality & PER_BSD_m){
+ defer_sf_option("pmem", SF_B_m);
+ break;
+ }
+ thread_flags |= TF_B_m;
+ break;
+ case 'n':
+ trace("n numeric output for WCHAN, and USER replaced by UID\n");
+ wchan_is_number = 1;
+ user_is_number = 1;
+ /* TODO add tty_is_number too? */
+ break;
+ case 'o': /* end */
+ trace("o specify user-defined format\n");
+ arg=get_opt_arg();
+ if(!arg) return _("format specification must follow o");
+ defer_sf_option(arg, SF_B_o);
+ return NULL; /* can't have any more options */
+ case 'p': /* end */
+ trace("p select by process ID\n");
+ arg=get_opt_arg();
+ if(!arg) return _("list of process IDs must follow p");
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_PID;
+ return NULL; /* can't have any more options */
+ case 'q': /* end */
+ trace("q Quick select by process ID\n");
+ arg=get_opt_arg();
+ if(!arg) return "List of process IDs must follow q.";
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_PID_QUICK;
+ return NULL; /* can't have any more options */
+ case 'r':
+ trace("r select running processes\n");
+ running_only = 1;
+ break;
+ case 's':
+ trace("s display signal format\n");
+ format_flags |= FF_Bs;
+ break;
+ case 't': /* end */
+ trace("t select by tty\n");
+ /* List of terminals (tty, pty...) _should_ follow t. */
+ arg=get_opt_arg();
+ if(!arg){
+ /* Wow, obsolete BSD syntax. Put our tty on a tiny list. */
+ selection_node *node;
+ node = xmalloc(sizeof(selection_node));
+ node->u = xmalloc(sizeof(sel_union));
+ node->u[0].tty = cached_tty;
+ node->typecode = SEL_TTY;
+ node->n = 1;
+ node->next = selection_list;
+ selection_list = node;
+ return NULL;
+ }
+ err=parse_list(arg, parse_tty);
+ if(err) return err;
+ selection_list->typecode = SEL_TTY;
+ return NULL; /* can't have any more options */
+ case 'u':
+ trace("u display user-oriented\n");
+ format_flags |= FF_Bu;
+ break;
+ case 'v':
+ trace("v display virtual memory\n");
+ format_flags |= FF_Bv;
+ break;
+ case 'w':
+ trace("w wide output\n");
+ w_count++;
+ break;
+ case 'x':
+ trace("x select processes without controlling ttys\n");
+ simple_select |= SS_B_x;
+ break;
+ case '-':
+ return _("embedded '-' among BSD options makes no sense");
+ break;
+ case '\0':
+ catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
+ break;
+ default:
+ return _("unsupported option (BSD syntax)");
+ } /* switch */
+ } /* while */
+ return NULL;
+}
+
+/*************** gnu long options **********************/
+
+/*
+ * Return the argument or NULL
+ */
+static const char *grab_gnu_arg(void){
+ switch(*flagptr){ /* argument is part of ps_argv[thisarg] */
+ default:
+ return NULL; /* something bad */
+ case '=': case ':':
+ if(*++flagptr) return flagptr; /* found it */
+ return NULL; /* empty '=' or ':' */
+ case '\0': /* try next argv[] */
+ ;
+ }
+ if(thisarg+2 > ps_argc) return NULL; /* there is nothing left */
+ /* argument follows ps_argv[thisarg] */
+ if(*(ps_argv[thisarg+1]) == '\0') return NULL;
+ return ps_argv[++thisarg];
+}
+
+typedef struct gnu_table_struct {
+ const char *name; /* long option name */
+ const void *jump; /* See gcc extension info. :-) */
+} gnu_table_struct;
+
+static int compare_gnu_table_structs(const void *a, const void *b){
+ return strcmp(((const gnu_table_struct*)a)->name,((const gnu_table_struct*)b)->name);
+}
+
+/* Option arguments are after ':', after '=', or in argv[n+1] */
+static const char *parse_gnu_option(void){
+ const char *arg;
+ const char *err;
+ char *s;
+ size_t sl;
+ char buf[16];
+ gnu_table_struct findme = { buf, NULL};
+ gnu_table_struct *found;
+ static const gnu_table_struct gnu_table[] = {
+ {"Group", &&case_Group}, /* rgid */
+ {"User", &&case_User}, /* ruid */
+ {"cols", &&case_cols},
+ {"columns", &&case_columns},
+ {"context", &&case_context},
+ {"cumulative", &&case_cumulative},
+ {"date-format", &&case_dateformat},
+ {"deselect", &&case_deselect}, /* -N */
+ {"forest", &&case_forest}, /* f -H */
+ {"format", &&case_format},
+ {"group", &&case_group}, /* egid */
+ {"header", &&case_header},
+ {"headers", &&case_headers},
+ {"heading", &&case_heading},
+ {"headings", &&case_headings},
+//{"help", &&case_help}, /* now TRANSLATABLE ! */
+ {"info", &&case_info},
+ {"lines", &&case_lines},
+ {"no-header", &&case_no_header},
+ {"no-headers", &&case_no_headers},
+ {"no-heading", &&case_no_heading},
+ {"no-headings", &&case_no_headings},
+ {"noheader", &&case_noheader},
+ {"noheaders", &&case_noheaders},
+ {"noheading", &&case_noheading},
+ {"noheadings", &&case_noheadings},
+ {"pid", &&case_pid},
+ {"ppid", &&case_ppid},
+ {"quick-pid", &&case_pid_quick},
+ {"rows", &&case_rows},
+ {"sid", &&case_sid},
+ {"signames", &&case_signames},
+ {"sort", &&case_sort},
+ {"tty", &&case_tty},
+ {"user", &&case_user}, /* euid */
+ {"version", &&case_version},
+ {"width", &&case_width},
+ };
+ const int gnu_table_count = sizeof(gnu_table)/sizeof(gnu_table_struct);
+
+ s = ps_argv[thisarg]+2;
+ sl = strcspn(s,":=");
+ if(sl > 15) return _("unknown gnu long option");
+ strncpy(buf, s, sl);
+ buf[sl] = '\0';
+ flagptr = s+sl;
+
+ found = bsearch(&findme, gnu_table, gnu_table_count,
+ sizeof(gnu_table_struct), compare_gnu_table_structs
+ );
+
+ if(!found) {
+ if (!strcmp(buf, the_word_help))
+ goto case_help;
+ return _("unknown gnu long option");
+ }
+
+ goto *(found->jump); /* See gcc extension info. :-) */
+
+ case_Group:
+ trace("--Group\n");
+ arg = grab_gnu_arg();
+ if(!arg) return _("list of real groups must follow --Group");
+ err=parse_list(arg, parse_gid);
+ if(err) return err;
+ selection_list->typecode = SEL_RGID;
+ return NULL;
+ case_User:
+ trace("--User\n");
+ arg = grab_gnu_arg();
+ if(!arg) return _("list of real users must follow --User");
+ err=parse_list(arg, parse_uid);
+ if(err) return err;
+ selection_list->typecode = SEL_RUID;
+ return NULL;
+ case_cols:
+ case_width:
+ case_columns:
+ trace("--cols\n");
+ arg = grab_gnu_arg();
+ if(arg && *arg){
+ long t;
+ char *endptr;
+ t = strtol(arg, &endptr, 0);
+ if(!*endptr && (t>0) && (t<2000000000)){
+ screen_cols = (int)t;
+ return NULL;
+ }
+ }
+ return _("number of columns must follow --cols, --width, or --columns");
+ case_cumulative:
+ trace("--cumulative\n");
+ if(s[sl]) return _("option --cumulative does not take an argument");
+ include_dead_children = 1;
+ return NULL;
+ case_dateformat:
+ arg=grab_gnu_arg();
+ if (!arg) return _("date format must follow --date-format");
+ if (lstart_format) free(lstart_format);
+ lstart_format = strdup(arg);
+ return NULL;
+ case_deselect:
+ trace("--deselect\n");
+ if(s[sl]) return _("option --deselect does not take an argument");
+ negate_selection = 1;
+ return NULL;
+ case_no_header:
+ case_no_headers:
+ case_no_heading:
+ case_no_headings:
+ case_noheader:
+ case_noheaders:
+ case_noheading:
+ case_noheadings:
+ trace("--noheaders\n");
+ if(s[sl]) return _("option --no-heading does not take an argument");
+ if(header_type) return _("only one heading option may be specified");
+ header_type = HEAD_NONE;
+ return NULL;
+ case_header:
+ case_headers:
+ case_heading:
+ case_headings:
+ trace("--headers\n");
+ if(s[sl]) return _("option --heading does not take an argument");
+ if(header_type) return _("only one heading option may be specified");
+ header_type = HEAD_MULTI;
+ return NULL;
+ case_forest:
+ trace("--forest\n");
+ if(s[sl]) return _("option --forest does not take an argument");
+ forest_type = 'g';
+ return NULL;
+ case_format:
+ trace("--format\n");
+ arg=grab_gnu_arg();
+ if(!arg) return _("format specification must follow --format");
+ defer_sf_option(arg, SF_G_format);
+ return NULL;
+ case_group:
+ trace("--group\n");
+ arg = grab_gnu_arg();
+ if(!arg) return _("list of effective groups must follow --group");
+ err=parse_list(arg, parse_gid);
+ if(err) return err;
+ selection_list->typecode = SEL_EGID;
+ return NULL;
+ case_help:
+ trace("--help\n");
+ arg = grab_gnu_arg();
+ do_help(arg, EXIT_SUCCESS);
+ case_info:
+ trace("--info\n");
+ exclusive("--info");
+ self_info();
+ exit(0);
+ return NULL;
+ case_pid:
+ trace("--pid\n");
+ arg = grab_gnu_arg();
+ if(!arg) return _("list of process IDs must follow --pid");
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_PID;
+ return NULL;
+ case_pid_quick:
+ trace("--quick-pid\n");
+ arg = grab_gnu_arg();
+ if(!arg) return "List of process IDs must follow --quick-pid.";
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_PID_QUICK;
+ return NULL;
+ case_ppid:
+ trace("--ppid\n");
+ arg = grab_gnu_arg();
+ if(!arg) return _("list of process IDs must follow --ppid");
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_PPID;
+ return NULL;
+ case_rows:
+ case_lines:
+ trace("--rows\n");
+ arg = grab_gnu_arg();
+ if(arg && *arg){
+ long t;
+ char *endptr;
+ t = strtol(arg, &endptr, 0);
+ if(!*endptr && (t>0) && (t<2000000000)){
+ screen_rows = (int)t;
+ return NULL;
+ }
+ }
+ return _("number of rows must follow --rows or --lines");
+ case_sid:
+ trace("--sid\n");
+ arg = grab_gnu_arg();
+ if(!arg) return _("some sid thing(s) must follow --sid");
+ err=parse_list(arg, parse_pid);
+ if(err) return err;
+ selection_list->typecode = SEL_SESS;
+ return NULL;
+ case_signames:
+ trace("--signames\n");
+ signal_names = TRUE;
+ return NULL;
+ case_sort:
+ trace("--sort\n");
+ arg=grab_gnu_arg();
+ if(!arg) return _("long sort specification must follow --sort");
+ defer_sf_option(arg, SF_G_sort);
+ return NULL;
+ case_tty:
+ trace("--tty\n");
+ arg = grab_gnu_arg();
+ if(!arg) return _("list of ttys must follow --tty");
+ err=parse_list(arg, parse_tty);
+ if(err) return err;
+ selection_list->typecode = SEL_TTY;
+ return NULL;
+ case_user:
+ trace("--user\n");
+ arg = grab_gnu_arg();
+ if(!arg) return _("list of effective users must follow --user");
+ err=parse_list(arg, parse_uid);
+ if(err) return err;
+ selection_list->typecode = SEL_EUID;
+ return NULL;
+ case_version:
+ trace("--version\n");
+ exclusive("--version");
+ display_ps_version();
+ exit(0);
+ return NULL;
+ case_context:
+ trace("--context\n");
+ format_flags |= FF_Fc;
+ return NULL;
+}
+
+/*************** process trailing PIDs **********************/
+static const char *parse_trailing_pids(void){
+ selection_node *pidnode; /* pid */
+ selection_node *grpnode; /* process group */
+ selection_node *sidnode; /* session */
+ char **argp; /* pointer to pointer to text of PID */
+ const char *err; /* error code that could or did happen */
+ int i;
+
+ i = ps_argc - thisarg; /* how many trailing PIDs, SIDs, PGRPs?? */
+ argp = ps_argv + thisarg;
+ thisarg = ps_argc - 1; /* we must be at the end now */
+
+ pidnode = xmalloc(sizeof(selection_node));
+ pidnode->u = xcalloc(i, sizeof(sel_union)); /* waste is insignificant */
+ pidnode->n = 0;
+
+ grpnode = xmalloc(sizeof(selection_node));
+ grpnode->u = xcalloc(i,sizeof(sel_union)); /* waste is insignificant */
+ grpnode->n = 0;
+
+ sidnode = xmalloc(sizeof(selection_node));
+ sidnode->u = xcalloc(i, sizeof(sel_union)); /* waste is insignificant */
+ sidnode->n = 0;
+
+ while(i--){
+ char *data;
+ data = *(argp++);
+ switch(*data){
+ default: err = parse_pid( data, pidnode->u + pidnode->n++); break;
+ case '-': err = parse_pid(++data, grpnode->u + grpnode->n++); break;
+ case '+': err = parse_pid(++data, sidnode->u + sidnode->n++); break;
+ }
+ if(err) return err; /* the node gets freed with the list */
+ }
+
+ if(pidnode->n){
+ pidnode->next = selection_list;
+ selection_list = pidnode;
+ selection_list->typecode = SEL_PID;
+ } /* else free both parts */
+
+ if(grpnode->n){
+ grpnode->next = selection_list;
+ selection_list = grpnode;
+ selection_list->typecode = SEL_PGRP;
+ } /* else free both parts */
+
+ if(sidnode->n){
+ sidnode->next = selection_list;
+ selection_list = sidnode;
+ selection_list->typecode = SEL_SESS;
+ } /* else free both parts */
+
+ return NULL;
+}
+
+/************** misc stuff ***********/
+
+static void reset_parser(void){
+ w_count = 0;
+}
+
+static int arg_type(const char *str){
+ int tmp = str[0];
+ if((tmp>='a') && (tmp<='z')) return ARG_BSD;
+ if((tmp>='A') && (tmp<='Z')) return ARG_BSD;
+ if((tmp>='0') && (tmp<='9')) return ARG_PID;
+ if(tmp=='+') return ARG_SESS;
+ if(tmp!='-') return ARG_FAIL;
+ tmp = str[1];
+ if((tmp>='a') && (tmp<='z')) return ARG_SYSV;
+ if((tmp>='A') && (tmp<='Z')) return ARG_SYSV;
+ if((tmp>='0') && (tmp<='9')) return ARG_PGRP;
+ if(tmp!='-') return ARG_FAIL;
+ tmp = str[2];
+ if((tmp>='a') && (tmp<='z')) return ARG_GNU;
+ if((tmp>='A') && (tmp<='Z')) return ARG_GNU;
+ if(tmp=='\0') return ARG_END;
+ return ARG_FAIL;
+}
+
+/* First assume sysv, because that is the POSIX and Unix98 standard. */
+static const char *parse_all_options(void){
+ const char *err = NULL;
+ int at;
+ while(++thisarg < ps_argc){
+ trace("parse_all_options calling arg_type for \"%s\"\n", ps_argv[thisarg]);
+ at = arg_type(ps_argv[thisarg]);
+ trace("ps_argv[thisarg] is %s\n", ps_argv[thisarg]);
+ switch(at){
+ case ARG_GNU:
+ err = parse_gnu_option();
+ break;
+ case ARG_SYSV:
+ if(!force_bsd){ /* else go past case ARG_BSD */
+ err = parse_sysv_option();
+ break;
+ case ARG_BSD:
+ if(force_bsd && !(personality & PER_FORCE_BSD)) return _("way bad");
+ }
+ prefer_bsd_defaults = 1;
+ err = parse_bsd_option();
+ break;
+ case ARG_PGRP:
+ case ARG_SESS:
+ case ARG_PID:
+ prefer_bsd_defaults = 1;
+ err = parse_trailing_pids();
+ break;
+ case ARG_END:
+ case ARG_FAIL:
+ trace(" FAIL/END on [%s]\n",ps_argv[thisarg]);
+ return _("garbage option");
+ break;
+ default:
+ printf(" ? %s\n",ps_argv[thisarg]);
+ return _("something broke");
+ } /* switch */
+ if(err) return err;
+ } /* while */
+ return NULL;
+}
+
+static void choose_dimensions(void){
+ if(w_count && (screen_cols<132)) screen_cols=132;
+ if(w_count>1) screen_cols=OUTBUF_SIZE;
+ /* perhaps --html and --null should set unlimited width */
+}
+
+static const char *thread_option_check(void){
+ if(!thread_flags){
+ thread_flags = TF_show_proc;
+ return NULL;
+ }
+
+ if(forest_type){
+ return _("thread display conflicts with forest display");
+ }
+ //thread_flags |= TF_no_forest;
+
+ if((thread_flags&TF_B_H) && (thread_flags&(TF_B_m|TF_U_m)))
+ return _("thread flags conflict; can't use H with m or -m");
+ if((thread_flags&TF_B_m) && (thread_flags&TF_U_m))
+ return _("thread flags conflict; can't use both m and -m");
+ if((thread_flags&TF_U_L) && (thread_flags&TF_U_T))
+ return _("thread flags conflict; can't use both -L and -T");
+
+ if(thread_flags&TF_B_H) thread_flags |= (TF_show_proc|TF_loose_tasks);
+ if(thread_flags&(TF_B_m|TF_U_m)) thread_flags |= (TF_show_proc|TF_show_task|TF_show_both);
+
+ if(thread_flags&(TF_U_T|TF_U_L)){
+ if(thread_flags&(TF_B_m|TF_U_m|TF_B_H)){
+ // Got a thread style, so format modification is a requirement?
+ // Maybe -T/-L has H thread style though. (sorting interaction?)
+ //return _("Huh? Tell procps@freelists.org what you expected.");
+ thread_flags |= TF_must_use;
+ }else{
+ // using -L/-T thread style, so format from elsewhere is OK
+ thread_flags |= TF_show_task; // or like the H option?
+ //thread_flags |= TF_no_sort;
+ }
+ }
+
+ return NULL;
+}
+
+int arg_parse(int argc, char *argv[]){
+ const char *err = NULL;
+ const char *err2 = NULL;
+ ps_argc = argc;
+ ps_argv = argv;
+ thisarg = 0;
+
+ if(personality & PER_FORCE_BSD) goto try_bsd;
+
+ err = parse_all_options();
+ if(err) goto try_bsd;
+ err = thread_option_check();
+ if(err) goto try_bsd;
+ err = process_sf_options();
+ if(err) goto try_bsd;
+ err = select_bits_setup();
+ if(err) goto try_bsd;
+
+ choose_dimensions();
+ return 0;
+
+try_bsd:
+ trace("--------- now try BSD ------\n");
+
+ reset_global();
+ reset_parser();
+ reset_sortformat();
+ format_flags = 0;
+ ps_argc = argc;
+ ps_argv = argv;
+ thisarg = 0;
+ /* no need to reset flagptr */
+ force_bsd=1;
+ prefer_bsd_defaults=1;
+ if(!( (PER_OLD_m|PER_BSD_m) & personality )) /* if default m setting... */
+ personality |= PER_OLD_m; /* Prefer old Linux over true BSD. */
+ /* Do not set PER_FORCE_BSD! It is tested below. */
+
+ err2 = parse_all_options();
+ if(err2) goto total_failure;
+ err2 = thread_option_check();
+ if(err2) goto total_failure;
+ err2 = process_sf_options();
+ if(err2) goto total_failure;
+ err2 = select_bits_setup();
+ if(err2) goto total_failure;
+
+ choose_dimensions();
+ return 0;
+
+total_failure:
+ reset_parser();
+ if(personality & PER_FORCE_BSD) fprintf(stderr, _("error: %s\n"), err2);
+ else fprintf(stderr, _("error: %s\n"), err);
+ do_help(NULL, EXIT_FAILURE);
+}
diff --git a/src/ps/regression b/src/ps/regression
new file mode 100644
index 0000000..c71c826
--- /dev/null
+++ b/src/ps/regression
@@ -0,0 +1,26 @@
+-u 500 -o pid,ppid,fname,comm,args # right margin trouble
+-u 500 -o pid,ppid,fname,comm,args,wchan,wchan,wchan,wchan,wchan,nice,wchan
+-u 500 -o pid,pid,pid,pid,user,user,user,args # had trouble
+-u 500 -o user,user,user,pid,pid,pid,pid,args # no trouble!
+
+Test with each type of field (RIGHT,LEFT,UNLIMITED...) hanging off the
+edge of the screen and each type of field to the left of the one that
+hangs off the edge.
+
+Test "ps ef" as _both_ normal user and root. Especially after su!
+
+On a 108-col screen, try "ps alx" and "ps alx | cat"
+
+These ought to be the same:
+CMD_ENV=old ps -m
+CMD_ENV=old ps m
+
+These ought to be the same:
+CMD_ENV=old ps -X
+CMD_ENV=old ps X
+ps X
+ps -X # needs to be a non-SysV option
+
+This should fail:
+ps x -x
+
diff --git a/src/ps/select.c b/src/ps/select.c
new file mode 100644
index 0000000..ab71040
--- /dev/null
+++ b/src/ps/select.c
@@ -0,0 +1,162 @@
+/*
+ * select.c - ps process selection
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net
+ * Copyright © 2004-2020 Craig Small <csmall@dropbear.xyz
+ * Copyright © 1998-2002 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+//#define process_group_leader(p) (rSv(ID_PID, s_int, p) == rSv(ID_TGID, s_int, p))
+//#define some_other_user(p) (rSv(ID_EUID, u_int, p) != cached_euid)
+#define has_our_euid(p) (rSv(ID_EUID, u_int, p) == cached_euid)
+#define on_our_tty(p) (rSv(TTY, s_int, p) == cached_tty)
+#define running(p) (rSv(STATE, s_ch, p) == 'R' || rSv(STATE, s_ch, p) == 'D')
+#define session_leader(p) (rSv(ID_SESSION, s_int, p) == rSv(ID_TGID, s_int, p))
+#define without_a_tty(p) (!rSv(TTY, s_int, p))
+
+static unsigned long select_bits = 0;
+
+/***** prepare select_bits for use */
+const char *select_bits_setup(void){
+ int switch_val = 0;
+ /* don't want a 'g' screwing up simple_select */
+ if(!simple_select && !prefer_bsd_defaults){
+ select_bits = 0xaa00; /* the STANDARD selection */
+ return NULL;
+ }
+ /* For every BSD but SunOS, the 'g' option is a NOP. (enabled by default) */
+ if( !(personality & PER_NO_DEFAULT_g) && !(simple_select&(SS_U_a|SS_U_d)) )
+ switch_val = simple_select|SS_B_g;
+ else
+ switch_val = simple_select;
+ switch(switch_val){
+ /* UNIX options */
+ case SS_U_a | SS_U_d: select_bits = 0x3f3f; break; /* 3333 or 3f3f */
+ case SS_U_a: select_bits = 0x0303; break; /* 0303 or 0f0f */
+ case SS_U_d: select_bits = 0x3333; break;
+ /* SunOS 4 only (others have 'g' enabled all the time) */
+ case 0: select_bits = 0x0202; break;
+ case SS_B_a: select_bits = 0x0303; break;
+ case SS_B_x : select_bits = 0x2222; break;
+ case SS_B_x | SS_B_a: select_bits = 0x3333; break;
+ /* General BSD options */
+ case SS_B_g : select_bits = 0x0a0a; break;
+ case SS_B_g | SS_B_a: select_bits = 0x0f0f; break;
+ case SS_B_g | SS_B_x : select_bits = 0xaaaa; break;
+ case SS_B_g | SS_B_x | SS_B_a: /* convert to -e instead of using 0xffff */
+ all_processes = 1;
+ simple_select = 0;
+ break;
+ default:
+ return _("process selection options conflict");
+ break;
+ }
+ return NULL;
+}
+
+/***** selected by simple option? */
+static int table_accept(proc_t *buf){
+ unsigned proc_index;
+ proc_index = (has_our_euid(buf) <<0)
+ | (session_leader(buf) <<1)
+ | (without_a_tty(buf) <<2)
+ | (on_our_tty(buf) <<3);
+ return (select_bits & (1<<proc_index));
+}
+
+/***** selected by some kind of list? */
+static int proc_was_listed(proc_t *buf){
+ selection_node *sn = selection_list;
+ int i;
+ if(!sn) return 0;
+ while(sn){
+ switch(sn->typecode){
+ default:
+ catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
+
+#define return_if_match(foo,bar) \
+ i=sn->n; while(i--) \
+ if((unsigned)foo == (unsigned)(*(sn->u+i)).bar) \
+ return 1
+
+ break; case SEL_RUID: return_if_match(rSv(ID_RUID, u_int, buf),uid);
+ break; case SEL_EUID: return_if_match(rSv(ID_EUID, u_int, buf),uid);
+ break; case SEL_SUID: return_if_match(rSv(ID_SUID, u_int, buf),uid);
+ break; case SEL_FUID: return_if_match(rSv(ID_FUID, u_int, buf),uid);
+
+ break; case SEL_RGID: return_if_match(rSv(ID_RGID, u_int, buf),gid);
+ break; case SEL_EGID: return_if_match(rSv(ID_EGID, u_int, buf),gid);
+ break; case SEL_SGID: return_if_match(rSv(ID_SGID, u_int, buf),gid);
+ break; case SEL_FGID: return_if_match(rSv(ID_FGID, u_int, buf),gid);
+
+ break; case SEL_PGRP: return_if_match(rSv(ID_PGRP, s_int, buf),pid);
+ break; case SEL_PID : return_if_match(rSv(ID_TGID, s_int, buf),pid);
+ break; case SEL_PID_QUICK : return_if_match(rSv(ID_TGID, s_int, buf),pid);
+ break; case SEL_PPID: return_if_match(rSv(ID_PPID, s_int, buf),ppid);
+ break; case SEL_TTY : return_if_match(rSv(TTY, s_int, buf),tty);
+ break; case SEL_SESS: return_if_match(rSv(ID_SESSION, s_int, buf),pid);
+
+ break;
+ case SEL_COMM:
+ i=sn->n;
+ while(i--) {
+ /* special case, comm is 16 characters but match is longer */
+ if (strlen(rSv(CMD, str, buf)) == 15 && strlen((*(sn->u+i)).cmd) >= 15)
+ if(!strncmp( rSv(CMD, str, buf), (*(sn->u+i)).cmd, 15 )) return 1;
+ if(!strncmp( rSv(CMD, str, buf), (*(sn->u+i)).cmd, 63 )) return 1;
+ }
+
+
+#undef return_if_match
+
+ }
+ sn = sn->next;
+ }
+ return 0;
+}
+
+
+/***** This must satisfy Unix98 and as much BSD as possible */
+int want_this_proc(proc_t *buf){
+ int accepted_proc = 1; /* assume success */
+ /* elsewhere, convert T to list, U sets x implicitly */
+
+ /* handle -e -A */
+ if(all_processes) goto finish;
+
+ /* use table for -a a d g x */
+ if((simple_select || !selection_list))
+ if(table_accept(buf)) goto finish;
+
+ /* search lists */
+ if(proc_was_listed(buf)) goto finish;
+
+ /* fail, fall through to loose ends */
+ accepted_proc = 0;
+
+ /* do r N */
+finish:
+ if(running_only && !running(buf)) accepted_proc = 0;
+ if(negate_selection) return !accepted_proc;
+ return accepted_proc;
+}
diff --git a/src/ps/signames.c b/src/ps/signames.c
new file mode 100644
index 0000000..129a4c2
--- /dev/null
+++ b/src/ps/signames.c
@@ -0,0 +1,170 @@
+/*
+ * signames.c - ps signal names
+ *
+ * Copyright © 2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2020 Luis Chamberlain <mcgrof@kernel.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+
+#include "common.h"
+#include "signals.h"
+
+/* For libraries like musl */
+#ifndef __SIGRTMIN
+#define __SIGRTMIN SIGRTMIN
+#endif
+#ifndef __SIGRTMAX
+#define __SIGRTMAX SIGRTMAX
+#endif
+
+/*
+ * The actual list of unsupported signals varies by operating system. This
+ * program is Linux specific as it processes /proc/ for signal information and
+ * there is no generic way to extract each process signal information for each
+ * OS. This program also relies on Linux glibc defines to figure out which
+ * signals are reserved for use by libc and then which ones are real time
+ * specific.
+ */
+
+/*
+ * As per glibc:
+ *
+ * A system that defines real time signals overrides __SIGRTMAX to be something
+ * other than __SIGRTMIN. This also means we can count on __SIGRTMIN being the
+ * first real time signal, meaning what Linux programs it for your architecture
+ * in the kernel. SIGRTMIN then will be the application specific first real
+ * time signal, that is, on top of libc. The values in between
+ *
+ * __SIGRTMIN .. SIGRTMIN
+ *
+ * are used by * libc, typically for helping threading implementation.
+ */
+static const char *sigstat_strsignal_abbrev(int sig, char *abbrev, size_t len)
+{
+ memset(abbrev, '\0', len);
+
+ if (sig == 0 || sig >= NSIG) {
+ snprintf(abbrev, len, "BOGUS_%02d", sig - _NSIG);
+ return abbrev;
+ }
+
+ /*
+ * The standard lower signals we can count on this being the kernel
+ * specific SIGRTMIN.
+ */
+ if (sig < __SIGRTMIN) {
+ const char *signame = NULL;
+#ifdef HAVE_SIGABBREV_NP
+ signame = sigabbrev_np(sig);
+#else
+ signame = signal_number_to_name(sig);
+#endif /* HAVE_SIGABBREV_NP */
+ if (signame != NULL && signame[0] != '\0') {
+ strncpy(abbrev, signame, len);
+ return abbrev;
+ }
+ }
+
+ /* This means your system should *not* have realtime signals */
+ if (__SIGRTMAX == __SIGRTMIN) {
+ snprintf(abbrev, len, "INVALID_%02d", sig);
+ return abbrev;
+ }
+
+ /*
+ * If we're dealing with a libc real time signal start counting
+ * after libc's version of SIGRTMIN
+ */
+ if (sig >= SIGRTMIN) {
+ if (sig == SIGRTMIN)
+ snprintf(abbrev, len, "RTMIN");
+ else if (sig == SIGRTMAX)
+ snprintf(abbrev, len, "RTMAX");
+ else
+ snprintf(abbrev, len, "RTMIN+%02d", sig - SIGRTMIN);
+ } else
+ snprintf(abbrev, len, "LIBC+%02d", sig - __SIGRTMIN);
+
+ return abbrev;
+}
+
+/*
+ * For instance SIGTERM is 15, but its actual mask value is
+ * 1 << (15-1) = 0x4000
+ */
+static uint64_t mask_sig_val_num(int signum)
+{
+ return ((uint64_t) 1 << (signum -1));
+}
+
+int print_signame(char *restrict const outbuf, const char *restrict const sig, const size_t len_in)
+{
+ unsigned int i;
+ char abbrev[PATH_MAX];
+ unsigned int n = 0;
+ char *c = outbuf;
+ size_t len = len_in;
+ uint64_t mask, mask_in;
+ uint64_t test_val = 0;
+
+ if (1 != sscanf(sig, "%" PRIu64, &mask_in))
+ return 0;
+ mask = mask_in;
+
+ for (i=1; i < NSIG; i++) {
+ test_val = mask_sig_val_num(i);
+ if (test_val & mask) {
+ n = strlen(sigstat_strsignal_abbrev(i, abbrev, PATH_MAX));
+ if (n+1 >= len) { // +1 for the '+'
+ strcpy(c, "+");
+ len -= 1;
+ c += 1;
+ break;
+ } else {
+ n = snprintf(c, len, (c==outbuf)?"%s":",%s",
+ sigstat_strsignal_abbrev(i, abbrev,
+ PATH_MAX));
+ len -= n;
+ c+=n;
+ }
+ }
+ }
+ if (c == outbuf) {
+ n = snprintf(c, len, "%c", '-');
+ len -= n;
+ c += n;
+ }
+ return (int) (c-outbuf);
+}
diff --git a/src/ps/sortformat.c b/src/ps/sortformat.c
new file mode 100644
index 0000000..a76ddee
--- /dev/null
+++ b/src/ps/sortformat.c
@@ -0,0 +1,949 @@
+/*
+ * sortformat.c - ps output sorting
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net
+ * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz
+ * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi>
+ * Copyright © 1998-2004 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <sys/types.h>
+
+#include "misc.h"
+#include "xalloc.h"
+
+#include "common.h"
+
+static sf_node *sf_list = NULL; /* deferred sorting and formatting */
+static int have_gnu_sort = 0; /* if true, "O" must be format */
+static int already_parsed_sort = 0; /* redundantly set in & out of fn */
+static int already_parsed_format = 0;
+
+
+/**************** Parse single format specifier *******************/
+static format_node *do_one_spec(const char *spec, const char *override){
+ const format_struct *fs;
+ const macro_struct *ms;
+
+ fs = search_format_array(spec);
+ if(fs){
+ int w1, w2;
+ format_node *thisnode;
+ thisnode = xmalloc(sizeof(format_node));
+ if(fs->flags & CF_PIDMAX){
+ w1 = (int)procps_pid_length();
+ w2 = strlen(fs->head);
+ if(w2>w1) w1=w2; // FIXME w/ separate header/body column sizing
+ }else{
+ w1 = fs->width;
+ }
+ if(override){
+ w2 = strlen(override);
+ thisnode->width = (w1>w2)?w1:w2;
+ thisnode->name = strdup(override);
+ }else{
+ thisnode->width = w1;
+ thisnode->name = strdup(fs->head);
+ }
+ thisnode->pr = fs->pr;
+ thisnode->vendor = fs->vendor;
+ thisnode->flags = fs->flags;
+ thisnode->next = NULL;
+ return thisnode;
+ }
+
+ /* That failed, so try it as a macro. */
+ ms = search_macro_array(spec);
+ if(ms){
+ format_node *list = NULL;
+ format_node *newnode;
+ const char *walk;
+ int dist;
+ char buf[16]; /* trust strings will be short (from above, not user) */
+ walk = ms->head;
+ while(*walk){
+ dist = strcspn(walk, ", ");
+ strncpy(buf,walk,dist);
+ buf[dist] = '\0';
+ newnode = do_one_spec(buf,override); /* call self, assume success */
+ newnode->next = list;
+ list = newnode;
+ walk += dist;
+ if(*walk) walk++;
+ }
+ return list;
+ }
+ return NULL; /* bad, spec not found */
+}
+
+
+/************ must wrap user format in default *************/
+static void O_wrap(sf_node *sfn, int otype){
+ format_node *fnode;
+ format_node *endp;
+ const char *trailer;
+
+ trailer = (otype=='b') ? "END_BSD" : "END_SYS5" ;
+
+ fnode = do_one_spec("pid",NULL);
+ if(!fnode)catastrophic_failure(__FILE__, __LINE__, _("seriously crashing: goodbye cruel world"));
+ endp = sfn->f_cooked; while(endp->next) endp = endp->next; /* find end */
+ endp->next = fnode;
+
+ fnode = do_one_spec(trailer,NULL);
+ if(!fnode)catastrophic_failure(__FILE__, __LINE__, _("seriously crashing: goodbye cruel world"));
+ endp = fnode; while(endp->next) endp = endp->next; /* find end */
+ endp->next = sfn->f_cooked;
+ sfn->f_cooked = fnode;
+}
+
+/******************************************************************
+ * Used to parse option AIX field descriptors.
+ * Put each completed format_node onto the list starting at ->f_cooked
+ */
+static const char *aix_format_parse(sf_node *sfn){
+ char *buf; /* temp copy of arg to hack on */
+ char *walk;
+ int items;
+
+ /*** sanity check and count items ***/
+ items = 0;
+ walk = sfn->sf;
+ /* state machine */ {
+ int c = *walk++;
+ initial:
+ if(c=='%') goto get_desc;
+ if(!c) goto looks_ok;
+ /* get_text: */
+ items++;
+ get_more:
+ c = *walk++;
+ if(c=='%') goto get_desc;
+ if(c==' ') goto get_more;
+ if(c) goto aix_oops;
+ goto looks_ok;
+ get_desc:
+ items++;
+ c = *walk++;
+ if(c&&c!=' ') goto initial;
+ return _("missing AIX field descriptor");
+ aix_oops:
+ return _("improper AIX field descriptor");
+ looks_ok:
+ ;
+ }
+
+ /*** sanity check passed ***/
+ buf = strdup(sfn->sf);
+ walk = sfn->sf;
+
+ while(items--){
+ format_node *fnode; /* newly allocated */
+ format_node *endp; /* for list manipulation */
+
+ if(*walk == '%'){
+ const aix_struct *aix;
+ walk++;
+ if(*walk == '%')
+ return _("missing AIX field descriptor");
+ aix = search_aix_array(*walk);
+ walk++;
+ if(!aix){
+ free(buf);
+ return _("unknown AIX field descriptor");
+ }
+ fnode = do_one_spec(aix->spec, aix->head);
+ if(!fnode){
+ free(buf);
+ return _("AIX field descriptor processing bug");
+ }
+ } else {
+ size_t len;
+ len = strcspn(walk, "%");
+ memcpy(buf,walk,len);
+ buf[len] = '\0';
+ walk += len;
+ fnode = xmalloc(sizeof(format_node));
+ fnode->width = len < INT_MAX ? len : INT_MAX;
+ fnode->name = strdup(buf);
+ fnode->pr = NULL; /* checked for */
+ fnode->vendor = AIX;
+ fnode->flags = CF_PRINT_EVERY_TIME;
+ fnode->next = NULL;
+ }
+
+ endp = fnode; while(endp->next) endp = endp->next; /* find end */
+ endp->next = sfn->f_cooked;
+ sfn->f_cooked = fnode;
+ }
+ free(buf);
+ already_parsed_format = 1;
+ return NULL;
+}
+
+/***************************************************************
+ * Used to parse option O lists. Option O is shared between
+ * sorting and formatting. Users may expect one or the other.
+ * Put each completed format_node onto the list starting at ->f_cooked
+ */
+static const char *format_parse(sf_node *sfn){
+ char *buf; /* temp copy of arg to hack on */
+ char *sep_loc; /* separator location: " \t,\n" */
+ char *walk;
+ const char *err; /* error code that could or did happen */
+ format_node *fnode;
+ int items;
+ int need_item;
+ static char errbuf[80]; /* for variable-text error message */
+
+ /*** prepare to operate ***/
+ buf = strdup(sfn->sf);
+
+ /*** sanity check and count items ***/
+ need_item = 1; /* true */
+ items = 0;
+ walk = buf;
+ do{
+ switch(*walk){
+ case ' ': case ',': case '\t': case '\n': case '\0':
+ /* Linux extension: allow \t and \n as delimiters */
+ if(need_item){
+ free(buf);
+ goto improper;
+ }
+ need_item=1;
+ break;
+ default:
+ if(need_item) items++;
+ need_item=0;
+ }
+ } while (*++walk);
+
+ if(!items){
+ free(buf);
+ goto empty;
+ }
+#ifdef STRICT_LIST
+ if(need_item){ /* can't have trailing deliminator */
+ free(buf);
+ goto improper;
+ }
+#else
+ if(need_item){ /* allow 1 trailing deliminator */
+ *--walk='\0'; /* remove the trailing deliminator */
+ }
+#endif
+ /*** actually parse the list ***/
+ walk = buf;
+ while(items--){
+ format_node *endp;
+ char *equal_loc;
+ char *colon_loc;
+ if(!walk) catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
+ sep_loc = strpbrk(walk," ,\t\n");
+ /* if items left, then sep_loc is not in header override */
+ if(items && sep_loc) *sep_loc = '\0';
+ equal_loc = strpbrk(walk,"=");
+ if(equal_loc){ /* if header override */
+ *equal_loc = '\0';
+ equal_loc++;
+ }
+ colon_loc = strpbrk(walk,":");
+ if(colon_loc){ /* if width override */
+ *colon_loc = '\0';
+ colon_loc++;
+ if(strspn(colon_loc,"0123456789") != strlen(colon_loc) || *colon_loc=='0' || !*colon_loc || atoi(colon_loc) <= 0){
+ free(buf);
+ goto badwidth;
+ }
+ }
+ fnode = do_one_spec(walk,equal_loc);
+ if(!fnode){
+ if(!*errbuf){ /* if didn't already create an error string */
+ snprintf(
+ errbuf,
+ sizeof(errbuf),
+ _("unknown user-defined format specifier \"%s\""),
+ walk
+ );
+ }
+ free(buf);
+ goto unknown;
+ }
+ if(colon_loc){
+ if(fnode->next){
+ free(buf);
+ goto notmacro;
+ }
+ // FIXME: enforce signal width to 8, 9, or 16 (grep: SIGNAL wide_signals)
+ fnode->width = atoi(colon_loc); // already verified to be a number
+ if(fnode->width <= 0) catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
+ }
+ endp = fnode; while(endp->next) endp = endp->next; /* find end */
+ endp->next = sfn->f_cooked;
+ sfn->f_cooked = fnode;
+ walk = sep_loc ? sep_loc + 1 : NULL; /* point to next item, if any */
+ }
+ free(buf);
+ already_parsed_format = 1;
+ return NULL;
+
+ /* errors may cause a retry looking for AIX format codes */
+ if(0) unknown: err=errbuf;
+ if(0) empty: err=_("empty format list");
+ if(0) improper: err=_("improper format list");
+ if(0) badwidth: err=_("column widths must be unsigned decimal numbers");
+ if(0) notmacro: err=_("can not set width for a macro (multi-column) format specifier");
+ if(strchr(sfn->sf,'%')) err = aix_format_parse(sfn);
+ return err;
+}
+
+/**************** Parse single sort specifier *******************/
+static sort_node *do_one_sort_spec(const char *spec){
+ const format_struct *fs;
+ enum pids_sort_order reverse = PIDS_SORT_ASCEND;
+ if(*spec == '-'){
+ reverse = PIDS_SORT_DESCEND;
+ spec++;
+ } else if(*spec == '+'){
+ spec++;
+ }
+ fs = search_format_array(spec);
+ if(fs){
+ sort_node *thisnode;
+ thisnode = xmalloc(sizeof(sort_node));
+ thisnode->sr = fs->sr;
+ // next is a special pointer, called to help with rel enums
+ thisnode->xe = (int(*)(char*,proc_t*))fs->pr;
+ thisnode->reverse = reverse;
+ thisnode->next = NULL;
+ return thisnode;
+ }
+ return NULL; /* bad, spec not found */
+}
+
+
+/**************************************************************
+ * Used to parse long sorting options.
+ * Put each completed sort_node onto the list starting at ->s_cooked
+ */
+static const char *long_sort_parse(sf_node *sfn){
+ char *buf; /* temp copy of arg to hack on */
+ char *sep_loc; /* separator location: " \t,\n" */
+ char *walk;
+ sort_node *snode;
+ int items;
+ int need_item;
+
+ /*** prepare to operate ***/
+ buf = strdup(sfn->sf);
+
+ /*** sanity check and count items ***/
+ need_item = 1; /* true */
+ items = 0;
+ walk = buf;
+ do{
+ switch(*walk){
+ case ' ': case ',': case '\t': case '\n': case '\0':
+ if(need_item){
+ free(buf);
+ return _("improper sort list");
+ }
+ need_item=1;
+ break;
+ default:
+ if(need_item) items++;
+ need_item=0;
+ }
+ } while (*++walk);
+ if(!items){
+ free(buf);
+ return _("empty sort list");
+ }
+#ifdef STRICT_LIST
+ if(need_item){ /* can't have trailing deliminator */
+ free(buf);
+ return _("improper sort list");
+ }
+#else
+ if(need_item){ /* allow 1 trailing deliminator */
+ *--walk='\0'; /* remove the trailing deliminator */
+ }
+#endif
+ /*** actually parse the list ***/
+ walk = buf;
+ while(items--){
+ sort_node *endp;
+ sep_loc = strpbrk(walk," ,\t\n");
+ if(sep_loc) *sep_loc = '\0';
+ snode = do_one_sort_spec(walk);
+ if(!snode){
+ free(buf);
+ return _("unknown sort specifier");
+ }
+ endp = snode; while(endp->next) endp = endp->next; /* find end */
+ endp->next = sfn->s_cooked;
+ sfn->s_cooked = snode;
+ walk = sep_loc + 1; /* point to next item, if any */
+ }
+ free(buf);
+ already_parsed_sort = 1;
+ return NULL;
+}
+
+
+
+
+
+
+/************ pre-parse short sorting option *************/
+/* Errors _must_ be detected so that the "O" option can try to
+ * reparse as formatting codes.
+ */
+static const char *verify_short_sort(const char *arg){
+ const char all[] = "CGJKMNPRSTUcfgjkmnoprstuvy+-";
+ char checkoff[256];
+ int i;
+ const char *walk;
+ int tmp;
+ if(strspn(arg,all) != strlen(arg)) return _("bad sorting code");
+ for(i=256; i--;) checkoff[i] = 0;
+ walk = arg;
+ for(;;){
+ tmp = *walk;
+ if(tmp < 0 || (size_t)tmp >= sizeof(checkoff)) return _("bad sorting code");
+ switch(tmp){
+ case '\0':
+ return NULL; /* looks good */
+ case '+':
+ case '-':
+ tmp = *(walk+1);
+ if(!tmp || tmp=='+' || tmp=='-') return _("bad sorting code");
+ break;
+ case 'P':
+ if(forest_type) return _("PPID sort and forest output conflict");
+ /* fall through */
+ default:
+ if(checkoff[tmp]) return _("bad sorting code"); /* repeated */
+ /* ought to check against already accepted sort options */
+ checkoff[tmp] = 1;
+ break;
+ }
+ walk++;
+ }
+}
+
+
+
+/************ parse short sorting option *************/
+static const char *short_sort_parse(sf_node *sfn){
+ enum pids_sort_order direction = PIDS_SORT_ASCEND;
+ const char *walk;
+ int tmp;
+ sort_node *snode;
+ sort_node *endp;
+ const struct shortsort_struct *ss;
+ walk = sfn->sf;
+ for(;;){
+ tmp = *walk;
+ switch(tmp){
+ case '\0':
+ already_parsed_sort = 1;
+ return NULL;
+ case '+':
+ direction = PIDS_SORT_ASCEND;
+ break;
+ case '-':
+ direction = PIDS_SORT_DESCEND;
+ break;
+ default:
+ ss = search_shortsort_array(tmp);
+ if(!ss) return _("unknown sort specifier");
+ snode = do_one_sort_spec(ss->spec);
+ if(!snode) return _("unknown sort specifier");
+ snode->reverse = direction;
+ endp = snode; while(endp->next) endp = endp->next; /* find end */
+ endp->next = sfn->s_cooked;
+ sfn->s_cooked = snode;
+ direction = 0;
+ break;
+ }
+ walk++;
+ }
+}
+
+/******************* high-level below here *********************/
+
+
+/*
+ * Used to parse option O lists. Option O is shared between
+ * sorting and formatting. Users may expect one or the other.
+ * Recursion is to preserve original order.
+ */
+static const char *parse_O_option(sf_node *sfn){
+ const char *err; /* error code that could or did happen */
+
+ if(sfn->next){
+ err = parse_O_option(sfn->next);
+ if(err) return err;
+ }
+
+ switch(sfn->sf_code){
+ case SF_B_o: case SF_G_format: case SF_U_o: /*** format ***/
+ err = format_parse(sfn);
+ if(!err) already_parsed_format = 1;
+ break;
+ case SF_U_O: /*** format ***/
+ /* Can have -l -f f u... set already_parsed_format like DEC does */
+ if(already_parsed_format) return _("option -O can not follow other format options");
+ err = format_parse(sfn);
+ if(err) return err;
+ already_parsed_format = 1;
+ O_wrap(sfn,'u'); /* must wrap user format in default */
+ break;
+ case SF_B_O: /*** both ***/
+ if(have_gnu_sort || already_parsed_sort) err = _("multiple sort options");
+ else err = verify_short_sort(sfn->sf);
+ if(!err){ /* success as sorting code */
+ short_sort_parse(sfn);
+ already_parsed_sort = 1;
+ return NULL;
+ }
+ if(already_parsed_format){
+ err = _("option O is neither first format nor sort order");
+ break;
+ }
+ if(!format_parse(sfn)){ /* if success as format code */
+ already_parsed_format = 1;
+ O_wrap(sfn,'b'); /* must wrap user format in default */
+ return NULL;
+ }
+ break;
+ case SF_G_sort: case SF_B_m: /*** sort ***/
+ if(already_parsed_sort) err = _("multiple sort options");
+ else err = long_sort_parse(sfn);
+ already_parsed_sort = 1;
+ break;
+ default: /*** junk ***/
+ catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
+ }
+ return err; /* could be NULL */
+}
+
+
+/************ Main parser calls this to save lists for later **********/
+/* store data for later and return 1 if arg looks non-standard */
+int defer_sf_option(const char *arg, int source){
+ sf_node *sfn;
+ char buf[16];
+ int dist;
+ const format_struct *fs;
+ int need_item = 1;
+
+ sfn = xmalloc(sizeof(sf_node));
+ sfn->sf = strdup(arg);
+ sfn->sf_code = source;
+ sfn->s_cooked = NULL;
+ sfn->f_cooked = NULL;
+ sfn->next = sf_list;
+ sf_list = sfn;
+
+ if(source == SF_G_sort) have_gnu_sort = 1;
+
+ /* Now try to find an excuse to ignore broken Unix98 parsing. */
+ if(source != SF_U_o) return 1; /* Wonderful! Already non-Unix98. */
+ do{
+ switch(*arg){
+ case ' ': case ',': case '\0': /* no \t\n\r support in Unix98 */
+ if(need_item) return 1; /* something wrong */
+ need_item=1;
+ break;
+ case '=':
+ if(need_item) return 1; /* something wrong */
+ return 0; /* broken Unix98 parsing is required */
+ default:
+ if(!need_item) break;
+ need_item=0;
+ dist = strcspn(arg,", =");
+ if(dist>15) return 1; /* something wrong, sort maybe? */
+ strncpy(buf,arg,dist); /* no '\0' on end */
+ buf[dist] = '\0'; /* fix that problem */
+ fs = search_format_array(buf);
+ if(!fs) return 1; /* invalid spec, macro or sort maybe? */
+ if(fs->vendor) return 1; /* Wonderful! Legal non-Unix98 spec. */
+ }
+ } while (*++arg);
+
+ return 0; /* boring, Unix98 is no change */
+}
+
+/***** Since ps is not long-lived, the memory leak can be ignored. ******/
+void reset_sortformat(void){
+ sf_list = NULL; /* deferred sorting and formatting */
+ format_list = NULL; /* digested formatting options */
+ sort_list = NULL; /* digested sorting options (redundant?) */
+ have_gnu_sort = 0;
+ already_parsed_sort = 0;
+ already_parsed_format = 0;
+}
+
+
+/***** Search format_list for findme, then insert putme after findme. ****/
+static int fmt_add_after(const char *findme, format_node *putme){
+ format_node *walk;
+ if(!strcmp(format_list->name, findme)){
+ putme->next = format_list->next;
+ format_list->next = putme;
+ return 1; /* success */
+ }
+ walk = format_list;
+ while(walk->next){
+ if(!strcmp(walk->next->name, findme)){
+ putme->next = walk->next->next;
+ walk->next->next = putme;
+ return 1; /* success */
+ }
+ walk = walk->next;
+ }
+ return 0; /* fail */
+}
+
+/******* Search format_list for findme, then delete it. ********/
+static int fmt_delete(const char *findme){
+ format_node *walk;
+ format_node *old;
+ if(!strcmp(format_list->name, findme)){
+ old = format_list;
+ format_list = format_list->next;
+ free(old);
+ return 1; /* success */
+ }
+ walk = format_list;
+ while(walk->next){
+ if(!strcmp(walk->next->name, findme)){
+ old = walk->next;
+ walk->next = walk->next->next;
+ free(old);
+ return 1; /* success */
+ }
+ walk = walk->next;
+ }
+ return 0; /* fail */
+}
+
+
+/************ Build a SysV format backwards. ***********/
+#define PUSH(foo) (fn=do_one_spec(foo, NULL), fn->next=format_list, format_list=fn)
+static const char *generate_sysv_list(void){
+ format_node *fn;
+ if((format_modifiers & FM_y) && !(format_flags & FF_Ul))
+ return _("modifier -y without format -l makes no sense");
+ if(prefer_bsd_defaults){
+ if(format_flags) PUSH("cmd");
+ else PUSH("args");
+ PUSH("bsdtime");
+ if(!(format_flags & FF_Ul)) PUSH("stat");
+ }else{
+ if(format_flags & FF_Uf) PUSH("cmd");
+ else PUSH("ucmd");
+ PUSH("time");
+ }
+ PUSH("tname"); /* Unix98 says "TTY" here, yet "tty" produces "TT". */
+ if(format_flags & FF_Uf) PUSH("stime");
+ /* avoid duplicate columns from -FP and -Fly */
+ if(format_modifiers & FM_F){
+ /* if -FP take the Sun-style column instead (sorry about "sgi_p") */
+ if(!(format_modifiers & FM_P)) PUSH("psr"); /* should be ENG */
+ /* if -Fly take the ADDR-replacement RSS instead */
+ if(!( (format_flags & FF_Ul) && (format_modifiers & FM_y) )) PUSH("rss");
+ }
+ if(format_flags & FF_Ul){
+ PUSH("wchan");
+ }
+ /* since FM_y adds RSS anyway, don't do this hack when that is true */
+ if( (format_flags & FF_Ul) && !(format_modifiers & FM_y) ){
+ if(personality & PER_IRIX_l){ /* add "rss" then ':' here */
+ PUSH("sgi_rss");
+ fn = xmalloc(sizeof(format_node));
+ fn->width = 1;
+ fn->name = strdup(":");
+ fn->pr = NULL; /* checked for */
+ fn->vendor = AIX; /* yes, for SGI weirdness */
+ fn->flags = CF_PRINT_EVERY_TIME;
+ fn->next = format_list;
+ format_list=fn;
+ }
+ }
+ if((format_modifiers & FM_F) || (format_flags & FF_Ul)){
+ PUSH("sz");
+ }
+ if(format_flags & FF_Ul){
+ if(format_modifiers & FM_y) PUSH("rss");
+ else if(personality & (PER_ZAP_ADDR|PER_IRIX_l)) PUSH("sgi_p");
+ else PUSH("addr_1");
+ }
+ if(format_modifiers & FM_c){
+ PUSH("pri"); PUSH("class");
+ }else if(format_flags & FF_Ul){
+ PUSH("ni");
+ if(personality & PER_IRIX_l) PUSH("priority");
+ else /* is this good? */ PUSH("opri");
+ }
+
+ // FIXME TODO XXX -- this is a serious problem
+ // These somehow got flipped around.
+ // The bug is in procps-3.1.1, procps-990211, prior too?
+ if((thread_flags & TF_U_L) && (format_flags & FF_Uf)) PUSH("nlwp");
+ if( (format_flags & (FF_Uf|FF_Ul)) && !(format_modifiers & FM_c) ) PUSH("c");
+
+ if(format_modifiers & FM_P) PUSH("psr");
+ if(thread_flags & TF_U_L) PUSH("lwp");
+ if(format_modifiers & FM_j){
+ PUSH("sid");
+ PUSH("pgid");
+ }
+ if(format_flags & (FF_Uf|FF_Ul)) PUSH("ppid");
+ if(thread_flags & TF_U_T) PUSH("spid");
+ PUSH("pid");
+ if(format_flags & FF_Uf){
+ if(personality & PER_SANE_USER) PUSH("user");
+ else PUSH("uid_hack");
+ }else if(format_flags & FF_Ul){
+ PUSH("uid");
+ }
+ if(format_flags & FF_Ul){
+ PUSH("s");
+ if(!(format_modifiers & FM_y)) PUSH("f");
+ }
+ if(format_modifiers & FM_M){
+ PUSH("label"); /* Mandatory Access Control */
+ }
+ return NULL;
+}
+
+
+/**************************************************************************
+ * Used to parse option O lists. Option O is shared between
+ * sorting and formatting. Users may expect one or the other.
+ * The "broken" flag enables a really bad Unix98 misfeature.
+ */
+const char *process_sf_options(void){
+ sf_node *sf_walk;
+
+ if(sf_list){
+ const char *err;
+ err = parse_O_option(sf_list);
+ if(err) return err;
+ }
+
+ if(format_list) catastrophic_failure(__FILE__, __LINE__, _("bug: must reset the list first"));
+
+ /* merge formatting info of sf_list into format_list here */
+ sf_walk = sf_list;
+ while(sf_walk){
+ format_node *fmt_walk;
+ fmt_walk = sf_walk->f_cooked;
+ sf_walk->f_cooked = NULL;
+ while(fmt_walk){ /* put any nodes onto format_list in opposite way */
+ format_node *travler;
+ travler = fmt_walk;
+ fmt_walk = fmt_walk->next;
+ travler->next = format_list;
+ format_list = travler;
+ }
+ sf_walk = sf_walk->next;
+ }
+
+ /* merge sorting info of sf_list into sort_list here */
+ sf_walk = sf_list;
+ while(sf_walk){
+ sort_node *srt_walk;
+ srt_walk = sf_walk->s_cooked;
+ sf_walk->s_cooked = NULL;
+ if (srt_walk) {
+ sort_node *travler = srt_walk;
+ while (travler->next) travler = travler->next;
+ travler->next = sort_list;
+ sort_list = srt_walk;
+ }
+ sf_walk = sf_walk->next;
+ }
+
+ // Get somebody to explain how -L/-T is supposed to interact
+ // with sorting. Do the threads remain grouped, with sorting
+ // by process, or do the threads get sorted by themselves?
+ if(sort_list && (thread_flags&TF_no_sort)){
+ return _("tell <procps@freelists.org> what you expected");
+ }
+
+ // If nothing else, try to use $PS_FORMAT before the default.
+ if(!format_flags && !format_modifiers && !format_list){
+ char *tmp;
+ tmp = getenv("PS_FORMAT"); /* user override kills default */
+ if(tmp && *tmp){
+ const char *err;
+ sf_node sfn;
+ if(thread_flags&TF_must_use) return _("tell <procps@freelists.org> what you want (-L/-T, -m/m/H, and $PS_FORMAT)");
+ sfn.sf = tmp;
+ sfn.f_cooked = NULL;
+ err = format_parse(&sfn);
+ if(!err){
+ format_node *fmt_walk;
+ fmt_walk = sfn.f_cooked;
+ while(fmt_walk){ /* put any nodes onto format_list in opposite way */
+ format_node *travler;
+ travler = fmt_walk;
+ fmt_walk = fmt_walk->next;
+ travler->next = format_list;
+ format_list = travler;
+ }
+ return NULL;
+ }
+ // FIXME: prove that this won't be hit on valid bogus-BSD options
+ fprintf(stderr, _("warning: $PS_FORMAT ignored. (%s)\n"), err);
+ }
+ }
+
+ if(format_list){
+ if(format_flags) return _("conflicting format options");
+ if(format_modifiers) return _("can not use output modifiers with user-defined output");
+ if(thread_flags&TF_must_use) return _("-L/-T with H/m/-m and -o/-O/o/O is nonsense");
+ return NULL;
+ }
+
+ do{
+ const char *spec;
+ switch(format_flags){
+
+ default: return _("conflicting format options");
+
+ /* These can be NULL, which enables SysV list generation code. */
+ case 0: spec=NULL; break;
+ case FF_Uf | FF_Ul: spec=sysv_fl_format; break;
+ case FF_Uf: spec=sysv_f_format; break;
+ case FF_Ul: spec=sysv_l_format; break;
+
+ /* These are NOT REACHED for normal -j processing. */
+ case FF_Uj: spec=sysv_j_format; break; /* Debian & Digital */
+ case FF_Uj | FF_Ul: spec="RD_lj"; break; /* Debian */
+ case FF_Uj | FF_Uf: spec="RD_fj"; break; /* Debian */
+
+ /* These are true BSD options. */
+ case FF_Bj: spec=bsd_j_format; break;
+ case FF_Bl: spec=bsd_l_format; break;
+ case FF_Bs: spec=bsd_s_format; break;
+ case FF_Bu: spec=bsd_u_format; break;
+ case FF_Bv: spec=bsd_v_format; break;
+
+ /* These are old Linux options. Option m is overloaded. */
+ case FF_LX: spec="OL_X"; break;
+ case FF_Lm: spec="OL_m"; break;
+
+ /* This is the sole FLASK security option. */
+ case FF_Fc: spec="FLASK_context"; break;
+
+ } /* end switch(format_flags) */
+
+ // not just for case 0, since sysv_l_format and such may be NULL
+ if(!spec) return generate_sysv_list();
+
+ do{
+ format_node *fmt_walk;
+ fmt_walk = do_one_spec(spec, NULL); /* use override "" for no headers */
+ while(fmt_walk){ /* put any nodes onto format_list in opposite way */
+ format_node *travler;
+ travler = fmt_walk;
+ fmt_walk = fmt_walk->next;
+ travler->next = format_list;
+ format_list = travler;
+ }
+ }while(0);
+ }while(0);
+
+ do{
+ format_node *fn;
+ if(format_modifiers & FM_j){
+ fn = do_one_spec("pgid", NULL);
+ if(!fmt_add_after("PPID", fn)) if(!fmt_add_after("PID", fn))
+ catastrophic_failure(__FILE__, __LINE__, _("internal error: no PID or PPID for -j option"));
+ fn = do_one_spec("sid", NULL);
+ if(!fmt_add_after("PGID", fn)) return _("lost my PGID");
+ }
+ if(format_modifiers & FM_y){
+ /* TODO: check for failure to do something, and complain if so */
+ fmt_delete("F");
+ fn = do_one_spec("rss", NULL);
+ if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR");
+ }
+ if(format_modifiers & FM_c){
+ fmt_delete("%CPU"); fmt_delete("CPU"); fmt_delete("CP"); fmt_delete("C");
+ fmt_delete("NI");
+ fn = do_one_spec("class", NULL);
+ if(!fmt_add_after("PRI", fn))
+ catastrophic_failure(__FILE__, __LINE__, _("internal error: no PRI for -c option"));
+ fmt_delete("PRI"); /* we want a different one */
+ fn = do_one_spec("pri", NULL);
+ if(!fmt_add_after("CLS", fn)) return _("lost my CLS");
+ }
+ if(thread_flags & TF_U_T){
+ fn = do_one_spec("spid", NULL);
+ if(!fmt_add_after("PID", fn) && (thread_flags&TF_must_use))
+ return _("-T with H/-m/m but no PID for SPID to follow");
+ }
+ if(thread_flags & TF_U_L){
+ fn = do_one_spec("lwp", NULL);
+ if(fmt_add_after("SID", fn)) goto did_lwp;
+ if(fmt_add_after("SESS", fn)) goto did_lwp;
+ if(fmt_add_after("PGID", fn)) goto did_lwp;
+ if(fmt_add_after("PGRP", fn)) goto did_lwp;
+ if(fmt_add_after("PPID", fn)) goto did_lwp;
+ if(fmt_add_after("PID", fn)) goto did_lwp;
+ if(thread_flags&TF_must_use)
+ return _("-L with H/-m/m but no PID/PGID/SID/SESS for NLWP to follow");
+did_lwp:
+ fn = do_one_spec("nlwp", NULL);
+ fmt_add_after("%CPU", fn);
+ }
+ if(format_modifiers & FM_M){ // Mandatory Access Control, IRIX style
+ fn = do_one_spec("label", NULL);
+ fn->next=format_list;
+ format_list=fn;
+ }
+ /* Do personality-specific translations not covered by format_flags.
+ * Generally, these only get hit when personality overrides unix output.
+ * That (mostly?) means the Digital and Debian personalities.
+ */
+ if((personality & PER_ZAP_ADDR) && (format_flags & FF_Ul)){
+ fn = do_one_spec("sgi_p", NULL);
+ if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR");
+ }
+ if((personality & PER_SANE_USER) && (format_flags & FF_Uf)){
+ fn = do_one_spec("user", NULL);
+ if(fmt_add_after("UID", fn)) fmt_delete("UID");
+ }
+ }while(0);
+
+ return NULL;
+}
+
diff --git a/src/ps/stacktrace.c b/src/ps/stacktrace.c
new file mode 100644
index 0000000..fdd2aa9
--- /dev/null
+++ b/src/ps/stacktrace.c
@@ -0,0 +1,191 @@
+/*
+ * stacktrace.c - ps debugging additions
+ *
+ * Gnu debugger stack trace code provided by Peter Mattis
+ * <petm@CSUA.Berkeley.EDU> on Thu, 2 Nov 1995
+ * Modified for easy use by Albert Cahalan.
+ *
+ * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2011-2016 Jim Warner <james.warner@comcast.net
+ * Copyright © 1998-2004 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "common.h"
+
+#define INTERACTIVE 0
+#define STACK_TRACE 1
+
+char *stored_prog_name = "you forgot to set \"program\"";
+static int stack_trace_done;
+
+/***********/
+static void debug_stop(char **args){
+ execvp (args[0], args);
+ perror ("exec failed");
+ _exit (0);
+}
+
+/***********/
+static void stack_trace_sigchld(int signum){
+ (void)signum;
+ stack_trace_done = 1;
+}
+
+/************/
+static void stack_trace(char **args){
+ pid_t pid;
+ int in_fd[2];
+ int out_fd[2];
+ fd_set fdset;
+ fd_set readset;
+ struct timeval tv;
+ int sel, index, state;
+ char buffer[256];
+ char c;
+
+ stack_trace_done = 0;
+ signal(SIGCHLD, stack_trace_sigchld);
+
+ if((pipe (in_fd) == -1) || (pipe (out_fd) == -1)){
+ perror ("could open pipe");
+ _exit (0);
+ }
+
+ pid = fork ();
+ if (pid == 0){
+ close (0); dup (in_fd[0]); /* set the stdin to the in pipe */
+ close (1); dup (out_fd[1]); /* set the stdout to the out pipe */
+ close (2); dup (out_fd[1]); /* set the stderr to the out pipe */
+ execvp (args[0], args); /* exec gdb */
+ perror ("exec failed");
+ _exit (0);
+ } else {
+ if(pid == (pid_t) -1){
+ perror ("could not fork");
+ _exit (0);
+ }
+ }
+
+ FD_ZERO (&fdset);
+ FD_SET (out_fd[0], &fdset);
+
+ write (in_fd[1], "backtrace\n", 10);
+ write (in_fd[1], "p x = 0\n", 8);
+ write (in_fd[1], "quit\n", 5);
+
+ index = 0;
+ state = 0;
+
+ for(;;){
+ readset = fdset;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ sel = select (FD_SETSIZE, &readset, NULL, NULL, &tv);
+ if (sel == -1) break;
+
+ if((sel > 0) && (FD_ISSET (out_fd[0], &readset))){
+ if(read (out_fd[0], &c, 1)){
+ switch(state){
+ case 0:
+ if(c == '#'){
+ state = 1;
+ index = 0;
+ buffer[index++] = c;
+ }
+ break;
+ case 1:
+ buffer[index++] = c;
+ if((c == '\n') || (c == '\r')){
+ buffer[index] = 0;
+ fprintf (stderr, "%s", buffer);
+ state = 0;
+ index = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ else if(stack_trace_done) break;
+ }
+
+ close (in_fd[0]);
+ close (in_fd[1]);
+ close (out_fd[0]);
+ close (out_fd[1]);
+ _exit (0);
+}
+
+/************/
+void debug(int method, char *prog_name){
+ pid_t pid;
+ char buf[16];
+ char *args[4] = { "gdb", NULL, NULL, NULL };
+ int x;
+
+ snprintf (buf, sizeof(buf), "%d", getpid ());
+
+ args[1] = prog_name;
+ args[2] = buf;
+
+ pid = fork ();
+ if(pid == 0){
+ switch (method){
+ case INTERACTIVE:
+ fprintf (stderr, "debug_stop\n");
+ debug_stop(args);
+ break;
+ case STACK_TRACE:
+ fprintf (stderr, "stack_trace\n");
+ stack_trace(args);
+ break;
+ }
+ _exit(0);
+ } else if(pid == (pid_t) -1){
+ perror ("could not fork");
+ return;
+ }
+
+ x = 1;
+ while(x); /* wait for debugger? */
+}
+
+#ifdef DEBUG
+/************/
+static void stack_trace_sigsegv(int signum){
+ (void)signum;
+ debug(STACK_TRACE, stored_prog_name);
+}
+
+/************/
+void init_stack_trace(char *prog_name){
+ stored_prog_name = prog_name;
+ signal(SIGSEGV, stack_trace_sigsegv);
+}
+#endif