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#include "filesystem.h"
23#include "vector.h"
24#include "debug.h"
25#include "one-of.h"
26#include "encoding.h"
27#include "refcount.h"
28#include "mutex.h"
29#include <map>
30
31namespace kj {
32
33Path::Path(StringPtr name): Path(heapString(name)) {}
34Path::Path(String&& name): parts(heapArray<String>(1)) {
35 parts[0] = kj::mv(name);
36 validatePart(parts[0]);
37}
38
39Path::Path(ArrayPtr<const StringPtr> parts)
40 : Path(KJ_MAP(p, parts) { return heapString(p); }) {}
41Path::Path(Array<String> partsParam)
42 : Path(kj::mv(partsParam), ALREADY_CHECKED) {
43 for (auto& p: parts) {
44 validatePart(p);
45 }
46}
47
48Path PathPtr::clone() {
49 return Path(KJ_MAP(p, parts) { return heapString(p); }, Path::ALREADY_CHECKED);
50}
51
52Path Path::parse(StringPtr path) {
53 KJ_REQUIRE(!path.startsWith("/"), "expected a relative path, got absolute", path) {
54 // When exceptions are disabled, go on -- the leading '/' will end up ignored.
55 break;
56 }
57 return evalImpl(Vector<String>(countParts(path)), path);
58}
59
60Path Path::parseWin32Api(ArrayPtr<const wchar_t> text) {
61 auto utf8 = decodeWideString(text);
62 return evalWin32Impl(Vector<String>(countPartsWin32(utf8)), utf8, true);
63}
64
65Path PathPtr::append(Path&& suffix) const {
66 auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size());
67 for (auto& p: parts) newParts.add(heapString(p));
68 for (auto& p: suffix.parts) newParts.add(kj::mv(p));
69 return Path(newParts.finish(), Path::ALREADY_CHECKED);
70}
71Path Path::append(Path&& suffix) && {
72 auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size());
73 for (auto& p: parts) newParts.add(kj::mv(p));
74 for (auto& p: suffix.parts) newParts.add(kj::mv(p));
75 return Path(newParts.finish(), ALREADY_CHECKED);
76}
77Path PathPtr::append(PathPtr suffix) const {
78 auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size());
79 for (auto& p: parts) newParts.add(heapString(p));
80 for (auto& p: suffix.parts) newParts.add(heapString(p));
81 return Path(newParts.finish(), Path::ALREADY_CHECKED);
82}
83Path Path::append(PathPtr suffix) && {
84 auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size());
85 for (auto& p: parts) newParts.add(kj::mv(p));
86 for (auto& p: suffix.parts) newParts.add(heapString(p));
87 return Path(newParts.finish(), ALREADY_CHECKED);
88}
89
90Path PathPtr::eval(StringPtr pathText) const {
91 if (pathText.startsWith("/")) {
92 // Optimization: avoid copying parts that will just be dropped.
93 return Path::evalImpl(Vector<String>(Path::countParts(pathText)), pathText);
94 } else {
95 Vector<String> newParts(parts.size() + Path::countParts(pathText));
96 for (auto& p: parts) newParts.add(heapString(p));
97 return Path::evalImpl(kj::mv(newParts), pathText);
98 }
99}
100Path Path::eval(StringPtr pathText) && {
101 if (pathText.startsWith("/")) {
102 // Optimization: avoid copying parts that will just be dropped.
103 return evalImpl(Vector<String>(countParts(pathText)), pathText);
104 } else {
105 Vector<String> newParts(parts.size() + countParts(pathText));
106 for (auto& p: parts) newParts.add(kj::mv(p));
107 return evalImpl(kj::mv(newParts), pathText);
108 }
109}
110
111PathPtr PathPtr::basename() const {
112 KJ_REQUIRE(parts.size() > 0, "root path has no basename");
113 return PathPtr(parts.slice(parts.size() - 1, parts.size()));
114}
115Path Path::basename() && {
116 KJ_REQUIRE(parts.size() > 0, "root path has no basename");
117 auto newParts = kj::heapArrayBuilder<String>(1);
118 newParts.add(kj::mv(parts[parts.size() - 1]));
119 return Path(newParts.finish(), ALREADY_CHECKED);
120}
121
122PathPtr PathPtr::parent() const {
123 KJ_REQUIRE(parts.size() > 0, "root path has no parent");
124 return PathPtr(parts.slice(0, parts.size() - 1));
125}
126Path Path::parent() && {
127 KJ_REQUIRE(parts.size() > 0, "root path has no parent");
128 return Path(KJ_MAP(p, parts.slice(0, parts.size() - 1)) { return kj::mv(p); }, ALREADY_CHECKED);
129}
130
131String PathPtr::toString(bool absolute) const {
132 if (parts.size() == 0) {
133 // Special-case empty path.
134 return absolute ? kj::str("/") : kj::str(".");
135 }
136
137 size_t size = absolute + (parts.size() - 1);
138 for (auto& p: parts) size += p.size();
139
140 String result = kj::heapString(size);
141
142 char* ptr = result.begin();
143 bool leadingSlash = absolute;
144 for (auto& p: parts) {
145 if (leadingSlash) *ptr++ = '/';
146 leadingSlash = true;
147 memcpy(ptr, p.begin(), p.size());
148 ptr += p.size();
149 }
150 KJ_ASSERT(ptr == result.end());
151
152 return result;
153}
154
155Path Path::slice(size_t start, size_t end) && {
156 return Path(KJ_MAP(p, parts.slice(start, end)) { return kj::mv(p); });
157}
158
159bool PathPtr::operator==(PathPtr other) const {
160 return parts == other.parts;
161}
162bool PathPtr::operator< (PathPtr other) const {
163 for (size_t i = 0; i < kj::min(parts.size(), other.parts.size()); i++) {
164 int comp = strcmp(parts[i].cStr(), other.parts[i].cStr());
165 if (comp < 0) return true;
166 if (comp > 0) return false;
167 }
168
169 return parts.size() < other.parts.size();
170}
171
172bool PathPtr::startsWith(PathPtr prefix) const {
173 return parts.size() >= prefix.parts.size() &&
174 parts.slice(0, prefix.parts.size()) == prefix.parts;
175}
176
177bool PathPtr::endsWith(PathPtr suffix) const {
178 return parts.size() >= suffix.parts.size() &&
179 parts.slice(parts.size() - suffix.parts.size(), parts.size()) == suffix.parts;
180}
181
182Path PathPtr::evalWin32(StringPtr pathText) const {
183 Vector<String> newParts(parts.size() + Path::countPartsWin32(pathText));
184 for (auto& p: parts) newParts.add(heapString(p));
185 return Path::evalWin32Impl(kj::mv(newParts), pathText);
186}
187Path Path::evalWin32(StringPtr pathText) && {
188 Vector<String> newParts(parts.size() + countPartsWin32(pathText));
189 for (auto& p: parts) newParts.add(kj::mv(p));
190 return evalWin32Impl(kj::mv(newParts), pathText);
191}
192
193String PathPtr::toWin32StringImpl(bool absolute, bool forApi) const {
194 if (parts.size() == 0) {
195 // Special-case empty path.
196 KJ_REQUIRE(!absolute, "absolute path is missing disk designator") {
197 break;
198 }
199 return absolute ? kj::str("\\\\") : kj::str(".");
200 }
201
202 bool isUncPath = false;
203 if (absolute) {
204 if (Path::isWin32Drive(parts[0])) {
205 // It's a win32 drive
206 } else if (Path::isNetbiosName(parts[0])) {
207 isUncPath = true;
208 } else {
209 KJ_FAIL_REQUIRE("absolute win32 path must start with drive letter or netbios host name",
210 parts[0]);
211 }
212 } else {
213 // Currently we do nothing differently in the forApi case for relative paths.
214 forApi = false;
215 }
216
217 size_t size = forApi
218 ? (isUncPath ? 8 : 4) + (parts.size() - 1)
219 : (isUncPath ? 2 : 0) + (parts.size() - 1);
220 for (auto& p: parts) size += p.size();
221
222 String result = heapString(size);
223
224 char* ptr = result.begin();
225
226 if (forApi) {
227 *ptr++ = '\\';
228 *ptr++ = '\\';
229 *ptr++ = '?';
230 *ptr++ = '\\';
231 if (isUncPath) {
232 *ptr++ = 'U';
233 *ptr++ = 'N';
234 *ptr++ = 'C';
235 *ptr++ = '\\';
236 }
237 } else {
238 if (isUncPath) {
239 *ptr++ = '\\';
240 *ptr++ = '\\';
241 }
242 }
243
244 bool leadingSlash = false;
245 for (auto& p: parts) {
246 if (leadingSlash) *ptr++ = '\\';
247 leadingSlash = true;
248
249 KJ_REQUIRE(!Path::isWin32Special(p), "path cannot contain DOS reserved name", p) {
250 // Recover by blotting out the name with invalid characters which Win32 syscalls will reject.
251 for (size_t i = 0; i < p.size(); i++) {
252 *ptr++ = '|';
253 }
254 goto skip;
255 }
256
257 memcpy(ptr, p.begin(), p.size());
258 ptr += p.size();
259 skip:;
260 }
261
262 KJ_ASSERT(ptr == result.end());
263
264 // Check for colons (other than in drive letter), which on NTFS would be interpreted as an
265 // "alternate data stream", which can lead to surprising results. If we want to support ADS, we
266 // should do so using an explicit API. Note that this check also prevents a relative path from
267 // appearing to start with a drive letter.
268 for (size_t i: kj::indices(result)) {
269 if (result[i] == ':') {
270 if (absolute && i == (forApi ? 5 : 1)) {
271 // False alarm: this is the drive letter.
272 } else {
273 KJ_FAIL_REQUIRE(
274 "colons are prohibited in win32 paths to avoid triggering alterante data streams",
275 result) {
276 // Recover by using a different character which we know Win32 syscalls will reject.
277 result[i] = '|';
278 break;
279 }
280 }
281 }
282 }
283
284 return result;
285}
286
287Array<wchar_t> PathPtr::forWin32Api(bool absolute) const {
288 return encodeWideString(toWin32StringImpl(absolute, true), true);
289}
290
291// -----------------------------------------------------------------------------
292
293String Path::stripNul(String input) {
294 kj::Vector<char> output(input.size());
295 for (char c: input) {
296 if (c != '\0') output.add(c);
297 }
298 output.add('\0');
299 return String(output.releaseAsArray());
300}
301
302void Path::validatePart(StringPtr part) {
303 KJ_REQUIRE(part != "" && part != "." && part != "..", "invalid path component", part);
304 KJ_REQUIRE(strlen(part.begin()) == part.size(), "NUL character in path component", part);
305 KJ_REQUIRE(part.findFirst('/') == nullptr,
306 "'/' character in path component; did you mean to use Path::parse()?", part);
307}
308
309void Path::evalPart(Vector<String>& parts, ArrayPtr<const char> part) {
310 if (part.size() == 0) {
311 // Ignore consecutive or trailing '/'s.
312 } else if (part.size() == 1 && part[0] == '.') {
313 // Refers to current directory; ignore.
314 } else if (part.size() == 2 && part[0] == '.' && part [1] == '.') {
315 KJ_REQUIRE(parts.size() > 0, "can't use \"..\" to break out of starting directory") {
316 // When exceptions are disabled, ignore.
317 return;
318 }
319 parts.removeLast();
320 } else {
321 auto str = heapString(part);
322 KJ_REQUIRE(strlen(str.begin()) == str.size(), "NUL character in path component", str) {
323 // When exceptions are disabled, strip out '\0' chars.
324 str = stripNul(kj::mv(str));
325 break;
326 }
327 parts.add(kj::mv(str));
328 }
329}
330
331Path Path::evalImpl(Vector<String>&& parts, StringPtr path) {
332 if (path.startsWith("/")) {
333 parts.clear();
334 }
335
336 size_t partStart = 0;
337 for (auto i: kj::indices(path)) {
338 if (path[i] == '/') {
339 evalPart(parts, path.slice(partStart, i));
340 partStart = i + 1;
341 }
342 }
343 evalPart(parts, path.slice(partStart));
344
345 return Path(parts.releaseAsArray(), Path::ALREADY_CHECKED);
346}
347
348Path Path::evalWin32Impl(Vector<String>&& parts, StringPtr path, bool fromApi) {
349 // Convert all forward slashes to backslashes.
350 String ownPath;
351 if (!fromApi && path.findFirst('/') != nullptr) {
352 ownPath = heapString(path);
353 for (char& c: ownPath) {
354 if (c == '/') c = '\\';
355 }
356 path = ownPath;
357 }
358
359 // Interpret various forms of absolute paths.
360 if (fromApi && path.startsWith("\\\\?\\")) {
361 path = path.slice(4);
362 if (path.startsWith("UNC\\")) {
363 path = path.slice(4);
364 }
365
366 // The path is absolute.
367 parts.clear();
368 } else if (path.startsWith("\\\\")) {
369 // UNC path.
370 path = path.slice(2);
371
372 // This path is absolute. The first component is a server name.
373 parts.clear();
374 } else if (path.startsWith("\\")) {
375 KJ_REQUIRE(!fromApi, "parseWin32Api() requires absolute path");
376
377 // Path is relative to the current drive / network share.
378 if (parts.size() >= 1 && isWin32Drive(parts[0])) {
379 // Leading \ interpreted as root of current drive.
380 parts.truncate(1);
381 } else if (parts.size() >= 2) {
382 // Leading \ interpreted as root of current network share (which is indicated by the first
383 // *two* components of the path).
384 parts.truncate(2);
385 } else {
386 KJ_FAIL_REQUIRE("must specify drive letter", path) {
387 // Recover by assuming C drive.
388 parts.clear();
389 parts.add(kj::str("c:"));
390 break;
391 }
392 }
393 } else if ((path.size() == 2 || (path.size() > 2 && path[2] == '\\')) &&
394 isWin32Drive(path.slice(0, 2))) {
395 // Starts with a drive letter.
396 parts.clear();
397 } else {
398 KJ_REQUIRE(!fromApi, "parseWin32Api() requires absolute path");
399 }
400
401 size_t partStart = 0;
402 for (auto i: kj::indices(path)) {
403 if (path[i] == '\\') {
404 evalPart(parts, path.slice(partStart, i));
405 partStart = i + 1;
406 }
407 }
408 evalPart(parts, path.slice(partStart));
409
410 return Path(parts.releaseAsArray(), Path::ALREADY_CHECKED);
411}
412
413size_t Path::countParts(StringPtr path) {
414 size_t result = 1;
415 for (char c: path) {
416 result += (c == '/');
417 }
418 return result;
419}
420
421size_t Path::countPartsWin32(StringPtr path) {
422 size_t result = 1;
423 for (char c: path) {
424 result += (c == '/' || c == '\\');
425 }
426 return result;
427}
428
429bool Path::isWin32Drive(ArrayPtr<const char> part) {
430 return part.size() == 2 && part[1] == ':' &&
431 (('a' <= part[0] && part[0] <= 'z') || ('A' <= part[0] && part[0] <= 'Z'));
432}
433
434bool Path::isNetbiosName(ArrayPtr<const char> part) {
435 // Characters must be alphanumeric or '.' or '-'.
436 for (char c: part) {
437 if (c != '.' && c != '-' &&
438 (c < 'a' || 'z' < c) &&
439 (c < 'A' || 'Z' < c) &&
440 (c < '0' || '9' < c)) {
441 return false;
442 }
443 }
444
445 // Can't be empty nor start or end with a '.' or a '-'.
446 return part.size() > 0 &&
447 part[0] != '.' && part[0] != '-' &&
448 part[part.size() - 1] != '.' && part[part.size() - 1] != '-';
449}
450
451bool Path::isWin32Special(StringPtr part) {
452 bool isNumbered;
453 if (part.size() == 3 || (part.size() > 3 && part[3] == '.')) {
454 // Filename is three characters or three characters followed by an extension.
455 isNumbered = false;
456 } else if ((part.size() == 4 || (part.size() > 4 && part[4] == '.')) &&
457 '1' <= part[3] && part[3] <= '9') {
458 // Filename is four characters or four characters followed by an extension, and the fourth
459 // character is a nonzero digit.
460 isNumbered = true;
461 } else {
462 return false;
463 }
464
465 // OK, this could be a Win32 special filename. We need to match the first three letters against
466 // the list of specials, case-insensitively.
467 char tmp[4];
468 memcpy(tmp, part.begin(), 3);
469 tmp[3] = '\0';
470 for (char& c: tmp) {
471 if ('A' <= c && c <= 'Z') {
472 c += 'a' - 'A';
473 }
474 }
475
476 StringPtr str(tmp, 3);
477 if (isNumbered) {
478 // Specials that are followed by a digit.
479 return str == "com" || str == "lpt";
480 } else {
481 // Specials that are not followed by a digit.
482 return str == "con" || str == "prn" || str == "aux" || str == "nul";
483 }
484}
485
486// =======================================================================================
487
488String ReadableFile::readAllText() const {
489 String result = heapString(stat().size);
490 size_t n = read(0, result.asBytes());
491 if (n < result.size()) {
492 // Apparently file was truncated concurrently. Reduce to new size to match.
493 result = heapString(result.slice(0, n));
494 }
495 return result;
496}
497
498Array<byte> ReadableFile::readAllBytes() const {
499 Array<byte> result = heapArray<byte>(stat().size);
500 size_t n = read(0, result.asBytes());
501 if (n < result.size()) {
502 // Apparently file was truncated concurrently. Reduce to new size to match.
503 result = heapArray(result.slice(0, n));
504 }
505 return result;
506}
507
508void File::writeAll(ArrayPtr<const byte> bytes) const {
509 truncate(0);
510 write(0, bytes);
511}
512
513void File::writeAll(StringPtr text) const {
514 writeAll(text.asBytes());
515}
516
517size_t File::copy(uint64_t offset, const ReadableFile& from,
518 uint64_t fromOffset, uint64_t size) const {
519 byte buffer[8192];
520
521 size_t result = 0;
522 while (size > 0) {
523 size_t n = from.read(fromOffset, kj::arrayPtr(buffer, kj::min(sizeof(buffer), size)));
524 write(offset, arrayPtr(buffer, n));
525 result += n;
526 if (n < sizeof(buffer)) {
527 // Either we copied the amount requested or we hit EOF.
528 break;
529 }
530 fromOffset += n;
531 offset += n;
532 size -= n;
533 }
534
535 return result;
536}
537
538FsNode::Metadata ReadableDirectory::lstat(PathPtr path) const {
539 KJ_IF_MAYBE(meta, tryLstat(path)) {
540 return *meta;
541 } else {
542 KJ_FAIL_REQUIRE("no such file", path) { break; }
543 return FsNode::Metadata();
544 }
545}
546
547Own<const ReadableFile> ReadableDirectory::openFile(PathPtr path) const {
548 KJ_IF_MAYBE(file, tryOpenFile(path)) {
549 return kj::mv(*file);
550 } else {
551 KJ_FAIL_REQUIRE("no such directory", path) { break; }
552 return newInMemoryFile(nullClock());
553 }
554}
555
556Own<const ReadableDirectory> ReadableDirectory::openSubdir(PathPtr path) const {
557 KJ_IF_MAYBE(dir, tryOpenSubdir(path)) {
558 return kj::mv(*dir);
559 } else {
560 KJ_FAIL_REQUIRE("no such file or directory", path) { break; }
561 return newInMemoryDirectory(nullClock());
562 }
563}
564
565String ReadableDirectory::readlink(PathPtr path) const {
566 KJ_IF_MAYBE(p, tryReadlink(path)) {
567 return kj::mv(*p);
568 } else {
569 KJ_FAIL_REQUIRE("not a symlink", path) { break; }
570 return kj::str(".");
571 }
572}
573
574Own<const File> Directory::openFile(PathPtr path, WriteMode mode) const {
575 KJ_IF_MAYBE(f, tryOpenFile(path, mode)) {
576 return kj::mv(*f);
577 } else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
578 KJ_FAIL_REQUIRE("file already exists", path) { break; }
579 } else if (has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) {
580 KJ_FAIL_REQUIRE("file does not exist", path) { break; }
581 } else if (!has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) {
582 KJ_FAIL_ASSERT("neither WriteMode::CREATE nor WriteMode::MODIFY was given", path) { break; }
583 } else {
584 // Shouldn't happen.
585 KJ_FAIL_ASSERT("tryOpenFile() returned null despite no preconditions", path) { break; }
586 }
587 return newInMemoryFile(nullClock());
588}
589
590Own<AppendableFile> Directory::appendFile(PathPtr path, WriteMode mode) const {
591 KJ_IF_MAYBE(f, tryAppendFile(path, mode)) {
592 return kj::mv(*f);
593 } else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
594 KJ_FAIL_REQUIRE("file already exists", path) { break; }
595 } else if (has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) {
596 KJ_FAIL_REQUIRE("file does not exist", path) { break; }
597 } else if (!has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) {
598 KJ_FAIL_ASSERT("neither WriteMode::CREATE nor WriteMode::MODIFY was given", path) { break; }
599 } else {
600 // Shouldn't happen.
601 KJ_FAIL_ASSERT("tryAppendFile() returned null despite no preconditions", path) { break; }
602 }
603 return newFileAppender(newInMemoryFile(nullClock()));
604}
605
606Own<const Directory> Directory::openSubdir(PathPtr path, WriteMode mode) const {
607 KJ_IF_MAYBE(f, tryOpenSubdir(path, mode)) {
608 return kj::mv(*f);
609 } else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
610 KJ_FAIL_REQUIRE("directory already exists", path) { break; }
611 } else if (has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) {
612 KJ_FAIL_REQUIRE("directory does not exist", path) { break; }
613 } else if (!has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) {
614 KJ_FAIL_ASSERT("neither WriteMode::CREATE nor WriteMode::MODIFY was given", path) { break; }
615 } else {
616 // Shouldn't happen.
617 KJ_FAIL_ASSERT("tryOpenSubdir() returned null despite no preconditions", path) { break; }
618 }
619 return newInMemoryDirectory(nullClock());
620}
621
622void Directory::symlink(PathPtr linkpath, StringPtr content, WriteMode mode) const {
623 if (!trySymlink(linkpath, content, mode)) {
624 if (has(mode, WriteMode::CREATE)) {
625 KJ_FAIL_REQUIRE("path already exsits", linkpath) { break; }
626 } else {
627 // Shouldn't happen.
628 KJ_FAIL_ASSERT("symlink() returned null despite no preconditions", linkpath) { break; }
629 }
630 }
631}
632
633void Directory::transfer(PathPtr toPath, WriteMode toMode,
634 const Directory& fromDirectory, PathPtr fromPath,
635 TransferMode mode) const {
636 if (!tryTransfer(toPath, toMode, fromDirectory, fromPath, mode)) {
637 if (has(toMode, WriteMode::CREATE)) {
638 KJ_FAIL_REQUIRE("toPath already exists or fromPath doesn't exist", toPath, fromPath) {
639 break;
640 }
641 } else {
642 KJ_FAIL_ASSERT("fromPath doesn't exist", fromPath) { break; }
643 }
644 }
645}
646
647static void copyContents(const Directory& to, const ReadableDirectory& from);
648
649static bool tryCopyDirectoryEntry(const Directory& to, PathPtr toPath, WriteMode toMode,
650 const ReadableDirectory& from, PathPtr fromPath,
651 FsNode::Type type, bool atomic) {
652 // TODO(cleanup): Make this reusable?
653
654 switch (type) {
655 case FsNode::Type::FILE: {
656 KJ_IF_MAYBE(fromFile, from.tryOpenFile(fromPath)) {
657 if (atomic) {
658 auto replacer = to.replaceFile(toPath, toMode);
659 replacer->get().copy(0, **fromFile, 0, kj::maxValue);
660 return replacer->tryCommit();
661 } else KJ_IF_MAYBE(toFile, to.tryOpenFile(toPath, toMode)) {
662 toFile->get()->copy(0, **fromFile, 0, kj::maxValue);
663 return true;
664 } else {
665 return false;
666 }
667 } else {
668 // Apparently disappeared. Treat as source-doesn't-exist.
669 return false;
670 }
671 }
672 case FsNode::Type::DIRECTORY:
673 KJ_IF_MAYBE(fromSubdir, from.tryOpenSubdir(fromPath)) {
674 if (atomic) {
675 auto replacer = to.replaceSubdir(toPath, toMode);
676 copyContents(replacer->get(), **fromSubdir);
677 return replacer->tryCommit();
678 } else KJ_IF_MAYBE(toSubdir, to.tryOpenSubdir(toPath, toMode)) {
679 copyContents(**toSubdir, **fromSubdir);
680 return true;
681 } else {
682 return false;
683 }
684 } else {
685 // Apparently disappeared. Treat as source-doesn't-exist.
686 return false;
687 }
688 case FsNode::Type::SYMLINK:
689 KJ_IF_MAYBE(content, from.tryReadlink(fromPath)) {
690 return to.trySymlink(toPath, *content, toMode);
691 } else {
692 // Apparently disappeared. Treat as source-doesn't-exist.
693 return false;
694 }
695 break;
696
697 default:
698 // Note: Unclear whether it's better to throw an error here or just ignore it / log a
699 // warning. Can reconsider when we see an actual use case.
700 KJ_FAIL_REQUIRE("can only copy files, directories, and symlinks", fromPath) {
701 return false;
702 }
703 }
704}
705
706static void copyContents(const Directory& to, const ReadableDirectory& from) {
707 for (auto& entry: from.listEntries()) {
708 Path subPath(kj::mv(entry.name));
709 tryCopyDirectoryEntry(to, subPath, WriteMode::CREATE, from, subPath, entry.type, false);
710 }
711}
712
713bool Directory::tryTransfer(PathPtr toPath, WriteMode toMode,
714 const Directory& fromDirectory, PathPtr fromPath,
715 TransferMode mode) const {
716 KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; }
717
718 // First try reversing.
719 KJ_IF_MAYBE(result, fromDirectory.tryTransferTo(*this, toPath, toMode, fromPath, mode)) {
720 return *result;
721 }
722
723 switch (mode) {
724 case TransferMode::COPY:
725 KJ_IF_MAYBE(meta, fromDirectory.tryLstat(fromPath)) {
726 return tryCopyDirectoryEntry(*this, toPath, toMode, fromDirectory,
727 fromPath, meta->type, true);
728 } else {
729 // Source doesn't exist.
730 return false;
731 }
732 case TransferMode::MOVE:
733 // Implement move as copy-then-delete.
734 if (!tryTransfer(toPath, toMode, fromDirectory, fromPath, TransferMode::COPY)) {
735 return false;
736 }
737 fromDirectory.remove(fromPath);
738 return true;
739 case TransferMode::LINK:
740 KJ_FAIL_REQUIRE("can't link across different Directory implementations") { return false; }
741 }
742
743 KJ_UNREACHABLE;
744}
745
746Maybe<bool> Directory::tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode,
747 PathPtr fromPath, TransferMode mode) const {
748 return nullptr;
749}
750
751void Directory::remove(PathPtr path) const {
752 if (!tryRemove(path)) {
753 KJ_FAIL_REQUIRE("path to remove doesn't exist", path) { break; }
754 }
755}
756
757void Directory::commitFailed(WriteMode mode) {
758 if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
759 KJ_FAIL_REQUIRE("replace target already exists") { break; }
760 } else if (has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) {
761 KJ_FAIL_REQUIRE("replace target does not exist") { break; }
762 } else if (!has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) {
763 KJ_FAIL_ASSERT("neither WriteMode::CREATE nor WriteMode::MODIFY was given") { break; }
764 } else {
765 KJ_FAIL_ASSERT("tryCommit() returned null despite no preconditions") { break; }
766 }
767}
768
769// =======================================================================================
770
771namespace {
772
773class InMemoryFile final: public File, public AtomicRefcounted {
774public:
775 InMemoryFile(const Clock& clock): impl(clock) {}
776
777 Own<const FsNode> cloneFsNode() const override {
778 return atomicAddRef(*this);
779 }
780
781 Maybe<int> getFd() const override {
782 return nullptr;
783 }
784
785 Metadata stat() const override {
786 auto lock = impl.lockShared();
787 uint64_t hash = reinterpret_cast<uintptr_t>(this);
788 return Metadata { Type::FILE, lock->size, lock->size, lock->lastModified, 1, hash };
789 }
790
791 void sync() const override {}
792 void datasync() const override {}
793 // no-ops
794
795 size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override {
796 auto lock = impl.lockShared();
797 if (offset >= lock->size) {
798 // Entirely out-of-range.
799 return 0;
800 }
801
802 size_t readSize = kj::min(buffer.size(), lock->size - offset);
803 memcpy(buffer.begin(), lock->bytes.begin() + offset, readSize);
804 return readSize;
805 }
806
807 Array<const byte> mmap(uint64_t offset, uint64_t size) const override {
808 KJ_REQUIRE(offset + size >= offset, "mmap() request overflows uint64");
809 auto lock = impl.lockExclusive();
810 lock->ensureCapacity(offset + size);
811
812 ArrayDisposer* disposer = new MmapDisposer(atomicAddRef(*this));
813 return Array<const byte>(lock->bytes.begin() + offset, size, *disposer);
814 }
815
816 Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override {
817 // Return a copy.
818
819 // Allocate exactly the size requested.
820 auto result = heapArray<byte>(size);
821
822 // Use read() to fill it.
823 size_t actual = read(offset, result);
824
825 // Ignore the rest.
826 if (actual < size) {
827 memset(result.begin() + actual, 0, size - actual);
828 }
829
830 return result;
831 }
832
833 void write(uint64_t offset, ArrayPtr<const byte> data) const override {
834 if (data.size() == 0) return;
835 auto lock = impl.lockExclusive();
836 lock->modified();
837 uint64_t end = offset + data.size();
838 KJ_REQUIRE(end >= offset, "write() request overflows uint64");
839 lock->ensureCapacity(end);
840 lock->size = kj::max(lock->size, end);
841 memcpy(lock->bytes.begin() + offset, data.begin(), data.size());
842 }
843
844 void zero(uint64_t offset, uint64_t zeroSize) const override {
845 if (zeroSize == 0) return;
846 auto lock = impl.lockExclusive();
847 lock->modified();
848 uint64_t end = offset + zeroSize;
849 KJ_REQUIRE(end >= offset, "zero() request overflows uint64");
850 lock->ensureCapacity(end);
851 lock->size = kj::max(lock->size, end);
852 memset(lock->bytes.begin() + offset, 0, zeroSize);
853 }
854
855 void truncate(uint64_t newSize) const override {
856 auto lock = impl.lockExclusive();
857 if (newSize < lock->size) {
858 lock->modified();
859 memset(lock->bytes.begin() + newSize, 0, lock->size - newSize);
860 lock->size = newSize;
861 } else if (newSize > lock->size) {
862 lock->modified();
863 lock->ensureCapacity(newSize);
864 lock->size = newSize;
865 }
866 }
867
868 Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const override {
869 uint64_t end = offset + size;
870 KJ_REQUIRE(end >= offset, "mmapWritable() request overflows uint64");
871 auto lock = impl.lockExclusive();
872 lock->ensureCapacity(end);
873 return heap<WritableFileMappingImpl>(atomicAddRef(*this), lock->bytes.slice(offset, end));
874 }
875
876 size_t copy(uint64_t offset, const ReadableFile& from,
877 uint64_t fromOffset, uint64_t copySize) const override {
878 size_t fromFileSize = from.stat().size;
879 if (fromFileSize <= fromOffset) return 0;
880
881 // Clamp size to EOF.
882 copySize = kj::min(copySize, fromFileSize - fromOffset);
883 if (copySize == 0) return 0;
884
885 auto lock = impl.lockExclusive();
886
887 // Allocate space for the copy.
888 uint64_t end = offset + copySize;
889 lock->ensureCapacity(end);
890
891 // Read directly into our backing store.
892 size_t n = from.read(fromOffset, lock->bytes.slice(offset, end));
893 lock->size = kj::max(lock->size, offset + n);
894
895 lock->modified();
896 return n;
897 }
898
899private:
900 struct Impl {
901 const Clock& clock;
902 Array<byte> bytes;
903 size_t size = 0; // bytes may be larger than this to accommodate mmaps
904 Date lastModified;
905 uint mmapCount = 0; // number of mappings outstanding
906
907 Impl(const Clock& clock): clock(clock), lastModified(clock.now()) {}
908
909 void ensureCapacity(size_t capacity) {
910 if (bytes.size() < capacity) {
911 KJ_ASSERT(mmapCount == 0,
912 "InMemoryFile cannot resize the file backing store while memory mappings exist.");
913
914 auto newBytes = heapArray<byte>(kj::max(capacity, bytes.size() * 2));
915 memcpy(newBytes.begin(), bytes.begin(), size);
916 memset(newBytes.begin() + size, 0, newBytes.size() - size);
917 bytes = kj::mv(newBytes);
918 }
919 }
920
921 void modified() {
922 lastModified = clock.now();
923 }
924 };
925 kj::MutexGuarded<Impl> impl;
926
927 class MmapDisposer final: public ArrayDisposer {
928 public:
929 MmapDisposer(Own<const InMemoryFile>&& refParam): ref(kj::mv(refParam)) {
930 ++ref->impl.getAlreadyLockedExclusive().mmapCount;
931 }
932 ~MmapDisposer() noexcept(false) {
933 --ref->impl.lockExclusive()->mmapCount;
934 }
935
936 void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
937 size_t capacity, void (*destroyElement)(void*)) const override {
938 delete this;
939 }
940
941 private:
942 Own<const InMemoryFile> ref;
943 };
944
945 class WritableFileMappingImpl final: public WritableFileMapping {
946 public:
947 WritableFileMappingImpl(Own<const InMemoryFile>&& refParam, ArrayPtr<byte> range)
948 : ref(kj::mv(refParam)), range(range) {
949 ++ref->impl.getAlreadyLockedExclusive().mmapCount;
950 }
951 ~WritableFileMappingImpl() noexcept(false) {
952 --ref->impl.lockExclusive()->mmapCount;
953 }
954
955 ArrayPtr<byte> get() const override {
956 // const_cast OK because WritableFileMapping does indeed provide a writable view despite
957 // being const itself.
958 return arrayPtr(const_cast<byte*>(range.begin()), range.size());
959 }
960
961 void changed(ArrayPtr<byte> slice) const override {
962 ref->impl.lockExclusive()->modified();
963 }
964
965 void sync(ArrayPtr<byte> slice) const override {
966 ref->impl.lockExclusive()->modified();
967 }
968
969 private:
970 Own<const InMemoryFile> ref;
971 ArrayPtr<byte> range;
972 };
973};
974
975// -----------------------------------------------------------------------------
976
977class InMemoryDirectory final: public Directory, public AtomicRefcounted {
978public:
979 InMemoryDirectory(const Clock& clock): impl(clock) {}
980
981 Own<const FsNode> cloneFsNode() const override {
982 return atomicAddRef(*this);
983 }
984
985 Maybe<int> getFd() const override {
986 return nullptr;
987 }
988
989 Metadata stat() const override {
990 auto lock = impl.lockShared();
991 uint64_t hash = reinterpret_cast<uintptr_t>(this);
992 return Metadata { Type::DIRECTORY, 0, 0, lock->lastModified, 1, hash };
993 }
994
995 void sync() const override {}
996 void datasync() const override {}
997 // no-ops
998
999 Array<String> listNames() const override {
1000 auto lock = impl.lockShared();
1001 return KJ_MAP(e, lock->entries) { return heapString(e.first); };
1002 }
1003
1004 Array<Entry> listEntries() const override {
1005 auto lock = impl.lockShared();
1006 return KJ_MAP(e, lock->entries) {
1007 FsNode::Type type;
1008 if (e.second.node.is<SymlinkNode>()) {
1009 type = FsNode::Type::SYMLINK;
1010 } else if (e.second.node.is<FileNode>()) {
1011 type = FsNode::Type::FILE;
1012 } else {
1013 KJ_ASSERT(e.second.node.is<DirectoryNode>());
1014 type = FsNode::Type::DIRECTORY;
1015 }
1016
1017 return Entry { type, heapString(e.first) };
1018 };
1019 }
1020
1021 bool exists(PathPtr path) const override {
1022 if (path.size() == 0) {
1023 return true;
1024 } else if (path.size() == 1) {
1025 auto lock = impl.lockShared();
1026 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
1027 return exists(lock, *entry);
1028 } else {
1029 return false;
1030 }
1031 } else {
1032 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) {
1033 return subdir->get()->exists(path.slice(1, path.size()));
1034 } else {
1035 return false;
1036 }
1037 }
1038 }
1039
1040 Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
1041 if (path.size() == 0) {
1042 return stat();
1043 } else if (path.size() == 1) {
1044 auto lock = impl.lockShared();
1045 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
1046 if (entry->node.is<FileNode>()) {
1047 return entry->node.get<FileNode>().file->stat();
1048 } else if (entry->node.is<DirectoryNode>()) {
1049 return entry->node.get<DirectoryNode>().directory->stat();
1050 } else if (entry->node.is<SymlinkNode>()) {
1051 auto& link = entry->node.get<SymlinkNode>();
1052 uint64_t hash = reinterpret_cast<uintptr_t>(link.content.begin());
1053 return FsNode::Metadata { FsNode::Type::SYMLINK, 0, 0, link.lastModified, 1, hash };
1054 } else {
1055 KJ_FAIL_ASSERT("unknown node type") { return nullptr; }
1056 }
1057 } else {
1058 return nullptr;
1059 }
1060 } else {
1061 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) {
1062 return subdir->get()->tryLstat(path.slice(1, path.size()));
1063 } else {
1064 return nullptr;
1065 }
1066 }
1067 }
1068
1069 Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
1070 if (path.size() == 0) {
1071 KJ_FAIL_REQUIRE("not a file") { return nullptr; }
1072 } else if (path.size() == 1) {
1073 auto lock = impl.lockShared();
1074 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
1075 return asFile(lock, *entry);
1076 } else {
1077 return nullptr;
1078 }
1079 } else {
1080 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) {
1081 return subdir->get()->tryOpenFile(path.slice(1, path.size()));
1082 } else {
1083 return nullptr;
1084 }
1085 }
1086 }
1087
1088 Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
1089 if (path.size() == 0) {
1090 return clone();
1091 } else if (path.size() == 1) {
1092 auto lock = impl.lockShared();
1093 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
1094 return asDirectory(lock, *entry);
1095 } else {
1096 return nullptr;
1097 }
1098 } else {
1099 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) {
1100 return subdir->get()->tryOpenSubdir(path.slice(1, path.size()));
1101 } else {
1102 return nullptr;
1103 }
1104 }
1105 }
1106
1107 Maybe<String> tryReadlink(PathPtr path) const override {
1108 if (path.size() == 0) {
1109 KJ_FAIL_REQUIRE("not a symlink") { return nullptr; }
1110 } else if (path.size() == 1) {
1111 auto lock = impl.lockShared();
1112 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
1113 return asSymlink(lock, *entry);
1114 } else {
1115 return nullptr;
1116 }
1117 } else {
1118 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) {
1119 return subdir->get()->tryReadlink(path.slice(1, path.size()));
1120 } else {
1121 return nullptr;
1122 }
1123 }
1124 }
1125
1126 Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const override {
1127 if (path.size() == 0) {
1128 if (has(mode, WriteMode::MODIFY)) {
1129 KJ_FAIL_REQUIRE("not a file") { return nullptr; }
1130 } else if (has(mode, WriteMode::CREATE)) {
1131 return nullptr; // already exists (as a directory)
1132 } else {
1133 KJ_FAIL_REQUIRE("can't replace self") { return nullptr; }
1134 }
1135 } else if (path.size() == 1) {
1136 auto lock = impl.lockExclusive();
1137 KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
1138 return asFile(lock, *entry, mode);
1139 } else {
1140 return nullptr;
1141 }
1142 } else {
1143 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) {
1144 return child->get()->tryOpenFile(path.slice(1, path.size()), mode);
1145 } else {
1146 return nullptr;
1147 }
1148 }
1149 }
1150
1151 Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override {
1152 if (path.size() == 0) {
1153 KJ_FAIL_REQUIRE("can't replace self") { break; }
1154 } else if (path.size() == 1) {
1155 // don't need lock just to read the clock ref
1156 return heap<ReplacerImpl<File>>(*this, path[0],
1157 newInMemoryFile(impl.getWithoutLock().clock), mode);
1158 } else {
1159 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) {
1160 return child->get()->replaceFile(path.slice(1, path.size()), mode);
1161 }
1162 }
1163 return heap<BrokenReplacer<File>>(newInMemoryFile(impl.getWithoutLock().clock));
1164 }
1165
1166 Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const override {
1167 if (path.size() == 0) {
1168 if (has(mode, WriteMode::MODIFY)) {
1169 return atomicAddRef(*this);
1170 } else if (has(mode, WriteMode::CREATE)) {
1171 return nullptr; // already exists
1172 } else {
1173 KJ_FAIL_REQUIRE("can't replace self") { return nullptr; }
1174 }
1175 } else if (path.size() == 1) {
1176 auto lock = impl.lockExclusive();
1177 KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
1178 return asDirectory(lock, *entry, mode);
1179 } else {
1180 return nullptr;
1181 }
1182 } else {
1183 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) {
1184 return child->get()->tryOpenSubdir(path.slice(1, path.size()), mode);
1185 } else {
1186 return nullptr;
1187 }
1188 }
1189 }
1190
1191 Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override {
1192 if (path.size() == 0) {
1193 KJ_FAIL_REQUIRE("can't replace self") { break; }
1194 } else if (path.size() == 1) {
1195 // don't need lock just to read the clock ref
1196 return heap<ReplacerImpl<Directory>>(*this, path[0],
1197 newInMemoryDirectory(impl.getWithoutLock().clock), mode);
1198 } else {
1199 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) {
1200 return child->get()->replaceSubdir(path.slice(1, path.size()), mode);
1201 }
1202 }
1203 return heap<BrokenReplacer<Directory>>(newInMemoryDirectory(impl.getWithoutLock().clock));
1204 }
1205
1206 Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const override {
1207 if (path.size() == 0) {
1208 if (has(mode, WriteMode::MODIFY)) {
1209 KJ_FAIL_REQUIRE("not a file") { return nullptr; }
1210 } else if (has(mode, WriteMode::CREATE)) {
1211 return nullptr; // already exists (as a directory)
1212 } else {
1213 KJ_FAIL_REQUIRE("can't replace self") { return nullptr; }
1214 }
1215 } else if (path.size() == 1) {
1216 auto lock = impl.lockExclusive();
1217 KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
1218 return asFile(lock, *entry, mode).map(newFileAppender);
1219 } else {
1220 return nullptr;
1221 }
1222 } else {
1223 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) {
1224 return child->get()->tryAppendFile(path.slice(1, path.size()), mode);
1225 } else {
1226 return nullptr;
1227 }
1228 }
1229 }
1230
1231 bool trySymlink(PathPtr path, StringPtr content, WriteMode mode) const override {
1232 if (path.size() == 0) {
1233 if (has(mode, WriteMode::CREATE)) {
1234 return false;
1235 } else {
1236 KJ_FAIL_REQUIRE("can't replace self") { return false; }
1237 }
1238 } else if (path.size() == 1) {
1239 auto lock = impl.lockExclusive();
1240 KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
1241 entry->init(SymlinkNode { lock->clock.now(), heapString(content) });
1242 lock->modified();
1243 return true;
1244 } else {
1245 return false;
1246 }
1247 } else {
1248 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) {
1249 return child->get()->trySymlink(path.slice(1, path.size()), content, mode);
1250 } else {
1251 KJ_FAIL_REQUIRE("couldn't create parent directory") { return false; }
1252 }
1253 }
1254 }
1255
1256 Own<const File> createTemporary() const override {
1257 // Don't need lock just to read the clock ref.
1258 return newInMemoryFile(impl.getWithoutLock().clock);
1259 }
1260
1261 bool tryTransfer(PathPtr toPath, WriteMode toMode,
1262 const Directory& fromDirectory, PathPtr fromPath,
1263 TransferMode mode) const override {
1264 if (toPath.size() == 0) {
1265 if (has(toMode, WriteMode::CREATE)) {
1266 return false;
1267 } else {
1268 KJ_FAIL_REQUIRE("can't replace self") { return false; }
1269 }
1270 } else if (toPath.size() == 1) {
1271 // tryTransferChild() needs to at least know the node type, so do an lstat.
1272 KJ_IF_MAYBE(meta, fromDirectory.tryLstat(fromPath)) {
1273 auto lock = impl.lockExclusive();
1274 KJ_IF_MAYBE(entry, lock->openEntry(toPath[0], toMode)) {
1275 // Make sure if we just cerated a new entry, and we don't successfully transfer to it, we
1276 // remove the entry before returning.
1277 bool needRollback = entry->node == nullptr;
1278 KJ_DEFER(if (needRollback) { lock->entries.erase(toPath[0]); });
1279
1280 if (lock->tryTransferChild(*entry, meta->type, meta->lastModified, meta->size,
1281 fromDirectory, fromPath, mode)) {
1282 lock->modified();
1283 needRollback = false;
1284 return true;
1285 } else {
1286 KJ_FAIL_REQUIRE("InMemoryDirectory can't link an inode of this type", fromPath) {
1287 return false;
1288 }
1289 }
1290 } else {
1291 return false;
1292 }
1293 } else {
1294 return false;
1295 }
1296 } else {
1297 // TODO(someday): Ideally we wouldn't create parent directories if fromPath doesn't exist.
1298 // This requires a different approach to the code here, though.
1299 KJ_IF_MAYBE(child, tryGetParent(toPath[0], toMode)) {
1300 return child->get()->tryTransfer(
1301 toPath.slice(1, toPath.size()), toMode, fromDirectory, fromPath, mode);
1302 } else {
1303 return false;
1304 }
1305 }
1306 }
1307
1308 Maybe<bool> tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode,
1309 PathPtr fromPath, TransferMode mode) const override {
1310 if (fromPath.size() <= 1) {
1311 // If `fromPath` is in this directory (or *is* this directory) then we don't have any
1312 // optimizations.
1313 return nullptr;
1314 }
1315
1316 // `fromPath` is in a subdirectory. It could turn out that that subdirectory is not an
1317 // InMemoryDirectory and is instead something `toDirectory` is friendly with. So let's follow
1318 // the path.
1319
1320 KJ_IF_MAYBE(child, tryGetParent(fromPath[0], WriteMode::MODIFY)) {
1321 // OK, switch back to tryTransfer() but use the subdirectory.
1322 return toDirectory.tryTransfer(toPath, toMode,
1323 **child, fromPath.slice(1, fromPath.size()), mode);
1324 } else {
1325 // Hmm, doesn't exist. Fall back to standard path.
1326 return nullptr;
1327 }
1328 }
1329
1330 bool tryRemove(PathPtr path) const override {
1331 if (path.size() == 0) {
1332 KJ_FAIL_REQUIRE("can't remove self from self") { return false; }
1333 } else if (path.size() == 1) {
1334 auto lock = impl.lockExclusive();
1335 auto iter = lock->entries.find(path[0]);
1336 if (iter == lock->entries.end()) {
1337 return false;
1338 } else {
1339 lock->entries.erase(iter);
1340 lock->modified();
1341 return true;
1342 }
1343 } else {
1344 KJ_IF_MAYBE(child, tryGetParent(path[0], WriteMode::MODIFY)) {
1345 return child->get()->tryRemove(path.slice(1, path.size()));
1346 } else {
1347 return false;
1348 }
1349 }
1350 }
1351
1352private:
1353 struct FileNode {
1354 Own<const File> file;
1355 };
1356 struct DirectoryNode {
1357 Own<const Directory> directory;
1358 };
1359 struct SymlinkNode {
1360 Date lastModified;
1361 String content;
1362
1363 Path parse() const {
1364 KJ_CONTEXT("parsing symlink", content);
1365 return Path::parse(content);
1366 }
1367 };
1368
1369 struct EntryImpl {
1370 String name;
1371 OneOf<FileNode, DirectoryNode, SymlinkNode> node;
1372
1373 EntryImpl(String&& name): name(kj::mv(name)) {}
1374
1375 Own<const File> init(FileNode&& value) {
1376 return node.init<FileNode>(kj::mv(value)).file->clone();
1377 }
1378 Own<const Directory> init(DirectoryNode&& value) {
1379 return node.init<DirectoryNode>(kj::mv(value)).directory->clone();
1380 }
1381 void init(SymlinkNode&& value) {
1382 node.init<SymlinkNode>(kj::mv(value));
1383 }
1384 bool init(OneOf<FileNode, DirectoryNode, SymlinkNode>&& value) {
1385 node = kj::mv(value);
1386 return node != nullptr;
1387 }
1388
1389 void set(Own<const File>&& value) {
1390 node.init<FileNode>(FileNode { kj::mv(value) });
1391 }
1392 void set(Own<const Directory>&& value) {
1393 node.init<DirectoryNode>(DirectoryNode { kj::mv(value) });
1394 }
1395 };
1396
1397 template <typename T>
1398 class ReplacerImpl final: public Replacer<T> {
1399 public:
1400 ReplacerImpl(const InMemoryDirectory& directory, kj::StringPtr name,
1401 Own<const T> inner, WriteMode mode)
1402 : Replacer<T>(mode), directory(atomicAddRef(directory)), name(heapString(name)),
1403 inner(kj::mv(inner)) {}
1404
1405 const T& get() override { return *inner; }
1406
1407 bool tryCommit() override {
1408 KJ_REQUIRE(!committed, "commit() already called") { return true; }
1409
1410 auto lock = directory->impl.lockExclusive();
1411 KJ_IF_MAYBE(entry, lock->openEntry(name, Replacer<T>::mode)) {
1412 entry->set(inner->clone());
1413 lock->modified();
1414 return true;
1415 } else {
1416 return false;
1417 }
1418 }
1419
1420 private:
1421 bool committed = false;
1422 Own<const InMemoryDirectory> directory;
1423 kj::String name;
1424 Own<const T> inner;
1425 };
1426
1427 template <typename T>
1428 class BrokenReplacer final: public Replacer<T> {
1429 // For recovery path when exceptions are disabled.
1430
1431 public:
1432 BrokenReplacer(Own<const T> inner)
1433 : Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY),
1434 inner(kj::mv(inner)) {}
1435
1436 const T& get() override { return *inner; }
1437 bool tryCommit() override { return false; }
1438
1439 private:
1440 Own<const T> inner;
1441 };
1442
1443 struct Impl {
1444 const Clock& clock;
1445
1446 std::map<StringPtr, EntryImpl> entries;
1447 // Note: If this changes to a non-sorted map, listNames() and listEntries() must be updated to
1448 // sort their results.
1449
1450 Date lastModified;
1451
1452 Impl(const Clock& clock): clock(clock), lastModified(clock.now()) {}
1453
1454 Maybe<EntryImpl&> openEntry(kj::StringPtr name, WriteMode mode) {
1455 // TODO(perf): We could avoid a copy if the entry exists, at the expense of a double-lookup
1456 // if it doesn't. Maybe a better map implementation will solve everything?
1457 return openEntry(heapString(name), mode);
1458 }
1459
1460 Maybe<EntryImpl&> openEntry(String&& name, WriteMode mode) {
1461 if (has(mode, WriteMode::CREATE)) {
1462 EntryImpl entry(kj::mv(name));
1463 StringPtr nameRef = entry.name;
1464 auto insertResult = entries.insert(std::make_pair(nameRef, kj::mv(entry)));
1465
1466 if (!insertResult.second && !has(mode, WriteMode::MODIFY)) {
1467 // Entry already existed and MODIFY not specified.
1468 return nullptr;
1469 }
1470
1471 return insertResult.first->second;
1472 } else if (has(mode, WriteMode::MODIFY)) {
1473 return tryGetEntry(name);
1474 } else {
1475 // Neither CREATE nor MODIFY specified: precondition always fails.
1476 return nullptr;
1477 }
1478 }
1479
1480 kj::Maybe<const EntryImpl&> tryGetEntry(kj::StringPtr name) const {
1481 auto iter = entries.find(name);
1482 if (iter == entries.end()) {
1483 return nullptr;
1484 } else {
1485 return iter->second;
1486 }
1487 }
1488
1489 kj::Maybe<EntryImpl&> tryGetEntry(kj::StringPtr name) {
1490 auto iter = entries.find(name);
1491 if (iter == entries.end()) {
1492 return nullptr;
1493 } else {
1494 return iter->second;
1495 }
1496 }
1497
1498 void modified() {
1499 lastModified = clock.now();
1500 }
1501
1502 bool tryTransferChild(EntryImpl& entry, const FsNode::Type type, kj::Maybe<Date> lastModified,
1503 kj::Maybe<uint64_t> size, const Directory& fromDirectory,
1504 PathPtr fromPath, TransferMode mode) {
1505 switch (type) {
1506 case FsNode::Type::FILE:
1507 KJ_IF_MAYBE(file, fromDirectory.tryOpenFile(fromPath, WriteMode::MODIFY)) {
1508 if (mode == TransferMode::COPY) {
1509 auto copy = newInMemoryFile(clock);
1510 copy->copy(0, **file, 0, size.orDefault(kj::maxValue));
1511 entry.set(kj::mv(copy));
1512 } else {
1513 if (mode == TransferMode::MOVE) {
1514 KJ_ASSERT(fromDirectory.tryRemove(fromPath), "couldn't move node", fromPath) {
1515 return false;
1516 }
1517 }
1518 entry.set(kj::mv(*file));
1519 }
1520 return true;
1521 } else {
1522 KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) {
1523 return false;
1524 }
1525 }
1526 case FsNode::Type::DIRECTORY:
1527 KJ_IF_MAYBE(subdir, fromDirectory.tryOpenSubdir(fromPath, WriteMode::MODIFY)) {
1528 if (mode == TransferMode::COPY) {
1529 auto copy = atomicRefcounted<InMemoryDirectory>(clock);
1530 auto& cpim = copy->impl.getWithoutLock(); // safe because just-created
1531 for (auto& subEntry: subdir->get()->listEntries()) {
1532 EntryImpl newEntry(kj::mv(subEntry.name));
1533 Path filename(newEntry.name);
1534 if (!cpim.tryTransferChild(newEntry, subEntry.type, nullptr, nullptr, **subdir,
1535 filename, TransferMode::COPY)) {
1536 KJ_LOG(ERROR, "couldn't copy node of type not supported by InMemoryDirectory",
1537 filename);
1538 } else {
1539 StringPtr nameRef = newEntry.name;
1540 cpim.entries.insert(std::make_pair(nameRef, kj::mv(newEntry)));
1541 }
1542 }
1543 entry.set(kj::mv(copy));
1544 } else {
1545 if (mode == TransferMode::MOVE) {
1546 KJ_ASSERT(fromDirectory.tryRemove(fromPath), "couldn't move node", fromPath) {
1547 return false;
1548 }
1549 }
1550 entry.set(kj::mv(*subdir));
1551 }
1552 return true;
1553 } else {
1554 KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) {
1555 return false;
1556 }
1557 }
1558 case FsNode::Type::SYMLINK:
1559 KJ_IF_MAYBE(content, fromDirectory.tryReadlink(fromPath)) {
1560 // Since symlinks are immutable, we can implement LINK the same as COPY.
1561 entry.init(SymlinkNode { lastModified.orDefault(clock.now()), kj::mv(*content) });
1562 if (mode == TransferMode::MOVE) {
1563 KJ_ASSERT(fromDirectory.tryRemove(fromPath), "couldn't move node", fromPath) {
1564 return false;
1565 }
1566 }
1567 return true;
1568 } else {
1569 KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) {
1570 return false;
1571 }
1572 }
1573 default:
1574 return false;
1575 }
1576 }
1577 };
1578
1579 kj::MutexGuarded<Impl> impl;
1580
1581 bool exists(kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
1582 if (entry.node.is<SymlinkNode>()) {
1583 auto newPath = entry.node.get<SymlinkNode>().parse();
1584 lock.release();
1585 return exists(newPath);
1586 } else {
1587 return true;
1588 }
1589 }
1590 Maybe<Own<const ReadableFile>> asFile(
1591 kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
1592 if (entry.node.is<FileNode>()) {
1593 return entry.node.get<FileNode>().file->clone();
1594 } else if (entry.node.is<SymlinkNode>()) {
1595 auto newPath = entry.node.get<SymlinkNode>().parse();
1596 lock.release();
1597 return tryOpenFile(newPath);
1598 } else {
1599 KJ_FAIL_REQUIRE("not a file") { return nullptr; }
1600 }
1601 }
1602 Maybe<Own<const ReadableDirectory>> asDirectory(
1603 kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
1604 if (entry.node.is<DirectoryNode>()) {
1605 return entry.node.get<DirectoryNode>().directory->clone();
1606 } else if (entry.node.is<SymlinkNode>()) {
1607 auto newPath = entry.node.get<SymlinkNode>().parse();
1608 lock.release();
1609 return tryOpenSubdir(newPath);
1610 } else {
1611 KJ_FAIL_REQUIRE("not a directory") { return nullptr; }
1612 }
1613 }
1614 Maybe<String> asSymlink(kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
1615 if (entry.node.is<SymlinkNode>()) {
1616 return heapString(entry.node.get<SymlinkNode>().content);
1617 } else {
1618 KJ_FAIL_REQUIRE("not a symlink") { return nullptr; }
1619 }
1620 }
1621
1622 Maybe<Own<const File>> asFile(kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const {
1623 if (entry.node.is<FileNode>()) {
1624 return entry.node.get<FileNode>().file->clone();
1625 } else if (entry.node.is<SymlinkNode>()) {
1626 // CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the
1627 // target itself can still be created.
1628 auto newPath = entry.node.get<SymlinkNode>().parse();
1629 lock.release();
1630 return tryOpenFile(newPath, mode - WriteMode::CREATE_PARENT);
1631 } else if (entry.node == nullptr) {
1632 KJ_ASSERT(has(mode, WriteMode::CREATE));
1633 lock->modified();
1634 return entry.init(FileNode { newInMemoryFile(lock->clock) });
1635 } else {
1636 KJ_FAIL_REQUIRE("not a file") { return nullptr; }
1637 }
1638 }
1639 Maybe<Own<const Directory>> asDirectory(
1640 kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const {
1641 if (entry.node.is<DirectoryNode>()) {
1642 return entry.node.get<DirectoryNode>().directory->clone();
1643 } else if (entry.node.is<SymlinkNode>()) {
1644 // CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the
1645 // target itself can still be created.
1646 auto newPath = entry.node.get<SymlinkNode>().parse();
1647 lock.release();
1648 return tryOpenSubdir(newPath, mode - WriteMode::CREATE_PARENT);
1649 } else if (entry.node == nullptr) {
1650 KJ_ASSERT(has(mode, WriteMode::CREATE));
1651 lock->modified();
1652 return entry.init(DirectoryNode { newInMemoryDirectory(lock->clock) });
1653 } else {
1654 KJ_FAIL_REQUIRE("not a directory") { return nullptr; }
1655 }
1656 }
1657
1658 kj::Maybe<Own<const ReadableDirectory>> tryGetParent(kj::StringPtr name) const {
1659 auto lock = impl.lockShared();
1660 KJ_IF_MAYBE(entry, impl.lockShared()->tryGetEntry(name)) {
1661 return asDirectory(lock, *entry);
1662 } else {
1663 return nullptr;
1664 }
1665 }
1666
1667 kj::Maybe<Own<const Directory>> tryGetParent(kj::StringPtr name, WriteMode mode) const {
1668 // Get a directory which is a parent of the eventual target. If `mode` includes
1669 // WriteMode::CREATE_PARENTS, possibly create the parent directory.
1670
1671 auto lock = impl.lockExclusive();
1672
1673 WriteMode parentMode = has(mode, WriteMode::CREATE) && has(mode, WriteMode::CREATE_PARENT)
1674 ? WriteMode::CREATE | WriteMode::MODIFY // create parent
1675 : WriteMode::MODIFY; // don't create parent
1676
1677 // Possibly create parent.
1678 KJ_IF_MAYBE(entry, lock->openEntry(name, parentMode)) {
1679 if (entry->node.is<DirectoryNode>()) {
1680 return entry->node.get<DirectoryNode>().directory->clone();
1681 } else if (entry->node == nullptr) {
1682 lock->modified();
1683 return entry->init(DirectoryNode { newInMemoryDirectory(lock->clock) });
1684 }
1685 // Continue on.
1686 }
1687
1688 if (has(mode, WriteMode::CREATE)) {
1689 // CREATE is documented as returning null when the file already exists. In this case, the
1690 // file does NOT exist because the parent directory does not exist or is not a directory.
1691 KJ_FAIL_REQUIRE("parent is not a directory") { return nullptr; }
1692 } else {
1693 return nullptr;
1694 }
1695 }
1696};
1697
1698// -----------------------------------------------------------------------------
1699
1700class AppendableFileImpl final: public AppendableFile {
1701public:
1702 AppendableFileImpl(Own<const File>&& fileParam): file(kj::mv(fileParam)) {}
1703
1704 Own<const FsNode> cloneFsNode() const override {
1705 return heap<AppendableFileImpl>(file->clone());
1706 }
1707
1708 Maybe<int> getFd() const override {
1709 return nullptr;
1710 }
1711
1712 Metadata stat() const override {
1713 return file->stat();
1714 }
1715
1716 void sync() const override { file->sync(); }
1717 void datasync() const override { file->datasync(); }
1718
1719 void write(const void* buffer, size_t size) override {
1720 file->write(file->stat().size, arrayPtr(reinterpret_cast<const byte*>(buffer), size));
1721 }
1722
1723private:
1724 Own<const File> file;
1725};
1726
1727} // namespace
1728
1729// -----------------------------------------------------------------------------
1730
1731Own<File> newInMemoryFile(const Clock& clock) {
1732 return atomicRefcounted<InMemoryFile>(clock);
1733}
1734Own<Directory> newInMemoryDirectory(const Clock& clock) {
1735 return atomicRefcounted<InMemoryDirectory>(clock);
1736}
1737Own<AppendableFile> newFileAppender(Own<const File> inner) {
1738 return heap<AppendableFileImpl>(kj::mv(inner));
1739}
1740
1741} // namespace kj
1742