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