/* *******************************************************************
 *                                                                   *
 * Copyright (c) L-DGO/MIT/JGOFS                                     *
 *                                                                   *
 *                                                                   *
 * File             : list.c                                         *
 *                                                                   *
 * Revision History :                                                *
 *                                                                   *
 * Date                              Developer                       *
 * ----                              ---------                       *
 */
#define LIST_VERSION "list version 1.9  28 Apr 2017"
/*
 * 22 Apr 17                          wjs                            *
 *	Have list output line counts                                 *
 *	Don't allow outfile to begin w/ a hyphen (happens if user    *
 *	  puts switch after object spec)			     *
 *		[begin v 1.9]                                        *
 * 21 Apr 17                          wjs                            *
 *  Put jdb condition into exit-after-jdbopen-failure message        *
 *		[begin v 1.8d]					     *
 * 30 Nov 16                          wjs                            *
 *	Fix LIST_VERSION, which was dated 2916			     *
 *  9 Aug 16                          wjs                            *
 *  Process INCLUDE_ATTRIBUTES_SWITCH stuff before processing com-   *
 *    mand line switches since the text that gets printed for bad    *
 *    switches depends on inc_attr_switch, etc                       *
 *		[begin v 1.8c]					     *
 * 22 Jan 15                          wjs                            *
 *	Make list behave as documented regarding env var	     *
 *	  INCLUDE_ATTRIBUTES_SWITCH; ie, implement it		     *
 *		[begin v 1.8b]					     *
 * 20 Feb 09                          wjs                            *
 *	Bug fix: off-by-1 length calculation in outstr		     *
 *	Bug fix: code tried to trim "separators" off the pretty-     *
 *	  print dividing lines which were char constants.  Make	     *
 *	  them variables so code succeeds.			     *
 *		[begin v 1.8a]					     *
 *  3 Aug 08                          wjs                            *
 *	-nohelp, -errprefix					     *
 *	Fix some formatting of help printout			     *
 *	Move old comments to list_revision.doc			     *
 *		[Needs list.h v 1.5]				     *
 *		[begin v 1.8]					     *
 * 25 Oct 07                          wjs                            *
 *	Special diagnostic for -a & -aw switches if they are	     *
 *	  diagnosed as being illegal				     *
 *		[begin v 1.7b]					     *
 *  6 Oct 06                          wjs                            *
 *	Parametrize JDB_CONDITION_EOF				     *
 *		[Needs core.h v 1.5]				     *
 *  2 Sep 06                          wjs                            *
 *	Bug fix: left help in for stuff that had been pulled out     *
 * 19 Jul 06                          wjs                            *
 *	Bug fix: can't have run-time decision about whether to use   *
 *	  subscripted st_ino (VMS).  Must be compile time decision;  *
 *	  else using variable as both subscripted & un.  Suspect     *
 *	  this problem introduced in 1.5; certainly in 1.6b... and   *
 *	  it never showed until now.				     *
 *		[begin v 1.7a]					     *
 * 14 Jul 06                          wjs                            *
 *	-a, -aw							     *
 *      Changed buffer sizes to BUFSIZ per arguments in list.h	     *
 *	  Don't know what motivated the list.h stuff, but seems	     *
 *	  that we changed things there and never implemented them    *
 *	  here!							     *
 *	Use ADD/COPY_INTO_FIXED_LEN_BUFFER & add_to_buffer	     *
 *	  instead of strcat/cpy.  (Even better, used buildstring     *
 *	  to get rid of a bunch.  Could probably get rid of the	     *
 *	  rest (as well as most of the -r buffer logic if we used    *
 *	  lengthen_str!  Next time...)				     *
 *	Bug fix: we tested a char beyond end-of-string processing    *
 *	  -r[st] switch						     *
 *		[Needs utils.c (library) v 2.1]			     *
 *		[Needs list.h v 1.4]				     *
 *		[Needs core.h v 1.4c]				     *
 *		[begin v 1.7]					     *
 *  6 Aug 05  	WJS						     *
 *	Get level_splits from library				     *
 *  3 Jun 05  	WJS						     *
 *	Fix typo in declaration of add_id_to_err		     *
 *		[begin v 1.6d]					     *
 * 30 Aug 04                          wjs                            *
 *	jgfuncdefns.h						     *
 *  9 Jul 04                          wjs                            *
 *	Add return after error_ to avoid compiler diagnostic	     *
 * Apr 23, 2004  v 1.6c		    Warren Sass                      *
 *	Bug fix: extra separator was appearing at end-of-line	     *
 *	  under some circumstances				     *
 *	Bug fix: don't consider repsep at EOL to be a separator	     *
 *	  unless user has asked for -r switch of some flavor	     *
 * Apr 16, 2004  v 1.6c		    Warren Sass                      *
 *	Don't want "version" in any function name since such 	     *
 * 	  names appear when grep'ping for "version"		     *
 * Mar 25, 2004  v 1.6c		    Warren Sass                      *
 *	stat.h now #include'd in core.h, so remove from here	     *
 * Feb 27, 2004  v 1.6c		    Warren Sass                      *
 *	Comment mod						     *
 *	Rename local return_version to global list_return_version    *
 * Feb 11, 2004  v 1.6c		    Warren Sass                      *
 *	Fixes to remove compiler warnings			     *
 *		[Needs utils.c v 1.8]				     *
 *		[begin v 1.6c]					     *

 * Feb  4, 2004  v 1.6b		    Warren Sass                      *
 *	Get list.h version defn into binary here instead of in	     *
 *	  list.h						     *
 * Jan 30, 2004  v 1.6b		    Warren Sass                      *
 *	add_id_to_err (common routine to add username, time &	     *
 *	  program version to error messages)			     *
 *	errn to defgb_utils.					     *
 *		[Needs defgb_utils.c v 1.7]			     *
 *		[begin v 1.6b]					     *
 * [move earlier comments to list_revision.doc			     *
 * Sat Oct 17  1992 10.00           Glenn Flierl                     *
 *                                                                   *   
 ******************************************************************* */

