/*		utils.  Utility routines for various other routines	*/
/*	Formerly defgb_utils.  Should probably end up including		*/
/*	outer_utils...							*/
/*	    NB: These routines report their errors to an externally	*/
/*	    defined function err(s,t).  This function should be coded	*/
/*	    in anticipation that there is trouble getting dynamic	*/
/*	    memory, since many error messages are about that		*/
/*	  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 JGOFS "system" (but do have knowledge	*/
/*	  about defgb conventions)					*/
/*	    Logical add_id_to_err(new_s,new_t,s,t,id_string)		*/
/*	    char *add_to_buffer(dest,sizeofdest,source,errmsg)		*/
/*	    void analy_source(f,separators,dirstring)			*/
/*	    char *buildstring(str1,str2,str3,errstring)			*/
/*	    Logical char_sets_disjoint(str_ptrs,n_str_ptrs)		*/
/*	    char *copy_into_buffer(dest,sizeofdest,source,errmsg)	*/
/*	    void errn(s,i)						*/
/*	    Logical extract_wjstbl					*/
/*		(synonym,max_len_synonym,variable,wjstbl)		*/
/*	    Logical get_logical_from_string (string,errmsg1,errmsg2)	*/
/*	    void free_lengthened_str(in_str_buf)			*/
/*	    void free_lengthened_str_special()				*/
/*	    Logical get_logical_value (string)				*/
/*	    Logical is_a_leap_year(year)				*/
/*	    int is_a_remote_object(s)					*/
/*	    int is_a_remote_object_old_way(s)				*/
/*	    int is_a_remote_object_new_way(s)				*/
/*	    Logical is_a_varname(s)					*/
/*	    int get_integer_attribute(ptr)				*/
/*	    int keyword_pairs_to_wjstbl 				*/
/*	     (wjstbl_ptr_ptr,keyword_pair_str,sep_array,err_msg_id_str)	*/
/*	    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)		*/
/*	    int *level_splits(handle,maxlev,nvars)			*/
/*	    char *lookup_wjstbl (s,wjstbl)				*/
/*	    char *nxttok(start,separators,start_next,quotes,		*/
/*		signif_consec_seps,nested_quotes) 			*/
/*	    char *rem_delims (ptr,delims)				*/
/*	    char *set_option_string					*/
/*		(option_name,option_default,wjstbl,n_wjstbl_args_left)	*/
/*	    char *strcasecmp_wjs(inptr1,inptr2)				*/
/*	    char *strdupl(outptr_ptr,inptr,errstring)			*/
/*	    char *strip_space(inptr)					*/
/*	    char *strip_space_n(inptr,&len)				*/
/*	    Logical string_in_list(list,string,sep)			*/
/*	    Logical string_is_numeric(datum,missing_is_numeric)		*/
/*	    char **string_sets_intersection(s1,num_s1,s2,num_s2)	*/
/*	    char **string_sets_union(s1,num_s1,s2,num_s2)		*/

#define UTILS_VERSION "utils  version 2.4a  29 Aug 2014"
/* 29 Aug 14.			WJS					*/
/*	lengthen_str has been allocating 1 too few bytes all these	*/
/*   years - classic string-length/buffer-size confusion		*/
/*		[Begin 2.4a]						*/
/* 28 Apr 13.			WJS					*/
/*	Typo fix in comment.						*/
/* 18 Oct 08.			WJS					*/
/*	Bug fix to is_a_remote_object.  Dot analysis needed to be	*/
/*    confined to portion of object defn relating to node:port		*/
/*  5 Aug 08.			WJS					*/
/*	Allow strings w/trailing blanks to be considered numeric	*/
/*		[needs core.h 2.0]					*/
/* 13 Jun 08.			WJS					*/
/*	Up version.  2.3 is in use in a "frozen" jgofs tree		*/
/* 29 Feb 08.			WJS					*/
/*	is_a_remote_object stuff					*/
/*	Change analy_source to use is_a_remote_object			*/
/*		[Begin 2.4]						*/
/* 20 Sep 07.			WJS					*/
/*	string_sets_intersection, string_sets_union			*/
/*  2 Aug 07.			WJS					*/
/*	is_a_varname							*/
/*	Another bug found in the realloc mod.  Regress to 2.1 		*/
/*    lengthen_str and, accordingly, up the version so we can declare	*/
/*    2.2 no good (not that 2.1 or 2.2 were ever "released".  Regarding	*/
/*    that, note that despite 2.0 comment, the first "JGOFS 2.0" tar	*/
/*    file appears to contain 2.0 (contents of fleetlink testjg root))	*/
/*		[Begin 2.3]						*/
/* 26 Apr 07.			WJS					*/
/*	Bug fix to realloc mod.  I should fiddle more w/working code..	*/
/* 11 Jan 07.			WJS					*/
/*	Bug fix: add_id_to_len did not handle NULL input		*/
/* 30 Dec 06.			WJS					*/
/*	recode lengthen_str to use realloc				*/
/*	set_option_string						*/
/* 20 Dec 06.			WJS					*/
/*	Check for unsucessful malloc in lookup_wjstbl			*/
/*	Check for null input, too					*/
/* 16 Dec 06.			WJS					*/
/*	strip_space; strip_space_n					*/
/*	char_sets_disjoint; keyword_pairs_to_wjstbl			*/
/*		[Begin 2.2]						*/
/* 14 Jul 06.			WJS					*/
/*	add_to_buffer							*/
/*	Change copy_into_fixed_length_buffer to copy_into_buffer	*/
/*		[Begin 2.1 - don't think 2.0 was ever "released"]	*/
/* 21 Aug 05.			WJS					*/
/*	Another bug fix to level_splits!				*/
/* 17 Aug 05.			WJS					*/
/*	string_in_list & string_is_numeric				*/
/* 12 Aug 05.			WJS					*/
/*	is_a_leap_year							*/
/*  5 Aug 05.			WJS					*/
/*	Bug fix; arg list change (nlevs -> maxlev) to level_splits	*/
/*  5 Jul 05.			WJS					*/
/*	get_integer_attribute						*/
/*		[Begin 2.0 (might be called 1.9a (5 Aug)]		*/
/*		[  or 1.10 (12 Aug) elsewhere)		]		*/

