filesystem-disk-win32.c++ (59218B)
1 // Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #if _WIN32 23 // For Unix implementation, see filesystem-disk-unix.c++. 24 25 // Request Vista-level APIs. 26 #include "win32-api-version.h" 27 28 #include "filesystem.h" 29 #include "debug.h" 30 #include "encoding.h" 31 #include "vector.h" 32 #include <algorithm> 33 #include <wchar.h> 34 35 #include <windows.h> 36 #include <winioctl.h> 37 #include "windows-sanity.h" 38 39 namespace kj { 40 41 static Own<ReadableDirectory> newDiskReadableDirectory(AutoCloseHandle fd, Path&& path); 42 static Own<Directory> newDiskDirectory(AutoCloseHandle fd, Path&& path); 43 44 static AutoCloseHandle* getHandlePointerHack(File& file) { return nullptr; } 45 static AutoCloseHandle* getHandlePointerHack(Directory& dir); 46 static Path* getPathPointerHack(File& file) { return nullptr; } 47 static Path* getPathPointerHack(Directory& dir); 48 49 namespace { 50 51 struct REPARSE_DATA_BUFFER { 52 // From ntifs.h, which is part of the driver development kit so not necessarily available I 53 // guess. 54 ULONG ReparseTag; 55 USHORT ReparseDataLength; 56 USHORT Reserved; 57 union { 58 struct { 59 USHORT SubstituteNameOffset; 60 USHORT SubstituteNameLength; 61 USHORT PrintNameOffset; 62 USHORT PrintNameLength; 63 ULONG Flags; 64 WCHAR PathBuffer[1]; 65 } SymbolicLinkReparseBuffer; 66 struct { 67 USHORT SubstituteNameOffset; 68 USHORT SubstituteNameLength; 69 USHORT PrintNameOffset; 70 USHORT PrintNameLength; 71 WCHAR PathBuffer[1]; 72 } MountPointReparseBuffer; 73 struct { 74 UCHAR DataBuffer[1]; 75 } GenericReparseBuffer; 76 }; 77 }; 78 79 #define HIDDEN_PREFIX ".kj-tmp." 80 // Prefix for temp files which should be hidden when listing a directory. 81 // 82 // If you change this, make sure to update the unit test. 83 84 static constexpr int64_t WIN32_EPOCH_OFFSET = 116444736000000000ull; 85 // Number of 100ns intervals from Jan 1, 1601 to Jan 1, 1970. 86 87 static Date toKjDate(FILETIME t) { 88 int64_t value = (static_cast<uint64_t>(t.dwHighDateTime) << 32) | t.dwLowDateTime; 89 return (value - WIN32_EPOCH_OFFSET) * (100 * kj::NANOSECONDS) + UNIX_EPOCH; 90 } 91 92 static FsNode::Type modeToType(DWORD attrs, DWORD reparseTag) { 93 if ((attrs & FILE_ATTRIBUTE_REPARSE_POINT) && 94 reparseTag == IO_REPARSE_TAG_SYMLINK) { 95 return FsNode::Type::SYMLINK; 96 } 97 if (attrs & FILE_ATTRIBUTE_DIRECTORY) return FsNode::Type::DIRECTORY; 98 return FsNode::Type::FILE; 99 } 100 101 static FsNode::Metadata statToMetadata(const BY_HANDLE_FILE_INFORMATION& stats) { 102 uint64_t size = (implicitCast<uint64_t>(stats.nFileSizeHigh) << 32) | stats.nFileSizeLow; 103 104 // Assume file index is usually a small number, i.e. nFileIndexHigh is usually 0. So we try to 105 // put the serial number in the upper 32 bits and the index in the lower. 106 uint64_t hash = ((uint64_t(stats.dwVolumeSerialNumber) << 32) 107 ^ (uint64_t(stats.nFileIndexHigh) << 32)) 108 | (uint64_t(stats.nFileIndexLow)); 109 110 return FsNode::Metadata { 111 modeToType(stats.dwFileAttributes, 0), 112 size, 113 // In theory, spaceUsed should be based on GetCompressedFileSize(), but requiring an extra 114 // syscall for something rarely used would be sad. 115 size, 116 toKjDate(stats.ftLastWriteTime), 117 stats.nNumberOfLinks, 118 hash 119 }; 120 } 121 122 static FsNode::Metadata statToMetadata(const WIN32_FIND_DATAW& stats) { 123 uint64_t size = (implicitCast<uint64_t>(stats.nFileSizeHigh) << 32) | stats.nFileSizeLow; 124 125 return FsNode::Metadata { 126 modeToType(stats.dwFileAttributes, stats.dwReserved0), 127 size, 128 // In theory, spaceUsed should be based on GetCompressedFileSize(), but requiring an extra 129 // syscall for something rarely used would be sad. 130 size, 131 toKjDate(stats.ftLastWriteTime), 132 // We can't get the number of links without opening the file, apparently. Meh. 133 1, 134 // We can't produce a reliable hashCode without opening the file. 135 0 136 }; 137 } 138 139 static Array<wchar_t> join16(ArrayPtr<const wchar_t> path, const wchar_t* file) { 140 // Assumes `path` ends with a NUL terminator (and `file` is of course NUL terminated as well). 141 142 size_t len = wcslen(file) + 1; 143 auto result = kj::heapArray<wchar_t>(path.size() + len); 144 memcpy(result.begin(), path.begin(), path.asBytes().size() - sizeof(wchar_t)); 145 result[path.size() - 1] = '\\'; 146 memcpy(result.begin() + path.size(), file, len * sizeof(wchar_t)); 147 return result; 148 } 149 150 static String dbgStr(ArrayPtr<const wchar_t> wstr) { 151 if (wstr.size() > 0 && wstr[wstr.size() - 1] == L'\0') { 152 wstr = wstr.slice(0, wstr.size() - 1); 153 } 154 return decodeWideString(wstr); 155 } 156 157 static void rmrfChildren(ArrayPtr<const wchar_t> path) { 158 auto glob = join16(path, L"*"); 159 160 WIN32_FIND_DATAW data; 161 HANDLE handle = FindFirstFileW(glob.begin(), &data); 162 if (handle == INVALID_HANDLE_VALUE) { 163 auto error = GetLastError(); 164 if (error == ERROR_FILE_NOT_FOUND) return; 165 KJ_FAIL_WIN32("FindFirstFile", error, dbgStr(glob)) { return; } 166 } 167 KJ_DEFER(KJ_WIN32(FindClose(handle)) { break; }); 168 169 do { 170 // Ignore "." and "..", ugh. 171 if (data.cFileName[0] == L'.') { 172 if (data.cFileName[1] == L'\0' || 173 (data.cFileName[1] == L'.' && data.cFileName[2] == L'\0')) { 174 continue; 175 } 176 } 177 178 auto child = join16(path, data.cFileName); 179 // For rmrf purposes, we assume any "reparse points" are symlink-like, even if they aren't 180 // actually the "symbolic link" reparse type, because we don't want to recursively delete any 181 // shared content. 182 if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && 183 !(data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { 184 rmrfChildren(child); 185 uint retryCount = 0; 186 retry: 187 KJ_WIN32_HANDLE_ERRORS(RemoveDirectoryW(child.begin())) { 188 case ERROR_DIR_NOT_EMPTY: 189 // On Windows, deleting a file actually only schedules it for deletion. Under heavy 190 // load it may take a bit for the deletion to go through. Or, if another process has 191 // the file open, it may not be deleted until that process closes it. 192 // 193 // We'll repeatedly retry for up to 100ms, then give up. This is awful but there's no 194 // way to tell for sure if the system is just being slow or if someone has the file 195 // open. 196 if (retryCount++ < 10) { 197 Sleep(10); 198 goto retry; 199 } 200 KJ_FALLTHROUGH; 201 default: 202 KJ_FAIL_WIN32("RemoveDirectory", error, dbgStr(child)) { break; } 203 } 204 } else { 205 KJ_WIN32(DeleteFileW(child.begin())); 206 } 207 } while (FindNextFileW(handle, &data)); 208 209 auto error = GetLastError(); 210 if (error != ERROR_NO_MORE_FILES) { 211 KJ_FAIL_WIN32("FindNextFile", error, dbgStr(path)) { return; } 212 } 213 } 214 215 static bool rmrf(ArrayPtr<const wchar_t> path) { 216 // Figure out whether this is a file or a directory. 217 // 218 // We use FindFirstFileW() because in the case of symlinks it will return info about the 219 // symlink rather than info about the target. 220 WIN32_FIND_DATAW data; 221 HANDLE handle = FindFirstFileW(path.begin(), &data); 222 if (handle == INVALID_HANDLE_VALUE) { 223 auto error = GetLastError(); 224 if (error == ERROR_FILE_NOT_FOUND) return false; 225 KJ_FAIL_WIN32("FindFirstFile", error, dbgStr(path)); 226 } 227 KJ_WIN32(FindClose(handle)); 228 229 // For remove purposes, we assume any "reparse points" are symlink-like, even if they aren't 230 // actually the "symbolic link" reparse type, because we don't want to recursively delete any 231 // shared content. 232 if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && 233 !(data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { 234 // directory 235 rmrfChildren(path); 236 KJ_WIN32(RemoveDirectoryW(path.begin()), dbgStr(path)); 237 } else { 238 KJ_WIN32(DeleteFileW(path.begin()), dbgStr(path)); 239 } 240 241 return true; 242 } 243 244 static Path getPathFromHandle(HANDLE handle) { 245 DWORD tryLen = MAX_PATH; 246 for (;;) { 247 auto temp = kj::heapArray<wchar_t>(tryLen + 1); 248 DWORD len = GetFinalPathNameByHandleW(handle, temp.begin(), tryLen, 0); 249 if (len == 0) { 250 KJ_FAIL_WIN32("GetFinalPathNameByHandleW", GetLastError()); 251 } 252 if (len < temp.size()) { 253 return Path::parseWin32Api(temp.slice(0, len)); 254 } 255 // Try again with new length. 256 tryLen = len; 257 } 258 } 259 260 struct MmapRange { 261 uint64_t offset; 262 uint64_t size; 263 }; 264 265 static size_t getAllocationGranularity() { 266 SYSTEM_INFO info; 267 GetSystemInfo(&info); 268 return info.dwAllocationGranularity; 269 }; 270 271 static MmapRange getMmapRange(uint64_t offset, uint64_t size) { 272 // Rounds the given offset down to the nearest page boundary, and adjusts the size up to match. 273 // (This is somewhat different from Unix: we do NOT round the size up to an even multiple of 274 // pages.) 275 static const uint64_t pageSize = getAllocationGranularity(); 276 uint64_t pageMask = pageSize - 1; 277 278 uint64_t realOffset = offset & ~pageMask; 279 280 uint64_t end = offset + size; 281 282 return { realOffset, end - realOffset }; 283 } 284 285 class MmapDisposer: public ArrayDisposer { 286 protected: 287 void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount, 288 size_t capacity, void (*destroyElement)(void*)) const { 289 auto range = getMmapRange(reinterpret_cast<uintptr_t>(firstElement), 290 elementSize * elementCount); 291 void* mapping = reinterpret_cast<void*>(range.offset); 292 if (mapping != nullptr) { 293 KJ_ASSERT(UnmapViewOfFile(mapping)) { break; } 294 } 295 } 296 }; 297 298 #if _MSC_VER && _MSC_VER < 1910 && !defined(__clang__) 299 // TODO(msvc): MSVC 2015 can't initialize a constexpr's vtable correctly. 300 const MmapDisposer mmapDisposer = MmapDisposer(); 301 #else 302 constexpr MmapDisposer mmapDisposer = MmapDisposer(); 303 #endif 304 305 void* win32Mmap(HANDLE handle, MmapRange range, DWORD pageProtect, DWORD access) { 306 HANDLE mappingHandle; 307 KJ_WIN32(mappingHandle = CreateFileMappingW(handle, NULL, pageProtect, 0, 0, NULL)); 308 KJ_DEFER(KJ_WIN32(CloseHandle(mappingHandle)) { break; }); 309 310 void* mapping = MapViewOfFile(mappingHandle, access, 311 static_cast<DWORD>(range.offset >> 32), static_cast<DWORD>(range.offset), range.size); 312 if (mapping == nullptr) { 313 KJ_FAIL_WIN32("MapViewOfFile", GetLastError()); 314 } 315 316 // It's unclear from the documentation whether mappings will always start at a multiple of the 317 // allocation granularity, but we depend on that later, so check it... 318 KJ_ASSERT(getMmapRange(reinterpret_cast<uintptr_t>(mapping), 0).size == 0); 319 320 return mapping; 321 } 322 323 class DiskHandle { 324 // We need to implement each of ReadableFile, AppendableFile, File, ReadableDirectory, and 325 // Directory for disk handles. There is a lot of implementation overlap between these, especially 326 // stat(), sync(), etc. We can't have everything inherit from a common DiskFsNode that implements 327 // these because then we get diamond inheritance which means we need to make all our inheritance 328 // virtual which means downcasting requires RTTI which violates our goal of supporting compiling 329 // with no RTTI. So instead we have the DiskHandle class which implements all the methods without 330 // inheriting anything, and then we have DiskFile, DiskDirectory, etc. hold this and delegate to 331 // it. Ugly, but works. 332 333 public: 334 DiskHandle(AutoCloseHandle&& handle, Maybe<Path> dirPath) 335 : handle(kj::mv(handle)), dirPath(kj::mv(dirPath)) {} 336 337 AutoCloseHandle handle; 338 kj::Maybe<Path> dirPath; // needed for directories, empty for files 339 340 Array<wchar_t> nativePath(PathPtr path) const { 341 return KJ_ASSERT_NONNULL(dirPath).append(path).forWin32Api(true); 342 } 343 344 // OsHandle ------------------------------------------------------------------ 345 346 AutoCloseHandle clone() const { 347 HANDLE newHandle; 348 KJ_WIN32(DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &newHandle, 349 0, FALSE, DUPLICATE_SAME_ACCESS)); 350 return AutoCloseHandle(newHandle); 351 } 352 353 HANDLE getWin32Handle() const { 354 return handle.get(); 355 } 356 357 // FsNode -------------------------------------------------------------------- 358 359 FsNode::Metadata stat() const { 360 BY_HANDLE_FILE_INFORMATION stats; 361 KJ_WIN32(GetFileInformationByHandle(handle, &stats)); 362 auto metadata = statToMetadata(stats); 363 364 // Get space usage, e.g. for sparse files. Apparently the correct way to do this is to query 365 // "compression". 366 FILE_COMPRESSION_INFO compInfo; 367 KJ_WIN32_HANDLE_ERRORS(GetFileInformationByHandleEx( 368 handle, FileCompressionInfo, &compInfo, sizeof(compInfo))) { 369 case ERROR_CALL_NOT_IMPLEMENTED: 370 // Probably WINE. 371 break; 372 default: 373 KJ_FAIL_WIN32("GetFileInformationByHandleEx(FileCompressionInfo)", error) { break; } 374 break; 375 } else { 376 metadata.spaceUsed = compInfo.CompressedFileSize.QuadPart; 377 } 378 379 return metadata; 380 } 381 382 void sync() const { KJ_WIN32(FlushFileBuffers(handle)); } 383 void datasync() const { KJ_WIN32(FlushFileBuffers(handle)); } 384 385 // ReadableFile -------------------------------------------------------------- 386 387 size_t read(uint64_t offset, ArrayPtr<byte> buffer) const { 388 // ReadFile() probably never returns short reads unless it hits EOF. Unfortunately, though, 389 // this is not documented, and it's unclear whether we can rely on it. 390 391 size_t total = 0; 392 while (buffer.size() > 0) { 393 // Apparently, the way to fake pread() on Windows is to provide an OVERLAPPED structure even 394 // though we're not doing overlapped I/O. 395 OVERLAPPED overlapped; 396 memset(&overlapped, 0, sizeof(overlapped)); 397 overlapped.Offset = static_cast<DWORD>(offset); 398 overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32); 399 400 DWORD n; 401 KJ_WIN32_HANDLE_ERRORS(ReadFile(handle, buffer.begin(), buffer.size(), &n, &overlapped)) { 402 case ERROR_HANDLE_EOF: 403 // The documentation claims this shouldn't happen for synchronous reads, but it seems 404 // to happen for me, at least under WINE. 405 n = 0; 406 break; 407 default: 408 KJ_FAIL_WIN32("ReadFile", offset, buffer.size()) { return total; } 409 } 410 if (n == 0) break; 411 total += n; 412 offset += n; 413 buffer = buffer.slice(n, buffer.size()); 414 } 415 return total; 416 } 417 418 Array<const byte> mmap(uint64_t offset, uint64_t size) const { 419 if (size == 0) return nullptr; // Windows won't allow zero-length mappings 420 auto range = getMmapRange(offset, size); 421 const void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_READ); 422 return Array<const byte>(reinterpret_cast<const byte*>(mapping) + (offset - range.offset), 423 size, mmapDisposer); 424 } 425 426 Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const { 427 if (size == 0) return nullptr; // Windows won't allow zero-length mappings 428 auto range = getMmapRange(offset, size); 429 void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_COPY); 430 return Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset), 431 size, mmapDisposer); 432 } 433 434 // File ---------------------------------------------------------------------- 435 436 void write(uint64_t offset, ArrayPtr<const byte> data) const { 437 // WriteFile() probably never returns short writes unless there's no space left on disk. 438 // Unfortunately, though, this is not documented, and it's unclear whether we can rely on it. 439 440 while (data.size() > 0) { 441 // Apparently, the way to fake pwrite() on Windows is to provide an OVERLAPPED structure even 442 // though we're not doing overlapped I/O. 443 OVERLAPPED overlapped; 444 memset(&overlapped, 0, sizeof(overlapped)); 445 overlapped.Offset = static_cast<DWORD>(offset); 446 overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32); 447 448 DWORD n; 449 KJ_WIN32(WriteFile(handle, data.begin(), data.size(), &n, &overlapped)); 450 KJ_ASSERT(n > 0, "WriteFile() returned zero?"); 451 offset += n; 452 data = data.slice(n, data.size()); 453 } 454 } 455 456 void zero(uint64_t offset, uint64_t size) const { 457 FILE_ZERO_DATA_INFORMATION info; 458 memset(&info, 0, sizeof(info)); 459 info.FileOffset.QuadPart = offset; 460 info.BeyondFinalZero.QuadPart = offset + size; 461 462 DWORD dummy; 463 KJ_WIN32_HANDLE_ERRORS(DeviceIoControl(handle, FSCTL_SET_ZERO_DATA, &info, 464 sizeof(info), NULL, 0, &dummy, NULL)) { 465 case ERROR_NOT_SUPPORTED: { 466 // Dang. Let's do it the hard way. 467 static const byte ZEROS[4096] = { 0 }; 468 469 while (size > sizeof(ZEROS)) { 470 write(offset, ZEROS); 471 size -= sizeof(ZEROS); 472 offset += sizeof(ZEROS); 473 } 474 write(offset, kj::arrayPtr(ZEROS, size)); 475 break; 476 } 477 478 default: 479 KJ_FAIL_WIN32("DeviceIoControl(FSCTL_SET_ZERO_DATA)", error); 480 break; 481 } 482 } 483 484 void truncate(uint64_t size) const { 485 // SetEndOfFile() would require seeking the file. It looks like SetFileInformationByHandle() 486 // lets us avoid this! 487 FILE_END_OF_FILE_INFO info; 488 memset(&info, 0, sizeof(info)); 489 info.EndOfFile.QuadPart = size; 490 KJ_WIN32_HANDLE_ERRORS( 491 SetFileInformationByHandle(handle, FileEndOfFileInfo, &info, sizeof(info))) { 492 case ERROR_CALL_NOT_IMPLEMENTED: { 493 // Wine doesn't implement this. :( 494 495 LONG currentHigh = 0; 496 LONG currentLow = SetFilePointer(handle, 0, ¤tHigh, FILE_CURRENT); 497 if (currentLow == INVALID_SET_FILE_POINTER) { 498 KJ_FAIL_WIN32("SetFilePointer", GetLastError()); 499 } 500 uint64_t current = (uint64_t(currentHigh) << 32) | uint64_t((ULONG)currentLow); 501 502 LONG endLow = size & 0x00000000ffffffffull; 503 LONG endHigh = size >> 32; 504 if (SetFilePointer(handle, endLow, &endHigh, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { 505 KJ_FAIL_WIN32("SetFilePointer", GetLastError()); 506 } 507 508 KJ_WIN32(SetEndOfFile(handle)); 509 510 if (current < size) { 511 if (SetFilePointer(handle, currentLow, ¤tHigh, FILE_BEGIN) == 512 INVALID_SET_FILE_POINTER) { 513 KJ_FAIL_WIN32("SetFilePointer", GetLastError()); 514 } 515 } 516 517 break; 518 } 519 default: 520 KJ_FAIL_WIN32("SetFileInformationByHandle", error); 521 } 522 } 523 524 class WritableFileMappingImpl final: public WritableFileMapping { 525 public: 526 WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {} 527 528 ArrayPtr<byte> get() const override { 529 // const_cast OK because WritableFileMapping does indeed provide a writable view despite 530 // being const itself. 531 return arrayPtr(const_cast<byte*>(bytes.begin()), bytes.size()); 532 } 533 534 void changed(ArrayPtr<byte> slice) const override { 535 KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(), 536 "byte range is not part of this mapping"); 537 538 // Nothing needed here -- NT tracks dirty pages. 539 } 540 541 void sync(ArrayPtr<byte> slice) const override { 542 KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(), 543 "byte range is not part of this mapping"); 544 545 // Zero is treated specially by FlushViewOfFile(), so check for it. (This also handles the 546 // case where `bytes` is actually empty and not a real mapping.) 547 if (slice.size() > 0) { 548 KJ_WIN32(FlushViewOfFile(slice.begin(), slice.size())); 549 } 550 } 551 552 private: 553 Array<byte> bytes; 554 }; 555 556 Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const { 557 if (size == 0) { 558 // Windows won't allow zero-length mappings 559 return heap<WritableFileMappingImpl>(nullptr); 560 } 561 auto range = getMmapRange(offset, size); 562 void* mapping = win32Mmap(handle, range, PAGE_READWRITE, FILE_MAP_ALL_ACCESS); 563 auto array = Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset), 564 size, mmapDisposer); 565 return heap<WritableFileMappingImpl>(kj::mv(array)); 566 } 567 568 // copy() is not optimized on Windows. 569 570 // ReadableDirectory --------------------------------------------------------- 571 572 template <typename Func> 573 auto list(bool needTypes, Func&& func) const 574 -> Array<Decay<decltype(func(instance<StringPtr>(), instance<FsNode::Type>()))>> { 575 PathPtr path = KJ_ASSERT_NONNULL(dirPath); 576 auto glob = join16(path.forWin32Api(true), L"*"); 577 578 // TODO(perf): Use FindFileEx() with FindExInfoBasic? Not apparently supported on Vista. 579 // TODO(someday): Use NtQueryDirectoryObject() instead? It's "internal", but so much cleaner. 580 WIN32_FIND_DATAW data; 581 HANDLE handle = FindFirstFileW(glob.begin(), &data); 582 if (handle == INVALID_HANDLE_VALUE) { 583 auto error = GetLastError(); 584 if (error == ERROR_FILE_NOT_FOUND) return nullptr; 585 KJ_FAIL_WIN32("FindFirstFile", error, dbgStr(glob)); 586 } 587 KJ_DEFER(KJ_WIN32(FindClose(handle)) { break; }); 588 589 typedef Decay<decltype(func(instance<StringPtr>(), instance<FsNode::Type>()))> Entry; 590 kj::Vector<Entry> entries; 591 592 do { 593 auto name = decodeUtf16( 594 arrayPtr(reinterpret_cast<char16_t*>(data.cFileName), wcslen(data.cFileName))); 595 if (name != "." && name != ".." && !name.startsWith(HIDDEN_PREFIX)) { 596 entries.add(func(name, modeToType(data.dwFileAttributes, data.dwReserved0))); 597 } 598 } while (FindNextFileW(handle, &data)); 599 600 auto error = GetLastError(); 601 if (error != ERROR_NO_MORE_FILES) { 602 KJ_FAIL_WIN32("FindNextFile", error, path); 603 } 604 605 auto result = entries.releaseAsArray(); 606 std::sort(result.begin(), result.end()); 607 return result; 608 } 609 610 Array<String> listNames() const { 611 return list(false, [](StringPtr name, FsNode::Type type) { return heapString(name); }); 612 } 613 614 Array<ReadableDirectory::Entry> listEntries() const { 615 return list(true, [](StringPtr name, FsNode::Type type) { 616 return ReadableDirectory::Entry { type, heapString(name), }; 617 }); 618 } 619 620 bool exists(PathPtr path) const { 621 DWORD result = GetFileAttributesW(nativePath(path).begin()); 622 if (result == INVALID_FILE_ATTRIBUTES) { 623 auto error = GetLastError(); 624 switch (error) { 625 case ERROR_FILE_NOT_FOUND: 626 case ERROR_PATH_NOT_FOUND: 627 return false; 628 default: 629 KJ_FAIL_WIN32("GetFileAttributesEx(path)", error, path) { return false; } 630 } 631 } else { 632 return true; 633 } 634 } 635 636 Maybe<FsNode::Metadata> tryLstat(PathPtr path) const { 637 // We use FindFirstFileW() because in the case of symlinks it will return info about the 638 // symlink rather than info about the target. 639 WIN32_FIND_DATAW data; 640 HANDLE handle = FindFirstFileW(nativePath(path).begin(), &data); 641 if (handle == INVALID_HANDLE_VALUE) { 642 auto error = GetLastError(); 643 if (error == ERROR_FILE_NOT_FOUND) return nullptr; 644 KJ_FAIL_WIN32("FindFirstFile", error, path); 645 } else { 646 KJ_WIN32(FindClose(handle)); 647 return statToMetadata(data); 648 } 649 } 650 651 Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const { 652 HANDLE newHandle; 653 KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW( 654 nativePath(path).begin(), 655 GENERIC_READ, 656 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 657 NULL, 658 OPEN_EXISTING, 659 FILE_ATTRIBUTE_NORMAL, 660 NULL)) { 661 case ERROR_FILE_NOT_FOUND: 662 case ERROR_PATH_NOT_FOUND: 663 return nullptr; 664 default: 665 KJ_FAIL_WIN32("CreateFile(path, OPEN_EXISTING)", error, path) { return nullptr; } 666 } 667 668 return newDiskReadableFile(kj::AutoCloseHandle(newHandle)); 669 } 670 671 Maybe<AutoCloseHandle> tryOpenSubdirInternal(PathPtr path) const { 672 HANDLE newHandle; 673 KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW( 674 nativePath(path).begin(), 675 GENERIC_READ, 676 // When opening directories, we do NOT use FILE_SHARE_DELETE, because we need the directory 677 // path to remain vaild. 678 // 679 // TODO(someday): Use NtCreateFile() and related "internal" APIs that allow for 680 // openat()-like behavior? 681 FILE_SHARE_READ | FILE_SHARE_WRITE, 682 NULL, 683 OPEN_EXISTING, 684 FILE_FLAG_BACKUP_SEMANTICS, // apparently, this flag is required for directories 685 NULL)) { 686 case ERROR_FILE_NOT_FOUND: 687 case ERROR_PATH_NOT_FOUND: 688 return nullptr; 689 default: 690 KJ_FAIL_WIN32("CreateFile(directoryPath, OPEN_EXISTING)", error, path) { return nullptr; } 691 } 692 693 kj::AutoCloseHandle ownHandle(newHandle); 694 695 BY_HANDLE_FILE_INFORMATION info; 696 KJ_WIN32(GetFileInformationByHandle(ownHandle, &info)); 697 698 KJ_REQUIRE(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY, "not a directory", path); 699 return kj::mv(ownHandle); 700 } 701 702 Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const { 703 return tryOpenSubdirInternal(path).map([&](AutoCloseHandle&& handle) { 704 return newDiskReadableDirectory(kj::mv(handle), KJ_ASSERT_NONNULL(dirPath).append(path)); 705 }); 706 } 707 708 Maybe<String> tryReadlink(PathPtr path) const { 709 // Windows symlinks work differently from Unix. Generally they are set up by the system 710 // administrator and apps are expected to treat them transparently. Hence, on Windows, we act 711 // as if nothing is a symlink by always returning null here. 712 // TODO(someday): If we want to treat Windows symlinks more like Unix ones, start by reverting 713 // the comment that added this comment. 714 return nullptr; 715 } 716 717 // Directory ----------------------------------------------------------------- 718 719 static LPSECURITY_ATTRIBUTES makeSecAttr(WriteMode mode) { 720 if (has(mode, WriteMode::PRIVATE)) { 721 KJ_UNIMPLEMENTED("WriteMode::PRIVATE on Win32 is not implemented"); 722 } 723 724 return nullptr; 725 } 726 727 bool tryMkdir(PathPtr path, WriteMode mode, bool noThrow) const { 728 // Internal function to make a directory. 729 730 auto filename = nativePath(path); 731 732 KJ_WIN32_HANDLE_ERRORS(CreateDirectoryW(filename.begin(), makeSecAttr(mode))) { 733 case ERROR_ALREADY_EXISTS: 734 case ERROR_FILE_EXISTS: { 735 // Apparently this path exists. 736 if (!has(mode, WriteMode::MODIFY)) { 737 // Require exclusive create. 738 return false; 739 } 740 741 // MODIFY is allowed, so we just need to check whether the existing entry is a directory. 742 DWORD attr = GetFileAttributesW(filename.begin()); 743 if (attr == INVALID_FILE_ATTRIBUTES) { 744 // CreateDirectory() says it already exists but we can't get attributes. Maybe it's a 745 // dangling link, or maybe we can't access it for some reason. Assume failure. 746 // 747 // TODO(someday): Maybe we should be creating the directory at the target of the 748 // link? 749 goto failed; 750 } 751 return attr & FILE_ATTRIBUTE_DIRECTORY; 752 } 753 case ERROR_PATH_NOT_FOUND: 754 if (has(mode, WriteMode::CREATE_PARENT) && path.size() > 0 && 755 tryMkdir(path.parent(), WriteMode::CREATE | WriteMode::MODIFY | 756 WriteMode::CREATE_PARENT, true)) { 757 // Retry, but make sure we don't try to create the parent again. 758 return tryMkdir(path, mode - WriteMode::CREATE_PARENT, noThrow); 759 } else { 760 goto failed; 761 } 762 default: 763 failed: 764 if (noThrow) { 765 // Caller requested no throwing. 766 return false; 767 } else { 768 KJ_FAIL_WIN32("CreateDirectory", error, path); 769 } 770 } 771 772 return true; 773 } 774 775 kj::Maybe<Array<wchar_t>> createNamedTemporary( 776 PathPtr finalName, WriteMode mode, Path& kjTempPath, 777 Function<BOOL(const wchar_t*)> tryCreate) const { 778 // Create a temporary file which will eventually replace `finalName`. 779 // 780 // Calls `tryCreate` to actually create the temporary, passing in the desired path. tryCreate() 781 // is expected to behave like a win32 call, returning a BOOL and setting `GetLastError()` on 782 // error. tryCreate() MUST fail with ERROR_{FILE,ALREADY}_EXISTS if the path exists -- this is 783 // not checked in advance, since it needs to be checked atomically. In the case of 784 // ERROR_*_EXISTS, tryCreate() will be called again with a new path. 785 // 786 // Returns the temporary path that succeeded. Only returns nullptr if there was an exception 787 // but we're compiled with -fno-exceptions. 788 // 789 // The optional parameter `kjTempPath` is filled in with the KJ Path of the temporary. 790 791 if (finalName.size() == 0) { 792 KJ_FAIL_REQUIRE("can't replace self") { break; } 793 return nullptr; 794 } 795 796 static uint counter = 0; 797 static const DWORD pid = GetCurrentProcessId(); 798 auto tempName = kj::str(HIDDEN_PREFIX, pid, '.', counter++, '.', 799 finalName.basename()[0], ".partial"); 800 kjTempPath = finalName.parent().append(tempName); 801 auto path = nativePath(kjTempPath); 802 803 KJ_WIN32_HANDLE_ERRORS(tryCreate(path.begin())) { 804 case ERROR_ALREADY_EXISTS: 805 case ERROR_FILE_EXISTS: 806 // Try again with a new counter value. 807 return createNamedTemporary(finalName, mode, kj::mv(tryCreate)); 808 case ERROR_PATH_NOT_FOUND: 809 if (has(mode, WriteMode::CREATE_PARENT) && finalName.size() > 1 && 810 tryMkdir(finalName.parent(), WriteMode::CREATE | WriteMode::MODIFY | 811 WriteMode::CREATE_PARENT, true)) { 812 // Retry, but make sure we don't try to create the parent again. 813 mode = mode - WriteMode::CREATE_PARENT; 814 return createNamedTemporary(finalName, mode, kj::mv(tryCreate)); 815 } 816 KJ_FALLTHROUGH; 817 default: 818 KJ_FAIL_WIN32("create(path)", error, path) { break; } 819 return nullptr; 820 } 821 822 return kj::mv(path); 823 } 824 825 kj::Maybe<Array<wchar_t>> createNamedTemporary( 826 PathPtr finalName, WriteMode mode, Function<BOOL(const wchar_t*)> tryCreate) const { 827 Path dummy = nullptr; 828 return createNamedTemporary(finalName, mode, dummy, kj::mv(tryCreate)); 829 } 830 831 bool tryReplaceNode(PathPtr path, WriteMode mode, 832 Function<BOOL(const wchar_t*)> tryCreate) const { 833 // Replaces the given path with an object created by calling tryCreate(). 834 // 835 // tryCreate() must behave like a win32 call which creates the node at the path passed to it, 836 // returning FALSE error. If the path passed to tryCreate already exists, it MUST fail with 837 // ERROR_{FILE,ALREADY}_EXISTS. 838 // 839 // When `mode` includes MODIFY, replaceNode() reacts to ERROR_*_EXISTS by creating the 840 // node in a temporary location and then rename()ing it into place. 841 842 if (path.size() == 0) { 843 KJ_FAIL_REQUIRE("can't replace self") { return false; } 844 } 845 846 auto filename = nativePath(path); 847 848 if (has(mode, WriteMode::CREATE)) { 849 // First try just cerating the node in-place. 850 KJ_WIN32_HANDLE_ERRORS(tryCreate(filename.begin())) { 851 case ERROR_ALREADY_EXISTS: 852 case ERROR_FILE_EXISTS: 853 // Target exists. 854 if (has(mode, WriteMode::MODIFY)) { 855 // Fall back to MODIFY path, below. 856 break; 857 } else { 858 return false; 859 } 860 case ERROR_PATH_NOT_FOUND: 861 if (has(mode, WriteMode::CREATE_PARENT) && path.size() > 0 && 862 tryMkdir(path.parent(), WriteMode::CREATE | WriteMode::MODIFY | 863 WriteMode::CREATE_PARENT, true)) { 864 // Retry, but make sure we don't try to create the parent again. 865 return tryReplaceNode(path, mode - WriteMode::CREATE_PARENT, kj::mv(tryCreate)); 866 } 867 KJ_FALLTHROUGH; 868 default: 869 KJ_FAIL_WIN32("create(path)", error, path) { return false; } 870 } else { 871 // Success. 872 return true; 873 } 874 } 875 876 // Either we don't have CREATE mode or the target already exists. We need to perform a 877 // replacement instead. 878 879 KJ_IF_MAYBE(tempPath, createNamedTemporary(path, mode, kj::mv(tryCreate))) { 880 if (tryCommitReplacement(path, *tempPath, mode)) { 881 return true; 882 } else { 883 KJ_WIN32_HANDLE_ERRORS(DeleteFileW(tempPath->begin())) { 884 case ERROR_FILE_NOT_FOUND: 885 // meh 886 break; 887 default: 888 KJ_FAIL_WIN32("DeleteFile(tempPath)", error, dbgStr(*tempPath)); 889 } 890 return false; 891 } 892 } else { 893 // threw, but exceptions are disabled 894 return false; 895 } 896 } 897 898 Maybe<AutoCloseHandle> tryOpenFileInternal(PathPtr path, WriteMode mode, bool append) const { 899 DWORD disposition; 900 if (has(mode, WriteMode::MODIFY)) { 901 if (has(mode, WriteMode::CREATE)) { 902 disposition = OPEN_ALWAYS; 903 } else { 904 disposition = OPEN_EXISTING; 905 } 906 } else { 907 if (has(mode, WriteMode::CREATE)) { 908 disposition = CREATE_NEW; 909 } else { 910 // Neither CREATE nor MODIFY -- impossible to satisfy preconditions. 911 return nullptr; 912 } 913 } 914 915 DWORD access = GENERIC_READ | GENERIC_WRITE; 916 if (append) { 917 // FILE_GENERIC_WRITE includes both FILE_APPEND_DATA and FILE_WRITE_DATA, but we only want 918 // the former. There are also a zillion other bits that we need, annoyingly. 919 access = (FILE_READ_ATTRIBUTES | FILE_GENERIC_WRITE) & ~FILE_WRITE_DATA; 920 } 921 922 auto filename = path.toString(); 923 924 HANDLE newHandle; 925 KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW( 926 nativePath(path).begin(), 927 access, 928 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 929 makeSecAttr(mode), 930 disposition, 931 FILE_ATTRIBUTE_NORMAL, 932 NULL)) { 933 case ERROR_PATH_NOT_FOUND: 934 if (has(mode, WriteMode::CREATE)) { 935 // A parent directory didn't exist. Maybe cerate it. 936 if (has(mode, WriteMode::CREATE_PARENT) && path.size() > 0 && 937 tryMkdir(path.parent(), WriteMode::CREATE | WriteMode::MODIFY | 938 WriteMode::CREATE_PARENT, true)) { 939 // Retry, but make sure we don't try to create the parent again. 940 return tryOpenFileInternal(path, mode - WriteMode::CREATE_PARENT, append); 941 } 942 943 KJ_FAIL_REQUIRE("parent is not a directory", path) { return nullptr; } 944 } else { 945 // MODIFY-only mode. ERROR_PATH_NOT_FOUND = parent path doesn't exist = return null. 946 return nullptr; 947 } 948 case ERROR_FILE_NOT_FOUND: 949 if (!has(mode, WriteMode::CREATE)) { 950 // MODIFY-only mode. ERROR_FILE_NOT_FOUND = doesn't exist = return null. 951 return nullptr; 952 } 953 goto failed; 954 case ERROR_ALREADY_EXISTS: 955 case ERROR_FILE_EXISTS: 956 if (!has(mode, WriteMode::MODIFY)) { 957 // CREATE-only mode. ERROR_ALREADY_EXISTS = already exists = return null. 958 return nullptr; 959 } 960 goto failed; 961 default: 962 failed: 963 KJ_FAIL_WIN32("CreateFile", error, path) { return nullptr; } 964 } 965 966 return kj::AutoCloseHandle(newHandle); 967 } 968 969 bool tryCommitReplacement( 970 PathPtr toPath, ArrayPtr<const wchar_t> fromPath, 971 WriteMode mode, kj::Maybe<kj::PathPtr> pathForCreatingParents = nullptr) const { 972 // Try to use MoveFileEx() to replace `toPath` with `fromPath`. 973 974 auto wToPath = nativePath(toPath); 975 976 DWORD flags = has(mode, WriteMode::MODIFY) ? MOVEFILE_REPLACE_EXISTING : 0; 977 978 if (!has(mode, WriteMode::CREATE)) { 979 // Non-atomically verify that target exists. There's no way to make this atomic. 980 DWORD result = GetFileAttributesW(wToPath.begin()); 981 if (result == INVALID_FILE_ATTRIBUTES) { 982 auto error = GetLastError(); 983 switch (error) { 984 case ERROR_FILE_NOT_FOUND: 985 case ERROR_PATH_NOT_FOUND: 986 return false; 987 default: 988 KJ_FAIL_WIN32("GetFileAttributesEx(toPath)", error, toPath) { return false; } 989 } 990 } 991 } 992 993 KJ_WIN32_HANDLE_ERRORS(MoveFileExW(fromPath.begin(), wToPath.begin(), flags)) { 994 case ERROR_ALREADY_EXISTS: 995 case ERROR_FILE_EXISTS: 996 // We must not be in MODIFY mode. 997 return false; 998 case ERROR_PATH_NOT_FOUND: 999 KJ_IF_MAYBE(p, pathForCreatingParents) { 1000 if (has(mode, WriteMode::CREATE_PARENT) && 1001 p->size() > 0 && tryMkdir(p->parent(), 1002 WriteMode::CREATE | WriteMode::MODIFY | WriteMode::CREATE_PARENT, true)) { 1003 // Retry, but make sure we don't try to create the parent again. 1004 return tryCommitReplacement(toPath, fromPath, mode - WriteMode::CREATE_PARENT); 1005 } 1006 } 1007 goto default_; 1008 1009 case ERROR_ACCESS_DENIED: { 1010 // This often means that the target already exists and cannot be replaced, e.g. because 1011 // it is a directory. Move it out of the way first, then move our replacement in, then 1012 // delete the old thing. 1013 1014 if (has(mode, WriteMode::MODIFY)) { 1015 KJ_IF_MAYBE(tempName, 1016 createNamedTemporary(toPath, WriteMode::CREATE, [&](const wchar_t* tempName2) { 1017 return MoveFileW(wToPath.begin(), tempName2); 1018 })) { 1019 KJ_WIN32_HANDLE_ERRORS(MoveFileW(fromPath.begin(), wToPath.begin())) { 1020 default: 1021 // Try to move back. 1022 MoveFileW(tempName->begin(), wToPath.begin()); 1023 KJ_FAIL_WIN32("MoveFile", error, dbgStr(fromPath), dbgStr(wToPath)) { 1024 return false; 1025 } 1026 } 1027 1028 // Succeeded, delete temporary. 1029 rmrf(*tempName); 1030 return true; 1031 } else { 1032 // createNamedTemporary() threw exception but exceptions are disabled. 1033 return false; 1034 } 1035 } else { 1036 // Not MODIFY, so no overwrite allowed. If the file really does exist, we need to return 1037 // false. 1038 if (GetFileAttributesW(wToPath.begin()) != INVALID_FILE_ATTRIBUTES) { 1039 return false; 1040 } 1041 } 1042 1043 goto default_; 1044 } 1045 1046 default: 1047 default_: 1048 KJ_FAIL_WIN32("MoveFileEx", error, dbgStr(wToPath), dbgStr(fromPath)) { return false; } 1049 } 1050 1051 return true; 1052 } 1053 1054 template <typename T> 1055 class ReplacerImpl final: public Directory::Replacer<T> { 1056 public: 1057 ReplacerImpl(Own<T>&& object, const DiskHandle& parentDirectory, 1058 Array<wchar_t>&& tempPath, Path&& path, WriteMode mode) 1059 : Directory::Replacer<T>(mode), 1060 object(kj::mv(object)), parentDirectory(parentDirectory), 1061 tempPath(kj::mv(tempPath)), path(kj::mv(path)) {} 1062 1063 ~ReplacerImpl() noexcept(false) { 1064 if (!committed) { 1065 object = Own<T>(); // Force close of handle before trying to delete. 1066 1067 if (kj::isSameType<T, File>()) { 1068 KJ_WIN32(DeleteFileW(tempPath.begin())) { break; } 1069 } else { 1070 rmrfChildren(tempPath); 1071 KJ_WIN32(RemoveDirectoryW(tempPath.begin())) { break; } 1072 } 1073 } 1074 } 1075 1076 const T& get() override { 1077 return *object; 1078 } 1079 1080 bool tryCommit() override { 1081 KJ_ASSERT(!committed, "already committed") { return false; } 1082 1083 // For directories, we intentionally don't use FILE_SHARE_DELETE on our handle because if the 1084 // directory name changes our paths would be wrong. But, this means we can't rename the 1085 // directory here to commit it. So, we need to close the handle and then re-open it 1086 // afterwards. Ick. 1087 AutoCloseHandle* objectHandle = getHandlePointerHack(*object); 1088 if (kj::isSameType<T, Directory>()) { 1089 *objectHandle = nullptr; 1090 } 1091 KJ_DEFER({ 1092 if (kj::isSameType<T, Directory>()) { 1093 HANDLE newHandle = nullptr; 1094 KJ_WIN32(newHandle = CreateFileW( 1095 committed ? parentDirectory.nativePath(path).begin() : tempPath.begin(), 1096 GENERIC_READ, 1097 FILE_SHARE_READ | FILE_SHARE_WRITE, 1098 NULL, 1099 OPEN_EXISTING, 1100 FILE_FLAG_BACKUP_SEMANTICS, // apparently, this flag is required for directories 1101 NULL)) { return; } 1102 *objectHandle = AutoCloseHandle(newHandle); 1103 *getPathPointerHack(*object) = KJ_ASSERT_NONNULL(parentDirectory.dirPath).append(path); 1104 } 1105 }); 1106 1107 return committed = parentDirectory.tryCommitReplacement( 1108 path, tempPath, Directory::Replacer<T>::mode); 1109 } 1110 1111 private: 1112 Own<T> object; 1113 const DiskHandle& parentDirectory; 1114 Array<wchar_t> tempPath; 1115 Path path; 1116 bool committed = false; // true if *successfully* committed (in which case tempPath is gone) 1117 }; 1118 1119 template <typename T> 1120 class BrokenReplacer final: public Directory::Replacer<T> { 1121 // For recovery path when exceptions are disabled. 1122 1123 public: 1124 BrokenReplacer(Own<const T> inner) 1125 : Directory::Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY), 1126 inner(kj::mv(inner)) {} 1127 1128 const T& get() override { return *inner; } 1129 bool tryCommit() override { return false; } 1130 1131 private: 1132 Own<const T> inner; 1133 }; 1134 1135 Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const { 1136 return tryOpenFileInternal(path, mode, false).map(newDiskFile); 1137 } 1138 1139 Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const { 1140 HANDLE newHandle_; 1141 KJ_IF_MAYBE(temp, createNamedTemporary(path, mode, 1142 [&](const wchar_t* candidatePath) { 1143 newHandle_ = CreateFileW( 1144 candidatePath, 1145 GENERIC_READ | GENERIC_WRITE, 1146 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 1147 makeSecAttr(mode), 1148 CREATE_NEW, 1149 FILE_ATTRIBUTE_NORMAL, 1150 NULL); 1151 return newHandle_ != INVALID_HANDLE_VALUE; 1152 })) { 1153 AutoCloseHandle newHandle(newHandle_); 1154 return heap<ReplacerImpl<File>>(newDiskFile(kj::mv(newHandle)), *this, kj::mv(*temp), 1155 path.clone(), mode); 1156 } else { 1157 // threw, but exceptions are disabled 1158 return heap<BrokenReplacer<File>>(newInMemoryFile(nullClock())); 1159 } 1160 } 1161 1162 Own<const File> createTemporary() const { 1163 HANDLE newHandle_; 1164 KJ_IF_MAYBE(temp, createNamedTemporary(Path("unnamed"), WriteMode::CREATE, 1165 [&](const wchar_t* candidatePath) { 1166 newHandle_ = CreateFileW( 1167 candidatePath, 1168 GENERIC_READ | GENERIC_WRITE, 1169 0, 1170 NULL, // TODO(someday): makeSecAttr(WriteMode::PRIVATE), when it's implemented 1171 CREATE_NEW, 1172 FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, 1173 NULL); 1174 return newHandle_ != INVALID_HANDLE_VALUE; 1175 })) { 1176 AutoCloseHandle newHandle(newHandle_); 1177 return newDiskFile(kj::mv(newHandle)); 1178 } else { 1179 // threw, but exceptions are disabled 1180 return newInMemoryFile(nullClock()); 1181 } 1182 } 1183 1184 Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const { 1185 return tryOpenFileInternal(path, mode, true).map(newDiskAppendableFile); 1186 } 1187 1188 Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const { 1189 // Must create before open. 1190 if (has(mode, WriteMode::CREATE)) { 1191 if (!tryMkdir(path, mode, false)) return nullptr; 1192 } 1193 1194 return tryOpenSubdirInternal(path).map([&](AutoCloseHandle&& handle) { 1195 return newDiskDirectory(kj::mv(handle), KJ_ASSERT_NONNULL(dirPath).append(path)); 1196 }); 1197 } 1198 1199 Own<Directory::Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const { 1200 Path kjTempPath = nullptr; 1201 KJ_IF_MAYBE(temp, createNamedTemporary(path, mode, kjTempPath, 1202 [&](const wchar_t* candidatePath) { 1203 return CreateDirectoryW(candidatePath, makeSecAttr(mode)); 1204 })) { 1205 HANDLE subdirHandle_; 1206 KJ_WIN32_HANDLE_ERRORS(subdirHandle_ = CreateFileW( 1207 temp->begin(), 1208 GENERIC_READ, 1209 FILE_SHARE_READ | FILE_SHARE_WRITE, 1210 NULL, 1211 OPEN_EXISTING, 1212 FILE_FLAG_BACKUP_SEMANTICS, // apparently, this flag is required for directories 1213 NULL)) { 1214 default: 1215 KJ_FAIL_WIN32("CreateFile(just-created-temporary, OPEN_EXISTING)", error, path) { 1216 goto fail; 1217 } 1218 } 1219 1220 AutoCloseHandle subdirHandle(subdirHandle_); 1221 return heap<ReplacerImpl<Directory>>( 1222 newDiskDirectory(kj::mv(subdirHandle), 1223 KJ_ASSERT_NONNULL(dirPath).append(kj::mv(kjTempPath))), 1224 *this, kj::mv(*temp), path.clone(), mode); 1225 } else { 1226 // threw, but exceptions are disabled 1227 fail: 1228 return heap<BrokenReplacer<Directory>>(newInMemoryDirectory(nullClock())); 1229 } 1230 } 1231 1232 bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const { 1233 // We can't really create symlinks on Windows. Reasons: 1234 // - We'd need to know whether the target is a file or a directory to pass the correct flags. 1235 // That means we'd need to evaluate the link content and track down the target. What if the 1236 // taget doesn't exist? It's unclear if this is even allowed on Windows. 1237 // - Apparently, creating symlinks is a privileged operation on Windows prior to Windows 10. 1238 // The flag SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE is very new. 1239 KJ_UNIMPLEMENTED( 1240 "Creating symbolic links is not supported on Windows due to semantic differences."); 1241 } 1242 1243 bool tryTransfer(PathPtr toPath, WriteMode toMode, 1244 const Directory& fromDirectory, PathPtr fromPath, 1245 TransferMode mode, const Directory& self) const { 1246 KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; } 1247 1248 // Try to get the "from" path. 1249 Array<wchar_t> rawFromPath; 1250 #if !KJ_NO_RTTI 1251 // Oops, dynamicDowncastIfAvailable() doesn't work since this isn't a downcast, it's a 1252 // side-cast... 1253 if (auto dh = dynamic_cast<const DiskHandle*>(&fromDirectory)) { 1254 rawFromPath = dh->nativePath(fromPath); 1255 } else 1256 #endif 1257 KJ_IF_MAYBE(h, fromDirectory.getWin32Handle()) { 1258 // Can't downcast to DiskHandle, but getWin32Handle() returns a handle... maybe RTTI is 1259 // disabled? Or maybe this is some kind of wrapper? 1260 rawFromPath = getPathFromHandle(*h).append(fromPath).forWin32Api(true); 1261 } else { 1262 // Not a disk directory, so fall back to default implementation. 1263 return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode); 1264 } 1265 1266 if (mode == TransferMode::LINK) { 1267 return tryReplaceNode(toPath, toMode, [&](const wchar_t* candidatePath) { 1268 return CreateHardLinkW(candidatePath, rawFromPath.begin(), NULL); 1269 }); 1270 } else if (mode == TransferMode::MOVE) { 1271 return tryCommitReplacement(toPath, rawFromPath, toMode, toPath); 1272 } else if (mode == TransferMode::COPY) { 1273 // We can accellerate copies on Windows. 1274 1275 if (!has(toMode, WriteMode::CREATE)) { 1276 // Non-atomically verify that target exists. There's no way to make this atomic. 1277 if (!exists(toPath)) return false; 1278 } 1279 1280 bool failIfExists = !has(toMode, WriteMode::MODIFY); 1281 KJ_WIN32_HANDLE_ERRORS( 1282 CopyFileW(rawFromPath.begin(), nativePath(toPath).begin(), failIfExists)) { 1283 case ERROR_ALREADY_EXISTS: 1284 case ERROR_FILE_EXISTS: 1285 case ERROR_FILE_NOT_FOUND: 1286 case ERROR_PATH_NOT_FOUND: 1287 return false; 1288 case ERROR_ACCESS_DENIED: 1289 // This usually means that fromPath was a directory or toPath was a direcotry. Fall back 1290 // to default implementation. 1291 break; 1292 default: 1293 KJ_FAIL_WIN32("CopyFile", error, fromPath, toPath) { return false; } 1294 } else { 1295 // Copy succeeded. 1296 return true; 1297 } 1298 } 1299 1300 // OK, we can't do anything efficient using the OS. Fall back to default implementation. 1301 return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode); 1302 } 1303 1304 bool tryRemove(PathPtr path) const { 1305 return rmrf(nativePath(path)); 1306 } 1307 }; 1308 1309 #define FSNODE_METHODS \ 1310 Maybe<void*> getWin32Handle() const override { return DiskHandle::getWin32Handle(); } \ 1311 \ 1312 Metadata stat() const override { return DiskHandle::stat(); } \ 1313 void sync() const override { DiskHandle::sync(); } \ 1314 void datasync() const override { DiskHandle::datasync(); } 1315 1316 class DiskReadableFile final: public ReadableFile, public DiskHandle { 1317 public: 1318 DiskReadableFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {} 1319 1320 Own<const FsNode> cloneFsNode() const override { 1321 return heap<DiskReadableFile>(DiskHandle::clone()); 1322 } 1323 1324 FSNODE_METHODS 1325 1326 size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override { 1327 return DiskHandle::read(offset, buffer); 1328 } 1329 Array<const byte> mmap(uint64_t offset, uint64_t size) const override { 1330 return DiskHandle::mmap(offset, size); 1331 } 1332 Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override { 1333 return DiskHandle::mmapPrivate(offset, size); 1334 } 1335 }; 1336 1337 class DiskAppendableFile final: public AppendableFile, public DiskHandle { 1338 public: 1339 DiskAppendableFile(AutoCloseHandle&& handle) 1340 : DiskHandle(kj::mv(handle), nullptr), 1341 stream(DiskHandle::handle.get()) {} 1342 1343 Own<const FsNode> cloneFsNode() const override { 1344 return heap<DiskAppendableFile>(DiskHandle::clone()); 1345 } 1346 1347 FSNODE_METHODS 1348 1349 void write(const void* buffer, size_t size) override { stream.write(buffer, size); } 1350 void write(ArrayPtr<const ArrayPtr<const byte>> pieces) override { 1351 implicitCast<OutputStream&>(stream).write(pieces); 1352 } 1353 1354 private: 1355 HandleOutputStream stream; 1356 }; 1357 1358 class DiskFile final: public File, public DiskHandle { 1359 public: 1360 DiskFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {} 1361 1362 Own<const FsNode> cloneFsNode() const override { 1363 return heap<DiskFile>(DiskHandle::clone()); 1364 } 1365 1366 FSNODE_METHODS 1367 1368 size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override { 1369 return DiskHandle::read(offset, buffer); 1370 } 1371 Array<const byte> mmap(uint64_t offset, uint64_t size) const override { 1372 return DiskHandle::mmap(offset, size); 1373 } 1374 Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override { 1375 return DiskHandle::mmapPrivate(offset, size); 1376 } 1377 1378 void write(uint64_t offset, ArrayPtr<const byte> data) const override { 1379 DiskHandle::write(offset, data); 1380 } 1381 void zero(uint64_t offset, uint64_t size) const override { 1382 DiskHandle::zero(offset, size); 1383 } 1384 void truncate(uint64_t size) const override { 1385 DiskHandle::truncate(size); 1386 } 1387 Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const override { 1388 return DiskHandle::mmapWritable(offset, size); 1389 } 1390 // copy() is not optimized on Windows. 1391 }; 1392 1393 class DiskReadableDirectory final: public ReadableDirectory, public DiskHandle { 1394 public: 1395 DiskReadableDirectory(AutoCloseHandle&& handle, Path&& path) 1396 : DiskHandle(kj::mv(handle), kj::mv(path)) {} 1397 1398 Own<const FsNode> cloneFsNode() const override { 1399 return heap<DiskReadableDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone()); 1400 } 1401 1402 FSNODE_METHODS 1403 1404 Array<String> listNames() const override { return DiskHandle::listNames(); } 1405 Array<Entry> listEntries() const override { return DiskHandle::listEntries(); } 1406 bool exists(PathPtr path) const override { return DiskHandle::exists(path); } 1407 Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override { 1408 return DiskHandle::tryLstat(path); 1409 } 1410 Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override { 1411 return DiskHandle::tryOpenFile(path); 1412 } 1413 Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override { 1414 return DiskHandle::tryOpenSubdir(path); 1415 } 1416 Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); } 1417 }; 1418 1419 class DiskDirectoryBase: public Directory, public DiskHandle { 1420 public: 1421 DiskDirectoryBase(AutoCloseHandle&& handle, Path&& path) 1422 : DiskHandle(kj::mv(handle), kj::mv(path)) {} 1423 1424 bool exists(PathPtr path) const override { return DiskHandle::exists(path); } 1425 Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override { return DiskHandle::tryLstat(path); } 1426 Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override { 1427 return DiskHandle::tryOpenFile(path); 1428 } 1429 Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override { 1430 return DiskHandle::tryOpenSubdir(path); 1431 } 1432 Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); } 1433 1434 Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const override { 1435 return DiskHandle::tryOpenFile(path, mode); 1436 } 1437 Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override { 1438 return DiskHandle::replaceFile(path, mode); 1439 } 1440 Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const override { 1441 return DiskHandle::tryAppendFile(path, mode); 1442 } 1443 Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const override { 1444 return DiskHandle::tryOpenSubdir(path, mode); 1445 } 1446 Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override { 1447 return DiskHandle::replaceSubdir(path, mode); 1448 } 1449 bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const override { 1450 return DiskHandle::trySymlink(linkpath, content, mode); 1451 } 1452 bool tryTransfer(PathPtr toPath, WriteMode toMode, 1453 const Directory& fromDirectory, PathPtr fromPath, 1454 TransferMode mode) const override { 1455 return DiskHandle::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode, *this); 1456 } 1457 // tryTransferTo() not implemented because we have nothing special we can do. 1458 bool tryRemove(PathPtr path) const override { 1459 return DiskHandle::tryRemove(path); 1460 } 1461 }; 1462 1463 class DiskDirectory final: public DiskDirectoryBase { 1464 public: 1465 DiskDirectory(AutoCloseHandle&& handle, Path&& path) 1466 : DiskDirectoryBase(kj::mv(handle), kj::mv(path)) {} 1467 1468 Own<const FsNode> cloneFsNode() const override { 1469 return heap<DiskDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone()); 1470 } 1471 1472 FSNODE_METHODS 1473 1474 Array<String> listNames() const override { return DiskHandle::listNames(); } 1475 Array<Entry> listEntries() const override { return DiskHandle::listEntries(); } 1476 Own<const File> createTemporary() const override { 1477 return DiskHandle::createTemporary(); 1478 } 1479 }; 1480 1481 class RootDiskDirectory final: public DiskDirectoryBase { 1482 // On Windows, the root directory is special. 1483 // 1484 // HACK: We only override a few functions of DiskDirectory, and we rely on the fact that 1485 // Path::forWin32Api(true) throws an exception complaining about missing drive letter if the 1486 // path is totally empty. 1487 1488 public: 1489 RootDiskDirectory(): DiskDirectoryBase(nullptr, Path(nullptr)) {} 1490 1491 Own<const FsNode> cloneFsNode() const override { 1492 return heap<RootDiskDirectory>(); 1493 } 1494 1495 Metadata stat() const override { 1496 return { Type::DIRECTORY, 0, 0, UNIX_EPOCH, 1, 0 }; 1497 } 1498 void sync() const override {} 1499 void datasync() const override {} 1500 1501 Array<String> listNames() const override { 1502 return KJ_MAP(e, listEntries()) { return kj::mv(e.name); }; 1503 } 1504 Array<Entry> listEntries() const override { 1505 DWORD drives = GetLogicalDrives(); 1506 if (drives == 0) { 1507 KJ_FAIL_WIN32("GetLogicalDrives()", GetLastError()) { return nullptr; } 1508 } 1509 1510 Vector<Entry> results; 1511 for (uint i = 0; i < 26; i++) { 1512 if (drives & (1 << i)) { 1513 char name[2] = { static_cast<char>('A' + i), ':' }; 1514 results.add(Entry { FsNode::Type::DIRECTORY, kj::heapString(name, 2) }); 1515 } 1516 } 1517 1518 return results.releaseAsArray(); 1519 } 1520 1521 Own<const File> createTemporary() const override { 1522 KJ_FAIL_REQUIRE("can't create temporaries in Windows pseudo-root directory (the drive list)"); 1523 } 1524 }; 1525 1526 class DiskFilesystem final: public Filesystem { 1527 public: 1528 DiskFilesystem() 1529 : DiskFilesystem(computeCurrentPath()) {} 1530 DiskFilesystem(Path currentPath) 1531 : current(KJ_ASSERT_NONNULL(root.tryOpenSubdirInternal(currentPath), 1532 "path returned by GetCurrentDirectory() doesn't exist?"), 1533 kj::mv(currentPath)) {} 1534 1535 const Directory& getRoot() const override { 1536 return root; 1537 } 1538 1539 const Directory& getCurrent() const override { 1540 return current; 1541 } 1542 1543 PathPtr getCurrentPath() const override { 1544 return KJ_ASSERT_NONNULL(current.dirPath); 1545 } 1546 1547 private: 1548 RootDiskDirectory root; 1549 DiskDirectory current; 1550 1551 static Path computeCurrentPath() { 1552 DWORD tryLen = MAX_PATH; 1553 for (;;) { 1554 auto temp = kj::heapArray<wchar_t>(tryLen + 1); 1555 DWORD len = GetCurrentDirectoryW(temp.size(), temp.begin()); 1556 if (len == 0) { 1557 KJ_FAIL_WIN32("GetCurrentDirectory", GetLastError()) { break; } 1558 return Path("."); 1559 } 1560 if (len < temp.size()) { 1561 return Path::parseWin32Api(temp.slice(0, len)); 1562 } 1563 // Try again with new length. 1564 tryLen = len; 1565 } 1566 } 1567 }; 1568 1569 } // namespace 1570 1571 Own<ReadableFile> newDiskReadableFile(AutoCloseHandle fd) { 1572 return heap<DiskReadableFile>(kj::mv(fd)); 1573 } 1574 Own<AppendableFile> newDiskAppendableFile(AutoCloseHandle fd) { 1575 return heap<DiskAppendableFile>(kj::mv(fd)); 1576 } 1577 Own<File> newDiskFile(AutoCloseHandle fd) { 1578 return heap<DiskFile>(kj::mv(fd)); 1579 } 1580 Own<ReadableDirectory> newDiskReadableDirectory(AutoCloseHandle fd) { 1581 return heap<DiskReadableDirectory>(kj::mv(fd), getPathFromHandle(fd)); 1582 } 1583 static Own<ReadableDirectory> newDiskReadableDirectory(AutoCloseHandle fd, Path&& path) { 1584 return heap<DiskReadableDirectory>(kj::mv(fd), kj::mv(path)); 1585 } 1586 Own<Directory> newDiskDirectory(AutoCloseHandle fd) { 1587 return heap<DiskDirectory>(kj::mv(fd), getPathFromHandle(fd)); 1588 } 1589 static Own<Directory> newDiskDirectory(AutoCloseHandle fd, Path&& path) { 1590 return heap<DiskDirectory>(kj::mv(fd), kj::mv(path)); 1591 } 1592 1593 Own<Filesystem> newDiskFilesystem() { 1594 return heap<DiskFilesystem>(); 1595 } 1596 1597 static AutoCloseHandle* getHandlePointerHack(Directory& dir) { 1598 return &static_cast<DiskDirectoryBase&>(dir).handle; 1599 } 1600 static Path* getPathPointerHack(Directory& dir) { 1601 return &KJ_ASSERT_NONNULL(static_cast<DiskDirectoryBase&>(dir).dirPath); 1602 } 1603 1604 } // namespace kj 1605 1606 #endif // _WIN32