Coverage Report

Created: 2023-05-28 06:42

/src/netcdf-c/libnczarr/zprov.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * @internal Add provenance info for netcdf-4 files.
4
 *
5
 * Copyright 2018, UCAR/Unidata See netcdf/COPYRIGHT file for copying
6
 * and redistribution conditions.
7
 * @author Dennis Heimbigner
8
 */
9
10
#include "zincludes.h"
11
#include "nc_provenance.h"
12
13
/* Provide a hack to suppress the writing of _NCProperties attribute.
14
   This is for creating a file without _NCProperties for testing purposes.
15
*/
16
#undef SUPPRESSNCPROPS
17
18
/* Various Constants */
19
#define NCPROPS_MAX_NAME 1024 /* max key name size */
20
#define NCPROPS_MAX_VALUE 1024 /* max value size */
21
#define NCZ_MAX_NAME 1024 /**< ZARR max name. */
22
23
#define ESCAPECHARS "\\=|,"
24
25
1
#define NCPNCZLIB "nczarr"
26
27
/** @internal Check NetCDF return code. */
28
#define NCHECK(expr) {if((expr)!=NC_NOERR) {goto done;}}
29
30
/** @internal Check ZARR return code. */
31
#define HCHECK(expr) {if((expr)<0) {ncstat = NC_EHDFERR; goto done;}}
32
33
static int NCZ_read_ncproperties(NC_FILE_INFO_T* h5, const char* value, char** propstring);
34
static int NCZ_write_ncproperties(NC_FILE_INFO_T* h5);
35
36
static int globalpropinitialized = 0;
37
static NC4_Provenance globalprovenance;
38
39
/**
40
 * @internal Initialize default provenance info
41
 * This will only be used for newly created files
42
 * or for opened files that do not contain an _NCProperties
43
 * attribute.
44
 *
45
 * @return ::NC_NOERR No error.
46
 * @author Dennis Heimbigner
47
 */
48
int
49
NCZ_provenance_init(void)
50
1
{
51
1
    int stat = NC_NOERR;
52
1
    char* name = NULL;
53
1
    char* value = NULL;
54
1
    unsigned long major, minor, release;
55
1
    NCbytes* buffer = NULL; /* for constructing the global _NCProperties */
56
1
    char printbuf[1024];
57
58
1
    if(globalpropinitialized)
59
0
        return stat;
60
61
    /* Build _NCProperties info */
62
63
    /* Initialize globalpropinfo */
64
1
    memset((void*)&globalprovenance,0,sizeof(NC4_Provenance));
65
1
    globalprovenance.version = NCPROPS_VERSION;
66
67
1
    buffer = ncbytesnew();
68
69
    /* Insert version as first entry */
70
1
    ncbytescat(buffer,NCPVERSION);
71
1
    ncbytescat(buffer,"=");
72
73
1
    snprintf(printbuf,sizeof(printbuf),"%d",globalprovenance.version);
74
1
    ncbytescat(buffer,printbuf);
75
76
    /* Insert the netcdf version */
77
1
    ncbytesappend(buffer,NCPROPSSEP2);
78
1
    ncbytescat(buffer,NCPNCLIB2);
79
1
    ncbytescat(buffer,"=");
80
1
    ncbytescat(buffer,PACKAGE_VERSION);
81
82
    /* This should be redundant since netcdf version => zarr format */
83
    /* Insert the ZARR as underlying storage format library */
84
1
    ncbytesappend(buffer,NCPROPSSEP2);
85
1
    ncbytescat(buffer,NCPNCZLIB);
86
1
    ncbytescat(buffer,"=");
87
1
    if((stat = NCZ_get_libversion(&major,&minor,&release))) return stat;
88
1
    snprintf(printbuf,sizeof(printbuf),"%lu.%lu.%lu",major,minor,release);
89
1
    ncbytescat(buffer,printbuf);
90
91
#ifdef NCPROPERTIES_EXTRA
92
    if(NCPROPERTIES_EXTRA != NULL && strlen(NCPROPERTIES_EXTRA) > 0)
93
    {
94
    /* Add any extra fields */
95
    const char* p = NCPROPERTIES_EXTRA;
96
    if(p != NULL && strlen(p) > 0) {
97
        if(p[0] == NCPROPSSEP2) p++; /* If leading separator */
98
        ncbytesappend(buffer,NCPROPSSEP2);
99
        ncbytescat(buffer,p);
100
    }
101
    }
102
#endif
103
1
    ncbytesnull(buffer);
104
1
    globalprovenance.ncproperties = ncbytesextract(buffer);
105
106
1
    ncbytesfree(buffer);
107
1
    if(name != NULL) free(name);
108
1
    if(value != NULL) free(value);
109
1
    if(stat == NC_NOERR)
110
1
        globalpropinitialized = 1; /* avoid repeating it */
111
1
    return stat;
112
1
}
113
114
/**
115
 * @internal finalize default provenance info
116
 *
117
 * @return ::NC_NOERR No error.
118
 * @author Dennis Heimbigner
119
 */
