1/*	$NetBSD: dir.c,v 1.2 2024/02/21 22:52:28 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/*! \file */
17
18#include <ctype.h>
19#include <errno.h>
20#include <sys/stat.h>
21#include <sys/types.h>
22#include <unistd.h>
23
24#include <isc/dir.h>
25#include <isc/magic.h>
26#include <isc/netdb.h>
27#include <isc/print.h>
28#include <isc/string.h>
29#include <isc/util.h>
30
31#include "errno2result.h"
32
33#define ISC_DIR_MAGIC  ISC_MAGIC('D', 'I', 'R', '*')
34#define VALID_DIR(dir) ISC_MAGIC_VALID(dir, ISC_DIR_MAGIC)
35
36void
37isc_dir_init(isc_dir_t *dir) {
38	REQUIRE(dir != NULL);
39
40	dir->entry.name[0] = '\0';
41	dir->entry.length = 0;
42
43	dir->handle = NULL;
44
45	dir->magic = ISC_DIR_MAGIC;
46}
47
48/*!
49 * \brief Allocate workspace and open directory stream. If either one fails,
50 * NULL will be returned.
51 */
52isc_result_t
53isc_dir_open(isc_dir_t *dir, const char *dirname) {
54	char *p;
55	isc_result_t result = ISC_R_SUCCESS;
56
57	REQUIRE(VALID_DIR(dir));
58	REQUIRE(dirname != NULL);
59
60	/*
61	 * Copy directory name.  Need to have enough space for the name,
62	 * a possible path separator, the wildcard, and the final NUL.
63	 */
64	if (strlen(dirname) + 3 > sizeof(dir->dirname)) {
65		/* XXXDCL ? */
66		return (ISC_R_NOSPACE);
67	}
68	strlcpy(dir->dirname, dirname, sizeof(dir->dirname));
69
70	/*
71	 * Append path separator, if needed, and "*".
72	 */
73	p = dir->dirname + strlen(dir->dirname);
74	if (dir->dirname < p && *(p - 1) != '/') {
75		*p++ = '/';
76	}
77	*p++ = '*';
78	*p = '\0';
79
80	/*
81	 * Open stream.
82	 */
83	dir->handle = opendir(dirname);
84
85	if (dir->handle == NULL) {
86		return (isc__errno2result(errno));
87	}
88
89	return (result);
90}
91
92/*!
93 * \brief Return previously retrieved file or get next one.
94 *
95 * Unix's dirent has
96 * separate open and read functions, but the Win32 and DOS interfaces open
97 * the dir stream and reads the first file in one operation.
98 */
99isc_result_t
100isc_dir_read(isc_dir_t *dir) {
101	struct dirent *entry;
102
103	REQUIRE(VALID_DIR(dir) && dir->handle != NULL);
104
105	/*
106	 * Fetch next file in directory.
107	 */
108	entry = readdir(dir->handle);
109
110	if (entry == NULL) {
111		return (ISC_R_NOMORE);
112	}
113
114	/*
115	 * Make sure that the space for the name is long enough.
116	 */
117	if (sizeof(dir->entry.name) <= strlen(entry->d_name)) {
118		return (ISC_R_UNEXPECTED);
119	}
120
121	strlcpy(dir->entry.name, entry->d_name, sizeof(dir->entry.name));
122
123	/*
124	 * Some dirents have d_namlen, but it is not portable.
125	 */
126	dir->entry.length = strlen(entry->d_name);
127
128	return (ISC_R_SUCCESS);
129}
130
131/*!
132 * \brief Close directory stream.
133 */
134void
135isc_dir_close(isc_dir_t *dir) {
136	REQUIRE(VALID_DIR(dir) && dir->handle != NULL);
137
138	(void)closedir(dir->handle);
139	dir->handle = NULL;
140}
141
142/*!
143 * \brief Reposition directory stream at start.
144 */
145isc_result_t
146isc_dir_reset(isc_dir_t *dir) {
147	REQUIRE(VALID_DIR(dir) && dir->handle != NULL);
148
149	rewinddir(dir->handle);
150
151	return (ISC_R_SUCCESS);
152}
153
154isc_result_t
155isc_dir_chdir(const char *dirname) {
156	/*!
157	 * \brief Change the current directory to 'dirname'.
158	 */
159
160	REQUIRE(dirname != NULL);
161
162	if (chdir(dirname) < 0) {
163		return (isc__errno2result(errno));
164	}
165
166	return (ISC_R_SUCCESS);
167}
168
169isc_result_t
170isc_dir_chroot(const char *dirname) {
171#ifdef HAVE_CHROOT
172	void *tmp;
173#endif /* ifdef HAVE_CHROOT */
174
175	REQUIRE(dirname != NULL);
176
177#ifdef HAVE_CHROOT
178	/*
179	 * Try to use getservbyname and getprotobyname before chroot.
180	 * If WKS records are used in a zone under chroot, Name Service Switch
181	 * may fail to load library in chroot.
182	 * Do not report errors if it fails, we do not need any result now.
183	 */
184	tmp = getprotobyname("udp");
185	if (tmp != NULL) {
186		(void)getservbyname("domain", "udp");
187	}
188
189	if (chroot(dirname) < 0 || chdir("/") < 0) {
190		return (isc__errno2result(errno));
191	}
192
193	return (ISC_R_SUCCESS);
194#else  /* ifdef HAVE_CHROOT */
195	return (ISC_R_NOTIMPLEMENTED);
196#endif /* ifdef HAVE_CHROOT */
197}
198
199isc_result_t
200isc_dir_createunique(char *templet) {
201	isc_result_t result;
202	char *x;
203	char *p;
204	int i;
205	int pid;
206
207	REQUIRE(templet != NULL);
208
209	/*!
210	 * \brief mkdtemp is not portable, so this emulates it.
211	 */
212
213	pid = getpid();
214
215	/*
216	 * Replace trailing Xs with the process-id, zero-filled.
217	 */
218	for (x = templet + strlen(templet) - 1; *x == 'X' && x >= templet;
219	     x--, pid /= 10)
220	{
221		*x = pid % 10 + '0';
222	}
223
224	x++; /* Set x to start of ex-Xs. */
225
226	do {
227		i = mkdir(templet, 0700);
228		if (i == 0 || errno != EEXIST) {
229			break;
230		}
231
232		/*
233		 * The BSD algorithm.
234		 */
235		p = x;
236		while (*p != '\0') {
237			if (isdigit((unsigned char)*p)) {
238				*p = 'a';
239			} else if (*p != 'z') {
240				++*p;
241			} else {
242				/*
243				 * Reset character and move to next.
244				 */
245				*p++ = 'a';
246				continue;
247			}
248
249			break;
250		}
251
252		if (*p == '\0') {
253			/*
254			 * Tried all combinations.  errno should already
255			 * be EEXIST, but ensure it is anyway for
256			 * isc__errno2result().
257			 */
258			errno = EEXIST;
259			break;
260		}
261	} while (1);
262
263	if (i == -1) {
264		result = isc__errno2result(errno);
265	} else {
266		result = ISC_R_SUCCESS;
267	}
268
269	return (result);
270}
271