Updated with Tizen:Base source codes
[external/procps.git] / ps / parser.c
1 /*
2  * Copyright 1998-2003 by Albert Cahalan; all rights reserved.
3  * This file may be used subject to the terms and conditions of the
4  * GNU Library General Public License Version 2, or any later version
5  * at your option, as published by the Free Software Foundation.
6  * This program is distributed in the hope that it will be useful,
7  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9  * GNU Library General Public License for more details.
10  */
11
12 /* Ought to have debug print stuff like this:
13  * #define Print(fmt, args...) printf("Debug: " fmt, ## args)
14  */
15
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19
20 /* username lookups */
21 #include <sys/types.h>
22 #include <pwd.h>
23 #include <grp.h>
24
25 #include <sys/stat.h>
26 #include <unistd.h>
27
28 #include "common.h"
29 #include "../proc/version.h"
30
31 #define ARG_GNU  0
32 #define ARG_END  1
33 #define ARG_PGRP 2
34 #define ARG_SYSV 3
35 #define ARG_PID  4
36 #define ARG_BSD  5
37 #define ARG_FAIL 6
38 #define ARG_SESS 7
39
40 static int w_count = 0;
41
42 static int ps_argc;    /* global argc */
43 static char **ps_argv; /* global argv */
44 static int thisarg;    /* index into ps_argv */
45 static char *flagptr;  /* current location in ps_argv[thisarg] */
46 static int not_pure_unix = 0;  /* set by BSD and GNU options */
47 static int force_bsd = 0;  /* set when normal parsing fails */
48
49 #define exclusive(x) if((ps_argc != 2) || strcmp(ps_argv[1],x))\
50   return "The " x " option is exclusive."
51
52
53 /********** utility functions **********/
54
55 /*
56  * Both "-Oppid" and "-O ppid" should be legal, though Unix98
57  * does not require it. BSD and Digital Unix allow both.
58  * Return the argument or NULL;
59  */
60 static const char *get_opt_arg(void){
61   if(*(flagptr+1)){     /* argument is part of ps_argv[thisarg] */
62     not_pure_unix = 1;
63     return flagptr+1;
64   }
65   if(thisarg+2 > ps_argc) return NULL;   /* there is nothing left */
66   /* argument follows ps_argv[thisarg] */
67   if(*(ps_argv[thisarg+1]) == '\0') return NULL;
68   return ps_argv[++thisarg];
69 }
70
71 /********** parse lists (of UID, tty, GID, PID...) **********/
72
73 static const char *parse_pid(char *str, sel_union *ret){
74   char *endp;
75   unsigned long num;
76   static const char pidrange[]  = "Process ID out of range.";
77   static const char pidsyntax[] = "Process ID list syntax error.";
78   num = strtoul(str, &endp, 0);
79   if(*endp != '\0')      return pidsyntax;
80   if(num<1)              return pidrange;
81   if(num > 0x7fffffffUL) return pidrange;
82   ret->pid = num;
83   return 0;
84 }
85
86 static const char *parse_uid(char *str, sel_union *ret){
87   struct passwd *passwd_data;
88   char *endp;
89   unsigned long num;
90   static const char uidrange[] = "User ID out of range.";
91   static const char uidexist[] = "User name does not exist.";
92   num = strtoul(str, &endp, 0);
93   if(*endp != '\0'){  /* hmmm, try as login name */
94     passwd_data = getpwnam(str);
95     if(!passwd_data)    return uidexist;
96     num = passwd_data->pw_uid;
97   }
98   if(num > 0xfffffffeUL) return uidrange;
99   ret->uid = num;
100   return 0;
101 }
102
103 static const char *parse_gid(char *str, sel_union *ret){
104   struct group *group_data;
105   char *endp;
106   unsigned long num;
107   static const char gidrange[] = "Group ID out of range.";
108   static const char gidexist[] = "Group name does not exist.";
109   num = strtoul(str, &endp, 0);
110   if(*endp != '\0'){  /* hmmm, try as login name */
111     group_data = getgrnam(str);
112     if(!group_data)    return gidexist;
113     num = group_data->gr_gid;
114   }
115   if(num > 0xfffffffeUL) return gidrange;
116   ret->gid = num;
117   return 0;
118 }
119
120 static const char *parse_cmd(char *str, sel_union *ret){
121   strncpy(ret->cmd, str, sizeof ret->cmd);  // strncpy pads to end
122   return 0;
123 }
124
125 static const char *parse_tty(char *str, sel_union *ret){
126   struct stat sbuf;
127   static const char missing[] = "TTY could not be found.";
128   static const char not_tty[] = "List member was not a TTY.";
129   char path[4096];
130   if(str[0]=='/'){
131     if(stat(str, &sbuf) >= 0) goto found_it;
132     return missing;
133   }
134 #define lookup(p) \
135   snprintf(path,4096,p,str); \
136   if(stat(path, &sbuf) >= 0) goto found_it
137
138   lookup("/dev/pts/%s");  /* New Unix98 ptys go first */
139   lookup("/dev/%s");
140   lookup("/dev/tty%s");
141   lookup("/dev/pty%s");
142   lookup("/dev/%snsole"); /* "co" means "console", maybe do all VCs too? */
143   if(!strcmp(str,"-")){   /* "-" means no tty (from AIX) */
144     ret->tty = 0;  /* processes w/o tty */
145     return 0;
146   }
147   if(!strcmp(str,"?")){   /* "?" means no tty, which bash eats (Reno BSD?) */
148     ret->tty = 0;  /* processes w/o tty */
149     return 0;
150   }
151   if(!*(str+1) && (stat(str,&sbuf)>=0)){  /* Kludge! Assume bash ate '?'. */
152     ret->tty = 0;  /* processes w/o tty */
153     return 0;
154   }
155 #undef lookup
156   return missing;
157 found_it:
158   if(!S_ISCHR(sbuf.st_mode)) return not_tty;
159   ret->tty = sbuf.st_rdev;
160   return 0;
161 }
162
163 /*
164  * Used to parse lists in a generic way. (function pointers)
165  */
166 static const char *parse_list(const char *arg, const char *(*parse_fn)(char *, sel_union *) ){
167   selection_node *node;
168   char *buf;                      /* temp copy of arg to hack on */
169   char *sep_loc;                  /* separator location: " \t," */
170   char *walk;
171   int items;
172   int need_item;
173   const char *err;       /* error code that could or did happen */
174   /*** prepare to operate ***/
175   node = malloc(sizeof(selection_node));
176   node->u = malloc(strlen(arg)*sizeof(sel_union)); /* waste is insignificant */
177   node->n = 0;
178   buf = malloc(strlen(arg)+1);
179   strcpy(buf, arg);
180   /*** sanity check and count items ***/
181   need_item = 1; /* true */
182   items = 0;
183   walk = buf;
184   err = "Improper list.";
185   do{
186     switch(*walk){
187     case ' ': case ',': case '\t': case '\0':
188       if(need_item) goto parse_error;
189       need_item=1;
190       break;
191     default:
192       if(need_item) items++;
193       need_item=0;
194     }
195   } while (*++walk);
196   if(need_item) goto parse_error;
197   node->n = items;
198   /*** actually parse the list ***/
199   walk = buf;
200   while(items--){
201     sep_loc = strpbrk(walk," ,\t");
202     if(sep_loc) *sep_loc = '\0';
203     if(( err=(parse_fn)(walk, node->u+items) )) goto parse_error;
204     walk = sep_loc + 1; /* point to next item, if any */
205   }
206   free(buf);
207   node->next = selection_list;
208   selection_list = node;
209   return NULL;
210 parse_error:
211   free(buf);
212   free(node->u);
213   free(node);
214   return err;
215 }
216
217 /***************** parse SysV options, including Unix98  *****************/
218 static const char *parse_sysv_option(void){
219   const char *arg;
220   const char *err;
221
222   flagptr = ps_argv[thisarg];
223   while(*++flagptr){
224     // Find any excuse to ignore stupid Unix98 misfeatures.
225     //
226     // This list of options is ONLY for those defined by the
227     // "IEEE Std 1003.1, 2004 Edition", "ISO/IEC 9945:2003",
228     // or "Version 2 of the Single Unix Specification".
229     //
230     // It may be time to re-think the existance of this list.
231     // In the meantime, please do not add to it. The list is
232     // intended to ONLY contain flags defined by the POSIX and UNIX
233     // standards published by The Open Group, IEEE, and ISO.
234     if(!strchr("aAdefgGlnoptuU", *flagptr)) not_pure_unix = 1;  // dude, -Z ain't in POSIX
235
236     switch(*flagptr){
237     case 'A':
238       trace("-A selects all processes.\n");
239       all_processes = 1;
240       break;
241     case 'C': /* end */
242       trace("-C select by process name.\n");  /* Why only HP/UX and us? */
243       arg=get_opt_arg();
244       if(!arg) return "List of command names must follow -C.";
245       err=parse_list(arg, parse_cmd);
246       if(err) return err;
247       selection_list->typecode = SEL_COMM;
248       return NULL; /* can't have any more options */
249     case 'F':  /* DYNIX/ptx -f plus sz,rss,psr=ENG between c and stime */
250       trace("-F does fuller listing\n");
251       format_modifiers |= FM_F;
252       format_flags |= FF_Uf;
253       unix_f_option = 1; /* does this matter? */
254       break;
255     case 'G': /* end */
256       trace("-G select by RGID (supports names)\n");
257       arg=get_opt_arg();
258       if(!arg) return "List of real groups must follow -G.";
259       err=parse_list(arg, parse_gid);
260       if(err) return err;
261       selection_list->typecode = SEL_RGID;
262       return NULL; /* can't have any more options */
263     case 'H':     /* another nice HP/UX feature */
264       trace("-H Process hierarchy (like ASCII art forest option)\n");
265       forest_type = 'u';
266       break;
267 #if 0
268     case 'J':  // specify list of job IDs in hex (IRIX) -- like HP "-R" maybe?
269       trace("-J select by job ID\n");  // want a JID ("jid") for "-j" too
270       arg=get_opt_arg();
271       if(!arg) return "List of jobs must follow -J.";
272       err=parse_list(arg, parse_jid);
273       if(err) return err;
274       selection_list->typecode = SEL_JID;
275       return NULL; /* can't have any more options */
276 #endif
277     case 'L':  /*  */
278       /* In spite of the insane 2-level thread system, Sun appears to
279        * have made this option Linux-compatible. If a process has N
280        * threads, ps will produce N lines of output. (not N+1 lines)
281        * Zombies are the only exception, with NLWP==0 and 1 output line.
282        * SCO UnixWare uses -L too.
283        */
284       trace("-L Print LWP (thread) info.\n");
285       thread_flags |= TF_U_L;
286 //      format_modifiers |= FM_L;
287       break;
288     case 'M':  // typically the SE Linux context
289       trace("-M Print security label for Mandatory Access Control.\n");
290       format_modifiers |= FM_M;
291       break;
292     case 'N':
293       trace("-N negates.\n");
294       negate_selection = 1;
295       break;
296     case 'O': /* end */
297       trace("-O is preloaded -o.\n");
298       arg=get_opt_arg();
299       if(!arg) return "Format or sort specification must follow -O.";
300       defer_sf_option(arg, SF_U_O);
301       return NULL; /* can't have any more options */
302     case 'P':     /* SunOS 5 "psr" or unknown HP/UX feature */
303       trace("-P adds columns of PRM info (HP-UX), PSR (SunOS), or capabilities (IRIX)\n");
304       format_modifiers |= FM_P;
305       break;
306 #if 0
307     case 'R':    // unknown HP/UX feature, like IRIX "-J" maybe?
308       trace("-R select by PRM group\n");
309       arg=get_opt_arg();
310       if(!arg) return "List of PRM groups must follow -R.";
311       err=parse_list(arg, parse_prm);
312       if(err) return err;
313       selection_list->typecode = SEL_PRM;
314       return NULL; /* can't have any more options */
315 #endif
316     case 'T':
317       /* IRIX 6.5 docs suggest POSIX threads get shown individually.
318        * This would make -T be like -L, -m, and m. (but an extra column)
319        * Testing (w/ normal processes) shows 1 line/process, not 2.
320        * Also, testing shows PID==SPID for all normal processes.
321        */
322       trace("-T adds strange SPID column (old sproc() threads?)\n");
323       thread_flags |= TF_U_T;
324 //      format_modifiers |= FM_T;
325       break;
326     case 'U': /* end */
327       trace("-U select by RUID (supports names).\n");
328       arg=get_opt_arg();
329       if(!arg) return "List of real groups must follow -U.";
330       err=parse_list(arg, parse_uid);
331       if(err) return err;
332       selection_list->typecode = SEL_RUID;
333       return NULL; /* can't have any more options */
334     case 'V': /* single */
335       trace("-V prints version.\n");
336       exclusive("-V");
337       display_version();
338       exit(0);
339     // This must be verified against SVR4-MP. (UnixWare or Powermax)
340     // Leave it undocumented until that problem is solved.
341     case 'Z':     /* full Mandatory Access Control level info */
342       trace("-Z shows full MAC info\n");
343       format_modifiers |= FM_M;
344       break;
345     case 'a':
346       trace("-a select all with a tty, but omit session leaders.\n");
347       simple_select |= SS_U_a;
348       break;
349     case 'c':
350       /* HP-UX and SunOS 5 scheduling info modifier */
351       trace("-c changes scheduling info.\n");
352       format_modifiers |= FM_c;
353       break;
354     case 'd':
355       trace("-d select all, but omit session leaders.\n");
356       simple_select |= SS_U_d;
357       break;
358     case 'e':
359       trace("-e selects all processes.\n");
360       all_processes = 1;
361       break;
362     case 'f':
363       trace("-f does full listing\n");
364       format_flags |= FF_Uf;
365       unix_f_option = 1; /* does this matter? */
366       break;
367     case 'g': /* end */
368       trace("-g selects by session leader OR by group name\n");
369       arg=get_opt_arg();
370       if(!arg) return "List of session leaders OR effective group names must follow -g.";
371       err=parse_list(arg, parse_pid);
372       if(!err){
373         selection_list->typecode = SEL_SESS;
374         return NULL; /* can't have any more options */
375       }
376       err=parse_list(arg, parse_gid);
377       if(!err){
378         selection_list->typecode = SEL_EGID;
379         return NULL; /* can't have any more options */
380       }
381       return "List of session leaders OR effective group IDs was invalid.";
382     case 'j':
383       trace("-j jobs format.\n");
384       /* old Debian used RD_j and Digital uses JFMT */
385       if(sysv_j_format) format_flags |= FF_Uj;
386       else format_modifiers |= FM_j;
387       break;
388     case 'l':
389       trace("-l long format.\n");
390       format_flags |= FF_Ul;
391       break;
392     case 'm':
393       trace("-m shows threads.\n");
394       /* note that AIX shows 2 lines for a normal process */
395       thread_flags |= TF_U_m;
396       break;
397     case 'n': /* end */
398       trace("-n sets namelist file.\n");
399       arg=get_opt_arg();
400       if(!arg) return "Alternate System.map file must follow -n.";
401       namelist_file = arg;
402       return NULL; /* can't have any more options */
403     case 'o': /* end */
404       /* Unix98 has gross behavior regarding this. From the following: */
405       /*            ps -o pid,nice=NICE,tty=TERMINAL,comm              */
406       /* The result must be 2 columns: "PID NICE,tty=TERMINAL,comm"    */
407       /* Yes, the second column has the name "NICE,tty=TERMINAL,comm"  */
408       /* This parser looks for any excuse to ignore that braindamage.  */
409       trace("-o user-defined format.\n");
410       arg=get_opt_arg();
411       if(!arg) return "Format specification must follow -o.";
412       not_pure_unix |= defer_sf_option(arg, SF_U_o);
413       return NULL; /* can't have any more options */
414     case 'p': /* end */
415       trace("-p select by PID.\n");
416       arg=get_opt_arg();
417       if(!arg) return "List of process IDs must follow -p.";
418       err=parse_list(arg, parse_pid);
419       if(err) return err;
420       selection_list->typecode = SEL_PID;
421       return NULL; /* can't have any more options */
422 #if 0
423     case 'r':
424       trace("-r some Digital Unix thing about warnings...\n");
425       trace("   or SCO's option to chroot() for new /proc and /dev.\n");
426       return "The -r option is reserved.";
427       break;
428 #endif
429     case 's': /* end */
430       trace("-s Select processes belonging to the sessions given.\n");
431       arg=get_opt_arg();
432       if(!arg) return "List of session IDs must follow -s.";
433       err=parse_list(arg, parse_pid);
434       if(err) return err;
435       selection_list->typecode = SEL_SESS;
436       return NULL; /* can't have any more options */
437     case 't': /* end */
438       trace("-t select by tty.\n");
439       arg=get_opt_arg();
440       if(!arg) return "List of terminals (pty, tty...) must follow -t.";
441       err=parse_list(arg, parse_tty);
442       if(err) return err;
443       selection_list->typecode = SEL_TTY;
444       return NULL; /* can't have any more options */
445     case 'u': /* end */
446       trace("-u select by user ID (the EUID?) (supports names).\n");
447       arg=get_opt_arg();
448       if(!arg) return "List of users must follow -u.";
449       err=parse_list(arg, parse_uid);
450       if(err) return err;
451       selection_list->typecode = SEL_EUID;
452       return NULL; /* can't have any more options */
453     case 'w':
454       trace("-w wide output.\n");
455       w_count++;
456       break;
457     case 'x':  /* behind personality until "ps -ax" habit is uncommon */
458       if(personality & PER_SVR4_x){
459         // Same as -y, but for System V Release 4 MP
460         trace("-x works like Sun Solaris & SCO Unixware -y option\n");
461         format_modifiers |= FM_y;
462         break;
463       }
464       if(personality & PER_HPUX_x){
465         trace("-x extends the command line\n");
466         w_count += 2;
467         unix_f_option = 1;
468         break;
469       }
470       return "Must set personality to get -x option.";
471     case 'y':  /* Sun's -l hack (also: Irix "lnode" resource control info) */
472       trace("-y Print lnone info in UID/USER column or do Sun -l hack.\n");
473       format_modifiers |= FM_y;
474       break;
475 #if 0
476     // This must be verified against SVR4-MP (UnixWare or Powermax)
477     case 'z':     /* alias of Mandatory Access Control level info */
478       trace("-z shows aliased MAC info\n");
479       format_modifiers |= FM_M;
480       break;
481     // Solaris 10 does this
482     case 'z':     /* select by zone */
483       trace("-z secects by zone\n");
484       arg=get_opt_arg();
485       if(!arg) return "List of zones (contexts, labels, whatever?) must follow -z.";
486       err=parse_list(arg, parse_zone);
487       if(err) return err;
488       selection_list->typecode = SEL_ZONE;
489       return NULL; /* can't have any more options */
490 #endif
491     case '-':
492       return "Embedded '-' among SysV options makes no sense.";
493       break;
494     case '\0':
495       return "Please report the \"SysV \\0 can't happen\" bug.";
496       break;
497     default:
498       return "Unsupported SysV option.";
499     } /* switch */
500   } /* while */
501   return NULL;
502 }
503
504 /************************* parse BSD options **********************/
505 static const char *parse_bsd_option(void){
506   const char *arg;
507   const char *err;
508
509   flagptr = ps_argv[thisarg];  /* assume we _have_ a '-' */
510   if(flagptr[0]=='-'){
511     if(!force_bsd) return "Can't happen!  Problem #1.";
512   }else{
513     flagptr--; /* off beginning, will increment before use */
514     if(personality & PER_FORCE_BSD){
515       if(!force_bsd) return "Can't happen!  Problem #2.";
516     }else{
517       if(force_bsd) return "2nd chance parse failed, not BSD or SysV.";
518     }
519   }
520
521   while(*++flagptr){
522     switch(*flagptr){
523     case '0' ... '9': /* end */
524       trace("0..9  Old BSD-style select by process ID\n");
525       arg=flagptr;
526       err=parse_list(arg, parse_pid);
527       if(err) return err;
528       selection_list->typecode = SEL_PID;
529       return NULL; /* can't have any more options */
530 #if 0
531     case 'A':
532       /* maybe this just does a larger malloc() ? */
533       trace("A Increases the argument space (Digital Unix)\n");
534       return "Option A is reserved.";
535       break;
536     case 'C':
537       /* should divide result by 1-(e**(foo*log(bar))) */
538       trace("C Use raw CPU time for %%CPU instead of decaying ave\n");
539       return "Option C is reserved.";
540       break;
541 #endif
542     case 'H':    // The FreeBSD way (NetBSD:s OpenBSD:k FreeBSD:H  -- NIH???)
543       trace("H Print LWP (thread) info.\n");   // was: Use /vmcore as c-dumpfile\n");
544       thread_flags |= TF_B_H;
545       //format_modifiers |= FM_L;    // FIXME: determine if we need something like this
546       break;
547     case 'L': /* single */
548       trace("L List all format specifiers\n");
549       exclusive("L");
550       print_format_specifiers();
551       exit(0);
552     case 'M':   // undocumented for now: these are proliferating!
553       trace("M MacOS X thread display, like AIX/Tru64\n");
554       thread_flags |= TF_B_m;
555       break;
556     case 'N': /* end */
557       trace("N Specify namelist file\n");
558       arg=get_opt_arg();
559       if(!arg) return "Alternate System.map file must follow N.";
560       namelist_file = arg;
561       return NULL; /* can't have any more options */
562     case 'O': /* end */
563       trace("O Like o + defaults, add new columns after PID. Also sort.\n");
564       arg=get_opt_arg();
565       if(!arg) return "Format or sort specification must follow O.";
566       defer_sf_option(arg, SF_B_O);
567       return NULL; /* can't have any more options */
568       break;
569     case 'S':
570       trace("S include dead kids in sum\n");
571       include_dead_children = 1;
572       break;
573     case 'T':
574       trace("T Select all processes on this terminal\n");
575       /* put our tty on a tiny list */
576       {
577         selection_node *node;
578         node = malloc(sizeof(selection_node));
579         node->u = malloc(sizeof(sel_union));
580         node->u[0].tty = cached_tty;
581         node->typecode = SEL_TTY;
582         node->n = 1;
583         node->next = selection_list;
584         selection_list = node;
585       }
586       break;
587     case 'U': /* end */
588       trace("U Select processes for specified users.\n");
589       arg=get_opt_arg();
590       if(!arg) return "List of users must follow U.";
591       err=parse_list(arg, parse_uid);
592       if(err) return err;
593       selection_list->typecode = SEL_EUID;
594       return NULL; /* can't have any more options */
595     case 'V': /* single */
596       trace("V show version info\n");
597       exclusive("V");
598       display_version();
599       exit(0);
600     case 'W':
601       trace("W N/A get swap info from ... not /dev/drum.\n");
602       return "Obsolete W option not supported. (You have a /dev/drum?)";
603       break;
604     case 'X':
605       trace("X Old Linux i386 register format\n");
606       format_flags |= FF_LX;
607       break;
608     case 'Z':  /* FreeBSD does MAC like SGI's Irix does it */
609       trace("Z Print security label for Mandatory Access Control.\n");
610       format_modifiers |= FM_M;
611       break;
612     case 'a':
613       trace("a Select all w/tty, including other users\n");
614       simple_select |= SS_B_a;
615       break;
616     case 'c':
617       trace("c true command name\n");
618       bsd_c_option = 1;
619       break;
620     case 'e':
621       trace("e environment\n");
622       bsd_e_option = 1;
623       break;
624     case 'f':
625       trace("f ASCII art forest\n");
626       forest_type = 'b';
627       break;
628     case 'g':
629       trace("g _all_, even group leaders!.\n");
630       simple_select |= SS_B_g;
631       break;
632     case 'h':
633       trace("h Repeat header... yow.\n");
634       if(header_type) return "Only one heading option may be specified.";
635       if(personality & PER_BSD_h) header_type = HEAD_MULTI;
636       else                        header_type = HEAD_NONE;
637       break;
638     case 'j':
639       trace("j job control format\n");
640       format_flags |= FF_Bj;
641       break;
642     case 'k':
643       // OpenBSD: don't hide "kernel threads" -- like the swapper?
644       // trace("k Print LWP (thread) info.\n");   // was: Use /vmcore as c-dumpfile\n");
645
646       // NetBSD, and soon (?) FreeBSD: sort-by-keyword
647       trace("k Specify sorting keywords.\n");
648       arg=get_opt_arg();
649       if(!arg) return "Long sort specification must follow 'k'.";
650       defer_sf_option(arg, SF_G_sort);
651       return NULL; /* can't have any more options */
652     case 'l':
653       trace("l Display long format\n");
654       format_flags |= FF_Bl;
655       break;
656     case 'm':
657       trace("m all threads, sort on mem use, show mem info\n");
658       if(personality & PER_OLD_m){
659         format_flags |= FF_Lm;
660         break;
661       }
662       if(personality & PER_BSD_m){
663         defer_sf_option("pmem", SF_B_m);
664         break;
665       }
666       thread_flags |= TF_B_m;
667       break;
668     case 'n':
669       trace("n Numeric output for WCHAN, and USER replaced by UID\n");
670       wchan_is_number = 1;
671       user_is_number = 1;
672       /* TODO add tty_is_number too? */
673       break;
674     case 'o': /* end */
675       trace("o Specify user-defined format\n");
676       arg=get_opt_arg();
677       if(!arg) return "Format specification must follow o.";
678       defer_sf_option(arg, SF_B_o);
679       return NULL; /* can't have any more options */
680     case 'p': /* end */
681       trace("p Select by process ID\n");
682       arg=get_opt_arg();
683       if(!arg) return "List of process IDs must follow p.";
684       err=parse_list(arg, parse_pid);
685       if(err) return err;
686       selection_list->typecode = SEL_PID;
687       return NULL; /* can't have any more options */
688     case 'r':
689       trace("r Select running processes\n");
690       running_only = 1;
691       break;
692     case 's':
693       trace("s Display signal format\n");
694       format_flags |= FF_Bs;
695       break;
696     case 't': /* end */
697       trace("t Select by tty.\n");
698       /* List of terminals (tty, pty...) _should_ follow t. */
699       arg=get_opt_arg();
700       if(!arg){
701         /* Wow, obsolete BSD syntax. Put our tty on a tiny list. */
702         selection_node *node;
703         node = malloc(sizeof(selection_node));
704         node->u = malloc(sizeof(sel_union));
705         node->u[0].tty = cached_tty;
706         node->typecode = SEL_TTY;
707         node->n = 1;
708         node->next = selection_list;
709         selection_list = node;
710         return NULL;
711       }
712       err=parse_list(arg, parse_tty);
713       if(err) return err;
714       selection_list->typecode = SEL_TTY;
715       return NULL; /* can't have any more options */
716     case 'u':
717       trace("u Display user-oriented\n");
718       format_flags |= FF_Bu;
719       break;
720     case 'v':
721       trace("v Display virtual memory\n");
722       format_flags |= FF_Bv;
723       break;
724     case 'w':
725       trace("w wide output\n");
726       w_count++;
727       break;
728     case 'x':
729       trace("x Select processes without controlling ttys\n");
730       simple_select |= SS_B_x;
731       break;
732     case '-':
733       return "Embedded '-' among BSD options makes no sense.";
734       break;
735     case '\0':
736       return "Please report the \"BSD \\0 can't happen\" bug.";
737       break;
738     default:
739       return "Unsupported option (BSD syntax)";
740     } /* switch */
741   } /* while */
742   return NULL;
743 }
744
745 /*************** gnu long options **********************/
746
747 /*
748  * Return the argument or NULL
749  */
750 static const char *grab_gnu_arg(void){
751   switch(*flagptr){     /* argument is part of ps_argv[thisarg] */
752   default:
753     return NULL;                     /* something bad */
754   case '=': case ':':
755     if(*++flagptr) return flagptr;   /* found it */
756     return NULL;                     /* empty '=' or ':' */
757   case '\0': /* try next argv[] */
758     ;
759   }
760   if(thisarg+2 > ps_argc) return NULL;   /* there is nothing left */
761   /* argument follows ps_argv[thisarg] */
762   if(*(ps_argv[thisarg+1]) == '\0') return NULL;
763   return ps_argv[++thisarg];
764 }
765
766 typedef struct gnu_table_struct {
767   const char *name; /* long option name */
768   const void *jump; /* See gcc extension info.   :-)   */
769 } gnu_table_struct;
770
771 static int compare_gnu_table_structs(const void *a, const void *b){
772   return strcmp(((const gnu_table_struct*)a)->name,((const gnu_table_struct*)b)->name);
773 }
774
775 /* Option arguments are after ':', after '=', or in argv[n+1] */
776 static const char *parse_gnu_option(void){
777   const char *arg;
778   const char *err;
779   char *s;
780   size_t sl;
781   char buf[16];
782   gnu_table_struct findme = { buf, NULL};
783   gnu_table_struct *found;
784   static const gnu_table_struct gnu_table[] = {
785   {"Group",         &&case_Group},       /* rgid */
786   {"User",          &&case_User},        /* ruid */
787   {"cols",          &&case_cols},
788   {"columns",       &&case_columns},
789   {"context",       &&case_context},
790   {"cumulative",    &&case_cumulative},
791   {"deselect",      &&case_deselect},    /* -N */
792   {"forest",        &&case_forest},      /* f -H */
793   {"format",        &&case_format},
794   {"group",         &&case_group},       /* egid */
795   {"header",        &&case_header},
796   {"headers",       &&case_headers},
797   {"heading",       &&case_heading},
798   {"headings",      &&case_headings},
799   {"help",          &&case_help},
800   {"info",          &&case_info},
801   {"lines",         &&case_lines},
802   {"no-header",     &&case_no_header},
803   {"no-headers",    &&case_no_headers},
804   {"no-heading",    &&case_no_heading},
805   {"no-headings",   &&case_no_headings},
806   {"noheader",      &&case_noheader},
807   {"noheaders",     &&case_noheaders},
808   {"noheading",     &&case_noheading},
809   {"noheadings",    &&case_noheadings},
810   {"pid",           &&case_pid},
811   {"ppid",          &&case_ppid},
812   {"rows",          &&case_rows},
813   {"sid",           &&case_sid},
814   {"sort",          &&case_sort},
815   {"tty",           &&case_tty},
816   {"user",          &&case_user},        /* euid */
817   {"version",       &&case_version},
818   {"width",         &&case_width},
819   };
820   const int gnu_table_count = sizeof(gnu_table)/sizeof(gnu_table_struct);
821
822   s = ps_argv[thisarg]+2;
823   sl = strcspn(s,":=");
824   if(sl > 15) return "Unknown gnu long option.";
825   strncpy(buf, s, sl);
826   buf[sl] = '\0';
827   flagptr = s+sl;
828
829   found = bsearch(&findme, gnu_table, gnu_table_count,
830       sizeof(gnu_table_struct), compare_gnu_table_structs
831   );
832
833   if(!found) return "Unknown gnu long option.";
834
835   goto *(found->jump);    /* See gcc extension info.  :-)   */
836
837   case_Group:
838     trace("--Group\n");
839     arg = grab_gnu_arg();
840     if(!arg) return "List of real groups must follow --Group.";
841     err=parse_list(arg, parse_gid);
842     if(err) return err;
843     selection_list->typecode = SEL_RGID;
844     return NULL;
845   case_User:
846     trace("--User\n");
847     arg = grab_gnu_arg();
848     if(!arg) return "List of real users must follow --User.";
849     err=parse_list(arg, parse_uid);
850     if(err) return err;
851     selection_list->typecode = SEL_RUID;
852     return NULL;
853   case_cols:
854   case_width:
855   case_columns:
856     trace("--cols\n");
857     arg = grab_gnu_arg();
858     if(arg && *arg){
859       long t;
860       char *endptr;
861       t = strtol(arg, &endptr, 0);
862       if(!*endptr && (t>0) && (t<2000000000)){
863         screen_cols = (int)t;
864         return NULL;
865       }
866     }
867     return "Number of columns must follow --cols, --width, or --columns.";
868   case_cumulative:
869     trace("--cumulative\n");
870     if(s[sl]) return "Option --cumulative does not take an argument.";
871     include_dead_children = 1;
872     return NULL;
873   case_deselect:
874     trace("--deselect\n");
875     if(s[sl]) return "Option --deselect does not take an argument.";
876     negate_selection = 1;
877     return NULL;
878   case_no_header:
879   case_no_headers:
880   case_no_heading:
881   case_no_headings:
882   case_noheader:
883   case_noheaders:
884   case_noheading:
885   case_noheadings:
886     trace("--noheaders\n");
887     if(s[sl]) return "Option --no-heading does not take an argument.";
888     if(header_type) return "Only one heading option may be specified.";
889     header_type = HEAD_NONE;
890     return NULL;
891   case_header:
892   case_headers:
893   case_heading:
894   case_headings:
895     trace("--headers\n");
896     if(s[sl]) return "Option --heading does not take an argument.";
897     if(header_type) return "Only one heading option may be specified.";
898     header_type = HEAD_MULTI;
899     return NULL;
900   case_forest:
901     trace("--forest\n");
902     if(s[sl]) return "Option --forest does not take an argument.";
903     forest_type = 'g';
904     return NULL;
905   case_format:
906     trace("--format\n");
907     arg=grab_gnu_arg();
908     if(!arg) return "Format specification must follow --format.";
909     defer_sf_option(arg, SF_G_format);
910     return NULL;
911   case_group:
912     trace("--group\n");
913     arg = grab_gnu_arg();
914     if(!arg) return "List of effective groups must follow --group.";
915     err=parse_list(arg, parse_gid);
916     if(err) return err;
917     selection_list->typecode = SEL_EGID;
918     return NULL;
919   case_help:
920     trace("--help\n");
921     exclusive("--help");
922     fwrite(help_message,1,strlen(help_message),stdout);
923     exit(0);
924     return NULL;
925   case_info:
926     trace("--info\n");
927     exclusive("--info");
928     self_info();
929     exit(0);
930     return NULL;
931   case_pid:
932     trace("--pid\n");
933     arg = grab_gnu_arg();
934     if(!arg) return "List of process IDs must follow --pid.";
935     err=parse_list(arg, parse_pid);
936     if(err) return err;
937     selection_list->typecode = SEL_PID;
938     return NULL;
939   case_ppid:
940     trace("--ppid\n");
941     arg = grab_gnu_arg();
942     if(!arg) return "List of process IDs must follow --ppid.";
943     err=parse_list(arg, parse_pid);
944     if(err) return err;
945     selection_list->typecode = SEL_PPID;
946     return NULL;
947   case_rows:
948   case_lines:
949     trace("--rows\n");
950     arg = grab_gnu_arg();
951     if(arg && *arg){
952       long t;
953       char *endptr;
954       t = strtol(arg, &endptr, 0);
955       if(!*endptr && (t>0) && (t<2000000000)){
956         screen_rows = (int)t;
957         return NULL;
958       }
959     }
960     return "Number of rows must follow --rows or --lines.";
961   case_sid:
962     trace("--sid\n");
963     arg = grab_gnu_arg();
964     if(!arg) return "Some sid thing(s) must follow --sid.";
965     err=parse_list(arg, parse_pid);
966     if(err) return err;
967     selection_list->typecode = SEL_SESS;
968     return NULL;
969   case_sort:
970     trace("--sort\n");
971     arg=grab_gnu_arg();
972     if(!arg) return "Long sort specification must follow --sort.";
973     defer_sf_option(arg, SF_G_sort);
974     return NULL;
975   case_tty:
976     trace("--tty\n");
977     arg = grab_gnu_arg();
978     if(!arg) return "List of ttys must follow --tty.";
979     err=parse_list(arg, parse_tty);
980     if(err) return err;
981     selection_list->typecode = SEL_TTY;
982     return NULL;
983   case_user:
984     trace("--user\n");
985     arg = grab_gnu_arg();
986     if(!arg) return "List of effective users must follow --user.";
987     err=parse_list(arg, parse_uid);
988     if(err) return err;
989     selection_list->typecode = SEL_EUID;
990     return NULL;
991   case_version:
992     trace("--version\n");
993     exclusive("--version");
994     display_version();
995     exit(0);
996     return NULL;
997   case_context:
998     trace("--context\n");
999     format_flags |= FF_Fc;
1000     return NULL;
1001 }
1002
1003 /*************** process trailing PIDs  **********************/
1004 static const char *parse_trailing_pids(void){
1005   selection_node *pidnode;  /* pid */
1006   selection_node *grpnode;  /* process group */
1007   selection_node *sidnode;  /* session */
1008   char **argp;     /* pointer to pointer to text of PID */
1009   const char *err;       /* error code that could or did happen */
1010   int i;
1011
1012   i = ps_argc - thisarg;  /* how many trailing PIDs, SIDs, PGRPs?? */
1013   argp = ps_argv + thisarg;
1014   thisarg = ps_argc - 1;   /* we must be at the end now */
1015
1016   pidnode = malloc(sizeof(selection_node));
1017   pidnode->u = malloc(i*sizeof(sel_union)); /* waste is insignificant */
1018   pidnode->n = 0;
1019
1020   grpnode = malloc(sizeof(selection_node));
1021   grpnode->u = malloc(i*sizeof(sel_union)); /* waste is insignificant */
1022   grpnode->n = 0;
1023
1024   sidnode = malloc(sizeof(selection_node));
1025   sidnode->u = malloc(i*sizeof(sel_union)); /* waste is insignificant */
1026   sidnode->n = 0;
1027
1028   while(i--){
1029     char *data;
1030     data = *(argp++);
1031     switch(*data){
1032     default:   err = parse_pid(  data, pidnode->u + pidnode->n++); break;
1033     case '-':  err = parse_pid(++data, grpnode->u + grpnode->n++); break;
1034     case '+':  err = parse_pid(++data, sidnode->u + sidnode->n++); break;
1035     }
1036     if(err) return err;     /* the node gets freed with the list */
1037   }
1038
1039   if(pidnode->n){
1040     pidnode->next = selection_list;
1041     selection_list = pidnode;
1042     selection_list->typecode = SEL_PID;
1043   }  /* else free both parts */
1044
1045   if(grpnode->n){
1046     grpnode->next = selection_list;
1047     selection_list = grpnode;
1048     selection_list->typecode = SEL_PGRP;
1049   }  /* else free both parts */
1050
1051   if(sidnode->n){
1052     sidnode->next = selection_list;
1053     selection_list = sidnode;
1054     selection_list->typecode = SEL_SESS;
1055   }  /* else free both parts */
1056
1057   return NULL;
1058 }
1059
1060 /************** misc stuff ***********/
1061
1062 static void reset_parser(void){
1063   w_count = 0;
1064 }
1065
1066 static int arg_type(const char *str){
1067   int tmp = str[0];
1068   if((tmp>='a') && (tmp<='z'))   return ARG_BSD;
1069   if((tmp>='A') && (tmp<='Z'))   return ARG_BSD;
1070   if((tmp>='0') && (tmp<='9'))   return ARG_PID;
1071   if(tmp=='+')                   return ARG_SESS;
1072   if(tmp!='-')                   return ARG_FAIL;
1073   tmp = str[1];
1074   if((tmp>='a') && (tmp<='z'))   return ARG_SYSV;
1075   if((tmp>='A') && (tmp<='Z'))   return ARG_SYSV;
1076   if((tmp>='0') && (tmp<='9'))   return ARG_PGRP;
1077   if(tmp!='-')                   return ARG_FAIL;
1078   tmp = str[2];
1079   if((tmp>='a') && (tmp<='z'))   return ARG_GNU;
1080   if((tmp>='A') && (tmp<='Z'))   return ARG_GNU;
1081   if(tmp=='\0')                  return ARG_END;
1082                                  return ARG_FAIL;
1083 }
1084
1085 /* First assume sysv, because that is the POSIX and Unix98 standard. */
1086 static const char *parse_all_options(void){
1087   const char *err = NULL;
1088   int at;
1089   while(++thisarg < ps_argc){
1090   trace("parse_all_options calling arg_type for \"%s\"\n", ps_argv[thisarg]);
1091     at = arg_type(ps_argv[thisarg]);
1092     trace("ps_argv[thisarg] is %s\n", ps_argv[thisarg]);
1093     if(at != ARG_SYSV) not_pure_unix = 1;
1094     switch(at){
1095     case ARG_GNU:
1096       err = parse_gnu_option();
1097       break;
1098     case ARG_SYSV:
1099       if(!force_bsd){   /* else go past case ARG_BSD */
1100         err = parse_sysv_option();
1101         break;
1102     case ARG_BSD:
1103         if(force_bsd && !(personality & PER_FORCE_BSD)) return "way bad";
1104       }
1105       prefer_bsd_defaults = 1;
1106       err = parse_bsd_option();
1107       break;
1108     case ARG_PGRP:
1109     case ARG_SESS:
1110     case ARG_PID:
1111       prefer_bsd_defaults = 1;
1112       err = parse_trailing_pids();
1113       break;
1114     case ARG_END:
1115     case ARG_FAIL:
1116       trace("              FAIL/END on [%s]\n",ps_argv[thisarg]);
1117       return "Garbage option.";
1118       break;
1119     default:
1120       printf("                  ?    %s\n",ps_argv[thisarg]);
1121       return "Something broke.";
1122     } /* switch */
1123     if(err) return err;
1124   } /* while */
1125   return NULL;
1126 }
1127
1128 static void choose_dimensions(void){
1129   if(w_count && (screen_cols<132)) screen_cols=132;
1130   if(w_count>1) screen_cols=OUTBUF_SIZE;
1131   /* perhaps --html and --null should set unlimited width */
1132 }
1133
1134 static const char *thread_option_check(void){
1135   if(!thread_flags){
1136     thread_flags = TF_show_proc;
1137     return NULL;
1138   }
1139
1140   if(forest_type){
1141     return "Thread display conflicts with forest display.";
1142   }
1143   //thread_flags |= TF_no_forest;
1144
1145   if((thread_flags&TF_B_H) && (thread_flags&(TF_B_m|TF_U_m)))
1146     return "Thread flags conflict; can't use H with m or -m.";
1147   if((thread_flags&TF_B_m) && (thread_flags&TF_U_m))
1148     return "Thread flags conflict; can't use both m and -m.";
1149   if((thread_flags&TF_U_L) && (thread_flags&TF_U_T))
1150     return "Thread flags conflict; can't use both -L and -T.";
1151
1152   if(thread_flags&TF_B_H) thread_flags |= (TF_show_proc|TF_loose_tasks);
1153   if(thread_flags&(TF_B_m|TF_U_m)) thread_flags |= (TF_show_proc|TF_show_task|TF_show_both);
1154
1155   if(thread_flags&(TF_U_T|TF_U_L)){
1156     if(thread_flags&(TF_B_m|TF_U_m|TF_B_H)){
1157       // Got a thread style, so format modification is a requirement?
1158       // Maybe -T/-L has H thread style though. (sorting interaction?)
1159       //return "Huh? Tell procps-feedback@lists.sf.net what you expected.";
1160       thread_flags |= TF_must_use;
1161     }else{
1162       // using -L/-T thread style, so format from elsewhere is OK
1163       thread_flags |= TF_show_task;  // or like the H option?
1164       //thread_flags |= TF_no_sort;
1165     }
1166   }
1167
1168   return NULL;
1169 }
1170
1171 int arg_parse(int argc, char *argv[]){
1172   const char *err = NULL;
1173   const char *err2 = NULL;
1174   ps_argc = argc;
1175   ps_argv = argv;
1176   thisarg = 0;
1177
1178   if(personality & PER_FORCE_BSD) goto try_bsd;
1179
1180   err = parse_all_options();
1181   if(err) goto try_bsd;
1182   err = thread_option_check();
1183   if(err) goto try_bsd;
1184   err = process_sf_options(!not_pure_unix);
1185   if(err) goto try_bsd;
1186   err = select_bits_setup();
1187   if(err) goto try_bsd;
1188
1189   choose_dimensions();
1190   return 0;
1191
1192 try_bsd:
1193   trace("--------- now try BSD ------\n");
1194
1195   reset_global();
1196   reset_parser();
1197   reset_sortformat();
1198   format_flags = 0;
1199   ps_argc = argc;
1200   ps_argv = argv;
1201   thisarg = 0;
1202   /* no need to reset flagptr */
1203   not_pure_unix=1;
1204   force_bsd=1;
1205   prefer_bsd_defaults=1;
1206   if(!( (PER_OLD_m|PER_BSD_m) & personality )) /* if default m setting... */
1207     personality |= PER_OLD_m; /* Prefer old Linux over true BSD. */
1208   /* Do not set PER_FORCE_BSD! It is tested below. */
1209
1210   err2 = parse_all_options();
1211   if(err2) goto total_failure;
1212   err2 = thread_option_check();
1213   if(err2) goto total_failure;
1214   err2 = process_sf_options(!not_pure_unix);
1215   if(err2) goto total_failure;
1216   err2 = select_bits_setup();
1217   if(err2) goto total_failure;
1218
1219   // Feel a need to patch this out? First of all, read the FAQ.
1220   // Second of all, talk to me. Without this warning, people can
1221   // get seriously confused. Ask yourself if users would freak out
1222   // about "ps -aux" suddenly changing behavior if a user "x" were
1223   // added to the system.
1224   //
1225   // Also, a "-x" option is coming. It's already there in fact,
1226   // for some non-default personalities. So "ps -ax" will parse
1227   // as SysV options... and you're screwed if you've been patching
1228   // out the friendly warning. Cut-over is likely to be in 2005.
1229   if(!(personality & PER_FORCE_BSD))
1230     fprintf(stderr, "Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html\n");
1231   // Remember: contact albert@users.sf.net or procps-feedback@lists.sf.net
1232   // if you should feel tempted. Be damn sure you understand all
1233   // the issues. The same goes for other stuff too, BTW. Please ask.
1234   // I'm happy to justify various implementation choices.
1235
1236   choose_dimensions();
1237   return 0;
1238
1239 total_failure:
1240   reset_parser();
1241   if(personality & PER_FORCE_BSD) fprintf(stderr, "ERROR: %s\n", err2);
1242   else fprintf(stderr, "ERROR: %s\n", err);
1243   fwrite(help_message,1,strlen(help_message),stderr);
1244   exit(1);
1245   /* return 1; */ /* useless */
1246 }