120
int
121
NCZ_provenance_finalize(void)
122
1
{
123
1
    return NCZ_clear_provenance(&globalprovenance);
124
1
}
125
126
/**
127
 * @internal
128
 *
129
 * Construct the provenance information for a newly created file.
130
 * Note that creation of the _NCProperties attribute is deferred
131
 * to the sync_netcdf4_file function.
132
 *
133
 * @param file Pointer to file object.
134
 *
135
 * @return ::NC_NOERR No error.
136
 * [Note: other errors are reported via LOG()]
137
 * @author Dennis Heimbigner
138
 */
139
int
140
NCZ_new_provenance(NC_FILE_INFO_T* file)
141
0
{
142
0
    int stat = NC_NOERR;
143
0
    NC4_Provenance* provenance = NULL;
144
0
    int superblock;
145
146
0
    LOG((5, "%s: ncid 0x%x", __func__, file->root_grp->hdr.id));
147
148
0
    assert(file->provenance.ncproperties == NULL); /* not yet defined */
149
150
0
    provenance = &file->provenance;
151
0
    memset(provenance,0,sizeof(NC4_Provenance)); /* make sure */
152
153
    /* Set the version */
154
0
    provenance->version = globalprovenance.version;
155
156
    /* Set the superblock number */
157
0
    if((stat = NCZ_get_superblock(file,&superblock))) goto done;
158
0
    provenance->superblockversion = superblock;
159
160
0
    if(globalprovenance.ncproperties != NULL) {
161
0
        if((provenance->ncproperties = strdup(globalprovenance.ncproperties)) == NULL)
162
0
      {stat = NC_ENOMEM; goto done;}
163
0
    }
164
165
0
done:
166
0
    if(stat) {
167
0
        LOG((0,"Could not create _NCProperties attribute"));
168
0
    }
169
0
    return NC_NOERR;
170
0
}
171
172
/**
173
 * @internal
174
 *
175
 * Construct the provenance information for an existing file.
176
 *
177
 * @param file Pointer to file object.
178
 *
179
 * @return ::NC_NOERR No error.
180
 * [Note: other errors are reported via LOG()]
181
 * @author Dennis Heimbigner
182
 */
