/*  dct ("dictionary") routines for JGOFS data system			*/
/*				G Flierl  pre 1997			*/


/* ====================  search sequence: =============
 //machine or /machine (see is_a_remote_object in utils.c)
 path/tail -  tail in path/.objects [local]
 /jgpath/tail - tail in OBJECT_ROOT/jgpath/.objects
 /jgpath/tail - tail in OBJECT_ROOT/jgpath/.remoteobjects
 path/exec - executeable program METHOD_ROOT/path/exec
         [local || dctcount<8 (not first pass)]
 path/datafile - use def method on local file [local]

 Also see dctsearch_operation_and_errs.doc

   ===================================================== */


/*  DETAILS ABOUT dct v 2.0 ROUTINE CHANGES (Aug 2012) AT END OF SOURCE */



#define DCT_VERSION "dct version 2.0a  8 May 2013"

/*   8 May 2013.  v 2.0a  WJS						*/
/*    Bug fix:  Parenthesis removal code assumed that if there is an	*/
/*	initial paren, there is a trailing paren and they match.  As	*/
/*	the song says, it ain't necessarily so				*/
/*    By the way, v 2.0 was "let out" without completing status		*/
/*      handling.  Don't know how much we'll do this time around	*/
/*		[begin v 2.0a]						*/

/*  28 Jan 2013.  v 2.0  WJS						*/
/*    Fix bad strdupl calls.  Amazingly, most worked!			*/
/*    Begin work to implement USE_MERGEPARAMS				*/
/*    Fiddle w/final default (to  def ).  Probably won't test it any	*/
/*	more than replaced code was tested				*/
/*  29 Aug 2012.  v 2.0  WJS						*/
/*    More changing over to dynamic memory allocation scheme, while	*/
/*	removing code that assumes it can write into input args		*/
/*    Eliminate some fixed-size temp buffers				*/
/*    Write new dctsearch which returns pointers to correctly sized	*/
/*	buffers and a shell which behaves the old way.  Eventually	*/
/*	need to change dctsearch callers (jdb & serv, at least) to	*/
/*	directly call the new routine; else problem continues		*/
/*    Write new scandct and shell for same reasons as new dctsearch	*/
/*	Nothing "in JGOFS system" outside of dct.c calls scandct, so	*/
/*	this should just be a precaution.  Another reason is to re-	*/
/*	port the status of scandct's putenv call (see v 1.2b comment)	*/
/*    New mergeparams, getting rid of mysterious [] stuff, along w/its	*/
/*	other problems.  Needs to be controlled w/compile-time switch	*/
/*	in case dctsearch users need the [] stuff (neither Chris nor	*/
/*	Bob remembers it at all)					*/
/*		[begin v 2.0]						*/

/*  24 Jun 2010.  v 1.5a  WJS						*/
/*    First-pass status for DCTSEARCH_METHOD_HISTORY should be		*/
/*	_WILLNOTBETESTED rather than _UNTESTED				*/
/*   3 Jul 2009.  v 1.5  WJS						*/
/*    Ignore lines w/# in col 1						*/
/*    Ignore 0-length object name specs and all-whitespace object name	*/
/*	specs.  Should probably error, and should probably check more	*/
/*	but not now							*/
/*		[needs core.h 2.0b]					*/
/*  29 Feb 2008.  v 1.4  WJS						*/
/*    Change way we determine remote object				*/
/*  18 Aug 2007.  v 1.3  WJS						*/
/*    Take a shot at diagnosing dctsearch failure			*/
/*      Not backward compatible - keep old behavior by compiling with	*/
/*		DCTSEARCH_EXTENDED_ERROR_INFO=0				*/
/*	  dctsearch returns 0/1 for failed/succeeded.  Failure info is	*/
/*	also copied into the string in which the caller expects to get	*/
/*	the filespec of the method, etc.  That info up until now has	*/
/*	been 1 of 2 strings.  In the vast majority of situations, that	*/
/*	string is "Cannot be found in dictionary:"			*/
/*	  We cannot expand the status space since programs explicitly	*/
/*	test for status 0.  Instead, we will hope that programs do not	*/
/*	explicitly test the return strings, and we will put info there.	*/
/*	  We are further constrained since there can be no length check	*/
/*	on the return buffer.  Therefore, we will attempt to keep the	*/
/*	length short, resulting in some cryptic stuff			*/
/*    Add dctsearch_trace to return a full, readable trace		*/
/*    Considerable "housecleaning" (loop that reads .objects files,	*/
/*      switching from "notfound" variable to "found" variable, etc)	*/
/*		[Begin 1.3]						*/
/*  10 Dec 2004.  v 1.2c  WJS						*/
/*    Bug fix: incorrect strdupl call					*/
/*		[Begin 1.2c]						*/
/*  25 Aug 2004.  v 1.2b  WJS						*/
/*    serv had its own putenv.  Recode dct to use the system one	*/
/*	Not checking return value yet - that's a job for dct v 2.0	*/
/*    Put MAX_SERV_DCT_BUF (formerly MAX_BUF) in core.h so serv &	*/
/*	dct can be coordinated						*/
/*    Standardize version stuff						*/
/*		[Begin 1.2b]						*/
/*  11 Feb 2004.  v 1.2a  WJS						*/
/*    Declare putenv to avoid diagnostic.  #include stdlib since man	*/
/*	page says it "goes with" putenv - don't know why.		*/
/*    Remove declaration for getenv since it's unused			*/
/*		[Begin 1.2a]						*/
/*  17 Jan 2004.  v 1.2  WJS						*/
/*    Divider between obj defn and env vars was 1st blank in obj defn	*/
/*      Arrange things not to count blanks between parens		*/
/*    Put a void type on mergeparams (this mod made on synthesis'	*/
/*	version 6 Nov 2000 by Chris Hammond)				*/
/*		[Begin 1.2]						*/

