Coverage Report

Created: 2025-11-11 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bind9/lib/isc/file.c
Line
Count
Source
1
/*
2
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3
 *
4
 * SPDX-License-Identifier: MPL-2.0
5
 *
6
 * This Source Code Form is subject to the terms of the Mozilla Public
7
 * License, v. 2.0. If a copy of the MPL was not distributed with this
8
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9
 *
10
 * See the COPYRIGHT file distributed with this work for additional
11
 * information regarding copyright ownership.
12
 */
13
14
/*
15
 * Portions Copyright (c) 1987, 1993
16
 *      The Regents of the University of California.  All rights reserved.
17
 *
18
 * Redistribution and use in source and binary forms, with or without
19
 * modification, are permitted provided that the following conditions
20
 * are met:
21
 * 1. Redistributions of source code must retain the above copyright
22
 *    notice, this list of conditions and the following disclaimer.
23
 * 2. Redistributions in binary form must reproduce the above copyright
24
 *    notice, this list of conditions and the following disclaimer in the
25
 *    documentation and/or other materials provided with the distribution.
26
 * 3. Neither the name of the University nor the names of its contributors
27
 *    may be used to endorse or promote products derived from this software
28
 *    without specific prior written permission.
29
 *
30
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
31
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
34
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40
 * SUCH DAMAGE.
41
 */
42
43
/*! \file */
44
45
#include <errno.h>
46
#include <fcntl.h>
47
#include <inttypes.h>
48
#include <limits.h>
49
#include <stdbool.h>
50
#include <stdlib.h>
51
#include <sys/stat.h>
52
#include <sys/time.h>
53
#include <time.h>   /* Required for utimes on some platforms. */
54
#include <unistd.h> /* Required for mkstemp on NetBSD. */
55
56
#ifdef HAVE_SYS_MMAN_H
57
#include <sys/mman.h>
58
#endif /* ifdef HAVE_SYS_MMAN_H */
59
60
#include <isc/dir.h>
61
#include <isc/file.h>
62
#include <isc/log.h>
63
#include <isc/md.h>
64
#include <isc/mem.h>
65
#include <isc/random.h>
66
#include <isc/string.h>
67
#include <isc/time.h>
68
#include <isc/util.h>
69
70
#include "errno2result.h"
71
72
/*
73
 * XXXDCL As the API for accessing file statistics undoubtedly gets expanded,
74
 * it might be good to provide a mechanism that allows for the results
75
 * of a previous stat() to be used again without having to do another stat,
76
 * such as perl's mechanism of using "_" in place of a file name to indicate
77
 * that the results of the last stat should be used.  But then you get into
78
 * annoying MP issues.   BTW, Win32 has stat().
79
 */