/* 10 Jun 05.			WJS					*/
/*	Comment change							*/
/*  3 Jun 05.			WJS					*/
/*	level_splits							*/
/*		[Needs utils.h 1.1]					*/
/*		[Begin 1.9]						*/
/* 23 Apr 04.			WJS					*/
/*	Mods to "version-returning function".				*/
/*	  a) change from local to global to ensure it can't be "opti-	*/
/*	     mized out"							*/
/*	  b) don't want "version" in any function name since such       */
/* 	     names appear when grep'ping for "version"		        */
/* 18 Feb 04.			WJS					*/
/*	Rename from defgb_utils to utils.				*/
/*	#include utils.h instead of defgb.h				*/
/*		[Begin 1.8]						*/
/*  4 Feb 04.			WJS					*/
/*	Get .h file versions into this module				*/
/* 30 Jan 04.			WJS					*/
/*	add_id_to_err; errn						*/
/*		[Begin 1.7]						*/
/*  [1.0 -> 1.6  comments in utils_revision.doc. 5 Jul 05]		*/

#include "utils.h"

  /*  Uses no global variables external to this routine			*/

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 routine :					*/
void err();

/************************************************************************/

char *utils_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[] = UTILS_VERSION"/"FULL_UTILSH_VERSION;
  return version;
}



char *strip_space_n(inptr,lenptr)
char *inptr;
int *lenptr;
/*  Return pointer to first non-whitespace char in input; NULL if none	*/
/*  Return length of non-whitespace string in lenptr; 0 if none		*/
{
  char *ptr,*ptr2;

  *lenptr = 0;

  if (  (ptr = inptr) == NULL  ) return NULL;

  while (isspace(*ptr)) ptr++;
  if (*ptr == '\0') return NULL;

  ptr2 = ptr + strlen(ptr);
  while (ptr2 != ptr)
    if ( ! isspace(*(--ptr2)) ) break;
  *lenptr = ptr2 - ptr + 1;
  return ptr;
}



char *strip_space(inptr)
char *inptr;
/*  Strip leading and trailing whitespace from input string.  Return	*/
/*  pointer to string.  Requires write access to input string		*/
/*  If string is all whitespace, return NULL				*/
{
  int len;
  char *ptr;
  if (  (ptr = strip_space_n(inptr,&len))  == NULL  ) return NULL;
  *(ptr+len) = '\0';
  return ptr;
}

Logical string_is_numeric(datum,missing_is_numeric)
char *datum;
Logical missing_is_numeric;
{
  Logical status;
  double dummy;

/*  MISSING_VALUE_STRING set in core.h					*/
/*  (so is GET_NUMBER_FROM_STRING)					*/
  if (  strcmp(MISSING_VALUE_STRING,datum) == 0  )
					return missing_is_numeric;
  GET_NUMBER_FROM_STRING(dummy,datum,status);
  return status;
}

Logical string_in_list(list,string,sep)
char *list,*string,sep;
/*  sep is list separator character					*/
{
  char *ptr;
  int string_len;
  Logical start_test,end_test;

  if (strchr(string,sep) != NULL) return FALSE;

  string_len = strlen(string);
  ptr = list;

  while (  (ptr = strstr(ptr,string)) != NULL  ) {
    start_test = (ptr == list) ? TRUE : (*(ptr-1) == sep);
    ptr += string_len;
    end_test = (*ptr == sep) || (*ptr == '\0');    
    if (start_test && end_test) return TRUE;
  }

  return FALSE;
}