#include "core.h"
  /*  Next value limits number of times search code is executed		*/
  /*  Typically, there is one pass to find the object defn, and another	*/
  /*  to find the method.  However, each defn is allowed to point to	*/
  /*  another, so we need a limit.  Has "always" been 8			*/
#define MAX_DCTCOUNT 8
  /*  Next value controls whether dctsearch uses the old mergeparams	*/
  /*  code.  User would need to spec parameter as 1 in the compilation	*/
  /*  to effect the regression						*/
#ifndef USE_MERGEPARAMS
#define USE_MERGEPARAMS 0
#endif
/*
#define OBJECT_ROOT "/d3/glenn/jg/objects"
#define METHOD_ROOT "/d3/glenn/jg/methods/"
#define DEBUG
*/

int putenv();
  /*  from utils.c (jgofs.a)						*/
char *buildstring();
void errn();
int is_a_remote_object();
char *strdupl();
char *strip_space();

  /*  scandct -> search status communication globals (sigh)		*/
int scandct_errno;
Logical scandct_dict_exists;
Logical scandct_dict_accessible;

  /*  status history pointers.  static so that we can dynamically	*/
  /*  allocate space once no matter how many times we're called		*/
  /*  (Actually, too lazy to free every time if I left things dynamic;	*/
  /*  this way I "leak" this much memory exactly once)			*/
static char *status_history[DCTSEARCH_NUMBER_HISTORIES];
static char *userobj_status = NULL;
static char *obj_status,*remobj_status,*method_status;
static char *datfile_status,*default_method_status;
/************************************************************************/

char *dct_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[] = DCT_VERSION"/"COREH_VERSION;
  return version;
}

Logical remove_encasing_parens(outstring,instring,quote_char)
  /*  Returns FALSE if there were unmatched quote_chars or unmatched	*/
  /*  parens								*/
char **outstring;
char *instring;
char quote_char;
{
  char *first,*last,*ptr;
  Logical init_paren_matches_close_paren,open_quote_in_effect;
  int net_open_parens;

  *outstring = instring;
  if (instring == NULL) return TRUE;
  if (*instring != '(') return TRUE;

  first = instring;
  init_paren_matches_close_paren = TRUE;
  net_open_parens = 1;
  open_quote_in_effect = FALSE;
  last = first + strlen(first) - 1;
  for (ptr = first+1; ptr <= last; ptr++) {
    if (*ptr == quote_char) open_quote_in_effect = (! open_quote_in_effect);
    if ( ! open_quote_in_effect) {
      if (*ptr == '(') net_open_parens++;
      if (*ptr == ')') net_open_parens--;
	    /*  Idea: if net_open_parens reaches 0 at any place before the end	*/
	    /*  then the initial paren that got us into this loop is not	*/
	    /*  matched with any final paren.  We could quit, but we continue	*/
	    /*  for syntax-checking purposes					*/
      if (  (net_open_parens == 0) && init_paren_matches_close_paren && 
								(ptr < last)  )
		init_paren_matches_close_paren = FALSE;
    }
  }
  if (open_quote_in_effect || (net_open_parens != 0)) return FALSE;
  if (init_paren_matches_close_paren) {
    *last = '\0';
    first++;
    first = strip_space(first);
    *outstring = first;
  }
  return TRUE;
} 