80
static isc_result_t
81
0
file_stats(const char *file, struct stat *stats) {
82
0
  isc_result_t result = ISC_R_SUCCESS;
83
84
0
  REQUIRE(file != NULL);
85
0
  REQUIRE(stats != NULL);
86
87
0
  if (stat(file, stats) != 0) {
88
0
    result = isc__errno2result(errno);
89
0
  }
90
91
0
  return result;
92
0
}
93
94
static isc_result_t
95
0
fd_stats(int fd, struct stat *stats) {
96
0
  isc_result_t result = ISC_R_SUCCESS;
97
98
0
  REQUIRE(stats != NULL);
99
100
0
  if (fstat(fd, stats) != 0) {
101
0
    result = isc__errno2result(errno);
102
0
  }
103
104
0
  return result;
105
0
}
106
107
isc_result_t
108
0
isc_file_getsizefd(int fd, off_t *size) {
109
0
  isc_result_t result;
110
0
  struct stat stats;
111
112
0
  REQUIRE(size != NULL);
113
114
0
  result = fd_stats(fd, &stats);
115
116
0
  if (result == ISC_R_SUCCESS) {
117
0
    *size = stats.st_size;
118
0
  }
119
120
0
  return result;
121
0
}
122
123
isc_result_t
124
0
isc_file_mode(const char *file, mode_t *modep) {
125
0
  isc_result_t result;
126
0
  struct stat stats;
127
128
0
  REQUIRE(modep != NULL);
129
130
0
  result = file_stats(file, &stats);
131
0
  if (result == ISC_R_SUCCESS) {
132
0
    *modep = (stats.st_mode & 07777);
133
0
  }
134
135
0
  return result;
136
0
}
137
138
isc_result_t
139
0
isc_file_getmodtime(const char *file, isc_time_t *modtime) {
140
0
  isc_result_t result;
141
0
  struct stat stats;
142
143
0
  REQUIRE(file != NULL);
144
0
  REQUIRE(modtime != NULL);
145
146
0
  result = file_stats(file, &stats);
147
148
0
  if (result == ISC_R_SUCCESS) {
149
0
#if defined(HAVE_STAT_NSEC)
150
0
    isc_time_set(modtime, stats.st_mtime, stats.st_mtim.tv_nsec);
151
#else  /* if defined(HAVE_STAT_NSEC) */
152
    isc_time_set(modtime, stats.st_mtime, 0);
153
#endif /* if defined(HAVE_STAT_NSEC) */
154
0
  }
155
156
0
  return result;
157
0
}
158
159
isc_result_t
160
0
isc_file_getsize(const char *file, off_t *size) {
161
0
  isc_result_t result;
162
0
  struct stat stats;
163
164
0
  REQUIRE(file != NULL);
165
0
  REQUIRE(size != NULL);
166
167
0
  result = file_stats(file, &stats);
168
169
0
  if (result == ISC_R_SUCCESS) {
170
0
    *size = stats.st_size;
171
0
  }
172
173
0
  return result;
174
0
}
175
176
isc_result_t
177
0
isc_file_settime(const char *file, isc_time_t *when) {
178
0
  struct timeval times[2];
179
180
0
  REQUIRE(file != NULL && when != NULL);
181
182
  /*
183
   * tv_sec is at least a 32 bit quantity on all platforms we're
184
   * dealing with, but it is signed on most (all?) of them,
185
   * so we need to make sure the high bit isn't set.  This unfortunately
186
   * loses when either:
187
   *   * tv_sec becomes a signed 64 bit integer but long is 32 bits
188
   *  and isc_time_seconds > LONG_MAX, or
189
   *   * isc_time_seconds is changed to be > 32 bits but long is 32 bits
190
   *      and isc_time_seconds has at least 33 significant bits.
191
   */
192
0
  times[0].tv_sec = times[1].tv_sec = (long)isc_time_seconds(when);
193
194
  /*
195
   * Here is the real check for the high bit being set.
196
   */
197
0
  if ((times[0].tv_sec &
198
0
       (1ULL << (sizeof(times[0].tv_sec) * CHAR_BIT - 1))) != 0)
199
0
  {
200
0
    return ISC_R_RANGE;
201
0
  }
202
203
  /*
204
   * isc_time_nanoseconds guarantees a value that divided by 1000 will
205
   * fit into the minimum possible size tv_usec field.
206
   */
207
0
  times[0].tv_usec = times[1].tv_usec =
208
0
    (int32_t)(isc_time_nanoseconds(when) / 1000);
209
210
0
  if (utimes(file, times) < 0) {
211
0
    return isc__errno2result(errno);
212
0
  }
213
214
0
  return ISC_R_SUCCESS;
215
0
}
216
217
#undef TEMPLATE
218
0
#define TEMPLATE "tmp-XXXXXXXXXX" /*%< 14 characters. */
219
220
isc_result_t
221
0
isc_file_mktemplate(const char *path, char *buf, size_t buflen) {
222
0
  return isc_file_template(path, TEMPLATE, buf, buflen);
223
0
}
224
225
isc_result_t
226
isc_file_template(const char *path, const char *templet, char *buf,
227
0
      size_t buflen) {
228
0
  const char *s;
229
230
0
  REQUIRE(templet != NULL);
231
0
  REQUIRE(buf != NULL);
232
233
0
  if (path == NULL) {
234
0
    path = "";
235
0
  }
236
237
0
  s = strrchr(templet, '/');
238
0
  if (s != NULL) {
239
0
    templet = s + 1;
240
0
  }
241
242
0
  s = strrchr(path, '/');
243
244
0
  if (s != NULL) {
245
0
    size_t prefixlen = s - path + 1;
246
0
    if ((prefixlen + strlen(templet) + 1) > buflen) {
247
0
      return ISC_R_NOSPACE;
248
0
    }
249
250
    /* Copy 'prefixlen' bytes and NUL terminate. */
251
0
    strlcpy(buf, path, ISC_MIN(prefixlen + 1, buflen));
252
0
    strlcat(buf, templet, buflen);
253
0
  } else {
254
0
    if ((strlen(templet) + 1) > buflen) {
255
0
      return ISC_R_NOSPACE;
256
0
    }
257
258
0
    strlcpy(buf, templet, buflen);
259
0
  }
260
261
0
  return ISC_R_SUCCESS;
262
0
}
263
264
static const char alphnum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv"
265
            "wxyz0123456789";
