blob: cad44c3b0baaf9e4e3f14d92bc16f69dcd270187 [file] [log] [blame]
// Copyright 2016 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_STORAGE_LIB_VFS_CPP_VFS_H_
#define SRC_STORAGE_LIB_VFS_CPP_VFS_H_
#include <lib/fdio/vfs.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 <memory>
#include <mutex>
#include <set>
#include <string_view>
#include <utility>
#include <variant>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include "src/storage/lib/vfs/cpp/vfs_types.h"
#include "src/storage/lib/vfs/cpp/vnode.h"
namespace fs {
class Vnode;
// A storage class for a vdircookie which is passed to Readdir. Common vnode implementations may use
// this struct as scratch space, or cast it to an alternative structure of the same size (or
// smaller).
struct VdirCookie {
uint64_t n = 0;
void* p = nullptr;
};
// The Vfs object contains global per-filesystem state, which may be valid across a collection of
// Vnodes. It dispatches requests to per-file/directory Vnode objects.
//
// This class can be used on a Fuchsia system or on the host computer where the compilation is done
// (the host builds of the filesystems are how system images are created). Normally Fuchsia builds
// will use the ManagedVfs subclass which handles the FIDL-to-vnode connections.
//
// The Vfs object must outlive the Vnodes which it serves. This class is thread-safe.
class Vfs {
public:
class OpenResult;
class Open2Result;
Vfs();
virtual ~Vfs() = default;
// Traverse the path to the target vnode, and create / open it using the underlying filesystem
// functions (lookup, create, open).
//
// The return value will suggest the next action to take. Refer to the variants in |OpenResult|
// for more information.
OpenResult Open(fbl::RefPtr<Vnode> vn, std::string_view path, VnodeConnectionOptions options,
fuchsia_io::Rights connection_rights) __TA_EXCLUDES(vfs_lock_);
// Traverse the path to the target node, and create or open it.
zx::result<Open2Result> Open2(fbl::RefPtr<Vnode> vndir, std::string_view path,
const fuchsia_io::wire::ConnectionProtocols& protocols,
fuchsia_io::Rights connection_rights) __TA_EXCLUDES(vfs_lock_);
// Implements Unlink for a pre-validated and trimmed name.
virtual zx_status_t Unlink(fbl::RefPtr<Vnode> vn, std::string_view name, bool must_be_dir)
__TA_EXCLUDES(vfs_lock_);
// Calls readdir on the Vnode while holding the vfs_lock, preventing path modification operations
// for the duration of the operation.
zx_status_t Readdir(Vnode* vn, VdirCookie* cookie, void* dirents, size_t len, size_t* out_actual)
__TA_EXCLUDES(vfs_lock_);
// Sets whether this file system is read-only.
void SetReadonly(bool value) __TA_EXCLUDES(vfs_lock_);
// Query if this file system is read-only.
bool IsReadonly() const __TA_EXCLUDES(vfs_lock_) {
std::lock_guard lock(vfs_lock_);
return readonly_;
}
protected:
// Whether this file system is read-only.
bool ReadonlyLocked() const __TA_REQUIRES(vfs_lock_) { return readonly_; }
// Trim trailing slashes from name before sending it to internal filesystem functions. This also
// validates whether the name has internal slashes and rejects them. Returns failure if the
// resulting name is too long, empty, or contains slashes after trimming.
//
// Returns true iff name is suffixed with a trailing slash indicating an explicit reference to a
// directory.
static zx::result<bool> TrimName(std::string_view& name);
// Create or lookup an entry with |name| inside of |vndir|. Returns a tuple of the resulting node,
// and a boolean indicating if the returned vnode is open or not.
zx::result<std::tuple<fbl::RefPtr<Vnode>, /*vnode_is_open*/ bool>> CreateOrLookup(
fbl::RefPtr<fs::Vnode> vndir, std::string_view name, CreationMode mode,
std::optional<CreationType> type, fuchsia_io::Rights connection_rights)
__TA_REQUIRES(vfs_lock_);
// A lock which should be used to protect lookup and walk operations
mutable std::mutex vfs_lock_;
// A separate lock to protected vnode registration. The vnodes will call into this class according
// to their lifetimes, and many of these lifetimes are managed from within the VFS lock which can
// result in reentrant locking. This lock should only be held for very short times when mutating
// the registered node tracking information.
mutable std::mutex live_nodes_lock_;
private:
bool readonly_ = false;
};
class Vfs::OpenResult {
public:
// When this variant is active, the indicated error occurred.
using Error = zx_status_t;
// When this variant is active, the path being opened contains a remote node. |path| is the
// remaining portion of the path yet to be traversed. The caller should forward the remainder of
// this open request to that vnode.
//
// Used only on Fuchsia.
struct Remote {
fbl::RefPtr<Vnode> vnode;
std::string_view path;
};
// When this variant is active, |Open| has successfully reached a vnode under this filesystem.
// |options| contains options to be used on the new connection, potentially adjusted for
// posix-flag rights expansion.
struct Ok {
fbl::RefPtr<Vnode> vnode;
VnodeConnectionOptions options;
};
// Forwards the constructor arguments into the underlying |std::variant|. This allows |OpenResult|
// to be constructed directly from one of the variants, e.g.
//
// OpenResult r = OpenResult::Error{ZX_ERR_ACCESS_DENIED};
//
template <typename T>
OpenResult(T&& v) : variants_(std::forward<T>(v)) {}
// Applies the |visitor| function to the variant payload. It simply forwards the visitor into the
// underlying |std::variant|. Returns the return value of |visitor|. Refer to C++ documentation
// for |std::visit|.
template <class Visitor>
constexpr auto visit(Visitor&& visitor) -> decltype(visitor(std::declval<zx_status_t>())) {
return std::visit(std::forward<Visitor>(visitor), variants_);
}
Ok& ok() { return std::get<Ok>(variants_); }
bool is_ok() const { return std::holds_alternative<Ok>(variants_); }
Error& error() { return std::get<Error>(variants_); }
bool is_error() const { return std::holds_alternative<Error>(variants_); }
Remote& remote() { return std::get<Remote>(variants_); }
bool is_remote() const { return std::holds_alternative<Remote>(variants_); }
private:
using Variants = std::variant<Error, Remote, Ok>;
Variants variants_;
};
// Holds a (possibly opened) Vnode, ensuring that the open count is managed correctly.
class Vfs::Open2Result {
public:
// Cannot allow copy as this will result in the open count being incorrect.
Open2Result(const Open2Result&) = delete;
Open2Result& operator=(const Open2Result&) = delete;
Open2Result(Open2Result&&) = default;
Open2Result& operator=(Open2Result&&) = default;
// Handles opening |vnode| if required based on the specified |protocol|. The |vnode| will be
// closed when this object is destroyed, if required, unless |TakeVnode()| is called.
static zx::result<Open2Result> OpenVnode(fbl::RefPtr<fs::Vnode> vnode, VnodeProtocol protocol) {
// We don't open the node for node reference connections.
if (protocol == VnodeProtocol::kNode) {
return Open2Result::Local(std::move(vnode), protocol);
}
if (zx_status_t status = fs::OpenVnode(&vnode); status != ZX_OK) {
return zx::error(status);
}
if (vnode->IsRemote()) {
// Opening the node redirected us to a remote, forward the request to it.
return Open2Result::Remote(std::move(vnode), ".");
}
return Open2Result::Local(std::move(vnode), protocol);
}
// Creates a new |Open2Result| from a remote |vnode|. This object keeps an unowned copy of |path|,
// so the underlying string must outlive this object.
static zx::result<Open2Result> Remote(fbl::RefPtr<fs::Vnode> vnode, std::string_view path) {
ZX_DEBUG_ASSERT(vnode->IsRemote());
return zx::ok(Open2Result(std::move(vnode), path));
}
// Creates a new |Open2Result| from a local node. |vnode| is assumed to already have been opened,
// and unless |TakeVnode()| is called, will be closed when this object is destroyed.
static zx::result<Open2Result> Local(fbl::RefPtr<fs::Vnode> vnode, VnodeProtocol protocol) {
ZX_DEBUG_ASSERT(!vnode->IsRemote());
return zx::ok(Open2Result(std::move(vnode), protocol));
}
// Ensure we roll back the vnode open count when required. There are only two cases where we do
// not open a vnode: 1) remote nodes, and 2) node-reference connections
~Open2Result() {
if (vnode_ && !vnode_->IsRemote() && protocol_ && *protocol_ != VnodeProtocol::kNode) {
vnode_->Close();
}
}
// Take the vnode out of this object. The caller is responsible for closing the node if required.
fbl::RefPtr<fs::Vnode> TakeVnode() { return std::exchange(vnode_, nullptr); }
const fbl::RefPtr<fs::Vnode>& vnode() const { return vnode_; }
// The protocol which was negotiated for this node if we resolved it locally. The vnode must not
// be a remote, as the remote server is responsible for performing protocol negotiation.
fs::VnodeProtocol protocol() const {
ZX_DEBUG_ASSERT(!vnode_->IsRemote());
return *protocol_;
}
// Remainder of the path to forward if this is a remote node.
std::string_view path() const { return path_; }
private:
Open2Result() = delete;
Open2Result(fbl::RefPtr<fs::Vnode> vnode, fs::VnodeProtocol protocol)
: vnode_(std::move(vnode)), protocol_(protocol) {}
Open2Result(fbl::RefPtr<fs::Vnode> vnode, std::string_view path)
: vnode_(std::move(vnode)), path_(path) {}
fbl::RefPtr<fs::Vnode> vnode_;
std::optional<fs::VnodeProtocol> protocol_;
std::string_view path_;
};
} // namespace fs
#endif // SRC_STORAGE_LIB_VFS_CPP_VFS_H_