#include "list.h"
#include "jdbfuncdefns.h"

int valsize = BUFSIZE;
int namebufsize = BUFSIZE;
char namebuf[NVAR][BUFSIZE], vals[NVAR][BUFSIZE];

int widths[NVAR];
int *firstvaratlevel;
char *outline;

int unit = -1;
int maxlevel,firstpass,num,level;
char one_char_buf;

/*	NB: the lm method reads "plain" (no switches) list output		*/
/*	That includes parsing the separator lines, etc, so that kind		*/
/*	of formatting is fixed (unless we admit nobody uses lm)			*/
char between_level_separator_line[] = "======================= ";
char varlist_delimiter_line[] = "........................ ";
char last_lev_varlist_delimiter_line[] = "------------------------ ";

/*  linecount counts the fprintf calls in writemln				*/
/*    Includes varline_linecount, commentline_linecount	and pretty-printing	*/
/*  varline_linecount counts the number of times outflush in writevar is called	*/
/*    presumably 0 if -b; 1 if -f, etc; nlevel if "levelized"			*/
/*  commentline_linecount counts the number of calls to jdbcomments		*/
/*  estdataline_linecount = linecount - varline_linecount  			*/
/*			- commentline_linecount - guess_at_pretty-printing	*/
struct linecount {
  char *name;
  int count;
  Logical do_it;
  char *destination;
  char *destination_access;
};
struct linecount linecount;
struct linecount varline_linecount;
struct linecount commentline_linecount;
struct linecount estdataline_linecount;

/*  outcount is screen-related							*/
int outcount = 0;
Logical moreflag = TRUE;
Logical fflag = FLAG_UNDEFINED;
Logical include_attributes,include_attributes_switch;
Logical widths_are_normal_attributes = FALSE;
Logical header_flag = TRUE;
Logical mflag = FALSE;
Logical outputcomments = TRUE;
Logical outlinelengthlimitflag = TRUE;
Logical rflag = FLAG_UNDEFINED;
Logical fillflag = TRUE;
Logical help_after_err = TRUE;
Logical broken_pipe_is_err = TRUE;
Logical err_echo = TRUE;
Logical die_on_outerr = TRUE;
Logical any_data_from_object = FALSE;
Logical forceheader = FALSE;
Logical print_help;
char osep[2] = {DEFAULT_OSEP , '\0'};
char repsep[2] = {DEFAULT_OSEP , '\0'};
char *err_prefix = "";
char *new_missing_value = DEFAULT_SUBSTITUTE_MISSING_VALUE;
char *object_name;

int olen;

FILE *ofile;
char *ofilename;
int ofile_status = 0;
char *ofile_access = "w";
struct stat ofileinfo;

FILE *errfile;
char *errfilename = NULL;
char *errfile_access = "w";
struct stat efileinfo;

Logical add_id_to_err();	/*  from utils.c			*/
void errn();			/*  from utils.c			*/
int *level_splits();		/*  from utils.c			*/
char *buildstring();		/*  from utils.c			*/
char *add_to_buffer();		/*  from utils.c			*/
Logical get_logical_value();	/*  from utils.c			*/

char *getenv();
int putenv();

void reset();
void reset_and_out();
void output_counts();

  /*  Should NOT be typed for "executable exit status".  This is what	*/
  /*  we'd LIKE the status to be.  Unfortunately, we need to cram it	*/
  /*  it into 8 bits most of the time...				*/
int desired_exit_status;
/************************************************************************/


char *list_return_vers()
/*  Routine exists mostly to force .h file version string into this	*/
/*  module, but we could call it if we want.  Note string must not be	*/
/*  global or we'll have conflicts if another routine similarly		*/
/*  includes the version string						*/
{
  static char version[] = 
	LIST_VERSION"/"FULL_LISTH_VERSION"/"FULL_JDBFUNCDEFNSH_VERSION;
  return version;
}


void error_(s,t)
char *s,*t;
{
  Logical print_to_ofile;
  char *ss = "", *tt = "",  *ss_eol = "",  *tt_eol= "";
    /*  exit_status should be typed for "executable exit status".	*/
    /*  Failing that, I'm too lazy to fool w/unsigned char and the	*/
    /*  various castings I'd have to do.  Besides, the exit function	*/
    /*  WANTS int and truncates, etc, itself				*/
  int exit_status;

    /*  Take care of null strings and strings w/o newlines.  This work	*/
    /*  should be redundant, but easier to do it than make sure...	*/
    /*  This code also makes it easy to modify into a message format	*/
    /*  of "s:t" if one prefers.  (Note, however, that if called from	*/
    /*  err, s is whole error message and t is time/program ID info)	*/
  if (s != NULL) {
    ss = s;
    if (ss[strlen(ss)-1] != '\n') ss_eol = "\n";
  }
  if (t != NULL) {
    tt = t;
    if (tt[strlen(tt)-1] != '\n') tt_eol = "\n";
  }


    /*  errfile will be NULL if attempt to open it failed...		*/
  if (errfile != NULL)
    fprintf (errfile,"%s%s%s%s%s",err_prefix,ss,ss_eol,tt,tt_eol);



   /*  Also write message to ofile if different from efile (unless	*/
   /*  user asked us not to).  Most of the code deals with abnormal	*/
   /*  circumstances.  If either but not both are abnormal, then they	*/
   /*  differ.  If both are abnormal, hey, try it - what do we have to	*/
   /*  lose?  We're beyond trying to diagnose the situation!		*/

  if (  err_echo && (ofile != NULL)  )  {

      /*  Any reason to print means we can stop testing.  We don't just	*/
      /*  OR the various things because we don't want to redo things we	*/
      /*  know in advance are NG (eg, fstat if file descriptor NULL)	*/
    print_to_ofile = (errfile == NULL);	/* Different since ofile !=NULL	*/

      /*  fstat returns 0 on success.  Failure means files are		*/
      /*  different; means we want to print				*/
    if (! print_to_ofile) 
      print_to_ofile = (fstat(fileno(errfile),&efileinfo) != 0);

    if (! print_to_ofile) {
      if (fstat(fileno(ofile),&ofileinfo) == 0) {
	  /*  The real test.  Files same if major/minor device & inode	*/
	  /*  are the same - print if not true				*/
        print_to_ofile =
#if VMS
	  ! (  (efileinfo.st_ino[0] == ofileinfo.st_ino[0]) &&
	       (efileinfo.st_ino[1] == ofileinfo.st_ino[1]) &&
	       (efileinfo.st_ino[2] == ofileinfo.st_ino[2]) &&
	       (efileinfo.st_dev == ofileinfo.st_dev)   );
#else
	  ! (  (efileinfo.st_ino == ofileinfo.st_ino) &&
	       (efileinfo.st_dev == ofileinfo.st_dev)   );
#endif
      } else {
	  /*  Who knows?  Give the write a shot...			*/
	print_to_ofile = TRUE;
      }
    }

    if (print_to_ofile) fprintf (ofile,"%s%s%s%s%s",
					err_prefix,ss,ss_eol,tt,tt_eol);
  }

    /*  Assume negative exit statuses come from jdb and "start" from	*/
    /*  -999.  Make -999 = 255; -998 = 254; etc.  After that mod, if	*/
    /*  the thing doesn't fit into 8 bits, we drop the most signifi-	*/
    /*  cant bits.  VMS is more liberal, but we still don't have the	*/
    /*  full 32 bits for a code & not worth trying to duplicate this	*/
    /*  work for the 21 (or whatever) bits we have.  How many VMS	*/
    /*  users of this are there, anyway?				*/

  exit_status = (desired_exit_status < 0) ? 
				255 - (999 + desired_exit_status) :
				desired_exit_status;
  exit(exit_status);
}