266
267
isc_result_t
268
0
isc_file_renameunique(const char *file, char *templet) {
269
0
  char *x;
270
0
  char *cp;
271
272
0
  REQUIRE(file != NULL);
273
0
  REQUIRE(templet != NULL);
274
275
0
  cp = templet;
276
0
  while (*cp != '\0') {
277
0
    cp++;
278
0
  }
279
0
  if (cp == templet) {
280
0
    return ISC_R_FAILURE;
281
0
  }
282
283
0
  x = cp--;
284
0
  while (cp >= templet && *cp == 'X') {
285
0
    *cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)];
286
0
    x = cp--;
287
0
  }
288
0
  while (link(file, templet) == -1) {
289
0
    if (errno != EEXIST) {
290
0
      return isc__errno2result(errno);
291
0
    }
292
0
    for (cp = x;;) {
293
0
      const char *t;
294
0
      if (*cp == '\0') {
295
0
        return ISC_R_FAILURE;
296
0
      }
297
0
      t = strchr(alphnum, *cp);
298
0
      if (t == NULL || *++t == '\0') {
299
0
        *cp++ = alphnum[0];
300
0
      } else {
301
0
        *cp = *t;
302
0
        break;
303
0
      }
304
0
    }
305
0
  }
306
0
  if (unlink(file) < 0) {
307
0
    if (errno != ENOENT) {
308
0
      return isc__errno2result(errno);
309
0
    }
310
0
  }
311
0
  return ISC_R_SUCCESS;
312
0
}
313
314
isc_result_t
315
0
isc_file_openunique(char *templet, FILE **fp) {
316
0
  int mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
317
0
  return isc_file_openuniquemode(templet, mode, fp);
318
0
}
319
320
isc_result_t
321
0
isc_file_openuniqueprivate(char *templet, FILE **fp) {
322
0
  int mode = S_IWUSR | S_IRUSR;
323
0
  return isc_file_openuniquemode(templet, mode, fp);
324
0
}
325
326
isc_result_t
327
0
isc_file_openuniquemode(char *templet, int mode, FILE **fp) {
328
0
  int fd;
329
0
  FILE *f;
330
0
  isc_result_t result = ISC_R_SUCCESS;
331
0
  char *x;
332
0
  char *cp;
333
334
0
  REQUIRE(templet != NULL);
335
0
  REQUIRE(fp != NULL && *fp == NULL);
336
337
0
  cp = templet;
338
0
  while (*cp != '\0') {
339
0
    cp++;
340
0
  }
341
0
  if (cp == templet) {
342
0
    return ISC_R_FAILURE;
343
0
  }
344
345
0
  x = cp--;
346
0
  while (cp >= templet && *cp == 'X') {
347
0
    *cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)];
348
0
    x = cp--;
349
0
  }
350
351
0
  while ((fd = open(templet, O_RDWR | O_CREAT | O_EXCL, mode)) == -1) {
352
0
    if (errno != EEXIST) {
353
0
      return isc__errno2result(errno);
354
0
    }
355
0
    for (cp = x;;) {
356
0
      char *t;
357
0
      if (*cp == '\0') {
358
0
        return ISC_R_FAILURE;
359
0
      }
360
0
      t = strchr(alphnum, *cp);
361
0
      if (t == NULL || *++t == '\0') {
362
0
        *cp++ = alphnum[0];
363
0
      } else {
364
0
        *cp = *t;
365
0
        break;
366
0
      }
367
0
    }
368
0
  }
369
0
  f = fdopen(fd, "w+");
370
0
  if (f == NULL) {
371
0
    result = isc__errno2result(errno);
372
0
    if (remove(templet) < 0) {
373
0
      isc_log_write(ISC_LOGCATEGORY_GENERAL,
374
0
              ISC_LOGMODULE_FILE, ISC_LOG_ERROR,
375
0
              "remove '%s': failed", templet);
376
0
    }
377
0
    (void)close(fd);
378
0
  } else {
379
0
    *fp = f;
380
0
  }
381
382
0
  return result;