int mergeparams1(result,user_sel_proj,dict_params)
/*    Concatenates dict_params and user_sel_proj, in that order	(NOT	*/
/*	arbitrary - consider obj defn of rs(defgb) - defgb MUST be	*/
/*	first in resulting param string)				*/
/*  Each input string has whitespace stripped.				*/
/*  If either input string enclosed in parens, remove them		*/
/*  Strip white space again						*/
/*  If both strings are present, separate with a comma			*/
/*  If there are no characters in the result (input null, input empty,	*/
/*	etc), or if there is an error, result is NULL			*/
/*    Function return is an error status				*/
/*	[to be defined]							*/
/*    Differences from mergeparams					*/
/*	mergeparams assumes dict_params has parens and user_sel_proj	*/
/*    does not, and does not strip whitespace is not an issue		*/
/*	mergeparams truncated dict_params to an empty string		*/
/*	mergeparams treated []-enclosed strings in dict_params in a	*/
/*    a special fashion; moving them to the end of the result string	*/
/*    under certain circumstances (selection in user_sel_proj?		*/
char **result;
char *user_sel_proj,*dict_params;
{
  int len_user_sel_proj,len_dict_params;
  char *start_user_sel_proj,*start_dict_params;
  char *result_ptr;
  char *free1,*free2,*ptr;

  /* We are forced into considering parens embedded in selections.	*/
  /* In turn, this leads to the question of whether such parens are	*/
  /* part of the syntax or part of the selection.  At this stage, I	*/
  /* just have no idea what the "allowed" selection syntax is.	*/
  /* Accordingly, I'm arbitrarily defining and using a quote char-	*/
  /* acter.  Of course, this belongs in a .h file, etc.  Oh well ...	*/
#define QUOTE_CHAR '"'

  *result = NULL;

  if (user_sel_proj == NULL) {
    if (dict_params == NULL) return MP1_NO_INPUT;
    if (*dict_params == '\0') return MP1_NO_INPUT;
  }
  if ((dict_params == NULL) && (*user_sel_proj == '\0')) return MP1_NO_INPUT;

  free1 = strdupl (&start_user_sel_proj,user_sel_proj,"mergeparams1 temp copy 1");
  free2 = strdupl (&start_dict_params,dict_params,"mergeparams1 temp copy 2");

  start_user_sel_proj = strip_space(start_user_sel_proj);
  start_dict_params = strip_space(start_dict_params);

  if ((start_user_sel_proj == NULL) && (start_dict_params == NULL)) {
    free (free1);
    free (free2);
    return MP1_NO_SIGNIF_INPUT;
  }

  if ( ! remove_encasing_parens
		(&start_user_sel_proj,start_user_sel_proj,QUOTE_CHAR)  )
					        return MP1_SYNTAX_ERROR_ARG1;
  if ( ! remove_encasing_parens
		(&start_dict_params,start_dict_params,QUOTE_CHAR)  )
					        return MP1_SYNTAX_ERROR_ARG2;

  if ((start_user_sel_proj == NULL) && (start_dict_params == NULL)) {
    free (free1);
    free (free2);
    return MP1_NO_SIGNIF_INPUT;
  }
      
  if (start_user_sel_proj == NULL) {
    strdupl(result,start_dict_params,"mergeparams1 returning arg 2");
  } else if (start_dict_params == NULL) {
    strdupl(result,start_user_sel_proj,"mergeparams1 returning arg 1");
  } else {
    len_user_sel_proj = strlen(start_user_sel_proj);
    len_dict_params = strlen(start_dict_params);
    *result = (char *)malloc(len_user_sel_proj + 1 + len_dict_params + 1);
    if (*result == NULL) {
      free (free1);
      free (free2);
      return MP1_MALLOC_FAILURE;
    }
    strcpy(*result,start_dict_params);
    * (*result + len_dict_params)  =  ',';
    strcpy (  (*result + len_dict_params + 1) , start_user_sel_proj  );
  }
  free (free1);
  free (free2);
  return MP1_OK;
}

int handle_dict_params(params_to_update,method_or_recursive_objspec,dict_results)
/*    Essentially splits dict results on (, if any, and merges what's	*/
/*  after the ( with the params_to_update (presumably the user-spec'ed	*/
/*  params), replacing the latter.  params_to_update must be dynamic-	*/
/*  ally allocated - pre-updated string is freed if there is an update	*/
/*    Returns mergeparams1 status or -1 if using old mergeparams	*/
/*    Returns what precedes any ( in a dynamically allocated string	*/
/*  pointed to by method_or_recursive_objspec				*/
char **params_to_update,**method_or_recursive_objspec;
char *dict_results;
{
  char *paren_ptr;
  char *tmp;
  int status;

  strdupl(method_or_recursive_objspec,dict_results,"in handle_dict_params");
  paren_ptr = strchr(*method_or_recursive_objspec,'(');
  if (paren_ptr == NULL) return MP1_OK;

#if USE_MERGEPARAMS
  mergeparams(*params_to_update,paren_ptr);
  return -1;
#else
  tmp = *params_to_update;
  status = mergeparams1(params_to_update,tmp,paren_ptr);
  free (tmp);
  *paren_ptr = '\0';
  return status;
#endif

}


