/* jdb.c  - forms primary routines for the JGOFS data system		*/
/*              used from jgofs.a					*/

#define JDB_VERSION "jdb version 1.4 20 Jan 2013"

/*  20 Jan 13.  1.4    WJS						*/
/* 	Recode to use dctsearch1. 					*/
/*	[Needs dct 2.0]							*/
/*  13 Jul 12.  1.4    WJS						*/
/*	Allocate some buffers dynamically, including the "method"	*/
/*    and "parameter" buffers used in cooperation w/dct			*/
/*	Check lengths of names and attr strings for overflow		*/
/*	[Needs core.h 2.0e]						*/
/*	[Begin 1.4]							*/
/*									*/
/*   4 Nov 09.  1.3b    WJS						*/
/*	Check that unit number is in range				*/
/*	Check success of memory allocation for unit structure		*/
/*	[Needs core.h 2.0d]						*/
/*	[Begin 1.3b]							*/
/*  21 Mar 09.  1.3a    WJS						*/
/*	jdbread_ should allow trailing blanks in its numeric fields	*/
/*	[Needs core.h 2.0]						*/
/*	[Begin 1.3a]							*/
/*  29 Feb 08.  1.3    WJS						*/
/*	Use is_a_remote_object instead of just initial //		*/
/*	Take responsibility here for putting // after http: rather	*/
/*    than relying on the object spec to start w/ //			*/
/*	Add a test for empty object spec in htopen			*/
/*	[Begin 1.3]							*/
/*									*/
/*  10 Jun 07.  1.2a   WJS						*/
/*	When removing ()s, check that 2nd char is a )			*/
/*	[Needs core.h 1.5a]						*/
/*	[Begin 1.2a]							*/
/*  29 Mar 07.  1.2    WJS						*/
/*	Check for jdbread buffer overflow (why did this take 7 years	*/
/*    since the first buffer length checks, which were harder to do	*/
/*    and far less common??!)						*/
/*	Parametrize nd							*/
/*  16 Dec 06.  1.2    WJS						*/
/*	Buffer comments to limits of system memory instead of to	*/
/*    COMMENTSIZE							*/
/*  13 Oct 06.  1.2    WJS						*/
/*	Mods to jdbread_ handling of non-numeric strings		*/
/*	    1) Use strtod to see if string is numeric.  Old code	*/
/*	       said "numeric if string is digits, +, -, ." which 	*/
/*	       accepts as legal +01-..23 but rejects 1.33E04		*/
/*	    2) Return -9999. for non-numeric strings instead of		*/
/*	       -9999.99.  THIS BREAKS THINGS.  However, w/o it,		*/
/*	      jdbread_ users would consider -9999. a legal value	*/
/*	Parametrize jdb conditions.  Note comment of 1 Oct 99 -		*/
/*	    turns out one of those pieces of "perverse code" is defgb!	*/
/*	[Needs core.h 1.5]						*/
/*	[Begin 1.2]							*/
/*									*/
/*   4 Jun 05.  1.1h    WJS						*/
/*	Need type as part of HTCheckActiveIcon definition		*/
/*  25 Aug 04.  1.1g    WJS						*/
/*	Use core.h							*/
/*	Standardize version stuff					*/
/*	Get rid of strdup (ULTRIX) function				*/
/*  17 Jan 04.  1.1f    WJS						*/
/*	localopen was parsing "par" starting from par+1, missing	*/
/*    initial (.  Seems this was an oversight - compare w/serv.c	*/
/*  18 Dec 03.  1.1e    WJS						*/
/*	Get rid of last(?) 3 exits (in favor of "return error"s)	*/
/*  29 Jul 03.  1.1d    WJS						*/
/*	JDB_DEBUGn conditional compilations				*/
/*	    n=1 means print something to stdout for all non-0 returns	*/
/*	      2 means enable original debug traces			*/
/*	      3 means dump data buffers					*/
/*  25 Jun 03.  1.1d    WJS						*/
/*	Get rid of extra "opening" in error message			*/
/*  15 Nov 01.  1.1c    WJS						*/
/*	'Bout time to fix last year's bug discoveries			*/
/*   4 Nov 00.  1.1c    WJS						*/
/*      Put in a couple of #### ERROR #### comments to point to probs	*/
/*    that should be fixed.  No fixes yet				*/
/*   5 May 00.  1.1b    WJS						*/
/*	"major" and "minor" variable names caused trouble on Mike Car-	*/
/*    uso's Solaris 7 box.						*/
/*   1 Feb 00.  1.1a    WJS						*/
/*	Bug fix: HTDoRead can return a negative value.  Handle this.	*/
/*	Bug fix: -998 comment from 1 Oct not fully implemented. Do it.	*/
/*  18 Oct 99.  1.1    WJS						*/
/*	Skip data before initial &.  Original code assumed jdbopen be-	*/
/*    gan in "&c" mode.  Addition of HTTP/1.0 to GET causes server to	*/
/*    respond w/various info (terminated by empty crlf line) before	*/
/*    beginning w/real data						*/
/*	Do some restructuring... but not enough.			*/
/*   1 Oct 99.  1.1    WJS						*/
/*      Add "Host: " request-header field to GET's, along with HTTP/1.0	*/
/*    ID.  Host: required to implement virtual hosting.  ID seems to be	*/
/*    required in order to make Host: effective... ?  Content of mods	*/
/*    from rfc 2616 and from Glenn's "last" transfer.c code, which ad-	*/
/*    dressed this issue ~Dec 97.					*/
/*	Add some buffer size tests in this part of the code.  Return	*/
/*    -998 if there would be overflow.  There might be perverse code	*/
/*    that thinks "only -999 means bad".  -998 will take the "OK" path	*/
/*    and die in an ugly fashion.  On the other hand, w/o the buffer    */
/*    testing, a failure would have been ugly whether it happened be-	*/
/*    fore or after return from jdb					*/
/*	Define macros to make it compile more cleanly (under VMS anyway)*/
/*	[Begin 1.1]							*/
/*									*/
/*  08 Sep 99.  1.0b.  CLH						*/
/*	Resolve differences from 2 sources - v1.0a and OO version  	*/
/*									*/
/*  15 Aug 99.  1.0a.  WJS						*/
/*    Looks to me like there is an "open file leak" in localopen/	*/
/*    jdbclose interaction.  We open a pipe to the child and one from	*/
/*    it, but only close the one from it.  Try closing the pipe to it	*/
/*    right from the start - don't see that we should be writing to	*/
/*    child...								*/
/*	[Begin 1.0a]							*/
/*  November 3, 1998, clh  system version 1.5 upgrade			*/
/*  November 10, 1998, clh several functions need to be 'typed'	 	*/

