/* Copyright 2018, UCAR/Unidata and OPeNDAP, Inc. See the COPYRIGHT file for more information. */ #define VALIDATE #define ALLATONCE #undef TRACK #include "config.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #ifdef HAVE_STRINGS_H #include #endif #ifdef HAVE_GETOPT_H #include #endif #include "netcdf.h" #include "ncuri.h" #include "ncbytes.h" #include "nclog.h" #include "ncpathmgr.h" #include "oc.h" #include "ocx.h" #if defined(_WIN32) && !defined(__MINGW32__) #include "XGetopt.h" #endif #ifndef nulldup #define nulldup(s) (s==NULL?NULL:strdup(s)) #endif #define CHECK(x) check_err((ocstat=(x)),0) #define FAIL(x) check_err((x),1) /* Define some classifiers */ #define iscontainer(t) ((t) == OC_Dataset || (t) == OC_Structure || (t) == OC_Sequence || (t) == OC_Grid) #define isatomic(t) ((t) == OC_Atomic) #define NORC "NONE" #define LBRACE "{" #define RBRACE "}" /*Mnemonic*/ #define TOPLEVEL 1 static OCerror ocstat; static OClink glink; /* define a large stack of DUMPPATH datanodes */ struct DUMPPATH { OCdatanode datanode; OCddsnode node; OCtype octype; size_t rank; size_t dimsizes[OC_MAX_DIMENSIONS]; int indexed; /* 1 => indices is valid */ size_t indices[OC_MAX_DIMENSIONS]; } stack[2048]; static size_t stacknext; /* Define the debug options */ struct OCD { int debug; /* -D */ int debuglevel; int dumpdds; /* -DN */ int dumpdatadds; /* -DX */ int dumpdatatree; /* -DD */ int dumplevel; int curl; /* -DC - make curl be verbose */ int verbose; /* -DV - produce more verbose output */ } debug; /* Define the -X options; currently unused*/ struct OCX { int ignore; } x; /* Define the other options */ static struct OCOPT { char* surl; /* full url string */ NCURI* url; struct OCD debug; struct OCX x; int showattributes; /* -A */ int logging; /* -L */ char* netrc ; /* -N */ char* rcfile ; /* -R */ int selfsigned ; /* -S */ int octest; /* -T */ /* match original octest output */ int generate; /* -g|-G */ int optdas; /* -p */ int optdatadds; /* -p */ int optdds; /* -p */ FILE* output; /* -o */ /* Deprecated */ char* constraint; /* -C */ NCbytes* userparams; /* -U */ } ocopt; static char blanks[2048]; #define BLANKSPERDENT 2 /* Forward*/ static void usage(char*); static int fail(char*); static void check_err(OCerror stat, int dofail); static void dumpflags(void); struct OCOPT; static OCerror processdata(int); static OCerror printdata(OClink, OCdatanode); static OCerror printdata_indices(OClink, OCdatanode, NCbytes*,int); static OCerror printdata_container(OClink, OCdatanode, NCbytes*,int); static OCerror printdata_leaf(OClink, OCdatanode, NCbytes*,int); static off_t odom_init(size_t rank, size_t* indices, size_t* dimsizes); static int odom_more(size_t rank, size_t* indices, size_t* dimsizes); static void odom_next(size_t rank, size_t* indices, size_t* dimsizes); static OCerror dumpdatanode(OClink, OCdatanode, size_t count, void* memory, NCbytes*); static OCerror generatedds(OClink, OCddsnode, NCbytes*, int depth); static char* generatedas(OClink,OCddsnode); static OCerror generatedasr(OClink, OCddsnode, NCbytes*, int depth); static OCerror generateddsattributes(OClink, OCddsnode node, NCbytes*, int); static OCerror printdims(OClink link, OCddsnode node, NCbytes* buffer); static char* stringescape(char*); static char* idescape(char*, char*, size_t); static int needsescapes(const char* s); static size_t totaldimsize(size_t,size_t*); static char* indent(int n); static void pushstack(OCdatanode datanode); static void popstack() {stacknext--;} #ifdef TRACK static void printstack(char* msg); #endif static char* optionmsg = " [-A]" " [-D ]" " [-G]" " [-L]" " [-N ]" " [-S]" " [-R ]" " [-T]" " [-h]" " [-o ]" " [-p das|dds|datadds]" " "; static OCflags ocflags; EXTERNL int nc_initialize(void); static void init() { memset(&ocopt,0,sizeof(ocopt)); ocopt.generate = 1; /* -G|-g */ ocopt.userparams = ncbytesnew(); /* -U */ nc_initialize(); } int main(int argc, char **argv) { int c; char* suffix; #ifdef OCDEBUG { int i; fprintf(stderr,"argv ="); for(i=0;i= '0' && c0 <= '9') {/* debug level */ ocopt.debug.debuglevel = atoi(optarg); break; } else switch (c0) { case 'C': ocopt.debug.curl = 1; break; case 'D': ocopt.debug.dumpdatatree = 1; break; case 'N': ocopt.debug.dumpdds = 1; break; case 'X': ocopt.debug.dumpdatadds = 1; ocopt.debug.dumplevel = atoi(optarg+1); break; case 'V': ocopt.debug.verbose = 1; break; default: usage("unknown -D option"); } } break; case 'X': { int c0; int so = (optarg == NULL ? 0 : strlen(optarg)); if(so == 0) usage("missing -X argument"); c0 = optarg[0]; switch (c0) { default: usage("unknown -X option"); } } break; case 'o': if(ocopt.output != NULL) fclose(ocopt.output); if(optarg == NULL) usage("-o does not specify a file name"); ocopt.output = NCfopen(optarg,"w"); if(ocopt.output == NULL) usage("-o file not writeable"); break; case 'u': case 'f': ocopt.surl = optarg; break; case 'p': if(optarg == NULL) usage("-p does not specify an argument"); if(strcasecmp(optarg,"das")==0) ocopt.optdas=1; else if(strcasecmp(optarg,"dds")==0) ocopt.optdds=1; else if(strcasecmp(optarg,"data")==0) ocopt.optdatadds=1; else if(strcasecmp(optarg,"datadds")==0) ocopt.optdatadds=1; else if(strcasecmp(optarg,"all")==0) { ocopt.optdas = 1; ocopt.optdds = 1; ocopt.optdatadds = 1; } else usage("unknown -p option"); break; case 'h': usage(""); exit(0); default: usage("unknown option"); } } if(ocopt.output == NULL) ocopt.output = stdout; if (ocopt.debug.debuglevel > 0) { ocdebug = ocopt.debug.debuglevel; } if(ocopt.logging) { ncloginit(); ncsetlogging(1); if(!nclogopen(NULL)) fprintf(stderr,"Failed to open logging output\n"); } argc -= optind; argv += optind; if (argc > 0 && ocopt.surl== NULL) { ocopt.surl = nulldup(argv[argc - 1]); } else usage("Multiple urls specified"); if (ocopt.surl == NULL) ocopt.surl = getenv("URLSRC"); if (ocopt.surl == NULL) { usage("no source url specified\n"); } /* Compile the url */ if(ncuriparse(ocopt.surl,&ocopt.url)) { fprintf(stderr,"malformed source url: %s\n",ocopt.surl); exit(1); } /* For convenience, see if the url has a trailing .dds, .das, or .dods and if so, use it */ suffix = strrchr(ocopt.url->path,'.'); if(suffix != NULL) { int match = 0; if(strcmp(suffix,".das")==0) { ocopt.optdas = 1; ocopt.optdds = 0; ocopt.optdatadds = 0; match = 1; } else if(strcmp(suffix,".dds")==0) { ocopt.optdas = 0; ocopt.optdds = 1; ocopt.optdatadds = 0; match = 1; } else if(strcmp(suffix,".dods")==0) { ocopt.optdas = 0; ocopt.optdds = 0; ocopt.optdatadds = 1; match = 1; } /* Remove the suffix */ if(match) *suffix = '\0'; } /* If -C was specified, then it has precedence */ if(ocopt.constraint != NULL) { ncurisetquery(ocopt.url,ocopt.constraint); nullfree(ocopt.constraint); ocopt.constraint = NULL; } /* Rebuild the url string */ if(ocopt.surl != NULL) free(ocopt.surl); ocopt.surl = ncuribuild(ocopt.url,NULL,NULL,NCURIALL); /* Reparse */ if(ncuriparse(ocopt.surl,&ocopt.url)) { fprintf(stderr,"malformed source url: %s\n",ocopt.surl); exit(1); } if(ocopt.rcfile != NULL) { } if (ocopt.debug.verbose) dumpflags(); processdata(ocflags); return 0; } static void dumpflags(void) { char* tmp; if(ocopt.showattributes) fprintf(stderr," -A"); if(ocopt.debug.debug) fprintf(stderr," -D%d",ocopt.debug.debuglevel); if(ocopt.debug.dumpdds) fprintf(stderr," -DN"); if(ocopt.debug.dumpdatatree) fprintf(stderr," -DD"); if(ocopt.debug.dumpdatadds) fprintf(stderr," -DX%d",ocopt.debug.dumplevel); if(ocopt.debug.verbose) fprintf(stderr," -DV"); if(ocopt.generate) fprintf(stderr," -G"); if(ocopt.logging) fprintf(stderr," -L"); if(ocopt.logging) fprintf(stderr," -N %s",ocopt.netrc); if(ocopt.logging) fprintf(stderr," -R %s",ocopt.rcfile); if(ocopt.optdas || ocopt.optdds || ocopt.optdatadds) { fprintf(stderr," -p"); if(ocopt.optdas) fprintf(stderr," das"); if(ocopt.optdds) fprintf(stderr," dds"); if(ocopt.optdatadds) fprintf(stderr," datadds"); } tmp = ncuribuild(ocopt.url,NULL,NULL,NCURIALL); fprintf(stderr,"%s\n",tmp); free(tmp); } static void usage(char* msg) { if(msg) fprintf(stderr,"error: %s\n",msg); fprintf(stderr,"usage: ocprint %s\n",optionmsg); fail(NULL); } static int fail(char* msg) { if(msg) fprintf(stderr,"fatalerror: %s\n",msg); fflush(ocopt.output); fflush(stderr); exit(1); } /******************************************/ static void check_err(OCerror ocstat, int dofail) { if(ocstat == OC_NOERR) return; fprintf(stderr,"error status returned: (%d) %s\n",ocstat,oc_errstring(ocstat)); if(dofail) fail(NULL); } static OCerror processdata(OCflags flags) { char* totalurl; OClink link; OCddsnode dasroot, ddsroot, dataddsroot; OCdatanode rootdatanode; totalurl = ncuribuild(ocopt.url,NULL,NULL,NCURIALL); FAIL(oc_open(totalurl,&link)); free(totalurl); glink = link; if(ocopt.debug.curl) oc_trace_curl(link); if(ocopt.netrc) oc_set_netrc(link,ocopt.netrc); #if 0 if(ocopt.selfsigned) oc_set_curlopt(link,"CURLOPT_VERIFYPEER", (void*)0L); #endif if(ocopt.optdas) { ocstat = oc_fetch(link,ocopt.url->query,OCDAS,0,&dasroot); if(ocstat != OC_NOERR) { fprintf(stderr,"error status returned: (%d) %s\n",ocstat,oc_errstring(ocstat)); fprintf(stderr,"Could not read DAS; continuing.\n"); ocopt.optdas = 0; ocopt.showattributes = 0; } else if(ocopt.generate) { char* das = generatedas(link,dasroot); fprintf(ocopt.output,"%s",das); free(das); } else { const char* text = oc_tree_text(link,dasroot); fprintf(ocopt.output,"%s",(text?text:"null")); } } fflush(ocopt.output); if(ocopt.optdds) { ocstat = oc_fetch(link,ocopt.url->query,OCDDS,flags,&ddsroot); if(ocstat != OC_NOERR) { fprintf(stderr,"error status returned: (%d) %s\n",ocstat,oc_errstring(ocstat)); fprintf(stderr,"Could not read DDS; continuing.\n"); ocopt.optdds = 0; } else { if(ocopt.showattributes && !ocopt.optdas) { FAIL(oc_fetch(link,ocopt.url->query,OCDAS,flags,&dasroot)); } if(ocopt.showattributes || ocopt.optdas) { FAIL(oc_merge_das(link,dasroot,ddsroot)); } if(ocopt.generate) { NCbytes* buffer = ncbytesnew(); FAIL(generatedds(link,ddsroot,buffer,0)); fprintf(ocopt.output,"%s",ncbytescontents(buffer)); ncbytesfree(buffer); } else { const char* text = oc_tree_text(link,ddsroot); fprintf(ocopt.output,"%s",(text?text:"null")); } } if(ocopt.debug.dumpdds) oc_dds_ddnode(link,ddsroot); } fflush(ocopt.output); if(ocopt.optdatadds) { ocstat = oc_fetch(link,ocopt.url->query,OCDATADDS,flags,&dataddsroot); if(ocstat) { fprintf(stderr,"Cannot read DATADDS: %s\n",ocopt.surl); exit(1); } if(ocopt.debug.dumpdds) oc_dds_ddnode(link,dataddsroot); if(ocopt.debug.dumpdatadds) oc_dds_dd(link,dataddsroot,ocopt.debug.dumplevel); FAIL(oc_dds_getdataroot(link,dataddsroot,&rootdatanode)); if(ocopt.debug.dumpdatatree) oc_data_ddtree(link,rootdatanode); stacknext = 0; printdata(link,rootdatanode); } fflush(ocopt.output); oc_close(link); return OC_NOERR; } /** This is the main procedure for printing a data tree. */ static OCerror printdata(OClink link, OCdatanode datanode) { NCbytes* buffer; OCtype octype; buffer = ncbytesnew(); /* verify that datanode is a Dataset datanode */ FAIL(oc_data_octype(link,datanode,&octype)); assert(octype == OC_Dataset); printdata_container(link,datanode,buffer,TOPLEVEL); fprintf(ocopt.output,"%s",ncbytescontents(buffer)); ncbytesfree(buffer); return OC_NOERR; } /* Print a single container datanode */ static OCerror printdata_container(OClink link, OCdatanode datanode, NCbytes* buffer, int istoplevel) { OCerror stat = OC_NOERR; size_t i; OCddsnode node; OCtype octype; size_t nsubnodes; /* Obtain some information about the node */ FAIL(oc_data_ddsnode(link,datanode,&node)); FAIL(oc_dds_nsubnodes(link,node,&nsubnodes)); FAIL(oc_data_octype(link,datanode,&octype)); /* If this is not a single instance container, then defer to the appropriate function */ if(isatomic(octype)) return printdata_leaf(link,datanode,buffer,istoplevel); if(oc_data_indexable(link,datanode)) return printdata_indices(link,datanode,buffer,!TOPLEVEL); /* Must be a container instance or a record */ for(i=0;i 0) { for(i=0;i 0) ncbytescat(buffer,","); if(ptype == OC_String || ptype == OC_URL) { char* se = stringescape(value); snprintf(tmp,sizeof(tmp)," \"%s\"",se); free(se); } else snprintf(tmp,sizeof(tmp)," %s",value); ncbytescat(buffer,tmp); free(value); } ncbytescat(buffer,";\n"); } else { snprintf(tmp,sizeof(tmp),"ocget DAS: unexpected type: %d",(int)octype); ncbytescat(buffer,tmp); } if(name) free(name); return OC_NOERR; } static char hexdigits[] = "0123456789abcdef"; /* Add escape characters to a string */ static char* stringescape(char* s) { size_t len; char* p; int c; char* escapedstring; if(s == NULL) return NULL; len = strlen(s); escapedstring = (char*)malloc(4*len); p = escapedstring; while((c=*s++)) { if(c == '"' || c == '\\') {*p++ = '\\'; *p++ = c;} else if (c < ' ' || c >= 127) { *p++ = '\\'; *p++ = 'x'; *p++ = hexdigits[(c & 0xf0)>>4]; *p++ = hexdigits[(c & 0xf)]; } else *p++ = c; } *p = '\0'; return escapedstring; } static char idchars[] = "_%"; /* Add escape characters to an identifier */ static char* idescape(char* id, char* escapeid, size_t esize) { char* p; int c; if(id == NULL) return NULL; p = escapeid; *p = '\0'; esize--; /* leave room for null */ while(esize-- > 0 && (c=*id++)) { if(c >= '0' && c <= '9') {*p++ = c;} else if(c >= 'a' && c <= 'z') {*p++ = c;} else if(c >= 'A' && c <= 'Z') {*p++ = c;} else if(strchr(idchars,c) != NULL) {*p++ = c;} else { *p++ = '%'; *p++ = hexdigits[(c & 0xf0)>>4]; *p++ = hexdigits[(c & 0xf)]; } } *p = '\0'; return escapeid; } static char valuechars[] = " \\\""; /** Return 1 if the given string, used as a value, should be escaped. */ static int needsescapes(const char* s) { const char* p = s; int c; while((c=*p++)) { if(strchr(valuechars,c) != NULL) return 1; /* needs to be escaped */ } return 0; } static OCerror dumpdatanode(OClink link, OCdatanode datanode, size_t count, void* memory, NCbytes* buffer) { size_t i; size_t delta; OCddsnode node; OCtype atomtype; OCtype octype; NCbytes* path = NULL; char* name = NULL; char id[1024]; char tmp[1024]; struct DUMPPATH* entry = NULL; FAIL(oc_data_ddsnode(link,datanode,&node)); FAIL(oc_dds_octype(link,node,&octype)); FAIL(oc_dds_atomictype(link,node,&atomtype)); delta = oc_typesize(atomtype); #ifdef TRACK printstack("dumpdatanode"); #endif /* construct the fully qualified name from the stack; watch out for duplicates from e.g. sequence versus record */ path = ncbytesnew(); for(i=0;inode == stack[i+1].node) continue; /* Get various pieces of additional node information */ FAIL(oc_dds_name(glink,entry->node,&name)); (void)idescape(name,id,sizeof(id)); if(name) free(name); switch (entry->octype) { case OC_Dataset: break; case OC_Structure: ncbytescat(path,"/"); ncbytescat(path,id); if(entry->rank > 0) { for(i=0;irank;i++) { sprintf(tmp,"[%lu]",(unsigned long)entry->indices[i]); ncbytescat(path,tmp); } } break; case OC_Grid: ncbytescat(path,"/"); ncbytescat(path,id); break; case OC_Sequence: ncbytescat(path,"/"); ncbytescat(path,id); sprintf(tmp,"[%lu]",(unsigned long)entry->indices[0]); ncbytescat(path,tmp); break; case OC_Atomic: ncbytescat(path,"/"); ncbytescat(path,id); break; /* deal with below */ default: ncbytescat(path,"?"); break; } } /* work with the final entry */ assert(entry == (stack + (stacknext - 1))); assert(datanode == entry->datanode); snprintf(tmp,sizeof(tmp),"%s %s", oc_typetostring(atomtype), ncbytescontents(path)); ncbytescat(buffer,tmp); if(entry->rank > 0) { if(ocopt.octest) { /* Match the octest output */ off_t xproduct; xproduct = totaldimsize(entry->rank,entry->dimsizes); snprintf(tmp,sizeof(tmp),"[0..%lu]",(unsigned long)xproduct-1); ncbytescat(buffer,tmp); } else { for(i=0;irank;i++) { snprintf(tmp,sizeof(tmp),"[0..%lu]",(unsigned long)entry->dimsizes[i]-1); ncbytescat(buffer,tmp); } } } count = totaldimsize(entry->rank,entry->dimsizes); for(i=0;i=0;i--) { indices[i]++; if(indices[i] < dimsizes[i]) break; if(i > 0) indices[i] = 0; } } /* Return 0 if we have exhausted the indices, 1 otherwise */ static int odom_more(size_t rank, size_t* indices, size_t* dimsizes) { if(indices[0] >= dimsizes[0]) return 0; return 1; } /* Compute total # of elements if dimensioned */ static size_t totaldimsize(size_t rank, size_t* sizes) { unsigned int i; size_t count = 1; for(i=0;idatanode = datanode; FAIL(oc_data_ddsnode(glink,entry->datanode,&entry->node)); FAIL(oc_dds_octype(glink,entry->node,&entry->octype)); FAIL(oc_dds_rank(glink,entry->node,&entry->rank)); if(entry->rank > 0) { FAIL(oc_dds_dimensionsizes(glink,entry->node,entry->dimsizes)); } entry->indexed = oc_data_indexed(glink,entry->datanode); if(entry->indexed) { FAIL(oc_data_position(glink,entry->datanode,entry->indices)); } stacknext++; } #ifdef TRACK static void printstack(char* msg) { size_t i,j; struct DUMPPATH* entry; fprintf(stderr,"\n%s@stack: %u\n",msg,stacknext); for(entry=stack,i=0;idatanode; OCddsnode node; size_t rank; size_t edges[OC_MAX_DIMENSIONS]; char* name; FAIL(oc_dds_rank(glink,entry->node,&rank)); if(entry->rank > 0) FAIL(oc_dds_dimensionsizes(glink,entry->node,edges)); FAIL(oc_dds_name(glink,node,&name)); fprintf(stderr," [%d] (%s)",(int)i,name) for(j=0;j