int scandct1(result,which_objects_file,object_spec)
/*  which_objects_file is either  .objects  or  .remoteobjects		*/
/*	(as of dct 2.0 and within JGOFS system				*/
/*  object_spec, if local, includes directory for  which_objects_file 	*/
/*	(explicitly or implicitly (eg, off server's OBJECT_ROOT).	*/
/*	There may be another wrinkle or 2				*/
char *which_objects_file,*object_spec;
char **result;
{
/*  Next buffer now (dct v 2.0)  used only for holding .objects file 	*/
/*  records.  Not sure how that needs coordination w/serv any more	*/
  char tdic[MAX_SERV_DCT_BUF];

  FILE *fdic;
  char *s,*t;
  char *dctname,*dict_search_string;
  char *sp;
  int nb,l,putenv_status;
  Logical found;
    /*  Next comment based on the idea that putenv does NOT make a copy	*/
    /*  of its argument [from man putenv on talia.whoi.edu, a linux box	*/
    /*   "Thus, it is an error is to call putenv() with  an  auto-	*/
    /*    matic  variable  as the argument, then return from the call-	*/
    /*    ing function while string is still part of the environment."]	*/
    /*  Next is a volatile pointer that is set to dynamically allocated	*/
    /*  memory.  Thus, the pointer will be lost upon function return,	*/
    /*  and the memory cannot be freed.  This is OK, since idea is to	*/
    /*  have this memory survive exit from scandct so env vars will be	*/
    /*  "present" when/if calling program needs them.  Cannot use a	*/
    /*  static buffer here in case scandct is called more than once	*/
  char *list_of_env_vars;

/*  Make copies since original code assumes it can write into input	*/
/*  args								*/
  strdupl(&s,which_objects_file,"Copying scandct1 arg 1");
  strdupl(&t,object_spec,"Copying scandct1 arg 2");

#ifdef DEBUG
  printf("Searching >%s< for >%s<\n",s,t);
#endif

/*  Routine is expected to return the putenv status, but we might not	*/
/*  end up calling putenv.  Return "putenv normal", without bothering	*/
/*  to try to figure out why 0 is normal.  It's documented that way,	*/
/*  but I don't think the c library has it parametrized			*/
  putenv_status = 0;

/*  Default return value to NULL, which takes care of the various error	*/
/*  conditions, as well as the actual "not found"			*/
  *result = NULL;

  sp = strrchr(t,'/');
  if (sp) {
    *(sp++)='\0';
    if (strip_space(sp) == NULL) return putenv_status;
    dctname = buildstring(t,"/",s,"building dictionary name string");
  } else {
    if (strip_space(t) == NULL) return putenv_status;
    sp = t;
    dctname = s;
  }
  dict_search_string = buildstring(sp,"=",NULL,"adding = to object name");
  free (t);

#ifdef DEBUG
  printf("Open >%s< and look for >%s<\n",dctname,dict_search_string);
#endif

  scandct_errno = 0;
  scandct_dict_exists = TRUE;
  scandct_dict_accessible = TRUE;
  fdic = fopen(dctname,"r");
  if (fdic == NULL) {
    scandct_errno = errno;
    scandct_dict_exists = (access(dctname,F_OK) == 0);
    scandct_dict_accessible = (access(dctname,R_OK) == 0);
    free (s);
    if (dctname != s) free (dctname);
    return putenv_status;
  }
  free (s);
  if (dctname != s) free (dctname);

  found = FALSE;
  while (fgets(tdic,MAX_SERV_DCT_BUF-1,fdic) != NULL) {
/*     If/when more restrictions get added here, note that		*/
/*     we guarantee to ignore empty lines and lines consisting		*/
/*     entirely of white space.  Not an issue in the forseeable		*/
/*     future because there is always an = in the search string.	*/
    if (tdic[0] != DCTSEARCH_COMMENT_CHAR) {
      if (strstr(tdic,dict_search_string) == tdic) {
	found = TRUE;
	break;
      }
    }
  }
  free (dict_search_string);
  if ( ! found) {
    if ( ! feof(fdic) ) scandct_errno = errno;
    fclose(fdic); /*  Prefer read failure status to any close status	*/
    return putenv_status;
  }
  fclose(fdic);   /* ... and don't care a lot about close status anyway	*/

  t = strchr(tdic,'=') + 1;
  l = strlen(t);
  if (t[l-1] == '\n')   t[--l] = '\0';
  nb=0;
  for(sp=t; sp<t+l; sp++) {
    if (*sp == '(') nb++;
    else if (*sp == ')') nb--;
    else if ((*sp == ' ') && (nb == 0)) break;
  }
  if (sp < t+l) {
    *sp = '\0';
    strdupl(&list_of_env_vars,sp+1,
			"copying list of env vars from end of .objects entry");
    sp = strtok(list_of_env_vars," ");
    while (sp) {
      putenv_status = putenv(sp);
      if (putenv_status != 0) break;
      sp = strtok(NULL," ");
    };
  };
  strdupl(result,t,"Copying returned portion of .objects entry");
  return putenv_status;
}