183
int
184
NCZ_read_provenance(NC_FILE_INFO_T* file, const char* name, const char* value)
185
0
{
186
0
    int stat = NC_NOERR;
187
0
    NC4_Provenance* provenance = NULL;
188
0
    char* propstring = NULL;
189
190
0
    LOG((5, "%s: ncid 0x%x", __func__, file->root_grp->hdr.id));
191
192
0
    assert(file->provenance.version == 0); /* not yet defined */
193
194
0
    provenance = &file->provenance;
195
0
    memset(provenance,0,sizeof(NC4_Provenance)); /* make sure */
196
197
    /* Set the superblock number */
198
0
    int superblock = -1;
199
0
    if((stat = NCZ_get_superblock(file,&superblock))) goto done;
200
0
    provenance->superblockversion = superblock;
201
202
    /* Process the _NCProperties value */
203
0
    if(strcmp(name,NCPROPS)==0) {
204
0
        if((stat = NCZ_read_ncproperties(file,value,&propstring))) goto done;
205
0
        provenance->ncproperties = propstring;
206
0
        propstring = NULL;
207
0
    }
208
209
0
done:
210
0
    nullfree(propstring);
211
0
    if(stat) {
212
0
        LOG((0,"Could not create _NCProperties attribute"));
213
0
    }
214
0
    return NC_NOERR;
215
0
}
216
217
/**
218
 * @internal
219
 *
220
 * Add the provenance information to a newly created file.
221
 *
222
 * @param file Pointer to file object.
223
 *
224
 * @return ::NC_NOERR No error.
225
 * [Note: other errors are reported via LOG()]
226
 * @author Dennis Heimbigner
227
 */
228
int
229
NCZ_write_provenance(NC_FILE_INFO_T* file)
230
0
{
231
0
    int stat = NC_NOERR;
232
0
    if((stat = NCZ_write_ncproperties(file)))
233
0
  goto done;
234
0
done:
235
0
    return stat;
236
0
}
237
238
/* ZARR Specific attribute read/write of _NCProperties */
239
static int
240
NCZ_read_ncproperties(NC_FILE_INFO_T* h5, const char* value, char** propstring)
241
0
{
242
0
    int stat = NC_NOERR;
243
0
    char* text = NULL;
244
0
    size_t len;
245
246
0
    LOG((5, "%s", __func__));
247
248
    /* NCPROPS Attribute exists, make sure it is legitimate */
249
0
    if(value == NULL || strlen(value) == 0)
250
0
  {stat = NC_EINVAL; goto done;}
251
0
    len = strlen(value);
252
0
    text = (char*)malloc(1+len);
253
0
    if(text == NULL) {stat = NC_ENOMEM; goto done;}
254
0
    memcpy(text,value,len);
255
    /* Make sure its null terminated */
256
0
    text[len] = '\0';
257
0
    if(propstring) {*propstring = text; text = NULL;}
258
259
0
done:
260
0
    if(text != NULL) free(text);
261
    /* For certain errors, actually fail, else log that attribute was invalid and ignore */
262
0
    if(stat != NC_NOERR) {
263
0
        if(stat != NC_ENOMEM && stat != NC_EHDFERR) {
264
0
            LOG((0,"Invalid _NCProperties attribute: ignored"));
265
0
            stat = NC_NOERR;
266
0
        }
267
0
    }
268
0
    return stat;
269
0
}
270
271
static int
272
NCZ_write_ncproperties(NC_FILE_INFO_T* h5)
273
0
{
274
#ifdef SUPPRESSNCPROPERTY
275
    return NC_NOERR;
276
#else /*!SUPPRESSNCPROPERTY*/
277
0
    int i,stat = NC_NOERR;
278
0
    NC4_Provenance* prov = &h5->provenance;
279
0
    NC_ATT_INFO_T* ncprops = NULL;
280
0
    NCindex* attlist = NULL;
281
282
0
    LOG((5, "%s", __func__));
283
284
    /* If the file is read-only, return an error. */
285
0
    if (h5->no_write)
286
0
  {stat = NC_EPERM; goto done;}
287
288
    /* See if it already exists */
289
    /* Load the root group attributes */
290
0
    if((stat = ncz_getattlist(h5->root_grp,NC_GLOBAL,NULL,&attlist)))
291
0
  goto done;
292
293
    /* Look for _NCProperties */
294
0
    for(i=0; i<ncindexsize(attlist); i++) {
295
0
  NC_ATT_INFO_T* att = (NC_ATT_INFO_T*)ncindexith(attlist,i);
296
0
  if(strcmp(NCPROPS,att->hdr.name)==0) {
297
0
      ncprops = att;
298
0
      break;  
299
0
  }
300
0
    }
301
0
    if(ncprops != NULL) goto done; /* Already exists, no overwrite */
302
303
    /* Build the property if we have legit value */
304
0
    if(prov->ncproperties != NULL) {
305
0
        if((stat=nc4_att_list_add(attlist,NCPROPS,&ncprops)))
306
0
      goto done;
307
0
  ncprops->nc_typeid = NC_CHAR;
308
0
  ncprops->len = strlen(prov->ncproperties);
309
0
  if((ncprops->data = strdup(prov->ncproperties)) == NULL)
310
0
      {stat = NC_ENOMEM; goto done;}
311
0
  ncprops->dirty = 1;
312
0
  if((ncprops->format_att_info = calloc(1,sizeof(NCZ_ATT_INFO_T)))==NULL)
313
0
      {stat = NC_ENOMEM; goto done;}
314
0
  ((NCZ_ATT_INFO_T*)ncprops->format_att_info)->common.file = h5;
315
0
    }
316
317
0
done:
318
    /* For certain errors, actually fail, else log that attribute was invalid and ignore */
319
0
    switch (stat) {
320
0
    case NC_ENOMEM:
321
0
    case NC_EHDFERR:
322
0
    case NC_EPERM:
323
0
    case NC_EFILEMETA:
324
0
    case NC_NOERR:
325
0
        break;
326
0
    default:
327
0
        LOG((0,"Invalid _NCProperties attribute"));
328
0
        stat = NC_NOERR;
329
0
        break;
330
0
    }
331
0
    return stat;
332
0
#endif /*!SUPPRESSNCPROPERTY*/
333
0
}
334
335
/**************************************************/
336
/* Utilities */
337
338
/* Debugging */
339
340
void
341
nczprintprovenance(NC4_Provenance* info)
342
0
{
343
0
    fprintf(stderr,"[%p] version=%d superblockversion=%d ncproperties=|%s|\n",
344
0
  info,
345
0
  info->version,
346
0
  info->superblockversion,
347
0
  (info->ncproperties==NULL?"":info->ncproperties));
348
0
}
349
350
/**
351
 * @internal
352
 *
353
 * Clear the NCPROVENANCE object; do not free it
354
 * @param prov Pointer to provenance object
355
 *
356
 * @return ::NC_NOERR No error.
357
 * @author Dennis Heimbigner
358
 */