void err(s,t)
char *s,*t;
{
  char *new_s,*new_t;
  add_id_to_err(&new_s,&new_t,s,t,LIST_VERSION);
  error_(new_s,new_t);
  return;		/*  Not that it should ever get here...		*/
}

void printhelp(stat)
int stat;	/* Should be typed for "executable exit status"	*/
{
  if ( ! print_help) exit(stat);

  printf ("Usage: list [-n] [-s|-t] [-z] [-c] [-l] [-f|-b|-r|-rs|-rt]\n");
  printf ("            ");
  if (include_attributes_switch) printf ("[-a] ");
  printf ("[-aw] [-h] [?] [-v]\n");
  printf ("            [-nohelp] [-errprefix [string]]\n");
  printf ("            [-linecount count_destination_file[+]] [-X_linecount count_destination_file[+]]\n");
  printf ("            [-noerrecho] [-nopipeerr] [-finishinp]\n");
  printf ("            [-errout errfile[+]] [-m [new_missing]]\n");
  printf ("            [-forceheader]\n");
  printf ("                        object [outfile[+]]\n");
  printf("Options: \n");
  printf("     -n   nonstop\n");
  printf("     -s   space-separated     -t   tab-separated\n");
  printf("     -z   delete extra spaces\n");
  printf("     -c   no comments\n");
  printf("     -l   no limit on output line length\n");
  printf("     -f   flat file output	-b   brief flat file (no header info)\n");
  printf("     -r   reps-on-one-line output\n");
  printf("         -rs   -r w/reps space-separated     -rt   -r w/reps tab-separated\n");
  if (include_attributes_switch) 
	printf("     -a   print attribute list after variable name\n");
  printf("     -aw  print width attribute if other attributes are being printed\n");
  printf("     -h   prints this message\n");
  printf("     ?    prints this message\n");
  printf("     -v   prints version information\n");
  printf("     -m   missing value converted to new_missing (default: %s) \n",
					DEFAULT_SUBSTITUTE_MISSING_VALUE);
  printf("\n");
  printf
("-nohelp     do not print help after list syntax errors\n");
  printf
("-errprefix  prefix list error messages w/string (default: %s)\n",
							DEFAULT_ERR_PREFIX);
  printf
("-X_linecount output various counts to count_destination_file\n");
  printf
("            X can be varline, commentline, or estdataline\n");
  printf
("                commentline_linecount counts the number of comment lines\n");
  printf
("                varline_linecount counts the number of lines with varlist information\n");
  printf
("                linecount counts \"all\" lines\n");
  printf
("                estdataline_linecount is linecount - varline_linecount - commentline_linecount\n");
  printf
("                    - \"pretty-print\" lines.  It is an estimate of the number of data lines\n");
  printf
("            With regard to file overwrite/append, lines are printed in the order above\n");
  printf
("-noerrecho  do not echo err messages to outfile if errfile is different\n");
  printf
("-nopipeerr  do not produce err message if output ends w/ \"broken pipe\"\n");
  printf
("-finishinp  read input until end-of-data even if output errors\n");
  printf
("-errout   send err messages to errfile instead of /dev/stdout after \
command line parsing\n");
  printf
("-forceheader  produce variable list even if there is no data from object\n");
  printf
("              (unless listing is in -b format)\n");
	printf
("\noutfile may not begin with a -\n");
  printf("\n%s\n",LIST_VERSION);

  exit(stat);
}

void badswitch(problem,badswitch)
char *problem,*badswitch;
{
  char *s;

    /*  problem is a string but we only look at first char. Please? Tnx	*/
  switch (*problem) {
    case 'I':
      s = "Illegal";
      break;
    case 'C':
      s = "Conflicting/duplicate";
      break;
    default:
      s = "Problem";
      break;
  }

  printf ("%s%s switch %s\n",err_prefix,s,badswitch);
    /*  Special diagnostic for -a 					*/
  if (  (*(++badswitch) == 'a') && (*(++badswitch) == '\0') )
    printf ("%s(Check compilation options with which list was built)\n",
								err_prefix);
  printhelp(ERROR_EXIT);

  return;
}