void mergeparams(req,dct)
char *req,*dct;
/*		Problems w/this routine (WJS Jul 12)			*/
/*	Definite: tmp buffer is fixed size and input buffers are of	*/
/*  unknown size, leading to buffer overflow possibility.  Work COULD	*/
/*  be done at compile time to get a "good" buffer size - maybe		*/
/*	Probable: routine seems to want to know if there are projec-	*/
/*  tions in the param lists being merged.  If so, routine has NOT been	*/
/*  ified to ID char string selections as being "not projections"	*/
/*	Possible: Not sure if redundant parens are handled		*/
/*	Possible: Not sure if selections within parens are done well	*/
/*  enough.  Paren nesting and selection detection are independent and	*/
/*  I don't think they should be. 					*/
{
  char *j,*k;
  int i,dctopt,reqproj,nb;
  char tmp[MAX_SERV_DCT_BUF];

  /* no params in dictionary */
  if(dct==NULL)return;

  /* no params requested */
  if(*req==0){
    j=dct+1;
    k=req;
    while(*j){
      if(*j == '[' || *j == ']') j++;
      else *(k++)= *(j++);
    };
    *dct=0;
    *(k-1)=0;
    return;
  };

  /* dictionary params first */

  nb=0;
  dctopt=1;
  j=dct;
  k=tmp;
  while(*(++j)){
    switch(*j){
      case '[':nb++;dctopt=0;break;
      case ']':nb--;break;
      default:if(nb==0) *(k++) = *j;break;
      };
  };
  if(k>tmp) if(*(k-1) == ')') *(k-1)=',';

  j=req-1;
  reqproj=0;
  nb=0;
  i=1;
  while(*(++j)){
    *(k++)= *j;
    switch(*j){
      case '(':nb++;break;
      case ')':nb--;break;
      case '<':
      case '>':
      case '=':i=0;break;
      case ',':if(nb==0 && i==1)reqproj=1;
	i=1;
	break;
      };
  };
  *k=0;

  if(reqproj || dctopt) {
    while(j=strstr(tmp,",,"))strcpy(j,j+1);
    strcpy(req,tmp);
    *dct=0;
    return;
  };

  j=dct;
  nb=0;
  while(*(++j)){
    switch(*j){
      case '[':nb++;break;
      case ']':nb--;break;
      default:if(nb) *(k++) = *j;break;
      };
  };
  if(k>tmp) { 
    if(*(k-1) == ',') *(k-1)=0; else *k=0;
    while(j=strstr(tmp,",,"))strcpy(j,j+1);
    strcpy(req,tmp);
  } else *req=0;
  *dct=0;
  return;
}

void set_status_history_buffers()
{
  int i;

  i = (MAX_DCTCOUNT+1) * sizeof(char);
  userobj_status = (char *) malloc (i);
  obj_status = (char *) malloc (i);
  remobj_status = (char *) malloc (i);
  method_status = (char *) malloc (i);
  datfile_status = (char *) malloc (i);
  default_method_status = (char *) malloc (i);
  if (  (userobj_status == NULL) ||
	(obj_status == NULL) ||
	(remobj_status == NULL) ||
	(method_status == NULL) ||
	(datfile_status == NULL) ||
	(default_method_status == NULL)
     )
		errn ("Could not get memory for status buffers. Nbytes=",i);
  status_history[DCTSEARCH_USEROBJ_HISTORY] = userobj_status;
  status_history[DCTSEARCH_OBJ_HISTORY] = obj_status;
  status_history[DCTSEARCH_REMOBJ_HISTORY] = remobj_status;
  status_history[DCTSEARCH_METHOD_HISTORY] = method_status;
  status_history[DCTSEARCH_DATFILE_HISTORY] = datfile_status;
  status_history[DCTSEARCH_DEFMETHOD_HISTORY] = default_method_status;

  return;
}

