Coverage Report

Created: 2026-10-31 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/netcdf-c/libdispatch/dutil.c
Line
Count
Source
1
/*********************************************************************
2
 *   Copyright 2018, UCAR/Unidata
3
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
4
 *********************************************************************/
5
6
#include "config.h"
7
#include <stddef.h>
8
#include <stdlib.h>
9
#include <string.h>
10
#include <stdio.h>
11
#include <assert.h>
12
#ifdef HAVE_UNISTD_H
13
#include <unistd.h>
14
#endif
15
16
#ifdef HAVE_SYS_STAT_H
17
#include <sys/stat.h>
18
#endif
19
#ifdef HAVE_FCNTL_H
20
#include <fcntl.h>
21
#endif
22
#ifdef _MSC_VER
23
#include <io.h>
24
#endif
25
#include "netcdf.h"
26
#include "ncuri.h"
27
#include "ncbytes.h"
28
#include "nclist.h"
29
#include "nclog.h"
30
#include "ncpathmgr.h"
31
#include "ncutil.h"
32
33
#define NC_MAX_PATH 4096
34
35
/**************************************************/
36
/** \internal
37
 * Provide a hidden interface to allow utilities
38
 * to check if a given path name is really a url.
39
 * If no, return null, else return basename of the url path
40
 * minus any extension in basenamep.
41
 * If is a url and the protocol is file:, set isfilep.
42
 * @return 0 if not URL, 1 if URL
43
 */
