blob: bd547949ff23c74878974c74152ba74ba0b0e448 [file] [log] [blame]
// Copyright 2021 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.
#include "src/lib/storage/vfs/cpp/fuchsia_vfs.h"
#include <fidl/fuchsia.io.admin/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <fidl/fuchsia.io2/cpp/wire.h>
#include <lib/fdio/watcher.h>
#include <lib/zx/event.h>
#include <lib/zx/process.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
#include <fbl/auto_lock.h>
#include <fbl/ref_ptr.h>
#include "src/lib/storage/vfs/cpp/connection.h"
#include "src/lib/storage/vfs/cpp/debug.h"
#include "src/lib/storage/vfs/cpp/directory_connection.h"
#include "src/lib/storage/vfs/cpp/mount_channel.h"
#include "src/lib/storage/vfs/cpp/node_connection.h"
#include "src/lib/storage/vfs/cpp/remote_file_connection.h"
#include "src/lib/storage/vfs/cpp/stream_file_connection.h"
#include "src/lib/storage/vfs/cpp/vnode.h"
namespace fio = fuchsia_io;
namespace fs {
namespace {
zx_koid_t GetTokenKoid(const zx::event& token) {
zx_info_handle_basic_t info = {};
token.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return info.koid;
}
uint32_t ToStreamOptions(const VnodeConnectionOptions& options) {
uint32_t stream_options = 0u;
if (options.rights.read) {
stream_options |= ZX_STREAM_MODE_READ;
}
if (options.rights.write) {
stream_options |= ZX_STREAM_MODE_WRITE;
}
return stream_options;
}
} // namespace
constexpr FuchsiaVfs::MountNode::MountNode() = default;
FuchsiaVfs::MountNode::~MountNode() { ZX_DEBUG_ASSERT(vn_ == nullptr); }
void FuchsiaVfs::MountNode::SetNode(fbl::RefPtr<Vnode> vn) {
ZX_DEBUG_ASSERT(vn_ == nullptr);
vn_ = vn;
}
fidl::ClientEnd<fio::Directory> FuchsiaVfs::MountNode::ReleaseRemote() {
ZX_DEBUG_ASSERT(vn_ != nullptr);
fidl::ClientEnd<fio::Directory> h = vn_->DetachRemote();
vn_ = nullptr;
return h;
}
bool FuchsiaVfs::MountNode::VnodeMatch(fbl::RefPtr<Vnode> vn) const {
ZX_DEBUG_ASSERT(vn_ != nullptr);
return vn == vn_;
}
FuchsiaVfs::FuchsiaVfs(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
FuchsiaVfs::~FuchsiaVfs() = default;
void FuchsiaVfs::SetDispatcher(async_dispatcher_t* dispatcher) {
ZX_ASSERT_MSG(!dispatcher_,
"FuchsiaVfs::SetDispatcher maybe only be called when dispatcher_ is not set.");
dispatcher_ = dispatcher;
}
zx_status_t FuchsiaVfs::Unlink(fbl::RefPtr<Vnode> vndir, std::string_view name, bool must_be_dir) {
if (zx_status_t s = Vfs::Unlink(vndir, name, must_be_dir); s != ZX_OK)
return s;
vndir->Notify(name, fio::wire::kWatchEventRemoved);
return ZX_OK;
}
void FuchsiaVfs::TokenDiscard(zx::event ios_token) {
std::lock_guard lock(vfs_lock_);
if (ios_token) {
// The token is cleared here to prevent the following race condition:
// 1) Open
// 2) GetToken
// 3) Close + Release Vnode
// 4) Use token handle to access defunct vnode (or a different vnode, if the memory for it is
// reallocated).
//
// By cleared the token cookie, any remaining handles to the event will be ignored by the
// filesystem server.
auto rename_request = vnode_tokens_.erase(GetTokenKoid(ios_token));
}
}
zx_status_t FuchsiaVfs::VnodeToToken(fbl::RefPtr<Vnode> vn, zx::event* ios_token, zx::event* out) {
zx_status_t r;
std::lock_guard lock(vfs_lock_);
if (ios_token->is_valid()) {
// Token has already been set for this iostate
if ((r = ios_token->duplicate(ZX_RIGHTS_BASIC, out) != ZX_OK)) {
return r;
}
return ZX_OK;
}
zx::event new_token;
zx::event new_ios_token;
if ((r = zx::event::create(0, &new_ios_token)) != ZX_OK) {
return r;
} else if ((r = new_ios_token.duplicate(ZX_RIGHTS_BASIC, &new_token) != ZX_OK)) {
return r;
}
auto koid = GetTokenKoid(new_ios_token);
vnode_tokens_.insert(std::make_unique<VnodeToken>(koid, std::move(vn)));
*ios_token = std::move(new_ios_token);
*out = std::move(new_token);
return ZX_OK;
}
bool FuchsiaVfs::IsTokenAssociatedWithVnode(zx::event token) {
std::lock_guard lock(vfs_lock_);
return TokenToVnode(std::move(token), nullptr) == ZX_OK;
}
zx_status_t FuchsiaVfs::EnsureExists(fbl::RefPtr<Vnode> vndir, std::string_view path,
fbl::RefPtr<Vnode>* out_vn, fs::VnodeConnectionOptions options,
uint32_t mode, Rights parent_rights, bool* did_create) {
if (zx_status_t s =
Vfs::EnsureExists(vndir, path, out_vn, options, mode, parent_rights, did_create);
s != ZX_OK)
return s;
vndir->Notify(path, fio::wire::kWatchEventAdded);
return ZX_OK;
}
zx_status_t FuchsiaVfs::TokenToVnode(zx::event token, fbl::RefPtr<Vnode>* out) {
const auto& vnode_token = vnode_tokens_.find(GetTokenKoid(token));
if (vnode_token == vnode_tokens_.end()) {
// TODO(smklein): Return a more specific error code for "token not from this server"
return ZX_ERR_INVALID_ARGS;
}
if (out) {
*out = vnode_token->get_vnode();
}
return ZX_OK;
}
zx_status_t FuchsiaVfs::Rename(zx::event token, fbl::RefPtr<Vnode> oldparent,
std::string_view oldStr, std::string_view newStr) {
// Local filesystem
bool old_must_be_dir;
bool new_must_be_dir;
zx_status_t r;
if ((r = TrimName(oldStr, &oldStr, &old_must_be_dir)) != ZX_OK) {
return r;
} else if (oldStr == ".") {
return ZX_ERR_UNAVAILABLE;
} else if (oldStr == "..") {
return ZX_ERR_INVALID_ARGS;
}
if ((r = TrimName(newStr, &newStr, &new_must_be_dir)) != ZX_OK) {
return r;
} else if (newStr == "." || newStr == "..") {
return ZX_ERR_INVALID_ARGS;
}
fbl::RefPtr<fs::Vnode> newparent;
{
std::lock_guard lock(vfs_lock_);
if (ReadonlyLocked()) {
return ZX_ERR_ACCESS_DENIED;
}
if ((r = TokenToVnode(std::move(token), &newparent)) != ZX_OK) {
return r;
}
r = oldparent->Rename(newparent, oldStr, newStr, old_must_be_dir, new_must_be_dir);
}
if (r != ZX_OK) {
return r;
}
oldparent->Notify(oldStr, fio::wire::kWatchEventRemoved);
newparent->Notify(newStr, fio::wire::kWatchEventAdded);
return ZX_OK;
}
zx_status_t FuchsiaVfs::Link(zx::event token, fbl::RefPtr<Vnode> oldparent, std::string_view oldStr,
std::string_view newStr) {
std::lock_guard lock(vfs_lock_);
fbl::RefPtr<fs::Vnode> newparent;
zx_status_t r;
if ((r = TokenToVnode(std::move(token), &newparent)) != ZX_OK) {
return r;
}
// Local filesystem
bool old_must_be_dir;
bool new_must_be_dir;
if (ReadonlyLocked()) {
return ZX_ERR_ACCESS_DENIED;
} else if ((r = TrimName(oldStr, &oldStr, &old_must_be_dir)) != ZX_OK) {
return r;
} else if (old_must_be_dir) {
return ZX_ERR_NOT_DIR;
} else if (oldStr == ".") {
return ZX_ERR_UNAVAILABLE;
} else if (oldStr == "..") {
return ZX_ERR_INVALID_ARGS;
}
if ((r = TrimName(newStr, &newStr, &new_must_be_dir)) != ZX_OK) {
return r;
} else if (new_must_be_dir) {
return ZX_ERR_NOT_DIR;
} else if (newStr == "." || newStr == "..") {
return ZX_ERR_INVALID_ARGS;
}
// Look up the target vnode
fbl::RefPtr<Vnode> target;
if ((r = oldparent->Lookup(oldStr, &target)) < 0) {
return r;
}
r = newparent->Link(newStr, target);
if (r != ZX_OK) {
return r;
}
newparent->Notify(newStr, fio::wire::kWatchEventAdded);
return ZX_OK;
}
zx_status_t FuchsiaVfs::Serve(fbl::RefPtr<Vnode> vnode, fidl::ServerEnd<fuchsia_io::Node> channel,
VnodeConnectionOptions options) {
auto result = vnode->ValidateOptions(options);
if (result.is_error()) {
return result.error();
}
return Serve(std::move(vnode), std::move(channel), result.value());
}
zx_status_t FuchsiaVfs::AddInotifyFilterToVnode(fbl::RefPtr<Vnode> vnode,
const fbl::RefPtr<Vnode>& parent_vnode,
fuchsia_io2::wire::InotifyWatchMask filter,
uint32_t watch_descriptor, zx::socket socket) {
// TODO we need parent vnode for inotify events when a directory is being watched for events on
// its directory entries.
vnode->InsertInotifyFilter(filter, watch_descriptor, std::move(socket));
return ZX_OK;
}
zx_status_t FuchsiaVfs::Serve(fbl::RefPtr<Vnode> vnode,
fidl::ServerEnd<fuchsia_io::Node> server_end,
Vnode::ValidatedOptions options) {
// |ValidateOptions| was called, hence at least one protocol must be supported.
auto candidate_protocols = options->protocols() & vnode->GetProtocols();
ZX_DEBUG_ASSERT(candidate_protocols.any());
auto maybe_protocol = candidate_protocols.which();
VnodeProtocol protocol;
if (maybe_protocol.has_value()) {
protocol = maybe_protocol.value();
} else {
protocol = vnode->Negotiate(candidate_protocols);
}
// Send an |fuchsia.io/OnOpen| event if requested.
if (options->flags.describe) {
fpromise::result<VnodeRepresentation, zx_status_t> result =
internal::Describe(vnode, protocol, *options);
if (result.is_error()) {
fidl::WireEventSender<fio::Node>(std::move(server_end))
.OnOpen(result.error(), fio::wire::NodeInfo());
return result.error();
}
ConvertToIoV1NodeInfo(result.take_value(), [&](fio::wire::NodeInfo&& info) {
// The channel may switch from |Node| protocol back to a custom protocol, after sending the
// event, in the case of |VnodeProtocol::kConnector|.
fidl::WireEventSender<fio::Node> event_sender{std::move(server_end)};
event_sender.OnOpen(ZX_OK, std::move(info));
server_end = std::move(event_sender.server_end());
});
}
// If |node_reference| is specified, serve |fuchsia.io/Node| even for |VnodeProtocol::kConnector|
// nodes. Otherwise, connect the raw channel to the custom service.
if (!options->flags.node_reference && protocol == VnodeProtocol::kConnector) {
return vnode->ConnectService(server_end.TakeChannel());
}
std::unique_ptr<internal::Connection> connection;
zx_status_t status = ([&] {
switch (protocol) {
case VnodeProtocol::kFile:
case VnodeProtocol::kDevice:
case VnodeProtocol::kTty:
// In memfs and bootfs, memory objects (vmo-files) appear to support |fuchsia.io/File.Read|.
// Therefore choosing a file connection here is the closest approximation.
case VnodeProtocol::kMemory: {
zx::stream stream;
zx_status_t status = vnode->CreateStream(ToStreamOptions(*options), &stream);
if (status == ZX_OK) {
connection = std::make_unique<internal::StreamFileConnection>(
this, std::move(vnode), std::move(stream), protocol, *options);
return ZX_OK;
}
if (status == ZX_ERR_NOT_SUPPORTED) {
connection = std::make_unique<internal::RemoteFileConnection>(this, std::move(vnode),
protocol, *options);
return ZX_OK;
}
return status;
}
case VnodeProtocol::kDirectory:
connection = std::make_unique<internal::DirectoryConnection>(this, std::move(vnode),
protocol, *options);
return ZX_OK;
case VnodeProtocol::kConnector:
case VnodeProtocol::kPipe:
connection =
std::make_unique<internal::NodeConnection>(this, std::move(vnode), protocol, *options);
return ZX_OK;
case VnodeProtocol::kDatagramSocket:
// The posix socket protocol is served by netstack.
ZX_PANIC("fuchsia.posix.socket/DatagramSocket is not implemented");
case VnodeProtocol::kStreamSocket:
// The posix socket protocol is served by netstack.
ZX_PANIC("fuchsia.posix.socket/StreamSocket is not implemented");
}
#ifdef __GNUC__
// GCC does not infer that the above switch statement will always return by handling all defined
// enum members.
__builtin_abort();
#endif
}());
if (status != ZX_OK) {
return status;
}
return RegisterConnection(std::move(connection), server_end.TakeChannel());
}
void FuchsiaVfs::OnConnectionClosedRemotely(internal::Connection* connection) {
ZX_DEBUG_ASSERT(connection);
UnregisterConnection(connection);
}
zx_status_t FuchsiaVfs::ServeDirectory(fbl::RefPtr<fs::Vnode> vn,
fidl::ServerEnd<fuchsia_io::Directory> server_end,
Rights rights) {
VnodeConnectionOptions options;
options.flags.directory = true;
options.rights = rights;
auto validated_options = vn->ValidateOptions(options);
if (validated_options.is_error()) {
return validated_options.error();
} else if (zx_status_t r = OpenVnode(validated_options.value(), &vn); r != ZX_OK) {
return r;
}
return Serve(std::move(vn), server_end.TakeChannel(), validated_options.value());
}
// Installs a remote filesystem on vn and adds it to the remote_list_.
zx_status_t FuchsiaVfs::InstallRemote(fbl::RefPtr<Vnode> vn, MountChannel h) {
if (vn == nullptr) {
return ZX_ERR_ACCESS_DENIED;
}
// Allocate a node to track the remote handle
fbl::AllocChecker ac;
std::unique_ptr<MountNode> mount_point(new (&ac) MountNode());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = vn->AttachRemote(std::move(h));
if (status != ZX_OK) {
return status;
}
// Save this node in the list of mounted vnodes
mount_point->SetNode(std::move(vn));
std::lock_guard lock(vfs_lock_);
remote_list_.push_front(std::move(mount_point));
return ZX_OK;
}
zx_status_t FuchsiaVfs::UninstallRemote(fbl::RefPtr<Vnode> vn, fidl::ClientEnd<fio::Directory>* h) {
std::lock_guard lock(vfs_lock_);
return UninstallRemoteLocked(std::move(vn), h);
}
zx_status_t FuchsiaVfs::MountMkdir(fbl::RefPtr<Vnode> vn, std::string_view name, MountChannel h,
uint32_t flags) {
std::lock_guard lock(vfs_lock_);
return OpenLocked(
vn, name,
fs::VnodeConnectionOptions::ReadOnly().set_create().set_directory().set_no_remote(),
fs::Rights::ReadWrite(), S_IFDIR)
.visit([&](auto&& result) __TA_REQUIRES(vfs_lock_) {
using T = std::decay_t<decltype(result)>;
using OpenResult = fs::FuchsiaVfs::OpenResult;
if constexpr (std::is_same_v<T, OpenResult::Error>) {
return result;
} else {
if (result.vnode->IsRemote()) {
if (flags & fuchsia_io_admin::wire::kMountCreateFlagReplace) {
// There is an old remote handle on this vnode; shut it down and replace it with our
// own.
fidl::ClientEnd<fio::Directory> old_remote;
FuchsiaVfs::UninstallRemoteLocked(vn, &old_remote);
// Passing |zx::time::infinite_past()| results in a fire-and-forget call.
// TODO(fxbug.dev/42264): Add proper tracking of remote filesystem teardown.
// Note: this is best-effort, and would fail if the remote endpoint does not speak the
// |fuchsia.io.admin/DirectoryAdmin| protocol.
fidl::ClientEnd<fuchsia_io_admin::DirectoryAdmin> old_remote_admin(
old_remote.TakeChannel());
FuchsiaVfs::UnmountHandle(std::move(old_remote_admin), zx::time::infinite_past());
} else {
return ZX_ERR_BAD_STATE;
}
}
return FuchsiaVfs::InstallRemoteLocked(result.vnode, std::move(h));
}
});
}
zx_status_t FuchsiaVfs::ForwardOpenRemote(fbl::RefPtr<Vnode> vn, fidl::ServerEnd<fio::Node> channel,
std::string_view path, VnodeConnectionOptions options,
uint32_t mode) {
std::lock_guard lock(vfs_lock_);
auto h = vn->GetRemote();
if (!h.is_valid()) {
return ZX_ERR_NOT_FOUND;
}
auto r = fidl::WireCall(h)
.Open(options.ToIoV1Flags(), mode, fidl::StringView::FromExternal(path),
std::move(channel))
.status();
if (r == ZX_ERR_PEER_CLOSED) {
fidl::ClientEnd<fio::Directory> c;
UninstallRemoteLocked(std::move(vn), &c);
}
return r;
}
// Uninstall all remote filesystems. Acts like 'UninstallRemote' for all known remotes.
zx_status_t FuchsiaVfs::UninstallAll(zx::time deadline) {
std::unique_ptr<MountNode> mount_point;
for (;;) {
{
std::lock_guard lock(vfs_lock_);
mount_point = remote_list_.pop_front();
}
if (mount_point) {
// Note: this is best-effort, and would fail if the remote endpoint does not speak the
// |fuchsia.io/DirectoryAdmin| protocol.
fidl::ClientEnd<fuchsia_io_admin::DirectoryAdmin> mount_admin(
mount_point->ReleaseRemote().TakeChannel());
FuchsiaVfs::UnmountHandle(std::move(mount_admin), deadline);
} else {
return ZX_OK;
}
}
}
zx_status_t FuchsiaVfs::UnmountHandle(fidl::ClientEnd<fuchsia_io_admin::DirectoryAdmin> handle,
zx::time deadline) {
fidl::WireResult<fuchsia_io_admin::DirectoryAdmin::Unmount> result(handle, deadline.get());
if (!result.ok()) {
return result.status();
}
return result.Unwrap()->s;
}
// Installs a remote filesystem on vn and adds it to the remote_list_.
zx_status_t FuchsiaVfs::InstallRemoteLocked(fbl::RefPtr<Vnode> vn, MountChannel h) {
if (vn == nullptr) {
return ZX_ERR_ACCESS_DENIED;
}
// Allocate a node to track the remote handle
fbl::AllocChecker ac;
std::unique_ptr<MountNode> mount_point(new (&ac) MountNode());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = vn->AttachRemote(std::move(h));
if (status != ZX_OK) {
return status;
}
// Save this node in the list of mounted vnodes
mount_point->SetNode(std::move(vn));
remote_list_.push_front(std::move(mount_point));
return ZX_OK;
}
// Uninstall the remote filesystem mounted on vn. Removes vn from the remote_list_, and sends its
// corresponding filesystem an 'unmount' signal.
zx_status_t FuchsiaVfs::UninstallRemoteLocked(fbl::RefPtr<Vnode> vn,
fidl::ClientEnd<fio::Directory>* h) {
std::unique_ptr<MountNode> mount_point;
{
mount_point =
remote_list_.erase_if([&vn](const MountNode& node) { return node.VnodeMatch(vn); });
if (!mount_point) {
return ZX_ERR_NOT_FOUND;
}
}
*h = mount_point->ReleaseRemote();
return ZX_OK;
}
} // namespace fs