char *pack_status_history(history)
char *history[];
{
  int i;
  char *ptr,*start_untesteds,*out_ptr;
  char end_delimiter;

    /*  +1 is for separator characters; 3 = delimiters & trailing \0	*/
    /*  Note that old string was 30 chars long...			*/
  static char out_buffer[DCTSEARCH_NUMBER_HISTORIES * (MAX_DCTCOUNT + 1) + 3];

    /*  Reduce length of status strings by removing trailing _UNTESTED	*/
    /*  and _WILLNOTBETESTED						*/
  for (i=0;  i < DCTSEARCH_NUMBER_HISTORIES;  i++) {
    ptr = history[i];
    if (*ptr == DCTSEARCH_STATUS_WILLNOTBETESTED) {
	/*  If "will not be tested" in first pass, will never be tested	*/
	/*  Leave 1 "placeholder" and remove the rest			*/
      start_untesteds = ptr + 1;
    } else { 
      start_untesteds = NULL;
	/*  Trim trailing .s.  Start from 2nd char so if whole string	*/
	/*  is .s , we still have 1 left				*/
      while ( *(++ptr) != '\0') {
	if (*ptr == DCTSEARCH_STATUS_UNTESTED) {
	  if (start_untesteds == NULL) start_untesteds = ptr;
	} else {
	  start_untesteds = NULL;
	}
      }
    }
    if (start_untesteds != NULL) *start_untesteds = '\0';
  }

  ptr = DCTSEARCH_HISTORY_DELIMITERS;
  *out_buffer = *(ptr++);
  end_delimiter = *ptr;

  out_ptr = out_buffer + 1;
  for (i=0;  i < DCTSEARCH_NUMBER_HISTORIES;  i++) {
    ptr = history[i];
    while (*ptr != '\0') 
      *(out_ptr++) = *(ptr++);
    *(out_ptr++) = DCTSEARCH_HISTORY_SEPARATOR;
  }
  *(--out_ptr) = end_delimiter;
  *(++out_ptr) = '\0';

  return out_buffer;
}

char set_status(exists,accessible,io_error)
Logical exists,accessible,io_error;
  /*  DCTSEARCH_STATUS_OK 	   File exists & is accessible		*/
  /*  DCTSEARCH_STATUS_NOSUCHFILE  Does not exist (or dirs inaccessible) */
  /*  DCTSEARCH_STATUS_NOACCESS    Exists; not accessible		*/
  /*  DCTSEARCH_STATUS_IOERR	   DCTSEARCH_STATUS_OK but read problem	*/
{
  char status;
  if (exists) {
    if (accessible) {
      status = (io_error) ? DCTSEARCH_STATUS_IOERR : DCTSEARCH_STATUS_OK;
    } else {
      status = DCTSEARCH_STATUS_NOACCESS;
    }
  } else {
    status = DCTSEARCH_STATUS_NOSUCHFILE;
  }
  return status;
}  

