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