blob: d72a608fa3f08efc6b4380da0f31c5dc0227176d [file] [log] [blame]
// 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 "src/storage/lib/vfs/cpp/connection/directory_connection.h"
#include <fidl/fuchsia.io/cpp/common_types.h>
#include <fidl/fuchsia.io/cpp/natural_types.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire_types.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/fidl/cpp/wire/object_view.h>
#include <lib/fidl/cpp/wire/status.h>
#include <lib/fidl/cpp/wire/string_view.h>
#include <lib/fidl/cpp/wire/vector_view.h>
#include <lib/file-lock/file-lock.h>
#include <lib/fit/function.h>
#include <lib/zx/channel.h>
#include <lib/zx/event.h>
#include <lib/zx/handle.h>
#include <lib/zx/result.h>
#include <stdint.h>
#include <stdlib.h>
#include <zircon/assert.h>
#include <zircon/availability.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <memory>
#include <optional>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <fbl/ref_ptr.h>
#include "src/storage/lib/vfs/cpp/connection/advisory_lock.h"
#include "src/storage/lib/vfs/cpp/connection/connection.h"
#include "src/storage/lib/vfs/cpp/debug.h"
#include "src/storage/lib/vfs/cpp/vfs_types.h"
#include "src/storage/lib/vfs/cpp/vnode.h"
namespace fio = fuchsia_io;
namespace fs::internal {
namespace {
constexpr zx::result<std::tuple<fio::Rights, fio::Rights>> ValidateRequestRights(
fio::Flags flags, fio::Rights parent_rights) {
// If the request will create a new object, ensure this connection allows it.
if (internal::CreationModeFromFidl(flags) != CreationMode::kNever &&
!(parent_rights & fio::Rights::kModifyDirectory)) {
return zx::error(ZX_ERR_ACCESS_DENIED);
}
// If the request attempts to truncate a file, it must also request write permissions.
if ((flags & fio::Flags::kFileTruncate) && !(flags & fio::Flags::kPermWriteBytes)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
fio::Rights requested_rights = internal::FlagsToRights(flags);
// If the requested rights exceed those of the parent connection, reject the request.
if (requested_rights - parent_rights) {
return zx::error(ZX_ERR_ACCESS_DENIED);
}
fio::Rights optional_rights;
if (flags & fio::Flags::kPermInheritWrite) {
optional_rights |= fio::kInheritedWritePermissions;
}
if (flags & fio::Flags::kPermInheritExecute) {
optional_rights |= fio::Rights::kExecute;
}
// *CAUTION*: The resulting connection rights must *never* exceed those of |parent_rights|.
return zx::ok(std::tuple{requested_rights & parent_rights, optional_rights & parent_rights});
}
#if FUCHSIA_API_LEVEL_AT_LEAST(27)
void ForwardRequestToRemote(fio::wire::DirectoryOpenRequest* request, Vfs::OpenResult open_result,
fio::Rights parent_rights) {
#else
void ForwardRequestToRemote(fio::wire::DirectoryOpen3Request* request, Vfs::OpenResult open_result,
fio::Rights parent_rights) {
#endif // FUCHSIA_API_LEVEL_AT_LEAST(27)
ZX_DEBUG_ASSERT(open_result.vnode()->IsRemote());
// Update the request path to point only to the remaining segment.
request->path = fidl::StringView::FromExternal(open_result.path());
// Remove optional rights from the request that the parent lacks.
if ((fio::kInheritedWritePermissions)-parent_rights) {
request->flags &= ~fio::Flags::kPermInheritWrite;
}
if (fio::Rights::kExecute - parent_rights) {
request->flags &= ~fio::Flags::kPermInheritExecute;
}
open_result.TakeVnode()->OpenRemote(std::move(*request));
}
} // namespace
DirectoryConnection::DirectoryConnection(fs::FuchsiaVfs* vfs, fbl::RefPtr<fs::Vnode> vnode,
fuchsia_io::Rights rights, zx_koid_t koid)
: Connection(vfs, std::move(vnode), rights), koid_(koid) {
// Ensure the VFS does not create connections that have privileges which cannot be used.
ZX_DEBUG_ASSERT(internal::DownscopeRights(rights, VnodeProtocol::kDirectory) == rights);
}
void DirectoryConnection::BindImpl(zx::channel channel, OnUnbound on_unbound) {
ZX_DEBUG_ASSERT(!binding_);
binding_.emplace(fidl::BindServer(
vfs()->dispatcher(), fidl::ServerEnd<fuchsia_io::Directory>{std::move(channel)}, this,
[on_unbound = std::move(on_unbound)](DirectoryConnection* self, fidl::UnbindInfo,
fidl::ServerEnd<fuchsia_io::Directory>) {
[[maybe_unused]] zx::result<> result = self->CloseVnode(self->koid_);
on_unbound(self);
}));
}
void DirectoryConnection::Unbind() {
// NOTE: This needs to be thread-safe!
if (binding_)
binding_->Unbind();
}
#if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) || FUCHSIA_API_LEVEL_AT_LEAST(PLATFORM)
void DirectoryConnection::DeprecatedClone(DeprecatedCloneRequestView request,
DeprecatedCloneCompleter::Sync& completer) {
fidl::ServerEnd<fio::Node> server_end{std::move(request->object)};
if (request->flags & fio::wire::OpenFlags::kDescribe) {
// Ignore errors since there is nothing we can do if this fails.
[[maybe_unused]] auto result =
fidl::WireSendEvent(server_end)->OnOpen(ZX_ERR_NOT_SUPPORTED, {});
}
server_end.Close(ZX_ERR_NOT_SUPPORTED);
}
#endif
void DirectoryConnection::Clone(CloneRequestView request, CloneCompleter::Sync& completer) {
Connection::NodeClone(fio::Flags::kProtocolDirectory | fs::internal::RightsToFlags(rights()),
request->request.TakeChannel());
}
void DirectoryConnection::Close(CloseCompleter::Sync& completer) {
completer.Reply(CloseVnode(koid_));
Unbind();
}
void DirectoryConnection::Query(QueryCompleter::Sync& completer) {
const std::string_view kProtocolName = fidl::DiscoverableProtocolName<fio::Directory>;
// TODO(https://fxbug.dev/42052765): avoid the const cast.
uint8_t* data = reinterpret_cast<uint8_t*>(const_cast<char*>(kProtocolName.data()));
completer.Reply(fidl::VectorView<uint8_t>::FromExternal(data, kProtocolName.size()));
}
void DirectoryConnection::Sync(SyncCompleter::Sync& completer) {
vnode()->Sync([completer = completer.ToAsync()](zx_status_t sync_status) mutable {
if (sync_status != ZX_OK) {
completer.ReplyError(sync_status);
} else {
completer.ReplySuccess();
}
});
}
#if FUCHSIA_API_LEVEL_AT_LEAST(28)
void DirectoryConnection::DeprecatedGetAttr(DeprecatedGetAttrCompleter::Sync& completer) {
#else
void DirectoryConnection::GetAttr(GetAttrCompleter::Sync& completer) {
#endif // FUCHSIA_API_LEVEL_AT_LEAST(28)
zx::result attrs = vnode()->GetAttributes();
if (attrs.is_ok()) {
completer.Reply(ZX_OK, attrs->ToIoV1NodeAttributes(*vnode()));
} else {
completer.Reply(attrs.error_value(), fio::wire::NodeAttributes());
}
}
#if FUCHSIA_API_LEVEL_AT_LEAST(28)
void DirectoryConnection::DeprecatedSetAttr(DeprecatedSetAttrRequestView request,
DeprecatedSetAttrCompleter::Sync& completer) {
#else
void DirectoryConnection::SetAttr(SetAttrRequestView request, SetAttrCompleter::Sync& completer) {
#endif // FUCHSIA_API_LEVEL_AT_LEAST(28)
VnodeAttributesUpdate update =
VnodeAttributesUpdate::FromIo1(request->attributes, request->flags);
completer.Reply(Connection::NodeUpdateAttributes(update).status_value());
}
void DirectoryConnection::GetAttributes(fio::wire::NodeGetAttributesRequest* request,
GetAttributesCompleter::Sync& completer) {
// TODO(https://fxbug.dev/346585458): This operation should require the GET_ATTRIBUTES right.
internal::NodeAttributeBuilder builder(vnode());
completer.Reply(builder.Build(request->query));
}
void DirectoryConnection::UpdateAttributes(fio::wire::MutableNodeAttributes* request,
UpdateAttributesCompleter::Sync& completer) {
VnodeAttributesUpdate update = VnodeAttributesUpdate::FromIo2(*request);
completer.Reply(Connection::NodeUpdateAttributes(update));
}
#if FUCHSIA_API_LEVEL_AT_LEAST(27)
void DirectoryConnection::GetFlags(GetFlagsCompleter::Sync& completer) {
completer.ReplySuccess(fio::Flags::kProtocolDirectory | RightsToFlags(rights()));
}
void DirectoryConnection::SetFlags(SetFlagsRequestView, SetFlagsCompleter::Sync& completer) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
#endif // FUCHSIA_API_LEVEL_AT_LEAST(27)
#if FUCHSIA_API_LEVEL_AT_LEAST(27)
void DirectoryConnection::DeprecatedGetFlags(DeprecatedGetFlagsCompleter::Sync& completer) {
#else
void DirectoryConnection::GetFlags(GetFlagsCompleter::Sync& completer) {
#endif // FUCHSIA_API_LEVEL_AT_LEAST(27)
completer.Reply(ZX_OK, RightsToOpenFlags(rights()));
}
#if FUCHSIA_API_LEVEL_AT_LEAST(27)
void DirectoryConnection::DeprecatedSetFlags(DeprecatedSetFlagsRequestView,
DeprecatedSetFlagsCompleter::Sync& completer) {
#else
void DirectoryConnection::SetFlags(SetFlagsRequestView, SetFlagsCompleter::Sync& completer) {
#endif // FUCHSIA_API_LEVEL_AT_LEAST(27)
completer.Reply(ZX_ERR_NOT_SUPPORTED);
}
#if FUCHSIA_API_LEVEL_AT_LEAST(27)
void DirectoryConnection::DeprecatedOpen(DeprecatedOpenRequestView request,
DeprecatedOpenCompleter::Sync& completer) {
#else
void DirectoryConnection::Open(OpenRequestView request, OpenCompleter::Sync& completer) {
#endif // FUCHSIA_API_LEVEL_AT_LEAST(27)
// TODO(https://fxbug.dev/346585458): This operation should require the TRAVERSE right.
zx_status_t status = [&]() -> zx_status_t {
std::string_view path(request->path.data(), request->path.size());
fio::OpenFlags flags = request->flags;
if (path.empty() || ((path == "." || path == "/") && (flags & fio::OpenFlags::kNotDirectory))) {
return ZX_ERR_INVALID_ARGS;
}
if (path.back() == '/') {
flags |= fio::OpenFlags::kDirectory;
}
zx::result open_options = DeprecatedOptions::FromOpen1Flags(flags);
if (open_options.is_error()) {
FS_PRETTY_TRACE_DEBUG("[DirectoryDeprecatedOpen] invalid flags: ", request->flags,
", path: ", request->path);
return open_options.error_value();
}
FS_PRETTY_TRACE_DEBUG("[DirectoryDeprecatedOpen] our rights ", rights(),
", incoming options: ", *open_options, ", path: ", path);
// The POSIX compatibility flags allow the child directory connection to inherit the writable
// and executable rights. If there exists a directory without the corresponding right along
// the Open() chain, we remove that POSIX flag preventing it from being inherited down the line
// (this applies both for local and remote mount points, as the latter may be served using
// a connection with vastly greater rights).
if (!(rights() & fio::Rights::kWriteBytes)) {
open_options->flags &= ~fio::OpenFlags::kPosixWritable;
}
if (!(rights() & fio::Rights::kExecute)) {
open_options->flags &= ~fio::OpenFlags::kPosixExecutable;
}
// Return ACCESS_DENIED if the client asked for a right the parent connection doesn't have.
// TODO(https://fxbug.dev/346585458): GET_ATTRIBUTES was unprivileged in io1 so we cannot
// enforce that correctly here.
if ((open_options->rights & ~fio::Rights::kGetAttributes) - rights()) {
return ZX_ERR_ACCESS_DENIED;
}
// If the request attempts to create a file, ensure this connection allows it.
if (internal::CreationModeFromFidl(open_options->flags) != CreationMode::kNever &&
!(rights() & fio::Rights::kModifyDirectory)) {
return ZX_ERR_ACCESS_DENIED;
}
auto fs = vfs();
if (!fs)
return ZX_ERR_CANCELED;
return fs->DeprecatedOpen(vnode(), path, *open_options, rights())
.visit([&](auto&& result) -> zx_status_t {
using ResultT = std::decay_t<decltype(result)>;
if constexpr (std::is_same_v<ResultT, Vfs::DeprecatedOpenResult::Error>) {
return result;
} else if constexpr (std::is_same_v<ResultT, Vfs::DeprecatedOpenResult::Remote>) {
result.vnode->DeprecatedOpenRemote(open_options->ToIoV1Flags(), {},
fidl::StringView::FromExternal(result.path),
std::move(request->object));
return ZX_OK;
} else if constexpr (std::is_same_v<ResultT, Vfs::DeprecatedOpenResult::Ok>) {
return fs->ServeDeprecated(result.vnode, request->object.TakeChannel(), result.options);
}
});
}();
// On any errors, if the channel wasn't consumed, we send an OnOpen event if required, and try
// closing the channel with an epitaph describing the error.
if (status != ZX_OK) {
FS_PRETTY_TRACE_DEBUG("[DirectoryDeprecatedOpen] error: ", zx_status_get_string(status));
if (request->object.is_valid()) {
if (request->flags & fio::wire::OpenFlags::kDescribe) {
// Ignore errors since there is nothing we can do if this fails.
[[maybe_unused]] auto result = fidl::WireSendEvent(request->object)->OnOpen(status, {});
}
request->object.Close(status);
}
}
}
#if FUCHSIA_API_LEVEL_AT_LEAST(27)
void DirectoryConnection::Open(OpenRequestView request, OpenCompleter::Sync& completer) {
#else
void DirectoryConnection::Open3(Open3RequestView request, Open3Completer::Sync& completer) {
#endif // FUCHSIA_API_LEVEL_AT_LEAST(27)
FS_PRETTY_TRACE_DEBUG("[DirectoryConnection::Open] our rights: ", rights(), ", path: '",
request->path, "', flags: ", request->flags, "options: ", request->options);
// Attempt to open/create the target vnode, and serve a connection to it.
zx::result handled = [&]() -> zx::result<> {
// If the request attempts to query attributes, this connection must allow it.
if (request->options.has_attributes() && request->options.attributes() &&
!(this->rights() & fio::Rights::kGetAttributes)) {
return zx::error(ZX_ERR_ACCESS_DENIED);
}
// Calculate the set of rights the connection should have.
zx::result resulting_rights = ValidateRequestRights(request->flags, this->rights());
if (resulting_rights.is_error()) {
return resulting_rights.take_error();
}
auto [rights, optional_rights] = *resulting_rights;
// The rights for the new connection must never exceed those of this connection.
ZX_DEBUG_ASSERT((rights - this->rights()) == fio::Rights());
ZX_DEBUG_ASSERT((optional_rights - this->rights()) == fio::Rights());
auto fs = vfs();
if (!fs)
return zx::error(ZX_ERR_CANCELED);
// Handle opening (or creating) the vnode.
std::string_view path(request->path.data(), request->path.size());
zx::result open_result = fs->Open(vnode(), path, request->flags, &request->options, rights);
if (open_result.is_error()) {
FS_PRETTY_TRACE_DEBUG("[DirectoryConnection::Open] Vfs::Open failed: ",
open_result.status_string());
return open_result.take_error();
}
// If we encountered a remote node, forward the remainder of the request there.
if (open_result->vnode()->IsRemote()) {
ForwardRequestToRemote(request, *std::move(open_result), /*parent_rights*/ this->rights());
return zx::ok();
}
// Expand optional rights if we negotiated the directory protocol.
if (open_result->protocol() == fs::VnodeProtocol::kDirectory && optional_rights) {
if (fs->IsReadonly()) {
// Ensure that we don't grant the ability to modify the filesystem if it's read only.
optional_rights &= ~fs::kAllMutableIo2Rights;
}
rights |= optional_rights;
}
// Serve a new connection to the vnode.
return fs->ServeResult(*std::move(open_result), rights, request->object, request->flags,
request->options);
}();
// On any errors above, the object request channel should remain usable, so that we can close it
// with the corresponding error epitaph.
if (handled.is_error()) {
FS_PRETTY_TRACE_DEBUG("[DirectoryConnection::Open] Error: ", handled.status_string());
if (request->object.is_valid()) {
fidl::ServerEnd<fio::Node>{std::move(request->object)}.Close(handled.error_value());
}
return;
}
}
void DirectoryConnection::Unlink(UnlinkRequestView request, UnlinkCompleter::Sync& completer) {
FS_PRETTY_TRACE_DEBUG("[DirectoryUnlink] our rights: ", rights(), ", name: ", request->name);
// TODO(https://fxbug.dev/346585458): This operation should require ENUMERATE and MODIFY_DIRECTORY
// rights, instead of WRITE_BYTES.
if (!(rights() & fuchsia_io::Rights::kWriteBytes)) {
completer.ReplyError(ZX_ERR_BAD_HANDLE);
return;
}
std::string_view name_str(request->name.data(), request->name.size());
if (!IsValidName(name_str)) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
auto fs = vfs();
zx_status_t status =
fs ? fs->Unlink(vnode(), name_str,
request->options.has_flags() &&
static_cast<bool>((request->options.flags() &
fuchsia_io::wire::UnlinkFlags::kMustBeDirectory)))
: ZX_ERR_CANCELED;
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void DirectoryConnection::ReadDirents(ReadDirentsRequestView request,
ReadDirentsCompleter::Sync& completer) {
FS_PRETTY_TRACE_DEBUG("[DirectoryReadDirents] our rights: ", rights());
// TODO(https://fxbug.dev/346585458): This operation should require the ENUMERATE right.
if (request->max_bytes > fio::wire::kMaxBuf) {
completer.Reply(ZX_ERR_BAD_HANDLE, fidl::VectorView<uint8_t>());
return;
}
auto data = std::make_unique<uint8_t[]>(request->max_bytes);
size_t actual = 0;
auto fs = vfs();
zx_status_t status =
fs ? fs->Readdir(vnode().get(), &dircookie_, data.get(), request->max_bytes, &actual)
: ZX_ERR_CANCELED;
completer.Reply(status, fidl::VectorView<uint8_t>::FromExternal(data.get(), actual));
}
void DirectoryConnection::Rewind(RewindCompleter::Sync& completer) {
FS_PRETTY_TRACE_DEBUG("[DirectoryRewind] our rights: ", rights());
// TODO(https://fxbug.dev/346585458): This operation should require the ENUMERATE right.
dircookie_ = VdirCookie();
completer.Reply(ZX_OK);
}
void DirectoryConnection::GetToken(GetTokenCompleter::Sync& completer) {
FS_PRETTY_TRACE_DEBUG("[DirectoryGetToken] our rights: ", rights());
// GetToken exists to support linking, so we must make sure the connection has the permission to
// modify the directory.
if (!(rights() & fuchsia_io::Rights::kModifyDirectory)) {
completer.Reply(ZX_ERR_BAD_HANDLE, zx::handle());
return;
}
zx::event returned_token;
auto fs = vfs();
zx_status_t status = fs ? fs->VnodeToToken(vnode(), &token(), &returned_token) : ZX_ERR_CANCELED;
completer.Reply(status, std::move(returned_token));
}
void DirectoryConnection::Rename(RenameRequestView request, RenameCompleter::Sync& completer) {
FS_PRETTY_TRACE_DEBUG("[DirectoryRename] our rights: ", rights(), ", src: ", request->src,
", dst: ", request->dst);
if (request->src.empty() || request->dst.empty()) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
// TODO(https://fxbug.dev/346585458): This operation should require the MODIFY_DIRECTORY right
// instead of the WRITE_BYTES right.
if (!(rights() & fuchsia_io::Rights::kWriteBytes)) {
completer.ReplyError(ZX_ERR_BAD_HANDLE);
return;
}
auto fs = vfs();
zx_status_t status = fs ? fs->Rename(std::move(request->dst_parent_token), vnode(),
std::string_view(request->src.data(), request->src.size()),
std::string_view(request->dst.data(), request->dst.size()))
: ZX_ERR_CANCELED;
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void DirectoryConnection::Link(LinkRequestView request, LinkCompleter::Sync& completer) {
FS_PRETTY_TRACE_DEBUG("[DirectoryLink] our rights: ", rights(), ", src: ", request->src,
", dst: ", request->dst);
// |fuchsia.io/Directory.Rename| only specified the token to be a generic handle; casting it here.
zx::event token(request->dst_parent_token.release());
if (request->src.empty() || request->dst.empty()) {
completer.Reply(ZX_ERR_INVALID_ARGS);
return;
}
// To avoid rights escalation, we must make sure that the connection to the source directory has
// the maximal set of file rights. We do not check for EXECUTE because mutable filesystems that
// support link don't currently support EXECUTE rights. The destination rights are verified by
// virtue of the fact that it is not possible to get a token without the MODIFY_DIRECTORY right.
if ((rights() & fuchsia_io::kRwStarDir) != fuchsia_io::kRwStarDir) {
completer.Reply(ZX_ERR_BAD_HANDLE);
return;
}
auto fs = vfs();
zx_status_t status = fs ? fs->Link(std::move(token), vnode(),
std::string_view(request->src.data(), request->src.size()),
std::string_view(request->dst.data(), request->dst.size()))
: ZX_ERR_CANCELED;
completer.Reply(status);
}
void DirectoryConnection::Watch(WatchRequestView request, WatchCompleter::Sync& completer) {
FS_PRETTY_TRACE_DEBUG("[DirectoryWatch] our rights: ", rights());
// TODO(https://fxbug.dev/346585458): This operation should require the ENUMERATE right.
auto fs = vfs();
zx_status_t status =
fs ? vnode()->WatchDir(fs.get(), request->mask, request->options, std::move(request->watcher))
: ZX_ERR_CANCELED;
completer.Reply(status);
}
void DirectoryConnection::QueryFilesystem(QueryFilesystemCompleter::Sync& completer) {
FS_PRETTY_TRACE_DEBUG("[DirectoryQueryFilesystem] our rights: ", rights());
zx::result result = Connection::NodeQueryFilesystem();
completer.Reply(result.status_value(),
result.is_ok() ? fidl::ObjectView<fuchsia_io::wire::FilesystemInfo>::FromExternal(
&result.value())
: nullptr);
}
void DirectoryConnection::AdvisoryLock(AdvisoryLockRequestView request,
AdvisoryLockCompleter::Sync& completer) {
// advisory_lock replies to the completer
auto async_completer = completer.ToAsync();
fit::callback<void(zx_status_t)> callback = file_lock::lock_completer_t(
[lock_completer = std::move(async_completer)](zx_status_t status) mutable {
lock_completer.ReplyError(status);
});
advisory_lock(koid_, vnode(), false, request->request, std::move(callback));
}
zx::result<> DirectoryConnection::WithRepresentation(
fit::callback<zx::result<>(fuchsia_io::wire::Representation)> handler,
std::optional<fuchsia_io::NodeAttributesQuery> query) const {
using DirectoryRepresentation = fio::wire::DirectoryInfo;
fidl::WireTableFrame<DirectoryRepresentation> representation_frame;
auto builder = DirectoryRepresentation::ExternalBuilder(
fidl::ObjectView<fidl::WireTableFrame<DirectoryRepresentation>>::FromExternal(
&representation_frame));
std::optional<NodeAttributeBuilder> attributes_builder;
if (query) {
attributes_builder.emplace(vnode());
zx::result<fio::wire::NodeAttributes2*> attributes = attributes_builder->Build(*query);
if (attributes.is_error()) {
return attributes.take_error();
}
builder.attributes(fidl::ObjectView<fio::wire::NodeAttributes2>::FromExternal(*attributes));
}
auto representation = builder.Build();
return handler(fuchsia_io::wire::Representation::WithDirectory(
fidl::ObjectView<DirectoryRepresentation>::FromExternal(&representation)));
}
zx_status_t DirectoryConnection::WithNodeInfoDeprecated(
fit::callback<zx_status_t(fuchsia_io::wire::NodeInfoDeprecated)> handler) const {
return handler(fuchsia_io::wire::NodeInfoDeprecated::WithDirectory({}));
}
} // namespace fs::internal