| // Copyright 2018 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 <dirent.h> |
| #include <fcntl.h> |
| #include <fidl/fuchsia.device/cpp/wire.h> |
| #include <fidl/fuchsia.hardware.pty/cpp/wire.h> |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <lib/stdcompat/span.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zxio/cpp/vector.h> |
| #include <lib/zxio/null.h> |
| #include <lib/zxio/ops.h> |
| #include <lib/zxio/posix_mode.h> |
| #include <lib/zxio/types.h> |
| #include <sys/stat.h> |
| #include <zircon/compiler.h> |
| #include <zircon/fidl.h> |
| #include <zircon/syscalls.h> |
| |
| #include "private.h" |
| |
| namespace fdevice = fuchsia_device; |
| namespace fio = fuchsia_io; |
| |
| namespace { |
| |
| // Implementation of |zxio_dirent_iterator_t| for |fuchsia.io| v1. |
| class DirentIteratorImpl { |
| public: |
| explicit DirentIteratorImpl(zxio_t* io) : io_(reinterpret_cast<zxio_remote_t*>(io)) { |
| static_assert(offsetof(DirentIteratorImpl, io_) == 0, |
| "zxio_dirent_iterator_t requires first field of implementation to be zxio_t"); |
| } |
| |
| ~DirentIteratorImpl() { |
| // TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it. |
| (void)fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(io_->control))->Rewind(); |
| } |
| |
| zx_status_t Next(zxio_dirent_t* inout_entry) { |
| if (index_ >= count_) { |
| zx_status_t status = RemoteReadDirents(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (count_ == 0) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| index_ = 0; |
| } |
| |
| // The format of the packed dirent structure, taken from io.fidl. |
| struct dirent { |
| // Describes the inode of the entry. |
| uint64_t ino; |
| // Describes the length of the dirent name in bytes. |
| uint8_t size; |
| // Describes the type of the entry. Aligned with the |
| // POSIX d_type values. Use `DIRENT_TYPE_*` constants. |
| uint8_t type; |
| // Unterminated name of entry. |
| char name[0]; |
| } __PACKED; |
| |
| auto packed_entry = reinterpret_cast<const dirent*>(&data_[index_]); |
| |
| // Check if we can read the entry size. |
| if (index_ + sizeof(dirent) > count_) { |
| // Should not happen |
| return ZX_ERR_INTERNAL; |
| } |
| |
| size_t packed_entry_size = sizeof(dirent) + packed_entry->size; |
| |
| // Check if we can read the whole entry. |
| if (index_ + packed_entry_size > count_) { |
| // Should not happen |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // Check that the name length is within bounds. |
| if (packed_entry->size > fio::wire::kMaxFilename) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| index_ += packed_entry_size; |
| |
| ZXIO_DIRENT_SET(*inout_entry, protocols, DTypeToProtocols(packed_entry->type)); |
| ZXIO_DIRENT_SET(*inout_entry, id, packed_entry->ino); |
| inout_entry->name_length = packed_entry->size; |
| if (inout_entry->name != nullptr) { |
| memcpy(inout_entry->name, packed_entry->name, packed_entry->size); |
| } |
| |
| return ZX_OK; |
| } |
| |
| private: |
| zx_status_t RemoteReadDirents() { |
| fidl::BufferSpan fidl_buffer(buffer_, sizeof(buffer_)); |
| const fidl::WireUnownedResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(io_->control)) |
| .buffer(fidl_buffer) |
| ->ReadDirents(kBufferSize); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (zx_status_t status = response.s; status != ZX_OK) { |
| return status; |
| } |
| const fidl::VectorView dirents = response.dirents; |
| if (dirents.count() > kBufferSize) { |
| return ZX_ERR_IO; |
| } |
| data_ = dirents.data(); |
| count_ = dirents.count(); |
| return ZX_OK; |
| } |
| |
| static zxio_node_protocols_t DTypeToProtocols(uint8_t type) { |
| switch (type) { |
| case DT_BLK: |
| return ZXIO_NODE_PROTOCOL_DEVICE; |
| case DT_CHR: |
| return ZXIO_NODE_PROTOCOL_TTY; |
| case DT_DIR: |
| return ZXIO_NODE_PROTOCOL_DIRECTORY; |
| case DT_FIFO: |
| return ZXIO_NODE_PROTOCOL_PIPE; |
| case DT_LNK: |
| // Not supported. |
| return ZXIO_NODE_PROTOCOL_NONE; |
| case DT_REG: |
| return ZXIO_NODE_PROTOCOL_FILE; |
| case DT_SOCK: |
| // TODO(jamesr): Switch to Directory2/Enumerate and remove this code. |
| // |
| // This points to the POSIX dirent data structure used by the io1 |
| // directory enumeration protocol not being sufficient to fully describe |
| // the protocol(s) understood on a node. The io2 enumeration API lists |
| // the protocols understood by each node. |
| return ZXIO_NODE_PROTOCOL_STREAM_SOCKET; |
| default: |
| return ZXIO_NODE_PROTOCOL_NONE; |
| } |
| } |
| |
| // The maximum buffer size that is supported by |fuchsia.io/Directory.ReadDirents|. |
| static constexpr size_t kBufferSize = fio::wire::kMaxBuf; |
| |
| zxio_remote_t* io_; |
| |
| // Issuing a FIDL call requires storage for both the request (16 bytes) and the largest possible |
| // response message (8192 bytes of payload). |
| // TODO(https://fxbug.dev/85843): Once overlapping request and response is allowed, reduce |
| // this allocation to a single channel message size. |
| FIDL_ALIGNDECL uint8_t |
| buffer_[fidl::SyncClientMethodBufferSizeInChannel<fio::Directory::ReadDirents>()]; |
| const uint8_t* data_ = nullptr; |
| uint64_t count_ = 0; |
| uint64_t index_ = 0; |
| }; |
| |
| static_assert(sizeof(DirentIteratorImpl) <= sizeof(zxio_dirent_iterator_t), |
| "DirentIteratorImpl should fit within a zxio_dirent_iterator_t"); |
| |
| // C++ wrapper around zxio_remote_t. |
| class Remote { |
| public: |
| explicit Remote(zxio_t* io) : rio_(reinterpret_cast<zxio_remote_t*>(io)) {} |
| |
| [[nodiscard]] zx::unowned_channel control() const { return zx::unowned_channel(rio_->control); } |
| |
| [[nodiscard]] zx::unowned_handle event() const { return zx::unowned_handle(rio_->event); } |
| |
| [[nodiscard]] zx::unowned_stream stream() const { return zx::unowned_stream(rio_->stream); } |
| |
| zx::channel Release() { |
| zx::channel control(rio_->control); |
| rio_->control = ZX_HANDLE_INVALID; |
| return control; |
| } |
| |
| void Close() { |
| Release().reset(); |
| if (rio_->event != ZX_HANDLE_INVALID) { |
| zx_handle_close(rio_->event); |
| rio_->event = ZX_HANDLE_INVALID; |
| } |
| if (rio_->stream != ZX_HANDLE_INVALID) { |
| zx_handle_close(rio_->stream); |
| rio_->stream = ZX_HANDLE_INVALID; |
| } |
| } |
| |
| zx::status<bool> IsATty() { |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Node>(control()))->Describe(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(result.value().info.is_tty()); |
| } |
| |
| private: |
| zxio_remote_t* rio_; |
| }; |
| |
| zxio_node_protocols_t ToZxioNodeProtocols(uint32_t mode) { |
| switch (mode & (S_IFMT | fuchsia_io::wire::kModeTypeService)) { |
| case S_IFDIR: |
| return ZXIO_NODE_PROTOCOL_DIRECTORY; |
| case S_IFCHR: |
| return ZXIO_NODE_PROTOCOL_TTY; |
| case S_IFBLK: |
| return ZXIO_NODE_PROTOCOL_DEVICE; |
| case S_IFREG: |
| return ZXIO_NODE_PROTOCOL_FILE; |
| case S_IFIFO: |
| return ZXIO_NODE_PROTOCOL_PIPE; |
| // fuchsia::io has mode type service which breaks stat. |
| // TODO(fxbug.dev/52930): return ZXIO_NODE_PROTOCOL_CONNECTOR instead. |
| case fuchsia_io::wire::kModeTypeService: |
| return ZXIO_NODE_PROTOCOL_FILE; |
| case S_IFLNK: |
| // Symbolic links are not supported on Fuchsia. |
| // A reasonable fallback is to keep the protocols unchanged, |
| // i.e. same as getting a protocol we do not understand. |
| return ZXIO_NODE_PROTOCOL_NONE; |
| case S_IFSOCK: |
| // TODO(jamesr): Could also be datagram or raw. How important is this? |
| return ZXIO_NODE_PROTOCOL_STREAM_SOCKET; |
| default: |
| return ZXIO_NODE_PROTOCOL_NONE; |
| } |
| } |
| |
| uint32_t ToIo1ModeFileType(zxio_node_protocols_t protocols) { |
| // The "file type" portion of mode only allow one bit, so we find |
| // the best approximation given some set of |protocols|, tie-breaking |
| // in the following precedence. |
| if (protocols & ZXIO_NODE_PROTOCOL_DIRECTORY) { |
| return S_IFDIR; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_FILE) { |
| return S_IFREG; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_MEMORY) { |
| return S_IFREG; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_PIPE) { |
| return S_IFIFO; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_STREAM_SOCKET) { |
| return S_IFSOCK; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_SYNCHRONOUS_DATAGRAM_SOCKET) { |
| return S_IFSOCK; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_DATAGRAM_SOCKET) { |
| return S_IFSOCK; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_RAW_SOCKET) { |
| return S_IFSOCK; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_DEVICE) { |
| return S_IFBLK; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_TTY) { |
| return S_IFCHR; |
| } |
| if (protocols & ZXIO_NODE_PROTOCOL_CONNECTOR) { |
| // There is no good analogue for FIDL services in POSIX land... |
| // Returning "regular file" as a fallback. |
| return S_IFREG; |
| } |
| return 0; |
| } |
| |
| class ToZxioAbilitiesForFile { |
| public: |
| zxio_abilities_t operator()(uint32_t mode) { |
| zxio_abilities_t abilities = ZXIO_OPERATION_NONE; |
| if (mode & S_IRUSR) { |
| abilities |= ZXIO_OPERATION_READ_BYTES; |
| } |
| if (mode & S_IWUSR) { |
| abilities |= ZXIO_OPERATION_WRITE_BYTES; |
| } |
| if (mode & S_IXUSR) { |
| abilities |= ZXIO_OPERATION_EXECUTE; |
| } |
| // In addition, POSIX seems to allow changing file metadata |
| // regardless of read/write permissions, as long as we are the |
| // owner. |
| abilities |= ZXIO_OPERATION_GET_ATTRIBUTES; |
| abilities |= ZXIO_OPERATION_UPDATE_ATTRIBUTES; |
| return abilities; |
| } |
| }; |
| |
| class ToIo1ModePermissionsForFile { |
| public: |
| uint32_t operator()(zxio_abilities_t abilities) { |
| // Permissions are not applicable on Fuchsia. |
| // We could approximate them using the |abilities| of a node. |
| uint32_t permission_bits = 0; |
| if (abilities & ZXIO_OPERATION_READ_BYTES) { |
| permission_bits |= S_IRUSR; |
| } |
| if (abilities & ZXIO_OPERATION_WRITE_BYTES) { |
| permission_bits |= S_IWUSR; |
| } |
| if (abilities & ZXIO_OPERATION_EXECUTE) { |
| permission_bits |= S_IXUSR; |
| } |
| return permission_bits; |
| } |
| }; |
| |
| class ToZxioAbilitiesForDirectory { |
| public: |
| zxio_abilities_t operator()(uint32_t mode) { |
| zxio_abilities_t abilities = ZXIO_OPERATION_NONE; |
| if (mode & S_IRUSR) { |
| abilities |= ZXIO_OPERATION_ENUMERATE; |
| } |
| if (mode & S_IWUSR) { |
| abilities |= ZXIO_OPERATION_MODIFY_DIRECTORY; |
| } |
| if (mode & S_IXUSR) { |
| abilities |= ZXIO_OPERATION_TRAVERSE; |
| } |
| // In addition, POSIX seems to allow changing file metadata |
| // regardless of read/write permissions, as long as we are the |
| // owner. |
| abilities |= ZXIO_OPERATION_GET_ATTRIBUTES; |
| abilities |= ZXIO_OPERATION_UPDATE_ATTRIBUTES; |
| return abilities; |
| } |
| }; |
| |
| class ToIo1ModePermissionsForDirectory { |
| public: |
| uint32_t operator()(zxio_abilities_t abilities) { |
| // Permissions are not applicable on Fuchsia. |
| // We could approximate them using the |abilities| of a node. |
| uint32_t permission_bits = 0; |
| if (abilities & ZXIO_OPERATION_ENUMERATE) { |
| permission_bits |= S_IRUSR; |
| } |
| if (abilities & ZXIO_OPERATION_MODIFY_DIRECTORY) { |
| permission_bits |= S_IWUSR; |
| } |
| if (abilities & ZXIO_OPERATION_TRAVERSE) { |
| permission_bits |= S_IXUSR; |
| } |
| return permission_bits; |
| } |
| }; |
| |
| template <typename ToZxioAbilities> |
| zxio_node_attributes_t ToZxioNodeAttributes(fio::wire::NodeAttributes attr, |
| ToZxioAbilities to_zxio) { |
| zxio_node_attributes_t zxio_attr = {}; |
| ZXIO_NODE_ATTR_SET(zxio_attr, protocols, ToZxioNodeProtocols(attr.mode)); |
| ZXIO_NODE_ATTR_SET(zxio_attr, abilities, to_zxio(attr.mode)); |
| ZXIO_NODE_ATTR_SET(zxio_attr, id, attr.id); |
| ZXIO_NODE_ATTR_SET(zxio_attr, content_size, attr.content_size); |
| ZXIO_NODE_ATTR_SET(zxio_attr, storage_size, attr.storage_size); |
| ZXIO_NODE_ATTR_SET(zxio_attr, link_count, attr.link_count); |
| ZXIO_NODE_ATTR_SET(zxio_attr, creation_time, attr.creation_time); |
| ZXIO_NODE_ATTR_SET(zxio_attr, modification_time, attr.modification_time); |
| return zxio_attr; |
| } |
| |
| template <typename ToIo1ModePermissions> |
| fio::wire::NodeAttributes ToNodeAttributes(zxio_node_attributes_t attr, |
| ToIo1ModePermissions to_io1) { |
| return fio::wire::NodeAttributes{ |
| .mode = ToIo1ModeFileType(attr.protocols) | to_io1(attr.abilities), |
| .id = attr.has.id ? attr.id : fio::wire::kInoUnknown, |
| .content_size = attr.content_size, |
| .storage_size = attr.storage_size, |
| .link_count = attr.link_count, |
| .creation_time = attr.creation_time, |
| .modification_time = attr.modification_time, |
| }; |
| } |
| |
| // POSIX expects EBADF for access denied errors which comes from ZX_ERR_BAD_STATE; |
| // ZX_ERR_ACCESS_DENIED produces EACCES which should only be used for sockets. |
| zx_status_t map_status(zx_status_t status) { |
| switch (status) { |
| case ZX_ERR_ACCESS_DENIED: |
| return ZX_ERR_BAD_HANDLE; |
| } |
| return status; |
| } |
| |
| zx_status_t zxio_remote_close(zxio_t* io) { |
| Remote rio(io); |
| zx_status_t status = zxio_raw_remote_close(rio.control()); |
| rio.Close(); |
| return status; |
| } |
| |
| zx_status_t zxio_remote_release(zxio_t* io, zx_handle_t* out_handle) { |
| Remote rio(io); |
| *out_handle = rio.Release().release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_borrow(zxio_t* io, zx_handle_t* out_handle) { |
| Remote rio(io); |
| *out_handle = rio.control()->get(); |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_reopen(zxio_t* io, zxio_reopen_flags_t flags, zx_handle_t* out_handle) { |
| Remote rio(io); |
| return zxio_raw_remote_reopen(rio.control(), flags, out_handle); |
| } |
| |
| void zxio_remote_wait_begin(zxio_t* io, zxio_signals_t zxio_signals, zx_handle_t* out_handle, |
| zx_signals_t* out_zx_signals) { |
| Remote rio(io); |
| *out_handle = rio.event()->get(); |
| |
| zx_signals_t zx_signals = ZX_SIGNAL_NONE; |
| zx_signals |= [zxio_signals]() { |
| fdevice::wire::DeviceSignal signals; |
| if (zxio_signals & ZXIO_SIGNAL_READABLE) { |
| signals |= fdevice::wire::DeviceSignal::kReadable; |
| } |
| if (zxio_signals & ZXIO_SIGNAL_OUT_OF_BAND) { |
| signals |= fdevice::wire::DeviceSignal::kOob; |
| } |
| if (zxio_signals & ZXIO_SIGNAL_WRITABLE) { |
| signals |= fdevice::wire::DeviceSignal::kWritable; |
| } |
| if (zxio_signals & ZXIO_SIGNAL_ERROR) { |
| signals |= fdevice::wire::DeviceSignal::kError; |
| } |
| if (zxio_signals & ZXIO_SIGNAL_PEER_CLOSED) { |
| signals |= fdevice::wire::DeviceSignal::kHangup; |
| } |
| return static_cast<zx_signals_t>(signals); |
| }(); |
| if (zxio_signals & ZXIO_SIGNAL_READ_DISABLED) { |
| zx_signals |= ZX_CHANNEL_PEER_CLOSED; |
| } |
| *out_zx_signals = zx_signals; |
| } |
| |
| void zxio_remote_wait_end(zxio_t* io, zx_signals_t zx_signals, zxio_signals_t* out_zxio_signals) { |
| zxio_signals_t zxio_signals = ZXIO_SIGNAL_NONE; |
| [&zxio_signals, signals = fdevice::wire::DeviceSignal::TruncatingUnknown(zx_signals)]() { |
| if (signals & fdevice::wire::DeviceSignal::kReadable) { |
| zxio_signals |= ZXIO_SIGNAL_READABLE; |
| } |
| if (signals & fdevice::wire::DeviceSignal::kOob) { |
| zxio_signals |= ZXIO_SIGNAL_OUT_OF_BAND; |
| } |
| if (signals & fdevice::wire::DeviceSignal::kWritable) { |
| zxio_signals |= ZXIO_SIGNAL_WRITABLE; |
| } |
| if (signals & fdevice::wire::DeviceSignal::kError) { |
| zxio_signals |= ZXIO_SIGNAL_ERROR; |
| } |
| if (signals & fdevice::wire::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) { |
| Remote rio(io); |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Node>(rio.control()))->Sync(); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| return ZX_OK; |
| } |
| |
| template <typename ToZxioAbilities> |
| zx_status_t zxio_common_attr_get(zx::unowned_channel control, ToZxioAbilities to_zxio, |
| zxio_node_attributes_t* out_attr) { |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Node>(control))->GetAttr(); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (zx_status_t status = response.s; status != ZX_OK) { |
| return status; |
| } |
| *out_attr = ToZxioNodeAttributes(response.attributes, to_zxio); |
| return ZX_OK; |
| } |
| |
| template <typename ToIo1ModePermissions> |
| zx_status_t zxio_common_attr_set(zx::unowned_channel control, ToIo1ModePermissions to_io1, |
| const zxio_node_attributes_t* attr) { |
| fio::wire::NodeAttributeFlags flags; |
| zxio_node_attributes_t::zxio_node_attr_has_t remaining = attr->has; |
| if (attr->has.creation_time) { |
| flags |= fio::wire::NodeAttributeFlags::kCreationTime; |
| remaining.creation_time = false; |
| } |
| if (attr->has.modification_time) { |
| flags |= fio::wire::NodeAttributeFlags::kModificationTime; |
| remaining.modification_time = false; |
| } |
| zxio_node_attributes_t::zxio_node_attr_has_t all_absent = {}; |
| if (remaining != all_absent) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| const fidl::WireResult result = fidl::WireCall(fidl::UnownedClientEnd<fio::Node>(control)) |
| ->SetAttr(flags, ToNodeAttributes(*attr, to_io1)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| return response.s; |
| } |
| |
| zx_status_t zxio_remote_attr_get(zxio_t* io, zxio_node_attributes_t* out_attr) { |
| Remote rio(io); |
| return zxio_common_attr_get(rio.control(), ToZxioAbilitiesForFile(), out_attr); |
| } |
| |
| zx_status_t zxio_remote_attr_set(zxio_t* io, const zxio_node_attributes_t* attr) { |
| Remote rio(io); |
| return zxio_common_attr_set(rio.control(), ToIo1ModePermissionsForFile(), attr); |
| } |
| |
| zx_status_t zxio_common_advisory_lock(zx::unowned_channel control, advisory_lock_req* req) { |
| fuchsia_io::wire::AdvisoryLockType lock_type; |
| switch (req->type) { |
| case ADVISORY_LOCK_SHARED: |
| lock_type = fuchsia_io::wire::AdvisoryLockType::kRead; |
| break; |
| case ADVISORY_LOCK_EXCLUSIVE: |
| lock_type = fuchsia_io::wire::AdvisoryLockType::kWrite; |
| break; |
| case ADVISORY_LOCK_UNLOCK: |
| lock_type = fuchsia_io::wire::AdvisoryLockType::kUnlock; |
| break; |
| default: |
| return ZX_ERR_INTERNAL; |
| } |
| fidl::Arena allocator; |
| fuchsia_io::wire::AdvisoryLockRequest lock_req(allocator); |
| lock_req.set_type(lock_type); |
| lock_req.set_wait(req->wait); |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::AdvisoryLocking>(control))->AdvisoryLock(lock_req); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| return ZX_OK; |
| } |
| |
| template <typename F> |
| static zx_status_t zxio_remote_do_vector(const Remote& 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::kMaxBuf); |
| size_t actual; |
| zx_status_t status = fn(rio.control(), buffer, chunk, &actual); |
| if (status != ZX_OK) { |
| if (total > 0) { |
| break; |
| } |
| return status; |
| } |
| total += actual; |
| if (actual != chunk) { |
| break; |
| } |
| buffer += actual; |
| capacity -= actual; |
| } |
| *out_actual = total; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t zxio_remote_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; |
| } |
| |
| Remote rio(io); |
| if (rio.stream()->is_valid()) { |
| return map_status(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::File::Read> fidl_buffer; |
| const fidl::WireUnownedResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File>(control)) |
| .buffer(fidl_buffer.view()) |
| ->Read(capacity); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| const fidl::VectorView data = response.value()->data; |
| 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_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; |
| } |
| |
| Remote rio(io); |
| if (rio.stream()->is_valid()) { |
| return map_status(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::File::ReadAt> fidl_buffer; |
| const fidl::WireUnownedResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File>(control)) |
| .buffer(fidl_buffer.view()) |
| ->ReadAt(capacity, offset); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| const fidl::VectorView data = response.value()->data; |
| 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_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; |
| } |
| |
| Remote rio(io); |
| if (rio.stream()->is_valid()) { |
| return map_status(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::File::Write> fidl_buffer; |
| const fidl::WireUnownedResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File>(control)) |
| .buffer(fidl_buffer.view()) |
| ->Write(fidl::VectorView<uint8_t>::FromExternal(buffer, capacity)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| const size_t actual = response.value()->actual_count; |
| if (actual > capacity) { |
| return ZX_ERR_IO; |
| } |
| *out_actual = actual; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t zxio_remote_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; |
| } |
| |
| Remote rio(io); |
| if (rio.stream()->is_valid()) { |
| return map_status(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::File::WriteAt> fidl_buffer; |
| const fidl::WireUnownedResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File>(control)) |
| .buffer(fidl_buffer.view()) |
| ->WriteAt(fidl::VectorView<uint8_t>::FromExternal(buffer, capacity), offset); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| const size_t actual = response.value()->actual_count; |
| if (actual > capacity) { |
| return ZX_ERR_IO; |
| } |
| offset += actual; |
| *out_actual = actual; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t zxio_remote_seek(zxio_t* io, zxio_seek_origin_t start, int64_t offset, |
| size_t* out_offset) { |
| Remote rio(io); |
| if (rio.stream()->is_valid()) { |
| return rio.stream()->seek(start, offset, out_offset); |
| } |
| |
| const fidl::WireResult result = fidl::WireCall(fidl::UnownedClientEnd<fio::File>(rio.control())) |
| ->Seek(static_cast<fio::wire::SeekOrigin>(start), offset); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| *out_offset = response.value()->offset_from_start; |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_truncate(zxio_t* io, uint64_t length) { |
| Remote rio(io); |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File>(rio.control()))->Resize(length); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_flags_get(zxio_t* io, uint32_t* out_flags) { |
| Remote rio(io); |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File>(rio.control()))->GetFlags(); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (zx_status_t status = response.s; status != ZX_OK) { |
| return status; |
| } |
| *out_flags = static_cast<uint32_t>(response.flags); |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_flags_set(zxio_t* io, uint32_t flags) { |
| Remote rio(io); |
| const fidl::WireResult result = fidl::WireCall(fidl::UnownedClientEnd<fio::File>(rio.control())) |
| ->SetFlags(static_cast<fio::wire::OpenFlags>(flags)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| return response.s; |
| } |
| |
| zx_status_t zxio_remote_vmo_get(zxio_t* io, zxio_vmo_flags_t zxio_flags, zx_handle_t* out_vmo, |
| size_t* out_size) { |
| Remote rio(io); |
| fio::wire::VmoFlags flags; |
| if (zxio_flags & ZXIO_VMO_READ) { |
| flags |= fio::wire::VmoFlags::kRead; |
| } |
| if (zxio_flags & ZXIO_VMO_WRITE) { |
| flags |= fio::wire::VmoFlags::kWrite; |
| } |
| if (zxio_flags & ZXIO_VMO_EXECUTE) { |
| flags |= fio::wire::VmoFlags::kExecute; |
| } |
| if (zxio_flags & ZXIO_VMO_PRIVATE_CLONE) { |
| flags |= fio::wire::VmoFlags::kPrivateClone; |
| } |
| if (zxio_flags & ZXIO_VMO_SHARED_BUFFER) { |
| flags |= fio::wire::VmoFlags::kSharedBuffer; |
| } |
| fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File>(rio.control()))->GetBackingMemory(flags); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| zx::vmo& vmo = response.value()->vmo; |
| if (out_size) { |
| if (zx_status_t status = vmo.get_prop_content_size(out_size); status != ZX_OK) { |
| return status; |
| } |
| } |
| *out_vmo = vmo.release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_dir_open(zxio_t* io, uint32_t flags, uint32_t mode, const char* path, |
| size_t path_len, zxio_storage_t* storage) { |
| Remote rio(io); |
| zx::status node_ends = fidl::CreateEndpoints<fio::Node>(); |
| if (node_ends.is_error()) { |
| return node_ends.status_value(); |
| } |
| auto [node_client_end, node_server_end] = std::move(node_ends.value()); |
| |
| fidl::Status result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(rio.control())) |
| ->Open(static_cast<fio::wire::OpenFlags>(flags) | fuchsia_io::wire::OpenFlags::kDescribe, |
| mode, fidl::StringView::FromExternal(path, path_len), std::move(node_server_end)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| return zxio_create_with_on_open(node_client_end.TakeChannel().release(), storage); |
| } |
| |
| zx_status_t zxio_remote_open_async(zxio_t* io, uint32_t flags, uint32_t mode, const char* path, |
| size_t path_len, zx_handle_t request) { |
| Remote rio(io); |
| fidl::ServerEnd<fio::Node> node_request{zx::channel(request)}; |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(rio.control())) |
| ->Open(static_cast<fio::wire::OpenFlags>(flags), mode, |
| fidl::StringView::FromExternal(path, path_len), std::move(node_request)); |
| return result.status(); |
| } |
| |
| zx_status_t zxio_remote_add_inotify_filter(zxio_t* io, const char* path, size_t path_len, |
| uint32_t mask, uint32_t watch_descriptor, |
| zx_handle_t socket_handle) { |
| Remote rio(io); |
| fio::wire::InotifyWatchMask inotify_mask = static_cast<fio::wire::InotifyWatchMask>(mask); |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(rio.control())) |
| ->AddInotifyFilter(fidl::StringView::FromExternal(path, path_len), inotify_mask, |
| watch_descriptor, zx::socket(socket_handle)); |
| return result.status(); |
| } |
| |
| zx_status_t zxio_remote_unlink(zxio_t* io, const char* name, size_t name_len, int flags) { |
| Remote rio(io); |
| fidl::Arena allocator; |
| fuchsia_io::wire::UnlinkOptions options(allocator); |
| auto io_flags = fuchsia_io::wire::UnlinkFlags::kMustBeDirectory; |
| if (flags & AT_REMOVEDIR) { |
| options.set_flags(fidl::ObjectView<decltype(io_flags)>::FromExternal(&io_flags)); |
| } |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(rio.control())) |
| ->Unlink(fidl::StringView::FromExternal(name, name_len), options); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_token_get(zxio_t* io, zx_handle_t* out_token) { |
| Remote rio(io); |
| fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(rio.control()))->GetToken(); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| auto& response = result.value(); |
| if (zx_status_t status = response.s; status != ZX_OK) { |
| return status; |
| } |
| *out_token = response.token.release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_rename(zxio_t* io, const char* old_path, size_t old_path_len, |
| zx_handle_t dst_token, const char* new_path, size_t new_path_len) { |
| Remote rio(io); |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(rio.control())) |
| ->Rename(fidl::StringView::FromExternal(old_path, old_path_len), zx::event(dst_token), |
| fidl::StringView::FromExternal(new_path, new_path_len)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_link(zxio_t* io, const char* src_path, size_t src_path_len, |
| zx_handle_t dst_token, const char* dst_path, size_t dst_path_len) { |
| Remote rio(io); |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(rio.control())) |
| ->Link(fidl::StringView::FromExternal(src_path, src_path_len), zx::handle(dst_token), |
| fidl::StringView::FromExternal(dst_path, dst_path_len)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| return response.s; |
| } |
| |
| zx_status_t zxio_remote_dirent_iterator_init(zxio_t* directory, zxio_dirent_iterator_t* iterator) { |
| new (iterator) DirentIteratorImpl(directory); |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_dirent_iterator_next(zxio_t* io, zxio_dirent_iterator_t* iterator, |
| zxio_dirent_t* inout_entry) { |
| return reinterpret_cast<DirentIteratorImpl*>(iterator)->Next(inout_entry); |
| } |
| |
| void zxio_remote_dirent_iterator_destroy(zxio_t* io, zxio_dirent_iterator_t* iterator) { |
| reinterpret_cast<DirentIteratorImpl*>(iterator)->~DirentIteratorImpl(); |
| } |
| |
| zx_status_t zxio_remote_isatty(zxio_t* io, bool* tty) { |
| Remote rio(io); |
| zx::status result = rio.IsATty(); |
| if (result.is_error()) { |
| return result.status_value(); |
| } |
| *tty = *result; |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_get_window_size(zxio_t* io, uint32_t* width, uint32_t* height) { |
| Remote rio(io); |
| zx::status tty_result = rio.IsATty(); |
| if (tty_result.is_error()) { |
| return tty_result.status_value(); |
| } |
| if (!*tty_result) { |
| // Not a tty. |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| fidl::UnownedClientEnd<fuchsia_hardware_pty::Device> device(rio.control()); |
| if (!device.is_valid()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| const fidl::WireResult result = fidl::WireCall(device)->GetWindowSize(); |
| if (!result.ok()) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| const auto& response = result.value(); |
| if (response.status != ZX_OK) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| *width = response.size.width; |
| *height = response.size.height; |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_remote_set_window_size(zxio_t* io, uint32_t width, uint32_t height) { |
| Remote rio(io); |
| zx::status tty_result = rio.IsATty(); |
| if (tty_result.is_error()) { |
| return tty_result.status_value(); |
| } |
| if (!*tty_result) { |
| // Not a tty. |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| fidl::UnownedClientEnd<fuchsia_hardware_pty::Device> device(rio.control()); |
| if (!device.is_valid()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| fuchsia_hardware_pty::wire::WindowSize size = { |
| .width = width, |
| .height = height, |
| }; |
| |
| const fidl::WireResult result = fidl::WireCall(device)->SetWindowSize(size); |
| if (!result.ok()) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| const auto& response = result.value(); |
| if (response.status != ZX_OK) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| static constexpr zxio_ops_t zxio_remote_ops = []() { |
| zxio_ops_t ops = zxio_default_ops; |
| ops.close = zxio_remote_close; |
| ops.release = zxio_remote_release; |
| ops.borrow = zxio_remote_borrow; |
| ops.reopen = zxio_remote_reopen; |
| ops.wait_begin = zxio_remote_wait_begin; |
| ops.wait_end = zxio_remote_wait_end; |
| ops.sync = zxio_remote_sync; |
| ops.attr_get = zxio_remote_attr_get; |
| ops.attr_set = zxio_remote_attr_set; |
| ops.readv = zxio_remote_readv; |
| ops.readv_at = zxio_remote_readv_at; |
| ops.writev = zxio_remote_writev; |
| ops.writev_at = zxio_remote_writev_at; |
| ops.seek = zxio_remote_seek; |
| ops.truncate = zxio_remote_truncate; |
| ops.flags_get = zxio_remote_flags_get; |
| ops.flags_set = zxio_remote_flags_set; |
| ops.vmo_get = zxio_remote_vmo_get; |
| ops.open_async = zxio_remote_open_async; |
| ops.add_inotify_filter = zxio_remote_add_inotify_filter; |
| ops.unlink = zxio_remote_unlink; |
| ops.token_get = zxio_remote_token_get; |
| ops.rename = zxio_remote_rename; |
| ops.link = zxio_remote_link; |
| ops.dirent_iterator_init = zxio_remote_dirent_iterator_init; |
| ops.dirent_iterator_next = zxio_remote_dirent_iterator_next; |
| ops.dirent_iterator_destroy = zxio_remote_dirent_iterator_destroy; |
| ops.isatty = zxio_remote_isatty; |
| ops.get_window_size = zxio_remote_get_window_size; |
| ops.set_window_size = zxio_remote_set_window_size; |
| return ops; |
| }(); |
| |
| zx_status_t zxio_remote_init(zxio_storage_t* storage, zx_handle_t control, zx_handle_t event) { |
| auto remote = reinterpret_cast<zxio_remote_t*>(storage); |
| zxio_init(&remote->io, &zxio_remote_ops); |
| remote->control = control; |
| remote->event = event; |
| remote->stream = ZX_HANDLE_INVALID; |
| return ZX_OK; |
| } |
| |
| namespace { |
| |
| zx_status_t zxio_dir_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; |
| } |
| |
| return zxio_do_vector(vector, vector_count, out_actual, |
| [](void* buffer, size_t capacity, size_t* out_actual) { |
| if (capacity > 0) { |
| return ZX_ERR_WRONG_TYPE; |
| } |
| *out_actual = 0; |
| return ZX_OK; |
| }); |
| } |
| |
| zx_status_t zxio_dir_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) { |
| return zxio_dir_readv(io, vector, vector_count, flags, out_actual); |
| } |
| |
| zx_status_t zxio_dir_attr_get(zxio_t* io, zxio_node_attributes_t* out_attr) { |
| Remote rio(io); |
| return zxio_common_attr_get(rio.control(), ToZxioAbilitiesForDirectory(), out_attr); |
| } |
| |
| zx_status_t zxio_dir_attr_set(zxio_t* io, const zxio_node_attributes_t* attr) { |
| Remote rio(io); |
| return zxio_common_attr_set(rio.control(), ToIo1ModePermissionsForDirectory(), attr); |
| } |
| |
| zx_status_t zxio_remote_advisory_lock(zxio_t* io, advisory_lock_req* req) { |
| Remote rio(io); |
| return zxio_common_advisory_lock(rio.control(), req); |
| } |
| |
| zx_status_t zxio_remote_watch_directory(zxio_t* io, zxio_watch_directory_cb cb, zx_time_t deadline, |
| void* context) { |
| if (cb == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| Remote rio(io); |
| zx::status endpoints = fidl::CreateEndpoints<fio::DirectoryWatcher>(); |
| if (endpoints.is_error()) { |
| return endpoints.status_value(); |
| } |
| |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Directory>(rio.control())) |
| ->Watch(fio::wire::WatchMask::kMask, 0, std::move(endpoints->server)); |
| |
| if (zx_status_t status = result.status(); status != ZX_OK) { |
| return status; |
| } |
| if (zx_status_t status = result.value().s; status != ZX_OK) { |
| return status; |
| } |
| |
| for (;;) { |
| uint8_t bytes[fio::wire::kMaxBuf + 1]; // Extra byte for temporary null terminator. |
| uint32_t num_bytes; |
| zx_status_t status = endpoints->client.channel().read_etc(0, &bytes, nullptr, sizeof(bytes), 0, |
| &num_bytes, nullptr); |
| if (status != ZX_OK) { |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| status = endpoints->client.channel().wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, |
| zx::time(deadline), nullptr); |
| if (status != ZX_OK) { |
| return status; |
| } |
| continue; |
| } |
| return status; |
| } |
| |
| // Message Format: { OP, LEN, DATA[LEN] } |
| cpp20::span span(bytes, num_bytes); |
| auto it = span.begin(); |
| for (;;) { |
| if (std::distance(it, span.end()) < 2) { |
| break; |
| } |
| |
| fio::wire::WatchEvent wire_event = static_cast<fio::wire::WatchEvent>(*it++); |
| uint8_t len = *it++; |
| uint8_t* name = it; |
| |
| if (std::distance(it, span.end()) < len) { |
| break; |
| } |
| it += len; |
| |
| zxio_watch_directory_event_t event; |
| switch (wire_event) { |
| case fio::wire::WatchEvent::kAdded: |
| case fio::wire::WatchEvent::kExisting: |
| event = ZXIO_WATCH_EVENT_ADD_FILE; |
| break; |
| case fio::wire::WatchEvent::kRemoved: |
| event = ZXIO_WATCH_EVENT_REMOVE_FILE; |
| break; |
| case fio::wire::WatchEvent::kIdle: |
| event = ZXIO_WATCH_EVENT_WAITING; |
| break; |
| default: |
| // unsupported event |
| continue; |
| } |
| |
| // The callback expects a null-terminated string. |
| uint8_t tmp = *it; |
| *it = 0; |
| status = cb(event, reinterpret_cast<const char*>(name), context); |
| *it = tmp; |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| } |
| } |
| |
| } // namespace |
| |
| static constexpr zxio_ops_t zxio_dir_ops = []() { |
| zxio_ops_t ops = zxio_default_ops; |
| ops.close = zxio_remote_close; |
| ops.release = zxio_remote_release; |
| ops.borrow = zxio_remote_borrow; |
| ops.reopen = zxio_remote_reopen; |
| ops.sync = zxio_remote_sync; |
| ops.attr_get = zxio_dir_attr_get; |
| ops.attr_set = zxio_dir_attr_set; |
| // use specialized read functions that succeed for zero-sized reads. |
| ops.readv = zxio_dir_readv; |
| ops.readv_at = zxio_dir_readv_at; |
| ops.flags_get = zxio_remote_flags_get; |
| ops.flags_set = zxio_remote_flags_set; |
| ops.open = zxio_dir_open; |
| ops.open_async = zxio_remote_open_async; |
| ops.add_inotify_filter = zxio_remote_add_inotify_filter; |
| ops.unlink = zxio_remote_unlink; |
| ops.token_get = zxio_remote_token_get; |
| ops.rename = zxio_remote_rename; |
| ops.link = zxio_remote_link; |
| ops.dirent_iterator_init = zxio_remote_dirent_iterator_init; |
| ops.dirent_iterator_next = zxio_remote_dirent_iterator_next; |
| ops.dirent_iterator_destroy = zxio_remote_dirent_iterator_destroy; |
| ops.advisory_lock = zxio_remote_advisory_lock; |
| ops.watch_directory = zxio_remote_watch_directory; |
| return ops; |
| }(); |
| |
| zx_status_t zxio_dir_init(zxio_storage_t* storage, zx_handle_t control) { |
| auto remote = reinterpret_cast<zxio_remote_t*>(storage); |
| zxio_init(&remote->io, &zxio_dir_ops); |
| remote->control = control; |
| remote->event = ZX_HANDLE_INVALID; |
| remote->stream = ZX_HANDLE_INVALID; |
| return ZX_OK; |
| } |
| |
| namespace { |
| |
| void zxio_file_wait_begin(zxio_t* io, zxio_signals_t zxio_signals, zx_handle_t* out_handle, |
| zx_signals_t* out_zx_signals) { |
| Remote rio(io); |
| *out_handle = rio.event()->get(); |
| |
| zx_signals_t zx_signals = ZX_SIGNAL_NONE; |
| if (zxio_signals & ZXIO_SIGNAL_READABLE) { |
| zx_signals |= static_cast<zx_signals_t>(fio::wire::FileSignal::kReadable); |
| } |
| if (zxio_signals & ZXIO_SIGNAL_WRITABLE) { |
| zx_signals |= static_cast<zx_signals_t>(fio::wire::FileSignal::kWritable); |
| } |
| *out_zx_signals = zx_signals; |
| } |
| |
| void zxio_file_wait_end(zxio_t* io, zx_signals_t zx_signals, zxio_signals_t* out_zxio_signals) { |
| zxio_signals_t zxio_signals = ZXIO_SIGNAL_NONE; |
| if (zx_signals & static_cast<zx_signals_t>(fio::wire::FileSignal::kReadable)) { |
| zxio_signals |= ZXIO_SIGNAL_READABLE; |
| } |
| if (zx_signals & static_cast<zx_signals_t>(fio::wire::FileSignal::kWritable)) { |
| zxio_signals |= ZXIO_SIGNAL_WRITABLE; |
| } |
| *out_zxio_signals = zxio_signals; |
| } |
| |
| zx_status_t zxio_file_attr_get(zxio_t* io, zxio_node_attributes_t* out_attr) { |
| Remote rio(io); |
| return zxio_common_attr_get(rio.control(), ToZxioAbilitiesForFile(), out_attr); |
| } |
| |
| zx_status_t zxio_file_attr_set(zxio_t* io, const zxio_node_attributes_t* attr) { |
| Remote rio(io); |
| return zxio_common_attr_set(rio.control(), ToIo1ModePermissionsForFile(), attr); |
| } |
| |
| } // namespace |
| |
| static constexpr zxio_ops_t zxio_file_ops = []() { |
| zxio_ops_t ops = zxio_default_ops; |
| ops.close = zxio_remote_close; |
| ops.release = zxio_remote_release; |
| ops.borrow = zxio_remote_borrow; |
| ops.reopen = zxio_remote_reopen; |
| ops.wait_begin = zxio_file_wait_begin; |
| ops.wait_end = zxio_file_wait_end; |
| ops.sync = zxio_remote_sync; |
| ops.attr_get = zxio_file_attr_get; |
| ops.attr_set = zxio_file_attr_set; |
| ops.readv = zxio_remote_readv; |
| ops.readv_at = zxio_remote_readv_at; |
| ops.writev = zxio_remote_writev; |
| ops.writev_at = zxio_remote_writev_at; |
| ops.seek = zxio_remote_seek; |
| ops.truncate = zxio_remote_truncate; |
| ops.flags_get = zxio_remote_flags_get; |
| ops.flags_set = zxio_remote_flags_set; |
| ops.vmo_get = zxio_remote_vmo_get; |
| ops.advisory_lock = zxio_remote_advisory_lock; |
| return ops; |
| }(); |
| |
| zx_status_t zxio_file_init(zxio_storage_t* storage, zx_handle_t control, zx_handle_t event, |
| zx_handle_t stream) { |
| auto remote = reinterpret_cast<zxio_remote_t*>(storage); |
| zxio_init(&remote->io, &zxio_file_ops); |
| remote->control = control; |
| remote->event = event; |
| remote->stream = stream; |
| return ZX_OK; |
| } |
| |
| uint32_t zxio_node_protocols_to_posix_type(zxio_node_protocols_t protocols) { |
| return ToIo1ModeFileType(protocols); |
| } |
| |
| __EXPORT |
| uint32_t zxio_get_posix_mode(zxio_node_protocols_t protocols, zxio_abilities_t abilities) { |
| uint32_t mode = zxio_node_protocols_to_posix_type(protocols); |
| if (mode & S_IFDIR) { |
| mode |= ToIo1ModePermissionsForDirectory()(abilities); |
| } else { |
| mode |= ToIo1ModePermissionsForFile()(abilities); |
| } |
| return mode; |
| } |
| |
| zx_status_t zxio_raw_remote_close(zx::unowned_channel control) { |
| const fidl::WireResult result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::Node>(control))->Close(); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.is_error()) { |
| return response.error_value(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_raw_remote_reopen(zx::unowned_channel source, zxio_reopen_flags_t zxio_flags, |
| zx_handle_t* out_handle) { |
| zx::status ends = fidl::CreateEndpoints<fio::Node>(); |
| if (ends.is_error()) { |
| return ends.status_value(); |
| } |
| fio::wire::OpenFlags flags = fio::wire::OpenFlags::kCloneSameRights; |
| if (zxio_flags & ZXIO_REOPEN_DESCRIBE) { |
| flags |= fio::wire::OpenFlags::kDescribe; |
| } |
| const fidl::WireResult result = fidl::WireCall(fidl::UnownedClientEnd<fio::Node>(source)) |
| ->Clone(flags, std::move(ends->server)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| *out_handle = ends->client.TakeChannel().release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t zxio_raw_remote_attr_get(zx::unowned_channel control, |
| zxio_node_attributes_t* out_attr) { |
| return zxio_common_attr_get(std::move(control), ToZxioAbilitiesForFile(), out_attr); |
| } |
| |
| zx_status_t zxio_raw_remote_attr_set(zx::unowned_channel control, |
| const zxio_node_attributes_t* attr) { |
| return zxio_common_attr_set(std::move(control), ToIo1ModePermissionsForFile(), attr); |
| } |