/* *******************************************************************
 *                                                                   *
 * Copyright (c) L-DGO/MIT/JGOFS                                     *
 *                                                                   *
 *                                                                   *
 * File             : list.c                                         *
 *                                                                   *
 * Revision History :                                                *
 *                                                                   *
 * Date                              Developer                       *
 * ----                              ---------                       *
 */
#define LIST_VERSION "list version 1.6c 30 Aug 2004"
/*
 * 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]					     *
 * Jan 21, 2004  v 1.6a		    Warren Sass                      *
 *	linux man pages say "don't use cuserid" and apparently	     *
 *	  cuserid function defns aren't #include'd from stdio.h	     *
 *	  under Red Hat v 9 w/ the "default" gcc compilation	     *
 *	  switches.  This causes compilation warning.  Sol'n:	     *
 *	  use getpwuid per linux man page suggestion		     *
 *	Unofficially released to Cyndy				     *
 *		[begin v 1.6a]					     *
 * Dec 28, 2003  v 1.6 		    Warren Sass                      *
 *	-forceheader						     *
 * Dec 18, 2003  v 1.6 		    Warren Sass                      *
 *	Size name and data buffers properly			     *
 *	Take NVAR from inner.h - stop using list-specific NVALS	     *
 * Dec 12, 2003  v 1.6 		    Warren Sass                      *
 *	-noerrecho						     *
 *	Allow user to spec value for -m				     *
 *	Common error/abnormal exit path				     *
 * Oct 10, 2003  v 1.6 		    Warren Sass                      *
 *	Bug fix: Some systems don't have /dev/stdout		     *
 * Oct 08, 2003  v 1.6 		    Warren Sass                      *
 *	Try to get list to exit w/errno or jdb's err stat in ab-     *
 *	  normal cases.  Test jdbread's err stat in the first place! *
 *	Add time & username to err msgs				     *
 *		[Needs list.h v 1.3]				     *
 *		[begin v 1.6]					     *
 *                                                                   *   
 * Oct 03, 2003  v 1.5 		    Warren Sass                      *
 *	Test writes to stdout for errors			     *
 *      New switch to allow redirection of error messages	     *
 *      New switch to control whether or not "broken pipe" deserves  *
 *	  error message.					     *
 *	New switch to tell list whether to exit on output errors     *
 *	Bug fix: -r help description printed twice		     *
 *	Bug fix: Switch conflicts involving -r flags not detected    *
 *		[begin v 1.5-released to globec; hopefully not]	     *
 *		[	     elsewhere			      ]	     *
 *                                                                   *   
 * Feb 19, 2002  v 1.4 		    Warren Sass                      *
 *	New option to allow "unlimited" output line length	     *
 *	New option to list transposed last level		     *
 *	Split out stuff for a list.h file			     *
 *	Check for mutually exclusive input switches		     *
 *	Allocate memory for output line buffer dynamically	     *
 *		[begin v 1.4]					     *
 *                                                                   *   
 * Sep 09, 1999  v 1.3b		    Christine Hammond                *
 *	get widely used constants from default.h                     *
 *	add conditional for DATUMSIZE, as value in default.h         *
 *	  is typically defined as 80, was 120 here previously        *
 * Aug 17, 1999  v 1.3a		    Warren Sass                      *
 *	Bug fix:  was attempting stty reset even if -n was set	     *
 *	Change name of nflag to reflect what it asserts		     *
 * Aug 14, 1999  v 1.3		    Warren Sass                      *
 *		Misc bug fixes & minor improvements		     *
 *	Bug fix:  prettyprint failed if width > strlen(var) + 10     *
 *	Bug fix:  olen should be terminal width for "user viewing";  *
 *	  max buf else.  When max buf raised, wrong olen changed     *
 *	Bug fix: -m would change a variable name that happened to be *
 *	  "nd" to -9999.0					     *
 *	Bug fix: "more" capability seemed broken on Bob's Solaris    *
 *	   box.  Improved there.  Seemed to work on Chris' IRIX box  *
 *	   before and after.  Doesn't work on my VMS box either way  *
 *	Rename/resize appropriate constants to their default.h state *
 *	   Buffers ended up WAYY bigger.			     *
 *	Add id string; print it during help			     *
 *	prettyprint is now really a right justifier - name it that.  *
 *	  Nobody used its return values - eliminate them	     *
 *	  Don't right justify if not enough room in buffer	     *
 *	fillsize is no longer that; it's a fill/nofill flag.	     *
 *	  Make a flag variable and a width variable to distinguish   *
 *	Make static temp buffer - saves reallocation		     *
 *	Declare jdb functions					     *
 *	#include errno; stdlib					     *
 *	Minimize use of #ifndef; remove #if's from runtime portion   *
 *	  of code						     *
 *	Add some error message text; remove redundant maxlevel test  *
 *	Reformat some comments relating to revision history	     *
 *	Reformat some program text				     *
 *		Unaddressed issues				     *
 *	1) Separator in data					     *
 *	     If a user of list chooses a separator character that    *
 *	  appears as part of the data displayed, there may be a      *
 *	  problem visually, and there will certainly be a problem    *
 *	  if list's output is going on to another program.	     *
 *	     We could diagnose this condition in list and abort.     *
 *	  We could couple the diagnosis to the -z flag if we assume  *
 *	  -z "data consumers" are programs and not people.  We could *
 *	  make the diagnostic itself switch-controllable.	     *
 *	2) Blanks in data					     *
 *	     Leading blank issues are probably moot, since iovalstr_ *
 *	  (at least the one in def), which feeds outer, which feeds  *
 *	  us, removes leading blanks unconditionally.  Theoretically *
 *	  however, we have an issue in this program		     *
 *	    The column alignment logic will treat the blanks as sig- *
 *	  nificant data, hence the output could look funny.  However *
 *	  maybe it SHOULD look funny because somebody took the trou- *
 *	  ble to be sure blanks were data in that particular object  *
 *	3) What should be affected by the -m flag?		     *
 *	    -m affects data that are exactly the 2 characters nd.    *
 *	  It does not affect, say, nd's embedded in fields whose     *
 *	  other characters are blanks, or nd's that have been quo-   *
 *	  ted to invoke outer's NOQUOTELINK capability.		     *
 *	    Similar to blank discussion, question becomes whether    *
 *	  it's up to list, a data displayer, to make data transform- *
 *	  ation decisions.  For example, somebody might use n/d to   *
 *	  represent no data.					     *
 *	4) PATH_INFO 						     *
 *	    If there's a PATH_INFO in the environment when list	     *
 *	  runs, it will foul things up if the object listed is	     *
 *	  local.  Effects include "object not found" errors and SEGV *
 *	  dumps							     *
 *	    This is an old issue w/ anything that uses the jdb	     *
 *	  routines.   In general, "system" has issued "unsetenv      *
 *	  PATH_INFO" commands.  However, this has gradually caused   *
 *	  down-the-line problems w/objects that themselves create    *
 *	  other processes to get the data.			     *
 *	    We could "unsetenv" here.  Most elegant soln is to use   *
 *	  path info routines to modify PATH_INFO minimally (clear    *
 *	  the "protocol" (.html, .flat) field, then reset it after   *
 *	  jdb use (paranoid, but mindlessly safe).  If we like this  *
 *	  idea, then I think we could default list's input (eg, no   *
 *	  object on command line) to "the object spec'd in	     *
 *	  PATH_INFO".  The idea is to arrive at an OO server	     *
 *	  environment where the shells need to do minimal parsing of *
 *	  PATH_INFO.						     *
 *		[begin v 1.3]					     *
 *                                                                   *   
 * Aug 11, 1999  v 1.2c		    Warren Sass                      *
 *	Essentially the same as 1.3, except I introduced a bug.      *
 *	Given to Bob, who put it in his /bin directory.  While test- *
 *    ing bug fix, found another bug.  Decided to do more code re-   *
 *    arrangement, and decided that a renumbering would be in order  *
 *    even though no major functionality was added		     *
 *		[begin v 1.2c]					     *
 *                                                                   *   
 * June 1, 1999 v 1.2b		Christine Hammond		     *
 *          per WJS and CLC - see comment in code re print of msg    *
 *		[begin v 1.2b]					     *
 *                                                                   *   
 * Nov 9, 1998 	v 1.2a		Christine Hammond                    *   
 *	    ver 1.2a resolves diffs between ver 1.2 and 1.1a 	     *
 *	     developed independently from same code		     *
 *		[begin v 1.2a]					     *
 *                                                                   *   
 * Aug 29, 1995 12.00           Christine Hammond                    * 
 *          ver 1.2 attribute for width determines width of field    *  
 *          option '-m' converts missing value from 'nd' to -9999.0  *
 *		[begin v 1.2]					     *
 *                                                                   *   
 * Oct. 8, 1996	v 1.1a		    Warren Sass                      *
 *    Change number of char per flatfile output line before new line *
 *       is inserted from 255 to 511                                 *
 *		[begin v 1.1a]					     *
 *                                                                   *   
 * Sat Oct 17  1992 10.00           Glenn Flierl                     *
 *                                                                   *   
 ******************************************************************* */

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

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

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

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

