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
getHandlePointerHack(File & file)44 static AutoCloseHandle* getHandlePointerHack(File& file) { return nullptr; }
45 static AutoCloseHandle* getHandlePointerHack(Directory& dir);
getPathPointerHack(File & file)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
toKjDate(FILETIME t)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
modeToType(DWORD attrs,DWORD reparseTag)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
statToMetadata(const BY_HANDLE_FILE_INFORMATION & stats)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
statToMetadata(const WIN32_FIND_DATAW & stats)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
join16(ArrayPtr<const wchar_t> path,const wchar_t * file)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
dbgStr(ArrayPtr<const wchar_t> wstr)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
rmrfChildren(ArrayPtr<const wchar_t> path)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
rmrf(ArrayPtr<const wchar_t> path)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
getPathFromHandle(HANDLE handle)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
getAllocationGranularity()265 static size_t getAllocationGranularity() {
266 SYSTEM_INFO info;
267 GetSystemInfo(&info);
268 return info.dwAllocationGranularity;
269 };
270
getMmapRange(uint64_t offset,uint64_t size)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:
disposeImpl(void * firstElement,size_t elementSize,size_t elementCount,size_t capacity,void (* destroyElement)(void *)) const287 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
win32Mmap(HANDLE handle,MmapRange range,DWORD pageProtect,DWORD access)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:
DiskHandle(AutoCloseHandle && handle,Maybe<Path> dirPath)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
nativePath(PathPtr path) const340 Array<wchar_t> nativePath(PathPtr path) const {
341 return KJ_ASSERT_NONNULL(dirPath).append(path).forWin32Api(true);
342 }
343
344 // OsHandle ------------------------------------------------------------------
345
clone() const346 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
getWin32Handle() const353 HANDLE getWin32Handle() const {
354 return handle.get();
355 }
356
357 // FsNode --------------------------------------------------------------------
358
stat() const359 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
sync() const382 void sync() const { KJ_WIN32(FlushFileBuffers(handle)); }
datasync() const383 void datasync() const { KJ_WIN32(FlushFileBuffers(handle)); }
384
385 // ReadableFile --------------------------------------------------------------
386
read(uint64_t offset,ArrayPtr<byte> buffer) const387 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
mmap(uint64_t offset,uint64_t size) const418 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
mmapPrivate(uint64_t offset,uint64_t size) const426 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
write(uint64_t offset,ArrayPtr<const byte> data) const436 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
zero(uint64_t offset,uint64_t size) const456 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
truncate(uint64_t size) const484 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:
WritableFileMappingImpl(Array<byte> bytes)526 WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {}
527
get() const528 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
changed(ArrayPtr<byte> slice) const534 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
sync(ArrayPtr<byte> slice) const541 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
mmapWritable(uint64_t offset,uint64_t size) const556 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>
list(bool needTypes,Func && func) const573 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
listNames() const610 Array<String> listNames() const {
611 return list(false, [](StringPtr name, FsNode::Type type) { return heapString(name); });
612 }
613
listEntries() const614 Array<ReadableDirectory::Entry> listEntries() const {
615 return list(true, [](StringPtr name, FsNode::Type type) {
616 return ReadableDirectory::Entry { type, heapString(name), };
617 });
618 }
619
exists(PathPtr path) const620 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
tryLstat(PathPtr path) const636 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
tryOpenFile(PathPtr path) const651 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
tryOpenSubdirInternal(PathPtr path) const671 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
tryOpenSubdir(PathPtr path) const702 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
tryReadlink(PathPtr path) const708 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
makeSecAttr(WriteMode mode)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
tryMkdir(PathPtr path,WriteMode mode,bool noThrow) const727 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
createNamedTemporary(PathPtr finalName,WriteMode mode,Path & kjTempPath,Function<BOOL (const wchar_t *)> tryCreate) const775 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
createNamedTemporary(PathPtr finalName,WriteMode mode,Function<BOOL (const wchar_t *)> tryCreate) const825 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
tryReplaceNode(PathPtr path,WriteMode mode,Function<BOOL (const wchar_t *)> tryCreate) const831 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
tryOpenFileInternal(PathPtr path,WriteMode mode,bool append) const898 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
tryCommitReplacement(PathPtr toPath,ArrayPtr<const wchar_t> fromPath,WriteMode mode,kj::Maybe<kj::PathPtr> pathForCreatingParents=nullptr) const969 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:
ReplacerImpl(Own<T> && object,const DiskHandle & parentDirectory,Array<wchar_t> && tempPath,Path && path,WriteMode mode)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
~ReplacerImpl()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
get()1076 const T& get() override {
1077 return *object;
1078 }
1079
tryCommit()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:
BrokenReplacer(Own<const T> inner)1124 BrokenReplacer(Own<const T> inner)
1125 : Directory::Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY),
1126 inner(kj::mv(inner)) {}
1127
get()1128 const T& get() override { return *inner; }
tryCommit()1129 bool tryCommit() override { return false; }
1130
1131 private:
1132 Own<const T> inner;
1133 };
1134
tryOpenFile(PathPtr path,WriteMode mode) const1135 Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const {
1136 return tryOpenFileInternal(path, mode, false).map(newDiskFile);
1137 }
1138
replaceFile(PathPtr path,WriteMode mode) const1139 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
createTemporary() const1162 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
tryAppendFile(PathPtr path,WriteMode mode) const1184 Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const {
1185 return tryOpenFileInternal(path, mode, true).map(newDiskAppendableFile);
1186 }
1187
tryOpenSubdir(PathPtr path,WriteMode mode) const1188 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
replaceSubdir(PathPtr path,WriteMode mode) const1199 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
trySymlink(PathPtr linkpath,StringPtr content,WriteMode mode) const1232 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
tryTransfer(PathPtr toPath,WriteMode toMode,const Directory & fromDirectory,PathPtr fromPath,TransferMode mode,const Directory & self) const1243 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
tryRemove(PathPtr path) const1304 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:
DiskReadableFile(AutoCloseHandle && handle)1318 DiskReadableFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {}
1319
cloneFsNode() const1320 Own<const FsNode> cloneFsNode() const override {
1321 return heap<DiskReadableFile>(DiskHandle::clone());
1322 }
1323
1324 FSNODE_METHODS
1325
read(uint64_t offset,ArrayPtr<byte> buffer) const1326 size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override {
1327 return DiskHandle::read(offset, buffer);
1328 }
mmap(uint64_t offset,uint64_t size) const1329 Array<const byte> mmap(uint64_t offset, uint64_t size) const override {
1330 return DiskHandle::mmap(offset, size);
1331 }
mmapPrivate(uint64_t offset,uint64_t size) const1332 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:
DiskAppendableFile(AutoCloseHandle && handle)1339 DiskAppendableFile(AutoCloseHandle&& handle)
1340 : DiskHandle(kj::mv(handle), nullptr),
1341 stream(DiskHandle::handle.get()) {}
1342
cloneFsNode() const1343 Own<const FsNode> cloneFsNode() const override {
1344 return heap<DiskAppendableFile>(DiskHandle::clone());
1345 }
1346
1347 FSNODE_METHODS
1348
write(const void * buffer,size_t size)1349 void write(const void* buffer, size_t size) override { stream.write(buffer, size); }
write(ArrayPtr<const ArrayPtr<const byte>> pieces)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:
DiskFile(AutoCloseHandle && handle)1360 DiskFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {}
1361
cloneFsNode() const1362 Own<const FsNode> cloneFsNode() const override {
1363 return heap<DiskFile>(DiskHandle::clone());
1364 }
1365
1366 FSNODE_METHODS
1367
read(uint64_t offset,ArrayPtr<byte> buffer) const1368 size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override {
1369 return DiskHandle::read(offset, buffer);
1370 }
mmap(uint64_t offset,uint64_t size) const1371 Array<const byte> mmap(uint64_t offset, uint64_t size) const override {
1372 return DiskHandle::mmap(offset, size);
1373 }
mmapPrivate(uint64_t offset,uint64_t size) const1374 Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override {
1375 return DiskHandle::mmapPrivate(offset, size);
1376 }
1377
write(uint64_t offset,ArrayPtr<const byte> data) const1378 void write(uint64_t offset, ArrayPtr<const byte> data) const override {
1379 DiskHandle::write(offset, data);
1380 }
zero(uint64_t offset,uint64_t size) const1381 void zero(uint64_t offset, uint64_t size) const override {
1382 DiskHandle::zero(offset, size);
1383 }
truncate(uint64_t size) const1384 void truncate(uint64_t size) const override {
1385 DiskHandle::truncate(size);
1386 }
mmapWritable(uint64_t offset,uint64_t size) const1387 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:
DiskReadableDirectory(AutoCloseHandle && handle,Path && path)1395 DiskReadableDirectory(AutoCloseHandle&& handle, Path&& path)
1396 : DiskHandle(kj::mv(handle), kj::mv(path)) {}
1397
cloneFsNode() const1398 Own<const FsNode> cloneFsNode() const override {
1399 return heap<DiskReadableDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone());
1400 }
1401
1402 FSNODE_METHODS
1403
listNames() const1404 Array<String> listNames() const override { return DiskHandle::listNames(); }
listEntries() const1405 Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
exists(PathPtr path) const1406 bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
tryLstat(PathPtr path) const1407 Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
1408 return DiskHandle::tryLstat(path);
1409 }
tryOpenFile(PathPtr path) const1410 Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
1411 return DiskHandle::tryOpenFile(path);
1412 }
tryOpenSubdir(PathPtr path) const1413 Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
1414 return DiskHandle::tryOpenSubdir(path);
1415 }
tryReadlink(PathPtr path) const1416 Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); }
1417 };
1418
1419 class DiskDirectoryBase: public Directory, public DiskHandle {
1420 public:
DiskDirectoryBase(AutoCloseHandle && handle,Path && path)1421 DiskDirectoryBase(AutoCloseHandle&& handle, Path&& path)
1422 : DiskHandle(kj::mv(handle), kj::mv(path)) {}
1423
exists(PathPtr path) const1424 bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
tryLstat(PathPtr path) const1425 Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override { return DiskHandle::tryLstat(path); }
tryOpenFile(PathPtr path) const1426 Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
1427 return DiskHandle::tryOpenFile(path);
1428 }
tryOpenSubdir(PathPtr path) const1429 Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
1430 return DiskHandle::tryOpenSubdir(path);
1431 }
tryReadlink(PathPtr path) const1432 Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); }
1433
tryOpenFile(PathPtr path,WriteMode mode) const1434 Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const override {
1435 return DiskHandle::tryOpenFile(path, mode);
1436 }
replaceFile(PathPtr path,WriteMode mode) const1437 Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override {
1438 return DiskHandle::replaceFile(path, mode);
1439 }
tryAppendFile(PathPtr path,WriteMode mode) const1440 Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const override {
1441 return DiskHandle::tryAppendFile(path, mode);
1442 }
tryOpenSubdir(PathPtr path,WriteMode mode) const1443 Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const override {
1444 return DiskHandle::tryOpenSubdir(path, mode);
1445 }
replaceSubdir(PathPtr path,WriteMode mode) const1446 Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override {
1447 return DiskHandle::replaceSubdir(path, mode);
1448 }
trySymlink(PathPtr linkpath,StringPtr content,WriteMode mode) const1449 bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const override {
1450 return DiskHandle::trySymlink(linkpath, content, mode);
1451 }
tryTransfer(PathPtr toPath,WriteMode toMode,const Directory & fromDirectory,PathPtr fromPath,TransferMode mode) const1452 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.
tryRemove(PathPtr path) const1458 bool tryRemove(PathPtr path) const override {
1459 return DiskHandle::tryRemove(path);
1460 }
1461 };
1462
1463 class DiskDirectory final: public DiskDirectoryBase {
1464 public:
DiskDirectory(AutoCloseHandle && handle,Path && path)1465 DiskDirectory(AutoCloseHandle&& handle, Path&& path)
1466 : DiskDirectoryBase(kj::mv(handle), kj::mv(path)) {}
1467
cloneFsNode() const1468 Own<const FsNode> cloneFsNode() const override {
1469 return heap<DiskDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone());
1470 }
1471
1472 FSNODE_METHODS
1473
listNames() const1474 Array<String> listNames() const override { return DiskHandle::listNames(); }
listEntries() const1475 Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
createTemporary() const1476 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:
RootDiskDirectory()1489 RootDiskDirectory(): DiskDirectoryBase(nullptr, Path(nullptr)) {}
1490
cloneFsNode() const1491 Own<const FsNode> cloneFsNode() const override {
1492 return heap<RootDiskDirectory>();
1493 }
1494
stat() const1495 Metadata stat() const override {
1496 return { Type::DIRECTORY, 0, 0, UNIX_EPOCH, 1, 0 };
1497 }
sync() const1498 void sync() const override {}
datasync() const1499 void datasync() const override {}
1500
listNames() const1501 Array<String> listNames() const override {
1502 return KJ_MAP(e, listEntries()) { return kj::mv(e.name); };
1503 }
listEntries() const1504 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
createTemporary() const1521 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:
DiskFilesystem()1528 DiskFilesystem()
1529 : DiskFilesystem(computeCurrentPath()) {}
DiskFilesystem(Path currentPath)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
getRoot() const1535 const Directory& getRoot() const override {
1536 return root;
1537 }
1538
getCurrent() const1539 const Directory& getCurrent() const override {
1540 return current;
1541 }
1542
getCurrentPath() const1543 PathPtr getCurrentPath() const override {
1544 return KJ_ASSERT_NONNULL(current.dirPath);
1545 }
1546
1547 private:
1548 RootDiskDirectory root;
1549 DiskDirectory current;
1550
computeCurrentPath()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
newDiskReadableFile(AutoCloseHandle fd)1571 Own<ReadableFile> newDiskReadableFile(AutoCloseHandle fd) {
1572 return heap<DiskReadableFile>(kj::mv(fd));
1573 }
newDiskAppendableFile(AutoCloseHandle fd)1574 Own<AppendableFile> newDiskAppendableFile(AutoCloseHandle fd) {
1575 return heap<DiskAppendableFile>(kj::mv(fd));
1576 }
newDiskFile(AutoCloseHandle fd)1577 Own<File> newDiskFile(AutoCloseHandle fd) {
1578 return heap<DiskFile>(kj::mv(fd));
1579 }
newDiskReadableDirectory(AutoCloseHandle fd)1580 Own<ReadableDirectory> newDiskReadableDirectory(AutoCloseHandle fd) {
1581 return heap<DiskReadableDirectory>(kj::mv(fd), getPathFromHandle(fd));
1582 }
newDiskReadableDirectory(AutoCloseHandle fd,Path && path)1583 static Own<ReadableDirectory> newDiskReadableDirectory(AutoCloseHandle fd, Path&& path) {
1584 return heap<DiskReadableDirectory>(kj::mv(fd), kj::mv(path));
1585 }
newDiskDirectory(AutoCloseHandle fd)1586 Own<Directory> newDiskDirectory(AutoCloseHandle fd) {
1587 return heap<DiskDirectory>(kj::mv(fd), getPathFromHandle(fd));
1588 }
newDiskDirectory(AutoCloseHandle fd,Path && path)1589 static Own<Directory> newDiskDirectory(AutoCloseHandle fd, Path&& path) {
1590 return heap<DiskDirectory>(kj::mv(fd), kj::mv(path));
1591 }
1592
newDiskFilesystem()1593 Own<Filesystem> newDiskFilesystem() {
1594 return heap<DiskFilesystem>();
1595 }
1596
getHandlePointerHack(Directory & dir)1597 static AutoCloseHandle* getHandlePointerHack(Directory& dir) {
1598 return &static_cast<DiskDirectoryBase&>(dir).handle;
1599 }
getPathPointerHack(Directory & dir)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
1607