Logical add_id_to_err(new_s,new_t,s,t,id_string)
char **new_s,**new_t;
char *s,*t,*id_string;
  /*  Takes error message that is presumably in 2 parts in strings s	*/
  /*  and t.  Concatenates s & t with colon and returns pointer to	*/
  /*  this string in new_s.  Makes a 3-section new string from present	*/
  /*  time, effective username running the process, and id_string.	*/
  /*  Returns pointer to this string in new_t.				*/
  /*    Function return values is FALSE if we couldn't get dynamic	*/
  /*  memory needed for string fooling.  new_s and new_t try hard to	*/
  /*  contain the best content possible under that circumstance...	*/
  /*  Function also returns FALSE if id_string is "too long" and had to	*/
  /*  be truncated.  Calling programs can test their id_string lengths	*/
  /*  against MAXLEN_PROGRAM_ID_STRING to avoid this (there is some	*/
  /*  extra space available in this routine, so strings longer than	*/
  /*  MAXLEN_PROGRAM_ID_STRING may end up whole anyway)			*/
  /*    Memory pointed to by new_s and new_t is malloc'ed and can be	*/
  /*  freed.  								*/
  /*    NB: If there were no malloc problems, the string pointed to by	*/
  /*  new_t begins with \n because of the way error_ (in outer;		*/
  /*  eventual consumer of many error messages)	interacts with this	*/
  /*  routine.  Start from new_t+1 if this is undesirable		*/
  /*									*/
  /*    This routine cannot call err to report trouble since this	*/
  /*  routine is usually called by err					*/
{
#define TIME_PREFIX "This message issued at "
#define USERNAME_PREFIX "Effective username of process: "
#define NO_USERNAME "cannot be determined"
#define LEN_NO_USERNAME 20/* Don't know how to sizeof() in preprocessor	*/
  /*  Assume L_cuserid value is represents max len of username returned	*/
  /*  via getpwuid (it really should...)				*/
#if LEN_NO_USERNAME > L_cuserid
#define SIZE_USERNAME LEN_NO_USERNAME
#else
#define SIZE_USERNAME L_cuserid
#endif

/*  Allow for some room if for some reason we can't concatenate s & t	*/
#define SLOP 256
/*  1 = size of \n							*/
/*  25 = size of ctime return less its \0 (but including its \n)	*/
/*  1 = size of \n							*/
/*  1 = size of \n							*/
/*  1 = size of \0							*/
/*  static since we could be using it in times of problem getting memory */
  static char buf[1 + \
		 sizeof(TIME_PREFIX) + 25 + \
		 sizeof(USERNAME_PREFIX) + SIZE_USERNAME + 1 + \
		 MAXLEN_PROGRAM_ID_STRING + 1 + 1 + SLOP];
  char *buf1,*buf2,*uname;
  int len_s,len_t;
  time_t timbuf;
  struct passwd *passwd;
  Logical retval = TRUE;

  len_s = (s == NULL) ? 0 : strlen(s);
  len_t = (t == NULL) ? 0 : strlen(t);
    
  if (len_s == 0)
    if (len_t == 0) 
	/*  Don't need to reset len_s					*/
      s = "Unknown error (add_id_to_err called w/ null or zero len args)";
    else {
      buf1 = t;
      t = s;
      s = buf1;
      len_s = len_t;
      len_t = 0;
    }
  
  buf2 = buf;

  if (len_t == 0)
    buf1 = s;
  else {
      /*  +1 for space between s & t; + 1 for \0			*/
    buf1 = (char *)malloc(len_s + len_t + 2);
    if (buf1 == NULL) {
      retval = FALSE;
      buf1 = s;
	/*  < takes care of trailing \0; + 2 for ": "			*/
      if (len_t < SLOP + 2) {
	  /*  Emulate a bit of error_ formatting			*/
	buf[0] = ':';
	buf[1] = ' ';
	strcpy(&buf[2],t);
      } else 
	buf2 = t;
    } else {
      strcpy (buf1,s);
      if ( ! isspace(*(s + len_s - 1)) )  *(buf1 + len_s++) = ' ';
      strcpy (buf1 + len_s,t);
    }
  }

  if (buf2 == buf) {
    buf2[0] = '\n';
    buf2[1] = '\0';
#if VMS
    uname = cuserid(NULL);
#else
    if (  (passwd = getpwuid(geteuid())) == NULL  )
      uname = NO_USERNAME;
    else
      uname = passwd->pw_name;
#endif
    strcat(buf2,TIME_PREFIX);
    time(&timbuf);
    strcat(buf2,ctime(&timbuf));
    strcat(buf2,USERNAME_PREFIX);
    strcat(buf2,uname);
    strcat(buf2,"\n");
    strncat(buf2,id_string,MAXLEN_PROGRAM_ID_STRING+SLOP);
    strcat(buf2,"\n");
    retval = (strlen(id_string) <= MAXLEN_PROGRAM_ID_STRING+SLOP);
  }

  *new_s = buf1;
  *new_t = buf2;
  return retval;
}

void errn(s,i)
char *s;
int i;
{
  static char intbuf[11];

  sprintf(intbuf,"%d",i);
  err(s,intbuf);
  return;
}

int get_integer_attribute(ptr)
/* Input is string supposedly of form "attribute=integer_string"	*/
/*  Verify format (in particular, that integer_string is legal).  If	*/
/*  so, return value of integer_string					*/

char *ptr;
{

  char *p;
  int len,ierr;

  if (  (p = strchr(ptr,'=')) == NULL  )
				err ("No \"=\" in attribute string ",ptr);

  switch (ierr = sscanf(p+1,"%d", &len)) {
    case EOF:
      err ("No value after \"=\" in attribute string ",ptr);
    case 0:
      err("Illegal integer after \"=\" in attribute string ",ptr);
    case 1:
      break;
    default:
      errn("Unexpected return from sscanf in get_integer_attribute.  Return=",
									  ierr);
  }

  return len;
}



/*  Because of troubles w/ Apache 2 web server and double slashes,	*/
/*  consider an object remote not only if it begins w/double slash	*/
/*  (old way), but also if it begins with /a.b.c, where a, b, & c 	*/
/*  are non-empty, non-period-containing strings (new way).  		*/
/*  This means that subdirs of objects directories may not have the 	*/
/*  a.b.c form.  Agreed to by RCG in email 31 Dec 07			*/

int is_a_remote_object_old_way(s)
char *s;
{
  if (strstr(s,REMOTE_OBJECT_PREFIX_OLD) == s) 
				return strlen(REMOTE_OBJECT_PREFIX_OLD);
  return 0;
}

int is_a_remote_object_new_way(s)
char *s;
{
/*  Interesting issue of REMOTE_OBJECT_HOST_TERMINATOR_CHAR = '.'	*/
/*  (or ':', for that matter) not dealt with either here or in utils.h  */
/*  (as of Oct 08)							*/
  char *dot1,*dot2,*end;
  int len_remote_prefix;

  if (strstr(s,REMOTE_OBJECT_PREFIX_NEW) != s) 
			return 0; 	/* doesn't start w/ slash	*/
  len_remote_prefix = strlen(REMOTE_OBJECT_PREFIX_NEW);
  end = strchr(s+len_remote_prefix,REMOTE_OBJECT_HOST_TERMINATOR_CHAR);
  if (end == NULL) end = s + strlen(s);
  dot1 = strchr(s+len_remote_prefix,'.');
  if (dot1 == NULL) return 0;		/* no .s			*/
  if (dot1 >= end) return 0;		/* not in host portion		*/
  if (dot1 == s+1) return 0;		/* a empty			*/
  dot2 = strchr(dot1+1,'.');
  if (dot2 == NULL) return 0;		/* only 1 .			*/
  if (dot2 >= end) return 0;		/* not in host portion		*/
  if (dot2 == dot1+1) return 0;		/* b empty			*/
  if (*(dot2+1) == '\0') return 0;	/* c empty			*/
  if (*(dot2+1) == '.') return 0;	/* c empty			*/
  return len_remote_prefix;
}

