| // 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 "remote_v2.h" |
| |
| #include <fidl/fuchsia.device/cpp/wire.h> |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zxio/cpp/vector.h> |
| #include <lib/zxio/null.h> |
| #include <lib/zxio/ops.h> |
| #include <zircon/syscalls.h> |
| |
| #include "common_utils.h" |
| #include "dirent_iterator.h" |
| |
| namespace fdevice = fuchsia_device; |
| namespace fio = fuchsia_io; |
| |
| namespace { |
| |
| zxio_node_attributes_t ToZxioNodeAttributes(const fio::wire::NodeAttributes2& attr) { |
| const fio::wire::MutableNodeAttributes& mutable_attributes = attr.mutable_attributes; |
| const fio::wire::ImmutableNodeAttributes& immutable_attributes = attr.immutable_attributes; |
| |
| zxio_node_attributes_t zxio_attr = {}; |
| if (immutable_attributes.has_protocols()) { |
| ZXIO_NODE_ATTR_SET(zxio_attr, protocols, ToZxioNodeProtocols(immutable_attributes.protocols())); |
| } |
| if (immutable_attributes.has_abilities()) { |
| ZXIO_NODE_ATTR_SET(zxio_attr, protocols, ToZxioAbilities(immutable_attributes.abilities())); |
| } |
| if (immutable_attributes.has_id()) { |
| ZXIO_NODE_ATTR_SET(zxio_attr, id, immutable_attributes.id()); |
| } |
| if (immutable_attributes.has_content_size()) { |
| ZXIO_NODE_ATTR_SET(zxio_attr, content_size, immutable_attributes.content_size()); |
| } |
| if (immutable_attributes.has_storage_size()) { |
| ZXIO_NODE_ATTR_SET(zxio_attr, storage_size, immutable_attributes.storage_size()); |
| } |
| if (immutable_attributes.has_link_count()) { |
| ZXIO_NODE_ATTR_SET(zxio_attr, link_count, immutable_attributes.link_count()); |
| } |
| if (mutable_attributes.has_creation_time()) { |
| ZXIO_NODE_ATTR_SET(zxio_attr, creation_time, mutable_attributes.creation_time()); |
| } |
| if (mutable_attributes.has_modification_time()) { |
| ZXIO_NODE_ATTR_SET(zxio_attr, modification_time, mutable_attributes.modification_time()); |
| } |
| return zxio_attr; |
| } |
| |
| fio::wire::NodeAttributes2 ToIo2NodeAttributes(fidl::AnyArena& allocator, |
| const zxio_node_attributes_t& attr) { |
| fio::wire::MutableNodeAttributes mutable_attributes(allocator); |
| fio::wire::ImmutableNodeAttributes immutable_attributes(allocator); |
| if (attr.has.protocols) { |
| immutable_attributes.set_protocols(allocator, ToIo2NodeProtocols(attr.protocols)); |
| } |
| if (attr.has.abilities) { |
| immutable_attributes.set_abilities(allocator, ToIo2Abilities(attr.abilities)); |
| } |
| if (attr.has.id) { |
| immutable_attributes.set_id(allocator, attr.id); |
| } |
| if (attr.has.content_size) { |
| immutable_attributes.set_content_size(allocator, attr.content_size); |
| } |
| if (attr.has.storage_size) { |
| immutable_attributes.set_storage_size(allocator, attr.storage_size); |
| } |
| if (attr.has.link_count) { |
| immutable_attributes.set_link_count(allocator, attr.link_count); |
| } |
| if (attr.has.creation_time) { |
| mutable_attributes.set_creation_time(allocator, attr.creation_time); |
| } |
| if (attr.has.modification_time) { |
| mutable_attributes.set_modification_time(allocator, attr.modification_time); |
| } |
| return { |
| .mutable_attributes = mutable_attributes, |
| .immutable_attributes = immutable_attributes, |
| }; |
| } |
| |
| // These functions are named with "v2" to avoid mixing up with fuchsia.io v1 |
| // backend during grepping. |
| |
| zx_status_t zxio_remote_v2_close(zxio_t* io) { |
| RemoteV2 rio(io); |
| zx_status_t status = [&]() { |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Node2>(rio.control()))->Close(); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto* res = result.Unwrap(); |
| if (res->is_error()) { |
| return res->error_value(); |
| } else if (res->is_ok()) { |
| return ZX_OK; |
| } |
| return rio.control()->wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), nullptr); |
| }(); |
| rio.Close(); |
| return status; |
| } |
| |
| zx_status_t zxio_remote_v2_release(zxio_t* io, zx_handle_t* out_handle) { |
| RemoteV2 rio(io); |
| *out_handle = rio.Release().release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_v2_reopen(zxio_t* io, zxio_reopen_flags_t flags, zx_handle_t* out_handle) { |
| RemoteV2 rio(io); |
| zx::status ends = fidl::CreateEndpoints<fio::Node2>(); |
| if (ends.is_error()) { |
| return ends.status_value(); |
| } |
| fio::wire::ConnectionOptions options; |
| if (flags & ZXIO_REOPEN_DESCRIBE) { |
| options.flags() |= fio::wire::ConnectionFlags::kGetConnectionInfo; |
| } |
| const fidl::WireResult result = fidl::WireCall(fidl::UnownedClientEnd<fio::Node2>(rio.control())) |
| ->Reopen(options, ends->server.TakeChannel()); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| *out_handle = ends->client.TakeChannel().release(); |
| return ZX_OK; |
| } |
| |
| void zxio_remote_v2_wait_begin(zxio_t* io, zxio_signals_t zxio_signals, zx_handle_t* out_handle, |
| zx_signals_t* out_zx_signals) { |
| RemoteV2 rio(io); |
| *out_handle = rio.observer()->get(); |
| using DeviceSignal = fdevice::wire::DeviceSignal; |
| auto device_signal_part = DeviceSignal(); |
| if (zxio_signals & ZXIO_SIGNAL_READABLE) { |
| device_signal_part |= DeviceSignal::kReadable; |
| } |
| if (zxio_signals & ZXIO_SIGNAL_OUT_OF_BAND) { |
| device_signal_part |= DeviceSignal::kOob; |
| } |
| if (zxio_signals & ZXIO_SIGNAL_WRITABLE) { |
| device_signal_part |= DeviceSignal::kWritable; |
| } |
| if (zxio_signals & ZXIO_SIGNAL_ERROR) { |
| device_signal_part |= DeviceSignal::kError; |
| } |
| if (zxio_signals & ZXIO_SIGNAL_PEER_CLOSED) { |
| device_signal_part |= DeviceSignal::kHangup; |
| } |
| // static_cast is a-okay, because |DeviceSignal| values are defined |
| // using Zircon ZX_USER_* signals. |
| auto zx_signals = static_cast<zx_signals_t>(device_signal_part); |
| if (zxio_signals & ZXIO_SIGNAL_READ_DISABLED) { |
| zx_signals |= ZX_CHANNEL_PEER_CLOSED; |
| } |
| *out_zx_signals = zx_signals; |
| } |
| |
| void zxio_remote_v2_wait_end(zxio_t* io, zx_signals_t zx_signals, |
| zxio_signals_t* out_zxio_signals) { |
| zxio_signals_t zxio_signals = ZXIO_SIGNAL_NONE; |
| using DeviceSignal = fdevice::wire::DeviceSignal; |
| // static_cast is a-okay, because |DeviceSignal| values are defined |
| // using Zircon ZX_USER_* signals. |
| auto device_signal_part = DeviceSignal::TruncatingUnknown(zx_signals); |
| if (device_signal_part & DeviceSignal::kReadable) { |
| zxio_signals |= ZXIO_SIGNAL_READABLE; |
| } |
| if (device_signal_part & DeviceSignal::kOob) { |
| zxio_signals |= ZXIO_SIGNAL_OUT_OF_BAND; |
| } |
| if (device_signal_part & DeviceSignal::kWritable) { |
| zxio_signals |= ZXIO_SIGNAL_WRITABLE; |
| } |
| if (device_signal_part & DeviceSignal::kError) { |
| zxio_signals |= ZXIO_SIGNAL_ERROR; |
| } |
| if (device_signal_part & DeviceSignal::kHangup) { |
| zxio_signals |= ZXIO_SIGNAL_PEER_CLOSED; |
| } |
| if (zx_signals & ZX_CHANNEL_PEER_CLOSED) { |
| zxio_signals |= ZXIO_SIGNAL_READ_DISABLED; |
| } |
| *out_zxio_signals = zxio_signals; |
| } |
| |
| zx_status_t zxio_remote_sync(zxio_t* io) { |
| RemoteV2 rio(io); |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Node2>(rio.control()))->Sync(); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto* res = result.Unwrap(); |
| if (res->is_error()) { |
| return res->error_value(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_v2_attr_get(zxio_t* io, zxio_node_attributes_t* out_attr) { |
| RemoteV2 rio(io); |
| const fidl::WireResult result = fidl::WireCall(fidl::UnownedClientEnd<fio::Node2>(rio.control())) |
| ->GetAttributes(fio::wire::NodeAttributesQuery::kMask); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| |
| const auto* res = result.Unwrap(); |
| if (res->is_error()) { |
| return res->error_value(); |
| } |
| const fio::wire::NodeAttributes2& attributes = *res->value(); |
| *out_attr = ToZxioNodeAttributes(attributes); |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_v2_attr_set(zxio_t* io, const zxio_node_attributes_t* attr) { |
| fidl::Arena<1024> allocator; |
| RemoteV2 rio(io); |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Node2>(rio.control())) |
| ->UpdateAttributes(ToIo2NodeAttributes(allocator, *attr).mutable_attributes); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto* res = result.Unwrap(); |
| if (res->is_error()) { |
| return res->error_value(); |
| } |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| void RemoteV2::Close() { |
| Release().reset(); |
| if (rio_->observer != ZX_HANDLE_INVALID) { |
| zx_handle_close(rio_->observer); |
| rio_->observer = ZX_HANDLE_INVALID; |
| } |
| if (rio_->stream != ZX_HANDLE_INVALID) { |
| zx_handle_close(rio_->stream); |
| rio_->stream = ZX_HANDLE_INVALID; |
| } |
| } |
| |
| zx::channel RemoteV2::Release() { |
| zx::channel control(rio_->control); |
| rio_->control = ZX_HANDLE_INVALID; |
| return control; |
| } |
| |
| static constexpr zxio_ops_t zxio_remote_v2_ops = []() { |
| zxio_ops_t ops = zxio_default_ops; |
| ops.close = zxio_remote_v2_close; |
| ops.release = zxio_remote_v2_release; |
| ops.reopen = zxio_remote_v2_reopen; |
| ops.wait_begin = zxio_remote_v2_wait_begin; |
| ops.wait_end = zxio_remote_v2_wait_end; |
| ops.sync = zxio_remote_sync; |
| ops.attr_get = zxio_remote_v2_attr_get; |
| ops.attr_set = zxio_remote_v2_attr_set; |
| return ops; |
| }(); |
| |
| zx_status_t zxio_remote_v2_init(zxio_storage_t* storage, zx_handle_t control, |
| zx_handle_t observer) { |
| auto remote = reinterpret_cast<zxio_remote_v2_t*>(storage); |
| zxio_init(&remote->io, &zxio_remote_v2_ops); |
| remote->control = control; |
| remote->observer = observer; |
| remote->stream = ZX_HANDLE_INVALID; |
| return ZX_OK; |
| } |
| |
| static constexpr zxio_ops_t zxio_dir_v2_ops = []() { |
| zxio_ops_t ops = zxio_default_ops; |
| ops.close = zxio_remote_v2_close; |
| ops.release = zxio_remote_v2_release; |
| ops.reopen = zxio_remote_v2_reopen; |
| ops.sync = zxio_remote_sync; |
| ops.attr_get = zxio_remote_v2_attr_get; |
| ops.attr_set = zxio_remote_v2_attr_set; |
| ops.dirent_iterator_init = zxio_remote_v2_dirent_iterator_init; |
| ops.dirent_iterator_next = zxio_remote_v2_dirent_iterator_next; |
| ops.dirent_iterator_destroy = zxio_remote_v2_dirent_iterator_destroy; |
| return ops; |
| }(); |
| |
| zx_status_t zxio_dir_v2_init(zxio_storage_t* storage, zx_handle_t control) { |
| auto remote = reinterpret_cast<zxio_remote_v2_t*>(storage); |
| zxio_init(&remote->io, &zxio_dir_v2_ops); |
| remote->control = control; |
| remote->observer = ZX_HANDLE_INVALID; |
| remote->stream = ZX_HANDLE_INVALID; |
| return ZX_OK; |
| } |
| |
| namespace { |
| |
| void zxio_file_v2_wait_begin(zxio_t* io, zxio_signals_t zxio_signals, zx_handle_t* out_handle, |
| zx_signals_t* out_zx_signals) { |
| using fio::wire::FileSignal; |
| RemoteV2 rio(io); |
| *out_handle = rio.observer()->get(); |
| auto file_signal_part = FileSignal(); |
| if (zxio_signals & ZXIO_SIGNAL_READABLE) { |
| file_signal_part |= FileSignal::kReadable; |
| } |
| if (zxio_signals & ZXIO_SIGNAL_WRITABLE) { |
| file_signal_part |= FileSignal::kWritable; |
| } |
| auto zx_signals = static_cast<zx_signals_t>(file_signal_part); |
| *out_zx_signals = zx_signals; |
| } |
| |
| void zxio_file_v2_wait_end(zxio_t* io, zx_signals_t zx_signals, zxio_signals_t* out_zxio_signals) { |
| using fio::wire::FileSignal; |
| zxio_signals_t zxio_signals = ZXIO_SIGNAL_NONE; |
| auto file_signal_part = FileSignal::TruncatingUnknown(zx_signals); |
| if (file_signal_part & FileSignal::kReadable) { |
| zxio_signals |= ZXIO_SIGNAL_READABLE; |
| } |
| if (file_signal_part & FileSignal::kWritable) { |
| zxio_signals |= ZXIO_SIGNAL_WRITABLE; |
| } |
| *out_zxio_signals = zxio_signals; |
| } |
| |
| template <typename F> |
| static zx_status_t zxio_remote_do_vector(const RemoteV2& rio, const zx_iovec_t* vector, |
| size_t vector_count, zxio_flags_t flags, |
| size_t* out_actual, F fn) { |
| return zxio_do_vector(vector, vector_count, out_actual, |
| [&](void* data, size_t capacity, size_t* out_actual) { |
| auto buffer = static_cast<uint8_t*>(data); |
| size_t total = 0; |
| while (capacity > 0) { |
| size_t chunk = std::min(capacity, fio::wire::kMaxTransferSize); |
| size_t actual; |
| zx_status_t status = fn(rio.control(), buffer, chunk, &actual); |
| if (status != ZX_OK) { |
| return status; |
| } |
| total += actual; |
| if (actual != chunk) { |
| break; |
| } |
| buffer += actual; |
| capacity -= actual; |
| } |
| *out_actual = total; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t zxio_remote_v2_readv(zxio_t* io, const zx_iovec_t* vector, size_t vector_count, |
| zxio_flags_t flags, size_t* out_actual) { |
| if (flags) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| RemoteV2 rio(io); |
| if (rio.stream()->is_valid()) { |
| return rio.stream()->readv(0, vector, vector_count, out_actual); |
| } |
| |
| return zxio_remote_do_vector( |
| rio, vector, vector_count, flags, out_actual, |
| [](zx::unowned_channel control, uint8_t* buffer, size_t capacity, size_t* out_actual) { |
| // Explicitly allocating message buffers to avoid heap allocation. |
| fidl::SyncClientBuffer<fio::File2::Read> fidl_buffer; |
| const fidl::WireUnownedResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File2>(control)) |
| .buffer(fidl_buffer.view()) |
| ->Read(capacity); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto* res = result.Unwrap(); |
| if (res->is_error()) { |
| return res->error_value(); |
| } |
| const fidl::VectorView data = res->value()->data; |
| const size_t actual = data.count(); |
| if (actual > capacity) { |
| return ZX_ERR_IO; |
| } |
| memcpy(buffer, data.begin(), actual); |
| *out_actual = actual; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t zxio_remote_v2_readv_at(zxio_t* io, zx_off_t offset, const zx_iovec_t* vector, |
| size_t vector_count, zxio_flags_t flags, size_t* out_actual) { |
| if (flags) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| RemoteV2 rio(io); |
| if (rio.stream()->is_valid()) { |
| return rio.stream()->readv_at(0, offset, vector, vector_count, out_actual); |
| } |
| |
| return zxio_remote_do_vector( |
| rio, vector, vector_count, flags, out_actual, |
| [&offset](zx::unowned_channel control, uint8_t* buffer, size_t capacity, size_t* out_actual) { |
| fidl::SyncClientBuffer<fio::File2::ReadAt> fidl_buffer; |
| const fidl::WireUnownedResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File2>(control)) |
| .buffer(fidl_buffer.view()) |
| ->ReadAt(capacity, offset); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto* res = result.Unwrap(); |
| if (res->is_error()) { |
| return res->error_value(); |
| } |
| const fidl::VectorView data = res->value()->data; |
| const size_t actual = data.count(); |
| if (actual > capacity) { |
| return ZX_ERR_IO; |
| } |
| offset += actual; |
| memcpy(buffer, data.begin(), actual); |
| *out_actual = actual; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t zxio_remote_v2_writev(zxio_t* io, const zx_iovec_t* vector, size_t vector_count, |
| zxio_flags_t flags, size_t* out_actual) { |
| if (flags) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| RemoteV2 rio(io); |
| if (rio.stream()->is_valid()) { |
| return rio.stream()->writev(0, vector, vector_count, out_actual); |
| } |
| |
| return zxio_remote_do_vector( |
| rio, vector, vector_count, flags, out_actual, |
| [](zx::unowned_channel control, uint8_t* buffer, size_t capacity, size_t* out_actual) { |
| // Explicitly allocating message buffers to avoid heap allocation. |
| fidl::SyncClientBuffer<fio::File2::Write> fidl_buffer; |
| const fidl::WireUnownedResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File2>(control)) |
| .buffer(fidl_buffer.view()) |
| ->Write(fidl::VectorView<uint8_t>::FromExternal(buffer, capacity)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto* res = result.Unwrap(); |
| if (res->is_error()) { |
| return res->error_value(); |
| } |
| const size_t actual = res->value()->actual_count; |
| if (actual > capacity) { |
| return ZX_ERR_IO; |
| } |
| *out_actual = actual; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t zxio_remote_v2_writev_at(zxio_t* io, zx_off_t offset, const zx_iovec_t* vector, |
| size_t vector_count, zxio_flags_t flags, size_t* out_actual) { |
| if (flags) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| RemoteV2 rio(io); |
| if (rio.stream()->is_valid()) { |
| return rio.stream()->writev_at(0, offset, vector, vector_count, out_actual); |
| } |
| |
| return zxio_remote_do_vector( |
| rio, vector, vector_count, flags, out_actual, |
| [&offset](zx::unowned_channel control, uint8_t* buffer, size_t capacity, size_t* out_actual) { |
| // Explicitly allocating message buffers to avoid heap allocation. |
| fidl::SyncClientBuffer<fio::File2::WriteAt> fidl_buffer; |
| const fidl::WireUnownedResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File2>(control)) |
| .buffer(fidl_buffer.view()) |
| ->WriteAt(fidl::VectorView<uint8_t>::FromExternal(buffer, capacity), offset); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto* res = result.Unwrap(); |
| if (res->is_error()) { |
| return res->error_value(); |
| } |
| const size_t actual = res->value()->actual_count; |
| if (actual > capacity) { |
| return ZX_ERR_IO; |
| } |
| offset += actual; |
| *out_actual = actual; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t zxio_remote_v2_seek(zxio_t* io, zxio_seek_origin_t start, int64_t offset, |
| size_t* out_offset) { |
| RemoteV2 rio(io); |
| if (rio.stream()->is_valid()) { |
| return rio.stream()->seek(start, offset, out_offset); |
| } |
| |
| const fidl::WireResult result = fidl::WireCall(fidl::UnownedClientEnd<fio::File2>(rio.control())) |
| ->Seek(static_cast<fio::wire::SeekOrigin>(start), offset); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto* res = result.Unwrap(); |
| if (res->is_error()) { |
| return res->error_value(); |
| } |
| *out_offset = res->value()->offset_from_start; |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| static constexpr zxio_ops_t zxio_file_v2_ops = []() { |
| zxio_ops_t ops = zxio_default_ops; |
| ops.close = zxio_remote_v2_close; |
| ops.release = zxio_remote_v2_release; |
| ops.reopen = zxio_remote_v2_reopen; |
| ops.wait_begin = zxio_file_v2_wait_begin; |
| ops.wait_end = zxio_file_v2_wait_end; |
| ops.sync = zxio_remote_sync; |
| ops.attr_get = zxio_remote_v2_attr_get; |
| ops.attr_set = zxio_remote_v2_attr_set; |
| ops.readv = zxio_remote_v2_readv; |
| ops.readv_at = zxio_remote_v2_readv_at; |
| ops.writev = zxio_remote_v2_writev; |
| ops.writev_at = zxio_remote_v2_writev_at; |
| ops.seek = zxio_remote_v2_seek; |
| return ops; |
| }(); |
| |
| zx_status_t zxio_file_v2_init(zxio_storage_t* storage, zx_handle_t control, zx_handle_t observer, |
| zx_handle_t stream) { |
| auto remote = reinterpret_cast<zxio_remote_v2_t*>(storage); |
| zxio_init(&remote->io, &zxio_file_v2_ops); |
| remote->control = control; |
| remote->observer = observer; |
| remote->stream = stream; |
| return ZX_OK; |
| } |