Commit 9d2ead08 authored by Kenton Varda's avatar Kenton Varda

Convert KJ filesystem API to be entirely threadsafe.

The disk-backed implementations were already inherently threadsafe. Making this explicit allows callers to rely on it.

This is particularly useful for SchemaParser, which is intended to be threadsafe.
parent fc620c73
...@@ -261,7 +261,7 @@ public: ...@@ -261,7 +261,7 @@ public:
// OsHandle ------------------------------------------------------------------ // OsHandle ------------------------------------------------------------------
AutoCloseFd clone() { AutoCloseFd clone() const {
int fd2; int fd2;
#ifdef F_DUPFD_CLOEXEC #ifdef F_DUPFD_CLOEXEC
KJ_SYSCALL_HANDLE_ERRORS(fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 3)) { KJ_SYSCALL_HANDLE_ERRORS(fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 3)) {
...@@ -283,19 +283,19 @@ public: ...@@ -283,19 +283,19 @@ public:
return result; return result;
} }
int getFd() { int getFd() const {
return fd.get(); return fd.get();
} }
// FsNode -------------------------------------------------------------------- // FsNode --------------------------------------------------------------------
FsNode::Metadata stat() { FsNode::Metadata stat() const {
struct stat stats; struct stat stats;
KJ_SYSCALL(::fstat(fd, &stats)); KJ_SYSCALL(::fstat(fd, &stats));
return statToMetadata(stats); return statToMetadata(stats);
} }
void sync() { void sync() const {
#if __APPLE__ #if __APPLE__
// For whatever reason, fsync() on OSX only flushes kernel buffers. It does not flush hardware // For whatever reason, fsync() on OSX only flushes kernel buffers. It does not flush hardware
// disk buffers. This makes it not very useful. But OSX documents fcntl F_FULLFSYNC which does // disk buffers. This makes it not very useful. But OSX documents fcntl F_FULLFSYNC which does
...@@ -306,7 +306,7 @@ public: ...@@ -306,7 +306,7 @@ public:
#endif #endif
} }
void datasync() { void datasync() const {
// The presence of the _POSIX_SYNCHRONIZED_IO define is supposed to tell us that fdatasync() // The presence of the _POSIX_SYNCHRONIZED_IO define is supposed to tell us that fdatasync()
// exists. But Apple defines this yet doesn't offer fdatasync(). Thanks, Apple. // exists. But Apple defines this yet doesn't offer fdatasync(). Thanks, Apple.
#if _POSIX_SYNCHRONIZED_IO && !__APPLE__ #if _POSIX_SYNCHRONIZED_IO && !__APPLE__
...@@ -318,7 +318,7 @@ public: ...@@ -318,7 +318,7 @@ public:
// ReadableFile -------------------------------------------------------------- // ReadableFile --------------------------------------------------------------
size_t read(uint64_t offset, ArrayPtr<byte> buffer) { size_t read(uint64_t offset, ArrayPtr<byte> buffer) const {
// pread() probably never returns short reads unless it hits EOF. Unfortunately, though, per // pread() probably never returns short reads unless it hits EOF. Unfortunately, though, per
// spec we are not allowed to assume this. // spec we are not allowed to assume this.
...@@ -334,7 +334,7 @@ public: ...@@ -334,7 +334,7 @@ public:
return total; return total;
} }
Array<const byte> mmap(uint64_t offset, uint64_t size) { Array<const byte> mmap(uint64_t offset, uint64_t size) const {
auto range = getMmapRange(offset, size); auto range = getMmapRange(offset, size);
const void* mapping = ::mmap(NULL, range.size, PROT_READ, MAP_SHARED, fd, range.offset); const void* mapping = ::mmap(NULL, range.size, PROT_READ, MAP_SHARED, fd, range.offset);
if (mapping == MAP_FAILED) { if (mapping == MAP_FAILED) {
...@@ -344,7 +344,7 @@ public: ...@@ -344,7 +344,7 @@ public:
size, mmapDisposer); size, mmapDisposer);
} }
Array<byte> mmapPrivate(uint64_t offset, uint64_t size) { Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const {
auto range = getMmapRange(offset, size); auto range = getMmapRange(offset, size);
void* mapping = ::mmap(NULL, range.size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, range.offset); void* mapping = ::mmap(NULL, range.size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, range.offset);
if (mapping == MAP_FAILED) { if (mapping == MAP_FAILED) {
...@@ -356,7 +356,7 @@ public: ...@@ -356,7 +356,7 @@ public:
// File ---------------------------------------------------------------------- // File ----------------------------------------------------------------------
void write(uint64_t offset, ArrayPtr<const byte> data) { void write(uint64_t offset, ArrayPtr<const byte> data) const {
// pwrite() probably never returns short writes unless there's no space left on disk. // pwrite() probably never returns short writes unless there's no space left on disk.
// Unfortunately, though, per spec we are not allowed to assume this. // Unfortunately, though, per spec we are not allowed to assume this.
...@@ -369,7 +369,7 @@ public: ...@@ -369,7 +369,7 @@ public:
} }
} }
void zero(uint64_t offset, uint64_t size) { void zero(uint64_t offset, uint64_t size) const {
#ifdef FALLOC_FL_PUNCH_HOLE #ifdef FALLOC_FL_PUNCH_HOLE
KJ_SYSCALL_HANDLE_ERRORS( KJ_SYSCALL_HANDLE_ERRORS(
fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset, size)) { fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset, size)) {
...@@ -426,7 +426,7 @@ public: ...@@ -426,7 +426,7 @@ public:
#endif #endif
} }
void truncate(uint64_t size) { void truncate(uint64_t size) const {
KJ_SYSCALL(ftruncate(fd, size)); KJ_SYSCALL(ftruncate(fd, size));
} }
...@@ -434,11 +434,13 @@ public: ...@@ -434,11 +434,13 @@ public:
public: public:
WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {} WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {}
ArrayPtr<byte> get() override { ArrayPtr<byte> get() const override {
return bytes; // const_cast OK because WritableFileMapping does indeed provide a writable view despite
// being const itself.
return arrayPtr(const_cast<byte*>(bytes.begin()), bytes.size());
} }
void changed(ArrayPtr<byte> slice) override { void changed(ArrayPtr<byte> slice) const override {
KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(), KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(),
"byte range is not part of this mapping"); "byte range is not part of this mapping");
...@@ -447,7 +449,7 @@ public: ...@@ -447,7 +449,7 @@ public:
KJ_SYSCALL(msync(reinterpret_cast<void*>(range.offset), range.size, MS_ASYNC)); KJ_SYSCALL(msync(reinterpret_cast<void*>(range.offset), range.size, MS_ASYNC));
} }
void sync(ArrayPtr<byte> slice) override { void sync(ArrayPtr<byte> slice) const override {
KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(), KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(),
"byte range is not part of this mapping"); "byte range is not part of this mapping");
...@@ -460,7 +462,7 @@ public: ...@@ -460,7 +462,7 @@ public:
Array<byte> bytes; Array<byte> bytes;
}; };
Own<WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) { Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const {
auto range = getMmapRange(offset, size); auto range = getMmapRange(offset, size);
void* mapping = ::mmap(NULL, range.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, range.offset); void* mapping = ::mmap(NULL, range.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, range.offset);
if (mapping == MAP_FAILED) { if (mapping == MAP_FAILED) {
...@@ -471,7 +473,7 @@ public: ...@@ -471,7 +473,7 @@ public:
return heap<WritableFileMappingImpl>(kj::mv(array)); return heap<WritableFileMappingImpl>(kj::mv(array));
} }
size_t copyChunk(uint64_t offset, int fromFd, uint64_t fromOffset, uint64_t size) { size_t copyChunk(uint64_t offset, int fromFd, uint64_t fromOffset, uint64_t size) const {
// Copies a range of bytes from `fromFd` to this file in the most efficient way possible for // Copies a range of bytes from `fromFd` to this file in the most efficient way possible for
// the OS. Only returns less than `size` if EOF. Does not account for holes. // the OS. Only returns less than `size` if EOF. Does not account for holes.
...@@ -510,7 +512,8 @@ public: ...@@ -510,7 +512,8 @@ public:
return total; return total;
} }
kj::Maybe<size_t> copy(uint64_t offset, ReadableFile& from, uint64_t fromOffset, uint64_t size) { kj::Maybe<size_t> copy(uint64_t offset, const ReadableFile& from,
uint64_t fromOffset, uint64_t size) const {
KJ_IF_MAYBE(otherFd, from.getFd()) { KJ_IF_MAYBE(otherFd, from.getFd()) {
#ifdef FICLONE #ifdef FICLONE
if (offset == 0 && fromOffset == 0 && size == kj::maxValue && stat().size == 0) { if (offset == 0 && fromOffset == 0 && size == kj::maxValue && stat().size == 0) {
...@@ -629,7 +632,7 @@ public: ...@@ -629,7 +632,7 @@ public:
// ReadableDirectory --------------------------------------------------------- // ReadableDirectory ---------------------------------------------------------
template <typename Func> template <typename Func>
auto list(bool needTypes, Func&& func) auto list(bool needTypes, Func&& func) const
-> Array<Decay<decltype(func(instance<StringPtr>(), instance<FsNode::Type>()))>> { -> Array<Decay<decltype(func(instance<StringPtr>(), instance<FsNode::Type>()))>> {
// Seek to start of directory. // Seek to start of directory.
KJ_SYSCALL(lseek(fd, 0, SEEK_SET)); KJ_SYSCALL(lseek(fd, 0, SEEK_SET));
...@@ -686,17 +689,17 @@ public: ...@@ -686,17 +689,17 @@ public:
return result; return result;
} }
Array<String> listNames() { Array<String> listNames() const {
return list(false, [](StringPtr name, FsNode::Type type) { return heapString(name); }); return list(false, [](StringPtr name, FsNode::Type type) { return heapString(name); });
} }
Array<ReadableDirectory::Entry> listEntries() { Array<ReadableDirectory::Entry> listEntries() const {
return list(true, [](StringPtr name, FsNode::Type type) { return list(true, [](StringPtr name, FsNode::Type type) {
return ReadableDirectory::Entry { type, heapString(name), }; return ReadableDirectory::Entry { type, heapString(name), };
}); });
} }
bool exists(PathPtr path) { bool exists(PathPtr path) const {
KJ_SYSCALL_HANDLE_ERRORS(faccessat(fd, path.toString().cStr(), F_OK, 0)) { KJ_SYSCALL_HANDLE_ERRORS(faccessat(fd, path.toString().cStr(), F_OK, 0)) {
case ENOENT: case ENOENT:
case ENOTDIR: case ENOTDIR:
...@@ -707,7 +710,7 @@ public: ...@@ -707,7 +710,7 @@ public:
return true; return true;
} }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) { Maybe<FsNode::Metadata> tryLstat(PathPtr path) const {
struct stat stats; struct stat stats;
KJ_SYSCALL_HANDLE_ERRORS(fstatat(fd, path.toString().cStr(), &stats, AT_SYMLINK_NOFOLLOW)) { KJ_SYSCALL_HANDLE_ERRORS(fstatat(fd, path.toString().cStr(), &stats, AT_SYMLINK_NOFOLLOW)) {
case ENOENT: case ENOENT:
...@@ -719,7 +722,7 @@ public: ...@@ -719,7 +722,7 @@ public:
return statToMetadata(stats); return statToMetadata(stats);
} }
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) { Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const {
int newFd; int newFd;
KJ_SYSCALL_HANDLE_ERRORS(newFd = openat( KJ_SYSCALL_HANDLE_ERRORS(newFd = openat(
fd, path.toString().cStr(), O_RDONLY | MAYBE_O_CLOEXEC)) { fd, path.toString().cStr(), O_RDONLY | MAYBE_O_CLOEXEC)) {
...@@ -738,7 +741,7 @@ public: ...@@ -738,7 +741,7 @@ public:
return newDiskReadableFile(kj::mv(result)); return newDiskReadableFile(kj::mv(result));
} }
Maybe<AutoCloseFd> tryOpenSubdirInternal(PathPtr path) { Maybe<AutoCloseFd> tryOpenSubdirInternal(PathPtr path) const {
int newFd; int newFd;
KJ_SYSCALL_HANDLE_ERRORS(newFd = openat( KJ_SYSCALL_HANDLE_ERRORS(newFd = openat(
fd, path.toString().cStr(), O_RDONLY | MAYBE_O_CLOEXEC | MAYBE_O_DIRECTORY)) { fd, path.toString().cStr(), O_RDONLY | MAYBE_O_CLOEXEC | MAYBE_O_DIRECTORY)) {
...@@ -764,11 +767,11 @@ public: ...@@ -764,11 +767,11 @@ public:
return kj::mv(result); return kj::mv(result);
} }
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) { Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const {
return tryOpenSubdirInternal(path).map(newDiskReadableDirectory); return tryOpenSubdirInternal(path).map(newDiskReadableDirectory);
} }
Maybe<String> tryReadlink(PathPtr path) { Maybe<String> tryReadlink(PathPtr path) const {
size_t trySize = 256; size_t trySize = 256;
for (;;) { for (;;) {
KJ_STACK_ARRAY(char, buf, trySize, 256, 4096); KJ_STACK_ARRAY(char, buf, trySize, 256, 4096);
...@@ -799,7 +802,7 @@ public: ...@@ -799,7 +802,7 @@ public:
// Directory ----------------------------------------------------------------- // Directory -----------------------------------------------------------------
bool tryMkdir(PathPtr path, WriteMode mode, bool noThrow) { bool tryMkdir(PathPtr path, WriteMode mode, bool noThrow) const {
// Internal function to make a directory. // Internal function to make a directory.
auto filename = path.toString(); auto filename = path.toString();
...@@ -849,7 +852,7 @@ public: ...@@ -849,7 +852,7 @@ public:
} }
kj::Maybe<String> createNamedTemporary( kj::Maybe<String> createNamedTemporary(
PathPtr finalName, WriteMode mode, Function<int(StringPtr)> tryCreate) { PathPtr finalName, WriteMode mode, Function<int(StringPtr)> tryCreate) const {
// Create a temporary file which will eventually replace `finalName`. // Create a temporary file which will eventually replace `finalName`.
// //
// Calls `tryCreate` to actually create the temporary, passing in the desired path. tryCreate() // Calls `tryCreate` to actually create the temporary, passing in the desired path. tryCreate()
...@@ -895,7 +898,7 @@ public: ...@@ -895,7 +898,7 @@ public:
return kj::mv(path); return kj::mv(path);
} }
bool tryReplaceNode(PathPtr path, WriteMode mode, Function<int(StringPtr)> tryCreate) { bool tryReplaceNode(PathPtr path, WriteMode mode, Function<int(StringPtr)> tryCreate) const {
// Replaces the given path with an object created by calling tryCreate(). // Replaces the given path with an object created by calling tryCreate().
// //
// tryCreate() must behave like a syscall which creates the node at the path passed to it, // tryCreate() must behave like a syscall which creates the node at the path passed to it,
...@@ -959,7 +962,7 @@ public: ...@@ -959,7 +962,7 @@ public:
} }
} }
Maybe<AutoCloseFd> tryOpenFileInternal(PathPtr path, WriteMode mode, bool append) { Maybe<AutoCloseFd> tryOpenFileInternal(PathPtr path, WriteMode mode, bool append) const {
uint flags = O_RDWR | MAYBE_O_CLOEXEC; uint flags = O_RDWR | MAYBE_O_CLOEXEC;
mode_t acl = 0666; mode_t acl = 0666;
if (has(mode, WriteMode::CREATE)) { if (has(mode, WriteMode::CREATE)) {
...@@ -1037,7 +1040,7 @@ public: ...@@ -1037,7 +1040,7 @@ public:
} }
bool tryCommitReplacement(StringPtr toPath, int fromDirFd, StringPtr fromPath, WriteMode mode, bool tryCommitReplacement(StringPtr toPath, int fromDirFd, StringPtr fromPath, WriteMode mode,
int* errorReason = nullptr) { int* errorReason = nullptr) const {
if (has(mode, WriteMode::CREATE) && has(mode, WriteMode::MODIFY)) { if (has(mode, WriteMode::CREATE) && has(mode, WriteMode::MODIFY)) {
// Always clobber. Try it. // Always clobber. Try it.
KJ_SYSCALL_HANDLE_ERRORS(renameat(fromDirFd, fromPath.cStr(), fd.get(), toPath.cStr())) { KJ_SYSCALL_HANDLE_ERRORS(renameat(fromDirFd, fromPath.cStr(), fd.get(), toPath.cStr())) {
...@@ -1227,7 +1230,7 @@ public: ...@@ -1227,7 +1230,7 @@ public:
template <typename T> template <typename T>
class ReplacerImpl final: public Directory::Replacer<T> { class ReplacerImpl final: public Directory::Replacer<T> {
public: public:
ReplacerImpl(Own<T>&& object, DiskHandle& handle, ReplacerImpl(Own<const T>&& object, const DiskHandle& handle,
String&& tempPath, String&& path, WriteMode mode) String&& tempPath, String&& path, WriteMode mode)
: Directory::Replacer<T>(mode), : Directory::Replacer<T>(mode),
object(kj::mv(object)), handle(handle), object(kj::mv(object)), handle(handle),
...@@ -1239,7 +1242,7 @@ public: ...@@ -1239,7 +1242,7 @@ public:
} }
} }
T& get() override { const T& get() override {
return *object; return *object;
} }
...@@ -1250,8 +1253,8 @@ public: ...@@ -1250,8 +1253,8 @@ public:
} }
private: private:
Own<T> object; Own<const T> object;
DiskHandle& handle; const DiskHandle& handle;
String tempPath; String tempPath;
String path; String path;
bool committed = false; // true if *successfully* committed (in which case tempPath is gone) bool committed = false; // true if *successfully* committed (in which case tempPath is gone)
...@@ -1262,22 +1265,22 @@ public: ...@@ -1262,22 +1265,22 @@ public:
// For recovery path when exceptions are disabled. // For recovery path when exceptions are disabled.
public: public:
BrokenReplacer(Own<T> inner) BrokenReplacer(Own<const T> inner)
: Directory::Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY), : Directory::Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY),
inner(kj::mv(inner)) {} inner(kj::mv(inner)) {}
T& get() override { return *inner; } const T& get() override { return *inner; }
bool tryCommit() override { return false; } bool tryCommit() override { return false; }
private: private:
Own<T> inner; Own<const T> inner;
}; };
Maybe<Own<File>> tryOpenFile(PathPtr path, WriteMode mode) { Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const {
return tryOpenFileInternal(path, mode, false).map(newDiskFile); return tryOpenFileInternal(path, mode, false).map(newDiskFile);
} }
Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) { Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const {
mode_t acl = 0666; mode_t acl = 0666;
if (has(mode, WriteMode::EXECUTABLE)) { if (has(mode, WriteMode::EXECUTABLE)) {
acl = 0777; acl = 0777;
...@@ -1304,7 +1307,7 @@ public: ...@@ -1304,7 +1307,7 @@ public:
} }
} }
Own<File> createTemporary() { Own<const File> createTemporary() const {
int newFd_; int newFd_;
#if __linux__ && defined(O_TMPFILE) #if __linux__ && defined(O_TMPFILE)
...@@ -1345,11 +1348,11 @@ public: ...@@ -1345,11 +1348,11 @@ public:
} }
} }
Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) { Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const {
return tryOpenFileInternal(path, mode, true).map(newDiskAppendableFile); return tryOpenFileInternal(path, mode, true).map(newDiskAppendableFile);
} }
Maybe<Own<Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) { Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const {
// Must create before open. // Must create before open.
if (has(mode, WriteMode::CREATE)) { if (has(mode, WriteMode::CREATE)) {
if (!tryMkdir(path, mode, false)) return nullptr; if (!tryMkdir(path, mode, false)) return nullptr;
...@@ -1358,7 +1361,7 @@ public: ...@@ -1358,7 +1361,7 @@ public:
return tryOpenSubdirInternal(path).map(newDiskDirectory); return tryOpenSubdirInternal(path).map(newDiskDirectory);
} }
Own<Directory::Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) { Own<Directory::Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const {
mode_t acl = has(mode, WriteMode::PRIVATE) ? 0700 : 0777; mode_t acl = has(mode, WriteMode::PRIVATE) ? 0700 : 0777;
KJ_IF_MAYBE(temp, createNamedTemporary(path, mode, KJ_IF_MAYBE(temp, createNamedTemporary(path, mode,
...@@ -1385,15 +1388,15 @@ public: ...@@ -1385,15 +1388,15 @@ public:
} }
} }
bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) { bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const {
return tryReplaceNode(linkpath, mode, [&](StringPtr candidatePath) { return tryReplaceNode(linkpath, mode, [&](StringPtr candidatePath) {
return symlinkat(content.cStr(), fd, candidatePath.cStr()); return symlinkat(content.cStr(), fd, candidatePath.cStr());
}); });
} }
bool tryTransfer(PathPtr toPath, WriteMode toMode, bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode, Directory& self) { TransferMode mode, const Directory& self) const {
KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; } KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; }
if (mode == TransferMode::LINK) { if (mode == TransferMode::LINK) {
...@@ -1444,7 +1447,7 @@ public: ...@@ -1444,7 +1447,7 @@ public:
return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode); return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode);
} }
bool tryRemove(PathPtr path) { bool tryRemove(PathPtr path) const {
return rmrf(fd, path.toString()); return rmrf(fd, path.toString());
} }
...@@ -1452,16 +1455,16 @@ protected: ...@@ -1452,16 +1455,16 @@ protected:
AutoCloseFd fd; AutoCloseFd fd;
}; };
#define FSNODE_METHODS(classname) \ #define FSNODE_METHODS(classname) \
Maybe<int> getFd() override { return DiskHandle::getFd(); } \ Maybe<int> getFd() const override { return DiskHandle::getFd(); } \
\ \
Own<FsNode> cloneFsNode() override { \ Own<const FsNode> cloneFsNode() const override { \
return heap<classname>(DiskHandle::clone()); \ return heap<classname>(DiskHandle::clone()); \
} \ } \
\ \
Metadata stat() override { return DiskHandle::stat(); } \ Metadata stat() const override { return DiskHandle::stat(); } \
void sync() override { DiskHandle::sync(); } \ void sync() const override { DiskHandle::sync(); } \
void datasync() override { DiskHandle::datasync(); } void datasync() const override { DiskHandle::datasync(); }
class DiskReadableFile final: public ReadableFile, public DiskHandle { class DiskReadableFile final: public ReadableFile, public DiskHandle {
public: public:
...@@ -1469,13 +1472,13 @@ public: ...@@ -1469,13 +1472,13 @@ public:
FSNODE_METHODS(DiskReadableFile); FSNODE_METHODS(DiskReadableFile);
size_t read(uint64_t offset, ArrayPtr<byte> buffer) override { size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override {
return DiskHandle::read(offset, buffer); return DiskHandle::read(offset, buffer);
} }
Array<const byte> mmap(uint64_t offset, uint64_t size) override { Array<const byte> mmap(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmap(offset, size); return DiskHandle::mmap(offset, size);
} }
Array<byte> mmapPrivate(uint64_t offset, uint64_t size) override { Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmapPrivate(offset, size); return DiskHandle::mmapPrivate(offset, size);
} }
}; };
...@@ -1488,7 +1491,9 @@ public: ...@@ -1488,7 +1491,9 @@ public:
FSNODE_METHODS(DiskAppendableFile); FSNODE_METHODS(DiskAppendableFile);
void write(const void* buffer, size_t size) override { FdOutputStream::write(buffer, size); } void write(const void* buffer, size_t size) override {
FdOutputStream::write(buffer, size);
}
void write(ArrayPtr<const ArrayPtr<const byte>> pieces) override { void write(ArrayPtr<const ArrayPtr<const byte>> pieces) override {
FdOutputStream::write(pieces); FdOutputStream::write(pieces);
} }
...@@ -1500,29 +1505,30 @@ public: ...@@ -1500,29 +1505,30 @@ public:
FSNODE_METHODS(DiskFile); FSNODE_METHODS(DiskFile);
size_t read(uint64_t offset, ArrayPtr<byte> buffer) override { size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override {
return DiskHandle::read(offset, buffer); return DiskHandle::read(offset, buffer);
} }
Array<const byte> mmap(uint64_t offset, uint64_t size) override { Array<const byte> mmap(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmap(offset, size); return DiskHandle::mmap(offset, size);
} }
Array<byte> mmapPrivate(uint64_t offset, uint64_t size) override { Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmapPrivate(offset, size); return DiskHandle::mmapPrivate(offset, size);
} }
void write(uint64_t offset, ArrayPtr<const byte> data) override { void write(uint64_t offset, ArrayPtr<const byte> data) const override {
DiskHandle::write(offset, data); DiskHandle::write(offset, data);
} }
void zero(uint64_t offset, uint64_t size) override { void zero(uint64_t offset, uint64_t size) const override {
DiskHandle::zero(offset, size); DiskHandle::zero(offset, size);
} }
void truncate(uint64_t size) override { void truncate(uint64_t size) const override {
DiskHandle::truncate(size); DiskHandle::truncate(size);
} }
Own<WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) override { Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmapWritable(offset, size); return DiskHandle::mmapWritable(offset, size);
} }
size_t copy(uint64_t offset, ReadableFile& from, uint64_t fromOffset, uint64_t size) override { size_t copy(uint64_t offset, const ReadableFile& from,
uint64_t fromOffset, uint64_t size) const override {
KJ_IF_MAYBE(result, DiskHandle::copy(offset, from, fromOffset, size)) { KJ_IF_MAYBE(result, DiskHandle::copy(offset, from, fromOffset, size)) {
return *result; return *result;
} else { } else {
...@@ -1537,17 +1543,19 @@ public: ...@@ -1537,17 +1543,19 @@ public:
FSNODE_METHODS(DiskReadableDirectory); FSNODE_METHODS(DiskReadableDirectory);
Array<String> listNames() override { return DiskHandle::listNames(); } Array<String> listNames() const override { return DiskHandle::listNames(); }
Array<Entry> listEntries() override { return DiskHandle::listEntries(); } Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
bool exists(PathPtr path) override { return DiskHandle::exists(path); } bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override { return DiskHandle::tryLstat(path); } Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) override { return DiskHandle::tryLstat(path);
}
Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
return DiskHandle::tryOpenFile(path); return DiskHandle::tryOpenFile(path);
} }
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) override { Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
return DiskHandle::tryOpenSubdir(path); return DiskHandle::tryOpenSubdir(path);
} }
Maybe<String> tryReadlink(PathPtr path) override { return DiskHandle::tryReadlink(path); } Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); }
}; };
class DiskDirectory final: public Directory, public DiskHandle { class DiskDirectory final: public Directory, public DiskHandle {
...@@ -1556,46 +1564,48 @@ public: ...@@ -1556,46 +1564,48 @@ public:
FSNODE_METHODS(DiskDirectory); FSNODE_METHODS(DiskDirectory);
Array<String> listNames() override { return DiskHandle::listNames(); } Array<String> listNames() const override { return DiskHandle::listNames(); }
Array<Entry> listEntries() override { return DiskHandle::listEntries(); } Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
bool exists(PathPtr path) override { return DiskHandle::exists(path); } bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override { return DiskHandle::tryLstat(path); } Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) override { return DiskHandle::tryLstat(path);
}
Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
return DiskHandle::tryOpenFile(path); return DiskHandle::tryOpenFile(path);
} }
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) override { Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
return DiskHandle::tryOpenSubdir(path); return DiskHandle::tryOpenSubdir(path);
} }
Maybe<String> tryReadlink(PathPtr path) override { return DiskHandle::tryReadlink(path); } Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); }
Maybe<Own<File>> tryOpenFile(PathPtr path, WriteMode mode) override { Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const override {
return DiskHandle::tryOpenFile(path, mode); return DiskHandle::tryOpenFile(path, mode);
} }
Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) override { Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override {
return DiskHandle::replaceFile(path, mode); return DiskHandle::replaceFile(path, mode);
} }
Own<File> createTemporary() override { Own<const File> createTemporary() const override {
return DiskHandle::createTemporary(); return DiskHandle::createTemporary();
} }
Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) override { Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const override {
return DiskHandle::tryAppendFile(path, mode); return DiskHandle::tryAppendFile(path, mode);
} }
Maybe<Own<Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) override { Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const override {
return DiskHandle::tryOpenSubdir(path, mode); return DiskHandle::tryOpenSubdir(path, mode);
} }
Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) override { Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override {
return DiskHandle::replaceSubdir(path, mode); return DiskHandle::replaceSubdir(path, mode);
} }
bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) override { bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const override {
return DiskHandle::trySymlink(linkpath, content, mode); return DiskHandle::trySymlink(linkpath, content, mode);
} }
bool tryTransfer(PathPtr toPath, WriteMode toMode, bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) override { TransferMode mode) const override {
return DiskHandle::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode, *this); return DiskHandle::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode, *this);
} }
// tryTransferTo() not implemented because we have nothing special we can do. // tryTransferTo() not implemented because we have nothing special we can do.
bool tryRemove(PathPtr path) override { bool tryRemove(PathPtr path) const override {
return DiskHandle::tryRemove(path); return DiskHandle::tryRemove(path);
} }
}; };
...@@ -1607,15 +1617,15 @@ public: ...@@ -1607,15 +1617,15 @@ public:
current(openDir(".")), current(openDir(".")),
currentPath(computeCurrentPath()) {} currentPath(computeCurrentPath()) {}
Directory& getRoot() override { const Directory& getRoot() const override {
return root; return root;
} }
Directory& getCurrent() override { const Directory& getCurrent() const override {
return current; return current;
} }
PathPtr getCurrentPath() override { PathPtr getCurrentPath() const override {
return currentPath; return currentPath;
} }
......
...@@ -349,26 +349,26 @@ public: ...@@ -349,26 +349,26 @@ public:
AutoCloseHandle handle; AutoCloseHandle handle;
kj::Maybe<Path> dirPath; // needed for directories, empty for files kj::Maybe<Path> dirPath; // needed for directories, empty for files
Array<wchar_t> nativePath(PathPtr path) { Array<wchar_t> nativePath(PathPtr path) const {
return KJ_ASSERT_NONNULL(dirPath).append(path).forWin32Api(true); return KJ_ASSERT_NONNULL(dirPath).append(path).forWin32Api(true);
} }
// OsHandle ------------------------------------------------------------------ // OsHandle ------------------------------------------------------------------
AutoCloseHandle clone() { AutoCloseHandle clone() const {
HANDLE newHandle; HANDLE newHandle;
KJ_WIN32(DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &newHandle, KJ_WIN32(DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &newHandle,
0, FALSE, DUPLICATE_SAME_ACCESS)); 0, FALSE, DUPLICATE_SAME_ACCESS));
return AutoCloseHandle(newHandle); return AutoCloseHandle(newHandle);
} }
HANDLE getWin32Handle() { HANDLE getWin32Handle() const {
return handle.get(); return handle.get();
} }
// FsNode -------------------------------------------------------------------- // FsNode --------------------------------------------------------------------
FsNode::Metadata stat() { FsNode::Metadata stat() const {
BY_HANDLE_FILE_INFORMATION stats; BY_HANDLE_FILE_INFORMATION stats;
KJ_WIN32(GetFileInformationByHandle(handle, &stats)); KJ_WIN32(GetFileInformationByHandle(handle, &stats));
auto metadata = statToMetadata(stats); auto metadata = statToMetadata(stats);
...@@ -391,12 +391,12 @@ public: ...@@ -391,12 +391,12 @@ public:
return metadata; return metadata;
} }
void sync() { KJ_WIN32(FlushFileBuffers(handle)); } void sync() const { KJ_WIN32(FlushFileBuffers(handle)); }
void datasync() { KJ_WIN32(FlushFileBuffers(handle)); } void datasync() const { KJ_WIN32(FlushFileBuffers(handle)); }
// ReadableFile -------------------------------------------------------------- // ReadableFile --------------------------------------------------------------
size_t read(uint64_t offset, ArrayPtr<byte> buffer) { size_t read(uint64_t offset, ArrayPtr<byte> buffer) const {
// ReadFile() probably never returns short reads unless it hits EOF. Unfortunately, though, // ReadFile() probably never returns short reads unless it hits EOF. Unfortunately, though,
// this is not documented, and it's unclear whether we can rely on it. // this is not documented, and it's unclear whether we can rely on it.
...@@ -427,14 +427,14 @@ public: ...@@ -427,14 +427,14 @@ public:
return total; return total;
} }
Array<const byte> mmap(uint64_t offset, uint64_t size) { Array<const byte> mmap(uint64_t offset, uint64_t size) const {
auto range = getMmapRange(offset, size); auto range = getMmapRange(offset, size);
const void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_READ); const void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_READ);
return Array<const byte>(reinterpret_cast<const byte*>(mapping) + (offset - range.offset), return Array<const byte>(reinterpret_cast<const byte*>(mapping) + (offset - range.offset),
size, mmapDisposer); size, mmapDisposer);
} }
Array<byte> mmapPrivate(uint64_t offset, uint64_t size) { Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const {
auto range = getMmapRange(offset, size); auto range = getMmapRange(offset, size);
void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_COPY); void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_COPY);
return Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset), return Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset),
...@@ -443,7 +443,7 @@ public: ...@@ -443,7 +443,7 @@ public:
// File ---------------------------------------------------------------------- // File ----------------------------------------------------------------------
void write(uint64_t offset, ArrayPtr<const byte> data) { void write(uint64_t offset, ArrayPtr<const byte> data) const {
// WriteFile() probably never returns short writes unless there's no space left on disk. // WriteFile() probably never returns short writes unless there's no space left on disk.
// Unfortunately, though, this is not documented, and it's unclear whether we can rely on it. // Unfortunately, though, this is not documented, and it's unclear whether we can rely on it.
...@@ -463,7 +463,7 @@ public: ...@@ -463,7 +463,7 @@ public:
} }
} }
void zero(uint64_t offset, uint64_t size) { void zero(uint64_t offset, uint64_t size) const {
FILE_ZERO_DATA_INFORMATION info; FILE_ZERO_DATA_INFORMATION info;
memset(&info, 0, sizeof(info)); memset(&info, 0, sizeof(info));
info.FileOffset.QuadPart = offset; info.FileOffset.QuadPart = offset;
...@@ -491,7 +491,7 @@ public: ...@@ -491,7 +491,7 @@ public:
} }
} }
void truncate(uint64_t size) { void truncate(uint64_t size) const {
// SetEndOfFile() would require seeking the file. It looks like SetFileInformationByHandle() // SetEndOfFile() would require seeking the file. It looks like SetFileInformationByHandle()
// lets us avoid this! // lets us avoid this!
FILE_END_OF_FILE_INFO info; FILE_END_OF_FILE_INFO info;
...@@ -535,18 +535,20 @@ public: ...@@ -535,18 +535,20 @@ public:
public: public:
WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {} WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {}
ArrayPtr<byte> get() override { ArrayPtr<byte> get() const override {
return bytes; // const_cast OK because WritableFileMapping does indeed provide a writable view despite
// being const itself.
return arrayPtr(const_cast<byte*>(bytes.begin()), bytes.size());
} }
void changed(ArrayPtr<byte> slice) override { void changed(ArrayPtr<byte> slice) const override {
KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(), KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(),
"byte range is not part of this mapping"); "byte range is not part of this mapping");
// Nothing needed here -- NT tracks dirty pages. // Nothing needed here -- NT tracks dirty pages.
} }
void sync(ArrayPtr<byte> slice) override { void sync(ArrayPtr<byte> slice) const override {
KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(), KJ_REQUIRE(slice.begin() >= bytes.begin() && slice.end() <= bytes.end(),
"byte range is not part of this mapping"); "byte range is not part of this mapping");
...@@ -560,7 +562,7 @@ public: ...@@ -560,7 +562,7 @@ public:
Array<byte> bytes; Array<byte> bytes;
}; };
Own<WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) { Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const {
auto range = getMmapRange(offset, size); auto range = getMmapRange(offset, size);
void* mapping = win32Mmap(handle, range, PAGE_READWRITE, FILE_MAP_ALL_ACCESS); void* mapping = win32Mmap(handle, range, PAGE_READWRITE, FILE_MAP_ALL_ACCESS);
auto array = Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset), auto array = Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset),
...@@ -573,7 +575,7 @@ public: ...@@ -573,7 +575,7 @@ public:
// ReadableDirectory --------------------------------------------------------- // ReadableDirectory ---------------------------------------------------------
template <typename Func> template <typename Func>
auto list(bool needTypes, Func&& func) auto list(bool needTypes, Func&& func) const
-> Array<Decay<decltype(func(instance<StringPtr>(), instance<FsNode::Type>()))>> { -> Array<Decay<decltype(func(instance<StringPtr>(), instance<FsNode::Type>()))>> {
PathPtr path = KJ_ASSERT_NONNULL(dirPath); PathPtr path = KJ_ASSERT_NONNULL(dirPath);
auto glob = join16(path.forWin32Api(true), L"*"); auto glob = join16(path.forWin32Api(true), L"*");
...@@ -610,17 +612,17 @@ public: ...@@ -610,17 +612,17 @@ public:
return result; return result;
} }
Array<String> listNames() { Array<String> listNames() const {
return list(false, [](StringPtr name, FsNode::Type type) { return heapString(name); }); return list(false, [](StringPtr name, FsNode::Type type) { return heapString(name); });
} }
Array<ReadableDirectory::Entry> listEntries() { Array<ReadableDirectory::Entry> listEntries() const {
return list(true, [](StringPtr name, FsNode::Type type) { return list(true, [](StringPtr name, FsNode::Type type) {
return ReadableDirectory::Entry { type, heapString(name), }; return ReadableDirectory::Entry { type, heapString(name), };
}); });
} }
bool exists(PathPtr path) { bool exists(PathPtr path) const {
DWORD result = GetFileAttributesW(nativePath(path).begin()); DWORD result = GetFileAttributesW(nativePath(path).begin());
if (result == INVALID_FILE_ATTRIBUTES) { if (result == INVALID_FILE_ATTRIBUTES) {
auto error = GetLastError(); auto error = GetLastError();
...@@ -636,7 +638,7 @@ public: ...@@ -636,7 +638,7 @@ public:
} }
} }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) { Maybe<FsNode::Metadata> tryLstat(PathPtr path) const {
// We use FindFirstFileW() because in the case of symlinks it will return info about the // We use FindFirstFileW() because in the case of symlinks it will return info about the
// symlink rather than info about the target. // symlink rather than info about the target.
WIN32_FIND_DATAW data; WIN32_FIND_DATAW data;
...@@ -651,7 +653,7 @@ public: ...@@ -651,7 +653,7 @@ public:
} }
} }
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) { Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const {
HANDLE newHandle; HANDLE newHandle;
KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW( KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW(
nativePath(path).begin(), nativePath(path).begin(),
...@@ -671,7 +673,7 @@ public: ...@@ -671,7 +673,7 @@ public:
return newDiskReadableFile(kj::AutoCloseHandle(newHandle)); return newDiskReadableFile(kj::AutoCloseHandle(newHandle));
} }
Maybe<AutoCloseHandle> tryOpenSubdirInternal(PathPtr path) { Maybe<AutoCloseHandle> tryOpenSubdirInternal(PathPtr path) const {
HANDLE newHandle; HANDLE newHandle;
KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW( KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW(
nativePath(path).begin(), nativePath(path).begin(),
...@@ -702,13 +704,13 @@ public: ...@@ -702,13 +704,13 @@ public:
return kj::mv(ownHandle); return kj::mv(ownHandle);
} }
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) { Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const {
return tryOpenSubdirInternal(path).map([&](AutoCloseHandle&& handle) { return tryOpenSubdirInternal(path).map([&](AutoCloseHandle&& handle) {
return newDiskReadableDirectory(kj::mv(handle), KJ_ASSERT_NONNULL(dirPath).append(path)); return newDiskReadableDirectory(kj::mv(handle), KJ_ASSERT_NONNULL(dirPath).append(path));
}); });
} }
Maybe<String> tryReadlink(PathPtr path) { Maybe<String> tryReadlink(PathPtr path) const {
// Windows symlinks work differently from Unix. Generally they are set up by the system // Windows symlinks work differently from Unix. Generally they are set up by the system
// administrator and apps are expected to treat them transparently. Hence, on Windows, we act // administrator and apps are expected to treat them transparently. Hence, on Windows, we act
// as if nothing is a symlink by always returning null here. // as if nothing is a symlink by always returning null here.
...@@ -727,7 +729,7 @@ public: ...@@ -727,7 +729,7 @@ public:
return nullptr; return nullptr;
} }
bool tryMkdir(PathPtr path, WriteMode mode, bool noThrow) { bool tryMkdir(PathPtr path, WriteMode mode, bool noThrow) const {
// Internal function to make a directory. // Internal function to make a directory.
auto filename = nativePath(path); auto filename = nativePath(path);
...@@ -777,7 +779,7 @@ public: ...@@ -777,7 +779,7 @@ public:
kj::Maybe<Array<wchar_t>> createNamedTemporary( kj::Maybe<Array<wchar_t>> createNamedTemporary(
PathPtr finalName, WriteMode mode, Path& kjTempPath, PathPtr finalName, WriteMode mode, Path& kjTempPath,
Function<BOOL(const wchar_t*)> tryCreate) { Function<BOOL(const wchar_t*)> tryCreate) const {
// Create a temporary file which will eventually replace `finalName`. // Create a temporary file which will eventually replace `finalName`.
// //
// Calls `tryCreate` to actually create the temporary, passing in the desired path. tryCreate() // Calls `tryCreate` to actually create the temporary, passing in the desired path. tryCreate()
...@@ -826,12 +828,13 @@ public: ...@@ -826,12 +828,13 @@ public:
} }
kj::Maybe<Array<wchar_t>> createNamedTemporary( kj::Maybe<Array<wchar_t>> createNamedTemporary(
PathPtr finalName, WriteMode mode, Function<BOOL(const wchar_t*)> tryCreate) { PathPtr finalName, WriteMode mode, Function<BOOL(const wchar_t*)> tryCreate) const {
Path dummy = nullptr; Path dummy = nullptr;
return createNamedTemporary(finalName, mode, dummy, kj::mv(tryCreate)); return createNamedTemporary(finalName, mode, dummy, kj::mv(tryCreate));
} }
bool tryReplaceNode(PathPtr path, WriteMode mode, Function<BOOL(const wchar_t*)> tryCreate) { bool tryReplaceNode(PathPtr path, WriteMode mode,
Function<BOOL(const wchar_t*)> tryCreate) const {
// Replaces the given path with an object created by calling tryCreate(). // Replaces the given path with an object created by calling tryCreate().
// //
// tryCreate() must behave like a win32 call which creates the node at the path passed to it, // tryCreate() must behave like a win32 call which creates the node at the path passed to it,
...@@ -896,7 +899,7 @@ public: ...@@ -896,7 +899,7 @@ public:
} }
} }
Maybe<AutoCloseHandle> tryOpenFileInternal(PathPtr path, WriteMode mode, bool append) { Maybe<AutoCloseHandle> tryOpenFileInternal(PathPtr path, WriteMode mode, bool append) const {
DWORD disposition; DWORD disposition;
if (has(mode, WriteMode::MODIFY)) { if (has(mode, WriteMode::MODIFY)) {
if (has(mode, WriteMode::CREATE)) { if (has(mode, WriteMode::CREATE)) {
...@@ -969,7 +972,7 @@ public: ...@@ -969,7 +972,7 @@ public:
bool tryCommitReplacement( bool tryCommitReplacement(
PathPtr toPath, ArrayPtr<const wchar_t> fromPath, PathPtr toPath, ArrayPtr<const wchar_t> fromPath,
WriteMode mode, kj::Maybe<kj::PathPtr> pathForCreatingParents = nullptr) { WriteMode mode, kj::Maybe<kj::PathPtr> pathForCreatingParents = nullptr) const {
// Try to use MoveFileEx() to replace `toPath` with `fromPath`. // Try to use MoveFileEx() to replace `toPath` with `fromPath`.
auto wToPath = nativePath(toPath); auto wToPath = nativePath(toPath);
...@@ -1055,7 +1058,7 @@ public: ...@@ -1055,7 +1058,7 @@ public:
template <typename T> template <typename T>
class ReplacerImpl final: public Directory::Replacer<T> { class ReplacerImpl final: public Directory::Replacer<T> {
public: public:
ReplacerImpl(Own<T>&& object, DiskHandle& parentDirectory, ReplacerImpl(Own<T>&& object, const DiskHandle& parentDirectory,
Array<wchar_t>&& tempPath, Path&& path, WriteMode mode) Array<wchar_t>&& tempPath, Path&& path, WriteMode mode)
: Directory::Replacer<T>(mode), : Directory::Replacer<T>(mode),
object(kj::mv(object)), parentDirectory(parentDirectory), object(kj::mv(object)), parentDirectory(parentDirectory),
...@@ -1074,7 +1077,7 @@ public: ...@@ -1074,7 +1077,7 @@ public:
} }
} }
T& get() override { const T& get() override {
return *object; return *object;
} }
...@@ -1111,7 +1114,7 @@ public: ...@@ -1111,7 +1114,7 @@ public:
private: private:
Own<T> object; Own<T> object;
DiskHandle& parentDirectory; const DiskHandle& parentDirectory;
Array<wchar_t> tempPath; Array<wchar_t> tempPath;
Path path; Path path;
bool committed = false; // true if *successfully* committed (in which case tempPath is gone) bool committed = false; // true if *successfully* committed (in which case tempPath is gone)
...@@ -1122,22 +1125,22 @@ public: ...@@ -1122,22 +1125,22 @@ public:
// For recovery path when exceptions are disabled. // For recovery path when exceptions are disabled.
public: public:
BrokenReplacer(Own<T> inner) BrokenReplacer(Own<const T> inner)
: Directory::Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY), : Directory::Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY),
inner(kj::mv(inner)) {} inner(kj::mv(inner)) {}
T& get() override { return *inner; } const T& get() override { return *inner; }
bool tryCommit() override { return false; } bool tryCommit() override { return false; }
private: private:
Own<T> inner; Own<const T> inner;
}; };
Maybe<Own<File>> tryOpenFile(PathPtr path, WriteMode mode) { Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const {
return tryOpenFileInternal(path, mode, false).map(newDiskFile); return tryOpenFileInternal(path, mode, false).map(newDiskFile);
} }
Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) { Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const {
HANDLE newHandle_; HANDLE newHandle_;
KJ_IF_MAYBE(temp, createNamedTemporary(path, mode, KJ_IF_MAYBE(temp, createNamedTemporary(path, mode,
[&](const wchar_t* candidatePath) { [&](const wchar_t* candidatePath) {
...@@ -1160,7 +1163,7 @@ public: ...@@ -1160,7 +1163,7 @@ public:
} }
} }
Own<File> createTemporary() { Own<const File> createTemporary() const {
HANDLE newHandle_; HANDLE newHandle_;
KJ_IF_MAYBE(temp, createNamedTemporary(Path("unnamed"), WriteMode::CREATE, KJ_IF_MAYBE(temp, createNamedTemporary(Path("unnamed"), WriteMode::CREATE,
[&](const wchar_t* candidatePath) { [&](const wchar_t* candidatePath) {
...@@ -1182,11 +1185,11 @@ public: ...@@ -1182,11 +1185,11 @@ public:
} }
} }
Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) { Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const {
return tryOpenFileInternal(path, mode, true).map(newDiskAppendableFile); return tryOpenFileInternal(path, mode, true).map(newDiskAppendableFile);
} }
Maybe<Own<Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) { Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const {
// Must create before open. // Must create before open.
if (has(mode, WriteMode::CREATE)) { if (has(mode, WriteMode::CREATE)) {
if (!tryMkdir(path, mode, false)) return nullptr; if (!tryMkdir(path, mode, false)) return nullptr;
...@@ -1197,7 +1200,7 @@ public: ...@@ -1197,7 +1200,7 @@ public:
}); });
} }
Own<Directory::Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) { Own<Directory::Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const {
Path kjTempPath = nullptr; Path kjTempPath = nullptr;
KJ_IF_MAYBE(temp, createNamedTemporary(path, mode, kjTempPath, KJ_IF_MAYBE(temp, createNamedTemporary(path, mode, kjTempPath,
[&](const wchar_t* candidatePath) { [&](const wchar_t* candidatePath) {
...@@ -1230,7 +1233,7 @@ public: ...@@ -1230,7 +1233,7 @@ public:
} }
} }
bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) { bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const {
// We can't really create symlinks on Windows. Reasons: // We can't really create symlinks on Windows. Reasons:
// - We'd need to know whether the target is a file or a directory to pass the correct flags. // - We'd need to know whether the target is a file or a directory to pass the correct flags.
// That means we'd need to evaluate the link content and track down the target. What if the // That means we'd need to evaluate the link content and track down the target. What if the
...@@ -1242,8 +1245,8 @@ public: ...@@ -1242,8 +1245,8 @@ public:
} }
bool tryTransfer(PathPtr toPath, WriteMode toMode, bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode, Directory& self) { TransferMode mode, const Directory& self) const {
KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; } KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; }
// Try to get the "from" path. // Try to get the "from" path.
...@@ -1251,7 +1254,7 @@ public: ...@@ -1251,7 +1254,7 @@ public:
#if !KJ_NO_RTTI #if !KJ_NO_RTTI
// Oops, dynamicDowncastIfAvailable() doesn't work since this isn't a downcast, it's a // Oops, dynamicDowncastIfAvailable() doesn't work since this isn't a downcast, it's a
// side-cast... // side-cast...
if (auto dh = dynamic_cast<DiskHandle*>(&fromDirectory)) { if (auto dh = dynamic_cast<const DiskHandle*>(&fromDirectory)) {
rawFromPath = dh->nativePath(fromPath); rawFromPath = dh->nativePath(fromPath);
} else } else
#endif #endif
...@@ -1302,35 +1305,35 @@ public: ...@@ -1302,35 +1305,35 @@ public:
return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode); return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode);
} }
bool tryRemove(PathPtr path) { bool tryRemove(PathPtr path) const {
return rmrf(nativePath(path)); return rmrf(nativePath(path));
} }
}; };
#define FSNODE_METHODS \ #define FSNODE_METHODS \
Maybe<void*> getWin32Handle() override { return DiskHandle::getWin32Handle(); } \ Maybe<void*> getWin32Handle() const override { return DiskHandle::getWin32Handle(); } \
\ \
Metadata stat() override { return DiskHandle::stat(); } \ Metadata stat() const override { return DiskHandle::stat(); } \
void sync() override { DiskHandle::sync(); } \ void sync() const override { DiskHandle::sync(); } \
void datasync() override { DiskHandle::datasync(); } void datasync() const override { DiskHandle::datasync(); }
class DiskReadableFile final: public ReadableFile, public DiskHandle { class DiskReadableFile final: public ReadableFile, public DiskHandle {
public: public:
DiskReadableFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {} DiskReadableFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {}
Own<FsNode> cloneFsNode() override { Own<const FsNode> cloneFsNode() const override {
return heap<DiskReadableFile>(DiskHandle::clone()); return heap<DiskReadableFile>(DiskHandle::clone());
} }
FSNODE_METHODS FSNODE_METHODS
size_t read(uint64_t offset, ArrayPtr<byte> buffer) override { size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override {
return DiskHandle::read(offset, buffer); return DiskHandle::read(offset, buffer);
} }
Array<const byte> mmap(uint64_t offset, uint64_t size) override { Array<const byte> mmap(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmap(offset, size); return DiskHandle::mmap(offset, size);
} }
Array<byte> mmapPrivate(uint64_t offset, uint64_t size) override { Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmapPrivate(offset, size); return DiskHandle::mmapPrivate(offset, size);
} }
}; };
...@@ -1341,7 +1344,7 @@ public: ...@@ -1341,7 +1344,7 @@ public:
: DiskHandle(kj::mv(handle), nullptr), : DiskHandle(kj::mv(handle), nullptr),
stream(DiskHandle::handle.get()) {} stream(DiskHandle::handle.get()) {}
Own<FsNode> cloneFsNode() override { Own<const FsNode> cloneFsNode() const override {
return heap<DiskAppendableFile>(DiskHandle::clone()); return heap<DiskAppendableFile>(DiskHandle::clone());
} }
...@@ -1360,32 +1363,32 @@ class DiskFile final: public File, public DiskHandle { ...@@ -1360,32 +1363,32 @@ class DiskFile final: public File, public DiskHandle {
public: public:
DiskFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {} DiskFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {}
Own<FsNode> cloneFsNode() override { Own<const FsNode> cloneFsNode() const override {
return heap<DiskFile>(DiskHandle::clone()); return heap<DiskFile>(DiskHandle::clone());
} }
FSNODE_METHODS FSNODE_METHODS
size_t read(uint64_t offset, ArrayPtr<byte> buffer) override { size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override {
return DiskHandle::read(offset, buffer); return DiskHandle::read(offset, buffer);
} }
Array<const byte> mmap(uint64_t offset, uint64_t size) override { Array<const byte> mmap(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmap(offset, size); return DiskHandle::mmap(offset, size);
} }
Array<byte> mmapPrivate(uint64_t offset, uint64_t size) override { Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmapPrivate(offset, size); return DiskHandle::mmapPrivate(offset, size);
} }
void write(uint64_t offset, ArrayPtr<const byte> data) override { void write(uint64_t offset, ArrayPtr<const byte> data) const override {
DiskHandle::write(offset, data); DiskHandle::write(offset, data);
} }
void zero(uint64_t offset, uint64_t size) override { void zero(uint64_t offset, uint64_t size) const override {
DiskHandle::zero(offset, size); DiskHandle::zero(offset, size);
} }
void truncate(uint64_t size) override { void truncate(uint64_t size) const override {
DiskHandle::truncate(size); DiskHandle::truncate(size);
} }
Own<WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) override { Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const override {
return DiskHandle::mmapWritable(offset, size); return DiskHandle::mmapWritable(offset, size);
} }
// copy() is not optimized on Windows. // copy() is not optimized on Windows.
...@@ -1396,23 +1399,25 @@ public: ...@@ -1396,23 +1399,25 @@ public:
DiskReadableDirectory(AutoCloseHandle&& handle, Path&& path) DiskReadableDirectory(AutoCloseHandle&& handle, Path&& path)
: DiskHandle(kj::mv(handle), kj::mv(path)) {} : DiskHandle(kj::mv(handle), kj::mv(path)) {}
Own<FsNode> cloneFsNode() override { Own<const FsNode> cloneFsNode() const override {
return heap<DiskReadableDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone()); return heap<DiskReadableDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone());
} }
FSNODE_METHODS FSNODE_METHODS
Array<String> listNames() override { return DiskHandle::listNames(); } Array<String> listNames() const override { return DiskHandle::listNames(); }
Array<Entry> listEntries() override { return DiskHandle::listEntries(); } Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
bool exists(PathPtr path) override { return DiskHandle::exists(path); } bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override { return DiskHandle::tryLstat(path); } Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) override { return DiskHandle::tryLstat(path);
}
Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
return DiskHandle::tryOpenFile(path); return DiskHandle::tryOpenFile(path);
} }
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) override { Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
return DiskHandle::tryOpenSubdir(path); return DiskHandle::tryOpenSubdir(path);
} }
Maybe<String> tryReadlink(PathPtr path) override { return DiskHandle::tryReadlink(path); } Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); }
}; };
class DiskDirectoryBase: public Directory, public DiskHandle { class DiskDirectoryBase: public Directory, public DiskHandle {
...@@ -1420,41 +1425,41 @@ public: ...@@ -1420,41 +1425,41 @@ public:
DiskDirectoryBase(AutoCloseHandle&& handle, Path&& path) DiskDirectoryBase(AutoCloseHandle&& handle, Path&& path)
: DiskHandle(kj::mv(handle), kj::mv(path)) {} : DiskHandle(kj::mv(handle), kj::mv(path)) {}
bool exists(PathPtr path) override { return DiskHandle::exists(path); } bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override { return DiskHandle::tryLstat(path); } Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override { return DiskHandle::tryLstat(path); }
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) override { Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
return DiskHandle::tryOpenFile(path); return DiskHandle::tryOpenFile(path);
} }
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) override { Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
return DiskHandle::tryOpenSubdir(path); return DiskHandle::tryOpenSubdir(path);
} }
Maybe<String> tryReadlink(PathPtr path) override { return DiskHandle::tryReadlink(path); } Maybe<String> tryReadlink(PathPtr path) const override { return DiskHandle::tryReadlink(path); }
Maybe<Own<File>> tryOpenFile(PathPtr path, WriteMode mode) override { Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const override {
return DiskHandle::tryOpenFile(path, mode); return DiskHandle::tryOpenFile(path, mode);
} }
Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) override { Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override {
return DiskHandle::replaceFile(path, mode); return DiskHandle::replaceFile(path, mode);
} }
Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) override { Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const override {
return DiskHandle::tryAppendFile(path, mode); return DiskHandle::tryAppendFile(path, mode);
} }
Maybe<Own<Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) override { Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const override {
return DiskHandle::tryOpenSubdir(path, mode); return DiskHandle::tryOpenSubdir(path, mode);
} }
Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) override { Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override {
return DiskHandle::replaceSubdir(path, mode); return DiskHandle::replaceSubdir(path, mode);
} }
bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) override { bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const override {
return DiskHandle::trySymlink(linkpath, content, mode); return DiskHandle::trySymlink(linkpath, content, mode);
} }
bool tryTransfer(PathPtr toPath, WriteMode toMode, bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) override { TransferMode mode) const override {
return DiskHandle::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode, *this); return DiskHandle::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode, *this);
} }
// tryTransferTo() not implemented because we have nothing special we can do. // tryTransferTo() not implemented because we have nothing special we can do.
bool tryRemove(PathPtr path) override { bool tryRemove(PathPtr path) const override {
return DiskHandle::tryRemove(path); return DiskHandle::tryRemove(path);
} }
}; };
...@@ -1464,15 +1469,15 @@ public: ...@@ -1464,15 +1469,15 @@ public:
DiskDirectory(AutoCloseHandle&& handle, Path&& path) DiskDirectory(AutoCloseHandle&& handle, Path&& path)
: DiskDirectoryBase(kj::mv(handle), kj::mv(path)) {} : DiskDirectoryBase(kj::mv(handle), kj::mv(path)) {}
Own<FsNode> cloneFsNode() override { Own<const FsNode> cloneFsNode() const override {
return heap<DiskDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone()); return heap<DiskDirectory>(DiskHandle::clone(), KJ_ASSERT_NONNULL(dirPath).clone());
} }
FSNODE_METHODS FSNODE_METHODS
Array<String> listNames() override { return DiskHandle::listNames(); } Array<String> listNames() const override { return DiskHandle::listNames(); }
Array<Entry> listEntries() override { return DiskHandle::listEntries(); } Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
Own<File> createTemporary() override { Own<const File> createTemporary() const override {
return DiskHandle::createTemporary(); return DiskHandle::createTemporary();
} }
}; };
...@@ -1487,20 +1492,20 @@ class RootDiskDirectory final: public DiskDirectoryBase { ...@@ -1487,20 +1492,20 @@ class RootDiskDirectory final: public DiskDirectoryBase {
public: public:
RootDiskDirectory(): DiskDirectoryBase(nullptr, Path(nullptr)) {} RootDiskDirectory(): DiskDirectoryBase(nullptr, Path(nullptr)) {}
Own<FsNode> cloneFsNode() override { Own<const FsNode> cloneFsNode() const override {
return heap<RootDiskDirectory>(); return heap<RootDiskDirectory>();
} }
Metadata stat() override { Metadata stat() const override {
return { Type::DIRECTORY, 0, 0, UNIX_EPOCH, 1, 0 }; return { Type::DIRECTORY, 0, 0, UNIX_EPOCH, 1, 0 };
} }
void sync() override {} void sync() const override {}
void datasync() override {} void datasync() const override {}
Array<String> listNames() override { Array<String> listNames() const override {
return KJ_MAP(e, listEntries()) { return kj::mv(e.name); }; return KJ_MAP(e, listEntries()) { return kj::mv(e.name); };
} }
Array<Entry> listEntries() override { Array<Entry> listEntries() const override {
DWORD drives = GetLogicalDrives(); DWORD drives = GetLogicalDrives();
if (drives == 0) { if (drives == 0) {
KJ_FAIL_WIN32("GetLogicalDrives()", GetLastError()) { return nullptr; } KJ_FAIL_WIN32("GetLogicalDrives()", GetLastError()) { return nullptr; }
...@@ -1517,7 +1522,7 @@ public: ...@@ -1517,7 +1522,7 @@ public:
return results.releaseAsArray(); return results.releaseAsArray();
} }
Own<File> createTemporary() override { Own<const File> createTemporary() const override {
KJ_FAIL_REQUIRE("can't create temporaries in Windows pseudo-root directory (the drive list)"); KJ_FAIL_REQUIRE("can't create temporaries in Windows pseudo-root directory (the drive list)");
} }
}; };
...@@ -1531,15 +1536,15 @@ public: ...@@ -1531,15 +1536,15 @@ public:
"path returned by GetCurrentDirectory() doesn't exist?"), "path returned by GetCurrentDirectory() doesn't exist?"),
kj::mv(currentPath)) {} kj::mv(currentPath)) {}
Directory& getRoot() override { const Directory& getRoot() const override {
return root; return root;
} }
Directory& getCurrent() override { const Directory& getCurrent() const override {
return current; return current;
} }
PathPtr getCurrentPath() override { PathPtr getCurrentPath() const override {
return KJ_ASSERT_NONNULL(current.dirPath); return KJ_ASSERT_NONNULL(current.dirPath);
} }
......
...@@ -264,13 +264,13 @@ public: ...@@ -264,13 +264,13 @@ public:
time += 1 * SECONDS; time += 1 * SECONDS;
} }
Date now() override { return time; } Date now() const override { return time; }
void expectChanged(FsNode& file) { void expectChanged(const FsNode& file) {
KJ_EXPECT(file.stat().lastModified == time); KJ_EXPECT(file.stat().lastModified == time);
time += 1 * SECONDS; time += 1 * SECONDS;
} }
void expectUnchanged(FsNode& file) { void expectUnchanged(const FsNode& file) {
KJ_EXPECT(file.stat().lastModified != time); KJ_EXPECT(file.stat().lastModified != time);
} }
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "one-of.h" #include "one-of.h"
#include "encoding.h" #include "encoding.h"
#include "refcount.h" #include "refcount.h"
#include "mutex.h"
#include <map> #include <map>
namespace kj { namespace kj {
...@@ -482,7 +483,7 @@ bool Path::isWin32Special(StringPtr part) { ...@@ -482,7 +483,7 @@ bool Path::isWin32Special(StringPtr part) {
// ======================================================================================= // =======================================================================================
String ReadableFile::readAllText() { String ReadableFile::readAllText() const {
String result = heapString(stat().size); String result = heapString(stat().size);
size_t n = read(0, result.asBytes()); size_t n = read(0, result.asBytes());
if (n < result.size()) { if (n < result.size()) {
...@@ -492,7 +493,7 @@ String ReadableFile::readAllText() { ...@@ -492,7 +493,7 @@ String ReadableFile::readAllText() {
return result; return result;
} }
Array<byte> ReadableFile::readAllBytes() { Array<byte> ReadableFile::readAllBytes() const {
Array<byte> result = heapArray<byte>(stat().size); Array<byte> result = heapArray<byte>(stat().size);
size_t n = read(0, result.asBytes()); size_t n = read(0, result.asBytes());
if (n < result.size()) { if (n < result.size()) {
...@@ -502,16 +503,17 @@ Array<byte> ReadableFile::readAllBytes() { ...@@ -502,16 +503,17 @@ Array<byte> ReadableFile::readAllBytes() {
return result; return result;
} }
void File::writeAll(ArrayPtr<const byte> bytes) { void File::writeAll(ArrayPtr<const byte> bytes) const {
truncate(0); truncate(0);
write(0, bytes); write(0, bytes);
} }
void File::writeAll(StringPtr text) { void File::writeAll(StringPtr text) const {
writeAll(text.asBytes()); writeAll(text.asBytes());
} }
size_t File::copy(uint64_t offset, ReadableFile& from, uint64_t fromOffset, uint64_t size) { size_t File::copy(uint64_t offset, const ReadableFile& from,
uint64_t fromOffset, uint64_t size) const {
byte buffer[8192]; byte buffer[8192];
size_t result = 0; size_t result = 0;
...@@ -531,7 +533,7 @@ size_t File::copy(uint64_t offset, ReadableFile& from, uint64_t fromOffset, uint ...@@ -531,7 +533,7 @@ size_t File::copy(uint64_t offset, ReadableFile& from, uint64_t fromOffset, uint
return result; return result;
} }
FsNode::Metadata ReadableDirectory::lstat(PathPtr path) { FsNode::Metadata ReadableDirectory::lstat(PathPtr path) const {
KJ_IF_MAYBE(meta, tryLstat(path)) { KJ_IF_MAYBE(meta, tryLstat(path)) {
return *meta; return *meta;
} else { } else {
...@@ -540,7 +542,7 @@ FsNode::Metadata ReadableDirectory::lstat(PathPtr path) { ...@@ -540,7 +542,7 @@ FsNode::Metadata ReadableDirectory::lstat(PathPtr path) {
} }
} }
Own<ReadableFile> ReadableDirectory::openFile(PathPtr path) { Own<const ReadableFile> ReadableDirectory::openFile(PathPtr path) const {
KJ_IF_MAYBE(file, tryOpenFile(path)) { KJ_IF_MAYBE(file, tryOpenFile(path)) {
return kj::mv(*file); return kj::mv(*file);
} else { } else {
...@@ -549,7 +551,7 @@ Own<ReadableFile> ReadableDirectory::openFile(PathPtr path) { ...@@ -549,7 +551,7 @@ Own<ReadableFile> ReadableDirectory::openFile(PathPtr path) {
} }
} }
Own<ReadableDirectory> ReadableDirectory::openSubdir(PathPtr path) { Own<const ReadableDirectory> ReadableDirectory::openSubdir(PathPtr path) const {
KJ_IF_MAYBE(dir, tryOpenSubdir(path)) { KJ_IF_MAYBE(dir, tryOpenSubdir(path)) {
return kj::mv(*dir); return kj::mv(*dir);
} else { } else {
...@@ -558,7 +560,7 @@ Own<ReadableDirectory> ReadableDirectory::openSubdir(PathPtr path) { ...@@ -558,7 +560,7 @@ Own<ReadableDirectory> ReadableDirectory::openSubdir(PathPtr path) {
} }
} }
String ReadableDirectory::readlink(PathPtr path) { String ReadableDirectory::readlink(PathPtr path) const {
KJ_IF_MAYBE(p, tryReadlink(path)) { KJ_IF_MAYBE(p, tryReadlink(path)) {
return kj::mv(*p); return kj::mv(*p);
} else { } else {
...@@ -567,7 +569,7 @@ String ReadableDirectory::readlink(PathPtr path) { ...@@ -567,7 +569,7 @@ String ReadableDirectory::readlink(PathPtr path) {
} }
} }
Own<File> Directory::openFile(PathPtr path, WriteMode mode) { Own<const File> Directory::openFile(PathPtr path, WriteMode mode) const {
KJ_IF_MAYBE(f, tryOpenFile(path, mode)) { KJ_IF_MAYBE(f, tryOpenFile(path, mode)) {
return kj::mv(*f); return kj::mv(*f);
} else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) { } else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
...@@ -583,7 +585,7 @@ Own<File> Directory::openFile(PathPtr path, WriteMode mode) { ...@@ -583,7 +585,7 @@ Own<File> Directory::openFile(PathPtr path, WriteMode mode) {
return newInMemoryFile(nullClock()); return newInMemoryFile(nullClock());
} }
Own<AppendableFile> Directory::appendFile(PathPtr path, WriteMode mode) { Own<AppendableFile> Directory::appendFile(PathPtr path, WriteMode mode) const {
KJ_IF_MAYBE(f, tryAppendFile(path, mode)) { KJ_IF_MAYBE(f, tryAppendFile(path, mode)) {
return kj::mv(*f); return kj::mv(*f);
} else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) { } else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
...@@ -599,7 +601,7 @@ Own<AppendableFile> Directory::appendFile(PathPtr path, WriteMode mode) { ...@@ -599,7 +601,7 @@ Own<AppendableFile> Directory::appendFile(PathPtr path, WriteMode mode) {
return newFileAppender(newInMemoryFile(nullClock())); return newFileAppender(newInMemoryFile(nullClock()));
} }
Own<Directory> Directory::openSubdir(PathPtr path, WriteMode mode) { Own<const Directory> Directory::openSubdir(PathPtr path, WriteMode mode) const {
KJ_IF_MAYBE(f, tryOpenSubdir(path, mode)) { KJ_IF_MAYBE(f, tryOpenSubdir(path, mode)) {
return kj::mv(*f); return kj::mv(*f);
} else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) { } else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
...@@ -615,7 +617,7 @@ Own<Directory> Directory::openSubdir(PathPtr path, WriteMode mode) { ...@@ -615,7 +617,7 @@ Own<Directory> Directory::openSubdir(PathPtr path, WriteMode mode) {
return newInMemoryDirectory(nullClock()); return newInMemoryDirectory(nullClock());
} }
void Directory::symlink(PathPtr linkpath, StringPtr content, WriteMode mode) { void Directory::symlink(PathPtr linkpath, StringPtr content, WriteMode mode) const {
if (!trySymlink(linkpath, content, mode)) { if (!trySymlink(linkpath, content, mode)) {
if (has(mode, WriteMode::CREATE)) { if (has(mode, WriteMode::CREATE)) {
KJ_FAIL_REQUIRE("path already exsits", linkpath) { break; } KJ_FAIL_REQUIRE("path already exsits", linkpath) { break; }
...@@ -627,8 +629,8 @@ void Directory::symlink(PathPtr linkpath, StringPtr content, WriteMode mode) { ...@@ -627,8 +629,8 @@ void Directory::symlink(PathPtr linkpath, StringPtr content, WriteMode mode) {
} }
void Directory::transfer(PathPtr toPath, WriteMode toMode, void Directory::transfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) { TransferMode mode) const {
if (!tryTransfer(toPath, toMode, fromDirectory, fromPath, mode)) { if (!tryTransfer(toPath, toMode, fromDirectory, fromPath, mode)) {
if (has(toMode, WriteMode::CREATE)) { if (has(toMode, WriteMode::CREATE)) {
KJ_FAIL_REQUIRE("toPath already exists or fromPath doesn't exist", toPath, fromPath) { KJ_FAIL_REQUIRE("toPath already exists or fromPath doesn't exist", toPath, fromPath) {
...@@ -640,10 +642,10 @@ void Directory::transfer(PathPtr toPath, WriteMode toMode, ...@@ -640,10 +642,10 @@ void Directory::transfer(PathPtr toPath, WriteMode toMode,
} }
} }
static void copyContents(Directory& to, ReadableDirectory& from); static void copyContents(const Directory& to, const ReadableDirectory& from);
static bool tryCopyDirectoryEntry(Directory& to, PathPtr toPath, WriteMode toMode, static bool tryCopyDirectoryEntry(const Directory& to, PathPtr toPath, WriteMode toMode,
ReadableDirectory& from, PathPtr fromPath, const ReadableDirectory& from, PathPtr fromPath,
FsNode::Type type, bool atomic) { FsNode::Type type, bool atomic) {
// TODO(cleanup): Make this reusable? // TODO(cleanup): Make this reusable?
...@@ -699,7 +701,7 @@ static bool tryCopyDirectoryEntry(Directory& to, PathPtr toPath, WriteMode toMod ...@@ -699,7 +701,7 @@ static bool tryCopyDirectoryEntry(Directory& to, PathPtr toPath, WriteMode toMod
} }
} }
static void copyContents(Directory& to, ReadableDirectory& from) { static void copyContents(const Directory& to, const ReadableDirectory& from) {
for (auto& entry: from.listEntries()) { for (auto& entry: from.listEntries()) {
Path subPath(kj::mv(entry.name)); Path subPath(kj::mv(entry.name));
tryCopyDirectoryEntry(to, subPath, WriteMode::CREATE, from, subPath, entry.type, false); tryCopyDirectoryEntry(to, subPath, WriteMode::CREATE, from, subPath, entry.type, false);
...@@ -707,8 +709,8 @@ static void copyContents(Directory& to, ReadableDirectory& from) { ...@@ -707,8 +709,8 @@ static void copyContents(Directory& to, ReadableDirectory& from) {
} }
bool Directory::tryTransfer(PathPtr toPath, WriteMode toMode, bool Directory::tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) { TransferMode mode) const {
KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; } KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; }
// First try reversing. // First try reversing.
...@@ -739,12 +741,12 @@ bool Directory::tryTransfer(PathPtr toPath, WriteMode toMode, ...@@ -739,12 +741,12 @@ bool Directory::tryTransfer(PathPtr toPath, WriteMode toMode,
KJ_UNREACHABLE; KJ_UNREACHABLE;
} }
Maybe<bool> Directory::tryTransferTo(Directory& toDirectory, PathPtr toPath, WriteMode toMode, Maybe<bool> Directory::tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode) { PathPtr fromPath, TransferMode mode) const {
return nullptr; return nullptr;
} }
void Directory::remove(PathPtr path) { void Directory::remove(PathPtr path) const {
if (!tryRemove(path)) { if (!tryRemove(path)) {
KJ_FAIL_REQUIRE("path to remove doesn't exist", path) { break; } KJ_FAIL_REQUIRE("path to remove doesn't exist", path) { break; }
} }
...@@ -766,47 +768,50 @@ void Directory::commitFailed(WriteMode mode) { ...@@ -766,47 +768,50 @@ void Directory::commitFailed(WriteMode mode) {
namespace { namespace {
class InMemoryFile final: public File, public Refcounted { class InMemoryFile final: public File, public AtomicRefcounted {
public: public:
InMemoryFile(Clock& clock): clock(clock), lastModified(clock.now()) {} InMemoryFile(const Clock& clock): impl(clock) {}
Own<FsNode> cloneFsNode() override { Own<const FsNode> cloneFsNode() const override {
return addRef(*this); return atomicAddRef(*this);
} }
Maybe<int> getFd() override { Maybe<int> getFd() const override {
return nullptr; return nullptr;
} }
Metadata stat() override { Metadata stat() const override {
auto lock = impl.lockShared();
uint64_t hash = reinterpret_cast<uintptr_t>(this); uint64_t hash = reinterpret_cast<uintptr_t>(this);
return Metadata { Type::FILE, size, size, lastModified, 1, hash }; return Metadata { Type::FILE, lock->size, lock->size, lock->lastModified, 1, hash };
} }
void sync() override {} void sync() const override {}
void datasync() override {} void datasync() const override {}
// no-ops // no-ops
size_t read(uint64_t offset, ArrayPtr<byte> buffer) override { size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override {
if (offset >= size) { auto lock = impl.lockShared();
if (offset >= lock->size) {
// Entirely out-of-range. // Entirely out-of-range.
return 0; return 0;
} }
size_t readSize = kj::min(buffer.size(), size - offset); size_t readSize = kj::min(buffer.size(), lock->size - offset);
memcpy(buffer.begin(), bytes.begin() + offset, readSize); memcpy(buffer.begin(), lock->bytes.begin() + offset, readSize);
return readSize; return readSize;
} }
Array<const byte> mmap(uint64_t offset, uint64_t size) override { Array<const byte> mmap(uint64_t offset, uint64_t size) const override {
KJ_REQUIRE(offset + size >= offset, "mmap() request overflows uint64"); KJ_REQUIRE(offset + size >= offset, "mmap() request overflows uint64");
ensureCapacity(offset + size); auto lock = impl.lockExclusive();
lock->ensureCapacity(offset + size);
ArrayDisposer* disposer = new MmapDisposer(addRef(*this)); ArrayDisposer* disposer = new MmapDisposer(atomicAddRef(*this));
return Array<const byte>(bytes.begin() + offset, size, *disposer); return Array<const byte>(lock->bytes.begin() + offset, size, *disposer);
} }
Array<byte> mmapPrivate(uint64_t offset, uint64_t size) override { Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override {
// Return a copy. // Return a copy.
// Allocate exactly the size requested. // Allocate exactly the size requested.
...@@ -823,47 +828,51 @@ public: ...@@ -823,47 +828,51 @@ public:
return result; return result;
} }
void write(uint64_t offset, ArrayPtr<const byte> data) override { void write(uint64_t offset, ArrayPtr<const byte> data) const override {
if (data.size() == 0) return; if (data.size() == 0) return;
modified(); auto lock = impl.lockExclusive();
lock->modified();
uint64_t end = offset + data.size(); uint64_t end = offset + data.size();
KJ_REQUIRE(end >= offset, "write() request overflows uint64"); KJ_REQUIRE(end >= offset, "write() request overflows uint64");
ensureCapacity(end); lock->ensureCapacity(end);
size = kj::max(size, end); lock->size = kj::max(lock->size, end);
memcpy(bytes.begin() + offset, data.begin(), data.size()); memcpy(lock->bytes.begin() + offset, data.begin(), data.size());
} }
void zero(uint64_t offset, uint64_t zeroSize) override { void zero(uint64_t offset, uint64_t zeroSize) const override {
if (zeroSize == 0) return; if (zeroSize == 0) return;
modified(); auto lock = impl.lockExclusive();
lock->modified();
uint64_t end = offset + zeroSize; uint64_t end = offset + zeroSize;
KJ_REQUIRE(end >= offset, "zero() request overflows uint64"); KJ_REQUIRE(end >= offset, "zero() request overflows uint64");
ensureCapacity(end); lock->ensureCapacity(end);
size = kj::max(size, end); lock->size = kj::max(lock->size, end);
memset(bytes.begin() + offset, 0, zeroSize); memset(lock->bytes.begin() + offset, 0, zeroSize);
} }
void truncate(uint64_t newSize) override { void truncate(uint64_t newSize) const override {
if (newSize < size) { auto lock = impl.lockExclusive();
modified(); if (newSize < lock->size) {
memset(bytes.begin() + newSize, 0, size - newSize); lock->modified();
size = newSize; memset(lock->bytes.begin() + newSize, 0, lock->size - newSize);
} else if (newSize > size) { lock->size = newSize;
modified(); } else if (newSize > lock->size) {
ensureCapacity(newSize); lock->modified();
size = newSize; lock->ensureCapacity(newSize);
lock->size = newSize;
} }
} }
Own<WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) override { Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const override {
uint64_t end = offset + size; uint64_t end = offset + size;
KJ_REQUIRE(end >= offset, "mmapWritable() request overflows uint64"); KJ_REQUIRE(end >= offset, "mmapWritable() request overflows uint64");
ensureCapacity(end); auto lock = impl.lockExclusive();
return heap<WritableFileMappingImpl>(addRef(*this), bytes.slice(offset, end)); lock->ensureCapacity(end);
return heap<WritableFileMappingImpl>(atomicAddRef(*this), lock->bytes.slice(offset, end));
} }
size_t copy(uint64_t offset, ReadableFile& from, size_t copy(uint64_t offset, const ReadableFile& from,
uint64_t fromOffset, uint64_t copySize) override { uint64_t fromOffset, uint64_t copySize) const override {
size_t fromFileSize = from.stat().size; size_t fromFileSize = from.stat().size;
if (fromFileSize <= fromOffset) return 0; if (fromFileSize <= fromOffset) return 0;
...@@ -871,48 +880,55 @@ public: ...@@ -871,48 +880,55 @@ public:
copySize = kj::min(copySize, fromFileSize - fromOffset); copySize = kj::min(copySize, fromFileSize - fromOffset);
if (copySize == 0) return 0; if (copySize == 0) return 0;
auto lock = impl.lockExclusive();
// Allocate space for the copy. // Allocate space for the copy.
uint64_t end = offset + copySize; uint64_t end = offset + copySize;
ensureCapacity(end); lock->ensureCapacity(end);
// Read directly into our backing store. // Read directly into our backing store.
size_t n = from.read(fromOffset, bytes.slice(offset, end)); size_t n = from.read(fromOffset, lock->bytes.slice(offset, end));
size = kj::max(size, offset + n); lock->size = kj::max(lock->size, offset + n);
modified(); lock->modified();
return n; return n;
} }
private: private:
Clock& clock; struct Impl {
Array<byte> bytes; const Clock& clock;
size_t size = 0; // bytes may be larger than this to accommodate mmaps Array<byte> bytes;
Date lastModified; size_t size = 0; // bytes may be larger than this to accommodate mmaps
uint mmapCount = 0; // number of mappings outstanding Date lastModified;
uint mmapCount = 0; // number of mappings outstanding
Impl(const Clock& clock): clock(clock), lastModified(clock.now()) {}
void ensureCapacity(size_t capacity) { void ensureCapacity(size_t capacity) {
if (bytes.size() < capacity) { if (bytes.size() < capacity) {
KJ_ASSERT(mmapCount == 0, KJ_ASSERT(mmapCount == 0,
"InMemoryFile cannot resize the file backing store while memory mappings exist."); "InMemoryFile cannot resize the file backing store while memory mappings exist.");
auto newBytes = heapArray<byte>(kj::max(capacity, bytes.size() * 2)); auto newBytes = heapArray<byte>(kj::max(capacity, bytes.size() * 2));
memcpy(newBytes.begin(), bytes.begin(), size); memcpy(newBytes.begin(), bytes.begin(), size);
memset(newBytes.begin() + size, 0, newBytes.size() - size); memset(newBytes.begin() + size, 0, newBytes.size() - size);
bytes = kj::mv(newBytes); bytes = kj::mv(newBytes);
}
} }
}
void modified() { void modified() {
lastModified = clock.now(); lastModified = clock.now();
} }
};
kj::MutexGuarded<Impl> impl;
class MmapDisposer final: public ArrayDisposer { class MmapDisposer final: public ArrayDisposer {
public: public:
MmapDisposer(Own<InMemoryFile>&& refParam): ref(kj::mv(refParam)) { MmapDisposer(Own<const InMemoryFile>&& refParam): ref(kj::mv(refParam)) {
++ref->mmapCount; ++ref->impl.getAlreadyLockedExclusive().mmapCount;
} }
~MmapDisposer() noexcept(false) { ~MmapDisposer() noexcept(false) {
--ref->mmapCount; --ref->impl.lockExclusive()->mmapCount;
} }
void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount, void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
...@@ -921,67 +937,71 @@ private: ...@@ -921,67 +937,71 @@ private:
} }
private: private:
Own<InMemoryFile> ref; Own<const InMemoryFile> ref;
}; };
class WritableFileMappingImpl final: public WritableFileMapping { class WritableFileMappingImpl final: public WritableFileMapping {
public: public:
WritableFileMappingImpl(Own<InMemoryFile>&& refParam, ArrayPtr<byte> range) WritableFileMappingImpl(Own<const InMemoryFile>&& refParam, ArrayPtr<byte> range)
: ref(kj::mv(refParam)), range(range) { : ref(kj::mv(refParam)), range(range) {
++ref->mmapCount; ++ref->impl.getAlreadyLockedExclusive().mmapCount;
} }
~WritableFileMappingImpl() noexcept(false) { ~WritableFileMappingImpl() noexcept(false) {
--ref->mmapCount; --ref->impl.lockExclusive()->mmapCount;
} }
ArrayPtr<byte> get() override { ArrayPtr<byte> get() const override {
return range; // 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) override { void changed(ArrayPtr<byte> slice) const override {
ref->modified(); ref->impl.lockExclusive()->modified();
} }
void sync(ArrayPtr<byte> slice) override { void sync(ArrayPtr<byte> slice) const override {
ref->modified(); ref->impl.lockExclusive()->modified();
} }
private: private:
Own<InMemoryFile> ref; Own<const InMemoryFile> ref;
ArrayPtr<byte> range; ArrayPtr<byte> range;
}; };
}; };
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
class InMemoryDirectory final: public Directory, public Refcounted { class InMemoryDirectory final: public Directory, public AtomicRefcounted {
public: public:
InMemoryDirectory(Clock& clock) InMemoryDirectory(const Clock& clock): impl(clock) {}
: clock(clock), lastModified(clock.now()) {}
Own<FsNode> cloneFsNode() override { Own<const FsNode> cloneFsNode() const override {
return addRef(*this); return atomicAddRef(*this);
} }
Maybe<int> getFd() override { Maybe<int> getFd() const override {
return nullptr; return nullptr;
} }
Metadata stat() override { Metadata stat() const override {
auto lock = impl.lockShared();
uint64_t hash = reinterpret_cast<uintptr_t>(this); uint64_t hash = reinterpret_cast<uintptr_t>(this);
return Metadata { Type::DIRECTORY, 0, 0, lastModified, 1, hash }; return Metadata { Type::DIRECTORY, 0, 0, lock->lastModified, 1, hash };
} }
void sync() override {} void sync() const override {}
void datasync() override {} void datasync() const override {}
// no-ops // no-ops
Array<String> listNames() override { Array<String> listNames() const override {
return KJ_MAP(e, entries) { return heapString(e.first); }; auto lock = impl.lockShared();
return KJ_MAP(e, lock->entries) { return heapString(e.first); };
} }
Array<Entry> listEntries() override { Array<Entry> listEntries() const override {
return KJ_MAP(e, entries) { auto lock = impl.lockShared();
return KJ_MAP(e, lock->entries) {
FsNode::Type type; FsNode::Type type;
if (e.second.node.is<SymlinkNode>()) { if (e.second.node.is<SymlinkNode>()) {
type = FsNode::Type::SYMLINK; type = FsNode::Type::SYMLINK;
...@@ -996,12 +1016,13 @@ public: ...@@ -996,12 +1016,13 @@ public:
}; };
} }
bool exists(PathPtr path) override { bool exists(PathPtr path) const override {
if (path.size() == 0) { if (path.size() == 0) {
return true; return true;
} else if (path.size() == 1) { } else if (path.size() == 1) {
KJ_IF_MAYBE(entry, tryGetEntry(path[0])) { auto lock = impl.lockShared();
return exists(*entry); KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
return exists(lock, *entry);
} else { } else {
return false; return false;
} }
...@@ -1014,11 +1035,12 @@ public: ...@@ -1014,11 +1035,12 @@ public:
} }
} }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override { Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
if (path.size() == 0) { if (path.size() == 0) {
return stat(); return stat();
} else if (path.size() == 1) { } else if (path.size() == 1) {
KJ_IF_MAYBE(entry, tryGetEntry(path[0])) { auto lock = impl.lockShared();
KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
if (entry->node.is<FileNode>()) { if (entry->node.is<FileNode>()) {
return entry->node.get<FileNode>().file->stat(); return entry->node.get<FileNode>().file->stat();
} else if (entry->node.is<DirectoryNode>()) { } else if (entry->node.is<DirectoryNode>()) {
...@@ -1042,12 +1064,13 @@ public: ...@@ -1042,12 +1064,13 @@ public:
} }
} }
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) override { Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
if (path.size() == 0) { if (path.size() == 0) {
KJ_FAIL_REQUIRE("not a file") { return nullptr; } KJ_FAIL_REQUIRE("not a file") { return nullptr; }
} else if (path.size() == 1) { } else if (path.size() == 1) {
KJ_IF_MAYBE(entry, tryGetEntry(path[0])) { auto lock = impl.lockShared();
return asFile(*entry); KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
return asFile(lock, *entry);
} else { } else {
return nullptr; return nullptr;
} }
...@@ -1060,12 +1083,13 @@ public: ...@@ -1060,12 +1083,13 @@ public:
} }
} }
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) override { Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
if (path.size() == 0) { if (path.size() == 0) {
return clone(); return clone();
} else if (path.size() == 1) { } else if (path.size() == 1) {
KJ_IF_MAYBE(entry, tryGetEntry(path[0])) { auto lock = impl.lockShared();
return asDirectory(*entry); KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
return asDirectory(lock, *entry);
} else { } else {
return nullptr; return nullptr;
} }
...@@ -1078,12 +1102,13 @@ public: ...@@ -1078,12 +1102,13 @@ public:
} }
} }
Maybe<String> tryReadlink(PathPtr path) override { Maybe<String> tryReadlink(PathPtr path) const override {
if (path.size() == 0) { if (path.size() == 0) {
KJ_FAIL_REQUIRE("not a symlink") { return nullptr; } KJ_FAIL_REQUIRE("not a symlink") { return nullptr; }
} else if (path.size() == 1) { } else if (path.size() == 1) {
KJ_IF_MAYBE(entry, tryGetEntry(path[0])) { auto lock = impl.lockShared();
return asSymlink(*entry); KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
return asSymlink(lock, *entry);
} else { } else {
return nullptr; return nullptr;
} }
...@@ -1096,7 +1121,7 @@ public: ...@@ -1096,7 +1121,7 @@ public:
} }
} }
Maybe<Own<File>> tryOpenFile(PathPtr path, WriteMode mode) override { Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const override {
if (path.size() == 0) { if (path.size() == 0) {
if (has(mode, WriteMode::MODIFY)) { if (has(mode, WriteMode::MODIFY)) {
KJ_FAIL_REQUIRE("not a file") { return nullptr; } KJ_FAIL_REQUIRE("not a file") { return nullptr; }
...@@ -1106,8 +1131,9 @@ public: ...@@ -1106,8 +1131,9 @@ public:
KJ_FAIL_REQUIRE("can't replace self") { return nullptr; } KJ_FAIL_REQUIRE("can't replace self") { return nullptr; }
} }
} else if (path.size() == 1) { } else if (path.size() == 1) {
KJ_IF_MAYBE(entry, openEntry(path[0], mode)) { auto lock = impl.lockExclusive();
return asFile(*entry, mode); KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
return asFile(lock, *entry, mode);
} else { } else {
return nullptr; return nullptr;
} }
...@@ -1120,31 +1146,34 @@ public: ...@@ -1120,31 +1146,34 @@ public:
} }
} }
Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) override { Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override {
if (path.size() == 0) { if (path.size() == 0) {
KJ_FAIL_REQUIRE("can't replace self") { break; } KJ_FAIL_REQUIRE("can't replace self") { break; }
} else if (path.size() == 1) { } else if (path.size() == 1) {
return heap<ReplacerImpl<File>>(*this, path[0], newInMemoryFile(clock), mode); // don't need lock just to read the clock ref
return heap<ReplacerImpl<File>>(*this, path[0],
newInMemoryFile(impl.getWithoutLock().clock), mode);
} else { } else {
KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) {
return child->get()->replaceFile(path.slice(1, path.size()), mode); return child->get()->replaceFile(path.slice(1, path.size()), mode);
} }
} }
return heap<BrokenReplacer<File>>(newInMemoryFile(clock)); return heap<BrokenReplacer<File>>(newInMemoryFile(impl.getWithoutLock().clock));
} }
Maybe<Own<Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) override { Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const override {
if (path.size() == 0) { if (path.size() == 0) {
if (has(mode, WriteMode::MODIFY)) { if (has(mode, WriteMode::MODIFY)) {
return addRef(*this); return atomicAddRef(*this);
} else if (has(mode, WriteMode::CREATE)) { } else if (has(mode, WriteMode::CREATE)) {
return nullptr; // already exists return nullptr; // already exists
} else { } else {
KJ_FAIL_REQUIRE("can't replace self") { return nullptr; } KJ_FAIL_REQUIRE("can't replace self") { return nullptr; }
} }
} else if (path.size() == 1) { } else if (path.size() == 1) {
KJ_IF_MAYBE(entry, openEntry(path[0], mode)) { auto lock = impl.lockExclusive();
return asDirectory(*entry, mode); KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
return asDirectory(lock, *entry, mode);
} else { } else {
return nullptr; return nullptr;
} }
...@@ -1157,20 +1186,22 @@ public: ...@@ -1157,20 +1186,22 @@ public:
} }
} }
Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) override { Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override {
if (path.size() == 0) { if (path.size() == 0) {
KJ_FAIL_REQUIRE("can't replace self") { break; } KJ_FAIL_REQUIRE("can't replace self") { break; }
} else if (path.size() == 1) { } else if (path.size() == 1) {
return heap<ReplacerImpl<Directory>>(*this, path[0], newInMemoryDirectory(clock), mode); // don't need lock just to read the clock ref
return heap<ReplacerImpl<Directory>>(*this, path[0],
newInMemoryDirectory(impl.getWithoutLock().clock), mode);
} else { } else {
KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) { KJ_IF_MAYBE(child, tryGetParent(path[0], mode)) {
return child->get()->replaceSubdir(path.slice(1, path.size()), mode); return child->get()->replaceSubdir(path.slice(1, path.size()), mode);
} }
} }
return heap<BrokenReplacer<Directory>>(newInMemoryDirectory(clock)); return heap<BrokenReplacer<Directory>>(newInMemoryDirectory(impl.getWithoutLock().clock));
} }
Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) override { Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const override {
if (path.size() == 0) { if (path.size() == 0) {
if (has(mode, WriteMode::MODIFY)) { if (has(mode, WriteMode::MODIFY)) {
KJ_FAIL_REQUIRE("not a file") { return nullptr; } KJ_FAIL_REQUIRE("not a file") { return nullptr; }
...@@ -1180,8 +1211,9 @@ public: ...@@ -1180,8 +1211,9 @@ public:
KJ_FAIL_REQUIRE("can't replace self") { return nullptr; } KJ_FAIL_REQUIRE("can't replace self") { return nullptr; }
} }
} else if (path.size() == 1) { } else if (path.size() == 1) {
KJ_IF_MAYBE(entry, openEntry(path[0], mode)) { auto lock = impl.lockExclusive();
return asFile(*entry, mode).map(newFileAppender); KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
return asFile(lock, *entry, mode).map(newFileAppender);
} else { } else {
return nullptr; return nullptr;
} }
...@@ -1194,7 +1226,7 @@ public: ...@@ -1194,7 +1226,7 @@ public:
} }
} }
bool trySymlink(PathPtr path, StringPtr content, WriteMode mode) override { bool trySymlink(PathPtr path, StringPtr content, WriteMode mode) const override {
if (path.size() == 0) { if (path.size() == 0) {
if (has(mode, WriteMode::CREATE)) { if (has(mode, WriteMode::CREATE)) {
return false; return false;
...@@ -1202,9 +1234,10 @@ public: ...@@ -1202,9 +1234,10 @@ public:
KJ_FAIL_REQUIRE("can't replace self") { return false; } KJ_FAIL_REQUIRE("can't replace self") { return false; }
} }
} else if (path.size() == 1) { } else if (path.size() == 1) {
KJ_IF_MAYBE(entry, openEntry(path[0], mode)) { auto lock = impl.lockExclusive();
entry->init(SymlinkNode { clock.now(), heapString(content) }); KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
modified(); entry->init(SymlinkNode { lock->clock.now(), heapString(content) });
lock->modified();
return true; return true;
} else { } else {
return false; return false;
...@@ -1218,12 +1251,14 @@ public: ...@@ -1218,12 +1251,14 @@ public:
} }
} }
Own<File> createTemporary() override { Own<const File> createTemporary() const override {
return newInMemoryFile(clock); // Don't need lock just to read the clock ref.
return newInMemoryFile(impl.getWithoutLock().clock);
} }
bool tryTransfer(PathPtr toPath, WriteMode toMode, bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, TransferMode mode) override { const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) const override {
if (toPath.size() == 0) { if (toPath.size() == 0) {
if (has(toMode, WriteMode::CREATE)) { if (has(toMode, WriteMode::CREATE)) {
return false; return false;
...@@ -1233,15 +1268,16 @@ public: ...@@ -1233,15 +1268,16 @@ public:
} else if (toPath.size() == 1) { } else if (toPath.size() == 1) {
// tryTransferChild() needs to at least know the node type, so do an lstat. // tryTransferChild() needs to at least know the node type, so do an lstat.
KJ_IF_MAYBE(meta, fromDirectory.tryLstat(fromPath)) { KJ_IF_MAYBE(meta, fromDirectory.tryLstat(fromPath)) {
KJ_IF_MAYBE(entry, openEntry(toPath[0], toMode)) { 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 // Make sure if we just cerated a new entry, and we don't successfully transfer to it, we
// remove the entry before returning. // remove the entry before returning.
bool needRollback = entry->node == nullptr; bool needRollback = entry->node == nullptr;
KJ_DEFER(if (needRollback) { entries.erase(toPath[0]); }); KJ_DEFER(if (needRollback) { lock->entries.erase(toPath[0]); });
if (tryTransferChild(*entry, meta->type, meta->lastModified, meta->size, if (lock->tryTransferChild(*entry, meta->type, meta->lastModified, meta->size,
fromDirectory, fromPath, mode)) { fromDirectory, fromPath, mode)) {
modified(); lock->modified();
needRollback = false; needRollback = false;
return true; return true;
} else { } else {
...@@ -1267,8 +1303,8 @@ public: ...@@ -1267,8 +1303,8 @@ public:
} }
} }
Maybe<bool> tryTransferTo(Directory& toDirectory, PathPtr toPath, WriteMode toMode, Maybe<bool> tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode) override { PathPtr fromPath, TransferMode mode) const override {
if (fromPath.size() <= 1) { if (fromPath.size() <= 1) {
// If `fromPath` is in this directory (or *is* this directory) then we don't have any // If `fromPath` is in this directory (or *is* this directory) then we don't have any
// optimizations. // optimizations.
...@@ -1289,16 +1325,17 @@ public: ...@@ -1289,16 +1325,17 @@ public:
} }
} }
bool tryRemove(PathPtr path) override { bool tryRemove(PathPtr path) const override {
if (path.size() == 0) { if (path.size() == 0) {
KJ_FAIL_REQUIRE("can't remove self from self") { return false; } KJ_FAIL_REQUIRE("can't remove self from self") { return false; }
} else if (path.size() == 1) { } else if (path.size() == 1) {
auto iter = entries.find(path[0]); auto lock = impl.lockExclusive();
if (iter == entries.end()) { auto iter = lock->entries.find(path[0]);
if (iter == lock->entries.end()) {
return false; return false;
} else { } else {
entries.erase(iter); lock->entries.erase(iter);
modified(); lock->modified();
return true; return true;
} }
} else { } else {
...@@ -1312,16 +1349,16 @@ public: ...@@ -1312,16 +1349,16 @@ public:
private: private:
struct FileNode { struct FileNode {
Own<File> file; Own<const File> file;
}; };
struct DirectoryNode { struct DirectoryNode {
Own<Directory> directory; Own<const Directory> directory;
}; };
struct SymlinkNode { struct SymlinkNode {
Date lastModified; Date lastModified;
String content; String content;
Path parse() { Path parse() const {
KJ_CONTEXT("parsing symlink", content); KJ_CONTEXT("parsing symlink", content);
return Path::parse(content); return Path::parse(content);
} }
...@@ -1333,10 +1370,10 @@ private: ...@@ -1333,10 +1370,10 @@ private:
EntryImpl(String&& name): name(kj::mv(name)) {} EntryImpl(String&& name): name(kj::mv(name)) {}
Own<File> init(FileNode&& value) { Own<const File> init(FileNode&& value) {
return node.init<FileNode>(kj::mv(value)).file->clone(); return node.init<FileNode>(kj::mv(value)).file->clone();
} }
Own<Directory> init(DirectoryNode&& value) { Own<const Directory> init(DirectoryNode&& value) {
return node.init<DirectoryNode>(kj::mv(value)).directory->clone(); return node.init<DirectoryNode>(kj::mv(value)).directory->clone();
} }
void init(SymlinkNode&& value) { void init(SymlinkNode&& value) {
...@@ -1347,37 +1384,31 @@ private: ...@@ -1347,37 +1384,31 @@ private:
return node != nullptr; return node != nullptr;
} }
void set(Own<File>&& value) { void set(Own<const File>&& value) {
node.init<FileNode>(FileNode { kj::mv(value) }); node.init<FileNode>(FileNode { kj::mv(value) });
} }
void set(Own<Directory>&& value) { void set(Own<const Directory>&& value) {
node.init<DirectoryNode>(DirectoryNode { kj::mv(value) }); node.init<DirectoryNode>(DirectoryNode { kj::mv(value) });
} }
}; };
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;
template <typename T> template <typename T>
class ReplacerImpl final: public Replacer<T> { class ReplacerImpl final: public Replacer<T> {
public: public:
ReplacerImpl(InMemoryDirectory& directory, kj::StringPtr name, Own<T> inner, WriteMode mode) ReplacerImpl(const InMemoryDirectory& directory, kj::StringPtr name,
: Replacer<T>(mode), directory(addRef(directory)), name(heapString(name)), Own<const T> inner, WriteMode mode)
: Replacer<T>(mode), directory(atomicAddRef(directory)), name(heapString(name)),
inner(kj::mv(inner)) {} inner(kj::mv(inner)) {}
T& get() override { return *inner; } const T& get() override { return *inner; }
bool tryCommit() override { bool tryCommit() override {
KJ_REQUIRE(!committed, "commit() already called") { return true; } KJ_REQUIRE(!committed, "commit() already called") { return true; }
KJ_IF_MAYBE(entry, directory->openEntry(name, Replacer<T>::mode)) { auto lock = directory->impl.lockExclusive();
KJ_IF_MAYBE(entry, lock->openEntry(name, Replacer<T>::mode)) {
entry->set(inner->clone()); entry->set(inner->clone());
directory->modified(); lock->modified();
return true; return true;
} else { } else {
return false; return false;
...@@ -1386,9 +1417,9 @@ private: ...@@ -1386,9 +1417,9 @@ private:
private: private:
bool committed = false; bool committed = false;
Own<InMemoryDirectory> directory; Own<const InMemoryDirectory> directory;
kj::String name; kj::String name;
Own<T> inner; Own<const T> inner;
}; };
template <typename T> template <typename T>
...@@ -1396,114 +1427,189 @@ private: ...@@ -1396,114 +1427,189 @@ private:
// For recovery path when exceptions are disabled. // For recovery path when exceptions are disabled.
public: public:
BrokenReplacer(Own<T> inner) BrokenReplacer(Own<const T> inner)
: Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY), : Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY),
inner(kj::mv(inner)) {} inner(kj::mv(inner)) {}
T& get() override { return *inner; } const T& get() override { return *inner; }
bool tryCommit() override { return false; } bool tryCommit() override { return false; }
private: private:
Own<T> inner; Own<const T> inner;
}; };
Maybe<EntryImpl&> openEntry(kj::StringPtr name, WriteMode mode) { struct Impl {
// TODO(perf): We could avoid a copy if the entry exists, at the expense of a double-lookup if const Clock& clock;
// it doesn't. Maybe a better map implementation will solve everything?
return openEntry(heapString(name), mode);
}
Maybe<EntryImpl&> openEntry(String&& name, WriteMode mode) { std::map<StringPtr, EntryImpl> entries;
if (has(mode, WriteMode::CREATE)) { // Note: If this changes to a non-sorted map, listNames() and listEntries() must be updated to
EntryImpl entry(kj::mv(name)); // sort their results.
StringPtr nameRef = entry.name;
auto insertResult = entries.insert(std::make_pair(nameRef, kj::mv(entry)));
if (!insertResult.second && !has(mode, WriteMode::MODIFY)) { Date lastModified;
// Entry already existed and MODIFY not specified.
return nullptr;
}
return insertResult.first->second; Impl(const Clock& clock): clock(clock), lastModified(clock.now()) {}
} else if (has(mode, WriteMode::MODIFY)) {
return tryGetEntry(name);
} else {
// Neither CREATE nor MODIFY specified: precondition always fails.
return nullptr;
}
}
kj::Maybe<EntryImpl&> tryGetEntry(kj::StringPtr name) { Maybe<EntryImpl&> openEntry(kj::StringPtr name, WriteMode mode) {
auto iter = entries.find(name); // TODO(perf): We could avoid a copy if the entry exists, at the expense of a double-lookup
if (iter == entries.end()) { // if it doesn't. Maybe a better map implementation will solve everything?
return nullptr; return openEntry(heapString(name), mode);
} else {
return iter->second;
} }
}
kj::Maybe<Own<ReadableDirectory>> tryGetParent(kj::StringPtr name) { Maybe<EntryImpl&> openEntry(String&& name, WriteMode mode) {
KJ_IF_MAYBE(entry, tryGetEntry(name)) { if (has(mode, WriteMode::CREATE)) {
return asDirectory(*entry); EntryImpl entry(kj::mv(name));
} else { StringPtr nameRef = entry.name;
return nullptr; auto insertResult = entries.insert(std::make_pair(nameRef, kj::mv(entry)));
}
}
kj::Maybe<Own<Directory>> tryGetParent(kj::StringPtr name, WriteMode mode) { if (!insertResult.second && !has(mode, WriteMode::MODIFY)) {
// Get a directory which is a parent of the eventual target. If `mode` includes // Entry already existed and MODIFY not specified.
// WriteMode::CREATE_PARENTS, possibly create the parent directory. return nullptr;
}
WriteMode parentMode = has(mode, WriteMode::CREATE) && has(mode, WriteMode::CREATE_PARENT) return insertResult.first->second;
? WriteMode::CREATE | WriteMode::MODIFY // create parent } else if (has(mode, WriteMode::MODIFY)) {
: WriteMode::MODIFY; // don't create parent return tryGetEntry(name);
} else {
// Neither CREATE nor MODIFY specified: precondition always fails.
return nullptr;
}
}
// Possibly create parent. kj::Maybe<const EntryImpl&> tryGetEntry(kj::StringPtr name) const {
KJ_IF_MAYBE(entry, openEntry(name, parentMode)) { auto iter = entries.find(name);
if (entry->node.is<DirectoryNode>()) { if (iter == entries.end()) {
return entry->node.get<DirectoryNode>().directory->clone(); return nullptr;
} else if (entry->node == nullptr) { } else {
modified(); return iter->second;
return entry->init(DirectoryNode { newInMemoryDirectory(clock) });
} }
// Continue on.
} }
if (has(mode, WriteMode::CREATE)) { kj::Maybe<EntryImpl&> tryGetEntry(kj::StringPtr name) {
// CREATE is documented as returning null when the file already exists. In this case, the auto iter = entries.find(name);
// file does NOT exist because the parent directory does not exist or is not a directory. if (iter == entries.end()) {
KJ_FAIL_REQUIRE("parent is not a directory") { return nullptr; } return nullptr;
} else { } else {
return nullptr; return iter->second;
}
} }
}
bool exists(EntryImpl& entry) { 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>()) { if (entry.node.is<SymlinkNode>()) {
return exists(entry.node.get<SymlinkNode>().parse()); auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return exists(newPath);
} else { } else {
return true; return true;
} }
} }
Maybe<Own<ReadableFile>> asFile(EntryImpl& entry) { Maybe<Own<const ReadableFile>> asFile(
kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
if (entry.node.is<FileNode>()) { if (entry.node.is<FileNode>()) {
return entry.node.get<FileNode>().file->clone(); return entry.node.get<FileNode>().file->clone();
} else if (entry.node.is<SymlinkNode>()) { } else if (entry.node.is<SymlinkNode>()) {
return tryOpenFile(entry.node.get<SymlinkNode>().parse()); auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return tryOpenFile(newPath);
} else { } else {
KJ_FAIL_REQUIRE("not a file") { return nullptr; } KJ_FAIL_REQUIRE("not a file") { return nullptr; }
} }
} }
Maybe<Own<ReadableDirectory>> asDirectory(EntryImpl& entry) { Maybe<Own<const ReadableDirectory>> asDirectory(
kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
if (entry.node.is<DirectoryNode>()) { if (entry.node.is<DirectoryNode>()) {
return entry.node.get<DirectoryNode>().directory->clone(); return entry.node.get<DirectoryNode>().directory->clone();
} else if (entry.node.is<SymlinkNode>()) { } else if (entry.node.is<SymlinkNode>()) {
return tryOpenSubdir(entry.node.get<SymlinkNode>().parse()); auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return tryOpenSubdir(newPath);
} else { } else {
KJ_FAIL_REQUIRE("not a directory") { return nullptr; } KJ_FAIL_REQUIRE("not a directory") { return nullptr; }
} }
} }
Maybe<String> asSymlink(EntryImpl& entry) { Maybe<String> asSymlink(kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
if (entry.node.is<SymlinkNode>()) { if (entry.node.is<SymlinkNode>()) {
return heapString(entry.node.get<SymlinkNode>().content); return heapString(entry.node.get<SymlinkNode>().content);
} else { } else {
...@@ -1511,113 +1617,78 @@ private: ...@@ -1511,113 +1617,78 @@ private:
} }
} }
Maybe<Own<File>> asFile(EntryImpl& entry, WriteMode mode) { Maybe<Own<const File>> asFile(kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const {
if (entry.node.is<FileNode>()) { if (entry.node.is<FileNode>()) {
return entry.node.get<FileNode>().file->clone(); return entry.node.get<FileNode>().file->clone();
} else if (entry.node.is<SymlinkNode>()) { } else if (entry.node.is<SymlinkNode>()) {
// CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the // CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the
// target itself can still be created. // target itself can still be created.
return tryOpenFile(entry.node.get<SymlinkNode>().parse(), mode - WriteMode::CREATE_PARENT); auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return tryOpenFile(newPath, mode - WriteMode::CREATE_PARENT);
} else if (entry.node == nullptr) { } else if (entry.node == nullptr) {
KJ_ASSERT(has(mode, WriteMode::CREATE)); KJ_ASSERT(has(mode, WriteMode::CREATE));
modified(); lock->modified();
return entry.init(FileNode { newInMemoryFile(clock) }); return entry.init(FileNode { newInMemoryFile(lock->clock) });
} else { } else {
KJ_FAIL_REQUIRE("not a file") { return nullptr; } KJ_FAIL_REQUIRE("not a file") { return nullptr; }
} }
} }
Maybe<Own<Directory>> asDirectory(EntryImpl& entry, WriteMode mode) { Maybe<Own<const Directory>> asDirectory(
kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const {
if (entry.node.is<DirectoryNode>()) { if (entry.node.is<DirectoryNode>()) {
return entry.node.get<DirectoryNode>().directory->clone(); return entry.node.get<DirectoryNode>().directory->clone();
} else if (entry.node.is<SymlinkNode>()) { } else if (entry.node.is<SymlinkNode>()) {
// CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the // CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the
// target itself can still be created. // target itself can still be created.
return tryOpenSubdir(entry.node.get<SymlinkNode>().parse(), mode - WriteMode::CREATE_PARENT); auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return tryOpenSubdir(newPath, mode - WriteMode::CREATE_PARENT);
} else if (entry.node == nullptr) { } else if (entry.node == nullptr) {
KJ_ASSERT(has(mode, WriteMode::CREATE)); KJ_ASSERT(has(mode, WriteMode::CREATE));
modified(); lock->modified();
return entry.init(DirectoryNode { newInMemoryDirectory(clock) }); return entry.init(DirectoryNode { newInMemoryDirectory(lock->clock) });
} else { } else {
KJ_FAIL_REQUIRE("not a directory") { return nullptr; } KJ_FAIL_REQUIRE("not a directory") { return nullptr; }
} }
} }
void modified() { kj::Maybe<Own<const ReadableDirectory>> tryGetParent(kj::StringPtr name) const {
lastModified = clock.now(); auto lock = impl.lockShared();
KJ_IF_MAYBE(entry, impl.lockShared()->tryGetEntry(name)) {
return asDirectory(lock, *entry);
} else {
return nullptr;
}
} }
bool tryTransferChild(EntryImpl& entry, const FsNode::Type type, kj::Maybe<Date> lastModified, kj::Maybe<Own<const Directory>> tryGetParent(kj::StringPtr name, WriteMode mode) const {
kj::Maybe<uint64_t> size, Directory& fromDirectory, PathPtr fromPath, // Get a directory which is a parent of the eventual target. If `mode` includes
TransferMode mode) { // WriteMode::CREATE_PARENTS, possibly create the parent directory.
switch (type) {
case FsNode::Type::FILE: auto lock = impl.lockExclusive();
KJ_IF_MAYBE(file, fromDirectory.tryOpenFile(fromPath, WriteMode::MODIFY)) {
if (mode == TransferMode::COPY) { WriteMode parentMode = has(mode, WriteMode::CREATE) && has(mode, WriteMode::CREATE_PARENT)
auto copy = newInMemoryFile(clock); ? WriteMode::CREATE | WriteMode::MODIFY // create parent
copy->copy(0, **file, 0, size.orDefault(kj::maxValue)); : WriteMode::MODIFY; // don't create parent
entry.set(kj::mv(copy));
} else { // Possibly create parent.
if (mode == TransferMode::MOVE) { KJ_IF_MAYBE(entry, lock->openEntry(name, parentMode)) {
KJ_ASSERT(fromDirectory.tryRemove(fromPath), "could't move node", fromPath) { if (entry->node.is<DirectoryNode>()) {
return false; return entry->node.get<DirectoryNode>().directory->clone();
} } else if (entry->node == nullptr) {
} lock->modified();
entry.set(kj::mv(*file)); return entry->init(DirectoryNode { newInMemoryDirectory(lock->clock) });
} }
return true; // Continue on.
} else { }
KJ_FAIL_ASSERT("source node deleted concurrently during transfer", fromPath) {
return false; 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.
case FsNode::Type::DIRECTORY: KJ_FAIL_REQUIRE("parent is not a directory") { return nullptr; }
KJ_IF_MAYBE(subdir, fromDirectory.tryOpenSubdir(fromPath, WriteMode::MODIFY)) { } else {
if (mode == TransferMode::COPY) { return nullptr;
auto copy = refcounted<InMemoryDirectory>(clock);
for (auto& subEntry: subdir->get()->listEntries()) {
EntryImpl newEntry(kj::mv(subEntry.name));
Path filename(newEntry.name);
if (!copy->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;
copy->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;
} }
} }
}; };
...@@ -1626,42 +1697,42 @@ private: ...@@ -1626,42 +1697,42 @@ private:
class AppendableFileImpl final: public AppendableFile { class AppendableFileImpl final: public AppendableFile {
public: public:
AppendableFileImpl(Own<File>&& fileParam): file(kj::mv(fileParam)) {} AppendableFileImpl(Own<const File>&& fileParam): file(kj::mv(fileParam)) {}
Own<FsNode> cloneFsNode() override { Own<const FsNode> cloneFsNode() const override {
return heap<AppendableFileImpl>(file->clone()); return heap<AppendableFileImpl>(file->clone());
} }
Maybe<int> getFd() override { Maybe<int> getFd() const override {
return nullptr; return nullptr;
} }
Metadata stat() override { Metadata stat() const override {
return file->stat(); return file->stat();
} }
void sync() override { file->sync(); } void sync() const override { file->sync(); }
void datasync() override { file->datasync(); } void datasync() const override { file->datasync(); }
void write(const void* buffer, size_t size) override { void write(const void* buffer, size_t size) override {
file->write(file->stat().size, arrayPtr(reinterpret_cast<const byte*>(buffer), size)); file->write(file->stat().size, arrayPtr(reinterpret_cast<const byte*>(buffer), size));
} }
private: private:
Own<File> file; Own<const File> file;
}; };
} // namespace } // namespace
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
Own<File> newInMemoryFile(Clock& clock) { Own<File> newInMemoryFile(const Clock& clock) {
return refcounted<InMemoryFile>(clock); return atomicRefcounted<InMemoryFile>(clock);
} }
Own<Directory> newInMemoryDirectory(Clock& clock) { Own<Directory> newInMemoryDirectory(const Clock& clock) {
return refcounted<InMemoryDirectory>(clock); return atomicRefcounted<InMemoryDirectory>(clock);
} }
Own<AppendableFile> newFileAppender(Own<File> inner) { Own<AppendableFile> newFileAppender(Own<const File> inner) {
return heap<AppendableFileImpl>(kj::mv(inner)); return heap<AppendableFileImpl>(kj::mv(inner));
} }
......
...@@ -295,22 +295,26 @@ private: ...@@ -295,22 +295,26 @@ private:
// slower than RAM (which is slower than L3 cache, which is slower than L2, which is slower than // slower than RAM (which is slower than L3 cache, which is slower than L2, which is slower than
// L1). You can't do asynchronous RAM access so why asynchronous filesystem? The only way to // L1). You can't do asynchronous RAM access so why asynchronous filesystem? The only way to
// parallelize these is using threads. // parallelize these is using threads.
//
// All KJ filesystem objects are thread-safe, and so all methods are marked "const" (even write
// methods). Of course, if you concurrently write the same bytes of a file from multiple threads,
// it's unspecified which write will "win".
class FsNode { class FsNode {
// Base class for filesystem node types. // Base class for filesystem node types.
public: public:
Own<FsNode> clone(); Own<const FsNode> clone() const;
// Creates a new object of exactly the same type as this one, pointing at exactly the same // Creates a new object of exactly the same type as this one, pointing at exactly the same
// external object. // external object.
// //
// Under the hood, this will call dup(), so the FD number will not be the same. // Under the hood, this will call dup(), so the FD number will not be the same.
virtual Maybe<int> getFd() { return nullptr; } virtual Maybe<int> getFd() const { return nullptr; }
// Get the underlying Unix file descriptor, if any. Returns nullptr if this object actually isn't // Get the underlying Unix file descriptor, if any. Returns nullptr if this object actually isn't
// wrapping a file descriptor. // wrapping a file descriptor.
virtual Maybe<void*> getWin32Handle() { return nullptr; } virtual Maybe<void*> getWin32Handle() const { return nullptr; }
// Get the underlying Win32 HANDLE, if any. Returns nullptr if this object actually isn't // Get the underlying Win32 HANDLE, if any. Returns nullptr if this object actually isn't
// wrapping a handle. // wrapping a handle.
...@@ -375,10 +379,10 @@ public: ...@@ -375,10 +379,10 @@ public:
// TODO(cleanup): This constructor is redundant in C++14, but needed in C++11. // TODO(cleanup): This constructor is redundant in C++14, but needed in C++11.
}; };
virtual Metadata stat() = 0; virtual Metadata stat() const = 0;
virtual void sync() = 0; virtual void sync() const = 0;
virtual void datasync() = 0; virtual void datasync() const = 0;
// Maps to fsync() and fdatasync() system calls. // Maps to fsync() and fdatasync() system calls.
// //
// Also, when creating or overwriting a file, the first call to sync() atomically links the file // Also, when creating or overwriting a file, the first call to sync() atomically links the file
...@@ -387,29 +391,29 @@ public: ...@@ -387,29 +391,29 @@ public:
// it.) // it.)
protected: protected:
virtual Own<FsNode> cloneFsNode() = 0; virtual Own<const FsNode> cloneFsNode() const = 0;
// Implements clone(). Required to return an object with exactly the same type as this one. // Implements clone(). Required to return an object with exactly the same type as this one.
// Hence, every subclass must implement this. // Hence, every subclass must implement this.
}; };
class ReadableFile: public FsNode { class ReadableFile: public FsNode {
public: public:
Own<ReadableFile> clone(); Own<const ReadableFile> clone() const;
String readAllText(); String readAllText() const;
// Read all text in the file and return as a big string. // Read all text in the file and return as a big string.
Array<byte> readAllBytes(); Array<byte> readAllBytes() const;
// Read all bytes in the file and return as a big byte array. // Read all bytes in the file and return as a big byte array.
// //
// This differs from mmap() in that the read is performed all at once. Future changes to the file // This differs from mmap() in that the read is performed all at once. Future changes to the file
// do not affect the returned copy. Consider using mmap() instead, particularly for large files. // do not affect the returned copy. Consider using mmap() instead, particularly for large files.
virtual size_t read(uint64_t offset, ArrayPtr<byte> buffer) = 0; virtual size_t read(uint64_t offset, ArrayPtr<byte> buffer) const = 0;
// Fills `buffer` with data starting at `offset`. Returns the number of bytes actually read -- // Fills `buffer` with data starting at `offset`. Returns the number of bytes actually read --
// the only time this is less than `buffer.size()` is when EOF occurs mid-buffer. // the only time this is less than `buffer.size()` is when EOF occurs mid-buffer.
virtual Array<const byte> mmap(uint64_t offset, uint64_t size) = 0; virtual Array<const byte> mmap(uint64_t offset, uint64_t size) const = 0;
// Maps the file to memory read-only. The returned array always has exactly the requested size. // Maps the file to memory read-only. The returned array always has exactly the requested size.
// Depending on the capabilities of the OS and filesystem, the mapping may or may not reflect // Depending on the capabilities of the OS and filesystem, the mapping may or may not reflect
// changes that happen to the file after mmap() returns. // changes that happen to the file after mmap() returns.
...@@ -423,7 +427,7 @@ public: ...@@ -423,7 +427,7 @@ public:
// The returned array is always exactly the size requested. However, accessing bytes beyond the // The returned array is always exactly the size requested. However, accessing bytes beyond the
// current end of the file may raise SIGBUS, or may simply return zero. // current end of the file may raise SIGBUS, or may simply return zero.
virtual Array<byte> mmapPrivate(uint64_t offset, uint64_t size) = 0; virtual Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const = 0;
// Like mmap() but returns a view that the caller can modify. Modifications will not be written // Like mmap() but returns a view that the caller can modify. Modifications will not be written
// to the underlying file. Every call to this method returns a unique mapping. Changes made to // to the underlying file. Every call to this method returns a unique mapping. Changes made to
// the underlying file by other clients may or may not be reflected in the mapping -- in fact, // the underlying file by other clients may or may not be reflected in the mapping -- in fact,
...@@ -436,26 +440,26 @@ public: ...@@ -436,26 +440,26 @@ public:
class AppendableFile: public FsNode, public OutputStream { class AppendableFile: public FsNode, public OutputStream {
public: public:
Own<AppendableFile> clone(); Own<const AppendableFile> clone() const;
// All methods are inherited. // All methods are inherited.
}; };
class WritableFileMapping { class WritableFileMapping {
public: public:
virtual ArrayPtr<byte> get() = 0; virtual ArrayPtr<byte> get() const = 0;
// Gets the mapped bytes. The returned array can be modified, and those changes may be written to // Gets the mapped bytes. The returned array can be modified, and those changes may be written to
// the underlying file, but there is no guarantee that they are written unless you subsequently // the underlying file, but there is no guarantee that they are written unless you subsequently
// call changed(). // call changed().
virtual void changed(ArrayPtr<byte> slice) = 0; virtual void changed(ArrayPtr<byte> slice) const = 0;
// Notifies the implementation that the given bytes have changed. For some implementations this // Notifies the implementation that the given bytes have changed. For some implementations this
// may be a no-op while for others it may be necessary in order for the changes to be written // may be a no-op while for others it may be necessary in order for the changes to be written
// back at all. // back at all.
// //
// `slice` must be a slice of `bytes()`. // `slice` must be a slice of `bytes()`.
virtual void sync(ArrayPtr<byte> slice) = 0; virtual void sync(ArrayPtr<byte> slice) const = 0;
// Implies `changed()`, and then waits until the range has actually been written to disk before // Implies `changed()`, and then waits until the range has actually been written to disk before
// returning. // returning.
// //
...@@ -470,31 +474,32 @@ public: ...@@ -470,31 +474,32 @@ public:
class File: public ReadableFile { class File: public ReadableFile {
public: public:
Own<File> clone(); Own<const File> clone() const;
void writeAll(ArrayPtr<const byte> bytes); void writeAll(ArrayPtr<const byte> bytes) const;
void writeAll(StringPtr text); void writeAll(StringPtr text) const;
// Completely replace the file with the given bytes or text. // Completely replace the file with the given bytes or text.
virtual void write(uint64_t offset, ArrayPtr<const byte> data) = 0; virtual void write(uint64_t offset, ArrayPtr<const byte> data) const = 0;
// Write the given data starting at the given offset in the file. // Write the given data starting at the given offset in the file.
virtual void zero(uint64_t offset, uint64_t size) = 0; virtual void zero(uint64_t offset, uint64_t size) const = 0;
// Write zeros to the file, starting at `offset` and continuing for `size` bytes. If the platform // Write zeros to the file, starting at `offset` and continuing for `size` bytes. If the platform
// supports it, this will "punch a hole" in the file, such that blocks that are entirely zeros // supports it, this will "punch a hole" in the file, such that blocks that are entirely zeros
// do not take space on disk. // do not take space on disk.
virtual void truncate(uint64_t size) = 0; virtual void truncate(uint64_t size) const = 0;
// Set the file end pointer to `size`. If `size` is less than the current size, data past the end // Set the file end pointer to `size`. If `size` is less than the current size, data past the end
// is truncated. If `size` is larger than the current size, zeros are added to the end of the // is truncated. If `size` is larger than the current size, zeros are added to the end of the
// file. If the platform supports it, blocks containing all-zeros will not be stored to disk. // file. If the platform supports it, blocks containing all-zeros will not be stored to disk.
virtual Own<WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) = 0; virtual Own<const WritableFileMapping> mmapWritable(uint64_t offset, uint64_t size) const = 0;
// Like ReadableFile::mmap() but returns a mapping for which any changes will be immediately // Like ReadableFile::mmap() but returns a mapping for which any changes will be immediately
// visible in other mappings of the file on the same system and will eventually be written back // visible in other mappings of the file on the same system and will eventually be written back
// to the file. // to the file.
virtual size_t copy(uint64_t offset, ReadableFile& from, uint64_t fromOffset, uint64_t size); virtual size_t copy(uint64_t offset, const ReadableFile& from, uint64_t fromOffset,
uint64_t size) const;
// Copies bytes from one file to another. // Copies bytes from one file to another.
// //
// Copies `size` bytes or to EOF, whichever comes first. Returns the number of bytes actually // Copies `size` bytes or to EOF, whichever comes first. Returns the number of bytes actually
...@@ -510,9 +515,9 @@ class ReadableDirectory: public FsNode { ...@@ -510,9 +515,9 @@ class ReadableDirectory: public FsNode {
// Read-only subset of `Directory`. // Read-only subset of `Directory`.
public: public:
Own<ReadableDirectory> clone(); Own<const ReadableDirectory> clone() const;
virtual Array<String> listNames() = 0; virtual Array<String> listNames() const = 0;
// List the contents of this directory. Does NOT include "." nor "..". // List the contents of this directory. Does NOT include "." nor "..".
struct Entry { struct Entry {
...@@ -526,37 +531,37 @@ public: ...@@ -526,37 +531,37 @@ public:
// Convenience comparison operators to sort entries by name. // Convenience comparison operators to sort entries by name.
}; };
virtual Array<Entry> listEntries() = 0; virtual Array<Entry> listEntries() const = 0;
// List the contents of the directory including the type of each file. On some platforms and // List the contents of the directory including the type of each file. On some platforms and
// filesystems, this is just as fast as listNames(), but on others it may require stat()ing each // filesystems, this is just as fast as listNames(), but on others it may require stat()ing each
// file. // file.
virtual bool exists(PathPtr path) = 0; virtual bool exists(PathPtr path) const = 0;
// Does the specified path exist? // Does the specified path exist?
// //
// If the path is a symlink, the symlink is followed and the return value indicates if the target // If the path is a symlink, the symlink is followed and the return value indicates if the target
// exists. If you want to know if the symlink exists, use lstat(). (This implies that listNames() // exists. If you want to know if the symlink exists, use lstat(). (This implies that listNames()
// may return names for which exists() reports false.) // may return names for which exists() reports false.)
FsNode::Metadata lstat(PathPtr path); FsNode::Metadata lstat(PathPtr path) const;
virtual Maybe<FsNode::Metadata> tryLstat(PathPtr path) = 0; virtual Maybe<FsNode::Metadata> tryLstat(PathPtr path) const = 0;
// Gets metadata about the path. If the path is a symlink, it is not followed -- the metadata // Gets metadata about the path. If the path is a symlink, it is not followed -- the metadata
// describes the symlink itself. `tryLstat()` returns null if the path doesn't exist. // describes the symlink itself. `tryLstat()` returns null if the path doesn't exist.
Own<ReadableFile> openFile(PathPtr path); Own<const ReadableFile> openFile(PathPtr path) const;
virtual Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) = 0; virtual Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const = 0;
// Open a file for reading. // Open a file for reading.
// //
// `tryOpenFile()` returns null if the path doesn't exist. Other errors still throw exceptions. // `tryOpenFile()` returns null if the path doesn't exist. Other errors still throw exceptions.
Own<ReadableDirectory> openSubdir(PathPtr path); Own<const ReadableDirectory> openSubdir(PathPtr path) const;
virtual Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) = 0; virtual Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const = 0;
// Opens a subdirectory. // Opens a subdirectory.
// //
// `tryOpenSubdir()` returns null if the path doesn't exist. Other errors still throw exceptions. // `tryOpenSubdir()` returns null if the path doesn't exist. Other errors still throw exceptions.
String readlink(PathPtr path); String readlink(PathPtr path) const;
virtual Maybe<String> tryReadlink(PathPtr path) = 0; virtual Maybe<String> tryReadlink(PathPtr path) const = 0;
// If `path` is a symlink, reads and returns the link contents. // If `path` is a symlink, reads and returns the link contents.
// //
// Note that tryReadlink() differs subtly from tryOpen*(). For example, tryOpenFile() throws if // Note that tryReadlink() differs subtly from tryOpen*(). For example, tryOpenFile() throws if
...@@ -694,7 +699,7 @@ class Directory: public ReadableDirectory { ...@@ -694,7 +699,7 @@ class Directory: public ReadableDirectory {
// behavior. // behavior.
public: public:
Own<Directory> clone(); Own<const Directory> clone() const;
template <typename T> template <typename T>
class Replacer { class Replacer {
...@@ -722,7 +727,7 @@ public: ...@@ -722,7 +727,7 @@ public:
public: public:
explicit Replacer(WriteMode mode); explicit Replacer(WriteMode mode);
virtual T& get() = 0; virtual const T& get() = 0;
// Gets the File or Directory representing the replacement data. Fill in this object before // Gets the File or Directory representing the replacement data. Fill in this object before
// calling commit(). // calling commit().
...@@ -778,45 +783,45 @@ public: ...@@ -778,45 +783,45 @@ public:
using ReadableDirectory::tryOpenFile; using ReadableDirectory::tryOpenFile;
using ReadableDirectory::tryOpenSubdir; using ReadableDirectory::tryOpenSubdir;
Own<File> openFile(PathPtr path, WriteMode mode); Own<const File> openFile(PathPtr path, WriteMode mode) const;
virtual Maybe<Own<File>> tryOpenFile(PathPtr path, WriteMode mode) = 0; virtual Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const = 0;
// Open a file for writing. // Open a file for writing.
// //
// `tryOpenFile()` returns null if the path is required to exist but doesn't (MODIFY or REPLACE) // `tryOpenFile()` returns null if the path is required to exist but doesn't (MODIFY or REPLACE)
// or if the path is required not to exist but does (CREATE or RACE). // or if the path is required not to exist but does (CREATE or RACE).
virtual Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) = 0; virtual Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const = 0;
// Construct a file which, when ready, will be atomically moved to `path`, replacing whatever // Construct a file which, when ready, will be atomically moved to `path`, replacing whatever
// is there already. See `Replacer<T>` for detalis. // is there already. See `Replacer<T>` for detalis.
// //
// The `CREATE` and `MODIFY` bits of `mode` are not enforced until commit time, hence // The `CREATE` and `MODIFY` bits of `mode` are not enforced until commit time, hence
// `replaceFile()` has no "try" variant. // `replaceFile()` has no "try" variant.
virtual Own<File> createTemporary() = 0; virtual Own<const File> createTemporary() const = 0;
// Create a temporary file backed by this directory's filesystem, but which isn't linked into // Create a temporary file backed by this directory's filesystem, but which isn't linked into
// the directory tree. The file is deleted from disk when all references to it have been dropped. // the directory tree. The file is deleted from disk when all references to it have been dropped.
Own<AppendableFile> appendFile(PathPtr path, WriteMode mode); Own<AppendableFile> appendFile(PathPtr path, WriteMode mode) const;
virtual Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) = 0; virtual Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const = 0;
// Opens the file for appending only. Useful for log files. // Opens the file for appending only. Useful for log files.
// //
// If the underlying filesystem supports it, writes to the file will always be appended even if // If the underlying filesystem supports it, writes to the file will always be appended even if
// other writers are writing to the same file at the same time -- however, some implementations // other writers are writing to the same file at the same time -- however, some implementations
// may instead assume that no other process is changing the file size between writes. // may instead assume that no other process is changing the file size between writes.
Own<Directory> openSubdir(PathPtr path, WriteMode mode); Own<const Directory> openSubdir(PathPtr path, WriteMode mode) const;
virtual Maybe<Own<Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) = 0; virtual Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const = 0;
// Opens a subdirectory for writing. // Opens a subdirectory for writing.
virtual Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) = 0; virtual Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const = 0;
// Construct a directory which, when ready, will be atomically moved to `path`, replacing // Construct a directory which, when ready, will be atomically moved to `path`, replacing
// whatever is there already. See `Replacer<T>` for detalis. // whatever is there already. See `Replacer<T>` for detalis.
// //
// The `CREATE` and `MODIFY` bits of `mode` are not enforced until commit time, hence // The `CREATE` and `MODIFY` bits of `mode` are not enforced until commit time, hence
// `replaceSubdir()` has no "try" variant. // `replaceSubdir()` has no "try" variant.
void symlink(PathPtr linkpath, StringPtr content, WriteMode mode); void symlink(PathPtr linkpath, StringPtr content, WriteMode mode) const;
virtual bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) = 0; virtual bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const = 0;
// Create a symlink. `content` is the raw text which will be written into the symlink node. // Create a symlink. `content` is the raw text which will be written into the symlink node.
// How this text is interpreted is entirely dependent on the filesystem. Note in particular that: // How this text is interpreted is entirely dependent on the filesystem. Note in particular that:
// - Windows will require a path that uses backslashes as the separator. // - Windows will require a path that uses backslashes as the separator.
...@@ -831,15 +836,15 @@ public: ...@@ -831,15 +836,15 @@ public:
// exists. // exists.
void transfer(PathPtr toPath, WriteMode toMode, void transfer(PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode); PathPtr fromPath, TransferMode mode) const;
void transfer(PathPtr toPath, WriteMode toMode, void transfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode); TransferMode mode) const;
virtual bool tryTransfer(PathPtr toPath, WriteMode toMode, virtual bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode); TransferMode mode) const;
virtual Maybe<bool> tryTransferTo(Directory& toDirectory, PathPtr toPath, WriteMode toMode, virtual Maybe<bool> tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode); PathPtr fromPath, TransferMode mode) const;
// Move, link, or copy a file/directory tree from one location to another. // Move, link, or copy a file/directory tree from one location to another.
// //
// Filesystems vary in what kinds of transfers are allowed, especially for TransferMode::LINK, // Filesystems vary in what kinds of transfers are allowed, especially for TransferMode::LINK,
...@@ -856,8 +861,8 @@ public: ...@@ -856,8 +861,8 @@ public:
// `toMode` controls how the target path is created. CREATE_PARENT is honored but EXECUTABLE and // `toMode` controls how the target path is created. CREATE_PARENT is honored but EXECUTABLE and
// PRIVATE have no effect. // PRIVATE have no effect.
void remove(PathPtr path); void remove(PathPtr path) const;
virtual bool tryRemove(PathPtr path) = 0; virtual bool tryRemove(PathPtr path) const = 0;
// Deletes/unlinks the given path. If the path names a directory, it is recursively deleted. // Deletes/unlinks the given path. If the path names a directory, it is recursively deleted.
// //
// tryRemove() returns false if the path doesn't exist; remove() throws in this case. // tryRemove() returns false if the path doesn't exist; remove() throws in this case.
...@@ -882,13 +887,13 @@ private: ...@@ -882,13 +887,13 @@ private:
class Filesystem { class Filesystem {
public: public:
virtual Directory& getRoot() = 0; virtual const Directory& getRoot() const = 0;
// Get the filesystem's root directory, as of the time the Filesystem object was created. // Get the filesystem's root directory, as of the time the Filesystem object was created.
virtual Directory& getCurrent() = 0; virtual const Directory& getCurrent() const = 0;
// Get the filesystem's current directory, as of the time the Filesystem object was created. // Get the filesystem's current directory, as of the time the Filesystem object was created.
virtual PathPtr getCurrentPath() = 0; virtual PathPtr getCurrentPath() const = 0;
// Get the path from the root to the current directory, as of the time the Filesystem object was // Get the path from the root to the current directory, as of the time the Filesystem object was
// created. Note that because a `Directory` does not provide access to its parent, if you want to // created. Note that because a `Directory` does not provide access to its parent, if you want to
// follow `..` from the current directory, you must use `getCurrentPath().eval("..")` or // follow `..` from the current directory, you must use `getCurrentPath().eval("..")` or
...@@ -909,8 +914,8 @@ public: ...@@ -909,8 +914,8 @@ public:
// ======================================================================================= // =======================================================================================
Own<File> newInMemoryFile(Clock& clock); Own<File> newInMemoryFile(const Clock& clock);
Own<Directory> newInMemoryDirectory(Clock& clock); Own<Directory> newInMemoryDirectory(const Clock& clock);
// Construct file and directory objects which reside in-memory. // Construct file and directory objects which reside in-memory.
// //
// InMemoryFile has the following special properties: // InMemoryFile has the following special properties:
...@@ -925,7 +930,7 @@ Own<Directory> newInMemoryDirectory(Clock& clock); ...@@ -925,7 +930,7 @@ Own<Directory> newInMemoryDirectory(Clock& clock);
// - link() and rename() accept any kind of Directory as `fromDirectory` -- it doesn't need to be // - link() and rename() accept any kind of Directory as `fromDirectory` -- it doesn't need to be
// another InMemoryDirectory. However, for rename(), the from path must be a directory. // another InMemoryDirectory. However, for rename(), the from path must be a directory.
Own<AppendableFile> newFileAppender(Own<File> inner); Own<AppendableFile> newFileAppender(Own<const File> inner);
// Creates an AppendableFile by wrapping a File. Note that this implementation assumes it is the // Creates an AppendableFile by wrapping a File. Note that this implementation assumes it is the
// only writer. A correct implementation should always append to the file even if other writes // only writer. A correct implementation should always append to the file even if other writes
// are happening simultaneously, as is achieved with the O_APPEND flag to open(2), but that // are happening simultaneously, as is achieved with the O_APPEND flag to open(2), but that
...@@ -1054,19 +1059,23 @@ inline String PathPtr::toNativeString(bool absolute) const { ...@@ -1054,19 +1059,23 @@ inline String PathPtr::toNativeString(bool absolute) const {
} }
#endif // _WIN32, else #endif // _WIN32, else
inline Own<FsNode> FsNode::clone() { return cloneFsNode().downcast<FsNode>(); } inline Own<const FsNode> FsNode::clone() const { return cloneFsNode().downcast<const FsNode>(); }
inline Own<ReadableFile> ReadableFile::clone() { return cloneFsNode().downcast<ReadableFile>(); } inline Own<const ReadableFile> ReadableFile::clone() const {
inline Own<AppendableFile> AppendableFile::clone() { return cloneFsNode().downcast<const ReadableFile>();
return cloneFsNode().downcast<AppendableFile>(); }
inline Own<const AppendableFile> AppendableFile::clone() const {
return cloneFsNode().downcast<const AppendableFile>();
}
inline Own<const File> File::clone() const { return cloneFsNode().downcast<const File>(); }
inline Own<const ReadableDirectory> ReadableDirectory::clone() const {
return cloneFsNode().downcast<const ReadableDirectory>();
} }
inline Own<File> File::clone() { return cloneFsNode().downcast<File>(); } inline Own<const Directory> Directory::clone() const {
inline Own<ReadableDirectory> ReadableDirectory::clone() { return cloneFsNode().downcast<const Directory>();
return cloneFsNode().downcast<ReadableDirectory>();
} }
inline Own<Directory> Directory::clone() { return cloneFsNode().downcast<Directory>(); }
inline void Directory::transfer( inline void Directory::transfer(
PathPtr toPath, WriteMode toMode, PathPtr fromPath, TransferMode mode) { PathPtr toPath, WriteMode toMode, PathPtr fromPath, TransferMode mode) const {
return transfer(toPath, toMode, *this, fromPath, mode); return transfer(toPath, toMode, *this, fromPath, mode);
} }
......
...@@ -26,12 +26,12 @@ ...@@ -26,12 +26,12 @@
namespace kj { namespace kj {
Clock& nullClock() { const Clock& nullClock() {
class NullClock final: public Clock { class NullClock final: public Clock {
public: public:
Date now() override { return UNIX_EPOCH; } Date now() const override { return UNIX_EPOCH; }
}; };
static NullClock NULL_CLOCK; static KJ_CONSTEXPR(const) NullClock NULL_CLOCK;
return NULL_CLOCK; return NULL_CLOCK;
} }
......
...@@ -63,10 +63,10 @@ constexpr Date UNIX_EPOCH = origin<Date>(); ...@@ -63,10 +63,10 @@ constexpr Date UNIX_EPOCH = origin<Date>();
class Clock { class Clock {
// Interface to read the current date and time. // Interface to read the current date and time.
public: public:
virtual Date now() = 0; virtual Date now() const = 0;
}; };
Clock& nullClock(); const Clock& nullClock();
// A clock which always returns UNIX_EPOCH as the current time. Useful when you don't care about // A clock which always returns UNIX_EPOCH as the current time. Useful when you don't care about
// time. // time.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment