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