blob: 68a7ae63e26554db9ffb7417b939ae205bd3c506 [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 <magenta/device/device.h>
#include <magenta/device/ioctl.h>
#include <magenta/processargs.h>
#include <magenta/syscalls.h>
#include <mxio/debug.h>
#include <mxio/io.h>
#include <mxio/namespace.h>
#include <mxio/remoteio.h>
#include <mxio/util.h>
#include "private-remoteio.h"
#define MXDEBUG 0
// POLL_MASK and POLL_SHIFT intend to convert the lower five POLL events into
// MX_USER_SIGNALs and vice-versa. Other events need to be manually converted to
// an mx_signal_t, if they are desired.
#define POLL_SHIFT 24
#define POLL_MASK 0x1F
static_assert(MX_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 pthread_key_t rchannel_key;
static void rchannel_cleanup(void* data) {
if (data == NULL) {
return;
}
mx_handle_t* handles = (mx_handle_t*)data;
if (handles[0] != MX_HANDLE_INVALID)
mx_handle_close(handles[0]);
if (handles[1] != MX_HANDLE_INVALID)
mx_handle_close(handles[1]);
free(handles);
}
void __mxio_rchannel_init(void) {
if (pthread_key_create(&rchannel_key, &rchannel_cleanup) != 0)
abort();
}
static const char* _opnames[] = MXRIO_OPNAMES;
const char* mxio_opname(uint32_t op) {
op = MXRIO_OPNAME(op);
if (op < MXRIO_NUM_OPS) {
return _opnames[op];
} else {
return "unknown";
}
}
static bool is_message_valid(mxrio_msg_t* msg) {
if ((msg->datalen > MXIO_CHUNK_SIZE) ||
(msg->hcount > MXIO_MAX_HANDLES)) {
return false;
}
return true;
}
static bool is_message_reply_valid(mxrio_msg_t* msg, uint32_t size) {
if ((size < MXRIO_HDR_SZ) ||
(msg->datalen != (size - MXRIO_HDR_SZ))) {
return false;
}
return is_message_valid(msg);
}
static void discard_handles(mx_handle_t* handles, unsigned count) {
while (count-- > 0) {
mx_handle_close(*handles++);
}
}
mx_status_t mxrio_handle_rpc(mx_handle_t h, mxrio_msg_t* msg, mxrio_cb_t cb, void* cookie) {
mx_status_t r;
// NOTE: hcount intentionally received out-of-bound from the message to
// avoid letting "client-supplied" bytes override the REAL hcount value.
uint32_t hcount = 0;
uint32_t dsz = sizeof(mxrio_msg_t);
if ((r = mx_channel_read(h, 0, msg, msg->handle, dsz, MXIO_MAX_HANDLES, &dsz, &hcount)) < 0) {
return r;
}
// Now, "msg->hcount" can be trusted once again.
msg->hcount = hcount;
if (!is_message_reply_valid(msg, dsz)) {
discard_handles(msg->handle, msg->hcount);
return MX_ERR_INVALID_ARGS;
}
bool is_close = (MXRIO_OP(msg->op) == MXRIO_CLOSE);
xprintf("handle_rio: op=%s arg=%d len=%u hsz=%d\n",
mxio_opname(msg->op), msg->arg, msg->datalen, msg->hcount);
if ((msg->arg = cb(msg, cookie)) == ERR_DISPATCHER_INDIRECT) {
// callback is handling the reply itself
// and took ownership of the reply handle
return MX_OK;
}
if ((msg->arg < 0) || !is_message_valid(msg)) {
// in the event of an error response or bad message
// release all the handles and data payload
discard_handles(msg->handle, msg->hcount);
msg->datalen = 0;
msg->hcount = 0;
// specific errors are prioritized over the bad
// message case which we represent as MX_ERR_INTERNAL
// to differentiate from MX_ERR_IO on the near side
// TODO(MG-974): consider a better error code
msg->arg = (msg->arg < 0) ? msg->arg : MX_ERR_INTERNAL;
}
msg->op = MXRIO_STATUS;
if ((r = mx_channel_write(h, 0, msg, MXRIO_HDR_SZ + msg->datalen, msg->handle, msg->hcount)) < 0) {
discard_handles(msg->handle, msg->hcount);
}
if (is_close) {
// signals to not perform a close callback
return ERR_DISPATCHER_DONE;
} else {
return r;
}
}
mx_status_t mxrio_handle_close(mxrio_cb_t cb, void* cookie) {
mxrio_msg_t msg;
// remote side was closed;
msg.op = MXRIO_CLOSE;
msg.arg = 0;
msg.datalen = 0;
msg.hcount = 0;
cb(&msg, cookie);
return MX_OK;
}
mx_status_t mxrio_handler(mx_handle_t h, void* _cb, void* cookie) {
mxrio_cb_t cb = _cb;
if (h == MX_HANDLE_INVALID) {
return mxrio_handle_close(cb, cookie);
} else {
mxrio_msg_t msg;
return mxrio_handle_rpc(h, &msg, cb, cookie);
}
}
void mxrio_txn_handoff(mx_handle_t srv, mx_handle_t reply, mxrio_msg_t* msg) {
msg->txid = 0;
msg->handle[0] = reply;
msg->hcount = 1;
mx_status_t r;
uint32_t dsize = MXRIO_HDR_SZ + msg->datalen;
if ((r = mx_channel_write(srv, 0, msg, dsize, msg->handle, msg->hcount)) < 0) {
// nothing to do but inform the caller that we failed
struct {
mx_status_t status;
uint32_t type;
} error = { r, 0 };
mx_channel_write(reply, 0, &error, sizeof(error), NULL, 0);
mx_handle_close(reply);
}
}
// on success, msg->hcount indicates number of valid handles in msg->handle
// on error there are never any handles
static mx_status_t mxrio_txn(mxrio_t* rio, mxrio_msg_t* msg) {
if (!is_message_valid(msg)) {
return MX_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);
mx_status_t r;
mx_status_t rs = MX_ERR_INTERNAL;
uint32_t dsize;
mx_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 = MXRIO_HDR_SZ + msg->datalen;
args.wr_num_handles = msg->hcount;
args.rd_num_bytes = MXRIO_HDR_SZ + MXIO_CHUNK_SIZE;
args.rd_num_handles = MXIO_MAX_HANDLES;
r = mx_channel_call(rio->h, 0, MX_TIME_INFINITE, &args, &dsize, &msg->hcount, &rs);
if (r < 0) {
if (r == MX_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_message_reply_valid(msg, dsize) ||
(MXRIO_OP(msg->op) != MXRIO_STATUS)) {
r = MX_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;
}
ssize_t mxrio_ioctl(mxio_t* io, uint32_t op, const void* in_buf,
size_t in_len, void* out_buf, size_t out_len) {
mxrio_t* rio = (mxrio_t*)io;
const uint8_t* data = in_buf;
mx_status_t r = 0;
mxrio_msg_t msg;
if (in_len > MXIO_IOCTL_MAX_INPUT || out_len > MXIO_CHUNK_SIZE) {
return MX_ERR_INVALID_ARGS;
}
memset(&msg, 0, MXRIO_HDR_SZ);
msg.op = MXRIO_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(mx_handle_t)) {
return MX_ERR_INVALID_ARGS;
}
break;
case IOCTL_KIND_GET_TWO_HANDLES:
if (out_len < 2 * sizeof(mx_handle_t)) {
return MX_ERR_INVALID_ARGS;
}
break;
case IOCTL_KIND_GET_THREE_HANDLES:
if (out_len < 3 * sizeof(mx_handle_t)) {
return MX_ERR_INVALID_ARGS;
}
break;
case IOCTL_KIND_SET_HANDLE:
msg.op = MXRIO_IOCTL_1H;
if (in_len < sizeof(mx_handle_t)) {
return MX_ERR_INVALID_ARGS;
}
msg.hcount = 1;
msg.handle[0] = *((mx_handle_t*) in_buf);
break;
}
memcpy(msg.data, data, in_len);
if ((r = mxrio_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(mx_handle_t));
} else {
memset(out_buf, 0, sizeof(mx_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(mx_handle_t));
}
if (handles < 2) {
memset(out_buf, 0, (2 - handles) * sizeof(mx_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(mx_handle_t));
}
if (handles < 3) {
memset(out_buf, 0, (3 - handles) * sizeof(mx_handle_t));
}
break;
}
discard_handles(msg.handle + handles, msg.hcount - handles);
return r;
}
static ssize_t write_common(uint32_t op, mxio_t* io, const void* _data, size_t len, off_t offset) {
mxrio_t* rio = (mxrio_t*)io;
const uint8_t* data = _data;
ssize_t count = 0;
mx_status_t r = 0;
mxrio_msg_t msg;
ssize_t xfer;
while (len > 0) {
xfer = (len > MXIO_CHUNK_SIZE) ? MXIO_CHUNK_SIZE : len;
memset(&msg, 0, MXRIO_HDR_SZ);
msg.op = op;
msg.datalen = xfer;
if (op == MXRIO_WRITE_AT)
msg.arg2.off = offset;
memcpy(msg.data, data, xfer);
if ((r = mxrio_txn(rio, &msg)) < 0) {
break;
}
discard_handles(msg.handle, msg.hcount);
if (r > xfer) {
r = MX_ERR_IO;
break;
}
count += r;
data += r;
len -= r;
if (op == MXRIO_WRITE_AT)
offset += r;
// stop at short read
if (r < xfer) {
break;
}
}
return count ? count : r;
}
static ssize_t mxrio_write(mxio_t* io, const void* _data, size_t len) {
return write_common(MXRIO_WRITE, io, _data, len, 0);
}
static ssize_t mxrio_write_at(mxio_t* io, const void* _data, size_t len, off_t offset) {
return write_common(MXRIO_WRITE_AT, io, _data, len, offset);
}
static ssize_t read_common(uint32_t op, mxio_t* io, void* _data, size_t len, off_t offset) {
mxrio_t* rio = (mxrio_t*)io;
uint8_t* data = _data;
ssize_t count = 0;
mx_status_t r = 0;
mxrio_msg_t msg;
ssize_t xfer;
while (len > 0) {
xfer = (len > MXIO_CHUNK_SIZE) ? MXIO_CHUNK_SIZE : len;
memset(&msg, 0, MXRIO_HDR_SZ);
msg.op = op;
msg.arg = xfer;
if (op == MXRIO_READ_AT)
msg.arg2.off = offset;
if ((r = mxrio_txn(rio, &msg)) < 0) {
break;
}
discard_handles(msg.handle, msg.hcount);
if ((r > (int)msg.datalen) || (r > xfer)) {
r = MX_ERR_IO;
break;
}
memcpy(data, msg.data, r);
count += r;
data += r;
len -= r;
if (op == MXRIO_READ_AT)
offset += r;
// stop at short read
if (r < xfer) {
break;
}
}
return count ? count : r;
}
static ssize_t mxrio_read(mxio_t* io, void* _data, size_t len) {
return read_common(MXRIO_READ, io, _data, len, 0);
}
static ssize_t mxrio_read_at(mxio_t* io, void* _data, size_t len, off_t offset) {
return read_common(MXRIO_READ_AT, io, _data, len, offset);
}
static off_t mxrio_seek(mxio_t* io, off_t offset, int whence) {
mxrio_t* rio = (mxrio_t*)io;
mxrio_msg_t msg;
mx_status_t r;
memset(&msg, 0, MXRIO_HDR_SZ);
msg.op = MXRIO_SEEK;
msg.arg2.off = offset;
msg.arg = whence;
if ((r = mxrio_txn(rio, &msg)) < 0) {
return r;
}
discard_handles(msg.handle, msg.hcount);
return msg.arg2.off;
}
mx_status_t mxrio_close(mxio_t* io) {
mxrio_t* rio = (mxrio_t*)io;
mxrio_msg_t msg;
mx_status_t r;
memset(&msg, 0, MXRIO_HDR_SZ);
msg.op = MXRIO_CLOSE;
if ((r = mxrio_txn(rio, &msg)) >= 0) {
discard_handles(msg.handle, msg.hcount);
}
mx_handle_t h = rio->h;
rio->h = 0;
mx_handle_close(h);
if (rio->h2 > 0) {
h = rio->h2;
rio->h2 = 0;
mx_handle_close(h);
}
return r;
}
static mx_status_t mxrio_reply_channel_call(mx_handle_t rio_h, mxrio_msg_t* msg,
mxrio_object_t* info) {
mx_status_t r;
mx_handle_t h;
if ((r = mx_channel_create(0, &h, &msg->handle[0])) < 0) {
return r;
}
msg->hcount = 1;
// Write the (one-way) request message
if ((r = mx_channel_write(rio_h, 0, msg, MXRIO_HDR_SZ + msg->datalen,
msg->handle, msg->hcount)) < 0) {
mx_handle_close(msg->handle[0]);
mx_handle_close(h);
return r;
}
// Wait
mx_object_wait_one(h, MX_CHANNEL_READABLE | MX_CHANNEL_PEER_CLOSED, MX_TIME_INFINITE, NULL);
// Attempt to read the callback response
memset(info, 0xfe, sizeof(*info));
uint32_t dsize = MXRIO_OBJECT_MAXSIZE;
info->hcount = MXIO_MAX_HANDLES;
r = mx_channel_read(h, 0, info, &info->handle[1], dsize,
info->hcount, &dsize, &info->hcount);
if (r < 0) {
mx_handle_close(h);
return r;
}
info->handle[0] = h;
info->hcount++;
if (dsize < MXRIO_OBJECT_MINSIZE) {
r = MX_ERR_IO;
} else {
info->esize = dsize - MXRIO_OBJECT_MINSIZE;
r = info->status;
}
if (r < 0) {
discard_handles(info->handle, info->hcount);
}
return r;
}
// This function always consumes the cnxn handle
// The svc handle is only used to send a message
static mx_status_t mxrio_connect(mx_handle_t svc, mx_handle_t cnxn,
uint32_t op, int32_t flags, uint32_t mode,
const char* name) {
size_t len = strlen(name);
if (len >= PATH_MAX) {
mx_handle_close(cnxn);
return MX_ERR_BAD_PATH;
}
mxrio_msg_t msg;
memset(&msg, 0, MXRIO_HDR_SZ);
msg.op = op;
msg.datalen = len;
msg.arg = O_PIPELINE | flags;
msg.arg2.mode = mode;
msg.hcount = 1;
msg.handle[0] = cnxn;
memcpy(msg.data, name, len);
mx_status_t r;
if ((r = mx_channel_write(svc, 0, &msg, MXRIO_HDR_SZ + msg.datalen, msg.handle, 1)) < 0) {
mx_handle_close(cnxn);
return r;
}
return MX_OK;
}
mx_status_t mxio_service_connect(const char* svcpath, mx_handle_t h) {
if (svcpath == NULL) {
mx_handle_close(h);
return MX_ERR_INVALID_ARGS;
}
// Otherwise attempt to connect through the root namespace
if (mxio_root_ns != NULL) {
return mxio_ns_connect(mxio_root_ns, svcpath, h);
}
// Otherwise we fail
mx_handle_close(h);
return MX_ERR_NOT_FOUND;
}
mx_status_t mxio_service_connect_at(mx_handle_t dir, const char* path, mx_handle_t h) {
if (path == NULL) {
mx_handle_close(h);
return MX_ERR_INVALID_ARGS;
}
if (dir == MX_HANDLE_INVALID) {
mx_handle_close(h);
return MX_ERR_UNAVAILABLE;
}
return mxrio_connect(dir, h, MXRIO_OPEN, O_RDWR, 0755, path);
}
mx_handle_t mxio_service_clone(mx_handle_t svc) {
mx_handle_t cli, srv;
mx_status_t r;
if (svc == MX_HANDLE_INVALID) {
return MX_HANDLE_INVALID;
}
if ((r = mx_channel_create(0, &cli, &srv)) < 0) {
return MX_HANDLE_INVALID;
}
if ((r = mxrio_connect(svc, srv, MXRIO_CLONE, O_RDWR, 0755, "")) < 0) {
mx_handle_close(cli);
return MX_HANDLE_INVALID;
}
return cli;
}
mx_status_t mxrio_misc(mxio_t* io, uint32_t op, int64_t off,
uint32_t maxreply, void* ptr, size_t len) {
mxrio_t* rio = (mxrio_t*)io;
mxrio_msg_t msg;
mx_status_t r;
if ((len > MXIO_CHUNK_SIZE) || (maxreply > MXIO_CHUNK_SIZE)) {
return MX_ERR_INVALID_ARGS;
}
memset(&msg, 0, MXRIO_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 MXRIO_RENAME:
case MXRIO_LINK:
// As a hack, 'Rename' and 'Link' take token handles through
// the offset argument.
msg.handle[0] = (mx_handle_t) off;
msg.hcount = 1;
}
if ((r = mxrio_txn(rio, &msg)) < 0) {
return r;
}
switch (op) {
case MXRIO_MMAP: {
// Ops which receive single handles:
if ((msg.hcount != 1) || (msg.datalen > maxreply)) {
discard_handles(msg.handle, msg.hcount);
return MX_ERR_IO;
}
r = msg.handle[0];
memcpy(ptr, msg.data, msg.datalen);
break;
}
case MXRIO_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 MX_ERR_IO;
}
if (ptr && msg.datalen > 0) {
memcpy(ptr, msg.data, msg.datalen);
}
}
return r;
}
mx_status_t mxio_create_fd(mx_handle_t* handles, uint32_t* types, size_t hcount,
int* fd_out) {
mxio_t* io;
mx_status_t r;
int fd;
uint32_t type;
switch (PA_HND_TYPE(types[0])) {
case PA_MXIO_REMOTE:
type = MXIO_PROTOCOL_REMOTE;
break;
case PA_MXIO_PIPE:
type = MXIO_PROTOCOL_PIPE;
break;
case PA_MXIO_SOCKET:
type = MXIO_PROTOCOL_SOCKET_CONNECTED;
break;
default:
r = MX_ERR_IO;
goto fail;
}
if ((r = mxio_from_handles(type, handles, hcount, NULL, 0, &io)) != MX_OK) {
goto fail;
}
fd = mxio_bind_to_fd(io, -1, 0);
if (fd < 0) {
mxio_close(io);
mxio_release(io);
return MX_ERR_BAD_STATE;
}
*fd_out = fd;
return MX_OK;
fail:
for (size_t i = 0; i < hcount; i++) {
mx_handle_close(handles[i]);
}
return r;
}
mx_status_t mxio_from_handles(uint32_t type, mx_handle_t* handles, int hcount,
void* extra, uint32_t esize, mxio_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.
mx_status_t r;
mxio_t* io;
switch (type) {
case MXIO_PROTOCOL_REMOTE:
if (hcount == 1) {
io = mxio_remote_create(handles[0], 0);
xprintf("rio (%x,%x) -> %p\n", handles[0], 0, io);
} else if (hcount == 2) {
io = mxio_remote_create(handles[0], handles[1]);
xprintf("rio (%x,%x) -> %p\n", handles[0], handles[1], io);
} else {
r = MX_ERR_INVALID_ARGS;
break;
}
if (io == NULL) {
return MX_ERR_NO_RESOURCES;
} else {
*out = io;
return MX_OK;
}
break;
case MXIO_PROTOCOL_SERVICE:
if (hcount != 1) {
r = MX_ERR_INVALID_ARGS;
break;
} else if ((*out = mxio_service_create(handles[0])) == NULL) {
return MX_ERR_NO_RESOURCES;
} else {
return MX_OK;
}
break;
case MXIO_PROTOCOL_PIPE:
if (hcount != 1) {
r = MX_ERR_INVALID_ARGS;
break;
} else if ((*out = mxio_pipe_create(handles[0])) == NULL) {
return MX_ERR_NO_RESOURCES;
} else {
return MX_OK;
}
case MXIO_PROTOCOL_VMOFILE: {
mx_off_t* args = extra;
if ((hcount != 2) || (esize != (sizeof(mx_off_t) * 2))) {
r = MX_ERR_INVALID_ARGS;
break;
}
// Currently, VMO Files don't use a client-side control channel.
mx_handle_close(handles[0]);
if ((*out = mxio_vmofile_create(handles[1], args[0], args[1])) == NULL) {
return MX_ERR_NO_RESOURCES;
} else {
return MX_OK;
}
}
case MXIO_PROTOCOL_SOCKET_CONNECTED:
case MXIO_PROTOCOL_SOCKET: {
int flags = (type == MXIO_PROTOCOL_SOCKET_CONNECTED) ? MXIO_FLAG_SOCKET_CONNECTED : 0;
if (hcount == 1) {
io = mxio_socket_create(handles[0], MX_HANDLE_INVALID, flags);
} else if (hcount == 2) {
io = mxio_socket_create(handles[0], handles[1], flags);
} else {
r = MX_ERR_INVALID_ARGS;
break;
}
if (io == NULL) {
return MX_ERR_NO_RESOURCES;
} else {
*out = io;
return MX_OK;
}
}
default:
r = MX_ERR_NOT_SUPPORTED;
break;
}
discard_handles(handles, hcount);
return r;
}
mx_status_t mxrio_getobject(mx_handle_t rio_h, uint32_t op, const char* name,
int32_t flags, uint32_t mode,
mxrio_object_t* info) {
if (name == NULL) {
return MX_ERR_INVALID_ARGS;
}
size_t len = strlen(name);
if (len >= PATH_MAX) {
return MX_ERR_BAD_PATH;
}
if (flags & O_PIPELINE) {
mx_handle_t h0, h1;
mx_status_t r;
if ((r = mx_channel_create(0, &h0, &h1)) < 0) {
return r;
}
if ((r = mxrio_connect(rio_h, h1, MXRIO_OPEN, flags, mode, name)) < 0) {
mx_handle_close(h0);
return r;
}
// fake up a reply message since pipelined opens don't generate one
info->status = MX_OK;
info->type = MXIO_PROTOCOL_REMOTE;
info->esize = 0;
info->hcount = 1;
info->handle[0] = h0;
return MX_OK;
} else {
mxrio_msg_t msg;
memset(&msg, 0, MXRIO_HDR_SZ);
msg.op = op;
msg.datalen = len;
msg.arg = flags;
msg.arg2.mode = mode;
memcpy(msg.data, name, len);
return mxrio_reply_channel_call(rio_h, &msg, info);
}
}
mx_status_t mxrio_open_handle(mx_handle_t h, const char* path, int32_t flags,
uint32_t mode, mxio_t** out) {
mxrio_object_t info;
mx_status_t r = mxrio_getobject(h, MXRIO_OPEN, path, flags, mode, &info);
if (r < 0) {
return r;
}
return mxio_from_handles(info.type, info.handle, info.hcount, info.extra, info.esize, out);
}
mx_status_t mxrio_open_handle_raw(mx_handle_t h, const char* path, int32_t flags,
uint32_t mode, mx_handle_t *out) {
mxrio_object_t info;
mx_status_t r = mxrio_getobject(h, MXRIO_OPEN, path, flags, mode, &info);
if (r < 0) {
return r;
}
if ((info.type == MXIO_PROTOCOL_REMOTE) && (info.hcount > 0)) {
for (unsigned n = 1; n < info.hcount; n++) {
mx_handle_close(info.handle[n]);
}
*out = info.handle[0];
return MX_OK;
}
for (unsigned n = 0; n < info.hcount; n++) {
mx_handle_close(info.handle[n]);
}
return MX_ERR_WRONG_TYPE;
}
mx_status_t mxrio_open(mxio_t* io, const char* path, int32_t flags, uint32_t mode, mxio_t** out) {
mxrio_t* rio = (void*)io;
mxrio_object_t info;
mx_status_t r = mxrio_getobject(rio->h, MXRIO_OPEN, path, flags, mode, &info);
if (r < 0) {
return r;
}
return mxio_from_handles(info.type, info.handle, info.hcount, info.extra, info.esize, out);
}
static mx_status_t mxrio_clone(mxio_t* io, mx_handle_t* handles, uint32_t* types) {
mxrio_t* rio = (void*)io;
mxrio_object_t info;
mx_status_t r = mxrio_getobject(rio->h, MXRIO_CLONE, "", 0, 0, &info);
if (r < 0) {
return r;
}
for (unsigned i = 0; i < info.hcount; i++) {
types[i] = PA_MXIO_REMOTE;
}
memcpy(handles, info.handle, info.hcount * sizeof(mx_handle_t));
return info.hcount;
}
mx_status_t __mxrio_clone(mx_handle_t h, mx_handle_t* handles, uint32_t* types) {
mxrio_t rio;
rio.h = h;
return mxrio_clone(&rio.io, handles, types);
}
static mx_status_t mxrio_unwrap(mxio_t* io, mx_handle_t* handles, uint32_t* types) {
mxrio_t* rio = (void*)io;
mx_status_t r;
handles[0] = rio->h;
types[0] = PA_MXIO_REMOTE;
if (rio->h2 != 0) {
handles[1] = rio->h2;
types[1] = PA_MXIO_REMOTE;
r = 2;
} else {
r = 1;
}
free(io);
return r;
}
static void mxrio_wait_begin(mxio_t* io, uint32_t events, mx_handle_t* handle, mx_signals_t* _signals) {
mxrio_t* rio = (void*)io;
*handle = rio->h2;
mx_signals_t signals = 0;
// Manually add signals that don't fit within POLL_MASK
if (events & POLLRDHUP) {
signals |= MX_CHANNEL_PEER_CLOSED;
}
// POLLERR is always detected
*_signals = (((POLLERR | events) & POLL_MASK) << POLL_SHIFT) | signals;
}
static void mxrio_wait_end(mxio_t* io, mx_signals_t signals, uint32_t* _events) {
// Manually add events that don't fit within POLL_MASK
uint32_t events = 0;
if (signals & MX_CHANNEL_PEER_CLOSED) {
events |= POLLRDHUP;
}
*_events = ((signals >> POLL_SHIFT) & POLL_MASK) | events;
}
static mxio_ops_t mx_remote_ops = {
.read = mxrio_read,
.read_at = mxrio_read_at,
.write = mxrio_write,
.write_at = mxrio_write_at,
.recvfrom = mxio_default_recvfrom,
.sendto = mxio_default_sendto,
.recvmsg = mxio_default_recvmsg,
.sendmsg = mxio_default_sendmsg,
.misc = mxrio_misc,
.seek = mxrio_seek,
.close = mxrio_close,
.open = mxrio_open,
.clone = mxrio_clone,
.ioctl = mxrio_ioctl,
.wait_begin = mxrio_wait_begin,
.wait_end = mxrio_wait_end,
.unwrap = mxrio_unwrap,
.shutdown = mxio_default_shutdown,
.posix_ioctl = mxio_default_posix_ioctl,
.get_vmo = mxio_default_get_vmo,
};
mxio_t* mxio_remote_create(mx_handle_t h, mx_handle_t e) {
mxrio_t* rio = calloc(1, sizeof(*rio));
if (rio == NULL) {
mx_handle_close(h);
mx_handle_close(e);
return NULL;
}
rio->io.ops = &mx_remote_ops;
rio->io.magic = MXIO_MAGIC;
atomic_init(&rio->io.refcount, 1);
rio->h = h;
rio->h2 = e;
return &rio->io;
}