void get_args(argc,argv)
     int argc;
     char *argv[];
{
  int i,len;
  char *s;
  int nprescan;
  struct linecount *struct_ptr;
#define NPRESCANSWITCHES 2

    /*  Prescan for switches related to the formatting of diagnostics,	*/
    /*  so that if we detect errs here, we do what user wants w/regard	*/
    /*  the output.  Haven't tried to do anything if these switches	*/
    /*  themselves are specified badly, multiple times, etc.  Present   */
    /*  thought is that	there's nothing to be done			*/
  nprescan = 0;
  for (i = 1; i < argc; i++) {
    if (strcmp("-nohelp",argv[i]) == 0) {
      help_after_err = FALSE;
      nprescan++;
    } else if (strcmp("-errprefix",argv[i]) == 0) {
      err_prefix = DEFAULT_ERR_PREFIX;
        /*  See -m code (from which this came) for discussion of	*/
        /*  problems w/							*/
        /*		-switch [value]					*/
        /*  syntax in general, as well as the next 2 if statements	*/
      if (argv[i+1] == NULL) break;
      if (argv[i+2] == NULL) break;
      if (*argv[i+1] != '-') err_prefix = argv[++i]; 
      nprescan++;
    }
    if (nprescan == NPRESCANSWITCHES) break;
  }
  print_help = help_after_err;

    /*  Handle args.  Switches must come before obj spec & opt outfile	*/
    /*  Note that we cannot recognize the following error at parse time	*/
    /*		list -errout object outfile				*/
  for (i = 1; i < argc; i++) {
    if (argv[i][0] != '-') break;
    struct_ptr = NULL;
    if (strcmp(argv[i],"-linecount") == 0) {
      struct_ptr = &linecount;
    } else if (strcmp(argv[i],"-varline_linecount") == 0) {
      struct_ptr = &varline_linecount;
    } else if (strcmp(argv[i],"-commentline_linecount") == 0) {
      struct_ptr = &commentline_linecount;
    } else if (strcmp(argv[i],"-estdataline_linecount") == 0) {
      struct_ptr = &estdataline_linecount;
    }
    if (struct_ptr != NULL) {
      if (++i >= argc) {
	printf("%sMissing linecount destination after %s switch\n",err_prefix,argv[i-1]);
	printhelp(ERROR_EXIT);
      }
      len = strlen(argv[i]);
      if (len == 0) {
	printf ("%scount_destination for %s empty",err_prefix,struct_ptr->name);
	printhelp (ERROR_EXIT);
      }
      struct_ptr->destination = argv[i];

      if (struct_ptr->destination[len-1] == '+') {
	if (len == 1) {
	  printf ("%scount_destination for %s cannot be just a \'+\'\n",err_prefix,struct_ptr->name);
	  printhelp(ERROR_EXIT);
	}
	struct_ptr->destination_access = "a";
	struct_ptr->destination[len-1] = '\0';
      } else {
	struct_ptr->destination_access = "w";
      }

      struct_ptr->do_it = TRUE;
      continue;
    }
    switch (argv[i][1]) {
      case 'a':
	if (argv[i][2] == '\0') {
	  if (include_attributes_switch) include_attributes = TRUE;
	  else badswitch("Illegal",argv[i]);
	} else {
	  if (argv[i][3] != '\0') badswitch("Illegal",argv[i]);
	  if (argv[i][2] == 'w') widths_are_normal_attributes = TRUE;
	  else badswitch("Illegal",argv[i]);
	}
	break;
      case 'b':
	if (  ! ((fflag == FLAG_UNDEFINED) && (rflag == FLAG_UNDEFINED))  )
						badswitch("Conflict",argv[i]);
	fflag = TRUE;
	header_flag = FALSE;
	outputcomments = FALSE;
	break;
      case 'c':
	outputcomments = FALSE;
	break;
      case 'e':
	if (strcmp("-errout",argv[i]) == 0) {
	  if (++i >= argc) {
	    printf("%sMissing errfile after -errout switch\n",err_prefix);
	    printhelp(ERROR_EXIT);
	  }
	  errfilename = argv[i];
	  len = strlen(errfilename);
	  if (errfilename[len-1] == '+') {
	    if (len == 1) {
	      printf ("%serrfile cannot be just a \'+\'\n",err_prefix);
	      printhelp(ERROR_EXIT);
	    }
	    errfile_access = "a";
	    errfilename[len-1] = '\0';
	  }
	} else if (strcmp("-errprefix",argv[i]) == 0) {
	    /*  Processing of the first occurrence of this switch	*/
	    /*  took place up top ... and will control formatting of	*/
	    /*  the following error message.  Hope 1st one was right	*/
	  if (*err_prefix != '\0') {
	    if (argv[i+1] == NULL) {
	      if (err_prefix != DEFAULT_ERR_PREFIX) 
						badswitch("Duplicate",argv[i]);
	      break;
	    }
	    if (argv[i+2] == NULL) {
	      if (err_prefix != DEFAULT_ERR_PREFIX) 
						badswitch("Duplicate",argv[i]);
	      break;
	    }
	    if (*argv[i+1] != '-') {
	      if (strcmp(err_prefix,argv[i+1]) != 0 ) 
						badswitch("Duplicate",argv[i]);
	      i++;
	      break;
	    }
	  }
	} else {
	  badswitch("Illegal",argv[i]);
	}
	break;
      case 'f':
	if (argv[i][2] == '\0') {
	  if (  ! ((fflag == FLAG_UNDEFINED) && (rflag == FLAG_UNDEFINED))  )
						 badswitch("Conflict",argv[i]);
	  fflag = TRUE;
	} else if (strcmp("-forceheader",argv[i]) == 0) {
	  forceheader = TRUE;
	} else if (strcmp("-finishinp",argv[i]) == 0) {
	  die_on_outerr = FALSE;
        } else {
	  badswitch("Illegal",argv[i]);
	}
	break;
      case 'h':
	print_help = TRUE;
	printhelp(OK_EXIT);
      case 'l':
	outlinelengthlimitflag = FALSE;
	break;
      case 'm':
	  /*  Cannot distinguish between "-m repl_string object" and	*/
	  /*  "-m object output_spec"					*/
	mflag = TRUE;
	  /*  If next arg not there, we're missing an object specifier,	*/
	  /*  but let some other code pick that up			*/
	if (argv[i+1] == NULL) break;
	  /*  If next arg is last arg, it's probably the object speci-	*/
	  /*  fier.  If not, we'll have error anyway, so let's pretend	*/
	  /*  it is.  Since next arg isn't value for -m, then, leave	*/
	if (argv[i+2] == NULL) break;
	if (*argv[i+1] != '-')
	  new_missing_value = argv[++i]; 
	else {
	    /*  Try to accept a string that's a negative number		*/
	    /*  Note this fails if there are blanks after the number.	*/
	    /*  To accept blanks there would mean a forward search to	*/
	    /*  see if there was anything after the blank, etc.		*/
	  strtod(argv[i+1],&s);
	  if (*s == '\0') new_missing_value = argv[++i]; 
	}
 	break;
      case 'n':
        if (argv[i][2] == '\0') moreflag = FALSE;
	else if (strcmp("-nopipeerr",argv[i]) == 0) broken_pipe_is_err = FALSE;
	else if (strcmp("-noerrecho",argv[i]) == 0) err_echo = FALSE;
	  /*  Handled nohelp already.  Next line accepts it for syntax	*/
	  /*  purposes							*/
	else if (strcmp("-nohelp",argv[i]) == 0) ;
	else badswitch("Illegal",argv[i]);
	break;
      case 'r':
	  /*  Handle -r[st] switch before -r so that "Illegal"		*/
	  /*  message appears instead of "Conflict" in case of -rZ	*/
	if (argv[i][2] != '\0') {
	  if (argv[i][3] != '\0') badswitch("Illegal",argv[i]);
	  switch (argv[i][2]) {
	    case 's':
	      repsep[0] = ' ';
	      break;
	    case 't':
	      repsep[0] = '\t';
	      break;
	    default:
	      badswitch("Illegal",argv[i]);
	  }
	}
	if (  ! ((fflag == FLAG_UNDEFINED) && (rflag == FLAG_UNDEFINED))  )
						badswitch("Conflict",argv[i]);
	rflag = TRUE;
	header_flag = FALSE;
	break;
      case 's':
	if (osep[0] != DEFAULT_OSEP) badswitch("Conflict",argv[i]);
	osep[0] = ' ';
	break;
      case 't':
	if (osep[0] != DEFAULT_OSEP) badswitch("Conflict",argv[i]);
	osep[0] = '\t';
	break;
      case 'v':
	printf("%s\n",LIST_VERSION);
        exit(OK_EXIT);
      case 'z':
	fillflag = FALSE;
	break;
      default:
	badswitch("Illegal",argv[i]);
	break;
    }
  }

  if (fflag == FLAG_UNDEFINED) fflag = DEFAULT_FFLAG;
  if (rflag == FLAG_UNDEFINED) rflag = DEFAULT_RFLAG;
  olen = (fflag || rflag || ! outlinelengthlimitflag) ? 
				DEFAULT_MAXOLEN : SCREEN_COLS - 1;

  if (i >= argc) {
    printf ("%sNeed an object specifier argument\n",err_prefix);
    printhelp(ERROR_EXIT);
  }
  object_name = argv[i];

  if (++i < argc) {
    if (*(argv[i]) == '-') {
      printf ("%sSwitches must precede object specifier argument\n",err_prefix);
      printhelp(ERROR_EXIT);
    }
    ofilename = argv[i];
    len = strlen(ofilename);
    if (ofilename[len-1] == '+') {
      if (len == 1) {
        printf ("%soutfile cannot be just a \'+\'\n",err_prefix);
        printhelp(ERROR_EXIT);
      }
      ofile_access = "a";
      ofilename[len-1] = '\0';
    }
  }

  if (++i < argc) {
    printf ("%sToo many arguments\n",err_prefix);
    printhelp(ERROR_EXIT);
  }

  return;
}