359
int
360
NCZ_clear_provenance(NC4_Provenance* prov)
361
1
{
362
1
    LOG((5, "%s", __func__));
363
364
1
    if(prov == NULL) return NC_NOERR;
365
1
    nullfree(prov->ncproperties);
366
1
    memset(prov,0,sizeof(NC4_Provenance));
367
1
    return NC_NOERR;
368
1
}
369
370
#if 0
371
/* Unused functions */
372
373
/**
374
 * @internal Parse file properties.
375
 *
376
 * @param text0 Text properties.
377
 * @param pairs list of parsed (key,value) pairs
378
 *
379
 * @return ::NC_NOERR No error.
380
 * @author Dennis Heimbigner
381
 */
382
static int
383
properties_parse(const char* text0, NClist* pairs)
384
{
385
    int ret = NC_NOERR;
386
    char* p;
387
    char* q;
388
    char* text = NULL;
389
390
    if(text0 == NULL || strlen(text0) == 0)
391
        goto done;
392
393
    text = strdup(text0);
394
    if(text == NULL) return NC_ENOMEM;
395
396
    /* For back compatibility with version 1, translate '|' -> ',' */
397
    for(p=text;*p;p++) {
398
        if(*p == NCPROPSSEP1)
399
            *p = NCPROPSSEP2;
400
    }
401
402
    /* Walk and fill in ncinfo */
403
    p = text;
404
    while(*p) {
405
        char* name = p;
406
        char* value = NULL;
407
        char* next = NULL;
408
409
        /* Delimit whole (key,value) pair */
410
        q = locate(p,NCPROPSSEP2);
411
        if(*q != '\0') /* Never go beyond the final nul term */
412
            *q++ = '\0';
413
        next = q;
414
        /* split key and value */
415
        q = locate(p,'=');
416
        name = p;
417
        *q++ = '\0';
418
        value = q;
419
        /* Set up p for next iteration */
420
        p = next;
421
        nclistpush(pairs,strdup(name));
422
        nclistpush(pairs,strdup(value));
423
    }
424
done:
425
    if(text) free(text);
426
    return ret;
427
}
428
429
/* Locate a specific character and return its pointer
430
   or EOS if not found
431
   take \ escapes into account */
