Updated with Tizen:Base source codes
[external/procps.git] / ps / sortformat.c
1 /*
2  * Copyright 1998-2004 by Albert Cahalan; all rights resered.         
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 #include <stdlib.h>
13 #include <stdio.h>
14 #include <string.h>
15
16 /* username lookups */
17 #include <sys/types.h>
18 #include <pwd.h>
19 #include <grp.h>
20
21 #include "../proc/readproc.h"
22 #include "../proc/sysinfo.h"
23 #include "common.h"
24
25 static sf_node *sf_list = NULL;         /* deferred sorting and formatting */
26 static int broken;                      /* use gross Unix98 parsing? */
27 static int have_gnu_sort = 0;           /* if true, "O" must be format */
28 static int already_parsed_sort = 0;     /* redundantly set in & out of fn */
29 static int already_parsed_format = 0;
30
31
32 /****************  Parse single format specifier *******************/
33 static format_node *do_one_spec(const char *spec, const char *override){
34   const format_struct *fs;
35   const macro_struct *ms;
36
37   fs = search_format_array(spec);
38   if(fs){
39     int w1, w2;
40     format_node *thisnode;
41     thisnode = malloc(sizeof(format_node));
42     if(fs->flags & CF_PIDMAX){
43       w1 = (int)get_pid_digits();
44       w2 = strlen(fs->head);
45       if(w2>w1) w1=w2; // FIXME w/ separate header/body column sizing
46     }else{
47       w1 = fs->width;
48     }
49     if(override){
50       w2 = strlen(override);
51       thisnode->width = (w1>w2)?w1:w2;
52       thisnode->name = malloc(strlen(override)+1);
53       strcpy(thisnode->name, override);
54     }else{
55       thisnode->width = w1;
56       thisnode->name = malloc(strlen(fs->head)+1);
57       strcpy(thisnode->name, fs->head);
58     }
59     thisnode->pr = fs->pr;
60     thisnode->need = fs->need;
61     thisnode->vendor = fs->vendor;
62     thisnode->flags = fs->flags;
63     thisnode->next = NULL;
64     return thisnode;
65   }
66
67   /* That failed, so try it as a macro. */
68   ms = search_macro_array(spec);
69   if(ms){
70     format_node *list = NULL;
71     format_node *newnode;
72     const char *walk;
73     int dist;
74     char buf[16]; /* trust strings will be short (from above, not user) */
75     walk = ms->head;
76     while(*walk){
77       dist = strcspn(walk, ", ");
78       strncpy(buf,walk,dist);
79       buf[dist] = '\0';
80       newnode = do_one_spec(buf,override); /* call self, assume success */
81       newnode->next = list;
82       list = newnode;
83       walk += dist;
84       if(*walk) walk++;
85     }
86     return list;
87   }
88   return NULL;   /* bad, spec not found */
89 }
90
91
92 /************ must wrap user format in default *************/
93 static void O_wrap(sf_node *sfn, int otype){
94   format_node *fnode;
95   format_node *endp;
96   const char *trailer;
97
98   trailer = (otype=='b') ? "END_BSD" : "END_SYS5" ;
99
100   fnode =  do_one_spec("pid",NULL);
101   if(!fnode)fprintf(stderr,"Seriously crashing. Goodbye cruel world.\n");
102   endp = sfn->f_cooked; while(endp->next) endp = endp->next;  /* find end */
103   endp->next = fnode;
104   
105   fnode =  do_one_spec(trailer,NULL);
106   if(!fnode)fprintf(stderr,"Seriously crashing. Goodbye cruel world.\n");
107   endp = fnode; while(endp->next) endp = endp->next;  /* find end */
108   endp->next = sfn->f_cooked;
109   sfn->f_cooked = fnode;
110 }
111
112 /******************************************************************
113  * Used to parse option AIX field descriptors.
114  * Put each completed format_node onto the list starting at ->f_cooked
115  */
116 static const char *aix_format_parse(sf_node *sfn){
117   char *buf;                   /* temp copy of arg to hack on */
118   char *walk;
119   int items;
120
121   /*** sanity check and count items ***/
122   items = 0;
123   walk = sfn->sf;
124   /* state machine */ {
125   int c;
126   initial:
127     c = *walk++;
128     if(c=='%')    goto get_desc;
129     if(!c)        goto looks_ok;
130   /* get_text: */
131     items++;
132   get_more_text:
133     c = *walk++;
134     if(c=='%')    goto get_desc;
135     if(c)         goto get_more_text;
136     goto looks_ok;
137   get_desc:
138     items++;
139     c = *walk++;
140     if(c)         goto initial;
141     return "Improper AIX field descriptor.";
142   looks_ok:
143     ;
144   }
145
146   /*** sanity check passed ***/
147   buf = malloc(strlen(sfn->sf)+1);
148   strcpy(buf, sfn->sf);
149   walk = sfn->sf;
150   
151   while(items--){
152     format_node *fnode;  /* newly allocated */
153     format_node *endp;   /* for list manipulation */
154
155     if(*walk == '%'){
156       const aix_struct *aix;
157       walk++;
158       if(*walk == '%') goto double_percent;
159       aix = search_aix_array(*walk);
160       walk++;
161       if(!aix){
162         free(buf);
163         return "Unknown AIX field descriptor.";
164       }
165       fnode =  do_one_spec(aix->spec, aix->head);
166       if(!fnode){
167         free(buf);
168         return "AIX field descriptor processing bug.";
169       }
170     } else {
171       int len;
172       len = strcspn(walk, "%");
173       memcpy(buf,walk,len);
174       if(0){
175 double_percent:
176         len = 1;
177         buf[0] = '%';
178       }
179       buf[len] = '\0';
180       walk += len;
181       fnode = malloc(sizeof(format_node));
182       fnode->width = len;
183       fnode->name = malloc(len+1);
184       strcpy(fnode->name, buf);
185       fnode->pr = NULL;     /* checked for */
186       fnode->need = 0;
187       fnode->vendor = AIX;
188       fnode->flags = CF_PRINT_EVERY_TIME;
189       fnode->next = NULL;
190     }
191     
192     endp = fnode; while(endp->next) endp = endp->next;  /* find end */
193     endp->next = sfn->f_cooked;
194     sfn->f_cooked = fnode;
195   }
196   free(buf);
197   already_parsed_format = 1;
198   return NULL;
199 }
200
201 /***************************************************************
202  * Used to parse option O lists. Option O is shared between
203  * sorting and formatting. Users may expect one or the other.
204  * The "broken" flag enables a really bad Unix98 misfeature.
205  * Put each completed format_node onto the list starting at ->f_cooked
206  */
207 static const char *format_parse(sf_node *sfn){
208   char *buf;                   /* temp copy of arg to hack on */
209   char *sep_loc;               /* separator location: " \t,\n" */
210   char *walk;
211   const char *err;       /* error code that could or did happen */
212   format_node *fnode;
213   int items;
214   int need_item;
215   static char errbuf[80]; /* for variable-text error message */
216
217   /*** prepare to operate ***/
218   buf = malloc(strlen(sfn->sf)+1);
219   strcpy(buf, sfn->sf);
220   
221   /*** sanity check and count items ***/
222   need_item = 1; /* true */
223   items = 0;
224   walk = buf;
225   do{
226     switch(*walk){
227     case ' ': case ',': case '\t': case '\n': case '\0':
228     /* Linux extension: allow \t and \n as delimiters */
229       if(need_item){
230         free(buf);
231         goto improper;
232       }
233       need_item=1;
234       break;
235     case '=':
236       if(broken) goto out;
237       /* fall through */
238     default:
239       if(need_item) items++;
240       need_item=0;
241     }
242   } while (*++walk);
243 out:
244   if(!items){
245     free(buf);
246     goto empty;
247   }
248 #ifdef STRICT_LIST
249   if(need_item){    /* can't have trailing deliminator */
250     free(buf);
251     goto improper;
252   }
253 #else
254   if(need_item){    /* allow 1 trailing deliminator */
255     *--walk='\0';  /* remove the trailing deliminator */
256   }
257 #endif
258   /*** actually parse the list ***/
259   walk = buf;
260   while(items--){
261     format_node *endp;
262     char *equal_loc;
263     char *colon_loc;
264     sep_loc = strpbrk(walk," ,\t\n");
265     /* if items left, then sep_loc is not in header override */
266     if(items && sep_loc) *sep_loc = '\0';
267     equal_loc = strpbrk(walk,"=");
268     if(equal_loc){   /* if header override */
269       *equal_loc = '\0';
270       equal_loc++;
271     }
272     colon_loc = strpbrk(walk,":");
273     if(colon_loc){   /* if width override */
274       *colon_loc = '\0';
275       colon_loc++;
276       if(strspn(colon_loc,"0123456789") != strlen(colon_loc) || *colon_loc=='0' || !*colon_loc){
277         free(buf);
278         goto badwidth;
279       }
280     }
281     fnode =  do_one_spec(walk,equal_loc);
282     if(!fnode){
283       if(!*errbuf){  /* if didn't already create an error string */
284         snprintf(
285           errbuf,
286           sizeof(errbuf),
287           "Unknown user-defined format specifier \"%s\".",
288           walk
289         );
290       }
291       free(buf);
292       goto unknown;
293     }
294     if(colon_loc){
295       if(fnode->next){
296         free(buf);
297         goto notmacro;
298       }
299       // FIXME: enforce signal width to 8, 9, or 16 (grep: SIGNAL wide_signals)
300       fnode->width = atoi(colon_loc); // already verified to be a number
301     }
302     endp = fnode; while(endp->next) endp = endp->next;  /* find end */
303     endp->next = sfn->f_cooked;
304     sfn->f_cooked = fnode;
305     walk = sep_loc + 1; /* point to next item, if any */
306   }
307   free(buf);
308   already_parsed_format = 1;
309   return NULL;
310
311   /* errors may cause a retry looking for AIX format codes */
312   if(0) unknown:  err=errbuf;
313   if(0) empty:    err="Empty format list.";
314   if(0) improper: err="Improper format list.";
315   if(0) badwidth: err="Column widths must be unsigned decimal numbers.";
316   if(0) notmacro: err="Can't set width for a macro (multi-column) format specifier.";
317   if(strchr(sfn->sf,'%')) err = aix_format_parse(sfn);
318   return err;
319 }
320
321 /****************  Parse single sort specifier *******************/
322 static sort_node *do_one_sort_spec(const char *spec){
323   const format_struct *fs;
324   int reverse = 0;
325   if(*spec == '-'){
326     reverse = 1;
327     spec++;
328   } else if(*spec == '+'){
329     spec++;
330   }
331   fs = search_format_array(spec);
332   if(fs){
333     sort_node *thisnode;
334     thisnode = malloc(sizeof(sort_node));
335     thisnode->sr = fs->sr;
336     thisnode->need = fs->need;
337     thisnode->reverse = reverse;
338     thisnode->next = NULL;
339     return thisnode;
340   }
341   return NULL;   /* bad, spec not found */
342 }
343
344
345 /**************************************************************
346  * Used to parse long sorting options.
347  * Put each completed sort_node onto the list starting at ->s_cooked
348  */
349 static const char *long_sort_parse(sf_node *sfn){
350   char *buf;                   /* temp copy of arg to hack on */
351   char *sep_loc;               /* separator location: " \t,\n" */
352   char *walk;
353   sort_node *snode;
354   int items;
355   int need_item;
356
357   /*** prepare to operate ***/
358   buf = malloc(strlen(sfn->sf)+1);
359   strcpy(buf, sfn->sf);
360   
361   /*** sanity check and count items ***/
362   need_item = 1; /* true */
363   items = 0;
364   walk = buf;
365   do{
366     switch(*walk){
367     case ' ': case ',': case '\t': case '\n': case '\0':
368       if(need_item){
369         free(buf);
370         return "Improper sort list";
371       }
372       need_item=1;
373       break;
374     default:
375       if(need_item) items++;
376       need_item=0;
377     }
378   } while (*++walk);
379   if(!items){
380     free(buf);
381     return "Empty sort list.";
382   }
383 #ifdef STRICT_LIST
384   if(need_item){    /* can't have trailing deliminator */
385     free(buf);
386     return "Improper sort list.";
387   }
388 #else
389   if(need_item){    /* allow 1 trailing deliminator */
390     *--walk='\0';  /* remove the trailing deliminator */
391   }
392 #endif
393   /*** actually parse the list ***/
394   walk = buf;
395   while(items--){
396     sort_node *endp;
397     sep_loc = strpbrk(walk," ,\t\n");
398     if(sep_loc) *sep_loc = '\0';
399     snode = do_one_sort_spec(walk);
400     if(!snode){
401       free(buf);
402       return "Unknown sort specifier.";
403     }
404     endp = snode; while(endp->next) endp = endp->next;  /* find end */
405     endp->next = sfn->s_cooked;
406     sfn->s_cooked = snode;
407     walk = sep_loc + 1; /* point to next item, if any */
408   }
409   free(buf);
410   already_parsed_sort = 1;
411   return NULL;
412 }
413
414
415
416
417
418
419 /************ pre-parse short sorting option *************/
420 /* Errors _must_ be detected so that the "O" option can try to
421  * reparse as formatting codes.
422  */
423 static const char *verify_short_sort(const char *arg){
424   const char all[] = "CGJKMNPRSTUcfgjkmnoprstuvy+-";
425   char checkoff[256];
426   int i;
427   const char *walk;
428   int tmp;
429   if(strspn(arg,all) != strlen(arg)) return "Bad sorting code.";
430   for(i=256; i--;) checkoff[i] = 0;
431   walk = arg;
432   for(;;){
433     tmp = *walk;
434     switch(tmp){
435     case '\0':
436       return NULL;   /* looks good */
437     case '+':
438     case '-':
439       tmp = *(walk+1);
440       if(!tmp || tmp=='+' || tmp=='-') return "Bad sorting code.";
441       break;
442     case 'P':
443       if(forest_type) return "PPID sort and forest output conflict.";
444       /* fall through */
445     default:
446       if(checkoff[tmp]) return "Bad sorting code.";   /* repeated */
447       /* ought to check against already accepted sort options */
448       checkoff[tmp] = 1;
449       break;
450     }
451     walk++;
452   }
453 }
454
455
456
457 /************ parse short sorting option *************/
458 static const char *short_sort_parse(sf_node *sfn){
459   int direction = 0;
460   const char *walk;
461   int tmp;
462   sort_node *snode;
463   sort_node *endp;
464   const struct shortsort_struct *ss;
465   walk = sfn->sf;
466   for(;;){
467     tmp = *walk;
468     switch(tmp){
469     case '\0':
470       already_parsed_sort = 1;
471       return NULL;
472     case '+':
473       direction = 0;
474       break;
475     case '-':
476       direction = 1;
477       break;
478     default:
479       ss = search_shortsort_array(tmp);
480       if(!ss) return "Unknown sort specifier.";
481       snode = do_one_sort_spec(ss->spec);
482       if(!snode) return "Unknown sort specifier.";
483       snode->reverse = direction;
484       endp = snode; while(endp->next) endp = endp->next;  /* find end */
485       endp->next = sfn->s_cooked;
486       sfn->s_cooked = snode;
487       direction = 0;
488       break;
489     }
490     walk++;
491   }
492 }
493
494 /******************* high-level below here *********************/
495
496
497 /*
498  * Used to parse option O lists. Option O is shared between
499  * sorting and formatting. Users may expect one or the other.
500  * The "broken" flag enables a really bad Unix98 misfeature.
501  * Recursion is to preserve original order.
502  */
503 static const char *parse_O_option(sf_node *sfn){
504   const char *err;     /* error code that could or did happen */
505
506   if(sfn->next){
507     err = parse_O_option(sfn->next);
508     if(err) return err;
509   }
510
511   switch(sfn->sf_code){
512     case SF_B_o: case SF_G_format: case SF_U_o: /*** format ***/
513       err = format_parse(sfn);
514       if(!err) already_parsed_format = 1;
515       break;
516     case SF_U_O:                                /*** format ***/
517       /* Can have -l -f f u... set already_parsed_format like DEC does */
518       if(already_parsed_format) return "option -O can not follow other format options.";
519       err = format_parse(sfn);
520       if(err) return err;
521       already_parsed_format = 1;
522       O_wrap(sfn,'u'); /* must wrap user format in default */
523       break;
524     case SF_B_O:                                /***  both  ***/
525       if(have_gnu_sort || already_parsed_sort) err = "Multiple sort options.";
526       else err = verify_short_sort(sfn->sf);
527       if(!err){ /* success as sorting code */
528         short_sort_parse(sfn);
529         already_parsed_sort = 1;
530         return NULL;
531       }
532       if(already_parsed_format){
533         err = "option O is neither first format nor sort order.";
534         break;
535       }
536       if(!format_parse(sfn)){ /* if success as format code */
537         already_parsed_format = 1;
538         O_wrap(sfn,'b'); /* must wrap user format in default */
539         return NULL;
540       }
541       break;
542     case SF_G_sort: case SF_B_m:                 /***  sort  ***/
543       if(already_parsed_sort) err = "Multiple sort options.";
544       else err = long_sort_parse(sfn);
545       already_parsed_sort = 1;
546       break;
547     default:                                    /***  junk  ***/
548       return "Bug: parse_O_option got weirdness!";
549   }
550   return err; /* could be NULL */
551 }
552
553
554 /************ Main parser calls this to save lists for later **********/
555 /* store data for later and return 1 if arg looks non-standard */
556 int defer_sf_option(const char *arg, int source){
557   sf_node *sfn;
558   char buf[16];
559   int dist;
560   const format_struct *fs;
561   int need_item = 1;
562
563   sfn = malloc(sizeof(sf_node));
564   sfn->sf = malloc(strlen(arg)+1);
565   strcpy(sfn->sf, arg);
566   sfn->sf_code = source;
567   sfn->s_cooked = NULL;
568   sfn->f_cooked = NULL;
569   sfn->next = sf_list;
570   sf_list = sfn;
571
572   if(source == SF_G_sort) have_gnu_sort = 1;
573
574   /* Now try to find an excuse to ignore broken Unix98 parsing. */
575   if(source != SF_U_o) return 1;    /* Wonderful! Already non-Unix98. */
576   do{
577     switch(*arg){
578     case ' ': case ',': case '\0':  /* no \t\n\r support in Unix98 */
579       if(need_item) return 1;       /* something wrong */
580       need_item=1;
581       break;
582     case '=':
583       if(need_item) return 1;       /* something wrong */
584       return 0;                     /* broken Unix98 parsing is required */
585     default:
586       if(!need_item) break;
587       need_item=0;
588       dist = strcspn(arg,", =");
589       if(dist>15) return 1;         /* something wrong, sort maybe? */
590       strncpy(buf,arg,dist);   /* no '\0' on end */
591       buf[dist] = '\0';        /* fix that problem */
592       fs = search_format_array(buf);
593       if(!fs) return 1;             /* invalid spec, macro or sort maybe? */
594       if(fs->vendor) return 1;      /* Wonderful! Legal non-Unix98 spec. */
595     }
596   } while (*++arg);
597
598   return 0;                         /* boring, Unix98 is no change */
599 }
600
601 /***** Since ps is not long-lived, the memory leak can be ignored. ******/
602 void reset_sortformat(void){
603   sf_list = NULL;          /* deferred sorting and formatting */
604   format_list = NULL;      /* digested formatting options */
605   sort_list = NULL;        /* digested sorting options (redundant?) */
606   have_gnu_sort = 0;
607   already_parsed_sort = 0;
608   already_parsed_format = 0;
609 }
610
611
612 /***** Search format_list for findme, then insert putme after findme. ****/
613 static int fmt_add_after(const char *findme, format_node *putme){
614   format_node *walk;
615   if(!strcmp(format_list->name, findme)){
616     putme->next = format_list->next;
617     format_list->next = putme;
618     return 1; /* success */
619   }
620   walk = format_list;
621   while(walk->next){
622     if(!strcmp(walk->next->name, findme)){
623       putme->next = walk->next->next;
624       walk->next->next = putme;
625       return 1; /* success */
626     }
627     walk = walk->next;
628   }
629   return 0; /* fail */
630 }
631
632 /******* Search format_list for findme, then delete it. ********/
633 static int fmt_delete(const char *findme){
634   format_node *walk;
635   format_node *old;
636   if(!strcmp(format_list->name, findme)){
637     old = format_list;
638     format_list = format_list->next;
639     free(old);
640     return 1; /* success */
641   }
642   walk = format_list;
643   while(walk->next){
644     if(!strcmp(walk->next->name, findme)){
645       old = walk->next;
646       walk->next = walk->next->next;
647       free(old);
648       return 1; /* success */
649     }
650     walk = walk->next;
651   }
652   return 0; /* fail */
653 }
654
655
656 /************ Build a SysV format backwards. ***********/
657 #define PUSH(foo) (fn=do_one_spec(foo, NULL), fn->next=format_list, format_list=fn)
658 static const char *generate_sysv_list(void){
659   format_node *fn;
660   if((format_modifiers & FM_y) && !(format_flags & FF_Ul))
661     return "Modifier -y without format -l makes no sense.";
662   if(prefer_bsd_defaults){
663     if(format_flags) PUSH("cmd");
664     else PUSH("args");
665     PUSH("bsdtime");
666     if(!(format_flags & FF_Ul)) PUSH("stat");
667   }else{
668     if(format_flags & FF_Uf) PUSH("cmd");
669     else PUSH("ucmd");
670     PUSH("time");
671   }
672   PUSH("tname");  /* Unix98 says "TTY" here, yet "tty" produces "TT". */
673   if(format_flags & FF_Uf) PUSH("stime");
674   /* avoid duplicate columns from -FP and -Fly */
675   if(format_modifiers & FM_F){
676     /* if -FP take the Sun-style column instead (sorry about "sgi_p") */
677     if(!(format_modifiers & FM_P)) PUSH("psr");  /* should be ENG */
678     /* if -Fly take the ADDR-replacement RSS instead */
679     if(!( (format_flags & FF_Ul) && (format_modifiers & FM_y) )) PUSH("rss");
680   }
681   if(format_flags & FF_Ul){
682     PUSH("wchan");
683   }
684   /* since FM_y adds RSS anyway, don't do this hack when that is true */
685   if( (format_flags & FF_Ul) && !(format_modifiers & FM_y) ){
686     if(personality & PER_IRIX_l){ /* add "rss" then ':' here */
687       PUSH("sgi_rss");
688       fn = malloc(sizeof(format_node));
689       fn->width = 1;
690       fn->name = malloc(2);
691       strcpy(fn->name, ":");
692       fn->pr = NULL;     /* checked for */
693       fn->need = 0;
694       fn->vendor = AIX;   /* yes, for SGI weirdness */
695       fn->flags = CF_PRINT_EVERY_TIME;
696       fn->next = format_list;
697       format_list=fn;
698     }
699   }
700   if((format_modifiers & FM_F) || (format_flags & FF_Ul)){
701     PUSH("sz");
702   }
703   if(format_flags & FF_Ul){
704     if(format_modifiers & FM_y) PUSH("rss");
705     else if(personality & (PER_ZAP_ADDR|PER_IRIX_l)) PUSH("sgi_p");
706     else PUSH("addr_1");
707   }
708   if(format_modifiers & FM_c){
709     PUSH("pri"); PUSH("class");
710   }else if(format_flags & FF_Ul){
711     PUSH("ni");
712     if(personality & PER_IRIX_l) PUSH("priority");
713     else /* is this good? */ PUSH("opri");
714   }
715
716   // FIXME TODO XXX -- this is a serious problem
717   // These somehow got flipped around.
718   // The bug is in procps-3.1.1, procps-990211, prior too?
719   if((thread_flags & TF_U_L) && (format_flags & FF_Uf)) PUSH("nlwp");
720   if( (format_flags & (FF_Uf|FF_Ul)) && !(format_modifiers & FM_c) ) PUSH("c");
721
722   if(format_modifiers & FM_P) PUSH("psr");
723   if(thread_flags & TF_U_L) PUSH("lwp");
724   if(format_modifiers & FM_j){
725     PUSH("sid");
726     PUSH("pgid");
727   }
728   if(format_flags & (FF_Uf|FF_Ul)) PUSH("ppid");
729   if(thread_flags & TF_U_T) PUSH("spid");
730   PUSH("pid");
731   if(format_flags & FF_Uf){
732     if(personality & PER_SANE_USER) PUSH("user");
733     else PUSH("uid_hack");
734   }else if(format_flags & FF_Ul){
735     PUSH("uid");
736   }
737   if(format_flags & FF_Ul){
738     PUSH("s");
739     if(!(format_modifiers & FM_y)) PUSH("f");
740   }
741   if(format_modifiers & FM_M){
742     PUSH("label");  /* Mandatory Access Control */
743   }
744   return NULL;
745 }
746
747
748 /**************************************************************************
749  * Used to parse option O lists. Option O is shared between
750  * sorting and formatting. Users may expect one or the other.
751  * The "broken" flag enables a really bad Unix98 misfeature.
752  */
753 const char *process_sf_options(int localbroken){
754   sf_node *sf_walk;
755
756   if(personality & PER_BROKEN_o) localbroken = 1;
757   if(personality & PER_GOOD_o)   localbroken = 0;
758   broken = localbroken;
759   if(sf_list){
760     const char *err;
761     err = parse_O_option(sf_list);
762     if(err) return err;
763   }
764
765   if(format_list) printf("Bug: must reset the list first!\n");
766
767   /* merge formatting info of sf_list into format_list here */
768   sf_walk = sf_list;
769   while(sf_walk){
770     format_node *fmt_walk;
771     fmt_walk = sf_walk->f_cooked;
772     sf_walk->f_cooked = NULL;
773     while(fmt_walk){   /* put any nodes onto format_list in opposite way */
774       format_node *travler;
775       travler = fmt_walk;
776       fmt_walk = fmt_walk->next;
777       travler->next = format_list;
778       format_list = travler;
779     }
780     sf_walk = sf_walk->next;
781   }
782
783   /* merge sorting info of sf_list into sort_list here */
784   sf_walk = sf_list;
785   while(sf_walk){
786     sort_node *srt_walk;
787     srt_walk = sf_walk->s_cooked;
788     sf_walk->s_cooked = NULL;
789     while(srt_walk){   /* put any nodes onto sort_list in opposite way */
790       sort_node *travler;
791       travler = srt_walk;
792       srt_walk = srt_walk->next;
793       travler->next = sort_list;
794       sort_list = travler;
795     }
796     sf_walk = sf_walk->next;
797   }
798
799   // Get somebody to explain how -L/-T is supposed to interact
800   // with sorting. Do the threads remain grouped, with sorting
801   // by process, or do the threads get sorted by themselves?
802   if(sort_list && (thread_flags&TF_no_sort)){
803     return "Tell procps-feedback@lists.sf.net what you expected.";
804   }
805
806   // If nothing else, try to use $PS_FORMAT before the default.
807   if(!format_flags && !format_modifiers && !format_list){
808     char *tmp;
809     tmp = getenv("PS_FORMAT");  /* user override kills default */
810     if(tmp && *tmp){
811       const char *err;
812       sf_node sfn;
813       if(thread_flags&TF_must_use) return "Tell procps-feedback@sf.net what you want. (-L/-T, -m/m/H, and $PS_FORMAT)";
814       sfn.sf = tmp;
815       sfn.f_cooked = NULL;
816       err = format_parse(&sfn);
817       if(!err){
818         format_node *fmt_walk;
819         fmt_walk = sfn.f_cooked;
820         while(fmt_walk){   /* put any nodes onto format_list in opposite way */
821           format_node *travler;
822           travler = fmt_walk;
823           fmt_walk = fmt_walk->next;
824           travler->next = format_list;
825           format_list = travler;
826         }
827         return NULL;
828       }
829       // FIXME: prove that this won't be hit on valid bogus-BSD options
830       fprintf(stderr, "Warning: $PS_FORMAT ignored. (%s)\n", err);
831     }
832   }
833
834   if(format_list){
835     if(format_flags) return "Conflicting format options.";
836     if(format_modifiers) return "Can't use output modifiers with user-defined output";
837     if(thread_flags&TF_must_use) return "-L/-T with H/m/-m and -o/-O/o/O is nonsense";
838     return NULL;
839   }
840
841   do{
842     const char *spec;
843     switch(format_flags){
844
845     default:             return "Conflicting format options.";
846
847     /* These can be NULL, which enables SysV list generation code. */
848     case 0:              spec=NULL;           break;
849     case FF_Uf | FF_Ul:  spec=sysv_fl_format; break;
850     case FF_Uf:          spec=sysv_f_format;  break;
851     case FF_Ul:          spec=sysv_l_format;  break;
852
853     /* These are NOT REACHED for normal -j processing. */
854     case FF_Uj:          spec=sysv_j_format;  break; /* Debian & Digital */
855     case FF_Uj | FF_Ul:  spec="RD_lj";        break; /* Debian */
856     case FF_Uj | FF_Uf:  spec="RD_fj";        break; /* Debian */
857
858     /* These are true BSD options. */
859     case FF_Bj:          spec=bsd_j_format;   break;
860     case FF_Bl:          spec=bsd_l_format;   break;
861     case FF_Bs:          spec=bsd_s_format;   break;
862     case FF_Bu:          spec=bsd_u_format;   break;
863     case FF_Bv:          spec=bsd_v_format;   break;
864
865     /* These are old Linux options. Option m is overloaded. */
866     case FF_LX:          spec="OL_X";         break;
867     case FF_Lm:          spec="OL_m";         break;
868
869     /* This is the sole FLASK security option. */
870     case FF_Fc:          spec="FLASK_context"; break;
871
872     }  /* end switch(format_flags) */
873
874     // not just for case 0, since sysv_l_format and such may be NULL
875     if(!spec) return generate_sysv_list();
876
877     do{
878       format_node *fmt_walk;
879       fmt_walk = do_one_spec(spec, NULL); /* use override "" for no headers */
880       while(fmt_walk){   /* put any nodes onto format_list in opposite way */
881         format_node *travler;
882         travler = fmt_walk;
883         fmt_walk = fmt_walk->next;
884         travler->next = format_list;
885         format_list = travler;
886       }
887     }while(0);
888   }while(0);
889
890   do{
891     format_node *fn;
892     if(format_modifiers & FM_j){
893       fn = do_one_spec("pgid", NULL);
894       if(!fmt_add_after("PPID", fn)) if(!fmt_add_after("PID", fn))
895         return "Internal error, no PID or PPID for -j option.";
896       fn = do_one_spec("sid", NULL);
897       if(!fmt_add_after("PGID", fn)) return "Lost my PGID!";
898     }
899     if(format_modifiers & FM_y){
900       /* TODO: check for failure to do something, and complain if so */
901       fmt_delete("F");
902       fn = do_one_spec("rss", NULL);
903       if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR");
904     }
905     if(format_modifiers & FM_c){
906       fmt_delete("%CPU"); fmt_delete("CPU"); fmt_delete("CP"); fmt_delete("C");
907       fmt_delete("NI");
908       fn = do_one_spec("class", NULL);
909       if(!fmt_add_after("PRI", fn))
910         return "Internal error, no PRI for -c option.";
911       fmt_delete("PRI"); /* we want a different one */
912       fn = do_one_spec("pri", NULL);
913       if(!fmt_add_after("CLS", fn)) return "Lost my CLS!";
914     }
915     if(thread_flags & TF_U_T){
916       fn = do_one_spec("spid", NULL);
917       if(!fmt_add_after("PID", fn) && (thread_flags&TF_must_use))
918         return "-T with H/-m/m but no PID for SPID to follow";
919     }
920     if(thread_flags & TF_U_L){
921       fn = do_one_spec("lwp", NULL);
922       if(fmt_add_after("SID",  fn)) goto did_lwp;
923       if(fmt_add_after("SESS", fn)) goto did_lwp;
924       if(fmt_add_after("PGID", fn)) goto did_lwp;
925       if(fmt_add_after("PGRP", fn)) goto did_lwp;
926       if(fmt_add_after("PPID", fn)) goto did_lwp;
927       if(fmt_add_after("PID",  fn)) goto did_lwp;
928       if(thread_flags&TF_must_use)
929         return "-L with H/-m/m but no PID/PGID/SID/SESS for NLWP to follow";
930 did_lwp:
931       fn = do_one_spec("nlwp", NULL);
932       fmt_add_after("%CPU",  fn);
933     }
934     if(format_modifiers & FM_M){    // Mandatory Access Control, IRIX style
935       fn = do_one_spec("label", NULL);
936       fn->next=format_list;
937       format_list=fn;
938     }
939     /* Do personality-specific translations not covered by format_flags.
940      * Generally, these only get hit when personality overrides unix output.
941      * That (mostly?) means the Digital and Debian personalities.
942      */
943     if((personality & PER_ZAP_ADDR) && (format_flags & FF_Ul)){
944       fn = do_one_spec("sgi_p", NULL);
945       if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR");
946     }
947     if((personality & PER_SANE_USER) && (format_flags & FF_Uf)){
948       fn = do_one_spec("user", NULL);
949       if(fmt_add_after("UID", fn)) fmt_delete("UID");
950     }
951   }while(0);
952
953   return NULL;
954 }
955