int is_a_remote_object(s)
char *s;
{
/*	Return 0 if NOT a remote object					*/
/*	Return length of prefix to be skipped to get at node name if	*/
/*		it IS a remote object					*/
/*	This means the following 2 less-than-ideal things work:		*/
/*			if (is_a_remote_object) ...			*/
/*				and					*/
/*			if (is_a_remote_object == 1) remote new way	*/
/*			if (is_a_remote_object == 2) remote old way	*/
  int i;

  i = is_a_remote_object_old_way(s);
  return (  (i == 0) ? is_a_remote_object_new_way(s) : i  );
}



Logical is_a_varname(s)
char *s;
{
/*  Ancient agreement of original "band of 4" said that varnames would	*/
/*  henceforth have only characters that would be legal in a c variable	*/
/*  names.  This code taken from Harbison & Steele, sec 12.1		*/
  char ch;
  if ((ch = *s++) == '\0') return FALSE;
  if (   ! (isalpha(ch) || ch == '_')   ) return FALSE;
  while ((ch = *s++) != '\0') {
    if (   ! (isalnum(ch) || ch == '_')   ) return FALSE;
  }
  return TRUE;
}

Logical is_a_leap_year(year)
int year;
{
/*  Algorithm from http://www.nist.gov/y2k/documents.htm#leapyear */
  return (year%4 == 0) && ((year%100 != 0) || (year%400 == 0));
}

int strcasecmp_wjs(str1,str2)
char *str1,*str2;
{
  char *ptr1,*ptr2;
  ptr1 = str1;
  ptr2 = str2;
  while ((*ptr1 != '\0') && (*ptr2 != '\0')) {
    if (toupper(*ptr1) != toupper(*ptr2)) break;
    ptr1++;
    ptr2++;
  }
  return toupper(*ptr1) - toupper(*ptr2);
}

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;

  if ( (str1 == NULL) && (str2 == NULL) && (str3 == NULL) ) return NULL;

  len=0;
  if (str1 != NULL) len += strlen(str1);
  if (str2 != NULL) len += strlen(str2);
  if (str3 != NULL) len += strlen(str3);

  out = (char *)malloc(len+1);

  if (out == NULL) {
      /*  We couldn't get memory.  					*/
    if (errstring == NULL) return NULL;
    sprintf (errbuf,
      "Failed 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;
  }
  return NULL;	/*  Never get here, but compiler wants return val	*/
}

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 *add_to_buffer(dest,sizeofdest,source,errmsg)
char *source,*dest,*errmsg;
int sizeofdest;		/* act buf size; = (useful size) + (1 for \0)	*/
{
  int len;

  len = strlen(dest);
  return copy_into_buffer(dest+len,sizeofdest-len,source,errmsg);
}