383
0
}
384
385
isc_result_t
386
0
isc_file_remove(const char *filename) {
387
0
  int r;
388
389
0
  REQUIRE(filename != NULL);
390
391
0
  r = unlink(filename);
392
0
  if (r == 0) {
393
0
    return ISC_R_SUCCESS;
394
0
  } else {
395
0
    return isc__errno2result(errno);
396
0
  }
397
0
}
398
399
isc_result_t
400
0
isc_file_rename(const char *oldname, const char *newname) {
401
0
  int r;
402
403
0
  REQUIRE(oldname != NULL);
404
0
  REQUIRE(newname != NULL);
405
406
0
  r = rename(oldname, newname);
407
0
  if (r == 0) {
408
0
    return ISC_R_SUCCESS;
409
0
  } else {
410
0
    return isc__errno2result(errno);
411
0
  }
412
0
}
413
414
bool
415
0
isc_file_exists(const char *pathname) {
416
0
  struct stat stats;
417
418
0
  REQUIRE(pathname != NULL);
419
420
0
  return file_stats(pathname, &stats) == ISC_R_SUCCESS;
421
0
}
422
423
isc_result_t
424
0
isc_file_isplainfile(const char *filename) {
425
  /*
426
   * This function returns success if filename is a plain file.
427
   */
428
0
  struct stat filestat;
429
0
  memset(&filestat, 0, sizeof(struct stat));
430
431
0
  if ((stat(filename, &filestat)) == -1) {
432
0
    return isc__errno2result(errno);
433
0
  }
434
435
0
  if (!S_ISREG(filestat.st_mode)) {
436
0
    return ISC_R_INVALIDFILE;
437
0
  }
438
439
0
  return ISC_R_SUCCESS;
440
0
}
441
442
isc_result_t
443
0
isc_file_isplainfilefd(int fd) {
444
  /*
445
   * This function returns success if filename is a plain file.
446
   */
447
0
  struct stat filestat;
448
0
  memset(&filestat, 0, sizeof(struct stat));
449
450
0
  if ((fstat(fd, &filestat)) == -1) {
451
0
    return isc__errno2result(errno);
452
0
  }
453
454
0
  if (!S_ISREG(filestat.st_mode)) {
455
0
    return ISC_R_INVALIDFILE;
456
0
  }
457
458
0
  return ISC_R_SUCCESS;
459
0
}
460
461
isc_result_t
462
0
isc_file_isdirectory(const char *filename) {
463
  /*
464
   * This function returns success if filename exists and is a
465
   * directory.
466
   */
467
0
  struct stat filestat;
468
0
  memset(&filestat, 0, sizeof(struct stat));
469
470
0
  if ((stat(filename, &filestat)) == -1) {
471
0
    return isc__errno2result(errno);
472
0
  }
473
474
0
  if (!S_ISDIR(filestat.st_mode)) {
475
0
    return ISC_R_INVALIDFILE;
476
0
  }
477
478
0
  return ISC_R_SUCCESS;
479
0
}
480
481
bool
482
0
isc_file_isabsolute(const char *filename) {
483
0
  REQUIRE(filename != NULL);
484
0
  return filename[0] == '/';
485
0
}
486
487
bool
488
0
isc_file_iscurrentdir(const char *filename) {
489
0
  REQUIRE(filename != NULL);
490
0
  return filename[0] == '.' && filename[1] == '\0';
491
0
}
492
493
bool
494
0
isc_file_ischdiridempotent(const char *filename) {
495
0
  REQUIRE(filename != NULL);
496
0
  if (isc_file_isabsolute(filename)) {
497
0
    return true;
498
0
  }
499
0
  if (isc_file_iscurrentdir(filename)) {
500
0
    return true;
501
0
  }
502
0
  return false;
503
0
}
504
505
const char *
506
0
isc_file_basename(const char *filename) {
507
0
  const char *s;
508
509
0
  REQUIRE(filename != NULL);
510
511
0
  s = strrchr(filename, '/');
512
0
  if (s == NULL) {
513
0
    return filename;
514
0
  }
515
516
0
  return s + 1;
517
0
}
518
519
void
520
0
isc_file_progname(const char *filename, char *buf, size_t buflen) {
521
0
  const char *base;
522
0
  size_t len;
523
524
0
  REQUIRE(filename != NULL);
525
0
  REQUIRE(buf != NULL);
526
527
0
  base = isc_file_basename(filename);
528
529
  /*
530
   * Libtool doesn't preserve the program name prior to final
531
   * installation.  Remove the libtool prefix ("lt-").
532
   */
533
0
  if (strncmp(base, "lt-", 3) == 0) {
534
0
    base += 3;
535
0
  }
536
537
0
  len = strlen(base) + 1;
538
539
0
  RUNTIME_CHECK(len <= buflen);
540
541
0
  memmove(buf, base, len);
542
0
}
543
544
/*
545
 * Put the absolute name of the current directory into 'dirname', which is
546
 * a buffer of at least 'length' characters.  End the string with the
547
 * appropriate path separator, such that the final product could be
548
 * concatenated with a relative pathname to make a valid pathname string.
549
 */
