filesystem.c++ (58211B)
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 31 namespace kj { 32 33 Path::Path(StringPtr name): Path(heapString(name)) {} 34 Path::Path(String&& name): parts(heapArray<String>(1)) { 35 parts[0] = kj::mv(name); 36 validatePart(parts[0]); 37 } 38 39 Path::Path(ArrayPtr<const StringPtr> parts) 40 : Path(KJ_MAP(p, parts) { return heapString(p); }) {} 41 Path::Path(Array<String> partsParam) 42 : Path(kj::mv(partsParam), ALREADY_CHECKED) { 43 for (auto& p: parts) { 44 validatePart(p); 45 } 46 } 47 48 Path PathPtr::clone() { 49 return Path(KJ_MAP(p, parts) { return heapString(p); }, Path::ALREADY_CHECKED); 50 } 51 52 Path 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 60 Path Path::parseWin32Api(ArrayPtr<const wchar_t> text) { 61 auto utf8 = decodeWideString(text); 62 return evalWin32Impl(Vector<String>(countPartsWin32(utf8)), utf8, true); 63 } 64 65 Path 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 } 71 Path 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 } 77 Path 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 } 83 Path 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 90 Path 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 } 100 Path 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 111 PathPtr 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 } 115 Path 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 122 PathPtr PathPtr::parent() const { 123 KJ_REQUIRE(parts.size() > 0, "root path has no parent"); 124 return PathPtr(parts.slice(0, parts.size() - 1)); 125 } 126 Path 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 131 String 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 155 Path Path::slice(size_t start, size_t end) && { 156 return Path(KJ_MAP(p, parts.slice(start, end)) { return kj::mv(p); }); 157 } 158 159 bool PathPtr::operator==(PathPtr other) const { 160 return parts == other.parts; 161 } 162 bool 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 172 bool PathPtr::startsWith(PathPtr prefix) const { 173 return parts.size() >= prefix.parts.size() && 174 parts.slice(0, prefix.parts.size()) == prefix.parts; 175 } 176 177 bool 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 182 Path 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 } 187 Path 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 193 String 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 287 Array<wchar_t> PathPtr::forWin32Api(bool absolute) const { 288 return encodeWideString(toWin32StringImpl(absolute, true), true); 289 } 290 291 // ----------------------------------------------------------------------------- 292 293 String 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 302 void 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 309 void 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 331 Path 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 348 Path 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 413 size_t Path::countParts(StringPtr path) { 414 size_t result = 1; 415 for (char c: path) { 416 result += (c == '/'); 417 } 418 return result; 419 } 420 421 size_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 429 bool 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 434 bool 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 451 bool 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 488 String 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 498 Array<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 508 void File::writeAll(ArrayPtr<const byte> bytes) const { 509 truncate(0); 510 write(0, bytes); 511 } 512 513 void File::writeAll(StringPtr text) const { 514 writeAll(text.asBytes()); 515 } 516 517 size_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 538 FsNode::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 547 Own<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 556 Own<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 565 String 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 574 Own<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 590 Own<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 606 Own<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 622 void 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 exists", linkpath) { break; } 626 } else { 627 // Shouldn't happen. 628 KJ_FAIL_ASSERT("symlink() returned null despite no preconditions", linkpath) { break; } 629 } 630 } 631 } 632 633 void 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 647 static void copyContents(const Directory& to, const ReadableDirectory& from); 648 649 static 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 706 static 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 713 bool 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 746 Maybe<bool> Directory::tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode, 747 PathPtr fromPath, TransferMode mode) const { 748 return nullptr; 749 } 750 751 void Directory::remove(PathPtr path) const { 752 if (!tryRemove(path)) { 753 KJ_FAIL_REQUIRE("path to remove doesn't exist", path) { break; } 754 } 755 } 756 757 void 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 771 namespace { 772 773 class InMemoryFile final: public File, public AtomicRefcounted { 774 public: 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 899 private: 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 if (size > 0) { // placate ubsan; bytes.begin() might be null 916 memcpy(newBytes.begin(), bytes.begin(), size); 917 } 918 memset(newBytes.begin() + size, 0, newBytes.size() - size); 919 bytes = kj::mv(newBytes); 920 } 921 } 922 923 void modified() { 924 lastModified = clock.now(); 925 } 926 }; 927 kj::MutexGuarded<Impl> impl; 928 929 class MmapDisposer final: public ArrayDisposer { 930 public: 931 MmapDisposer(Own<const InMemoryFile>&& refParam): ref(kj::mv(refParam)) { 932 ++ref->impl.getAlreadyLockedExclusive().mmapCount; 933 } 934 ~MmapDisposer() noexcept(false) { 935 --ref->impl.lockExclusive()->mmapCount; 936 } 937 938 void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount, 939 size_t capacity, void (*destroyElement)(void*)) const override { 940 delete this; 941 } 942 943 private: 944 Own<const InMemoryFile> ref; 945 }; 946 947 class WritableFileMappingImpl final: public WritableFileMapping { 948 public: 949 WritableFileMappingImpl(Own<const InMemoryFile>&& refParam, ArrayPtr<byte> range) 950 : ref(kj::mv(refParam)), range(range) { 951 ++ref->impl.getAlreadyLockedExclusive().mmapCount; 952 } 953 ~WritableFileMappingImpl() noexcept(false) { 954 --ref->impl.lockExclusive()->mmapCount; 955 } 956 957 ArrayPtr<byte> get() const override { 958 // const_cast OK because WritableFileMapping does indeed provide a writable view despite 959 // being const itself. 960 return arrayPtr(const_cast<byte*>(range.begin()), range.size()); 961 } 962 963 void changed(ArrayPtr<byte> slice) const override { 964 ref->impl.lockExclusive()->modified(); 965 } 966 967 void sync(ArrayPtr<byte> slice) const override { 968 ref->impl.lockExclusive()->modified(); 969 } 970 971 private: 972 Own<const InMemoryFile> ref; 973 ArrayPtr<byte> range; 974 }; 975 }; 976 977 // ----------------------------------------------------------------------------- 978 979 class InMemoryDirectory final: public Directory, public AtomicRefcounted { 980 public: 981 InMemoryDirectory(const Clock& clock): impl(clock) {} 982 983 Own<const FsNode> cloneFsNode() const override { 984 return atomicAddRef(*this); 985 } 986 987 Maybe<int> getFd() const override { 988 return nullptr; 989 } 990 991 Metadata stat() const override { 992 auto lock = impl.lockShared(); 993 uint64_t hash = reinterpret_cast<uintptr_t>(this); 994 return Metadata { Type::DIRECTORY, 0, 0, lock->lastModified, 1, hash }; 995 } 996 997 void sync() const override {} 998 void datasync() const override {} 999 // no-ops 1000 1001 Array<String> listNames() const override { 1002 auto lock = impl.lockShared(); 1003 return KJ_MAP(e, lock->entries) { return heapString(e.first); }; 1004 } 1005 1006 Array<Entry> listEntries() const override { 1007 auto lock = impl.lockShared(); 1008 return KJ_MAP(e, lock->entries) { 1009 FsNode::Type type; 1010 if (e.second.node.is<SymlinkNode>()) { 1011 type = FsNode::Type::SYMLINK; 1012 } else if (e.second.node.is<FileNode>()) { 1013 type = FsNode::Type::FILE; 1014 } else { 1015 KJ_ASSERT(e.second.node.is<DirectoryNode>()); 1016 type = FsNode::Type::DIRECTORY; 1017 } 1018 1019 return Entry { type, heapString(e.first) }; 1020 }; 1021 } 1022 1023 bool exists(PathPtr path) const override { 1024 if (path.size() == 0) { 1025 return true; 1026 } else if (path.size() == 1) { 1027 auto lock = impl.lockShared(); 1028 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { 1029 return exists(lock, *entry); 1030 } else { 1031 return false; 1032 } 1033 } else { 1034 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { 1035 return subdir->get()->exists(path.slice(1, path.size())); 1036 } else { 1037 return false; 1038 } 1039 } 1040 } 1041 1042 Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override { 1043 if (path.size() == 0) { 1044 return stat(); 1045 } else if (path.size() == 1) { 1046 auto lock = impl.lockShared(); 1047 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { 1048 if (entry->node.is<FileNode>()) { 1049 return entry->node.get<FileNode>().file->stat(); 1050 } else if (entry->node.is<DirectoryNode>()) { 1051 return entry->node.get<DirectoryNode>().directory->stat(); 1052 } else if (entry->node.is<SymlinkNode>()) { 1053 auto& link = entry->node.get<SymlinkNode>(); 1054 uint64_t hash = reinterpret_cast<uintptr_t>(link.content.begin()); 1055 return FsNode::Metadata { FsNode::Type::SYMLINK, 0, 0, link.lastModified, 1, hash }; 1056 } else { 1057 KJ_FAIL_ASSERT("unknown node type") { return nullptr; } 1058 } 1059 } else { 1060 return nullptr; 1061 } 1062 } else { 1063 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { 1064 return subdir->get()->tryLstat(path.slice(1, path.size())); 1065 } else { 1066 return nullptr; 1067 } 1068 } 1069 } 1070 1071 Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override { 1072 if (path.size() == 0) { 1073 KJ_FAIL_REQUIRE("not a file") { return nullptr; } 1074 } else if (path.size() == 1) { 1075 auto lock = impl.lockShared(); 1076 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { 1077 return asFile(lock, *entry); 1078 } else { 1079 return nullptr; 1080 } 1081 } else { 1082 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { 1083 return subdir->get()->tryOpenFile(path.slice(1, path.size())); 1084 } else { 1085 return nullptr; 1086 } 1087 } 1088 } 1089 1090 Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override { 1091 if (path.size() == 0) { 1092 return clone(); 1093 } else if (path.size() == 1) { 1094 auto lock = impl.lockShared(); 1095 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { 1096 return asDirectory(lock, *entry); 1097 } else { 1098 return nullptr; 1099 } 1100 } else { 1101 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { 1102 return subdir->get()->tryOpenSubdir(path.slice(1, path.size())); 1103 } else { 1104 return nullptr; 1105 } 1106 } 1107 } 1108 1109 Maybe<String> tryReadlink(PathPtr path) const override { 1110 if (path.size() == 0) { 1111 KJ_FAIL_REQUIRE("not a symlink") { return nullptr; } 1112 } else if (path.size() == 1) { 1113 auto lock = impl.lockShared(); 1114 KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { 1115 return asSymlink(lock, *entry); 1116 } else { 1117 return nullptr; 1118 } 1119 } else { 1120 KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { 1121 return subdir->get()->tryReadlink(path.slice(1, path.size())); 1122 } else { 1123 return nullptr; 1124 } 1125 } 1126 } 1127 1128 Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const override { 1129 if (path.size() == 0) { 1130 if (has(mode, WriteMode::MODIFY)) { 1131 KJ_FAIL_REQUIRE("not a file") { return nullptr; } 1132 } else if (has(mode, WriteMode::CREATE)) { 1133 return nullptr; // already exists (as a directory) 1134 } else { 1135 KJ_FAIL_REQUIRE("can't replace self") { return nullptr; } 1136 } 1137 } else if (path.size() == 1) { 1138 auto lock = impl.lockExclusive(); 1139 KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) { 1140 return asFile(lock, *entry, mode); 1141 } else { 1142 return nullptr; 1143 } 1144 } else { 1145 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { 1146 return child->get()->tryOpenFile(path.slice(1, path.size()), mode); 1147 } else { 1148 return nullptr; 1149 } 1150 } 1151 } 1152 1153 Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override { 1154 if (path.size() == 0) { 1155 KJ_FAIL_REQUIRE("can't replace self") { break; } 1156 } else if (path.size() == 1) { 1157 // don't need lock just to read the clock ref 1158 return heap<ReplacerImpl<File>>(*this, path[0], 1159 newInMemoryFile(impl.getWithoutLock().clock), mode); 1160 } else { 1161 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { 1162 return child->get()->replaceFile(path.slice(1, path.size()), mode); 1163 } 1164 } 1165 return heap<BrokenReplacer<File>>(newInMemoryFile(impl.getWithoutLock().clock)); 1166 } 1167 1168 Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const override { 1169 if (path.size() == 0) { 1170 if (has(mode, WriteMode::MODIFY)) { 1171 return atomicAddRef(*this); 1172 } else if (has(mode, WriteMode::CREATE)) { 1173 return nullptr; // already exists 1174 } else { 1175 KJ_FAIL_REQUIRE("can't replace self") { return nullptr; } 1176 } 1177 } else if (path.size() == 1) { 1178 auto lock = impl.lockExclusive(); 1179 KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) { 1180 return asDirectory(lock, *entry, mode); 1181 } else { 1182 return nullptr; 1183 } 1184 } else { 1185 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { 1186 return child->get()->tryOpenSubdir(path.slice(1, path.size()), mode); 1187 } else { 1188 return nullptr; 1189 } 1190 } 1191 } 1192 1193 Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override { 1194 if (path.size() == 0) { 1195 KJ_FAIL_REQUIRE("can't replace self") { break; } 1196 } else if (path.size() == 1) { 1197 // don't need lock just to read the clock ref 1198 return heap<ReplacerImpl<Directory>>(*this, path[0], 1199 newInMemoryDirectory(impl.getWithoutLock().clock), mode); 1200 } else { 1201 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { 1202 return child->get()->replaceSubdir(path.slice(1, path.size()), mode); 1203 } 1204 } 1205 return heap<BrokenReplacer<Directory>>(newInMemoryDirectory(impl.getWithoutLock().clock)); 1206 } 1207 1208 Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const override { 1209 if (path.size() == 0) { 1210 if (has(mode, WriteMode::MODIFY)) { 1211 KJ_FAIL_REQUIRE("not a file") { return nullptr; } 1212 } else if (has(mode, WriteMode::CREATE)) { 1213 return nullptr; // already exists (as a directory) 1214 } else { 1215 KJ_FAIL_REQUIRE("can't replace self") { return nullptr; } 1216 } 1217 } else if (path.size() == 1) { 1218 auto lock = impl.lockExclusive(); 1219 KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) { 1220 return asFile(lock, *entry, mode).map(newFileAppender); 1221 } else { 1222 return nullptr; 1223 } 1224 } else { 1225 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { 1226 return child->get()->tryAppendFile(path.slice(1, path.size()), mode); 1227 } else { 1228 return nullptr; 1229 } 1230 } 1231 } 1232 1233 bool trySymlink(PathPtr path, StringPtr content, WriteMode mode) const override { 1234 if (path.size() == 0) { 1235 if (has(mode, WriteMode::CREATE)) { 1236 return false; 1237 } else { 1238 KJ_FAIL_REQUIRE("can't replace self") { return false; } 1239 } 1240 } else if (path.size() == 1) { 1241 auto lock = impl.lockExclusive(); 1242 KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) { 1243 entry->init(SymlinkNode { lock->clock.now(), heapString(content) }); 1244 lock->modified(); 1245 return true; 1246 } else { 1247 return false; 1248 } 1249 } else { 1250 KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { 1251 return child->get()->trySymlink(path.slice(1, path.size()), content, mode); 1252 } else { 1253 KJ_FAIL_REQUIRE("couldn't create parent directory") { return false; } 1254 } 1255 } 1256 } 1257 1258 Own<const File> createTemporary() const override { 1259 // Don't need lock just to read the clock ref. 1260 return newInMemoryFile(impl.getWithoutLock().clock); 1261 } 1262 1263 bool tryTransfer(PathPtr toPath, WriteMode toMode, 1264 const Directory& fromDirectory, PathPtr fromPath, 1265 TransferMode mode) const override { 1266 if (toPath.size() == 0) { 1267 if (has(toMode, WriteMode::CREATE)) { 1268 return false; 1269 } else { 1270 KJ_FAIL_REQUIRE("can't replace self") { return false; } 1271 } 1272 } else if (toPath.size() == 1) { 1273 // tryTransferChild() needs to at least know the node type, so do an lstat. 1274 KJ_IF_MAYBE(meta, fromDirectory.tryLstat(fromPath)) { 1275 auto lock = impl.lockExclusive(); 1276 KJ_IF_MAYBE(entry, lock->openEntry(toPath[0], toMode)) { 1277 // Make sure if we just cerated a new entry, and we don't successfully transfer to it, we 1278 // remove the entry before returning. 1279 bool needRollback = entry->node == nullptr; 1280 KJ_DEFER(if (needRollback) { lock->entries.erase(toPath[0]); }); 1281 1282 if (lock->tryTransferChild(*entry, meta->type, meta->lastModified, meta->size, 1283 fromDirectory, fromPath, mode)) { 1284 lock->modified(); 1285 needRollback = false; 1286 return true; 1287 } else { 1288 KJ_FAIL_REQUIRE("InMemoryDirectory can't link an inode of this type", fromPath) { 1289 return false; 1290 } 1291 } 1292 } else { 1293 return false; 1294 } 1295 } else { 1296 return false; 1297 } 1298 } else { 1299 // TODO(someday): Ideally we wouldn't create parent directories if fromPath doesn't exist. 1300 // This requires a different approach to the code here, though. 1301 KJ_IF_MAYBE(child, tryGetParent(toPath[0], toMode)) { 1302 return child->get()->tryTransfer( 1303 toPath.slice(1, toPath.size()), toMode, fromDirectory, fromPath, mode); 1304 } else { 1305 return false; 1306 } 1307 } 1308 } 1309 1310 Maybe<bool> tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode, 1311 PathPtr fromPath, TransferMode mode) const override { 1312 if (fromPath.size() <= 1) { 1313 // If `fromPath` is in this directory (or *is* this directory) then we don't have any 1314 // optimizations. 1315 return nullptr; 1316 } 1317 1318 // `fromPath` is in a subdirectory. It could turn out that that subdirectory is not an 1319 // InMemoryDirectory and is instead something `toDirectory` is friendly with. So let's follow 1320 // the path. 1321 1322 KJ_IF_MAYBE(child, tryGetParent(fromPath[0], WriteMode::MODIFY)) { 1323 // OK, switch back to tryTransfer() but use the subdirectory. 1324 return toDirectory.tryTransfer(toPath, toMode, 1325 **child, fromPath.slice(1, fromPath.size()), mode); 1326 } else { 1327 // Hmm, doesn't exist. Fall back to standard path. 1328 return nullptr; 1329 } 1330 } 1331 1332 bool tryRemove(PathPtr path) const override { 1333 if (path.size() == 0) { 1334 KJ_FAIL_REQUIRE("can't remove self from self") { return false; } 1335 } else if (path.size() == 1) { 1336 auto lock = impl.lockExclusive(); 1337 auto iter = lock->entries.find(path[0]); 1338 if (iter == lock->entries.end()) { 1339 return false; 1340 } else { 1341 lock->entries.erase(iter); 1342 lock->modified(); 1343 return true; 1344 } 1345 } else { 1346 KJ_IF_MAYBE(child, tryGetParent(path[0], WriteMode::MODIFY)) { 1347 return child->get()->tryRemove(path.slice(1, path.size())); 1348 } else { 1349 return false; 1350 } 1351 } 1352 } 1353 1354 private: 1355 struct FileNode { 1356 Own<const File> file; 1357 }; 1358 struct DirectoryNode { 1359 Own<const Directory> directory; 1360 }; 1361 struct SymlinkNode { 1362 Date lastModified; 1363 String content; 1364 1365 Path parse() const { 1366 KJ_CONTEXT("parsing symlink", content); 1367 return Path::parse(content); 1368 } 1369 }; 1370 1371 struct EntryImpl { 1372 String name; 1373 OneOf<FileNode, DirectoryNode, SymlinkNode> node; 1374 1375 EntryImpl(String&& name): name(kj::mv(name)) {} 1376 1377 Own<const File> init(FileNode&& value) { 1378 return node.init<FileNode>(kj::mv(value)).file->clone(); 1379 } 1380 Own<const Directory> init(DirectoryNode&& value) { 1381 return node.init<DirectoryNode>(kj::mv(value)).directory->clone(); 1382 } 1383 void init(SymlinkNode&& value) { 1384 node.init<SymlinkNode>(kj::mv(value)); 1385 } 1386 bool init(OneOf<FileNode, DirectoryNode, SymlinkNode>&& value) { 1387 node = kj::mv(value); 1388 return node != nullptr; 1389 } 1390 1391 void set(Own<const File>&& value) { 1392 node.init<FileNode>(FileNode { kj::mv(value) }); 1393 } 1394 void set(Own<const Directory>&& value) { 1395 node.init<DirectoryNode>(DirectoryNode { kj::mv(value) }); 1396 } 1397 }; 1398 1399 template <typename T> 1400 class ReplacerImpl final: public Replacer<T> { 1401 public: 1402 ReplacerImpl(const InMemoryDirectory& directory, kj::StringPtr name, 1403 Own<const T> inner, WriteMode mode) 1404 : Replacer<T>(mode), directory(atomicAddRef(directory)), name(heapString(name)), 1405 inner(kj::mv(inner)) {} 1406 1407 const T& get() override { return *inner; } 1408 1409 bool tryCommit() override { 1410 KJ_REQUIRE(!committed, "commit() already called") { return true; } 1411 1412 auto lock = directory->impl.lockExclusive(); 1413 KJ_IF_MAYBE(entry, lock->openEntry(name, Replacer<T>::mode)) { 1414 entry->set(inner->clone()); 1415 lock->modified(); 1416 return true; 1417 } else { 1418 return false; 1419 } 1420 } 1421 1422 private: 1423 bool committed = false; 1424 Own<const InMemoryDirectory> directory; 1425 kj::String name; 1426 Own<const T> inner; 1427 }; 1428 1429 template <typename T> 1430 class BrokenReplacer final: public Replacer<T> { 1431 // For recovery path when exceptions are disabled. 1432 1433 public: 1434 BrokenReplacer(Own<const T> inner) 1435 : Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY), 1436 inner(kj::mv(inner)) {} 1437 1438 const T& get() override { return *inner; } 1439 bool tryCommit() override { return false; } 1440 1441 private: 1442 Own<const T> inner; 1443 }; 1444 1445 struct Impl { 1446 const Clock& clock; 1447 1448 std::map<StringPtr, EntryImpl> entries; 1449 // Note: If this changes to a non-sorted map, listNames() and listEntries() must be updated to 1450 // sort their results. 1451 1452 Date lastModified; 1453 1454 Impl(const Clock& clock): clock(clock), lastModified(clock.now()) {} 1455 1456 Maybe<EntryImpl&> openEntry(kj::StringPtr name, WriteMode mode) { 1457 // TODO(perf): We could avoid a copy if the entry exists, at the expense of a double-lookup 1458 // if it doesn't. Maybe a better map implementation will solve everything? 1459 return openEntry(heapString(name), mode); 1460 } 1461 1462 Maybe<EntryImpl&> openEntry(String&& name, WriteMode mode) { 1463 if (has(mode, WriteMode::CREATE)) { 1464 EntryImpl entry(kj::mv(name)); 1465 StringPtr nameRef = entry.name; 1466 auto insertResult = entries.insert(std::make_pair(nameRef, kj::mv(entry))); 1467 1468 if (!insertResult.second && !has(mode, WriteMode::MODIFY)) { 1469 // Entry already existed and MODIFY not specified. 1470 return nullptr; 1471 } 1472 1473 return insertResult.first->second; 1474 } else if (has(mode, WriteMode::MODIFY)) { 1475 return tryGetEntry(name); 1476 } else { 1477 // Neither CREATE nor MODIFY specified: precondition always fails. 1478 return nullptr; 1479 } 1480 } 1481 1482 kj::Maybe<const EntryImpl&> tryGetEntry(kj::StringPtr name) const { 1483 auto iter = entries.find(name); 1484 if (iter == entries.end()) { 1485 return nullptr; 1486 } else { 1487 return iter->second; 1488 } 1489 } 1490 1491 kj::Maybe<EntryImpl&> tryGetEntry(kj::StringPtr name) { 1492 auto iter = entries.find(name); 1493 if (iter == entries.end()) { 1494 return nullptr; 1495 } else { 1496 return iter->second; 1497 } 1498 } 1499 1500 void modified() { 1501 lastModified = clock.now(); 1502 } 1503 1504 bool tryTransferChild(EntryImpl& entry, const FsNode::Type type, kj::Maybe<Date> lastModified, 1505 kj::Maybe<uint64_t> size, const Directory& fromDirectory, 1506 PathPtr fromPath, TransferMode mode) { 1507 switch (type) { 1508 case FsNode::Type::FILE: 1509 KJ_IF_MAYBE(file, fromDirectory.tryOpenFile(fromPath, WriteMode::MODIFY)) { 1510 if (mode == TransferMode::COPY) { 1511 auto copy = newInMemoryFile(clock); 1512 copy->copy(0, **file, 0, size.orDefault(kj::maxValue)); 1513 entry.set(kj::mv(copy)); 1514 } else { 1515 if (mode == TransferMode::MOVE) { 1516 KJ_ASSERT(fromDirectory.tryRemove(fromPath), "couldn't move node", fromPath) { 1517 return false; 1518 } 1519 } 1520 entry.set(kj::mv(*file)); 1521 } 1522 return true; 1523 } else { 1524 KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) { 1525 return false; 1526 } 1527 } 1528 case FsNode::Type::DIRECTORY: 1529 KJ_IF_MAYBE(subdir, fromDirectory.tryOpenSubdir(fromPath, WriteMode::MODIFY)) { 1530 if (mode == TransferMode::COPY) { 1531 auto copy = atomicRefcounted<InMemoryDirectory>(clock); 1532 auto& cpim = copy->impl.getWithoutLock(); // safe because just-created 1533 for (auto& subEntry: subdir->get()->listEntries()) { 1534 EntryImpl newEntry(kj::mv(subEntry.name)); 1535 Path filename(newEntry.name); 1536 if (!cpim.tryTransferChild(newEntry, subEntry.type, nullptr, nullptr, **subdir, 1537 filename, TransferMode::COPY)) { 1538 KJ_LOG(ERROR, "couldn't copy node of type not supported by InMemoryDirectory", 1539 filename); 1540 } else { 1541 StringPtr nameRef = newEntry.name; 1542 cpim.entries.insert(std::make_pair(nameRef, kj::mv(newEntry))); 1543 } 1544 } 1545 entry.set(kj::mv(copy)); 1546 } else { 1547 if (mode == TransferMode::MOVE) { 1548 KJ_ASSERT(fromDirectory.tryRemove(fromPath), "couldn't move node", fromPath) { 1549 return false; 1550 } 1551 } 1552 entry.set(kj::mv(*subdir)); 1553 } 1554 return true; 1555 } else { 1556 KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) { 1557 return false; 1558 } 1559 } 1560 case FsNode::Type::SYMLINK: 1561 KJ_IF_MAYBE(content, fromDirectory.tryReadlink(fromPath)) { 1562 // Since symlinks are immutable, we can implement LINK the same as COPY. 1563 entry.init(SymlinkNode { lastModified.orDefault(clock.now()), kj::mv(*content) }); 1564 if (mode == TransferMode::MOVE) { 1565 KJ_ASSERT(fromDirectory.tryRemove(fromPath), "couldn't move node", fromPath) { 1566 return false; 1567 } 1568 } 1569 return true; 1570 } else { 1571 KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) { 1572 return false; 1573 } 1574 } 1575 default: 1576 return false; 1577 } 1578 } 1579 }; 1580 1581 kj::MutexGuarded<Impl> impl; 1582 1583 bool exists(kj::Locked<const Impl>& lock, const EntryImpl& entry) const { 1584 if (entry.node.is<SymlinkNode>()) { 1585 auto newPath = entry.node.get<SymlinkNode>().parse(); 1586 lock.release(); 1587 return exists(newPath); 1588 } else { 1589 return true; 1590 } 1591 } 1592 Maybe<Own<const ReadableFile>> asFile( 1593 kj::Locked<const Impl>& lock, const EntryImpl& entry) const { 1594 if (entry.node.is<FileNode>()) { 1595 return entry.node.get<FileNode>().file->clone(); 1596 } else if (entry.node.is<SymlinkNode>()) { 1597 auto newPath = entry.node.get<SymlinkNode>().parse(); 1598 lock.release(); 1599 return tryOpenFile(newPath); 1600 } else { 1601 KJ_FAIL_REQUIRE("not a file") { return nullptr; } 1602 } 1603 } 1604 Maybe<Own<const ReadableDirectory>> asDirectory( 1605 kj::Locked<const Impl>& lock, const EntryImpl& entry) const { 1606 if (entry.node.is<DirectoryNode>()) { 1607 return entry.node.get<DirectoryNode>().directory->clone(); 1608 } else if (entry.node.is<SymlinkNode>()) { 1609 auto newPath = entry.node.get<SymlinkNode>().parse(); 1610 lock.release(); 1611 return tryOpenSubdir(newPath); 1612 } else { 1613 KJ_FAIL_REQUIRE("not a directory") { return nullptr; } 1614 } 1615 } 1616 Maybe<String> asSymlink(kj::Locked<const Impl>& lock, const EntryImpl& entry) const { 1617 if (entry.node.is<SymlinkNode>()) { 1618 return heapString(entry.node.get<SymlinkNode>().content); 1619 } else { 1620 KJ_FAIL_REQUIRE("not a symlink") { return nullptr; } 1621 } 1622 } 1623 1624 Maybe<Own<const File>> asFile(kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const { 1625 if (entry.node.is<FileNode>()) { 1626 return entry.node.get<FileNode>().file->clone(); 1627 } else if (entry.node.is<SymlinkNode>()) { 1628 // CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the 1629 // target itself can still be created. 1630 auto newPath = entry.node.get<SymlinkNode>().parse(); 1631 lock.release(); 1632 return tryOpenFile(newPath, mode - WriteMode::CREATE_PARENT); 1633 } else if (entry.node == nullptr) { 1634 KJ_ASSERT(has(mode, WriteMode::CREATE)); 1635 lock->modified(); 1636 return entry.init(FileNode { newInMemoryFile(lock->clock) }); 1637 } else { 1638 KJ_FAIL_REQUIRE("not a file") { return nullptr; } 1639 } 1640 } 1641 Maybe<Own<const Directory>> asDirectory( 1642 kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const { 1643 if (entry.node.is<DirectoryNode>()) { 1644 return entry.node.get<DirectoryNode>().directory->clone(); 1645 } else if (entry.node.is<SymlinkNode>()) { 1646 // CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the 1647 // target itself can still be created. 1648 auto newPath = entry.node.get<SymlinkNode>().parse(); 1649 lock.release(); 1650 return tryOpenSubdir(newPath, mode - WriteMode::CREATE_PARENT); 1651 } else if (entry.node == nullptr) { 1652 KJ_ASSERT(has(mode, WriteMode::CREATE)); 1653 lock->modified(); 1654 return entry.init(DirectoryNode { newInMemoryDirectory(lock->clock) }); 1655 } else { 1656 KJ_FAIL_REQUIRE("not a directory") { return nullptr; } 1657 } 1658 } 1659 1660 kj::Maybe<Own<const ReadableDirectory>> tryGetParent(kj::StringPtr name) const { 1661 auto lock = impl.lockShared(); 1662 KJ_IF_MAYBE(entry, impl.lockShared()->tryGetEntry(name)) { 1663 return asDirectory(lock, *entry); 1664 } else { 1665 return nullptr; 1666 } 1667 } 1668 1669 kj::Maybe<Own<const Directory>> tryGetParent(kj::StringPtr name, WriteMode mode) const { 1670 // Get a directory which is a parent of the eventual target. If `mode` includes 1671 // WriteMode::CREATE_PARENTS, possibly create the parent directory. 1672 1673 auto lock = impl.lockExclusive(); 1674 1675 WriteMode parentMode = has(mode, WriteMode::CREATE) && has(mode, WriteMode::CREATE_PARENT) 1676 ? WriteMode::CREATE | WriteMode::MODIFY // create parent 1677 : WriteMode::MODIFY; // don't create parent 1678 1679 // Possibly create parent. 1680 KJ_IF_MAYBE(entry, lock->openEntry(name, parentMode)) { 1681 if (entry->node.is<DirectoryNode>()) { 1682 return entry->node.get<DirectoryNode>().directory->clone(); 1683 } else if (entry->node == nullptr) { 1684 lock->modified(); 1685 return entry->init(DirectoryNode { newInMemoryDirectory(lock->clock) }); 1686 } 1687 // Continue on. 1688 } 1689 1690 if (has(mode, WriteMode::CREATE)) { 1691 // CREATE is documented as returning null when the file already exists. In this case, the 1692 // file does NOT exist because the parent directory does not exist or is not a directory. 1693 KJ_FAIL_REQUIRE("parent is not a directory") { return nullptr; } 1694 } else { 1695 return nullptr; 1696 } 1697 } 1698 }; 1699 1700 // ----------------------------------------------------------------------------- 1701 1702 class AppendableFileImpl final: public AppendableFile { 1703 public: 1704 AppendableFileImpl(Own<const File>&& fileParam): file(kj::mv(fileParam)) {} 1705 1706 Own<const FsNode> cloneFsNode() const override { 1707 return heap<AppendableFileImpl>(file->clone()); 1708 } 1709 1710 Maybe<int> getFd() const override { 1711 return nullptr; 1712 } 1713 1714 Metadata stat() const override { 1715 return file->stat(); 1716 } 1717 1718 void sync() const override { file->sync(); } 1719 void datasync() const override { file->datasync(); } 1720 1721 void write(const void* buffer, size_t size) override { 1722 file->write(file->stat().size, arrayPtr(reinterpret_cast<const byte*>(buffer), size)); 1723 } 1724 1725 private: 1726 Own<const File> file; 1727 }; 1728 1729 } // namespace 1730 1731 // ----------------------------------------------------------------------------- 1732 1733 Own<File> newInMemoryFile(const Clock& clock) { 1734 return atomicRefcounted<InMemoryFile>(clock); 1735 } 1736 Own<Directory> newInMemoryDirectory(const Clock& clock) { 1737 return atomicRefcounted<InMemoryDirectory>(clock); 1738 } 1739 Own<AppendableFile> newFileAppender(Own<const File> inner) { 1740 return heap<AppendableFileImpl>(kj::mv(inner)); 1741 } 1742 1743 } // namespace kj