char *copy_into_buffer(dest,sizeofdest,source,errmsg)
char *source,*dest,*errmsg;
int sizeofdest;		/* act buf size; = (useful size) + (1 for \0)	*/
{
  int sourcelen,sourcefragsize;
  char *init_char;
  char msg1[] = "    Tried to fit ";
  char msg2[] = " chars (including trailing \\0) into a ";
  char msg3[] = " char buffer\n    Next line is ";
    /*  Each of next 2 need to be big enough to hold biggest int	*/
    /*  Too lazy to think about exact #					*/
  char nsource_chars[15],ndest_chars[15];
    /*  Initial 1 is max size of init_char.  +1 is \0 concluding	*/
    /*  errbuf1.  -3 is the \0s concluding msgNs			*/
  char err1_part1[1 + sizeof(msg1) + sizeof(msg2) + sizeof(msg3) + 
	    sizeof(nsource_chars) + sizeof(ndest_chars) + 1 - 3];
  char *err2,*err1_part2;

    /*  = is a problem because of need for trailing \0			*/
  if (  (sourcelen = strlen(source)) < sizeofdest  )
						return strcpy(dest,source);

  

  init_char = (  errmsg[strlen(errmsg) - 1] == '\n'  ) ? "" : "\n";
    /*  -1 accounts for trailing \0 in source				*/
  sprintf (err1_part1,"%s%s%d%s%d%s",
			init_char,msg1,sourcelen+1,msg2,sizeofdest,msg3);

    /*  Try to print bad string, but obviously we don't want to print	*/
    /*  a runaway.  Decided to try to print what would have fit plus	*/
    /*  next 10 chars, but must be able to copy truncated portion. 	*/
    /*  Sigh (Can't just store a \0 into proper spot because offending	*/
    /*  string might be a literal or some such...gee, it'd be nice if	*/
    /*  one could try the store and then, if it failed, do the copy)	*/
  if (  sourcelen > (sourcefragsize = sizeofdest + 10)  ) {
    err2 = (char *)malloc(sourcefragsize + 1);
    if (err2 == NULL) {
      err2 = source;
    } else {
      strncpy(err2,source,sourcefragsize);
      err2[sourcefragsize+1] = '\0';
    }
  } else {
    err2 = source;
  }

  err1_part2 = (err2 == source) ? 
			"offending string\n" :
			"beginning of offending string\n";

    /*  2nd line to err is analyzed for bad chars and funky whitespace	*/
    /*  so try to confine it to problem string (else our formating gets	*/
    /*  "analyzed")							*/
  err (buildstring(errmsg,err1_part1,err1_part2,"fixed_str_ovf_msg1"),err2);
  return NULL;		/*  Should not get to this statement		*/
}

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;
/*   [Looks like this was written w/o knowledge of realloc. WJS Jul 03]	*/
/*   [Changed malloc to realloc per above comment. WJS Dec 06]		*/
/*   [Did it so well that I regressed to malloc code.  WJS Aug 07]	*/
/*	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 (or special key-see below), lengthen_str	*/
/*   returns NULL.  Otherwise, function err is called with errstring	*/
/*   and other information.  Exception: if errstring is a special	*/
/*   string (currently "Use special entries"), it is not used in error	*/
/*   messages.  It indicates that this particular string extension	*/
/*   cannot fail because too many other strings are being lengthened.	*/
/*	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 100 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 table entries			*/
/*	To free memory allocated to the special buffer (see "errstring"	*/
/*   doc above) call free_lengthened_str_special()			*/
/*									*/
/*   Details:								*/
/*	in_str_buf is looked up in an internal table.  If not found,	*/
/*   a new entry is made.  Exception: if errstring is a special string,	*/
/*   a special, extra table entry is used.  There is only one such	*/
/*   extra entry.  Idea is to be able to use lengthen_str to diagnose	*/
/*   its own errors from previous calls					*/
/*	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.						*/
/*	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 100
  static char *addr[NLENGTHEN_STR_TBL+1];
  static char *bufptr[NLENGTHEN_STR_TBL+1];
  static int size[NLENGTHEN_STR_TBL+1];
    /*  The zeros to which the c standard will init all these static	*/
    /*  arrays will be FALSEs in the case of the next array, which will	*/
    /*  protect us in case somebody tries a free before an allocate.	*/
    /*  However, it's not nice for the caller to depend on that...	*/
  static Logical free_addr[NLENGTHEN_STR_TBL+1];
  static Logical spare_buffer_initialized = FALSE;

  static int ntbl = 0;

  int itbl,len,len1,len2;
  char *tmp;
  char errbuf[80];	/*  Too lazy to count				*/
  Logical use_spare_buffer;

    /*  USE_SPECIALS defined in utils.h					*/
  if (errstring == NULL) use_spare_buffer = FALSE;
  else use_spare_buffer = (strcmp(USE_SPECIALS,errstring) == 0);

  if (use_spare_buffer) itbl = NLENGTHEN_STR_TBL;
  else
    for ( itbl = 0; itbl < ntbl; itbl++ )
      if (in_str_buf == addr[itbl]) break;

  if ((itbl == ntbl) || (use_spare_buffer && ! spare_buffer_initialized)) {
      /*  Need to add a table entry					*/
    if (  (ntbl == NLENGTHEN_STR_TBL) && ! use_spare_buffer  )
      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;
    if (use_spare_buffer) spare_buffer_initialized = TRUE;
    else ntbl++;
  }
    /* Note: can make new entry in block above and remove it here	*/
    /* during same call to lengthen_str...				*/
  if (append_str1 == NULL) {
    if (free_addr[itbl]) free(addr[itbl]);
    if (use_spare_buffer)
      spare_buffer_initialized = FALSE;
    else {
	/*  Remove this table entry by copying last one over it &	*/
	/*  shortening list						*/
      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];
      /*    +1 for terminating null. "size"s are str lens, not buf sizes*/
    addr[itbl] = (char *)malloc(size[itbl]+1);
    if (addr[itbl] == NULL) {
      if (  (errstring == NULL) || use_spare_buffer  ) return NULL;
      sprintf (errbuf,
	"Error trying to get %d (0x%X) bytes of memory\n  Purpose = ",
	size[itbl], size[itbl]);
      err (errbuf,errstring);
    }
    if (tmp != NULL) {
      strcpy(addr[itbl],tmp);
	/*    Free old string if appropriate.				*/
      if (free_addr[itbl]) free(tmp);
    }
      /*  Adjust pointer to first free character in string		*/
    bufptr[itbl] += (addr[itbl] - tmp);
      /*  Mark this string to be freed if another extension is needed	*/
    free_addr[itbl] = TRUE;
  }

  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] = '\0';
  }

  return addr[itbl];
}

void free_lengthened_str_special()
{
  lengthen_str(NULL,NULL,NULL,0,USE_SPECIALS);
  return;
}

void free_lengthened_str(in_str_buf)
char *in_str_buf;
{
  lengthen_str(in_str_buf,NULL,NULL,0,"... free_lengthened_str");
  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;
}

Logical get_logical_value (string)
char *string;
/*  Return 1 if string is synonym for TRUE, 0 for FALSE, -1 if not a synonym */
{
if (string != NULL)
  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 TRUE;
      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 FALSE;
      break;
    default:		/*  For completeness				*/
      ;
      break;
  }

return NOT_VALID;
}

