blob: 123498015017131bd96bc426ab36b496480a5627 [file] [log] [blame]
// Copyright 2016 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 <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <threads.h>
#include <zircon/assert.h>
#include <zircon/device/device.h>
#include <zircon/device/ioctl.h>
#include <zircon/device/vfs.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <fdio/debug.h>
#include <fdio/io.fidl2.h>
#include <fdio/io.h>
#include <fdio/namespace.h>
#include <fdio/remoteio.h>
#include <fdio/util.h>
#include <fdio/vfs.h>
#include "private-fidl.h"
#include "private-remoteio.h"
#define ZXDEBUG 0
// POLL_MASK and POLL_SHIFT intend to convert the lower five POLL events into
// ZX_USER_SIGNALs and vice-versa. Other events need to be manually converted to
// an zx_signal_t, if they are desired.
#define POLL_SHIFT 24
#define POLL_MASK 0x1F
static_assert(ZX_USER_SIGNAL_0 == (1 << POLL_SHIFT), "");
static_assert((POLLIN << POLL_SHIFT) == DEVICE_SIGNAL_READABLE, "");
static_assert((POLLPRI << POLL_SHIFT) == DEVICE_SIGNAL_OOB, "");
static_assert((POLLOUT << POLL_SHIFT) == DEVICE_SIGNAL_WRITABLE, "");
static_assert((POLLERR << POLL_SHIFT) == DEVICE_SIGNAL_ERROR, "");
static_assert((POLLHUP << POLL_SHIFT) == DEVICE_SIGNAL_HANGUP, "");
static const char* _opnames[] = ZXRIO_OPNAMES;
const char* fdio_opname(uint32_t op) {
op = ZXRIO_OPNAME(op);
if (op < ZXRIO_NUM_OPS) {
return _opnames[op];
} else {
return "unknown";
}
}
static void discard_handles(zx_handle_t* handles, unsigned count) {
while (count-- > 0) {
zx_handle_close(*handles++);
}
}
zx_status_t zxrio_handle_rpc(zx_handle_t h, zxrio_msg_t* msg, zxrio_cb_t cb, void* cookie) {
zx_status_t r = zxrio_read_request(h, msg);
if (r != ZX_OK) {
return r;
}
bool is_close = (ZXRIO_OP(msg->op) == ZXRIO_CLOSE) ||
(ZXRIO_OP(msg->op) == ZXFIDL_CLOSE);
r = cb(msg, cookie);
switch (r) {
case ERR_DISPATCHER_INDIRECT:
// callback is handling the reply itself
// and took ownership of the reply handle
return ZX_OK;
case ERR_DISPATCHER_ASYNC:
// Same as the indirect case, but also identify that
// the callback will asynchronously re-trigger the
// dispatcher.
return ERR_DISPATCHER_ASYNC;
}
r = zxrio_write_response(h, r, msg);
if (is_close) {
// signals to not perform a close callback
return ERR_DISPATCHER_DONE;
} else {
return r;
}
}
zx_status_t zxrio_handle_close(zxrio_cb_t cb, void* cookie) {
zxrio_msg_t msg;
// remote side was closed;
#ifdef ZXRIO_FIDL
ObjectCloseRequest* request = (ObjectCloseRequest*) &msg;
memset(request, 0, sizeof(ObjectCloseRequest));
request->hdr.ordinal = ZXFIDL_CLOSE;
#else
msg.op = ZXRIO_CLOSE;
msg.arg = 0;
msg.datalen = 0;
msg.hcount = 0;
#endif
cb(&msg, cookie);
return ZX_OK;
}
zx_status_t zxrio_handler(zx_handle_t h, zxrio_cb_t cb, void* cookie) {
if (h == ZX_HANDLE_INVALID) {
return zxrio_handle_close(cb, cookie);
} else {
char buffer[ZX_CHANNEL_MAX_MSG_BYTES];
return zxrio_handle_rpc(h, (zxrio_msg_t*) buffer, cb, cookie);
}
}
zx_status_t zxrio_txn_handoff(zx_handle_t srv, zx_handle_t reply, zxrio_msg_t* msg) {
msg->txid = 0;
uint32_t dsize;
switch (msg->op) {
case ZXFIDL_OPEN: {
DirectoryOpenRequest* request = (DirectoryOpenRequest*) msg;
request->object = FIDL_HANDLE_PRESENT;
dsize = FIDL_ALIGN(sizeof(DirectoryOpenRequest)) + FIDL_ALIGN(request->path.size);
break;
}
case ZXFIDL_CLONE: {
ObjectCloneRequest* request = (ObjectCloneRequest*) msg;
request->object = FIDL_HANDLE_PRESENT;
dsize = sizeof(ObjectCloneRequest);
break;
}
default:
ZX_DEBUG_ASSERT(!ZXRIO_FIDL_MSG(msg->op));
msg->handle[0] = reply;
msg->hcount = 1;
dsize = ZXRIO_HDR_SZ + msg->datalen;
}
zx_status_t r;
if ((r = zx_channel_write(srv, 0, msg, dsize, &reply, 1)) != ZX_OK) {
printf("zxrio_txn_handoff: Failed to write\n");
// The caller may or may not be expecting a response. Either way,
// we need to close the channel, since it will not arrive at its
// intended destination.
zx_handle_close(reply);
}
return r;
}
// on success, msg->hcount indicates number of valid handles in msg->handle
// on error there are never any handles
static zx_status_t zxrio_txn(zxrio_t* rio, zxrio_msg_t* msg) {
if (!is_rio_message_valid(msg)) {
return ZX_ERR_INVALID_ARGS;
}
msg->txid = atomic_fetch_add(&rio->txid, 1);
xprintf("txn h=%x txid=%x op=%d len=%u\n", rio->h, msg->txid, msg->op, msg->datalen);
zx_status_t r;
zx_status_t rs = ZX_ERR_INTERNAL;
uint32_t dsize;
zx_channel_call_args_t args;
args.wr_bytes = msg;
args.wr_handles = msg->handle;
args.rd_bytes = msg;
args.rd_handles = msg->handle;
args.wr_num_bytes = ZXRIO_HDR_SZ + msg->datalen;
args.wr_num_handles = msg->hcount;
args.rd_num_bytes = ZXRIO_HDR_SZ + FDIO_CHUNK_SIZE;
args.rd_num_handles = FDIO_MAX_HANDLES;
r = zx_channel_call(rio->h, 0, ZX_TIME_INFINITE, &args, &dsize, &msg->hcount, &rs);
if (r < 0) {
if (r == ZX_ERR_CALL_FAILED) {
// read phase failed, true status is in rs
msg->hcount = 0;
return rs;
} else {
// write phase failed, we must discard the handles
goto fail_discard_handles;
}
}
// check for protocol errors
if (!is_rio_message_reply_valid(msg, dsize) ||
(ZXRIO_OP(msg->op) != ZXRIO_STATUS)) {
r = ZX_ERR_IO;
goto fail_discard_handles;
}
// check for remote error
if ((r = msg->arg) < 0) {
goto fail_discard_handles;
}
return r;
fail_discard_handles:
// We failed either writing at all (still have the handles)
// or after reading (need to abandon any handles we received)
discard_handles(msg->handle, msg->hcount);
msg->hcount = 0;
return r;
}
void zxrio_new_txid(zxrio_t* rio, zx_txid_t* txid) {
*txid = atomic_fetch_add(&rio->txid, 1);
}
zx_handle_t zxrio_handle(zxrio_t* rio) {
return rio->h;
}
zx_status_t zxrio_object_extract_handle(const zxrio_object_info_t* info,
zx_handle_t* out) {
switch (info->tag) {
case FDIO_PROTOCOL_FILE:
if (info->file.e != ZX_HANDLE_INVALID) {
*out = info->file.e;
return ZX_OK;
}
break;
case FDIO_PROTOCOL_SOCKET_CONNECTED:
case FDIO_PROTOCOL_SOCKET:
if (info->socket.s != ZX_HANDLE_INVALID) {
*out = info->socket.s;
return ZX_OK;
}
break;
case FDIO_PROTOCOL_PIPE:
if (info->pipe.s != ZX_HANDLE_INVALID) {
*out = info->pipe.s;
return ZX_OK;
}
break;
case FDIO_PROTOCOL_VMOFILE:
if (info->vmofile.v != ZX_HANDLE_INVALID) {
*out = info->vmofile.v;
return ZX_OK;
}
break;
case FDIO_PROTOCOL_DEVICE:
if (info->device.e != ZX_HANDLE_INVALID) {
*out = info->device.e;
return ZX_OK;
}
break;
}
return ZX_ERR_NOT_FOUND;
}
#ifdef ZXRIO_FIDL
zx_status_t zxrio_close(fdio_t* io) {
zxrio_t* rio = (zxrio_t*)io;
zx_status_t r = fidl_close(rio);
zx_handle_t h = rio->h;
rio->h = 0;
zx_handle_close(h);
if (rio->h2 > 0) {
h = rio->h2;
rio->h2 = 0;
zx_handle_close(h);
}
return r;
}
// Synchronously (non-pipelined) open an object
// The svc handle is only used to send a message
static zx_status_t zxrio_sync_open_connection(zx_handle_t svc, uint32_t op,
uint32_t flags, uint32_t mode,
const char* path, size_t pathlen,
zxrio_describe_t* info, zx_handle_t* out) {
if (!(flags & ZX_FS_FLAG_DESCRIBE)) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t r;
zx_handle_t h;
zx_handle_t cnxn;
if ((r = zx_channel_create(0, &h, &cnxn)) != ZX_OK) {
return r;
}
switch (op) {
case ZXRIO_CLONE:
r = fidl_clone_request(svc, cnxn, flags);
break;
case ZXRIO_OPEN:
r = fidl_open_request(svc, cnxn, flags, mode, path, pathlen);
break;
default:
zx_handle_close(cnxn);
r = ZX_ERR_NOT_SUPPORTED;
}
if (r != ZX_OK) {
zx_handle_close(h);
return r;
}
if ((r = zxrio_process_open_response(h, info)) != ZX_OK) {
zx_handle_close(h);
return r;
}
*out = h;
return ZX_OK;
}
// Open an object without waiting for the response.
// This function always consumes the cnxn handle
// The svc handle is only used to send a message
static zx_status_t zxrio_connect(zx_handle_t svc, zx_handle_t cnxn,
uint32_t op, uint32_t flags, uint32_t mode,
const char* name) {
size_t len = strlen(name);
if (len >= PATH_MAX) {
zx_handle_close(cnxn);
return ZX_ERR_BAD_PATH;
}
if (flags & ZX_FS_FLAG_DESCRIBE) {
zx_handle_close(cnxn);
return ZX_ERR_INVALID_ARGS;
}
zx_status_t r;
switch (op) {
case ZXRIO_CLONE:
r = fidl_clone_request(svc, cnxn, flags);
break;
case ZXRIO_OPEN:
r = fidl_open_request(svc, cnxn, flags, mode, name, len);
break;
default:
zx_handle_close(cnxn);
r = ZX_ERR_NOT_SUPPORTED;
}
return r;
}
static ssize_t zxrio_write(fdio_t* io, const void* data, size_t len) {
zxrio_t* rio = (zxrio_t*) io;
zx_status_t status = ZX_OK;
uint64_t count = 0;
uint64_t xfer;
while (len > 0) {
xfer = (len > FDIO_CHUNK_SIZE) ? FDIO_CHUNK_SIZE : len;
uint64_t actual = 0;
if ((status = fidl_write(rio, data, xfer, &actual)) != ZX_OK) {
return status;
}
count += actual;
data += actual;
len -= actual;
if (xfer != actual) {
break;
}
}
if (count == 0) {
return status;
}
return count;
}
static ssize_t zxrio_write_at(fdio_t* io, const void* data, size_t len, off_t offset) {
zxrio_t* rio = (zxrio_t*) io;
zx_status_t status = ZX_ERR_IO;
uint64_t count = 0;
uint64_t xfer;
while (len > 0) {
xfer = (len > FDIO_CHUNK_SIZE) ? FDIO_CHUNK_SIZE : len;
uint64_t actual = 0;
if ((status = fidl_writeat(rio, data, xfer, offset, &actual)) != ZX_OK) {
return status;
}
count += actual;
data += actual;
offset += actual;
len -= actual;
if (xfer != actual) {
break;
}
}
if (count == 0) {
return status;
}
return count;
}
static ssize_t zxrio_read(fdio_t* io, void* data, size_t len) {
zxrio_t* rio = (zxrio_t*) io;
zx_status_t status;
uint64_t count = 0;
uint64_t xfer;
while (len > 0) {
xfer = (len > FDIO_CHUNK_SIZE) ? FDIO_CHUNK_SIZE : len;
uint64_t actual = 0;
if ((status = fidl_read(rio, data, xfer, &actual)) != ZX_OK) {
return status;
}
count += actual;
data += actual;
len -= actual;
if (xfer != actual) {
break;
}
}
if (count == 0) {
return status;
}
return count;
}
static ssize_t zxrio_read_at(fdio_t* io, void* data, size_t len, off_t offset) {
zxrio_t* rio = (zxrio_t*) io;
zx_status_t status;
uint64_t count = 0;
uint64_t xfer;
while (len > 0) {
xfer = (len > FDIO_CHUNK_SIZE) ? FDIO_CHUNK_SIZE : len;
uint64_t actual = 0;
if ((status = fidl_readat(rio, data, xfer, offset, &actual)) != ZX_OK) {
return status;
}
offset += actual;
count += actual;
data += actual;
len -= actual;
if (xfer != actual) {
break;
}
}
if (count == 0) {
return status;
}
return count;
}
static off_t zxrio_seek(fdio_t* io, off_t offset, int whence) {
zxrio_t* rio = (zxrio_t*)io;
zx_status_t status = fidl_seek(rio, offset, whence, &offset);
if (status != ZX_OK) {
return status;
}
return offset;
}
ssize_t zxrio_ioctl(fdio_t* io, uint32_t op, const void* in_buf,
size_t in_len, void* out_buf, size_t out_len) {
zxrio_t* rio = (zxrio_t*)io;
if (in_len > FDIO_IOCTL_MAX_INPUT || out_len > FDIO_CHUNK_SIZE) {
return ZX_ERR_INVALID_ARGS;
}
size_t actual;
zx_status_t status = fidl_ioctl(rio, op, in_buf, in_len, out_buf, out_len, &actual);
if (status != ZX_OK) {
return status;
}
return actual;
}
#else // ZXRIO_FIDL
zx_status_t zxrio_close(fdio_t* io) {
zxrio_t* rio = (zxrio_t*)io;
zxrio_msg_t msg;
zx_status_t r;
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = ZXRIO_CLOSE;
if ((r = zxrio_txn(rio, &msg)) >= 0) {
discard_handles(msg.handle, msg.hcount);
}
zx_handle_t h = rio->h;
rio->h = 0;
zx_handle_close(h);
if (rio->h2 > 0) {
h = rio->h2;
rio->h2 = 0;
zx_handle_close(h);
}
return r;
}
// Synchronously (non-pipelined) open an object
// The svc handle is only used to send a message
static zx_status_t zxrio_sync_open_connection(zx_handle_t svc, uint32_t op,
uint32_t flags, uint32_t mode,
const char* path, size_t pathlen,
zxrio_describe_t* info, zx_handle_t* out) {
zxrio_msg_t msg;
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = op;
msg.datalen = pathlen;
msg.arg = flags;
msg.arg2.mode = mode;
memcpy(msg.data, path, pathlen);
zx_status_t r;
zx_handle_t h;
if ((r = zx_channel_create(0, &h, &msg.handle[0])) < 0) {
return r;
}
msg.hcount = 1;
// Write the (one-way) request message
if ((r = zx_channel_write(svc, 0, &msg, ZXRIO_HDR_SZ + msg.datalen,
msg.handle, msg.hcount)) < 0) {
zx_handle_close(msg.handle[0]);
zx_handle_close(h);
return r;
}
if ((r = zxrio_process_open_response(h, info)) != ZX_OK) {
zx_handle_close(h);
return r;
}
*out = h;
return ZX_OK;
}
// Open an object without waiting for the response.
// This function always consumes the cnxn handle
// The svc handle is only used to send a message
static zx_status_t zxrio_connect(zx_handle_t svc, zx_handle_t cnxn,
uint32_t op, uint32_t flags, uint32_t mode,
const char* name) {
size_t len = strlen(name);
if (len >= PATH_MAX) {
zx_handle_close(cnxn);
return ZX_ERR_BAD_PATH;
}
if (flags & ZX_FS_FLAG_DESCRIBE) {
zx_handle_close(cnxn);
return ZX_ERR_INVALID_ARGS;
}
zxrio_msg_t msg;
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = op;
msg.datalen = len;
msg.arg = flags;
msg.arg2.mode = mode;
msg.hcount = 1;
msg.handle[0] = cnxn;
memcpy(msg.data, name, len);
zx_status_t r;
if ((r = zx_channel_write(svc, 0, &msg, ZXRIO_HDR_SZ + msg.datalen, msg.handle, 1)) < 0) {
zx_handle_close(cnxn);
return r;
}
return ZX_OK;
}
static ssize_t write_common(uint32_t op, fdio_t* io, const void* _data, size_t len, off_t offset) {
zxrio_t* rio = (zxrio_t*)io;
const uint8_t* data = _data;
ssize_t count = 0;
zx_status_t r = 0;
zxrio_msg_t msg;
ssize_t xfer;
while (len > 0) {
xfer = (len > FDIO_CHUNK_SIZE) ? FDIO_CHUNK_SIZE : len;
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = op;
msg.datalen = xfer;
if (op == ZXRIO_WRITE_AT)
msg.arg2.off = offset;
memcpy(msg.data, data, xfer);
if ((r = zxrio_txn(rio, &msg)) < 0) {
break;
}
discard_handles(msg.handle, msg.hcount);
if (r > xfer) {
r = ZX_ERR_IO;
break;
}
count += r;
data += r;
len -= r;
if (op == ZXRIO_WRITE_AT)
offset += r;
// stop at short read
if (r < xfer) {
break;
}
}
return count ? count : r;
}
static ssize_t zxrio_write(fdio_t* io, const void* _data, size_t len) {
return write_common(ZXRIO_WRITE, io, _data, len, 0);
}
static ssize_t zxrio_write_at(fdio_t* io, const void* _data, size_t len, off_t offset) {
return write_common(ZXRIO_WRITE_AT, io, _data, len, offset);
}
static ssize_t read_common(uint32_t op, fdio_t* io, void* _data, size_t len, off_t offset) {
zxrio_t* rio = (zxrio_t*)io;
uint8_t* data = _data;
ssize_t count = 0;
zx_status_t r = 0;
zxrio_msg_t msg;
ssize_t xfer;
while (len > 0) {
xfer = (len > FDIO_CHUNK_SIZE) ? FDIO_CHUNK_SIZE : len;
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = op;
msg.arg = xfer;
if (op == ZXRIO_READ_AT)
msg.arg2.off = offset;
if ((r = zxrio_txn(rio, &msg)) < 0) {
break;
}
discard_handles(msg.handle, msg.hcount);
if ((r > (int)msg.datalen) || (r > xfer)) {
r = ZX_ERR_IO;
break;
}
memcpy(data, msg.data, r);
count += r;
data += r;
len -= r;
if (op == ZXRIO_READ_AT)
offset += r;
// stop at short read
if (r < xfer) {
break;
}
}
return count ? count : r;
}
static ssize_t zxrio_read(fdio_t* io, void* _data, size_t len) {
return read_common(ZXRIO_READ, io, _data, len, 0);
}
static ssize_t zxrio_read_at(fdio_t* io, void* _data, size_t len, off_t offset) {
return read_common(ZXRIO_READ_AT, io, _data, len, offset);
}
static off_t zxrio_seek(fdio_t* io, off_t offset, int whence) {
zxrio_t* rio = (zxrio_t*)io;
zxrio_msg_t msg;
zx_status_t r;
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = ZXRIO_SEEK;
msg.arg2.off = offset;
msg.arg = whence;
if ((r = zxrio_txn(rio, &msg)) < 0) {
return r;
}
discard_handles(msg.handle, msg.hcount);
return msg.arg2.off;
}
ssize_t zxrio_ioctl(fdio_t* io, uint32_t op, const void* in_buf,
size_t in_len, void* out_buf, size_t out_len) {
zxrio_t* rio = (zxrio_t*)io;
const uint8_t* data = in_buf;
zx_status_t r = 0;
zxrio_msg_t msg;
if (in_len > FDIO_IOCTL_MAX_INPUT || out_len > FDIO_CHUNK_SIZE) {
return ZX_ERR_INVALID_ARGS;
}
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = ZXRIO_IOCTL;
msg.datalen = in_len;
msg.arg = out_len;
msg.arg2.op = op;
switch (IOCTL_KIND(op)) {
case IOCTL_KIND_GET_HANDLE:
if (out_len < sizeof(zx_handle_t)) {
return ZX_ERR_INVALID_ARGS;
}
break;
case IOCTL_KIND_GET_TWO_HANDLES:
if (out_len < 2 * sizeof(zx_handle_t)) {
return ZX_ERR_INVALID_ARGS;
}
break;
case IOCTL_KIND_GET_THREE_HANDLES:
if (out_len < 3 * sizeof(zx_handle_t)) {
return ZX_ERR_INVALID_ARGS;
}
break;
case IOCTL_KIND_SET_HANDLE:
msg.op = ZXRIO_IOCTL_1H;
if (in_len < sizeof(zx_handle_t)) {
return ZX_ERR_INVALID_ARGS;
}
msg.hcount = 1;
msg.handle[0] = *((zx_handle_t*) in_buf);
break;
case IOCTL_KIND_SET_TWO_HANDLES:
msg.op = ZXRIO_IOCTL_2H;
if (in_len < 2 * sizeof(zx_handle_t)) {
return ZX_ERR_INVALID_ARGS;
}
msg.hcount = 2;
msg.handle[0] = *((zx_handle_t*) in_buf);
msg.handle[1] = *(((zx_handle_t*) in_buf) + 1);
break;
}
memcpy(msg.data, data, in_len);
if ((r = zxrio_txn(rio, &msg)) < 0) {
return r;
}
size_t copy_len = msg.datalen;
if (msg.datalen > out_len) {
copy_len = out_len;
}
memcpy(out_buf, msg.data, copy_len);
int handles = 0;
switch (IOCTL_KIND(op)) {
case IOCTL_KIND_GET_HANDLE:
handles = (msg.hcount > 0 ? 1 : 0);
if (handles) {
memcpy(out_buf, msg.handle, sizeof(zx_handle_t));
} else {
memset(out_buf, 0, sizeof(zx_handle_t));
}
break;
case IOCTL_KIND_GET_TWO_HANDLES:
handles = (msg.hcount > 2 ? 2 : msg.hcount);
if (handles) {
memcpy(out_buf, msg.handle, handles * sizeof(zx_handle_t));
}
if (handles < 2) {
memset(out_buf, 0, (2 - handles) * sizeof(zx_handle_t));
}
break;
case IOCTL_KIND_GET_THREE_HANDLES:
handles = (msg.hcount > 3 ? 3 : msg.hcount);
if (handles) {
memcpy(out_buf, msg.handle, handles * sizeof(zx_handle_t));
}
if (handles < 3) {
memset(out_buf, 0, (3 - handles) * sizeof(zx_handle_t));
}
break;
}
discard_handles(msg.handle + handles, msg.hcount - handles);
LOG(1, "rio: close(%p)\n", io);
return r;
}
#endif // ZXRIO_FIDL
zx_status_t zxrio_process_open_response(zx_handle_t h, zxrio_describe_t* info) {
zx_object_wait_one(h, ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
ZX_TIME_INFINITE, NULL);
// Attempt to read the description from open
uint32_t dsize = sizeof(*info);
zx_handle_t extra_handle = ZX_HANDLE_INVALID;
uint32_t actual_handles;
zx_status_t r = zx_channel_read(h, 0, info, &extra_handle, dsize, 1, &dsize,
&actual_handles);
if (r != ZX_OK) {
return r;
}
if (dsize < ZXRIO_DESCRIBE_HDR_SZ || info->op != ZXRIO_ON_OPEN) {
r = ZX_ERR_IO;
} else {
r = info->status;
}
if (dsize != sizeof(zxrio_describe_t)) {
r = (r != ZX_OK) ? r : ZX_ERR_IO;
}
if (r != ZX_OK) {
if (extra_handle != ZX_HANDLE_INVALID) {
zx_handle_close(extra_handle);
}
return r;
}
// Confirm that the objects "zxrio_describe_t" and "ObjectOnOpenEvent"
// are aligned enough to be compatible.
//
// This is somewhat complicated by the fact that the "ObjectOnOpenEvent"
// object has an optional "ObjectInfo" secondary which exists immediately
// following the struct.
static_assert(__builtin_offsetof(zxrio_describe_t, extra) ==
FIDL_ALIGN(sizeof(ObjectOnOpenEvent)),
"RIO Description message doesn't align with FIDL response secondary");
static_assert(sizeof(zxrio_object_info_t) == sizeof(ObjectInfo),
"RIO Object Info doesn't align with FIDL object info");
static_assert(__builtin_offsetof(zxrio_object_info_t, file.e) ==
__builtin_offsetof(ObjectInfo, file.event), "Unaligned File");
static_assert(__builtin_offsetof(zxrio_object_info_t, pipe.s) ==
__builtin_offsetof(ObjectInfo, pipe.socket), "Unaligned Pipe");
static_assert(__builtin_offsetof(zxrio_object_info_t, vmofile.v) ==
__builtin_offsetof(ObjectInfo, vmofile.vmo), "Unaligned Vmofile");
static_assert(__builtin_offsetof(zxrio_object_info_t, device.e) ==
__builtin_offsetof(ObjectInfo, device.event), "Unaligned Device");
switch (info->extra.tag) {
// Case: No extra handles expected
case FDIO_PROTOCOL_SERVICE:
case FDIO_PROTOCOL_DIRECTORY:
if (extra_handle != ZX_HANDLE_INVALID) {
zx_handle_close(extra_handle);
return ZX_ERR_IO;
}
break;
// Case: Extra handles optional
case FDIO_PROTOCOL_FILE:
info->extra.file.e = extra_handle;
break;
case FDIO_PROTOCOL_DEVICE:
info->extra.device.e = extra_handle;
break;
case FDIO_PROTOCOL_SOCKET:
info->extra.socket.s = extra_handle;
break;
// Case: Extra handles required
case FDIO_PROTOCOL_PIPE:
if (extra_handle == ZX_HANDLE_INVALID) {
return ZX_ERR_IO;
}
info->extra.pipe.s = extra_handle;
break;
case FDIO_PROTOCOL_VMOFILE:
if (extra_handle == ZX_HANDLE_INVALID) {
return ZX_ERR_IO;
}
info->extra.vmofile.v = extra_handle;
break;
default:
printf("Unexpected protocol type opening connection\n");
if (extra_handle != ZX_HANDLE_INVALID) {
zx_handle_close(extra_handle);
}
return ZX_ERR_IO;
}
return r;
}
zx_status_t fdio_service_connect(const char* svcpath, zx_handle_t h) {
if (svcpath == NULL) {
zx_handle_close(h);
return ZX_ERR_INVALID_ARGS;
}
// Otherwise attempt to connect through the root namespace
if (fdio_root_ns != NULL) {
return fdio_ns_connect(fdio_root_ns, svcpath,
ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE, h);
}
// Otherwise we fail
zx_handle_close(h);
return ZX_ERR_NOT_FOUND;
}
zx_status_t fdio_service_connect_at(zx_handle_t dir, const char* path, zx_handle_t h) {
if (path == NULL) {
zx_handle_close(h);
return ZX_ERR_INVALID_ARGS;
}
if (dir == ZX_HANDLE_INVALID) {
zx_handle_close(h);
return ZX_ERR_UNAVAILABLE;
}
return zxrio_connect(dir, h, ZXRIO_OPEN, ZX_FS_RIGHT_READABLE |
ZX_FS_RIGHT_WRITABLE, 0755, path);
}
zx_status_t fdio_open_at(zx_handle_t dir, const char* path, uint32_t flags, zx_handle_t h) {
if (path == NULL) {
zx_handle_close(h);
return ZX_ERR_INVALID_ARGS;
}
if (dir == ZX_HANDLE_INVALID) {
zx_handle_close(h);
return ZX_ERR_UNAVAILABLE;
}
return zxrio_connect(dir, h, ZXRIO_OPEN, flags, 0755, path);
}
zx_handle_t fdio_service_clone(zx_handle_t svc) {
zx_handle_t cli, srv;
zx_status_t r;
if (svc == ZX_HANDLE_INVALID) {
return ZX_HANDLE_INVALID;
}
if ((r = zx_channel_create(0, &cli, &srv)) < 0) {
return ZX_HANDLE_INVALID;
}
if ((r = zxrio_connect(svc, srv, ZXRIO_CLONE, ZX_FS_RIGHT_READABLE |
ZX_FS_RIGHT_WRITABLE, 0755, "")) < 0) {
zx_handle_close(cli);
return ZX_HANDLE_INVALID;
}
return cli;
}
zx_status_t fdio_service_clone_to(zx_handle_t svc, zx_handle_t srv) {
if (srv == ZX_HANDLE_INVALID) {
return ZX_ERR_INVALID_ARGS;
}
if (svc == ZX_HANDLE_INVALID) {
zx_handle_close(srv);
return ZX_ERR_INVALID_ARGS;
}
return zxrio_connect(svc, srv, ZXRIO_CLONE, ZX_FS_RIGHT_READABLE |
ZX_FS_RIGHT_WRITABLE, 0755, "");
}
zx_status_t zxrio_misc(fdio_t* io, uint32_t op, int64_t off,
uint32_t maxreply, void* ptr, size_t len) {
zxrio_t* rio = (zxrio_t*)io;
zx_status_t r;
#ifdef ZXRIO_FIDL
// Reroute FIDL2 operations
switch (op) {
case ZXRIO_STAT: {
size_t out_sz;
if ((r = fidl_stat(rio, maxreply, ptr, &out_sz)) != ZX_OK) {
return r;
}
return out_sz;
}
case ZXRIO_SETATTR: {
if (len != sizeof(vnattr_t)) {
return ZX_ERR_INVALID_ARGS;
}
return fidl_setattr(rio, (const vnattr_t*) ptr);
}
case ZXRIO_SYNC: {
return fidl_sync(rio);
}
case ZXRIO_READDIR: {
switch (off) {
case READDIR_CMD_RESET:
if ((r = fidl_rewind(rio)) != ZX_OK) {
return r;
}
// Fall-through to CMD_NONE
case READDIR_CMD_NONE: {
size_t out_sz;
if ((r = fidl_readdirents(rio, ptr, maxreply, &out_sz)) != ZX_OK) {
return r;
}
return out_sz;
}
default:
return ZX_ERR_INVALID_ARGS;
}
}
case ZXRIO_UNLINK: {
return fidl_unlink(rio, ptr, len);
}
case ZXRIO_TRUNCATE: {
return fidl_truncate(rio, off);
}
case ZXRIO_RENAME: {
size_t srclen = strlen(ptr);
size_t dstlen = len - (srclen + 2);
const char* src = ptr;
const char* dst = ptr + srclen + 1;
return fidl_rename(rio, src, srclen, (zx_handle_t) off, dst, dstlen);
}
case ZXRIO_LINK: {
size_t srclen = strlen(ptr);
size_t dstlen = len - (srclen + 2);
const char* src = ptr;
const char* dst = ptr + srclen + 1;
return fidl_link(rio, src, srclen, (zx_handle_t) off, dst, dstlen);
}
case ZXRIO_FCNTL: {
// zxrio_misc is extremely overloaded, so the interpretation
// of these arguments can seem somewhat obtuse.
uint32_t fcntl_op = maxreply;
switch (fcntl_op) {
case F_GETFL: {
uint32_t* outflags = ptr;
return fidl_getflags(rio, outflags);
}
case F_SETFL: {
uint32_t flags = off;
return fidl_setflags(rio, flags);
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
case ZXRIO_MMAP: {
if (len != sizeof(zxrio_mmap_data_t)) {
printf("fdio/remoteio.c: ZXRIO_MMAP: Bad args\n");
return ZX_ERR_INVALID_ARGS;
}
zxrio_mmap_data_t* data = ptr;
zx_handle_t vmo;
zx_status_t r = fidl_getvmo(rio, data->flags, &vmo);
if (r != ZX_OK) {
return r;
}
return vmo;
}
}
#endif // ZXRIO_FIDL
zxrio_msg_t msg;
if ((len > FDIO_CHUNK_SIZE) || (maxreply > FDIO_CHUNK_SIZE)) {
return ZX_ERR_INVALID_ARGS;
}
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = op;
msg.arg = maxreply;
msg.arg2.off = off;
msg.datalen = len;
if (ptr && len > 0) {
memcpy(msg.data, ptr, len);
}
switch (op) {
case ZXRIO_RENAME:
case ZXRIO_LINK:
// As a hack, 'Rename' and 'Link' take token handles through
// the offset argument.
msg.handle[0] = (zx_handle_t) off;
msg.hcount = 1;
}
if ((r = zxrio_txn(rio, &msg)) < 0) {
return r;
}
switch (op) {
case ZXRIO_MMAP: {
// Ops which receive single handles:
if ((msg.hcount != 1) || (msg.datalen > maxreply)) {
discard_handles(msg.handle, msg.hcount);
return ZX_ERR_IO;
}
r = msg.handle[0];
memcpy(ptr, msg.data, msg.datalen);
break;
}
case ZXRIO_FCNTL:
// This is a bit of a hack, but for this case, we
// return 'msg.arg2.mode' in the data field to simplify
// this call for the client.
discard_handles(msg.handle, msg.hcount);
if (ptr) {
memcpy(ptr, &msg.arg2.mode, sizeof(msg.arg2.mode));
}
break;
default:
// Ops which don't receive handles:
discard_handles(msg.handle, msg.hcount);
if (msg.datalen > maxreply) {
return ZX_ERR_IO;
}
if (ptr && msg.datalen > 0) {
memcpy(ptr, msg.data, msg.datalen);
}
}
return r;
}
zx_status_t fdio_create_fd(zx_handle_t* handles, uint32_t* types, size_t hcount,
int* fd_out) {
fdio_t* io;
zx_status_t r;
int fd;
zxrio_object_info_t info;
zx_handle_t control_channel = ZX_HANDLE_INVALID;
// Pack additional handles into |info|, if possible.
switch (PA_HND_TYPE(types[0])) {
case PA_FDIO_REMOTE:
switch (hcount) {
case 1:
io = fdio_remote_create(handles[0], 0);
goto bind;
case 2:
io = fdio_remote_create(handles[0], handles[1]);
goto bind;
default:
r = ZX_ERR_INVALID_ARGS;
goto fail;
}
case PA_FDIO_PIPE:
info.tag = FDIO_PROTOCOL_PIPE;
// Expected: Single pipe handle
if (hcount != 1) {
r = ZX_ERR_INVALID_ARGS;
goto fail;
}
info.pipe.s = handles[0];
break;
case PA_FDIO_SOCKET:
info.tag = FDIO_PROTOCOL_SOCKET_CONNECTED;
// Expected: Single socket handle
if (hcount != 1) {
r = ZX_ERR_INVALID_ARGS;
goto fail;
}
info.socket.s = handles[0];
break;
default:
r = ZX_ERR_IO;
goto fail;
}
if ((r = fdio_from_handles(control_channel, &info, &io)) != ZX_OK) {
return r;
}
bind:
fd = fdio_bind_to_fd(io, -1, 0);
if (fd < 0) {
fdio_close(io);
fdio_release(io);
return ZX_ERR_BAD_STATE;
}
*fd_out = fd;
return ZX_OK;
fail:
discard_handles(handles, hcount);
return r;
}
zx_status_t fdio_from_handles(zx_handle_t handle, zxrio_object_info_t* info,
fdio_t** out) {
// All failure cases which require discard_handles set r and break
// to the end. All other cases in which handle ownership is moved
// on return locally.
zx_status_t r;
fdio_t* io;
switch (info->tag) {
case FDIO_PROTOCOL_DIRECTORY:
case FDIO_PROTOCOL_SERVICE:
if (handle == ZX_HANDLE_INVALID) {
r = ZX_ERR_INVALID_ARGS;
break;
}
io = fdio_remote_create(handle, 0);
xprintf("rio (%x,%x) -> %p\n", handle, 0, io);
if (io == NULL) {
return ZX_ERR_NO_RESOURCES;
}
*out = io;
return ZX_OK;
case FDIO_PROTOCOL_FILE:
if (info->file.e == ZX_HANDLE_INVALID) {
io = fdio_remote_create(handle, 0);
xprintf("rio (%x,%x) -> %p\n", handle, 0, io);
} else {
io = fdio_remote_create(handle, info->file.e);
xprintf("rio (%x,%x) -> %p\n", handle, info->file.e, io);
}
if (io == NULL) {
return ZX_ERR_NO_RESOURCES;
}
*out = io;
return ZX_OK;
case FDIO_PROTOCOL_DEVICE:
if (info->device.e == ZX_HANDLE_INVALID) {
io = fdio_remote_create(handle, 0);
xprintf("rio (%x,%x) -> %p\n", handle, 0, io);
} else {
io = fdio_remote_create(handle, info->device.e);
xprintf("rio (%x,%x) -> %p\n", handle, info->device.e, io);
}
if (io == NULL) {
return ZX_ERR_NO_RESOURCES;
}
*out = io;
return ZX_OK;
case FDIO_PROTOCOL_PIPE:
if (handle != ZX_HANDLE_INVALID) {
r = ZX_ERR_INVALID_ARGS;
break;
} else if ((*out = fdio_pipe_create(info->pipe.s)) == NULL) {
return ZX_ERR_NO_RESOURCES;
}
return ZX_OK;
case FDIO_PROTOCOL_VMOFILE: {
if (info->vmofile.v == ZX_HANDLE_INVALID) {
r = ZX_ERR_INVALID_ARGS;
break;
}
// Currently, VMO Files don't use a client-side control channel.
zx_handle_close(handle);
*out = fdio_vmofile_create(info->vmofile.v, info->vmofile.offset,
info->vmofile.length);
if (*out == NULL) {
return ZX_ERR_NO_RESOURCES;
}
return ZX_OK;
}
case FDIO_PROTOCOL_SOCKET_CONNECTED:
case FDIO_PROTOCOL_SOCKET: {
int flags = (info->tag == FDIO_PROTOCOL_SOCKET_CONNECTED) ? IOFLAG_SOCKET_CONNECTED : 0;
#if WITH_NEW_SOCKET
if (handle == ZX_HANDLE_INVALID || info->socket.s == ZX_HANDLE_INVALID) {
r = ZX_ERR_INVALID_ARGS;
break;
}
zx_handle_close(handle);
if ((*out = fdio_socket_create(info->socket.s, flags)) == NULL) {
return ZX_ERR_NO_RESOURCES;
}
return ZX_OK;
#else
if (handle == ZX_HANDLE_INVALID) {
r = ZX_ERR_INVALID_ARGS;
break;
}
io = fdio_socket_create(handle, info->socket.s, flags);
if (io == NULL) {
return ZX_ERR_NO_RESOURCES;
}
*out = io;
return ZX_OK;
#endif
}
default:
printf("fdio_from_handles: Not supported\n");
r = ZX_ERR_NOT_SUPPORTED;
break;
}
zx_handle_t extra;
if (zxrio_object_extract_handle(info, &extra) == ZX_OK) {
zx_handle_close(extra);
}
zx_handle_close(handle);
return r;
}
zx_status_t zxrio_getobject(zx_handle_t rio_h, uint32_t op, const char* name,
uint32_t flags, uint32_t mode,
zxrio_describe_t* info, zx_handle_t* out) {
if (name == NULL) {
return ZX_ERR_INVALID_ARGS;
}
size_t len = strlen(name);
if (len >= PATH_MAX) {
return ZX_ERR_BAD_PATH;
}
if (flags & ZX_FS_FLAG_DESCRIBE) {
return zxrio_sync_open_connection(rio_h, op, flags, mode, name, len, info, out);
} else {
zx_handle_t h0, h1;
zx_status_t r;
if ((r = zx_channel_create(0, &h0, &h1)) < 0) {
return r;
}
if ((r = zxrio_connect(rio_h, h1, ZXRIO_OPEN, flags, mode, name)) < 0) {
zx_handle_close(h0);
return r;
}
// fake up a reply message since pipelined opens don't generate one
info->status = ZX_OK;
info->extra.tag = FDIO_PROTOCOL_SERVICE;
*out = h0;
return ZX_OK;
}
}
zx_status_t zxrio_open_handle(zx_handle_t h, const char* path, uint32_t flags,
uint32_t mode, fdio_t** out) {
zx_handle_t control_channel;
zxrio_describe_t info;
zx_status_t r = zxrio_getobject(h, ZXRIO_OPEN, path, flags, mode, &info, &control_channel);
if (r < 0) {
return r;
}
return fdio_from_handles(control_channel, &info.extra, out);
}
zx_status_t zxrio_open_handle_raw(zx_handle_t h, const char* path, uint32_t flags,
uint32_t mode, zx_handle_t *out) {
zx_handle_t control_channel;
zxrio_describe_t info;
zx_status_t r = zxrio_getobject(h, ZXRIO_OPEN, path, flags, mode, &info, &control_channel);
if (r < 0) {
return r;
}
if (info.extra.tag == FDIO_PROTOCOL_SERVICE) {
*out = control_channel;
return ZX_OK;
}
zx_handle_t extracted;
if (zxrio_object_extract_handle(&info.extra, &extracted) == ZX_OK) {
zx_handle_close(extracted);
}
return ZX_ERR_WRONG_TYPE;
}
zx_status_t zxrio_open(fdio_t* io, const char* path, uint32_t flags, uint32_t mode, fdio_t** out) {
zxrio_t* rio = (void*)io;
return zxrio_open_handle(rio->h, path, flags, mode, out);
}
static zx_status_t zxrio_clone(fdio_t* io, zx_handle_t* handles, uint32_t* types) {
zxrio_t* rio = (void*)io;
zx_handle_t h;
zxrio_describe_t info;
zx_status_t r = zxrio_getobject(rio->h, ZXRIO_CLONE, "", ZX_FS_FLAG_DESCRIBE, 0, &info, &h);
if (r < 0) {
return r;
}
handles[0] = h;
types[0] = PA_FDIO_REMOTE;
if (zxrio_object_extract_handle(&info.extra, &handles[1]) == ZX_OK) {
types[1] = PA_FDIO_REMOTE;
return 2;
}
return 1;
}
static zx_status_t zxrio_unwrap(fdio_t* io, zx_handle_t* handles, uint32_t* types) {
zxrio_t* rio = (void*)io;
LOG(1, "fdio: zxrio_unwrap(%p,...)\n");
zx_status_t r;
handles[0] = rio->h;
types[0] = PA_FDIO_REMOTE;
if (rio->h2 != 0) {
handles[1] = rio->h2;
types[1] = PA_FDIO_REMOTE;
r = 2;
} else {
r = 1;
}
return r;
}
static void zxrio_wait_begin(fdio_t* io, uint32_t events, zx_handle_t* handle, zx_signals_t* _signals) {
zxrio_t* rio = (void*)io;
*handle = rio->h2;
zx_signals_t signals = 0;
// Manually add signals that don't fit within POLL_MASK
if (events & POLLRDHUP) {
signals |= ZX_CHANNEL_PEER_CLOSED;
}
// POLLERR is always detected
*_signals = (((POLLERR | events) & POLL_MASK) << POLL_SHIFT) | signals;
}
static void zxrio_wait_end(fdio_t* io, zx_signals_t signals, uint32_t* _events) {
// Manually add events that don't fit within POLL_MASK
uint32_t events = 0;
if (signals & ZX_CHANNEL_PEER_CLOSED) {
events |= POLLRDHUP;
}
*_events = ((signals >> POLL_SHIFT) & POLL_MASK) | events;
}
static fdio_ops_t zx_remote_ops = {
.read = zxrio_read,
.read_at = zxrio_read_at,
.write = zxrio_write,
.write_at = zxrio_write_at,
.recvfrom = fdio_default_recvfrom,
.sendto = fdio_default_sendto,
.recvmsg = fdio_default_recvmsg,
.sendmsg = fdio_default_sendmsg,
.misc = zxrio_misc,
.seek = zxrio_seek,
.close = zxrio_close,
.open = zxrio_open,
.clone = zxrio_clone,
.ioctl = zxrio_ioctl,
.wait_begin = zxrio_wait_begin,
.wait_end = zxrio_wait_end,
.unwrap = zxrio_unwrap,
.shutdown = fdio_default_shutdown,
.posix_ioctl = fdio_default_posix_ioctl,
.get_vmo = fdio_default_get_vmo,
};
fdio_t* fdio_remote_create(zx_handle_t h, zx_handle_t e) {
zxrio_t* rio = fdio_alloc(sizeof(*rio));
if (rio == NULL) {
zx_handle_close(h);
zx_handle_close(e);
return NULL;
}
rio->io.ops = &zx_remote_ops;
rio->io.magic = FDIO_MAGIC;
atomic_init(&rio->io.refcount, 1);
rio->h = h;
rio->h2 = e;
atomic_init(&rio->txid, 1);
return &rio->io;
}