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