void writemln(outline)
char *outline;
{
  int len,lensep;

  len = strlen(outline);

    /*  Remove separators at end of line.  Presumably if both repsep	*/
    /*  and osep are present, repsep came first				*/
    /*  seps should be 1 char long, but let's protect ourselves...	*/
  lensep = strlen(osep);
  if (  strcmp(outline+len-lensep,osep) == 0  ) 
					outline[len-lensep] = '\0';
    /*  Only remove repsep if it's a separator!				*/
  if (rflag) {
    lensep = strlen(repsep);
    if (  strcmp(outline+len-lensep,repsep) == 0  ) 
					outline[len-lensep] = '\0';
  }

  if (fprintf(ofile,"%s\n",outline) < 0) {
    ofile_status = errno;
    return;
  }
  linecount.count++;

  if (moreflag) {

      /*  Do "more" emulation						*/
    if ( ++outcount < SCREEN_ROWS - 1) return;

    printf("--More--");

    switch (getchar()) {
      case SPACE:
        printf("\n");
        outcount = 0;
        break;
      case CR:
        if (PC) printf("\n");
	  /*  Arrange things so that we interrupt after next line of	*/
	  /*  output.  Use -2 instead of -1 to account for the --More--	*/
	  /*  we put out						*/
        outcount = SCREEN_ROWS - 2;
        break;
      default:
        printf("\n");
        reset();
        exit(ERROR_EXIT);
    }
  }

  return;
}

