/*		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)		*/
/*	    void analy_source(f,separators,dirstring)			*/
/*	    char *buildstring(str1,str2,str3,errstring)			*/
/*	    char *copy_into_fixed_len_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)				*/
/*	    int get_integer_attribute(ptr)				*/
/*	    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,nlevs,nvars)			*/
/*	    char *lookup_wjstbl (s,wjstbl)				*/
/*	    char *nxttok(start,separators,start_next,quotes,		*/
/*		signif_consec_seps,nested_quotes) 			*/
/*	    char *rem_delims (ptr,delims)				*/
/*	    char *strcasecmp_wjs(inptr1,inptr2)				*/
/*	    char *strdupl(outptr_ptr,inptr,errstring)			*/

#define UTILS_VERSION "utils  version 1.9a  5 Jul 2005"
/*  5 Jul 05.			WJS					*/
/*	get_integer_attribute						*/
/*		[Begin 1.9a]						*/
/* 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;
}

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 = strlen(s);
  len_t = 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/ 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;
}

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 *copy_into_fixed_len_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]	*/
/*	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];
    addr[itbl] = (char *)malloc(size[itbl]);
    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 = 
     (strncmp(source,REMOTE_OBJECT_PREFIX,strlen(REMOTE_OBJECT_PREFIX)) == 0) ?
			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_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;
}

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,nlevs,nvars)
int handle,nlevs,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[nlevs] is defined, and	*/
/*    set = to the number of variables in the object (= nvars)		*/
{
  int *firstvarlevel;
  int i;

  firstvarlevel = (int *) malloc ((nlevs)*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<=nlevs; 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<=nlevs; i++)
    firstvarlevel[i+1] += firstvarlevel[i];

  return firstvarlevel;
}