#include "core.h"

#ifndef JDB_DEBUG1
#define JDB_DEBUG1 0
#endif
#ifndef JDB_DEBUG2
#define JDB_DEBUG2 0
#endif
#ifndef JDB_DEBUG3
#define JDB_DEBUG3 0
#endif
#if JDB_DEBUG1
#define CHAR_DEBUG1 "DEBUG1 "
#else
#define CHAR_DEBUG1 ""
#endif
#if JDB_DEBUG2
#define CHAR_DEBUG2 "DEBUG2 "
#else
#define CHAR_DEBUG2 ""
#endif
#if JDB_DEBUG3
#define CHAR_DEBUG3 "DEBUG3 "
#else
#define CHAR_DEBUG3 ""
#endif
#if JDB_DEBUG1 || JDB_DEBUG2 || JDB_DEBUG3
#define NO_DEBUG "D"
#else
#define NO_DEBUG "No d"
#endif
  char *debug_flags=NO_DEBUG "ebug flags " CHAR_DEBUG1 CHAR_DEBUG2 CHAR_DEBUG3;

#if SOL || HP
#include <sys/time.h>
#include <sys/resource.h>
#endif

/* added for JGOFS release v1.5 compatibility (not clear which parts of	*/
/*   these OPTIONS & INNEROPTIONS are actually required - WJS)		*/
#include INNEROPTIONS

int dctsearch1();
int is_a_remote_object();	/* utils.c; library			*/
char *copy_into_buffer();	/* utils.c; library			*/

int HTDoConnect();
int HTDoRead();

/*  As new conditions come along, define them in core.h and add them	*/
/*  here.  For various reasons, do NOT put EOF in this list		*/
/*  Note that COMM buffer and comment buffer are NOT the same		*/
int jdb_conditions [] = {
	JDB_CONDITION_ERROR,
	JDB_CONDITION_INTERNAL_BUFOVF,
	JDB_CONDITION_BADATTRLIST,
	JDB_CONDITION_DATA_BUFOVF,
	JDB_CONDITION_NOPIPES,
	JDB_CONDITION_BADFORK,
	JDB_CONDITION_DCTSEARCHFAILURE,
	JDB_CONDITION_VARNOTFOUND,
	JDB_CONDITION_COMMENT_BUF_OUT_OF_MEM,
	JDB_CONDITION_NO_CLOSE_PAREN,
	JDB_CONDITION_BADOBJECTSPEC,
	JDB_CONDITION_HANDLEOUTOFRANGE,
	JDB_CONDITION_NOMEM_FOR_UNITSTRUCT,
	JDB_CONDITION_NOMEM_FOR_COMM_BUFFER,
	JDB_CONDITION_NAMEBUF_OVF,
	JDB_CONDITION_SINGLE_ATTR_BUF_OVF
};

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

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

Logical jdb_known_condition(status)
int status;
{
  int i;
  for (i=0; i<(sizeof(jdb_conditions)/sizeof(int)); i++)
    if (status == jdb_conditions[i]) return TRUE;
  return FALSE;
}

int HTCheckActiveIcon(i)
int i;
{
return 0;
}

void HTClearActiveIcon()
{
}

void HTProgress(msg)
char *msg;
{
/*
fprintf(stderr,"%s\n",msg);
*/
}


/* below values indicating buffers should be minimally 4k */
#define HTSIZE INBUFSIZE
#define MAX_BUF INBUFSIZE
#define NUMNAMES NVAR

/*  attributes names can NOT conveniently be dynamically allocated	*/
/*  They are filled during jdbopen and used during jdbattributes.	*/
/*  There is no enforcement of the sequence  single jdbopen  followed	*/
/*  single jdbattributes .  This enforcement is required to make	*/
/*  attributes a  static *char[NUMNAMES] , freed after the		*/
/*  jdbattributes call							*/
struct OBJECT {
 char htbuff[HTSIZE];
 int htbufflen;
 int htbuffptr;
 int socket;
 int maxlevel,ntotal;
 int lvlpntr[11];
 int rdpntr[NUMNAMES];
 int commentbufsize, comment_size_so_far, next_unreturned_comment_offset;
 char *commentbuf;
 char attributes[NUMNAMES][TOTATTRSIZE], *attributeptr[NUMNAMES];
};

