// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors // Licensed under the MIT License: // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if _WIN32 // For Unix implementation, see filesystem-disk-unix.c++. #include "filesystem.h" #include "debug.h" #include "encoding.h" #include "vector.h" #include <algorithm> #include <wchar.h> // Request Vista-level APIs. #define WINVER 0x0600 #define _WIN32_WINNT 0x0600 #define WIN32_LEAN_AND_MEAN // ::eyeroll:: #include <windows.h> #include <winioctl.h> #include "windows-sanity.h" namespace kj { static Own<ReadableDirectory> newDiskReadableDirectory(AutoCloseHandle fd, Path&& path); static Own<Directory> newDiskDirectory(AutoCloseHandle fd, Path&& path); static AutoCloseHandle* getHandlePointerHack(File& file) { return nullptr; } static AutoCloseHandle* getHandlePointerHack(Directory& dir); static Path* getPathPointerHack(File& file) { return nullptr; } static Path* getPathPointerHack(Directory& dir); namespace { struct REPARSE_DATA_BUFFER { // From ntifs.h, which is part of the driver development kit so not necessarily available I // guess. ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; }; #define HIDDEN_PREFIX ".kj-tmp." // Prefix for temp files which should be hidden when listing a directory. // // If you change this, make sure to update the unit test. static constexpr int64_t WIN32_EPOCH_OFFSET = 116444736000000000ull; // Number of 100ns intervals from Jan 1, 1601 to Jan 1, 1970. static Date toKjDate(FILETIME t) { int64_t value = (static_cast<uint64_t>(t.dwHighDateTime) << 32) | t.dwLowDateTime; return (value - WIN32_EPOCH_OFFSET) * (100 * kj::NANOSECONDS) + UNIX_EPOCH; } static FsNode::Type modeToType(DWORD attrs, DWORD reparseTag) { if ((attrs & FILE_ATTRIBUTE_REPARSE_POINT) && reparseTag == IO_REPARSE_TAG_SYMLINK) { return FsNode::Type::SYMLINK; } if (attrs & FILE_ATTRIBUTE_DIRECTORY) return FsNode::Type::DIRECTORY; return FsNode::Type::FILE; } static FsNode::Metadata statToMetadata(const BY_HANDLE_FILE_INFORMATION& stats) { uint64_t size = (implicitCast<uint64_t>(stats.nFileSizeHigh) << 32) | stats.nFileSizeLow; // Assume file index is usually a small number, i.e. nFileIndexHigh is usually 0. So we try to // put the serial number in the upper 32 bits and the index in the lower. uint64_t hash = ((uint64_t(stats.dwVolumeSerialNumber) << 32) ^ (uint64_t(stats.nFileIndexHigh) << 32)) | (uint64_t(stats.nFileIndexLow)); return FsNode::Metadata { modeToType(stats.dwFileAttributes, 0), size, // In theory, spaceUsed should be based on GetCompressedFileSize(), but requiring an extra // syscall for something rarely used would be sad. size, toKjDate(stats.ftLastWriteTime), stats.nNumberOfLinks, hash }; } static FsNode::Metadata statToMetadata(const WIN32_FIND_DATAW& stats) { uint64_t size = (implicitCast<uint64_t>(stats.nFileSizeHigh) << 32) | stats.nFileSizeLow; return FsNode::Metadata { modeToType(stats.dwFileAttributes, stats.dwReserved0), size, // In theory, spaceUsed should be based on GetCompressedFileSize(), but requiring an extra // syscall for something rarely used would be sad. size, toKjDate(stats.ftLastWriteTime), // We can't get the number of links without opening the file, apparently. Meh. 1, // We can't produce a reliable hashCode without opening the file. 0 }; } static Array<wchar_t> join16(ArrayPtr<const wchar_t> path, const wchar_t* file) { // Assumes `path` ends with a NUL terminator (and `file` is of course NUL terminated as well). size_t len = wcslen(file) + 1; auto result = kj::heapArray<wchar_t>(path.size() + len); memcpy(result.begin(), path.begin(), path.asBytes().size() - sizeof(wchar_t)); result[path.size() - 1] = '\\'; memcpy(result.begin() + path.size(), file, len * sizeof(wchar_t)); return result; } static String dbgStr(ArrayPtr<const wchar_t> wstr) { if (wstr.size() > 0 && wstr[wstr.size() - 1] == L'\0') { wstr = wstr.slice(0, wstr.size() - 1); } return decodeWideString(wstr); } static void rmrfChildren(ArrayPtr<const wchar_t> path) { auto glob = join16(path, L"*"); WIN32_FIND_DATAW data; HANDLE handle = FindFirstFileW(glob.begin(), &data); if (handle == INVALID_HANDLE_VALUE) { auto error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) return; KJ_FAIL_WIN32("FindFirstFile", error, dbgStr(glob)) { return; } } KJ_DEFER(KJ_WIN32(FindClose(handle)) { break; }); do { // Ignore "." and "..", ugh. if (data.cFileName[0] == L'.') { if (data.cFileName[1] == L'\0' || (data.cFileName[1] == L'.' && data.cFileName[2] == L'\0')) { continue; } } auto child = join16(path, data.cFileName); // For rmrf purposes, we assume any "reparse points" are symlink-like, even if they aren't // actually the "symbolic link" reparse type, because we don't want to recursively delete any // shared content. if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { rmrfChildren(child); uint retryCount = 0; retry: KJ_WIN32_HANDLE_ERRORS(RemoveDirectoryW(child.begin())) { case ERROR_DIR_NOT_EMPTY: // On Windows, deleting a file actually only schedules it for deletion. Under heavy // load it may take a bit for the deletion to go through. Or, if another process has // the file open, it may not be deleted until that process closes it. // // We'll repeatedly retry for up to 100ms, then give up. This is awful but there's no // way to tell for sure if the system is just being slow or if someone has the file // open. if (retryCount++ < 10) { Sleep(10); goto retry; } // fallthrough default: KJ_FAIL_WIN32("RemoveDirectory", error, dbgStr(child)) { break; } } } else { KJ_WIN32(DeleteFileW(child.begin())); } } while (FindNextFileW(handle, &data)); auto error = GetLastError(); if (error != ERROR_NO_MORE_FILES) { KJ_FAIL_WIN32("FindNextFile", error, dbgStr(path)) { return; } } } static bool rmrf(ArrayPtr<const wchar_t> path) { // Figure out whether this is a file or a directory. // // We use FindFirstFileW() because in the case of symlinks it will return info about the // symlink rather than info about the target. WIN32_FIND_DATAW data; HANDLE handle = FindFirstFileW(path.begin(), &data); if (handle == INVALID_HANDLE_VALUE) { auto error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) return false; KJ_FAIL_WIN32("FindFirstFile", error, dbgStr(path)); } KJ_WIN32(FindClose(handle)); // For remove purposes, we assume any "reparse points" are symlink-like, even if they aren't // actually the "symbolic link" reparse type, because we don't want to recursively delete any // shared content. if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { // directory rmrfChildren(path); KJ_WIN32(RemoveDirectoryW(path.begin()), dbgStr(path)); } else { KJ_WIN32(DeleteFileW(path.begin()), dbgStr(path)); } return true; } static Path getPathFromHandle(HANDLE handle) { DWORD tryLen = MAX_PATH; for (;;) { auto temp = kj::heapArray<wchar_t>(tryLen + 1); DWORD len = GetFinalPathNameByHandleW(handle, temp.begin(), tryLen, 0); if (len == 0) { KJ_FAIL_WIN32("GetFinalPathNameByHandleW", GetLastError()); } if (len < temp.size()) { return Path::parseWin32Api(temp.slice(0, len)); } // Try again with new length. tryLen = len; } } struct MmapRange { uint64_t offset; uint64_t size; }; static size_t getAllocationGranularity() { SYSTEM_INFO info; GetSystemInfo(&info); return info.dwAllocationGranularity; }; static MmapRange getMmapRange(uint64_t offset, uint64_t size) { // Rounds the given offset down to the nearest page boundary, and adjusts the size up to match. // (This is somewhat different from Unix: we do NOT round the size up to an even multiple of // pages.) static const uint64_t pageSize = getAllocationGranularity(); uint64_t pageMask = pageSize - 1; uint64_t realOffset = offset & ~pageMask; uint64_t end = offset + size; return { realOffset, end - realOffset }; } class MmapDisposer: public ArrayDisposer { protected: void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount, size_t capacity, void (*destroyElement)(void*)) const { auto range = getMmapRange(reinterpret_cast<uintptr_t>(firstElement), elementSize * elementCount); void* mapping = reinterpret_cast<void*>(range.offset); if (mapping != nullptr) { KJ_ASSERT(UnmapViewOfFile(mapping)) { break; } } } }; #if _MSC_VER && _MSC_VER < 1910 // TODO(msvc): MSVC 2015 can't initialize a constexpr's vtable correctly. const MmapDisposer mmapDisposer = MmapDisposer(); #else constexpr MmapDisposer mmapDisposer = MmapDisposer(); #endif void* win32Mmap(HANDLE handle, MmapRange range, DWORD pageProtect, DWORD access) { HANDLE mappingHandle; mappingHandle = CreateFileMappingW(handle, NULL, pageProtect, 0, 0, NULL); if (mappingHandle == INVALID_HANDLE_VALUE) { auto error = GetLastError(); if (error == ERROR_FILE_INVALID && range.size == 0) { // The documentation says that CreateFileMapping will fail with ERROR_FILE_INVALID if the // file size is zero. Ugh. return nullptr; } KJ_FAIL_WIN32("CreateFileMapping", error); } KJ_DEFER(KJ_WIN32(CloseHandle(mappingHandle)) { break; }); void* mapping = MapViewOfFile(mappingHandle, access, static_cast<DWORD>(range.offset >> 32), static_cast<DWORD>(range.offset), range.size); if (mapping == nullptr) { KJ_FAIL_WIN32("MapViewOfFile", GetLastError()); } // It's unclear from the documentation whether mappings will always start at a multiple of the // allocation granularity, but we depend on that later, so check it... KJ_ASSERT(getMmapRange(reinterpret_cast<uintptr_t>(mapping), 0).size == 0); return mapping; } class DiskHandle { // We need to implement each of ReadableFile, AppendableFile, File, ReadableDirectory, and // Directory for disk handles. There is a lot of implementation overlap between these, especially // stat(), sync(), etc. We can't have everything inherit from a common DiskFsNode that implements // these because then we get diamond inheritance which means we need to make all our inheritance // virtual which means downcasting requires RTTI which violates our goal of supporting compiling // with no RTTI. So instead we have the DiskHandle class which implements all the methods without // inheriting anything, and then we have DiskFile, DiskDirectory, etc. hold this and delegate to // it. Ugly, but works. public: DiskHandle(AutoCloseHandle&& handle, Maybe<Path> dirPath) : handle(kj::mv(handle)), dirPath(kj::mv(dirPath)) {} AutoCloseHandle handle; kj::Maybe<Path> dirPath; // needed for directories, empty for files Array<wchar_t> nativePath(PathPtr path) const { return KJ_ASSERT_NONNULL(dirPath).append(path).forWin32Api(true); } // OsHandle ------------------------------------------------------------------ AutoCloseHandle clone() const { HANDLE newHandle; KJ_WIN32(DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &newHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)); return AutoCloseHandle(newHandle); } HANDLE getWin32Handle() const { return handle.get(); } // FsNode -------------------------------------------------------------------- FsNode::Metadata stat() const { BY_HANDLE_FILE_INFORMATION stats; KJ_WIN32(GetFileInformationByHandle(handle, &stats)); auto metadata = statToMetadata(stats); // Get space usage, e.g. for sparse files. Apparently the correct way to do this is to query // "compression". FILE_COMPRESSION_INFO compInfo; KJ_WIN32_HANDLE_ERRORS(GetFileInformationByHandleEx( handle, FileCompressionInfo, &compInfo, sizeof(compInfo))) { case ERROR_CALL_NOT_IMPLEMENTED: // Probably WINE. break; default: KJ_FAIL_WIN32("GetFileInformationByHandleEx(FileCompressionInfo)", error) { break; } break; } else { metadata.spaceUsed = compInfo.CompressedFileSize.QuadPart; } return metadata; } void sync() const { KJ_WIN32(FlushFileBuffers(handle)); } void datasync() const { KJ_WIN32(FlushFileBuffers(handle)); } // ReadableFile -------------------------------------------------------------- size_t read(uint64_t offset, ArrayPtr<byte> buffer) const { // ReadFile() probably never returns short reads unless it hits EOF. Unfortunately, though, // this is not documented, and it's unclear whether we can rely on it. size_t total = 0; while (buffer.size() > 0) { // Apparently, the way to fake pread() on Windows is to provide an OVERLAPPED structure even // though we're not doing overlapped I/O. OVERLAPPED overlapped; memset(&overlapped, 0, sizeof(overlapped)); overlapped.Offset = static_cast<DWORD>(offset); overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32); DWORD n; KJ_WIN32_HANDLE_ERRORS(ReadFile(handle, buffer.begin(), buffer.size(), &n, &overlapped)) { case ERROR_HANDLE_EOF: // The documentation claims this shouldn't happen for synchronous reads, but it seems // to happen for me, at least under WINE. n = 0; break; default: KJ_FAIL_WIN32("ReadFile", offset, buffer.size()) { return total; } } if (n == 0) break; total += n; offset += n; buffer = buffer.slice(n, buffer.size()); } return total; } Array<const byte> mmap(uint64_t offset, uint64_t size) const { auto range = getMmapRange(offset, size); const void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_READ); return Array<const byte>(reinterpret_cast<const byte*>(mapping) + (offset - range.offset), size, mmapDisposer); } Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const { auto range = getMmapRange(offset, size); void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_COPY); return Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset), size, mmapDisposer); } // File ---------------------------------------------------------------------- void write(uint64_t offset, ArrayPtr<const byte> data) const { // WriteFile() probably never returns short writes unless there's no space left on disk. // Unfortunately, though, this is not documented, and it's unclear whether we can rely on it. while (data.size() > 0) { // Apparently, the way to fake pwrite() on Windows is to provide an OVERLAPPED structure even // though we're not doing overlapped I/O. OVERLAPPED overlapped; memset(&overlapped, 0, sizeof(overlapped)); overlapped.Offset = static_cast<DWORD>(offset); overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32); DWORD n; KJ_WIN32(WriteFile(handle, data.begin(), data.size(), &n, &overlapped)); KJ_ASSERT(n > 0, "WriteFile() returned zero?"); offset += n; data = data.slice(n, data.size()); } } void zero(uint64_t offset, uint64_t size) const { FILE_ZERO_DATA_INFORMATION info; memset(&info, 0, sizeof(info)); info.FileOffset.QuadPart = offset; info.BeyondFinalZero.QuadPart = offset + size; DWORD dummy; KJ_WIN32_HANDLE_ERRORS(DeviceIoControl(handle, FSCTL_SET_ZERO_DATA, &info, sizeof(info), NULL, 0, &dummy, NULL)) { case ERROR_NOT_SUPPORTED: { // Dang. Let's do it the hard way. static const byte ZEROS[4096] = { 0 }; while (size > sizeof(ZEROS)) { write(offset, ZEROS); size -= sizeof(ZEROS); offset += sizeof(ZEROS); } write(offset, kj::arrayPtr(ZEROS, size)); break; } default: KJ_FAIL_WIN32("DeviceIoControl(FSCTL_SET_ZERO_DATA)", error); break; } } void truncate(uint64_t size) const { // SetEndOfFile() would require seeking the file. It looks like SetFileInformationByHandle() // lets us avoid this! FILE_END_OF_FILE_INFO info; memset(&info, 0, sizeof(info)); info.EndOfFile.QuadPart = size; KJ_WIN32_HANDLE_ERRORS( SetFileInformationByHandle(handle, FileEndOfFileInfo, &info, sizeof(info))) { case ERROR_CALL_NOT_IMPLEMENTED: { // Wine doesn't implement this. :( LONG currentHigh = 0; LONG currentLow = SetFilePointer(handle, 0, ¤tHigh, FILE_CURRENT); if (currentLow == INVALID_SET_FILE_POINTER) { KJ_FAIL_WIN32("SetFilePointer", GetLastError()); } uint64_t current = (uint64_t(currentHigh) << 32) | uint64_t((ULONG)currentLow); LONG endLow = size & 0x00000000ffffffffull; LONG endHigh = size >> 32; if (SetFilePointer(handle, endLow, &endHigh, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { KJ_FAIL_WIN32("SetFilePointer", GetLastError()); } KJ_WIN32(SetEndOfFile(handle)); if (current < size) { if (SetFilePointer(handle, currentLow, ¤tHigh, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { KJ_FAIL_WIN32("SetFilePointer", GetLastError()); } } break; } default: KJ_FAIL_WIN32("SetFileInformationByHandle", error); } } class WritableFileMappingImpl final: public WritableFileMapping { public: WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {} ArrayPtr<byte> get() const override { // const_cast OK because WritableFileMapping does indeed provide a writable view despite // being const itself. return arrayPtr(const_cast<byte*>(bytes.begin()), bytes.size()); } void changed(ArrayPtr<byte> slice) const override { KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(), "byte range is not part of this mapping"); // Nothing needed here -- NT tracks dirty pages. } void sync(ArrayPtr<byte> slice) const override { KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(), "byte range is not part of this mapping"); // Zero is treated specially by FlushViewOfFile(), so check for it. if (slice.size() > 0) { KJ_WIN32(FlushViewOfFile(slice.begin(), slice.size())); } } private: Array<byte> bytes; }; Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const { auto range = getMmapRange(offset, size); void* mapping = win32Mmap(handle, range, PAGE_READWRITE, FILE_MAP_ALL_ACCESS); auto array = Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset), size, mmapDisposer); return heap<WritableFileMappingImpl>(kj::mv(array)); } // copy() is not optimized on Windows. // ReadableDirectory --------------------------------------------------------- template <typename Func> auto list(bool needTypes, Func&& func) const -> Array<Decay<decltype(func(instance<StringPtr>(), instance<FsNode::Type>()))>> { PathPtr path = KJ_ASSERT_NONNULL(dirPath); auto glob = join16(path.forWin32Api(true), L"*"); // TODO(perf): Use FindFileEx() with FindExInfoBasic? Not apparently supported on Vista. // TODO(someday): Use NtQueryDirectoryObject() instead? It's "internal", but so much cleaner. WIN32_FIND_DATAW data; HANDLE handle = FindFirstFileW(glob.begin(), &data); if (handle == INVALID_HANDLE_VALUE) { auto error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) return nullptr; KJ_FAIL_WIN32("FindFirstFile", error, dbgStr(glob)); } KJ_DEFER(KJ_WIN32(FindClose(handle)) { break; }); typedef Decay<decltype(func(instance<StringPtr>(), instance<FsNode::Type>()))> Entry; kj::Vector<Entry> entries; do { auto name = decodeUtf16( arrayPtr(reinterpret_cast<char16_t*>(data.cFileName), wcslen(data.cFileName))); if (name != "." && name != ".." && !name.startsWith(HIDDEN_PREFIX)) { entries.add(func(name, modeToType(data.dwFileAttributes, data.dwReserved0))); } } while (FindNextFileW(handle, &data)); auto error = GetLastError(); if (error != ERROR_NO_MORE_FILES) { KJ_FAIL_WIN32("FindNextFile", error, path); } auto result = entries.releaseAsArray(); std::sort(result.begin(), result.end()); return result; } Array<String> listNames() const { return list(false, [](StringPtr name, FsNode::Type type) { return heapString(name); }); } Array<ReadableDirectory::Entry> listEntries() const { return list(true, [](StringPtr name, FsNode::Type type) { return ReadableDirectory::Entry { type, heapString(name), }; }); } bool exists(PathPtr path) const { DWORD result = GetFileAttributesW(nativePath(path).begin()); if (result == INVALID_FILE_ATTRIBUTES) { auto error = GetLastError(); switch (error) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: return false; default: KJ_FAIL_WIN32("GetFileAttributesEx(path)", error, path) { return false; } } } else { return true; } } Maybe<FsNode::Metadata> tryLstat(PathPtr path) const { // We use FindFirstFileW() because in the case of symlinks it will return info about the // symlink rather than info about the target. WIN32_FIND_DATAW data; HANDLE handle = FindFirstFileW(nativePath(path).begin(), &data); if (handle == INVALID_HANDLE_VALUE) { auto error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) return nullptr; KJ_FAIL_WIN32("FindFirstFile", error, path); } else { KJ_WIN32(FindClose(handle)); return statToMetadata(data); } } Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const { HANDLE newHandle; KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW( nativePath(path).begin(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: return nullptr; default: KJ_FAIL_WIN32("CreateFile(path, OPEN_EXISTING)", error, path) { return nullptr; } } return newDiskReadableFile(kj::AutoCloseHandle(newHandle)); } Maybe<AutoCloseHandle> tryOpenSubdirInternal(PathPtr path) const { HANDLE newHandle; KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW( nativePath(path).begin(), GENERIC_READ, // When opening directories, we do NOT use FILE_SHARE_DELETE, because we need the directory // path to remain vaild. // // TODO(someday): Use NtCreateFile() and related "internal" APIs that allow for // openat()-like behavior? FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, // apparently, this flag is required for directories NULL)) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: return nullptr; default: KJ_FAIL_WIN32("CreateFile(directoryPath, OPEN_EXISTING)", error, path) { return nullptr; } } kj::AutoCloseHandle ownHandle(newHandle); BY_HANDLE_FILE_INFORMATION info; KJ_WIN32(GetFileInformationByHandle(ownHandle, &info)); KJ_REQUIRE(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY, "not a directory", path); return kj::mv(ownHandle); } Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const { return tryOpenSubdirInternal(path).map([&](AutoCloseHandle&& handle) { return newDiskReadableDirectory(kj::mv(handle), KJ_ASSERT_NONNULL(dirPath).append(path)); }); } Maybe<String> tryReadlink(PathPtr path) const { // Windows symlinks work differently from Unix. Generally they are set up by the system // administrator and apps are expected to treat them transparently. Hence, on Windows, we act // as if nothing is a symlink by always returning null here. // TODO(someday): If we want to treat Windows symlinks more like Unix ones, start by reverting // the comment that added this comment. return nullptr; } // Directory ----------------------------------------------------------------- static LPSECURITY_ATTRIBUTES makeSecAttr(WriteMode mode) { if (has(mode, WriteMode::PRIVATE)) { KJ_UNIMPLEMENTED("WriteMode::PRIVATE on Win32 is not implemented"); } return nullptr; } bool tryMkdir(PathPtr path, WriteMode mode, bool noThrow) const { // Internal function to make a directory. auto filename = nativePath(path); KJ_WIN32_HANDLE_ERRORS(CreateDirectoryW(filename.begin(), makeSecAttr(mode))) { case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: { // Apparently this path exists. if (!has(mode, WriteMode::MODIFY)) { // Require exclusive create. return false; } // MODIFY is allowed, so we just need to check whether the existing entry is a directory. DWORD attr = GetFileAttributesW(filename.begin()); if (attr == INVALID_FILE_ATTRIBUTES) { // CreateDirectory() says it already exists but we can't get attributes. Maybe it's a // dangling link, or maybe we can't access it for some reason. Assume failure. // // TODO(someday): Maybe we should be creating the directory at the target of the // link? goto failed; } return attr & FILE_ATTRIBUTE_DIRECTORY; } case ERROR_PATH_NOT_FOUND: if (has(mode, WriteMode::CREATE_PARENT) && path.size() > 0 && tryMkdir(path.parent(), WriteMode::CREATE | WriteMode::MODIFY | WriteMode::CREATE_PARENT, true)) { // Retry, but make sure we don't try to create the parent again. return tryMkdir(path, mode - WriteMode::CREATE_PARENT, noThrow); } else { goto failed; } default: failed: if (noThrow) { // Caller requested no throwing. return false; } else { KJ_FAIL_WIN32("CreateDirectory", error, path); } } return true; } kj::Maybe<Array<wchar_t>> createNamedTemporary( PathPtr finalName, WriteMode mode, Path& kjTempPath, Function<BOOL(const wchar_t*)> tryCreate) const { // Create a temporary file which will eventually replace `finalName`. // // Calls `tryCreate` to actually create the temporary, passing in the desired path. tryCreate() // is expected to behave like a win32 call, returning a BOOL and setting `GetLastError()` on // error. tryCreate() MUST fail with ERROR_{FILE,ALREADY}_EXISTS if the path exists -- this is // not checked in advance, since it needs to be checked atomically. In the case of // ERROR_*_EXISTS, tryCreate() will be called again with a new path. // // Returns the temporary path that succeeded. Only returns nullptr if there was an exception // but we're compiled with -fno-exceptions. // // The optional parameter `kjTempPath` is filled in with the KJ Path of the temporary. if (finalName.size() == 0) { KJ_FAIL_REQUIRE("can't replace self") { break; } return nullptr; } static uint counter = 0; static const DWORD pid = GetCurrentProcessId(); auto tempName = kj::str(HIDDEN_PREFIX, pid, '.', counter++, '.', finalName.basename()[0], ".partial"); kjTempPath = finalName.parent().append(tempName); auto path = nativePath(kjTempPath); KJ_WIN32_HANDLE_ERRORS(tryCreate(path.begin())) { case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: // Try again with a new counter value. return createNamedTemporary(finalName, mode, kj::mv(tryCreate)); case ERROR_PATH_NOT_FOUND: if (has(mode, WriteMode::CREATE_PARENT) && finalName.size() > 1 && tryMkdir(finalName.parent(), WriteMode::CREATE | WriteMode::MODIFY | WriteMode::CREATE_PARENT, true)) { // Retry, but make sure we don't try to create the parent again. mode = mode - WriteMode::CREATE_PARENT; return createNamedTemporary(finalName, mode, kj::mv(tryCreate)); } // fallthrough default: KJ_FAIL_WIN32("create(path)", error, path) { break; } return nullptr; } return kj::mv(path); } kj::Maybe<Array<wchar_t>> createNamedTemporary( PathPtr finalName, WriteMode mode, Function<BOOL(const wchar_t*)> tryCreate) const { Path dummy = nullptr; return createNamedTemporary(finalName, mode, dummy, kj::mv(tryCreate)); } bool tryReplaceNode(PathPtr path, WriteMode mode, Function<BOOL(const wchar_t*)> tryCreate) const { // Replaces the given path with an object created by calling tryCreate(). // // tryCreate() must behave like a win32 call which creates the node at the path passed to it, // returning FALSE error. If the path passed to tryCreate already exists, it MUST fail with // ERROR_{FILE,ALREADY}_EXISTS. // // When `mode` includes MODIFY, replaceNode() reacts to ERROR_*_EXISTS by creating the // node in a temporary location and then rename()ing it into place. if (path.size() == 0) { KJ_FAIL_REQUIRE("can't replace self") { return false; } } auto filename = nativePath(path); if (has(mode, WriteMode::CREATE)) { // First try just cerating the node in-place. KJ_WIN32_HANDLE_ERRORS(tryCreate(filename.begin())) { case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: // Target exists. if (has(mode, WriteMode::MODIFY)) { // Fall back to MODIFY path, below. break; } else { return false; } case ERROR_PATH_NOT_FOUND: if (has(mode, WriteMode::CREATE_PARENT) && path.size() > 0 && tryMkdir(path.parent(), WriteMode::CREATE | WriteMode::MODIFY | WriteMode::CREATE_PARENT, true)) { // Retry, but make sure we don't try to create the parent again. return tryReplaceNode(path, mode - WriteMode::CREATE_PARENT, kj::mv(tryCreate)); } default: KJ_FAIL_WIN32("create(path)", error, path) { return false; } } else { // Success. return true; } } // Either we don't have CREATE mode or the target already exists. We need to perform a // replacement instead. KJ_IF_MAYBE(tempPath, createNamedTemporary(path, mode, kj::mv(tryCreate))) { if (tryCommitReplacement(path, *tempPath, mode)) { return true; } else { KJ_WIN32_HANDLE_ERRORS(DeleteFileW(tempPath->begin())) { case ERROR_FILE_NOT_FOUND: // meh break; default: KJ_FAIL_WIN32("DeleteFile(tempPath)", error, dbgStr(*tempPath)); } return false; } } else { // threw, but exceptions are disabled return false; } } Maybe<AutoCloseHandle> tryOpenFileInternal(PathPtr path, WriteMode mode, bool append) const { DWORD disposition; if (has(mode, WriteMode::MODIFY)) { if (has(mode, WriteMode::CREATE)) { disposition = OPEN_ALWAYS; } else { disposition = OPEN_EXISTING; } } else { if (has(mode, WriteMode::CREATE)) { disposition = CREATE_NEW; } else { // Neither CREATE nor MODIFY -- impossible to satisfy preconditions. return nullptr; } } DWORD access = GENERIC_READ | GENERIC_WRITE; if (append) { // FILE_GENERIC_WRITE includes both FILE_APPEND_DATA and FILE_WRITE_DATA, but we only want // the former. There are also a zillion other bits that we need, annoyingly. access = (FILE_READ_ATTRIBUTES | FILE_GENERIC_WRITE) & ~FILE_WRITE_DATA; } auto filename = path.toString(); HANDLE newHandle; KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW( nativePath(path).begin(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, makeSecAttr(mode), disposition, FILE_ATTRIBUTE_NORMAL, NULL)) { case ERROR_PATH_NOT_FOUND: if (has(mode, WriteMode::CREATE)) { // A parent directory didn't exist. Maybe cerate it. if (has(mode, WriteMode::CREATE_PARENT) && path.size() > 0 && tryMkdir(path.parent(), WriteMode::CREATE | WriteMode::MODIFY | WriteMode::CREATE_PARENT, true)) { // Retry, but make sure we don't try to create the parent again. return tryOpenFileInternal(path, mode - WriteMode::CREATE_PARENT, append); } KJ_FAIL_REQUIRE("parent is not a directory", path) { return nullptr; } } else { // MODIFY-only mode. ERROR_PATH_NOT_FOUND = parent path doesn't exist = return null. return nullptr; } case ERROR_FILE_NOT_FOUND: if (!has(mode, WriteMode::CREATE)) { // MODIFY-only mode. ERROR_FILE_NOT_FOUND = doesn't exist = return null. return nullptr; } goto failed; case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: if (!has(mode, WriteMode::MODIFY)) { // CREATE-only mode. ERROR_ALREADY_EXISTS = already exists = return null. return nullptr; } goto failed; default: failed: KJ_FAIL_WIN32("CreateFile", error, path) { return nullptr; } } return kj::AutoCloseHandle(newHandle); } bool tryCommitReplacement( PathPtr toPath, ArrayPtr<const wchar_t> fromPath, WriteMode mode, kj::Maybe<kj::PathPtr> pathForCreatingParents = nullptr) const { // Try to use MoveFileEx() to replace `toPath` with `fromPath`. auto wToPath = nativePath(toPath); DWORD flags = has(mode, WriteMode::MODIFY) ? MOVEFILE_REPLACE_EXISTING : 0; if (!has(mode, WriteMode::CREATE)) { // Non-atomically verify that target exists. There's no way to make this atomic. DWORD result = GetFileAttributesW(wToPath.begin()); if (result == INVALID_FILE_ATTRIBUTES) { auto error = GetLastError(); switch (error) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: return false; default: KJ_FAIL_WIN32("GetFileAttributesEx(toPath)", error, toPath) { return false; } } } } KJ_WIN32_HANDLE_ERRORS(MoveFileExW(fromPath.begin(), wToPath.begin(), flags)) { case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: // We must not be in MODIFY mode. return false; case ERROR_PATH_NOT_FOUND: KJ_IF_MAYBE(p, pathForCreatingParents) { if (has(mode, WriteMode::CREATE_PARENT) && p->size() > 0 && tryMkdir(p->parent(), WriteMode::CREATE | WriteMode::MODIFY | WriteMode::CREATE_PARENT, true)) { // Retry, but make sure we don't try to create the parent again. return tryCommitReplacement(toPath, fromPath, mode - WriteMode::CREATE_PARENT); } } goto default_; case ERROR_ACCESS_DENIED: { // This often means that the target already exists and cannot be replaced, e.g. because // it is a directory. Move it out of the way first, then move our replacement in, then // delete the old thing. if (has(mode, WriteMode::MODIFY)) { KJ_IF_MAYBE(tempName, createNamedTemporary(toPath, WriteMode::CREATE, [&](const wchar_t* tempName2) { return MoveFileW(wToPath.begin(), tempName2); })) { KJ_WIN32_HANDLE_ERRORS(MoveFileW(fromPath.begin(), wToPath.begin())) { default: // Try to move back. MoveFileW(tempName->begin(), wToPath.begin()); KJ_FAIL_WIN32("MoveFile", error, dbgStr(fromPath), dbgStr(wToPath)) { return false; } } // Succeded, delete temporary. rmrf(*tempName); return true; } else { // createNamedTemporary() threw exception but exceptions are disabled. return false; } } else { // Not MODIFY, so no overwrite allowed. If the file really does exist, we need to return // false. if (GetFileAttributesW(wToPath.begin()) != INVALID_FILE_ATTRIBUTES) { return false; } } goto default_; } default: default_: KJ_FAIL_WIN32("MoveFileEx", error, dbgStr(wToPath), dbgStr(fromPath)) { return false; } } return true; } template <typename T> class ReplacerImpl final: public Directory::Replacer<T> { public: ReplacerImpl(Own<T>&& object, const DiskHandle& parentDirectory, Array<wchar_t>&& tempPath, Path&& path, WriteMode mode) : Directory::Replacer<T>(mode), object(kj::mv(object)), parentDirectory(parentDirectory), tempPath(kj::mv(tempPath)), path(kj::mv(path)) {} ~ReplacerImpl() noexcept(false) { if (!committed) { object = Own<T>(); // Force close of handle before trying to delete. if (kj::isSameType<T, File>()) { KJ_WIN32(DeleteFileW(tempPath.begin())) { break; } } else { rmrfChildren(tempPath); KJ_WIN32(RemoveDirectoryW(tempPath.begin())) { break; } } } } const T& get() override { return *object; } bool tryCommit() override { KJ_ASSERT(!committed, "already committed") { return false; } // For directories, we intentionally don't use FILE_SHARE_DELETE on our handle because if the // directory name changes our paths would be wrong. But, this means we can't rename the // directory here to commit it. So, we need to close the handle and then re-open it // afterwards. Ick. AutoCloseHandle* objectHandle = getHandlePointerHack(*object); if (kj::isSameType<T, Directory>()) { *objectHandle = nullptr; } KJ_DEFER({ if (kj::isSameType<T, Directory>()) { HANDLE newHandle = nullptr; KJ_WIN32(newHandle = CreateFileW( committed ? parentDirectory.nativePath(path).begin() : tempPath.begin(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, // apparently, this flag is required for directories NULL)) { return; } *objectHandle = AutoCloseHandle(newHandle); *getPathPointerHack(*object) = KJ_ASSERT_NONNULL(parentDirectory.dirPath).append(path); } }); return committed = parentDirectory.tryCommitReplacement( path, tempPath, Directory::Replacer<T>::mode); } private: Own<T> object; const DiskHandle& parentDirectory; Array<wchar_t> tempPath; Path path; bool committed = false; // true if *successfully* committed (in which case tempPath is gone) }; template <typename T> class BrokenReplacer final: public Directory::Replacer<T> { // For recovery path when exceptions are disabled. public: BrokenReplacer(Own<const T> inner) : Directory::Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY), inner(kj::mv(inner)) {} const T& get() override { return *inner; } bool tryCommit() override { return false; } private: Own<const T> inner; }; Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const { return tryOpenFileInternal(path, mode, false).map(newDiskFile); } Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const { HANDLE newHandle_; KJ_IF_MAYBE(temp, createNamedTemporary(path, mode, [&](const wchar_t* candidatePath) { newHandle_ = CreateFileW( candidatePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, makeSecAttr(mode), CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); return newHandle_ != INVALID_HANDLE_VALUE; })) { AutoCloseHandle newHandle(newHandle_); return heap<ReplacerImpl<File>>(newDiskFile(kj::mv(newHandle)), *this, kj::mv(*temp), path.clone(), mode); } else { // threw, but exceptions are disabled return heap<BrokenReplacer<File>>(newInMemoryFile(nullClock())); } } Own<const File> createTemporary() const { HANDLE newHandle_; KJ_IF_MAYBE(temp, createNamedTemporary(Path("unnamed"), WriteMode::CREATE, [&](const wchar_t* candidatePath) { newHandle_ = CreateFileW( candidatePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, // TODO(soon): makeSecAttr(WriteMode::PRIVATE), when it's implemented CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); return newHandle_ != INVALID_HANDLE_VALUE; })) { AutoCloseHandle newHandle(newHandle_); return newDiskFile(kj::mv(newHandle)); } else { // threw, but exceptions are disabled return newInMemoryFile(nullClock()); } } Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const { return tryOpenFileInternal(path, mode, true).map(newDiskAppendableFile); } Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const { // Must create before open. if (has(mode, WriteMode::CREATE)) { if (!tryMkdir(path, mode, false)) return nullptr; } return tryOpenSubdirInternal(path).map([&](AutoCloseHandle&& handle) { return newDiskDirectory(kj::mv(handle), KJ_ASSERT_NONNULL(dirPath).append(path)); }); } Own<Directory::Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const { Path kjTempPath = nullptr; KJ_IF_MAYBE(temp, createNamedTemporary(path, mode, kjTempPath, [&](const wchar_t* candidatePath) { return CreateDirectoryW(candidatePath, makeSecAttr(mode)); })) { HANDLE subdirHandle_; KJ_WIN32_HANDLE_ERRORS(subdirHandle_ = CreateFileW( temp->begin(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, // apparently, this flag is required for directories NULL)) { default: KJ_FAIL_WIN32("CreateFile(just-created-temporary, OPEN_EXISTING)", error, path) { goto fail; } } AutoCloseHandle subdirHandle(subdirHandle_); return heap<ReplacerImpl<Directory>>( newDiskDirectory(kj::mv(subdirHandle), KJ_ASSERT_NONNULL(dirPath).append(kj::mv(kjTempPath))), *this, kj::mv(*temp), path.clone(), mode); } else { // threw, but exceptions are disabled fail: return heap<BrokenReplacer<Directory>>(newInMemoryDirectory(nullClock())); } } bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const { // We can't really create symlinks on Windows. Reasons: // - We'd need to know whether the target is a file or a directory to pass the correct flags. // That means we'd need to evaluate the link content and track down the target. What if the // taget doesn't exist? It's unclear if this is even allowed on Windows. // - Apparently, creating symlinks is a privileged operation on Windows prior to Windows 10. // The flag SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE is very new. KJ_UNIMPLEMENTED( "Creating symbolic links is not supported on Windows due to semantic differences."); } bool tryTransfer(PathPtr toPath, WriteMode toMode, const Directory& fromDirectory, PathPtr fromPath, TransferMode mode, const Directory& self) const { KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; } // Try to get the "from" path. Array<wchar_t> rawFromPath; #if !KJ_NO_RTTI // Oops, dynamicDowncastIfAvailable() doesn't work since this isn't a downcast, it's a // side-cast... if (auto dh = dynamic_cast<const DiskHandle*>(&fromDirectory)) { rawFromPath = dh->nativePath(fromPath); } else #endif KJ_IF_MAYBE(h, fromDirectory.getWin32Handle()) { // Can't downcast to DiskHandle, but getWin32Handle() returns a handle... maybe RTTI is // disabled? Or maybe this is some kind of wrapper? rawFromPath = getPathFromHandle(*h).append(fromPath).forWin32Api(true); } else { // Not a disk directory, so fall back to default implementation. return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode); } if (mode == TransferMode::LINK) { return tryReplaceNode(toPath, toMode, [&](const wchar_t* candidatePath) { return CreateHardLinkW(candidatePath, rawFromPath.begin(), NULL); }); } else if (mode == TransferMode::MOVE) { return tryCommitReplacement(toPath, rawFromPath, toMode, toPath); } else if (mode == TransferMode::COPY) { // We can accellerate copies on Windows. if (!has(toMode, WriteMode::CREATE)) { // Non-atomically verify that target exists. There's no way to make this atomic. if (!exists(toPath)) return false; } bool failIfExists = !has(toMode, WriteMode::MODIFY); KJ_WIN32_HANDLE_ERRORS( CopyFileW(rawFromPath.begin(), nativePath(toPath).begin(), failIfExists)) { case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: return false; case ERROR_ACCESS_DENIED: // This usually means that fromPath was a directory or toPath was a direcotry. Fall back // to default implementation. break; default: KJ_FAIL_WIN32("CopyFile", error, fromPath, toPath) { return false; } } else { // Copy succeded. return true; } } // OK, we can't do anything efficient using the OS. Fall back to default implementation. return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode); } bool tryRemove(PathPtr path) const { return rmrf(nativePath(path)); } }; #define FSNODE_METHODS \ Maybe<void*> getWin32Handle() const override { return DiskHandle::getWin32Handle(); } \ \ Metadata stat() const override { return DiskHandle::stat(); } \ void sync() const override { DiskHandle::sync(); } \ void datasync() const override { DiskHandle::datasync(); } class DiskReadableFile final: public ReadableFile, public DiskHandle { public: DiskReadableFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {} Own<const FsNode> cloneFsNode() const override { return heap<DiskReadableFile>(DiskHandle::clone()); } FSNODE_METHODS size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override { return DiskHandle::read(offset, buffer); } Array<const byte> mmap(uint64_t offset, uint64_t size) const override { return DiskHandle::mmap(offset, size); } Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override { return DiskHandle::mmapPrivate(offset, size); } }; class DiskAppendableFile final: public AppendableFile, public DiskHandle { public: DiskAppendableFile(AutoCloseHandle&& handle) : DiskHandle(kj::mv(handle), nullptr), stream(DiskHandle::handle.get()) {} Own<const FsNode> cloneFsNode() const override { return heap<DiskAppendableFile>(DiskHandle::clone()); } FSNODE_METHODS void write(const void* buffer, size_t size) override { stream.write(buffer, size); } void write(ArrayPtr<const ArrayPtr<const byte>> pieces) override { implicitCast<OutputStream&>(stream).write(pieces); } private: HandleOutputStream stream; }; class DiskFile final: public File, public DiskHandle { public: DiskFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {} Own<const FsNode> cloneFsNode() const override { return heap<DiskFile>(DiskHandle::clone()); } FSNODE_METHODS size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override { return DiskHandle::read(offset, buffer); } Array<const byte> mmap(uint64_t offset, uint64_t size) const override { return DiskHandle::mmap(offset, size); } Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override { return DiskHandle::mmapPrivate(offset, size); } void write(uint64_t offset, ArrayPtr<const byte> data) const override { DiskHandle::write(offset, data); } void zero(uint64_t offset, uint64_t size) const override { DiskHandle::zero(offset, size); } void truncate(uint64_t size) const override { DiskHandle::truncate(size); } Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const override { return DiskHandle::mmapWritable(offset, size); } // copy() is not optimized on Windows. }; class DiskReadableDirectory final: public ReadableDirectory, public DiskHandle { public: DiskReadableDirectory(AutoCloseHandle&& handle, Path&& path) : DiskHandle(kj::mv(handle), kj::mv(path)) {} Own<const FsNode> cloneFsNode() const override { return heap<DiskReadableDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone()); } FSNODE_METHODS Array<String> listNames() const override { return DiskHandle::listNames(); } Array<Entry> listEntries() const override { return DiskHandle::listEntries(); } bool exists(PathPtr path) const override { return DiskHandle::exists(path); } Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override { return DiskHandle::tryLstat(path); } Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override { return DiskHandle::tryOpenFile(path); } Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override { return DiskHandle::tryOpenSubdir(path); } Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); } }; class DiskDirectoryBase: public Directory, public DiskHandle { public: DiskDirectoryBase(AutoCloseHandle&& handle, Path&& path) : DiskHandle(kj::mv(handle), kj::mv(path)) {} bool exists(PathPtr path) const override { return DiskHandle::exists(path); } Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override { return DiskHandle::tryLstat(path); } Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override { return DiskHandle::tryOpenFile(path); } Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override { return DiskHandle::tryOpenSubdir(path); } Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); } Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const override { return DiskHandle::tryOpenFile(path, mode); } Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override { return DiskHandle::replaceFile(path, mode); } Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const override { return DiskHandle::tryAppendFile(path, mode); } Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const override { return DiskHandle::tryOpenSubdir(path, mode); } Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override { return DiskHandle::replaceSubdir(path, mode); } bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const override { return DiskHandle::trySymlink(linkpath, content, mode); } bool tryTransfer(PathPtr toPath, WriteMode toMode, const Directory& fromDirectory, PathPtr fromPath, TransferMode mode) const override { return DiskHandle::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode, *this); } // tryTransferTo() not implemented because we have nothing special we can do. bool tryRemove(PathPtr path) const override { return DiskHandle::tryRemove(path); } }; class DiskDirectory final: public DiskDirectoryBase { public: DiskDirectory(AutoCloseHandle&& handle, Path&& path) : DiskDirectoryBase(kj::mv(handle), kj::mv(path)) {} Own<const FsNode> cloneFsNode() const override { return heap<DiskDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone()); } FSNODE_METHODS Array<String> listNames() const override { return DiskHandle::listNames(); } Array<Entry> listEntries() const override { return DiskHandle::listEntries(); } Own<const File> createTemporary() const override { return DiskHandle::createTemporary(); } }; class RootDiskDirectory final: public DiskDirectoryBase { // On Windows, the root directory is special. // // HACK: We only override a few functions of DiskDirectory, and we rely on the fact that // Path::forWin32Api(true) throws an exception complaining about missing drive letter if the // path is totally empty. public: RootDiskDirectory(): DiskDirectoryBase(nullptr, Path(nullptr)) {} Own<const FsNode> cloneFsNode() const override { return heap<RootDiskDirectory>(); } Metadata stat() const override { return { Type::DIRECTORY, 0, 0, UNIX_EPOCH, 1, 0 }; } void sync() const override {} void datasync() const override {} Array<String> listNames() const override { return KJ_MAP(e, listEntries()) { return kj::mv(e.name); }; } Array<Entry> listEntries() const override { DWORD drives = GetLogicalDrives(); if (drives == 0) { KJ_FAIL_WIN32("GetLogicalDrives()", GetLastError()) { return nullptr; } } Vector<Entry> results; for (uint i = 0; i < 26; i++) { if (drives & (1 << i)) { char name[2] = { 'A' + i, ':' }; results.add(Entry { FsNode::Type::DIRECTORY, kj::heapString(name, 2) }); } } return results.releaseAsArray(); } Own<const File> createTemporary() const override { KJ_FAIL_REQUIRE("can't create temporaries in Windows pseudo-root directory (the drive list)"); } }; class DiskFilesystem final: public Filesystem { public: DiskFilesystem() : DiskFilesystem(computeCurrentPath()) {} DiskFilesystem(Path currentPath) : current(KJ_ASSERT_NONNULL(root.tryOpenSubdirInternal(currentPath), "path returned by GetCurrentDirectory() doesn't exist?"), kj::mv(currentPath)) {} const Directory& getRoot() const override { return root; } const Directory& getCurrent() const override { return current; } PathPtr getCurrentPath() const override { return KJ_ASSERT_NONNULL(current.dirPath); } private: RootDiskDirectory root; DiskDirectory current; static Path computeCurrentPath() { DWORD tryLen = MAX_PATH; for (;;) { auto temp = kj::heapArray<wchar_t>(tryLen + 1); DWORD len = GetCurrentDirectoryW(temp.size(), temp.begin()); if (len == 0) { KJ_FAIL_WIN32("GetCurrentDirectory", GetLastError()) { break; } return Path("."); } if (len < temp.size()) { return Path::parseWin32Api(temp.slice(0, len)); } // Try again with new length. tryLen = len; } } }; } // namespace Own<ReadableFile> newDiskReadableFile(AutoCloseHandle fd) { return heap<DiskReadableFile>(kj::mv(fd)); } Own<AppendableFile> newDiskAppendableFile(AutoCloseHandle fd) { return heap<DiskAppendableFile>(kj::mv(fd)); } Own<File> newDiskFile(AutoCloseHandle fd) { return heap<DiskFile>(kj::mv(fd)); } Own<ReadableDirectory> newDiskReadableDirectory(AutoCloseHandle fd) { return heap<DiskReadableDirectory>(kj::mv(fd), getPathFromHandle(fd)); } static Own<ReadableDirectory> newDiskReadableDirectory(AutoCloseHandle fd, Path&& path) { return heap<DiskReadableDirectory>(kj::mv(fd), kj::mv(path)); } Own<Directory> newDiskDirectory(AutoCloseHandle fd) { return heap<DiskDirectory>(kj::mv(fd), getPathFromHandle(fd)); } static Own<Directory> newDiskDirectory(AutoCloseHandle fd, Path&& path) { return heap<DiskDirectory>(kj::mv(fd), kj::mv(path)); } Own<Filesystem> newDiskFilesystem() { return heap<DiskFilesystem>(); } static AutoCloseHandle* getHandlePointerHack(Directory& dir) { return &static_cast<DiskDirectoryBase&>(dir).handle; } static Path* getPathPointerHack(Directory& dir) { return &KJ_ASSERT_NONNULL(static_cast<DiskDirectoryBase&>(dir).dirPath); } } // namespace kj #endif // _WIN32