summaryrefslogtreecommitdiffstats
path: root/src/ps/parser.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:34:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:34:44 +0000
commite3be059d4da38aa36f1aee1d56f8ceb943d92f1c (patch)
tree26edef31e4e503dd1c92a112de174f366dd61802 /src/ps/parser.c
parentInitial commit. (diff)
downloadprocps-e3be059d4da38aa36f1aee1d56f8ceb943d92f1c.tar.xz
procps-e3be059d4da38aa36f1aee1d56f8ceb943d92f1c.zip
Adding upstream version 2:4.0.4.upstream/2%4.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/ps/parser.c')
-rw-r--r--src/ps/parser.c1268
1 files changed, 1268 insertions, 0 deletions
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);
+}