blob: 1c78a794ce4115ab639127b993b20fff33583b5f [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.
#pragma once
#include "trace.h"
#include <fdio/remoteio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <zircon/device/vfs.h>
#include <zircon/types.h>
#include <fdio/vfs.h>
#ifdef __Fuchsia__
#include <threads.h>
#include <fdio/io.h>
#endif
// VFS Helpers (vfs.c)
// clang-format off
#define VFS_FLAG_DEVICE 0x00000001
#define VFS_FLAG_MOUNT_READY 0x00000002
#define VFS_FLAG_DEVICE_DETACHED 0x00000004
#define VFS_FLAG_RESERVED_MASK 0x0000FFFF
// clang-format on
__BEGIN_CDECLS
typedef struct vfs_iostate vfs_iostate_t;
// Send an unmount signal on a handle to a filesystem and await a response.
zx_status_t vfs_unmount_handle(zx_handle_t h, zx_time_t deadline);
__END_CDECLS
#ifdef __cplusplus
#ifdef __Fuchsia__
#include <fs/dispatcher.h>
#include <zx/channel.h>
#include <zx/event.h>
#include <zx/vmo.h>
#include <fbl/mutex.h>
#endif // __Fuchsia__
#include <fbl/intrusive_double_list.h>
#include <fbl/macros.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
namespace fs {
class Vnode;
class Vfs;
#ifdef __Fuchsia__
// MountChannel functions exactly the same as a channel, except that it
// intentionally destructs by sending a clean "shutdown" signal to the
// underlying filesystem. Up until the point that a remote handle is
// attached to a vnode, this wrapper guarantees not only that the
// underlying handle gets closed on error, but also that the sub-filesystem
// is released (which cleans up the underlying connection to the block
// device).
class MountChannel {
public:
constexpr MountChannel() = default;
explicit MountChannel(zx_handle_t handle) : channel_(handle) {}
explicit MountChannel(zx::channel channel) : channel_(fbl::move(channel)) {}
MountChannel(MountChannel&& other) : channel_(fbl::move(other.channel_)) {}
zx::channel TakeChannel() { return fbl::move(channel_); }
~MountChannel() {
if (channel_.is_valid()) {
vfs_unmount_handle(channel_.release(), 0);
}
}
private:
zx::channel channel_;
};
#endif // __Fuchsia__
// 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(const char* name, size_t len, uint32_t type);
zx_status_t BytesFilled() const {
return static_cast<zx_status_t>(pos_);
}
private:
char* ptr_;
size_t pos_;
const size_t len_;
};
inline bool vfs_valid_name(const char* name, size_t len) {
return (len <= NAME_MAX &&
memchr(name, '/', len) == nullptr &&
(len != 1 || strncmp(name, ".", 1)) &&
(len != 2 || strncmp(name, "..", 2)));
}
// The VFS interface declares a default abtract Vnode class with
// common operations that may be overwritten.
//
// 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".
//
// The lower half of flags (VFS_FLAG_RESERVED_MASK) is reserved
// for usage by fs::Vnode, but the upper half of flags may
// be used by subclasses of Vnode.
class Vnode : public fbl::RefCounted<Vnode> {
public:
#ifdef __Fuchsia__
// Allocate iostate and register the transferred handle with a dispatcher.
// Allows Vnode to act as server.
virtual zx_status_t Serve(fs::Vfs* vfs, zx::channel channel, uint32_t flags);
// Extract handle(s), type, and extra info from a vnode.
// Returns the number of handles which should be returned on the requesting handle.
virtual zx_status_t GetHandles(uint32_t flags, zx_handle_t* hnds,
uint32_t* type, void* extra, uint32_t* esize) {
*type = FDIO_PROTOCOL_REMOTE;
return 0;
}
virtual zx_status_t WatchDir(zx::channel* out) { return ZX_ERR_NOT_SUPPORTED; }
virtual zx_status_t WatchDirV2(Vfs* vfs, const vfs_watch_dir_t* cmd) {
return ZX_ERR_NOT_SUPPORTED;
}
#endif
virtual void Notify(const char* name, size_t len, unsigned event) {}
// Ensure that it is valid to open vn.
virtual zx_status_t Open(uint32_t flags) = 0;
// Closes vn. Typically, most Vnodes simply return "ZX_OK".
virtual zx_status_t Close();
// Read data from vn at offset.
virtual ssize_t Read(void* data, size_t len, size_t off) {
return ZX_ERR_NOT_SUPPORTED;
}
// Write data to vn at offset.
virtual ssize_t Write(const void* data, size_t len, size_t off) {
return ZX_ERR_NOT_SUPPORTED;
}
// 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(fbl::RefPtr<Vnode>* out, const char* name, size_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
// Read attributes of vn.
virtual zx_status_t Getattr(vnattr_t* a) {
return ZX_ERR_NOT_SUPPORTED;
}
// Set attributes of vn.
virtual zx_status_t Setattr(vnattr_t* a) {
return ZX_ERR_NOT_SUPPORTED;
}
// Read directory entries of vn, error if not a directory.
// FS-specific Cookie must be a buffer of vdircookie_t 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(void* cookie, void* dirents, size_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
// Create a new node under vn.
// 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(fbl::RefPtr<Vnode>* out, const char* name, size_t len, uint32_t mode) {
return ZX_ERR_NOT_SUPPORTED;
}
// Performs the given ioctl op on vn.
// On success, returns the number of bytes received.
virtual ssize_t Ioctl(uint32_t op, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len) {
return ZX_ERR_NOT_SUPPORTED;
}
// Removes name from directory vn
virtual zx_status_t Unlink(const char* name, size_t len, bool must_be_dir) {
return ZX_ERR_NOT_SUPPORTED;
}
// Change the size of vn
virtual zx_status_t Truncate(size_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
// 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,
const char* oldname, size_t oldlen,
const char* newname, size_t newlen,
bool src_must_be_dir, bool dst_must_be_dir) {
return ZX_ERR_NOT_SUPPORTED;
}
// Creates a hard link to the 'target' vnode with a provided name in vndir
virtual zx_status_t Link(const char* name, size_t len, fbl::RefPtr<Vnode> target) {
return ZX_ERR_NOT_SUPPORTED;
}
// 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 Mmap(int flags, size_t len, size_t* off, zx_handle_t* out) {
return ZX_ERR_NOT_SUPPORTED;
}
// Syncs the vnode with its underlying storage
virtual zx_status_t Sync() {
return ZX_ERR_NOT_SUPPORTED;
}
virtual ~Vnode() {};
#ifdef __Fuchsia__
// Attaches a handle to the vnode, if possible. Otherwise, returns an error.
virtual zx_status_t AttachRemote(MountChannel h) { return ZX_ERR_NOT_SUPPORTED; }
// 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 { return false; }
virtual zx::channel DetachRemote() { return zx::channel(); }
virtual zx_handle_t WaitForRemote() { return ZX_HANDLE_INVALID; }
virtual zx_handle_t GetRemote() const { return ZX_HANDLE_INVALID; }
virtual void SetRemote(zx::channel remote) { ZX_DEBUG_ASSERT(false); }
// The vnode is a device. Devices may opt to reveal themselves as directories
// or endpoints, depending on context. For the purposes of our VFS layer,
// during path traversal, devices are NOT treated as mount points, even though
// they contain remote handles.
bool IsDevice() const { return (flags_ & VFS_FLAG_DEVICE) && IsRemote(); }
void DetachDevice() {
ZX_DEBUG_ASSERT(flags_ & VFS_FLAG_DEVICE);
flags_ |= VFS_FLAG_DEVICE_DETACHED;
}
bool IsDetachedDevice() const { return (flags_ & VFS_FLAG_DEVICE_DETACHED); }
#endif
protected:
DISALLOW_COPY_ASSIGN_AND_MOVE(Vnode);
Vnode() : flags_(0) {};
uint32_t flags_;
};
#ifdef __Fuchsia__
// Non-intrusive node in linked list of vnodes acting as mount points
class MountNode final : public fbl::DoublyLinkedListable<fbl::unique_ptr<MountNode>> {
public:
using ListType = fbl::DoublyLinkedList<fbl::unique_ptr<MountNode>>;
constexpr MountNode() : vn_(nullptr) {}
~MountNode() { ZX_DEBUG_ASSERT(vn_ == nullptr); }
void SetNode(fbl::RefPtr<Vnode> vn) {
ZX_DEBUG_ASSERT(vn_ == nullptr);
vn_ = vn;
}
zx::channel ReleaseRemote() {
ZX_DEBUG_ASSERT(vn_ != nullptr);
zx::channel h = vn_->DetachRemote();
vn_ = nullptr;
return h;
}
bool VnodeMatch(fbl::RefPtr<Vnode> vn) const {
ZX_DEBUG_ASSERT(vn_ != nullptr);
return vn == vn_;
}
private:
fbl::RefPtr<Vnode> vn_;
};
#endif
// The Vfs object contains global per-filesystem state, which
// may be valid across a collection of Vnodes.
//
// The Vfs object must outlive the Vnodes which it serves.
class Vfs {
public:
Vfs();
// Walk from vn --> out until either only one path segment remains or we
// encounter a remote filesystem.
zx_status_t Walk(fbl::RefPtr<Vnode> vn, fbl::RefPtr<Vnode>* out,
const char* path, const char** pathout) __TA_REQUIRES(vfs_lock_);
// Traverse the path to the target vnode, and create / open it using
// the underlying filesystem functions (lookup, create, open).
zx_status_t Open(fbl::RefPtr<Vnode> vn, fbl::RefPtr<Vnode>* out,
const char* path, const char** pathout,
uint32_t flags, uint32_t mode) __TA_EXCLUDES(vfs_lock_);
zx_status_t Unlink(fbl::RefPtr<Vnode> vn, const char* path, size_t len) __TA_EXCLUDES(vfs_lock_);
ssize_t Ioctl(fbl::RefPtr<Vnode> vn, uint32_t op, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len) __TA_EXCLUDES(vfs_lock_);
#ifdef __Fuchsia__
void TokenDiscard(zx::event* ios_token) __TA_EXCLUDES(vfs_lock_);
zx_status_t VnodeToToken(fbl::RefPtr<Vnode> vn, zx::event* ios_token,
zx::event* out) __TA_EXCLUDES(vfs_lock_);
zx_status_t Link(zx::event token, fbl::RefPtr<Vnode> oldparent,
const char* oldname, const char* newname) __TA_EXCLUDES(vfs_lock_);
zx_status_t Rename(zx::event token, fbl::RefPtr<Vnode> oldparent,
const char* oldname, const char* newname) __TA_EXCLUDES(vfs_lock_);
Vfs(Dispatcher* dispatcher);
void SetDispatcher(Dispatcher* dispatcher) { dispatcher_ = dispatcher; }
// Dispatches to a Vnode over the specified handle (normal case)
zx_status_t Serve(zx::channel channel, void* ios);
// Serves a Vnode over the specified handle (used for creating new filesystems)
zx_status_t ServeDirectory(fbl::RefPtr<fs::Vnode> vn, zx::channel channel);
// Pins a handle to a remote filesystem onto a vnode, if possible.
zx_status_t InstallRemote(fbl::RefPtr<Vnode> vn, MountChannel h) __TA_EXCLUDES(vfs_lock_);
// Create and mount a directory with a provided name
zx_status_t MountMkdir(fbl::RefPtr<Vnode> vn,
const mount_mkdir_config_t* config) __TA_EXCLUDES(vfs_lock_);
// Unpin a handle to a remote filesystem from a vnode, if one exists.
zx_status_t UninstallRemote(fbl::RefPtr<Vnode> vn, zx::channel* h) __TA_EXCLUDES(vfs_lock_);
// Unpins all remote filesystems in the current filesystem, and waits for the
// response of each one with the provided deadline.
zx_status_t UninstallAll(zx_time_t deadline) __TA_EXCLUDES(vfs_lock_);
// A lock which should be used to protect lookup and walk operations
// TODO(smklein): Encapsulate the lock; make it private.
mtx_t vfs_lock_{};
#endif
private:
zx_status_t OpenLocked(fbl::RefPtr<Vnode> vn, fbl::RefPtr<Vnode>* out,
const char* path, const char** pathout,
uint32_t flags, uint32_t mode) __TA_REQUIRES(vfs_lock_);
#ifdef __Fuchsia__
zx_status_t TokenToVnode(zx::event token, fbl::RefPtr<Vnode>* out) __TA_REQUIRES(vfs_lock_);
zx_status_t InstallRemoteLocked(fbl::RefPtr<Vnode> vn, MountChannel h) __TA_REQUIRES(vfs_lock_);
zx_status_t UninstallRemoteLocked(fbl::RefPtr<Vnode> vn,
zx::channel* h) __TA_REQUIRES(vfs_lock_);
// Waits for a remote handle on a Vnode to become ready to receive requests.
// Returns |ZX_ERR_PEER_CLOSED| if the remote will never become available, since it is closed.
// Returns |ZX_ERR_UNAVAILABLE| if there is no remote handle, or if the remote handle is not yet ready.
// On success, returns the remote handle.
zx_handle_t WaitForRemoteLocked(fbl::RefPtr<Vnode> vn) __TA_REQUIRES(vfs_lock_);
// The mount list is a global static variable, but it only uses
// constexpr constructors during initialization. As a consequence,
// the .init_array section of the compiled vfs-mount object file is
// empty; "remote_list" is a member of the bss section.
MountNode::ListType remote_list_ __TA_GUARDED(vfs_lock_){};
Dispatcher* dispatcher_{};
#endif // ifdef __Fuchsia__
};
} // namespace fs
#endif // ifdef __cplusplus
__BEGIN_CDECLS
typedef struct vnattr vnattr_t;
typedef struct vdirent vdirent_t;
typedef struct vdircookie {
uint64_t n;
void* p;
} vdircookie_t;
// Handle incoming zxrio messages, dispatching them to vnode operations.
zx_status_t vfs_handler(zxrio_msg_t* msg, void* cookie);
__END_CDECLS