Logical get_logical_from_string (string,errmsg1,errmsg2)
char *string,*errmsg1,*errmsg2;
/*  get_logical_value + error msgs.  Still returns -1 if NG (if err	*/
/*  does not terminate program...)					*/
{
  char *errtmp1,*errtmp2;

  switch (get_logical_value(string)) {
    case 1:
      return TRUE;
    case 0:
      return FALSE;
    default:
      ;
  }

  errtmp1 = buildstring("Illegal logical value ",string,"\n   ",
	" get_logical_from_string");
  errtmp2 = buildstring(errmsg1,errmsg2,NULL," get_logical_from_string");
  err (errtmp1,errtmp2);

/*  Lines below for completeness (err does not return)			*/
  free (errtmp1);
  free (errtmp2);

  return NOT_VALID;
}

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.  It's also	*/
/*  a JGOFS object if it leads off with http node delimiters.  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_DEVICE) == 0) {
    f->source_type = CREATE_NULL_DATA;
    nxt = NULL;

  } else {
      /*  All characters are part of source string			*/
    f->source_type = is_a_remote_object(source) ? JGOFS_OBJECT : 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	*/
    /*  to 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_character(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 == NULL) err("lookup_wjstbl received null wjstbl","");
  if (wjstbl[0] == '\0') return NULL;

  key_len = strlen(s);
  ptr = (char *)malloc(key_len + 3);
  if (ptr == NULL) 
    errn ("Could not allocate mem for copy of key in lookup_wjstbl. Nbytes=",
	   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;
}

Logical extract_wjstbl(synonym,max_len_synonym,variable,wjstbl)
char *variable,*synonym,*wjstbl;
int max_len_synonym;
/*  Returns NOT_VALID if synonym string is truncated			*/
/*	else								*/
/*	     TRUE if translation found.  *synonym contains translation	*/
/*	     FALSE 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 FALSE;
    else {
      strncpy (synonym,variable,max_len_synonym);
      if (strlen(variable) <= max_len_synonym) return FALSE;
      else return NOT_VALID;
    }
  } 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<max_len_synonym;i++) {
      if (*trans_loc == white_space) break;
      *synonym_ptr++ = *trans_loc++;
    }
    *synonym_ptr = '\0';

    if (i < max_len_synonym) return TRUE;
    else return NOT_VALID;
  }
}

int *level_splits(handle,maxlev,nvars)
int handle,maxlev,nvars;
/*  Returns NULL if memory allocation problem				*/
/*  Otherwise, returns a pointer to an array whose indices are the	*/
/*    object levels, and whose values are the "first variable number"	*/
/*    on that level; eg, if array[2]=6, then the first variable on	*/
/*    level 2 is variable number 6.  Counts start from 0; ie,		*/
/*    array[0]=0.  For convenience, array[maxlev+1] is defined, and	*/
/*    set = to the number of variables in the object (= nvars)		*/
/*  Apologies for using "maximum" for 1 dummy and "number of" for the	*/
/*    other, but this way stays parallel to other pieces of software	*/
{
  int *firstvarlevel;
  int i;

  firstvarlevel = (int *) malloc ((maxlev+2)*sizeof(int));
  if (firstvarlevel == NULL) return NULL;

    /*    Assume jdblevel return is legit.  Returns -1 if i		*/
    /*    too big but that should be OK.  Returns ? (0?) if		*/
    /*    handle "invalid", but 0 is a legit return, too...		*/
  for (i=0; i<=maxlev+1; i++) firstvarlevel[i]=0;
    /*    Loop per variable in object, getting counts			*/
  for (i=0; i<nvars; i++) firstvarlevel[jdblevel_(&handle,&i)+1]++;
    /*	  Change per-level count into "break points"			*/
  for (i=0; i<=maxlev; i++)
    firstvarlevel[i+1] += firstvarlevel[i];

  return firstvarlevel;
}


char **string_sets_intersection(s1,num_s1,s2,num_s2)
char *s1[],*s2[];
int num_s1,num_s2;
  /*    Given 2 sets of pointers to strings (and the number of pointers	*/
  /*  in each), return set of pointers to strings such that the 	*/
  /*  strings pointed to in the return set consist of strings in both	*/
  /*  input sets.							*/
  /*    Returned set of pointers is in dynamically allocated memory	*/
  /*  NULL is returned if memory could not be allocated			*/
  /*    Returned set of pointers is terminated by a NULL pointer.	*/
  /*    No 2 returned pointers point to identical strings		*/
{
  char **intersection;
  int i,j,k,num_intersection;

  i = (num_s1 < num_s2) ? num_s1 : num_s2;
  i = (i + 1) * sizeof (char *);
  intersection = (char **)malloc(i);
  if (intersection == NULL) return NULL;

  num_intersection = 0;
  for (i=0; i<num_s1; i++) {
    for (j=0; j<num_s2; j++) if (strcmp(s1[i],s2[j]) == 0) break;
      /*  Must have found s1[i] in s2 for s1[i] to be in intersection	*/
    if (j < num_s2) {
      for (k=0; k<num_intersection; k++)
				if (strcmp(s1[i],intersection[k]) == 0) break;
	/*  Only add to list if it was NOT there previously		*/
      if (k >= num_intersection) intersection[num_intersection++] = s1[i];
    }
  }
  intersection[num_intersection] = NULL;
  return intersection;
}


char **string_sets_union(s1,num_s1,s2,num_s2)
char *s1[],*s2[];
int num_s1,num_s2;
  /*    Given 2 sets of pointers to strings (and the number of pointers	*/
  /*  in each), return set of pointers to strings such that the 	*/
  /*  strings pointed to in the return set consist of strings in either	*/
  /*  input sets.							*/
  /*    Returned set of pointers is in dynamically allocated memory	*/
  /*  NULL is returned if memory could not be allocated			*/
  /*    Returned set of pointers is terminated by a NULL pointer.	*/
  /*    No 2 returned pointers point to identical strings		*/
{
  char **union_;
  int i,k,num_union;

  i = num_s1 + num_s2 + 1;
  i *= sizeof (char *);
  union_ = (char **)malloc(i);
  if (union_ == NULL) return NULL;

  num_union = 0;
  for (i=0; i<num_s1; i++) {
    for (k=0; k<num_union; k++) if (strcmp(s1[i],union_[k]) == 0) break;
      /*  Only add to list if it was NOT there previously		*/
    if (k >= num_union) union_[num_union++] = s1[i];
  }
  for (i=0; i<num_s2; i++) {
    for (k=0; k<num_union; k++) if (strcmp(s2[i],union_[k]) == 0) break;
      /*  Only add to list if it was NOT there previously		*/
    if (k >= num_union) union_[num_union++] = s2[i];
  }
  union_[num_union] = NULL;
  return union_;
}