550
static isc_result_t
551
0
dir_current(char *dirname, size_t length) {
552
0
  char *cwd;
553
0
  isc_result_t result = ISC_R_SUCCESS;
554
555
0
  REQUIRE(dirname != NULL);
556
0
  REQUIRE(length > 0U);
557
558
0
  cwd = getcwd(dirname, length);
559
560
0
  if (cwd == NULL) {
561
0
    if (errno == ERANGE) {
562
0
      result = ISC_R_NOSPACE;
563
0
    } else {
564
0
      result = isc__errno2result(errno);
565
0
    }
566
0
  } else {
567
0
    if (strlen(dirname) + 1 == length) {
568
0
      result = ISC_R_NOSPACE;
569
0
    } else if (dirname[1] != '\0') {
570
0
      strlcat(dirname, "/", length);
571
0
    }
572
0
  }
573
574
0
  return result;
575
0
}
576
577
isc_result_t
578
0
isc_file_absolutepath(const char *filename, char *path, size_t pathlen) {
579
0
  isc_result_t result;
580
0
  result = dir_current(path, pathlen);
581
0
  if (result != ISC_R_SUCCESS) {
582
0
    return result;
583
0
  }
584
0
  if (strlen(path) + strlen(filename) + 1 > pathlen) {
585
0
    return ISC_R_NOSPACE;
586
0
  }
587
0
  strlcat(path, filename, pathlen);
588
0
  return ISC_R_SUCCESS;
589
0
}
590
591
isc_result_t
592
0
isc_file_truncate(const char *filename, off_t size) {
593
0
  isc_result_t result = ISC_R_SUCCESS;
594
595
0
  if (truncate(filename, size) < 0) {
596
0
    result = isc__errno2result(errno);
597
0
  }
598
0
  return result;
599
0
}
600
601
isc_result_t
602
0
isc_file_safecreate(const char *filename, FILE **fp) {
603
0
  isc_result_t result;
604
0
  int flags;
605
0
  struct stat sb;
606
0
  FILE *f;
607
0
  int fd;
608
609
0
  REQUIRE(filename != NULL);
610
0
  REQUIRE(fp != NULL && *fp == NULL);
611
612
0
  result = file_stats(filename, &sb);
613
0
  if (result == ISC_R_SUCCESS) {
614
0
    if ((sb.st_mode & S_IFREG) == 0) {
615
0
      return ISC_R_INVALIDFILE;
616
0
    }
617
0
    flags = O_WRONLY | O_TRUNC;
618
0
  } else if (result == ISC_R_FILENOTFOUND) {
619
0
    flags = O_WRONLY | O_CREAT | O_EXCL;
620
0
  } else {
621
0
    return result;
622
0
  }
623
624
0
  fd = open(filename, flags, S_IRUSR | S_IWUSR);
625
0
  if (fd == -1) {
626
0
    return isc__errno2result(errno);
627
0
  }
628
629
0
  f = fdopen(fd, "w");
630
0
  if (f == NULL) {
631
0
    result = isc__errno2result(errno);
632
0
    close(fd);
633
0
    return result;
634
0
  }
635
636
0
  *fp = f;
637
0
  return ISC_R_SUCCESS;
638
0
}
639
640
isc_result_t
641
isc_file_splitpath(isc_mem_t *mctx, const char *path, char **dirname,
642
0
       char const **bname) {
643
0
  char *dir = NULL;
644
0
  const char *file = NULL, *slash = NULL;
645
646
0
  if (path == NULL) {
647
0
    return ISC_R_INVALIDFILE;
648
0
  }
649
650
0
  slash = strrchr(path, '/');
651
652
0
  if (slash == path) {
653
0
    file = ++slash;
654
0
    dir = isc_mem_strdup(mctx, "/");
655
0
  } else if (slash != NULL) {
656
0
    file = ++slash;
657
0
    dir = isc_mem_allocate(mctx, slash - path);
658
0
    strlcpy(dir, path, slash - path);
659
0
  } else {
660
0
    file = path;
661
0
    dir = isc_mem_strdup(mctx, ".");
662
0
  }
663
664
0
  if (*file == '\0') {
665
0
    isc_mem_free(mctx, dir);
666
0
    return ISC_R_INVALIDFILE;
667
0
  }
668
669
0
  *dirname = dir;
670
0
  *bname = file;
671
672
0
  return ISC_R_SUCCESS;
673
0
}
674
675
0
#define DISALLOW "\\/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
676
677
static isc_result_t
678
digest2hex(unsigned char *digest, unsigned int digestlen, char *hash,
679
0
     size_t hashlen) {
680
0
  unsigned int i;
681
0
  int ret;
682
0
  for (i = 0; i < digestlen; i++) {
683
0
    size_t left = hashlen - i * 2;
684
0
    ret = snprintf(hash + i * 2, left, "%02x", digest[i]);
685
0
    if (ret < 0 || (size_t)ret >= left) {
686
0
      return ISC_R_NOSPACE;
687
0
    }
688
0
  }
689
0
  return ISC_R_SUCCESS;
690
0
}
691
692
isc_result_t
693
isc_file_sanitize(const char *dir, const char *base, const char *ext,
694
0
      char *path, size_t length) {
695
0
  char buf[PATH_MAX];
696
0
  unsigned char digest[ISC_MAX_MD_SIZE];
697
0
  unsigned int digestlen;
698
0
  char hash[ISC_MAX_MD_SIZE * 2 + 1];
699
0
  size_t l = 0;
700
0
  isc_result_t err;
701
702
0
  REQUIRE(base != NULL);
703
0
  REQUIRE(path != NULL);
704
705
0
  l = strlen(base) + 1;
706
707
  /*
708
   * allow room for a full sha256 hash (64 chars
709
   * plus null terminator)
710
   */
711
0
  if (l < 65U) {
712
0
    l = 65;
713
0
  }
714
715
0
  if (dir != NULL) {
716
0
    l += strlen(dir) + 1;
717
0
  }
718
0
  if (ext != NULL) {
719
0
    l += strlen(ext) + 1;
720
0
  }
721
722
0
  if (l > length || l > (unsigned int)PATH_MAX) {
723
0
    return ISC_R_NOSPACE;
724
0
  }
725
726
  /* Check whether the full-length SHA256 hash filename exists */
727
0
  err = isc_md(ISC_MD_SHA256, (const unsigned char *)base, strlen(base),
728
0
         digest, &digestlen);
729
0
  if (err != ISC_R_SUCCESS) {
730
0
    return err;
731
0
  }
732
733
0
  err = digest2hex(digest, digestlen, hash, sizeof(hash));
734
0
  if (err != ISC_R_SUCCESS) {
735
0
    return err;
736
0
  }
737
738
0
  snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
739
0
     dir != NULL ? "/" : "", hash, ext != NULL ? "." : "",
740
0
     ext != NULL ? ext : "");
