netcdf-c/nczarr_test/ncdumpchunks.c
Dennis Heimbigner 0b7a5382e7 Codify cross-platform file paths
The netcdf-c code has to deal with a variety of platforms:
Windows, OSX, Linux, Cygwin, MSYS, etc.  These platforms differ
significantly in the kind of file paths that they accept.  So in
order to handle this, I have created a set of replacements for
the most common file system operations such as _open_ or _fopen_
or _access_ to manage the file path differences correctly.

A more limited version of this idea was already implemented via
the ncwinpath.h and dwinpath.c code. So this can be viewed as a
replacement for that code. And in path in many cases, the only
change that was required was to replace '#include <ncwinpath.h>'
with '#include <ncpathmgt.h>' and then replace file operation
calls with the NCxxx equivalent from ncpathmgr.h Note that
recently, the ncwinpath.h was renamed ncpathmgmt.h, so this pull
request should not require dealing with winpath.

The heart of the change is include/ncpathmgmt.h, which provides
alternate operations such as NCfopen or NCaccess and which properly
parse and rebuild path arguments to work for the platform on which
the code is executing. This mostly matters for Windows because of the
way that it uses backslash and drive letters, as compared to *nix*.
One important feature is that the user can do string manipulations
on a file path without having to worry too much about the platform
because the path management code will properly handle most mixed cases.
So one can for example concatenate a path suffix that uses forward
slashes to a Windows path and have it work correctly.

The conversion code is in libdispatch/dpathmgr.c, and the
important function there is NCpathcvt which does the proper
conversions to the local path format.

As a rule, most code should just replace their file operations with
the corresponding NCxxx ones defined in include/ncpathmgmt.h. These
NCxxx functions all call NCpathcvt on their path arguments before
executing the actual file operation.

In some rare cases, the client may need to directly use NCpathcvt,
but this should be avoided as much as possible. If there is a need
for supporting a new file operation not already in ncpathmgmt.h, then
use the code in dpathmgr.c as a template. Also please notify Unidata
so we can include it as a formal part or our supported operations.
Also, if you see an operation in the library that is not using the
NCxxx form, then please submit an issue so we can fix it.

Misc. Changes:
* Clean up the utf8 testing code; it is impossible to get some
  tests to work under windows using shell scripts; the args do
  not pass as utf8 but as some other encoding.
* Added an extra utf8 test case: test_unicode_path.sh
* Add a true test for HDF5 1.10.6 or later because as noted in
  PR https://github.com/Unidata/netcdf-c/pull/1794,
  HDF5 changed its Windows file path handling.
2021-03-04 13:41:31 -07:00