int dctsearch1(obj,method,params,localflag)
char *obj,**method,**params;
int localflag;
  /*  Do not  free  the string returned into method unless function	*/
  /*  returns  1 .  If the function returns  0 , method may point to	*/
  /*  an error message string.						*/
{
  int dctcount;

  char *pathreq;
  char *parreq;
  char *method_candidate;

  char *tmp;
  char *ptr;
  Logical dctfound,free_tmp;
  int i,status;

/*  Next are returns from scandct1					*/
  int putenv_status;
  char *dict_search_results;

  if (userobj_status == NULL) set_status_history_buffers();

  strdupl(&pathreq,obj,"dct: copying object string"); 
  strdupl(&parreq,*params,"dct: copying parameter string"); 

  dctcount=0;

  while (dctcount < MAX_DCTCOUNT) {

    dctfound = FALSE;
    for (i=0;  i < DCTSEARCH_NUMBER_HISTORIES;  i++) {
      if (localflag)
        status_history[i][dctcount] = DCTSEARCH_STATUS_UNTESTED;
      else
	status_history[i][dctcount] =
		( (i == DCTSEARCH_USEROBJ_HISTORY) || 
		  (i == DCTSEARCH_DATFILE_HISTORY) || 
		  (i == DCTSEARCH_METHOD_HISTORY) || 
		  (i == DCTSEARCH_DEFMETHOD_HISTORY)  )
			?
		DCTSEARCH_STATUS_WILLNOTBETESTED : DCTSEARCH_STATUS_UNTESTED;
    }

      /* //machine ... 							*/
#ifdef DEBUG
    printf("test // ... %s**%s\n",pathreq,parreq);
#endif
    if (is_a_remote_object(pathreq) != 0)  {
      *method = pathreq;
      *params = parreq;
      return 1;
    }

      /* check for object dictionary 					*/
    if (localflag)  {
#ifdef DEBUG
    printf("test local obj... %s**%s\n",pathreq,parreq);
#endif
      putenv_status = scandct1(&dict_search_results,".objects",pathreq);
      if (putenv_status != 0) {
	*method = strerror(putenv_status);
	return 0;
      }
      if (dict_search_results != NULL)  {
	status = handle_dict_params(&parreq,&method_candidate,dict_search_results);
/*    do something with status */
	if (is_a_remote_object(method_candidate) != 0)  {
	  *method = method_candidate;
	  *params = parreq;
	  return 1;
	}
	  
	pathreq = method_candidate;
	dctfound = TRUE;
      }
      userobj_status[dctcount] = 
			set_status(scandct_dict_exists,
				   scandct_dict_accessible,
				   (scandct_errno != 0)
				  );
    }
    
      /* check system .objects dictionary 				*/
    if (  (! dctfound)  &&  (*pathreq == '/')   )  {
      if (*pathreq) {
	tmp = buildstring(OBJECT_ROOT,"/",pathreq,"Building objroot subdir");
        free_tmp = TRUE;
      } else {
        tmp = OBJECT_ROOT;
	free_tmp = FALSE;
      }
#ifdef DEBUG
    printf("test sys local ... %s**%s\n",tmp,parreq);
#endif
      putenv_status = scandct1(&dict_search_results,".objects",tmp);
      if (free_tmp) free (tmp);
      if (putenv_status != 0) {
	*method = strerror(putenv_status);
	return 0;
      }
      if (dict_search_results != NULL)  {
	status = handle_dict_params(&parreq,&method_candidate,dict_search_results);
/*    do something with status */
	if (is_a_remote_object(method_candidate) != 0)  {
	  *method = method_candidate;
	  *params = parreq;
	  return 1;
	}
	pathreq = method_candidate;
	dctfound = TRUE;
      }
      obj_status[dctcount] =
			set_status(scandct_dict_exists,
				   scandct_dict_accessible,
				   (scandct_errno != 0)
				  );
    }
    
      /* check system  .remoteobjects dictionary 			*/
    if (  (! dctfound)  &&  (*pathreq == '/')   )  {
      if (*pathreq) {
	tmp = buildstring(OBJECT_ROOT,"/",pathreq,"Building objroot subdir2");
	free_tmp = TRUE;
      } else {
        tmp = OBJECT_ROOT;
	free_tmp = FALSE;
      }
#ifdef DEBUG
    printf("test sys global ... %s**%s\n",tmp,parreq);
#endif
      putenv_status = scandct1(&dict_search_results,".remoteobjects",tmp);
      if (free_tmp) free (tmp);
      if (putenv_status != 0) {
	*method = strerror(putenv_status);
	return 0;
      }
      if (dict_search_results != NULL)  {
	status = handle_dict_params(&parreq,&method_candidate,dict_search_results);
/*    do something with status */
	if (is_a_remote_object(method_candidate) != 0)  {
	  *method = method_candidate;
	  *params = parreq;
	  return 1;
	}
	pathreq = method_candidate;
	dctfound = TRUE;
      }
      remobj_status[dctcount] =
			set_status(scandct_dict_exists,
				   scandct_dict_accessible,
				   (scandct_errno != 0)
				  );
    }
    
      /* check for system method (not first time through, though)	*/
    if(   (! dctfound)  &&  (localflag || dctcount > 0)   )    {
      tmp = buildstring
		(METHOD_ROOT,pathreq,NULL,"Building system method string");
#ifdef DEBUG
    printf("test sys meth ... %s**%s\n",tmp,parreq);
#endif
      method_status[dctcount] = 
	set_status( (access(tmp,F_OK) == 0),
		    (access(tmp,X_OK) == 0),
		    FALSE
		  );
      if (method_status[dctcount] == DCTSEARCH_STATUS_OK)  {
	*method = tmp;
	*params = parreq;
	return 1;
      }
    }

      /* local file - use default method 				*/
    if(  (! dctfound) && localflag   )    {
#ifdef DEBUG
    printf("test def file ... %s**\n",pathreq);
#endif
      datfile_status[dctcount] = 
	set_status( (access(pathreq,F_OK) == 0),
		    (access(pathreq,R_OK) == 0),
		    FALSE
		  );
      if (datfile_status[dctcount] == DCTSEARCH_STATUS_OK)  {
	tmp = buildstring (   "(", pathreq, ")",   "parenthesizing pathreq" );
	status = handle_dict_params(&parreq,&method_candidate,tmp);
/*    do something with status */
	default_method_status[dctcount] = 
		set_status( 	(access(DCTSEARCH_DEFAULT_METHOD,F_OK) == 0),
		    		(access(DCTSEARCH_DEFAULT_METHOD,X_OK) == 0),
		    		FALSE
			  );
        if (default_method_status[dctcount] == DCTSEARCH_STATUS_OK)  {
	  strdupl(&pathreq,DCTSEARCH_DEFAULT_METHOD,
					"Copying default method name");
	  dctfound = TRUE;
	}
      }
    }

      /* error - return info in buffer where method name was expected	*/
    if (! dctfound)  {
      if (DCTSEARCH_EXTENDED_ERROR_INFO) {
	  /*  Turn char arrays into strings				*/
	for (i=0;  i < DCTSEARCH_NUMBER_HISTORIES;  i++)
					status_history[i][dctcount+1] = '\0';
	*method = pack_status_history(status_history);
      } else {
	*method = "Cannot be found in dictionary:";
      }
      return 0;
    }
    dctcount++;
  }
*method = "Too many iterations in dictionary - check for loops:";
return 0;
}

