diff options
Diffstat (limited to '')
-rw-r--r-- | src/ps/HACKING | 46 | ||||
-rw-r--r-- | src/ps/common.h | 512 | ||||
-rw-r--r-- | src/ps/display.c | 681 | ||||
-rw-r--r-- | src/ps/global.c | 651 | ||||
-rw-r--r-- | src/ps/help.c | 223 | ||||
-rw-r--r-- | src/ps/output.c | 2370 | ||||
-rw-r--r-- | src/ps/parser.c | 1268 | ||||
-rw-r--r-- | src/ps/regression | 26 | ||||
-rw-r--r-- | src/ps/select.c | 162 | ||||
-rw-r--r-- | src/ps/signames.c | 170 | ||||
-rw-r--r-- | src/ps/sortformat.c | 949 | ||||
-rw-r--r-- | src/ps/stacktrace.c | 191 |
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 |