text: /*********************************************************************
   *   Copyright 1993, UCAR/Unidata
   *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
   *********************************************************************/
  
  #include "ncdap.h"
  #include "ncd2dispatch.h"
  #include "dapalign.h"
  
  #ifdef _MSC_VER
  #include <crtdbg.h>
  #endif
  
  #define NCRCFILE "NCRCFILE"
  
  #ifdef HAVE_GETRLIMIT
  #  ifdef HAVE_SYS_RESOURCE_H
  #    include <sys/time.h>
  #  endif
  #  ifdef HAVE_SYS_RESOURCE_H
  #    include <sys/resource.h>
  #  endif
  #endif
  
  #define getncid(drno) (((NC*)drno)->ext_ncid)
  
  /* Define the set of protocols known to be constrainable */
  static char* constrainableprotocols[] = {"http", "https",NULL};
  
  static int ncd2initialized = 0;
  
  size_t dap_one[NC_MAX_VAR_DIMS];
  size_t dap_zero[NC_MAX_VAR_DIMS];
  
  /* Forward */
  static NCerror buildncstructures(NCDAPCOMMON*);
  static NCerror builddims(NCDAPCOMMON*);
  static char* getdefinename(CDFnode* node);
  static NCerror buildvars(NCDAPCOMMON*);
  static NCerror buildglobalattrs(NCDAPCOMMON*, CDFnode* root);
  static NCerror buildattribute(NCDAPCOMMON*, NCattribute*, nc_type, int);
  static void computedimindexanon(CDFnode* dim, CDFnode* var);
  static void replacedims(NClist* dims);
  static int equivalentdim(CDFnode* basedim, CDFnode* dupdim);
  static NCerror addstringdims(NCDAPCOMMON*);
  static NCerror defrecorddim(NCDAPCOMMON*);
  static NCerror defseqdims(NCDAPCOMMON*);
  static NCerror showprojection(NCDAPCOMMON*, CDFnode* var);
  static NCerror getseqdimsize(NCDAPCOMMON*, CDFnode* seq, size_t* sizep);
  static NCerror makeseqdim(NCDAPCOMMON*, CDFnode* seq, size_t count, CDFnode** sqdimp);
  static NCerror countsequence(NCDAPCOMMON*, CDFnode* xseq, size_t* sizep);
  static NCerror freeNCDAPCOMMON(NCDAPCOMMON*);
  static NCerror fetchpatternmetadata(NCDAPCOMMON*);
  static size_t fieldindex(CDFnode* parent, CDFnode* child);
  static NCerror computeseqcountconstraints(NCDAPCOMMON*, CDFnode*, NCbytes*);
  static void computeseqcountconstraintsr(NCDAPCOMMON*, CDFnode*, CDFnode**);
  static void estimatevarsizes(NCDAPCOMMON*);
  static NCerror fetchconstrainedmetadata(NCDAPCOMMON*);
  static NCerror suppressunusablevars(NCDAPCOMMON*);
  static NCerror fixzerodims(NCDAPCOMMON*);
  static void applyclientparamcontrols(NCDAPCOMMON*);
  static NCerror applyclientparams(NCDAPCOMMON*);
  
  /**************************************************/
  
  static int
  NCD2_create(const char *path, int cmode,
             size_t initialsz, int basepe, size_t *chunksizehintp,
  	   int use_parallel, void* mpidata,
             NC_Dispatch*,NC* ncp);
  
  static int NCD2_redef(int ncid);
  static int NCD2__enddef(int ncid, size_t h_minfree, size_t v_align, size_t v_minfree, size_t r_align);
  static int NCD2_sync(int ncid);
  static int NCD2_abort(int ncid);
  
  static int NCD2_put_vara(int ncid, int varid,
  	    const size_t *start, const size_t *edges0,
              const void *value0,
  	    nc_type memtype);
  
  static int NCD2_get_vara(int ncid, int varid,
  	    const size_t *start, const size_t *edges,
              void *value,
  	    nc_type memtype);
  
  static int NCD2_put_vars(int ncid, int varid,
  	    const size_t *start, const size_t *edges, const ptrdiff_t* stride,
              const void *value0, nc_type memtype);
  
  static int NCD2_get_vars(int ncid, int varid,
  	    const size_t *start, const size_t *edges, const ptrdiff_t* stride,
              void *value, nc_type memtype);
  
  static NC_Dispatch NCD2_dispatch_base = {
  
  NC_FORMATX_DAP2,
  
  NCD2_create,
  NCD2_open,
  
  NCD2_redef,
  NCD2__enddef,
  NCD2_sync,
  NCD2_abort,
  NCD2_close,
  NULL, /*set_fill*/
  NULL, /*inq_base_pe*/
  NULL, /*set_base_pe*/
  NULL, /*inq_format*/
  NCD2_inq_format_extended, /*inq_format_extended*/
  
  NULL, /*inq*/
  NULL, /*inq_type*/
  
  NULL, /*def_dim*/
  NULL, /*inq_dimid*/
  NULL, /*inq_dim*/
  NULL, /*inq_unlimdim*/
  NULL, /*rename_dim*/
  
  NULL, /*inq_att*/
  NULL, /*inq_attid*/
  NULL, /*inq_attname*/
  NULL, /*rename_att*/
  NULL, /*del_att*/
  NULL, /*get_att*/
  NULL, /*put_att*/
  
  NULL, /*def_var*/
  NULL, /*inq_varid*/
  NULL, /*rename_var*/
  NCD2_get_vara,
  NCD2_put_vara,
  NCD2_get_vars,
  NCD2_put_vars,
  NCDEFAULT_get_varm,
  NCDEFAULT_put_varm,
  
  NULL, /*inq_var_all*/
  
  NULL, /*var_par_access*/
  
  #ifdef USE_NETCDF4
  NULL, /*show_metadata*/
  NULL, /*inq_unlimdims*/
  NULL, /*inq_ncid*/
  NULL, /*inq_grps*/
  NULL, /*inq_grpname*/
  NULL, /*inq_grpname_full*/
  NULL, /*inq_grp_parent*/
  NULL, /*inq_grp_full_ncid*/
  NULL, /*inq_varids*/
  NULL, /*inq_dimids*/
  NULL, /*inq_typeids*/
  NULL, /*inq_type_equal*/
  NULL, /*def_grp*/
  NULL, /*rename_grp*/
  NULL, /*inq_user_type*/
  NULL, /*inq_typeid*/
  
  NULL, /*def_compound*/
  NULL, /*insert_compound*/
  NULL, /*insert_array_compound*/
  NULL, /*inq_compound_field*/
  NULL, /*inq_compound_fieldindex*/
  NULL, /*def_vlen*/
  NULL, /*put_vlen_element*/
  NULL, /*get_vlen_element*/
  NULL, /*def_enum*/
  NULL, /*insert_enum*/
  NULL, /*inq_enum_member*/
  NULL, /*inq_enum_ident*/
  NULL, /*def_opaque*/
  NULL, /*def_var_deflate*/
  NULL, /*def_var_fletcher32*/
  NULL, /*def_var_chunking*/
  NULL, /*def_var_fill*/
  NULL, /*def_var_endian*/
  NULL, /*set_var_chunk_cache*/
  NULL, /*get_var_chunk_cache*/
  
  #endif /*USE_NETCDF4*/
  
  };
  
  NC_Dispatch* NCD2_dispatch_table = NULL; /* moved here from ddispatch.c */
  
  static NC_Dispatch NCD2_dispatcher; /* overlay result */
  
  int
  NCD2_initialize(void)
  {
      int i;
      /* Create our dispatch table as the merge of NCD2 table and NCSUBSTRATE */
      /* watch the order because we want NCD2 to overwrite NCSUBSTRATE */
      NC_dispatch_overlay(&NCD2_dispatch_base, NCSUBSTRATE_dispatch_table, &NCD2_dispatcher);
      NCD2_dispatch_table = &NCD2_dispatcher;
      /* Local Initialization */
      compute_nccalignments();
      for(i=0;i<NC_MAX_VAR_DIMS;i++) {
  	dap_one[i] = 1;
  	dap_zero[i] = 0;
      }
      ncd2initialized = 1;
  #ifdef DEBUG
      /* force logging to go to stderr */
      nclogclose();
      if(nclogopen(NULL))
          ncsetlogging(1); /* turn it on */
  #endif
      /* Look at env vars for rc file location */
      if(getenv(NCRCFILE) != NULL) {
  	const char* ncrcfile = getenv(NCRCFILE);
  	if(oc_set_rcfile(ncrcfile) != OC_NOERR)
  	    return NC_EAUTH;
      }
      return NC_NOERR;
  }
  
  int
  NCD2_finalize(void)
  {
      return NC_NOERR;
  }
  
  static int
  NCD2_redef(int ncid)
  {
      return (NC_EPERM);
  }
  
  static int
  NCD2__enddef(int ncid, size_t h_minfree, size_t v_align, size_t v_minfree, size_t r_align)
  {
      return (NC_EPERM);
  }
  
  static int
  NCD2_sync(int ncid)
  {
      return (NC_EINVAL);
  }
  
  static int
  NCD2_abort(int ncid)
  {
      return NCD2_close(ncid);
  }
  
  static int
  NCD2_create(const char *path, int cmode,
             size_t initialsz, int basepe, size_t *chunksizehintp,
  	   int use_parallel, void* mpidata,
             NC_Dispatch* dispatch, NC* ncp)
  {
     return NC_EPERM;
  }
  
  static int
  NCD2_put_vara(int ncid, int varid,
  	    const size_t *start, const size_t *edges,
              const void *value,
  	    nc_type memtype)
  {
      return NC_EPERM;
  }
  
  static int
  NCD2_get_vara(int ncid, int varid,
  	    const size_t *start, const size_t *edges,
              void *value,
  	    nc_type memtype)
  {
      int stat = nc3d_getvarx(ncid, varid, start, edges, nc_ptrdiffvector1, value,memtype);
      return stat;
  }
  
  static int
  NCD2_put_vars(int ncid, int varid,
  	    const size_t *start, const size_t *edges, const ptrdiff_t* stride,
              const void *value0, nc_type memtype)
  {
      return NC_EPERM;
  }
  
  static int
  NCD2_get_vars(int ncid, int varid,
  	    const size_t *start, const size_t *edges, const ptrdiff_t* stride,
              void *value, nc_type memtype)
  {
      int stat = nc3d_getvarx(ncid, varid, start, edges, stride, value, memtype);
      return stat;
  }
  
  /* See ncd2dispatch.c for other version */
  int
  NCD2_open(const char * path, int mode,
                 int basepe, size_t *chunksizehintp,
   	       int useparallel, void* mpidata,
                 NC_Dispatch* dispatch, NC* drno)
  {
      NCerror ncstat = NC_NOERR;
      OCerror ocstat = OC_NOERR;
      NCDAPCOMMON* dapcomm = NULL;
      const char* value;
  
      if(path == NULL)
  	return NC_EDAPURL;
      if(dispatch == NULL) PANIC("NC3D_open: no dispatch table");
  
      /* Setup our NC and NCDAPCOMMON state*/
  
      dapcomm = (NCDAPCOMMON*)calloc(1,sizeof(NCDAPCOMMON));
      if(dapcomm == NULL) {ncstat = NC_ENOMEM; goto done;}
  
      NCD2_DATA_SET(drno,dapcomm);
      drno->int_ncid = nc__pseudofd(); /* create a unique id */
      dapcomm->controller = (NC*)drno;
  
      dapcomm->cdf.separator = ".";
      dapcomm->cdf.smallsizelimit = DFALTSMALLLIMIT;
      dapcomm->cdf.cache = createnccache();
  
  #ifdef HAVE_GETRLIMIT
      { struct rlimit rl;
        if(getrlimit(RLIMIT_NOFILE, &rl) >= 0) {
  	dapcomm->cdf.cache->cachecount = (size_t)(rl.rlim_cur / 2);
        }
      }
  #endif
  
  #ifdef OCCOMPILEBYDEFAULT
      /* set the compile flag by default */
      dapcomm->oc.rawurltext = (char*)emalloc(strlen(path)+strlen("[compile]")+1);
      strcpy(dapcomm->oc.rawurltext,"[compile]");
      strcat(dapcomm->oc.rawurltext, path);
  #else
      dapcomm->oc.rawurltext = strdup(path);
  #endif
  
      ncuriparse(dapcomm->oc.rawurltext,&dapcomm->oc.url);
  
      /* parse the client parameters */
      ncuridecodeparams(dapcomm->oc.url);
  
      if(!constrainable(dapcomm->oc.url))
  	SETFLAG(dapcomm->controls,NCF_UNCONSTRAINABLE);
  
  #ifdef COLUMBIA_HACK
      {
  	const char* p;
  	/* Does this url look like it is from columbia? */
  	if(dapcomm->oc.url->host != NULL) {
  	    for(p=dapcomm->oc.url->host;*p;p++) {
  	        if(strncmp(p,COLUMBIA_HACK,strlen(COLUMBIA_HACK))==0)
  		    SETFLAG(dapcomm->controls,NCF_COLUMBIA);
  	    }
  	}
      }
  #endif
  
      /* fail if we are unconstrainable but have constraints */
      if(FLAGSET(dapcomm->controls,NCF_UNCONSTRAINABLE)) {
  	if(dapcomm->oc.url->constraint != NULL) {
  	    nclog(NCLOGWARN,"Attempt to constrain an unconstrainable data source: %s",
  		   dapcomm->oc.url->constraint);
  	    ncstat = THROW(NC_EDAPCONSTRAINT);
  	    goto done;
  	}
      }
  
      /* Use libsrc code for storing metadata */
      {
  	char tmpname[32];
  
          /* Create fake file name: exact name must be unique,
             but is otherwise irrelevant because we are using NC_DISKLESS
          */
          snprintf(tmpname,sizeof(tmpname),"%d",drno->int_ncid);
  
          /* Now, use the file to create the netcdf file; force classic.  */
          ncstat = nc_create(tmpname,NC_DISKLESS|NC_CLASSIC_MODEL,&drno->substrate);
          if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto done;}
      }
  
      /* Avoid fill */
      nc_set_fill(drno->substrate,NC_NOFILL,NULL);
  
      dapcomm->oc.dapconstraint = (DCEconstraint*)dcecreate(CES_CONSTRAINT);
      dapcomm->oc.dapconstraint->projections = nclistnew();
      dapcomm->oc.dapconstraint->selections = nclistnew();
  
       /* Parse constraints to make sure they are syntactically correct */
       ncstat = dapparsedapconstraints(dapcomm,dapcomm->oc.url->constraint,dapcomm->oc.dapconstraint);
       if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto done;}
  
      /* Construct a url for oc minus any constraint and params*/
      dapcomm->oc.urltext = ncuribuild(dapcomm->oc.url,NULL,NULL,
  				      (NCURISTD ^ NCURICONSTRAINTS));
  
      /* Pass to OC */
      ocstat = oc_open(dapcomm->oc.urltext,&dapcomm->oc.conn);
      if(ocstat != OC_NOERR) {THROWCHK(ocstat); goto done;}
  
  #ifdef DEBUG1
      (void)oc_trace_curl(dapcomm->oc.conn);
  #endif
  
      nullfree(dapcomm->oc.urltext); /* clean up */
      dapcomm->oc.urltext = NULL;
  
      /* process control client parameters */
      applyclientparamcontrols(dapcomm);
  
      /* Turn on logging; only do this after oc_open*/
      if((value = dapparamvalue(dapcomm,"log")) != NULL) {
  	ncloginit();
          if(nclogopen(value))
  	    ncsetlogging(1);
  	ncloginit();
          if(nclogopen(value))
  	    ncsetlogging(1);
      }
  
      /* fetch and build the unconstrained DDS for use as
         pattern */
      ncstat = fetchpatternmetadata(dapcomm);
      if(ncstat != NC_NOERR) goto done;
  
      /* Operations on the pattern tree */
  
      /* Accumulate useful nodes sets  */
      ncstat = computecdfnodesets(dapcomm,dapcomm->cdf.fullddsroot->tree);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* Define the dimsettrans list */
      ncstat = definedimsettrans(dapcomm,dapcomm->cdf.fullddsroot->tree);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* Mark the nodes of the pattern that are eligible for prefetch */
      ncstat = markprefetch(dapcomm);
  
      /* fetch and build the constrained DDS */
      ncstat = fetchconstrainedmetadata(dapcomm);
      if(ncstat != NC_NOERR) goto done;
  
  #ifdef DEBUG2
  fprintf(stderr,"constrained dds: %s\n",dumptree(dapcomm->cdf.ddsroot));
  #endif
  
      /* Operations on the constrained tree */
  
      /* Accumulate useful nodes sets  */
      ncstat = computecdfnodesets(dapcomm,dapcomm->cdf.ddsroot->tree);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* Fix grids */
      ncstat = fixgrids(dapcomm);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* Locate and mark usable sequences */
      ncstat = sequencecheck(dapcomm);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* suppress variables not in usable sequences */
      ncstat = suppressunusablevars(dapcomm);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* apply client parameters */
      ncstat = applyclientparams(dapcomm);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* Add (as needed) string dimensions*/
      ncstat = addstringdims(dapcomm);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      if(nclistlength(dapcomm->cdf.ddsroot->tree->seqnodes) > 0) {
  	/* Build the sequence related dimensions */
          ncstat = defseqdims(dapcomm);
          if(ncstat) {THROWCHK(ncstat); goto done;}
      }
  
      /* Define the dimsetplus and dimsetall lists */
      ncstat = definedimsets(dapcomm,dapcomm->cdf.ddsroot->tree);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* Re-compute the dimension names*/
      ncstat = computecdfdimnames(dapcomm);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* Deal with zero size dimensions */
      ncstat = fixzerodims(dapcomm);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* Attempt to use the DODS_EXTRA info to turn
         one of the dimensions into unlimited.
         Assume computecdfdimnames34 has already been called.
      */
      ncstat = defrecorddim(dapcomm);
      if(ncstat) {THROWCHK(ncstat); goto done;}
      if(dapcomm->cdf.recorddimname != NULL
         && nclistlength(dapcomm->cdf.ddsroot->tree->seqnodes) > 0) {
  	/*nclog(NCLOGWARN,"unlimited dimension specified, but sequences exist in DDS");*/
  	PANIC("unlimited dimension specified, but sequences exist in DDS");
      }
  
      /* Re-compute the var names*/
      ncstat = computecdfvarnames(dapcomm,
  				 dapcomm->cdf.ddsroot,
  				 dapcomm->cdf.ddsroot->tree->varnodes);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* Transfer data from the unconstrained DDS data to the unconstrained DDS */
      ncstat = dimimprint(dapcomm);
      if(ncstat) goto done;
  
      /* Process the constraints to map to the constrained CDF tree */
      /* (must follow fixgrids3 */
      ncstat = dapmapconstraints(dapcomm->oc.dapconstraint,dapcomm->cdf.ddsroot);
      if(ncstat != NC_NOERR) goto done;
  
      /* Canonicalize the constraint */
      ncstat = dapfixprojections(dapcomm->oc.dapconstraint->projections);
      if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto done;}
  
      /* Fill in segment information */
      ncstat = dapqualifyconstraints(dapcomm->oc.dapconstraint);
      if(ncstat != NC_NOERR) goto done;
  
      /* Accumulate set of variables in the constraint's projections */
      ncstat = dapcomputeprojectedvars(dapcomm,dapcomm->oc.dapconstraint);
      if(ncstat) {THROWCHK(ncstat); goto done;}
  
      /* using the modified constraint, rebuild the constraint string */
      if(FLAGSET(dapcomm->controls,NCF_UNCONSTRAINABLE)) {
  	/* ignore all constraints */
  	dapcomm->oc.urltext = ncuribuild(dapcomm->oc.url,NULL,NULL,0);
      } else {
  	char* constraintstring = dcebuildconstraintstring(dapcomm->oc.dapconstraint);
          ncurisetconstraints(dapcomm->oc.url,constraintstring);
  	nullfree(constraintstring);
          dapcomm->oc.urltext = ncuribuild(dapcomm->oc.url,NULL,NULL,NCURICONSTRAINTS);
      }
  
  #ifdef DEBUG
  fprintf(stderr,"ncdap3: final constraint: %s\n",dapcomm->oc.url->constraint);
  #endif
  
      /* Estimate the variable sizes */
      estimatevarsizes(dapcomm);
  
      /* Build the meta data */
      ncstat = buildncstructures(dapcomm);
      if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto done;}
  
      /* Explicitly do not call enddef because it will complain
         about variables that are too large.
      */
  #if 0
      ncstat = nc_endef(drno->substrate,NC_NOFILL,NULL);
      if(ncstat != NC_NOERR && ncstat != NC_EVARSIZE)
          {THROWCHK(ncstat); goto done;}
  #endif
  
      {
          NC* ncsub;
          NC* drno = dapcomm->controller;
  	CDFnode* unlimited = dapcomm->cdf.recorddim;
          /* (for now) break abstractions*/
  	NC3_INFO* nc3i;
  
          /* get the id for the substrate */
          ncstat = NC_check_id(drno->substrate,&ncsub);
          if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto done;}
  	nc3i = (NC3_INFO*)ncsub->dispatchdata;
  
          if(unlimited != NULL) {
              /* Set the effective size of UNLIMITED */
              NC_set_numrecs(nc3i,unlimited->dim.declsize);
          }
  
          /* Pretend the substrate is read-only */
  	NC_set_readonly(nc3i);
  
      }
  
      /* Do any necessary data prefetch */
      if(FLAGSET(dapcomm->controls,NCF_PREFETCH)
         && FLAGSET(dapcomm->controls,NCF_PREFETCH_EAGER)) {
          ncstat = prefetchdata(dapcomm);
          if(ncstat != NC_NOERR) {
              del_from_NCList((NC*)drno); /* undefine here */
  	    {THROWCHK(ncstat); goto done;}
  	}
      }
  
      return ncstat;
  
  done:
      if(drno != NULL) NCD2_close(drno->ext_ncid);
      if(ocstat != OC_NOERR) ncstat = ocerrtoncerr(ocstat);
      return THROW(ncstat);
  }
  
  int
  NCD2_close(int ncid)
  {
      NC* drno;
      NCDAPCOMMON* dapcomm;
      int ncstatus = NC_NOERR;
  
      ncstatus = NC_check_id(ncid, (NC**)&drno);
      if(ncstatus != NC_NOERR) return THROW(ncstatus);
      dapcomm = (NCDAPCOMMON*)drno->dispatchdata;
  
      /* We call abort rather than close to avoid
         trying to write anything or try to pad file length
       */
      ncstatus = nc_abort(drno->substrate);
  
      /* clean NC* */
      freeNCDAPCOMMON(dapcomm);
  
      return THROW(ncstatus);
  }
  
  /**************************************************/
  
  static NCerror
  buildncstructures(NCDAPCOMMON* dapcomm)
  {
      NCerror ncstat = NC_NOERR;
      CDFnode* dds = dapcomm->cdf.ddsroot;
      NC* ncsub;
  
      ncstat = NC_check_id(dapcomm->controller->substrate,&ncsub);
      if(ncstat != NC_NOERR) goto done;
  
      ncstat = buildglobalattrs(dapcomm,dds);
      if(ncstat != NC_NOERR) goto done;
  
      ncstat = builddims(dapcomm);
      if(ncstat != NC_NOERR) goto done;
  
      ncstat = buildvars(dapcomm);
      if(ncstat != NC_NOERR) goto done;
  
  done:
      return THROW(ncstat);
  }
  
  static NCerror
  builddims(NCDAPCOMMON* dapcomm)
  {
      int i;
      NCerror ncstat = NC_NOERR;
      int dimid;
      NClist* dimset = NULL;
      NC* drno = dapcomm->controller;
      NC* ncsub;
      char* definename;
  
      /* collect all dimensions from variables */
      dimset = dapcomm->cdf.ddsroot->tree->dimnodes;
  
      /* Sort by fullname just for the fun of it */
      for(;;) {
  	int last = nclistlength(dimset) - 1;
  	int swap = 0;
          for(i=0;i<last;i++) {
  	    CDFnode* dim1 = (CDFnode*)nclistget(dimset,i);
  	    CDFnode* dim2 = (CDFnode*)nclistget(dimset,i+1);
     	    if(strcmp(dim1->ncfullname,dim2->ncfullname) > 0) {
  		nclistset(dimset,i,(void*)dim2);
  		nclistset(dimset,i+1,(void*)dim1);
  		swap = 1;
  		break;
  	    }
  	}
  	if(!swap) break;
      }
  
      /* Define unlimited only if needed */
      if(dapcomm->cdf.recorddim != NULL) {
  	CDFnode* unlimited = dapcomm->cdf.recorddim;
  	definename = getdefinename(unlimited);
          ncstat = nc_def_dim(drno->substrate,
  			definename,
  			NC_UNLIMITED,
  			&unlimited->ncid);
  	nullfree(definename);
          if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto done;}
  
          /* get the id for the substrate */
          ncstat = NC_check_id(drno->substrate,&ncsub);
          if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto done;}
  #if 0
  	nc3sub = (NC3_INFO*)&ncsub->dispatchdata;
          /* Set the effective size of UNLIMITED;
             note that this cannot easily be done thru the normal API.*/
          NC_set_numrecs(nc3sub,unlimited->dim.declsize);
  #endif
  
      }
  
      for(i=0;i<nclistlength(dimset);i++) {
  	CDFnode* dim = (CDFnode*)nclistget(dimset,i);
          if(dim->dim.basedim != NULL) continue; /* handle below */
  	if(DIMFLAG(dim,CDFDIMRECORD)) continue; /* defined above */
  #ifdef DEBUG1
  fprintf(stderr,"define: dim: %s=%ld\n",dim->ncfullname,(long)dim->dim.declsize);
  #endif
  	definename = getdefinename(dim);
          ncstat = nc_def_dim(drno->substrate,definename,dim->dim.declsize,&dimid);
          if(ncstat != NC_NOERR) {
            THROWCHK(ncstat); nullfree(definename); goto done;
  	}
  	nullfree(definename);
          dim->ncid = dimid;
      }
  
      /* Make all duplicate dims have same dimid as basedim*/
      /* (see computecdfdimnames)*/
      for(i=0;i<nclistlength(dimset);i++) {
  	CDFnode* dim = (CDFnode*)nclistget(dimset,i);
          if(dim->dim.basedim != NULL) {
  	    dim->ncid = dim->dim.basedim->ncid;
  	}
      }
  done:
      nclistfree(dimset);
      return THROW(ncstat);
  }
  
  /* Simultaneously build any associated attributes*/
  /* and any necessary pseudo-dimensions for string types*/
  static NCerror
  buildvars(NCDAPCOMMON* dapcomm)
  {
      int i,j;
      NCerror ncstat = NC_NOERR;
      int varid;
      NClist* varnodes = dapcomm->cdf.ddsroot->tree->varnodes;
      NC* drno = dapcomm->controller;
      char* definename;
  
      ASSERT((varnodes != NULL));
      for(i=0;i<nclistlength(varnodes);i++) {
  	CDFnode* var = (CDFnode*)nclistget(varnodes,i);
          int dimids[NC_MAX_VAR_DIMS];
  	unsigned int ncrank;
          NClist* vardims = NULL;
  
  	if(var->invisible) continue;
  	if(var->array.basevar != NULL) continue;
  
  #ifdef DEBUG1
  fprintf(stderr,"buildvars.candidate=|%s|\n",var->ncfullname);
  #endif
  
  	vardims = var->array.dimsetall;
  	ncrank = nclistlength(vardims);
  	if(ncrank > 0) {
              for(j=0;j<ncrank;j++) {
                  CDFnode* dim = (CDFnode*)nclistget(vardims,j);
                  dimids[j] = dim->ncid;
   	    }
          }
  
  
  
  	definename = getdefinename(var);
  
  #ifdef DEBUG1
  fprintf(stderr,"define: var: %s/%s",
  		definename,var->ocname);
  if(ncrank > 0) {
  int k;
  for(k=0;k<ncrank;k++) {
  CDFnode* dim = (CDFnode*)nclistget(vardims,k);
  fprintf(stderr,"[%ld]",dim->dim.declsize);
   }
   }
  fprintf(stderr,"\n");
  #endif
          ncstat = nc_def_var(drno->substrate,
  		        definename,
                          var->externaltype,
                          ncrank,
                          (ncrank==0?NULL:dimids),
                          &varid);
  	nullfree(definename);
          if(ncstat != NC_NOERR) {
  	    THROWCHK(ncstat);
  	    goto done;
  	}
          var->ncid = varid;
  	if(var->attributes != NULL) {
  	    for(j=0;j<nclistlength(var->attributes);j++) {
  		NCattribute* att = (NCattribute*)nclistget(var->attributes,j);
  		ncstat = buildattribute(dapcomm,att,var->etype,varid);
          	if(ncstat != NC_NOERR) goto done;
  	    }
  	}
  	/* Tag the variable with its DAP path */
  	if(dapparamcheck(dapcomm,"show","projection"))
  	    showprojection(dapcomm,var);
      }
  done:
      return THROW(ncstat);
  }
  
  static NCerror
  buildglobalattrs(NCDAPCOMMON* dapcomm, CDFnode* root)
  {
      int i;
      NCerror ncstat = NC_NOERR;
      const char* txt;
      char *nltxt, *p;
      NCbytes* buf = NULL;
      NClist* cdfnodes;
      NC* drno = dapcomm->controller;
  
      if(root->attributes != NULL) {
          for(i=0;i<nclistlength(root->attributes);i++) {
     	    NCattribute* att = (NCattribute*)nclistget(root->attributes,i);
  	    ncstat = buildattribute(dapcomm,att,NC_NAT,NC_GLOBAL);
              if(ncstat != NC_NOERR) goto done;
  	}
      }
  
      /* Add global attribute identifying the sequence dimensions */
      if(dapparamcheck(dapcomm,"show","seqdims")) {
          buf = ncbytesnew();
          cdfnodes = dapcomm->cdf.ddsroot->tree->nodes;
          for(i=0;i<nclistlength(cdfnodes);i++) {
  	    CDFnode* dim = (CDFnode*)nclistget(cdfnodes,i);
  	    if(dim->nctype != NC_Dimension) continue;
  	    if(DIMFLAG(dim,CDFDIMSEQ)) {
  	        char* cname = cdflegalname(dim->ocname);
  	        if(ncbyteslength(buf) > 0) ncbytescat(buf,", ");
  	        ncbytescat(buf,cname);
  	        nullfree(cname);
  	    }
  	}
          if(ncbyteslength(buf) > 0) {
              ncstat = nc_put_att_text(drno->substrate,NC_GLOBAL,"_sequence_dimensions",
  	           ncbyteslength(buf),ncbytescontents(buf));
  	}
      }
  
      /* Define some additional system global attributes
         depending on show= clientparams*/
      /* Ignore failures*/
  
      if(dapparamcheck(dapcomm,"show","translate")) {
          /* Add a global attribute to show the translation */
          ncstat = nc_put_att_text(drno->substrate,NC_GLOBAL,"_translate",
  	           strlen("netcdf-3"),"netcdf-3");
      }
      if(dapparamcheck(dapcomm,"show","url")) {
  	if(dapcomm->oc.rawurltext != NULL)
              ncstat = nc_put_att_text(drno->substrate,NC_GLOBAL,"_url",
  				       strlen(dapcomm->oc.rawurltext),dapcomm->oc.rawurltext);
      }
      if(dapparamcheck(dapcomm,"show","dds")) {
  	txt = NULL;
  	if(dapcomm->cdf.ddsroot != NULL)
    	    txt = oc_tree_text(dapcomm->oc.conn,dapcomm->cdf.ddsroot->ocnode);
  	if(txt != NULL) {
  	    /* replace newlines with spaces*/
  	    nltxt = nulldup(txt);
  	    for(p=nltxt;*p;p++) {if(*p == '\n' || *p == '\r' || *p == '\t') {*p = ' ';}};
              ncstat = nc_put_att_text(drno->substrate,NC_GLOBAL,"_dds",strlen(nltxt),nltxt);
  	    nullfree(nltxt);
  	}
      }
      if(dapparamcheck(dapcomm,"show","das")) {
  	txt = NULL;
  	if(dapcomm->oc.ocdasroot != NULL)
  	    txt = oc_tree_text(dapcomm->oc.conn,dapcomm->oc.ocdasroot);
  	if(txt != NULL) {
  	    nltxt = nulldup(txt);
  	    for(p=nltxt;*p;p++) {if(*p == '\n' || *p == '\r' || *p == '\t') {*p = ' ';}};
              ncstat = nc_put_att_text(drno->substrate,NC_GLOBAL,"_das",strlen(nltxt),nltxt);
  	    nullfree(nltxt);
  	}
      }
  
  done:
      ncbytesfree(buf);
      return THROW(ncstat);
  }
  
  static NCerror
  buildattribute(NCDAPCOMMON* dapcomm, NCattribute* att, nc_type vartype, int varid)
  {
      int i;
      NCerror ncstat = NC_NOERR;
      unsigned int nvalues = nclistlength(att->values);
      NC* drno = dapcomm->controller;
  
      /* If the type of the attribute is string, then we need*/
      /* to convert to a single character string by concatenation.
  	modified: 10/23/09 to insert newlines.
  	modified: 10/28/09 to interpret escapes
      */
      if(att->etype == NC_STRING || att->etype == NC_URL) {
  	char* newstring = NULL;
  	size_t newlen = 0;
  	for(i=0;i<nvalues;i++) {
  	    char* s = (char*)nclistget(att->values,i);
  	    newlen += (1+strlen(s));
  	}
      if(newlen > 0)
        newstring = (char*)malloc(newlen);
  
      MEMCHECK(newstring,NC_ENOMEM);
  	newstring[0] = '\0';
  	for(i=0;i<nvalues;i++) {
  	    char* s = (char*)nclistget(att->values,i);
  	    if(i > 0) strcat(newstring,"\n");
  	    strcat(newstring,s);
  	}
          dapexpandescapes(newstring);
  	if(newstring[0]=='\0')
  	    ncstat = nc_put_att_text(drno->substrate,varid,att->name,1,newstring);
  	else
  	    ncstat = nc_put_att_text(drno->substrate,varid,att->name,strlen(newstring),newstring);
  	free(newstring);
      } else {
  	nc_type atype;
  	unsigned int typesize;
  	void* mem = NULL;
  	/* It turns out that some servers upgrade the type
             of _FillValue in order to correctly preserve the
             original value. However, since the type of the
             underlying variable is not changes, we get a type
             mismatch. So, make sure the type of the fillvalue
             is the same as that of the controlling variable.
  	*/
          if(varid != NC_GLOBAL && strcmp(att->name,"_FillValue")==0)
  	    atype = nctypeconvert(dapcomm,vartype);
  	else
  	    atype = nctypeconvert(dapcomm,att->etype);
  	typesize = nctypesizeof(atype);
  	if (nvalues > 0) {
  		mem = malloc(typesize * nvalues);
  #ifdef _MSC_VER
  		_ASSERTE(_CrtCheckMemory());
  #endif
  	}
      ncstat = dapcvtattrval(atype,mem,att->values);
  #ifdef _MSC_VER
  	_ASSERTE(_CrtCheckMemory());
  #endif
      ncstat = nc_put_att(drno->substrate,varid,att->name,atype,nvalues,mem);
  #ifdef _MSC_VER
  	_ASSERTE(_CrtCheckMemory());
  #endif
  	if (mem != NULL)
  		free(mem);
  	}
      return THROW(ncstat);
  }
  
  static char*
  getdefinename(CDFnode* node)
  {
      char* spath = NULL;
      NClist* path = NULL;
  
      switch (node->nctype) {
      case NC_Atomic:
  	/* The define name is same as the fullname with elided nodes */
  	path = nclistnew();
          collectnodepath(node,path,!WITHDATASET);
          spath = makepathstring(path,".",PATHNC|PATHELIDE);
          nclistfree(path);
  	break;
  
      case NC_Dimension:
  	/* Return just the node's ncname */
  	spath = nulldup(node->ncbasename);
  	break;
  
      default:
  	PANIC("unexpected nctype");
      }
      return spath;
  }
  
  int
  NCDAP_ping(const char* url)
  {
      OCerror ocstat = OC_NOERR;
      ocstat = oc_ping(url);
      return ocerrtoncerr(ocstat);
  }
  
  int
  NCD2_inq_format_extended(int ncid, int* formatp, int* modep)
  {
      NC* nc;
      int ncstatus = NC_check_id(ncid, (NC**)&nc);
      if(ncstatus != NC_NOERR) return THROW(ncstatus);
      if(modep) *modep = nc->mode;
      if(formatp) *formatp = NC_FORMATX_DAP2;
      return NC_NOERR;
  }
  
  /**************************************************/
  /* Support functions */
  
  /*
     Provide short and/or unified names for dimensions.
     This must mimic lib-ncdap, which is difficult.
  */
  NCerror
  computecdfdimnames(NCDAPCOMMON* nccomm)
  {
      int i,j;
      char tmp[NC_MAX_NAME*2];
      NClist* conflicts = nclistnew();
      NClist* varnodes = nccomm->cdf.ddsroot->tree->varnodes;
      NClist* alldims;
      NClist* basedims;
  
      /* Collect all dimension nodes from dimsetall lists */
  
      alldims = getalldims(nccomm,0);
  
      /* Assign an index to all anonymous dimensions
         vis-a-vis its containing variable
      */
      for(i=0;i<nclistlength(varnodes);i++) {
  	CDFnode* var = (CDFnode*)nclistget(varnodes,i);
          for(j=0;j<nclistlength(var->array.dimsetall);j++) {
  	    CDFnode* dim = (CDFnode*)nclistget(var->array.dimsetall,j);
  	    if(dim->ocname != NULL) continue; /* not anonymous */
   	    computedimindexanon(dim,var);
  	}
      }
  
      /* Unify dimensions by defining one dimension as the "base"
         dimension, and make all "equivalent" dimensions point to the
         base dimension.
  	1. Equivalent means: same size and both have identical non-null names.
  	2. Dims with same name but different sizes will be handled separately
      */
      for(i=0;i<nclistlength(alldims);i++) {
  	CDFnode* dupdim = NULL;
  	CDFnode* basedim = (CDFnode*)nclistget(alldims,i);
  	if(basedim == NULL) continue;
  	if(basedim->dim.basedim != NULL) continue; /* already processed*/
  	for(j=i+1;j<nclistlength(alldims);j++) { /* Sigh, n**2 */
  	    dupdim = (CDFnode*)nclistget(alldims,j);
  	    if(basedim == dupdim) continue;
  	    if(dupdim == NULL) continue;
  	    if(dupdim->dim.basedim != NULL) continue; /* already processed */
  	    if(!equivalentdim(basedim,dupdim))
  		continue;
              dupdim->dim.basedim = basedim; /* equate */
  #ifdef DEBUG1
  fprintf(stderr,"assign: %s/%s -> %s/%s\n",
  basedim->dim.array->ocname,basedim->ocname,
  dupdim->dim.array->ocname,dupdim->ocname
  );
  #endif
  	}
      }
  
      /* Next case: same name and different sizes*/
      /* => rename second dim */
  
      for(i=0;i<nclistlength(alldims);i++) {
  	CDFnode* basedim = (CDFnode*)nclistget(alldims,i);
  	if(basedim->dim.basedim != NULL) continue;
  	/* Collect all conflicting dimensions */
  	nclistclear(conflicts);
          for(j=i+1;j<nclistlength(alldims);j++) {
  	    CDFnode* dim = (CDFnode*)nclistget(alldims,j);
  	    if(dim->dim.basedim != NULL) continue;
  	    if(dim->ocname == NULL && basedim->ocname == NULL) continue;
  	    if(dim->ocname == NULL || basedim->ocname == NULL) continue;
  	    if(strcmp(dim->ocname,basedim->ocname)!=0) continue;
  	    if(dim->dim.declsize == basedim->dim.declsize) continue;
  #ifdef DEBUG2
  fprintf(stderr,"conflict: %s[%lu] %s[%lu]\n",
  			basedim->ncfullname,(unsigned long)basedim->dim.declsize,
  			dim->ncfullname,(unsigned long)dim->dim.declsize);
  #endif
  	    nclistpush(conflicts,(void*)dim);
  	}
  	/* Give  all the conflicting dimensions an index */
  	for(j=0;j<nclistlength(conflicts);j++) {
  	    CDFnode* dim = (CDFnode*)nclistget(conflicts,j);
  	    dim->dim.index1 = j+1;
  	}
      }
      nclistfree(conflicts);
  
      /* Replace all non-base dimensions with their base dimension */
      for(i=0;i<nclistlength(varnodes);i++) {
  	CDFnode* node = (CDFnode*)nclistget(varnodes,i);
  	replacedims(node->array.dimsetall);
  	replacedims(node->array.dimsetplus);
  	replacedims(node->array.dimset0);
      }
  
      /* Collect list of all basedims */
      basedims = nclistnew();
      for(i=0;i<nclistlength(alldims);i++) {
  	CDFnode* dim = (CDFnode*)nclistget(alldims,i);
  	if(dim->dim.basedim == NULL) {
  	    if(!nclistcontains(basedims,(void*)dim)) {
  		nclistpush(basedims,(void*)dim);
  	    }
  	}
      }
  
      nccomm->cdf.ddsroot->tree->dimnodes = basedims;
  
      /* cleanup */
      nclistfree(alldims);
  
      /* Assign ncbasenames and ncfullnames to base dimensions */
      for(i=0;i<nclistlength(basedims);i++) {
  	CDFnode* dim = (CDFnode*)nclistget(basedims,i);
  	CDFnode* var = dim->dim.array;
  	if(dim->dim.basedim != NULL) PANIC1("nonbase basedim: %s\n",dim->ocname);
  	/* stringdim names are already assigned */
  	if(dim->ocname == NULL) { /* anonymous: use the index to compute the name */
              snprintf(tmp,sizeof(tmp),"%s_%d",
                              var->ncfullname,dim->dim.index1-1);
              nullfree(dim->ncbasename);
              dim->ncbasename = cdflegalname(tmp);
              nullfree(dim->ncfullname);
              dim->ncfullname = nulldup(dim->ncbasename);
      	} else { /* !anonymous; use index1 if defined */
     	    char* legalname = cdflegalname(dim->ocname);
  	    nullfree(dim->ncbasename);
  	    if(dim->dim.index1 > 0) {/* need to fix conflicting names (see above) */
  	        char sindex[64];
  		snprintf(sindex,sizeof(sindex),"_%d",dim->dim.index1);
  		dim->ncbasename = (char*)malloc(strlen(sindex)+strlen(legalname)+1);
  		if(dim->ncbasename == NULL) {nullfree(legalname); return NC_ENOMEM;}
  		strcpy(dim->ncbasename,legalname);
  		strcat(dim->ncbasename,sindex);
  		nullfree(legalname);
  	    } else {/* standard case */
  	        dim->ncbasename = legalname;
  	    }
      	    nullfree(dim->ncfullname);
  	    dim->ncfullname = nulldup(dim->ncbasename);
  	}
       }
  
      /* Verify unique and defined names for dimensions*/
      for(i=0;i<nclistlength(basedims);i++) {
  	CDFnode* dim1 = (CDFnode*)nclistget(basedims,i);
          CDFnode* dim2 = NULL;
  	if(dim1->dim.basedim != NULL) PANIC1("nonbase basedim: %s\n",dim1->ncbasename);
  	if(dim1->ncbasename == NULL || dim1->ncfullname == NULL)
  	    PANIC1("missing dim names: %s",dim1->ocname);
  	/* search backward so we can delete duplicates */
  	for(j=nclistlength(basedims)-1;j>i;j--) {
        if(!dim1->ncfullname) continue;
        dim2 = (CDFnode*)nclistget(basedims,j);
        if(strcmp(dim1->ncfullname,dim2->ncfullname)==0) {
  		/* complain and suppress one of them */
  		fprintf(stderr,"duplicate dim names: %s[%lu] %s[%lu]\n",
                  dim1->ncfullname,(unsigned long)dim1->dim.declsize,
                  dim2->ncfullname,(unsigned long)dim2->dim.declsize);
  		nclistremove(basedims,j);
        }
  	}
      }
  
  #ifdef DEBUG
  for(i=0;i<nclistlength(basedims);i++) {
  CDFnode* dim = (CDFnode*)nclistget(basedims,i);
  fprintf(stderr,"basedim: %s=%ld\n",dim->ncfullname,(long)dim->dim.declsize);
   }
  #endif
  
      return NC_NOERR;
  }
  
  int
  constrainable(NCURI* durl)
  {
     char** protocol = constrainableprotocols;
     for(;*protocol;protocol++) {
  	if(strcmp(durl->protocol,*protocol)==0)
  	    return 1;
     }
     return 0;
  }
  
  /* Note: this routine only applies some common
     client parameters, other routines may apply
     specific ones.
  */
  
  static NCerror
  applyclientparams(NCDAPCOMMON* nccomm)
  {
      int i,len;
      int dfaltstrlen = DEFAULTSTRINGLENGTH;
      int dfaltseqlim = DEFAULTSEQLIMIT;
      const char* value;
      char tmpname[NC_MAX_NAME+32];
      char* pathstr;
      OClink conn = nccomm->oc.conn;
      unsigned long limit;
  
      ASSERT(nccomm->oc.url != NULL);
  
      nccomm->cdf.cache->cachelimit = DFALTCACHELIMIT;
      value = oc_clientparam_get(conn,"cachelimit");
      limit = getlimitnumber(value);
      if(limit > 0) nccomm->cdf.cache->cachelimit = limit;
  
      nccomm->cdf.fetchlimit = DFALTFETCHLIMIT;
      value = oc_clientparam_get(conn,"fetchlimit");
      limit = getlimitnumber(value);
      if(limit > 0) nccomm->cdf.fetchlimit = limit;
  
      nccomm->cdf.smallsizelimit = DFALTSMALLLIMIT;
      value = oc_clientparam_get(conn,"smallsizelimit");
      limit = getlimitnumber(value);
      if(limit > 0) nccomm->cdf.smallsizelimit = limit;
  
      nccomm->cdf.cache->cachecount = DFALTCACHECOUNT;
  #ifdef HAVE_GETRLIMIT
      { struct rlimit rl;
        if(getrlimit(RLIMIT_NOFILE, &rl) >= 0) {
  	nccomm->cdf.cache->cachecount = (size_t)(rl.rlim_cur / 2);
        }
      }
  #endif
      value = oc_clientparam_get(conn,"cachecount");
      limit = getlimitnumber(value);
      if(limit > 0) nccomm->cdf.cache->cachecount = limit;
      /* Ignore limit if not caching */
      if(!FLAGSET(nccomm->controls,NCF_CACHE))
          nccomm->cdf.cache->cachecount = 0;
  
      if(oc_clientparam_get(conn,"nolimit") != NULL)
  	dfaltseqlim = 0;
      value = oc_clientparam_get(conn,"limit");
      if(value != NULL && strlen(value) != 0) {
          if(sscanf(value,"%d",&len) && len > 0) dfaltseqlim = len;
      }
      nccomm->cdf.defaultsequencelimit = dfaltseqlim;
  
      /* allow embedded _ */
      value = oc_clientparam_get(conn,"stringlength");
      if(value != NULL && strlen(value) != 0) {
          if(sscanf(value,"%d",&len) && len > 0) dfaltstrlen = len;
      }
      nccomm->cdf.defaultstringlength = dfaltstrlen;
  
      /* String dimension limits apply to variables */
      for(i=0;i<nclistlength(nccomm->cdf.ddsroot->tree->varnodes);i++) {
  	CDFnode* var = (CDFnode*)nclistget(nccomm->cdf.ddsroot->tree->varnodes,i);
  	/* Define the client param stringlength for this variable*/
  	var->maxstringlength = 0; /* => use global dfalt */
  	strcpy(tmpname,"stringlength_");
  	pathstr = makeocpathstring(conn,var->ocnode,".");
  	strncat(tmpname,pathstr,NC_MAX_NAME);
  	nullfree(pathstr);
  	value = oc_clientparam_get(conn,tmpname);
          if(value != NULL && strlen(value) != 0) {
              if(sscanf(value,"%d",&len) && len > 0) var->maxstringlength = len;
  	}
      }
      /* Sequence limits apply to sequences */
      for(i=0;i<nclistlength(nccomm->cdf.ddsroot->tree->nodes);i++) {
  	CDFnode* var = (CDFnode*)nclistget(nccomm->cdf.ddsroot->tree->nodes,i);
  	if(var->nctype != NC_Sequence) continue;
  	var->sequencelimit = dfaltseqlim;
  	strcpy(tmpname,"nolimit_");
  	pathstr = makeocpathstring(conn,var->ocnode,".");
  	strncat(tmpname,pathstr,NC_MAX_NAME);
  	if(oc_clientparam_get(conn,tmpname) != NULL)
  	    var->sequencelimit = 0;
  	strcpy(tmpname,"limit_");
  	strncat(tmpname,pathstr,NC_MAX_NAME);
  	value = oc_clientparam_get(conn,tmpname);
          if(value != NULL && strlen(value) != 0) {
              if(sscanf(value,"%d",&len) && len > 0)
  		var->sequencelimit = len;
  	}
  	nullfree(pathstr);
      }
  
      /* test for the appropriate fetch flags */
      value = oc_clientparam_get(conn,"fetch");
      if(value != NULL && strlen(value) > 0) {
  	if(value[0] == 'd' || value[0] == 'D') {
              SETFLAG(nccomm->controls,NCF_ONDISK);
  	}
      }
  
      /* test for the force-whole-var flag */
      value = oc_clientparam_get(conn,"wholevar");
      if(value != NULL) {
          SETFLAG(nccomm->controls,NCF_WHOLEVAR);
      }
  
      return NC_NOERR;
  }
  
  /**
   *  Given an anonymous dimension, compute the
   *  effective 0-based index wrt to the specified var.
   *  The result should mimic the libnc-dap indices.
   */
  
  static void
  computedimindexanon(CDFnode* dim, CDFnode* var)
  {
      int i;
      NClist* dimset = var->array.dimsetall;
      for(i=0;i<nclistlength(dimset);i++) {
  	CDFnode* candidate = (CDFnode*)nclistget(dimset,i);
          if(dim == candidate) {
  	   dim->dim.index1=i+1;
  	   return;
  	}
      }
  }
  
  /* Replace dims in a list with their corresponding basedim */
  static void
  replacedims(NClist* dims)
  {
      int i;
      for(i=0;i<nclistlength(dims);i++) {
          CDFnode* dim = (CDFnode*)nclistget(dims,i);
  	CDFnode* basedim = dim->dim.basedim;
  	if(basedim == NULL) continue;
  	nclistset(dims,i,(void*)basedim);
      }
  }
  
  /**
   Two dimensions are equivalent if
   1. they have the same size
   2. neither are anonymous
   3. they ave the same names.
   */
  static int
  equivalentdim(CDFnode* basedim, CDFnode* dupdim)
  {
      if(dupdim->dim.declsize != basedim->dim.declsize) return 0;
      if(basedim->ocname == NULL && dupdim->ocname == NULL) return 0;
      if(basedim->ocname == NULL || dupdim->ocname == NULL) return 0;
      if(strcmp(dupdim->ocname,basedim->ocname) != 0) return 0;
      return 1;
  }
  
  static void
  getalldimsa(NClist* dimset, NClist* alldims)
  {
      int i;
      for(i=0;i<nclistlength(dimset);i++) {
  	CDFnode* dim = (CDFnode*)nclistget(dimset,i);
  	if(!nclistcontains(alldims,(void*)dim)) {
  #ifdef DEBUG3
  fprintf(stderr,"getalldims: %s[%lu]\n",
  			dim->ncfullname,(unsigned long)dim->dim.declsize);
  #endif
  	    nclistpush(alldims,(void*)dim);
  	}
      }
  }
  
  /* Accumulate a set of all the known dimensions
     vis-a-vis defined variables
  */
  NClist*
  getalldims(NCDAPCOMMON* nccomm, int visibleonly)
  {
      int i;
      NClist* alldims = nclistnew();
      NClist* varnodes = nccomm->cdf.ddsroot->tree->varnodes;
  
      /* get bag of all dimensions */
      for(i=0;i<nclistlength(varnodes);i++) {
  	CDFnode* node = (CDFnode*)nclistget(varnodes,i);
  	if(!visibleonly || !node->invisible) {
  	    getalldimsa(node->array.dimsetall,alldims);
  	}
      }
      return alldims;
  }
  
  
  static NCerror
  addstringdims(NCDAPCOMMON* dapcomm)
  {
      /* for all variables of string type, we will need another dimension
         to represent the string; Accumulate the needed sizes and create
         the dimensions with a specific name: either as specified
         in DODS{...} attribute set or defaulting to the variable name.
         All such dimensions are global.
      */
      int i;
      NClist* varnodes = dapcomm->cdf.ddsroot->tree->varnodes;
      CDFnode* globalsdim = NULL;
      char dimname[4096];
      size_t dimsize;
  
      /* Start by creating the global string dimension */
      snprintf(dimname,sizeof(dimname),"maxStrlen%lu",
  	    (unsigned long)dapcomm->cdf.defaultstringlength);
      globalsdim = makecdfnode(dapcomm, dimname, OC_Dimension, NULL,
                                   dapcomm->cdf.ddsroot);
      nclistpush(dapcomm->cdf.ddsroot->tree->nodes,(void*)globalsdim);
      DIMFLAGSET(globalsdim,CDFDIMSTRING);
      globalsdim->dim.declsize = dapcomm->cdf.defaultstringlength;
      globalsdim->dim.declsize0 = globalsdim->dim.declsize;
      globalsdim->dim.array = dapcomm->cdf.ddsroot;
      globalsdim->ncbasename = cdflegalname(dimname);
      globalsdim->ncfullname = nulldup(globalsdim->ncbasename);
      dapcomm->cdf.globalstringdim = globalsdim;
  
      for(i=0;i<nclistlength(varnodes);i++) {
  	CDFnode* var = (CDFnode*)nclistget(varnodes,i);
  	CDFnode* sdim = NULL;
  
  	/* Does this node need a string dim? */
  	if(var->etype != NC_STRING && var->etype != NC_URL) continue;
  
  	dimsize = 0;
  	if(var->dodsspecial.maxstrlen > 0)
  	    dimsize = var->dodsspecial.maxstrlen;
  	else
  	    dimsize = var->maxstringlength;
  
  	/* check is a variable-specific string length was specified */
  	if(dimsize == 0)
  	    sdim = dapcomm->cdf.globalstringdim; /* use default */
  	else {
  	    /* create a psuedo dimension for the charification of the string*/
  	    if(var->dodsspecial.dimname != NULL) {
  	        strncpy(dimname,var->dodsspecial.dimname,sizeof(dimname));
  	        dimname[sizeof(dimname)-1] = '\0';
  	    } else
  	        snprintf(dimname,sizeof(dimname),"maxStrlen%lu",
  			 (unsigned long)dimsize);
  	    sdim = makecdfnode(dapcomm, dimname, OC_Dimension, NULL,
                                   dapcomm->cdf.ddsroot);
  	    if(sdim == NULL) return THROW(NC_ENOMEM);
  	    nclistpush(dapcomm->cdf.ddsroot->tree->nodes,(void*)sdim);
  	    DIMFLAGSET(sdim,CDFDIMSTRING);
  	    sdim->dim.declsize = dimsize;
  	    sdim->dim.declsize0 = dimsize;
  	    sdim->dim.array = var;
  	    sdim->ncbasename = cdflegalname(sdim->ocname);
  	    sdim->ncfullname = nulldup(sdim->ncbasename);
  	}
  	/* tag the variable with its string dimension*/
  	var->array.stringdim = sdim;
      }
      return NC_NOERR;
  }
  
  static NCerror
  defrecorddim(NCDAPCOMMON* dapcomm)
  {
      unsigned int i;
      NCerror ncstat = NC_NOERR;
      NClist* basedims;
  
      if(dapcomm->cdf.recorddimname == NULL) return NC_NOERR; /* ignore */
      /* Locate the base dimension matching the record dim */
      basedims = dapcomm->cdf.ddsroot->tree->dimnodes;
      for(i=0;i<nclistlength(basedims);i++) {
          CDFnode* dim = (CDFnode*)nclistget(basedims,i);
  	if(strcmp(dim->ocname,dapcomm->cdf.recorddimname) != 0) continue;
          DIMFLAGSET(dim,CDFDIMRECORD);
  	dapcomm->cdf.recorddim = dim;
  	break;
      }
  
      return ncstat;
  }
  
  static NCerror
  defseqdims(NCDAPCOMMON* dapcomm)
  {
      unsigned int i = 0;
      NCerror ncstat = NC_NOERR;
      int seqdims = 1; /* default is to compute seq dims counts */
  
      /* Does the user want to compute actual sequence sizes? */
      if(dapparamvalue(dapcomm,"noseqdims")) seqdims = 0;
  
      /*
  	Compute and define pseudo dimensions for sequences
  	meeting the following qualifications:
  	1. all parents (transitively) of the sequence must
             be either a dataset or a scalar structure.
  	2. it must be possible to find a usable sequence constraint.
  	All other sequences will be ignored.
      */
  
      for(i=0;i<nclistlength(dapcomm->cdf.ddsroot->tree->seqnodes);i++) {
          CDFnode* seq = (CDFnode*)nclistget(dapcomm->cdf.ddsroot->tree->seqnodes,i);
  	size_t seqsize = 0;
  	CDFnode* sqdim = NULL;
  	CDFnode* container;
  	/* Does this sequence match the requirements for use ? */
  	seq->usesequence = 1; /* assume */
  	for(container=seq->container;container != NULL;container=container->container) {
  	    if(container->nctype == NC_Dataset) break;
  	    if(container->nctype != NC_Structure
  	       || nclistlength(container->array.dimset0) > 0)
  		{seq->usesequence = 0; break;}/* no good */
  	}
  	/* Does the user want us to compute the actual sequence dim size? */
  	if(seq->usesequence && seqdims) {
  	    ncstat = getseqdimsize(dapcomm,seq,&seqsize);
  	    if(ncstat != NC_NOERR) {
                  /* Cannot read sequence; mark as unusable */
  	        seq->usesequence = 0;
  	    }
  	} else { /* !seqdims default to size = 1 */
  	    seqsize = 1;
  	}
  	if(seq->usesequence) {
  	    /* Note: we are making the dimension in the dds root tree */
              ncstat = makeseqdim(dapcomm,seq,seqsize,&sqdim);
              if(ncstat) goto fail;
              seq->array.seqdim = sqdim;
  	} else
              seq->array.seqdim = NULL;
      }
  
  fail:
      return ncstat;
  }
  
  static NCerror
  showprojection(NCDAPCOMMON* dapcomm, CDFnode* var)
  {
      int i,rank;
      NCerror ncstat = NC_NOERR;
      NCbytes* projection = ncbytesnew();
      NClist* path = nclistnew();
      NC* drno = dapcomm->controller;
  
      /* Collect the set of DDS node name forming the xpath */
      collectnodepath(var,path,WITHOUTDATASET);
      for(i=0;i<nclistlength(path);i++) {
          CDFnode* node = (CDFnode*)nclistget(path,i);
  	if(i > 0) ncbytescat(projection,".");
  	ncbytescat(projection,node->ocname);
      }
      nclistfree(path);
      /* Now, add the dimension info */
      rank = nclistlength(var->array.dimset0);
      for(i=0;i<rank;i++) {
  	CDFnode* dim = (CDFnode*)nclistget(var->array.dimset0,i);
  	char tmp[32];
  	ncbytescat(projection,"[");
  	snprintf(tmp,sizeof(tmp),"%lu",(unsigned long)dim->dim.declsize);
  	ncbytescat(projection,tmp);
  	ncbytescat(projection,"]");
      }
      /* Define the attribute */
      ncstat = nc_put_att_text(getncid(drno),var->ncid,
                                 "_projection",
  		               ncbyteslength(projection),
  			       ncbytescontents(projection));
      ncbytesfree(projection);
      return ncstat;
  }
  
  static NCerror
  getseqdimsize(NCDAPCOMMON* dapcomm, CDFnode* seq, size_t* sizep)
  {
      NCerror ncstat = NC_NOERR;
      OCerror ocstat = OC_NOERR;
      OClink conn = dapcomm->oc.conn;
      OCdatanode rootcontent = NULL;
      OCddsnode ocroot;
      CDFnode* dxdroot;
      CDFnode* xseq;
      NCbytes* seqcountconstraints = ncbytesnew();
      size_t seqsize = 0;
  
      /* Read the minimal amount of data in order to get the count */
      /* If the url is unconstrainable, then get the whole thing */
      computeseqcountconstraints(dapcomm,seq,seqcountconstraints);
  #ifdef DEBUG
  fprintf(stderr,"seqcountconstraints: %s\n",ncbytescontents(seqcountconstraints));
  #endif
  
      /* Fetch the minimal data */
      if(FLAGSET(dapcomm->controls,NCF_UNCONSTRAINABLE))
          ncstat = dap_fetch(dapcomm,conn,NULL,OCDATADDS,&ocroot);
      else
          ncstat = dap_fetch(dapcomm,conn,ncbytescontents(seqcountconstraints),OCDATADDS,&ocroot);
      if(ncstat) goto fail;
  
      ncstat = buildcdftree(dapcomm,ocroot,OCDATA,&dxdroot);
      if(ncstat) goto fail;
      /* attach DATADDS to DDS */
      ncstat = attach(dxdroot,seq);
      if(ncstat) goto fail;
  
      /* WARNING: we are now switching to datadds tree */
      xseq = seq->attachment;
      ncstat = countsequence(dapcomm,xseq,&seqsize);
      if(ncstat != NC_NOERR) goto fail;
  
  #ifdef DEBUG
  fprintf(stderr,"sequencesize: %s = %lu\n",seq->ocname,(unsigned long)seqsize);
  #endif
  
      /* throw away the fetch'd trees */
      unattach(dapcomm->cdf.ddsroot);
      freecdfroot(dxdroot);
  #if 1
  /*Note sure what this is doing?*/
      if(ncstat != NC_NOERR) {
          /* Cannot get DATADDDS*/
  	char* code;
  	char* msg;
  	long httperr;
  	oc_svcerrordata(dapcomm->oc.conn,&code,&msg,&httperr);
  	if(code != NULL) {
  	    nclog(NCLOGERR,"oc_fetch_datadds failed: %s %s %l",
  			code,msg,httperr);
  	}
  	ocstat = OC_NOERR;
      }
  #endif
  
      if(sizep) *sizep = seqsize;
  
  fail:
      ncbytesfree(seqcountconstraints);
      oc_data_free(conn,rootcontent);
      if(ocstat != OC_NOERR) ncstat = ocerrtoncerr(ocstat);
      return ncstat;
  }
  
  static NCerror
  makeseqdim(NCDAPCOMMON* dapcomm, CDFnode* seq, size_t count, CDFnode** sqdimp)
  {
      CDFnode* sqdim;
      CDFnode* root = seq->root;
      CDFtree* tree = root->tree;
  
      /* build the dimension with given size; keep the dimension anonymous */
      sqdim = makecdfnode(dapcomm,seq->ocname,OC_Dimension,NULL,root);
      if(sqdim == NULL) return THROW(NC_ENOMEM);
      nclistpush(tree->nodes,(void*)sqdim);
      /* Assign a name to the sequence node */
      sqdim->ncbasename = cdflegalname(seq->ocname);
      sqdim->ncfullname = nulldup(sqdim->ncbasename);
      DIMFLAGSET(sqdim,CDFDIMSEQ);
      sqdim->dim.declsize = count;
      sqdim->dim.declsize0 = count;
      sqdim->dim.array = seq;
      if(sqdimp) *sqdimp = sqdim;
      return NC_NOERR;
  }
  
  static NCerror
  countsequence(NCDAPCOMMON* dapcomm, CDFnode* xseq, size_t* sizep)
  {
      unsigned int i;
      NClist* path = nclistnew();
      int index;
      OCerror ocstat = OC_NOERR;
      NCerror ncstat = NC_NOERR;
      OClink conn = dapcomm->oc.conn;
      size_t recordcount;
      CDFnode* xroot;
      OCdatanode data = NULL;
  
      ASSERT((xseq->nctype == NC_Sequence));
  
      /* collect the path to the sequence node */
      collectnodepath(xseq,path,WITHDATASET);
  
      /* Get tree root */
      ASSERT(xseq->root == (CDFnode*)nclistget(path,0));
      xroot = xseq->root;
      ocstat = oc_data_getroot(conn,xroot->tree->ocroot,&data);
      if(ocstat) goto done;
  
      /* Basically we use the path to walk the data instances to reach
         the sequence instance
      */
      for(i=0;i<nclistlength(path);i++) {
          CDFnode* current = (CDFnode*)nclistget(path,i);
  	OCdatanode nextdata = NULL;
  	CDFnode* next = NULL;
  
  	/* invariant: current = ith node in path; data = corresponding
             datanode
          */
  
  	/* get next node in next and next instance in nextdata */
  	if(current->nctype == NC_Structure
  	   || current->nctype == NC_Dataset) {
  	    if(nclistlength(current->array.dimset0) > 0) {
  		/* Cannot handle this case */
  		ncstat = THROW(NC_EDDS);
  		goto done;
  	    }
  	    /* get next node in path; structure/dataset => exists */
  	    next = (CDFnode*)nclistget(path,i+1);
  	    index = fieldindex(current,next);
              /* Move to appropriate field */
  	    ocstat = oc_data_ithfield(conn,data,index,&nextdata);
  	    if(ocstat) goto done;
  	    oc_data_free(conn,data);
  	    data = nextdata; /* set up for next loop iteration */
  	} else if(current->nctype ==  NC_Sequence) {
  	    /* Check for nested Sequences */
  	    if(current != xseq) {
  		/* Cannot handle this case */
  		ncstat = THROW(NC_EDDS);
  		goto done;
  	    }
  	    /* Get the record count */
  	    ocstat = oc_data_recordcount(conn,data,&recordcount);
      	    if(sizep) *sizep = recordcount;
  	    oc_data_free(conn,data); /* reclaim */
  	    break; /* leave the loop */
  	} else {
  	    PANIC("unexpected mode");
  	    return NC_EINVAL;
          }
      }
  
  done:
      nclistfree(path);
      if(ocstat != OC_NOERR) ncstat = ocerrtoncerr(ocstat);
      return THROW(ncstat);
  }
  
  static NCerror
  freeNCDAPCOMMON(NCDAPCOMMON* dapcomm)
  {
      freenccache(dapcomm,dapcomm->cdf.cache);
      nclistfree(dapcomm->cdf.projectedvars);
      nullfree(dapcomm->cdf.recorddimname);
  
      /* free the trees */
      freecdfroot(dapcomm->cdf.ddsroot);
      dapcomm->cdf.ddsroot = NULL;
      freecdfroot(dapcomm->cdf.fullddsroot);
      dapcomm->cdf.fullddsroot = NULL;
      if(dapcomm->oc.ocdasroot != NULL)
          oc_root_free(dapcomm->oc.conn,dapcomm->oc.ocdasroot);
      dapcomm->oc.ocdasroot = NULL;
      oc_close(dapcomm->oc.conn); /* also reclaims remaining OC trees */
      ncurifree(dapcomm->oc.url);
      nullfree(dapcomm->oc.urltext);
      nullfree(dapcomm->oc.rawurltext);
  
      dcefree((DCEnode*)dapcomm->oc.dapconstraint);
      dapcomm->oc.dapconstraint = NULL;
  
      free(dapcomm);
  
      return NC_NOERR;
  }
  
  static size_t
  fieldindex(CDFnode* parent, CDFnode* child)
  {
      unsigned int i;
      for(i=0;i<nclistlength(parent->subnodes);i++) {
  	CDFnode* node = (CDFnode*)nclistget(parent->subnodes,i);
  	if(node == child) return i;
      }
      return -1;
  }
  
  /*
  This is more complex than one might think. We want to find
  a path to a variable inside the given node so that we can
  ask for a single instance of that variable to minimize the
  amount of data we retrieve. However, we want to avoid passing
  through any nested sequence. This is possible because of the way
  that sequencecheck() works.
  TODO: some servers will not accept an unconstrained fetch, so
  make sure we always have a constraint.
  */
  
  static NCerror
  computeseqcountconstraints(NCDAPCOMMON* dapcomm, CDFnode* seq, NCbytes* seqcountconstraints)
  {
      int i,j;
      NClist* path = NULL;
      CDFnode* var = NULL;
  
      ASSERT(seq->nctype == NC_Sequence);
      computeseqcountconstraintsr(dapcomm,seq,&var);
  
      ASSERT((var != NULL));
  
      /* Compute var path */
      path = nclistnew();
      collectnodepath(var,path,WITHOUTDATASET);
  
      /* construct the projection path using minimal index values */
      for(i=0;i<nclistlength(path);i++) {
  	CDFnode* node = (CDFnode*)nclistget(path,i);
  	if(i > 0) ncbytescat(seqcountconstraints,".");
  	ncbytescat(seqcountconstraints,node->ocname);
  	if(node == seq) {
  	    /* Use the limit */
  	    if(node->sequencelimit > 0) {
  		char tmp[64];
  		snprintf(tmp,sizeof(tmp),"[0:%lu]",
  		         (unsigned long)(node->sequencelimit - 1));
  		ncbytescat(seqcountconstraints,tmp);
  	    }
  	} else if(nclistlength(node->array.dimset0) > 0) {
  	    int ndims = nclistlength(node->array.dimset0);
  	    for(j=0;j<ndims;j++) {
  		CDFnode* dim = (CDFnode*)nclistget(node->array.dimset0,j);
  		if(DIMFLAG(dim,CDFDIMSTRING)) {
  		    ASSERT((j == (ndims - 1)));
  		    break;
  		}
  		ncbytescat(seqcountconstraints,"[0]");
  	    }
  	}
      }
      /* Finally, add in any selection from the original URL */
      if(dapcomm->oc.url->selection != NULL)
          ncbytescat(seqcountconstraints,dapcomm->oc.url->selection);
      nclistfree(path);
      return NC_NOERR;
  }
  
  
  /* Given an existing candidate, see if we prefer newchoice */
  static CDFnode*
  prefer(CDFnode* candidate, CDFnode* newchoice)
  {
      nc_type newtyp;
      nc_type cantyp;
      int newisstring;
      int canisstring;
      int newisscalar;
      int canisscalar;
  
      /* always choose !null over null */
      if(newchoice == NULL)
  	return candidate;
      if(candidate == NULL)
  	return newchoice;
  
      newtyp = newchoice->etype;
      cantyp = candidate->etype;
      newisstring = (newtyp == NC_STRING || newtyp == NC_URL);
      canisstring = (cantyp == NC_STRING || cantyp == NC_URL);
      newisscalar = (nclistlength(newchoice->array.dimset0) == 0);
      canisscalar = (nclistlength(candidate->array.dimset0) == 0);
  
      ASSERT(candidate->nctype == NC_Atomic && newchoice->nctype == NC_Atomic);
  
      /* choose non-string over string */
      if(canisstring && !newisstring)
  	return newchoice;
      if(!canisstring && newisstring)
  	return candidate;
  
      /* choose scalar over array */
      if(canisscalar && !newisscalar)
  	return candidate;
      if(!canisscalar && newisscalar)
  	return candidate;
  
      /* otherwise choose existing candidate */
      return candidate;
  }
  
  /* computeseqcountconstraints recursive helper function */
  static void
  computeseqcountconstraintsr(NCDAPCOMMON* dapcomm, CDFnode* node, CDFnode** candidatep)
  {
      CDFnode* candidate;
      CDFnode* compound;
      unsigned int i;
  
      candidate = NULL;
      compound = NULL;
      if(node == NULL)
        return;
      for(i=0;i<nclistlength(node->subnodes);i++) {
          CDFnode* subnode = (CDFnode*)nclistget(node->subnodes,i);
          if(subnode->nctype == NC_Structure || subnode->nctype == NC_Grid)
  	    compound = subnode; /* save for later recursion */
  	else if(subnode->nctype == NC_Atomic)
  	    candidate = prefer(candidate,subnode);
      }
      if(candidate == NULL && compound == NULL) {
  	PANIC("cannot find candidate for seqcountconstraints for a sequence");
      } else if(candidate != NULL && candidatep != NULL) {
  	*candidatep = candidate;
      } else { /* compound != NULL by construction */
  	/* recurse on a nested grids or strucures */
          computeseqcountconstraintsr(dapcomm,compound,candidatep);
      }
  }
  
  
  static unsigned long
  cdftotalsize(NClist* dimensions)
  {
      unsigned int i;
      unsigned long total = 1;
      if(dimensions != NULL) {
  	for(i=0;i<nclistlength(dimensions);i++) {
  	    CDFnode* dim = (CDFnode*)nclistget(dimensions,i);
  	    total *= dim->dim.declsize;
  	}
      }
      return total;
  }
  
  /* Estimate variables sizes and then resort the variable list
     by that size
  */
  static void
  estimatevarsizes(NCDAPCOMMON* dapcomm)
  {
      int ivar;
      unsigned int rank;
      size_t totalsize = 0;
  
      for(ivar=0;ivar<nclistlength(dapcomm->cdf.ddsroot->tree->varnodes);ivar++) {
          CDFnode* var = (CDFnode*)nclistget(dapcomm->cdf.ddsroot->tree->varnodes,ivar);
  	NClist* ncdims = var->array.dimset0;
  	rank = nclistlength(ncdims);
  	if(rank == 0) { /* use instance size of the type */
  	    var->estimatedsize = nctypesizeof(var->etype);
  #ifdef DEBUG1
  fprintf(stderr,"scalar %s.estimatedsize = %lu\n",
  	makecdfpathstring(var,"."),var->estimatedsize);
  #endif
  	} else {
  	    unsigned long size = cdftotalsize(ncdims);
  	    size *= nctypesizeof(var->etype);
  #ifdef DEBUG1
  fprintf(stderr,"array %s(%u).estimatedsize = %lu\n",
  	makecdfpathstring(var,"."),rank,size);
  #endif
  	    var->estimatedsize = size;
  	}
  	totalsize += var->estimatedsize;
      }
  #ifdef DEBUG1
  fprintf(stderr,"total estimatedsize = %lu\n",totalsize);
  #endif
      dapcomm->cdf.totalestimatedsize = totalsize;
  }
  
  static NCerror
  fetchpatternmetadata(NCDAPCOMMON* dapcomm)
  {
      NCerror ncstat = NC_NOERR;
      OCerror ocstat = OC_NOERR;
      OCddsnode ocroot = NULL;
      CDFnode* ddsroot = NULL;
      char* ce = NULL;
  
      /* Temporary hack: we need to get the selection string
         from the url
      */
      /* Get (almost) unconstrained DDS; In order to handle functions
         correctly, those selections must always be included
      */
      if(FLAGSET(dapcomm->controls,NCF_UNCONSTRAINABLE))
  	ce = NULL;
      else
          ce = nulldup(dapcomm->oc.url->selection);
  
      /* Get selection constrained DDS */
      ncstat = dap_fetch(dapcomm,dapcomm->oc.conn,ce,OCDDS,&ocroot);
      if(ncstat != NC_NOERR) {
  	/* Special Hack. If the protocol is file, then see if
             we can get the dds from the .dods file
          */
  	if(strcmp(dapcomm->oc.url->protocol,"file") != 0) {
  	    THROWCHK(ocstat); goto done;
  	}
  	/* Fetch the data dds */
          ncstat = dap_fetch(dapcomm,dapcomm->oc.conn,ce,OCDATADDS,&ocroot);
          if(ncstat != NC_NOERR) {
  	    THROWCHK(ncstat); goto done;
  	}
  	/* Note what we did */
  	nclog(NCLOGWARN,"Cannot locate .dds file, using .dods file");
      }
  
      /* Get selection constrained DAS */
      ncstat = dap_fetch(dapcomm,dapcomm->oc.conn,ce,OCDAS,&dapcomm->oc.ocdasroot);
      if(ncstat != NC_NOERR) {
  	/* Ignore but complain */
  	nclog(NCLOGWARN,"Could not read DAS; ignored");
          dapcomm->oc.ocdasroot = NULL;
  	ncstat = NC_NOERR;
      }
  
      /* Construct the netcdf cdf tree corresponding to the dds tree*/
      ncstat = buildcdftree(dapcomm,ocroot,OCDDS,&ddsroot);
      if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto done;}
      dapcomm->cdf.fullddsroot = ddsroot;
      ddsroot = NULL; /* avoid double reclaim */
  
      /* Combine DDS and DAS */
      if(dapcomm->oc.ocdasroot != NULL) {
  	ncstat = dapmerge(dapcomm,dapcomm->cdf.fullddsroot,
                             dapcomm->oc.ocdasroot);
          if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto done;}
      }
  
  #ifdef DEBUG
  fprintf(stderr,"full pattern:\n%s",dumptree(dapcomm->cdf.fullddsroot));
  #endif
  
  done:
      nullfree(ce);
      if(ocstat != OC_NOERR) ncstat = ocerrtoncerr(ocstat);
      return ncstat;
  }
  
  static NCerror
  fetchconstrainedmetadata(NCDAPCOMMON* dapcomm)
  {
      NCerror ncstat = NC_NOERR;
      OCerror ocstat = OC_NOERR;
      OCddsnode ocroot;
      CDFnode* ddsroot; /* constrained */
      char* ce = NULL;
  
      if(FLAGSET(dapcomm->controls,NCF_UNCONSTRAINABLE))
  	ce = NULL;
      else
          ce = dcebuildconstraintstring(dapcomm->oc.dapconstraint);
      {
          ncstat = dap_fetch(dapcomm,dapcomm->oc.conn,ce,OCDDS,&ocroot);
          if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto fail;}
  
          /* Construct our parallel dds tree; including attributes*/
          ncstat = buildcdftree(dapcomm,ocroot,OCDDS,&ddsroot);
          if(ncstat) goto fail;
  	ocroot = NULL; /* avoid duplicate reclaim */
  
  	dapcomm->cdf.ddsroot = ddsroot;
  	ddsroot = NULL; /* to avoid double reclamation */
  
          if(!FLAGSET(dapcomm->controls,NCF_UNCONSTRAINABLE)) {
              /* fix DAP server problem by adding back any inserting needed structure nodes */
              ncstat = restruct(dapcomm, dapcomm->cdf.ddsroot,dapcomm->cdf.fullddsroot,dapcomm->oc.dapconstraint->projections);
              if(ncstat) goto fail;
  	}
  
  #ifdef DEBUG
  fprintf(stderr,"constrained:\n%s",dumptree(dapcomm->cdf.ddsroot));
  #endif
  
          /* Combine DDS and DAS */
  	if(dapcomm->oc.ocdasroot != NULL) {
              ncstat = dapmerge(dapcomm,dapcomm->cdf.ddsroot,
                                 dapcomm->oc.ocdasroot);
              if(ncstat != NC_NOERR) {THROWCHK(ncstat); goto fail;}
  	}
  
          /* map the constrained DDS to the unconstrained DDS */
          ncstat = mapnodes(dapcomm->cdf.ddsroot,dapcomm->cdf.fullddsroot);
          if(ncstat) goto fail;
  
      }
  
  fail:
      nullfree(ce);
      if(ocstat != OC_NOERR) ncstat = ocerrtoncerr(ocstat);
      return ncstat;
  }
  
  /* Suppress variables not in usable sequences*/
  static NCerror
  suppressunusablevars(NCDAPCOMMON* dapcomm)
  {
      int i,j;
      int found = 1;
      NClist* path = nclistnew();
  
      while(found) {
  	found = 0;
  	/* Walk backwards to aid removal semantics */
  	for(i=nclistlength(dapcomm->cdf.ddsroot->tree->varnodes)-1;i>=0;i--) {
  	    CDFnode* var = (CDFnode*)nclistget(dapcomm->cdf.ddsroot->tree->varnodes,i);
  	    /* See if this var is under an unusable sequence */
  	    nclistclear(path);
  	    collectnodepath(var,path,WITHOUTDATASET);
  	    for(j=0;j<nclistlength(path);j++) {
  		CDFnode* node = (CDFnode*)nclistget(path,j);
  		if(node->nctype == NC_Sequence
  		   && !node->usesequence) {
  #ifdef DEBUG
  fprintf(stderr,"suppressing var in unusable sequence: %s.%s\n",node->ncfullname,var->ncbasename);
  #endif
  		    found = 1;
  		    break;
  		}
  	    }
  	    if(found) break;
  	}
          if(found) nclistremove(dapcomm->cdf.ddsroot->tree->varnodes,i);
      }
      nclistfree(path);
      return NC_NOERR;
  }
  
  
  /*
  For variables which have a zero size dimension,
  make them invisible.
  */
  static NCerror
  fixzerodims(NCDAPCOMMON* dapcomm)
  {
      int i,j;
      for(i=0;i<nclistlength(dapcomm->cdf.ddsroot->tree->varnodes);i++) {
  	CDFnode* var = (CDFnode*)nclistget(dapcomm->cdf.ddsroot->tree->varnodes,i);
          NClist* ncdims = var->array.dimsetplus;
  	if(nclistlength(ncdims) == 0) continue;
          for(j=0;j<nclistlength(ncdims);j++) {
  	    CDFnode* dim = (CDFnode*)nclistget(ncdims,j);
  	    if(dim->dim.declsize == 0) {
  	 	/* make node invisible */
  		var->invisible = 1;
  		var->zerodim = 1;
  	    }
  	}
      }
      return NC_NOERR;
  }
  
  static void
  applyclientparamcontrols(NCDAPCOMMON* dapcomm)
  {
      /* clear the flags */
      CLRFLAG(dapcomm->controls,NCF_CACHE);
      CLRFLAG(dapcomm->controls,NCF_SHOWFETCH);
      CLRFLAG(dapcomm->controls,NCF_NC3);
      CLRFLAG(dapcomm->controls,NCF_NCDAP);
      CLRFLAG(dapcomm->controls,NCF_PREFETCH);
      CLRFLAG(dapcomm->controls,NCF_PREFETCH_EAGER);
  
      /* Turn on any default on flags */
      SETFLAG(dapcomm->controls,DFALT_ON_FLAGS);
      SETFLAG(dapcomm->controls,(NCF_NC3|NCF_NCDAP));
  
      /* enable/disable caching */
      if(dapparamcheck(dapcomm,"cache",NULL))
  	SETFLAG(dapcomm->controls,NCF_CACHE);
      else if(dapparamcheck(dapcomm,"nocache",NULL))
  	CLRFLAG(dapcomm->controls,NCF_CACHE);
  
      /* enable/disable cache prefetch and lazy vs eager*/
      if(dapparamcheck(dapcomm,"prefetch","eager")) {
          SETFLAG(dapcomm->controls,NCF_PREFETCH);
          SETFLAG(dapcomm->controls,NCF_PREFETCH_EAGER);
      } else if(dapparamcheck(dapcomm,"prefetch","lazy")
                || dapparamcheck(dapcomm,"prefetch",NULL)) {
          SETFLAG(dapcomm->controls,NCF_PREFETCH);
          CLRFLAG(dapcomm->controls,NCF_PREFETCH_EAGER);
      } else if(dapparamcheck(dapcomm,"noprefetch",NULL))
          CLRFLAG(dapcomm->controls,NCF_PREFETCH);
  
      if(FLAGSET(dapcomm->controls,NCF_UNCONSTRAINABLE))
  	SETFLAG(dapcomm->controls,NCF_CACHE);
  
      if(dapparamcheck(dapcomm,"show","fetch"))
  	SETFLAG(dapcomm->controls,NCF_SHOWFETCH);
  
      nclog(NCLOGNOTE,"Caching=%d",FLAGSET(dapcomm->controls,NCF_CACHE));
  
  }
  