#define MAX_UNITS 5
static struct OBJECT *object[MAX_UNITS]={NULL,NULL,NULL,NULL,NULL};

int bgets(comm,len,obj)
char *comm;
int len;
struct OBJECT *obj;

{
  int op;
  char c;
#if JDB_DEBUG2
  printf("entered bgets\n");
#endif

if (obj->htbufflen < 0) {
#if JDB_DEBUG1
  printf ("Immediate exit from bgets - obj->bufflen = %d on entry\n",
		obj->htbufflen);
#endif
  return 1;
}

op = 0;
while (1) {
  if (obj->htbuffptr >= obj->htbufflen) {
    obj->htbufflen = HTDoRead(obj->socket,obj->htbuff,HTSIZE);
    if (obj->htbufflen <= 0) {
#if JDB_DEBUG1
      printf("Exit from bgets due to non-pos return (%d) from HTDoRead\n",
		obj->htbufflen);
#endif
      obj->htbufflen= -1;
      return 1;
    }
    obj->htbuffptr = 0;
#if JDB_DEBUG3
    printf("<pre>DATA!!!%s!!!ENDDATA\n</pre>",obj->htbuff);
#endif
  }

    /*  Note: RFC 1945 says that "bare" LF or CR (as well as CRLF)	*/
    /*  should be accepted.  Code below doesn't accept bare CR		*/
  if (  (c=obj->htbuff[obj->htbuffptr++]) != '\r'  ) {
    if (op < len) comm[op++] = c;
    else {
      comm[len] = '\0';		/* For sanity's sake...			*/
      obj->htbufflen= -1;
#if JDB_DEBUG1
      printf("Exit from bgets due to buffer ovf.  buf siz = %d\n",
		len);
#endif
      return -1;
    }
  }
  if (c == '\n') {
    comm[--op] = '\0';
    return 0;
  }
}
}  
  
int htopen(comm,par,obj)
char *comm,*par;
struct OBJECT *obj;
{
  char *cp,*host_start;
  char met[MAX_BUF];
  int i,handle;
  int reqd_htbuff_size;

    /*  dataserver host name starts after 1 or 2 initial slashes	*/
  host_start = comm;
  if (*host_start == '/') {
    host_start++;
  } else {
#if JDB_DEBUG1
    printf ("Exit from htopen - initial / missing.  buffer = %s\n",comm);
#endif
    return JDB_CONDITION_BADOBJECTSPEC;
  }
  if (*host_start == '/') host_start++;

  cp=strchr(host_start,'/');
  if (cp==NULL) {
#if JDB_DEBUG1
    printf ("Exit from htopen - / after host missing.  buffer = %s\n",comm);
#endif
    return JDB_CONDITION_BADOBJECTSPEC;
  }
  if (cp == host_start+1) {
#if JDB_DEBUG1
    printf ("Exit from htopen - no object spec aft host.  buffer = %s\n",comm);
#endif
    return JDB_CONDITION_BADOBJECTSPEC;
  }
  *cp =0;

    /*  See jgofsroot/src/lib/jgserv_parametrization.doc for discussion	*/
    /*  about these jg/serv-related magic constants			*/
    /*  13 for GET /jg/serv/; 9 for " HTTP/1.0"; 3 for cr,lf,null	*/
    /*  at end								*/
  reqd_htbuff_size = 13 + strlen(cp+1) + 9 + 3;
  if (reqd_htbuff_size > sizeof(obj->htbuff)) 
					return JDB_CONDITION_INTERNAL_BUFOVF;
  strcpy(obj->htbuff,"GET /jg/serv/");
  strcat(obj->htbuff,cp+1);
  if(par){
    if(*par){
	/*  1 for ?							*/
      reqd_htbuff_size += 1 + strlen(par);
      if (reqd_htbuff_size > sizeof(obj->htbuff))
					return JDB_CONDITION_INTERNAL_BUFOVF;
      strcat(obj->htbuff,"?");
      strcat(obj->htbuff,par);
    };
  };
  strcat(obj->htbuff," HTTP/1.0");

    /* 7 for http://							*/
  if ((7 + strlen(host_start)) > sizeof(met))
					return JDB_CONDITION_INTERNAL_BUFOVF;
  strcpy(met,"http://");
  strcat(met,host_start);
  i=HTDoConnect(met,"HTTP",80,&handle);
  if (i != 0) {
    printf("Connect to %s failed\nHTDoConnect return = %d\n",met,i);
    return JDB_CONDITION_ERROR;
  }
  obj->socket = handle;

  cp=obj->htbuff+strlen(obj->htbuff);
  *(cp++)='\r';
  *(cp++)='\n';
  *cp=0;
    /*  6 for "Host: "; 4 for crs,lfs.  Don't need to count null since	*/
    /*  "old" null, above, is being overwritten by stuff		*/
  reqd_htbuff_size += 6 + strlen(comm+2) + 4;
  if (reqd_htbuff_size > sizeof(obj->htbuff))
					return JDB_CONDITION_INTERNAL_BUFOVF;
  strcat(obj->htbuff,"Host: ");
  strcat(obj->htbuff,comm+2);
  cp = obj->htbuff + reqd_htbuff_size - 5; /* ... + strlen(obj->htbuff)	*/
  *(cp++)='\r';
  *(cp++)='\n';
  *(cp++)='\r';
  *(cp++)='\n';
  *cp=0;

