/* ******************************************************************* * * * Copyright (c) L-DGO/MIT/JGOFS * * * * * * File : list.c * * * * Revision History : * * * * Date Developer * * ---- --------- * */ #define LIST_VERSION "list version 1.8a 20 Feb 2009" /* * 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; char between_level_separator_line[] = "======================= "; char varlist_delimiter_line[] = "........................ "; char last_lev_varlist_delimiter_line[] = "------------------------ "; 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 */ 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%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 (" [-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 ("-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("\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; #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; 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) { 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; } 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); } if (header_flag) { 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; }