951 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			951 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * sortformat - ps output sorting
 | 
						|
 * Copyright 1998-2004 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
 | 
						|
 */
 | 
						|
 | 
						|
#include <grp.h>
 | 
						|
#include <pwd.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <limits.h>
 | 
						|
 | 
						|
#include <sys/types.h>
 | 
						|
 | 
						|
#include "../proc/readproc.h"
 | 
						|
#include "../proc/sysinfo.h"
 | 
						|
 | 
						|
#include "common.h"
 | 
						|
 | 
						|
static sf_node *sf_list = NULL;         /* deferred sorting and formatting */
 | 
						|
static int have_gnu_sort = 0;           /* if true, "O" must be format */
 | 
						|
static int already_parsed_sort = 0;     /* redundantly set in & out of fn */
 | 
						|
static int already_parsed_format = 0;
 | 
						|
 | 
						|
 | 
						|
/****************  Parse single format specifier *******************/
 | 
						|
static format_node *do_one_spec(const char *spec, const char *override){
 | 
						|
  const format_struct *fs;
 | 
						|
  const macro_struct *ms;
 | 
						|
 | 
						|
  fs = search_format_array(spec);
 | 
						|
  if(fs){
 | 
						|
    int w1, w2;
 | 
						|
    format_node *thisnode;
 | 
						|
    thisnode = malloc(sizeof(format_node));
 | 
						|
    if(fs->flags & CF_PIDMAX){
 | 
						|
      w1 = (int)get_pid_digits();
 | 
						|
      w2 = strlen(fs->head);
 | 
						|
      if(w2>w1) w1=w2; // FIXME w/ separate header/body column sizing
 | 
						|
    }else{
 | 
						|
      w1 = fs->width;
 | 
						|
    }
 | 
						|
    if(override){
 | 
						|
      w2 = strlen(override);
 | 
						|
      thisnode->width = (w1>w2)?w1:w2;
 | 
						|
      thisnode->name = strdup(override);
 | 
						|
    }else{
 | 
						|
      thisnode->width = w1;
 | 
						|
      thisnode->name = strdup(fs->head);
 | 
						|
    }
 | 
						|
    thisnode->pr = fs->pr;
 | 
						|
    thisnode->need = fs->need;
 | 
						|
    thisnode->vendor = fs->vendor;
 | 
						|
    thisnode->flags = fs->flags;
 | 
						|
    thisnode->next = NULL;
 | 
						|
    return thisnode;
 | 
						|
  }
 | 
						|
 | 
						|
  /* That failed, so try it as a macro. */
 | 
						|
  ms = search_macro_array(spec);
 | 
						|
  if(ms){
 | 
						|
    format_node *list = NULL;
 | 
						|
    format_node *newnode;
 | 
						|
    const char *walk;
 | 
						|
    int dist;
 | 
						|
    char buf[16]; /* trust strings will be short (from above, not user) */
 | 
						|
    walk = ms->head;
 | 
						|
    while(*walk){
 | 
						|
      dist = strcspn(walk, ", ");
 | 
						|
      strncpy(buf,walk,dist);
 | 
						|
      buf[dist] = '\0';
 | 
						|
      newnode = do_one_spec(buf,override); /* call self, assume success */
 | 
						|
      newnode->next = list;
 | 
						|
      list = newnode;
 | 
						|
      walk += dist;
 | 
						|
      if(*walk) walk++;
 | 
						|
    }
 | 
						|
    return list;
 | 
						|
  }
 | 
						|
  return NULL;   /* bad, spec not found */
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/************ must wrap user format in default *************/
 | 
						|
static void O_wrap(sf_node *sfn, int otype){
 | 
						|
  format_node *fnode;
 | 
						|
  format_node *endp;
 | 
						|
  const char *trailer;
 | 
						|
 | 
						|
  trailer = (otype=='b') ? "END_BSD" : "END_SYS5" ;
 | 
						|
 | 
						|
  fnode =  do_one_spec("pid",NULL);
 | 
						|
  if(!fnode)catastrophic_failure(__FILE__, __LINE__, _("seriously crashing: goodbye cruel world"));
 | 
						|
  endp = sfn->f_cooked; while(endp->next) endp = endp->next;  /* find end */
 | 
						|
  endp->next = fnode;
 | 
						|
 | 
						|
  fnode =  do_one_spec(trailer,NULL);
 | 
						|
  if(!fnode)catastrophic_failure(__FILE__, __LINE__, _("seriously crashing: goodbye cruel world"));
 | 
						|
  endp = fnode; while(endp->next) endp = endp->next;  /* find end */
 | 
						|
  endp->next = sfn->f_cooked;
 | 
						|
  sfn->f_cooked = fnode;
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************
 | 
						|
 * Used to parse option AIX field descriptors.
 | 
						|
 * Put each completed format_node onto the list starting at ->f_cooked
 | 
						|
 */
 | 
						|
static const char *aix_format_parse(sf_node *sfn){
 | 
						|
  char *buf;                   /* temp copy of arg to hack on */
 | 
						|
  char *walk;
 | 
						|
  int items;
 | 
						|
 | 
						|
  /*** sanity check and count items ***/
 | 
						|
  items = 0;
 | 
						|
  walk = sfn->sf;
 | 
						|
  /* state machine */ {
 | 
						|
  int c;
 | 
						|
  initial:
 | 
						|
    c = *walk++;
 | 
						|
    if(c=='%')    goto get_desc;
 | 
						|
    if(!c)        goto looks_ok;
 | 
						|
  /* get_text: */
 | 
						|
    items++;
 | 
						|
  get_more_text:
 | 
						|
    c = *walk++;
 | 
						|
    if(c=='%')    goto get_desc;
 | 
						|
    if(c)         goto get_more_text;
 | 
						|
    goto looks_ok;
 | 
						|
  get_desc:
 | 
						|
    items++;
 | 
						|
    c = *walk++;
 | 
						|
    if(c)         goto initial;
 | 
						|
    return _("improper AIX field descriptor");
 | 
						|
  looks_ok:
 | 
						|
    ;
 | 
						|
  }
 | 
						|
 | 
						|
  /*** sanity check passed ***/
 | 
						|
  buf = strdup(sfn->sf);
 | 
						|
  walk = sfn->sf;
 | 
						|
 | 
						|
  while(items--){
 | 
						|
    format_node *fnode;  /* newly allocated */
 | 
						|
    format_node *endp;   /* for list manipulation */
 | 
						|
 | 
						|
    if(*walk == '%'){
 | 
						|
      const aix_struct *aix;
 | 
						|
      walk++;
 | 
						|
      if(*walk == '%') goto double_percent;
 | 
						|
      aix = search_aix_array(*walk);
 | 
						|
      walk++;
 | 
						|
      if(!aix){
 | 
						|
        free(buf);
 | 
						|
        return _("unknown AIX field descriptor");
 | 
						|
      }
 | 
						|
      fnode =  do_one_spec(aix->spec, aix->head);
 | 
						|
      if(!fnode){
 | 
						|
        free(buf);
 | 
						|
        return _("AIX field descriptor processing bug");
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      size_t len;
 | 
						|
      len = strcspn(walk, "%");
 | 
						|
      memcpy(buf,walk,len);
 | 
						|
      if(0){
 | 
						|
double_percent:
 | 
						|
        len = 1;
 | 
						|
        buf[0] = '%';
 | 
						|
      }
 | 
						|
      buf[len] = '\0';
 | 
						|
      walk += len;
 | 
						|
      fnode = malloc(sizeof(format_node));
 | 
						|
      fnode->width = len < INT_MAX ? len : INT_MAX;
 | 
						|
      fnode->name = strdup(buf);
 | 
						|
      fnode->pr = NULL;     /* checked for */
 | 
						|
      fnode->need = 0;
 | 
						|
      fnode->vendor = AIX;
 | 
						|
      fnode->flags = CF_PRINT_EVERY_TIME;
 | 
						|
      fnode->next = NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    endp = fnode; while(endp->next) endp = endp->next;  /* find end */
 | 
						|
    endp->next = sfn->f_cooked;
 | 
						|
    sfn->f_cooked = fnode;
 | 
						|
  }
 | 
						|
  free(buf);
 | 
						|
  already_parsed_format = 1;
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/***************************************************************
 | 
						|
 * Used to parse option O lists. Option O is shared between
 | 
						|
 * sorting and formatting. Users may expect one or the other.
 | 
						|
 * Put each completed format_node onto the list starting at ->f_cooked
 | 
						|
 */
 | 
						|
static const char *format_parse(sf_node *sfn){
 | 
						|
  char *buf;                   /* temp copy of arg to hack on */
 | 
						|
  char *sep_loc;               /* separator location: " \t,\n" */
 | 
						|
  char *walk;
 | 
						|
  const char *err;       /* error code that could or did happen */
 | 
						|
  format_node *fnode;
 | 
						|
  int items;
 | 
						|
  int need_item;
 | 
						|
  static char errbuf[80]; /* for variable-text error message */
 | 
						|
 | 
						|
  /*** prepare to operate ***/
 | 
						|
  buf = strdup(sfn->sf);
 | 
						|
 | 
						|
  /*** sanity check and count items ***/
 | 
						|
  need_item = 1; /* true */
 | 
						|
  items = 0;
 | 
						|
  walk = buf;
 | 
						|
  do{
 | 
						|
    switch(*walk){
 | 
						|
    case ' ': case ',': case '\t': case '\n': case '\0':
 | 
						|
    /* Linux extension: allow \t and \n as delimiters */
 | 
						|
      if(need_item){
 | 
						|
        free(buf);
 | 
						|
        goto improper;
 | 
						|
      }
 | 
						|
      need_item=1;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      if(need_item) items++;
 | 
						|
      need_item=0;
 | 
						|
    }
 | 
						|
  } while (*++walk);
 | 
						|
 | 
						|
  if(!items){
 | 
						|
    free(buf);
 | 
						|
    goto empty;
 | 
						|
  }
 | 
						|
#ifdef STRICT_LIST
 | 
						|
  if(need_item){    /* can't have trailing deliminator */
 | 
						|
    free(buf);
 | 
						|
    goto improper;
 | 
						|
  }
 | 
						|
#else
 | 
						|
  if(need_item){    /* allow 1 trailing deliminator */
 | 
						|
    *--walk='\0';  /* remove the trailing deliminator */
 | 
						|
  }
 | 
						|
#endif
 | 
						|
  /*** actually parse the list ***/
 | 
						|
  walk = buf;
 | 
						|
  while(items--){
 | 
						|
    format_node *endp;
 | 
						|
    char *equal_loc;
 | 
						|
    char *colon_loc;
 | 
						|
    if(!walk) catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
 | 
						|
    sep_loc = strpbrk(walk," ,\t\n");
 | 
						|
    /* if items left, then sep_loc is not in header override */
 | 
						|
    if(items && sep_loc) *sep_loc = '\0';
 | 
						|
    equal_loc = strpbrk(walk,"=");
 | 
						|
    if(equal_loc){   /* if header override */
 | 
						|
      *equal_loc = '\0';
 | 
						|
      equal_loc++;
 | 
						|
    }
 | 
						|
    colon_loc = strpbrk(walk,":");
 | 
						|
    if(colon_loc){   /* if width override */
 | 
						|
      *colon_loc = '\0';
 | 
						|
      colon_loc++;
 | 
						|
      if(strspn(colon_loc,"0123456789") != strlen(colon_loc) || *colon_loc=='0' || !*colon_loc || atoi(colon_loc) <= 0){
 | 
						|
        free(buf);
 | 
						|
        goto badwidth;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    fnode =  do_one_spec(walk,equal_loc);
 | 
						|
    if(!fnode){
 | 
						|
      if(!*errbuf){  /* if didn't already create an error string */
 | 
						|
        snprintf(
 | 
						|
          errbuf,
 | 
						|
          sizeof(errbuf),
 | 
						|
          _("unknown user-defined format specifier \"%s\""),
 | 
						|
          walk
 | 
						|
        );
 | 
						|
      }
 | 
						|
      free(buf);
 | 
						|
      goto unknown;
 | 
						|
    }
 | 
						|
    if(colon_loc){
 | 
						|
      if(fnode->next){
 | 
						|
        free(buf);
 | 
						|
        goto notmacro;
 | 
						|
      }
 | 
						|
      // FIXME: enforce signal width to 8, 9, or 16 (grep: SIGNAL wide_signals)
 | 
						|
      fnode->width = atoi(colon_loc); // already verified to be a number
 | 
						|
      if(fnode->width <= 0) catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
 | 
						|
    }
 | 
						|
    endp = fnode; while(endp->next) endp = endp->next;  /* find end */
 | 
						|
    endp->next = sfn->f_cooked;
 | 
						|
    sfn->f_cooked = fnode;
 | 
						|
    walk = sep_loc ? sep_loc + 1 : NULL; /* point to next item, if any */
 | 
						|
  }
 | 
						|
  free(buf);
 | 
						|
  already_parsed_format = 1;
 | 
						|
  return NULL;
 | 
						|
 | 
						|
  /* errors may cause a retry looking for AIX format codes */
 | 
						|
  if(0) unknown:  err=errbuf;
 | 
						|
  if(0) empty:    err=_("empty format list");
 | 
						|
  if(0) improper: err=_("improper format list");
 | 
						|
  if(0) badwidth: err=_("column widths must be unsigned decimal numbers");
 | 
						|
  if(0) notmacro: err=_("can not set width for a macro (multi-column) format specifier");
 | 
						|
  if(strchr(sfn->sf,'%')) err = aix_format_parse(sfn);
 | 
						|
  return err;
 | 
						|
}
 | 
						|
 | 
						|
/****************  Parse single sort specifier *******************/
 | 
						|
static sort_node *do_one_sort_spec(const char *spec){
 | 
						|
  const format_struct *fs;
 | 
						|
  int reverse = 0;
 | 
						|
  if(*spec == '-'){
 | 
						|
    reverse = 1;
 | 
						|
    spec++;
 | 
						|
  } else if(*spec == '+'){
 | 
						|
    spec++;
 | 
						|
  }
 | 
						|
  fs = search_format_array(spec);
 | 
						|
  if(fs){
 | 
						|
    sort_node *thisnode;
 | 
						|
    thisnode = malloc(sizeof(sort_node));
 | 
						|
    thisnode->sr = fs->sr;
 | 
						|
    thisnode->need = fs->need;
 | 
						|
    thisnode->reverse = reverse;
 | 
						|
    thisnode->next = NULL;
 | 
						|
    return thisnode;
 | 
						|
  }
 | 
						|
  return NULL;   /* bad, spec not found */
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**************************************************************
 | 
						|
 * Used to parse long sorting options.
 | 
						|
 * Put each completed sort_node onto the list starting at ->s_cooked
 | 
						|
 */
 | 
						|
static const char *long_sort_parse(sf_node *sfn){
 | 
						|
  char *buf;                   /* temp copy of arg to hack on */
 | 
						|
  char *sep_loc;               /* separator location: " \t,\n" */
 | 
						|
  char *walk;
 | 
						|
  sort_node *snode;
 | 
						|
  int items;
 | 
						|
  int need_item;
 | 
						|
 | 
						|
  /*** prepare to operate ***/
 | 
						|
  buf = strdup(sfn->sf);
 | 
						|
 | 
						|
  /*** sanity check and count items ***/
 | 
						|
  need_item = 1; /* true */
 | 
						|
  items = 0;
 | 
						|
  walk = buf;
 | 
						|
  do{
 | 
						|
    switch(*walk){
 | 
						|
    case ' ': case ',': case '\t': case '\n': case '\0':
 | 
						|
      if(need_item){
 | 
						|
        free(buf);
 | 
						|
        return _("improper sort list");
 | 
						|
      }
 | 
						|
      need_item=1;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      if(need_item) items++;
 | 
						|
      need_item=0;
 | 
						|
    }
 | 
						|
  } while (*++walk);
 | 
						|
  if(!items){
 | 
						|
    free(buf);
 | 
						|
    return _("empty sort list");
 | 
						|
  }
 | 
						|
#ifdef STRICT_LIST
 | 
						|
  if(need_item){    /* can't have trailing deliminator */
 | 
						|
    free(buf);
 | 
						|
    return _("improper sort list");
 | 
						|
  }
 | 
						|
#else
 | 
						|
  if(need_item){    /* allow 1 trailing deliminator */
 | 
						|
    *--walk='\0';  /* remove the trailing deliminator */
 | 
						|
  }
 | 
						|
#endif
 | 
						|
  /*** actually parse the list ***/
 | 
						|
  walk = buf;
 | 
						|
  while(items--){
 | 
						|
    sort_node *endp;
 | 
						|
    sep_loc = strpbrk(walk," ,\t\n");
 | 
						|
    if(sep_loc) *sep_loc = '\0';
 | 
						|
    snode = do_one_sort_spec(walk);
 | 
						|
    if(!snode){
 | 
						|
      free(buf);
 | 
						|
      return _("unknown sort specifier");
 | 
						|
    }
 | 
						|
    endp = snode; while(endp->next) endp = endp->next;  /* find end */
 | 
						|
    endp->next = sfn->s_cooked;
 | 
						|
    sfn->s_cooked = snode;
 | 
						|
    walk = sep_loc + 1; /* point to next item, if any */
 | 
						|
  }
 | 
						|
  free(buf);
 | 
						|
  already_parsed_sort = 1;
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/************ pre-parse short sorting option *************/
 | 
						|
/* Errors _must_ be detected so that the "O" option can try to
 | 
						|
 * reparse as formatting codes.
 | 
						|
 */
 | 
						|
static const char *verify_short_sort(const char *arg){
 | 
						|
  const char all[] = "CGJKMNPRSTUcfgjkmnoprstuvy+-";
 | 
						|
  char checkoff[256];
 | 
						|
  int i;
 | 
						|
  const char *walk;
 | 
						|
  int tmp;
 | 
						|
  if(strspn(arg,all) != strlen(arg)) return _("bad sorting code");
 | 
						|
  for(i=256; i--;) checkoff[i] = 0;
 | 
						|
  walk = arg;
 | 
						|
  for(;;){
 | 
						|
    tmp = *walk;
 | 
						|
    if(tmp < 0 || (size_t)tmp >= sizeof(checkoff)) return _("bad sorting code");
 | 
						|
    switch(tmp){
 | 
						|
    case '\0':
 | 
						|
      return NULL;   /* looks good */
 | 
						|
    case '+':
 | 
						|
    case '-':
 | 
						|
      tmp = *(walk+1);
 | 
						|
      if(!tmp || tmp=='+' || tmp=='-') return _("bad sorting code");
 | 
						|
      break;
 | 
						|
    case 'P':
 | 
						|
      if(forest_type) return _("PPID sort and forest output conflict");
 | 
						|
      /* fall through */
 | 
						|
    default:
 | 
						|
      if(checkoff[tmp]) return _("bad sorting code");   /* repeated */
 | 
						|
      /* ought to check against already accepted sort options */
 | 
						|
      checkoff[tmp] = 1;
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    walk++;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/************ parse short sorting option *************/
 | 
						|
static const char *short_sort_parse(sf_node *sfn){
 | 
						|
  int direction = 0;
 | 
						|
  const char *walk;
 | 
						|
  int tmp;
 | 
						|
  sort_node *snode;
 | 
						|
  sort_node *endp;
 | 
						|
  const struct shortsort_struct *ss;
 | 
						|
  walk = sfn->sf;
 | 
						|
  for(;;){
 | 
						|
    tmp = *walk;
 | 
						|
    switch(tmp){
 | 
						|
    case '\0':
 | 
						|
      already_parsed_sort = 1;
 | 
						|
      return NULL;
 | 
						|
    case '+':
 | 
						|
      direction = 0;
 | 
						|
      break;
 | 
						|
    case '-':
 | 
						|
      direction = 1;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      ss = search_shortsort_array(tmp);
 | 
						|
      if(!ss) return _("unknown sort specifier");
 | 
						|
      snode = do_one_sort_spec(ss->spec);
 | 
						|
      if(!snode) return _("unknown sort specifier");
 | 
						|
      snode->reverse = direction;
 | 
						|
      endp = snode; while(endp->next) endp = endp->next;  /* find end */
 | 
						|
      endp->next = sfn->s_cooked;
 | 
						|
      sfn->s_cooked = snode;
 | 
						|
      direction = 0;
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    walk++;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/******************* high-level below here *********************/
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Used to parse option O lists. Option O is shared between
 | 
						|
 * sorting and formatting. Users may expect one or the other.
 | 
						|
 * Recursion is to preserve original order.
 | 
						|
 */
 | 
						|
static const char *parse_O_option(sf_node *sfn){
 | 
						|
  const char *err;     /* error code that could or did happen */
 | 
						|
 | 
						|
  if(sfn->next){
 | 
						|
    err = parse_O_option(sfn->next);
 | 
						|
    if(err) return err;
 | 
						|
  }
 | 
						|
 | 
						|
  switch(sfn->sf_code){
 | 
						|
    case SF_B_o: case SF_G_format: case SF_U_o: /*** format ***/
 | 
						|
      err = format_parse(sfn);
 | 
						|
      if(!err) already_parsed_format = 1;
 | 
						|
      break;
 | 
						|
    case SF_U_O:                                /*** format ***/
 | 
						|
      /* Can have -l -f f u... set already_parsed_format like DEC does */
 | 
						|
      if(already_parsed_format) return _("option -O can not follow other format options");
 | 
						|
      err = format_parse(sfn);
 | 
						|
      if(err) return err;
 | 
						|
      already_parsed_format = 1;
 | 
						|
      O_wrap(sfn,'u'); /* must wrap user format in default */
 | 
						|
      break;
 | 
						|
    case SF_B_O:                                /***  both  ***/
 | 
						|
      if(have_gnu_sort || already_parsed_sort) err = _("multiple sort options");
 | 
						|
      else err = verify_short_sort(sfn->sf);
 | 
						|
      if(!err){ /* success as sorting code */
 | 
						|
        short_sort_parse(sfn);
 | 
						|
        already_parsed_sort = 1;
 | 
						|
        return NULL;
 | 
						|
      }
 | 
						|
      if(already_parsed_format){
 | 
						|
        err = _("option O is neither first format nor sort order");
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      if(!format_parse(sfn)){ /* if success as format code */
 | 
						|
        already_parsed_format = 1;
 | 
						|
        O_wrap(sfn,'b'); /* must wrap user format in default */
 | 
						|
        return NULL;
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    case SF_G_sort: case SF_B_m:                 /***  sort  ***/
 | 
						|
      if(already_parsed_sort) err = _("multiple sort options");
 | 
						|
      else err = long_sort_parse(sfn);
 | 
						|
      already_parsed_sort = 1;
 | 
						|
      break;
 | 
						|
    default:                                    /***  junk  ***/
 | 
						|
      catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
 | 
						|
  }
 | 
						|
  return err; /* could be NULL */
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/************ Main parser calls this to save lists for later **********/
 | 
						|
/* store data for later and return 1 if arg looks non-standard */
 | 
						|
int defer_sf_option(const char *arg, int source){
 | 
						|
  sf_node *sfn;
 | 
						|
  char buf[16];
 | 
						|
  int dist;
 | 
						|
  const format_struct *fs;
 | 
						|
  int need_item = 1;
 | 
						|
 | 
						|
  sfn = malloc(sizeof(sf_node));
 | 
						|
  sfn->sf = strdup(arg);
 | 
						|
  sfn->sf_code = source;
 | 
						|
  sfn->s_cooked = NULL;
 | 
						|
  sfn->f_cooked = NULL;
 | 
						|
  sfn->next = sf_list;
 | 
						|
  sf_list = sfn;
 | 
						|
 | 
						|
  if(source == SF_G_sort) have_gnu_sort = 1;
 | 
						|
 | 
						|
  /* Now try to find an excuse to ignore broken Unix98 parsing. */
 | 
						|
  if(source != SF_U_o) return 1;    /* Wonderful! Already non-Unix98. */
 | 
						|
  do{
 | 
						|
    switch(*arg){
 | 
						|
    case ' ': case ',': case '\0':  /* no \t\n\r support in Unix98 */
 | 
						|
      if(need_item) return 1;       /* something wrong */
 | 
						|
      need_item=1;
 | 
						|
      break;
 | 
						|
    case '=':
 | 
						|
      if(need_item) return 1;       /* something wrong */
 | 
						|
      return 0;                     /* broken Unix98 parsing is required */
 | 
						|
    default:
 | 
						|
      if(!need_item) break;
 | 
						|
      need_item=0;
 | 
						|
      dist = strcspn(arg,", =");
 | 
						|
      if(dist>15) return 1;         /* something wrong, sort maybe? */
 | 
						|
      strncpy(buf,arg,dist);   /* no '\0' on end */
 | 
						|
      buf[dist] = '\0';        /* fix that problem */
 | 
						|
      fs = search_format_array(buf);
 | 
						|
      if(!fs) return 1;             /* invalid spec, macro or sort maybe? */
 | 
						|
      if(fs->vendor) return 1;      /* Wonderful! Legal non-Unix98 spec. */
 | 
						|
    }
 | 
						|
  } while (*++arg);
 | 
						|
 | 
						|
  return 0;                         /* boring, Unix98 is no change */
 | 
						|
}
 | 
						|
 | 
						|
/***** Since ps is not long-lived, the memory leak can be ignored. ******/
 | 
						|
void reset_sortformat(void){
 | 
						|
  sf_list = NULL;          /* deferred sorting and formatting */
 | 
						|
  format_list = NULL;      /* digested formatting options */
 | 
						|
  sort_list = NULL;        /* digested sorting options (redundant?) */
 | 
						|
  have_gnu_sort = 0;
 | 
						|
  already_parsed_sort = 0;
 | 
						|
  already_parsed_format = 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/***** Search format_list for findme, then insert putme after findme. ****/
 | 
						|
static int fmt_add_after(const char *findme, format_node *putme){
 | 
						|
  format_node *walk;
 | 
						|
  if(!strcmp(format_list->name, findme)){
 | 
						|
    putme->next = format_list->next;
 | 
						|
    format_list->next = putme;
 | 
						|
    return 1; /* success */
 | 
						|
  }
 | 
						|
  walk = format_list;
 | 
						|
  while(walk->next){
 | 
						|
    if(!strcmp(walk->next->name, findme)){
 | 
						|
      putme->next = walk->next->next;
 | 
						|
      walk->next->next = putme;
 | 
						|
      return 1; /* success */
 | 
						|
    }
 | 
						|
    walk = walk->next;
 | 
						|
  }
 | 
						|
  return 0; /* fail */
 | 
						|
}
 | 
						|
 | 
						|
/******* Search format_list for findme, then delete it. ********/
 | 
						|
static int fmt_delete(const char *findme){
 | 
						|
  format_node *walk;
 | 
						|
  format_node *old;
 | 
						|
  if(!strcmp(format_list->name, findme)){
 | 
						|
    old = format_list;
 | 
						|
    format_list = format_list->next;
 | 
						|
    free(old);
 | 
						|
    return 1; /* success */
 | 
						|
  }
 | 
						|
  walk = format_list;
 | 
						|
  while(walk->next){
 | 
						|
    if(!strcmp(walk->next->name, findme)){
 | 
						|
      old = walk->next;
 | 
						|
      walk->next = walk->next->next;
 | 
						|
      free(old);
 | 
						|
      return 1; /* success */
 | 
						|
    }
 | 
						|
    walk = walk->next;
 | 
						|
  }
 | 
						|
  return 0; /* fail */
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/************ Build a SysV format backwards. ***********/
 | 
						|
#define PUSH(foo) (fn=do_one_spec(foo, NULL), fn->next=format_list, format_list=fn)
 | 
						|
static const char *generate_sysv_list(void){
 | 
						|
  format_node *fn;
 | 
						|
  if((format_modifiers & FM_y) && !(format_flags & FF_Ul))
 | 
						|
    return _("modifier -y without format -l makes no sense");
 | 
						|
  if(prefer_bsd_defaults){
 | 
						|
    if(format_flags) PUSH("cmd");
 | 
						|
    else PUSH("args");
 | 
						|
    PUSH("bsdtime");
 | 
						|
    if(!(format_flags & FF_Ul)) PUSH("stat");
 | 
						|
  }else{
 | 
						|
    if(format_flags & FF_Uf) PUSH("cmd");
 | 
						|
    else PUSH("ucmd");
 | 
						|
    PUSH("time");
 | 
						|
  }
 | 
						|
  PUSH("tname");  /* Unix98 says "TTY" here, yet "tty" produces "TT". */
 | 
						|
  if(format_flags & FF_Uf) PUSH("stime");
 | 
						|
  /* avoid duplicate columns from -FP and -Fly */
 | 
						|
  if(format_modifiers & FM_F){
 | 
						|
    /* if -FP take the Sun-style column instead (sorry about "sgi_p") */
 | 
						|
    if(!(format_modifiers & FM_P)) PUSH("psr");  /* should be ENG */
 | 
						|
    /* if -Fly take the ADDR-replacement RSS instead */
 | 
						|
    if(!( (format_flags & FF_Ul) && (format_modifiers & FM_y) )) PUSH("rss");
 | 
						|
  }
 | 
						|
  if(format_flags & FF_Ul){
 | 
						|
    PUSH("wchan");
 | 
						|
  }
 | 
						|
  /* since FM_y adds RSS anyway, don't do this hack when that is true */
 | 
						|
  if( (format_flags & FF_Ul) && !(format_modifiers & FM_y) ){
 | 
						|
    if(personality & PER_IRIX_l){ /* add "rss" then ':' here */
 | 
						|
      PUSH("sgi_rss");
 | 
						|
      fn = malloc(sizeof(format_node));
 | 
						|
      fn->width = 1;
 | 
						|
      fn->name = strdup(":");
 | 
						|
      fn->pr = NULL;     /* checked for */
 | 
						|
      fn->need = 0;
 | 
						|
      fn->vendor = AIX;   /* yes, for SGI weirdness */
 | 
						|
      fn->flags = CF_PRINT_EVERY_TIME;
 | 
						|
      fn->next = format_list;
 | 
						|
      format_list=fn;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if((format_modifiers & FM_F) || (format_flags & FF_Ul)){
 | 
						|
    PUSH("sz");
 | 
						|
  }
 | 
						|
  if(format_flags & FF_Ul){
 | 
						|
    if(format_modifiers & FM_y) PUSH("rss");
 | 
						|
    else if(personality & (PER_ZAP_ADDR|PER_IRIX_l)) PUSH("sgi_p");
 | 
						|
    else PUSH("addr_1");
 | 
						|
  }
 | 
						|
  if(format_modifiers & FM_c){
 | 
						|
    PUSH("pri"); PUSH("class");
 | 
						|
  }else if(format_flags & FF_Ul){
 | 
						|
    PUSH("ni");
 | 
						|
    if(personality & PER_IRIX_l) PUSH("priority");
 | 
						|
    else /* is this good? */ PUSH("opri");
 | 
						|
  }
 | 
						|
 | 
						|
  // FIXME TODO XXX -- this is a serious problem
 | 
						|
  // These somehow got flipped around.
 | 
						|
  // The bug is in procps-3.1.1, procps-990211, prior too?
 | 
						|
  if((thread_flags & TF_U_L) && (format_flags & FF_Uf)) PUSH("nlwp");
 | 
						|
  if( (format_flags & (FF_Uf|FF_Ul)) && !(format_modifiers & FM_c) ) PUSH("c");
 | 
						|
 | 
						|
  if(format_modifiers & FM_P) PUSH("psr");
 | 
						|
  if(thread_flags & TF_U_L) PUSH("lwp");
 | 
						|
  if(format_modifiers & FM_j){
 | 
						|
    PUSH("sid");
 | 
						|
    PUSH("pgid");
 | 
						|
  }
 | 
						|
  if(format_flags & (FF_Uf|FF_Ul)) PUSH("ppid");
 | 
						|
  if(thread_flags & TF_U_T) PUSH("spid");
 | 
						|
  PUSH("pid");
 | 
						|
  if(format_flags & FF_Uf){
 | 
						|
    if(personality & PER_SANE_USER) PUSH("user");
 | 
						|
    else PUSH("uid_hack");
 | 
						|
  }else if(format_flags & FF_Ul){
 | 
						|
    PUSH("uid");
 | 
						|
  }
 | 
						|
  if(format_flags & FF_Ul){
 | 
						|
    PUSH("s");
 | 
						|
    if(!(format_modifiers & FM_y)) PUSH("f");
 | 
						|
  }
 | 
						|
  if(format_modifiers & FM_M){
 | 
						|
    PUSH("label");  /* Mandatory Access Control */
 | 
						|
  }
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**************************************************************************
 | 
						|
 * Used to parse option O lists. Option O is shared between
 | 
						|
 * sorting and formatting. Users may expect one or the other.
 | 
						|
 * The "broken" flag enables a really bad Unix98 misfeature.
 | 
						|
 */
 | 
						|
const char *process_sf_options(void){
 | 
						|
  sf_node *sf_walk;
 | 
						|
 | 
						|
  if(sf_list){
 | 
						|
    const char *err;
 | 
						|
    err = parse_O_option(sf_list);
 | 
						|
    if(err) return err;
 | 
						|
  }
 | 
						|
 | 
						|
  if(format_list) catastrophic_failure(__FILE__, __LINE__, _("bug: must reset the list first"));
 | 
						|
 | 
						|
  /* merge formatting info of sf_list into format_list here */
 | 
						|
  sf_walk = sf_list;
 | 
						|
  while(sf_walk){
 | 
						|
    format_node *fmt_walk;
 | 
						|
    fmt_walk = sf_walk->f_cooked;
 | 
						|
    sf_walk->f_cooked = NULL;
 | 
						|
    while(fmt_walk){   /* put any nodes onto format_list in opposite way */
 | 
						|
      format_node *travler;
 | 
						|
      travler = fmt_walk;
 | 
						|
      fmt_walk = fmt_walk->next;
 | 
						|
      travler->next = format_list;
 | 
						|
      format_list = travler;
 | 
						|
    }
 | 
						|
    sf_walk = sf_walk->next;
 | 
						|
  }
 | 
						|
 | 
						|
  /* merge sorting info of sf_list into sort_list here */
 | 
						|
  sf_walk = sf_list;
 | 
						|
  while(sf_walk){
 | 
						|
    sort_node *srt_walk;
 | 
						|
    srt_walk = sf_walk->s_cooked;
 | 
						|
    sf_walk->s_cooked = NULL;
 | 
						|
    while(srt_walk){   /* put any nodes onto sort_list in opposite way */
 | 
						|
      sort_node *travler;
 | 
						|
      travler = srt_walk;
 | 
						|
      srt_walk = srt_walk->next;
 | 
						|
      travler->next = sort_list;
 | 
						|
      sort_list = travler;
 | 
						|
    }
 | 
						|
    sf_walk = sf_walk->next;
 | 
						|
  }
 | 
						|
 | 
						|
  // Get somebody to explain how -L/-T is supposed to interact
 | 
						|
  // with sorting. Do the threads remain grouped, with sorting
 | 
						|
  // by process, or do the threads get sorted by themselves?
 | 
						|
  if(sort_list && (thread_flags&TF_no_sort)){
 | 
						|
    return _("tell <procps@freelists.org> what you expected");
 | 
						|
  }
 | 
						|
 | 
						|
  // If nothing else, try to use $PS_FORMAT before the default.
 | 
						|
  if(!format_flags && !format_modifiers && !format_list){
 | 
						|
    char *tmp;
 | 
						|
    tmp = getenv("PS_FORMAT");  /* user override kills default */
 | 
						|
    if(tmp && *tmp){
 | 
						|
      const char *err;
 | 
						|
      sf_node sfn;
 | 
						|
      if(thread_flags&TF_must_use) return _("tell <procps@freelists.org> what you want (-L/-T, -m/m/H, and $PS_FORMAT)");
 | 
						|
      sfn.sf = tmp;
 | 
						|
      sfn.f_cooked = NULL;
 | 
						|
      err = format_parse(&sfn);
 | 
						|
      if(!err){
 | 
						|
        format_node *fmt_walk;
 | 
						|
        fmt_walk = sfn.f_cooked;
 | 
						|
        while(fmt_walk){   /* put any nodes onto format_list in opposite way */
 | 
						|
          format_node *travler;
 | 
						|
          travler = fmt_walk;
 | 
						|
          fmt_walk = fmt_walk->next;
 | 
						|
          travler->next = format_list;
 | 
						|
          format_list = travler;
 | 
						|
        }
 | 
						|
        return NULL;
 | 
						|
      }
 | 
						|
      // FIXME: prove that this won't be hit on valid bogus-BSD options
 | 
						|
      fprintf(stderr, _("warning: $PS_FORMAT ignored. (%s)\n"), err);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if(format_list){
 | 
						|
    if(format_flags) return _("conflicting format options");
 | 
						|
    if(format_modifiers) return _("can not use output modifiers with user-defined output");
 | 
						|
    if(thread_flags&TF_must_use) return _("-L/-T with H/m/-m and -o/-O/o/O is nonsense");
 | 
						|
    return NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  do{
 | 
						|
    const char *spec;
 | 
						|
    switch(format_flags){
 | 
						|
 | 
						|
    default:             return _("conflicting format options");
 | 
						|
 | 
						|
    /* These can be NULL, which enables SysV list generation code. */
 | 
						|
    case 0:              spec=NULL;           break;
 | 
						|
    case FF_Uf | FF_Ul:  spec=sysv_fl_format; break;
 | 
						|
    case FF_Uf:          spec=sysv_f_format;  break;
 | 
						|
    case FF_Ul:          spec=sysv_l_format;  break;
 | 
						|
 | 
						|
    /* These are NOT REACHED for normal -j processing. */
 | 
						|
    case FF_Uj:          spec=sysv_j_format;  break; /* Debian & Digital */
 | 
						|
    case FF_Uj | FF_Ul:  spec="RD_lj";        break; /* Debian */
 | 
						|
    case FF_Uj | FF_Uf:  spec="RD_fj";        break; /* Debian */
 | 
						|
 | 
						|
    /* These are true BSD options. */
 | 
						|
    case FF_Bj:          spec=bsd_j_format;   break;
 | 
						|
    case FF_Bl:          spec=bsd_l_format;   break;
 | 
						|
    case FF_Bs:          spec=bsd_s_format;   break;
 | 
						|
    case FF_Bu:          spec=bsd_u_format;   break;
 | 
						|
    case FF_Bv:          spec=bsd_v_format;   break;
 | 
						|
 | 
						|
    /* These are old Linux options. Option m is overloaded. */
 | 
						|
    case FF_LX:          spec="OL_X";         break;
 | 
						|
    case FF_Lm:          spec="OL_m";         break;
 | 
						|
 | 
						|
    /* This is the sole FLASK security option. */
 | 
						|
    case FF_Fc:          spec="FLASK_context"; break;
 | 
						|
 | 
						|
    }  /* end switch(format_flags) */
 | 
						|
 | 
						|
    // not just for case 0, since sysv_l_format and such may be NULL
 | 
						|
    if(!spec) return generate_sysv_list();
 | 
						|
 | 
						|
    do{
 | 
						|
      format_node *fmt_walk;
 | 
						|
      fmt_walk = do_one_spec(spec, NULL); /* use override "" for no headers */
 | 
						|
      while(fmt_walk){   /* put any nodes onto format_list in opposite way */
 | 
						|
        format_node *travler;
 | 
						|
        travler = fmt_walk;
 | 
						|
        fmt_walk = fmt_walk->next;
 | 
						|
        travler->next = format_list;
 | 
						|
        format_list = travler;
 | 
						|
      }
 | 
						|
    }while(0);
 | 
						|
  }while(0);
 | 
						|
 | 
						|
  do{
 | 
						|
    format_node *fn;
 | 
						|
    if(format_modifiers & FM_j){
 | 
						|
      fn = do_one_spec("pgid", NULL);
 | 
						|
      if(!fmt_add_after("PPID", fn)) if(!fmt_add_after("PID", fn))
 | 
						|
        catastrophic_failure(__FILE__, __LINE__, _("internal error: no PID or PPID for -j option"));
 | 
						|
      fn = do_one_spec("sid", NULL);
 | 
						|
      if(!fmt_add_after("PGID", fn)) return _("lost my PGID");
 | 
						|
    }
 | 
						|
    if(format_modifiers & FM_y){
 | 
						|
      /* TODO: check for failure to do something, and complain if so */
 | 
						|
      fmt_delete("F");
 | 
						|
      fn = do_one_spec("rss", NULL);
 | 
						|
      if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR");
 | 
						|
    }
 | 
						|
    if(format_modifiers & FM_c){
 | 
						|
      fmt_delete("%CPU"); fmt_delete("CPU"); fmt_delete("CP"); fmt_delete("C");
 | 
						|
      fmt_delete("NI");
 | 
						|
      fn = do_one_spec("class", NULL);
 | 
						|
      if(!fmt_add_after("PRI", fn))
 | 
						|
        catastrophic_failure(__FILE__, __LINE__, _("internal error: no PRI for -c option"));
 | 
						|
      fmt_delete("PRI"); /* we want a different one */
 | 
						|
      fn = do_one_spec("pri", NULL);
 | 
						|
      if(!fmt_add_after("CLS", fn)) return _("lost my CLS");
 | 
						|
    }
 | 
						|
    if(thread_flags & TF_U_T){
 | 
						|
      fn = do_one_spec("spid", NULL);
 | 
						|
      if(!fmt_add_after("PID", fn) && (thread_flags&TF_must_use))
 | 
						|
        return _("-T with H/-m/m but no PID for SPID to follow");
 | 
						|
    }
 | 
						|
    if(thread_flags & TF_U_L){
 | 
						|
      fn = do_one_spec("lwp", NULL);
 | 
						|
      if(fmt_add_after("SID",  fn)) goto did_lwp;
 | 
						|
      if(fmt_add_after("SESS", fn)) goto did_lwp;
 | 
						|
      if(fmt_add_after("PGID", fn)) goto did_lwp;
 | 
						|
      if(fmt_add_after("PGRP", fn)) goto did_lwp;
 | 
						|
      if(fmt_add_after("PPID", fn)) goto did_lwp;
 | 
						|
      if(fmt_add_after("PID",  fn)) goto did_lwp;
 | 
						|
      if(thread_flags&TF_must_use)
 | 
						|
        return _("-L with H/-m/m but no PID/PGID/SID/SESS for NLWP to follow");
 | 
						|
did_lwp:
 | 
						|
      fn = do_one_spec("nlwp", NULL);
 | 
						|
      fmt_add_after("%CPU",  fn);
 | 
						|
    }
 | 
						|
    if(format_modifiers & FM_M){    // Mandatory Access Control, IRIX style
 | 
						|
      fn = do_one_spec("label", NULL);
 | 
						|
      fn->next=format_list;
 | 
						|
      format_list=fn;
 | 
						|
    }
 | 
						|
    /* Do personality-specific translations not covered by format_flags.
 | 
						|
     * Generally, these only get hit when personality overrides unix output.
 | 
						|
     * That (mostly?) means the Digital and Debian personalities.
 | 
						|
     */
 | 
						|
    if((personality & PER_ZAP_ADDR) && (format_flags & FF_Ul)){
 | 
						|
      fn = do_one_spec("sgi_p", NULL);
 | 
						|
      if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR");
 | 
						|
    }
 | 
						|
    if((personality & PER_SANE_USER) && (format_flags & FF_Uf)){
 | 
						|
      fn = do_one_spec("user", NULL);
 | 
						|
      if(fmt_add_after("UID", fn)) fmt_delete("UID");
 | 
						|
    }
 | 
						|
  }while(0);
 | 
						|
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 |