/*  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

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


#define DCT_VERSION "dct version 1.5a  24 Jun 2010"

/*  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
/*
#define OBJECT_ROOT "/d3/glenn/jg/objects"
#define METHOD_ROOT "/d3/glenn/jg/methods/"
#define DEBUG
*/

int putenv();
  /*  from utils.c (jgofs.a)						*/
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;
}

int scandct(s,t)
char *s,*t;
{
  FILE *fdic;
  char tdic[MAX_SERV_DCT_BUF],dctname[MAX_SERV_DCT_BUF],*sp;
  int nb,l;
  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;

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

  sp=strrchr(t,'/');
  if(sp){
    *(sp++)=0;
    strcpy(dctname,t);
    strcat(dctname,"/");
    strcat(dctname,s);
  } else {
    strcpy(dctname,s);
    sp=t;
  };
  if (strip_space(sp) == NULL) return 0;
  strcat(sp,"=");

#ifdef DEBUG
  printf("Open >%s< and look for >%s<\n",dctname,sp);
#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);
    return 0;
  }

  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,sp) == tdic) {
	found = TRUE;
	break;
      }
    }
  }
  if ( ! found) {
    if ( ! feof(fdic) ) scandct_errno = errno;
    fclose(fdic); /*  Prefer read failure status to any close status	*/
    return 0;
  }
  fclose(fdic);   /* ... and don't care a lot about close status anyway	*/

  strcpy(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(sp);
      sp = strtok(NULL," ");
    };
  };
  return 1;
}

void mergeparams(req,dct)
char *req,*dct;
{
  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 dctsearch(obj,method,params,localflag)
char *obj,*method,*params;
int localflag;
{
  int dctcount;

  char pathreq[MAX_SERV_DCT_BUF];
  char metreq[MAX_SERV_DCT_BUF];
  char parreq[MAX_SERV_DCT_BUF];
  char tmp[MAX_SERV_DCT_BUF];

  char *ptr;
  Logical dctfound;
  int i;

  if (userobj_status == NULL) set_status_history_buffers();

  COPY_INTO_FIXED_LEN_BUFFER(pathreq,obj,"dct: copying object string"); 
  COPY_INTO_FIXED_LEN_BUFFER(parreq,params,"dct: copying parameter string"); 

  *method = '\0';
  *params = '\0';

  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)  {
      strcpy(method,pathreq);
      strcpy(params,parreq);
      return 1;
    }

      /* check for object dictionary 					*/
    if (localflag)  {
      strcpy(tmp,pathreq);
#ifdef DEBUG
    printf("test local obj... %s**%s\n",tmp,parreq);
#endif
      if (scandct(".objects",tmp))  {
	ptr = strchr(tmp,'(');
	if (ptr) mergeparams(parreq,ptr);
	if (is_a_remote_object(tmp) != 0)  {
	  strcpy(method,tmp);
	  strcpy(params,parreq);
	  return 1;
	}
	strcpy(pathreq,tmp);
	dctfound = TRUE;
      }
      userobj_status[dctcount] = 
			set_status(scandct_dict_exists,
				   scandct_dict_accessible,
				   (scandct_errno != 0)
				  );
    }
    
      /* check system .objects dictionary 				*/
    if (  (! dctfound)  &&  (*pathreq == '/')   )  {
      strcpy(tmp,OBJECT_ROOT);
      if (*pathreq) {
	strcat(tmp,"/");
	strcat(tmp,pathreq);
      }
#ifdef DEBUG
    printf("test sys local ... %s**%s\n",tmp,parreq);
#endif
      if (scandct(".objects",tmp))  {
	ptr = strchr(tmp,'(');
	if (ptr) mergeparams(parreq,ptr);
	if (is_a_remote_object(tmp) != 0)  {
	  strcpy(method,tmp);
	  strcpy(params,parreq);
	  return 1;
	}
	strcpy(pathreq,tmp);
	dctfound = TRUE;
      }
      obj_status[dctcount] =
			set_status(scandct_dict_exists,
				   scandct_dict_accessible,
				   (scandct_errno != 0)
				  );
    }
    
      /* check system  .remoteobjects dictionary 			*/
    if (  (! dctfound)  &&  (*pathreq == '/')   )  {
      strcpy(tmp,OBJECT_ROOT);
      if (*pathreq) {
	strcat(tmp,"/");
	strcat(tmp,pathreq);
      }
#ifdef DEBUG
    printf("test sys global ... %s**%s\n",tmp,parreq);
#endif
      if (scandct(".remoteobjects",tmp))  {
	ptr = strchr(tmp,'(');
	if (ptr) mergeparams(parreq,ptr);
	if (is_a_remote_object(method) != 0)  {
	  strcpy(method,tmp);
	  strcpy(params,parreq);
	  return 1;
	};
	strcpy(pathreq,tmp);
	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)   )    {
      strcpy(tmp,METHOD_ROOT);
      strcat(tmp,pathreq);
#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)  {
	strcpy(method,tmp);
	strcpy(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(tmp,F_OK) == 0),
		    (access(tmp,R_OK) == 0),
		    FALSE
		  );
      if (datfile_status[dctcount] == DCTSEARCH_STATUS_OK)  {
	strcpy(tmp,"(");
	strcat(tmp,pathreq);
	strcat(tmp,")");
	mergeparams(parreq,tmp);
	default_method_status[dctcount] = 
	set_status( (access(tmp,F_OK) == 0),
		    (access(tmp,X_OK) == 0),
		    FALSE
		  );
        if (default_method_status[dctcount] == DCTSEARCH_STATUS_OK)  {
	  strcpy(pathreq,DCTSEARCH_DEFAULT_METHOD);
	  dctfound = TRUE;
	}
      }
    }

      /* error - return info in buffer where method name was expected	*/
      /* Potential overflow problem, of course...			*/
    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';
	strcpy(method,pack_status_history(status_history));
      } else {
	strcpy(method,"Cannot be found in dictionary:");
      }
      return 0;
    }
    dctcount++;
  }
strcpy(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;
}

/*
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");
}
*/
