blob: ea670bd44ea44eec154d00ebbb83685ebf3ad9a5 [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 <fuchsia/io/c/fidl.h>
#include <lib/fdio/debug.h>
#include <lib/fdio/io.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/util.h>
#include <lib/fdio/vfs.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
// a zx_signals_t, if they are desired.
#define POLL_SHIFT 24
#define POLL_MASK 0x1F
static_assert(FDIO_CHUNK_SIZE >= PATH_MAX,
"FDIO_CHUNK_SIZE must be large enough to contain paths");
static_assert(fuchsia_io_VMO_FLAG_READ == ZX_VM_PERM_READ,
"Vmar / Vmo flags should be aligned");
static_assert(fuchsia_io_VMO_FLAG_WRITE == ZX_VM_PERM_WRITE,
"Vmar / Vmo flags should be aligned");
static_assert(fuchsia_io_VMO_FLAG_EXEC == ZX_VM_PERM_EXECUTE,
"Vmar / Vmo flags should be aligned");
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, "");
// Acquire the additional handle from |info|.
//
// Returns |ZX_OK| if a handle was returned.
// Returns |ZX_ERR_NOT_FOUND| if no handle can be returned.
static zx_status_t zxrio_object_extract_handle(const fuchsia_io_NodeInfo* info,
zx_handle_t* out) {
switch (info->tag) {
case fuchsia_io_NodeInfoTag_file:
if (info->file.event != ZX_HANDLE_INVALID) {
*out = info->file.event;
return ZX_OK;
}
break;
case fuchsia_io_NodeInfoTag_pipe:
if (info->pipe.socket != ZX_HANDLE_INVALID) {
*out = info->pipe.socket;
return ZX_OK;
}
break;
case fuchsia_io_NodeInfoTag_vmofile:
if (info->vmofile.vmo != ZX_HANDLE_INVALID) {
*out = info->vmofile.vmo;
return ZX_OK;
}
break;
case fuchsia_io_NodeInfoTag_device:
if (info->device.event != ZX_HANDLE_INVALID) {
*out = info->device.event;
return ZX_OK;
}
break;
}
return ZX_ERR_NOT_FOUND;
}
// 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 fuchsia_io_NodeCloneOrdinal:
r = fuchsia_io_NodeClone(svc, flags, cnxn);
break;
case fuchsia_io_DirectoryOpenOrdinal:
r = fuchsia_io_DirectoryOpen(svc, flags, mode, name, len, cnxn);
break;
default:
zx_handle_close(cnxn);
r = ZX_ERR_NOT_SUPPORTED;
}
return r;
}
// A one-way message which may be emitted by the server without an
// accompanying request. Optionally used as a part of the Open handshake.
typedef struct {
fuchsia_io_NodeOnOpenEvent primary;
fuchsia_io_NodeInfo extra;
} fdio_on_open_msg_t;
// Takes ownership of the optional |extra_handle|.
//
// Decodes the handle into |info|, if it exists and should
// be decoded.
static zx_status_t zxrio_decode_describe_handle(fdio_on_open_msg_t* info,
zx_handle_t extra_handle) {
bool have_handle = (extra_handle != ZX_HANDLE_INVALID);
bool want_handle = false;
zx_handle_t* handle_target = NULL;
switch (info->extra.tag) {
// Case: No extra handles expected
case fuchsia_io_NodeInfoTag_service:
case fuchsia_io_NodeInfoTag_directory:
break;
// Case: Extra handles optional
case fuchsia_io_NodeInfoTag_file:
handle_target = &info->extra.file.event;
goto handle_optional;
case fuchsia_io_NodeInfoTag_device:
handle_target = &info->extra.device.event;
goto handle_optional;
handle_optional:
want_handle = *handle_target == FIDL_HANDLE_PRESENT;
break;
// Case: Extra handles required
case fuchsia_io_NodeInfoTag_pipe:
handle_target = &info->extra.pipe.socket;
goto handle_required;
case fuchsia_io_NodeInfoTag_vmofile:
handle_target = &info->extra.vmofile.vmo;
goto handle_required;
handle_required:
want_handle = *handle_target == FIDL_HANDLE_PRESENT;
if (!want_handle) {
goto fail;
}
break;
default:
printf("Unexpected protocol type opening connection\n");
goto fail;
}
if (have_handle != want_handle) {
goto fail;
}
if (have_handle) {
*handle_target = extra_handle;
}
return ZX_OK;
fail:
if (have_handle) {
zx_handle_close(extra_handle);
}
return ZX_ERR_IO;
}
// Wait/Read from a new client connection, with the expectation of
// acquiring an Open response.
//
// Shared implementation between RemoteIO and FIDL, since the response
// message is aligned.
//
// Does not close |h|, even on error.
static zx_status_t zxrio_process_open_response(zx_handle_t h, fdio_on_open_msg_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 < sizeof(fuchsia_io_NodeOnOpenEvent) ||
info->primary.hdr.ordinal != fuchsia_io_NodeOnOpenOrdinal) {
r = ZX_ERR_IO;
} else {
r = info->primary.s;
}
if (dsize != sizeof(fdio_on_open_msg_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 "fdio_on_open_msg_t" and "fuchsia_io_NodeOnOpenEvent"
// are aligned enough to be compatible.
//
// This is somewhat complicated by the fact that the "fuchsia_io_NodeOnOpenEvent"
// object has an optional "fuchsia_io_NodeInfo" secondary which exists immediately
// following the struct.
static_assert(__builtin_offsetof(fdio_on_open_msg_t, extra) ==
FIDL_ALIGN(sizeof(fuchsia_io_NodeOnOpenEvent)),
"RIO Description message doesn't align with FIDL response secondary");
// Connection::NodeDescribe also relies on these static_asserts.
// fidl_describe also relies on these static_asserts.
return zxrio_decode_describe_handle(info, extra_handle);
}
__EXPORT
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;
}
__EXPORT
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, fuchsia_io_DirectoryOpenOrdinal, ZX_FS_RIGHT_READABLE |
ZX_FS_RIGHT_WRITABLE, 0755, path);
}
__EXPORT
zx_status_t fdio_open(const char* path, uint32_t flags, zx_handle_t h) {
if (path == 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, path, flags, h);
}
// Otherwise we fail
zx_handle_close(h);
return ZX_ERR_NOT_FOUND;
}
__EXPORT
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, fuchsia_io_DirectoryOpenOrdinal, flags, 0755, path);
}
__EXPORT
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, fuchsia_io_NodeCloneOrdinal, ZX_FS_RIGHT_READABLE |
ZX_FS_RIGHT_WRITABLE, 0755, "")) < 0) {
zx_handle_close(cli);
return ZX_HANDLE_INVALID;
}
return cli;
}
__EXPORT
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, fuchsia_io_NodeCloneOrdinal, ZX_FS_RIGHT_READABLE |
ZX_FS_RIGHT_WRITABLE, 0755, "");
}
zx_status_t fdio_from_channel(zx_handle_t channel, fdio_t** out_io) {
fuchsia_io_NodeInfo info;
memset(&info, 0, sizeof(info));
zx_status_t status = fuchsia_io_NodeDescribe(channel, &info);
if (status != ZX_OK) {
zx_handle_close(channel);
return status;
}
zx_handle_t event = ZX_HANDLE_INVALID;
switch (info.tag) {
case fuchsia_io_NodeInfoTag_file:
event = info.file.event;
break;
case fuchsia_io_NodeInfoTag_device:
event = info.device.event;
break;
case fuchsia_io_NodeInfoTag_vmofile: {
uint64_t seek = 0u;
zx_status_t io_status = fuchsia_io_FileSeek(
channel, 0, fuchsia_io_SeekOrigin_START, &status, &seek);
if (io_status != ZX_OK) {
status = io_status;
}
if (status != ZX_OK) {
zx_handle_close(channel);
zx_handle_close(info.vmofile.vmo);
return status;
}
*out_io = fdio_vmofile_create(channel, info.vmofile.vmo,
info.vmofile.offset, info.vmofile.length,
seek);
return ZX_OK;
}
default:
event = ZX_HANDLE_INVALID;
break;
}
*out_io = fdio_remote_create(channel, event);
return ZX_OK;
}
zx_status_t fdio_from_socket(zx_handle_t socket, fdio_t** out_io) {
zx_info_socket_t info;
memset(&info, 0, sizeof(info));
zx_status_t status = zx_object_get_info(socket, ZX_INFO_SOCKET, &info, sizeof(info), NULL, NULL);
if (status != ZX_OK) {
zx_handle_close(socket);
return status;
}
fdio_t* io = NULL;
if ((info.options & ZX_SOCKET_HAS_CONTROL) != 0) {
// If the socket has a control plane, then the socket is either
// a stream or a datagram socket.
if ((info.options & ZX_SOCKET_DATAGRAM) != 0) {
io = fdio_socket_create_datagram(socket, IOFLAG_SOCKET_CONNECTED);
} else {
io = fdio_socket_create_stream(socket, IOFLAG_SOCKET_CONNECTED);
}
} else {
// Without a control plane, the socket is a pipe.
io = fdio_pipe_create(socket);
}
if (!io) {
return ZX_ERR_NO_RESOURCES;
}
*out_io = io;
return ZX_OK;
}
// Create a fdio (if possible) from handles and info.
//
// The Control channel is provided in |handle|, and auxiliary
// handles may be provided in the |info| object.
//
// This function always takes control of all handles.
// They are transferred into the |out| object on success,
// or closed on failure.
static zx_status_t fdio_from_handles(zx_handle_t handle, fuchsia_io_NodeInfo* info,
fdio_t** out) {
// All failure cases which 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 fuchsia_io_NodeInfoTag_directory:
if (handle == ZX_HANDLE_INVALID) {
r = ZX_ERR_INVALID_ARGS;
break;
}
io = fdio_dir_create(handle);
xprintf("rio (%x,%x) -> %p\n", handle, 0, io);
if (io == NULL) {
return ZX_ERR_NO_RESOURCES;
}
*out = io;
return ZX_OK;
case fuchsia_io_NodeInfoTag_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 fuchsia_io_NodeInfoTag_file:
if (info->file.event == 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.event);
xprintf("rio (%x,%x) -> %p\n", handle, info->file.event, io);
}
if (io == NULL) {
return ZX_ERR_NO_RESOURCES;
}
*out = io;
return ZX_OK;
case fuchsia_io_NodeInfoTag_device:
if (info->device.event == 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.event);
xprintf("rio (%x,%x) -> %p\n", handle, info->device.event, io);
}
if (io == NULL) {
return ZX_ERR_NO_RESOURCES;
}
*out = io;
return ZX_OK;
case fuchsia_io_NodeInfoTag_vmofile: {
if (info->vmofile.vmo == ZX_HANDLE_INVALID) {
r = ZX_ERR_INVALID_ARGS;
break;
}
*out = fdio_vmofile_create(handle, info->vmofile.vmo, info->vmofile.offset,
info->vmofile.length, 0u);
if (*out == NULL) {
return ZX_ERR_NO_RESOURCES;
}
return ZX_OK;
}
case fuchsia_io_NodeInfoTag_pipe: {
if (info->pipe.socket == ZX_HANDLE_INVALID) {
r = ZX_ERR_INVALID_ARGS;
break;
}
zx_handle_close(handle);
return fdio_from_socket(info->pipe.socket, out);
}
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;
}
__EXPORT
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;
fuchsia_io_NodeInfo info;
// 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_SOCKET:
info.tag = fuchsia_io_NodeInfoTag_pipe;
// Expected: Single socket handle
if (hcount != 1) {
r = ZX_ERR_INVALID_ARGS;
goto fail;
}
info.pipe.socket = handles[0];
break;
default:
r = ZX_ERR_IO;
goto fail;
}
if ((r = fdio_from_handles(ZX_HANDLE_INVALID, &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:
zx_handle_close_many(handles, hcount);
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,
fdio_on_open_msg_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 fuchsia_io_NodeCloneOrdinal:
r = fuchsia_io_NodeClone(svc, flags, cnxn);
break;
case fuchsia_io_DirectoryOpenOrdinal:
r = fuchsia_io_DirectoryOpen(svc, flags, mode, path, pathlen, cnxn);
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;
}
// Acquires a new connection to an object.
//
// Returns a description of the opened object in |info|, and
// the control channel to the object in |out|.
//
// |info| may contain an additional handle.
static zx_status_t zxrio_getobject(zx_handle_t rio_h, uint32_t op, const char* name,
uint32_t flags, uint32_t mode,
fdio_on_open_msg_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, op, flags, mode, name)) < 0) {
zx_handle_close(h0);
return r;
}
// fake up a reply message since pipelined opens don't generate one
info->primary.s = ZX_OK;
info->extra.tag = fuchsia_io_NodeInfoTag_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 = ZX_HANDLE_INVALID;
fdio_on_open_msg_t info;
zx_status_t r = zxrio_getobject(h, fuchsia_io_DirectoryOpenOrdinal, path, flags, mode, &info, &control_channel);
if (r < 0) {
return r;
}
return fdio_from_handles(control_channel, &info.extra, out);
}