xref: /netbsd-src/external/mpl/bind/dist/lib/isc/file.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: file.c,v 1.3 2025/01/26 16:25:37 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/random.h>
68 #include <isc/string.h>
69 #include <isc/time.h>
70 #include <isc/util.h>
71 
72 #include "errno2result.h"
73 
74 /*
75  * XXXDCL As the API for accessing file statistics undoubtedly gets expanded,
76  * it might be good to provide a mechanism that allows for the results
77  * of a previous stat() to be used again without having to do another stat,
78  * such as perl's mechanism of using "_" in place of a file name to indicate
79  * that the results of the last stat should be used.  But then you get into
80  * annoying MP issues.   BTW, Win32 has stat().
81  */
82 static isc_result_t
83 file_stats(const char *file, struct stat *stats) {
84 	isc_result_t result = ISC_R_SUCCESS;
85 
86 	REQUIRE(file != NULL);
87 	REQUIRE(stats != NULL);
88 
89 	if (stat(file, stats) != 0) {
90 		result = isc__errno2result(errno);
91 	}
92 
93 	return result;
94 }
95 
96 static isc_result_t
97 fd_stats(int fd, struct stat *stats) {
98 	isc_result_t result = ISC_R_SUCCESS;
99 
100 	REQUIRE(stats != NULL);
101 
102 	if (fstat(fd, stats) != 0) {
103 		result = isc__errno2result(errno);
104 	}
105 
106 	return result;
107 }
108 
109 isc_result_t
110 isc_file_getsizefd(int fd, off_t *size) {
111 	isc_result_t result;
112 	struct stat stats;
113 
114 	REQUIRE(size != NULL);
115 
116 	result = fd_stats(fd, &stats);
117 
118 	if (result == ISC_R_SUCCESS) {
119 		*size = stats.st_size;
120 	}
121 
122 	return result;
123 }
124 
125 isc_result_t
126 isc_file_mode(const char *file, mode_t *modep) {
127 	isc_result_t result;
128 	struct stat stats;
129 
130 	REQUIRE(modep != NULL);
131 
132 	result = file_stats(file, &stats);
133 	if (result == ISC_R_SUCCESS) {
134 		*modep = (stats.st_mode & 07777);
135 	}
136 
137 	return result;
138 }
139 
140 isc_result_t
141 isc_file_getmodtime(const char *file, isc_time_t *modtime) {
142 	isc_result_t result;
143 	struct stat stats;
144 
145 	REQUIRE(file != NULL);
146 	REQUIRE(modtime != NULL);
147 
148 	result = file_stats(file, &stats);
149 
150 	if (result == ISC_R_SUCCESS) {
151 #if defined(HAVE_STAT_NSEC)
152 		isc_time_set(modtime, stats.st_mtime, stats.st_mtim.tv_nsec);
153 #else  /* if defined(HAVE_STAT_NSEC) */
154 		isc_time_set(modtime, stats.st_mtime, 0);
155 #endif /* if defined(HAVE_STAT_NSEC) */
156 	}
157 
158 	return result;
159 }
160 
161 isc_result_t
162 isc_file_getsize(const char *file, off_t *size) {
163 	isc_result_t result;
164 	struct stat stats;
165 
166 	REQUIRE(file != NULL);
167 	REQUIRE(size != NULL);
168 
169 	result = file_stats(file, &stats);
170 
171 	if (result == ISC_R_SUCCESS) {
172 		*size = stats.st_size;
173 	}
174 
175 	return result;
176 }
177 
178 isc_result_t
179 isc_file_settime(const char *file, isc_time_t *when) {
180 	struct timeval times[2];
181 
182 	REQUIRE(file != NULL && when != NULL);
183 
184 	/*
185 	 * tv_sec is at least a 32 bit quantity on all platforms we're
186 	 * dealing with, but it is signed on most (all?) of them,
187 	 * so we need to make sure the high bit isn't set.  This unfortunately
188 	 * loses when either:
189 	 *   * tv_sec becomes a signed 64 bit integer but long is 32 bits
190 	 *	and isc_time_seconds > LONG_MAX, or
191 	 *   * isc_time_seconds is changed to be > 32 bits but long is 32 bits
192 	 *      and isc_time_seconds has at least 33 significant bits.
193 	 */
194 	times[0].tv_sec = times[1].tv_sec = (long)isc_time_seconds(when);
195 
196 	/*
197 	 * Here is the real check for the high bit being set.
198 	 */
199 	if ((times[0].tv_sec &
200 	     (1ULL << (sizeof(times[0].tv_sec) * CHAR_BIT - 1))) != 0)
201 	{
202 		return ISC_R_RANGE;
203 	}
204 
205 	/*
206 	 * isc_time_nanoseconds guarantees a value that divided by 1000 will
207 	 * fit into the minimum possible size tv_usec field.
208 	 */
209 	times[0].tv_usec = times[1].tv_usec =
210 		(int32_t)(isc_time_nanoseconds(when) / 1000);
211 
212 	if (utimes(file, times) < 0) {
213 		return isc__errno2result(errno);
214 	}
215 
216 	return ISC_R_SUCCESS;
217 }
218 
219 #undef TEMPLATE
220 #define TEMPLATE "tmp-XXXXXXXXXX" /*%< 14 characters. */
221 
222 isc_result_t
223 isc_file_mktemplate(const char *path, char *buf, size_t buflen) {
224 	return isc_file_template(path, TEMPLATE, buf, buflen);
225 }
226 
227 isc_result_t
228 isc_file_template(const char *path, const char *templet, char *buf,
229 		  size_t buflen) {
230 	const char *s;
231 
232 	REQUIRE(templet != NULL);
233 	REQUIRE(buf != NULL);
234 
235 	if (path == NULL) {
236 		path = "";
237 	}
238 
239 	s = strrchr(templet, '/');
240 	if (s != NULL) {
241 		templet = s + 1;
242 	}
243 
244 	s = strrchr(path, '/');
245 
246 	if (s != NULL) {
247 		size_t prefixlen = s - path + 1;
248 		if ((prefixlen + strlen(templet) + 1) > buflen) {
249 			return ISC_R_NOSPACE;
250 		}
251 
252 		/* Copy 'prefixlen' bytes and NUL terminate. */
253 		strlcpy(buf, path, ISC_MIN(prefixlen + 1, buflen));
254 		strlcat(buf, templet, buflen);
255 	} else {
256 		if ((strlen(templet) + 1) > buflen) {
257 			return ISC_R_NOSPACE;
258 		}
259 
260 		strlcpy(buf, templet, buflen);
261 	}
262 
263 	return ISC_R_SUCCESS;
264 }
265 
266 static const char alphnum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv"
267 			      "wxyz0123456789";
268 
269 isc_result_t
270 isc_file_renameunique(const char *file, char *templet) {
271 	char *x;
272 	char *cp;
273 
274 	REQUIRE(file != NULL);
275 	REQUIRE(templet != NULL);
276 
277 	cp = templet;
278 	while (*cp != '\0') {
279 		cp++;
280 	}
281 	if (cp == templet) {
282 		return ISC_R_FAILURE;
283 	}
284 
285 	x = cp--;
286 	while (cp >= templet && *cp == 'X') {
287 		*cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)];
288 		x = cp--;
289 	}
290 	while (link(file, templet) == -1) {
291 		if (errno != EEXIST) {
292 			return isc__errno2result(errno);
293 		}
294 		for (cp = x;;) {
295 			const char *t;
296 			if (*cp == '\0') {
297 				return ISC_R_FAILURE;
298 			}
299 			t = strchr(alphnum, *cp);
300 			if (t == NULL || *++t == '\0') {
301 				*cp++ = alphnum[0];
302 			} else {
303 				*cp = *t;
304 				break;
305 			}
306 		}
307 	}
308 	if (unlink(file) < 0) {
309 		if (errno != ENOENT) {
310 			return isc__errno2result(errno);
311 		}
312 	}
313 	return ISC_R_SUCCESS;
314 }
315 
316 isc_result_t
317 isc_file_openunique(char *templet, FILE **fp) {
318 	int mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
319 	return isc_file_openuniquemode(templet, mode, fp);
320 }
321 
322 isc_result_t
323 isc_file_openuniqueprivate(char *templet, FILE **fp) {
324 	int mode = S_IWUSR | S_IRUSR;
325 	return isc_file_openuniquemode(templet, mode, fp);
326 }
327 
328 isc_result_t
329 isc_file_openuniquemode(char *templet, int mode, FILE **fp) {
330 	int fd;
331 	FILE *f;
332 	isc_result_t result = ISC_R_SUCCESS;
333 	char *x;
334 	char *cp;
335 
336 	REQUIRE(templet != NULL);
337 	REQUIRE(fp != NULL && *fp == NULL);
338 
339 	cp = templet;
340 	while (*cp != '\0') {
341 		cp++;
342 	}
343 	if (cp == templet) {
344 		return ISC_R_FAILURE;
345 	}
346 
347 	x = cp--;
348 	while (cp >= templet && *cp == 'X') {
349 		*cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)];
350 		x = cp--;
351 	}
352 
353 	while ((fd = open(templet, O_RDWR | O_CREAT | O_EXCL, mode)) == -1) {
354 		if (errno != EEXIST) {
355 			return isc__errno2result(errno);
356 		}
357 		for (cp = x;;) {
358 			char *t;
359 			if (*cp == '\0') {
360 				return ISC_R_FAILURE;
361 			}
362 			t = strchr(alphnum, *cp);
363 			if (t == NULL || *++t == '\0') {
364 				*cp++ = alphnum[0];
365 			} else {
366 				*cp = *t;
367 				break;
368 			}
369 		}
370 	}
371 	f = fdopen(fd, "w+");
372 	if (f == NULL) {
373 		result = isc__errno2result(errno);
374 		if (remove(templet) < 0) {
375 			isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
376 				      ISC_LOGMODULE_FILE, ISC_LOG_ERROR,
377 				      "remove '%s': failed", templet);
378 		}
379 		(void)close(fd);
380 	} else {
381 		*fp = f;
382 	}
383 
384 	return result;
385 }
386 
387 isc_result_t
388 isc_file_remove(const char *filename) {
389 	int r;
390 
391 	REQUIRE(filename != NULL);
392 
393 	r = unlink(filename);
394 	if (r == 0) {
395 		return ISC_R_SUCCESS;
396 	} else {
397 		return isc__errno2result(errno);
398 	}
399 }
400 
401 isc_result_t
402 isc_file_rename(const char *oldname, const char *newname) {
403 	int r;
404 
405 	REQUIRE(oldname != NULL);
406 	REQUIRE(newname != NULL);
407 
408 	r = rename(oldname, newname);
409 	if (r == 0) {
410 		return ISC_R_SUCCESS;
411 	} else {
412 		return isc__errno2result(errno);
413 	}
414 }
415 
416 bool
417 isc_file_exists(const char *pathname) {
418 	struct stat stats;
419 
420 	REQUIRE(pathname != NULL);
421 
422 	return file_stats(pathname, &stats) == ISC_R_SUCCESS;
423 }
424 
425 isc_result_t
426 isc_file_isplainfile(const char *filename) {
427 	/*
428 	 * This function returns success if filename is a plain file.
429 	 */
430 	struct stat filestat;
431 	memset(&filestat, 0, sizeof(struct stat));
432 
433 	if ((stat(filename, &filestat)) == -1) {
434 		return isc__errno2result(errno);
435 	}
436 
437 	if (!S_ISREG(filestat.st_mode)) {
438 		return ISC_R_INVALIDFILE;
439 	}
440 
441 	return ISC_R_SUCCESS;
442 }
443 
444 isc_result_t
445 isc_file_isplainfilefd(int fd) {
446 	/*
447 	 * This function returns success if filename is a plain file.
448 	 */
449 	struct stat filestat;
450 	memset(&filestat, 0, sizeof(struct stat));
451 
452 	if ((fstat(fd, &filestat)) == -1) {
453 		return isc__errno2result(errno);
454 	}
455 
456 	if (!S_ISREG(filestat.st_mode)) {
457 		return ISC_R_INVALIDFILE;
458 	}
459 
460 	return ISC_R_SUCCESS;
461 }
462 
463 isc_result_t
464 isc_file_isdirectory(const char *filename) {
465 	/*
466 	 * This function returns success if filename exists and is a
467 	 * directory.
468 	 */
469 	struct stat filestat;
470 	memset(&filestat, 0, sizeof(struct stat));
471 
472 	if ((stat(filename, &filestat)) == -1) {
473 		return isc__errno2result(errno);
474 	}
475 
476 	if (!S_ISDIR(filestat.st_mode)) {
477 		return ISC_R_INVALIDFILE;
478 	}
479 
480 	return ISC_R_SUCCESS;
481 }
482 
483 bool
484 isc_file_isabsolute(const char *filename) {
485 	REQUIRE(filename != NULL);
486 	return filename[0] == '/';
487 }
488 
489 bool
490 isc_file_iscurrentdir(const char *filename) {
491 	REQUIRE(filename != NULL);
492 	return filename[0] == '.' && filename[1] == '\0';
493 }
494 
495 bool
496 isc_file_ischdiridempotent(const char *filename) {
497 	REQUIRE(filename != NULL);
498 	if (isc_file_isabsolute(filename)) {
499 		return true;
500 	}
501 	if (isc_file_iscurrentdir(filename)) {
502 		return true;
503 	}
504 	return false;
505 }
506 
507 const char *
508 isc_file_basename(const char *filename) {
509 	const char *s;
510 
511 	REQUIRE(filename != NULL);
512 
513 	s = strrchr(filename, '/');
514 	if (s == NULL) {
515 		return filename;
516 	}
517 
518 	return s + 1;
519 }
520 
521 isc_result_t
522 isc_file_progname(const char *filename, char *buf, size_t buflen) {
523 	const char *base;
524 	size_t len;
525 
526 	REQUIRE(filename != NULL);
527 	REQUIRE(buf != NULL);
528 
529 	base = isc_file_basename(filename);
530 	len = strlen(base) + 1;
531 
532 	if (len > buflen) {
533 		return ISC_R_NOSPACE;
534 	}
535 	memmove(buf, base, len);
536 
537 	return ISC_R_SUCCESS;
538 }
539 
540 /*
541  * Put the absolute name of the current directory into 'dirname', which is
542  * a buffer of at least 'length' characters.  End the string with the
543  * appropriate path separator, such that the final product could be
544  * concatenated with a relative pathname to make a valid pathname string.
545  */
546 static isc_result_t
547 dir_current(char *dirname, size_t length) {
548 	char *cwd;
549 	isc_result_t result = ISC_R_SUCCESS;
550 
551 	REQUIRE(dirname != NULL);
552 	REQUIRE(length > 0U);
553 
554 	cwd = getcwd(dirname, length);
555 
556 	if (cwd == NULL) {
557 		if (errno == ERANGE) {
558 			result = ISC_R_NOSPACE;
559 		} else {
560 			result = isc__errno2result(errno);
561 		}
562 	} else {
563 		if (strlen(dirname) + 1 == length) {
564 			result = ISC_R_NOSPACE;
565 		} else if (dirname[1] != '\0') {
566 			strlcat(dirname, "/", length);
567 		}
568 	}
569 
570 	return result;
571 }
572 
573 isc_result_t
574 isc_file_absolutepath(const char *filename, char *path, size_t pathlen) {
575 	isc_result_t result;
576 	result = dir_current(path, pathlen);
577 	if (result != ISC_R_SUCCESS) {
578 		return result;
579 	}
580 	if (strlen(path) + strlen(filename) + 1 > pathlen) {
581 		return ISC_R_NOSPACE;
582 	}
583 	strlcat(path, filename, pathlen);
584 	return ISC_R_SUCCESS;
585 }
586 
587 isc_result_t
588 isc_file_truncate(const char *filename, off_t size) {
589 	isc_result_t result = ISC_R_SUCCESS;
590 
591 	if (truncate(filename, size) < 0) {
592 		result = isc__errno2result(errno);
593 	}
594 	return result;
595 }
596 
597 isc_result_t
598 isc_file_safecreate(const char *filename, FILE **fp) {
599 	isc_result_t result;
600 	int flags;
601 	struct stat sb;
602 	FILE *f;
603 	int fd;
604 
605 	REQUIRE(filename != NULL);
606 	REQUIRE(fp != NULL && *fp == NULL);
607 
608 	result = file_stats(filename, &sb);
609 	if (result == ISC_R_SUCCESS) {
610 		if ((sb.st_mode & S_IFREG) == 0) {
611 			return ISC_R_INVALIDFILE;
612 		}
613 		flags = O_WRONLY | O_TRUNC;
614 	} else if (result == ISC_R_FILENOTFOUND) {
615 		flags = O_WRONLY | O_CREAT | O_EXCL;
616 	} else {
617 		return result;
618 	}
619 
620 	fd = open(filename, flags, S_IRUSR | S_IWUSR);
621 	if (fd == -1) {
622 		return isc__errno2result(errno);
623 	}
624 
625 	f = fdopen(fd, "w");
626 	if (f == NULL) {
627 		result = isc__errno2result(errno);
628 		close(fd);
629 		return result;
630 	}
631 
632 	*fp = f;
633 	return ISC_R_SUCCESS;
634 }
635 
636 isc_result_t
637 isc_file_splitpath(isc_mem_t *mctx, const char *path, char **dirname,
638 		   char const **bname) {
639 	char *dir;
640 	const char *file, *slash;
641 
642 	if (path == NULL) {
643 		return ISC_R_INVALIDFILE;
644 	}
645 
646 	slash = strrchr(path, '/');
647 
648 	if (slash == path) {
649 		file = ++slash;
650 		dir = isc_mem_strdup(mctx, "/");
651 	} else if (slash != NULL) {
652 		file = ++slash;
653 		dir = isc_mem_allocate(mctx, slash - path);
654 		strlcpy(dir, path, slash - path);
655 	} else {
656 		file = path;
657 		dir = isc_mem_strdup(mctx, ".");
658 	}
659 
660 	if (dir == NULL) {
661 		return ISC_R_NOMEMORY;
662 	}
663 
664 	if (*file == '\0') {
665 		isc_mem_free(mctx, dir);
666 		return ISC_R_INVALIDFILE;
667 	}
668 
669 	*dirname = dir;
670 	*bname = file;
671 
672 	return ISC_R_SUCCESS;
673 }
674 
675 #define DISALLOW "\\/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
676 
677 static isc_result_t
678 digest2hex(unsigned char *digest, unsigned int digestlen, char *hash,
679 	   size_t hashlen) {
680 	unsigned int i;
681 	int ret;
682 	for (i = 0; i < digestlen; i++) {
683 		size_t left = hashlen - i * 2;
684 		ret = snprintf(hash + i * 2, left, "%02x", digest[i]);
685 		if (ret < 0 || (size_t)ret >= left) {
686 			return ISC_R_NOSPACE;
687 		}
688 	}
689 	return ISC_R_SUCCESS;
690 }
691 
692 isc_result_t
693 isc_file_sanitize(const char *dir, const char *base, const char *ext,
694 		  char *path, size_t length) {
695 	char buf[PATH_MAX];
696 	unsigned char digest[ISC_MAX_MD_SIZE];
697 	unsigned int digestlen;
698 	char hash[ISC_MAX_MD_SIZE * 2 + 1];
699 	size_t l = 0;
700 	isc_result_t err;
701 
702 	REQUIRE(base != NULL);
703 	REQUIRE(path != NULL);
704 
705 	l = strlen(base) + 1;
706 
707 	/*
708 	 * allow room for a full sha256 hash (64 chars
709 	 * plus null terminator)
710 	 */
711 	if (l < 65U) {
712 		l = 65;
713 	}
714 
715 	if (dir != NULL) {
716 		l += strlen(dir) + 1;
717 	}
718 	if (ext != NULL) {
719 		l += strlen(ext) + 1;
720 	}
721 
722 	if (l > length || l > (unsigned int)PATH_MAX) {
723 		return ISC_R_NOSPACE;
724 	}
725 
726 	/* Check whether the full-length SHA256 hash filename exists */
727 	err = isc_md(ISC_MD_SHA256, (const unsigned char *)base, strlen(base),
728 		     digest, &digestlen);
729 	if (err != ISC_R_SUCCESS) {
730 		return err;
731 	}
732 
733 	err = digest2hex(digest, digestlen, hash, sizeof(hash));
734 	if (err != ISC_R_SUCCESS) {
735 		return err;
736 	}
737 
738 	snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
739 		 dir != NULL ? "/" : "", hash, ext != NULL ? "." : "",
740 		 ext != NULL ? ext : "");
741 	if (isc_file_exists(buf)) {
742 		strlcpy(path, buf, length);
743 		return ISC_R_SUCCESS;
744 	}
745 
746 	/* Check for a truncated SHA256 hash filename */
747 	hash[16] = '\0';
748 	snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
749 		 dir != NULL ? "/" : "", hash, ext != NULL ? "." : "",
750 		 ext != NULL ? ext : "");
751 	if (isc_file_exists(buf)) {
752 		strlcpy(path, buf, length);
753 		return ISC_R_SUCCESS;
754 	}
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 	if (strpbrk(base, DISALLOW) != NULL) {
762 		strlcpy(path, buf, length);
763 		return ISC_R_SUCCESS;
764 	}
765 
766 	snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
767 		 dir != NULL ? "/" : "", base, ext != NULL ? "." : "",
768 		 ext != NULL ? ext : "");
769 	strlcpy(path, buf, length);
770 	return ISC_R_SUCCESS;
771 }
772 
773 bool
774 isc_file_isdirwritable(const char *path) {
775 	return access(path, W_OK | X_OK) == 0;
776 }
777