Coverage Report

Created: 2025-10-28 07:06

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; /* sucess */
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
0
    char *part = (char*) malloc(READ_BLOCK_SIZE);
301
302
0
    while(amount < 0 || red < amount) {
303
0
  size_t count = fread(part, 1, READ_BLOCK_SIZE, stream);
304
0
  if(ferror(stream)) {ret = NC_EIO; goto done;}
305
0
  if(count > 0) ncbytesappendn(content,part,(unsigned long)count);
306
0
  red += (long long)count;
307
0
    if (feof(stream)) break;
308
0
    }
309
    /* Keep only amount */
310
0
    if(amount >= 0) {
311
0
  if(red > amount) ncbytessetlength(content, (unsigned long)amount); /* read too much */
312
0
  if(red < amount) ret = NC_ETRUNC; /* |file| < amount */
313
0
    }
314
0
    ncbytesnull(content);
315
0
done:
316
0
    free(part);
317
0
    return ret;
318
0
}
319
320
/** \internal */
321
int
322
NC_writefile(const char* filename, size_t size, void* content)
323
0
{
324
0
    int ret = NC_NOERR;
325
0
    FILE* stream = NULL;
326
0
    void* p;
327
0
    size_t remain;
328
329
0
    if(content == NULL) {content = ""; size = 0;}
330
331
0
    stream = NCfopen(filename,"w");
332
0
    if(stream == NULL) {ret=errno; goto done;}
333
0
    p = content;
334
0
    remain = size;
335
0
    while(remain > 0) {
336
0
  size_t written = fwrite(p, 1, remain, stream);
337
0
  if(ferror(stream)) {ret = NC_EIO; goto done;}
338
0
  remain -= written;
339
0
    if (feof(stream)) break;
340
0
    }
341
0
done:
342
0
    if(stream) fclose(stream);
343
0
    return ret;
344
0
}
345
346
/** \internal
347
Parse a path as a url and extract the modelist.
348
If the path is not a URL, then return a NULL list.
349
If a URL, but modelist is empty or does not exist,
350
then return empty list.
351
*/
352
int
353
NC_getmodelist(const char* modestr, NClist** modelistp)
354
0
{
355
0
    int stat=NC_NOERR;
356
0
    NClist* modelist = NULL;
357
358
0
    modelist = nclistnew();
359
0
    if(modestr == NULL || strlen(modestr) == 0) goto done;
360
361
    /* Parse the mode string at the commas or EOL */
362
0
    if((stat = NC_split_delim(modestr,',',modelist))) goto done;
363
364
0
done:
365
0
    if(stat == NC_NOERR) {
366
0
  if(modelistp) {*modelistp = modelist; modelist = NULL;}
367
0
    } else
368
0
        nclistfree(modelist);
369
0
    return stat;
370
0
}
371
372
/** \internal
373
Check "mode=" list for a path and return 1 if present, 0 otherwise.
374
*/
375
int
376
NC_testpathmode(const char* path, const char* tag)
377
0
{
378
0
    int found = 0;
379
0
    NCURI* uri = NULL;
380
0
    ncuriparse(path,&uri);
381
0
    if(uri != NULL) {
382
0
        found = NC_testmode(uri,tag);
383
0
        ncurifree(uri);
384
0
    }
385
0
    return found;
386
0
}
387
388
/** \internal
389
Check "mode=" list for a url and return 1 if present, 0 otherwise.
390
*/
391
int
392
NC_testmode(NCURI* uri, const char* tag)
393
0
{
394
0
    int stat = NC_NOERR;
395
0
    int found = 0;
396
0
    size_t i;
397
0
    const char* modestr = NULL;
398
0
    NClist* modelist = NULL;
399
400
0
    modestr = ncurifragmentlookup(uri,"mode");
401
0
    if(modestr == NULL) goto done;
402
    /* Parse mode str */
403
0
    if((stat = NC_getmodelist(modestr,&modelist))) goto done;
404
    /* Search for tag */
405
0
    for(i=0;i<nclistlength(modelist);i++) {
406
0
        const char* mode = (const char*)nclistget(modelist,i);
407
0
  if(strcasecmp(mode,tag)==0) {found = 1; break;}
408
0
    }
409
0
done:
410
0
    nclistfreeall(modelist);
411
0
    return found;
412
0
}
413
414
/** \internal
415
Add tag to fragment mode list unless already present.
416
*/
417
int
418
NC_addmodetag(NCURI* uri, const char* tag)
419
0
{
420
0
    int stat = NC_NOERR;
421
0
    int found = 0;
422
0
    const char* modestr = NULL;
423
0
    char* modevalue = NULL;
424
0
    NClist* modelist = NULL;
425
426
0
    modestr = ncurifragmentlookup(uri,"mode");
427
0
    if(modestr != NULL) {
428
        /* Parse mode str */
429
0
        if((stat = NC_getmodelist(modestr,&modelist))) goto done;
430
0
    } else
431
0
        modelist = nclistnew();
432
    /* Search for tag */
433
0
    for(size_t i=0;i<nclistlength(modelist);i++) {
434
0
        const char* mode = (const char*)nclistget(modelist,i);
435
0
  if(strcasecmp(mode,tag)==0) {found = 1; break;}
436
0
    }
437
    /* If not found, then add to modelist */
438
0
    if(!found) nclistpush(modelist,strdup(tag));
439
    /* Convert modelist back to string */
440
0
    if((stat=NC_joinwith(modelist,",",NULL,NULL,&modevalue))) goto done;
441
    /* modify the url */
442
0
    if((stat=ncurisetfragmentkey(uri,"mode",modevalue))) goto done;
443
444
0
done:
445
0
    nclistfreeall(modelist);
446
0
    nullfree(modevalue);
447
0
    return stat;
448
0
}
449
450
#if ! defined __INTEL_COMPILER
451
#if defined __APPLE__ 
452
/** \internal */
453
454
#if ! defined HAVE_DECL_ISINF
455
456
int isinf(double x)
457
{
458
    union { unsigned long long u; double f; } ieee754;
459
    ieee754.f = x;
460
    return ( (unsigned)(ieee754.u >> 32) & 0x7fffffff ) == 0x7ff00000 &&
461
           ( (unsigned)ieee754.u == 0 );
462
}
463
464
#endif /* HAVE_DECL_ISINF */
465
466
#if ! defined HAVE_DECL_ISNAN
467
/** \internal */
468
int isnan(double x)
469
{
470
    union { unsigned long long u; double f; } ieee754;
471
    ieee754.f = x;
472
    return ( (unsigned)(ieee754.u >> 32) & 0x7fffffff ) +
473
           ( (unsigned)ieee754.u != 0 ) > 0x7ff00000;
474
}
475
476
#endif /* HAVE_DECL_ISNAN */
477
478
#endif /*APPLE*/
479
#endif /*!_INTEL_COMPILER*/
480
481
/** \internal */
482
int
483
NC_split_delim(const char* arg, char delim, NClist* segments)
484
0
{
485
0
    int stat = NC_NOERR;
486
0
    const char* p = NULL;
487
0
    const char* q = NULL;
488
0
    ptrdiff_t len = 0;
489
0
    char* seg = NULL;
490
491
0
    if(arg == NULL || strlen(arg)==0 || segments == NULL)
492
0
        goto done;
493
0
    p = arg;
494
0
    if(p[0] == delim) p++;
495
0
    for(;*p;) {
496
0
  q = strchr(p,delim);
497
0
  if(q==NULL)
498
0
      q = p + strlen(p); /* point to trailing nul */
499
0
        len = (q - p);
500
0
  if(len == 0)
501
0
      {stat = NC_EURL; goto done;}
502
0
  if((seg = malloc((size_t)len+1)) == NULL)
503
0
      {stat = NC_ENOMEM; goto done;}
504
0
  memcpy(seg,p,(size_t)len);
505
0
  seg[len] = '\0';
506
0
  nclistpush(segments,seg);
507
0
  seg = NULL; /* avoid mem errors */
508
0
  if(*q) p = q+1; else p = q;
509
0
    }
510
511
0
done:
512
0
    nullfree(seg);
513
0
    return stat;
514
0
}
515
516
/** \internal concat the the segments with each segment preceded by '/' */
517
int
518
NC_join(NClist* segments, char** pathp)
519
0
{
520
0
    return NC_joinwith(segments,"/","/",NULL,pathp);
521
0
}
522
523
/** \internal
524
Concat the the segments with separator.
525
@param segments to join
526
@param sep to use between segments
527
@param prefix put at front of joined string: NULL => no prefix
528
@param suffix put at end of joined string: NULL => no suffix
529
@param pathp return the join in this
530
*/
531
int
532
NC_joinwith(NClist* segments, const char* sep, const char* prefix, const char* suffix, char** pathp)
533
0
{
534
0
    int stat = NC_NOERR;
535
0
    size_t i;
536
0
    NCbytes* buf = NULL;
537
0
    size_t seplen = nulllen(sep);
538
539
0
    if(segments == NULL)
540
0
  {stat = NC_EINVAL; goto done;}
541
0
    if((buf = ncbytesnew())==NULL)
542
0
  {stat = NC_ENOMEM; goto done;}
543
0
    if(prefix) ncbytescat(buf,prefix);
544
0
    for(i=0;i<nclistlength(segments);i++) {
545
0
  const char* seg = nclistget(segments,i);
546
0
  if(i>0 && strncmp(seg,sep,seplen)!=0)
547
0
      ncbytescat(buf,sep);
548
0
  ncbytescat(buf,seg);
549
0
    }
550
0
    if(suffix) ncbytescat(buf,suffix);
551
0
    if(pathp) *pathp = ncbytesextract(buf);
552
0
done:
553
0
    ncbytesfree(buf);
554
0
    return stat;
555
0
}
556
557
#if 0
558
/* concat the the segments with each segment preceded by '/' */
559
int
560
NC_join(NClist* segments, char** pathp)
561
{
562
    int stat = NC_NOERR;
563
    size_t i;
564
    NCbytes* buf = NULL;
565
566
    if(segments == NULL)
567
  {stat = NC_EINVAL; goto done;}
568
    if((buf = ncbytesnew())==NULL)
569
  {stat = NC_ENOMEM; goto done;}
570
    if(nclistlength(segments) == 0)
571
        ncbytescat(buf,"/");
572
    else for(i=0;i<nclistlength(segments);i++) {
573
  const char* seg = nclistget(segments,i);
574
  if(seg[0] != '/')
575
      ncbytescat(buf,"/");
576
  ncbytescat(buf,seg);    
577
    }
578
579
done:
580
    if(!stat) {
581
  if(pathp) *pathp = ncbytesextract(buf);
582
    }
583
    ncbytesfree(buf);
584
    return THROW(stat);
585
}
586
#endif
587
588
#if 0
589
static int
590
extendenvv(char*** envvp, int amount, int* oldlenp)
591
{
592
    char** envv = *envvp;
593
    char** p;
594
    int len;
595
    for(len=0,p=envv;*p;p++) len++;
596
    *oldlenp = len;
597
    if((envv = (char**)malloc((amount+len+1)*sizeof(char*)))==NULL) return NC_ENOMEM;
598
    memcpy(envv,*envvp,sizeof(char*)*len);
599
    envv[len] = NULL;
600
    nullfree(*envvp);
601
    *envvp = envv; envv = NULL;
602
    return NC_NOERR;
603
}
604
#endif
605
606
static int
607
nc_compare(const void* arg1, const void* arg2)
608
0
{
609
0
    char* n1 = *((char**)arg1);
610
0
    char* n2 = *((char**)arg2);
611
0
    return strcmp(n1,n2);
612
0
}
613
614
/* quick sort a list of strings */
615
void
616
NC_sortenvv(size_t n, char** envv)
617
0
{
618
0
    if(n <= 1) return;
619
0
    qsort(envv, n, sizeof(char*), nc_compare);
620
#if 0
621
{int i;
622
for(i=0;i<n;i++)
623
fprintf(stderr,">>> sorted: [%d] %s\n",i,(const char*)envv[i]);
624
}
625
#endif
626
0
}
627
628
void
629
NC_freeenvv(size_t n, char** envv)
630
0
{
631
0
    size_t i;
632
0
    char** p;
633
0
    if(envv == NULL) return;
634
0
    if(n < 0)
635
0
       {for(n=0, p = envv; *p; n++) {}; /* count number of strings */}
636
0
    for(i=0;i<n;i++) {
637
0
        if(envv[i]) {
638
0
      free(envv[i]);
639
0
  }
640
0
    }
641
0
    free(envv);    
642
0
}
643