xref: /netbsd-src/external/bsd/ntp/dist/libntp/lib/isc/unix/file.c (revision 8e33eff89e26cf71871ead62f0d5063e1313c33a)
1 /*	$NetBSD: file.c,v 1.2 2024/08/18 20:47:15 christos Exp $	*/
2 
3 /*
4  * Copyright (C) 2004, 2005, 2007, 2009, 2011, 2012  Internet Systems Consortium, Inc. ("ISC")
5  * Copyright (C) 2000-2002  Internet Software Consortium.
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
12  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
14  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
16  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17  * PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 /*
21  * Portions Copyright (c) 1987, 1993
22  *      The Regents of the University of California.  All rights reserved.
23  *
24  * Redistribution and use in source and binary forms, with or without
25  * modification, are permitted provided that the following conditions
26  * are met:
27  * 1. Redistributions of source code must retain the above copyright
28  *    notice, this list of conditions and the following disclaimer.
29  * 2. Redistributions in binary form must reproduce the above copyright
30  *    notice, this list of conditions and the following disclaimer in the
31  *    documentation and/or other materials provided with the distribution.
32  * 3. All advertising materials mentioning features or use of this software
33  *    must display the following acknowledgement:
34  *      This product includes software developed by the University of
35  *      California, Berkeley and its contributors.
36  * 4. Neither the name of the University nor the names of its contributors
37  *    may be used to endorse or promote products derived from this software
38  *    without specific prior written permission.
39  *
40  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
41  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
42  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
43  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
44  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
45  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
46  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
47  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
48  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
49  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
50  * SUCH DAMAGE.
51  */
52 
53 /* Id */
54 
55 /*! \file */
56 
57 #include <config.h>
58 
59 #include <errno.h>
60 #include <fcntl.h>
61 #include <limits.h>
62 #include <stdlib.h>
63 #include <time.h>		/* Required for utimes on some platforms. */
64 #include <unistd.h>		/* Required for mkstemp on NetBSD. */
65 
66 
67 #include <sys/stat.h>
68 #include <sys/time.h>
69 
70 #include <isc/dir.h>
71 #include <isc/file.h>
72 #include <isc/log.h>
73 #include <isc/mem.h>
74 #include <isc/random.h>
75 #include <isc/string.h>
76 #include <isc/time.h>
77 #include <isc/util.h>
78 
79 #include "errno2result.h"
80 #include "ntp_stdlib.h"		/* NTP change for strlcpy, strlcat */
81 
82 /*
83  * XXXDCL As the API for accessing file statistics undoubtedly gets expanded,
84  * it might be good to provide a mechanism that allows for the results
85  * of a previous stat() to be used again without having to do another stat,
86  * such as perl's mechanism of using "_" in place of a file name to indicate
87  * that the results of the last stat should be used.  But then you get into
88  * annoying MP issues.   BTW, Win32 has stat().
89  */
90 static isc_result_t
91 file_stats(const char *file, struct stat *stats) {
92 	isc_result_t result = ISC_R_SUCCESS;
93 
94 	REQUIRE(file != NULL);
95 	REQUIRE(stats != NULL);
96 
97 	if (stat(file, stats) != 0)
98 		result = isc__errno2result(errno);
99 
100 	return (result);
101 }
102 
103 isc_result_t
104 isc_file_getmodtime(const char *file, isc_time_t *itime) {
105 	isc_result_t result;
106 	struct stat stats;
107 
108 	REQUIRE(file != NULL);
109 	REQUIRE(itime != NULL);
110 
111 	result = file_stats(file, &stats);
112 
113 	if (result == ISC_R_SUCCESS)
114 		/*
115 		 * XXXDCL some operating systems provide nanoseconds, too,
116 		 * such as BSD/OS via st_mtimespec.
117 		 */
118 		isc_time_set(itime, stats.st_mtime, 0);
119 
120 	return (result);
121 }
122 
123 isc_result_t
124 isc_file_settime(const char *file, isc_time_t *itime) {
125 	struct timeval times[2];
126 
127 	REQUIRE(file != NULL && itime != NULL);
128 
129 	/*
130 	 * tv_sec is at least a 32 bit quantity on all platforms we're
131 	 * dealing with, but it is signed on most (all?) of them,
132 	 * so we need to make sure the high bit isn't set.  This unfortunately
133 	 * loses when either:
134 	 *   * tv_sec becomes a signed 64 bit integer but long is 32 bits
135 	 *	and isc_time_seconds > LONG_MAX, or
136 	 *   * isc_time_seconds is changed to be > 32 bits but long is 32 bits
137 	 *      and isc_time_seconds has at least 33 significant bits.
138 	 */
139 	times[0].tv_sec = times[1].tv_sec = (long)isc_time_seconds(itime);
140 
141 	/*
142 	 * Here is the real check for the high bit being set.
143 	 */
144 	if ((times[0].tv_sec &
145 	     (1ULL << (sizeof(times[0].tv_sec) * CHAR_BIT - 1))) != 0)
146 		return (ISC_R_RANGE);
147 
148 	/*
149 	 * isc_time_nanoseconds guarantees a value that divided by 1000 will
150 	 * fit into the minimum possible size tv_usec field.  Unfortunately,
151 	 * we don't know what that type is so can't cast directly ... but
152 	 * we can at least cast to signed so the IRIX compiler shuts up.
153 	 */
154 	times[0].tv_usec = times[1].tv_usec =
155 		(isc_int32_t)(isc_time_nanoseconds(itime) / 1000);
156 
157 	if (utimes(file, times) < 0)
158 		return (isc__errno2result(errno));
159 
160 	return (ISC_R_SUCCESS);
161 }
162 
163 #undef TEMPLATE
164 #define TEMPLATE "tmp-XXXXXXXXXX" /*%< 14 characters. */
165 
166 isc_result_t
167 isc_file_mktemplate(const char *path, char *buf, size_t buflen) {
168 	return (isc_file_template(path, TEMPLATE, buf, buflen));
169 }
170 
171 isc_result_t
172 isc_file_template(const char *path, const char *templet, char *buf,
173 			size_t buflen) {
174 	char *s;
175 
176 	REQUIRE(path != NULL);
177 	REQUIRE(templet != NULL);
178 	REQUIRE(buf != NULL);
179 
180 	s = strrchr(templet, '/');
181 	if (s != NULL)
182 		templet = s + 1;
183 
184 	s = strrchr(path, '/');
185 
186 	if (s != NULL) {
187 		if ((s - path + 1 + strlen(templet) + 1) > buflen)
188 			return (ISC_R_NOSPACE);
189 
190 		strlcpy(buf, path, buflen);
191 		buf[s - path + 1] = '\0';
192 		strlcat(buf, templet, buflen);
193 	} else {
194 		if ((strlen(templet) + 1) > buflen)
195 			return (ISC_R_NOSPACE);
196 
197 		strlcpy(buf, templet, buflen);
198 	}
199 
200 	return (ISC_R_SUCCESS);
201 }
202 
203 static char alphnum[] =
204 	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
205 
206 isc_result_t
207 isc_file_renameunique(const char *file, char *templet) {
208 	char *x;
209 	char *cp;
210 	isc_uint32_t which;
211 
212 	REQUIRE(file != NULL);
213 	REQUIRE(templet != NULL);
214 
215 	cp = templet;
216 	while (*cp != '\0')
217 		cp++;
218 	if (cp == templet)
219 		return (ISC_R_FAILURE);
220 
221 	x = cp--;
222 	while (cp >= templet && *cp == 'X') {
223 		isc_random_get(&which);
224 		*cp = alphnum[which % (sizeof(alphnum) - 1)];
225 		x = cp--;
226 	}
227 	while (link(file, templet) == -1) {
228 		if (errno != EEXIST)
229 			return (isc__errno2result(errno));
230 		for (cp = x;;) {
231 			char *t;
232 			if (*cp == '\0')
233 				return (ISC_R_FAILURE);
234 			t = strchr(alphnum, *cp);
235 			if (t == NULL || *++t == '\0')
236 				*cp++ = alphnum[0];
237 			else {
238 				*cp = *t;
239 				break;
240 			}
241 		}
242 	}
243 	if (unlink(file) < 0)
244 		if (errno != ENOENT)
245 			return (isc__errno2result(errno));
246 	return (ISC_R_SUCCESS);
247 }
248 
249 isc_result_t
250 isc_file_openunique(char *templet, FILE **fp) {
251 	int mode = S_IWUSR|S_IRUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
252 	return (isc_file_openuniquemode(templet, mode, fp));
253 }
254 
255 isc_result_t
256 isc_file_openuniqueprivate(char *templet, FILE **fp) {
257 	int mode = S_IWUSR|S_IRUSR;
258 	return (isc_file_openuniquemode(templet, mode, fp));
259 }
260 
261 isc_result_t
262 isc_file_openuniquemode(char *templet, int mode, FILE **fp) {
263 	int fd;
264 	FILE *f;
265 	isc_result_t result = ISC_R_SUCCESS;
266 	char *x;
267 	char *cp;
268 	isc_uint32_t which;
269 
270 	REQUIRE(templet != NULL);
271 	REQUIRE(fp != NULL && *fp == NULL);
272 
273 	cp = templet;
274 	while (*cp != '\0')
275 		cp++;
276 	if (cp == templet)
277 		return (ISC_R_FAILURE);
278 
279 	x = cp--;
280 	while (cp >= templet && *cp == 'X') {
281 		isc_random_get(&which);
282 		*cp = alphnum[which % (sizeof(alphnum) - 1)];
283 		x = cp--;
284 	}
285 
286 
287 	while ((fd = open(templet, O_RDWR|O_CREAT|O_EXCL, mode)) == -1) {
288 		if (errno != EEXIST)
289 			return (isc__errno2result(errno));
290 		for (cp = x;;) {
291 			char *t;
292 			if (*cp == '\0')
293 				return (ISC_R_FAILURE);
294 			t = strchr(alphnum, *cp);
295 			if (t == NULL || *++t == '\0')
296 				*cp++ = alphnum[0];
297 			else {
298 				*cp = *t;
299 				break;
300 			}
301 		}
302 	}
303 	f = fdopen(fd, "w+");
304 	if (f == NULL) {
305 		result = isc__errno2result(errno);
306 		if (remove(templet) < 0) {
307 			isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
308 				      ISC_LOGMODULE_FILE, ISC_LOG_ERROR,
309 				      "remove '%s': failed", templet);
310 		}
311 		(void)close(fd);
312 	} else
313 		*fp = f;
314 
315 	return (result);
316 }
317 
318 isc_result_t
319 isc_file_remove(const char *filename) {
320 	int r;
321 
322 	REQUIRE(filename != NULL);
323 
324 	r = unlink(filename);
325 	if (r == 0)
326 		return (ISC_R_SUCCESS);
327 	else
328 		return (isc__errno2result(errno));
329 }
330 
331 isc_result_t
332 isc_file_rename(const char *oldname, const char *newname) {
333 	int r;
334 
335 	REQUIRE(oldname != NULL);
336 	REQUIRE(newname != NULL);
337 
338 	r = rename(oldname, newname);
339 	if (r == 0)
340 		return (ISC_R_SUCCESS);
341 	else
342 		return (isc__errno2result(errno));
343 }
344 
345 isc_boolean_t
346 isc_file_exists(const char *pathname) {
347 	struct stat stats;
348 
349 	REQUIRE(pathname != NULL);
350 
351 	return (ISC_TF(file_stats(pathname, &stats) == ISC_R_SUCCESS));
352 }
353 
354 isc_result_t
355 isc_file_isplainfile(const char *filename) {
356 	/*
357 	 * This function returns success if filename is a plain file.
358 	 */
359 	struct stat filestat;
360 	memset(&filestat,0,sizeof(struct stat));
361 
362 	if ((stat(filename, &filestat)) == -1)
363 		return(isc__errno2result(errno));
364 
365 	if(! S_ISREG(filestat.st_mode))
366 		return(ISC_R_INVALIDFILE);
367 
368 	return(ISC_R_SUCCESS);
369 }
370 
371 isc_boolean_t
372 isc_file_isabsolute(const char *filename) {
373 	REQUIRE(filename != NULL);
374 	return (ISC_TF(filename[0] == '/'));
375 }
376 
377 isc_boolean_t
378 isc_file_iscurrentdir(const char *filename) {
379 	REQUIRE(filename != NULL);
380 	return (ISC_TF(filename[0] == '.' && filename[1] == '\0'));
381 }
382 
383 isc_boolean_t
384 isc_file_ischdiridempotent(const char *filename) {
385 	REQUIRE(filename != NULL);
386 	if (isc_file_isabsolute(filename))
387 		return (ISC_TRUE);
388 	if (isc_file_iscurrentdir(filename))
389 		return (ISC_TRUE);
390 	return (ISC_FALSE);
391 }
392 
393 const char *
394 isc_file_basename(const char *filename) {
395 	char *s;
396 
397 	REQUIRE(filename != NULL);
398 
399 	s = strrchr(filename, '/');
400 	if (s == NULL)
401 		return (filename);
402 
403 	return (s + 1);
404 }
405 
406 isc_result_t
407 isc_file_progname(const char *filename, char *buf, size_t buflen) {
408 	const char *base;
409 	size_t len;
410 
411 	REQUIRE(filename != NULL);
412 	REQUIRE(buf != NULL);
413 
414 	base = isc_file_basename(filename);
415 	len = strlen(base) + 1;
416 
417 	if (len > buflen)
418 		return (ISC_R_NOSPACE);
419 	memcpy(buf, base, len);
420 
421 	return (ISC_R_SUCCESS);
422 }
423 
424 /*
425  * Put the absolute name of the current directory into 'dirname', which is
426  * a buffer of at least 'length' characters.  End the string with the
427  * appropriate path separator, such that the final product could be
428  * concatenated with a relative pathname to make a valid pathname string.
429  */
430 static isc_result_t
431 dir_current(char *dirname, size_t length) {
432 	char *cwd;
433 	isc_result_t result = ISC_R_SUCCESS;
434 
435 	REQUIRE(dirname != NULL);
436 	REQUIRE(length > 0U);
437 
438 	cwd = getcwd(dirname, length);
439 
440 	if (cwd == NULL) {
441 		if (errno == ERANGE)
442 			result = ISC_R_NOSPACE;
443 		else
444 			result = isc__errno2result(errno);
445 	} else {
446 		if (strlen(dirname) + 1 == length)
447 			result = ISC_R_NOSPACE;
448 		else if (dirname[1] != '\0')
449 			strlcat(dirname, "/", length);
450 	}
451 
452 	return (result);
453 }
454 
455 isc_result_t
456 isc_file_absolutepath(const char *filename, char *path, size_t pathlen) {
457 	isc_result_t result;
458 	result = dir_current(path, pathlen);
459 	if (result != ISC_R_SUCCESS)
460 		return (result);
461 	if (strlen(path) + strlen(filename) + 1 > pathlen)
462 		return (ISC_R_NOSPACE);
463 	strlcat(path, filename, pathlen);
464 	return (ISC_R_SUCCESS);
465 }
466 
467 isc_result_t
468 isc_file_truncate(const char *filename, isc_offset_t size) {
469 	isc_result_t result = ISC_R_SUCCESS;
470 
471 	if (truncate(filename, size) < 0)
472 		result = isc__errno2result(errno);
473 	return (result);
474 }
475 
476 isc_result_t
477 isc_file_safecreate(const char *filename, FILE **fp) {
478 	isc_result_t result;
479 	int flags;
480 	struct stat sb;
481 	FILE *f;
482 	int fd;
483 
484 	REQUIRE(filename != NULL);
485 	REQUIRE(fp != NULL && *fp == NULL);
486 
487 	result = file_stats(filename, &sb);
488 	if (result == ISC_R_SUCCESS) {
489 		if ((sb.st_mode & S_IFREG) == 0)
490 			return (ISC_R_INVALIDFILE);
491 		flags = O_WRONLY | O_TRUNC;
492 	} else if (result == ISC_R_FILENOTFOUND) {
493 		flags = O_WRONLY | O_CREAT | O_EXCL;
494 	} else
495 		return (result);
496 
497 	fd = open(filename, flags, S_IRUSR | S_IWUSR);
498 	if (fd == -1)
499 		return (isc__errno2result(errno));
500 
501 	f = fdopen(fd, "w");
502 	if (f == NULL) {
503 		result = isc__errno2result(errno);
504 		close(fd);
505 		return (result);
506 	}
507 
508 	*fp = f;
509 	return (ISC_R_SUCCESS);
510 }
511 
512 isc_result_t
513 isc_file_splitpath(isc_mem_t *mctx, char *path, char **dirnam, char **basenam)
514 {
515 	char *dir, *file, *slash;
516 
517 	REQUIRE(path != NULL);
518 
519 	slash = strrchr(path, '/');
520 
521 	if (slash == path) {
522 		file = ++slash;
523 		dir = isc_mem_strdup(mctx, "/");
524 	} else if (slash != NULL) {
525 		file = ++slash;
526 		dir = isc_mem_allocate(mctx, slash - path);
527 		if (dir != NULL)
528 			strlcpy(dir, path, slash - path);
529 	} else {
530 		file = path;
531 		dir = isc_mem_strdup(mctx, ".");
532 	}
533 
534 	if (dir == NULL)
535 		return (ISC_R_NOMEMORY);
536 
537 	if (*file == '\0') {
538 		isc_mem_free(mctx, dir);
539 		return (ISC_R_INVALIDFILE);
540 	}
541 
542 	*dirnam = dir;
543 	*basenam = file;
544 
545 	return (ISC_R_SUCCESS);
546 }
547