1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "apr.h" 18#include "apr_arch_file_io.h" 19#include "apr_file_io.h" 20#include "apr_strings.h" 21#include "apr_portable.h" 22#include "apr_arch_atime.h" 23 24#if APR_HAVE_ERRNO_H 25#include <errno.h> 26#endif 27#if APR_HAVE_STRING_H 28#include <string.h> 29#endif 30#if APR_HAVE_DIRENT_H 31#include <dirent.h> 32#endif 33#ifdef HAVE_SYS_STAT_H 34#include <sys/stat.h> 35#endif 36 37 38static apr_status_t dir_cleanup(void *thedir) 39{ 40 apr_dir_t *dir = thedir; 41 if (dir->dirhand != INVALID_HANDLE_VALUE && !FindClose(dir->dirhand)) { 42 return apr_get_os_error(); 43 } 44 dir->dirhand = INVALID_HANDLE_VALUE; 45 return APR_SUCCESS; 46} 47 48APR_DECLARE(apr_status_t) apr_dir_open(apr_dir_t **new, const char *dirname, 49 apr_pool_t *pool) 50{ 51 apr_status_t rv; 52 53 apr_size_t len = strlen(dirname); 54 (*new) = apr_pcalloc(pool, sizeof(apr_dir_t)); 55 /* Leave room here to add and pop the '*' wildcard for FindFirstFile 56 * and double-null terminate so we have one character to change. 57 */ 58 (*new)->dirname = apr_palloc(pool, len + 3); 59 memcpy((*new)->dirname, dirname, len); 60 if (len && (*new)->dirname[len - 1] != '/') { 61 (*new)->dirname[len++] = '/'; 62 } 63 (*new)->dirname[len++] = '\0'; 64 (*new)->dirname[len] = '\0'; 65 66#if APR_HAS_UNICODE_FS 67 IF_WIN_OS_IS_UNICODE 68 { 69 /* Create a buffer for the longest file name we will ever see 70 */ 71 (*new)->w.entry = apr_pcalloc(pool, sizeof(WIN32_FIND_DATAW)); 72 (*new)->name = apr_pcalloc(pool, APR_FILE_MAX * 3 + 1); 73 } 74#endif 75#if APR_HAS_ANSI_FS 76 ELSE_WIN_OS_IS_ANSI 77 { 78 /* Note that we won't open a directory that is greater than MAX_PATH, 79 * counting the additional '/' '*' wildcard suffix. If a * won't fit 80 * then neither will any other file name within the directory. 81 * The length not including the trailing '*' is stored as rootlen, to 82 * skip over all paths which are too long. 83 */ 84 if (len >= APR_PATH_MAX) { 85 (*new) = NULL; 86 return APR_ENAMETOOLONG; 87 } 88 (*new)->n.entry = apr_pcalloc(pool, sizeof(WIN32_FIND_DATAW)); 89 } 90#endif 91 (*new)->rootlen = len - 1; 92 (*new)->pool = pool; 93 (*new)->dirhand = INVALID_HANDLE_VALUE; 94 apr_pool_cleanup_register((*new)->pool, (void *)(*new), dir_cleanup, 95 apr_pool_cleanup_null); 96 97 rv = apr_dir_read(NULL, 0, *new); 98 if (rv != APR_SUCCESS) { 99 dir_cleanup(*new); 100 *new = NULL; 101 } 102 103 return rv; 104} 105 106APR_DECLARE(apr_status_t) apr_dir_close(apr_dir_t *dir) 107{ 108 apr_pool_cleanup_kill(dir->pool, dir, dir_cleanup); 109 return dir_cleanup(dir); 110} 111 112APR_DECLARE(apr_status_t) apr_dir_read(apr_finfo_t *finfo, apr_int32_t wanted, 113 apr_dir_t *thedir) 114{ 115 apr_status_t rv; 116 char *fname; 117 /* The while loops below allow us to skip all invalid file names, so that 118 * we aren't reporting any files where their absolute paths are too long. 119 */ 120#if APR_HAS_UNICODE_FS 121 apr_wchar_t wdirname[APR_PATH_MAX]; 122 apr_wchar_t *eos = NULL; 123 IF_WIN_OS_IS_UNICODE 124 { 125 /* This code path is always be invoked by apr_dir_open or 126 * apr_dir_rewind, so return without filling out the finfo. 127 */ 128 if (thedir->dirhand == INVALID_HANDLE_VALUE) 129 { 130 apr_status_t rv; 131 if ((rv = utf8_to_unicode_path(wdirname, sizeof(wdirname) 132 / sizeof(apr_wchar_t), 133 thedir->dirname))) { 134 return rv; 135 } 136 eos = wcschr(wdirname, '\0'); 137 eos[0] = '*'; 138 eos[1] = '\0'; 139 thedir->dirhand = FindFirstFileW(wdirname, thedir->w.entry); 140 eos[0] = '\0'; 141 if (thedir->dirhand == INVALID_HANDLE_VALUE) { 142 return apr_get_os_error(); 143 } 144 thedir->bof = 1; 145 return APR_SUCCESS; 146 } 147 else if (thedir->bof) { 148 /* Noop - we already called FindFirstFileW from 149 * either apr_dir_open or apr_dir_rewind ... use 150 * that first record. 151 */ 152 thedir->bof = 0; 153 } 154 else if (!FindNextFileW(thedir->dirhand, thedir->w.entry)) { 155 return apr_get_os_error(); 156 } 157 158 while (thedir->rootlen && 159 thedir->rootlen + wcslen(thedir->w.entry->cFileName) >= APR_PATH_MAX) 160 { 161 if (!FindNextFileW(thedir->dirhand, thedir->w.entry)) { 162 return apr_get_os_error(); 163 } 164 } 165 if ((rv = unicode_to_utf8_path(thedir->name, APR_FILE_MAX * 3 + 1, 166 thedir->w.entry->cFileName))) 167 return rv; 168 fname = thedir->name; 169 } 170#endif 171#if APR_HAS_ANSI_FS 172 ELSE_WIN_OS_IS_ANSI 173 { 174 /* This code path is always be invoked by apr_dir_open or 175 * apr_dir_rewind, so return without filling out the finfo. 176 */ 177 if (thedir->dirhand == INVALID_HANDLE_VALUE) { 178 /* '/' terminated, so add the '*' and pop it when we finish */ 179 char *eop = strchr(thedir->dirname, '\0'); 180 eop[0] = '*'; 181 eop[1] = '\0'; 182 thedir->dirhand = FindFirstFileA(thedir->dirname, 183 thedir->n.entry); 184 eop[0] = '\0'; 185 if (thedir->dirhand == INVALID_HANDLE_VALUE) { 186 return apr_get_os_error(); 187 } 188 thedir->bof = 1; 189 return APR_SUCCESS; 190 } 191 else if (thedir->bof) { 192 /* Noop - we already called FindFirstFileW from 193 * either apr_dir_open or apr_dir_rewind ... use 194 * that first record. 195 */ 196 thedir->bof = 0; 197 } 198 else if (!FindNextFileA(thedir->dirhand, thedir->n.entry)) { 199 return apr_get_os_error(); 200 } 201 while (thedir->rootlen && 202 thedir->rootlen + strlen(thedir->n.entry->cFileName) >= MAX_PATH) 203 { 204 if (!FindNextFileA(thedir->dirhand, thedir->n.entry)) { 205 return apr_get_os_error(); 206 } 207 } 208 fname = thedir->n.entry->cFileName; 209 } 210#endif 211 212 fillin_fileinfo(finfo, (WIN32_FILE_ATTRIBUTE_DATA *) thedir->w.entry, 213 0, wanted); 214 finfo->pool = thedir->pool; 215 216 finfo->valid |= APR_FINFO_NAME; 217 finfo->name = fname; 218 219 if (wanted &= ~finfo->valid) { 220 /* Go back and get more_info if we can't answer the whole inquiry 221 */ 222#if APR_HAS_UNICODE_FS 223 IF_WIN_OS_IS_UNICODE 224 { 225 /* Almost all our work is done. Tack on the wide file name 226 * to the end of the wdirname (already / delimited) 227 */ 228 if (!eos) 229 eos = wcschr(wdirname, '\0'); 230 wcscpy(eos, thedir->w.entry->cFileName); 231 rv = more_finfo(finfo, wdirname, wanted, MORE_OF_WFSPEC); 232 eos[0] = '\0'; 233 return rv; 234 } 235#endif 236#if APR_HAS_ANSI_FS 237 ELSE_WIN_OS_IS_ANSI 238 { 239#if APR_HAS_UNICODE_FS 240 /* Don't waste stack space on a second buffer, the one we set 241 * aside for the wide directory name is twice what we need. 242 */ 243 char *fspec = (char*)wdirname; 244#else 245 char fspec[APR_PATH_MAX]; 246#endif 247 apr_size_t dirlen = strlen(thedir->dirname); 248 if (dirlen >= sizeof(fspec)) 249 dirlen = sizeof(fspec) - 1; 250 apr_cpystrn(fspec, thedir->dirname, sizeof(fspec)); 251 apr_cpystrn(fspec + dirlen, fname, sizeof(fspec) - dirlen); 252 return more_finfo(finfo, fspec, wanted, MORE_OF_FSPEC); 253 } 254#endif 255 } 256 257 return APR_SUCCESS; 258} 259 260APR_DECLARE(apr_status_t) apr_dir_rewind(apr_dir_t *dir) 261{ 262 apr_status_t rv; 263 264 /* this will mark the handle as invalid and we'll open it 265 * again if apr_dir_read() is subsequently called 266 */ 267 rv = dir_cleanup(dir); 268 269 if (rv == APR_SUCCESS) 270 rv = apr_dir_read(NULL, 0, dir); 271 272 return rv; 273} 274 275APR_DECLARE(apr_status_t) apr_dir_make(const char *path, apr_fileperms_t perm, 276 apr_pool_t *pool) 277{ 278#if APR_HAS_UNICODE_FS 279 IF_WIN_OS_IS_UNICODE 280 { 281 apr_wchar_t wpath[APR_PATH_MAX]; 282 apr_status_t rv; 283 if ((rv = utf8_to_unicode_path(wpath, 284 sizeof(wpath) / sizeof(apr_wchar_t), 285 path))) { 286 return rv; 287 } 288 if (!CreateDirectoryW(wpath, NULL)) { 289 return apr_get_os_error(); 290 } 291 } 292#endif 293#if APR_HAS_ANSI_FS 294 ELSE_WIN_OS_IS_ANSI 295 if (!CreateDirectory(path, NULL)) { 296 return apr_get_os_error(); 297 } 298#endif 299 return APR_SUCCESS; 300} 301 302 303static apr_status_t dir_make_parent(char *path, 304 apr_fileperms_t perm, 305 apr_pool_t *pool) 306{ 307 apr_status_t rv; 308 char *ch = strrchr(path, '\\'); 309 if (!ch) { 310 return APR_ENOENT; 311 } 312 313 *ch = '\0'; 314 rv = apr_dir_make (path, perm, pool); /* Try to make straight off */ 315 316 if (APR_STATUS_IS_ENOENT(rv)) { /* Missing an intermediate dir */ 317 rv = dir_make_parent(path, perm, pool); 318 319 if (rv == APR_SUCCESS) { 320 rv = apr_dir_make (path, perm, pool); /* And complete the path */ 321 } 322 } 323 324 *ch = '\\'; /* Always replace the slash before returning */ 325 return rv; 326} 327 328APR_DECLARE(apr_status_t) apr_dir_make_recursive(const char *path, 329 apr_fileperms_t perm, 330 apr_pool_t *pool) 331{ 332 apr_status_t rv = 0; 333 334 rv = apr_dir_make (path, perm, pool); /* Try to make PATH right out */ 335 336 if (APR_STATUS_IS_ENOENT(rv)) { /* Missing an intermediate dir */ 337 char *dir; 338 339 rv = apr_filepath_merge(&dir, "", path, APR_FILEPATH_NATIVE, pool); 340 341 if (rv == APR_SUCCESS) 342 rv = dir_make_parent(dir, perm, pool); /* Make intermediate dirs */ 343 344 if (rv == APR_SUCCESS) 345 rv = apr_dir_make (dir, perm, pool); /* And complete the path */ 346 } 347 348 /* 349 * It's OK if PATH exists. Timing issues can lead to the second 350 * apr_dir_make being called on existing dir, therefore this check 351 * has to come last. 352 */ 353 if (APR_STATUS_IS_EEXIST(rv)) 354 return APR_SUCCESS; 355 356 return rv; 357} 358 359 360APR_DECLARE(apr_status_t) apr_dir_remove(const char *path, apr_pool_t *pool) 361{ 362#if APR_HAS_UNICODE_FS 363 IF_WIN_OS_IS_UNICODE 364 { 365 apr_wchar_t wpath[APR_PATH_MAX]; 366 apr_status_t rv; 367 if ((rv = utf8_to_unicode_path(wpath, 368 sizeof(wpath) / sizeof(apr_wchar_t), 369 path))) { 370 return rv; 371 } 372 if (!RemoveDirectoryW(wpath)) { 373 return apr_get_os_error(); 374 } 375 } 376#endif 377#if APR_HAS_ANSI_FS 378 ELSE_WIN_OS_IS_ANSI 379 if (!RemoveDirectory(path)) { 380 return apr_get_os_error(); 381 } 382#endif 383 return APR_SUCCESS; 384} 385 386APR_DECLARE(apr_status_t) apr_os_dir_get(apr_os_dir_t **thedir, 387 apr_dir_t *dir) 388{ 389 if (dir == NULL) { 390 return APR_ENODIR; 391 } 392 *thedir = dir->dirhand; 393 return APR_SUCCESS; 394} 395 396APR_DECLARE(apr_status_t) apr_os_dir_put(apr_dir_t **dir, 397 apr_os_dir_t *thedir, 398 apr_pool_t *pool) 399{ 400 return APR_ENOTIMPL; 401} 402