blob: 95836b4c033240ba19dccc403cb9e6785b405f57 [file] [log] [blame] [edit]
// Copyright 2019 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 "local-filesystem.h"
#include <lib/fdio/directory.h>
#include <lib/fdio/namespace.h>
#include <lib/fit/defer.h>
#include <lib/stdcompat/string_view.h>
#include <lib/zx/channel.h>
#include <lib/zxio/cpp/create_with_type.h>
#include <lib/zxio/types.h>
#include <zircon/processargs.h>
#include <utility>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <fbl/string.h>
#include <sdk/lib/fdio/zxio.h>
#include "local-connection.h"
#include "local-vnode.h"
namespace fio = fuchsia_io;
namespace {
std::pair<std::string_view, bool> FindNextPathSegment(std::string_view path) {
const size_t next_slash = path.find('/');
return {path.substr(0, next_slash), next_slash == std::string_view::npos};
}
zx::result<fbl::RefPtr<fdio_internal::LocalVnode>> CreateRemoteVnode(
std::optional<fdio_internal::LocalVnode::ParentAndId> parent_and_id,
fidl::ClientEnd<fio::Directory> remote) {
zxio_storage_t remote_storage;
if (zx_status_t status = zxio::CreateDirectory(&remote_storage, std::move(remote));
status != ZX_OK) {
return zx::error(status);
}
return zx::ok(fbl::MakeRefCounted<fdio_internal::LocalVnode>(
parent_and_id, std::in_place_type_t<fdio_internal::LocalVnode::Remote>(), remote_storage));
}
} // namespace
fdio_namespace::fdio_namespace() { ResetRoot(); }
void fdio_namespace::ResetRoot() {
root_ = fbl::MakeRefCounted<LocalVnode>(std::nullopt,
std::in_place_type_t<LocalVnode::Intermediate>());
}
zx_status_t fdio_namespace::WalkLocked(fbl::RefPtr<LocalVnode>* in_out_vn,
std::string_view* in_out_path) const {
fbl::RefPtr<LocalVnode> vn = *in_out_vn;
std::string_view path_remaining = *in_out_path;
// Empty path or "." matches initial node.
if (path_remaining.empty() || path_remaining == ".") {
return ZX_OK;
}
for (;;) {
auto [next_path_segment, is_last_segment] = FindNextPathSegment(path_remaining);
// Path segments may not longer than NAME_MAX.
if (next_path_segment.length() > NAME_MAX) {
return ZX_ERR_BAD_PATH;
}
// "." matches current node.
if (next_path_segment != ".") {
// The outcome of this visit is a ternary that either communicates a success/failure in
// walking a path within the namespace, or a need to continue parsing the path to
// find the end of the path within the namespace.
std::optional walk_status_opt =
std::visit(fdio_internal::overloaded{
[](LocalVnode::Local& c) -> std::optional<zx_status_t> {
// Local file are never directories.
return ZX_ERR_NOT_FOUND;
},
[&, next_path_segment = next_path_segment](
LocalVnode::Intermediate& c) -> std::optional<zx_status_t> {
fbl::RefPtr<LocalVnode> child = c.Lookup(next_path_segment);
// If we didn't find the next valid child, there's no way to proceed.
if (child == nullptr) {
return ZX_ERR_NOT_FOUND;
}
// Proceed to parse the path, with the new child.
vn = child;
return std::nullopt;
},
[&](LocalVnode::Remote& s) -> std::optional<zx_status_t> {
*in_out_vn = vn;
*in_out_path = path_remaining;
return ZX_OK;
},
},
vn->NodeType());
if (walk_status_opt.has_value()) {
return walk_status_opt.value();
}
}
if (is_last_segment) {
// The full path is contained within the fdio_namespace. Return
// the terminal local_vnode, along with a self-referential
// remaining path.
*in_out_vn = vn;
*in_out_path = ".";
return ZX_OK;
}
// Lookup completed successfully, but more segments exist.
path_remaining.remove_prefix(next_path_segment.length() + 1);
}
}
zx::result<fdio_ptr> fdio_namespace::OpenAt(fbl::RefPtr<LocalVnode> vn, std::string_view path,
fio::Flags flags) const {
{
std::lock_guard lock(lock_);
zx_status_t status = WalkLocked(&vn, &path);
if (status != ZX_OK) {
return zx::error(status);
}
}
return std::visit(
fdio_internal::overloaded{
[&](LocalVnode::Local& l) -> zx::result<fdio_ptr> { return l.Open(); },
[&](LocalVnode::Intermediate& c) -> zx::result<fdio_ptr> { return CreateConnection(vn); },
[&](LocalVnode::Remote& s) -> zx::result<fdio_ptr> {
if ((flags & fio::Flags::kFlagMustCreate) && path == ".") {
return zx::error(ZX_ERR_ALREADY_EXISTS);
}
fdio_ptr io = fbl::MakeRefCounted<fdio_internal::remote>();
return zx::make_result(
zxio_open(s.Connection(), path.data(), path.length(), zxio_open_flags_t{flags},
/*options*/ {}, &io->zxio_storage()),
io);
},
},
vn->NodeType());
}
zx_status_t fdio_namespace::Readdir(const LocalVnode& vn, DirentIteratorState* state,
zxio_dirent_t* inout_entry) const {
std::lock_guard lock(lock_);
auto populate_entry = [](zxio_dirent_t* inout_entry, std::string_view name) {
if (name.size() > NAME_MAX) {
return ZX_ERR_INVALID_ARGS;
}
ZXIO_DIRENT_SET(*inout_entry, protocols, ZXIO_NODE_PROTOCOL_DIRECTORY);
uint8_t name_size = static_cast<uint8_t>(name.size());
inout_entry->name_length = name_size;
memcpy(inout_entry->name, name.data(), name_size);
return ZX_OK;
};
if (!state->encountered_dot) {
zx_status_t status = populate_entry(inout_entry, std::string_view("."));
if (status != ZX_OK) {
return status;
}
state->encountered_dot = true;
return ZX_OK;
}
zx::result name = vn.Readdir(&state->last_seen);
if (name.is_error()) {
return name.error_value();
}
return populate_entry(inout_entry, name.value());
}
zx::result<fdio_ptr> fdio_namespace::CreateConnection(fbl::RefPtr<LocalVnode> vn) const {
return fdio_internal::CreateLocalConnection(fbl::RefPtr(this), std::move(vn));
}
zx_status_t fdio_namespace::OpenRemoteDeprecated(std::string_view path, fio::wire::OpenFlags flags,
fidl::ServerEnd<fio::Node> server_end) const {
// Require that we start at /
if (!cpp20::starts_with(path, '/')) {
return ZX_ERR_NOT_FOUND;
}
// Skip leading slash.
path.remove_prefix(1);
fbl::RefPtr<LocalVnode> vn;
{
std::lock_guard lock(lock_);
vn = root_;
zx_status_t status = WalkLocked(&vn, &path);
if (status != ZX_OK) {
return status;
}
}
return std::visit(fdio_internal::overloaded{
[](LocalVnode::Local& l) {
// Cannot connect to non-mount-points.
return ZX_ERR_NOT_SUPPORTED;
},
[](LocalVnode::Intermediate& c) {
// Cannot connect to non-mount-points.
return ZX_ERR_NOT_SUPPORTED;
},
[&](LocalVnode::Remote& s) {
zx_handle_t borrowed_handle = ZX_HANDLE_INVALID;
zx_status_t status = zxio_borrow(s.Connection(), &borrowed_handle);
if (status != ZX_OK) {
return status;
}
fidl::UnownedClientEnd<fio::Directory> directory(borrowed_handle);
#if FUCHSIA_API_LEVEL_AT_LEAST(27)
return fidl::WireCall(directory)
->DeprecatedOpen(flags, {}, fidl::StringView::FromExternal(path),
std::move(server_end))
.status();
#else
return fidl::WireCall(directory)
->Open(flags, {}, fidl::StringView::FromExternal(path),
std::move(server_end))
.status();
#endif
},
},
vn->NodeType());
}
zx_status_t fdio_namespace::OpenRemote(std::string_view path, fio::Flags flags,
zx::channel object) const {
// Require that we start at /
if (!cpp20::starts_with(path, '/')) {
return ZX_ERR_NOT_FOUND;
}
// Skip leading slash.
path.remove_prefix(1);
fbl::RefPtr<LocalVnode> vn;
{
std::lock_guard lock(lock_);
vn = root_;
zx_status_t status = WalkLocked(&vn, &path);
if (status != ZX_OK) {
return status;
}
}
return std::visit(fdio_internal::overloaded{
[](LocalVnode::Local& l) {
// Cannot connect to non-mount-points.
return ZX_ERR_NOT_SUPPORTED;
},
[](LocalVnode::Intermediate& c) {
// Cannot connect to non-mount-points.
return ZX_ERR_NOT_SUPPORTED;
},
[&](LocalVnode::Remote& s) {
zx_handle_t borrowed_handle = ZX_HANDLE_INVALID;
zx_status_t status = zxio_borrow(s.Connection(), &borrowed_handle);
if (status != ZX_OK) {
return status;
}
fidl::UnownedClientEnd<fio::Directory> directory(borrowed_handle);
#if FUCHSIA_API_LEVEL_AT_LEAST(27)
return fidl::WireCall(directory)
->Open(fidl::StringView::FromExternal(path), flags, {},
std::move(object))
.status();
#else
return fidl::WireCall(directory)
->Open3(fidl::StringView::FromExternal(path), flags, {},
std::move(object))
.status();
#endif
},
},
vn->NodeType());
}
zx_status_t fdio_namespace::Unbind(std::string_view path) {
if (!cpp20::starts_with(path, '/')) {
return ZX_ERR_INVALID_ARGS;
}
// Skip leading slash.
path.remove_prefix(1);
std::lock_guard lock(lock_);
fbl::RefPtr<LocalVnode> vn = root_;
if (path.empty()) {
if (zx_status_t status =
std::visit(fdio_internal::overloaded{
[&](LocalVnode::Local&) -> zx_status_t { return ZX_OK; },
[](LocalVnode::Intermediate&) -> zx_status_t {
// The node identified by the path is not a mount point,
// so unbinding makes no sense.
return ZX_ERR_NOT_FOUND;
},
[&](LocalVnode::Remote&) -> zx_status_t { return ZX_OK; },
},
vn->NodeType());
status != ZX_OK) {
return status;
}
ResetRoot();
return ZX_OK;
}
// This node denotes the "highest" node in a lineage of nodes with
// one or fewer children. It is tracked to ensure that when the target
// node identified by `path` is identified, we unbind it and all
// child-less parents that its removal would have created.
fbl::RefPtr<LocalVnode> removable_origin_vn;
for (;;) {
auto [next_path_segment, is_last_segment] = FindNextPathSegment(path);
if (next_path_segment.length() > NAME_MAX) {
return ZX_ERR_BAD_PATH;
}
// Check to see if the working node contains a child identified by the next path segment.
zx::result next_vn =
std::visit(fdio_internal::overloaded{
[](LocalVnode::Local&) -> zx::result<fbl::RefPtr<LocalVnode>> {
// At the end of each iteration, its considered a failure for the "next"
// working node to not be intermediate if more segments remain, so the only
// way to arrive here is if our first working node is a mount point. Our
// first working node is always root, and unbinding root is not supported.
return zx::error(ZX_ERR_BAD_PATH);
},
[&, next_path_segment = next_path_segment](
LocalVnode::Intermediate& c) -> zx::result<fbl::RefPtr<LocalVnode>> {
fbl::RefPtr<LocalVnode> next_vn = c.Lookup(next_path_segment);
if (next_vn == nullptr) {
// The working node was an intermediate node, and Lookup failed to find
// the relevant next path segment.
return zx::error(ZX_ERR_NOT_FOUND);
}
return zx::ok(next_vn);
},
[](LocalVnode::Remote&) -> zx::result<fbl::RefPtr<LocalVnode>> {
// At the end of each iteration, its considered a failure for the "next"
// working node to not be intermediate if more segments remain, so the only
// way to arrive here is if our first working node is a mount point. Our
// first working node is always root, and unbinding root is not supported.
return zx::error(ZX_ERR_BAD_PATH);
},
},
vn->NodeType());
if (next_vn.is_error()) {
return next_vn.error_value();
}
vn = std::move(next_vn.value());
auto handle_terminal_node = [&removable_origin_vn, &vn, &is_last_segment]() -> zx_status_t {
if (!is_last_segment) {
// If the non-final segment of a namespace path has a Local node,
// then the path is invalid, since this Vnode has no children, and future
// segments cannot exist in the namespace.
return ZX_ERR_NOT_FOUND;
}
// There is no higher parent to unlink than our target node.
if (removable_origin_vn == nullptr) {
removable_origin_vn = vn;
}
removable_origin_vn->UnlinkFromParent();
return ZX_OK;
};
// The outcome of this visit is a ternary that either communicates a success/failure in
// an unbind attempt, or a need to continue parsing the path to find the bind location.
std::optional status_opt = std::visit(
fdio_internal::overloaded{
[&handle_terminal_node](const LocalVnode::Local&) -> std::optional<zx_status_t> {
return handle_terminal_node();
},
[&, is_last_segment = is_last_segment, next_path_segment = next_path_segment](
const LocalVnode::Intermediate& c) -> std::optional<zx_status_t> {
if (is_last_segment) {
// The node identified by the path is not a mount point, so unbinding
// makes no sense.
return ZX_ERR_NOT_FOUND;
}
if (c.num_children() > 1) {
// If this node has multiple children (including something OTHER than the
// node we're potentially unbinding), we shouldn't try to remove it while
// deleting childless intermediate nodes.
removable_origin_vn = nullptr;
} else if (removable_origin_vn == nullptr) {
// If this node has one or fewer children, it's a viable candidate for
// removal. Only set this if it's the "highest" node we've seen
// satisfying this property.
removable_origin_vn = vn;
}
// We only remove the prefix if children are present, as this is the only
// case in which future iterations will find a new node.
path.remove_prefix(next_path_segment.length() + 1);
return std::nullopt;
},
[&handle_terminal_node](const LocalVnode::Remote&) -> std::optional<zx_status_t> {
return handle_terminal_node();
},
},
vn->NodeType());
if (status_opt.has_value()) {
return status_opt.value();
}
}
}
bool fdio_namespace::IsBound(std::string_view path) {
if (!cpp20::starts_with(path, '/')) {
return false;
}
path.remove_prefix(1);
std::lock_guard lock(lock_);
fbl::RefPtr<LocalVnode> vn = root_;
zx_status_t status = WalkLocked(&vn, &path);
if (status != ZX_OK) {
return false;
}
return std::visit(fdio_internal::overloaded{
[&](LocalVnode::Local& l) { return path == "."; },
[](LocalVnode::Intermediate& c) { return false; },
[&](LocalVnode::Remote& s) { return path == "."; },
},
vn->NodeType());
}
zx_status_t fdio_namespace::Bind(std::string_view path, fidl::ClientEnd<fio::Directory> remote) {
if (!remote.is_valid()) {
return ZX_ERR_BAD_HANDLE;
}
return Bind(path, [remote = std::move(remote)](
std::optional<LocalVnode::ParentAndId> parent_and_id) mutable {
return CreateRemoteVnode(parent_and_id, std::move(remote));
});
}
zx_status_t fdio_namespace::Bind(std::string_view path, fdio_open_local_func_t on_open,
void* context) {
return Bind(path, [on_open, context](std::optional<LocalVnode::ParentAndId> parent_and_id) {
return zx::ok(fbl::MakeRefCounted<LocalVnode>(
parent_and_id, std::in_place_type_t<LocalVnode::Local>(), on_open, context));
});
}
zx_status_t fdio_namespace::Bind(
std::string_view path,
fit::function<zx::result<fbl::RefPtr<LocalVnode>>(std::optional<LocalVnode::ParentAndId>)>
builder) {
if (!cpp20::starts_with(path, '/')) {
return ZX_ERR_INVALID_ARGS;
}
// Skip leading slash.
path.remove_prefix(1);
std::lock_guard lock(lock_);
if (path.empty()) {
// We've been asked to bind the namespace root. In this function, we will not
// bind root if:
// A) root was previously an intermediate node, and already has any children.
// B) root was previously a remote or local node.
return std::visit(fdio_internal::overloaded{
[](LocalVnode::Local& l) {
// Root is already a local node. Bind must fail.
return ZX_ERR_ALREADY_EXISTS;
},
[&](LocalVnode::Intermediate& c) {
// Convince the compiler that the lock is held. This is safe to
// perform because the lock's scope extends over the synchronous
// std::visit call, so the lifetime of the lambda cannot be extended.
//
// This function is required because std::visit has no way to provide
// TA_REQ annotations for lock_, and annotating the lambdas with
// NO_TA will prevent future internal locks from being scrutinized.
[]() __TA_ASSERT(lock_) {}();
if (c.num_children() != 0) {
// Overlay remotes are disallowed.
return ZX_ERR_NOT_SUPPORTED;
}
// The path was "/" so we're trying to bind to the root vnode.
zx::result vn_res = builder({});
if (vn_res.is_error()) {
return vn_res.error_value();
}
root_ = std::move(vn_res.value());
return ZX_OK;
},
[](LocalVnode::Remote& s) {
// Root is already a remote node. Bind must fail.
return ZX_ERR_ALREADY_EXISTS;
},
},
root_->NodeType());
}
fbl::RefPtr<LocalVnode> vn = root_;
fbl::RefPtr<LocalVnode> first_new_node = nullptr;
// If we fail, but leave any intermediate nodes, we need to clean them up
// before unlocking and returning.
auto cleanup = fit::defer([&first_new_node]() {
if (first_new_node != nullptr) {
first_new_node->UnlinkFromParent();
}
});
for (;;) {
auto [next_path_segment, is_last_segment] = FindNextPathSegment(path);
if (next_path_segment.length() > NAME_MAX) {
return ZX_ERR_BAD_PATH;
}
auto next_path_segment_builder =
[&](LocalVnode::ParentAndId parent_and_id) -> zx::result<fbl::RefPtr<LocalVnode>> {
if (is_last_segment) {
return builder(parent_and_id);
}
return zx::ok(fbl::MakeRefCounted<LocalVnode>(
parent_and_id, std::in_place_type_t<LocalVnode::Intermediate>()));
};
// The outcome of this visit is a ternary that either communicates a success/failure in
// a bind attempt, or a need to continue parsing the path to find the bind location.
std::optional walk_status_opt = std::visit(
fdio_internal::overloaded{
[](LocalVnode::Local& l) -> std::optional<zx_status_t> {
// Encountering a valid storage end at any point in the bind path
// implies shadowing, which is not supported.
return ZX_ERR_NOT_SUPPORTED;
},
[&](LocalVnode::Intermediate& c) -> std::optional<zx_status_t> {
zx::result res = c.LookupOrInsert(next_path_segment, next_path_segment_builder);
if (res.is_error()) {
return res.error_value();
}
auto& [child, created] = res.value();
vn = std::move(child);
if (is_last_segment) {
// If the final segment already exists as a child on our working node,
// we cannot overwrite.
if (!created) {
return ZX_ERR_ALREADY_EXISTS;
}
return ZX_OK;
}
// Keep track of the first node we create. If any subsequent
// operation fails during bind, we will need to delete all nodes
// in this subtree.
if (created && first_new_node == nullptr) {
first_new_node = vn;
}
// Our working node is our new intermediate node. Let's continue
// the bind.
return std::nullopt;
},
[](LocalVnode::Remote& s) -> std::optional<zx_status_t> {
// Encountering a valid storage end at any point in the bind path
// implies shadowing, which is not supported.
return ZX_ERR_NOT_SUPPORTED;
},
},
vn->NodeType());
if (walk_status_opt.has_value()) {
zx_status_t walk_status = walk_status_opt.value();
// Make sure to cancel our deferred cleanup if
// the bind succeeded.
if (walk_status == ZX_OK) {
cleanup.cancel();
}
return walk_status;
}
// Proceed to loop onto subpath.
path.remove_prefix(next_path_segment.length() + 1);
}
}
zx::result<fdio_ptr> fdio_namespace::OpenRoot() const {
fbl::RefPtr<LocalVnode> vn = [this]() {
std::lock_guard lock(lock_);
return root_;
}();
return std::visit(
fdio_internal::overloaded{
[&](LocalVnode::Local&) -> zx::result<fdio_ptr> {
// The root node should never be a local node.
return zx::error(ZX_ERR_NOT_SUPPORTED);
},
[&](LocalVnode::Intermediate&) -> zx::result<fdio_ptr> { return CreateConnection(vn); },
[](LocalVnode::Remote& s) -> zx::result<fdio_ptr> {
zx::result endpoints = fidl::CreateEndpoints<fio::Node>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
zx::channel clone;
zx_status_t status = zxio_clone(s.Connection(), clone.reset_and_get_address());
if (status != ZX_OK) {
return zx::error(status);
}
// We know this is a Directory.
fio::wire::DirectoryInfo info;
return fdio::create(fidl::ClientEnd<fio::Node>(std::move(clone)),
fio::wire::Representation::WithDirectory(
fidl::ObjectView<decltype(info)>::FromExternal(&info)));
},
},
vn->NodeType());
}
zx_status_t fdio_namespace::SetRoot(fdio_t* io) {
fbl::RefPtr<LocalVnode> vn = fdio_internal::GetLocalNodeFromConnectionIfAny(io);
if (!vn) {
fidl::ClientEnd<fio::Directory> client_end;
zx_status_t status = io->clone(client_end.channel().reset_and_get_address());
if (status != ZX_OK) {
return status;
}
zx::result vn_res = CreateRemoteVnode({}, std::move(client_end));
if (vn_res.is_error()) {
return vn_res.error_value();
}
vn = std::move(vn_res.value());
}
std::lock_guard lock(lock_);
if (vn == root_) {
// Nothing to do.
return ZX_OK;
}
vn->UnlinkFromParent();
std::swap(root_, vn);
return ZX_OK;
}
zx_status_t fdio_namespace::Export(fdio_flat_namespace_t** out) const {
std::lock_guard lock(lock_);
size_t count = 0;
size_t buffer_size = 0;
auto count_callback = [&](std::string_view path, zxio_t* remote) {
count += 1;
buffer_size += path.size() + 1;
return ZX_OK;
};
if (zx_status_t status = root_->EnumerateRemotes(count_callback); status != ZX_OK) {
return status;
}
// Allocate enough space for a hypothetical:
// struct {
// fdio_flat_namespace_t flat;
// zx_handle_t handle[count];
// char* path[count];
// char buffer[buffer_size];
// };
// Insert padding where needed for alignment.
auto padding = [](size_t offset, size_t alignment) {
return (alignment - (offset % alignment)) % alignment;
};
size_t offset = 0;
offset += sizeof(fdio_flat_namespace_t);
offset += padding(offset, alignof(zx_handle_t));
const size_t handle_offset = offset;
offset += sizeof(zx_handle_t) * count;
offset += padding(offset, alignof(char*));
const size_t path_offset = offset;
offset += sizeof(char*) * count;
const size_t buffer_offset = offset;
offset += buffer_size;
std::byte* ptr = static_cast<std::byte*>(malloc(offset));
if (ptr == nullptr) {
return ZX_ERR_NO_MEMORY;
}
fdio_flat_namespace_t& flat = *reinterpret_cast<fdio_flat_namespace_t*>(ptr);
flat = {
.count = 0,
.handle = reinterpret_cast<zx_handle_t*>(ptr + handle_offset),
.path = reinterpret_cast<char**>(ptr + path_offset),
};
char* buffer = reinterpret_cast<char*>(ptr + buffer_offset);
auto cleanup = fit::defer([flat = &flat]() { fdio_ns_free_flat_ns(flat); });
auto export_callback = [&](std::string_view path, zxio_t* remote) {
zx::channel remote_clone;
zx_status_t status = zxio_clone(remote, remote_clone.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
flat.handle[flat.count] = remote_clone.release();
const_cast<char**>(flat.path)[flat.count] = buffer;
flat.count += 1;
memcpy(buffer, path.data(), path.length());
buffer += path.length();
*buffer++ = 0;
return ZX_OK;
};
if (zx_status_t status = root_->EnumerateRemotes(export_callback); status != ZX_OK) {
return status;
}
cleanup.cancel();
*out = &flat;
return ZX_OK;
}