Commit e3c4dd6f authored by Kenton Varda's avatar Kenton Varda Committed by Kenton Varda

Implement the actual Filesystem interface for disks.

parent 20148c78
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include <errno.h> #include <errno.h>
#include <dirent.h> #include <dirent.h>
#include <syscall.h> #include <syscall.h>
#include <stdlib.h>
#include "vector.h" #include "vector.h"
#include "miniposix.h" #include "miniposix.h"
...@@ -950,7 +951,7 @@ public: ...@@ -950,7 +951,7 @@ public:
// Check for broken link. // Check for broken link.
if (!has(mode, WriteMode::MODIFY) && if (!has(mode, WriteMode::MODIFY) &&
faccessat(fd, path.toString().cStr(), F_OK, AT_SYMLINK_NOFOLLOW) >= 0) { faccessat(fd, filename.cStr(), F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
// Yep. We treat this as already-exists, which means in CREATE-only mode this is a // Yep. We treat this as already-exists, which means in CREATE-only mode this is a
// simple failure. // simple failure.
return nullptr; return nullptr;
...@@ -1542,6 +1543,91 @@ public: ...@@ -1542,6 +1543,91 @@ public:
} }
}; };
class DiskFilesystem final: public Filesystem {
public:
DiskFilesystem()
: root(openDir("/")),
current(openDir(".")),
currentPath(computeCurrentPath()) {}
Directory& getRoot() override {
return root;
}
Directory& getCurrent() override {
return current;
}
PathPtr getCurrentPath() override {
return currentPath;
}
private:
DiskDirectory root;
DiskDirectory current;
Path currentPath;
static AutoCloseFd openDir(const char* dir) {
int newFd;
KJ_SYSCALL(newFd = open(dir, O_RDONLY | MAYBE_O_CLOEXEC | MAYBE_O_DIRECTORY));
AutoCloseFd result(newFd);
#ifndef O_CLOEXEC
setCloexec(result);
#endif
return result;
}
static Path computeCurrentPath() {
// If env var PWD is set and points to the current directory, use it. This captures the current
// path according to the user's shell, which may differ from the kernel's idea in the presence
// of symlinks.
const char* pwd = getenv("PWD");
if (pwd != nullptr) {
Path result = nullptr;
struct stat pwdStat, dotStat;
KJ_IF_MAYBE(e, kj::runCatchingExceptions([&]() {
KJ_ASSERT(pwd[0] == '/') { return; }
result = Path::parse(pwd + 1);
KJ_SYSCALL(lstat(result.toString(true).cStr(), &pwdStat), result) { return; }
KJ_SYSCALL(lstat(".", &dotStat)) { return; }
})) {
// failed, give up on PWD
KJ_LOG(WARNING, "PWD environment variable seems invalid", pwd, *e);
} else {
if (pwdStat.st_ino == dotStat.st_ino &&
pwdStat.st_dev == dotStat.st_dev) {
return kj::mv(result);
} else {
KJ_LOG(WARNING, "PWD environment variable doesn't match current directory", pwd);
}
}
}
size_t size = 256;
retry:
KJ_STACK_ARRAY(char, buf, size, 256, 4096);
if (getcwd(buf.begin(), size) == nullptr) {
int error = errno;
if (error == ENAMETOOLONG) {
size *= 2;
goto retry;
} else {
KJ_FAIL_SYSCALL("getcwd()", error);
}
}
StringPtr path = buf.begin();
// On Linux, the path will start with "(unreachable)" if the working directory is not a subdir
// of the root directory, which is possible via chroot() or mount namespaces.
KJ_ASSERT(!path.startsWith("(unreachable)"),
"working directory is not reachable from root", path);
KJ_ASSERT(path.startsWith("/"), "current directory is not absolute", path);
return Path::parse(path.slice(1));
}
};
} // namespace } // namespace
Own<ReadableFile> newDiskReadableFile(kj::AutoCloseFd fd) { Own<ReadableFile> newDiskReadableFile(kj::AutoCloseFd fd) {
...@@ -1560,4 +1646,8 @@ Own<Directory> newDiskDirectory(kj::AutoCloseFd fd) { ...@@ -1560,4 +1646,8 @@ Own<Directory> newDiskDirectory(kj::AutoCloseFd fd) {
return heap<DiskDirectory>(kj::mv(fd)); return heap<DiskDirectory>(kj::mv(fd));
} }
Own<Filesystem> newDiskFilesystem() {
return heap<DiskFilesystem>();
}
} // namespace kj } // namespace kj
...@@ -775,25 +775,16 @@ private: ...@@ -775,25 +775,16 @@ private:
class Filesystem { class Filesystem {
public: public:
virtual Directory& getRoot() = 0; virtual Directory& getRoot() = 0;
// Get the filesystem's root directory. // Get the filesystem's root directory, as of the time the Filesystem object was created.
//
// `getRoot()` returns a special directory for which `getFd()` throws. You may call
// `getRoot().openSubdir(nullptr)` to get a Directory representing the root which has a valid
// file descriptor (and which isn't affected by `chroot()`).
virtual Directory& getCurrent() = 0; virtual Directory& getCurrent() = 0;
// Get the filesystem's current directory. // Get the filesystem's current directory, as of the time the Filesystem object was created.
//
// The returned Directory object is a special one for which a call to the `chdir()` syscall will
// change the Directory's target. We recommend against using `chdir()` in KJ code, but if you
// want a Directory that avoids this behavior, you can call `.openSubdir(nullptr)` on it to
// reopen itself. Additionally, `getCurrent()` returns a directory for which `getFd()` throws;
// `openSubdir(nullptr)` solves that problem as well.
virtual Path getCurrentPath() = 0; virtual PathPtr getCurrentPath() = 0;
// Get the path from the root to the current directory. Note that because a `Directory` does not // Get the path from the root to the current directory, as of the time the Filesystem object was
// provide access to its parent, if you want to follow `..` from the current directory, you must // created. Note that because a `Directory` does not provide access to its parent, if you want to
// use `getCurrentPath().eval("..")` or `getCurrentPath().parent()`. // follow `..` from the current directory, you must use `getCurrentPath().eval("..")` or
// `getCurrentPath().parent()`.
// //
// This function attempts to determine the path as it appeared in the user's shell before this // This function attempts to determine the path as it appeared in the user's shell before this
// program was started. That means, if the user had `cd`ed into a symlink, the path through that // program was started. That means, if the user had `cd`ed into a symlink, the path through that
...@@ -837,8 +828,18 @@ Own<AppendableFile> newDiskAppendableFile(kj::AutoCloseFd fd); ...@@ -837,8 +828,18 @@ Own<AppendableFile> newDiskAppendableFile(kj::AutoCloseFd fd);
Own<File> newDiskFile(kj::AutoCloseFd fd); Own<File> newDiskFile(kj::AutoCloseFd fd);
Own<ReadableDirectory> newDiskReadableDirectory(kj::AutoCloseFd fd); Own<ReadableDirectory> newDiskReadableDirectory(kj::AutoCloseFd fd);
Own<Directory> newDiskDirectory(kj::AutoCloseFd fd); Own<Directory> newDiskDirectory(kj::AutoCloseFd fd);
// Wrap a file descriptor as various filesystem types.
Own<Filesystem> newDiskFilesystem(); Own<Filesystem> newDiskFilesystem();
// Get at implementation of `Filesystem` representing the real filesystem.
//
// DO NOT CALL THIS except at the top level of your program, e.g. in main(). Anywhere else, you
// should instead have your caller pass in a Filesystem object, or a specific Directory object,
// or whatever it is that your code needs. This ensures that your code supports dependency
// injection, which makes it more reusable and testable.
//
// newDiskFilesystem() reads the current working directory at the time it is called. The returned
// object is not affected by subsequent calls to chdir().
// ======================================================================================= // =======================================================================================
// inline implementation details // inline implementation details
......
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