// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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: 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); }