    /*  -1 so null is not included					*/
  write(handle,obj->htbuff,reqd_htbuff_size-1);
  return 0;
}

int localopen(comm,par,obj)
char *comm,*par;
struct OBJECT *obj;
{
#define MAXNUM_PARPTR 250
  char *cp,met[MAX_BUF];
  int i,handle;
  FILE *cin,*cout;
  static int pipeto[2],pipefrom[2];
  char *parptr[MAXNUM_PARPTR],*sp;
  int j,np,childpid,numfds;
#if SOL || HP
  struct rlimit rlp;
#endif
  j=0;
  parptr[j]=comm;
  if(*par){
    parptr[++j]=par;
    np=0;
    sp=par;
    while(*sp){
      switch(*sp){
      case '(':np++;break;
      case ')':np--;break;
      case ',':if(np)break;
	*sp=0;
	if (++j == MAXNUM_PARPTR) return JDB_CONDITION_PARPTR_OVF;
	parptr[j]=sp+1;
	break;
      };
      sp++;
    };
  };
  parptr[++j]=NULL;
  
  if (pipe(pipeto) || pipe(pipefrom) <0)
    {
      perror("no pipes");
      return JDB_CONDITION_NOPIPES;
    }
  switch(childpid = fork())
    {
    case -1:
      perror("bad fork");
      return JDB_CONDITION_BADFORK;
    case 0:
#if JDB_DEBUG2
      printf("got here after fork\n");
#endif
      dup2(pipeto[0],0);
      dup2(pipefrom[1],1);
#if SOL || HP
      getrlimit(RLIMIT_NOFILE,&rlp);
      numfds=rlp.rlim_cur;
#else
      numfds = getdtablesize();
#endif
      for (i=3; i<numfds; i++)
	close(i);
      
      execvp(comm,parptr);
      perror(comm);
      return JDB_CONDITION_ERROR;
    default:
      close(pipeto[0]);
      close(pipefrom[1]);
      /*    ptoch = pipeto[1];*/
      /*    pfrch = pipefrom[0];*/
      obj->socket=pipefrom[0];
      close(pipeto[1]);			/*  wjs addition 15 Aug 99	*/
      /*    setbuf(frch,NULL);*/
      return 0;
    }
  
}

void close_object(tunit)
int tunit;
{
  close (object[tunit]->socket);
  free (object[tunit]);
  object[tunit] = NULL;
  return;
}

int skip_one_line_from_outer(comm,tunit)
int tunit;
char *comm;
{
  switch (bgets(comm,MAX_BUF-1,object[tunit])) {
  case -1:
    printf("&x <b>jdb: data line too big.  Start of line:</b> %s\n",comm);
    return JDB_CONDITION_DATA_BUFOVF;
  case 1:
#if JDB_DEBUG1
    printf ("skip_one_line exiting due to getting a 1 from bgets\n");
#endif
    return JDB_CONDITION_ERROR;
  }
  if (comm[0]=='&')
    if (comm[1]=='x') {
      printf("%s\n",comm);
      close_object(tunit);
      return JDB_CONDITION_ERROR;
    }
  return 0;
}

