/src/netcdf-c/libnczarr/zmap_file.c
| Line | Count | Source | 
| 1 |  | /* | 
| 2 |  |  *  Copyright 2018, University Corporation for Atmospheric Research | 
| 3 |  |  *      See netcdf/COPYRIGHT file for copying and redistribution conditions. | 
| 4 |  |  */ | 
| 5 |  |  | 
| 6 |  | #include <stddef.h> | 
| 7 |  | #undef DEBUG | 
| 8 |  |  | 
| 9 |  | /* Not sure this has any effect */ | 
| 10 |  | #define _LARGEFILE_SOURCE 1 | 
| 11 |  | #define _LARGEFILE64_SOURCE 1 | 
| 12 |  |  | 
| 13 |  | #include "zincludes.h" | 
| 14 |  |  | 
| 15 |  | #include <errno.h> | 
| 16 |  |  | 
| 17 |  | #ifdef HAVE_SYS_TYPES_H | 
| 18 |  | #include <sys/types.h> | 
| 19 |  | #endif | 
| 20 |  | #ifdef HAVE_UNISTD_H | 
| 21 |  | #include <unistd.h> | 
| 22 |  | #endif | 
| 23 |  | #ifdef HAVE_FCNTL_H | 
| 24 |  | #include <fcntl.h> | 
| 25 |  | #endif | 
| 26 |  | #ifdef HAVE_SYS_STAT_H | 
| 27 |  | #include <sys/stat.h> | 
| 28 |  | #endif | 
| 29 |  | #ifdef HAVE_DIRENT_H | 
| 30 |  | #include <dirent.h> | 
| 31 |  | #endif | 
| 32 |  |  | 
| 33 |  | #ifdef _WIN32 | 
| 34 |  | #include <windows.h> | 
| 35 |  | #ifndef S_ISDIR | 
| 36 |  | #define S_ISDIR(mode) ((mode) & _S_IFDIR) | 
| 37 |  | #define S_ISREG(mode) ((mode) & _S_IFREG) | 
| 38 |  | #endif | 
| 39 |  | #if 0 | 
| 40 |  | #ifndef __cplusplus | 
| 41 |  | #include <io.h> | 
| 42 |  | #include <iostream> | 
| 43 |  | #endif | 
| 44 |  | #endif | 
| 45 |  | #endif | 
| 46 |  |  | 
| 47 |  | #include "fbits.h" | 
| 48 |  | #include "ncpathmgr.h" | 
| 49 |  |  | 
| 50 |  | #define VERIFY | 
| 51 |  |  | 
| 52 |  | #ifndef O_DIRECTORY | 
| 53 |  | # define O_DIRECTORY  0200000 | 
| 54 |  | #endif | 
| 55 |  |  | 
| 56 |  | /*Mnemonic*/ | 
| 57 | 0 | #define FLAG_ISDIR 1 | 
| 58 |  | #define FLAG_CREATE 1 | 
| 59 | 0 | #define SKIPLAST 1 | 
| 60 |  | #define WHOLEPATH 0 | 
| 61 |  |  | 
| 62 |  | #define NCZM_FILE_V1 1 | 
| 63 |  |  | 
| 64 |  | #ifdef S_IRUSR | 
| 65 |  | static int NC_DEFAULT_CREATE_PERMS = | 
| 66 |  |            (S_IRUSR|S_IWUSR        |S_IRGRP|S_IWGRP); | 
| 67 |  | static int NC_DEFAULT_RWOPEN_PERMS = | 
| 68 |  |            (S_IRUSR|S_IWUSR        |S_IRGRP|S_IWGRP); | 
| 69 |  | static int NC_DEFAULT_ROPEN_PERMS = | 
| 70 |  | //           (S_IRUSR                |S_IRGRP); | 
| 71 |  |            (S_IRUSR|S_IWUSR        |S_IRGRP|S_IWGRP); | 
| 72 |  | static int NC_DEFAULT_DIR_PERMS = | 
| 73 |  |        (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IWGRP); | 
| 74 |  | #else | 
| 75 |  | static int NC_DEFAULT_CREATE_PERMS = 0660; | 
| 76 |  | static int NC_DEFAULT_RWOPEN_PERMS = 0660; | 
| 77 |  | static int NC_DEFAULT_ROPEN_PERMS = 0660; | 
| 78 |  | static int NC_DEFAULT_DIR_PERMS = 0770; | 
| 79 |  | #endif | 
| 80 |  |  | 
| 81 |  | /* | 
| 82 |  | Do a simple mapping of our simplified map model | 
| 83 |  | to a file system. | 
| 84 |  |  | 
| 85 |  | Every dataset is assumed to be rooted at some directory in the | 
| 86 |  | file tree. So, its location is defined by some path to a | 
| 87 |  | directory representing both the dataset and the root group of | 
| 88 |  | that dataset. The root is recognized because it uniquely | 
| 89 |  | contains a "superblock" file name ".nczarr" that provides | 
| 90 |  | general information about a dataset. Nesting a dataset | 
| 91 |  | inside a dataset is prohibited. This can be detected | 
| 92 |  | by looking for an occurrence of a ".nczarr" file in any containing | 
| 93 |  | directory. If such a file is found, then an illegal nested | 
| 94 |  | dataset has been found. | 
| 95 |  |  | 
| 96 |  | For the object API, the mapping is as follows: | 
| 97 |  | 1. Every content-bearing object (e.g. .zgroup or .zarray) is mapped to a file. | 
| 98 |  |    The key constraint is that the content bearing objects are files. | 
| 99 |  |    This means that if a key  points to a content bearing object then | 
| 100 |  |    no other key can have that content bearing key as a suffix. | 
| 101 |  | 2. The meta data containing files are assumed to contain | 
| 102 |  |    UTF-8 character data. | 
| 103 |  | 3. The chunk containing files are assumed to contain raw unsigned 8-bit byte data. | 
| 104 |  | */ | 
| 105 |  |  | 
| 106 |  | /* define the var name containing an objects content */ | 
| 107 |  | #define ZCONTENT "data" | 
| 108 |  |  | 
| 109 |  | typedef struct FD { | 
| 110 |  |   int fd; | 
| 111 |  | } FD; | 
| 112 |  |  | 
| 113 |  | static FD FDNUL = {-1}; | 
| 114 |  |  | 
| 115 |  | /* Define the "subclass" of NCZMAP */ | 
| 116 |  | typedef struct ZFMAP { | 
| 117 |  |     NCZMAP map; | 
| 118 |  |     char* root; | 
| 119 |  | } ZFMAP; | 
| 120 |  |  | 
| 121 |  | /* Forward */ | 
| 122 |  | static NCZMAP_API zapi; | 
| 123 |  | static int zfileclose(NCZMAP* map, int delete); | 
| 124 |  | static int zfcreategroup(ZFMAP*, const char* key, int nskip); | 
| 125 |  | static int zflookupobj(ZFMAP*, const char* key, FD* fd); | 
| 126 |  | static int zfparseurl(const char* path0, NCURI** urip); | 
| 127 |  | static int zffullpath(ZFMAP* zfmap, const char* key, char**); | 
| 128 |  | static void zfrelease(ZFMAP* zfmap, FD* fd); | 
| 129 |  | static void zfunlink(const char* canonpath); | 
| 130 |  |  | 
| 131 |  | static int platformerr(int err); | 
| 132 |  | static int platformcreatefile(int mode, const char* truepath,FD*); | 
| 133 |  | static int platformcreatedir(int, const char* truepath); | 
| 134 |  | static int platformopenfile(int mode, const char* truepath, FD* fd); | 
| 135 |  | static int platformopendir(int mode, const char* truepath); | 
| 136 |  | static int platformdircontent(const char* path, NClist* contents); | 
| 137 |  | static int platformdelete(const char* path, int delroot); | 
| 138 |  | static int platformseek(FD* fd, int pos, size64_t* offset); | 
| 139 |  | static int platformread(FD* fd, size64_t count, void* content); | 
| 140 |  | static int platformwrite(FD* fd, size64_t count, const void* content); | 
| 141 |  | static void platformrelease(FD* fd); | 
| 142 |  | static int platformtestcontentbearing(const char* truepath); | 
| 143 |  |  | 
| 144 |  | #ifdef VERIFY | 
| 145 |  | static int verify(const char* path, int isdir); | 
| 146 |  | static int verifykey(const char* key, int isdir); | 
| 147 |  | #endif | 
| 148 |  |  | 
| 149 |  | static int zfinitialized = 0; | 
| 150 |  | static void | 
| 151 |  | zfileinitialize(void) | 
| 152 | 0 | { | 
| 153 | 0 |     if(!zfinitialized) { | 
| 154 | 0 |         ZTRACE(5,NULL); | 
| 155 | 0 |   const char* env = NULL; | 
| 156 | 0 |   int perms = 0; | 
| 157 | 0 |   env = getenv("NC_DEFAULT_CREATE_PERMS"); | 
| 158 | 0 |   if(env != NULL && strlen(env) > 0) { | 
| 159 | 0 |       if(sscanf(env,"%d",&perms) == 1) NC_DEFAULT_CREATE_PERMS = perms; | 
| 160 | 0 |   } | 
| 161 | 0 |   env = getenv("NC_DEFAULT_DIR_PERMS"); | 
| 162 | 0 |   if(env != NULL && strlen(env) > 0) { | 
| 163 | 0 |       if(sscanf(env,"%d",&perms) == 1) NC_DEFAULT_DIR_PERMS = perms; | 
| 164 | 0 |   } | 
| 165 | 0 |         zfinitialized = 1; | 
| 166 | 0 |   (void)ZUNTRACE(NC_NOERR); | 
| 167 | 0 |     } | 
| 168 | 0 | } | 
| 169 |  |  | 
| 170 |  | /* Define the Dataset level API */ | 
| 171 |  |  | 
| 172 |  | /* | 
| 173 |  | @param datasetpath abs path in the file tree of the root of the dataset' | 
| 174 |  |        might be a relative path. | 
| 175 |  | @param mode the netcdf-c mode flags | 
| 176 |  | @param flags extra flags | 
| 177 |  | @param flags extra parameters | 
| 178 |  | @param mapp return the map object in this | 
| 179 |  | */ | 
| 180 |  |  | 
| 181 |  | static int | 
| 182 |  | zfilecreate(const char *path, int mode, size64_t flags, void* parameters, NCZMAP** mapp) | 
| 183 | 0 | { | 
| 184 | 0 |     int stat = NC_NOERR; | 
| 185 | 0 |     char* canonpath = NULL; | 
| 186 | 0 |     char* abspath = NULL; | 
| 187 | 0 |     ZFMAP* zfmap = NULL; | 
| 188 | 0 |     NCURI* url = NULL; | 
| 189 |  |    | 
| 190 | 0 |     NC_UNUSED(parameters); | 
| 191 | 0 |     ZTRACE(5,"path=%s mode=%d flag=%llu",path,mode,flags); | 
| 192 |  | 
 | 
| 193 | 0 |     if(!zfinitialized) zfileinitialize(); | 
| 194 |  |  | 
| 195 |  |     /* Fixup mode flags */ | 
| 196 | 0 |     mode |= (NC_NETCDF4 | NC_WRITE); | 
| 197 |  | 
 | 
| 198 | 0 |     if(!(mode & NC_WRITE)) | 
| 199 | 0 |         {stat = NC_EPERM; goto done;} | 
| 200 |  |  | 
| 201 |  |     /* path must be a url with file: protocol*/ | 
| 202 | 0 |     if((stat=zfparseurl(path,&url))) | 
| 203 | 0 |   goto done; | 
| 204 | 0 |     if(strcasecmp(url->protocol,"file") != 0) | 
| 205 | 0 |         {stat = NC_EURL; goto done;} | 
| 206 |  |  | 
| 207 |  |     /* Convert the root path */ | 
| 208 | 0 |     if((canonpath = NCpathcvt(url->path))==NULL) | 
| 209 | 0 |   {stat = NC_ENOMEM; goto done;} | 
| 210 |  |  | 
| 211 |  |     /* Make the root path be absolute */ | 
| 212 | 0 |     if((abspath = NCpathabsolute(canonpath)) == NULL) | 
| 213 | 0 |   {stat = NC_EURL; goto done;} | 
| 214 |  |  | 
| 215 |  |     /* Build the zmap state */ | 
| 216 | 0 |     if((zfmap = calloc(1,sizeof(ZFMAP))) == NULL) | 
| 217 | 0 |   {stat = NC_ENOMEM; goto done;} | 
| 218 |  |  | 
| 219 | 0 |     zfmap->map.format = NCZM_FILE; | 
| 220 | 0 |     zfmap->map.url = ncuribuild(url,NULL,NULL,NCURIALL); | 
| 221 | 0 |     zfmap->map.flags = flags; | 
| 222 |  |     /* create => NC_WRITE */ | 
| 223 | 0 |     zfmap->map.mode = mode; | 
| 224 | 0 |     zfmap->map.api = &zapi; | 
| 225 | 0 |     zfmap->root = abspath; | 
| 226 | 0 |         abspath = NULL; | 
| 227 |  |  | 
| 228 |  |     /* If NC_CLOBBER, then delete below file tree */ | 
| 229 | 0 |     if(!fIsSet(mode,NC_NOCLOBBER)) | 
| 230 | 0 |   platformdelete(zfmap->root,0); | 
| 231 |  |      | 
| 232 |  |     /* make sure we can access the root directory; create if necessary */ | 
| 233 | 0 |     if((stat = platformcreatedir(zfmap->map.mode, zfmap->root))) | 
| 234 | 0 |   goto done; | 
| 235 |  |  | 
| 236 |  |     /* Dataset superblock will be written by higher layer */ | 
| 237 |  |       | 
| 238 | 0 |     if(mapp) *mapp = (NCZMAP*)zfmap;     | 
| 239 |  | 
 | 
| 240 | 0 | done: | 
| 241 | 0 |     ncurifree(url); | 
| 242 | 0 |     nullfree(canonpath); | 
| 243 | 0 |     nullfree(abspath); | 
| 244 | 0 |     if(stat) | 
| 245 | 0 |       zfileclose((NCZMAP*)zfmap,1); | 
| 246 | 0 |     return ZUNTRACE(stat); | 
| 247 | 0 | } | 
| 248 |  |  | 
| 249 |  | /* | 
| 250 |  | @param datasetpath abs path in the file tree of the root of the dataset' | 
| 251 |  |        might be a relative path. | 
| 252 |  | @param mode the netcdf-c mode flags | 
| 253 |  | @param flags extra flags | 
| 254 |  | @param flags extra parameters | 
| 255 |  | @param mapp return the map object in this | 
| 256 |  | */ | 
| 257 |  |  | 
| 258 |  | static int | 
| 259 |  | zfileopen(const char *path, int mode, size64_t flags, void* parameters, NCZMAP** mapp) | 
| 260 | 0 | { | 
| 261 | 0 |     int stat = NC_NOERR; | 
| 262 | 0 |     char* canonpath = NULL; | 
| 263 | 0 |     char* abspath = NULL; | 
| 264 | 0 |     ZFMAP* zfmap = NULL; | 
| 265 | 0 |     NCURI*url = NULL; | 
| 266 |  |      | 
| 267 | 0 |     NC_UNUSED(parameters); | 
| 268 | 0 |     ZTRACE(5,"path=%s mode=%d flags=%llu",path,mode,flags); | 
| 269 |  | 
 | 
| 270 | 0 |     if(!zfinitialized) zfileinitialize(); | 
| 271 |  |  | 
| 272 |  |     /* Fixup mode flags */ | 
| 273 | 0 |     mode = (NC_NETCDF4 | mode); | 
| 274 |  |  | 
| 275 |  |     /* path must be a url with file: protocol*/ | 
| 276 | 0 |     if((stat=zfparseurl(path,&url))) | 
| 277 | 0 |   goto done; | 
| 278 | 0 |     if(strcasecmp(url->protocol,"file") != 0) | 
| 279 | 0 |         {stat = NC_EURL; goto done;} | 
| 280 |  |  | 
| 281 |  |     /* Convert the root path */ | 
| 282 | 0 |     if((canonpath = NCpathcvt(url->path))==NULL) | 
| 283 | 0 |   {stat = NC_ENOMEM; goto done;} | 
| 284 |  |  | 
| 285 |  |     /* Make the root path be absolute */ | 
| 286 | 0 |     if((abspath = NCpathabsolute(canonpath)) == NULL) | 
| 287 | 0 |   {stat = NC_EURL; goto done;} | 
| 288 |  |  | 
| 289 |  |     /* Build the zmap state */ | 
| 290 | 0 |     if((zfmap = calloc(1,sizeof(ZFMAP))) == NULL) | 
| 291 | 0 |   {stat = NC_ENOMEM; goto done;} | 
| 292 |  |  | 
| 293 | 0 |     zfmap->map.format = NCZM_FILE; | 
| 294 | 0 |     zfmap->map.url = ncuribuild(url,NULL,NULL,NCURIALL); | 
| 295 | 0 |     zfmap->map.flags = flags; | 
| 296 | 0 |     zfmap->map.mode = mode; | 
| 297 | 0 |     zfmap->map.api = (NCZMAP_API*)&zapi; | 
| 298 | 0 |     zfmap->root = abspath; | 
| 299 | 0 |   abspath = NULL; | 
| 300 |  |      | 
| 301 |  |     /* Verify root dir exists */ | 
| 302 | 0 |     if((stat = platformopendir(zfmap->map.mode,zfmap->root))) | 
| 303 | 0 |   goto done; | 
| 304 |  |      | 
| 305 |  |     /* Dataset superblock will be read by higher layer */ | 
| 306 |  |      | 
| 307 | 0 |     if(mapp) *mapp = (NCZMAP*)zfmap;     | 
| 308 |  | 
 | 
| 309 | 0 | done: | 
| 310 | 0 |     ncurifree(url); | 
| 311 | 0 |     nullfree(canonpath); | 
| 312 | 0 |     nullfree(abspath); | 
| 313 | 0 |     if(stat) zfileclose((NCZMAP*)zfmap,0); | 
| 314 | 0 |     return ZUNTRACE(stat); | 
| 315 | 0 | } | 
| 316 |  |  | 
| 317 |  | static int | 
| 318 |  | zfiletruncate(const char* surl) | 
| 319 | 0 | { | 
| 320 | 0 |     int stat = NC_NOERR; | 
| 321 | 0 |     NCURI* url = NULL; | 
| 322 |  | 
 | 
| 323 | 0 |     ZTRACE(6,"url=%s",surl); | 
| 324 | 0 |     ncuriparse(surl,&url); | 
| 325 | 0 |     if(url == NULL) {stat = NC_EURL; goto done;} | 
| 326 | 0 |     platformdelete(url->path,0); /* leave root; ignore errors */ | 
| 327 | 0 | done: | 
| 328 | 0 |     ncurifree(url); | 
| 329 | 0 |     return ZUNTRACE(stat); | 
| 330 | 0 | } | 
| 331 |  |  | 
| 332 |  | /**************************************************/ | 
| 333 |  | /* Object API */ | 
| 334 |  |  | 
| 335 |  | static int | 
| 336 |  | zfileexists(NCZMAP* map, const char* key) | 
| 337 | 0 | { | 
| 338 | 0 |     int stat = NC_NOERR; | 
| 339 | 0 |     ZFMAP* zfmap = (ZFMAP*)map; | 
| 340 | 0 |     FD fd = FDNUL; | 
| 341 |  | 
 | 
| 342 | 0 |     ZTRACE(5,"map=%s key=%s",zfmap->map.url,key); | 
| 343 | 0 |     switch(stat=zflookupobj(zfmap,key,&fd)) { | 
| 344 | 0 |     case NC_NOERR: break; | 
| 345 | 0 |     case NC_ENOOBJECT: stat = NC_EEMPTY; | 
| 346 | 0 |     case NC_EEMPTY: break; | 
| 347 | 0 |     default: break; | 
| 348 | 0 |     } | 
| 349 | 0 |     zfrelease(zfmap,&fd);     | 
| 350 | 0 |     return ZUNTRACE(stat); | 
| 351 | 0 | } | 
| 352 |  |  | 
| 353 |  | static int | 
| 354 |  | zfilelen(NCZMAP* map, const char* key, size64_t* lenp) | 
| 355 | 0 | { | 
| 356 | 0 |     int stat = NC_NOERR; | 
| 357 | 0 |     ZFMAP* zfmap = (ZFMAP*)map; | 
| 358 | 0 |     size64_t len = 0; | 
| 359 | 0 |     FD fd = FDNUL; | 
| 360 |  | 
 | 
| 361 | 0 |     ZTRACE(5,"map=%s key=%s",map->url,key); | 
| 362 |  | 
 | 
| 363 | 0 |     switch (stat=zflookupobj(zfmap,key,&fd)) { | 
| 364 | 0 |     case NC_NOERR: | 
| 365 |  |         /* Get file size */ | 
| 366 | 0 |         if((stat=platformseek(&fd, SEEK_END, &len))) goto done; | 
| 367 | 0 |   break; | 
| 368 | 0 |     case NC_ENOOBJECT: stat = NC_EEMPTY; | 
| 369 | 0 |     case NC_EEMPTY: break; | 
| 370 | 0 |     default: break; | 
| 371 | 0 |     } | 
| 372 | 0 |     zfrelease(zfmap,&fd); | 
| 373 | 0 |     if(lenp) *lenp = len; | 
| 374 |  | 
 | 
| 375 | 0 | done: | 
| 376 | 0 |     return ZUNTRACEX(stat,"len=%llu",(lenp?*lenp:777777777777)); | 
| 377 | 0 | } | 
| 378 |  |  | 
| 379 |  | static int | 
| 380 |  | zfileread(NCZMAP* map, const char* key, size64_t start, size64_t count, void* content) | 
| 381 | 0 | { | 
| 382 | 0 |     int stat = NC_NOERR; | 
| 383 | 0 |     FD fd = FDNUL; | 
| 384 | 0 |     ZFMAP* zfmap = (ZFMAP*)map; /* cast to true type */ | 
| 385 |  |      | 
| 386 | 0 |     ZTRACE(5,"map=%s key=%s start=%llu count=%llu",map->url,key,start,count); | 
| 387 |  | 
 | 
| 388 | 0 | #ifdef VERIFY | 
| 389 | 0 |     if(!verifykey(key,!FLAG_ISDIR)) | 
| 390 | 0 |         assert(!"expected file, have dir"); | 
| 391 | 0 | #endif | 
| 392 |  | 
 | 
| 393 | 0 |     switch (stat = zflookupobj(zfmap,key,&fd)) { | 
| 394 | 0 |     case NC_NOERR: | 
| 395 | 0 |         if((stat = platformseek(&fd, SEEK_SET, &start))) goto done; | 
| 396 | 0 |         if((stat = platformread(&fd, count, content))) goto done; | 
| 397 | 0 |   break; | 
| 398 | 0 |     case NC_ENOOBJECT: stat = NC_EEMPTY; | 
| 399 | 0 |     case NC_EEMPTY: break; | 
| 400 | 0 |     default: break; | 
| 401 | 0 |     } | 
| 402 |  |      | 
| 403 | 0 | done: | 
| 404 | 0 |     zfrelease(zfmap,&fd); | 
| 405 | 0 |     return ZUNTRACE(stat); | 
| 406 | 0 | } | 
| 407 |  |  | 
| 408 |  | static int | 
| 409 |  | zfilewrite(NCZMAP* map, const char* key, size64_t count, const void* content) | 
| 410 | 0 | { | 
| 411 | 0 |     int stat = NC_NOERR; | 
| 412 | 0 |     FD fd = FDNUL; | 
| 413 | 0 |     ZFMAP* zfmap = (ZFMAP*)map; /* cast to true type */ | 
| 414 | 0 |     char* truepath = NULL; | 
| 415 | 0 |     size64_t start = 0; | 
| 416 |  | 
 | 
| 417 | 0 |     ZTRACE(5,"map=%s key=%s start=%llu count=%llu",map->url,key,start,count); | 
| 418 |  | 
 | 
| 419 | 0 | #ifdef VERIFY | 
| 420 | 0 |     if(!verifykey(key,!FLAG_ISDIR)) | 
| 421 | 0 |         assert(!"expected file, have dir"); | 
| 422 | 0 | #endif | 
| 423 |  | 
 | 
| 424 | 0 |     switch (stat = zflookupobj(zfmap,key,&fd)) { | 
| 425 | 0 |     case NC_ENOOBJECT: | 
| 426 | 0 |     case NC_EEMPTY: | 
| 427 | 0 |   stat = NC_NOERR; | 
| 428 |  |   /* Create the directories leading to this */ | 
| 429 | 0 |   if((stat = zfcreategroup(zfmap,key,SKIPLAST))) goto done; | 
| 430 |  |         /* Create truepath */ | 
| 431 | 0 |         if((stat = zffullpath(zfmap,key,&truepath))) goto done; | 
| 432 |  |   /* Create file */ | 
| 433 | 0 |   if((stat = platformcreatefile(zfmap->map.mode,truepath,&fd))) goto done; | 
| 434 |  |   /* Fall thru to write the object */ | 
| 435 | 0 |     case NC_NOERR: | 
| 436 | 0 |         if((stat = platformseek(&fd, SEEK_SET, &start))) goto done; | 
| 437 | 0 |         if((stat = platformwrite(&fd, count, content))) goto done; | 
| 438 | 0 |   break; | 
| 439 | 0 |     default: break; | 
| 440 | 0 |     } | 
| 441 |  |  | 
| 442 | 0 | done: | 
| 443 | 0 |     nullfree(truepath); | 
| 444 | 0 |     zfrelease(zfmap,&fd); | 
| 445 | 0 |     return ZUNTRACE(stat); | 
| 446 | 0 | } | 
| 447 |  |  | 
| 448 |  | static int | 
| 449 |  | zfileclose(NCZMAP* map, int delete) | 
| 450 | 0 | { | 
| 451 | 0 |     int stat = NC_NOERR; | 
| 452 | 0 |     ZFMAP* zfmap = (ZFMAP*)map; | 
| 453 |  | 
 | 
| 454 | 0 |     ZTRACE(5,"map=%s delete=%d",map->url,delete); | 
| 455 | 0 |     if(zfmap == NULL) return NC_NOERR; | 
| 456 |  |      | 
| 457 |  |     /* Delete the subtree below the root and the root */ | 
| 458 | 0 |     if(delete) { | 
| 459 | 0 |   stat = platformdelete(zfmap->root,1); | 
| 460 | 0 |   zfunlink(zfmap->root); | 
| 461 | 0 |     } | 
| 462 | 0 |     nczm_clear(map); | 
| 463 | 0 |     nullfree(zfmap->root); | 
| 464 | 0 |     zfmap->root = NULL; | 
| 465 | 0 |     free(zfmap); | 
| 466 | 0 |     return ZUNTRACE(stat); | 
| 467 | 0 | } | 
| 468 |  |  | 
| 469 |  | /* | 
| 470 |  | Return a list of names immediately "below" a specified prefix key. | 
| 471 |  | In theory, the returned list should be sorted in lexical order, | 
| 472 |  | but it possible that it is not. | 
| 473 |  | The prefix key is not included.  | 
| 474 |  | */ | 
| 475 |  | int | 
| 476 |  | zfilesearch(NCZMAP* map, const char* prefixkey, NClist* matches) | 
| 477 | 0 | { | 
| 478 | 0 |     int stat = NC_NOERR; | 
| 479 | 0 |     ZFMAP* zfmap = (ZFMAP*)map; | 
| 480 | 0 |     char* fullpath = NULL; | 
| 481 | 0 |     NClist* nextlevel = nclistnew(); | 
| 482 | 0 |     NCbytes* buf = ncbytesnew(); | 
| 483 |  | 
 | 
| 484 | 0 |     ZTRACE(5,"map=%s prefixkey=%s",map->url,prefixkey); | 
| 485 |  |  | 
| 486 |  |     /* Make the root path be true */ | 
| 487 | 0 |     if(prefixkey == NULL || strlen(prefixkey)==0 || strcmp(prefixkey,"/")==0) | 
| 488 | 0 |   fullpath = strdup(zfmap->root); | 
| 489 | 0 |     else if((stat = nczm_concat(zfmap->root,prefixkey,&fullpath))) goto done; | 
| 490 |  |  | 
| 491 |  |     /* get names of the next level path entries */ | 
| 492 | 0 |     switch (stat = platformdircontent(fullpath, nextlevel)) { | 
| 493 | 0 |     case NC_NOERR: /* ok */ | 
| 494 | 0 |   break; | 
| 495 | 0 |     case NC_EEMPTY: /* not a dir */ | 
| 496 | 0 |   stat = NC_NOERR; | 
| 497 | 0 |   goto done; | 
| 498 | 0 |     case NC_ENOOBJECT: | 
| 499 | 0 |     default: | 
| 500 | 0 |   goto done; | 
| 501 | 0 |     } | 
| 502 | 0 |     while(nclistlength(nextlevel) > 0) { | 
| 503 | 0 |   char* segment = nclistremove(nextlevel,0); | 
| 504 | 0 |   nclistpush(matches,segment); | 
| 505 | 0 |     } | 
| 506 |  |      | 
| 507 | 0 | done: | 
| 508 | 0 |     nclistfreeall(nextlevel); | 
| 509 | 0 |     ncbytesfree(buf); | 
| 510 | 0 |     nullfree(fullpath); | 
| 511 | 0 |     return ZUNTRACEX(stat,"|matches|=%d",(int)nclistlength(matches)); | 
| 512 | 0 | } | 
| 513 |  |  | 
| 514 |  | /**************************************************/ | 
| 515 |  | /* Utilities */ | 
| 516 |  |  | 
| 517 |  | static void | 
| 518 |  | zfunlink(const char* canonpath) | 
| 519 | 0 | { | 
| 520 | 0 |     char* local = NULL; | 
| 521 | 0 |     if((local = NCpathcvt(canonpath))==NULL) goto done; | 
| 522 | 0 |     unlink(local); | 
| 523 | 0 | done: | 
| 524 | 0 |     nullfree(local); | 
| 525 | 0 | } | 
| 526 |  |  | 
| 527 |  | /* Lookup a group by parsed path (segments)*/ | 
| 528 |  | /* Return NC_EEMPTY if not found, NC_EINVAL if not a directory; create if create flag is set */ | 
| 529 |  | static int | 
| 530 |  | zfcreategroup(ZFMAP* zfmap, const char* key, int nskip) | 
| 531 | 0 | { | 
| 532 | 0 |     int stat = NC_NOERR; | 
| 533 | 0 |     size_t i; | 
| 534 | 0 |     size_t len; | 
| 535 | 0 |     char* fullpath = NULL; | 
| 536 | 0 |     NCbytes* path = ncbytesnew(); | 
| 537 | 0 |     NClist* segments = nclistnew(); | 
| 538 |  | 
 | 
| 539 | 0 |     ZTRACE(5,"map=%s key=%s nskip=%d",zfmap->map.url,key,nskip); | 
| 540 | 0 |     if((stat=nczm_split(key,segments))) | 
| 541 | 0 |   goto done;     | 
| 542 | 0 |     len = nclistlength(segments); | 
| 543 | 0 |     if(len >= (size_t)nskip) | 
| 544 | 0 |   len -= (size_t)nskip; /* leave off last nskip segments */ | 
| 545 | 0 |     else | 
| 546 | 0 |         len = 0; | 
| 547 | 0 |     ncbytescat(path,zfmap->root); /* We need path to be absolute */ | 
| 548 | 0 |     for(i=0;i<len;i++) { | 
| 549 | 0 |   const char* seg = nclistget(segments,i); | 
| 550 | 0 |   ncbytescat(path,"/"); | 
| 551 | 0 |   ncbytescat(path,seg); | 
| 552 |  |   /* open and optionally create the directory */   | 
| 553 | 0 |   stat = platformcreatedir(zfmap->map.mode,ncbytescontents(path)); | 
| 554 | 0 |   if(stat) goto done; | 
| 555 | 0 |     } | 
| 556 | 0 | done: | 
| 557 | 0 |     nullfree(fullpath); | 
| 558 | 0 |     ncbytesfree(path); | 
| 559 | 0 |     nclistfreeall(segments); | 
| 560 | 0 |     return ZUNTRACE(stat); | 
| 561 | 0 | } | 
| 562 |  |  | 
| 563 |  | /* Lookup an object | 
| 564 |  | @return NC_NOERR if found and is a content-bearing object | 
| 565 |  | @return NC_EEMPTY if exists but is not-content-bearing | 
| 566 |  | @return NC_ENOOBJECT if not found | 
| 567 |  | */ | 
| 568 |  | static int | 
| 569 |  | zflookupobj(ZFMAP* zfmap, const char* key, FD* fd) | 
| 570 | 0 | { | 
| 571 | 0 |     int stat = NC_NOERR; | 
| 572 | 0 |     char* path = NULL; | 
| 573 |  | 
 | 
| 574 | 0 |     ZTRACE(5,"map=%s key=%s",zfmap->map.url,key); | 
| 575 |  | 
 | 
| 576 | 0 |     if((stat = zffullpath(zfmap,key,&path))) | 
| 577 | 0 |   {goto done;}     | 
| 578 |  |  | 
| 579 |  |     /* See if this is content-bearing */ | 
| 580 | 0 |     if((stat = platformtestcontentbearing(path))) | 
| 581 | 0 |   goto done;         | 
| 582 |  |  | 
| 583 |  |     /* Open the file */ | 
| 584 | 0 |     if((stat = platformopenfile(zfmap->map.mode,path,fd))) | 
| 585 | 0 |         goto done; | 
| 586 |  |  | 
| 587 | 0 | done: | 
| 588 | 0 |     errno = 0; | 
| 589 | 0 |     nullfree(path); | 
| 590 | 0 |     return ZUNTRACE(stat); | 
| 591 | 0 | } | 
| 592 |  |  | 
| 593 |  | /* When we are finished accessing object */ | 
| 594 |  | static void | 
| 595 |  | zfrelease(ZFMAP* zfmap, FD* fd) | 
| 596 | 0 | { | 
| 597 | 0 |     ZTRACE(5,"map=%s fd=%d",zfmap->map.url,(fd?fd->fd:-1)); | 
| 598 | 0 |     platformrelease(fd); | 
| 599 | 0 |     (void)ZUNTRACE(NC_NOERR); | 
| 600 | 0 | } | 
| 601 |  |  | 
| 602 |  | /**************************************************/ | 
| 603 |  | /* External API objects */ | 
| 604 |  |  | 
| 605 |  | NCZMAP_DS_API zmap_file = { | 
| 606 |  |     NCZM_FILE_V1, | 
| 607 |  |     0, | 
| 608 |  |     zfilecreate, | 
| 609 |  |     zfileopen, | 
| 610 |  |     zfiletruncate, | 
| 611 |  | }; | 
| 612 |  |  | 
| 613 |  | static NCZMAP_API zapi = { | 
| 614 |  |     NCZM_FILE_V1, | 
| 615 |  |     zfileclose, | 
| 616 |  |     zfileexists, | 
| 617 |  |     zfilelen, | 
| 618 |  |     zfileread, | 
| 619 |  |     zfilewrite, | 
| 620 |  |     zfilesearch, | 
| 621 |  | }; | 
| 622 |  |  | 
| 623 |  | static int | 
| 624 |  | zffullpath(ZFMAP* zfmap, const char* key, char** pathp) | 
| 625 | 0 | { | 
| 626 | 0 |     int stat = NC_NOERR; | 
| 627 | 0 |     size_t klen, pxlen, flen; | 
| 628 | 0 |     char* path = NULL; | 
| 629 |  | 
 | 
| 630 | 0 |     ZTRACE(6,"map=%s key=%s",zfmap->map.url,key); | 
| 631 |  | 
 | 
| 632 | 0 |     klen = nulllen(key); | 
| 633 | 0 |     pxlen = strlen(zfmap->root); | 
| 634 | 0 |     flen = klen+pxlen+1+1; | 
| 635 | 0 |     if((path = malloc(flen)) == NULL) {stat = NC_ENOMEM; goto done;} | 
| 636 | 0 |     path[0] = '\0'; | 
| 637 | 0 |     strlcat(path,zfmap->root,flen); | 
| 638 |  |     /* look for special cases */ | 
| 639 | 0 |     if(key != NULL) { | 
| 640 | 0 |         if(key[0] != '/') strlcat(path,"/",flen); | 
| 641 | 0 |   if(strcmp(key,"/") != 0) | 
| 642 | 0 |             strlcat(path,key,flen); | 
| 643 | 0 |     } | 
| 644 | 0 |     if(pathp) {*pathp = path; path = NULL;} | 
| 645 | 0 | done: | 
| 646 | 0 |     nullfree(path) | 
| 647 | 0 |     return ZUNTRACEX(stat,"path=%s",(pathp?*pathp:"null")); | 
| 648 | 0 | } | 
| 649 |  |  | 
| 650 |  | static int | 
| 651 |  | zfparseurl(const char* path0, NCURI** urip) | 
| 652 | 0 | { | 
| 653 | 0 |     int stat = NC_NOERR; | 
| 654 | 0 |     NCURI* uri = NULL; | 
| 655 | 0 |     ZTRACE(6,"path0=%s",path0); | 
| 656 | 0 |     ncuriparse(path0,&uri); | 
| 657 | 0 |     if(uri == NULL) | 
| 658 | 0 |   {stat = NC_EURL; goto done;} | 
| 659 | 0 |     if(urip) {*urip = uri; uri = NULL;} | 
| 660 |  | 
 | 
| 661 | 0 | done: | 
| 662 | 0 |     ncurifree(uri); | 
| 663 | 0 |     return ZUNTRACEX(stat,"uri=%p",(urip?(void*)*urip:(void*)urip)); | 
| 664 | 0 |     return stat; | 
| 665 | 0 | } | 
| 666 |  |  | 
| 667 |  | /**************************************************/ | 
| 668 |  | static int | 
| 669 |  | platformerr(int err) | 
| 670 | 0 | { | 
| 671 | 0 |     ZTRACE(6,"err=%d",err); | 
| 672 | 0 |     switch (err) { | 
| 673 | 0 |      case ENOENT: err = NC_ENOOBJECT; break; /* File does not exist */ | 
| 674 | 0 |      case ENOTDIR: err = NC_EEMPTY; break; /* no content */ | 
| 675 | 0 |      case EACCES: err = NC_EAUTH; break; /* file permissions */ | 
| 676 | 0 |      case EPERM:  err = NC_EAUTH; break; /* ditto */ | 
| 677 | 0 |      default: break; | 
| 678 | 0 |      } | 
| 679 | 0 |      return ZUNTRACE(err); | 
| 680 | 0 | } | 
| 681 |  |  | 
| 682 |  | /* Test type of the specified file. | 
| 683 |  | @return NC_NOERR if found and is a content-bearing object (file) | 
| 684 |  | @return NC_EEMPTY if exists but is not-content-bearing (a directory) | 
| 685 |  | @return NC_ENOOBJECT if not found | 
| 686 |  | */ | 
| 687 |  | static int | 
| 688 |  | platformtestcontentbearing(const char* canonpath) | 
| 689 | 0 | { | 
| 690 | 0 |     int ret = 0; | 
| 691 |  |     #ifdef _WIN64 | 
| 692 |  |         struct _stat64 buf; | 
| 693 |  |     #else | 
| 694 | 0 |         struct stat buf; | 
| 695 | 0 |     #endif | 
| 696 |  | 
 | 
| 697 | 0 |     ZTRACE(6,"canonpath=%s",canonpath); | 
| 698 |  | 
 | 
| 699 | 0 |     errno = 0; | 
| 700 | 0 |     ret = NCstat(canonpath, &buf); | 
| 701 | 0 |     ZTRACEMORE(6,"\tstat: ret=%d, errno=%d st_mode=%d",ret,errno,buf.st_mode); | 
| 702 | 0 |     if(ret < 0) { | 
| 703 | 0 |   ret = platformerr(errno); | 
| 704 | 0 |     } else if(S_ISDIR(buf.st_mode)) { | 
| 705 | 0 |         ret = NC_EEMPTY; | 
| 706 | 0 |     } else | 
| 707 | 0 |         ret = NC_NOERR; | 
| 708 | 0 |     errno = 0; | 
| 709 | 0 |     return ZUNTRACE(ret); | 
| 710 | 0 | } | 
| 711 |  |  | 
| 712 |  | /* Create a file */ | 
| 713 |  | static int | 
| 714 |  | platformcreatefile(int mode, const char* canonpath, FD* fd) | 
| 715 | 0 | { | 
| 716 | 0 |     int stat = NC_NOERR; | 
| 717 | 0 |     int ioflags = 0; | 
| 718 | 0 |     int createflags = 0; | 
| 719 | 0 |     int permissions = NC_DEFAULT_ROPEN_PERMS; | 
| 720 |  | 
 | 
| 721 | 0 |     ZTRACE(6,"canonpath=%s",canonpath); | 
| 722 |  |      | 
| 723 | 0 |     errno = 0; | 
| 724 | 0 |     if(!fIsSet((mode_t)mode, NC_WRITE)) | 
| 725 | 0 |         ioflags |= (O_RDONLY); | 
| 726 | 0 |     else { | 
| 727 | 0 |         ioflags |= (O_RDWR); | 
| 728 | 0 |   permissions = NC_DEFAULT_RWOPEN_PERMS; | 
| 729 | 0 |     } | 
| 730 |  | #ifdef O_BINARY | 
| 731 |  |     fSet(ioflags, O_BINARY); | 
| 732 |  | #endif | 
| 733 |  | 
 | 
| 734 | 0 |     if(fIsSet(mode, NC_NOCLOBBER)) | 
| 735 | 0 |         fSet(createflags, O_EXCL); | 
| 736 | 0 |     else | 
| 737 | 0 |   fSet(createflags, O_TRUNC); | 
| 738 |  | 
 | 
| 739 | 0 |     if(fIsSet(mode,NC_WRITE)) | 
| 740 | 0 |         createflags = (ioflags|O_CREAT); | 
| 741 |  |  | 
| 742 |  |     /* Try to create file (will also NCpathcvt) */ | 
| 743 | 0 |     fd->fd = NCopen3(canonpath, createflags, permissions); | 
| 744 | 0 |     if(fd->fd < 0) { /* could not create */ | 
| 745 | 0 |   stat = platformerr(errno); | 
| 746 | 0 |         goto done; /* could not open */ | 
| 747 | 0 |     } | 
| 748 | 0 | done: | 
| 749 | 0 |     errno = 0; | 
| 750 | 0 |     return ZUNTRACEX(stat,"fd=%d",(fd?fd->fd:-1)); | 
| 751 | 0 | } | 
| 752 |  |  | 
| 753 |  | /* Open a file; fail if it does not exist */ | 
| 754 |  | static int | 
| 755 |  | platformopenfile(int mode, const char* canonpath, FD* fd) | 
| 756 | 0 | { | 
| 757 | 0 |     int stat = NC_NOERR; | 
| 758 | 0 |     int ioflags = 0; | 
| 759 | 0 |     int permissions = 0; | 
| 760 |  | 
 | 
| 761 | 0 |     ZTRACE(6,"canonpath=%s",canonpath); | 
| 762 |  | 
 | 
| 763 | 0 |     errno = 0; | 
| 764 | 0 |     if(!fIsSet(mode, NC_WRITE)) { | 
| 765 | 0 |         ioflags |= (O_RDONLY); | 
| 766 | 0 |   permissions = NC_DEFAULT_ROPEN_PERMS; | 
| 767 | 0 |     } else { | 
| 768 | 0 |         ioflags |= (O_RDWR); | 
| 769 | 0 |   permissions = NC_DEFAULT_RWOPEN_PERMS; | 
| 770 | 0 |     } | 
| 771 |  | #ifdef O_BINARY | 
| 772 |  |     fSet(ioflags, O_BINARY); | 
| 773 |  | #endif | 
| 774 |  | 
 | 
| 775 | 0 | #ifdef VERIFY | 
| 776 | 0 |     if(!verify(canonpath,!FLAG_ISDIR)) | 
| 777 | 0 |         assert(!"expected file, have dir"); | 
| 778 | 0 | #endif | 
| 779 |  |  | 
| 780 |  |     /* Try to open file  (will localize) */ | 
| 781 | 0 |     fd->fd = NCopen3(canonpath, ioflags, permissions); | 
| 782 | 0 |     if(fd->fd < 0) | 
| 783 | 0 |         {stat = platformerr(errno); goto done;} /* could not open */ | 
| 784 | 0 | done: | 
| 785 | 0 |     errno = 0; | 
| 786 | 0 |     return ZUNTRACEX(stat,"fd=%d",(fd?fd->fd:-1)); | 
| 787 | 0 | } | 
| 788 |  |  | 
| 789 |  | /* Create a dir */ | 
| 790 |  | static int | 
| 791 |  | platformcreatedir(int mode, const char* canonpath) | 
| 792 | 0 | { | 
| 793 | 0 |     int ret = NC_NOERR; | 
| 794 |  | 
 | 
| 795 | 0 |     ZTRACE(6,"canonpath=%s",canonpath); | 
| 796 |  | 
 | 
| 797 | 0 |     errno = 0; | 
| 798 |  |     /* Try to access file as if it exists */ | 
| 799 | 0 |     ret = NCaccess(canonpath,ACCESS_MODE_EXISTS); | 
| 800 | 0 |     if(ret < 0) { /* it does not exist, then it can be anything */ | 
| 801 | 0 |   if(fIsSet(mode,NC_WRITE)) { | 
| 802 |  |       /* Try to create it */ | 
| 803 |  |             /* Create the directory using mkdir */ | 
| 804 | 0 |         if(NCmkdir(canonpath,(mode_t)NC_DEFAULT_DIR_PERMS) < 0) | 
| 805 | 0 |           {ret = platformerr(errno); goto done;} | 
| 806 |  |       /* try to access again */ | 
| 807 | 0 |       ret = NCaccess(canonpath,ACCESS_MODE_EXISTS); | 
| 808 | 0 |           if(ret < 0) | 
| 809 | 0 |           {ret = platformerr(errno); goto done;} | 
| 810 | 0 |   } else | 
| 811 | 0 |       {ret = platformerr(errno); goto done;}   | 
| 812 | 0 |     } | 
| 813 |  |  | 
| 814 | 0 | done: | 
| 815 | 0 |     errno = 0; | 
| 816 | 0 |     return ZUNTRACE(ret); | 
| 817 | 0 | } | 
| 818 |  |  | 
| 819 |  | /* Open a dir; fail if it does not exist */ | 
| 820 |  | static int | 
| 821 |  | platformopendir(int mode, const char* canonpath) | 
| 822 | 0 | { | 
| 823 | 0 |     int ret = NC_NOERR; | 
| 824 |  | 
 | 
| 825 | 0 |     ZTRACE(6,"canonpath=%s",canonpath); | 
| 826 |  | 
 | 
| 827 | 0 |     errno = 0; | 
| 828 |  |     /* Try to access file as if it exists */ | 
| 829 | 0 |     ret = NCaccess(canonpath,ACCESS_MODE_EXISTS); | 
| 830 | 0 |     if(ret < 0) | 
| 831 | 0 |   {ret = platformerr(errno); goto done;}   | 
| 832 | 0 | done: | 
| 833 | 0 |     errno = 0; | 
| 834 | 0 |     return ZUNTRACE(ret); | 
| 835 | 0 | } | 
| 836 |  |  | 
| 837 |  | /** | 
| 838 |  | Given a path, return the list of all files+dirs immediately below | 
| 839 |  | the specified path: e.g. X s.t. path/X exists. | 
| 840 |  | There are several possibilities: | 
| 841 |  | 1. path does not exist => return NC_ENOTFOUND | 
| 842 |  | 2. path is not a directory => return NC_EEMPTY and |contents| == 0 | 
| 843 |  | 3. path is a directory => return NC_NOERR and |contents| >= 0 | 
| 844 |  |  | 
| 845 |  | @return NC_NOERR if path is a directory | 
| 846 |  | @return NC_EEMPTY if path is not a directory | 
| 847 |  | @return NC_ENOTFOUND if path does not exist | 
| 848 |  | */ | 
| 849 |  |  | 
| 850 |  | #ifdef _WIN32 | 
| 851 |  | static int | 
| 852 |  | platformdircontent(const char* canonpath, NClist* contents) | 
| 853 |  | { | 
| 854 |  |     int ret = NC_NOERR; | 
| 855 |  |     errno = 0; | 
| 856 |  |     WIN32_FIND_DATA FindFileData; | 
| 857 |  |     HANDLE dir = NULL; | 
| 858 |  |     char* ffpath = NULL; | 
| 859 |  |     char* lpath = NULL; | 
| 860 |  |     size_t len; | 
| 861 |  |     char* d = NULL; | 
| 862 |  |  | 
| 863 |  |     ZTRACE(6,"canonpath=%s",canonpath); | 
| 864 |  |  | 
| 865 |  |     switch (ret = platformtestcontentbearing(canonpath)) { | 
| 866 |  |     case NC_EEMPTY: ret = NC_NOERR; break; /* directory */     | 
| 867 |  |     case NC_NOERR: ret = NC_EEMPTY; goto done; | 
| 868 |  |     default: goto done; | 
| 869 |  |     } | 
| 870 |  |  | 
| 871 |  |     /* We need to process the path to make it work with FindFirstFile */ | 
| 872 |  |     len = strlen(canonpath); | 
| 873 |  |     /* Need to terminate path with '/''*' */ | 
| 874 |  |     ffpath = (char*)malloc(len+2+1); | 
| 875 |  |     memcpy(ffpath,canonpath,len); | 
| 876 |  |     if(canonpath[len-1] != '/') { | 
| 877 |  |   ffpath[len] = '/';   | 
| 878 |  |   len++; | 
| 879 |  |     } | 
| 880 |  |     ffpath[len] = '*'; len++; | 
| 881 |  |     ffpath[len] = '\0'; | 
| 882 |  |  | 
| 883 |  |     /* localize it */ | 
| 884 |  |     if((lpath = NCpathcvt(ffpath))==NULL) | 
| 885 |  |   {ret = NC_ENOMEM; goto done;} | 
| 886 |  |     dir = FindFirstFile(lpath, &FindFileData); | 
| 887 |  |     if(dir == INVALID_HANDLE_VALUE) { | 
| 888 |  |   /* Distinquish not-a-directory from no-matching-file */ | 
| 889 |  |         switch (GetLastError()) { | 
| 890 |  |   case ERROR_FILE_NOT_FOUND: /* No matching files */ /* fall thru */ | 
| 891 |  |       ret = NC_NOERR; | 
| 892 |  |       goto done; | 
| 893 |  |   case ERROR_DIRECTORY: /* not a directory */ | 
| 894 |  |   default: | 
| 895 |  |             ret = NC_EEMPTY; | 
| 896 |  |       goto done; | 
| 897 |  |   } | 
| 898 |  |     } | 
| 899 |  |     do { | 
| 900 |  |   const char* name = NULL; | 
| 901 |  |         name = FindFileData.cFileName; | 
| 902 |  |   if(strcmp(name,".")==0 || strcmp(name,"..")==0) | 
| 903 |  |       continue; | 
| 904 |  |   nclistpush(contents,strdup(name)); | 
| 905 |  |     } while(FindNextFile(dir, &FindFileData)); | 
| 906 |  |  | 
| 907 |  | done: | 
| 908 |  |     if(dir) FindClose(dir); | 
| 909 |  |     nullfree(lpath); | 
| 910 |  |     nullfree(ffpath); | 
| 911 |  |     nullfree(d); | 
| 912 |  |     errno = 0; | 
| 913 |  |     return ZUNTRACEX(ret,"|contents|=%d",(int)nclistlength(contents)); | 
| 914 |  | } | 
| 915 |  |  | 
| 916 |  | #else /*!_WIN32*/ | 
| 917 |  |  | 
| 918 |  | static int | 
| 919 |  | platformdircontent(const char* canonpath, NClist* contents) | 
| 920 | 0 | { | 
| 921 | 0 |     int ret = NC_NOERR; | 
| 922 | 0 |     errno = 0; | 
| 923 | 0 |     DIR* dir = NULL; | 
| 924 |  | 
 | 
| 925 | 0 |     ZTRACE(6,"canonpath=%s",canonpath); | 
| 926 |  | 
 | 
| 927 | 0 |     switch (ret = platformtestcontentbearing(canonpath)) { | 
| 928 | 0 |     case NC_EEMPTY: ret = NC_NOERR; break; /* directory */     | 
| 929 | 0 |     case NC_NOERR: ret = NC_EEMPTY; goto done;  | 
| 930 | 0 |     case NC_ENOOBJECT: ret = NC_EEMPTY; goto done; | 
| 931 | 0 |     default: goto done; | 
| 932 | 0 |     } | 
| 933 |  |  | 
| 934 | 0 |     dir = NCopendir(canonpath); | 
| 935 | 0 |     if(dir == NULL) | 
| 936 | 0 |         {ret = platformerr(errno); goto done;} | 
| 937 | 0 |     for(;;) { | 
| 938 | 0 |   const char* name = NULL; | 
| 939 | 0 |   struct dirent* de = NULL; | 
| 940 | 0 |   errno = 0; | 
| 941 | 0 |         de = readdir(dir); | 
| 942 | 0 |         if(de == NULL) | 
| 943 | 0 |       {ret = platformerr(errno); goto done;} | 
| 944 | 0 |   if(strcmp(de->d_name,".")==0 || strcmp(de->d_name,"..")==0) | 
| 945 | 0 |       continue; | 
| 946 | 0 |   name = de->d_name; | 
| 947 | 0 |   nclistpush(contents,strdup(name)); | 
| 948 | 0 |     } | 
| 949 | 0 | done: | 
| 950 | 0 |     if(dir) NCclosedir(dir); | 
| 951 | 0 |     errno = 0; | 
| 952 | 0 |     return ZUNTRACEX(ret,"|contents|=%d",(int)nclistlength(contents)); | 
| 953 | 0 | } | 
| 954 |  | #endif /*_WIN32*/ | 
| 955 |  |  | 
| 956 |  | static int | 
| 957 |  | platformdeleter(NCbytes* canonpath, int depth) | 
| 958 | 0 | { | 
| 959 | 0 |     int ret = NC_NOERR; | 
| 960 | 0 |     size_t i; | 
| 961 | 0 |     NClist* subfiles = nclistnew(); | 
| 962 | 0 |     size_t tpathlen = ncbyteslength(canonpath); | 
| 963 | 0 |     char* local = NULL; | 
| 964 |  | 
 | 
| 965 | 0 |     local = ncbytescontents(canonpath); | 
| 966 | 0 |     ZTRACE(6,"canonpath=%s depth=%d",local,depth); | 
| 967 |  | 
 | 
| 968 | 0 |     ret = platformdircontent(local, subfiles); | 
| 969 |  | #ifdef DEBUG | 
| 970 |  |     {int i; | 
| 971 |  |   fprintf(stderr,"xxx: contents:\n"); | 
| 972 |  |   for(i=0;i<nclistlength(contents);i++) | 
| 973 |  |       fprintf(stderr,"\t%s\n",(const char*)nclistget(contents,i)); | 
| 974 |  |   fprintf(stderr,"xxx: end contents\n"); | 
| 975 |  |     } | 
| 976 |  | #endif | 
| 977 | 0 |     switch (ret) { | 
| 978 | 0 |     case NC_NOERR: /* recurse to remove levels below */ | 
| 979 | 0 |         for(i=0;i<nclistlength(subfiles);i++) { | 
| 980 | 0 |       const char* name = nclistget(subfiles,i); | 
| 981 |  |             /* append name to current path */ | 
| 982 | 0 |             ncbytescat(canonpath, "/"); | 
| 983 | 0 |             ncbytescat(canonpath, name); | 
| 984 |  |             /* recurse */ | 
| 985 | 0 |             if ((ret = platformdeleter(canonpath,depth+1))) goto done; | 
| 986 | 0 |             ncbytessetlength(canonpath,tpathlen); /* reset */ | 
| 987 | 0 |       ncbytesnull(canonpath); | 
| 988 | 0 |       local = ncbytescontents(canonpath); | 
| 989 | 0 |   } | 
| 990 | 0 |   if(depth > 0) { | 
| 991 |  | #ifdef DEBUG | 
| 992 |  | fprintf(stderr,"xxx: remove:  %s\n",canonpath); | 
| 993 |  | #endif | 
| 994 | 0 |             if(NCrmdir(local) < 0) { /* kill this dir */ | 
| 995 |  | #ifdef DEBUG | 
| 996 |  | fprintf(stderr,"xxx: remove: errno=%d|%s\n",errno,nc_strerror(errno)); | 
| 997 |  | #endif | 
| 998 | 0 |     ret = errno; | 
| 999 | 0 |     goto done; | 
| 1000 | 0 |       } | 
| 1001 | 0 |   } | 
| 1002 | 0 |   break;     | 
| 1003 | 0 |     case NC_EEMPTY: /* Not a directory */ | 
| 1004 | 0 |   ret = NC_NOERR; | 
| 1005 |  | #ifdef DEBUG | 
| 1006 |  | fprintf(stderr,"xxx: remove:  %s\n",canonpath); | 
| 1007 |  | #endif | 
| 1008 | 0 |             if(NCremove(local) < 0) {/* kill this file */ | 
| 1009 |  | #ifdef DEBUG | 
| 1010 |  | fprintf(stderr,"xxx: remove: errno=%d|%s\n",errno,nc_strerror(errno)); | 
| 1011 |  | #endif | 
| 1012 | 0 |           ret = errno; | 
| 1013 | 0 |           goto done; | 
| 1014 | 0 |       } | 
| 1015 | 0 |   break; | 
| 1016 | 0 |     case NC_ENOTFOUND: | 
| 1017 | 0 |     default: | 
| 1018 | 0 |   goto done; | 
| 1019 | 0 |     } | 
| 1020 |  |  | 
| 1021 | 0 | done: | 
| 1022 | 0 |     errno = 0; | 
| 1023 | 0 |     nclistfreeall(subfiles); | 
| 1024 | 0 |     ncbytessetlength(canonpath,tpathlen); | 
| 1025 | 0 |     ncbytesnull(canonpath); | 
| 1026 | 0 |     return ZUNTRACE(ret); | 
| 1027 | 0 | } | 
| 1028 |  |  | 
| 1029 |  | /* Deep file/dir deletion; depth first */ | 
| 1030 |  | static int | 
| 1031 |  | platformdelete(const char* rootpath, int delroot) | 
| 1032 | 0 | { | 
| 1033 | 0 |     int stat = NC_NOERR; | 
| 1034 | 0 |     NCbytes* canonpath = ncbytesnew(); | 
| 1035 |  | 
 | 
| 1036 | 0 |     ZTRACE(6,"rootpath=%s delroot=%d",rootpath,delroot); | 
| 1037 |  |      | 
| 1038 | 0 |     if(rootpath == NULL || strlen(rootpath) == 0) goto done; | 
| 1039 | 0 |     ncbytescat(canonpath,rootpath); | 
| 1040 | 0 |     if(rootpath[strlen(rootpath)-1] == '/') /* elide trailing '/' */ | 
| 1041 | 0 |   ncbytessetlength(canonpath,ncbyteslength(canonpath)-1); | 
| 1042 |  |     /* See if file even exists */ | 
| 1043 | 0 |     stat = NCaccess(ncbytescontents(canonpath),F_OK); | 
| 1044 | 0 |     if(stat < 0) { | 
| 1045 | 0 |   stat = errno; | 
| 1046 | 0 |   goto done; | 
| 1047 | 0 |     } | 
| 1048 | 0 |     if((stat = platformdeleter(canonpath,0))) goto done; | 
| 1049 | 0 |     if(delroot) { | 
| 1050 | 0 |         if(NCrmdir(rootpath) < 0) { /* kill this dir */ | 
| 1051 | 0 |           stat = errno; | 
| 1052 | 0 |       goto done; | 
| 1053 | 0 |    } | 
| 1054 | 0 |     } | 
| 1055 | 0 | done: | 
| 1056 | 0 |     ncbytesfree(canonpath); | 
| 1057 | 0 |     errno = 0; | 
| 1058 | 0 |     return ZUNTRACE(stat); | 
| 1059 | 0 | } | 
| 1060 |  |  | 
| 1061 |  | static int | 
| 1062 |  | platformseek(FD* fd, int pos, size64_t* sizep) | 
| 1063 | 0 | { | 
| 1064 | 0 |     int ret = NC_NOERR; | 
| 1065 | 0 |     size64_t size, newsize; | 
| 1066 | 0 |     struct stat statbuf;     | 
| 1067 |  |      | 
| 1068 | 0 |     assert(fd && fd->fd >= 0); | 
| 1069 |  |      | 
| 1070 | 0 |     ZTRACE(6,"fd=%d pos=%d",(fd?fd->fd:-1),pos); | 
| 1071 |  | 
 | 
| 1072 | 0 |     errno = 0; | 
| 1073 | 0 |     ret = NCfstat(fd->fd, &statbuf);     | 
| 1074 | 0 |     if(ret < 0) | 
| 1075 | 0 |   {ret = platformerr(errno); goto done;} | 
| 1076 | 0 |     if(sizep) size = *sizep; else size = 0; | 
| 1077 | 0 |     newsize = (size64_t)lseek(fd->fd,(off_t)size,pos); | 
| 1078 | 0 |     if(sizep) *sizep = newsize; | 
| 1079 | 0 | done: | 
| 1080 | 0 |     errno = 0; | 
| 1081 | 0 |     return ZUNTRACEX(ret,"sizep=%llu",*sizep); | 
| 1082 | 0 | } | 
| 1083 |  |  | 
| 1084 |  | static int | 
| 1085 |  | platformread(FD* fd, size64_t count, void* content) | 
| 1086 | 0 | { | 
| 1087 | 0 |     int stat = NC_NOERR; | 
| 1088 | 0 |     size_t need = count; | 
| 1089 | 0 |     unsigned char* readpoint = content; | 
| 1090 |  | 
 | 
| 1091 | 0 |     assert(fd && fd->fd >= 0); | 
| 1092 |  | 
 | 
| 1093 | 0 |     ZTRACE(6,"fd=%d count=%llu",(fd?fd->fd:-1),count); | 
| 1094 |  | 
 | 
| 1095 | 0 |     while(need > 0) { | 
| 1096 | 0 |         ssize_t red; | 
| 1097 | 0 |         if((red = read(fd->fd,readpoint,need)) <= 0) | 
| 1098 | 0 |       {stat = errno; goto done;} | 
| 1099 | 0 |         need -= red; | 
| 1100 | 0 |   readpoint += red; | 
| 1101 | 0 |     } | 
| 1102 | 0 | done: | 
| 1103 | 0 |     errno = 0; | 
| 1104 | 0 |     return ZUNTRACE(stat); | 
| 1105 | 0 | } | 
| 1106 |  |  | 
| 1107 |  | static int | 
| 1108 |  | platformwrite(FD* fd, size64_t count, const void* content) | 
| 1109 | 0 | { | 
| 1110 | 0 |     int ret = NC_NOERR; | 
| 1111 | 0 |     size_t need = count; | 
| 1112 | 0 |     unsigned char* writepoint = (unsigned char*)content; | 
| 1113 |  | 
 | 
| 1114 | 0 |     assert(fd && fd->fd >= 0); | 
| 1115 |  |      | 
| 1116 | 0 |     ZTRACE(6,"fd=%d count=%llu",(fd?fd->fd:-1),count); | 
| 1117 |  | 
 | 
| 1118 | 0 |     while(need > 0) { | 
| 1119 | 0 |         ssize_t red = 0; | 
| 1120 | 0 |         if((red = write(fd->fd,(void*)writepoint,need)) <= 0)  | 
| 1121 | 0 |       {ret = NC_EACCESS; goto done;} | 
| 1122 | 0 |         need -= red; | 
| 1123 | 0 |   writepoint += red; | 
| 1124 | 0 |     } | 
| 1125 | 0 | done: | 
| 1126 | 0 |     return ZUNTRACE(ret); | 
| 1127 | 0 | } | 
| 1128 |  |  | 
| 1129 |  | #if 0 | 
| 1130 |  | static int | 
| 1131 |  | platformcwd(char** cwdp) | 
| 1132 |  | { | 
| 1133 |  |     char buf[4096]; | 
| 1134 |  |     char* cwd = NULL; | 
| 1135 |  |     cwd = NCcwd(buf,sizeof(buf)); | 
| 1136 |  |     if(cwd == NULL) return errno; | 
| 1137 |  |     if(cwdp) *cwdp = strdup(buf); | 
| 1138 |  |     return NC_NOERR; | 
| 1139 |  | } | 
| 1140 |  | #endif | 
| 1141 |  |  | 
| 1142 |  | /* When we are finished accessing FD; essentially | 
| 1143 |  |    equivalent to closing the file descriptor. | 
| 1144 |  | */ | 
| 1145 |  | static void | 
| 1146 |  | platformrelease(FD* fd) | 
| 1147 | 0 | { | 
| 1148 | 0 |     ZTRACE(6,"fd=%d",(fd?fd->fd:-1)); | 
| 1149 | 0 |     if(fd->fd >=0) NCclose(fd->fd); | 
| 1150 | 0 |     fd->fd = -1; | 
| 1151 | 0 |     (void)ZUNTRACE(NC_NOERR); | 
| 1152 | 0 | } | 
| 1153 |  |  | 
| 1154 |  | #if 0 | 
| 1155 |  | /* Close FD => return typ to FDNONE */ | 
| 1156 |  | */ | 
| 1157 |  | static void | 
| 1158 |  | platformclose(FD* fd) | 
| 1159 |  | { | 
| 1160 |  |     if(fd->typ == FDFILE) { | 
| 1161 |  |         if(fd->fd >=0) close(fd->u,fd); | 
| 1162 |  |   fd->fd = -1; | 
| 1163 |  |     } else if(fd->type == FDDIR) { | 
| 1164 |  |   if(fd->u.dir) NCclosedir(fd->u,dir); | 
| 1165 |  |     } | 
| 1166 |  |     fd->typ = FDNONE; | 
| 1167 |  | } | 
| 1168 |  | #endif | 
| 1169 |  |  | 
| 1170 |  |  | 
| 1171 |  | #ifdef VERIFY | 
| 1172 |  | static int | 
| 1173 |  | verify(const char* path, int isdir) | 
| 1174 | 0 | { | 
| 1175 | 0 |     int ret = 0; | 
| 1176 |  |     #ifdef _WIN64 | 
| 1177 |  |         struct _stat64 buf; | 
| 1178 |  |     #else | 
| 1179 | 0 |         struct stat buf; | 
| 1180 | 0 |     #endif  | 
| 1181 |  | 
 | 
| 1182 | 0 |     ret = NCaccess(path,ACCESS_MODE_EXISTS); | 
| 1183 | 0 |     if(ret < 0) | 
| 1184 | 0 |         return 1; /* If it does not exist, then it can be anything */ | 
| 1185 | 0 |     ret = NCstat(path,&buf); | 
| 1186 | 0 |     if(ret < 0) abort(); | 
| 1187 | 0 |     if(isdir && S_ISDIR(buf.st_mode)) return 1; | 
| 1188 | 0 |     if(!isdir && S_ISREG(buf.st_mode)) return 1;            | 
| 1189 | 0 |     return 0; | 
| 1190 | 0 | } | 
| 1191 |  |  | 
| 1192 |  | static int | 
| 1193 |  | verifykey(const char* key, int isdir) | 
| 1194 | 0 | { | 
| 1195 | 0 |     int ret = 0; | 
| 1196 |  |     #ifdef _WIN64 | 
| 1197 |  |         struct _stat64 buf; | 
| 1198 |  |     #else | 
| 1199 | 0 |         struct stat buf; | 
| 1200 | 0 |     #endif  | 
| 1201 |  | 
 | 
| 1202 | 0 |     if(key[0] == '/') key++; /* Want relative name */ | 
| 1203 |  | 
 | 
| 1204 | 0 |     ret = NCaccess(key,ACCESS_MODE_EXISTS); | 
| 1205 | 0 |     if(ret < 0) | 
| 1206 | 0 |         return 1; /* If it does not exist, then it can be anything */ | 
| 1207 | 0 |     ret = NCstat(key,&buf); | 
| 1208 | 0 |     if(ret < 0) abort(); | 
| 1209 | 0 |     if(isdir && S_ISDIR(buf.st_mode)) return 1; | 
| 1210 | 0 |     if(!isdir && S_ISREG(buf.st_mode)) return 1;            | 
| 1211 | 0 |     return 0; | 
| 1212 | 0 | } | 
| 1213 |  | #endif | 
| 1214 |  |  | 
| 1215 |  | #if 0 | 
| 1216 |  | /* Return NC_EINVAL if path does not exist; els 1/0 in isdirp and local path in canonpathp */ | 
| 1217 |  | static int | 
| 1218 |  | testifdir(const char* path, int* isdirp, char** canonpathp) | 
| 1219 |  | { | 
| 1220 |  |     int ret = NC_NOERR; | 
| 1221 |  |     char* tmp = NULL; | 
| 1222 |  |     char* canonpath = NULL; | 
| 1223 |  |     struct stat statbuf; | 
| 1224 |  |  | 
| 1225 |  |     /* Make path be windows compatible */ | 
| 1226 |  |     if((ret = nczm_fixpath(path,&tmp))) goto done; | 
| 1227 |  |     if((canonpath = NCpathcvt(tmp))==NULL) {ret = NC_ENOMEM; goto done;} | 
| 1228 |  |  | 
| 1229 |  |     errno = 0; | 
| 1230 |  |     ret = NCstat(canonpath, &statbuf); | 
| 1231 |  |     if(ret < 0) { | 
| 1232 |  |         if(errno == ENOENT) | 
| 1233 |  |       ret = NC_ENOTFOUND;  /* path does not exist */ | 
| 1234 |  |   else | 
| 1235 |  |       ret = platformerr(errno); | 
| 1236 |  |   goto done; | 
| 1237 |  |     } | 
| 1238 |  |     /* Check for being a directory */ | 
| 1239 |  |     if(isdirp) { | 
| 1240 |  |         if(S_ISDIR(statbuf.st_mode)) {*isdirp = 1;} else {*isdirp = 0;} | 
| 1241 |  |     } | 
| 1242 |  |     if(canonpathp) {*canonpathp = canonpath; canonpath = NULL;} | 
| 1243 |  | done: | 
| 1244 |  |     errno = 0; | 
| 1245 |  |     nullfree(tmp); | 
| 1246 |  |     nullfree(canonpath); | 
| 1247 |  |     return ZUNTRACE(ret);     | 
| 1248 |  | } | 
| 1249 |  | #endif /* 0 */ |