/* * parser.c - ps command options parser * Copyright 1998-2003 by 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 #include #include #include #include #include #include #include #include "../include/c.h" #include "../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->u = xmalloc(strlen(arg)*sizeof(sel_union)); /* waste is insignificant */ node->n = 0; 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++; need_item=0; } } while (*++walk); if(need_item) goto parse_error; node->n = items; /*** 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 '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}, {"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}, {"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_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_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 = xmalloc(i*sizeof(sel_union)); /* waste is insignificant */ pidnode->n = 0; grpnode = xmalloc(sizeof(selection_node)); grpnode->u = xmalloc(i*sizeof(sel_union)); /* waste is insignificant */ grpnode->n = 0; sidnode = xmalloc(sizeof(selection_node)); sidnode->u = xmalloc(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); }