741
0
  if (isc_file_exists(buf)) {
742
0
    strlcpy(path, buf, length);
743
0
    return ISC_R_SUCCESS;
744
0
  }
745
746
  /* Check for a truncated SHA256 hash filename */
747
0
  hash[16] = '\0';
748
0
  snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
749
0
     dir != NULL ? "/" : "", hash, ext != NULL ? "." : "",
750
0
     ext != NULL ? ext : "");
751
0
  if (isc_file_exists(buf)) {
752
0
    strlcpy(path, buf, length);
753
0
    return ISC_R_SUCCESS;
754
0
  }
755
756
  /*
757
   * If neither hash filename already exists, then we'll use
758
   * the original base name if it has no disallowed characters,
759
   * or the truncated hash name if it does.
760
   */
761
0
  if (strpbrk(base, DISALLOW) != NULL) {
762
0
    strlcpy(path, buf, length);
763
0
    return ISC_R_SUCCESS;
764
0
  }
765
766
0
  snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
767
0
     dir != NULL ? "/" : "", base, ext != NULL ? "." : "",
768
0
     ext != NULL ? ext : "");
769
0
  strlcpy(path, buf, length);
770
0
  return ISC_R_SUCCESS;
771
0
}
772
773
bool
774
0
isc_file_isdirwritable(const char *path) {
775
0
  return access(path, W_OK | X_OK) == 0;
776
0
}