/* ******************************************************************* * * * Copyright (c) L-DGO/MIT/JGOFS * * * * * * File : list.c * * * * Revision History : * * * * Date Developer * * ---- --------- * */ #define LIST_VERSION "list version 1.6d 3 Jun 2005" /* * 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] * * 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_err(); /* 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= 0 ) { any_data_from_object = TRUE; for (i=firstvaratlevel[level]; i= 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; }