534 lines
12 KiB
C
Executable File

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifdef _WIN32
#include "XGetopt.h"
#endif
#include "netcdf.h"
#include "ncpathmgr.h"
#include "nclog.h"
#ifdef HAVE_HDF5_H
#include <hdf5.h>
#include <H5DSpublic.h>
#endif
#ifdef ENABLE_NCZARR
#include "zincludes.h"
#endif
#undef DEBUG
/* Short Aliases */
#ifdef HDF5_SUPPORTS_PAR_FILTERS
#define H5
#endif
#ifdef ENABLE_NCZARR
#define NZ
#endif
typedef struct Format {
int format;
char file_name[NC_MAX_NAME];
char var_name[NC_MAX_NAME];
int fillvalue;
int debug;
int rank;
size_t dimlens[NC_MAX_VAR_DIMS];
size_t chunklens[NC_MAX_VAR_DIMS];
size_t chunkcounts[NC_MAX_VAR_DIMS];
size_t chunkprod;
size_t dimprod;
nc_type xtype;
} Format;
typedef struct Odometer {
size_t rank; /*rank */
size_t start[NC_MAX_VAR_DIMS];
size_t stop[NC_MAX_VAR_DIMS];
size_t max[NC_MAX_VAR_DIMS]; /* max size of ith index */
size_t index[NC_MAX_VAR_DIMS]; /* current value of the odometer*/
} Odometer;
#define floordiv(x,y) ((x) / (y))
#define ceildiv(x,y) (((x) % (y)) == 0 ? ((x) / (y)) : (((x) / (y)) + 1))
static char* captured[4096];
static int ncap = 0;
extern int nc__testurl(const char*,char**);
Odometer* odom_new(size_t rank, const size_t* stop, const size_t* max);
void odom_free(Odometer* odom);
int odom_more(Odometer* odom);
int odom_next(Odometer* odom);
size_t* odom_indices(Odometer* odom);
size_t odom_offset(Odometer* odom);
const char* odom_print(Odometer* odom);
static void
usage(int err)
{
if(err != 0) {
fprintf(stderr,"Error: (%d) %s\n",err,nc_strerror(err));
}
fprintf(stderr,"usage: ncdumpchunks -v <var> <file> \n");
fflush(stderr);
exit(1);
}
const char*
printvector(int rank, size_t* vec)
{
char svec[NC_MAX_VAR_DIMS*3+1];
int i;
svec[0] = '\0';
for(i=0;i<rank;i++) {
char s[3+1];
if(i > 0) strlcat(svec,",",sizeof(svec));
snprintf(s,sizeof(s),"%u",(unsigned)vec[i]);
strlcat(svec,s,sizeof(svec));
}
captured[ncap++] = strdup(svec);
return captured[ncap-1];
}
void
cleanup(void)
{
int i;
for(i=0;i<ncap;i++)
if(captured[i]) free(captured[i]);
}
Odometer*
odom_new(size_t rank, const size_t* stop, const size_t* max)
{
int i;
Odometer* odom = NULL;
if((odom = calloc(1,sizeof(Odometer))) == NULL)
return NULL;
odom->rank = rank;
for(i=0;i<rank;i++) {
odom->start[i] = 0;
odom->stop[i] = stop[i];
odom->max[i] = max[i];
odom->index[i] = 0;
}
return odom;
}
void
odom_free(Odometer* odom)
{
if(odom) free(odom);
}
int
odom_more(Odometer* odom)
{
return (odom->index[0] < odom->stop[0]);
}
int
odom_next(Odometer* odom)
{
size_t i;
for(i=odom->rank-1;i>=0;i--) {
odom->index[i]++;
if(odom->index[i] < odom->stop[i]) break;
if(i == 0) return 0; /* leave the 0th entry if it overflows */
odom->index[i] = 0; /* reset this position */
}
return 1;
}
/* Get the value of the odometer */
size_t*
odom_indices(Odometer* odom)
{
return odom->index;
}
size_t
odom_offset(Odometer* odom)
{
size_t offset;
int i;
offset = 0;
for(i=0;i<odom->rank;i++) {
offset *= odom->max[i];
offset += odom->index[i];
}
return offset;
}
const char*
odom_print(Odometer* odom)
{
static char s[4096];
static char tmp[4096];
const char* sv;
s[0] = '\0';
snprintf(tmp,sizeof(tmp),"{rank=%u",(unsigned)odom->rank);
strcat(s,tmp);
strcat(s," start=("); sv = printvector(odom->rank,odom->start); strcat(s,sv); strcat(s,")");
strcat(s," stop=("); sv = printvector(odom->rank,odom->stop); strcat(s,sv); strcat(s,")");
strcat(s," max=("); sv = printvector(odom->rank,odom->max); strcat(s,sv); strcat(s,")");
snprintf(tmp,sizeof(tmp)," offset=%u",(unsigned)odom_offset(odom)); strcat(s,tmp);
strcat(s," indices=("); sv = printvector(odom->rank,odom->index); strcat(s,sv); strcat(s,")");
strcat(s,"}");
return s;
}
#ifdef DEBUG
char*
chunk_key(int format->rank, size_t* indices)
{
char key[NC_MAX_VAR_DIMS*3+1];
int i;
key[0] = '\0';
for(i=0;i<format->rank;i++) {
char s[3+1];
if(i > 0) strlcat(key,".",sizeof(key));
snprintf(s,sizeof(s),"%u",(unsigned)indices[i]);
strlcat(key,s,sizeof(key));
}
return strdup(key);
}
#endif
void
setoffset(Odometer* odom, size_t* chunksizes, size_t* offset)
{
int i;
for(i=0;i<odom->rank;i++)
offset[i] = odom->index[i] * chunksizes[i];
}
static void
printindent(size_t indent)
{
while(indent-- > 0) printf(" ");
}
static void
printchunk(Format* format, int* chunkdata, size_t indent)
{
int k[3];
unsigned cols[3], pos;
size_t* chl = format->chunklens;
memset(cols,0,sizeof(cols));
switch (format->rank) {
case 1:
cols[0] = 1;
cols[1] = 1;
cols[2] = chl[0];
break;
case 2:
cols[0] = 1;
cols[1] = chl[0];
cols[2] = chl[1];
break;
case 3:
cols[0] = chl[0];
cols[1] = chl[1];
cols[2] = chl[2];
break;
default:
cols[0] = 1;
cols[1] = 1;
cols[2] = format->chunkprod;
break;
}
// offset = (((k0*chl[0])+k1)*chl[1])+k2;
pos = 0;
for(k[0]=0;k[0]<cols[0];k[0]++) {
if(k[0] > 0) printindent(indent);
k[1] = 0; k[2] = 0; /* reset */
for(k[1]=0;k[1]<cols[1];k[1]++) {
k[2] = 0;
if(k[1] > 0) printf(" |");
for(k[2]=0;k[2]<cols[2];k[2]++) {
printf(" %02d", chunkdata[pos]);
pos++;
}
}
printf("\n");
}
#if 0
for(k=0;k<format->chunkprod;k++) {
if(k > 0 && k % cols == 0) printf(" |");
printf(" %02d", chunkdata[k]);
}
printf("\n");
#endif
}
int
dump(Format* format)
{
int* chunkdata = NULL; /*[CHUNKPROD];*/
Odometer* odom = NULL;
int r;
size_t offset[NC_MAX_VAR_DIMS];
int holechunk = 0;
char sindices[64];
#ifdef H5
int i;
hid_t fileid, grpid, datasetid;
hid_t dxpl_id = H5P_DEFAULT; /*data transfer property list */
unsigned int filter_mask = 0;
hsize_t hoffset[NC_MAX_VAR_DIMS];
#endif
#ifdef NZ
int stat = NC_NOERR;
size64_t zindices[NC_MAX_VAR_DIMS];
int ncid, varid;
#endif
#ifdef H5
if(format->debug) {
H5Eset_auto2(H5E_DEFAULT,(H5E_auto2_t)H5Eprint,stderr);
}
#endif
memset(offset,0,sizeof(offset));
#ifdef H5
memset(hoffset,0,sizeof(hoffset));
#endif
switch (format->format) {
#ifdef H5
case NC_FORMATX_NC_HDF5:
if ((fileid = H5Fopen(format->file_name, H5F_ACC_RDONLY, H5P_DEFAULT)) < 0) usage(NC_EHDFERR);
if ((grpid = H5Gopen1(fileid, "/")) < 0) usage(NC_EHDFERR);
if ((datasetid = H5Dopen1(grpid, format->var_name)) < 0) usage(NC_EHDFERR);
break;
#endif
#ifdef NZ
case NC_FORMATX_NCZARR:
if((stat=nc_open(format->file_name,0,&ncid))) usage(stat);
if((stat=nc_inq_varid(ncid,format->var_name,&varid))) usage(stat);
break;
#endif
default: usage(NC_EINVAL);
}
if((odom = odom_new(format->rank,format->chunkcounts,format->dimlens))==NULL) usage(NC_ENOMEM);
if((chunkdata = calloc(sizeof(int),format->chunkprod))==NULL) usage(NC_ENOMEM);
printf("rank=%d dims=(%s) chunks=(%s)\n",format->rank,printvector(format->rank,format->dimlens),
printvector(format->rank,format->chunklens));
while(odom_more(odom)) {
setoffset(odom,format->chunklens,offset);
#ifdef DEBUG
fprintf(stderr,"odom=%s\n",odom_print(odom));
fprintf(stderr,"offset=(");
for(i=0;i<format->rank;i++)
fprintf(stderr,"%s%lu",(i > 0 ? "," : ""),(unsigned long)offset[i]);
fprintf(stderr,")\n");
fflush(stderr);
#endif
if(format->debug) {
fprintf(stderr,"chunk: %s\n",printvector(format->rank,offset));
}
holechunk = 0;
switch (format->format) {
#ifdef H5
case NC_FORMATX_NC_HDF5: {
for(i=0;i<format->rank;i++) hoffset[i] = (hsize_t)offset[i];
if(H5Dread_chunk(datasetid, dxpl_id, hoffset, &filter_mask, chunkdata) < 0)
holechunk = 1;
} break;
#endif
#ifdef NZ
case NC_FORMATX_NCZARR:
for(r=0;r<format->rank;r++) zindices[r] = (size64_t)odom->index[r];
switch (stat=NCZ_read_chunk(ncid, varid, zindices, chunkdata)) {
case NC_NOERR: break;
case NC_EEMPTY: holechunk = 1; break;
default: usage(stat);
}
break;
#endif
default: usage(NC_EINVAL);
}
if(holechunk) {
/* Hole chunk: use fillvalue */
size_t i = 0;
int* idata = (int*)chunkdata;
for(i=0;i<format->chunkprod;i++)
idata[i] = format->fillvalue;
}
sindices[0] = '\0';
for(r=0;r<format->rank;r++) {
char sstep[64];
snprintf(sstep,sizeof(sstep),"[%lu/%lu]",(unsigned long)odom->index[r],(unsigned long)offset[r]);
strcat(sindices,sstep);
}
strcat(sindices," =");
printf("%s",sindices);
printchunk(format,chunkdata,strlen(sindices));
fflush(stdout);
odom_next(odom);
}
/* Close up. */
switch (format->format) {
#ifdef H5
case NC_FORMATX_NC_HDF5:
if (H5Dclose(datasetid) < 0) abort();
if (H5Gclose(grpid) < 0) abort();
if (H5Fclose(fileid) < 0) abort();
break;
#endif
#ifdef NZ
case NC_FORMATX_NCZARR:
if((stat=nc_close(ncid))) usage(stat);
break;
#endif
default: usage(NC_EINVAL);
}
/* Cleanup */
free(chunkdata);
odom_free(odom);
return 0;
}
static const char* urlexts[] = {"file", "zip", NULL};
static const char*
filenamefor(const char* f0)
{
static char result[4096];
const char** extp;
char* p;
strcpy(result,f0); /* default */
if(nc__testurl(f0,NULL)) goto done;
/* Not a URL */
p = strrchr(f0,'.'); /* look at the extension, if any */
if(p == NULL) goto done; /* No extension */
p++;
for(extp=urlexts;*extp;extp++) {
if(strcmp(p,*extp)==0) break;
}
if(*extp == NULL) goto done; /* not found */
/* Assemble the url */
strcpy(result,"file://");
strcat(result,f0); /* core path */
strcat(result,"#mode=nczarr,");
strcat(result,*extp);
done:
return result;
}
int
main(int argc, char** argv)
{
int i,stat = NC_NOERR;
Format format;
int ncid, varid, dimids[NC_MAX_VAR_DIMS];
int vtype, storage;
int mode;
int c;
memset(&format,0,sizeof(format));
while ((c = getopt(argc, argv, "v:DT:")) != EOF) {
switch(c) {
case 'v':
strcpy(format.var_name,optarg);
break;
case 'D':
format.debug = 1;
break;
case 'T':
nctracelevel(atoi(optarg));
break;
case '?':
fprintf(stderr,"unknown option: '%c'\n",c);
exit(1);
}
}
/* get file argument */
argc -= optind;
argv += optind;
if (argc == 0) {
fprintf(stderr, "no input file specified\n");
exit(1);
}
{
char* s = NC_backslashUnescape(argv[0]);
strcpy(format.file_name,filenamefor(s));
nullfree(s);
}
if(strlen(format.file_name) == 0) {
fprintf(stderr, "no input file specified\n");
exit(1);
}
if(strlen(format.var_name) == 0) {
fprintf(stderr, "no input var specified\n");
exit(1);
}
/* Get info about the file type */
if((stat=nc_open(format.file_name,0,&ncid))) usage(stat);
if((stat=nc_inq_format_extended(ncid,&format.format,&mode))) usage(stat);
/* Get the info about the var */
if((stat=nc_inq_varid(ncid,format.var_name,&varid))) usage(stat);
if((stat=nc_inq_var(ncid,varid,NULL,&vtype,&format.rank,dimids,NULL))) usage(stat);
if(format.rank == 0) usage(NC_EDIMSIZE);
if((stat=nc_inq_var_chunking(ncid,varid,&storage,format.chunklens))) usage(stat);
if(storage != NC_CHUNKED) usage(NC_EBADCHUNK);
if((stat=nc_get_att(ncid,varid,"_FillValue",&format.fillvalue))) usage(stat);
format.xtype = NC_INT;
for(i=0;i<format.rank;i++) {
if((stat=nc_inq_dimlen(ncid,dimids[i],&format.dimlens[i]))) usage(stat);
format.chunkcounts[i] = ceildiv(format.dimlens[i],format.chunklens[i]);
}
if((stat=nc_close(ncid))) usage(stat);
/* Precompute */
for(format.chunkprod=1,i=0;i<format.rank;i++) format.chunkprod *= format.chunklens[i];
for(format.dimprod=1,i=0;i<format.rank;i++) format.dimprod *= format.dimlens[i];
dump(&format);
cleanup();
return 0;
}