void outstr(s,sep)
char *s,*sep;
{
  int newlen;

    /*  sep should be 1 char long, but let's protect ourselves...	*/
    /*  Too bad that while subtly thinking the above, forgot about	*/
    /*  trailing \0! ... hence the +1					*/
  newlen = strlen(outline)+strlen(s)+strlen(sep)+1;
  if ( newlen > olen ) {
    if (outlinelengthlimitflag) {
      writemln (outline);
      outline[0] = '\0';
    } else {
	/*  Get more memory.						*/
	/*  No particular reason to use DEFAULT_MAXOLEN as increment	*/
	/*  No particular reason to increment in a loop rather than	*/
	/*    do the division to find out how many increments we need	*/
      while ((newlen > olen) && (olen <= OLEN_SANITY_LIMIT))
	olen += DEFAULT_MAXOLEN;
      if (olen > OLEN_SANITY_LIMIT) {
	desired_exit_status = ERROR_EXIT;
	errn ("Output line size too big.  Limit = ",OLEN_SANITY_LIMIT);
      }
      outline = (char *)realloc(outline,olen);
      if (outline == NULL) {
	olen = newlen;
	outline = (char *)realloc(outline,olen);
      }
      if (outline == NULL) {
	desired_exit_status = ERROR_EXIT;
	errn (
	  "Cannot increase size of output line buffer. Desired size = ",olen);
      }
    }
  }
  add_to_buffer(outline,olen,s,"adding datum to output buffer");
  add_to_buffer(outline,olen,sep,"adding separator to output buffer");
  return;
}  

void outflush()
{
  if (outline[0] != '\0') {
    writemln(outline);
    outline[0] = '\0';
  }
  return;
}

void writevar(level)
int level;
{
  int i,j;
  char *ptr;

/*  +1 for newline, +2 for initial '# ', +1 for trailing blank		*/
  static char msg[COMMENTLINE+4] = {'#',' '};

  if (fflag || rflag) {
    if (firstpass) {
      if (outputcomments && any_data_from_object)
	while (jdbcomments_(&unit,msg+2)) {
	  ADD_INTO_FIXED_LEN_BUFFER
			(msg," ","adding blank to comment buffer (-f/-r)");
	  writemln(msg);
	  commentline_linecount.count++;
        }
      if (header_flag) {
	for (i=0 ;i<num; i++) outstr(namebuf[i],osep);
	outflush();
	varline_linecount.count++;
      }
      firstpass = 0;
    }
    if (any_data_from_object) {
      if (  rflag && (level == maxlevel)  ) {
	for (i = firstvaratlevel[maxlevel]; i < num-1; i++)
	  outstr(vals[i],osep);
	outstr(vals[num-1],repsep);
      } else {
	outflush();
	for (i=0; i<num; i++) outstr(vals[i],osep);
      }
    } else {
      outflush();
    }
  } else {
    if (  (level < maxlevel) || firstpass  ) {
	/* do comments */
      if (outputcomments)
	while (jdbcomments_(&unit,msg+2)) {
	  ADD_INTO_FIXED_LEN_BUFFER(msg," ","adding blank to comment buffer");
	  writemln(msg);
	  commentline_linecount.count++;
        }
	/* do headers */
      for (i=level; i<=maxlevel; i++) {
	writemln(between_level_separator_line);
	estdataline_linecount.count--;
	for (j=firstvaratlevel[i]; j<firstvaratlevel[i+1]; j++)
	  outstr(namebuf[j],osep);
	outflush();
	varline_linecount.count++;
        ptr = (i < maxlevel) ? 
		varlist_delimiter_line : last_lev_varlist_delimiter_line;
        writemln(ptr);
	estdataline_linecount.count--;
	if (any_data_from_object) {
	  for (j=firstvaratlevel[i]; j<firstvaratlevel[i+1]; j++)
	    outstr(vals[j],osep);
	  outflush();
	}
      }
    } else {
      if (any_data_from_object) {
	for (j=firstvaratlevel[maxlevel]; j<num; j++)
	  outstr(vals[j],osep);
	outflush();
      }
    }
    firstpass = 0;
  }

  return;
}