#if IBM || HP
int jdbopen(unit,obj,names,namesize,num)
#else
int jdbopen_(unit,obj,names,namesize,num)
#endif
int *unit;
char *obj;
char *names;
int *namesize;
int *num;
/*  namesize big enough to include \0					*/
/*  Positive num means namelist is being supplied - rarely used by me	*/
/*	It appears that it builds a projection for the supplied names	*/
/*  In case of negative num, abs(num) = max # names.  jdbopen replaces	*/
/*	this w/the number of names in object 				*/
{
  int i,j,k,numd,tunit,comment_len,len_comm_buffer;
  int open_error_status,remote_object_flag,nitem;
  int http_vers_major,http_vers_minor,http_status,http_text_offset;
  char **dctsearch_param2,**dctsearch_param3;
  char *comm,*met,*par;
  char state,*cp,*ap;
  char *empty = {'\0'};
  struct OBJECT *o;

  for (tunit = 0; tunit < MAX_UNITS; tunit++)
    if (object[tunit] == NULL) break;
  if (tunit >= MAX_UNITS) {
    printf("Out of units\n");
    return JDB_CONDITION_ERROR;
  }
  o = (struct OBJECT *)malloc(sizeof(struct OBJECT));
  if (o == NULL) return JDB_CONDITION_NOMEM_FOR_UNITSTRUCT;
  object[tunit]=o;
  *unit = tunit;

/*  Compute needed length of buffer BEFORE filling it - what a concept!	*/
/*  Algorithm: approx "execute" the "fill" code, below, "in advance"	*/
/*  +1 for ( whether we need it or not; +1 for concluding \0 		*/
/*  Buffer is re-used later in code, requiring the MAX_BUF test		*/
  len_comm_buffer = strlen(obj) +1 +1;
/*  Note that if *num negative (as allowed for in fill loop), we don't	*/
/*  actually execute the loop.  +1 in accumulation is for comma that	*/
/*  separates the names							*/
  numd = *num;
  for (i = 0; i < numd; i++) {
    len_comm_buffer += strlen(names+i*(*namesize)) + 1;
  }
  if (len_comm_buffer < MAX_BUF) len_comm_buffer = MAX_BUF;
  comm = (char *)malloc(len_comm_buffer);
  if (comm == NULL) return JDB_CONDITION_NOMEM_FOR_COMM_BUFFER;
    
  strcpy(comm,obj);
  if (*num <0 ) *num=0;
  else {
    if (comm[strlen(comm)-1] == ')') comm[strlen(comm)-1] = ',';
    else strcat(comm,"(");
    for (i = 0; i < numd; i++) {
      strcat(comm,names+i*(*namesize));
      strcat(comm,",");
    }
    comm[strlen(comm)-1]=')';
  }
#if JDB_DEBUG2
  printf("++%s++\n",comm);
#endif

/* find it */
  if (cp = strchr(comm,'(')) {
    par = cp+1;
    *cp = '\0';
    i = strlen(par)-1;
    if (  par[i] == ')'  )  {
      par[i]='\0';
    } else {
      printf("&x <b>Error - parenthesis mismatch:</b> %s\n",comm);
      return JDB_CONDITION_NO_CLOSE_PAREN;
    }
  } else {
    par = empty;
  }

  dctsearch_param2 = &met;
  dctsearch_param3 = &par;
  if (dctsearch1(comm,dctsearch_param2,dctsearch_param3,1) == 0) {
    printf("&x <b>Error - not found:</b> %s %s\n",*dctsearch_param2,comm);
    return JDB_CONDITION_DCTSEARCHFAILURE;
  }
/*  dctsearch1 uses the string pointed to by dctsearh_param3 & replaces	*/
/*  it w/another string in another place.  It's that 2nd string that we	*/
/*  now want.								*/
  par = *dctsearch_param3;

  open_error_status = 0;
  remote_object_flag = (is_a_remote_object(met) != 0);
  if (remote_object_flag) {
    if (  (i = htopen(met,par,o)) != 0 ) {
      cp = (i == JDB_CONDITION_INTERNAL_BUFOVF ) ? 
					"htopen buffer overflow" : "Error";
      printf("&x <b> %s opening remote object:</b> %s\n",cp,met);
      open_error_status = i;
    }
  } else {
    if (localopen(met,par,o) != 0) {
      printf("&x <b>Error opening local object:</b> %s\n",met);
      open_error_status = JDB_CONDITION_ERROR;
    }
  }
  free (met);
  free (par);
  if (open_error_status != 0) return open_error_status;

  o->htbufflen=0;
  o->htbuffptr=0;
  o->commentbufsize = COMMENTSIZE;
  o->commentbuf = (char *) malloc (o->commentbufsize);
  if (o->commentbuf == NULL) return JDB_CONDITION_COMMENT_BUF_OUT_OF_MEM;
  *(o->commentbuf) = '\0'; 
  o->next_unreturned_comment_offset = 0;
  o->comment_size_so_far = 0;

  for(i=0;i<NUMNAMES;i++){
    o->rdpntr[i]= -1;
    o->attributeptr[i] = o->attributes[i];
  };
  o->ntotal=0;
  
  if (remote_object_flag) {
      /*  We sent a "full request".  Check that we got a "full		*/
      /*  response" (eg, "HTTP/1.0 200 OK")... and (mostly) skip it.	*/
      /*  Details in RFC 1945.  Note in particular that (as I read it)	*/
      /*  the space between the "200" and the "OK" is required.  Also	*/
      /*  note that we allow more than one blank around status which	*/
      /*  may not be standard-compliant					*/
    switch (bgets(comm,MAX_BUF-1,o)) {
    case -1:
       printf("&x <b>jdb: data line too big.  Start of line:</b> %s\n",comm);
       return JDB_CONDITION_DATA_BUFOVF;
    case 1:
#if JDB_DEBUG1
       printf("jdbopen (remote obj) exiting due to return of 1 from bgets#1\n");
#endif
       return JDB_CONDITION_ERROR;
    }
    nitem = sscanf(comm,"HTTP/%d.%d %d %n",
	&http_vers_major,&http_vers_minor,&http_status,&http_text_offset);
    if (nitem < 3) {
      printf ("jdb: remote http server not >= v 1.0? response: %s\n",comm);
      close_object(tunit);
      return JDB_CONDITION_ERROR;
    }
      /*  Maybe should do better job w/other statuses			*/
    if (http_status >= 400) {
      printf ("jdb: http protocol error %d text: %s\n",
					http_status, comm + http_text_offset);
      close_object(tunit);
      return JDB_CONDITION_ERROR;
    }
    while (comm[0] != '\0') {
      if (bgets(comm,MAX_BUF-1,o) == 1) {
#if JDB_DEBUG1
       printf("jdbopen (remote obj) exiting due to return of 1 from bgets#2\n");
#endif
       return JDB_CONDITION_ERROR;
      }
    }
  } else {
      /*  Skip "content-type:text/html"; crlf lines			*/
    i = skip_one_line_from_outer(comm,tunit);
    if (i != 0) return i;
    i = skip_one_line_from_outer(comm,tunit);
    if (i != 0) return i;
  }

  state='c';
  o->maxlevel= 0;
/*  Main loop.  Normal exit from jdbopen_ is after processing case 'r'	*/
  while(1) {
    switch (bgets(comm,MAX_BUF-1,o)) {
    case -1:
       printf("&x <b>jdb: data line too big.  Start of line:</b> %s\n",comm);
       return JDB_CONDITION_DATA_BUFOVF;
    case 1:
#if JDB_DEBUG1
       printf("jdbopen exiting due to return of 1 from bgets#3\n");
#endif
       return JDB_CONDITION_ERROR;
    }
    if(comm[0]=='&') state=comm[1];
    switch(state){
    case 'x':
      printf("%s\n",comm);
      close_object(tunit);
      return JDB_CONDITION_ERROR;
    case 'e':
#if JDB_DEBUG1 || JDB_DEBUG2
      printf("jdbopen exiting upon receipt of premature eod: %s\n",comm);
#endif
      close_object(tunit);
      return JDB_CONDITION_ERROR;
    case 'c':
      if (comm[0] != '&') {
	  /*  + 1 for newline char we insert				*/
	comment_len = strlen(comm) + 1;
	if (  (o->comment_size_so_far + comment_len) > o->commentbufsize   ) {
	    /*  Set size to enough for this comment + 1 chunk		*/
	  o->commentbufsize = COMMENTSIZE +
					o->comment_size_so_far + comment_len;
	  o->commentbuf = (char *) realloc (o->commentbuf,o->commentbufsize);
	  if (o->commentbuf == NULL) 
				return JDB_CONDITION_COMMENT_BUF_OUT_OF_MEM;
	}
	strcpy (o->commentbuf + o->comment_size_so_far,  comm);
	o->comment_size_so_far += comment_len; 
	*(o->commentbuf + o->comment_size_so_far - 1) = '\n';
	*(o->commentbuf + o->comment_size_so_far) = '\0';
      }
      break;
    case 'r':
      if (numd > 0)
        for (i = 0; i < numd; i++) {
          for (j = 0; j < o->ntotal; j++)
	    if (o->rdpntr[j] == i) break;
          if (j == o->ntotal) {
	    close_object(tunit);
	      /*  Wants to return index of varname not found		*/
	      /*  Check, however, that this index is not also an error	*/
	      /*  code.  If it is, change return val.  Breaks code that	*/
	      /*  relies on index, but we're in trouble either way.	*/
	      /*  Note that EOF, -1, cannot normally be returned by	*/
	      /*  jdbopen, so it can be used as index			*/
            j = -(i+1);
	    if (jdb_known_condition(j)) {
	      return (j == JDB_CONDITION_EOF) ? j : JDB_CONDITION_VARNOTFOUND;
	    } else {
	      return j;
	    }
          }
        }
      o->lvlpntr[o->maxlevel+1] = o->ntotal;
      free (comm);
      return o->maxlevel;
    case 'v':
      if (comm[0] == '&'){
/*	  10 level limit a consequence of next statement		*/
	o->maxlevel = comm[2]-'0';
	o->lvlpntr[o->maxlevel] = o->ntotal;
      } else {
	cp=strtok(comm,"\t\n");
	while(cp){
	  ap=strchr(cp,'[');
	  if(ap) {
	    i = strlen(ap)-1;
	    if (ap[i] == ']') {
	      ap[0] = '\0';
	      ap[i] = ';';
	      ap++;
	    } else {
	      printf("&x <b>Malformed attribute list (missing ]):</b> %s\n",ap);
	      return JDB_CONDITION_BADATTRLIST;
	    }
	  };
	  o->ntotal++;
	  if(numd < 0){
	    (*num)++;
	    if(*num > -numd){
	      printf("&x <b>Too many variables (max = %d)</b>\n",-numd);
	      close_object(tunit);
	      return JDB_CONDITION_ERROR;
	    };
	    if (strlen(cp) > *namesize) {
	      printf("&x <b>varname %s too long (max len = %d)</b>\n",
								cp,namesize);
	      close_object(tunit);
	      return JDB_CONDITION_NAMEBUF_OVF;
	    }
	    strcpy(   names+(*namesize)*(*num-1), cp   );
	    if (ap) {
	      if (  strlen(ap) > sizeof(o->attributes[*num-1])  )    {
	        printf("&x <b>attribute %s too long (max len = %d)</b>\n",
					ap,sizeof(o->attributes[*num-1])  );
		close_object(tunit);
	        return JDB_CONDITION_SINGLE_ATTR_BUF_OVF;
	      }
	      strcpy(o->attributes[*num-1],ap);
	    }
	    k= *num -1;
	  } else {
	    k = -1;
	    for (i = 0; i < *num; i++) 
	      if (strcmp(names+(*namesize)*i,cp) == 0) {
		k = i;
		if (ap) {
		  if (  strlen(ap) > sizeof(o->attributes[*num-1])  )   {
	            printf("&x <b>attribute %s too long (max len = %d)</b>\n",
					ap,sizeof(o->attributes[*num-1])  );
		    close_object(tunit);
	            return JDB_CONDITION_SINGLE_ATTR_BUF_OVF;
		  }
		  strcpy(o->attributes[*num-1],ap);
		}
	      }
	  }
	  if (k >= 0) o->rdpntr[o->ntotal-1] = k;
	  cp = strtok(NULL,"\t\n");
	}
      }
    }
  }
}

