jpayne@69: // Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors jpayne@69: // Licensed under the MIT License: jpayne@69: // jpayne@69: // Permission is hereby granted, free of charge, to any person obtaining a copy jpayne@69: // of this software and associated documentation files (the "Software"), to deal jpayne@69: // in the Software without restriction, including without limitation the rights jpayne@69: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell jpayne@69: // copies of the Software, and to permit persons to whom the Software is jpayne@69: // furnished to do so, subject to the following conditions: jpayne@69: // jpayne@69: // The above copyright notice and this permission notice shall be included in jpayne@69: // all copies or substantial portions of the Software. jpayne@69: // jpayne@69: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR jpayne@69: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, jpayne@69: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE jpayne@69: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER jpayne@69: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, jpayne@69: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN jpayne@69: // THE SOFTWARE. jpayne@69: jpayne@69: #pragma once jpayne@69: jpayne@69: #include "memory.h" jpayne@69: #include "io.h" jpayne@69: #include jpayne@69: #include "time.h" jpayne@69: #include "function.h" jpayne@69: #include "hash.h" jpayne@69: jpayne@69: KJ_BEGIN_HEADER jpayne@69: jpayne@69: namespace kj { jpayne@69: jpayne@69: template jpayne@69: class Vector; jpayne@69: jpayne@69: class PathPtr; jpayne@69: jpayne@69: class Path { jpayne@69: // A Path identifies a file in a directory tree. jpayne@69: // jpayne@69: // In KJ, we avoid representing paths as plain strings because this can lead to path injection jpayne@69: // bugs as well as numerous kinds of bugs relating to path parsing edge cases. The Path class's jpayne@69: // interface is designed to "make it hard to screw up". jpayne@69: // jpayne@69: // A "Path" is in fact a list of strings, each string being one component of the path (as would jpayne@69: // normally be separated by '/'s). Path components are not allowed to contain '/' nor '\0', nor jpayne@69: // are they allowed to be the special names "", ".", nor "..". jpayne@69: // jpayne@69: // If you explicitly want to parse a path that contains '/'s, ".", and "..", you must use jpayne@69: // parse() and/or eval(). However, users of this interface are encouraged to avoid parsing jpayne@69: // paths at all, and instead express paths as string arrays. jpayne@69: // jpayne@69: // Note that when using the Path class, ".." is always canonicalized in path space without jpayne@69: // consulting the actual filesystem. This means that "foo/some-symlink/../bar" is exactly jpayne@69: // equivalent to "foo/bar". This differs from the kernel's behavior when resolving paths passed jpayne@69: // to system calls: the kernel would have resolved "some-symlink" to its target physical path, jpayne@69: // and then would have interpreted ".." relative to that. In practice, the kernel's behavior is jpayne@69: // rarely what the user or programmer intended, hence canonicalizing in path space produces a jpayne@69: // better result. jpayne@69: // jpayne@69: // Path objects are "immutable": functions that "modify" the path return a new path. However, jpayne@69: // if the path being operated on is an rvalue, copying can be avoided. Hence it makes sense to jpayne@69: // write code like: jpayne@69: // jpayne@69: // Path p = ...; jpayne@69: // p = kj::mv(p).append("bar"); // in-place update, avoids string copying jpayne@69: jpayne@69: public: jpayne@69: Path(decltype(nullptr)); // empty path jpayne@69: jpayne@69: explicit Path(StringPtr name); jpayne@69: explicit Path(String&& name); jpayne@69: // Create a Path containing only one component. `name` is a single filename; it cannot contain jpayne@69: // '/' nor '\0' nor can it be exactly "" nor "." nor "..". jpayne@69: // jpayne@69: // If you want to allow '/'s and such, you must call Path::parse(). We force you to do this to jpayne@69: // prevent path injection bugs where you didn't consider what would happen if the path contained jpayne@69: // a '/'. jpayne@69: jpayne@69: explicit Path(std::initializer_list parts); jpayne@69: explicit Path(ArrayPtr parts); jpayne@69: explicit Path(Array parts); jpayne@69: // Construct a path from an array. Note that this means you can do: jpayne@69: // jpayne@69: // Path{"foo", "bar", "baz"} // equivalent to Path::parse("foo/bar/baz") jpayne@69: jpayne@69: KJ_DISALLOW_COPY(Path); jpayne@69: Path(Path&&) = default; jpayne@69: Path& operator=(Path&&) = default; jpayne@69: jpayne@69: Path clone() const; jpayne@69: jpayne@69: static Path parse(StringPtr path); jpayne@69: // Parses a path in traditional format. Components are separated by '/'. Any use of "." or jpayne@69: // ".." will be canonicalized (if they can't be canonicalized, e.g. because the path starts with jpayne@69: // "..", an exception is thrown). Multiple consecutive '/'s will be collapsed. A leading '/' jpayne@69: // is NOT accepted -- if that is a problem, you probably want `eval()`. Trailing '/'s are jpayne@69: // ignored. jpayne@69: jpayne@69: Path append(Path&& suffix) const&; jpayne@69: Path append(Path&& suffix) &&; jpayne@69: Path append(PathPtr suffix) const&; jpayne@69: Path append(PathPtr suffix) &&; jpayne@69: Path append(StringPtr suffix) const&; jpayne@69: Path append(StringPtr suffix) &&; jpayne@69: Path append(String&& suffix) const&; jpayne@69: Path append(String&& suffix) &&; jpayne@69: // Create a new path by appending the given path to this path. jpayne@69: // jpayne@69: // `suffix` cannot contain '/' characters. Instead, you can append an array: jpayne@69: // jpayne@69: // path.append({"foo", "bar"}) jpayne@69: // jpayne@69: // Or, use Path::parse(): jpayne@69: // jpayne@69: // path.append(Path::parse("foo//baz/../bar")) jpayne@69: jpayne@69: Path eval(StringPtr pathText) const&; jpayne@69: Path eval(StringPtr pathText) &&; jpayne@69: // Evaluates a traditional path relative to this one. `pathText` is parsed like `parse()` would, jpayne@69: // except that: jpayne@69: // - It can contain leading ".." components that traverse up the tree. jpayne@69: // - It can have a leading '/' which completely replaces the current path. jpayne@69: // jpayne@69: // THE NAME OF THIS METHOD WAS CHOSEN TO INSPIRE FEAR. jpayne@69: // jpayne@69: // Instead of using `path.eval(str)`, always consider whether you really want jpayne@69: // `path.append(Path::parse(str))`. The former is much riskier than the latter in terms of path jpayne@69: // injection vulnerabilities. jpayne@69: jpayne@69: PathPtr basename() const&; jpayne@69: Path basename() &&; jpayne@69: // Get the last component of the path. (Use `basename()[0]` to get just the string.) jpayne@69: jpayne@69: PathPtr parent() const&; jpayne@69: Path parent() &&; jpayne@69: // Get the parent path. jpayne@69: jpayne@69: String toString(bool absolute = false) const; jpayne@69: // Converts the path to a traditional path string, appropriate to pass to a unix system call. jpayne@69: // Never throws. jpayne@69: jpayne@69: const String& operator[](size_t i) const&; jpayne@69: String operator[](size_t i) &&; jpayne@69: size_t size() const; jpayne@69: const String* begin() const; jpayne@69: const String* end() const; jpayne@69: PathPtr slice(size_t start, size_t end) const&; jpayne@69: Path slice(size_t start, size_t end) &&; jpayne@69: // A Path can be accessed as an array of strings. jpayne@69: jpayne@69: bool operator==(PathPtr other) const; jpayne@69: bool operator!=(PathPtr other) const; jpayne@69: bool operator< (PathPtr other) const; jpayne@69: bool operator> (PathPtr other) const; jpayne@69: bool operator<=(PathPtr other) const; jpayne@69: bool operator>=(PathPtr other) const; jpayne@69: // Compare path components lexically. jpayne@69: jpayne@69: bool operator==(const Path& other) const; jpayne@69: bool operator!=(const Path& other) const; jpayne@69: bool operator< (const Path& other) const; jpayne@69: bool operator> (const Path& other) const; jpayne@69: bool operator<=(const Path& other) const; jpayne@69: bool operator>=(const Path& other) const; jpayne@69: jpayne@69: uint hashCode() const; jpayne@69: // Can use in HashMap. jpayne@69: jpayne@69: bool startsWith(PathPtr prefix) const; jpayne@69: bool endsWith(PathPtr suffix) const; jpayne@69: // Compare prefix / suffix. jpayne@69: jpayne@69: Path evalWin32(StringPtr pathText) const&; jpayne@69: Path evalWin32(StringPtr pathText) &&; jpayne@69: // Evaluates a Win32-style path, as might be written by a user. Differences from `eval()` jpayne@69: // include: jpayne@69: // jpayne@69: // - Backslashes can be used as path separators. jpayne@69: // - Absolute paths begin with a drive letter followed by a colon. The drive letter, including jpayne@69: // the colon, will become the first component of the path, e.g. "c:\foo" becomes {"c:", "foo"}. jpayne@69: // - A network path like "\\host\share\path" is parsed as {"host", "share", "path"}. jpayne@69: jpayne@69: Path evalNative(StringPtr pathText) const&; jpayne@69: Path evalNative(StringPtr pathText) &&; jpayne@69: // Alias for either eval() or evalWin32() depending on the target platform. Use this when you are jpayne@69: // parsing a path provided by a user and you want the user to be able to use the "natural" format jpayne@69: // for their platform. jpayne@69: jpayne@69: String toWin32String(bool absolute = false) const; jpayne@69: // Converts the path to a Win32 path string, as you might display to a user. jpayne@69: // jpayne@69: // This is meant for display. For making Win32 system calls, consider `toWin32Api()` instead. jpayne@69: // jpayne@69: // If `absolute` is true, the path is expected to be an absolute path, meaning the first jpayne@69: // component is a drive letter, namespace, or network host name. These are converted to their jpayne@69: // regular Win32 format -- i.e. this method does the reverse of `evalWin32()`. jpayne@69: // jpayne@69: // This throws if the path would have unexpected special meaning or is otherwise invalid on jpayne@69: // Windows, such as if it contains backslashes (within a path component), colons, or special jpayne@69: // names like "con". jpayne@69: jpayne@69: String toNativeString(bool absolute = false) const; jpayne@69: // Alias for either toString() or toWin32String() depending on the target platform. Use this when jpayne@69: // you are formatting a path to display to a user and you want to present it in the "natural" jpayne@69: // format for the user's platform. jpayne@69: jpayne@69: Array forWin32Api(bool absolute) const; jpayne@69: // Like toWin32String, but additionally: jpayne@69: // - Converts the path to UTF-16, with a NUL terminator included. jpayne@69: // - For absolute paths, adds the "\\?\" prefix which opts into permitting paths longer than jpayne@69: // MAX_PATH, and turns off relative path processing (which KJ paths already handle in userspace jpayne@69: // anyway). jpayne@69: // jpayne@69: // This method is good to use when making a Win32 API call, e.g.: jpayne@69: // jpayne@69: // DeleteFileW(path.forWin32Api(true).begin()); jpayne@69: jpayne@69: static Path parseWin32Api(ArrayPtr text); jpayne@69: // Parses an absolute path as returned by a Win32 API call like GetFinalPathNameByHandle() or jpayne@69: // GetCurrentDirectory(). A "\\?\" prefix is optional but understood if present. jpayne@69: // jpayne@69: // Since such Win32 API calls generally return a length, this function inputs an array slice. jpayne@69: // The slice should not include any NUL terminator. jpayne@69: jpayne@69: private: jpayne@69: Array parts; jpayne@69: jpayne@69: // TODO(perf): Consider unrolling one element from `parts`, so that a one-element path doesn't jpayne@69: // require allocation of an array. jpayne@69: jpayne@69: enum { ALREADY_CHECKED }; jpayne@69: Path(Array parts, decltype(ALREADY_CHECKED)); jpayne@69: jpayne@69: friend class PathPtr; jpayne@69: jpayne@69: static String stripNul(String input); jpayne@69: static void validatePart(StringPtr part); jpayne@69: static void evalPart(Vector& parts, ArrayPtr part); jpayne@69: static Path evalImpl(Vector&& parts, StringPtr path); jpayne@69: static Path evalWin32Impl(Vector&& parts, StringPtr path, bool fromApi = false); jpayne@69: static size_t countParts(StringPtr path); jpayne@69: static size_t countPartsWin32(StringPtr path); jpayne@69: static bool isWin32Drive(ArrayPtr part); jpayne@69: static bool isNetbiosName(ArrayPtr part); jpayne@69: static bool isWin32Special(StringPtr part); jpayne@69: }; jpayne@69: jpayne@69: class PathPtr { jpayne@69: // Points to a Path or a slice of a Path, but doesn't own it. jpayne@69: // jpayne@69: // PathPtr is to Path as ArrayPtr is to Array and StringPtr is to String. jpayne@69: jpayne@69: public: jpayne@69: PathPtr(decltype(nullptr)); jpayne@69: PathPtr(const Path& path); jpayne@69: jpayne@69: Path clone(); jpayne@69: Path append(Path&& suffix) const; jpayne@69: Path append(PathPtr suffix) const; jpayne@69: Path append(StringPtr suffix) const; jpayne@69: Path append(String&& suffix) const; jpayne@69: Path eval(StringPtr pathText) const; jpayne@69: PathPtr basename() const; jpayne@69: PathPtr parent() const; jpayne@69: String toString(bool absolute = false) const; jpayne@69: const String& operator[](size_t i) const; jpayne@69: size_t size() const; jpayne@69: const String* begin() const; jpayne@69: const String* end() const; jpayne@69: PathPtr slice(size_t start, size_t end) const; jpayne@69: bool operator==(PathPtr other) const; jpayne@69: bool operator!=(PathPtr other) const; jpayne@69: bool operator< (PathPtr other) const; jpayne@69: bool operator> (PathPtr other) const; jpayne@69: bool operator<=(PathPtr other) const; jpayne@69: bool operator>=(PathPtr other) const; jpayne@69: uint hashCode() const; jpayne@69: bool startsWith(PathPtr prefix) const; jpayne@69: bool endsWith(PathPtr suffix) const; jpayne@69: Path evalWin32(StringPtr pathText) const; jpayne@69: Path evalNative(StringPtr pathText) const; jpayne@69: String toWin32String(bool absolute = false) const; jpayne@69: String toNativeString(bool absolute = false) const; jpayne@69: Array forWin32Api(bool absolute) const; jpayne@69: // Equivalent to the corresponding methods of `Path`. jpayne@69: jpayne@69: private: jpayne@69: ArrayPtr parts; jpayne@69: jpayne@69: explicit PathPtr(ArrayPtr parts); jpayne@69: jpayne@69: String toWin32StringImpl(bool absolute, bool forApi) const; jpayne@69: jpayne@69: friend class Path; jpayne@69: }; jpayne@69: jpayne@69: // ======================================================================================= jpayne@69: // The filesystem API jpayne@69: // jpayne@69: // This API is strictly synchronous because, unfortunately, there's no such thing as asynchronous jpayne@69: // filesystem access in practice. The filesystem drivers on Linux are written to assume they can jpayne@69: // block. The AIO API is only actually asynchronous for reading/writing the raw file blocks, but if jpayne@69: // the filesystem needs to be involved (to allocate blocks, update metadata, etc.) that will block. jpayne@69: // It's best to imagine that the filesystem is just another tier of memory that happens to be jpayne@69: // slower than RAM (which is slower than L3 cache, which is slower than L2, which is slower than jpayne@69: // L1). You can't do asynchronous RAM access so why asynchronous filesystem? The only way to jpayne@69: // parallelize these is using threads. jpayne@69: // jpayne@69: // All KJ filesystem objects are thread-safe, and so all methods are marked "const" (even write jpayne@69: // methods). Of course, if you concurrently write the same bytes of a file from multiple threads, jpayne@69: // it's unspecified which write will "win". jpayne@69: jpayne@69: class FsNode { jpayne@69: // Base class for filesystem node types. jpayne@69: jpayne@69: public: jpayne@69: Own clone() const; jpayne@69: // Creates a new object of exactly the same type as this one, pointing at exactly the same jpayne@69: // external object. jpayne@69: // jpayne@69: // Under the hood, this will call dup(), so the FD number will not be the same. jpayne@69: jpayne@69: virtual Maybe getFd() const { return nullptr; } jpayne@69: // Get the underlying Unix file descriptor, if any. Returns nullptr if this object actually isn't jpayne@69: // wrapping a file descriptor. jpayne@69: jpayne@69: virtual Maybe getWin32Handle() const { return nullptr; } jpayne@69: // Get the underlying Win32 HANDLE, if any. Returns nullptr if this object actually isn't jpayne@69: // wrapping a handle. jpayne@69: jpayne@69: enum class Type { jpayne@69: FILE, jpayne@69: DIRECTORY, jpayne@69: SYMLINK, jpayne@69: BLOCK_DEVICE, jpayne@69: CHARACTER_DEVICE, jpayne@69: NAMED_PIPE, jpayne@69: SOCKET, jpayne@69: OTHER, jpayne@69: }; jpayne@69: jpayne@69: struct Metadata { jpayne@69: Type type = Type::FILE; jpayne@69: jpayne@69: uint64_t size = 0; jpayne@69: // Logical size of the file. jpayne@69: jpayne@69: uint64_t spaceUsed = 0; jpayne@69: // Physical size of the file on disk. May be smaller for sparse files, or larger for jpayne@69: // pre-allocated files. jpayne@69: jpayne@69: Date lastModified = UNIX_EPOCH; jpayne@69: // Last modification time of the file. jpayne@69: jpayne@69: uint linkCount = 1; jpayne@69: // Number of hard links pointing to this node. jpayne@69: jpayne@69: uint64_t hashCode = 0; jpayne@69: // Hint which can be used to determine if two FsNode instances point to the same underlying jpayne@69: // file object. If two FsNodes report different hashCodes, then they are not the same object. jpayne@69: // If they report the same hashCode, then they may or may not be the same object. jpayne@69: // jpayne@69: // The Unix filesystem implementation builds the hashCode based on st_dev and st_ino of jpayne@69: // `struct stat`. However, note that some filesystems -- especially FUSE-based -- may not fill jpayne@69: // in st_ino. jpayne@69: // jpayne@69: // The Windows filesystem implementation builds the hashCode based on dwVolumeSerialNumber and jpayne@69: // dwFileIndex{Low,High} of the BY_HANDLE_FILE_INFORMATION structure. However, these are again jpayne@69: // not guaranteed to be unique on all filesystems. In particular the documentation says that jpayne@69: // ReFS uses 128-bit identifiers which can't be represented here, and again virtual filesystems jpayne@69: // may often not report real identifiers. jpayne@69: // jpayne@69: // Of course, the process of hashing values into a single hash code can also cause collisions jpayne@69: // even if the filesystem reports reliable information. jpayne@69: // jpayne@69: // Additionally note that this value is not reliable when returned by `lstat()`. You should jpayne@69: // actually open the object, then call `stat()` on the opened object. jpayne@69: jpayne@69: // Not currently included: jpayne@69: // - Access control info: Differs wildly across platforms, and KJ prefers capabilities anyway. jpayne@69: // - Other timestamps: Differs across platforms. jpayne@69: // - Device number: If you care, you're probably doing platform-specific stuff anyway. jpayne@69: jpayne@69: Metadata() = default; jpayne@69: Metadata(Type type, uint64_t size, uint64_t spaceUsed, Date lastModified, uint linkCount, jpayne@69: uint64_t hashCode) jpayne@69: : type(type), size(size), spaceUsed(spaceUsed), lastModified(lastModified), jpayne@69: linkCount(linkCount), hashCode(hashCode) {} jpayne@69: // TODO(cleanup): This constructor is redundant in C++14, but needed in C++11. jpayne@69: }; jpayne@69: jpayne@69: virtual Metadata stat() const = 0; jpayne@69: jpayne@69: virtual void sync() const = 0; jpayne@69: virtual void datasync() const = 0; jpayne@69: // Maps to fsync() and fdatasync() system calls. jpayne@69: // jpayne@69: // Also, when creating or overwriting a file, the first call to sync() atomically links the file jpayne@69: // into the filesystem (*after* syncing the data), so than incomplete data is never visible to jpayne@69: // other processes. (In practice this works by writing into a temporary file and then rename()ing jpayne@69: // it.) jpayne@69: jpayne@69: protected: jpayne@69: virtual Own cloneFsNode() const = 0; jpayne@69: // Implements clone(). Required to return an object with exactly the same type as this one. jpayne@69: // Hence, every subclass must implement this. jpayne@69: }; jpayne@69: jpayne@69: class ReadableFile: public FsNode { jpayne@69: public: jpayne@69: Own clone() const; jpayne@69: jpayne@69: String readAllText() const; jpayne@69: // Read all text in the file and return as a big string. jpayne@69: jpayne@69: Array readAllBytes() const; jpayne@69: // Read all bytes in the file and return as a big byte array. jpayne@69: // jpayne@69: // This differs from mmap() in that the read is performed all at once. Future changes to the file jpayne@69: // do not affect the returned copy. Consider using mmap() instead, particularly for large files. jpayne@69: jpayne@69: virtual size_t read(uint64_t offset, ArrayPtr buffer) const = 0; jpayne@69: // Fills `buffer` with data starting at `offset`. Returns the number of bytes actually read -- jpayne@69: // the only time this is less than `buffer.size()` is when EOF occurs mid-buffer. jpayne@69: jpayne@69: virtual Array mmap(uint64_t offset, uint64_t size) const = 0; jpayne@69: // Maps the file to memory read-only. The returned array always has exactly the requested size. jpayne@69: // Depending on the capabilities of the OS and filesystem, the mapping may or may not reflect jpayne@69: // changes that happen to the file after mmap() returns. jpayne@69: // jpayne@69: // Multiple calls to mmap() on the same file may or may not return the same mapping (it is jpayne@69: // immutable, so there's no possibility of interference). jpayne@69: // jpayne@69: // If the file cannot be mmap()ed, an implementation may choose to allocate a buffer on the heap, jpayne@69: // read into it, and return that. This should only happen if a real mmap() is impossible. jpayne@69: // jpayne@69: // The returned array is always exactly the size requested. However, accessing bytes beyond the jpayne@69: // current end of the file may raise SIGBUS, or may simply return zero. jpayne@69: jpayne@69: virtual Array mmapPrivate(uint64_t offset, uint64_t size) const = 0; jpayne@69: // Like mmap() but returns a view that the caller can modify. Modifications will not be written jpayne@69: // to the underlying file. Every call to this method returns a unique mapping. Changes made to jpayne@69: // the underlying file by other clients may or may not be reflected in the mapping -- in fact, jpayne@69: // some changes may be reflected while others aren't, even within the same mapping. jpayne@69: // jpayne@69: // In practice this is often implemented using copy-on-write pages. When you first write to a jpayne@69: // page, a copy is made. Hence, changes to the underlying file within that page stop being jpayne@69: // reflected in the mapping. jpayne@69: }; jpayne@69: jpayne@69: class AppendableFile: public FsNode, public OutputStream { jpayne@69: public: jpayne@69: Own clone() const; jpayne@69: jpayne@69: // All methods are inherited. jpayne@69: }; jpayne@69: jpayne@69: class WritableFileMapping { jpayne@69: public: jpayne@69: virtual ArrayPtr get() const = 0; jpayne@69: // Gets the mapped bytes. The returned array can be modified, and those changes may be written to jpayne@69: // the underlying file, but there is no guarantee that they are written unless you subsequently jpayne@69: // call changed(). jpayne@69: jpayne@69: virtual void changed(ArrayPtr slice) const = 0; jpayne@69: // Notifies the implementation that the given bytes have changed. For some implementations this jpayne@69: // may be a no-op while for others it may be necessary in order for the changes to be written jpayne@69: // back at all. jpayne@69: // jpayne@69: // `slice` must be a slice of `bytes()`. jpayne@69: jpayne@69: virtual void sync(ArrayPtr slice) const = 0; jpayne@69: // Implies `changed()`, and then waits until the range has actually been written to disk before jpayne@69: // returning. jpayne@69: // jpayne@69: // `slice` must be a slice of `bytes()`. jpayne@69: // jpayne@69: // On Windows, this calls FlushViewOfFile(). The documentation for this function implies that in jpayne@69: // some circumstances, to fully sync to physical disk, you may need to call FlushFileBuffers() on jpayne@69: // the file HANDLE as well. The documentation is not very clear on when and why this is needed. jpayne@69: // If you believe your program needs this, you can accomplish it by calling `.sync()` on the File jpayne@69: // object after calling `.sync()` on the WritableFileMapping. jpayne@69: }; jpayne@69: jpayne@69: class File: public ReadableFile { jpayne@69: public: jpayne@69: Own clone() const; jpayne@69: jpayne@69: void writeAll(ArrayPtr bytes) const; jpayne@69: void writeAll(StringPtr text) const; jpayne@69: // Completely replace the file with the given bytes or text. jpayne@69: jpayne@69: virtual void write(uint64_t offset, ArrayPtr data) const = 0; jpayne@69: // Write the given data starting at the given offset in the file. jpayne@69: jpayne@69: virtual void zero(uint64_t offset, uint64_t size) const = 0; jpayne@69: // Write zeros to the file, starting at `offset` and continuing for `size` bytes. If the platform jpayne@69: // supports it, this will "punch a hole" in the file, such that blocks that are entirely zeros jpayne@69: // do not take space on disk. jpayne@69: jpayne@69: virtual void truncate(uint64_t size) const = 0; jpayne@69: // Set the file end pointer to `size`. If `size` is less than the current size, data past the end jpayne@69: // is truncated. If `size` is larger than the current size, zeros are added to the end of the jpayne@69: // file. If the platform supports it, blocks containing all-zeros will not be stored to disk. jpayne@69: jpayne@69: virtual Own mmapWritable(uint64_t offset, uint64_t size) const = 0; jpayne@69: // Like ReadableFile::mmap() but returns a mapping for which any changes will be immediately jpayne@69: // visible in other mappings of the file on the same system and will eventually be written back jpayne@69: // to the file. jpayne@69: jpayne@69: virtual size_t copy(uint64_t offset, const ReadableFile& from, uint64_t fromOffset, jpayne@69: uint64_t size) const; jpayne@69: // Copies bytes from one file to another. jpayne@69: // jpayne@69: // Copies `size` bytes or to EOF, whichever comes first. Returns the number of bytes actually jpayne@69: // copied. Hint: Pass kj::maxValue for `size` to always copy to EOF. jpayne@69: // jpayne@69: // The copy is not atomic. Concurrent writes may lead to garbage results. jpayne@69: // jpayne@69: // The default implementation performs a series of reads and writes. Subclasses can often provide jpayne@69: // superior implementations that offload the work to the OS or even implement copy-on-write. jpayne@69: }; jpayne@69: jpayne@69: class ReadableDirectory: public FsNode { jpayne@69: // Read-only subset of `Directory`. jpayne@69: jpayne@69: public: jpayne@69: Own clone() const; jpayne@69: jpayne@69: virtual Array listNames() const = 0; jpayne@69: // List the contents of this directory. Does NOT include "." nor "..". jpayne@69: jpayne@69: struct Entry { jpayne@69: FsNode::Type type; jpayne@69: String name; jpayne@69: jpayne@69: inline bool operator< (const Entry& other) const { return name < other.name; } jpayne@69: inline bool operator> (const Entry& other) const { return name > other.name; } jpayne@69: inline bool operator<=(const Entry& other) const { return name <= other.name; } jpayne@69: inline bool operator>=(const Entry& other) const { return name >= other.name; } jpayne@69: // Convenience comparison operators to sort entries by name. jpayne@69: }; jpayne@69: jpayne@69: virtual Array listEntries() const = 0; jpayne@69: // List the contents of the directory including the type of each file. On some platforms and jpayne@69: // filesystems, this is just as fast as listNames(), but on others it may require stat()ing each jpayne@69: // file. jpayne@69: jpayne@69: virtual bool exists(PathPtr path) const = 0; jpayne@69: // Does the specified path exist? jpayne@69: // jpayne@69: // If the path is a symlink, the symlink is followed and the return value indicates if the target jpayne@69: // exists. If you want to know if the symlink exists, use lstat(). (This implies that listNames() jpayne@69: // may return names for which exists() reports false.) jpayne@69: jpayne@69: FsNode::Metadata lstat(PathPtr path) const; jpayne@69: virtual Maybe tryLstat(PathPtr path) const = 0; jpayne@69: // Gets metadata about the path. If the path is a symlink, it is not followed -- the metadata jpayne@69: // describes the symlink itself. `tryLstat()` returns null if the path doesn't exist. jpayne@69: jpayne@69: Own openFile(PathPtr path) const; jpayne@69: virtual Maybe> tryOpenFile(PathPtr path) const = 0; jpayne@69: // Open a file for reading. jpayne@69: // jpayne@69: // `tryOpenFile()` returns null if the path doesn't exist. Other errors still throw exceptions. jpayne@69: jpayne@69: Own openSubdir(PathPtr path) const; jpayne@69: virtual Maybe> tryOpenSubdir(PathPtr path) const = 0; jpayne@69: // Opens a subdirectory. jpayne@69: // jpayne@69: // `tryOpenSubdir()` returns null if the path doesn't exist. Other errors still throw exceptions. jpayne@69: jpayne@69: String readlink(PathPtr path) const; jpayne@69: virtual Maybe tryReadlink(PathPtr path) const = 0; jpayne@69: // If `path` is a symlink, reads and returns the link contents. jpayne@69: // jpayne@69: // Note that tryReadlink() differs subtly from tryOpen*(). For example, tryOpenFile() throws if jpayne@69: // the path is not a file (e.g. if it's a directory); it only returns null if the path doesn't jpayne@69: // exist at all. tryReadlink() returns null if either the path doesn't exist, or if it does exist jpayne@69: // but isn't a symlink. This is because if it were to throw instead, then almost every real-world jpayne@69: // use case of tryReadlink() would be forced to perform an lstat() first for the sole purpose of jpayne@69: // checking if it is a link, wasting a syscall and a path traversal. jpayne@69: // jpayne@69: // See Directory::symlink() for warnings about symlinks. jpayne@69: }; jpayne@69: jpayne@69: enum class WriteMode { jpayne@69: // Mode for opening a file (or directory) for write. jpayne@69: // jpayne@69: // (To open a file or directory read-only, do not specify a mode.) jpayne@69: // jpayne@69: // WriteMode is a bitfield. Hence, it overloads the bitwise logic operators. To check if a jpayne@69: // particular bit is set in a bitfield, use kj::has(), like: jpayne@69: // jpayne@69: // if (kj::has(mode, WriteMode::MUST_EXIST)) { jpayne@69: // requireExists(path); jpayne@69: // } jpayne@69: // jpayne@69: // (`if (mode & WriteMode::MUST_EXIST)` doesn't work because WriteMode is an enum class, which jpayne@69: // cannot be converted to bool. Alas, C++ does not allow you to define a conversion operator jpayne@69: // on an enum type, so we can't define a conversion to bool.) jpayne@69: jpayne@69: // ----------------------------------------- jpayne@69: // Core flags jpayne@69: // jpayne@69: // At least one of CREATE or MODIFY must be specified. Optionally, the two flags can be combined jpayne@69: // with a bitwise-OR. jpayne@69: jpayne@69: CREATE = 1, jpayne@69: // Create a new empty file. jpayne@69: // jpayne@69: // When not combined with MODIFY, if the file already exists (including as a broken symlink), jpayne@69: // tryOpenFile() returns null (and openFile() throws). jpayne@69: // jpayne@69: // When combined with MODIFY, if the path already exists, it will be opened as if CREATE hadn't jpayne@69: // been specified at all. If the path refers to a broken symlink, the file at the target of the jpayne@69: // link will be created (if its parent directory exists). jpayne@69: jpayne@69: MODIFY = 2, jpayne@69: // Modify an existing file. jpayne@69: // jpayne@69: // When not combined with CREATE, if the file doesn't exist (including if it is a broken symlink), jpayne@69: // tryOpenFile() returns null (and openFile() throws). jpayne@69: // jpayne@69: // When combined with CREATE, if the path doesn't exist, it will be created as if MODIFY hadn't jpayne@69: // been specified at all. If the path refers to a broken symlink, the file at the target of the jpayne@69: // link will be created (if its parent directory exists). jpayne@69: jpayne@69: // ----------------------------------------- jpayne@69: // Additional flags jpayne@69: // jpayne@69: // Any number of these may be OR'd with the core flags. jpayne@69: jpayne@69: CREATE_PARENT = 4, jpayne@69: // Indicates that if the target node's parent directory doesn't exist, it should be created jpayne@69: // automatically, along with its parent, and so on. This creation is NOT atomic. jpayne@69: // jpayne@69: // This bit only makes sense with CREATE or REPLACE. jpayne@69: jpayne@69: EXECUTABLE = 8, jpayne@69: // Mark this file executable, if this is a meaningful designation on the host platform. jpayne@69: jpayne@69: PRIVATE = 16, jpayne@69: // Indicates that this file is sensitive and should have permissions masked so that it is only jpayne@69: // accessible by the current user. jpayne@69: // jpayne@69: // When this is not used, the platform's default access control settings are used. On Unix, jpayne@69: // that usually means the umask is applied. On Windows, it means permissions are inherited from jpayne@69: // the parent. jpayne@69: }; jpayne@69: jpayne@69: inline constexpr WriteMode operator|(WriteMode a, WriteMode b) { jpayne@69: return static_cast(static_cast(a) | static_cast(b)); jpayne@69: } jpayne@69: inline constexpr WriteMode operator&(WriteMode a, WriteMode b) { jpayne@69: return static_cast(static_cast(a) & static_cast(b)); jpayne@69: } jpayne@69: inline constexpr WriteMode operator+(WriteMode a, WriteMode b) { jpayne@69: return static_cast(static_cast(a) | static_cast(b)); jpayne@69: } jpayne@69: inline constexpr WriteMode operator-(WriteMode a, WriteMode b) { jpayne@69: return static_cast(static_cast(a) & ~static_cast(b)); jpayne@69: } jpayne@69: template > jpayne@69: bool has(T haystack, T needle) { jpayne@69: return (static_cast<__underlying_type(T)>(haystack) & jpayne@69: static_cast<__underlying_type(T)>(needle)) == jpayne@69: static_cast<__underlying_type(T)>(needle); jpayne@69: } jpayne@69: jpayne@69: enum class TransferMode { jpayne@69: // Specifies desired behavior for Directory::transfer(). jpayne@69: jpayne@69: MOVE, jpayne@69: // The node is moved to the new location, i.e. the old location is deleted. If possible, this jpayne@69: // move is performed without copying, otherwise it is performed as a copy followed by a delete. jpayne@69: jpayne@69: LINK, jpayne@69: // The new location becomes a synonym for the old location (a "hard link"). Filesystems have jpayne@69: // varying support for this -- typically, it is not supported on directories. jpayne@69: jpayne@69: COPY jpayne@69: // The new location becomes a copy of the old. jpayne@69: // jpayne@69: // Some filesystems may implement this in terms of copy-on-write. jpayne@69: // jpayne@69: // If the filesystem supports sparse files, COPY takes sparseness into account -- it will punch jpayne@69: // holes in the target file where holes exist in the source file. jpayne@69: }; jpayne@69: jpayne@69: class Directory: public ReadableDirectory { jpayne@69: // Refers to a specific directory on disk. jpayne@69: // jpayne@69: // A `Directory` object *only* provides access to children of the directory, not parents. That jpayne@69: // is, you cannot open the file "..", nor jump to the root directory with "/". jpayne@69: // jpayne@69: // On OSs that support it, a `Directory` is backed by an open handle to the directory node. This jpayne@69: // means: jpayne@69: // - If the directory is renamed on-disk, the `Directory` object still points at it. jpayne@69: // - Opening files in the directory only requires the OS to traverse the path from the directory jpayne@69: // to the file; it doesn't have to re-traverse all the way from the filesystem root. jpayne@69: // jpayne@69: // On Windows, a `Directory` object holds a lock on the underlying directory such that it cannot jpayne@69: // be renamed nor deleted while the object exists. This is necessary because Windows does not jpayne@69: // fully support traversing paths relative to file handles (it does for some operations but not jpayne@69: // all), so the KJ filesystem implementation is forced to remember the full path and needs to jpayne@69: // ensure that the path is not invalidated. If, in the future, Windows fully supports jpayne@69: // handle-relative paths, KJ may stop locking directories in this way, so do not rely on this jpayne@69: // behavior. jpayne@69: jpayne@69: public: jpayne@69: Own clone() const; jpayne@69: jpayne@69: template jpayne@69: class Replacer { jpayne@69: // Implements an atomic replacement of a file or directory, allowing changes to be made to jpayne@69: // storage in a way that avoids losing data in a power outage and prevents other processes jpayne@69: // from observing content in an inconsistent state. jpayne@69: // jpayne@69: // `T` may be `File` or `Directory`. For readability, the text below describes replacing a jpayne@69: // file, but the logic is the same for directories. jpayne@69: // jpayne@69: // When you call `Directory::replaceFile()`, a temporary file is created, but the specified jpayne@69: // path is not yet touched. You may call `get()` to obtain the temporary file object, through jpayne@69: // which you may initialize its content, knowing that no other process can see it yet. The file jpayne@69: // is atomically moved to its final path when you call `commit()`. If you destroy the Replacer jpayne@69: // without calling commit(), the temporary file is deleted. jpayne@69: // jpayne@69: // Note that most operating systems sadly do not support creating a truly unnamed temporary file jpayne@69: // and then linking it in later. Moreover, the file cannot necessarily be created in the system jpayne@69: // temporary directory because it might not be on the same filesystem as the target. Therefore, jpayne@69: // the replacement file may initially be created in the same directory as its eventual target. jpayne@69: // The implementation of Directory will choose a name that is unique and "hidden" according to jpayne@69: // the conventions of the filesystem. Additionally, the implementation of Directory will avoid jpayne@69: // returning these temporary files from its list*() methods, in order to avoid observable jpayne@69: // inconsistencies across platforms. jpayne@69: public: jpayne@69: explicit Replacer(WriteMode mode); jpayne@69: jpayne@69: virtual const T& get() = 0; jpayne@69: // Gets the File or Directory representing the replacement data. Fill in this object before jpayne@69: // calling commit(). jpayne@69: jpayne@69: void commit(); jpayne@69: virtual bool tryCommit() = 0; jpayne@69: // Commit the replacement. jpayne@69: // jpayne@69: // `tryCommit()` may return false based on the CREATE/MODIFY bits passed as the WriteMode when jpayne@69: // the replacement was initiated. (If CREATE but not MODIFY was used, tryCommit() returns jpayne@69: // false to indicate that the target file already existed. If MODIFY but not CREATE was used, jpayne@69: // tryCommit() returns false to indicate that the file didn't exist.) jpayne@69: // jpayne@69: // `commit()` is atomic, meaning that there is no point in time at which other processes jpayne@69: // observing the file will see it in an intermediate state -- they will either see the old jpayne@69: // content or the complete new content. This includes in the case of a power outage or machine jpayne@69: // failure: on recovery, the file will either be in the old state or the new state, but not in jpayne@69: // some intermediate state. jpayne@69: // jpayne@69: // It's important to note that a power failure *after commit() returns* can still revert the jpayne@69: // file to its previous state. That is, `commit()` does NOT guarantee that, upon return, the jpayne@69: // new content is durable. In order to guarantee this, you must call `sync()` on the immediate jpayne@69: // parent directory of the replaced file. jpayne@69: // jpayne@69: // Note that, sadly, not all filesystems / platforms are capable of supporting all of the jpayne@69: // guarantees documented above. In such cases, commit() will make a best-effort attempt to do jpayne@69: // what it claims. Some examples of possible problems include: jpayne@69: // - Any guarantees about durability through a power outage probably require a journaling jpayne@69: // filesystem. jpayne@69: // - Many platforms do not support atomically replacing a non-empty directory. Linux does as jpayne@69: // of kernel 3.15 (via the renameat2() syscall using RENAME_EXCHANGE). Where not supported, jpayne@69: // the old directory will be moved away just before the replacement is moved into place. jpayne@69: // - Many platforms do not support atomically requiring the existence or non-existence of a jpayne@69: // file before replacing it. In these cases, commit() may have to perform the check as a jpayne@69: // separate step, with a small window for a race condition. jpayne@69: // - Many platforms do not support "unlinking" a non-empty directory, meaning that a replaced jpayne@69: // directory will need to be deconstructed by deleting all contents. If another process has jpayne@69: // the directory open when it is replaced, that process will observe the contents jpayne@69: // disappearing after the replacement (actually, a swap) has taken place. This differs from jpayne@69: // files, where a process that has opened a file before it is replaced will continue see the jpayne@69: // file's old content unchanged after the replacement. jpayne@69: // - On Windows, there are multiple ways to replace one file with another in a single system jpayne@69: // call, but none are documented as being atomic. KJ always uses `MoveFileEx()` with jpayne@69: // MOVEFILE_REPLACE_EXISTING. While the alternative `ReplaceFile()` is attractive for many jpayne@69: // reasons, it has the critical problem that it cannot be used when the source file has open jpayne@69: // file handles, which is generally the case when using Replacer. jpayne@69: jpayne@69: protected: jpayne@69: const WriteMode mode; jpayne@69: }; jpayne@69: jpayne@69: using ReadableDirectory::openFile; jpayne@69: using ReadableDirectory::openSubdir; jpayne@69: using ReadableDirectory::tryOpenFile; jpayne@69: using ReadableDirectory::tryOpenSubdir; jpayne@69: jpayne@69: Own openFile(PathPtr path, WriteMode mode) const; jpayne@69: virtual Maybe> tryOpenFile(PathPtr path, WriteMode mode) const = 0; jpayne@69: // Open a file for writing. jpayne@69: // jpayne@69: // `tryOpenFile()` returns null if the path is required to exist but doesn't (MODIFY or REPLACE) jpayne@69: // or if the path is required not to exist but does (CREATE or RACE). These are the only cases jpayne@69: // where it returns null -- all other types of errors (like "access denied") throw exceptions. jpayne@69: jpayne@69: virtual Own> replaceFile(PathPtr path, WriteMode mode) const = 0; jpayne@69: // Construct a file which, when ready, will be atomically moved to `path`, replacing whatever jpayne@69: // is there already. See `Replacer` for detalis. jpayne@69: // jpayne@69: // The `CREATE` and `MODIFY` bits of `mode` are not enforced until commit time, hence jpayne@69: // `replaceFile()` has no "try" variant. jpayne@69: jpayne@69: virtual Own createTemporary() const = 0; jpayne@69: // Create a temporary file backed by this directory's filesystem, but which isn't linked into jpayne@69: // the directory tree. The file is deleted from disk when all references to it have been dropped. jpayne@69: jpayne@69: Own appendFile(PathPtr path, WriteMode mode) const; jpayne@69: virtual Maybe> tryAppendFile(PathPtr path, WriteMode mode) const = 0; jpayne@69: // Opens the file for appending only. Useful for log files. jpayne@69: // jpayne@69: // If the underlying filesystem supports it, writes to the file will always be appended even if jpayne@69: // other writers are writing to the same file at the same time -- however, some implementations jpayne@69: // may instead assume that no other process is changing the file size between writes. jpayne@69: jpayne@69: Own openSubdir(PathPtr path, WriteMode mode) const; jpayne@69: virtual Maybe> tryOpenSubdir(PathPtr path, WriteMode mode) const = 0; jpayne@69: // Opens a subdirectory for writing. jpayne@69: jpayne@69: virtual Own> replaceSubdir(PathPtr path, WriteMode mode) const = 0; jpayne@69: // Construct a directory which, when ready, will be atomically moved to `path`, replacing jpayne@69: // whatever is there already. See `Replacer` for detalis. jpayne@69: // jpayne@69: // The `CREATE` and `MODIFY` bits of `mode` are not enforced until commit time, hence jpayne@69: // `replaceSubdir()` has no "try" variant. jpayne@69: jpayne@69: void symlink(PathPtr linkpath, StringPtr content, WriteMode mode) const; jpayne@69: virtual bool trySymlink(PathPtr linkpath, StringPtr content, WriteMode mode) const = 0; jpayne@69: // Create a symlink. `content` is the raw text which will be written into the symlink node. jpayne@69: // How this text is interpreted is entirely dependent on the filesystem. Note in particular that: jpayne@69: // - Windows will require a path that uses backslashes as the separator. jpayne@69: // - InMemoryDirectory does not support symlinks containing "..". jpayne@69: // jpayne@69: // Unfortunately under many implementations symlink() can be used to break out of the directory jpayne@69: // by writing an absolute path or utilizing "..". Do not call this method with a value for jpayne@69: // `target` that you don't trust. jpayne@69: // jpayne@69: // `mode` must be CREATE or REPLACE, not MODIFY. CREATE_PARENT is honored but EXECUTABLE and jpayne@69: // PRIVATE have no effect. `trySymlink()` returns false in CREATE mode when the target already jpayne@69: // exists. jpayne@69: jpayne@69: void transfer(PathPtr toPath, WriteMode toMode, jpayne@69: PathPtr fromPath, TransferMode mode) const; jpayne@69: void transfer(PathPtr toPath, WriteMode toMode, jpayne@69: const Directory& fromDirectory, PathPtr fromPath, jpayne@69: TransferMode mode) const; jpayne@69: virtual bool tryTransfer(PathPtr toPath, WriteMode toMode, jpayne@69: const Directory& fromDirectory, PathPtr fromPath, jpayne@69: TransferMode mode) const; jpayne@69: virtual Maybe tryTransferTo(const Directory& toDirectory, PathPtr toPath, WriteMode toMode, jpayne@69: PathPtr fromPath, TransferMode mode) const; jpayne@69: // Move, link, or copy a file/directory tree from one location to another. jpayne@69: // jpayne@69: // Filesystems vary in what kinds of transfers are allowed, especially for TransferMode::LINK, jpayne@69: // and whether TransferMode::MOVE is implemented as an actual move vs. copy+delete. jpayne@69: // jpayne@69: // tryTransfer() returns false if the source location didn't exist, or when `toMode` is CREATE jpayne@69: // and the target already exists. The default implementation implements only TransferMode::COPY. jpayne@69: // jpayne@69: // tryTransferTo() exists to implement double-dispatch. It should be called as a fallback by jpayne@69: // implementations of tryTransfer() in cases where the target directory would otherwise fail or jpayne@69: // perform a pessimal transfer. The default implementation returns nullptr, which the caller jpayne@69: // should interpret as: "I don't have any special optimizations; do the obvious thing." jpayne@69: // jpayne@69: // `toMode` controls how the target path is created. CREATE_PARENT is honored but EXECUTABLE and jpayne@69: // PRIVATE have no effect. jpayne@69: jpayne@69: void remove(PathPtr path) const; jpayne@69: virtual bool tryRemove(PathPtr path) const = 0; jpayne@69: // Deletes/unlinks the given path. If the path names a directory, it is recursively deleted. jpayne@69: // jpayne@69: // tryRemove() returns false in the specific case that the path doesn't exist. remove() would jpayne@69: // throw in this case. In all other error cases (like "access denied"), tryRemove() still throws; jpayne@69: // it is only "does not exist" that produces a false return. jpayne@69: // jpayne@69: // WARNING: The Windows implementation of recursive deletion is currently not safe to call from a jpayne@69: // privileged process to delete directories writable by unprivileged users, due to a race jpayne@69: // condition in which the user could trick the algorithm into following a symlink and deleting jpayne@69: // everything at the destination. This race condition is not present in the Unix jpayne@69: // implementation. Fixing it for Windows would require rewriting a lot of code to use different jpayne@69: // APIs. If you're interested, see the TODO(security) in filesystem-disk-win32.c++. jpayne@69: jpayne@69: // TODO(someday): jpayne@69: // - Support sockets? There's no openat()-like interface for sockets, so it's hard to support jpayne@69: // them currently. Also you'd probably want to use them with the async library. jpayne@69: // - Support named pipes? Unclear if there's a use case that isn't better-served by sockets. jpayne@69: // Then again, they can be openat()ed. jpayne@69: // - Support watching for changes (inotify). Probably also requires the async library. Also jpayne@69: // lacks openat()-like semantics. jpayne@69: // - xattrs -- linux-specific jpayne@69: // - chown/chmod/etc. -- unix-specific, ACLs, eww jpayne@69: // - set timestamps -- only needed by archiving programs/ jpayne@69: // - advisory locks jpayne@69: // - sendfile? jpayne@69: // - fadvise and such jpayne@69: jpayne@69: private: jpayne@69: static void commitFailed(WriteMode mode); jpayne@69: }; jpayne@69: jpayne@69: class Filesystem { jpayne@69: public: jpayne@69: virtual const Directory& getRoot() const = 0; jpayne@69: // Get the filesystem's root directory, as of the time the Filesystem object was created. jpayne@69: jpayne@69: virtual const Directory& getCurrent() const = 0; jpayne@69: // Get the filesystem's current directory, as of the time the Filesystem object was created. jpayne@69: jpayne@69: virtual PathPtr getCurrentPath() const = 0; jpayne@69: // Get the path from the root to the current directory, as of the time the Filesystem object was jpayne@69: // created. Note that because a `Directory` does not provide access to its parent, if you want to jpayne@69: // follow `..` from the current directory, you must use `getCurrentPath().eval("..")` or jpayne@69: // `getCurrentPath().parent()`. jpayne@69: // jpayne@69: // This function attempts to determine the path as it appeared in the user's shell before this jpayne@69: // program was started. That means, if the user had `cd`ed into a symlink, the path through that jpayne@69: // symlink is returned, *not* the canonical path. jpayne@69: // jpayne@69: // Because of this, there is an important difference between how the operating system interprets jpayne@69: // "../foo" and what you get when you write `getCurrentPath().eval("../foo")`: The former jpayne@69: // will interpret ".." relative to the directory's canonical path, whereas the latter will jpayne@69: // interpret it relative to the path shown in the user's shell. In practice, the latter is jpayne@69: // almost always what the user wants! But the former behavior is what almost all commands do jpayne@69: // in practice, and it leads to confusion. KJ commands should implement the behavior the user jpayne@69: // expects. jpayne@69: }; jpayne@69: jpayne@69: // ======================================================================================= jpayne@69: jpayne@69: Own newInMemoryFile(const Clock& clock); jpayne@69: Own newInMemoryDirectory(const Clock& clock); jpayne@69: // Construct file and directory objects which reside in-memory. jpayne@69: // jpayne@69: // InMemoryFile has the following special properties: jpayne@69: // - The backing store is not sparse and never gets smaller even if you truncate the file. jpayne@69: // - While a non-private memory mapping exists, the backing store cannot get larger. Any operation jpayne@69: // which would expand it will throw. jpayne@69: // jpayne@69: // InMemoryDirectory has the following special properties: jpayne@69: // - Symlinks are processed using Path::parse(). This implies that a symlink cannot point to a jpayne@69: // parent directory -- InMemoryDirectory does not know its parent. jpayne@69: // - link() can link directory nodes in addition to files. jpayne@69: // - link() and rename() accept any kind of Directory as `fromDirectory` -- it doesn't need to be jpayne@69: // another InMemoryDirectory. However, for rename(), the from path must be a directory. jpayne@69: jpayne@69: Own newFileAppender(Own inner); jpayne@69: // Creates an AppendableFile by wrapping a File. Note that this implementation assumes it is the jpayne@69: // only writer. A correct implementation should always append to the file even if other writes jpayne@69: // are happening simultaneously, as is achieved with the O_APPEND flag to open(2), but that jpayne@69: // behavior is not possible to emulate on top of `File`. jpayne@69: jpayne@69: #if _WIN32 jpayne@69: typedef AutoCloseHandle OsFileHandle; jpayne@69: #else jpayne@69: typedef AutoCloseFd OsFileHandle; jpayne@69: #endif jpayne@69: jpayne@69: Own newDiskReadableFile(OsFileHandle fd); jpayne@69: Own newDiskAppendableFile(OsFileHandle fd); jpayne@69: Own newDiskFile(OsFileHandle fd); jpayne@69: Own newDiskReadableDirectory(OsFileHandle fd); jpayne@69: Own newDiskDirectory(OsFileHandle fd); jpayne@69: // Wrap a file descriptor (or Windows HANDLE) as various filesystem types. jpayne@69: jpayne@69: Own newDiskFilesystem(); jpayne@69: // Get at implementation of `Filesystem` representing the real filesystem. jpayne@69: // jpayne@69: // DO NOT CALL THIS except at the top level of your program, e.g. in main(). Anywhere else, you jpayne@69: // should instead have your caller pass in a Filesystem object, or a specific Directory object, jpayne@69: // or whatever it is that your code needs. This ensures that your code supports dependency jpayne@69: // injection, which makes it more reusable and testable. jpayne@69: // jpayne@69: // newDiskFilesystem() reads the current working directory at the time it is called. The returned jpayne@69: // object is not affected by subsequent calls to chdir(). jpayne@69: jpayne@69: // ======================================================================================= jpayne@69: // inline implementation details jpayne@69: jpayne@69: inline Path::Path(decltype(nullptr)): parts(nullptr) {} jpayne@69: inline Path::Path(std::initializer_list parts) jpayne@69: : Path(arrayPtr(parts.begin(), parts.end())) {} jpayne@69: inline Path::Path(Array parts, decltype(ALREADY_CHECKED)) jpayne@69: : parts(kj::mv(parts)) {} jpayne@69: inline Path Path::clone() const { return PathPtr(*this).clone(); } jpayne@69: inline Path Path::append(Path&& suffix) const& { return PathPtr(*this).append(kj::mv(suffix)); } jpayne@69: inline Path Path::append(PathPtr suffix) const& { return PathPtr(*this).append(suffix); } jpayne@69: inline Path Path::append(StringPtr suffix) const& { return append(Path(suffix)); } jpayne@69: inline Path Path::append(StringPtr suffix) && { return kj::mv(*this).append(Path(suffix)); } jpayne@69: inline Path Path::append(String&& suffix) const& { return append(Path(kj::mv(suffix))); } jpayne@69: inline Path Path::append(String&& suffix) && { return kj::mv(*this).append(Path(kj::mv(suffix))); } jpayne@69: inline Path Path::eval(StringPtr pathText) const& { return PathPtr(*this).eval(pathText); } jpayne@69: inline PathPtr Path::basename() const& { return PathPtr(*this).basename(); } jpayne@69: inline PathPtr Path::parent() const& { return PathPtr(*this).parent(); } jpayne@69: inline const String& Path::operator[](size_t i) const& { return parts[i]; } jpayne@69: inline String Path::operator[](size_t i) && { return kj::mv(parts[i]); } jpayne@69: inline size_t Path::size() const { return parts.size(); } jpayne@69: inline const String* Path::begin() const { return parts.begin(); } jpayne@69: inline const String* Path::end() const { return parts.end(); } jpayne@69: inline PathPtr Path::slice(size_t start, size_t end) const& { jpayne@69: return PathPtr(*this).slice(start, end); jpayne@69: } jpayne@69: inline bool Path::operator==(PathPtr other) const { return PathPtr(*this) == other; } jpayne@69: inline bool Path::operator!=(PathPtr other) const { return PathPtr(*this) != other; } jpayne@69: inline bool Path::operator< (PathPtr other) const { return PathPtr(*this) < other; } jpayne@69: inline bool Path::operator> (PathPtr other) const { return PathPtr(*this) > other; } jpayne@69: inline bool Path::operator<=(PathPtr other) const { return PathPtr(*this) <= other; } jpayne@69: inline bool Path::operator>=(PathPtr other) const { return PathPtr(*this) >= other; } jpayne@69: inline bool Path::operator==(const Path& other) const { return PathPtr(*this) == PathPtr(other); } jpayne@69: inline bool Path::operator!=(const Path& other) const { return PathPtr(*this) != PathPtr(other); } jpayne@69: inline bool Path::operator< (const Path& other) const { return PathPtr(*this) < PathPtr(other); } jpayne@69: inline bool Path::operator> (const Path& other) const { return PathPtr(*this) > PathPtr(other); } jpayne@69: inline bool Path::operator<=(const Path& other) const { return PathPtr(*this) <= PathPtr(other); } jpayne@69: inline bool Path::operator>=(const Path& other) const { return PathPtr(*this) >= PathPtr(other); } jpayne@69: inline uint Path::hashCode() const { return kj::hashCode(parts); } jpayne@69: inline bool Path::startsWith(PathPtr prefix) const { return PathPtr(*this).startsWith(prefix); } jpayne@69: inline bool Path::endsWith (PathPtr suffix) const { return PathPtr(*this).endsWith (suffix); } jpayne@69: inline String Path::toString(bool absolute) const { return PathPtr(*this).toString(absolute); } jpayne@69: inline Path Path::evalWin32(StringPtr pathText) const& { jpayne@69: return PathPtr(*this).evalWin32(pathText); jpayne@69: } jpayne@69: inline String Path::toWin32String(bool absolute) const { jpayne@69: return PathPtr(*this).toWin32String(absolute); jpayne@69: } jpayne@69: inline Array Path::forWin32Api(bool absolute) const { jpayne@69: return PathPtr(*this).forWin32Api(absolute); jpayne@69: } jpayne@69: jpayne@69: inline PathPtr::PathPtr(decltype(nullptr)): parts(nullptr) {} jpayne@69: inline PathPtr::PathPtr(const Path& path): parts(path.parts) {} jpayne@69: inline PathPtr::PathPtr(ArrayPtr parts): parts(parts) {} jpayne@69: inline Path PathPtr::append(StringPtr suffix) const { return append(Path(suffix)); } jpayne@69: inline Path PathPtr::append(String&& suffix) const { return append(Path(kj::mv(suffix))); } jpayne@69: inline const String& PathPtr::operator[](size_t i) const { return parts[i]; } jpayne@69: inline size_t PathPtr::size() const { return parts.size(); } jpayne@69: inline const String* PathPtr::begin() const { return parts.begin(); } jpayne@69: inline const String* PathPtr::end() const { return parts.end(); } jpayne@69: inline PathPtr PathPtr::slice(size_t start, size_t end) const { jpayne@69: return PathPtr(parts.slice(start, end)); jpayne@69: } jpayne@69: inline bool PathPtr::operator!=(PathPtr other) const { return !(*this == other); } jpayne@69: inline bool PathPtr::operator> (PathPtr other) const { return other < *this; } jpayne@69: inline bool PathPtr::operator<=(PathPtr other) const { return !(other < *this); } jpayne@69: inline bool PathPtr::operator>=(PathPtr other) const { return !(*this < other); } jpayne@69: inline uint PathPtr::hashCode() const { return kj::hashCode(parts); } jpayne@69: inline String PathPtr::toWin32String(bool absolute) const { jpayne@69: return toWin32StringImpl(absolute, false); jpayne@69: } jpayne@69: jpayne@69: #if _WIN32 jpayne@69: inline Path Path::evalNative(StringPtr pathText) const& { jpayne@69: return evalWin32(pathText); jpayne@69: } jpayne@69: inline Path Path::evalNative(StringPtr pathText) && { jpayne@69: return kj::mv(*this).evalWin32(pathText); jpayne@69: } jpayne@69: inline String Path::toNativeString(bool absolute) const { jpayne@69: return toWin32String(absolute); jpayne@69: } jpayne@69: inline Path PathPtr::evalNative(StringPtr pathText) const { jpayne@69: return evalWin32(pathText); jpayne@69: } jpayne@69: inline String PathPtr::toNativeString(bool absolute) const { jpayne@69: return toWin32String(absolute); jpayne@69: } jpayne@69: #else jpayne@69: inline Path Path::evalNative(StringPtr pathText) const& { jpayne@69: return eval(pathText); jpayne@69: } jpayne@69: inline Path Path::evalNative(StringPtr pathText) && { jpayne@69: return kj::mv(*this).eval(pathText); jpayne@69: } jpayne@69: inline String Path::toNativeString(bool absolute) const { jpayne@69: return toString(absolute); jpayne@69: } jpayne@69: inline Path PathPtr::evalNative(StringPtr pathText) const { jpayne@69: return eval(pathText); jpayne@69: } jpayne@69: inline String PathPtr::toNativeString(bool absolute) const { jpayne@69: return toString(absolute); jpayne@69: } jpayne@69: #endif // _WIN32, else jpayne@69: jpayne@69: inline Own FsNode::clone() const { return cloneFsNode(); } jpayne@69: inline Own ReadableFile::clone() const { jpayne@69: return cloneFsNode().downcast(); jpayne@69: } jpayne@69: inline Own AppendableFile::clone() const { jpayne@69: return cloneFsNode().downcast(); jpayne@69: } jpayne@69: inline Own File::clone() const { return cloneFsNode().downcast(); } jpayne@69: inline Own ReadableDirectory::clone() const { jpayne@69: return cloneFsNode().downcast(); jpayne@69: } jpayne@69: inline Own Directory::clone() const { jpayne@69: return cloneFsNode().downcast(); jpayne@69: } jpayne@69: jpayne@69: inline void Directory::transfer( jpayne@69: PathPtr toPath, WriteMode toMode, PathPtr fromPath, TransferMode mode) const { jpayne@69: return transfer(toPath, toMode, *this, fromPath, mode); jpayne@69: } jpayne@69: jpayne@69: template jpayne@69: inline Directory::Replacer::Replacer(WriteMode mode): mode(mode) {} jpayne@69: jpayne@69: template jpayne@69: void Directory::Replacer::commit() { jpayne@69: if (!tryCommit()) commitFailed(mode); jpayne@69: } jpayne@69: jpayne@69: } // namespace kj jpayne@69: jpayne@69: KJ_END_HEADER