file.c revision 1.3 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