44
45
int
46
NC__testurl(const char* path, char** basenamep, int* isfilep)
47
0
{
48
0
    int stat = NC_NOERR;
49
0
    NCURI* uri = NULL;
50
0
    int isurl = 1;
51
0
    int isfile = 0;
52
0
    const char* uripath = NULL;
53
0
    char* slash = NULL;
54
0
    char* dot = NULL;
55
56
    /* Parse url; if fails or returns null URL, then assume path is not a URL */
57
0
    stat = ncuriparse(path,&uri);
58
0
    if(stat || uri == NULL) {isurl = 0; isfile = 0; goto done;} /* not a url */
59
0
    isurl = 1;
60
0
    if(strcmp(uri->protocol,"file")==0) isfile = 1;
61
    /* Extract the basename of the URL */
62
0
    if(uri->path == NULL)
63
0
        uripath = "/";
64
0
    else
65
0
        uripath = uri->path;
66
0
    slash = (char*)strrchr(uripath, '/');
67
0
    if(slash == NULL) slash = (char*)uripath; else slash++;
68
0
    slash = nulldup(slash);
69
0
    assert(slash != NULL);
70
0
    dot = strrchr(slash, '.');
71
0
    if(dot != NULL &&  dot != slash) *dot = '\0';
72
0
    if(basenamep)
73
0
  {*basenamep=slash; slash = NULL;}
74
0
done:
75
0
    if(isfilep) *isfilep = isfile;
76
0
    nullfree(slash);
77
0
    ncurifree(uri);
78
0
    return isurl;
79
0
}
80
81
/** \internal Return 1 if this machine is little endian */
82
int
83
NC_isLittleEndian(void)
84
0
{
85
0
    union {
86
0
        unsigned char bytes[SIZEOF_INT];
87
0
  int i;
88
0
    } u;
89
0
    u.i = 1;
90
0
    return (u.bytes[0] == 1 ? 1 : 0);
91
0
}
92
93
/** \internal */
94
char*
95
NC_backslashEscape(const char* s)
96
0
{
97
0
    const char* p;
98
0
    char* q;
99
0
    size_t len;
100
0
    char* escaped = NULL;
101
102
0
    len = strlen(s);
103
0
    escaped = (char*)malloc(1+(2*len)); /* max is everychar is escaped */
104
0
    if(escaped == NULL) return NULL;
105
0
    for(p=s,q=escaped;*p;p++) {
106
0
        char c = *p;
107
0
        switch (c) {
108
0
  case '\\':
109
0
  case '/':
110
0
  case '.':
111
0
  case '@':
112
0
      *q++ = '\\'; *q++ = '\\';
113
0
      break;
114
0
  default: *q++ = c; break;
115
0
        }
116
0
    }
117
0
    *q = '\0';
118
0
    return escaped;
119
0
}
120
121
/** \internal */
122
char*
123
NC_backslashUnescape(const char* esc)
124
0
{
125
0
    size_t len;
126
0
    char* s;
127
0
    const char* p;
128
0
    char* q;
129
130
0
    if(esc == NULL) return NULL;
131
0
    len = strlen(esc);
132
0
    s = (char*)malloc(len+1);
133
0
    if(s == NULL) return NULL;
134
0
    for(p=esc,q=s;*p;) {
135
0
  switch (*p) {
136
0
  case '\\':
137
0
       p++;
138
       /* fall thru */
139
0
  default: *q++ = *p++; break;
140
0
  }
141
0
    }
142
0
    *q = '\0';
143
0
    return s;
144
0
}
145
146
/** \internal */
147
char*
148
NC_entityescape(const char* s)
149
0
{
150
0
    const char* p;
151
0
    char* q;
152
0
    size_t len;
153
0
    char* escaped = NULL;
154
0
    const char* entity;
155
156
0
    len = strlen(s);
157
0
    escaped = (char*)malloc(1+(6*len)); /* 6 = |&apos;| */
158
0
    if(escaped == NULL) return NULL;
159
0
    for(p=s,q=escaped;*p;p++) {
160
0
  char c = *p;
161
0
  switch (c) {
162
0
  case '&':  entity = "&amp;"; break;
163
0
  case '<':  entity = "&lt;"; break;
164
0
  case '>':  entity = "&gt;"; break;
165
0
  case '"':  entity = "&quot;"; break;
166
0
  case '\'': entity = "&apos;"; break;
167
0
  default  : entity = NULL; break;
168
0
  }
169
0
  if(entity == NULL)
170
0
      *q++ = c;
171
0
  else {
172
0
      len = strlen(entity);
173
0
      memcpy(q,entity,len);
174
0
      q+=len;
175
0
  }
176
0
    }
177
0
    *q = '\0';
178
0
    return escaped;
179
0
}
180
181
/** \internal
182
Depending on the platform, the shell will sometimes
183
pass an escaped octotherpe character without removing
184
the backslash. So this function is appropriate to be called
185
on possible url paths to unescape such cases. See e.g. ncgen.
186
*/
187
char*
188
NC_shellUnescape(const char* esc)
189
0
{
190
0
    size_t len;
191
0
    char* s;
192
0
    const char* p;
193
0
    char* q;
194
195
0
    if(esc == NULL) return NULL;
196
0
    len = strlen(esc);
197
0
    s = (char*)malloc(len+1);
198
0
    if(s == NULL) return NULL;
199
0
    for(p=esc,q=s;*p;) {
200
0
  switch (*p) {
201
0
  case '\\':
202
0
       if(p[1] == '#')
203
0
           p++;
204
       /* fall thru */
205
0
  default: *q++ = *p++; break;
206
0
  }
207
0
    }
208
0
    *q = '\0';
209
0
    return s;
210
0
}
211
212
/** \internal
213
Wrap mktmp and return the generated path,
214
or null if failed.
215
Base is the base file path. XXXXX is appended
216
to allow mktmp add its unique id.
217
Return any error
218
*/
219
220
int
221
NC_mktmp(const char* base, char** tmpp)
222
0
{
223
0
    int ret = NC_NOERR;
224
0
    int fd = -1;
225
0
    char* tmp = NULL;
226
0
    size_t len;
227
#ifndef HAVE_MKSTEMP
228
    int tries;
229
#define MAXTRIES 4
230
#else
231
0
    mode_t mask;
232
0
#endif
233
234
0
    len = strlen(base)+6+1;
235
0
    if((tmp = (char*)calloc(1,len))==NULL)
236
0
        goto done;
237
0
#ifdef HAVE_MKSTEMP
238
0
    strlcat(tmp,base,len);
239
0
    strlcat(tmp, "XXXXXX", len);
240
0
    mask=umask(0077);
241
0
    fd = NCmkstemp(tmp);
242
0
    (void)umask(mask);
243
#else /* !HAVE_MKSTEMP */
244
    /* Need to simulate by using some kind of pseudo-random number */
245
    for(tries=0;tries<MAXTRIES;tries++) {
246
  int rno = rand();
247
  char spid[7];
248
  if(rno < 0) rno = -rno;
249
  tmp[0] = '\0';
250
        strlcat(tmp,base,len);
251
        snprintf(spid,sizeof(spid),"%06d",rno);
252
        strlcat(tmp,spid,len);
253
        fd=NCopen3(tmp,O_RDWR|O_CREAT, _S_IREAD|_S_IWRITE);
254
  if(fd >= 0) break; /* success */
255
  fd = -1; /* try again */
256
    }
257
#endif /* !HAVE_MKSTEMP */
258
0
    if(fd < 0) {
259
0
        nclog(NCLOGERR, "Could not create temp file: %s",tmp);
260
0
  ret = errno; errno = 0;
261
0
        nullfree(tmp);
262
0
  tmp = NULL;
263
0
        goto done;
264
0
    }
265
0
done:
266
0
    if(fd >= 0) close(fd);
267
0
    if(tmpp) {*tmpp = tmp;}
268
0
    return ret;
269
0
}
270
271
/** \internal */
272
int
273
NC_readfile(const char* filename, NCbytes* content)
274
2
{
275
2
    int stat;
276
2
    stat = NC_readfilen(filename, content, -1);
277
2
    return stat;
278
2
}
279
280
int
281
NC_readfilen(const char* filename, NCbytes* content, long long amount)
282
2
{
283
2
    int ret = NC_NOERR;
284
2
    FILE* stream = NULL;
285
286
2
    stream = NCfopen(filename,"r");
287
2
    if(stream == NULL) {ret=errno; goto done;}
288
0
    ret = NC_readfileF(stream,content,amount);
289
0
    if (stream) fclose(stream);
290
2
done:
291
2
    return ret;
292
0
}
293
294
int
295
NC_readfileF(FILE* stream, NCbytes* content, long long amount)
296
0
{
297
0
#define READ_BLOCK_SIZE 4194304
298
0
    int ret = NC_NOERR;
299
0
    long long red = 0;
300
    /* Do not allocate or read more than the caller asked for, so that
301
       e.g. checking a magic number does not read the whole file. */
302
0
    size_t blocksize = (amount >= 0 && amount < READ_BLOCK_SIZE) ? (size_t)amount : READ_BLOCK_SIZE;
303
0
    char *part = (char*) malloc(blocksize > 0 ? blocksize : 1);
304
305
0
    if(part == NULL) {ret = NC_ENOMEM; goto done;}
306
307
0
    while(amount < 0 || red < amount) {
308
0
  size_t want = (amount >= 0 && (amount - red) < (long long)blocksize) ? (size_t)(amount - red) : blocksize;
309
0
  size_t count = fread(part, 1, want, stream);
310
0
  if(ferror(stream)) {ret = NC_EIO; goto done;}
311
0
  if(count > 0) ncbytesappendn(content,part,(unsigned long)count);
312
0
  red += (long long)count;
313
0
    if (feof(stream)) break;
314
0
    }
315
    /* Keep only amount */
316
0
    if(amount >= 0) {
317
0
  if(red > amount) ncbytessetlength(content, (unsigned long)amount); /* read too much */
318
0
  if(red < amount) ret = NC_ETRUNC; /* |file| < amount */
319
0
    }
320
0
    ncbytesnull(content);
321
0
done:
322
0
    free(part);
323
0
    return ret;
324
0
}
325
326
/** \internal */
327
int
328
NC_writefile(const char* filename, size_t size, void* content)
329
0
{
330
0
    int ret = NC_NOERR;
331
0
    FILE* stream = NULL;
332
0
    void* p;
333
0
    size_t remain;
334
335
0
    if(content == NULL) {content = ""; size = 0;}
336
337
0
    stream = NCfopen(filename,"w");
338
0
    if(stream == NULL) {ret=errno; goto done;}
339
0
    p = content;
340
0
    remain = size;
341
0
    while(remain > 0) {
342
0
  size_t written = fwrite(p, 1, remain, stream);
343
0
  if(ferror(stream)) {ret = NC_EIO; goto done;}
344
0
  remain -= written;
345
0
    if (feof(stream)) break;
346
0
    }
347
0
done:
348
0
    if(stream) fclose(stream);
349
0
    return ret;
350
0
}
351
352
/** \internal
353
Parse a path as a url and extract the modelist.
354
If the path is not a URL, then return a NULL list.
355
If a URL, but modelist is empty or does not exist,
356
then return empty list.
357
*/
358
int
359
NC_getmodelist(const char* modestr, NClist** modelistp)
360
0
{
361
0
    int stat=NC_NOERR;
362
0
    NClist* modelist = NULL;
363
364
0
    modelist = nclistnew();
365
0
    if(modestr == NULL || strlen(modestr) == 0) goto done;
366
367
    /* Parse the mode string at the commas or EOL */
368
0
    if((stat = NC_split_delim(modestr,',',modelist))) goto done;
369
370
0
done:
371
0
    if(stat == NC_NOERR) {
372
0
  if(modelistp) {*modelistp = modelist; modelist = NULL;}
373
0
    } else
374
0
        nclistfree(modelist);
375
0
    return stat;
376
0
}
377
378
/** \internal
379
Check "mode=" list for a path and return 1 if present, 0 otherwise.
380
*/
381
int
382
NC_testpathmode(const char* path, const char* tag)
383
0
{
384
0
    int found = 0;
385
0
    NCURI* uri = NULL;
386
0
    ncuriparse(path,&uri);
387
0
    if(uri != NULL) {
388
0
        found = NC_testmode(uri,tag);
389
0
        ncurifree(uri);
390
0
    }
391
0
    return found;
392
0
}
393
394
/** \internal
395
Check "mode=" list for a url and return 1 if present, 0 otherwise.
396
*/
397
int
398
NC_testmode(NCURI* uri, const char* tag)
399
0
{
400
0
    int stat = NC_NOERR;
401
0
    int found = 0;
402
0
    size_t i;
403
0
    const char* modestr = NULL;
404
0
    NClist* modelist = NULL;
405
406
0
    modestr = ncurifragmentlookup(uri,"mode");
407
0
    if(modestr == NULL) goto done;
408
    /* Parse mode str */
409
0
    if((stat = NC_getmodelist(modestr,&modelist))) goto done;
410
    /* Search for tag */
411
0
    for(i=0;i<nclistlength(modelist);i++) {
412
0
        const char* mode = (const char*)nclistget(modelist,i);
413
0
  if(strcasecmp(mode,tag)==0) {found = 1; break;}
414
0
    }
415
0
done:
416
0
    nclistfreeall(modelist);
417
0
    return found;
418
0
}
419
420
/** \internal
421
Add tag to fragment mode list unless already present.
422
*/
423
int
424
NC_addmodetag(NCURI* uri, const char* tag)
425
0
{
426
0
    int stat = NC_NOERR;
427
0
    int found = 0;
428
0
    const char* modestr = NULL;
429
0
    char* modevalue = NULL;
430
0
    NClist* modelist = NULL;
431
432
0
    modestr = ncurifragmentlookup(uri,"mode");
433
0
    if(modestr != NULL) {
434
        /* Parse mode str */
435
0
        if((stat = NC_getmodelist(modestr,&modelist))) goto done;
436
0
    } else
437
0
        modelist = nclistnew();
438
    /* Search for tag */
439
0
    for(size_t i=0;i<nclistlength(modelist);i++) {
440
0
        const char* mode = (const char*)nclistget(modelist,i);
441
0
  if(strcasecmp(mode,tag)==0) {found = 1; break;}
442
0
    }
443
    /* If not found, then add to modelist */
444
0
    if(!found) nclistpush(modelist,strdup(tag));
445
    /* Convert modelist back to string */
446
0
    if((stat=NC_joinwith(modelist,",",NULL,NULL,&modevalue))) goto done;
447
    /* modify the url */
448
0
    if((stat=ncurisetfragmentkey(uri,"mode",modevalue))) goto done;
449
450
0
done:
451
0
    nclistfreeall(modelist);
452
0
    nullfree(modevalue);
453
0
    return stat;
454
0
}
455
456
#if ! defined __INTEL_COMPILER
457
#if defined __APPLE__ 
458
/** \internal */
459
460
#if ! defined HAVE_DECL_ISINF
461
462
int isinf(double x)
463
{
464
    union { unsigned long long u; double f; } ieee754;
465
    ieee754.f = x;
466
    return ( (unsigned)(ieee754.u >> 32) & 0x7fffffff ) == 0x7ff00000 &&
467
           ( (unsigned)ieee754.u == 0 );
468
}
469
470
#endif /* HAVE_DECL_ISINF */
471
472
#if ! defined HAVE_DECL_ISNAN
473
/** \internal */
474
int isnan(double x)
475
{
476
    union { unsigned long long u; double f; } ieee754;
477
    ieee754.f = x;
478
    return ( (unsigned)(ieee754.u >> 32) & 0x7fffffff ) +
479
           ( (unsigned)ieee754.u != 0 ) > 0x7ff00000;
480
}
481
482
#endif /* HAVE_DECL_ISNAN */
483
484
#endif /*APPLE*/
485
#endif /*!_INTEL_COMPILER*/
486
487
/** \internal */
488
int
489
NC_split_delim(const char* arg, char delim, NClist* segments)
490
0
{
491
0
    int stat = NC_NOERR;
492
0
    const char* p = NULL;
493
0
    const char* q = NULL;
494
0
    ptrdiff_t len = 0;
495
0
    char* seg = NULL;
496
497
0
    if(arg == NULL || strlen(arg)==0 || segments == NULL)
498
0
        goto done;
499
0
    p = arg;
500
0
    if(p[0] == delim) p++;
501
0
    for(;*p;) {
502
0
  q = strchr(p,delim);
503
0
  if(q==NULL)
504
0
      q = p + strlen(p); /* point to trailing nul */
505
0
        len = (q - p);
506
0
  if(len == 0)
507
0
      {stat = NC_EURL; goto done;}
508
0
  if((seg = malloc((size_t)len+1)) == NULL)
509
0
      {stat = NC_ENOMEM; goto done;}
510
0
  memcpy(seg,p,(size_t)len);
511
0
  seg[len] = '\0';
512
0
  nclistpush(segments,seg);
513
0
  seg = NULL; /* avoid mem errors */
514
0
  if(*q) p = q+1; else p = q;
515
0
    }
516
517
0
done:
518
0
    nullfree(seg);
519
0
    return stat;
520
0
}
521
522
/** \internal concat the the segments with each segment preceded by '/' */
523
int
524
NC_join(NClist* segments, char** pathp)
525
0
{
526
0
    return NC_joinwith(segments,"/","/",NULL,pathp);
527
0
}
528
529
/** \internal
530
Concat the the segments with separator.
531
@param segments to join
532
@param sep to use between segments
533
@param prefix put at front of joined string: NULL => no prefix
534
@param suffix put at end of joined string: NULL => no suffix
535
@param pathp return the join in this
536
*/
537
int
538
NC_joinwith(NClist* segments, const char* sep, const char* prefix, const char* suffix, char** pathp)
539
0
{
540
0
    int stat = NC_NOERR;
541
0
    size_t i;
542
0
    NCbytes* buf = NULL;
543
0
    size_t seplen = nulllen(sep);
544
545
0
    if(segments == NULL)
546
0
  {stat = NC_EINVAL; goto done;}
547
0
    if((buf = ncbytesnew())==NULL)
548
0
  {stat = NC_ENOMEM; goto done;}
549
0
    if(prefix) ncbytescat(buf,prefix);
550
0
    for(i=0;i<nclistlength(segments);i++) {
551
0
  const char* seg = nclistget(segments,i);
552
0
  if(i>0 && strncmp(seg,sep,seplen)!=0)
553
0
      ncbytescat(buf,sep);
554
0
  ncbytescat(buf,seg);
555
0
    }
556
0
    if(suffix) ncbytescat(buf,suffix);
557
0
    if(pathp) *pathp = ncbytesextract(buf);
558
0
done:
559
0
    ncbytesfree(buf);
560
0
    return stat;
561
0
}
562
563
#if 0
564
/* concat the the segments with each segment preceded by '/' */
565
int
566
NC_join(NClist* segments, char** pathp)
567
{
568
    int stat = NC_NOERR;
569
    size_t i;
570
    NCbytes* buf = NULL;
571
572
    if(segments == NULL)
573
  {stat = NC_EINVAL; goto done;}
574
    if((buf = ncbytesnew())==NULL)
575
  {stat = NC_ENOMEM; goto done;}
576
    if(nclistlength(segments) == 0)
577
        ncbytescat(buf,"/");
578
    else for(i=0;i<nclistlength(segments);i++) {
579
  const char* seg = nclistget(segments,i);
580
  if(seg[0] != '/')
581
      ncbytescat(buf,"/");
582
  ncbytescat(buf,seg);    
583
    }
584
585
done:
586
    if(!stat) {
587
  if(pathp) *pathp = ncbytesextract(buf);
588
    }
589
    ncbytesfree(buf);
590
    return THROW(stat);
591
}
592
#endif
593
594
#if 0
595
static int
596
extendenvv(char*** envvp, int amount, int* oldlenp)
597
{
598
    char** envv = *envvp;
599
    char** p;
600
    int len;
601
    for(len=0,p=envv;*p;p++) len++;
602
    *oldlenp = len;
603
    if((envv = (char**)malloc((amount+len+1)*sizeof(char*)))==NULL) return NC_ENOMEM;
604
    memcpy(envv,*envvp,sizeof(char*)*len);
605
    envv[len] = NULL;
606
    nullfree(*envvp);
607
    *envvp = envv; envv = NULL;
608
    return NC_NOERR;
609
}
610
#endif
611
612
static int
613
nc_compare(const void* arg1, const void* arg2)
614
0
{
615
0
    char* n1 = *((char**)arg1);
616
0
    char* n2 = *((char**)arg2);
617
0
    return strcmp(n1,n2);
618
0
}
619
620
/* quick sort a list of strings */
621
void
622
NC_sortenvv(size_t n, char** envv)
623
0
{
624
0
    if(n <= 1) return;
625
0
    qsort(envv, n, sizeof(char*), nc_compare);
626
#if 0
627
{int i;
628
for(i=0;i<n;i++)
629
fprintf(stderr,">>> sorted: [%d] %s\n",i,(const char*)envv[i]);
630
}
631
#endif
632
0
}
633
634
void
635
NC_freeenvv(size_t n, char** envv)
636
0
{
637
0
    size_t i;
638
0
    char** p;
639
0
    if(envv == NULL) return;
640
0
    if(n < 0)
641
0
       {for(n=0, p = envv; *p; n++) {}; /* count number of strings */}
642
0
    for(i=0;i<n;i++) {
643
0
        if(envv[i]) {
644
0
      free(envv[i]);
645
0
  }
646
0
    }
647
0
    free(envv);    
648
0
}
649