int main(argc,argv)
     int argc;
     char *argv[];
{
  char tmp[ATTRSIZE];
  int i,len_buf;
  char *s;
  char *include_attributes_switch_env_var = "INCLUDE_ATTRIBUTES_SWITCH";
  void in_getwidth();
  void rightjust();
  Logical keep_writing = TRUE;
  Logical width_attr;
  linecount.name = "linecount";
  varline_linecount.name = "varline_linecount";
  commentline_linecount.name = "commentline_linecount";
  estdataline_linecount.name = "estdataline_linecount";

/*   Need to "do" the inc_attr_switch stuff before processing args since it */
/*   controls the help that gets printed in case of a bad arg (or if help is requested) */
  s = getenv(include_attributes_switch_env_var);
  include_attributes_switch = (s == NULL) ? INCLUDE_ATTRIBUTES_SWITCH : get_logical_value(s);
  if (include_attributes == NOT_VALID) {
    printf ("Illegal value for env var %s; namely, -->%s<--.  Must be JGOFS-system logical value (1,0,T,F,etc)",
		include_attributes_switch_env_var,s);
    printf("\n%s\n",LIST_VERSION);
    exit(ERROR_EXIT);
  }
    /*  Default attrib inclusion behavior is that switch is not		*/
    /*  acceptable; ="old way"; = include attribs			*/
    /*  Note that the _switch variables only address the issue of	*/
    /*  whether the -a switch should be accepted, and do NOT address	*/
    /*  whether the switch was specified.				*/
  include_attributes = ! include_attributes_switch;
 
 /*  No args or single arg '?' get help and exit			*/
  if (argc <= 1) {
    print_help = TRUE;
    printhelp(ERROR_EXIT);
  }
  if ((argc == 2) && (argv[1][0] == '?') && (argv[1][1] == '\0')) {
    print_help = TRUE;
    printhelp(OK_EXIT);
  }
 
  get_args(argc,argv);

/*  Done w/args.  Use standard error reporting from here on, hence	*/
/*  foolery after opening the first file...				*/

    /*  Historically, error info was written to stdout, so this		*/
    /*  default cannot be changed w/o risk of breaking existing code	*/
  if (errfilename == NULL) errfilename = STDOUT_NAME;
    /*  Don't open standard devices.  2 reasons: first, not sure what	*/
    /*  this does if shell has already opened; and second, some systems	*/
    /*  don't HAVE a /dev/stdout, etc					*/
  if (strcmp(errfilename,STDOUT_NAME) == 0) errfile = stdout;
  else if (strcmp(errfilename,STDERR_NAME) == 0) errfile = stderr;
  else errfile = fopen(errfilename,errfile_access);

    /*  Cannot report error completely yet, so save status (will only	*/
    /*  use it if errfile is NULL, so can save bogus errnos)		*/
  i = errno;

  if (ofilename == NULL) ofilename = STDOUT_NAME;
    /*  See above comments about not opening standard files		*/
  if (strcmp(ofilename,STDOUT_NAME) == 0) ofile = stdout;
  else if (strcmp(ofilename,STDERR_NAME) == 0) ofile = stderr;
  else ofile = fopen(ofilename,ofile_access);

  if (ofile == NULL) {
      /*  Do following even if we have no error file on which to put	*/
      /*  the message.  Doing so will allow uniform communication of	*/
      /*  error code into the exit status (be nice - don't ask so what)	*/
    s = buildstring("Could not open ",ofilename," for write: ",
					"ofilename open error msg buffer");
      /*  Don't try to write error message to ofile			*/
    err_echo = FALSE;
    desired_exit_status = errno;
    err (s,strerror(errno));
  }
    /*  Original list turned off moreflag if user spec'd outfile.	*/
    /*  Since we explicitly allow outfile=/dev/stdout=terminal device	*/
    /*  we can't do that.  Note that a 0 return might mean "could not	*/
    /*  perform the test" (due to an I/O problem presumably).  Don't	*/
    /*  see a problem w/turning off flag in that situation		*/
  if (isatty(fileno(ofile)) == 0) moreflag = FALSE;

  if (errfile == NULL) {
    s = buildstring("Could not open ",errfilename," for write: ",
					"errfilename open error msg buffer");
    desired_exit_status = i;
    err (s,strerror(i));
  }


    /*  Copy object spec into temp buffer.  Don't know why we need a	*/
    /*  copy - maybe jdbopen alters object spec?  Main use of outline	*/
    /*  is as line output buffer - size for its first use while we're	*/
    /*  at it...							*/
  len_buf = strlen(object_name) + 1;
  if (len_buf < DEFAULT_MAXOLEN) len_buf = DEFAULT_MAXOLEN;
  outline = (char *)malloc(len_buf);
  if (outline == NULL) {
    desired_exit_status = ERROR_EXIT;
    errn ("Could not allocate buffer outline.  Desired size = ",len_buf);
  }
  strcpy(outline,object_name);

  linecount.count = varline_linecount.count = 0;
  commentline_linecount.count = estdataline_linecount.count = 0;

  unit = 1;
  num = -NVAR;
  maxlevel = jdbopen_(&unit,outline,namebuf,&namebufsize,&num);
  if (maxlevel < 0) {
    desired_exit_status = maxlevel;
    if (desired_exit_status == JDB_CONDITION_DCTSEARCHFAILURE) {
      strcpy (tmp,"object not found");
    } else if (desired_exit_status == JDB_CONDITION_ERROR) {
      strcpy (tmp,"catch-all error");
    } else {
      sprintf (tmp,"jdb_condition %d-look in core.h for defn",desired_exit_status);
    }
    s = buildstring("jdbopen failure (",tmp,")  Object: ","jdb failure err msg");
    err(s,object_name);
  }


    /*  do levels							*/
    /*  maxlevel+1 because we want # levels, not max			*/
  firstvaratlevel = level_splits(unit,maxlevel,num);
  if (firstvaratlevel == NULL) {
    desired_exit_status = ERROR_EXIT;
    errn ("Could not get memory for levels buffer.  Desired # integers = ",
	  maxlevel+2);
  }

   /*  do attributes & cleanup names					*/
  for (i=0; i<num; i++) {
    strcpy(outline,"[");
    while (jdbattributes_(&unit,&i,tmp)) {
      width_attr = (strncmp(tmp,"width=",6) == 0);
      if (width_attr) in_getwidth(&i,tmp);
      if (	include_attributes && 
		(widths_are_normal_attributes || !width_attr)
	 ) {
        add_to_buffer(outline,len_buf,tmp,
					"adding datum to attribute list");
        add_to_buffer(outline,len_buf,";",
					"adding separator to attribute list");
      }
    }
    if (outline[1] == '\0') outline[0] = '\0';
    else outline[strlen(outline)-1] = ']';
    ADD_INTO_FIXED_LEN_BUFFER
		(namebuf[i],outline,"adding attribute list to variable name");
    if (fillflag) rightjust (namebuf[i],widths[i],namebufsize-1);
  }

  outline[0] = '\0';
  outcount = 0;

    /*  Set up terminals for "more" emulation				*/
  if (moreflag) {
    if (PC) setbuf(stdin,NULL);
    if ( ! PC)
      if (signal(SIGINT,reset_and_out) == SIG_ERR) {
	desired_exit_status = ERROR_EXIT;
	err("Cannot handle SIGINT","");
      }
    if (*setup_term_command != '\0') system (setup_term_command);
  }

  if ( ! VMS) 
      /*  Finally!  Result of ignoring the signal is that write state-	*/
      /*  ments to the non-existent pipe end up setting errno to EPIPE	*/
      /*  synchronously, which is what I would have expected in the	*/
      /*  first place.  Maybe they figured that if users didn't check	*/
      /*  status, they'd end up looping, writing new lines to the non-	*/
      /*  existent pipe.  So - if they're going to signal, why not do	*/
      /*  it when the pipe goes away and not wait until the first	*/
      /*  write?!							*/
    if (signal(SIGPIPE,SIG_IGN) == SIG_ERR) {
      desired_exit_status = ERROR_EXIT;
      err("Cannot ignore SIGPIPE","");
    }
  
  firstpass = 1;
  
  while (  (level = jdbreada_(&unit,vals,&valsize)) >= 0  ) {
    any_data_from_object = TRUE;
    for (i=firstvaratlevel[level]; i<num; i++) {
	/*  if the user wants to translate 'nd' vals to numeric */
      if (  mflag && (strcmp(vals[i],MISSING_VALUE_STRING) == 0)  )
	strcpy(vals[i],new_missing_value);
      if (fillflag) rightjust(vals[i],widths[i],valsize-1);
    }
      /*  Next if block is to allow jdb to continue reading even if	*/
      /*  we can't write any more (and user wants this feature!)	*/
      /*  After writevar errors, we normally take the "die_on_outerr"	*/
      /*  break and exit the while loop					*/
    if (keep_writing) {
      writevar(level);
      if (ofile_status != 0) {
        if (die_on_outerr) break;
        keep_writing = FALSE;
      }
    }
  }

    /*  Handle any write errors that have occurred.  Do this before	*/
    /*  testing for read errors in case reporting write error		*/
    /*  has been deferred						*/
  if (ofile_status != 0) {
    reset();
    if (  (ofile_status == EPIPE) && ! broken_pipe_is_err  )
      exit (EPIPE);
    else {
      s = buildstring("Error writing to ",ofilename," : ",
					"ofilename write error msg buffer");
      desired_exit_status = ofile_status;
      err(s,strerror(ofile_status));
    }
  }
      

  if (level != JDB_CONDITION_EOF) {
    reset();
    desired_exit_status = level;
    err("jdbread failure.  Object: ",object_name);
  }

  if (forceheader && ! any_data_from_object)
    for (i=0; i<=maxlevel; i++)
      writevar(i);

  outflush();
  if (  ! (fflag || rflag)   ) fprintf(ofile,"****** End of object ***\n");

  reset();
  output_counts();
  exit(OK_EXIT);
}