#if IBM || HP
int jdbread(unit,values)
#else
int jdbread_(unit,values)
#endif
int *unit;
float values[];
{
  char comm[MAX_BUF],state,*cp;
  Logical is_a_number;
  int i,k,ilvl,iret,comment_len;
  struct OBJECT *o;

  if ((*unit < 0) || (*unit >= MAX_UNITS)) 
					return JDB_CONDITION_HANDLEOUTOFRANGE ;
  if(!(o=object[*unit]))return -1;

  ilvl=o->maxlevel;
  iret=ilvl;
  state='d';
  while(1) {
#if JDB_DEBUG2
    printf(">>%c>>%s\n",state,comm);
#endif
    switch (bgets(comm,MAX_BUF-1,o)) {
    case -1:
       printf("&x <b>jdb: data line too big.  Start of line:</b> %s\n",comm);
       return JDB_CONDITION_DATA_BUFOVF;
    case 1:
#if JDB_DEBUG1
       printf("jdbread exiting due to return of 1 from bgets#4\n");
#endif
       return JDB_CONDITION_ERROR;
    }

    if(comm[0]=='&') {
      state=comm[1];
      switch(state){
      case 'x':
	printf("%s\n",comm);
	close_object(*unit);
	return JDB_CONDITION_ERROR;
      case 'e':
	close_object(*unit);
	return -1;
      case 'd':
        ilvl = comm[2]-'0';
	if (ilvl < iret) iret = ilvl;
	break;
      case 'c':
	*(o->commentbuf)='\0';
	o->comment_size_so_far = 0;
	o->next_unreturned_comment_offset = 0;
      };
    } else {
      switch(state){
      case 'c':
	  /*  + 1 for newline char we insert				*/
	comment_len = strlen(comm) + 1;
	if (  (o->comment_size_so_far + comment_len) > o->commentbufsize  ) {
	    /*  Set size to enough for this comment + 1 chunk		*/
	  o->commentbufsize = COMMENTSIZE +
					o->comment_size_so_far + comment_len;
	  o->commentbuf = (char *) realloc (o->commentbuf,o->commentbufsize);
	  if (o->commentbuf == NULL) 
				return JDB_CONDITION_COMMENT_BUF_OUT_OF_MEM;
	}
	strcpy (o->commentbuf + o->comment_size_so_far,  comm);
	o->comment_size_so_far += comment_len; 
	*(o->commentbuf + o->comment_size_so_far - 1) = '\n';
	break;
      case 'd':
	cp=strtok(comm,"\t\n");
	for(i=o->lvlpntr[ilvl];i<o->lvlpntr[ilvl+1];i++){
	  k=o->rdpntr[i];
	  if(cp && k>=0){
	    GET_NUMBER_FROM_STRING(values[k],cp,is_a_number);
	    if ( ! is_a_number ) values[k]= MISSING_VALUE_REAL;
	    cp=strtok(NULL,"\t\n");
	  } else if(cp) cp=strtok(NULL,"\t\n");
	  else if(k>=0) values[k]= MISSING_VALUE_REAL;
	};
	if(ilvl == o->maxlevel)return iret;
      };
    };
  };
}