int outcount = 0;
Logical moreflag = TRUE;
Logical fflag = FLAG_UNDEFINED;
Logical mflag = FALSE;
Logical hflag = TRUE;
Logical outputcomments = TRUE;
Logical outlinelengthlimitflag = TRUE;
Logical rflag = FLAG_UNDEFINED;
Logical fillflag = 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;
char osep[2] = {DEFAULT_OSEP , '\0'};
char repsep[2] = {DEFAULT_OSEP , '\0'};
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_error();	/*  from utils.c			*/
void errn();			/*  from utils.c			*/

void reset();
void reset_and_out();

  /*  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",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",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"	*/
{
  printf ("Usage: list [-n] [-s|-t] [-z] [-m [new_missing]] [-c] [-l] [-f|-b|-r|-rs|-rt]\n");
  printf ("            [-errout errfile[+]] [-noerrecho] [-nopipeerr] [-finishinp]\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("     -m   missing value converted to new_missing (default -9999.0) \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");
  printf("     -h   prints this message\n");
  printf("     ?    prints this message\n");
  printf("     -v   prints version information\n");
  printf("\n");
  printf
("-errout   send err messages to errfile instead of /dev/stdout after \
command line parsing\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
("-forceheader  produce variable list even if there is no data from object\n");
  printf
("              (unless listing is in -b format)\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 switch %s\n",s,badswitch);
  printhelp(ERROR_EXIT);

  return;
}

void get_args(argc,argv)
     int argc;
     char *argv[];
{
  int i,len;
  char *s;

    /*  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;
    switch (argv[i][1]) {
      case 'b':
	if (  ! ((fflag == FLAG_UNDEFINED) && (rflag == FLAG_UNDEFINED))  )
						badswitch("Conflict",argv[i]);
	fflag = TRUE;
	hflag = FALSE;
	outputcomments = FALSE;
	break;
      case 'c':
	outputcomments = FALSE;
	break;
      case 'e':
	if (strcmp("-errout",argv[i]) == 0) {
	  if (++i >= argc) {
	    printf("Missing errfile after -errout switch\n");
	    printhelp(ERROR_EXIT);
	  }
	  errfilename = argv[i];
	  len = strlen(errfilename);
	  if (errfilename[len-1] == '+') {
	    if (len == 1) {
	      printf ("errfile cannot be just a \'+\'\n");
	      printhelp(ERROR_EXIT);
	    }
	    errfile_access = "a";
	    errfilename[len-1] = '\0';
	  }
	} 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':
	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;
	else badswitch("Illegal",argv[i]);
	break;
      case 'r':
	if (  ! ((fflag == FLAG_UNDEFINED) && (rflag == FLAG_UNDEFINED))  )
						  badswitch("Conflict",argv[i]);
	rflag = TRUE;
	hflag = FALSE;
        if (argv[i][3] == '\0') {
          if (argv[i][2] == 's') repsep[0] = ' ';
          if (argv[i][2] == 't') repsep[0] = '\t';
	} else if (argv[i][2] != '\0') {
	  badswitch("Illegal",argv[i]);
	}
	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 ("Need an object specifier argument\n");
    printhelp(ERROR_EXIT);
  }
  object_name = argv[i];

  if (++i < argc) {
    ofilename = argv[i];
    len = strlen(ofilename);
    if (ofilename[len-1] == '+') {
      if (len == 1) {
        printf ("outfile cannot be just a \'+\'\n");
        printhelp(ERROR_EXIT);
      }
      ofile_access = "a";
      ofilename[len-1] = '\0';
    }
  }

  if (++i < argc) {
    printf ("Too many arguments\n");
    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;
  }

  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...	*/
  newlen = strlen(outline)+strlen(s)+strlen(sep);
  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);
      }
    }
  }
  strcat(outline,s);
  strcat(outline,sep);
  return;
}  

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

