Home | History | Annotate | Line # | Download | only in isc
      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