/* defgb_utils. Utility routines for defgb package. */ /* These routines are used by more than one other defgb module */ /* or are more-or-less generic. Also, these routines have no */ /* "knowledge" of the defgb "system" */ /* void analy_source(f,separators,dirstring) */ /* char *buildstring(str1,str2,str3,errstring) */ /* void do_data_trace (f,bufs,nbufs) */ /* int extract_wjstbl(synonym,max_len_synonym,variable,wjstbl) */ /* Logical get_logical_from_string (string,errmsg1,errmsg2) */ /* void free_lengthened_str(in_str_buf) */ /* char *lengthen_str(in_str_buf, */ /* append_str1,append_str2,extend_len,errstr) */ /* char *lengthen_str_nl(in_str_buf, */ /* append_str1,append_str2,extend_len,errstr) */ /* char *lengthen_str_and_free(in_str_buf, */ /* append_str1,append_str2,extend_len,errstr) */ /* char *lookup_wjstbl (s,wjstbl) */ /* char *nxttok(start,separators,start_next,quotes, */ /* signif_consec_seps,nested_quotes) */ /* char *rem_delims (ptr,delims) */ /* char *strdupl(outptr_ptr,inptr,errstring) */ #define DEFGB_UTILS_VERSION "defgb_utils version 1.1 26 Apr 97" /* 26 Apr 97. */ /* lengthen_str suite */ /* 3 Apr 97. */ /* Bug fix: analy_source freed wrong string. Symptom: */ /* crash if script spec needed prefixed directory */ /* [Begin 1.1] */ /* 17 Mar 97. */ /* Add version variable so string is in executable */ /* Change data_trace ID to include file source instead */ /* of descrip if datafile */ /* Bug fix: do_data_trace print*lines = 1 results in */ /* no printout. if syntax was wrong, too */ /* Bug fix: Bad /dev/null input logic */ /* [Begin 1.0a] */ /* 10 Feb 97. */ /* nxttok handles embedded quoted strings */ /* 28 Jan 97. */ /* Allow NULL first string to buildstring */ /* [Begin 1.0] */ #include "defgb_nonconfigurable.h" /* Uses no global variables external to this routine */ /* Defines version & free_flag; neither used outside this file */ char *version_utils = DEFGB_UTILS_VERSION; Logical free_flag = FALSE; /* Communicates between lengthen_str & */ /* lengthen_str_and_free. Couldn't */ /* think of better way to do this */ /* from a usage point of view */ Logical add_nl = FALSE; /* Communicates between lengthen_str & */ /* lengthen_str_nl. */ /* Requires following routines: */ void err(); void errn(); char *buildstring(str1,str2,str3,errstring) char *str1,*str2,*str3,*errstring; /* Allocate memory for, and return pointer to, concatenation of the */ /* 3 input strings. Returns NULL if all strings are NULL */ /* On alloc failure, call err with errstring & the number of bytes we */ /* tried to get, unless errstring NULL, in which case return NULL. */ /* See lengthen_str for more general string concatenator ... */ { char errbuf[80]; /* Too lazy to count */ char *out; int len; len=0; if (str1 != NULL) len += strlen(str1); if (str2 != NULL) len += strlen(str2); if (str3 != NULL) len += strlen(str3); if (len == 0) return NULL; out = (char *)malloc(len+1); if (out == NULL) { /* We couldn't get memory. */ if (errstring == NULL) return NULL; sprintf (errbuf, "Error trying to get %d (0x%X) bytes of memory\n Purpose = ",len,len); err (errbuf,errstring); } else { *out = '\0'; if (str1 != NULL) strcat (out,str1); if (str2 != NULL) strcat (out,str2); if (str3 != NULL) strcat (out,str3); return out; } } char *strdupl(out,in,errstring) char **out,*in,*errstring; /* Finds length of in, grabs that much memory, copies in to the */ /* scratch area, and puts the addr of the scratch area in *out (w/pass */ /* by value, cannot directly affect out). Returns addr of out */ /* (like strcpy does) */ /* Some C libs have a strdup, which should probably be used, but some */ /* don't */ { *out=buildstring(in,NULL,NULL,errstring); return *out; } char *lengthen_str(in_str_buf,append_str1,append_str2,extend_len,errstring) char *in_str_buf,*append_str1,*append_str2,*errstring; int extend_len; /* lengthen_str appends append_str1 & append_str2 (and a newline, */ /* if entry was made via lengthen_str_nl) to the string within */ /* in_str_buf while checking that the result will fit in in_str_buf. */ /* If the result will not fit, memory is allocated for a new */ /* in_str_buf, and the result is put there. Memory of the original */ /* buffer is freed if lengthen_str allocated it. (To cause the */ /* freeing of the original, user-supplied buffer, use function */ /* lengthen_str_and_free instead of lengthen_str on at least the */ /* first call) */ /* extend_len is provided to allow the caller to try to minimize */ /* the number of memory allocations/deallocations. It is optional, */ /* and may always be specified as 0. It specifies the amount to */ /* extend in_str_buf if extension is required. It is ignored if it */ /* is insufficient to hold the result. An exception is a negative */ /* extend_len on the first call to lengthen_str for a particular */ /* in_str_buf. In this case, the absolute value of extend_len is */ /* used if extension is required. This absolute value also re- */ /* presents the initial size of in_str_buf (not the length of the */ /* string in it). Negative extend_len on other calls is ignored. */ /* errstring is used for ID purposes when a memory allocation */ /* failure occurs. If NULL, lengthen_str returns NULL. Otherwise, */ /* function err is called with errstring and other information. */ /* The return value of lengthen_str is a pointer to the */ /* first character of the buffer containing the complete string */ /* */ /* lengthen_str keeps an internal pointer to the next free char- */ /* acter in the buffer, so be very careful of adding or removing */ /* characters from the buffer between lengthen_str calls. The */ /* number of pointers is limited (to 20 at the moment). */ /* */ /* When finished using a string, free its space w/a call to */ /* free_lengthened_str rather than using free. This serves 2 pur- */ /* poses. First, it won't free a string that hasn't been extended */ /* (unless entry was made via lengthen_str_and_free). Second, it */ /* will free the internal lengthen_str pointer */ /* */ /* Details: */ /* in_str_buf is looked up in an internal table. If not found, */ /* a new entry is made. The absolute value of extend_len (or the */ /* length of in_str_buf) is assumed to be the size of the buffer. */ /* If append_str1 is NULL, memory allocated to in_str_buf is freed */ /* if appropriate, and in_str_buf is removed from the table */ /* extend_len is added to the original length of in_str_buf if */ /* this particular call to lengthen_str requires a buffer extension. */ /* As a length, extend_len is ignored if using it does not make */ /* enough space for the resulting string. Thus, 0 extend_len */ /* produces an extension each call, exactly big enough to hold the */ /* concatenated string. */ /* in_str_buf of 0 is allowed. It causes a copy of append_str to */ /* be made, and returns that address (but why do this?) */ /* As extensions are made, the internal table is updated. There- */ /* fore, the most usual in_str_buf value is the return of a previous */ /* lengthen_str call */ { #define NLENGTHEN_STR_TBL 20 static char *addr[NLENGTHEN_STR_TBL]; static char *bufptr[NLENGTHEN_STR_TBL]; static int size[NLENGTHEN_STR_TBL]; static Logical free_addr[NLENGTHEN_STR_TBL]; static ntbl = 0; int itbl,len,len1,len2; char *tmp; char errbuf[80]; /* Too lazy to count */ for ( itbl = 0; itbl < ntbl; itbl++ ) if (in_str_buf == addr[itbl]) break; if (itbl == ntbl) { /* Need to add a table entry */ if (ntbl == NLENGTHEN_STR_TBL) errn("Too many string lengthenings in progress. Max is ",ntbl); addr[itbl] = in_str_buf; len = (in_str_buf == NULL) ? 0 : strlen(in_str_buf); bufptr[itbl] = in_str_buf + len; /* free_flag is FALSE unless got here via lengthen_str_and_free */ free_addr[itbl] = free_flag; /* Initial size from extend_len if extend_len negative */ if (extend_len < 0) { extend_len = -extend_len; /* -1 is to account for terminating null */ size[itbl] = (extend_len-1 > len) ? extend_len-1 : len; } else size[itbl] = len; ntbl++; } else if (append_str1 == NULL) { /* Remove this table entry by copying last one over it & */ /* shortening list */ if (free_addr[itbl]) free(addr[itbl]); addr[itbl] = addr[ntbl]; bufptr[itbl] = bufptr[ntbl]; free_addr[itbl] = free_addr[ntbl]; size[itbl] = size[ntbl]; ntbl--; return NULL; } len1 = strlen(append_str1); len2 = (append_str2 == NULL) ? 0 : strlen(append_str2); len = len1 + len2; if (add_nl) len++; if ( (addr[itbl] + size[itbl]) < (bufptr[itbl] + len) ) { /* Need a bigger string. */ /* Figure out its length */ size[itbl] += (extend_len > len) ? extend_len : len; /* Get it, and move old string to new location */ tmp = addr[itbl]; addr[itbl] = (char *)malloc(size[itbl]); if (addr[itbl] == NULL) { if (errstring == NULL) return NULL; sprintf (errbuf, "Error trying to get %d (0x%X) bytes of memory\n Purpose = ", size[itbl], size[itbl]); err (errbuf,errstring); } strcpy(addr[itbl],tmp); /* Free old string if appropriate. Mark this string to be */ /* freed if another extension is needed */ if (free_addr[itbl]) free(tmp); else free_addr[itbl] = TRUE; /* Adjust pointer to first free character in string */ bufptr[itbl] += (addr[itbl] - tmp); } strcpy(bufptr[itbl],append_str1); bufptr[itbl] += len1; if (append_str2 != NULL) { strcpy(bufptr[itbl],append_str2); bufptr[itbl] += len2; } if (add_nl) { *(bufptr[itbl]) = '\n'; bufptr[itbl]++; } return addr[itbl]; } void free_lengthened_str(in_str_buf) char *in_str_buf; { lengthen_str(in_str_buf,NULL,NULL,0,NULL); return; } char *lengthen_str_nl(in_str_buf,ap_str1,ap_str2,extend_len,errstring) char *in_str_buf,*ap_str1,*ap_str2,*errstring; int extend_len; /* Asks lengthen_str to add a newline character after ap_strs */ { char *tmp; add_nl = TRUE; tmp = lengthen_str(in_str_buf,ap_str1,ap_str2,extend_len,errstring); add_nl = FALSE; return tmp; } char *lengthen_str_and_free(in_str_buf,ap_str1,ap_str2,extend_len,errstring) char *in_str_buf,*ap_str1,*ap_str2,*errstring; int extend_len; /* Needed only if first call to lengthen_str wants to spec an */ /* input buffer which should be freed if it needs lengthening */ /* Except for first call for a particular input buffer, can be used */ /* interchangeably w/lengthen_str */ { char *tmp; free_flag = TRUE; tmp = lengthen_str(in_str_buf,ap_str1,ap_str2,extend_len,errstring); free_flag = FALSE; return tmp; } char *nxttok(start,separators,nxt,quotes,signif_consec_seps,nested_quotes) char *start,*separators,*quotes,**nxt; Logical signif_consec_seps,nested_quotes; /* strtok w/ quoting & significant consecutive separator capability */ /* nxt is pointer to place where we return pointer to next string */ /* start. Returns pointer to token start; NULL if no more tokens. */ /* Does not return nxt if no more tokens */ /* if quote is first non-separator character of token, it and its */ /* trailing quote are removed. Otherwise, quotes are left in. */ /* nxttok can also be used to replace strtok when "nested" strtok's */ /* are desired. In the sequence */ /* tok = strtok (start,separators); */ /* while (tok != NULL) { */ /* do_whatever(tok); */ /* tok = strtok (NULL,separators); */ /* } */ /* do_whatever may not call strtok on a different string. The */ /* sequence can be replaced with */ /* char *nxt; */ /* tok = nxttok(start,separators,&nxt,NULL,FALSE,FALSE); */ /* while (tok != NULL) { */ /* do_whatever(tok); */ /* tok = nxttok(nxt,separators,&nxt,NULL,FALSE,FALSE); */ /* } */ { char *rtn,*next_start,*next_quote; char open_quote,close_quote; if (start == NULL) return NULL; /* Determine string start based on whether or not 2 consecutive */ /* separators means an empty field */ if (separators == NULL || signif_consec_seps) rtn = start; else rtn = start + strspn(start,separators); if (*rtn == '\0') return NULL; /* Field was all separators */ if (quotes == NULL) open_quote='\0'; else { open_quote = *quotes; close_quote = *(quotes+1); } if (*rtn == open_quote) { /* Field is between quote characters */ /* Advance beyond open quote */ rtn++; /* Find terminating quote-either last one or next one */ /* depending on how field is being parsed */ if (nested_quotes) next_start=strrchr(rtn,close_quote); else next_start=strchr(rtn,close_quote); /* Chop string on top of close quote and advance beyond it */ if (next_start == NULL) err ("Unclosed delimited field. Field = ",rtn-1); else *(next_start++) = '\0'; } else { /* Field is between separator characters */ /* No separators means next token is whole string */ if (separators == NULL) next_start = NULL; else { next_start = rtn; while (next_start != NULL) { /* Get next separator & next open quote. */ if ((next_quote = quotes) != NULL) next_quote = strchr(next_start,open_quote); next_start = strpbrk(next_start,separators); /* If no open quote or open quote after next separator, done */ if (next_quote == NULL || next_quote > next_start) break; /* Advance beyond open quote & find appropriate close quote */ if (nested_quotes) next_start=strrchr(++next_quote,close_quote); else next_start=strchr(++next_quote,close_quote); /* If appropriate, advance beyond close quote & try again */ if (next_start == NULL) err ("Unclosed delimited field. Field = ",rtn); else if (*(++next_start) == '\0') next_start == NULL; } } /* If more separators, chop string on top of next one and */ /* advance beyond it */ if (next_start != NULL) *(next_start++) = '\0'; } if (nxt != NULL) *nxt = next_start; return rtn; } int get_logical_value (string) char *string; /* Return 1 if string is synonym for TRUE, 0 for FALSE, -1 if not a synonym */ { switch (*string) { case 't': case 'T': case 'y': case 'Y': case '1': if (*(string+1) == '\0') return TRUE; if ( (strcmp("YES",string) == 0) || (strcmp("yes",string) == 0) || (strcmp("TRUE",string) == 0) || (strcmp("true",string) == 0) ) return 1; break; case 'f': case 'F': case 'n': case 'N': case '0': if (*(string+1) == '\0') return FALSE; if ( (strcmp("NO",string) == 0) || (strcmp("no",string) == 0) || (strcmp("FALSE",string) == 0) || (strcmp("false",string) == 0) ) return 0; break; default: /* For completeness */ ; break; } return -1; } Logical get_logical_from_string (string,errmsg1,errmsg2) char *string,*errmsg1,*errmsg2; /* get_logical_value + error msgs. Still returns -1 if NG */ { char *errtmp1,*errtmp2; switch (get_logical_value(string)) { case 1: return TRUE; break; case 0: return FALSE; break; default: ; } errtmp1 = buildstring("Illegal logical value ",string,"\n ", " get_logical_from_string"); errtmp2 = buildstring(errmsg1,errmsg2,NULL," get_logical_from_string"); err (errtmp1,errtmp2); free (errtmp1); free (errtmp2); return -1; } void print_data_trace(stream,f,bufs,nbufs) struct fileinfo *f; FILE *stream; char *bufs; int nbufs; /* bufs can be either string or array of strings. Assume string if */ /* nbufs = 1. (No check that nbufs >= 1) */ /* If array, print out as tab-separated string. Idea is */ /* that arrays are data "lines" that came directly from data objects. */ /* Most likely they "came across" as tab-separated strings. */ /* If datafile, ID with file spec; else ID with opt file description */ { int i; char *ptr; if (strcmp((ptr = f->descrip),"datafile") == 0) ptr = f->source; if (nbufs == 1) fprintf (stream,"%s rec %-4d : %s\n", ptr, f->nrecs, bufs); else { fprintf (stream, "%s rec %-4d : %s", ptr, f->nrecs, bufs[0]); for (i=1; idup_print_lines == 0) { if (print_it (f->nrecs,f->print_lines)) primary_stream = stream; } else { if (print_it(f->nrecs,f->dup_print_lines)) addl_stream = stream; if (print_it(f->nrecs,f->print_lines)) primary_stream = PRIMARY_ERROR_STREAM; } if (primary_stream != NULL) print_data_trace(primary_stream,f,bufs,nbufs); if (addl_stream != NULL) print_data_trace(addl_stream,f,bufs,nbufs); return; } char *rem_delims (ptr,delims) char *ptr,*delims; /* Logically remove one set of nested delimiters, if found, from */ /* *ptr. Does this by returning a pointer to a string w/o the */ /* delimiters */ /* */ /* delims is a 2 character string consisting of the open and close */ /* delimiter characters. */ /* */ /* Error if first character of input string */ /* is opening delimiter and last character is not closing delim */ { char *nxt,*retval; retval = nxttok(ptr,NULL,&nxt,delims,FALSE,TRUE); if (nxt != NULL) /* No delim string should end up NULL */ if (*nxt != '\0') /* Delim string put '\0' over close delim, */ /* hence "next string" is '\0' that closed */ /* original string */ err ("Closing delimiter not last character of field. Field = ",ptr); return retval; } void analy_source(f,separators,dirstring) struct fileinfo *f; char *separators,*dirstring; /* Alters f->source (maybe) and f->source_type */ /* All f->source values point to dynamically allocated fields on */ /* both input and output. Therefore, they must point to the first */ /* characters of their fields so they can be subsequently freed if */ /* necessary. */ /* Examine .source field of structure f to determine what kind of */ /* input it represents. Set the .source_type field accordingly. */ /* Along the way, remove the special characters that identified the */ /* field as whatever type it was, and do some validity checks. */ /* Field represents a command or script if it's wrapped in one set */ /* of delimiters; or a JGOFS object if wrapped in another. If, */ /* instead, it leads off with a special character, it represents the */ /* data itself. If none of the above, it represents a file. If that */ /* file is /dev/null, it's a special type (phony "missing data" record */ /* can be supplied if necessary) */ { char badscriptchars[4] = {PIPECHAR, INP_REDIRECT_CHAR, OUTP_REDIRECT_CHAR, '\0'}; char *msg,*nxt,*source,*savsrc; Logical might_need_dir; /* Make copy of string for error message purposes. As long as we */ /* have it, and since copying is necessary, muck w/copy, even- */ /* tually overwriting original */ /* Must retain pointer to string start in order to free it... */ strdupl(&savsrc,f->source," analy_source temp buffer"); source = savsrc; source += strspn(source,separators); /* Skip leading separators */ if (*source == '\0') { msg=buildstring("Null input specifier for ", f->descrip, " file.", " analy_source err msg1"); err (msg,""); free (msg); } else if (*source == *EXEC_DELIM) { f->source_type = COMMAND_FILE; source = nxttok(source,separators,&nxt,EXEC_DELIM,FALSE,TRUE); if (strpbrk(source,badscriptchars) != NULL) err ("May not do piping or i/o redirection in command\n Command was ",source); } else if (*source == SOURCE_IS_DATA) { f->source_type = INDIRECT_FILE_LINE; ++source; /* Advance past \ */ nxt = NULL; } else if (*source == *OBJECT_DELIM) { f->source_type = JGOFS_OBJECT; source = nxttok(source,separators,&nxt,OBJECT_DELIM,FALSE,TRUE); } else if (strcmp(source,NULL_INPUT) == 0) { f->source_type = CREATE_NULL_DATA; nxt = NULL; } else { f->source_type = DATA_FILE; /* Next line will not reset source because of strspn before */ /* loop. However, it will truncate trailing delims & set nxt, */ /* both of which are necessary. Left in source = for symmetry */ source = nxttok(source,separators,&nxt,NULL,FALSE,FALSE); if (strpbrk(source," \t") != NULL) err ("Embedded blank or tab in file name. Name = ",source); } /* Check for extra entry */ if (nxttok(nxt,separators,NULL,NULL,FALSE,FALSE) != NULL) { msg=buildstring("Extra parameter in input specifier for ", f->descrip, " file. Specifier = ", " analy_source err msg"); err (msg,f->source); free (msg); } /* Final string is generally a substring of original. Because */ /* of dynamic memory issues, f->source pointer cannot be moved, so */ /* copy substring to beginning of string. If original string was */ /* "perfect" (just file spec, no leading/trailing whitespace, etc) */ /* copy is unnecessary. However, although this is probably most */ /* common situation, too tough to keep track of whether or not */ /* copy is needed. */ /* Exception to substring occurs when we need to prefix file */ /* spec w/directory. In this case, chuck out old string and point */ /* new one. Note that this logic depends on source pointing to a */ /* copy of the original string (as opposed to original string) */ might_need_dir = ( (f->source_type == DATA_FILE) || (f->source_type == COMMAND_FILE) ); if ( might_need_dir && (*source != DIRSEP) && (dirstring != NULL) ) { free (f->source); f->source = buildstring(dirstring,source,NULL," putting filespec together"); } else strcpy (f->source,source); free (savsrc); return; } char *lookup_wjstbl (s,wjstbl) char s[],wjstbl[]; /* Looks up string s in a "wjs table". If found, return a pointer */ /* to the "value" corresponding to the key; else return NULL */ /* A "wjs table" is a string consisting of a number of objects. */ /* Each object is of the form */ /* separator_character, */ /* key_string, */ /* separator_character, */ /* value_string, */ /* white_space_characer(s) */ /* The 2 separator chars must be the same, and must not be white */ /* space characters or characters found in any key or value. */ /* There may be multiple blanks */ { char *found,*ptr; int key_len; if (wjstbl[0] == '\0') return NULL; key_len = strlen(s); ptr = (char *)malloc(key_len + 3); /* Build string consisting of input string surrounded by */ /* separator characters */ ptr[0] = wjstbl[0]; strcpy(ptr+1,s); ptr[key_len+1] = wjstbl[0]; ptr[key_len+2] = '\0'; found=strstr(wjstbl,ptr); free(ptr); if (found == NULL) return NULL; else return found+key_len+2; } int extract_wjstbl(synonym,max_len_synonym,variable,wjstbl) char *variable,*synonym,*wjstbl; int max_len_synonym; /* Returns -1 if synonym string is truncated because it's too small */ /* else */ /* 1 if translation found. *synonym contains translation */ /* 0 if no translation found. *synonym contains copy of */ /* *variable */ { char *trans_loc,*synonym_ptr,white_space; int i; trans_loc=lookup_wjstbl(variable,wjstbl); if (trans_loc == NULL) { if (variable == synonym) return 0; else { strncpy (synonym,variable,max_len_synonym); if (strlen(variable) <= max_len_synonym) return 0; else return -1; } } else { synonym_ptr = synonym; /* See description of a wjstbl for location of white space char */ white_space = *(wjstbl+strlen(wjstbl)-1); for (i=0;i