void writevar(level)
int level;
{
  int i,j;
/*  +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)) {
	  strcat(msg," ");
	  writemln(msg);
        }
      if (hflag) {
	for (i=0 ;i<num; i++) outstr(namebuf[i],osep);
	outflush();
      }
      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)) {
	  strcat(msg," ");
	  writemln(msg);
        }
	/* do headers */
      for (i=level; i<=maxlevel; i++) {
	writemln("======================= ");
	for (j=firstvaratlevel[i]; j<firstvaratlevel[i+1]; j++)
	  outstr(namebuf[j],osep);
	outflush();
        if (i < maxlevel)
          writemln("........................ ");
        else
          writemln("------------------------ ");
	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;
  char *s;
  void in_getwidth();
  void rightjust();
  Logical keep_writing = TRUE;

    /*  No args or single arg '?' get help and exit			*/
  if (argc <= 1) printhelp(ERROR_EXIT);
  if ((argc == 2) && (argv[1][0] == '?') && (argv[1][1] == '\0'))
    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 = (char *) malloc (
		strlen("Could not open  for write: ") + strlen(ofilename) + 1
			);
    if (s == NULL)
      s = ofilename;
    else {
      strcpy(s,"Could not open ");
      strcat(s,ofilename);
      strcat(s," for write: ");
    }
      /*  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 = (char *) malloc (
		strlen("Could not open  for write: ") + strlen(errfilename) + 1
			);
    if (s == NULL)
      s = errfilename;
    else {
      strcpy(s,"Could not open ");
      strcat(s,errfilename);
      strcat(s," for write: ");
    }
    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...							*/
  i = strlen(object_name) + 1;
  if (i < DEFAULT_MAXOLEN) i = DEFAULT_MAXOLEN;
  outline = (char *)malloc(i);
  if (outline == NULL) {
    desired_exit_status = ERROR_EXIT;
    errn ("Could not allocate buffer outline.  Desired size = ",i);
  }
  strcpy(outline,object_name);

  unit = 1;
  num = -NVAR;
  maxlevel = jdbopen_(&unit,outline,namebuf,&namebufsize,&num);
  if (maxlevel < 0) {
    desired_exit_status = maxlevel;
    err("jdbopen failure (object not found?)  Object: ",object_name);
  }


    /*  do levels							*/
  firstvaratlevel = (int *)malloc((maxlevel+2)*sizeof(int));
  if (firstvaratlevel == NULL) {
    desired_exit_status = ERROR_EXIT;
    errn ("Could not get memory for levels buffer.  Desired # integers = ",
	  maxlevel+2);
  }
    /*  firstvaratlevel[0] = 0; [1] = # of first var at level 1; etc	*/
    /*  For convenience, add "level N+1" w/value=tot # vars		*/
  for (i = 0;  i <= maxlevel + 1;  i++) {
    firstvaratlevel[i] = 0;
  }
    /*  count variables at each level putting tot in next level's index	*/
  for (i=0; i<num; i++) {
    firstvaratlevel[1 + jdblevel_(&unit,&i)]++;
  }
    /*  develop cumulative total					*/
  for (i = 0;  i <= maxlevel;  i++) {
    firstvaratlevel[i+1] += firstvaratlevel[i];
  }


   /*  do attributes & cleanup names					*/
  for (i=0; i<num; i++) {
    strcpy(outline,"[");
    while (jdbattributes_(&unit,&i,tmp)) {
       if (strncmp(tmp,"width=",6) == 0) {
         in_getwidth(&i,tmp);
       } else {     
         strcat(outline,tmp);
         strcat(outline,";");
       }
    }
    if (outline[1] == '\0') outline[0] = '\0';
    else outline[strlen(outline)-1] = ']';
    strcat (namebuf[i],outline);
    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 = (char *) malloc
	    (strlen("Error writing to  : ") + strlen(ofilename) + 1);
      if (s == NULL)
	s = ofilename;
      else {
	strcpy (s,"Error writing to ");
	strcat (s,ofilename);
	strcat (s," : ");
      }
      desired_exit_status = ofile_status;
      err(s,strerror(ofile_status));
    }
  }
      

    /*  -1 is jdb's EOF...						*/
  if (level != -1) {
    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();
  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;
}