432
static char*
433
locate(char* p, char tag)
434
{
435
    char* next;
436
    int c;
437
    assert(p != NULL);
438
    for(next = p;(c = *next);next++) {
439
        if(c == tag)
440
            return next;
441
        else if(c == '\\' && next[1] != '\0')
442
            next++; /* skip escaped char */
443
    }
444
    return next; /* not found */
445
}
446
447
/* Utility to transfer a string to a buffer with escaping */
448
static void
449
escapify(NCbytes* buffer, const char* s)
450
{
451
    const char* p;
452
    for(p=s;*p;p++) {
453
        if(strchr(ESCAPECHARS,*p) != NULL)
454
            ncbytesappend(buffer,'\\');
455
        ncbytesappend(buffer,*p);
456
    }
457
}
458
459
/**
460
 * @internal
461
 *
462
 * Clear and Free the NC4_Provenance object
463
 * @param prov Pointer to provenance object
464
 *
465
 * @return ::NC_NOERR No error.
466
 * @author Dennis Heimbigner
467
 */
468
static int
469
NCZ_free_provenance(NC4_Provenance* prov)
470
{
471
    LOG((5, "%s", __func__));
472
473
    if(prov == NULL) return NC_NOERR;
474
    NCZ_clear_provenance(prov);
475
    free(prov);
476
    return NC_NOERR;
477
}
478
479
/**
480
 * @internal Build _NCProperties attribute value.
481
 *
482
 * Convert a NCPROPINFO instance to a single string.
483
 * Will always convert to current format
484
 *
485
 * @param version
486
 * @param list Properties list
487
 * @param spropp Pointer that gets properties string.
488
 * @return ::NC_NOERR No error.
489
 * @return ::NC_EINVAL failed.
490
 * @author Dennis Heimbigner
491
 */
492
static int
493
build_propstring(int version, NClist* list, char** spropp)
494
{
495
    int stat = NC_NOERR;
496
    int i;
497
    NCbytes* buffer = NULL;
498
    char sversion[64];
499
500
    LOG((5, "%s version=%d", __func__, version));
501
502
    if(spropp != NULL) *spropp = NULL;
503
504
    if(version == 0 || version > NCPROPS_VERSION) /* unknown case */
505
  goto done;
506
     if(list == NULL)
507
        {stat = NC_EINVAL; goto done;}
508
509
    if((buffer = ncbytesnew()) ==  NULL)
510
        {stat = NC_ENOMEM; goto done;}
511
512
    /* start with version */
513
    ncbytescat(buffer,NCPVERSION);
514
    ncbytesappend(buffer,'=');
515
    /* Use current version */
516
    snprintf(sversion,sizeof(sversion),"%d",version);
517
    ncbytescat(buffer,sversion);
518
519
    for(i=0;i<nclistlength(list);i+=2) {
520
        char* value, *name;
521
        name = nclistget(list,i);
522
        if(name == NULL) continue;
523
        value = nclistget(list,i+1);
524
        ncbytesappend(buffer,NCPROPSSEP2); /* terminate last entry */
525
        escapify(buffer,name);
526
        ncbytesappend(buffer,'=');
527
        escapify(buffer,value);
528
    }
529
    /* Force null termination */
530
    ncbytesnull(buffer);
531
    if(spropp) *spropp = ncbytesextract(buffer);
532
533
done:
534
    if(buffer != NULL) ncbytesfree(buffer);
535
    return stat;
536
}
537
538
static int
539
properties_getversion(const char* propstring, int* versionp)
540
{
541
    int stat = NC_NOERR;
542
    int version = 0;
543
    /* propstring should begin with "version=dddd" */
544
    if(propstring == NULL || strlen(propstring) < strlen("version=") + strlen("1"))
545
        {stat = NC_EINVAL; goto done;} /* illegal version */
546
    if(memcmp(propstring,"version=",strlen("version=")) != 0)
547
        {stat = NC_EINVAL; goto done;} /* illegal version */
548
    propstring += strlen("version=");
549
    /* get version */
550
    version = atoi(propstring);
551
    if(version < 0)
552
        {stat = NC_EINVAL; goto done;} /* illegal version */
553
    if(versionp) *versionp = version;
554
done:
555
    return stat;
556
}
557
558
/**
559
 * @internal
560
 *
561
 * Construct the parsed provenance information
562
 *
563
 * @param prov Pointer to provenance object
564
 *
565
 * @return ::NC_NOERR No error.
566
 * @return ::NC_ENOMEM
567
 * @return ::NC_EINVAL
568
 * @author Dennis Heimbigner
569
 */
