| // 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 = ♭ |
| return ZX_OK; |
| } |