Logical char_sets_disjoint(str_ptrs,n_str_ptrs)
char *str_ptrs[];
int n_str_ptrs;
{
  int i,j;
  for (i = 0; i < n_str_ptrs; i++)
    for (j = i+1; j < n_str_ptrs; j++)
      if (strpbrk(str_ptrs[i],str_ptrs[j])) return FALSE;
  return TRUE;
}


int keyword_pairs_to_wjstbl 
		(wjstbl_ptr_ptr,keyword_pair_str,sep_array,err_msg_id_str)
char **wjstbl_ptr_ptr;
char *keyword_pair_str;
char *sep_array[PARSE_SEPARATOR_ARRAY_SIZE];
char *err_msg_id_str;

/*  Makes a wjstbl from parsed keyword/value string			*/
/*  Returns # pairs in wjstbl as function return, and pointer to	*/
/*    (dynamically allocated) wjstbl in provided wjstbl_ptr_ptr arg	*/
/*  NOTE: returns  - (# pairs)  if some keywords began w/special chars	*/
/*    and some did not							*/

/*  See lookup_wjstbl routines in utils.c for info about wjstbls	*/

/* Parse things like							*/
/*	a=b; *foo* = bar; ENABLE_FEATURE_X				*/
/*	[x1]KEYWORD1[x1] [y1 VALUE1z1] [x2]KEYWORD2[x2] [y2 VALUE2z2]	*/
/*  x, y, and z are all characters, but come from strings of perm-	*/
/*  issible characters.  Also allow for a pair of quotation characters	*/
/*  to permit literal representation of x, y, & z chars in VALUEs	*/
/*  (quotation characters may be nested to allow for THEIR literal	*/
/*  representation)							*/
/*	The sets of quotation, x, y, and z characters  must be disjoint	*/
/*  (all but y for logical reasons; y by design choice).  The x		*/
/*  characters before and after keywords must match (parsing design	*/
/*  choice).  x characters must not be legal JGOFS variable name	*/
/*  characters (parsing	design choice - presence/absence of x		*/
/*  characters can then distinguish selections/projections from other	*/
/*  a=b arguments.							*/
/*	Unquoted leading and trailing white space is removed from	*/
/*  KEYWORDs and VALUEs.  If a VALUE begins and ends with a quotation	*/
/*  character, that pair of characters is removed			*/
/*	x characters are removed from KEYWORDs				*/
/*	If a KEYWORD is supplied without a VALUE, the string TRUE is	*/
/*  assigned as the value						*/

/*  Coded from create_wjstbl (in ioopen_routines.c, in defgb release)	*/