570
static int
571
parse_provenance(NC4_Provenance* prov)
572
{
573
    int stat = NC_NOERR;
574
    char *name = NULL;
575
    char *value = NULL;
576
    int version = 0;
577
    NClist* list = NULL;
578
579
    LOG((5, "%s: prov 0x%x", __func__, prov));
580
581
    if(prov->ncproperty == NULL || strlen(prov->ncproperty) < strlen("version="))
582
        {stat = NC_EINVAL; goto done;}
583
    if((list = nclistnew()) == NULL)
584
        {stat = NC_ENOMEM; goto done;}
585
586
    /* Do we understand the version? */
587
    if(prov->version > 0 && prov->version <= NCPROPS_VERSION) {/* recognized version */
588
        if((stat=properties_parse(prov->ncproperty,list)))
589
      goto done;
590
        /* Remove version pair from properties list*/
591
        if(nclistlength(list) < 2)
592
            {stat = NC_EINVAL; goto done;} /* bad _NCProperties attribute */
593
        /* Throw away the purported version=... */
594
        nclistremove(list,0); /* version key */
595
        nclistremove(list,0); /* version value */
596
597
        /* Now, rebuild to the latest version */
598
  switch (version) {
599
  default: break; /* do nothing */
600
  case 1: {
601
            int i;
602
            for(i=0;i<nclistlength(list);i+=2) {
603
                char* newname = NULL;
604
                name = nclistget(list,i);
605
                if(name == NULL) continue; /* ignore */
606
                if(strcmp(name,NCPNCLIB1) == 0)
607
                    newname = NCPNCLIB2; /* change name */
608
                else if(strcmp(name,NCPNCZLIB1) == 0)
609
                    newname = NCPNCZLIB2;
610
                else continue; /* ignore */
611
                /* Do any rename */
612
                nclistset(list,i,strdup(newname));
613
                if(name) {free(name); name = NULL;}
614
            }
615
        } break;
616
  } /*switch*/
617
    }
618
    prov->properties = list;
619
    list = NULL;
620
621
done:
622
    nclistfreeall(list);
623
    if(name != NULL) free(name);
624
    if(value != NULL) free(value);
625
    return stat;
626
}
627
628
/* Utility to copy contents of the dfalt into an NCPROPINFO object */
629
static int
630
propinfo_default(NCZ_Properties* dst, const NCZ_Properties* dfalt)
631
{
632
    int i;
633
    if(dst->properties == NULL) {
634
        dst->properties = nclistnew();
635
        if(dst->properties == NULL) return NC_ENOMEM;
636
    }
637
    dst->version = dfalt->version;
638
    for(i=0;i<nclistlength(dfalt->properties);i++) {
639
        char* s = nclistget(dfalt->properties,i);
640
        s = strdup(s);
641
        if(s == NULL) return NC_ENOMEM;
642
        nclistpush(dst->properties,s);
643
    }
644
    return NC_NOERR;
645
}
646
647
#endif /*0*/