// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors // Licensed under the MIT License: // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include "filesystem.h" #include "vector.h" #include "debug.h" #include "one-of.h" #include "encoding.h" #include "refcount.h" #include "mutex.h" #include <map> namespace kj { Path::Path(StringPtr name): Path(heapString(name)) {} Path::Path(String&& name): parts(heapArray<String>(1)) { parts[0] = kj::mv(name); validatePart(parts[0]); } Path::Path(ArrayPtr<const StringPtr> parts) : Path(KJ_MAP(p, parts) { return heapString(p); }) {} Path::Path(Array<String> partsParam) : Path(kj::mv(partsParam), ALREADY_CHECKED) { for (auto& p: parts) { validatePart(p); } } Path PathPtr::clone() { return Path(KJ_MAP(p, parts) { return heapString(p); }, Path::ALREADY_CHECKED); } Path Path::parse(StringPtr path) { KJ_REQUIRE(!path.startsWith("/"), "expected a relative path, got absolute", path) { // When exceptions are disabled, go on -- the leading '/' will end up ignored. break; } return evalImpl(Vector<String>(countParts(path)), path); } Path Path::parseWin32Api(ArrayPtr<const wchar_t> text) { auto utf8 = decodeWideString(text); return evalWin32Impl(Vector<String>(countPartsWin32(utf8)), utf8, true); } Path PathPtr::append(Path&& suffix) const { auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size()); for (auto& p: parts) newParts.add(heapString(p)); for (auto& p: suffix.parts) newParts.add(kj::mv(p)); return Path(newParts.finish(), Path::ALREADY_CHECKED); } Path Path::append(Path&& suffix) && { auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size()); for (auto& p: parts) newParts.add(kj::mv(p)); for (auto& p: suffix.parts) newParts.add(kj::mv(p)); return Path(newParts.finish(), ALREADY_CHECKED); } Path PathPtr::append(PathPtr suffix) const { auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size()); for (auto& p: parts) newParts.add(heapString(p)); for (auto& p: suffix.parts) newParts.add(heapString(p)); return Path(newParts.finish(), Path::ALREADY_CHECKED); } Path Path::append(PathPtr suffix) && { auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size()); for (auto& p: parts) newParts.add(kj::mv(p)); for (auto& p: suffix.parts) newParts.add(heapString(p)); return Path(newParts.finish(), ALREADY_CHECKED); } Path PathPtr::eval(StringPtr pathText) const { if (pathText.startsWith("/")) { // Optimization: avoid copying parts that will just be dropped. return Path::evalImpl(Vector<String>(Path::countParts(pathText)), pathText); } else { Vector<String> newParts(parts.size() + Path::countParts(pathText)); for (auto& p: parts) newParts.add(heapString(p)); return Path::evalImpl(kj::mv(newParts), pathText); } } Path Path::eval(StringPtr pathText) && { if (pathText.startsWith("/")) { // Optimization: avoid copying parts that will just be dropped. return evalImpl(Vector<String>(countParts(pathText)), pathText); } else { Vector<String> newParts(parts.size() + countParts(pathText)); for (auto& p: parts) newParts.add(kj::mv(p)); return evalImpl(kj::mv(newParts), pathText); } } PathPtr PathPtr::basename() const { KJ_REQUIRE(parts.size() > 0, "root path has no basename"); return PathPtr(parts.slice(parts.size() - 1, parts.size())); } Path Path::basename() && { KJ_REQUIRE(parts.size() > 0, "root path has no basename"); auto newParts = kj::heapArrayBuilder<String>(1); newParts.add(kj::mv(parts[parts.size() - 1])); return Path(newParts.finish(), ALREADY_CHECKED); } PathPtr PathPtr::parent() const { KJ_REQUIRE(parts.size() > 0, "root path has no parent"); return PathPtr(parts.slice(0, parts.size() - 1)); } Path Path::parent() && { KJ_REQUIRE(parts.size() > 0, "root path has no parent"); return Path(KJ_MAP(p, parts.slice(0, parts.size() - 1)) { return kj::mv(p); }, ALREADY_CHECKED); } String PathPtr::toString(bool absolute) const { if (parts.size() == 0) { // Special-case empty path. return absolute ? kj::str("/") : kj::str("."); } size_t size = absolute + (parts.size() - 1); for (auto& p: parts) size += p.size(); String result = kj::heapString(size); char* ptr = result.begin(); bool leadingSlash = absolute; for (auto& p: parts) { if (leadingSlash) *ptr++ = '/'; leadingSlash = true; memcpy(ptr, p.begin(), p.size()); ptr += p.size(); } KJ_ASSERT(ptr == result.end()); return result; } Path Path::slice(size_t start, size_t end) && { return Path(KJ_MAP(p, parts.slice(start, end)) { return kj::mv(p); }); } bool PathPtr::operator==(PathPtr other) const { return parts == other.parts; } bool PathPtr::operator< (PathPtr other) const { for (size_t i = 0; i < kj::min(parts.size(), other.parts.size()); i++) { int comp = strcmp(parts[i].cStr(), other.parts[i].cStr()); if (comp < 0) return true; if (comp > 0) return false; } return parts.size() < other.parts.size(); } bool PathPtr::startsWith(PathPtr prefix) const { return parts.size() >= prefix.parts.size() && parts.slice(0, prefix.parts.size()) == prefix.parts; } bool PathPtr::endsWith(PathPtr suffix) const { return parts.size() >= suffix.parts.size() && parts.slice(parts.size() - suffix.parts.size(), parts.size()) == suffix.parts; } Path PathPtr::evalWin32(StringPtr pathText) const { Vector<String> newParts(parts.size() + Path::countPartsWin32(pathText)); for (auto& p: parts) newParts.add(heapString(p)); return Path::evalWin32Impl(kj::mv(newParts), pathText); } Path Path::evalWin32(StringPtr pathText) && { Vector<String> newParts(parts.size() + countPartsWin32(pathText)); for (auto& p: parts) newParts.add(kj::mv(p)); return evalWin32Impl(kj::mv(newParts), pathText); } String PathPtr::toWin32StringImpl(bool absolute, bool forApi) const { if (parts.size() == 0) { // Special-case empty path. KJ_REQUIRE(!absolute, "absolute path is missing disk designator") { break; } return absolute ? kj::str("\\\\") : kj::str("."); } bool isUncPath = false; if (absolute) { if (Path::isWin32Drive(parts[0])) { // It's a win32 drive } else if (Path::isNetbiosName(parts[0])) { isUncPath = true; } else { KJ_FAIL_REQUIRE("absolute win32 path must start with drive letter or netbios host name", parts[0]); } } else { // Currently we do nothing differently in the forApi case for relative paths. forApi = false; } size_t size = forApi ? (isUncPath ? 8 : 4) + (parts.size() - 1) : (isUncPath ? 2 : 0) + (parts.size() - 1); for (auto& p: parts) size += p.size(); String result = heapString(size); char* ptr = result.begin(); if (forApi) { *ptr++ = '\\'; *ptr++ = '\\'; *ptr++ = '?'; *ptr++ = '\\'; if (isUncPath) { *ptr++ = 'U'; *ptr++ = 'N'; *ptr++ = 'C'; *ptr++ = '\\'; } } else { if (isUncPath) { *ptr++ = '\\'; *ptr++ = '\\'; } } bool leadingSlash = false; for (auto& p: parts) { if (leadingSlash) *ptr++ = '\\'; leadingSlash = true; KJ_REQUIRE(!Path::isWin32Special(p), "path cannot contain DOS reserved name", p) { // Recover by blotting out the name with invalid characters which Win32 syscalls will reject. for (size_t i = 0; i < p.size(); i++) { *ptr++ = '|'; } goto skip; } memcpy(ptr, p.begin(), p.size()); ptr += p.size(); skip:; } KJ_ASSERT(ptr == result.end()); // Check for colons (other than in drive letter), which on NTFS would be interpreted as an // "alternate data stream", which can lead to surprising results. If we want to support ADS, we // should do so using an explicit API. Note that this check also prevents a relative path from // appearing to start with a drive letter. for (size_t i: kj::indices(result)) { if (result[i] == ':') { if (absolute && i == (forApi ? 5 : 1)) { // False alarm: this is the drive letter. } else { KJ_FAIL_REQUIRE( "colons are prohibited in win32 paths to avoid triggering alterante data streams", result) { // Recover by using a different character which we know Win32 syscalls will reject. result[i] = '|'; break; } } } } return result; } Array<wchar_t> PathPtr::forWin32Api(bool absolute) const { return encodeWideString(toWin32StringImpl(absolute, true), true); } // ----------------------------------------------------------------------------- String Path::stripNul(String input) { kj::Vector<char> output(input.size()); for (char c: input) { if (c != '\0') output.add(c); } output.add('\0'); return String(output.releaseAsArray()); } void Path::validatePart(StringPtr part) { KJ_REQUIRE(part != "" && part != "." && part != "..", "invalid path component", part); KJ_REQUIRE(strlen(part.begin()) == part.size(), "NUL character in path component", part); KJ_REQUIRE(part.findFirst('/') == nullptr, "'/' character in path component; did you mean to use Path::parse()?", part); } void Path::evalPart(Vector<String>& parts, ArrayPtr<const char> part) { if (part.size() == 0) { // Ignore consecutive or trailing '/'s. } else if (part.size() == 1 && part[0] == '.') { // Refers to current directory; ignore. } else if (part.size() == 2 && part[0] == '.' && part [1] == '.') { KJ_REQUIRE(parts.size() > 0, "can't use \"..\" to break out of starting directory") { // When exceptions are disabled, ignore. return; } parts.removeLast(); } else { auto str = heapString(part); KJ_REQUIRE(strlen(str.begin()) == str.size(), "NUL character in path component", str) { // When exceptions are disabled, strip out '\0' chars. str = stripNul(kj::mv(str)); break; } parts.add(kj::mv(str)); } } Path Path::evalImpl(Vector<String>&& parts, StringPtr path) { if (path.startsWith("/")) { parts.clear(); } size_t partStart = 0; for (auto i: kj::indices(path)) { if (path[i] == '/') { evalPart(parts, path.slice(partStart, i)); partStart = i + 1; } } evalPart(parts, path.slice(partStart)); return Path(parts.releaseAsArray(), Path::ALREADY_CHECKED); } Path Path::evalWin32Impl(Vector<String>&& parts, StringPtr path, bool fromApi) { // Convert all forward slashes to backslashes. String ownPath; if (!fromApi && path.findFirst('/') != nullptr) { ownPath = heapString(path); for (char& c: ownPath) { if (c == '/') c = '\\'; } path = ownPath; } // Interpret various forms of absolute paths. if (fromApi && path.startsWith("\\\\?\\")) { path = path.slice(4); if (path.startsWith("UNC\\")) { path = path.slice(4); } // The path is absolute. parts.clear(); } else if (path.startsWith("\\\\")) { // UNC path. path = path.slice(2); // This path is absolute. The first component is a server name. parts.clear(); } else if (path.startsWith("\\")) { KJ_REQUIRE(!fromApi, "parseWin32Api() requires absolute path"); // Path is relative to the current drive / network share. if (parts.size() >= 1 && isWin32Drive(parts[0])) { // Leading \ interpreted as root of current drive. parts.truncate(1); } else if (parts.size() >= 2) { // Leading \ interpreted as root of current network share (which is indicated by the first // *two* components of the path). parts.truncate(2); } else { KJ_FAIL_REQUIRE("must specify drive letter", path) { // Recover by assuming C drive. parts.clear(); parts.add(kj::str("c:")); break; } } } else if ((path.size() == 2 || (path.size() > 2 && path[2] == '\\')) && isWin32Drive(path.slice(0, 2))) { // Starts with a drive letter. parts.clear(); } else { KJ_REQUIRE(!fromApi, "parseWin32Api() requires absolute path"); } size_t partStart = 0; for (auto i: kj::indices(path)) { if (path[i] == '\\') { evalPart(parts, path.slice(partStart, i)); partStart = i + 1; } } evalPart(parts, path.slice(partStart)); return Path(parts.releaseAsArray(), Path::ALREADY_CHECKED); } size_t Path::countParts(StringPtr path) { size_t result = 1; for (char c: path) { result += (c == '/'); } return result; } size_t Path::countPartsWin32(StringPtr path) { size_t result = 1; for (char c: path) { result += (c == '/' || c == '\\'); } return result; } bool Path::isWin32Drive(ArrayPtr<const char> part) { return part.size() == 2 && part[1] == ':' && (('a' <= part[0] && part[0] <= 'z') || ('A' <= part[0] && part[0] <= 'Z')); } bool Path::isNetbiosName(ArrayPtr<const char> part) { // Characters must be alphanumeric or '.' or '-'. for (char c: part) { if (c != '.' && c != '-' && (c < 'a' || 'z' < c) && (c < 'A' || 'Z' < c) && (c < '0' || '9' < c)) { return false; } } // Can't be empty nor start or end with a '.' or a '-'. return part.size() > 0 && part[0] != '.' && part[0] != '-' && part[part.size() - 1] != '.' && part[part.size() - 1] != '-'; } bool Path::isWin32Special(StringPtr part) { bool isNumbered; if (part.size() == 3 || (part.size() > 3 && part[3] == '.')) { // Filename is three characters or three characters followed by an extension. isNumbered = false; } else if ((part.size() == 4 || (part.size() > 4 && part[4] == '.')) && '1' <= part[3] && part[3] <= '9') { // Filename is four characters or four characters followed by an extension, and the fourth // character is a nonzero digit. isNumbered = true; } else { return false; } // OK, this could be a Win32 special filename. We need to match the first three letters against // the list of specials, case-insensitively. char tmp[4]; memcpy(tmp, part.begin(), 3); tmp[3] = '\0'; for (char& c: tmp) { if ('A' <= c && c <= 'Z') { c += 'a' - 'A'; } } StringPtr str(tmp, 3); if (isNumbered) { // Specials that are followed by a digit. return str == "com" || str == "lpt"; } else { // Specials that are not followed by a digit. return str == "con" || str == "prn" || str == "aux" || str == "nul"; } } // ======================================================================================= String ReadableFile::readAllText() const { String result = heapString(stat().size); size_t n = read(0, result.asBytes()); if (n < result.size()) { // Apparently file was truncated concurrently. Reduce to new size to match. result = heapString(result.slice(0, n)); } return result; } Array<byte> ReadableFile::readAllBytes() const { Array<byte> result = heapArray<byte>(stat().size); size_t n = read(0, result.asBytes()); if (n < result.size()) { // Apparently file was truncated concurrently. Reduce to new size to match. result = heapArray(result.slice(0, n)); } return result; } void File::writeAll(ArrayPtr<const byte> bytes) const { truncate(0); write(0, bytes); } void File::writeAll(StringPtr text) const { writeAll(text.asBytes()); } size_t File::copy(uint64_t offset, const ReadableFile& from, uint64_t fromOffset, uint64_t size) const { byte buffer[8192]; size_t result = 0; while (size > 0) { size_t n = from.read(fromOffset, kj::arrayPtr(buffer, kj::min(sizeof(buffer), size))); write(offset, arrayPtr(buffer, n)); result += n; if (n < sizeof(buffer)) { // Either we copied the amount requested or we hit EOF. break; } fromOffset += n; offset += n; size -= n; } return result; } FsNode::Metadata ReadableDirectory::lstat(PathPtr path) const { KJ_IF_MAYBE(meta, tryLstat(path)) { return *meta; } else { KJ_FAIL_REQUIRE("no such file", path) { break; } return FsNode::Metadata(); } } Own<const ReadableFile> ReadableDirectory::openFile(PathPtr path) const { KJ_IF_MAYBE(file, tryOpenFile(path)) { return kj::mv(*file); } else { KJ_FAIL_REQUIRE("no such directory", path) { break; } return newInMemoryFile(nullClock()); } } Own<const ReadableDirectory> ReadableDirectory::openSubdir(PathPtr path) const { KJ_IF_MAYBE(dir, tryOpenSubdir(path)) { return kj::mv(*dir); } else { KJ_FAIL_REQUIRE("no such file or directory", path) { break; } return newInMemoryDirectory(nullClock()); } } String ReadableDirectory::readlink(PathPtr path) const { KJ_IF_MAYBE(p, tryReadlink(path)) { return kj::mv(*p); } else { KJ_FAIL_REQUIRE("not a symlink", path) { break; } return kj::str("."); } } Own<const File> Directory::openFile(PathPtr path, WriteMode mode) const { KJ_IF_MAYBE(f, tryOpenFile(path, mode)) { return kj::mv(*f); } else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) { KJ_FAIL_REQUIRE("file already exists", path) { break; } } else if (has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) { KJ_FAIL_REQUIRE("file does not exist", path) { break; } } else if (!has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) { KJ_FAIL_ASSERT("neither WriteMode::CREATE nor WriteMode::MODIFY was given", path) { break; } } else { // Shouldn't happen. KJ_FAIL_ASSERT("tryOpenFile() returned null despite no preconditions", path) { break; } } return newInMemoryFile(nullClock()); } Own<AppendableFile> Directory::appendFile(PathPtr path, WriteMode mode) const { KJ_IF_MAYBE(f, tryAppendFile(path, mode)) { return kj::mv(*f); } else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) { KJ_FAIL_REQUIRE("file already exists", path) { break; } } else if (has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) { KJ_FAIL_REQUIRE("file does not exist", path) { break; } } else if (!has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) { KJ_FAIL_ASSERT("neither WriteMode::CREATE nor WriteMode::MODIFY was given", path) { break; } } else { // Shouldn't happen. KJ_FAIL_ASSERT("tryAppendFile() returned null despite no preconditions", path) { break; } } return newFileAppender(newInMemoryFile(nullClock())); } Own<const Directory> Directory::openSubdir(PathPtr path, WriteMode mode) const { KJ_IF_MAYBE(f, tryOpenSubdir(path, mode)) { return kj::mv(*f); } else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) { KJ_FAIL_REQUIRE("directory already exists", path) { break; } } else if (has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) { KJ_FAIL_REQUIRE("directory does not exist", path) { break; } } else if (!has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) { KJ_FAIL_ASSERT("neither WriteMode::CREATE nor WriteMode::MODIFY was given", path) { break; } } else { // Shouldn't happen. KJ_FAIL_ASSERT("tryOpenSubdir() returned null despite no preconditions", path) { break; } } return newInMemoryDirectory(nullClock()); } void Directory::symlink(PathPtr linkpath, StringPtr content, WriteMode mode) const { if (!trySymlink(linkpath, content, mode)) { if (has(mode, WriteMode::CREATE)) { KJ_FAIL_REQUIRE("path already exsits", linkpath) { break; } } else { // Shouldn't happen. KJ_FAIL_ASSERT("symlink() returned null despite no preconditions", linkpath) { break; } } } } void Directory::transfer(PathPtr toPath, WriteMode toMode, const Directory& fromDirectory, PathPtr fromPath, TransferMode mode) const { if (!tryTransfer(toPath, toMode, fromDirectory, fromPath, mode)) { if (has(toMode, WriteMode::CREATE)) { KJ_FAIL_REQUIRE("toPath already exists or fromPath doesn't exist", toPath, fromPath) { break; } } else { KJ_FAIL_ASSERT("fromPath doesn't exist", fromPath) { break; } } } } static void copyContents(const Directory& to, const ReadableDirectory& from); static bool tryCopyDirectoryEntry(const Directory& to, PathPtr toPath, WriteMode toMode, const ReadableDirectory& from, PathPtr fromPath, FsNode::Type type, bool atomic) { // TODO(cleanup): Make this reusable? switch (type) { case FsNode::Type::FILE: { KJ_IF_MAYBE(fromFile, from.tryOpenFile(fromPath)) { if (atomic) { auto replacer = to.replaceFile(toPath, toMode); replacer->get().copy(0, **fromFile, 0, kj::maxValue); return replacer->tryCommit(); } else KJ_IF_MAYBE(toFile, to.tryOpenFile(toPath, toMode)) { toFile->get()->copy(0, **fromFile, 0, kj::maxValue); return true; } else { return false; } } else { // Apparently disappeared. Treat as source-doesn't-exist. return false; } } case FsNode::Type::DIRECTORY: KJ_IF_MAYBE(fromSubdir, from.tryOpenSubdir(fromPath)) { if (atomic) { auto replacer = to.replaceSubdir(toPath, toMode); copyContents(replacer->get(), **fromSubdir); return replacer->tryCommit(); } else KJ_IF_MAYBE(toSubdir, to.tryOpenSubdir(toPath, toMode)) { copyContents(**toSubdir, **fromSubdir); return true; } else { return false; } } else { // Apparently disappeared. Treat as source-doesn't-exist. return false; } case FsNode::Type::SYMLINK: KJ_IF_MAYBE(content, from.tryReadlink(fromPath)) { return to.trySymlink(toPath, *content, toMode); } else { // Apparently disappeared. Treat as source-doesn't-exist. return false; } break; default: // Note: Unclear whether it's better to throw an error here or just ignore it / log a // warning. Can reconsider when we see an actual use case. KJ_FAIL_REQUIRE("can only copy files, directories, and symlinks", fromPath) { return false; } } } static void copyContents(const Directory& to, const ReadableDirectory& from) { for (auto& entry: from.listEntries()) { Path subPath(kj::mv(entry.name)); tryCopyDirectoryEntry(to, subPath, WriteMode::CREATE, from, subPath, entry.type, false); } } bool Directory::tryTransfer(PathPtr toPath, WriteMode toMode, const Directory& fromDirectory, PathPtr fromPath, TransferMode mode) const { KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; } // First try reversing. KJ_IF_MAYBE(result, fromDirectory.tryTransferTo(*this, toPath, toMode, fromPath, mode)) { return *result; } switch (mode) { case TransferMode::COPY: KJ_IF_MAYBE(meta, fromDirectory.tryLstat(fromPath)) { return tryCopyDirectoryEntry(*this, toPath, toMode, fromDirectory, fromPath, meta->type, true); } else { // Source doesn't exist. return false; } case TransferMode::MOVE: // Implement move as copy-then-delete. if (!tryTransfer(toPath, toMode, fromDirectory, fromPath, TransferMode::COPY)) { return false; } fromDirectory.remove(fromPath); return true; case TransferMode::LINK: KJ_FAIL_REQUIRE("can't link across different Directory implementations") { return false; } } KJ_UNREACHABLE; } Maybe<bool> Directory::tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode, PathPtr fromPath, TransferMode mode) const { return nullptr; } void Directory::remove(PathPtr path) const { if (!tryRemove(path)) { KJ_FAIL_REQUIRE("path to remove doesn't exist", path) { break; } } } void Directory::commitFailed(WriteMode mode) { if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) { KJ_FAIL_REQUIRE("replace target already exists") { break; } } else if (has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) { KJ_FAIL_REQUIRE("replace target does not exist") { break; } } else if (!has(mode, WriteMode::MODIFY) && !has(mode, WriteMode::CREATE)) { KJ_FAIL_ASSERT("neither WriteMode::CREATE nor WriteMode::MODIFY was given") { break; } } else { KJ_FAIL_ASSERT("tryCommit() returned null despite no preconditions") { break; } } } // ======================================================================================= namespace { class InMemoryFile final: public File, public AtomicRefcounted { public: InMemoryFile(const Clock& clock): impl(clock) {} Own<const FsNode> cloneFsNode() const override { return atomicAddRef(*this); } Maybe<int> getFd() const override { return nullptr; } Metadata stat() const override { auto lock = impl.lockShared(); uint64_t hash = reinterpret_cast<uintptr_t>(this); return Metadata { Type::FILE, lock->size, lock->size, lock->lastModified, 1, hash }; } void sync() const override {} void datasync() const override {} // no-ops size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override { auto lock = impl.lockShared(); if (offset >= lock->size) { // Entirely out-of-range. return 0; } size_t readSize = kj::min(buffer.size(), lock->size - offset); memcpy(buffer.begin(), lock->bytes.begin() + offset, readSize); return readSize; } Array<const byte> mmap(uint64_t offset, uint64_t size) const override { KJ_REQUIRE(offset + size >= offset, "mmap() request overflows uint64"); auto lock = impl.lockExclusive(); lock->ensureCapacity(offset + size); ArrayDisposer* disposer = new MmapDisposer(atomicAddRef(*this)); return Array<const byte>(lock->bytes.begin() + offset, size, *disposer); } Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override { // Return a copy. // Allocate exactly the size requested. auto result = heapArray<byte>(size); // Use read() to fill it. size_t actual = read(offset, result); // Ignore the rest. if (actual < size) { memset(result.begin() + actual, 0, size - actual); } return result; } void write(uint64_t offset, ArrayPtr<const byte> data) const override { if (data.size() == 0) return; auto lock = impl.lockExclusive(); lock->modified(); uint64_t end = offset + data.size(); KJ_REQUIRE(end >= offset, "write() request overflows uint64"); lock->ensureCapacity(end); lock->size = kj::max(lock->size, end); memcpy(lock->bytes.begin() + offset, data.begin(), data.size()); } void zero(uint64_t offset, uint64_t zeroSize) const override { if (zeroSize == 0) return; auto lock = impl.lockExclusive(); lock->modified(); uint64_t end = offset + zeroSize; KJ_REQUIRE(end >= offset, "zero() request overflows uint64"); lock->ensureCapacity(end); lock->size = kj::max(lock->size, end); memset(lock->bytes.begin() + offset, 0, zeroSize); } void truncate(uint64_t newSize) const override { auto lock = impl.lockExclusive(); if (newSize < lock->size) { lock->modified(); memset(lock->bytes.begin() + newSize, 0, lock->size - newSize); lock->size = newSize; } else if (newSize > lock->size) { lock->modified(); lock->ensureCapacity(newSize); lock->size = newSize; } } Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const override { uint64_t end = offset + size; KJ_REQUIRE(end >= offset, "mmapWritable() request overflows uint64"); auto lock = impl.lockExclusive(); lock->ensureCapacity(end); return heap<WritableFileMappingImpl>(atomicAddRef(*this), lock->bytes.slice(offset, end)); } size_t copy(uint64_t offset, const ReadableFile& from, uint64_t fromOffset, uint64_t copySize) const override { size_t fromFileSize = from.stat().size; if (fromFileSize <= fromOffset) return 0; // Clamp size to EOF. copySize = kj::min(copySize, fromFileSize - fromOffset); if (copySize == 0) return 0; auto lock = impl.lockExclusive(); // Allocate space for the copy. uint64_t end = offset + copySize; lock->ensureCapacity(end); // Read directly into our backing store. size_t n = from.read(fromOffset, lock->bytes.slice(offset, end)); lock->size = kj::max(lock->size, offset + n); lock->modified(); return n; } private: struct Impl { const Clock& clock; Array<byte> bytes; size_t size = 0; // bytes may be larger than this to accommodate mmaps Date lastModified; uint mmapCount = 0; // number of mappings outstanding Impl(const Clock& clock): clock(clock), lastModified(clock.now()) {} void ensureCapacity(size_t capacity) { if (bytes.size() < capacity) { KJ_ASSERT(mmapCount == 0, "InMemoryFile cannot resize the file backing store while memory mappings exist."); auto newBytes = heapArray<byte>(kj::max(capacity, bytes.size() * 2)); memcpy(newBytes.begin(), bytes.begin(), size); memset(newBytes.begin() + size, 0, newBytes.size() - size); bytes = kj::mv(newBytes); } } void modified() { lastModified = clock.now(); } }; kj::MutexGuarded<Impl> impl; class MmapDisposer final: public ArrayDisposer { public: MmapDisposer(Own<const InMemoryFile>&& refParam): ref(kj::mv(refParam)) { ++ref->impl.getAlreadyLockedExclusive().mmapCount; } ~MmapDisposer() noexcept(false) { --ref->impl.lockExclusive()->mmapCount; } void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount, size_t capacity, void (*destroyElement)(void*)) const override { delete this; } private: Own<const InMemoryFile> ref; }; class WritableFileMappingImpl final: public WritableFileMapping { public: WritableFileMappingImpl(Own<const InMemoryFile>&& refParam, ArrayPtr<byte> range) : ref(kj::mv(refParam)), range(range) { ++ref->impl.getAlreadyLockedExclusive().mmapCount; } ~WritableFileMappingImpl() noexcept(false) { --ref->impl.lockExclusive()->mmapCount; } ArrayPtr<byte> get() const override { // const_cast OK because WritableFileMapping does indeed provide a writable view despite // being const itself. return arrayPtr(const_cast<byte*>(range.begin()), range.size()); } void changed(ArrayPtr<byte> slice) const override { ref->impl.lockExclusive()->modified(); } void sync(ArrayPtr<byte> slice) const override { ref->impl.lockExclusive()->modified(); } private: Own<const InMemoryFile> ref; ArrayPtr<byte> range; }; }; // ----------------------------------------------------------------------------- class InMemoryDirectory final: public Directory, public AtomicRefcounted { public: InMemoryDirectory(const Clock& clock): impl(clock) {} Own<const FsNode> cloneFsNode() const override { return atomicAddRef(*this); } Maybe<int> getFd() const override { return nullptr; } Metadata stat() const override { auto lock = impl.lockShared(); uint64_t hash = reinterpret_cast<uintptr_t>(this); return Metadata { Type::DIRECTORY, 0, 0, lock->lastModified, 1, hash }; } void sync() const override {} void datasync() const override {} // no-ops Array<String> listNames() const override { auto lock = impl.lockShared(); return KJ_MAP(e, lock->entries) { return heapString(e.first); }; } Array<Entry> listEntries() const override { auto lock = impl.lockShared(); return KJ_MAP(e, lock->entries) { FsNode::Type type; if (e.second.node.is<SymlinkNode>()) { type = FsNode::Type::SYMLINK; } else if (e.second.node.is<FileNode>()) { type = FsNode::Type::FILE; } else { KJ_ASSERT(e.second.node.is<DirectoryNode>()); type = FsNode::Type::DIRECTORY; } return Entry { type, heapString(e.first) }; }; } bool exists(PathPtr path) const override { if (path.size() == 0) { return true; } else if (path.size() == 1) { auto lock = impl.lockShared(); KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { return exists(lock, *entry); } else { return false; } } else { KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { return subdir->get()->exists(path.slice(1, path.size())); } else { return false; } } } Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override { if (path.size() == 0) { return stat(); } else if (path.size() == 1) { auto lock = impl.lockShared(); KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { if (entry->node.is<FileNode>()) { return entry->node.get<FileNode>().file->stat(); } else if (entry->node.is<DirectoryNode>()) { return entry->node.get<DirectoryNode>().directory->stat(); } else if (entry->node.is<SymlinkNode>()) { auto& link = entry->node.get<SymlinkNode>(); uint64_t hash = reinterpret_cast<uintptr_t>(link.content.begin()); return FsNode::Metadata { FsNode::Type::SYMLINK, 0, 0, link.lastModified, 1, hash }; } else { KJ_FAIL_ASSERT("unknown node type") { return nullptr; } } } else { return nullptr; } } else { KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { return subdir->get()->tryLstat(path.slice(1, path.size())); } else { return nullptr; } } } Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override { if (path.size() == 0) { KJ_FAIL_REQUIRE("not a file") { return nullptr; } } else if (path.size() == 1) { auto lock = impl.lockShared(); KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { return asFile(lock, *entry); } else { return nullptr; } } else { KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { return subdir->get()->tryOpenFile(path.slice(1, path.size())); } else { return nullptr; } } } Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override { if (path.size() == 0) { return clone(); } else if (path.size() == 1) { auto lock = impl.lockShared(); KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { return asDirectory(lock, *entry); } else { return nullptr; } } else { KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { return subdir->get()->tryOpenSubdir(path.slice(1, path.size())); } else { return nullptr; } } } Maybe<String> tryReadlink(PathPtr path) const override { if (path.size() == 0) { KJ_FAIL_REQUIRE("not a symlink") { return nullptr; } } else if (path.size() == 1) { auto lock = impl.lockShared(); KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) { return asSymlink(lock, *entry); } else { return nullptr; } } else { KJ_IF_MAYBE(subdir, tryGetParent(path[0])) { return subdir->get()->tryReadlink(path.slice(1, path.size())); } else { return nullptr; } } } Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const override { if (path.size() == 0) { if (has(mode, WriteMode::MODIFY)) { KJ_FAIL_REQUIRE("not a file") { return nullptr; } } else if (has(mode, WriteMode::CREATE)) { return nullptr; // already exists (as a directory) } else { KJ_FAIL_REQUIRE("can't replace self") { return nullptr; } } } else if (path.size() == 1) { auto lock = impl.lockExclusive(); KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) { return asFile(lock, *entry, mode); } else { return nullptr; } } else { KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { return child->get()->tryOpenFile(path.slice(1, path.size()), mode); } else { return nullptr; } } } Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override { if (path.size() == 0) { KJ_FAIL_REQUIRE("can't replace self") { break; } } else if (path.size() == 1) { // don't need lock just to read the clock ref return heap<ReplacerImpl<File>>(*this, path[0], newInMemoryFile(impl.getWithoutLock().clock), mode); } else { KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { return child->get()->replaceFile(path.slice(1, path.size()), mode); } } return heap<BrokenReplacer<File>>(newInMemoryFile(impl.getWithoutLock().clock)); } Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const override { if (path.size() == 0) { if (has(mode, WriteMode::MODIFY)) { return atomicAddRef(*this); } else if (has(mode, WriteMode::CREATE)) { return nullptr; // already exists } else { KJ_FAIL_REQUIRE("can't replace self") { return nullptr; } } } else if (path.size() == 1) { auto lock = impl.lockExclusive(); KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) { return asDirectory(lock, *entry, mode); } else { return nullptr; } } else { KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { return child->get()->tryOpenSubdir(path.slice(1, path.size()), mode); } else { return nullptr; } } } Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override { if (path.size() == 0) { KJ_FAIL_REQUIRE("can't replace self") { break; } } else if (path.size() == 1) { // don't need lock just to read the clock ref return heap<ReplacerImpl<Directory>>(*this, path[0], newInMemoryDirectory(impl.getWithoutLock().clock), mode); } else { KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { return child->get()->replaceSubdir(path.slice(1, path.size()), mode); } } return heap<BrokenReplacer<Directory>>(newInMemoryDirectory(impl.getWithoutLock().clock)); } Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const override { if (path.size() == 0) { if (has(mode, WriteMode::MODIFY)) { KJ_FAIL_REQUIRE("not a file") { return nullptr; } } else if (has(mode, WriteMode::CREATE)) { return nullptr; // already exists (as a directory) } else { KJ_FAIL_REQUIRE("can't replace self") { return nullptr; } } } else if (path.size() == 1) { auto lock = impl.lockExclusive(); KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) { return asFile(lock, *entry, mode).map(newFileAppender); } else { return nullptr; } } else { KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { return child->get()->tryAppendFile(path.slice(1, path.size()), mode); } else { return nullptr; } } } bool trySymlink(PathPtr path, StringPtr content, WriteMode mode) const override { if (path.size() == 0) { if (has(mode, WriteMode::CREATE)) { return false; } else { KJ_FAIL_REQUIRE("can't replace self") { return false; } } } else if (path.size() == 1) { auto lock = impl.lockExclusive(); KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) { entry->init(SymlinkNode { lock->clock.now(), heapString(content) }); lock->modified(); return true; } else { return false; } } else { KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { return child->get()->trySymlink(path.slice(1, path.size()), content, mode); } else { KJ_FAIL_REQUIRE("couldn't create parent directory") { return false; } } } } Own<const File> createTemporary() const override { // Don't need lock just to read the clock ref. return newInMemoryFile(impl.getWithoutLock().clock); } bool tryTransfer(PathPtr toPath, WriteMode toMode, const Directory& fromDirectory, PathPtr fromPath, TransferMode mode) const override { if (toPath.size() == 0) { if (has(toMode, WriteMode::CREATE)) { return false; } else { KJ_FAIL_REQUIRE("can't replace self") { return false; } } } else if (toPath.size() == 1) { // tryTransferChild() needs to at least know the node type, so do an lstat. KJ_IF_MAYBE(meta, fromDirectory.tryLstat(fromPath)) { auto lock = impl.lockExclusive(); KJ_IF_MAYBE(entry, lock->openEntry(toPath[0], toMode)) { // Make sure if we just cerated a new entry, and we don't successfully transfer to it, we // remove the entry before returning. bool needRollback = entry->node == nullptr; KJ_DEFER(if (needRollback) { lock->entries.erase(toPath[0]); }); if (lock->tryTransferChild(*entry, meta->type, meta->lastModified, meta->size, fromDirectory, fromPath, mode)) { lock->modified(); needRollback = false; return true; } else { KJ_FAIL_REQUIRE("InMemoryDirectory can't link an inode of this type", fromPath) { return false; } } } else { return false; } } else { return false; } } else { // TODO(someday): Ideally we wouldn't create parent directories if fromPath doesn't exist. // This requires a different approach to the code here, though. KJ_IF_MAYBE(child, tryGetParent(toPath[0], toMode)) { return child->get()->tryTransfer( toPath.slice(1, toPath.size()), toMode, fromDirectory, fromPath, mode); } else { return false; } } } Maybe<bool> tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode, PathPtr fromPath, TransferMode mode) const override { if (fromPath.size() <= 1) { // If `fromPath` is in this directory (or *is* this directory) then we don't have any // optimizations. return nullptr; } // `fromPath` is in a subdirectory. It could turn out that that subdirectory is not an // InMemoryDirectory and is instead something `toDirectory` is friendly with. So let's follow // the path. KJ_IF_MAYBE(child, tryGetParent(fromPath[0], WriteMode::MODIFY)) { // OK, switch back to tryTransfer() but use the subdirectory. return toDirectory.tryTransfer(toPath, toMode, **child, fromPath.slice(1, fromPath.size()), mode); } else { // Hmm, doesn't exist. Fall back to standard path. return nullptr; } } bool tryRemove(PathPtr path) const override { if (path.size() == 0) { KJ_FAIL_REQUIRE("can't remove self from self") { return false; } } else if (path.size() == 1) { auto lock = impl.lockExclusive(); auto iter = lock->entries.find(path[0]); if (iter == lock->entries.end()) { return false; } else { lock->entries.erase(iter); lock->modified(); return true; } } else { KJ_IF_MAYBE(child, tryGetParent(path[0], WriteMode::MODIFY)) { return child->get()->tryRemove(path.slice(1, path.size())); } else { return false; } } } private: struct FileNode { Own<const File> file; }; struct DirectoryNode { Own<const Directory> directory; }; struct SymlinkNode { Date lastModified; String content; Path parse() const { KJ_CONTEXT("parsing symlink", content); return Path::parse(content); } }; struct EntryImpl { String name; OneOf<FileNode, DirectoryNode, SymlinkNode> node; EntryImpl(String&& name): name(kj::mv(name)) {} Own<const File> init(FileNode&& value) { return node.init<FileNode>(kj::mv(value)).file->clone(); } Own<const Directory> init(DirectoryNode&& value) { return node.init<DirectoryNode>(kj::mv(value)).directory->clone(); } void init(SymlinkNode&& value) { node.init<SymlinkNode>(kj::mv(value)); } bool init(OneOf<FileNode, DirectoryNode, SymlinkNode>&& value) { node = kj::mv(value); return node != nullptr; } void set(Own<const File>&& value) { node.init<FileNode>(FileNode { kj::mv(value) }); } void set(Own<const Directory>&& value) { node.init<DirectoryNode>(DirectoryNode { kj::mv(value) }); } }; template <typename T> class ReplacerImpl final: public Replacer<T> { public: ReplacerImpl(const InMemoryDirectory& directory, kj::StringPtr name, Own<const T> inner, WriteMode mode) : Replacer<T>(mode), directory(atomicAddRef(directory)), name(heapString(name)), inner(kj::mv(inner)) {} const T& get() override { return *inner; } bool tryCommit() override { KJ_REQUIRE(!committed, "commit() already called") { return true; } auto lock = directory->impl.lockExclusive(); KJ_IF_MAYBE(entry, lock->openEntry(name, Replacer<T>::mode)) { entry->set(inner->clone()); lock->modified(); return true; } else { return false; } } private: bool committed = false; Own<const InMemoryDirectory> directory; kj::String name; Own<const T> inner; }; template <typename T> class BrokenReplacer final: public Replacer<T> { // For recovery path when exceptions are disabled. public: BrokenReplacer(Own<const T> inner) : Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY), inner(kj::mv(inner)) {} const T& get() override { return *inner; } bool tryCommit() override { return false; } private: Own<const T> inner; }; struct Impl { const Clock& clock; std::map<StringPtr, EntryImpl> entries; // Note: If this changes to a non-sorted map, listNames() and listEntries() must be updated to // sort their results. Date lastModified; Impl(const Clock& clock): clock(clock), lastModified(clock.now()) {} Maybe<EntryImpl&> openEntry(kj::StringPtr name, WriteMode mode) { // TODO(perf): We could avoid a copy if the entry exists, at the expense of a double-lookup // if it doesn't. Maybe a better map implementation will solve everything? return openEntry(heapString(name), mode); } Maybe<EntryImpl&> openEntry(String&& name, WriteMode mode) { if (has(mode, WriteMode::CREATE)) { EntryImpl entry(kj::mv(name)); StringPtr nameRef = entry.name; auto insertResult = entries.insert(std::make_pair(nameRef, kj::mv(entry))); if (!insertResult.second && !has(mode, WriteMode::MODIFY)) { // Entry already existed and MODIFY not specified. return nullptr; } return insertResult.first->second; } else if (has(mode, WriteMode::MODIFY)) { return tryGetEntry(name); } else { // Neither CREATE nor MODIFY specified: precondition always fails. return nullptr; } } kj::Maybe<const EntryImpl&> tryGetEntry(kj::StringPtr name) const { auto iter = entries.find(name); if (iter == entries.end()) { return nullptr; } else { return iter->second; } } kj::Maybe<EntryImpl&> tryGetEntry(kj::StringPtr name) { auto iter = entries.find(name); if (iter == entries.end()) { return nullptr; } else { return iter->second; } } void modified() { lastModified = clock.now(); } bool tryTransferChild(EntryImpl& entry, const FsNode::Type type, kj::Maybe<Date> lastModified, kj::Maybe<uint64_t> size, const Directory& fromDirectory, PathPtr fromPath, TransferMode mode) { switch (type) { case FsNode::Type::FILE: KJ_IF_MAYBE(file, fromDirectory.tryOpenFile(fromPath, WriteMode::MODIFY)) { if (mode == TransferMode::COPY) { auto copy = newInMemoryFile(clock); copy->copy(0, **file, 0, size.orDefault(kj::maxValue)); entry.set(kj::mv(copy)); } else { if (mode == TransferMode::MOVE) { KJ_ASSERT(fromDirectory.tryRemove(fromPath), "could't move node", fromPath) { return false; } } entry.set(kj::mv(*file)); } return true; } else { KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) { return false; } } case FsNode::Type::DIRECTORY: KJ_IF_MAYBE(subdir, fromDirectory.tryOpenSubdir(fromPath, WriteMode::MODIFY)) { if (mode == TransferMode::COPY) { auto copy = atomicRefcounted<InMemoryDirectory>(clock); auto& cpim = copy->impl.getWithoutLock(); // safe because just-created for (auto& subEntry: subdir->get()->listEntries()) { EntryImpl newEntry(kj::mv(subEntry.name)); Path filename(newEntry.name); if (!cpim.tryTransferChild(newEntry, subEntry.type, nullptr, nullptr, **subdir, filename, TransferMode::COPY)) { KJ_LOG(ERROR, "couldn't copy node of type not supported by InMemoryDirectory", filename); } else { StringPtr nameRef = newEntry.name; cpim.entries.insert(std::make_pair(nameRef, kj::mv(newEntry))); } } entry.set(kj::mv(copy)); } else { if (mode == TransferMode::MOVE) { KJ_ASSERT(fromDirectory.tryRemove(fromPath), "could't move node", fromPath) { return false; } } entry.set(kj::mv(*subdir)); } return true; } else { KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) { return false; } } case FsNode::Type::SYMLINK: KJ_IF_MAYBE(content, fromDirectory.tryReadlink(fromPath)) { // Since symlinks are immutable, we can implement LINK the same as COPY. entry.init(SymlinkNode { lastModified.orDefault(clock.now()), kj::mv(*content) }); if (mode == TransferMode::MOVE) { KJ_ASSERT(fromDirectory.tryRemove(fromPath), "could't move node", fromPath) { return false; } } return true; } else { KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) { return false; } } default: return false; } } }; kj::MutexGuarded<Impl> impl; bool exists(kj::Locked<const Impl>& lock, const EntryImpl& entry) const { if (entry.node.is<SymlinkNode>()) { auto newPath = entry.node.get<SymlinkNode>().parse(); lock.release(); return exists(newPath); } else { return true; } } Maybe<Own<const ReadableFile>> asFile( kj::Locked<const Impl>& lock, const EntryImpl& entry) const { if (entry.node.is<FileNode>()) { return entry.node.get<FileNode>().file->clone(); } else if (entry.node.is<SymlinkNode>()) { auto newPath = entry.node.get<SymlinkNode>().parse(); lock.release(); return tryOpenFile(newPath); } else { KJ_FAIL_REQUIRE("not a file") { return nullptr; } } } Maybe<Own<const ReadableDirectory>> asDirectory( kj::Locked<const Impl>& lock, const EntryImpl& entry) const { if (entry.node.is<DirectoryNode>()) { return entry.node.get<DirectoryNode>().directory->clone(); } else if (entry.node.is<SymlinkNode>()) { auto newPath = entry.node.get<SymlinkNode>().parse(); lock.release(); return tryOpenSubdir(newPath); } else { KJ_FAIL_REQUIRE("not a directory") { return nullptr; } } } Maybe<String> asSymlink(kj::Locked<const Impl>& lock, const EntryImpl& entry) const { if (entry.node.is<SymlinkNode>()) { return heapString(entry.node.get<SymlinkNode>().content); } else { KJ_FAIL_REQUIRE("not a symlink") { return nullptr; } } } Maybe<Own<const File>> asFile(kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const { if (entry.node.is<FileNode>()) { return entry.node.get<FileNode>().file->clone(); } else if (entry.node.is<SymlinkNode>()) { // CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the // target itself can still be created. auto newPath = entry.node.get<SymlinkNode>().parse(); lock.release(); return tryOpenFile(newPath, mode - WriteMode::CREATE_PARENT); } else if (entry.node == nullptr) { KJ_ASSERT(has(mode, WriteMode::CREATE)); lock->modified(); return entry.init(FileNode { newInMemoryFile(lock->clock) }); } else { KJ_FAIL_REQUIRE("not a file") { return nullptr; } } } Maybe<Own<const Directory>> asDirectory( kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const { if (entry.node.is<DirectoryNode>()) { return entry.node.get<DirectoryNode>().directory->clone(); } else if (entry.node.is<SymlinkNode>()) { // CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the // target itself can still be created. auto newPath = entry.node.get<SymlinkNode>().parse(); lock.release(); return tryOpenSubdir(newPath, mode - WriteMode::CREATE_PARENT); } else if (entry.node == nullptr) { KJ_ASSERT(has(mode, WriteMode::CREATE)); lock->modified(); return entry.init(DirectoryNode { newInMemoryDirectory(lock->clock) }); } else { KJ_FAIL_REQUIRE("not a directory") { return nullptr; } } } kj::Maybe<Own<const ReadableDirectory>> tryGetParent(kj::StringPtr name) const { auto lock = impl.lockShared(); KJ_IF_MAYBE(entry, impl.lockShared()->tryGetEntry(name)) { return asDirectory(lock, *entry); } else { return nullptr; } } kj::Maybe<Own<const Directory>> tryGetParent(kj::StringPtr name, WriteMode mode) const { // Get a directory which is a parent of the eventual target. If `mode` includes // WriteMode::CREATE_PARENTS, possibly create the parent directory. auto lock = impl.lockExclusive(); WriteMode parentMode = has(mode, WriteMode::CREATE) && has(mode, WriteMode::CREATE_PARENT) ? WriteMode::CREATE | WriteMode::MODIFY // create parent : WriteMode::MODIFY; // don't create parent // Possibly create parent. KJ_IF_MAYBE(entry, lock->openEntry(name, parentMode)) { if (entry->node.is<DirectoryNode>()) { return entry->node.get<DirectoryNode>().directory->clone(); } else if (entry->node == nullptr) { lock->modified(); return entry->init(DirectoryNode { newInMemoryDirectory(lock->clock) }); } // Continue on. } if (has(mode, WriteMode::CREATE)) { // CREATE is documented as returning null when the file already exists. In this case, the // file does NOT exist because the parent directory does not exist or is not a directory. KJ_FAIL_REQUIRE("parent is not a directory") { return nullptr; } } else { return nullptr; } } }; // ----------------------------------------------------------------------------- class AppendableFileImpl final: public AppendableFile { public: AppendableFileImpl(Own<const File>&& fileParam): file(kj::mv(fileParam)) {} Own<const FsNode> cloneFsNode() const override { return heap<AppendableFileImpl>(file->clone()); } Maybe<int> getFd() const override { return nullptr; } Metadata stat() const override { return file->stat(); } void sync() const override { file->sync(); } void datasync() const override { file->datasync(); } void write(const void* buffer, size_t size) override { file->write(file->stat().size, arrayPtr(reinterpret_cast<const byte*>(buffer), size)); } private: Own<const File> file; }; } // namespace // ----------------------------------------------------------------------------- Own<File> newInMemoryFile(const Clock& clock) { return atomicRefcounted<InMemoryFile>(clock); } Own<Directory> newInMemoryDirectory(const Clock& clock) { return atomicRefcounted<InMemoryDirectory>(clock); } Own<AppendableFile> newFileAppender(Own<const File> inner) { return heap<AppendableFileImpl>(kj::mv(inner)); } } // namespace kj