{
  int paircnt,outsize;
  char *key,*val;
  char *pair_ptr,*next_pair_ptr,*copy_keyword_pair_ptr;
  char *next_item_ptr;
  char *wjstbl_ptr,*wjstbl_start;
  char *ptr;
  char *msg = NULL;
  char wjstbl_separators[2] = {WJSTBL_SEPARATOR,WJSTBL_WHITE_SPACE};
  Logical any_specials = FALSE;
  Logical any_non_specials = FALSE;

  if (sep_array == NULL) 
    err("No separator array provided for keyword/value parsing","");
  if (sep_array[BETWEEN_PAIRS] == NULL)
    err("No between_pairs separators provided for keyword/value parsing","");
  if (sep_array[WITHIN_PAIRS] == NULL)
    err("No within_pairs separators provided for keyword/value parsing","");
  if ( ! char_sets_disjoint(sep_array,PARSE_SEPARATOR_ARRAY_SIZE)  )
    err ("Separator sets for keyword/value parsing not disjoint",
	 err_msg_id_str);

  if (keyword_pair_str == NULL)
    err("Null keyword/values provided for keyword/value parsing","");
  if (*keyword_pair_str == '\0')
    err("No keyword/values provided for keyword/value parsing","");

    /*  Pair count as determined below can be high since it counts	*/
    /*  pair separators that might be "quoted out".  Hopefully somebody	*/
    /*  is not quoting out thousands of separators			*/
  paircnt=0;
  next_pair_ptr = keyword_pair_str;
  while (
	  (
	    next_pair_ptr = strpbrk(next_pair_ptr,sep_array[BETWEEN_PAIRS])
	  )
		!= NULL
        )								    {
    next_pair_ptr++;
    paircnt++;
  }
  paircnt++;

    /*    Output buffer							*/
    /*  Sizing on the high side because of paircnt (above) and because	*/
    /*  there is stuff in input string (white space, special chars,	*/
    /*  etc) that is not moved to output buffer				*/
    /*    +3 for 2 WJSTBL_SEPARATORs and 1 WJSTBL_WHITE_SPACE		*/
    /*    +1 for '\0'							*/
  outsize = strlen(keyword_pair_str) + 3*paircnt + 1;
  wjstbl_start = (char *) malloc (outsize);
  if (wjstbl_start == NULL) {
    msg = buildstring  ("Could not allocate memory for keyword parsing\n",
			err_msg_id_str,
			"Number of bytes to be allocated = ",
			" creating err msg 1");
    errn (msg,outsize);
  }
  wjstbl_ptr = wjstbl_start;

    /*  Need a copy of input string because we are going to slice it up	*/
    /*  and need to be able to write into it (as well as provide un-	*/
    /*  sliced version in error messages).				*/
  strdupl (&copy_keyword_pair_ptr,keyword_pair_str,err_msg_id_str);
  next_pair_ptr = copy_keyword_pair_ptr;
  

  paircnt=0;

    /*  Loop per entry (pair) in input string				*/
  while (  (pair_ptr  = nxttok (next_pair_ptr,
				sep_array[BETWEEN_PAIRS],
				&next_pair_ptr,
				sep_array[QUOTES],
				TRUE,
				TRUE)		   	   )
			!= NULL					) {

    key = nxttok(pair_ptr,
		 sep_array[WITHIN_PAIRS],
		 &next_item_ptr,
		 NULL,
		 TRUE,
		 TRUE);
    if (key == NULL) {
      msg = buildstring ("No keyword in pair somewhere in string ",
			 keyword_pair_str,NULL,
			 " creating err msg 2");
      err (msg,err_msg_id_str);
    }
    if (   (key = strip_space(key))  == NULL   ) {
      msg = buildstring ("No keyword in pair somewhere in string ",
			 keyword_pair_str,NULL,
			 " creating err msg 2a");
      err (msg,err_msg_id_str);
    }
    if (strpbrk(key,wjstbl_separators) != NULL) {
      msg = buildstring("Table separator appears in keyword ",
		key,
		"\n  NB: separator probably an unprintable or space char",
		" creating err msg 5");
      err (msg,err_msg_id_str);
    }
      /*  If keyword begins w/'special' character find its mate and 	*/
      /*  remove both							*/
    if (strchr(sep_array[SPECIALS],*key) == NULL) {
      any_non_specials = TRUE;
    } else {
      any_specials = TRUE;
      ptr = strchr(key+1,*key);
      if (ptr == NULL) {
	msg =buildstring ("Unpaired special characters enclosing keyword ",
			  key,NULL,
			  " creating err msg 6");
	err (msg,err_msg_id_str);
      }
      if (*(ptr+1) != '\0') {
	msg =buildstring ("Paired special characters do not enclose keyword ",
			  key,NULL,
			  " creating err msg 7");
	err (msg,err_msg_id_str);
      }
      key++;
      *ptr = '\0';
    }
    if (lookup_wjstbl(key,wjstbl_ptr) != NULL) {
      msg =buildstring ("keyword appears twice in list\n   keyword = ",key,NULL,
			" creating err msg 8");
      err (msg,err_msg_id_str);
    }

    val = nxttok(next_item_ptr,
		 sep_array[WITHIN_PAIRS],
		 &next_item_ptr,
		 sep_array[QUOTES],
		 TRUE,
		 TRUE);
    if (val != NULL) val = strip_space(val);
    if (val == NULL) val = "TRUE";
    if (strpbrk(val,wjstbl_separators) != NULL) {
      msg = buildstring("Table separator appears in string ",
		val,
		"\n  NB: separator probably an unprintable or space char",
		" creating err msg 15");
      err (msg,err_msg_id_str);
    }


    ptr = nxttok(next_item_ptr,
		 sep_array[WITHIN_PAIRS],
		 &next_item_ptr,
		 sep_array[QUOTES],
		 TRUE,
		 TRUE);
    if (ptr != NULL) {
      msg = buildstring("Pair appears to be a triple somewhere in ",
			keyword_pair_str,NULL,
			" creating err msg 16");
      err (msg,err_msg_id_str);
    }

    *wjstbl_ptr++ = WJSTBL_SEPARATOR;
    while (*key != '\0') *wjstbl_ptr++ = *key++;
    *wjstbl_ptr++ = WJSTBL_SEPARATOR;
    while (*val != '\0') *wjstbl_ptr++ = *val++;
    *wjstbl_ptr++ = WJSTBL_WHITE_SPACE;

    paircnt++;
  }

  *wjstbl_ptr = '\0';
  *wjstbl_ptr_ptr = wjstbl_start;

  free(copy_keyword_pair_ptr);

  return (any_specials && any_non_specials) ? -paircnt : paircnt;
}

char *set_option_string(option_name,option_default,wjstbl,n_wjstbl_args_left)
int *n_wjstbl_args_left;
char *wjstbl,*option_name,*option_default;
/*  Return "something" corresponding to option_name as follows:		*/
/*	If found in wjstbl, return value from there (& adjust # wjstbl	*/
/*	  args still to be processed)					*/
/*	If not found there, look for env var "option_name"		*/
/*	If not found there, return option_default passed to us.		*/
/*    Appropriate setting of default (eg, NULL) allows determination	*/
/*	of whether anything was explicitly provided			*/
{
  char *ptr,*term_char;
    /*  extract_wjstbl wants buffer from us.  malloc one per call so	*/
    /*  multiple calls to this routine do not result in a single buffer	*/
    /*  being overwritten.  This introduces a memory leak, since a	*/
    /*  caller of this routine does not know when the pointer returned	*/
    /*  should be freed							*/
  char *option_val;
  int size_wjstbl_val = 100;

  if (*n_wjstbl_args_left > 0) {
    option_val = (char *)malloc(size_wjstbl_val);
    if (option_val == NULL) 
      errn ("Could not get space for wjstbl val buffer. Nbytes = ",
	    size_wjstbl_val);
    switch (extract_wjstbl(option_val,size_wjstbl_val,option_name,wjstbl)) {
      case NOT_VALID:
	ptr = buildstring("Size of value string for keyword ",
			  option_name,
			  " exceeds buffer size.  Buffer size = ",
			  "err msg in set_option_string");
	errn (ptr,size_wjstbl_val);
      case TRUE:
	(*n_wjstbl_args_left)--;
	return option_val;
      default:
	free (option_val);
	break;
    }
  }

  if (   (option_val = getenv(option_name))  ==  NULL   )
    option_val = option_default;

  return option_val;
}
