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 <aclapi.h> 19#include "apr_private.h" 20#include "apr_arch_file_io.h" 21#include "apr_file_io.h" 22#include "apr_general.h" 23#include "apr_strings.h" 24#include "apr_errno.h" 25#include "apr_time.h" 26#include <sys/stat.h> 27#include "apr_arch_atime.h" 28#include "apr_arch_misc.h" 29 30/* We have to assure that the file name contains no '*'s, or other 31 * wildcards when using FindFirstFile to recover the true file name. 32 */ 33static apr_status_t test_safe_name(const char *name) 34{ 35 /* Only accept ':' in the second position of the filename, 36 * as the drive letter delimiter: 37 */ 38 if (apr_isalpha(*name) && (name[1] == ':')) { 39 name += 2; 40 } 41 while (*name) { 42 if (!IS_FNCHAR(*name) && (*name != '\\') && (*name != '/')) { 43 if (*name == '?' || *name == '*') 44 return APR_EPATHWILD; 45 else 46 return APR_EBADPATH; 47 } 48 ++name; 49 } 50 return APR_SUCCESS; 51} 52 53static apr_status_t free_localheap(void *heap) { 54 LocalFree(heap); 55 return APR_SUCCESS; 56} 57 58static apr_gid_t worldid = NULL; 59 60static void free_world(void) 61{ 62 if (worldid) { 63 FreeSid(worldid); 64 worldid = NULL; 65 } 66} 67 68/* Left bit shifts from World scope to given scope */ 69typedef enum prot_scope_e { 70 prot_scope_world = 0, 71 prot_scope_group = 4, 72 prot_scope_user = 8 73} prot_scope_e; 74 75static apr_fileperms_t convert_prot(ACCESS_MASK acc, prot_scope_e scope) 76{ 77 /* These choices are based on the single filesystem bit that controls 78 * the given behavior. They are -not- recommended for any set protection 79 * function, such a function should -set- use GENERIC_READ/WRITE/EXECUTE 80 */ 81 apr_fileperms_t prot = 0; 82 if (acc & FILE_EXECUTE) 83 prot |= APR_WEXECUTE; 84 if (acc & FILE_WRITE_DATA) 85 prot |= APR_WWRITE; 86 if (acc & FILE_READ_DATA) 87 prot |= APR_WREAD; 88 return (prot << scope); 89} 90 91static void resolve_prot(apr_finfo_t *finfo, apr_int32_t wanted, PACL dacl) 92{ 93 TRUSTEE_W ident = {NULL, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID}; 94 ACCESS_MASK acc; 95 /* 96 * This function is only invoked for WinNT, 97 * there is no reason for os_level testing here. 98 */ 99 if ((wanted & APR_FINFO_WPROT) && !worldid) { 100 SID_IDENTIFIER_AUTHORITY SIDAuth = {SECURITY_WORLD_SID_AUTHORITY}; 101 if (AllocateAndInitializeSid(&SIDAuth, 1, SECURITY_WORLD_RID, 102 0, 0, 0, 0, 0, 0, 0, &worldid)) 103 atexit(free_world); 104 else 105 worldid = NULL; 106 } 107 if ((wanted & APR_FINFO_UPROT) && (finfo->valid & APR_FINFO_USER)) { 108 ident.TrusteeType = TRUSTEE_IS_USER; 109 ident.ptstrName = finfo->user; 110 /* GetEffectiveRightsFromAcl isn't supported under Win9x, 111 * which shouldn't come as a surprize. Since we are passing 112 * TRUSTEE_IS_SID, always skip the A->W layer. 113 */ 114 if (GetEffectiveRightsFromAclW(dacl, &ident, &acc) == ERROR_SUCCESS) { 115 finfo->protection |= convert_prot(acc, prot_scope_user); 116 finfo->valid |= APR_FINFO_UPROT; 117 } 118 } 119 /* Windows NT: did not return group rights. 120 * Windows 2000 returns group rights information. 121 * Since WinNT kernels don't follow the unix model of 122 * group associations, this all all pretty mute. 123 */ 124 if ((wanted & APR_FINFO_GPROT) && (finfo->valid & APR_FINFO_GROUP)) { 125 ident.TrusteeType = TRUSTEE_IS_GROUP; 126 ident.ptstrName = finfo->group; 127 if (GetEffectiveRightsFromAclW(dacl, &ident, &acc) == ERROR_SUCCESS) { 128 finfo->protection |= convert_prot(acc, prot_scope_group); 129 finfo->valid |= APR_FINFO_GPROT; 130 } 131 } 132 if ((wanted & APR_FINFO_WPROT) && (worldid)) { 133 ident.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; 134 ident.ptstrName = worldid; 135 if (GetEffectiveRightsFromAclW(dacl, &ident, &acc) == ERROR_SUCCESS) { 136 finfo->protection |= convert_prot(acc, prot_scope_world); 137 finfo->valid |= APR_FINFO_WPROT; 138 } 139 } 140} 141 142static apr_status_t resolve_ident(apr_finfo_t *finfo, const char *fname, 143 apr_int32_t wanted, apr_pool_t *pool) 144{ 145 apr_file_t *thefile = NULL; 146 apr_status_t rv; 147 /* 148 * NT5 (W2K) only supports symlinks in the same manner as mount points. 149 * This code should eventually take that into account, for now treat 150 * every reparse point as a symlink... 151 * 152 * We must open the file with READ_CONTROL if we plan to retrieve the 153 * user, group or permissions. 154 */ 155 156 if ((rv = apr_file_open(&thefile, fname, APR_OPENINFO 157 | ((wanted & APR_FINFO_LINK) ? APR_OPENLINK : 0) 158 | ((wanted & (APR_FINFO_PROT | APR_FINFO_OWNER)) 159 ? APR_READCONTROL : 0), 160 APR_OS_DEFAULT, pool)) == APR_SUCCESS) { 161 rv = apr_file_info_get(finfo, wanted, thefile); 162 finfo->filehand = NULL; 163 apr_file_close(thefile); 164 } 165 else if (APR_STATUS_IS_EACCES(rv) && (wanted & (APR_FINFO_PROT 166 | APR_FINFO_OWNER))) { 167 /* We have a backup plan. Perhaps we couldn't grab READ_CONTROL? 168 * proceed without asking for that permission... 169 */ 170 if ((rv = apr_file_open(&thefile, fname, APR_OPENINFO 171 | ((wanted & APR_FINFO_LINK) ? APR_OPENLINK : 0), 172 APR_OS_DEFAULT, pool)) == APR_SUCCESS) { 173 rv = apr_file_info_get(finfo, wanted & ~(APR_FINFO_PROT 174 | APR_FINFO_OWNER), 175 thefile); 176 finfo->filehand = NULL; 177 apr_file_close(thefile); 178 } 179 } 180 181 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) 182 return (rv); 183 184 /* We picked up this case above and had opened the link's properties */ 185 if (wanted & APR_FINFO_LINK) 186 finfo->valid |= APR_FINFO_LINK; 187 188 return rv; 189} 190 191static apr_status_t guess_protection_bits(apr_finfo_t *finfo, 192 apr_int32_t wanted) 193{ 194 /* Read, write execute for owner. In the Win9x environment, any 195 * readable file is executable (well, not entirely 100% true, but 196 * still looking for some cheap logic that would help us here.) 197 * The same holds on NT if a file doesn't have a DACL (e.g., on FAT) 198 */ 199 if (finfo->protection & APR_FREADONLY) { 200 finfo->protection |= APR_WREAD | APR_WEXECUTE; 201 } 202 else { 203 finfo->protection |= APR_WREAD | APR_WEXECUTE | APR_WWRITE; 204 } 205 finfo->protection |= (finfo->protection << prot_scope_group) 206 | (finfo->protection << prot_scope_user); 207 208 finfo->valid |= APR_FINFO_UPROT | APR_FINFO_GPROT | APR_FINFO_WPROT; 209 210 return ((wanted & ~finfo->valid) ? APR_INCOMPLETE : APR_SUCCESS); 211} 212 213apr_status_t more_finfo(apr_finfo_t *finfo, const void *ufile, 214 apr_int32_t wanted, int whatfile) 215{ 216 PSID user = NULL, grp = NULL; 217 PACL dacl = NULL; 218 apr_status_t rv; 219 220 if (apr_os_level < APR_WIN_NT) 221 return guess_protection_bits(finfo, wanted); 222 223 if (wanted & (APR_FINFO_PROT | APR_FINFO_OWNER)) 224 { 225 /* On NT this request is incredibly expensive, but accurate. 226 * Since the WinNT-only functions below are protected by the 227 * (apr_os_level < APR_WIN_NT) case above, we need no extra 228 * tests, but remember GetNamedSecurityInfo & GetSecurityInfo 229 * are not supported on 9x. 230 */ 231 SECURITY_INFORMATION sinf = 0; 232 PSECURITY_DESCRIPTOR pdesc = NULL; 233 if (wanted & (APR_FINFO_USER | APR_FINFO_UPROT)) 234 sinf |= OWNER_SECURITY_INFORMATION; 235 if (wanted & (APR_FINFO_GROUP | APR_FINFO_GPROT)) 236 sinf |= GROUP_SECURITY_INFORMATION; 237 if (wanted & APR_FINFO_PROT) 238 sinf |= DACL_SECURITY_INFORMATION; 239 if (whatfile == MORE_OF_WFSPEC) { 240 apr_wchar_t *wfile = (apr_wchar_t*) ufile; 241 int fix = 0; 242 if (wcsncmp(wfile, L"\\\\?\\", 4) == 0) { 243 fix = 4; 244 if (wcsncmp(wfile + fix, L"UNC\\", 4) == 0) 245 wfile[6] = L'\\', fix = 6; 246 } 247 rv = GetNamedSecurityInfoW(wfile + fix, 248 SE_FILE_OBJECT, sinf, 249 ((wanted & (APR_FINFO_USER | APR_FINFO_UPROT)) ? &user : NULL), 250 ((wanted & (APR_FINFO_GROUP | APR_FINFO_GPROT)) ? &grp : NULL), 251 ((wanted & APR_FINFO_PROT) ? &dacl : NULL), 252 NULL, &pdesc); 253 if (fix == 6) 254 wfile[6] = L'C'; 255 } 256 else if (whatfile == MORE_OF_FSPEC) 257 rv = GetNamedSecurityInfoA((char*)ufile, 258 SE_FILE_OBJECT, sinf, 259 ((wanted & (APR_FINFO_USER | APR_FINFO_UPROT)) ? &user : NULL), 260 ((wanted & (APR_FINFO_GROUP | APR_FINFO_GPROT)) ? &grp : NULL), 261 ((wanted & APR_FINFO_PROT) ? &dacl : NULL), 262 NULL, &pdesc); 263 else if (whatfile == MORE_OF_HANDLE) 264 rv = GetSecurityInfo((HANDLE)ufile, 265 SE_FILE_OBJECT, sinf, 266 ((wanted & (APR_FINFO_USER | APR_FINFO_UPROT)) ? &user : NULL), 267 ((wanted & (APR_FINFO_GROUP | APR_FINFO_GPROT)) ? &grp : NULL), 268 ((wanted & APR_FINFO_PROT) ? &dacl : NULL), 269 NULL, &pdesc); 270 else 271 return APR_INCOMPLETE; /* should not occur */ 272 if (rv == ERROR_SUCCESS) 273 apr_pool_cleanup_register(finfo->pool, pdesc, free_localheap, 274 apr_pool_cleanup_null); 275 else 276 user = grp = dacl = NULL; 277 278 if (user) { 279 finfo->user = user; 280 finfo->valid |= APR_FINFO_USER; 281 } 282 283 if (grp) { 284 finfo->group = grp; 285 finfo->valid |= APR_FINFO_GROUP; 286 } 287 288 if (dacl) { 289 /* Retrieved the discresionary access list */ 290 resolve_prot(finfo, wanted, dacl); 291 } 292 else if (wanted & APR_FINFO_PROT) 293 guess_protection_bits(finfo, wanted); 294 } 295 296 if ((apr_os_level >= APR_WIN_2000) && (wanted & APR_FINFO_CSIZE) 297 && (finfo->filetype == APR_REG)) 298 { 299 DWORD sizelo, sizehi; 300 if (whatfile == MORE_OF_HANDLE) { 301 /* Not available for development and implementation under 302 * a reasonable license; if you review the licensing 303 * terms and conditions of; 304 * http://go.microsoft.com/fwlink/?linkid=84083 305 * you probably understand why APR chooses not to implement. 306 */ 307 IOSB sb; 308 FSI fi; 309 if ((ZwQueryInformationFile((HANDLE)ufile, &sb, 310 &fi, sizeof(fi), 5) == 0) 311 && (sb.Status == 0)) { 312 finfo->csize = fi.AllocationSize; 313 finfo->valid |= APR_FINFO_CSIZE; 314 } 315 } 316 else { 317 SetLastError(NO_ERROR); 318 if (whatfile == MORE_OF_WFSPEC) 319 sizelo = GetCompressedFileSizeW((apr_wchar_t*)ufile, &sizehi); 320 else if (whatfile == MORE_OF_FSPEC) 321 sizelo = GetCompressedFileSizeA((char*)ufile, &sizehi); 322 else 323 return APR_EGENERAL; /* should not occur */ 324 325 if (sizelo != INVALID_FILE_SIZE || GetLastError() == NO_ERROR) { 326#if APR_HAS_LARGE_FILES 327 finfo->csize = (apr_off_t)sizelo 328 | ((apr_off_t)sizehi << 32); 329#else 330 finfo->csize = (apr_off_t)sizelo; 331 if (finfo->csize < 0 || sizehi) 332 finfo->csize = 0x7fffffff; 333#endif 334 finfo->valid |= APR_FINFO_CSIZE; 335 } 336 } 337 } 338 return ((wanted & ~finfo->valid) ? APR_INCOMPLETE : APR_SUCCESS); 339} 340 341 342/* This generic fillin depends upon byhandle to be passed as 0 when 343 * a WIN32_FILE_ATTRIBUTE_DATA or either WIN32_FIND_DATA [A or W] is 344 * passed for wininfo. When the BY_HANDLE_FILE_INFORMATION structure 345 * is passed for wininfo, byhandle is passed as 1 to offset the one 346 * dword discrepancy in offset of the High/Low size structure members. 347 * 348 * The generic fillin returns 1 if the caller should further inquire 349 * if this is a CHR filetype. If it's reasonably certain it can't be, 350 * then the function returns 0. 351 */ 352int fillin_fileinfo(apr_finfo_t *finfo, 353 WIN32_FILE_ATTRIBUTE_DATA *wininfo, 354 int byhandle, apr_int32_t wanted) 355{ 356 DWORD *sizes = &wininfo->nFileSizeHigh + byhandle; 357 int warn = 0; 358 359 memset(finfo, '\0', sizeof(*finfo)); 360 361 FileTimeToAprTime(&finfo->atime, &wininfo->ftLastAccessTime); 362 FileTimeToAprTime(&finfo->ctime, &wininfo->ftCreationTime); 363 FileTimeToAprTime(&finfo->mtime, &wininfo->ftLastWriteTime); 364 365#if APR_HAS_LARGE_FILES 366 finfo->size = (apr_off_t)sizes[1] 367 | ((apr_off_t)sizes[0] << 32); 368#else 369 finfo->size = (apr_off_t)sizes[1]; 370 if (finfo->size < 0 || sizes[0]) 371 finfo->size = 0x7fffffff; 372#endif 373 374 if (wanted & APR_FINFO_LINK && 375 wininfo->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { 376 finfo->filetype = APR_LNK; 377 } 378 else if (wininfo->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 379 finfo->filetype = APR_DIR; 380 } 381 else if (wininfo->dwFileAttributes & FILE_ATTRIBUTE_DEVICE) { 382 /* Warning: This test only succeeds on Win9x, on NT these files 383 * (con, aux, nul, lpt#, com# etc) escape early detection! 384 */ 385 finfo->filetype = APR_CHR; 386 } 387 else { 388 /* Warning: Short of opening the handle to the file, the 'FileType' 389 * appears to be unknowable (in any trustworthy or consistent sense) 390 * on WinNT/2K as far as PIPE, CHR, etc are concerned. 391 */ 392 if (!wininfo->ftLastWriteTime.dwLowDateTime 393 && !wininfo->ftLastWriteTime.dwHighDateTime 394 && !finfo->size) 395 warn = 1; 396 finfo->filetype = APR_REG; 397 } 398 399 /* The following flags are [for this moment] private to Win32. 400 * That's the only excuse for not toggling valid bits to reflect them. 401 */ 402 if (wininfo->dwFileAttributes & FILE_ATTRIBUTE_READONLY) 403 finfo->protection = APR_FREADONLY; 404 405 finfo->valid = APR_FINFO_ATIME | APR_FINFO_CTIME | APR_FINFO_MTIME 406 | APR_FINFO_SIZE | APR_FINFO_TYPE; /* == APR_FINFO_MIN */ 407 408 /* Only byhandle optionally tests link targets, so tell that caller 409 * what it wants to hear, otherwise the byattributes is never 410 * reporting anything but the link. 411 */ 412 if (!byhandle || (wanted & APR_FINFO_LINK)) 413 finfo->valid |= APR_FINFO_LINK; 414 return warn; 415} 416 417 418APR_DECLARE(apr_status_t) apr_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted, 419 apr_file_t *thefile) 420{ 421 BY_HANDLE_FILE_INFORMATION FileInfo; 422 423 if (thefile->buffered) { 424 /* XXX: flush here is not mutex protected */ 425 apr_status_t rv = apr_file_flush(thefile); 426 if (rv != APR_SUCCESS) 427 return rv; 428 } 429 430 if (!GetFileInformationByHandle(thefile->filehand, &FileInfo)) { 431 return apr_get_os_error(); 432 } 433 434 fillin_fileinfo(finfo, (WIN32_FILE_ATTRIBUTE_DATA *) &FileInfo, 1, wanted); 435 436 if (finfo->filetype == APR_REG) 437 { 438 /* Go the extra mile to be -certain- that we have a real, regular 439 * file, since the attribute bits aren't a certain thing. Even 440 * though fillin should have hinted if we *must* do this, we 441 * don't need to take chances while the handle is already open. 442 */ 443 DWORD FileType; 444 if ((FileType = GetFileType(thefile->filehand))) { 445 if (FileType == FILE_TYPE_CHAR) { 446 finfo->filetype = APR_CHR; 447 } 448 else if (FileType == FILE_TYPE_PIPE) { 449 finfo->filetype = APR_PIPE; 450 } 451 /* Otherwise leave the original conclusion alone 452 */ 453 } 454 } 455 456 finfo->pool = thefile->pool; 457 458 /* ### The finfo lifetime may exceed the lifetime of thefile->pool 459 * but finfo's aren't managed in pools, so where on earth would 460 * we pstrdup the fname into??? 461 */ 462 finfo->fname = thefile->fname; 463 464 /* Extra goodies known only by GetFileInformationByHandle() */ 465 finfo->inode = (apr_ino_t)FileInfo.nFileIndexLow 466 | ((apr_ino_t)FileInfo.nFileIndexHigh << 32); 467 finfo->device = FileInfo.dwVolumeSerialNumber; 468 finfo->nlink = FileInfo.nNumberOfLinks; 469 470 finfo->valid |= APR_FINFO_IDENT | APR_FINFO_NLINK; 471 472 /* If we still want something more (besides the name) go get it! 473 */ 474 if ((wanted &= ~finfo->valid) & ~APR_FINFO_NAME) { 475 return more_finfo(finfo, thefile->filehand, wanted, MORE_OF_HANDLE); 476 } 477 478 return APR_SUCCESS; 479} 480 481APR_DECLARE(apr_status_t) apr_file_perms_set(const char *fname, 482 apr_fileperms_t perms) 483{ 484 return APR_ENOTIMPL; 485} 486 487APR_DECLARE(apr_status_t) apr_stat(apr_finfo_t *finfo, const char *fname, 488 apr_int32_t wanted, apr_pool_t *pool) 489{ 490 /* XXX: is constant - needs testing - which requires a lighter-weight root test fn */ 491 int isroot = 0; 492 apr_status_t ident_rv = 0; 493 apr_status_t rv; 494#if APR_HAS_UNICODE_FS 495 apr_wchar_t wfname[APR_PATH_MAX]; 496 497#endif 498 char *filename = NULL; 499 /* These all share a common subset of this structure */ 500 union { 501 WIN32_FIND_DATAW w; 502 WIN32_FIND_DATAA n; 503 WIN32_FILE_ATTRIBUTE_DATA i; 504 } FileInfo; 505 506 /* Catch fname length == MAX_PATH since GetFileAttributesEx fails 507 * with PATH_NOT_FOUND. We would rather indicate length error than 508 * 'not found' 509 */ 510 if (strlen(fname) >= APR_PATH_MAX) { 511 return APR_ENAMETOOLONG; 512 } 513 514#if APR_HAS_UNICODE_FS 515 IF_WIN_OS_IS_UNICODE 516 { 517 if ((wanted & (APR_FINFO_IDENT | APR_FINFO_NLINK)) 518 || (~wanted & APR_FINFO_LINK)) { 519 /* FindFirstFile and GetFileAttributesEx can't figure the inode, 520 * device or number of links, so we need to resolve with an open 521 * file handle. If the user has asked for these fields, fall over 522 * to the get file info by handle method. If we fail, or the user 523 * also asks for the file name, continue by our usual means. 524 * 525 * We also must use this method for a 'true' stat, that resolves 526 * a symlink (NTFS Junction) target. This is because all fileinfo 527 * on a Junction always returns the junction, opening the target 528 * is the only way to resolve the target's attributes. 529 */ 530 if ((ident_rv = resolve_ident(finfo, fname, wanted, pool)) 531 == APR_SUCCESS) 532 return ident_rv; 533 else if (ident_rv == APR_INCOMPLETE) 534 wanted &= ~finfo->valid; 535 } 536 537 if ((rv = utf8_to_unicode_path(wfname, sizeof(wfname) 538 / sizeof(apr_wchar_t), fname))) 539 return rv; 540 if (!(wanted & APR_FINFO_NAME)) { 541 if (!GetFileAttributesExW(wfname, GetFileExInfoStandard, 542 &FileInfo.i)) 543 return apr_get_os_error(); 544 } 545 else { 546 /* Guard against bogus wildcards and retrieve by name 547 * since we want the true name, and set aside a long 548 * enough string to handle the longest file name. 549 */ 550 char tmpname[APR_FILE_MAX * 3 + 1]; 551 HANDLE hFind; 552 if ((rv = test_safe_name(fname)) != APR_SUCCESS) { 553 return rv; 554 } 555 hFind = FindFirstFileW(wfname, &FileInfo.w); 556 if (hFind == INVALID_HANDLE_VALUE) 557 return apr_get_os_error(); 558 FindClose(hFind); 559 if (unicode_to_utf8_path(tmpname, sizeof(tmpname), 560 FileInfo.w.cFileName)) { 561 return APR_ENAMETOOLONG; 562 } 563 filename = apr_pstrdup(pool, tmpname); 564 } 565 } 566#endif 567#if APR_HAS_ANSI_FS 568 ELSE_WIN_OS_IS_ANSI 569 { 570 char *root = NULL; 571 const char *test = fname; 572 rv = apr_filepath_root(&root, &test, APR_FILEPATH_NATIVE, pool); 573 isroot = (root && *root && !(*test)); 574 575 if ((apr_os_level >= APR_WIN_98) && (!(wanted & APR_FINFO_NAME) || isroot)) 576 { 577 /* cannot use FindFile on a Win98 root, it returns \* 578 * GetFileAttributesExA is not available on Win95 579 */ 580 if (!GetFileAttributesExA(fname, GetFileExInfoStandard, 581 &FileInfo.i)) { 582 return apr_get_os_error(); 583 } 584 } 585 else if (isroot) { 586 /* This is Win95 and we are trying to stat a root. Lie. 587 */ 588 if (GetDriveType(fname) != DRIVE_UNKNOWN) 589 { 590 finfo->pool = pool; 591 finfo->filetype = 0; 592 finfo->mtime = apr_time_now(); 593 finfo->protection |= APR_WREAD | APR_WEXECUTE | APR_WWRITE; 594 finfo->protection |= (finfo->protection << prot_scope_group) 595 | (finfo->protection << prot_scope_user); 596 finfo->valid |= APR_FINFO_TYPE | APR_FINFO_PROT 597 | APR_FINFO_MTIME 598 | (wanted & APR_FINFO_LINK); 599 return (wanted &= ~finfo->valid) ? APR_INCOMPLETE 600 : APR_SUCCESS; 601 } 602 else 603 return APR_FROM_OS_ERROR(ERROR_PATH_NOT_FOUND); 604 } 605 else { 606 /* Guard against bogus wildcards and retrieve by name 607 * since we want the true name, or are stuck in Win95, 608 * or are looking for the root of a Win98 drive. 609 */ 610 HANDLE hFind; 611 if ((rv = test_safe_name(fname)) != APR_SUCCESS) { 612 return rv; 613 } 614 hFind = FindFirstFileA(fname, &FileInfo.n); 615 if (hFind == INVALID_HANDLE_VALUE) { 616 return apr_get_os_error(); 617 } 618 FindClose(hFind); 619 filename = apr_pstrdup(pool, FileInfo.n.cFileName); 620 } 621 } 622#endif 623 624 if (ident_rv != APR_INCOMPLETE) { 625 if (fillin_fileinfo(finfo, (WIN32_FILE_ATTRIBUTE_DATA *) &FileInfo, 626 0, wanted)) 627 { 628 /* Go the extra mile to assure we have a file. WinNT/2000 seems 629 * to reliably translate char devices to the path '\\.\device' 630 * so go ask for the full path. 631 */ 632 if (apr_os_level >= APR_WIN_NT) 633 { 634#if APR_HAS_UNICODE_FS 635 apr_wchar_t tmpname[APR_FILE_MAX]; 636 apr_wchar_t *tmpoff = NULL; 637 if (GetFullPathNameW(wfname, sizeof(tmpname) / sizeof(apr_wchar_t), 638 tmpname, &tmpoff)) 639 { 640 if (!wcsncmp(tmpname, L"\\\\.\\", 4)) { 641#else 642 /* Same initial logic as above, but 643 * only for WinNT/non-UTF-8 builds of APR: 644 */ 645 char tmpname[APR_FILE_MAX]; 646 char *tmpoff; 647 if (GetFullPathName(fname, sizeof(tmpname), tmpname, &tmpoff)) 648 { 649 if (!strncmp(tmpname, "\\\\.\\", 4)) { 650#endif 651 if (tmpoff == tmpname + 4) { 652 finfo->filetype = APR_CHR; 653 } 654 /* For WHATEVER reason, CHR devices such as \\.\con 655 * or \\.\lpt1 *may*not* update tmpoff; in fact the 656 * resulting tmpoff is set to NULL. Guard against 657 * either case. 658 * 659 * This code is identical for wide and narrow chars... 660 */ 661 else if (!tmpoff) { 662 tmpoff = tmpname + 4; 663 while (*tmpoff) { 664 if (*tmpoff == '\\' || *tmpoff == '/') { 665 break; 666 } 667 ++tmpoff; 668 } 669 if (!*tmpoff) { 670 finfo->filetype = APR_CHR; 671 } 672 } 673 } 674 } 675 else { 676 finfo->valid &= ~APR_FINFO_TYPE; 677 } 678 679 } 680 else { 681 finfo->valid &= ~APR_FINFO_TYPE; 682 } 683 } 684 finfo->pool = pool; 685 } 686 687 if (filename && !isroot) { 688 finfo->name = filename; 689 finfo->valid |= APR_FINFO_NAME; 690 } 691 692 if (wanted &= ~finfo->valid) { 693 /* Caller wants more than APR_FINFO_MIN | APR_FINFO_NAME */ 694#if APR_HAS_UNICODE_FS 695 if (apr_os_level >= APR_WIN_NT) 696 return more_finfo(finfo, wfname, wanted, MORE_OF_WFSPEC); 697#endif 698 return more_finfo(finfo, fname, wanted, MORE_OF_FSPEC); 699 } 700 701 return APR_SUCCESS; 702} 703 704APR_DECLARE(apr_status_t) apr_file_attrs_set(const char *fname, 705 apr_fileattrs_t attributes, 706 apr_fileattrs_t attr_mask, 707 apr_pool_t *pool) 708{ 709 DWORD flags; 710 apr_status_t rv; 711#if APR_HAS_UNICODE_FS 712 apr_wchar_t wfname[APR_PATH_MAX]; 713#endif 714 715 /* Don't do anything if we can't handle the requested attributes */ 716 if (!(attr_mask & (APR_FILE_ATTR_READONLY 717 | APR_FILE_ATTR_HIDDEN))) 718 return APR_SUCCESS; 719 720#if APR_HAS_UNICODE_FS 721 IF_WIN_OS_IS_UNICODE 722 { 723 if ((rv = utf8_to_unicode_path(wfname, 724 sizeof(wfname) / sizeof(wfname[0]), 725 fname))) 726 return rv; 727 flags = GetFileAttributesW(wfname); 728 } 729#endif 730#if APR_HAS_ANSI_FS 731 ELSE_WIN_OS_IS_ANSI 732 { 733 flags = GetFileAttributesA(fname); 734 } 735#endif 736 737 if (flags == 0xFFFFFFFF) 738 return apr_get_os_error(); 739 740 if (attr_mask & APR_FILE_ATTR_READONLY) 741 { 742 if (attributes & APR_FILE_ATTR_READONLY) 743 flags |= FILE_ATTRIBUTE_READONLY; 744 else 745 flags &= ~FILE_ATTRIBUTE_READONLY; 746 } 747 748 if (attr_mask & APR_FILE_ATTR_HIDDEN) 749 { 750 if (attributes & APR_FILE_ATTR_HIDDEN) 751 flags |= FILE_ATTRIBUTE_HIDDEN; 752 else 753 flags &= ~FILE_ATTRIBUTE_HIDDEN; 754 } 755 756#if APR_HAS_UNICODE_FS 757 IF_WIN_OS_IS_UNICODE 758 { 759 rv = SetFileAttributesW(wfname, flags); 760 } 761#endif 762#if APR_HAS_ANSI_FS 763 ELSE_WIN_OS_IS_ANSI 764 { 765 rv = SetFileAttributesA(fname, flags); 766 } 767#endif 768 769 if (rv == 0) 770 return apr_get_os_error(); 771 772 return APR_SUCCESS; 773} 774 775 776APR_DECLARE(apr_status_t) apr_file_mtime_set(const char *fname, 777 apr_time_t mtime, 778 apr_pool_t *pool) 779{ 780 apr_file_t *thefile; 781 apr_status_t rv; 782 783 rv = apr_file_open(&thefile, fname, 784 APR_FOPEN_READ | APR_WRITEATTRS, 785 APR_OS_DEFAULT, pool); 786 if (!rv) 787 { 788 FILETIME file_ctime; 789 FILETIME file_atime; 790 FILETIME file_mtime; 791 792 if (!GetFileTime(thefile->filehand, 793 &file_ctime, &file_atime, &file_mtime)) 794 rv = apr_get_os_error(); 795 else 796 { 797 AprTimeToFileTime(&file_mtime, mtime); 798 if (!SetFileTime(thefile->filehand, 799 &file_ctime, &file_atime, &file_mtime)) 800 rv = apr_get_os_error(); 801 } 802 803 apr_file_close(thefile); 804 } 805 806 return rv; 807} 808