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