Home | History | Annotate | Line # | Download | only in isc
file.c revision 1.2.4.2
      1 /*	$NetBSD: file.c,v 1.2.4.2 2024/02/29 12:34:59 martin 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