| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SRC_LIB_STORAGE_VFS_CPP_VNODE_H_ |
| #define SRC_LIB_STORAGE_VFS_CPP_VNODE_H_ |
| |
| #include <lib/fdio/io.h> |
| #include <lib/fdio/vfs.h> |
| #include <lib/fit/function.h> |
| #include <lib/fit/result.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <zircon/assert.h> |
| #include <zircon/compiler.h> |
| #include <zircon/types.h> |
| |
| #include <string_view> |
| #include <type_traits> |
| #include <utility> |
| |
| #include <fbl/intrusive_double_list.h> |
| #include <fbl/intrusive_single_list.h> |
| #include <fbl/macros.h> |
| #include <fbl/ref_counted_internal.h> |
| #include <fbl/ref_ptr.h> |
| |
| #include "src/lib/storage/vfs/cpp/ref_counted.h" |
| #include "src/lib/storage/vfs/cpp/shared_mutex.h" |
| #include "src/lib/storage/vfs/cpp/vfs_types.h" |
| |
| #ifdef __Fuchsia__ |
| #include <fuchsia/io/llcpp/fidl.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/stream.h> |
| #include <zircon/device/vfs.h> |
| |
| #include "src/lib/storage/vfs/cpp/mount_channel.h" |
| #endif // __Fuchsia__ |
| |
| namespace fs { |
| |
| class Vfs; |
| struct VdirCookie; |
| |
| inline bool vfs_valid_name(std::string_view name) { |
| return name.length() > 0 && name.length() <= NAME_MAX && |
| memchr(name.data(), '/', name.length()) == nullptr && name != "." && name != ".."; |
| } |
| |
| // The VFS interface declares a default abstract Vnode class with common operations that may be |
| // overridden. |
| // |
| // The ops are used for dispatch and the lifecycle of Vnodes are owned by RefPtrs. |
| // |
| // All names passed to the Vnode class are valid according to "vfs_valid_name". |
| class Vnode : public VnodeRefCounted<Vnode>, public fbl::Recyclable<Vnode> { |
| public: |
| virtual ~Vnode(); |
| virtual void fbl_recycle() { delete this; } |
| |
| template <typename T> |
| class Validated { |
| public: |
| Validated(const Validated&) = default; |
| Validated& operator=(const Validated&) = default; |
| Validated(Validated&&) noexcept = default; |
| Validated& operator=(Validated&&) noexcept = default; |
| |
| const T& value() const { return value_; } |
| const T* operator->() const { return &value(); } |
| const T& operator*() const { return value(); } |
| |
| private: |
| explicit Validated(T value) : value_(value) {} |
| friend class Vnode; // Such that only |Vnode| methods may mint new instances of |Validated<T>|. |
| T value_; |
| }; |
| using ValidatedOptions = Validated<VnodeConnectionOptions>; |
| |
| // METHODS FOR OPTION VALIDATION AND PROTOCOL NEGOTIATION |
| // |
| // Implementations should override |GetProtocols| to express which representation(s) are supported |
| // by the vnode. Implementations may optionally override |Negotiate| to insert custom tie-breaking |
| // behavior when the vnode supports multiple protocols, and the client requested multiple at open |
| // time. |
| |
| // Returns the set of all protocols supported by the vnode. |
| virtual VnodeProtocolSet GetProtocols() const = 0; |
| |
| // Returns true iff the vnode supports _any_ protocol requested by |protocols|. |
| bool Supports(VnodeProtocolSet protocols) const; |
| |
| // To be overridden by implementations to check that it is valid to access the vnode with the |
| // given |rights|. The default implementation always returns true. The vnode will only be opened |
| // for a particular request if the validation passes. |
| virtual bool ValidateRights(Rights rights); |
| |
| // Ensures that it is valid to access the vnode with given connection options. The vnode will only |
| // be opened for a particular request if the validation returns |fit::ok(...)|. |
| // |
| // The |fit::ok| variant of the return value is a |ValidatedOptions| object that encodes the fact |
| // that |options| has been validated. It may be used to call other functions that only accepts |
| // validated options. |
| // |
| // The |fit::error| variant of the return value contains a suitable error code |
| // when validation fails. |
| fit::result<ValidatedOptions, zx_status_t> ValidateOptions(VnodeConnectionOptions options); |
| |
| // Picks one protocol from |protocols|, when the intersection of the protocols requested by the |
| // client and the ones supported by the vnode has more than one elements i.e. tie-breaking is |
| // required to determine the resultant protocol. |
| // |
| // This method is only called when tie-breaking is required. |protocols| is guaranteed to be a |
| // subset of the supported protocols. The default implementation performs tie-breaking in the |
| // order of element declaration within |VnodeProtocol|. |
| virtual VnodeProtocol Negotiate(VnodeProtocolSet protocols) const; |
| |
| // Opens the vnode. This is a callback to signal that a new connection is about to be created and |
| // I/O operations will follow. In addition, it provides an opportunity to redirect subsequent I/O. |
| // If the open fails, the file will be deemed to be not opened and Close() will not be called. |
| // |
| // Vnode implementations should override OpenNode() which this function calls after some |
| // bookeeping. |
| // |
| // |options| contain the flags and rights supplied by the client, parsed into a struct with |
| // individual fields. It will have already been validated by |ValidateOptions|. |
| // |
| // Open is never invoked if |options.flags| includes |node_reference|. This behavior corresponds |
| // to Posix open()'s O_PATH flag which will create a thing representing the path to the file |
| // without giving the ability to do most operations like read or write. In the future, we may want |
| // the ability to track these connections, in which case we should add a Connect()/Disconnect() |
| // pair that would surround the Open()/Close() for the normal case, but would be called regardless |
| // of the flags to cover the node-reference case. |
| // |
| // If the implementation of |Open()| sets |out_redirect| to a non-null value, all following I/O |
| // operations on the opened object will be redirected to the indicated vnode instead of being |
| // handled by this instance. This is useful when implementing lazy files/pseudo files, where a |
| // different vnode may be used for each new connection to a file. Note that the |out_redirect| |
| // vnode is not |Open()|ed further for the purpose of creating this connection. Furthermore, the |
| // redirected vnode must support the same set of protocols as the original vnode. |
| zx_status_t Open(ValidatedOptions options, fbl::RefPtr<Vnode>* out_redirect) |
| __TA_EXCLUDES(mutex_); |
| |
| // Same as |Open|, but calls |ValidateOptions| on |options| automatically. Errors from |
| // |ValidateOptions| are propagated via the return value. This is convenient when serving a |
| // connection with the validated options is unnecessary e.g. when used from a non-Fuchsia |
| // operating system. |
| zx_status_t OpenValidating(VnodeConnectionOptions options, fbl::RefPtr<Vnode>* out_redirect) |
| __TA_EXCLUDES(mutex_); |
| |
| // METHODS FOR OPENED NODES |
| // |
| // The following operations will not be invoked unless the Vnode has been "Open()"-ed |
| // successfully. |
| // |
| // For files opened with O_PATH (as a file descriptor only) the base classes' implementation of |
| // some of these functions may be invoked anyway. |
| |
| #ifdef __Fuchsia__ |
| // Serves a custom FIDL protocol over the specified |channel|, when the node protocol is |
| // |VnodeProtocol::kConnector|. |
| // |
| // The default implementation returns |ZX_ERR_NOT_SUPPORTED|. |
| // Subclasses may override this behavior to serve custom protocols over the channel. |
| virtual zx_status_t ConnectService(zx::channel channel); |
| |
| // Dispatches incoming FIDL messages which aren't recognized by |Connection::HandleMessage|. |
| // |
| // The default implementation just closes the connection through |txn|. |
| // |
| // This implementation may be overridden to support additional non-fuchsia.io FIDL protocols. |
| virtual void HandleFsSpecificMessage(fidl::IncomingMessage& msg, fidl::Transaction* txn); |
| |
| // Extract handle, type, and extra info from a vnode. |
| // |
| // The |protocol| argument specifies which protocol the connection is negotiated to speak. For |
| // vnodes which only support a single protocol, the method may safely ignore this argument. |
| // Callers should make sure to supply one of the supported protocols, or call |GetNodeInfo| if the |
| // vnode is know to support a single protocol. |
| // |
| // The |rights| argument contain the access rights requested by the client, and should determine |
| // corresponding access rights on the returned handles if applicable. |
| // |
| // The returned variant in |info| should correspond to the |protocol|. |
| virtual zx_status_t GetNodeInfoForProtocol(VnodeProtocol protocol, Rights rights, |
| VnodeRepresentation* info) = 0; |
| |
| // Extract handle, type, and extra info from a vnode. This version differs from |
| // |GetNodeInfoForProtocol| that it is a convenience wrapper for vnodes which only support a |
| // single protocol. If the vnode supports multiple protocols, clients should always call |
| // |GetNodeInfoForProtocol| and specify a protocol. |
| // |
| // The |rights| argument contain the access rights requested by the client, and should determine |
| // corresponding access rights on the returned handles if applicable. |
| // |
| // The returned variant in |info| should correspond to the |protocol|. |
| zx_status_t GetNodeInfo(Rights rights, VnodeRepresentation* info); |
| |
| virtual zx_status_t WatchDir(Vfs* vfs, uint32_t mask, uint32_t options, zx::channel watcher); |
| |
| // Create a |zx::stream| for reading and writing this vnode. |
| // |
| // If this function returns |ZX_OK|, then all |Read|, |Write|, and |Append| operations will be |
| // directed to the stream returned via |out_stream| rather than to the |Read|, |Write|, and |
| // |Append| methods on the vnode. The |zx::stream| might be transported to a remote process to |
| // improve performance. |
| // |
| // If the client modifies the underlying data for this node via the returned |zx::stream|, the |
| // node will be notified via |DidModifyStream|. |
| // |
| // Implementations should pass the given |stream_options| as the options to |zx::stream::create|. |
| // These options ensure that the created |zx::stream| object has the appropriate rights for the |
| // given connection. |
| // |
| // If the vnode does not support reading and writing using a |zx::stream|, return |
| // ZX_ERR_NOT_SUPPORTED, which will cause |Read|, |Write|, and |Append| operations to be called as |
| // methods on the vnode. Other errors are considered fatal and will terminate the connection. |
| virtual zx_status_t CreateStream(uint32_t stream_options, zx::stream* out_stream); |
| #endif |
| |
| // Closes the vnode. Will be called once for each successful Open(). |
| // |
| // Vnode implementations should override CloseNode() which this function calls after some |
| // bookkeeping. |
| zx_status_t Close() __TA_EXCLUDES(mutex_); |
| |
| // Read data from the vnode at offset. |
| // |
| // If successful, returns the number of bytes read in |out_actual|. This must be less than or |
| // equal to |len|. |
| // |
| // See |CreateStream| for a mechanism to offload |Read| to a |zx::stream| object. |
| virtual zx_status_t Read(void* data, size_t len, size_t off, size_t* out_actual); |
| |
| // Write |len| bytes of |data| to the file, starting at |offset|. |
| // |
| // If successful, returns the number of bytes written in |out_actual|. This must be less than or |
| // equal to |len|. |
| // |
| // See |CreateStream| for a mechanism to offload |Write| to a |zx::stream| object. |
| virtual zx_status_t Write(const void* data, size_t len, size_t offset, size_t* out_actual); |
| |
| // Write |len| bytes of |data| to the end of the file. |
| // |
| // If successful, returns the number of bytes written in |out_actual|, and |
| // returns the new end of file offset in |out_end|. |
| // |
| // See |CreateStream| for a mechanism to offload |Append| to a |zx::stream| object. |
| virtual zx_status_t Append(const void* data, size_t len, size_t* out_end, size_t* out_actual); |
| |
| // The data for this node was modified via the |zx::stream| returned by |CreateStream|. |
| // |
| // When a client writes to the |zx::stream| returned by |CreateStream|, there is currently no |
| // mechanism for the node to observe this modification and update its internal state (e.g., the |
| // modification time of the file represented by this node). This method provides that notification |
| // for the time being. In the future, we might switch to using a usermode pager to provide that |
| // notification. |
| virtual void DidModifyStream(); |
| |
| // Change the size of the vnode. |
| virtual zx_status_t Truncate(size_t len); |
| |
| #ifdef __Fuchsia__ |
| // Acquire a vmo from a vnode. |
| // |
| // At the moment, mmap can only map files from read-only filesystems, since (without paging) there |
| // is no mechanism to update either |
| // 1) The file by writing to the mapping, or |
| // 2) The mapping by writing to the underlying file. |
| virtual zx_status_t GetVmo(int flags, zx::vmo* out_vmo, size_t* out_size); |
| #endif // __Fuchsia__ |
| |
| // Syncs the vnode with its underlying storage. |
| // |
| // Returns the result status through a closure. The closure may be executed on a different thread |
| // than called the Sync() function, or reentrantly from the same thread. |
| using SyncCallback = fit::callback<void(zx_status_t status)>; |
| virtual void Sync(SyncCallback closure); |
| |
| // Read directory entries of vn, error if not a directory. FS-specific Cookie must be a buffer of |
| // VdirCookie size or smaller. Cookie must be zero'd before first call and will be used by the |
| // readdir implementation to maintain state across calls. To "rewind" and start from the |
| // beginning, cookie may be zero'd. |
| virtual zx_status_t Readdir(VdirCookie* cookie, void* dirents, size_t len, size_t* out_actual); |
| |
| // METHODS FOR OPENED OR UNOPENED NODES |
| // |
| // The following operations may be invoked on a Vnode, even if it has not been "Open()"-ed. |
| |
| // Attempt to find child of vn, child returned on success. Name is len bytes long, and does not |
| // include a null terminator. |
| virtual zx_status_t Lookup(std::string_view name, fbl::RefPtr<Vnode>* out); |
| |
| // Read attributes of the vnode. |
| virtual zx_status_t GetAttributes(fs::VnodeAttributes* a); |
| |
| // Set attributes of the vnode. |
| virtual zx_status_t SetAttributes(VnodeAttributesUpdate a); |
| |
| // Create a new node under vn. The vfs layer assumes that upon success, the |out| vnode has been |
| // already opened i.e. |Open()| is not called again on the created vnode. Name is len bytes long, |
| // and does not include a null terminator. Mode specifies the type of entity to create. |
| virtual zx_status_t Create(std::string_view name, uint32_t mode, fbl::RefPtr<Vnode>* out); |
| |
| // Removes name from directory vn |
| virtual zx_status_t Unlink(std::string_view name, bool must_be_dir); |
| |
| // Renames the path at oldname in olddir to the path at newname in newdir. Called on the "olddir" |
| // vnode. |
| // |
| // Unlinks any prior newname if it already exists. |
| virtual zx_status_t Rename(fbl::RefPtr<Vnode> newdir, std::string_view oldname, |
| std::string_view newname, bool src_must_be_dir, bool dst_must_be_dir); |
| |
| // Creates a hard link to the 'target' vnode with a provided name in vndir |
| virtual zx_status_t Link(std::string_view name, fbl::RefPtr<Vnode> target); |
| |
| // Invoked by the VFS layer whenever files are added or removed. |
| virtual void Notify(std::string_view name, unsigned event); |
| |
| // Called when the Vfs associated with this node is shutting down. The associated VFS will still |
| // be valid at the time of the call. |
| // |
| // Derived classes can implement this to do cleanup that requires the Vfs. Because Vnodes are |
| // reference-counted, they can outlive their associated Vfs. |
| // |
| // The default implementation will clear the vfs_ back-pointer, it should always be called by |
| // overridden implementations. |
| virtual void WillDestroyVfs(); |
| |
| #ifdef __Fuchsia__ |
| // Return information about the underlying filesystem, if desired. |
| virtual zx_status_t QueryFilesystem(fuchsia_io::wire::FilesystemInfo* out); |
| |
| // Returns the name of the device backing the filesystem, if one exists. |
| virtual zx_status_t GetDevicePath(size_t buffer_len, char* out_name, size_t* out_len); |
| |
| // Attaches a handle to the vnode, if possible. Otherwise, returns an error. |
| virtual zx_status_t AttachRemote(MountChannel h); |
| |
| // The following methods are required to mount sub-filesystems. The logic (and storage) necessary |
| // to implement these functions exists within the "RemoteContainer" class, which may be composed |
| // inside Vnodes that wish to act as mount points. |
| |
| // The vnode is acting as a mount point for a remote filesystem or device. |
| virtual bool IsRemote() const; |
| virtual fidl::ClientEnd<fuchsia_io::Directory> DetachRemote(); |
| virtual fidl::UnownedClientEnd<fuchsia_io::Directory> GetRemote() const; |
| virtual void SetRemote(fidl::ClientEnd<fuchsia_io::Directory> remote); |
| #endif // __Fuchsia__ |
| |
| // Invoked by internal Connections to account transactions |
| void RegisterInflightTransaction() __TA_EXCLUDES(mutex_); |
| void UnregisterInflightTransaction() __TA_EXCLUDES(mutex_); |
| |
| // Number of FIDL messages issued on this vnode that have been dispatched, but for which a reply |
| // has not been made. |
| size_t GetInflightTransactions() const __TA_EXCLUDES(mutex_); |
| |
| protected: |
| DISALLOW_COPY_ASSIGN_AND_MOVE(Vnode); |
| |
| // Opens/Closes the vnode. These are the callbacks that the Vnode implementation overrides to do |
| // the open and close work. They are called by the public Open() and Close() functions which |
| // handles bookeeping for the base class. |
| // |
| // The open_count() will be updated BEFORE each call. If OpenNode fails, the open count will be |
| // rolled back. |
| // |
| // See Open() above for documentation. |
| virtual zx_status_t OpenNode(ValidatedOptions options, fbl::RefPtr<Vnode>* out_redirect) |
| __TA_EXCLUDES(mutex_) { |
| return ZX_OK; |
| } |
| virtual zx_status_t CloseNode() __TA_EXCLUDES(mutex_) { return ZX_OK; } |
| |
| // The associated Vfs pointer is optional. Subclasses should require this if they need to access |
| // the Vfs, but can leave null if not. See vfs() getter for more. |
| explicit Vnode(Vfs* vfs = nullptr); |
| |
| // Mutex for the data of this vnode. This is a shared mutex to support derived classes |
| // implementing multiple simultaneous readers if desired. |
| mutable SharedMutex mutex_; |
| |
| // The Vfs associated with this node, if any. |
| // |
| // The Vfs doesn't need to be set. It is tracked on the Vnode because some subclasses need it, |
| // but it is not directly used by the Vnode. Therefore, subclasses should enforce through their |
| // own constructors whether the vfs_ is set or not during construction. |
| // |
| // Additionally, this will be null when the Vfs is destroyed (since Vnodes are reference-counted |
| // they can outlive the Vfs). Uses should always be inside the mutex_. |
| Vfs* vfs() __TA_REQUIRES_SHARED(mutex_) { return vfs_; } |
| |
| // Returns the number of open connections, not counting node_reference connections. See Open(). |
| size_t open_count() const __TA_REQUIRES_SHARED(mutex_) { return open_count_; } |
| |
| private: |
| Vfs* vfs_ __TA_GUARDED(mutex_) = nullptr; // Possibly null, see getter above. |
| size_t inflight_transactions_ __TA_GUARDED(mutex_) = 0; |
| size_t open_count_ __TA_GUARDED(mutex_) = 0; |
| }; |
| |
| // Opens a vnode by reference. |
| // The |vnode| reference is updated in-place if redirection occurs. |
| inline zx_status_t OpenVnode(Vnode::ValidatedOptions options, fbl::RefPtr<Vnode>* vnode) { |
| fbl::RefPtr<Vnode> redirect; |
| zx_status_t status = (*vnode)->Open(options, &redirect); |
| if (status == ZX_OK && redirect != nullptr) { |
| ZX_DEBUG_ASSERT((*vnode)->GetProtocols() == redirect->GetProtocols()); |
| *vnode = std::move(redirect); |
| } |
| return status; |
| } |
| |
| // Helper class used to fill direntries during calls to Readdir. |
| class DirentFiller { |
| public: |
| DISALLOW_COPY_ASSIGN_AND_MOVE(DirentFiller); |
| |
| DirentFiller(void* ptr, size_t len); |
| |
| // Attempts to add the name to the end of the dirent buffer |
| // which is returned by readdir. |
| zx_status_t Next(std::string_view name, uint8_t type, uint64_t ino); |
| |
| zx_status_t BytesFilled() const { return static_cast<zx_status_t>(pos_); } |
| |
| private: |
| char* ptr_; |
| size_t pos_; |
| const size_t len_; |
| }; |
| |
| // Helper class to track outstanding operations associated to a |
| // particular Vnode. |
| class VnodeToken : public fbl::SinglyLinkedListable<std::unique_ptr<VnodeToken>> { |
| public: |
| VnodeToken(zx_koid_t koid, fbl::RefPtr<Vnode> vnode) : koid_(koid), vnode_(std::move(vnode)) {} |
| |
| zx_koid_t get_koid() const { return koid_; } |
| fbl::RefPtr<Vnode> get_vnode() const { return vnode_; } |
| |
| // Trait implementation for fbl::HashTable |
| zx_koid_t GetKey() const { return koid_; } |
| static size_t GetHash(zx_koid_t koid) { return koid; } |
| |
| private: |
| zx_koid_t koid_; |
| fbl::RefPtr<Vnode> vnode_; |
| }; |
| |
| } // namespace fs |
| |
| #endif // SRC_LIB_STORAGE_VFS_CPP_VNODE_H_ |