#if IBM || HP
int jdbreada(unit,values,valuesize)
#else
int jdbreada_(unit,values,valuesize)
#endif
int *unit;
char *values;
int *valuesize;
{
  char comm[MAX_BUF],state,*cp;
  int i,k,ilvl,iret,comment_len;
  struct OBJECT *o;

  if ((*unit < 0) || (*unit >= MAX_UNITS)) 
					return JDB_CONDITION_HANDLEOUTOFRANGE ;

  if (  ! (o=object[*unit]) )   return -1;

  ilvl=o->maxlevel;
  iret=ilvl;
  state='d';

  while(1) {
#if JDB_DEBUG2
    printf(">>%c>>%s\n",state,comm);
#endif
    switch (bgets(comm,MAX_BUF-1,o)) {
    case -1:
       printf("&x <b>jdb: data line too big.  Start of line:</b> %s\n",comm);
       return JDB_CONDITION_DATA_BUFOVF;
    case 1:
#if JDB_DEBUG1
       printf("jdbreada exiting due to return of 1 from bgets#5\n");
#endif
       return JDB_CONDITION_ERROR;
    }

    if(comm[0]=='&') {
      state=comm[1];
      switch(state){
      case 'x':
	printf("%s\n",comm);
	close_object(*unit);
	return JDB_CONDITION_ERROR;
      case 'e':
	close_object(*unit);
	return -1;
      case 'd':
        ilvl=comm[2]-'0';
	if(ilvl<iret)iret=ilvl;
	break;
      case 'c':
	*(o->commentbuf)='\0';
	o->comment_size_so_far = 0;
	o->next_unreturned_comment_offset = 0;
      };
    } else {
      switch(state){
      case 'c':
	  /*  + 1 for newline char we insert				*/
	comment_len = strlen(comm) + 1;
	if (  (o->comment_size_so_far + comment_len) > o->commentbufsize  ) {
	    /*  Set size to enough for this comment + 1 chunk		*/
	  o->commentbufsize = COMMENTSIZE +
					o->comment_size_so_far + comment_len;
	  o->commentbuf = (char *) realloc (o->commentbuf,o->commentbufsize);
	  if (o->commentbuf == NULL) 
				return JDB_CONDITION_COMMENT_BUF_OUT_OF_MEM;
	}
	strcpy (o->commentbuf + o->comment_size_so_far,  comm);
	o->comment_size_so_far += comment_len; 
	*(o->commentbuf + o->comment_size_so_far - 1) = '\n';
	*(o->commentbuf + o->comment_size_so_far) = '\0';
	break;
      case 'd':
	cp=strtok(comm,"\t\n");
	for(i=o->lvlpntr[ilvl];i<o->lvlpntr[ilvl+1];i++){
	  k=o->rdpntr[i];
	  if(cp && k>=0) {
	    if (strlen(cp) > *valuesize) {
	      printf("&x <b>jdb: datum too big.  Datum:</b> %s\n",cp);
	      return JDB_CONDITION_DATA_BUFOVF;
	    }
	    strcpy(values+(*valuesize)*k,cp);
	    cp=strtok(NULL,"\t\n");
	  } else if (cp) {
	    cp=strtok(NULL,"\t\n");
	  } else if (k>=0) {
	    if (strlen(MISSING_VALUE_STRING) > *valuesize) {
	      printf
	("&x <b>jdb: jdbreada data buffer cannot hold missing value string");
	      return JDB_CONDITION_DATA_BUFOVF;
	    }
	    strcpy(values+(*valuesize)*k,MISSING_VALUE_STRING);
	  }
	}
	if(ilvl == o->maxlevel)return iret;
      };
    };
  };
}