void reset()
{
  if (unit >= 0) jdbclose_(&unit);
  if (  moreflag && (*reset_term_command != '\0')  )
						system (reset_term_command);
  return;
}

void reset_and_out(signal)
int signal;
{
  reset();
  exit(ERROR_EXIT);
}

void rightjust(s,fillwidth,maxwidth)
char *s;
int fillwidth,maxwidth;
/*  Right justify the string in s to a width of fillwidth, pre-filling  */
/*    with blanks.  Do nothing if s already wider than fillwidth or	*/
/*    if fillwidth > maxwidth (? might use maxwidth instead...)		*/
/*  Not clear what should be done if user says "fill" but no width	*/
/*  spec'd.   Duplicate 1.2b behavior of filling to width 7 if		*/
/*    1) fillsize > 0  but 2) widths[vn]= 0 (fillsize & widths[vn] were	*/
/*  variables in 1.2b source)						*/
{
  int i,chars_to_fill;

    /*  Who knows what < 0 would mean?  Too lazy to issue error msg	*/
  if (fillwidth <= 0) fillwidth = DEFAULT_FIELD_WIDTH;
  if (fillwidth > maxwidth) return;
  chars_to_fill = fillwidth - strlen(s);
  if (chars_to_fill <= 0) return;

    /*  Right justify.  Go backwards to deal w/overlap problem		*/
  for (i = fillwidth-1; i >= chars_to_fill; i--)
    s[i] = s[i-chars_to_fill];

    /*  Blank fill at the front						*/
  for (i = 0; i < chars_to_fill; i++)
    s[i] = ' ';

  s[fillwidth] = '\0';

  return;
}

void in_getwidth(vn, ptr)
/* read the width from the string 'ptr' (an attribute that has
        been tested prior to coming here for containing the 
        string 'width=') and write it to the widths array
        element '*vn'
*/
int *vn;
char *ptr;
{

  char *p;
  int i,len,ierr;

  i = *vn;
  p = strchr(ptr,'=');

  switch (ierr = sscanf(p+1,"%d", &len)) {
    case EOF:
      errn("No width after \"width=\" for variable ",i);
    case 0:
      err("Illegal width after \"width=\".  Bad string=",ptr);
    case 1:
      break;
    default:
      errn("Unexpected return value from sscanf in in_getwidth.  Value=",ierr);
  }

  widths[i] = len;
  return;
}

void output_count(count)
struct linecount *count;
{
  FILE *file;
  Logical close_it;
/*  Next is arbitrary.  40 2 lines down is > max len of an int formatted into ASCII	*/
#define MAX_DEST_LEN 1024
  char count_buf[MAX_DEST_LEN+40];
  int status;
  if (count->do_it) {
    status = sprintf (count_buf,"%s=%d\n",count->name,count->count);
/*    Harbison & Steele is source for what constitutes an error here.  			*/
/*    EOF is negative, so we have redundancy ... but see Harbison & Steele		*/
    if ((status == EOF) || (status < 0)) errn ("sprintf failure encoding count.  status=",status);
    close_it = FALSE;
    /*  See above comments about not opening standard files		*/
    if (strcmp(count->destination,STDOUT_NAME) == 0) {
      file = stdout;
    } else if (strcmp(count->destination,STDERR_NAME) == 0) {
      file = stderr;
    } else {
      file = fopen(count->destination,count->destination_access);
      if (file == NULL) {
	errn(
	  buildstring("Could not open ",count->destination, " for write/append. errno=","count_open_err_msg"),
	  errno
	    );
      }
      close_it = TRUE;
    }
    status = fprintf(file,"%s",count_buf);
    if ((status == EOF) || (status < 0)) errn ("fprintf failure writing count.  status=",status);
    if (close_it) 
      if (fclose(file) == EOF) errn ("fclose failure after writing count.  errno=",errno);
  }
  return;
}

void output_counts()
{
  output_count (&commentline_linecount);
  output_count (&varline_linecount);
  output_count (&linecount);
  estdataline_linecount.count += linecount.count - commentline_linecount.count - varline_linecount.count;
  output_count (&estdataline_linecount);
  return;
}
