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:
// OsHandle ------------------------------------------------------------------
AutoCloseFd clone() {
AutoCloseFd clone() const {
int fd2;
#ifdef F_DUPFD_CLOEXEC
KJ_SYSCALL_HANDLE_ERRORS(fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 3)) {
......@@ -283,19 +283,19 @@ public:
return result;
}
int getFd() {
int getFd() const {
return fd.get();
}
// FsNode --------------------------------------------------------------------
FsNode::Metadata stat() {
FsNode::Metadata stat() const {
struct stat stats;
KJ_SYSCALL(::fstat(fd, &stats));
return statToMetadata(stats);
}
void sync() {
void sync() const {
#if __APPLE__
// 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
......@@ -306,7 +306,7 @@ public:
#endif
}
void datasync() {
void datasync() const {
// 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.
#if _POSIX_SYNCHRONIZED_IO && !__APPLE__
......@@ -318,7 +318,7 @@ public:
// 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
// spec we are not allowed to assume this.
......@@ -334,7 +334,7 @@ public:
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);
const void* mapping = ::mmap(NULL, range.size, PROT_READ, MAP_SHARED, fd, range.offset);
if (mapping == MAP_FAILED) {
......@@ -344,7 +344,7 @@ public:
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);
void* mapping = ::mmap(NULL, range.size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, range.offset);
if (mapping == MAP_FAILED) {
......@@ -356,7 +356,7 @@ public:
// 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.
// Unfortunately, though, per spec we are not allowed to assume this.
......@@ -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
KJ_SYSCALL_HANDLE_ERRORS(
fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset, size)) {
......@@ -426,7 +426,7 @@ public:
#endif
}
void truncate(uint64_t size) {
void truncate(uint64_t size) const {
KJ_SYSCALL(ftruncate(fd, size));
}
......@@ -434,11 +434,13 @@ public:
public:
WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {}
ArrayPtr<byte> get() override {
return bytes;
ArrayPtr<byte> get() const override {
// const_cast OK because WritableFileMapping does indeed provide a writable view despite
// being const itself.
return arrayPtr(const_cast<byte*>(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(),
"byte range is not part of this mapping");
......@@ -447,7 +449,7 @@ public:
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(),
"byte range is not part of this mapping");
......@@ -460,7 +462,7 @@ public:
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);
void* mapping = ::mmap(NULL, range.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, range.offset);
if (mapping == MAP_FAILED) {
......@@ -471,7 +473,7 @@ public:
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
// the OS. Only returns less than `size` if EOF. Does not account for holes.
......@@ -510,7 +512,8 @@ public:
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()) {
#ifdef FICLONE
if (offset == 0 && fromOffset == 0 && size == kj::maxValue && stat().size == 0) {
......@@ -629,7 +632,7 @@ public:
// ReadableDirectory ---------------------------------------------------------
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>()))>> {
// Seek to start of directory.
KJ_SYSCALL(lseek(fd, 0, SEEK_SET));
......@@ -686,17 +689,17 @@ public:
return result;
}
Array<String> listNames() {
Array<String> listNames() const {
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 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)) {
case ENOENT:
case ENOTDIR:
......@@ -707,7 +710,7 @@ public:
return true;
}
Maybe<FsNode::Metadata> tryLstat(PathPtr path) {
Maybe<FsNode::Metadata> tryLstat(PathPtr path) const {
struct stat stats;
KJ_SYSCALL_HANDLE_ERRORS(fstatat(fd, path.toString().cStr(), &stats, AT_SYMLINK_NOFOLLOW)) {
case ENOENT:
......@@ -719,7 +722,7 @@ public:
return statToMetadata(stats);
}
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) {
Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const {
int newFd;
KJ_SYSCALL_HANDLE_ERRORS(newFd = openat(
fd, path.toString().cStr(), O_RDONLY | MAYBE_O_CLOEXEC)) {
......@@ -738,7 +741,7 @@ public:
return newDiskReadableFile(kj::mv(result));
}
Maybe<AutoCloseFd> tryOpenSubdirInternal(PathPtr path) {
Maybe<AutoCloseFd> tryOpenSubdirInternal(PathPtr path) const {
int newFd;
KJ_SYSCALL_HANDLE_ERRORS(newFd = openat(
fd, path.toString().cStr(), O_RDONLY | MAYBE_O_CLOEXEC | MAYBE_O_DIRECTORY)) {
......@@ -764,11 +767,11 @@ public:
return kj::mv(result);
}
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) {
Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const {
return tryOpenSubdirInternal(path).map(newDiskReadableDirectory);
}
Maybe<String> tryReadlink(PathPtr path) {
Maybe<String> tryReadlink(PathPtr path) const {
size_t trySize = 256;
for (;;) {
KJ_STACK_ARRAY(char, buf, trySize, 256, 4096);
......@@ -799,7 +802,7 @@ public:
// Directory -----------------------------------------------------------------
bool tryMkdir(PathPtr path, WriteMode mode, bool noThrow) {
bool tryMkdir(PathPtr path, WriteMode mode, bool noThrow) const {
// Internal function to make a directory.
auto filename = path.toString();
......@@ -849,7 +852,7 @@ public:
}
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`.
//
// Calls `tryCreate` to actually create the temporary, passing in the desired path. tryCreate()
......@@ -895,7 +898,7 @@ public:
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().
//
// tryCreate() must behave like a syscall which creates the node at the path passed to it,
......@@ -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;
mode_t acl = 0666;
if (has(mode, WriteMode::CREATE)) {
......@@ -1037,7 +1040,7 @@ public:
}
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)) {
// Always clobber. Try it.
KJ_SYSCALL_HANDLE_ERRORS(renameat(fromDirFd, fromPath.cStr(), fd.get(), toPath.cStr())) {
......@@ -1227,7 +1230,7 @@ public:
template <typename T>
class ReplacerImpl final: public Directory::Replacer<T> {
public:
ReplacerImpl(Own<T>&& object, DiskHandle& handle,
ReplacerImpl(Own<const T>&& object, const DiskHandle& handle,
String&& tempPath, String&& path, WriteMode mode)
: Directory::Replacer<T>(mode),
object(kj::mv(object)), handle(handle),
......@@ -1239,7 +1242,7 @@ public:
}
}
T& get() override {
const T& get() override {
return *object;
}
......@@ -1250,8 +1253,8 @@ public:
}
private:
Own<T> object;
DiskHandle& handle;
Own<const T> object;
const DiskHandle& handle;
String tempPath;
String path;
bool committed = false; // true if *successfully* committed (in which case tempPath is gone)
......@@ -1262,22 +1265,22 @@ public:
// For recovery path when exceptions are disabled.
public:
BrokenReplacer(Own<T> inner)
BrokenReplacer(Own<const T> inner)
: Directory::Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY),
inner(kj::mv(inner)) {}
T& get() override { return *inner; }
const T& get() override { return *inner; }
bool tryCommit() override { return false; }
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);
}
Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) {
Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const {
mode_t acl = 0666;
if (has(mode, WriteMode::EXECUTABLE)) {
acl = 0777;
......@@ -1304,7 +1307,7 @@ public:
}
}
Own<File> createTemporary() {
Own<const File> createTemporary() const {
int newFd_;
#if __linux__ && defined(O_TMPFILE)
......@@ -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);
}
Maybe<Own<Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) {
Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const {
// Must create before open.
if (has(mode, WriteMode::CREATE)) {
if (!tryMkdir(path, mode, false)) return nullptr;
......@@ -1358,7 +1361,7 @@ public:
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;
KJ_IF_MAYBE(temp, createNamedTemporary(path, mode,
......@@ -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 symlinkat(content.cStr(), fd, candidatePath.cStr());
});
}
bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath,
TransferMode mode, Directory& self) {
const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode, const Directory& self) const {
KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; }
if (mode == TransferMode::LINK) {
......@@ -1444,7 +1447,7 @@ public:
return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode);
}
bool tryRemove(PathPtr path) {
bool tryRemove(PathPtr path) const {
return rmrf(fd, path.toString());
}
......@@ -1453,15 +1456,15 @@ protected:
};
#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()); \
} \
\
Metadata stat() override { return DiskHandle::stat(); } \
void sync() override { DiskHandle::sync(); } \
void datasync() override { DiskHandle::datasync(); }
Metadata stat() const override { return DiskHandle::stat(); } \
void sync() const override { DiskHandle::sync(); } \
void datasync() const override { DiskHandle::datasync(); }
class DiskReadableFile final: public ReadableFile, public DiskHandle {
public:
......@@ -1469,13 +1472,13 @@ public:
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);
}
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);
}
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);
}
};
......@@ -1488,7 +1491,9 @@ public:
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 {
FdOutputStream::write(pieces);
}
......@@ -1500,29 +1505,30 @@ public:
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);
}
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);
}
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);
}
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);
}
void zero(uint64_t offset, uint64_t size) override {
void zero(uint64_t offset, uint64_t size) const override {
DiskHandle::zero(offset, size);
}
void truncate(uint64_t size) override {
void truncate(uint64_t size) const override {
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);
}
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)) {
return *result;
} else {
......@@ -1537,17 +1543,19 @@ public:
FSNODE_METHODS(DiskReadableDirectory);
Array<String> listNames() override { return DiskHandle::listNames(); }
Array<Entry> listEntries() override { return DiskHandle::listEntries(); }
bool exists(PathPtr path) override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override { return DiskHandle::tryLstat(path); }
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) override {
Array<String> listNames() const override { return DiskHandle::listNames(); }
Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
return DiskHandle::tryLstat(path);
}
Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
return DiskHandle::tryOpenFile(path);
}
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) override {
Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
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 {
......@@ -1556,46 +1564,48 @@ public:
FSNODE_METHODS(DiskDirectory);
Array<String> listNames() override { return DiskHandle::listNames(); }
Array<Entry> listEntries() override { return DiskHandle::listEntries(); }
bool exists(PathPtr path) override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override { return DiskHandle::tryLstat(path); }
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) override {
Array<String> listNames() const override { return DiskHandle::listNames(); }
Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
return DiskHandle::tryLstat(path);
}
Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
return DiskHandle::tryOpenFile(path);
}
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) override {
Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
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);
}
Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) override {
Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override {
return DiskHandle::replaceFile(path, mode);
}
Own<File> createTemporary() override {
Own<const File> createTemporary() const override {
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);
}
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);
}
Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) override {
Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override {
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);
}
bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) override {
const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) const override {
return DiskHandle::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode, *this);
}
// 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);
}
};
......@@ -1607,15 +1617,15 @@ public:
current(openDir(".")),
currentPath(computeCurrentPath()) {}
Directory& getRoot() override {
const Directory& getRoot() const override {
return root;
}
Directory& getCurrent() override {
const Directory& getCurrent() const override {
return current;
}
PathPtr getCurrentPath() override {
PathPtr getCurrentPath() const override {
return currentPath;
}
......
......@@ -349,26 +349,26 @@ public:
AutoCloseHandle handle;
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);
}
// OsHandle ------------------------------------------------------------------
AutoCloseHandle clone() {
AutoCloseHandle clone() const {
HANDLE newHandle;
KJ_WIN32(DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &newHandle,
0, FALSE, DUPLICATE_SAME_ACCESS));
return AutoCloseHandle(newHandle);
}
HANDLE getWin32Handle() {
HANDLE getWin32Handle() const {
return handle.get();
}
// FsNode --------------------------------------------------------------------
FsNode::Metadata stat() {
FsNode::Metadata stat() const {
BY_HANDLE_FILE_INFORMATION stats;
KJ_WIN32(GetFileInformationByHandle(handle, &stats));
auto metadata = statToMetadata(stats);
......@@ -391,12 +391,12 @@ public:
return metadata;
}
void sync() { KJ_WIN32(FlushFileBuffers(handle)); }
void datasync() { KJ_WIN32(FlushFileBuffers(handle)); }
void sync() const { KJ_WIN32(FlushFileBuffers(handle)); }
void datasync() const { KJ_WIN32(FlushFileBuffers(handle)); }
// 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,
// this is not documented, and it's unclear whether we can rely on it.
......@@ -427,14 +427,14 @@ public:
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);
const void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_READ);
return Array<const byte>(reinterpret_cast<const byte*>(mapping) + (offset - range.offset),
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);
void* mapping = win32Mmap(handle, range, PAGE_READONLY, FILE_MAP_COPY);
return Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset),
......@@ -443,7 +443,7 @@ public:
// 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.
// Unfortunately, though, this is not documented, and it's unclear whether we can rely on it.
......@@ -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;
memset(&info, 0, sizeof(info));
info.FileOffset.QuadPart = offset;
......@@ -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()
// lets us avoid this!
FILE_END_OF_FILE_INFO info;
......@@ -535,18 +535,20 @@ public:
public:
WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {}
ArrayPtr<byte> get() override {
return bytes;
ArrayPtr<byte> get() const override {
// const_cast OK because WritableFileMapping does indeed provide a writable view despite
// being const itself.
return arrayPtr(const_cast<byte*>(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(),
"byte range is not part of this mapping");
// 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(),
"byte range is not part of this mapping");
......@@ -560,7 +562,7 @@ public:
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);
void* mapping = win32Mmap(handle, range, PAGE_READWRITE, FILE_MAP_ALL_ACCESS);
auto array = Array<byte>(reinterpret_cast<byte*>(mapping) + (offset - range.offset),
......@@ -573,7 +575,7 @@ public:
// ReadableDirectory ---------------------------------------------------------
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>()))>> {
PathPtr path = KJ_ASSERT_NONNULL(dirPath);
auto glob = join16(path.forWin32Api(true), L"*");
......@@ -610,17 +612,17 @@ public:
return result;
}
Array<String> listNames() {
Array<String> listNames() const {
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 ReadableDirectory::Entry { type, heapString(name), };
});
}
bool exists(PathPtr path) {
bool exists(PathPtr path) const {
DWORD result = GetFileAttributesW(nativePath(path).begin());
if (result == INVALID_FILE_ATTRIBUTES) {
auto error = GetLastError();
......@@ -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
// symlink rather than info about the target.
WIN32_FIND_DATAW data;
......@@ -651,7 +653,7 @@ public:
}
}
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) {
Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const {
HANDLE newHandle;
KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW(
nativePath(path).begin(),
......@@ -671,7 +673,7 @@ public:
return newDiskReadableFile(kj::AutoCloseHandle(newHandle));
}
Maybe<AutoCloseHandle> tryOpenSubdirInternal(PathPtr path) {
Maybe<AutoCloseHandle> tryOpenSubdirInternal(PathPtr path) const {
HANDLE newHandle;
KJ_WIN32_HANDLE_ERRORS(newHandle = CreateFileW(
nativePath(path).begin(),
......@@ -702,13 +704,13 @@ public:
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 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
// 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.
......@@ -727,7 +729,7 @@ public:
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.
auto filename = nativePath(path);
......@@ -777,7 +779,7 @@ public:
kj::Maybe<Array<wchar_t>> createNamedTemporary(
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`.
//
// Calls `tryCreate` to actually create the temporary, passing in the desired path. tryCreate()
......@@ -826,12 +828,13 @@ public:
}
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;
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().
//
// tryCreate() must behave like a win32 call which creates the node at the path passed to it,
......@@ -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;
if (has(mode, WriteMode::MODIFY)) {
if (has(mode, WriteMode::CREATE)) {
......@@ -969,7 +972,7 @@ public:
bool tryCommitReplacement(
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`.
auto wToPath = nativePath(toPath);
......@@ -1055,7 +1058,7 @@ public:
template <typename T>
class ReplacerImpl final: public Directory::Replacer<T> {
public:
ReplacerImpl(Own<T>&& object, DiskHandle& parentDirectory,
ReplacerImpl(Own<T>&& object, const DiskHandle& parentDirectory,
Array<wchar_t>&& tempPath, Path&& path, WriteMode mode)
: Directory::Replacer<T>(mode),
object(kj::mv(object)), parentDirectory(parentDirectory),
......@@ -1074,7 +1077,7 @@ public:
}
}
T& get() override {
const T& get() override {
return *object;
}
......@@ -1111,7 +1114,7 @@ public:
private:
Own<T> object;
DiskHandle& parentDirectory;
const DiskHandle& parentDirectory;
Array<wchar_t> tempPath;
Path path;
bool committed = false; // true if *successfully* committed (in which case tempPath is gone)
......@@ -1122,22 +1125,22 @@ public:
// For recovery path when exceptions are disabled.
public:
BrokenReplacer(Own<T> inner)
BrokenReplacer(Own<const T> inner)
: Directory::Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY),
inner(kj::mv(inner)) {}
T& get() override { return *inner; }
const T& get() override { return *inner; }
bool tryCommit() override { return false; }
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);
}
Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) {
Own<Directory::Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const {
HANDLE newHandle_;
KJ_IF_MAYBE(temp, createNamedTemporary(path, mode,
[&](const wchar_t* candidatePath) {
......@@ -1160,7 +1163,7 @@ public:
}
}
Own<File> createTemporary() {
Own<const File> createTemporary() const {
HANDLE newHandle_;
KJ_IF_MAYBE(temp, createNamedTemporary(Path("unnamed"), WriteMode::CREATE,
[&](const wchar_t* candidatePath) {
......@@ -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);
}
Maybe<Own<Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) {
Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const {
// Must create before open.
if (has(mode, WriteMode::CREATE)) {
if (!tryMkdir(path, mode, false)) return nullptr;
......@@ -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;
KJ_IF_MAYBE(temp, createNamedTemporary(path, mode, kjTempPath,
[&](const wchar_t* candidatePath) {
......@@ -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'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
......@@ -1242,8 +1245,8 @@ public:
}
bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath,
TransferMode mode, Directory& self) {
const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode, const Directory& self) const {
KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; }
// Try to get the "from" path.
......@@ -1251,7 +1254,7 @@ public:
#if !KJ_NO_RTTI
// Oops, dynamicDowncastIfAvailable() doesn't work since this isn't a downcast, it's a
// side-cast...
if (auto dh = dynamic_cast<DiskHandle*>(&fromDirectory)) {
if (auto dh = dynamic_cast<const DiskHandle*>(&fromDirectory)) {
rawFromPath = dh->nativePath(fromPath);
} else
#endif
......@@ -1302,35 +1305,35 @@ public:
return self.Directory::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode);
}
bool tryRemove(PathPtr path) {
bool tryRemove(PathPtr path) const {
return rmrf(nativePath(path));
}
};
#define FSNODE_METHODS \
Maybe<void*> getWin32Handle() override { return DiskHandle::getWin32Handle(); } \
Maybe<void*> getWin32Handle() const override { return DiskHandle::getWin32Handle(); } \
\
Metadata stat() override { return DiskHandle::stat(); } \
void sync() override { DiskHandle::sync(); } \
void datasync() override { DiskHandle::datasync(); }
Metadata stat() const override { return DiskHandle::stat(); } \
void sync() const override { DiskHandle::sync(); } \
void datasync() const override { DiskHandle::datasync(); }
class DiskReadableFile final: public ReadableFile, public DiskHandle {
public:
DiskReadableFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {}
Own<FsNode> cloneFsNode() override {
Own<const FsNode> cloneFsNode() const override {
return heap<DiskReadableFile>(DiskHandle::clone());
}
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);
}
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);
}
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);
}
};
......@@ -1341,7 +1344,7 @@ public:
: DiskHandle(kj::mv(handle), nullptr),
stream(DiskHandle::handle.get()) {}
Own<FsNode> cloneFsNode() override {
Own<const FsNode> cloneFsNode() const override {
return heap<DiskAppendableFile>(DiskHandle::clone());
}
......@@ -1360,32 +1363,32 @@ class DiskFile final: public File, public DiskHandle {
public:
DiskFile(AutoCloseHandle&& handle): DiskHandle(kj::mv(handle), nullptr) {}
Own<FsNode> cloneFsNode() override {
Own<const FsNode> cloneFsNode() const override {
return heap<DiskFile>(DiskHandle::clone());
}
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);
}
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);
}
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);
}
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);
}
void zero(uint64_t offset, uint64_t size) override {
void zero(uint64_t offset, uint64_t size) const override {
DiskHandle::zero(offset, size);
}
void truncate(uint64_t size) override {
void truncate(uint64_t size) const override {
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);
}
// copy() is not optimized on Windows.
......@@ -1396,23 +1399,25 @@ public:
DiskReadableDirectory(AutoCloseHandle&& handle, Path&& 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());
}
FSNODE_METHODS
Array<String> listNames() override { return DiskHandle::listNames(); }
Array<Entry> listEntries() override { return DiskHandle::listEntries(); }
bool exists(PathPtr path) override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override { return DiskHandle::tryLstat(path); }
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) override {
Array<String> listNames() const override { return DiskHandle::listNames(); }
Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
return DiskHandle::tryLstat(path);
}
Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
return DiskHandle::tryOpenFile(path);
}
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) override {
Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
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 {
......@@ -1420,41 +1425,41 @@ public:
DiskDirectoryBase(AutoCloseHandle&& handle, Path&& path)
: DiskHandle(kj::mv(handle), kj::mv(path)) {}
bool exists(PathPtr path) override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override { return DiskHandle::tryLstat(path); }
Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) override {
bool exists(PathPtr path) const override { return DiskHandle::exists(path); }
Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override { return DiskHandle::tryLstat(path); }
Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const override {
return DiskHandle::tryOpenFile(path);
}
Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) override {
Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const override {
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);
}
Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) override {
Own<Replacer<File>> replaceFile(PathPtr path, WriteMode mode) const override {
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);
}
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);
}
Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) override {
Own<Replacer<Directory>> replaceSubdir(PathPtr path, WriteMode mode) const override {
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);
}
bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) override {
const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) const override {
return DiskHandle::tryTransfer(toPath, toMode, fromDirectory, fromPath, mode, *this);
}
// 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);
}
};
......@@ -1464,15 +1469,15 @@ public:
DiskDirectory(AutoCloseHandle&& handle, Path&& 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());
}
FSNODE_METHODS
Array<String> listNames() override { return DiskHandle::listNames(); }
Array<Entry> listEntries() override { return DiskHandle::listEntries(); }
Own<File> createTemporary() override {
Array<String> listNames() const override { return DiskHandle::listNames(); }
Array<Entry> listEntries() const override { return DiskHandle::listEntries(); }
Own<const File> createTemporary() const override {
return DiskHandle::createTemporary();
}
};
......@@ -1487,20 +1492,20 @@ class RootDiskDirectory final: public DiskDirectoryBase {
public:
RootDiskDirectory(): DiskDirectoryBase(nullptr, Path(nullptr)) {}
Own<FsNode> cloneFsNode() override {
Own<const FsNode> cloneFsNode() const override {
return heap<RootDiskDirectory>();
}
Metadata stat() override {
Metadata stat() const override {
return { Type::DIRECTORY, 0, 0, UNIX_EPOCH, 1, 0 };
}
void sync() override {}
void datasync() override {}
void sync() const override {}
void datasync() const override {}
Array<String> listNames() override {
Array<String> listNames() const override {
return KJ_MAP(e, listEntries()) { return kj::mv(e.name); };
}
Array<Entry> listEntries() override {
Array<Entry> listEntries() const override {
DWORD drives = GetLogicalDrives();
if (drives == 0) {
KJ_FAIL_WIN32("GetLogicalDrives()", GetLastError()) { return nullptr; }
......@@ -1517,7 +1522,7 @@ public:
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)");
}
};
......@@ -1531,15 +1536,15 @@ public:
"path returned by GetCurrentDirectory() doesn't exist?"),
kj::mv(currentPath)) {}
Directory& getRoot() override {
const Directory& getRoot() const override {
return root;
}
Directory& getCurrent() override {
const Directory& getCurrent() const override {
return current;
}
PathPtr getCurrentPath() override {
PathPtr getCurrentPath() const override {
return KJ_ASSERT_NONNULL(current.dirPath);
}
......
......@@ -264,13 +264,13 @@ public:
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);
time += 1 * SECONDS;
}
void expectUnchanged(FsNode& file) {
void expectUnchanged(const FsNode& file) {
KJ_EXPECT(file.stat().lastModified != time);
}
......
......@@ -25,6 +25,7 @@
#include "one-of.h"
#include "encoding.h"
#include "refcount.h"
#include "mutex.h"
#include <map>
namespace kj {
......@@ -482,7 +483,7 @@ bool Path::isWin32Special(StringPtr part) {
// =======================================================================================
String ReadableFile::readAllText() {
String ReadableFile::readAllText() const {
String result = heapString(stat().size);
size_t n = read(0, result.asBytes());
if (n < result.size()) {
......@@ -492,7 +493,7 @@ String ReadableFile::readAllText() {
return result;
}
Array<byte> ReadableFile::readAllBytes() {
Array<byte> ReadableFile::readAllBytes() const {
Array<byte> result = heapArray<byte>(stat().size);
size_t n = read(0, result.asBytes());
if (n < result.size()) {
......@@ -502,16 +503,17 @@ Array<byte> ReadableFile::readAllBytes() {
return result;
}
void File::writeAll(ArrayPtr<const byte> bytes) {
void File::writeAll(ArrayPtr<const byte> bytes) const {
truncate(0);
write(0, bytes);
}
void File::writeAll(StringPtr text) {
void File::writeAll(StringPtr text) const {
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];
size_t result = 0;
......@@ -531,7 +533,7 @@ size_t File::copy(uint64_t offset, ReadableFile& from, uint64_t fromOffset, uint
return result;
}
FsNode::Metadata ReadableDirectory::lstat(PathPtr path) {
FsNode::Metadata ReadableDirectory::lstat(PathPtr path) const {
KJ_IF_MAYBE(meta, tryLstat(path)) {
return *meta;
} else {
......@@ -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)) {
return kj::mv(*file);
} else {
......@@ -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)) {
return kj::mv(*dir);
} else {
......@@ -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)) {
return kj::mv(*p);
} else {
......@@ -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)) {
return kj::mv(*f);
} else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
......@@ -583,7 +585,7 @@ Own<File> Directory::openFile(PathPtr path, WriteMode mode) {
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)) {
return kj::mv(*f);
} else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
......@@ -599,7 +601,7 @@ Own<AppendableFile> Directory::appendFile(PathPtr path, WriteMode mode) {
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)) {
return kj::mv(*f);
} else if (has(mode, WriteMode::CREATE) && !has(mode, WriteMode::MODIFY)) {
......@@ -615,7 +617,7 @@ Own<Directory> Directory::openSubdir(PathPtr path, WriteMode mode) {
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 (has(mode, WriteMode::CREATE)) {
KJ_FAIL_REQUIRE("path already exsits", linkpath) { break; }
......@@ -627,8 +629,8 @@ void Directory::symlink(PathPtr linkpath, StringPtr content, WriteMode mode) {
}
void Directory::transfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) {
const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) const {
if (!tryTransfer(toPath, toMode, fromDirectory, fromPath, mode)) {
if (has(toMode, WriteMode::CREATE)) {
KJ_FAIL_REQUIRE("toPath already exists or fromPath doesn't exist", toPath, fromPath) {
......@@ -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,
ReadableDirectory& from, PathPtr fromPath,
static bool tryCopyDirectoryEntry(const Directory& to, PathPtr toPath, WriteMode toMode,
const ReadableDirectory& from, PathPtr fromPath,
FsNode::Type type, bool atomic) {
// TODO(cleanup): Make this reusable?
......@@ -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()) {
Path subPath(kj::mv(entry.name));
tryCopyDirectoryEntry(to, subPath, WriteMode::CREATE, from, subPath, entry.type, false);
......@@ -707,8 +709,8 @@ static void copyContents(Directory& to, ReadableDirectory& from) {
}
bool Directory::tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) {
const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) const {
KJ_REQUIRE(toPath.size() > 0, "can't replace self") { return false; }
// First try reversing.
......@@ -739,12 +741,12 @@ bool Directory::tryTransfer(PathPtr toPath, WriteMode toMode,
KJ_UNREACHABLE;
}
Maybe<bool> Directory::tryTransferTo(Directory& toDirectory, PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode) {
Maybe<bool> Directory::tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode) const {
return nullptr;
}
void Directory::remove(PathPtr path) {
void Directory::remove(PathPtr path) const {
if (!tryRemove(path)) {
KJ_FAIL_REQUIRE("path to remove doesn't exist", path) { break; }
}
......@@ -766,47 +768,50 @@ void Directory::commitFailed(WriteMode mode) {
namespace {
class InMemoryFile final: public File, public Refcounted {
class InMemoryFile final: public File, public AtomicRefcounted {
public:
InMemoryFile(Clock& clock): clock(clock), lastModified(clock.now()) {}
InMemoryFile(const Clock& clock): impl(clock) {}
Own<FsNode> cloneFsNode() override {
return addRef(*this);
Own<const FsNode> cloneFsNode() const override {
return atomicAddRef(*this);
}
Maybe<int> getFd() override {
Maybe<int> getFd() const override {
return nullptr;
}
Metadata stat() override {
Metadata stat() const override {
auto lock = impl.lockShared();
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 datasync() override {}
void sync() const override {}
void datasync() const override {}
// no-ops
size_t read(uint64_t offset, ArrayPtr<byte> buffer) override {
if (offset >= size) {
size_t read(uint64_t offset, ArrayPtr<byte> buffer) const override {
auto lock = impl.lockShared();
if (offset >= lock->size) {
// Entirely out-of-range.
return 0;
}
size_t readSize = kj::min(buffer.size(), size - offset);
memcpy(buffer.begin(), bytes.begin() + offset, readSize);
size_t readSize = kj::min(buffer.size(), lock->size - offset);
memcpy(buffer.begin(), lock->bytes.begin() + offset, readSize);
return readSize;
}
Array<const byte> mmap(uint64_t offset, uint64_t size) override {
Array<const byte> mmap(uint64_t offset, uint64_t size) const override {
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));
return Array<const byte>(bytes.begin() + offset, size, *disposer);
ArrayDisposer* disposer = new MmapDisposer(atomicAddRef(*this));
return Array<const byte>(lock->bytes.begin() + offset, size, *disposer);
}
Array<byte> mmapPrivate(uint64_t offset, uint64_t size) override {
Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override {
// Return a copy.
// Allocate exactly the size requested.
......@@ -823,47 +828,51 @@ public:
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;
modified();
auto lock = impl.lockExclusive();
lock->modified();
uint64_t end = offset + data.size();
KJ_REQUIRE(end >= offset, "write() request overflows uint64");
ensureCapacity(end);
size = kj::max(size, end);
memcpy(bytes.begin() + offset, data.begin(), data.size());
lock->ensureCapacity(end);
lock->size = kj::max(lock->size, end);
memcpy(lock->bytes.begin() + offset, data.begin(), data.size());
}
void zero(uint64_t offset, uint64_t zeroSize) override {
void zero(uint64_t offset, uint64_t zeroSize) const override {
if (zeroSize == 0) return;
modified();
auto lock = impl.lockExclusive();
lock->modified();
uint64_t end = offset + zeroSize;
KJ_REQUIRE(end >= offset, "zero() request overflows uint64");
ensureCapacity(end);
size = kj::max(size, end);
memset(bytes.begin() + offset, 0, zeroSize);
lock->ensureCapacity(end);
lock->size = kj::max(lock->size, end);
memset(lock->bytes.begin() + offset, 0, zeroSize);
}
void truncate(uint64_t newSize) override {
if (newSize < size) {
modified();
memset(bytes.begin() + newSize, 0, size - newSize);
size = newSize;
} else if (newSize > size) {
modified();
ensureCapacity(newSize);
size = newSize;
void truncate(uint64_t newSize) const override {
auto lock = impl.lockExclusive();
if (newSize < lock->size) {
lock->modified();
memset(lock->bytes.begin() + newSize, 0, lock->size - newSize);
lock->size = newSize;
} else if (newSize > lock->size) {
lock->modified();
lock->ensureCapacity(newSize);
lock->size = newSize;
}
}
Own<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;
KJ_REQUIRE(end >= offset, "mmapWritable() request overflows uint64");
ensureCapacity(end);
return heap<WritableFileMappingImpl>(addRef(*this), bytes.slice(offset, end));
auto lock = impl.lockExclusive();
lock->ensureCapacity(end);
return heap<WritableFileMappingImpl>(atomicAddRef(*this), lock->bytes.slice(offset, end));
}
size_t copy(uint64_t offset, ReadableFile& from,
uint64_t fromOffset, uint64_t copySize) override {
size_t copy(uint64_t offset, const ReadableFile& from,
uint64_t fromOffset, uint64_t copySize) const override {
size_t fromFileSize = from.stat().size;
if (fromFileSize <= fromOffset) return 0;
......@@ -871,25 +880,30 @@ public:
copySize = kj::min(copySize, fromFileSize - fromOffset);
if (copySize == 0) return 0;
auto lock = impl.lockExclusive();
// Allocate space for the copy.
uint64_t end = offset + copySize;
ensureCapacity(end);
lock->ensureCapacity(end);
// Read directly into our backing store.
size_t n = from.read(fromOffset, bytes.slice(offset, end));
size = kj::max(size, offset + n);
size_t n = from.read(fromOffset, lock->bytes.slice(offset, end));
lock->size = kj::max(lock->size, offset + n);
modified();
lock->modified();
return n;
}
private:
Clock& clock;
struct Impl {
const Clock& clock;
Array<byte> bytes;
size_t size = 0; // bytes may be larger than this to accommodate mmaps
Date lastModified;
uint mmapCount = 0; // number of mappings outstanding
Impl(const Clock& clock): clock(clock), lastModified(clock.now()) {}
void ensureCapacity(size_t capacity) {
if (bytes.size() < capacity) {
KJ_ASSERT(mmapCount == 0,
......@@ -905,14 +919,16 @@ private:
void modified() {
lastModified = clock.now();
}
};
kj::MutexGuarded<Impl> impl;
class MmapDisposer final: public ArrayDisposer {
public:
MmapDisposer(Own<InMemoryFile>&& refParam): ref(kj::mv(refParam)) {
++ref->mmapCount;
MmapDisposer(Own<const InMemoryFile>&& refParam): ref(kj::mv(refParam)) {
++ref->impl.getAlreadyLockedExclusive().mmapCount;
}
~MmapDisposer() noexcept(false) {
--ref->mmapCount;
--ref->impl.lockExclusive()->mmapCount;
}
void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
......@@ -921,67 +937,71 @@ private:
}
private:
Own<InMemoryFile> ref;
Own<const InMemoryFile> ref;
};
class WritableFileMappingImpl final: public WritableFileMapping {
public:
WritableFileMappingImpl(Own<InMemoryFile>&& refParam, ArrayPtr<byte> range)
WritableFileMappingImpl(Own<const InMemoryFile>&& refParam, ArrayPtr<byte> range)
: ref(kj::mv(refParam)), range(range) {
++ref->mmapCount;
++ref->impl.getAlreadyLockedExclusive().mmapCount;
}
~WritableFileMappingImpl() noexcept(false) {
--ref->mmapCount;
--ref->impl.lockExclusive()->mmapCount;
}
ArrayPtr<byte> get() override {
return range;
ArrayPtr<byte> get() const override {
// const_cast OK because WritableFileMapping does indeed provide a writable view despite
// being const itself.
return arrayPtr(const_cast<byte*>(range.begin()), range.size());
}
void changed(ArrayPtr<byte> slice) override {
ref->modified();
void changed(ArrayPtr<byte> slice) const override {
ref->impl.lockExclusive()->modified();
}
void sync(ArrayPtr<byte> slice) override {
ref->modified();
void sync(ArrayPtr<byte> slice) const override {
ref->impl.lockExclusive()->modified();
}
private:
Own<InMemoryFile> ref;
Own<const InMemoryFile> ref;
ArrayPtr<byte> range;
};
};
// -----------------------------------------------------------------------------
class InMemoryDirectory final: public Directory, public Refcounted {
class InMemoryDirectory final: public Directory, public AtomicRefcounted {
public:
InMemoryDirectory(Clock& clock)
: clock(clock), lastModified(clock.now()) {}
InMemoryDirectory(const Clock& clock): impl(clock) {}
Own<FsNode> cloneFsNode() override {
return addRef(*this);
Own<const FsNode> cloneFsNode() const override {
return atomicAddRef(*this);
}
Maybe<int> getFd() override {
Maybe<int> getFd() const override {
return nullptr;
}
Metadata stat() override {
Metadata stat() const override {
auto lock = impl.lockShared();
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 datasync() override {}
void sync() const override {}
void datasync() const override {}
// no-ops
Array<String> listNames() override {
return KJ_MAP(e, entries) { return heapString(e.first); };
Array<String> listNames() const override {
auto lock = impl.lockShared();
return KJ_MAP(e, lock->entries) { return heapString(e.first); };
}
Array<Entry> listEntries() override {
return KJ_MAP(e, entries) {
Array<Entry> listEntries() const override {
auto lock = impl.lockShared();
return KJ_MAP(e, lock->entries) {
FsNode::Type type;
if (e.second.node.is<SymlinkNode>()) {
type = FsNode::Type::SYMLINK;
......@@ -996,12 +1016,13 @@ public:
};
}
bool exists(PathPtr path) override {
bool exists(PathPtr path) const override {
if (path.size() == 0) {
return true;
} else if (path.size() == 1) {
KJ_IF_MAYBE(entry, tryGetEntry(path[0])) {
return exists(*entry);
auto lock = impl.lockShared();
KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
return exists(lock, *entry);
} else {
return false;
}
......@@ -1014,11 +1035,12 @@ public:
}
}
Maybe<FsNode::Metadata> tryLstat(PathPtr path) override {
Maybe<FsNode::Metadata> tryLstat(PathPtr path) const override {
if (path.size() == 0) {
return stat();
} 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>()) {
return entry->node.get<FileNode>().file->stat();
} else if (entry->node.is<DirectoryNode>()) {
......@@ -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) {
KJ_FAIL_REQUIRE("not a file") { return nullptr; }
} else if (path.size() == 1) {
KJ_IF_MAYBE(entry, tryGetEntry(path[0])) {
return asFile(*entry);
auto lock = impl.lockShared();
KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
return asFile(lock, *entry);
} else {
return nullptr;
}
......@@ -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) {
return clone();
} else if (path.size() == 1) {
KJ_IF_MAYBE(entry, tryGetEntry(path[0])) {
return asDirectory(*entry);
auto lock = impl.lockShared();
KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
return asDirectory(lock, *entry);
} else {
return nullptr;
}
......@@ -1078,12 +1102,13 @@ public:
}
}
Maybe<String> tryReadlink(PathPtr path) override {
Maybe<String> tryReadlink(PathPtr path) const override {
if (path.size() == 0) {
KJ_FAIL_REQUIRE("not a symlink") { return nullptr; }
} else if (path.size() == 1) {
KJ_IF_MAYBE(entry, tryGetEntry(path[0])) {
return asSymlink(*entry);
auto lock = impl.lockShared();
KJ_IF_MAYBE(entry, lock->tryGetEntry(path[0])) {
return asSymlink(lock, *entry);
} else {
return nullptr;
}
......@@ -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 (has(mode, WriteMode::MODIFY)) {
KJ_FAIL_REQUIRE("not a file") { return nullptr; }
......@@ -1106,8 +1131,9 @@ public:
KJ_FAIL_REQUIRE("can't replace self") { return nullptr; }
}
} else if (path.size() == 1) {
KJ_IF_MAYBE(entry, openEntry(path[0], mode)) {
return asFile(*entry, mode);
auto lock = impl.lockExclusive();
KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
return asFile(lock, *entry, mode);
} else {
return nullptr;
}
......@@ -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) {
KJ_FAIL_REQUIRE("can't replace self") { break; }
} 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 {
KJ_IF_MAYBE(child, tryGetParent(path[0], 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 (has(mode, WriteMode::MODIFY)) {
return addRef(*this);
return atomicAddRef(*this);
} else if (has(mode, WriteMode::CREATE)) {
return nullptr; // already exists
} else {
KJ_FAIL_REQUIRE("can't replace self") { return nullptr; }
}
} else if (path.size() == 1) {
KJ_IF_MAYBE(entry, openEntry(path[0], mode)) {
return asDirectory(*entry, mode);
auto lock = impl.lockExclusive();
KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
return asDirectory(lock, *entry, mode);
} else {
return nullptr;
}
......@@ -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) {
KJ_FAIL_REQUIRE("can't replace self") { break; }
} 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 {
KJ_IF_MAYBE(child, tryGetParent(path[0], 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 (has(mode, WriteMode::MODIFY)) {
KJ_FAIL_REQUIRE("not a file") { return nullptr; }
......@@ -1180,8 +1211,9 @@ public:
KJ_FAIL_REQUIRE("can't replace self") { return nullptr; }
}
} else if (path.size() == 1) {
KJ_IF_MAYBE(entry, openEntry(path[0], mode)) {
return asFile(*entry, mode).map(newFileAppender);
auto lock = impl.lockExclusive();
KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
return asFile(lock, *entry, mode).map(newFileAppender);
} else {
return nullptr;
}
......@@ -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 (has(mode, WriteMode::CREATE)) {
return false;
......@@ -1202,9 +1234,10 @@ public:
KJ_FAIL_REQUIRE("can't replace self") { return false; }
}
} else if (path.size() == 1) {
KJ_IF_MAYBE(entry, openEntry(path[0], mode)) {
entry->init(SymlinkNode { clock.now(), heapString(content) });
modified();
auto lock = impl.lockExclusive();
KJ_IF_MAYBE(entry, lock->openEntry(path[0], mode)) {
entry->init(SymlinkNode { lock->clock.now(), heapString(content) });
lock->modified();
return true;
} else {
return false;
......@@ -1218,12 +1251,14 @@ public:
}
}
Own<File> createTemporary() override {
return newInMemoryFile(clock);
Own<const File> createTemporary() const override {
// Don't need lock just to read the clock ref.
return newInMemoryFile(impl.getWithoutLock().clock);
}
bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath, TransferMode mode) override {
const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) const override {
if (toPath.size() == 0) {
if (has(toMode, WriteMode::CREATE)) {
return false;
......@@ -1233,15 +1268,16 @@ public:
} else if (toPath.size() == 1) {
// tryTransferChild() needs to at least know the node type, so do an lstat.
KJ_IF_MAYBE(meta, fromDirectory.tryLstat(fromPath)) {
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
// remove the entry before returning.
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)) {
modified();
lock->modified();
needRollback = false;
return true;
} else {
......@@ -1267,8 +1303,8 @@ public:
}
}
Maybe<bool> tryTransferTo(Directory& toDirectory, PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode) override {
Maybe<bool> tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode) const override {
if (fromPath.size() <= 1) {
// If `fromPath` is in this directory (or *is* this directory) then we don't have any
// optimizations.
......@@ -1289,16 +1325,17 @@ public:
}
}
bool tryRemove(PathPtr path) override {
bool tryRemove(PathPtr path) const override {
if (path.size() == 0) {
KJ_FAIL_REQUIRE("can't remove self from self") { return false; }
} else if (path.size() == 1) {
auto iter = entries.find(path[0]);
if (iter == entries.end()) {
auto lock = impl.lockExclusive();
auto iter = lock->entries.find(path[0]);
if (iter == lock->entries.end()) {
return false;
} else {
entries.erase(iter);
modified();
lock->entries.erase(iter);
lock->modified();
return true;
}
} else {
......@@ -1312,16 +1349,16 @@ public:
private:
struct FileNode {
Own<File> file;
Own<const File> file;
};
struct DirectoryNode {
Own<Directory> directory;
Own<const Directory> directory;
};
struct SymlinkNode {
Date lastModified;
String content;
Path parse() {
Path parse() const {
KJ_CONTEXT("parsing symlink", content);
return Path::parse(content);
}
......@@ -1333,10 +1370,10 @@ private:
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();
}
Own<Directory> init(DirectoryNode&& value) {
Own<const Directory> init(DirectoryNode&& value) {
return node.init<DirectoryNode>(kj::mv(value)).directory->clone();
}
void init(SymlinkNode&& value) {
......@@ -1347,37 +1384,31 @@ private:
return node != nullptr;
}
void set(Own<File>&& value) {
void set(Own<const File>&& 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) });
}
};
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>
class ReplacerImpl final: public Replacer<T> {
public:
ReplacerImpl(InMemoryDirectory& directory, kj::StringPtr name, Own<T> inner, WriteMode mode)
: Replacer<T>(mode), directory(addRef(directory)), name(heapString(name)),
ReplacerImpl(const InMemoryDirectory& directory, kj::StringPtr name,
Own<const T> inner, WriteMode mode)
: Replacer<T>(mode), directory(atomicAddRef(directory)), name(heapString(name)),
inner(kj::mv(inner)) {}
T& get() override { return *inner; }
const T& get() override { return *inner; }
bool tryCommit() override {
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());
directory->modified();
lock->modified();
return true;
} else {
return false;
......@@ -1386,9 +1417,9 @@ private:
private:
bool committed = false;
Own<InMemoryDirectory> directory;
Own<const InMemoryDirectory> directory;
kj::String name;
Own<T> inner;
Own<const T> inner;
};
template <typename T>
......@@ -1396,20 +1427,31 @@ private:
// For recovery path when exceptions are disabled.
public:
BrokenReplacer(Own<T> inner)
BrokenReplacer(Own<const T> inner)
: Replacer<T>(WriteMode::CREATE | WriteMode::MODIFY),
inner(kj::mv(inner)) {}
T& get() override { return *inner; }
const T& get() override { return *inner; }
bool tryCommit() override { return false; }
private:
Own<T> inner;
Own<const T> inner;
};
struct Impl {
const Clock& clock;
std::map<StringPtr, EntryImpl> entries;
// Note: If this changes to a non-sorted map, listNames() and listEntries() must be updated to
// sort their results.
Date lastModified;
Impl(const Clock& clock): clock(clock), lastModified(clock.now()) {}
Maybe<EntryImpl&> openEntry(kj::StringPtr name, WriteMode mode) {
// TODO(perf): We could avoid a copy if the entry exists, at the expense of a double-lookup if
// it doesn't. Maybe a better map implementation will solve everything?
// TODO(perf): We could avoid a copy if the entry exists, at the expense of a double-lookup
// if it doesn't. Maybe a better map implementation will solve everything?
return openEntry(heapString(name), mode);
}
......@@ -1433,7 +1475,7 @@ private:
}
}
kj::Maybe<EntryImpl&> tryGetEntry(kj::StringPtr name) {
kj::Maybe<const EntryImpl&> tryGetEntry(kj::StringPtr name) const {
auto iter = entries.find(name);
if (iter == entries.end()) {
return nullptr;
......@@ -1442,103 +1484,12 @@ private:
}
}
kj::Maybe<Own<ReadableDirectory>> tryGetParent(kj::StringPtr name) {
KJ_IF_MAYBE(entry, tryGetEntry(name)) {
return asDirectory(*entry);
} else {
return nullptr;
}
}
kj::Maybe<Own<Directory>> tryGetParent(kj::StringPtr name, WriteMode mode) {
// Get a directory which is a parent of the eventual target. If `mode` includes
// WriteMode::CREATE_PARENTS, possibly create the parent directory.
WriteMode parentMode = has(mode, WriteMode::CREATE) && has(mode, WriteMode::CREATE_PARENT)
? WriteMode::CREATE | WriteMode::MODIFY // create parent
: WriteMode::MODIFY; // don't create parent
// Possibly create parent.
KJ_IF_MAYBE(entry, openEntry(name, parentMode)) {
if (entry->node.is<DirectoryNode>()) {
return entry->node.get<DirectoryNode>().directory->clone();
} else if (entry->node == nullptr) {
modified();
return entry->init(DirectoryNode { newInMemoryDirectory(clock) });
}
// Continue on.
}
if (has(mode, WriteMode::CREATE)) {
// CREATE is documented as returning null when the file already exists. In this case, the
// file does NOT exist because the parent directory does not exist or is not a directory.
KJ_FAIL_REQUIRE("parent is not a directory") { return nullptr; }
} else {
kj::Maybe<EntryImpl&> tryGetEntry(kj::StringPtr name) {
auto iter = entries.find(name);
if (iter == entries.end()) {
return nullptr;
}
}
bool exists(EntryImpl& entry) {
if (entry.node.is<SymlinkNode>()) {
return exists(entry.node.get<SymlinkNode>().parse());
} else {
return true;
}
}
Maybe<Own<ReadableFile>> asFile(EntryImpl& entry) {
if (entry.node.is<FileNode>()) {
return entry.node.get<FileNode>().file->clone();
} else if (entry.node.is<SymlinkNode>()) {
return tryOpenFile(entry.node.get<SymlinkNode>().parse());
} else {
KJ_FAIL_REQUIRE("not a file") { return nullptr; }
}
}
Maybe<Own<ReadableDirectory>> asDirectory(EntryImpl& entry) {
if (entry.node.is<DirectoryNode>()) {
return entry.node.get<DirectoryNode>().directory->clone();
} else if (entry.node.is<SymlinkNode>()) {
return tryOpenSubdir(entry.node.get<SymlinkNode>().parse());
} else {
KJ_FAIL_REQUIRE("not a directory") { return nullptr; }
}
}
Maybe<String> asSymlink(EntryImpl& entry) {
if (entry.node.is<SymlinkNode>()) {
return heapString(entry.node.get<SymlinkNode>().content);
} else {
KJ_FAIL_REQUIRE("not a symlink") { return nullptr; }
}
}
Maybe<Own<File>> asFile(EntryImpl& entry, WriteMode mode) {
if (entry.node.is<FileNode>()) {
return entry.node.get<FileNode>().file->clone();
} else if (entry.node.is<SymlinkNode>()) {
// CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the
// target itself can still be created.
return tryOpenFile(entry.node.get<SymlinkNode>().parse(), mode - WriteMode::CREATE_PARENT);
} else if (entry.node == nullptr) {
KJ_ASSERT(has(mode, WriteMode::CREATE));
modified();
return entry.init(FileNode { newInMemoryFile(clock) });
} else {
KJ_FAIL_REQUIRE("not a file") { return nullptr; }
}
}
Maybe<Own<Directory>> asDirectory(EntryImpl& entry, WriteMode mode) {
if (entry.node.is<DirectoryNode>()) {
return entry.node.get<DirectoryNode>().directory->clone();
} else if (entry.node.is<SymlinkNode>()) {
// CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the
// target itself can still be created.
return tryOpenSubdir(entry.node.get<SymlinkNode>().parse(), mode - WriteMode::CREATE_PARENT);
} else if (entry.node == nullptr) {
KJ_ASSERT(has(mode, WriteMode::CREATE));
modified();
return entry.init(DirectoryNode { newInMemoryDirectory(clock) });
} else {
KJ_FAIL_REQUIRE("not a directory") { return nullptr; }
return iter->second;
}
}
......@@ -1547,8 +1498,8 @@ private:
}
bool tryTransferChild(EntryImpl& entry, const FsNode::Type type, kj::Maybe<Date> lastModified,
kj::Maybe<uint64_t> size, Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) {
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)) {
......@@ -1573,17 +1524,18 @@ private:
case FsNode::Type::DIRECTORY:
KJ_IF_MAYBE(subdir, fromDirectory.tryOpenSubdir(fromPath, WriteMode::MODIFY)) {
if (mode == TransferMode::COPY) {
auto copy = refcounted<InMemoryDirectory>(clock);
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 (!copy->tryTransferChild(newEntry, subEntry.type, nullptr, nullptr, **subdir,
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;
copy->entries.insert(std::make_pair(nameRef, kj::mv(newEntry)));
cpim.entries.insert(std::make_pair(nameRef, kj::mv(newEntry)));
}
}
entry.set(kj::mv(copy));
......@@ -1620,48 +1572,167 @@ private:
return false;
}
}
};
kj::MutexGuarded<Impl> impl;
bool exists(kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
if (entry.node.is<SymlinkNode>()) {
auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return exists(newPath);
} else {
return true;
}
}
Maybe<Own<const ReadableFile>> asFile(
kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
if (entry.node.is<FileNode>()) {
return entry.node.get<FileNode>().file->clone();
} else if (entry.node.is<SymlinkNode>()) {
auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return tryOpenFile(newPath);
} else {
KJ_FAIL_REQUIRE("not a file") { return nullptr; }
}
}
Maybe<Own<const ReadableDirectory>> asDirectory(
kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
if (entry.node.is<DirectoryNode>()) {
return entry.node.get<DirectoryNode>().directory->clone();
} else if (entry.node.is<SymlinkNode>()) {
auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return tryOpenSubdir(newPath);
} else {
KJ_FAIL_REQUIRE("not a directory") { return nullptr; }
}
}
Maybe<String> asSymlink(kj::Locked<const Impl>& lock, const EntryImpl& entry) const {
if (entry.node.is<SymlinkNode>()) {
return heapString(entry.node.get<SymlinkNode>().content);
} else {
KJ_FAIL_REQUIRE("not a symlink") { return nullptr; }
}
}
Maybe<Own<const File>> asFile(kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const {
if (entry.node.is<FileNode>()) {
return entry.node.get<FileNode>().file->clone();
} else if (entry.node.is<SymlinkNode>()) {
// CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the
// target itself can still be created.
auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return tryOpenFile(newPath, mode - WriteMode::CREATE_PARENT);
} else if (entry.node == nullptr) {
KJ_ASSERT(has(mode, WriteMode::CREATE));
lock->modified();
return entry.init(FileNode { newInMemoryFile(lock->clock) });
} else {
KJ_FAIL_REQUIRE("not a file") { return nullptr; }
}
}
Maybe<Own<const Directory>> asDirectory(
kj::Locked<Impl>& lock, EntryImpl& entry, WriteMode mode) const {
if (entry.node.is<DirectoryNode>()) {
return entry.node.get<DirectoryNode>().directory->clone();
} else if (entry.node.is<SymlinkNode>()) {
// CREATE_PARENT doesn't apply to creating the parents of a symlink target. However, the
// target itself can still be created.
auto newPath = entry.node.get<SymlinkNode>().parse();
lock.release();
return tryOpenSubdir(newPath, mode - WriteMode::CREATE_PARENT);
} else if (entry.node == nullptr) {
KJ_ASSERT(has(mode, WriteMode::CREATE));
lock->modified();
return entry.init(DirectoryNode { newInMemoryDirectory(lock->clock) });
} else {
KJ_FAIL_REQUIRE("not a directory") { return nullptr; }
}
}
kj::Maybe<Own<const ReadableDirectory>> tryGetParent(kj::StringPtr name) const {
auto lock = impl.lockShared();
KJ_IF_MAYBE(entry, impl.lockShared()->tryGetEntry(name)) {
return asDirectory(lock, *entry);
} else {
return nullptr;
}
}
kj::Maybe<Own<const Directory>> tryGetParent(kj::StringPtr name, WriteMode mode) const {
// Get a directory which is a parent of the eventual target. If `mode` includes
// WriteMode::CREATE_PARENTS, possibly create the parent directory.
auto lock = impl.lockExclusive();
WriteMode parentMode = has(mode, WriteMode::CREATE) && has(mode, WriteMode::CREATE_PARENT)
? WriteMode::CREATE | WriteMode::MODIFY // create parent
: WriteMode::MODIFY; // don't create parent
// Possibly create parent.
KJ_IF_MAYBE(entry, lock->openEntry(name, parentMode)) {
if (entry->node.is<DirectoryNode>()) {
return entry->node.get<DirectoryNode>().directory->clone();
} else if (entry->node == nullptr) {
lock->modified();
return entry->init(DirectoryNode { newInMemoryDirectory(lock->clock) });
}
// Continue on.
}
if (has(mode, WriteMode::CREATE)) {
// CREATE is documented as returning null when the file already exists. In this case, the
// file does NOT exist because the parent directory does not exist or is not a directory.
KJ_FAIL_REQUIRE("parent is not a directory") { return nullptr; }
} else {
return nullptr;
}
}
};
// -----------------------------------------------------------------------------
class AppendableFileImpl final: public AppendableFile {
public:
AppendableFileImpl(Own<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());
}
Maybe<int> getFd() override {
Maybe<int> getFd() const override {
return nullptr;
}
Metadata stat() override {
Metadata stat() const override {
return file->stat();
}
void sync() override { file->sync(); }
void datasync() override { file->datasync(); }
void sync() const override { file->sync(); }
void datasync() const override { file->datasync(); }
void write(const void* buffer, size_t size) override {
file->write(file->stat().size, arrayPtr(reinterpret_cast<const byte*>(buffer), size));
}
private:
Own<File> file;
Own<const File> file;
};
} // namespace
// -----------------------------------------------------------------------------
Own<File> newInMemoryFile(Clock& clock) {
return refcounted<InMemoryFile>(clock);
Own<File> newInMemoryFile(const Clock& clock) {
return atomicRefcounted<InMemoryFile>(clock);
}
Own<Directory> newInMemoryDirectory(Clock& clock) {
return refcounted<InMemoryDirectory>(clock);
Own<Directory> newInMemoryDirectory(const Clock& clock) {
return atomicRefcounted<InMemoryDirectory>(clock);
}
Own<AppendableFile> newFileAppender(Own<File> inner) {
Own<AppendableFile> newFileAppender(Own<const File> inner) {
return heap<AppendableFileImpl>(kj::mv(inner));
}
......
......@@ -295,22 +295,26 @@ private:
// 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
// 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 {
// Base class for filesystem node types.
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
// external object.
//
// 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
// 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
// wrapping a handle.
......@@ -375,10 +379,10 @@ public:
// 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 datasync() = 0;
virtual void sync() const = 0;
virtual void datasync() const = 0;
// Maps to fsync() and fdatasync() system calls.
//
// Also, when creating or overwriting a file, the first call to sync() atomically links the file
......@@ -387,29 +391,29 @@ public:
// it.)
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.
// Hence, every subclass must implement this.
};
class ReadableFile: public FsNode {
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.
Array<byte> readAllBytes();
Array<byte> readAllBytes() const;
// 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
// 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 --
// 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.
// 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.
......@@ -423,7 +427,7 @@ public:
// 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.
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
// 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,
......@@ -436,26 +440,26 @@ public:
class AppendableFile: public FsNode, public OutputStream {
public:
Own<AppendableFile> clone();
Own<const AppendableFile> clone() const;
// All methods are inherited.
};
class WritableFileMapping {
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
// the underlying file, but there is no guarantee that they are written unless you subsequently
// 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
// may be a no-op while for others it may be necessary in order for the changes to be written
// back at all.
//
// `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
// returning.
//
......@@ -470,31 +474,32 @@ public:
class File: public ReadableFile {
public:
Own<File> clone();
Own<const File> clone() const;
void writeAll(ArrayPtr<const byte> bytes);
void writeAll(StringPtr text);
void writeAll(ArrayPtr<const byte> bytes) const;
void writeAll(StringPtr text) const;
// 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.
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
// supports it, this will "punch a hole" in the file, such that blocks that are entirely zeros
// 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
// 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.
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
// visible in other mappings of the file on the same system and will eventually be written back
// 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 `size` bytes or to EOF, whichever comes first. Returns the number of bytes actually
......@@ -510,9 +515,9 @@ class ReadableDirectory: public FsNode {
// Read-only subset of `Directory`.
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 "..".
struct Entry {
......@@ -526,37 +531,37 @@ public:
// 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
// filesystems, this is just as fast as listNames(), but on others it may require stat()ing each
// file.
virtual bool exists(PathPtr path) = 0;
virtual bool exists(PathPtr path) const = 0;
// Does the specified path exist?
//
// 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()
// may return names for which exists() reports false.)
FsNode::Metadata lstat(PathPtr path);
virtual Maybe<FsNode::Metadata> tryLstat(PathPtr path) = 0;
FsNode::Metadata lstat(PathPtr path) const;
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
// describes the symlink itself. `tryLstat()` returns null if the path doesn't exist.
Own<ReadableFile> openFile(PathPtr path);
virtual Maybe<Own<ReadableFile>> tryOpenFile(PathPtr path) = 0;
Own<const ReadableFile> openFile(PathPtr path) const;
virtual Maybe<Own<const ReadableFile>> tryOpenFile(PathPtr path) const = 0;
// Open a file for reading.
//
// `tryOpenFile()` returns null if the path doesn't exist. Other errors still throw exceptions.
Own<ReadableDirectory> openSubdir(PathPtr path);
virtual Maybe<Own<ReadableDirectory>> tryOpenSubdir(PathPtr path) = 0;
Own<const ReadableDirectory> openSubdir(PathPtr path) const;
virtual Maybe<Own<const ReadableDirectory>> tryOpenSubdir(PathPtr path) const = 0;
// Opens a subdirectory.
//
// `tryOpenSubdir()` returns null if the path doesn't exist. Other errors still throw exceptions.
String readlink(PathPtr path);
virtual Maybe<String> tryReadlink(PathPtr path) = 0;
String readlink(PathPtr path) const;
virtual Maybe<String> tryReadlink(PathPtr path) const = 0;
// If `path` is a symlink, reads and returns the link contents.
//
// Note that tryReadlink() differs subtly from tryOpen*(). For example, tryOpenFile() throws if
......@@ -694,7 +699,7 @@ class Directory: public ReadableDirectory {
// behavior.
public:
Own<Directory> clone();
Own<const Directory> clone() const;
template <typename T>
class Replacer {
......@@ -722,7 +727,7 @@ public:
public:
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
// calling commit().
......@@ -778,45 +783,45 @@ public:
using ReadableDirectory::tryOpenFile;
using ReadableDirectory::tryOpenSubdir;
Own<File> openFile(PathPtr path, WriteMode mode);
virtual Maybe<Own<File>> tryOpenFile(PathPtr path, WriteMode mode) = 0;
Own<const File> openFile(PathPtr path, WriteMode mode) const;
virtual Maybe<Own<const File>> tryOpenFile(PathPtr path, WriteMode mode) const = 0;
// Open a file for writing.
//
// `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).
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
// is there already. See `Replacer<T>` for detalis.
//
// The `CREATE` and `MODIFY` bits of `mode` are not enforced until commit time, hence
// `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
// the directory tree. The file is deleted from disk when all references to it have been dropped.
Own<AppendableFile> appendFile(PathPtr path, WriteMode mode);
virtual Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) = 0;
Own<AppendableFile> appendFile(PathPtr path, WriteMode mode) const;
virtual Maybe<Own<AppendableFile>> tryAppendFile(PathPtr path, WriteMode mode) const = 0;
// 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
// 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.
Own<Directory> openSubdir(PathPtr path, WriteMode mode);
virtual Maybe<Own<Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) = 0;
Own<const Directory> openSubdir(PathPtr path, WriteMode mode) const;
virtual Maybe<Own<const Directory>> tryOpenSubdir(PathPtr path, WriteMode mode) const = 0;
// 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
// whatever is there already. See `Replacer<T>` for detalis.
//
// The `CREATE` and `MODIFY` bits of `mode` are not enforced until commit time, hence
// `replaceSubdir()` has no "try" variant.
void symlink(PathPtr linkpath, StringPtr content, WriteMode mode);
virtual bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) = 0;
void symlink(PathPtr linkpath, StringPtr content, WriteMode mode) const;
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.
// 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.
......@@ -831,15 +836,15 @@ public:
// exists.
void transfer(PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode);
PathPtr fromPath, TransferMode mode) const;
void transfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath,
TransferMode mode);
const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) const;
virtual bool tryTransfer(PathPtr toPath, WriteMode toMode,
Directory& fromDirectory, PathPtr fromPath,
TransferMode mode);
virtual Maybe<bool> tryTransferTo(Directory& toDirectory, PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode);
const Directory& fromDirectory, PathPtr fromPath,
TransferMode mode) const;
virtual Maybe<bool> tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode,
PathPtr fromPath, TransferMode mode) const;
// 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,
......@@ -856,8 +861,8 @@ public:
// `toMode` controls how the target path is created. CREATE_PARENT is honored but EXECUTABLE and
// PRIVATE have no effect.
void remove(PathPtr path);
virtual bool tryRemove(PathPtr path) = 0;
void remove(PathPtr path) const;
virtual bool tryRemove(PathPtr path) const = 0;
// 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.
......@@ -882,13 +887,13 @@ private:
class Filesystem {
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.
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.
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
// 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
......@@ -909,8 +914,8 @@ public:
// =======================================================================================
Own<File> newInMemoryFile(Clock& clock);
Own<Directory> newInMemoryDirectory(Clock& clock);
Own<File> newInMemoryFile(const Clock& clock);
Own<Directory> newInMemoryDirectory(const Clock& clock);
// Construct file and directory objects which reside in-memory.
//
// InMemoryFile has the following special properties:
......@@ -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
// 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
// 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
......@@ -1054,19 +1059,23 @@ inline String PathPtr::toNativeString(bool absolute) const {
}
#endif // _WIN32, else
inline Own<FsNode> FsNode::clone() { return cloneFsNode().downcast<FsNode>(); }
inline Own<ReadableFile> ReadableFile::clone() { return cloneFsNode().downcast<ReadableFile>(); }
inline Own<AppendableFile> AppendableFile::clone() {
return cloneFsNode().downcast<AppendableFile>();
inline Own<const FsNode> FsNode::clone() const { return cloneFsNode().downcast<const FsNode>(); }
inline Own<const ReadableFile> ReadableFile::clone() const {
return cloneFsNode().downcast<const ReadableFile>();
}
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<ReadableDirectory> ReadableDirectory::clone() {
return cloneFsNode().downcast<ReadableDirectory>();
inline Own<const Directory> Directory::clone() const {
return cloneFsNode().downcast<const Directory>();
}
inline Own<Directory> Directory::clone() { return cloneFsNode().downcast<Directory>(); }
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);
}
......
......@@ -26,12 +26,12 @@
namespace kj {
Clock& nullClock() {
const Clock& nullClock() {
class NullClock final: public Clock {
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;
}
......
......@@ -63,10 +63,10 @@ constexpr Date UNIX_EPOCH = origin<Date>();
class Clock {
// Interface to read the current date and time.
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
// 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