Coverage Report

Created: 2025-10-28 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 */