#if IBM || HP
int jdbcomments(unit,outcom)
#else
int jdbcomments_(unit,outcom)
#endif
int *unit;
char *outcom;
{
  int j;
  struct OBJECT *o;

  int commentbufsize, comment_size_so_far, next_unreturned_comment_offset;
  if ((*unit < 0) || (*unit >= MAX_UNITS)) 
					return JDB_CONDITION_HANDLEOUTOFRANGE ;
  if ( (o=object[*unit]) == NULL  )    return 0;
  if ( o->next_unreturned_comment_offset < o->comment_size_so_far) {
    j=strcspn(o->commentbuf + o->next_unreturned_comment_offset,"\n");
    strncpy(outcom,o->commentbuf + o->next_unreturned_comment_offset,j);
    *(outcom+j)='\0';
    o->next_unreturned_comment_offset += j+1;
    return 1;
  } else return 0;
}

#if IBM || HP
int jdbattributes(unit,id,outcom)
#else
int jdbattributes_(unit,id,outcom)
#endif
int *unit;
int *id;
char *outcom;
{
   int j;
   char *sp;
   struct OBJECT *o;

  if ((*unit < 0) || (*unit >= MAX_UNITS)) 
					return JDB_CONDITION_HANDLEOUTOFRANGE ;
  if(!(o=object[*unit]))return 0;
  sp=o->attributeptr[*id];
  if(*sp){
  j=strcspn(sp,";");
  strncpy(outcom,sp,j);
  *(outcom+j)='\0';
  o->attributeptr[*id] += j+1;
  return 1;
  } else return 0;
}

#if IBM || HP
int jdbclose(unit)
#else
int jdbclose_(unit)
#endif
int *unit;
{
  struct OBJECT *o;
  
  if ((*unit < 0) || (*unit >= MAX_UNITS)) 
					return JDB_CONDITION_HANDLEOUTOFRANGE ;
  if ( (o = object[*unit]) == NULL ) return 1;
  close_object(*unit);
  return 0;
}

#if IBM || HP
int jdblevel(unit,varnum)
#else
int jdblevel_(unit,varnum)
#endif
int *unit;
int *varnum;
{
  int i,j;
  struct OBJECT *o;
  
  if ((*unit < 0) || (*unit >= MAX_UNITS)) 
					return JDB_CONDITION_HANDLEOUTOFRANGE ;
  if(!(o=object[*unit]))return JDB_CONDITION_ERROR;
  for(j=0;j<o->ntotal;j++)if(*varnum==o->rdpntr[j])break;
  if(j>=o->ntotal)return -1;
  for(i=1;i<=o->maxlevel;i++)if(j<o->lvlpntr[i])return i-1;
  return o->maxlevel;
}

/*
char names[20][TOKEN];
char values[20][TOKEN];
int maxlev;

main(argc,argv)
int argc;
char *argv[];
{
char objname[256];
int i,j,clev;
int unit=1;
int nn;
int namesize=TOKEN;
int valuesize=TOKEN;

nn= -20;

strcpy(names[0],"lon");
strcpy(names[1],"lat");
strcpy(names[2],"press");
strcpy(names[3],"o2");
nn=4;

strcpy(objname,argv[1]);
maxlev=jdbopen_(&unit,objname,names,&namesize,values,&valuesize,&nn);
if(maxlev<0){printf("Bad object %s\n",argv[1]);exit(1);};

*
while(jdbcomments_(&unit,objname))printf("#%s\n",objname);
/

for(i=0;i<nn;i++)printf("%-20.20s",names[i]);
printf("\n");
*
for(i=0;i<nn;i++)
  while(jdbattributes_(&unit,&i,objname))printf("%s . %s \n",names[i],objname);
for(i=0;i<nn;i++)printf("%-20d",levels[i]);
printf("\n");
for(i=0;i<nn;i++)printf("%-20d",sizes[i]);
printf("\n");
/

while((clev=jdbreada_(&unit,values,&valuesize)) >= 0){
  if(clev<maxlev) while(jdbcomments_(&unit,objname))printf("#%s\n",objname);
  for(i=0;i<nn;i++)printf("%-20.20s",values[i]);
  printf("\n");
  };
jdbclose_(&unit);
}
*/