char *dctsearch_trace()
{
/*  Easiest option is to mod dctsearch to use lengthen_str to build	*/
/*  diagnostics "on the fly", one line per file access.  Make this	*/
/*  static - when caller comes here, give them the pointer		*/
/*  Another approach is to save the _HISTORIES*_MAX_DCTCOUNT file specs	*/
/*  along w/the status buffer.  Easier to do options			*/
/*    Options: by default, do NOT want to include OBJECT_ROOT,		*/
/*  METHOD_ROOT, ".objects", etc for security reasons.  Control this	*/
/*  via ?? can't use a per-object env var!!.  Caller might want		*/
/*  successes, might not.  Might want host name, might not.  Might	*/
/*  want chronological trace or an by-file-type trace.  Might want	*/
/*  reverse chronological trace!					*/
/*    Practically speaking, calling code is going to select one format,	*/
/*  option set, etc.  Human will see output and not be happy, so con-	*/
/*  trol has to be from outside the program.  Further, consider that	*/
/*  if search takes place on remote box, can't be locally controlled	*/
/*    Might just want "easiest"...					*/
  return NULL;
}

int dctsearch(obj,method,params,localflag)
char *obj,*method,*params;
int localflag;
{
  int dctsearch1_status;
  char **dynamic_method,**dynamic_params;
  char *copy_method,*copy_params;

  strdupl(&copy_method,method,"dctsearch: copying method");
  strdupl(&copy_params,params,"dctsearch: copying params");
  dynamic_method = &copy_method;
  dynamic_params = &copy_params;

  dctsearch1_status = dctsearch1(obj,dynamic_method,dynamic_params,localflag);
  free (copy_method);
  free (copy_params);

  if (*dynamic_method != NULL) {
/*    Note potential for buffer overflow in following statement.	*/
/*    That's the main reason to convert to dctsearch1			*/
    strcpy(method,*dynamic_method);
    free (*dynamic_method);
  } else {
    *method = '\0';
  }

  if (*dynamic_params != NULL) {
/*    Note potential for buffer overflow in following statement.	*/
/*    That's the main reason to convert to dctsearch1			*/
    strcpy(params,*dynamic_params);
    free (*dynamic_params);
  } else {
    *params = '\0';
  }

  return dctsearch1_status;
}

int scandct(s,t)
char *s,*t;
{
  char *result_of_dict_lookup;
/*  Note that scandct1 returns status of putenv, but scandct never	*/
/*  dealt w/that before, so for consistency, ignore the status.  Note	*/
/*  that if things are/were able to proceed after a putenv failure,	*/
/*  logical consequences are/were extreme - env vars upon which the	*/
/*  object depends would NOT be in the environment as expected		*/
  scandct1(&result_of_dict_lookup,s,t);
  if (result_of_dict_lookup ==NULL) return 0;
/*  Note potential for buffer overflow in following statement.  That's	*/
/*  the main reason to convert to scandct1				*/
  strcpy(t,result_of_dict_lookup);
  return 1;
}

/*
main(argc,argv)
int argc;
char *argv[];
{
char met[1024],par[1024];
int i;
if(dctsearch(argv[1],met,par,1))
  printf("dctsearch = %s**%s\n",met,par);
else printf("Not found\n");
}
*/

/*		Routine changes dct 2.0 (Aug 2012)			*/
/* scandct -> scandct1							*/
/*	int scandct(s,t)						*/
/*		Returns 1 or 0; overwrites  t  with result of scan	*/
/*	int scandct1(scandct_result,s,t)				*/
/*		scandct_result is  char ** into which is placed a	*/
/*		  pointer to the result of the scan of the dictionary	*/
/*		  In the case where scandct returned 0 as the function	*/
/*		  value, scandct_result points to NULL.  The pointer	*/
/*		  (if non-NULL) is to a dynamically allocated string,	*/
/*		  which can be freed when no longer needed		*/
/*		The return value of scandct1 is the result of the	*/
/*		  attempt by scandct1 to put strings into the environ-	*/
/*		  ment should the object definition require that.  0	*/
/*		  is OK; otherwise the value is an  errno .		*/
/* mergeparams -> mergeparams1						*/
/* dctsearch -> dctsearch1						*/
/*	int dctsearch(obj,method,params,localflag)			*/
/*		Writes into both method and params, which are char *	*/
/*	int dctsearch1(obj,method,params,localflag)			*/
/*		Returns pointers to result strings.  method and params	*/
/*		  are both  char ** .					*/ 
/*		If dctsearch1 was OK (return value != 0), pointers are	*/
/*		  to dynamically allocated strings, which can be freed	*/
/*		  If dctsearch1 was NOT OK, pointers are to string	*/
/*		  constants describing the error, so do NOT free	*/
