mirror of
https://github.com/Unidata/netcdf-c.git
synced 2024-12-27 08:49:16 +08:00
1015 lines
26 KiB
C
1015 lines
26 KiB
C
/**
|
|
* @file
|
|
*
|
|
* Infer as much as possible from the omode + path.
|
|
* Possibly rewrite the path.
|
|
*
|
|
* Copyright 2018 University Corporation for Atmospheric
|
|
* Research/Unidata. See COPYRIGHT file for more info.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "ncdispatch.h"
|
|
#include "ncwinpath.h"
|
|
#include "netcdf_mem.h"
|
|
#include "fbits.h"
|
|
#include "ncbytes.h"
|
|
#include "nclist.h"
|
|
#include "nclog.h"
|
|
#ifdef ENABLE_HTTP
|
|
#include "nchttp.h"
|
|
#endif
|
|
|
|
#undef DEBUG
|
|
|
|
/**
|
|
Sort info for open/read/close of
|
|
file when searching for magic numbers
|
|
*/
|
|
struct MagicFile {
|
|
const char* path;
|
|
struct NCURI* uri;
|
|
NCmodel* model;
|
|
fileoffset_t filelen;
|
|
int use_parallel;
|
|
void* parameters; /* !NULL if inmemory && !diskless */
|
|
FILE* fp;
|
|
#ifdef USE_PARALLEL
|
|
MPI_File fh;
|
|
#endif
|
|
#ifdef ENABLE_HTTP
|
|
void* curl; /* avoid need to include curl.h */
|
|
char* curlurl; /* url to use with CURLOPT_SET_URL */
|
|
#endif
|
|
};
|
|
|
|
/** @internal Magic number for HDF5 files. To be consistent with
|
|
* H5Fis_hdf5, use the complete HDF5 magic number */
|
|
static char HDF5_SIGNATURE[MAGIC_NUMBER_LEN] = "\211HDF\r\n\032\n";
|
|
|
|
#ifdef DEBUG
|
|
|
|
static void dbgflush(void)
|
|
{
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
}
|
|
|
|
static void
|
|
fail(int err)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static int
|
|
check(int err)
|
|
{
|
|
if(err != NC_NOERR)
|
|
fail(err);
|
|
return err;
|
|
}
|
|
#else
|
|
#define check(err) (err)
|
|
#endif
|
|
|
|
#define modelcomplete(model) ((model)->format != 0 && (model)->iosp != 0 && (model)->impl != 0)
|
|
|
|
enum mfield {MF, MI, MIO, MV};
|
|
|
|
/* Wrap model field assignment to fail if the
|
|
existing value is not zero and not same as src value
|
|
*/
|
|
#define conflictset(f,dst,src) do {if((dst) != 0 && (src) != (dst)) {stat=conflictfail(f,(dst),(src)); goto done;} else {(dst) = (src);} } while(0)
|
|
|
|
/*
|
|
Define a table of iosp string values for "mode=".
|
|
Includes cases where the impl or format implies the
|
|
iosp. Does not includes cases where NC_IOSP_FILE is
|
|
the inferred iosp.
|
|
*/
|
|
static struct IOSPS {
|
|
const char* tag;
|
|
const int iosp; /* NC_IOSP_XXX value */
|
|
} iosps[] = {
|
|
{"dap2",NC_IOSP_DAP2},
|
|
{"dap4",NC_IOSP_DAP4},
|
|
{"bytes",NC_IOSP_HTTP},
|
|
{NULL,0}
|
|
};
|
|
|
|
/*
|
|
Define a table of "mode=" string values.
|
|
Note that only cases that can currently
|
|
take URLs are included.
|
|
*/
|
|
static struct FORMATMODES {
|
|
const char* tag;
|
|
const int format; /* NC_FORMAT_XXX value */
|
|
const int impl; /* NC_FORMATX_XXX value */
|
|
} formatmodes[] = {
|
|
{"dap2",NC_FORMAT_CLASSIC,NC_FORMATX_DAP2},
|
|
{"dap4",NC_FORMAT_NETCDF4,NC_FORMATX_DAP4},
|
|
{"netcdf-3",NC_FORMAT_CLASSIC,NC_FORMATX_NC3},
|
|
{"classic",NC_FORMAT_CLASSIC,NC_FORMATX_NC3},
|
|
{"netcdf-4",NC_FORMAT_NETCDF4,NC_FORMATX_NC4},
|
|
{"enhanced",NC_FORMAT_NETCDF4,NC_FORMATX_NC4},
|
|
{"64bitoffset",NC_FORMAT_64BIT_OFFSET,0},
|
|
{"64bitdata",NC_FORMAT_64BIT_DATA,0},
|
|
{"cdf5",NC_FORMAT_64BIT_DATA,0}, /*alias*/
|
|
#if 0
|
|
{"hdf4",NC_FORMAT_HDF4,NC_FORMATX_NC4},
|
|
#endif
|
|
{NULL,0,0},
|
|
};
|
|
|
|
/* Define the legal singleton mode tags */
|
|
static const char* modesingles[] = {
|
|
"dap2", "dap4", "bytes", "zarr", NULL,
|
|
};
|
|
|
|
/* Map IOSP to readability to get magic number */
|
|
static struct IospRead {
|
|
int iosp;
|
|
int readable;
|
|
} readable[] = {
|
|
{NC_IOSP_FILE,1},
|
|
{NC_IOSP_MEMORY,1},
|
|
{NC_IOSP_UDF,0},
|
|
{NC_IOSP_HTTP,1},
|
|
{0,0},
|
|
};
|
|
|
|
/* Define the known URL protocols and their interpretation */
|
|
static struct NCPROTOCOLLIST {
|
|
const char* protocol;
|
|
const char* substitute;
|
|
const char* mode;
|
|
} ncprotolist[] = {
|
|
{"http",NULL,NULL},
|
|
{"https",NULL,NULL},
|
|
{"file",NULL,NULL},
|
|
{"dods","http","dap2"},
|
|
{"dap4","http","dap4"},
|
|
{NULL,NULL,NULL} /* Terminate search */
|
|
};
|
|
|
|
/* Forward */
|
|
static int NC_omodeinfer(int omode, NCmodel*);
|
|
static int NC_implinfer(int useparallel, NCmodel* model);
|
|
static int NC_dapinfer(NClist*, NCmodel* model);
|
|
static int check_file_type(const char *path, int flags, int use_parallel, void *parameters, NCmodel* model, NCURI* uri);
|
|
static int processuri(const char* path, NCURI** urip, char** newpathp, NClist* modeargs);
|
|
static int extractiosp(NClist* modeargs, int mode, NCmodel* model);
|
|
|
|
static int openmagic(struct MagicFile* file);
|
|
static int readmagic(struct MagicFile* file, long pos, char* magic);
|
|
static int closemagic(struct MagicFile* file);
|
|
static int NC_interpret_magic_number(char* magic, NCmodel* model);
|
|
#ifdef DEBUG
|
|
static void printmagic(const char* tag, char* magic,struct MagicFile*);
|
|
#endif
|
|
static int isreadable(int iosp);
|
|
|
|
/* Report a conflicting model field assignment;
|
|
see the conflictset macro above */
|
|
static int
|
|
conflictfail(enum mfield f, int dst, int src)
|
|
{
|
|
const char* sf = NULL;
|
|
switch (f) {
|
|
case MF: sf = "format"; break;
|
|
case MI: sf = "impl"; break;
|
|
case MIO: sf = "iosp"; break;
|
|
case MV: sf = "version"; break;
|
|
default: sf = "?"; break;
|
|
}
|
|
nclog(NCLOGERR,"Model inference conflict: field=%s dst=%d src=%d", sf,dst,src);
|
|
return NC_EINVAL;
|
|
}
|
|
|
|
/* Parse a mode string at the commas and convert to envv form */
|
|
static int
|
|
parseurlmode(const char* modestr, NClist* list)
|
|
{
|
|
int stat = NC_NOERR;
|
|
const char* p = NULL;
|
|
const char* endp = NULL;
|
|
|
|
if(modestr == NULL || *modestr == '\0') goto done;
|
|
|
|
/* Split modestr at the commas or EOL */
|
|
p = modestr;
|
|
for(;;) {
|
|
char* s;
|
|
ptrdiff_t slen;
|
|
endp = strchr(p,',');
|
|
if(endp == NULL) endp = p + strlen(p);
|
|
slen = (endp - p);
|
|
if((s = malloc(slen+1)) == NULL) {stat = NC_ENOMEM; goto done;}
|
|
memcpy(s,p,slen);
|
|
s[slen] = '\0';
|
|
nclistpush(list,s);
|
|
if(*endp == '\0') break;
|
|
p = endp+1;
|
|
}
|
|
|
|
done:
|
|
return check(stat);
|
|
}
|
|
|
|
/* Given a mode= argument, and the mode flags,
|
|
infer the iosp part of the model */
|
|
static int
|
|
extractiosp(NClist* modeargs, int cmode, NCmodel* model)
|
|
{
|
|
int stat = NC_NOERR;
|
|
struct IOSPS* io = iosps;
|
|
|
|
assert(model->iosp == 0);
|
|
for(;io->tag;io++) {
|
|
int i;
|
|
for(i=0;i<nclistlength(modeargs);i++) {
|
|
const char* p = nclistget(modeargs,i);
|
|
if(strcmp(p,io->tag)==0) {
|
|
conflictset(MIO,model->iosp,io->iosp);
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
done:
|
|
if(model->iosp == 0)
|
|
model->iosp = (fIsSet(cmode,NC_INMEMORY) ? NC_IOSP_MEMORY:NC_IOSP_FILE);
|
|
return stat;
|
|
}
|
|
|
|
/* Given a mode= argument, fill in the matching part of the model; except IOSP */
|
|
static int
|
|
processmodearg(const char* arg, NCmodel* model)
|
|
{
|
|
int stat = NC_NOERR;
|
|
struct FORMATMODES* format = formatmodes;
|
|
for(;format->tag;format++) {
|
|
if(strcmp(format->tag,arg)==0) {
|
|
conflictset(MF,model->format,format->format);
|
|
conflictset(MI,model->impl,format->impl);
|
|
}
|
|
}
|
|
done:
|
|
return check(stat);
|
|
}
|
|
|
|
/* Search singleton list */
|
|
static int
|
|
issingleton(const char* tag)
|
|
{
|
|
const char** p;
|
|
for(p=modesingles;*p;p++) {
|
|
if(strcmp(*p,tag)==0) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* If we have a url, see if we can determine DAP */
|
|
static int
|
|
NC_dapinfer(NClist* modeargs, NCmodel* model)
|
|
{
|
|
int stat = NC_NOERR;
|
|
int i;
|
|
|
|
/* 1. search modeargs for indicators */
|
|
for(i=0;i<nclistlength(modeargs);i++) {
|
|
const char* arg = nclistget(modeargs,i);
|
|
if(strcasecmp(arg,"bytes")==0
|
|
|| strcasecmp(arg,"zarr")==0) {
|
|
/* Ok, we know this is not DAP, so give up */
|
|
return stat;
|
|
}
|
|
if(strcasecmp(arg,"dap2")==0) {
|
|
model->format = NC_FORMAT_NC3;
|
|
model->iosp = NC_IOSP_DAP2;
|
|
model->impl = NC_FORMATX_DAP2;
|
|
} else if(strcasecmp(arg,"dap4")==0) {
|
|
model->format = NC_FORMAT_NETCDF4;
|
|
model->iosp = NC_IOSP_DAP4;
|
|
model->impl = NC_FORMATX_DAP4;
|
|
}
|
|
}
|
|
/* Ok, we have a URL, but no tags to tell us what it is, so assume DAP2 */
|
|
if(model->impl == 0) {
|
|
model->format = NC_FORMAT_NC3;
|
|
model->iosp = NC_IOSP_DAP2;
|
|
model->impl = NC_FORMATX_DAP2;
|
|
}
|
|
return stat;
|
|
}
|
|
|
|
/*
|
|
Infer from the mode
|
|
only call if iscreate or file is not easily readable.
|
|
*/
|
|
static int
|
|
NC_omodeinfer(int cmode, NCmodel* model)
|
|
{
|
|
int stat = NC_NOERR;
|
|
|
|
/* If no format flags are set, then use default */
|
|
if(!fIsSet(cmode,NC_FORMAT_ALL))
|
|
conflictset(MF,model->format,nc_get_default_format());
|
|
|
|
/* Process the cmode; may override some already set flags */
|
|
if(fIsSet(cmode,NC_64BIT_OFFSET)) {
|
|
conflictset(MF,model->format,NC_FORMAT_64BIT_OFFSET);
|
|
}
|
|
if(fIsSet(cmode,NC_64BIT_DATA)) {
|
|
conflictset(MF,model->format,NC_FORMAT_64BIT_DATA);
|
|
}
|
|
if(fIsSet(cmode,NC_NETCDF4)) {
|
|
conflictset(MF,model->format,NC_FORMAT_NETCDF4);
|
|
}
|
|
if(fIsSet(cmode,(NC_UDF0|NC_UDF1))) {
|
|
conflictset(MF,model->format,NC_FORMAT_NETCDF4);
|
|
/* For user formats, we must back out some previous decisions */
|
|
model->iosp = NC_IOSP_UDF; /* Do not know anything about this */
|
|
if(fIsSet(cmode,NC_UDF0)) {
|
|
conflictset(MI,model->impl,NC_FORMATX_UDF0);
|
|
} else {
|
|
conflictset(MI,model->impl,NC_FORMATX_UDF1);
|
|
}
|
|
}
|
|
/* Ignore following flags for now */
|
|
#if 0 /* keep lgtm happy
|
|
if(fIsSet(cmode,NC_CLASSIC_MODEL)) {}
|
|
if(fIsSet(cmode,NC_DISKLESS)) {}
|
|
#endif
|
|
|
|
done:
|
|
return check(stat);
|
|
}
|
|
|
|
/* Infer the implementation/dispatcher from format*/
|
|
static int
|
|
NC_implinfer(int useparallel, NCmodel* model)
|
|
{
|
|
int stat = NC_NOERR;
|
|
|
|
/* If we do not have a format, then use default format */
|
|
if(model->format == 0)
|
|
conflictset(MF,model->format,nc_get_default_format());
|
|
|
|
/* Try to infer impl based on format; may modify mode flags */
|
|
if(model->impl == 0) {
|
|
switch (model->format) {
|
|
case NC_FORMAT_NETCDF4:
|
|
conflictset(MI,model->impl,NC_FORMATX_NC4);
|
|
break;
|
|
case NC_FORMAT_NETCDF4_CLASSIC:
|
|
conflictset(MI,model->impl,NC_FORMATX_NC4);
|
|
break;
|
|
case NC_FORMAT_CDF5:
|
|
conflictset(MI,model->impl,NC_FORMATX_NC3);
|
|
break;
|
|
case NC_FORMAT_64BIT_OFFSET:
|
|
conflictset(MI,model->impl,NC_FORMATX_NC3);
|
|
break;
|
|
case NC_FORMAT_CLASSIC:
|
|
conflictset(MI,model->impl,NC_FORMATX_NC3);
|
|
break;
|
|
default: break;
|
|
}
|
|
/* default dispatcher if above did not infer an implementation */
|
|
if (model->impl == 0)
|
|
conflictset(MI,model->impl,NC_FORMATX_NC3); /* Final choice */
|
|
/* Check for using PNETCDF */
|
|
if (model->impl== NC_FORMATX_NC3
|
|
&& useparallel
|
|
&& model->iosp == NC_IOSP_FILE)
|
|
model->impl = NC_FORMATX_PNETCDF; /* Use this instead */
|
|
}
|
|
|
|
assert(model->impl != 0);
|
|
done:
|
|
return check(stat);
|
|
}
|
|
|
|
static int
|
|
processuri(const char* path, NCURI** urip, char** newpathp, NClist* modeargs)
|
|
{
|
|
int stat = NC_NOERR;
|
|
int found = 0;
|
|
const char** fragp = NULL;
|
|
struct NCPROTOCOLLIST* protolist;
|
|
NCURI* uri = NULL;
|
|
size_t pathlen = strlen(path);
|
|
|
|
if(path == NULL || pathlen == 0) {stat = NC_EURL; goto done;}
|
|
|
|
/* Defaults */
|
|
if(newpathp) *newpathp = NULL;
|
|
if(urip) *urip = NULL;
|
|
|
|
if(ncuriparse(path,&uri) != NCU_OK) goto done; /* not url */
|
|
|
|
/* Look up the protocol */
|
|
for(found=0,protolist=ncprotolist;protolist->protocol;protolist++) {
|
|
if(strcmp(uri->protocol,protolist->protocol) == 0) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if(!found)
|
|
{stat = NC_EINVAL; goto done;} /* unrecognized URL form */
|
|
|
|
/* process the corresponding mode arg */
|
|
if(protolist->mode != NULL)
|
|
nclistpush(modeargs,strdup(protolist->mode));
|
|
|
|
/* Substitute the protocol in any case */
|
|
if(protolist->substitute) ncurisetprotocol(uri,protolist->substitute);
|
|
|
|
/* Iterate over the url fragment parameters */
|
|
for(fragp=ncurifragmentparams(uri);fragp && *fragp;fragp+=2) {
|
|
const char* name = fragp[0];
|
|
const char* value = fragp[1];
|
|
if(strcmp(name,"protocol")==0) {
|
|
nclistpush(modeargs,strdup(value));
|
|
} else
|
|
if(strcmp(name,"mode")==0) {
|
|
if((stat = parseurlmode(value,modeargs))) goto done;
|
|
} else
|
|
if(issingleton(name) && (value == NULL || strlen(value)==0)) {
|
|
nclistpush(modeargs,strdup(name));
|
|
} /*else ignore*/
|
|
}
|
|
|
|
/* At this point modeargs should contain all mode args from the URL */
|
|
|
|
/* Rebuild the path (including fragment)*/
|
|
if(newpathp)
|
|
*newpathp = ncuribuild(uri,NULL,NULL,NCURIALL);
|
|
if(urip) {
|
|
*urip = uri;
|
|
uri = NULL;
|
|
}
|
|
done:
|
|
if(uri != NULL) ncurifree(uri);
|
|
return check(stat);
|
|
}
|
|
|
|
/**************************************************/
|
|
/*
|
|
Infer model for this dataset using some
|
|
combination of cmode, path, and reading the dataset.
|
|
|
|
The precedence order is:
|
|
1. file contents -- highest precedence
|
|
2. path
|
|
2. isurl -- check for DAP
|
|
3. mode
|
|
4. default format -- lowest precedence
|
|
|
|
@param path
|
|
@param omode
|
|
@param iscreate
|
|
@param useparallel
|
|
@param params
|
|
@param model
|
|
@param newpathp
|
|
|
|
*/
|
|
|
|
int
|
|
NC_infermodel(const char* path, int* omodep, int iscreate, int useparallel, void* params, NCmodel* model, char** newpathp)
|
|
{
|
|
int stat = NC_NOERR;
|
|
char* newpath = NULL;
|
|
NCURI* uri = NULL;
|
|
int omode = *omodep;
|
|
int isuri = 0;
|
|
NClist* modeargs = nclistnew();
|
|
|
|
if((stat = processuri(path, &uri, &newpath, modeargs))) goto done;
|
|
isuri = (uri != NULL);
|
|
|
|
/* Phase 1: compute the IOSP */
|
|
if((stat = extractiosp(modeargs,omode,model))) goto done;
|
|
assert(model->iosp != 0);
|
|
|
|
/* Phase 2: Process the non-iosp mode arguments */
|
|
if(!modelcomplete(model)) {
|
|
if(isuri) {
|
|
int i;
|
|
for(i=0;i<nclistlength(modeargs);i++) {
|
|
const char* arg = nclistget(modeargs,i);
|
|
if((stat=processmodearg(arg,model))) goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Phase 3: See if we can infer DAP */
|
|
if(!modelcomplete(model)) {
|
|
if(isuri) {
|
|
if((stat = NC_dapinfer(modeargs,model))) goto done;
|
|
}
|
|
}
|
|
|
|
/* Phase 4: mode inference */
|
|
if(!modelcomplete(model)) {
|
|
if((stat = NC_omodeinfer(omode,model))) goto done;
|
|
}
|
|
|
|
/* Phase 5: Infer from file content, if possible;
|
|
this has highest precedence, so it may override
|
|
previous decisions.
|
|
*/
|
|
if(!iscreate && isreadable(model->iosp)) {
|
|
/* Ok, we need to try to read the file */
|
|
if((stat = check_file_type(path, omode, useparallel, params, model, uri))) goto done;
|
|
}
|
|
|
|
/* Phase 6: Infer impl from format */
|
|
if(!modelcomplete(model)) {
|
|
if((stat = NC_implinfer(useparallel, model))) goto done;
|
|
}
|
|
|
|
assert(modelcomplete(model));
|
|
|
|
/* Force flag consistency */
|
|
switch (model->impl) {
|
|
case NC_FORMATX_NC4:
|
|
case NC_FORMATX_NC_HDF4:
|
|
case NC_FORMATX_DAP4:
|
|
case NC_FORMATX_UDF0:
|
|
case NC_FORMATX_UDF1:
|
|
omode |= NC_NETCDF4;
|
|
if(model->format == NC_FORMAT_NETCDF4_CLASSIC)
|
|
omode |= NC_CLASSIC_MODEL;
|
|
break;
|
|
case NC_FORMATX_DAP2:
|
|
omode &= ~(NC_NETCDF4|NC_64BIT_OFFSET|NC_64BIT_DATA);
|
|
break;
|
|
case NC_FORMATX_NC3:
|
|
omode &= ~NC_NETCDF4; /* must be netcdf-3 (CDF-1, CDF-2, CDF-5) */
|
|
if(model->format == NC_FORMAT_64BIT_OFFSET) omode |= NC_64BIT_OFFSET;
|
|
else if(model->format == NC_FORMAT_64BIT_DATA) omode |= NC_64BIT_DATA;
|
|
break;
|
|
default:
|
|
{stat = NC_ENOTNC; goto done;}
|
|
}
|
|
|
|
done:
|
|
if(uri) ncurifree(uri);
|
|
nclistfree(modeargs);
|
|
if(stat == NC_NOERR && newpathp) {*newpathp = newpath; newpath = NULL;}
|
|
nullfree(newpath);
|
|
*omodep = omode; /* in/out */
|
|
return check(stat);
|
|
}
|
|
|
|
static int
|
|
isreadable(int iosp)
|
|
{
|
|
struct IospRead* r;
|
|
/* Look up the protocol */
|
|
for(r=readable;r->iosp;r++) {
|
|
if(iosp == r->iosp) return r->readable;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**************************************************/
|
|
#if 0
|
|
/* return 1 if path looks like a url; 0 otherwise */
|
|
int
|
|
NC_testurl(const char* path)
|
|
{
|
|
int isurl = 0;
|
|
NCURI* tmpurl = NULL;
|
|
|
|
if(path == NULL) return 0;
|
|
|
|
/* Ok, try to parse as a url */
|
|
if(ncuriparse(path,&tmpurl)==NCU_OK) {
|
|
/* Do some extra testing to make sure this really is a url */
|
|
/* Look for a known/accepted protocol */
|
|
struct NCPROTOCOLLIST* protolist;
|
|
for(protolist=ncprotolist;protolist->protocol;protolist++) {
|
|
if(strcmp(tmpurl->protocol,protolist->protocol) == 0) {
|
|
isurl=1;
|
|
break;
|
|
}
|
|
}
|
|
ncurifree(tmpurl);
|
|
return isurl;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/**************************************************/
|
|
/**
|
|
* Provide a hidden interface to allow utilities
|
|
* to check if a given path name is really a url.
|
|
* If not, put null in basenamep, else put basename of the url
|
|
* minus any extension into basenamep; caller frees.
|
|
* Return 1 if it looks like a url, 0 otherwise.
|
|
*/
|
|
|
|
int
|
|
nc__testurl(const char* path, char** basenamep)
|
|
{
|
|
NCURI* uri;
|
|
int ok = 0;
|
|
if(ncuriparse(path,&uri) == NCU_OK) {
|
|
char* slash = (uri->path == NULL ? NULL : strrchr(uri->path, '/'));
|
|
char* dot;
|
|
if(slash == NULL) slash = (char*)path; else slash++;
|
|
slash = nulldup(slash);
|
|
if(slash == NULL)
|
|
dot = NULL;
|
|
else
|
|
dot = strrchr(slash, '.');
|
|
if(dot != NULL && dot != slash) *dot = '\0';
|
|
if(basenamep)
|
|
*basenamep=slash;
|
|
else if(slash)
|
|
free(slash);
|
|
ncurifree(uri);
|
|
ok = 1;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
/**************************************************/
|
|
/**
|
|
* @internal Given an existing file, figure out its format and return
|
|
* that format value (NC_FORMATX_XXX) in model arg. Assume any path
|
|
* conversion was already performed at a higher level.
|
|
*
|
|
* @param path File name.
|
|
* @param flags
|
|
* @param use_parallel
|
|
* @param parameters
|
|
* @param model Pointer that gets the model to use for the dispatch table.
|
|
* @param version Pointer that gets version of the file.
|
|
*
|
|
* @return ::NC_NOERR No error.
|
|
* @author Dennis Heimbigner
|
|
*/
|
|
static int
|
|
check_file_type(const char *path, int flags, int use_parallel,
|
|
void *parameters, NCmodel* model, NCURI* uri)
|
|
{
|
|
char magic[NC_MAX_MAGIC_NUMBER_LEN];
|
|
int status = NC_NOERR;
|
|
struct MagicFile magicinfo;
|
|
|
|
memset((void*)&magicinfo,0,sizeof(magicinfo));
|
|
magicinfo.path = path; /* do not free */
|
|
magicinfo.uri = uri; /* do not free */
|
|
magicinfo.model = model; /* do not free */
|
|
magicinfo.parameters = parameters; /* do not free */
|
|
magicinfo.use_parallel = use_parallel;
|
|
|
|
if((status = openmagic(&magicinfo))) goto done;
|
|
|
|
/* Verify we have a large enough file */
|
|
if(magicinfo.filelen < MAGIC_NUMBER_LEN)
|
|
{status = NC_ENOTNC; goto done;}
|
|
if((status = readmagic(&magicinfo,0L,magic)) != NC_NOERR) {
|
|
status = NC_ENOTNC;
|
|
goto done;
|
|
}
|
|
|
|
/* Look at the magic number */
|
|
if(NC_interpret_magic_number(magic,model) == NC_NOERR
|
|
&& model->format != 0) {
|
|
if (model->format == NC_FORMAT_NC3 && use_parallel)
|
|
/* this is called from nc_open_par() and file is classic */
|
|
model->impl = NC_FORMATX_PNETCDF;
|
|
goto done; /* found something */
|
|
}
|
|
|
|
/* Remaining case when implementation is an HDF5 file;
|
|
search forward at starting at 512
|
|
and doubling to see if we have HDF5 magic number */
|
|
{
|
|
long pos = 512L;
|
|
for(;;) {
|
|
if((pos+MAGIC_NUMBER_LEN) > magicinfo.filelen)
|
|
{status = NC_ENOTNC; goto done;}
|
|
if((status = readmagic(&magicinfo,pos,magic)) != NC_NOERR)
|
|
{status = NC_ENOTNC; goto done; }
|
|
NC_interpret_magic_number(magic,model);
|
|
if(model->impl == NC_FORMATX_NC4) break;
|
|
/* double and try again */
|
|
pos = 2*pos;
|
|
}
|
|
}
|
|
done:
|
|
closemagic(&magicinfo);
|
|
return check(status);
|
|
}
|
|
|
|
/**
|
|
\internal
|
|
\ingroup datasets
|
|
Provide open, read and close for use when searching for magic numbers
|
|
*/
|
|
static int
|
|
openmagic(struct MagicFile* file)
|
|
{
|
|
int status = NC_NOERR;
|
|
|
|
switch (file->model->iosp) {
|
|
case NC_IOSP_MEMORY: {
|
|
/* Get its length */
|
|
NC_memio* meminfo = (NC_memio*)file->parameters;
|
|
assert(meminfo != NULL);
|
|
file->filelen = (long long)meminfo->size;
|
|
} break;
|
|
case NC_IOSP_FILE: {
|
|
#ifdef USE_PARALLEL
|
|
if (file->use_parallel) {
|
|
int retval;
|
|
MPI_Offset size;
|
|
assert(file->parameters != NULL);
|
|
if((retval = MPI_File_open(((NC_MPI_INFO*)file->parameters)->comm,
|
|
(char*)file->path,MPI_MODE_RDONLY,
|
|
((NC_MPI_INFO*)file->parameters)->info,
|
|
&file->fh)) != MPI_SUCCESS) {
|
|
#ifdef MPI_ERR_NO_SUCH_FILE
|
|
int errorclass;
|
|
MPI_Error_class(retval, &errorclass);
|
|
if (errorclass == MPI_ERR_NO_SUCH_FILE)
|
|
#ifdef NC_ENOENT
|
|
status = NC_ENOENT;
|
|
#else
|
|
status = errno;
|
|
#endif
|
|
else
|
|
#endif
|
|
status = NC_EPARINIT;
|
|
goto done;
|
|
}
|
|
/* Get its length */
|
|
if((retval=MPI_File_get_size(file->fh, &size)) != MPI_SUCCESS)
|
|
{status = NC_EPARINIT; goto done;}
|
|
file->filelen = (long long)size;
|
|
} else
|
|
#endif /* USE_PARALLEL */
|
|
{
|
|
if(file->path == NULL || strlen(file->path)==0)
|
|
{status = NC_EINVAL; goto done;}
|
|
#ifdef _WIN32
|
|
file->fp = fopen(file->path, "rb");
|
|
#else
|
|
file->fp = fopen(file->path, "r");
|
|
#endif
|
|
if(file->fp == NULL)
|
|
{status = errno; goto done;}
|
|
/* Get its length */
|
|
{
|
|
int fd = fileno(file->fp);
|
|
#ifdef _WIN32
|
|
__int64 len64 = _filelengthi64(fd);
|
|
if(len64 < 0)
|
|
{status = errno; goto done;}
|
|
file->filelen = (long long)len64;
|
|
#else
|
|
off_t size;
|
|
size = lseek(fd, 0, SEEK_END);
|
|
if(size == -1)
|
|
{status = errno; goto done;}
|
|
file->filelen = (long long)size;
|
|
#endif
|
|
}
|
|
rewind(file->fp);
|
|
}
|
|
} break;
|
|
|
|
#ifdef ENABLE_HTTP
|
|
case NC_IOSP_HTTP: {
|
|
/* Construct a URL minus any fragment */
|
|
file->curlurl = ncuribuild(file->uri,NULL,NULL,NCURISVC);
|
|
/* Open the curl handle */
|
|
if((status=nc_http_open(file->curlurl,&file->curl,&file->filelen))) goto done;
|
|
} break;
|
|
#endif
|
|
|
|
default: assert(0);
|
|
}
|
|
|
|
done:
|
|
return check(status);
|
|
}
|
|
|
|
static int
|
|
readmagic(struct MagicFile* file, long pos, char* magic)
|
|
{
|
|
int status = NC_NOERR;
|
|
memset(magic,0,MAGIC_NUMBER_LEN);
|
|
switch (file->model->iosp) {
|
|
case NC_IOSP_MEMORY: {
|
|
char* mempos;
|
|
NC_memio* meminfo = (NC_memio*)file->parameters;
|
|
if((pos + MAGIC_NUMBER_LEN) > meminfo->size)
|
|
{status = NC_EINMEMORY; goto done;}
|
|
mempos = ((char*)meminfo->memory) + pos;
|
|
memcpy((void*)magic,mempos,MAGIC_NUMBER_LEN);
|
|
#ifdef DEBUG
|
|
printmagic("XXX: readmagic",magic,file);
|
|
#endif
|
|
} break;
|
|
|
|
case NC_IOSP_FILE:
|
|
#ifdef USE_PARALLEL
|
|
if (file->use_parallel) {
|
|
MPI_Status mstatus;
|
|
int retval;
|
|
if((retval = MPI_File_read_at_all(file->fh, pos, magic,
|
|
MAGIC_NUMBER_LEN, MPI_CHAR, &mstatus)) != MPI_SUCCESS)
|
|
{status = NC_EPARINIT; goto done;}
|
|
} else
|
|
#endif /* USE_PARALLEL */
|
|
{
|
|
int count;
|
|
int i = fseek(file->fp,pos,SEEK_SET);
|
|
if(i < 0)
|
|
{status = errno; goto done;}
|
|
for(i=0;i<MAGIC_NUMBER_LEN;) {/* make sure to read proper # of bytes */
|
|
count=fread(&magic[i],1,(size_t)(MAGIC_NUMBER_LEN-i),file->fp);
|
|
if(count == 0 || ferror(file->fp))
|
|
{status = errno; goto done;}
|
|
i += count;
|
|
}
|
|
}
|
|
break;
|
|
|
|
#ifdef ENABLE_HTTP
|
|
case NC_IOSP_HTTP: {
|
|
NCbytes* buf = ncbytesnew();
|
|
fileoffset_t start = (size_t)pos;
|
|
fileoffset_t count = MAGIC_NUMBER_LEN;
|
|
status = nc_http_read(file->curl,file->curlurl,start,count,buf);
|
|
if(status == NC_NOERR) {
|
|
if(ncbyteslength(buf) != count)
|
|
status = NC_EINVAL;
|
|
else
|
|
memcpy(magic,ncbytescontents(buf),count);
|
|
}
|
|
ncbytesfree(buf);
|
|
} break;
|
|
#endif
|
|
|
|
default: assert(0);
|
|
}
|
|
|
|
done:
|
|
if(file && file->fp) clearerr(file->fp);
|
|
return check(status);
|
|
}
|
|
|
|
/**
|
|
* Close the file opened to check for magic number.
|
|
*
|
|
* @param file pointer to the MagicFile struct for this open file.
|
|
* @returns NC_NOERR for success
|
|
* @returns NC_EPARINIT if there was a problem closing file with MPI
|
|
* (parallel builds only).
|
|
* @author Dennis Heimbigner
|
|
*/
|
|
static int
|
|
closemagic(struct MagicFile* file)
|
|
{
|
|
int status = NC_NOERR;
|
|
switch (file->model->iosp) {
|
|
case NC_IOSP_MEMORY:
|
|
break; /* noop */
|
|
|
|
case NC_IOSP_FILE:
|
|
#ifdef USE_PARALLEL
|
|
if (file->use_parallel) {
|
|
int retval;
|
|
if((retval = MPI_File_close(&file->fh)) != MPI_SUCCESS)
|
|
{status = NC_EPARINIT; goto done;}
|
|
} else
|
|
#endif
|
|
{
|
|
if(file->fp) fclose(file->fp);
|
|
}
|
|
break;
|
|
|
|
#ifdef ENABLE_HTTP
|
|
case NC_IOSP_HTTP:
|
|
status = nc_http_close(file->curl);
|
|
nullfree(file->curlurl);
|
|
break;
|
|
#endif
|
|
|
|
default: assert(0);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/*!
|
|
Interpret the magic number found in the header of a netCDF file.
|
|
This function interprets the magic number/string contained in the header of a netCDF file and sets the appropriate NC_FORMATX flags.
|
|
|
|
@param[in] magic Pointer to a character array with the magic number block.
|
|
@param[out] model Pointer to an integer to hold the corresponding netCDF type.
|
|
@param[out] version Pointer to an integer to hold the corresponding netCDF version.
|
|
@returns NC_NOERR if a legitimate file type found
|
|
@returns NC_ENOTNC otherwise
|
|
|
|
\internal
|
|
\ingroup datasets
|
|
|
|
*/
|
|
static int
|
|
NC_interpret_magic_number(char* magic, NCmodel* model)
|
|
{
|
|
int status = NC_NOERR;
|
|
/* Look at the magic number */
|
|
#ifdef USE_NETCDF4
|
|
if (strlen(UDF0_magic_number) && !strncmp(UDF0_magic_number, magic,
|
|
strlen(UDF0_magic_number)))
|
|
{
|
|
model->impl = NC_FORMATX_UDF0;
|
|
model->format = NC_FORMAT_NETCDF4;
|
|
goto done;
|
|
}
|
|
if (strlen(UDF1_magic_number) && !strncmp(UDF1_magic_number, magic,
|
|
strlen(UDF1_magic_number)))
|
|
{
|
|
model->impl = NC_FORMATX_UDF1;
|
|
model->format = NC_FORMAT_NETCDF4;
|
|
goto done;
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
/* Use the complete magic number string for HDF5 */
|
|
if(memcmp(magic,HDF5_SIGNATURE,sizeof(HDF5_SIGNATURE))==0) {
|
|
model->impl = NC_FORMATX_NC4;
|
|
model->format = NC_FORMAT_NETCDF4;
|
|
goto done;
|
|
}
|
|
if(magic[0] == '\016' && magic[1] == '\003'
|
|
&& magic[2] == '\023' && magic[3] == '\001') {
|
|
model->impl = NC_FORMATX_NC_HDF4;
|
|
model->format = NC_FORMAT_NETCDF4;
|
|
goto done;
|
|
}
|
|
if(magic[0] == 'C' && magic[1] == 'D' && magic[2] == 'F') {
|
|
if(magic[3] == '\001') {
|
|
model->impl = NC_FORMATX_NC3;
|
|
model->format = NC_FORMAT_CLASSIC;
|
|
goto done;
|
|
}
|
|
if(magic[3] == '\002') {
|
|
model->impl = NC_FORMATX_NC3;
|
|
model->format = NC_FORMAT_64BIT_OFFSET;
|
|
goto done;
|
|
}
|
|
if(magic[3] == '\005') {
|
|
model->impl = NC_FORMATX_NC3;
|
|
model->format = NC_FORMAT_64BIT_DATA;
|
|
goto done;
|
|
}
|
|
}
|
|
/* No match */
|
|
status = NC_ENOTNC;
|
|
goto done;
|
|
|
|
done:
|
|
return check(status);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void
|
|
printmagic(const char* tag, char* magic, struct MagicFile* f)
|
|
{
|
|
int i;
|
|
fprintf(stderr,"%s: ispar=%d magic=",tag,f->use_parallel);
|
|
for(i=0;i<MAGIC_NUMBER_LEN;i++) {
|
|
unsigned int c = (unsigned int)magic[i];
|
|
c = c & 0x000000FF;
|
|
if(c == '\n')
|
|
fprintf(stderr," 0x%0x/'\\n'",c);
|
|
else if(c == '\r')
|
|
fprintf(stderr," 0x%0x/'\\r'",c);
|
|
else if(c < ' ')
|
|
fprintf(stderr," 0x%0x/'?'",c);
|
|
else
|
|
fprintf(stderr," 0x%0x/'%c'",c,c);
|
|
}
|
|
fprintf(stderr,"\n");
|
|
fflush(stderr);
|
|
}
|
|
#endif
|