/* * Copyright (c) 2021 Nutanix Inc. All rights reserved. * * Authors: Thanos Makatos * Swapnil Ingle * Felipe Franciosi * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Nutanix nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * */ #include #include #include #include #include #include #include "tran_pipe.h" typedef struct { int in_fd; int out_fd; } tran_pipe_t; static int tran_pipe_send_iovec(int fd, uint16_t msg_id, bool is_reply, enum vfio_user_command cmd, struct iovec *iovecs, size_t nr_iovecs, int err) { struct vfio_user_header hdr = { .msg_id = msg_id }; ssize_t ret; if (nr_iovecs == 0) { iovecs = alloca(sizeof(*iovecs)); nr_iovecs = 1; } if (is_reply) { hdr.flags |= VFIO_USER_F_TYPE_REPLY; hdr.cmd = cmd; if (err != 0) { hdr.flags |= VFIO_USER_F_ERROR; hdr.error_no = err; } } else { hdr.cmd = cmd; hdr.flags |= VFIO_USER_F_TYPE_COMMAND; } iovecs[0].iov_base = &hdr; iovecs[0].iov_len = sizeof(hdr); ret = writev(fd, iovecs, nr_iovecs); /* Quieten static analysis. */ iovecs[0].iov_base = NULL; iovecs[0].iov_len = 0; if (ret == -1) { /* Treat a failed write due to EPIPE the same as a short write. */ if (errno == EPIPE) { return ERROR_INT(ECONNRESET); } return -1; } else if (ret < (ssize_t)hdr.msg_size) { return ERROR_INT(ECONNRESET); } return 0; } static int tran_pipe_get_msg(void *data, size_t len, int fd) { ssize_t ret; ret = read(fd, data, len); if (ret == -1) { return -1; } else if (ret == 0) { return ERROR_INT(ENOMSG); } else if ((size_t)ret < len) { return ERROR_INT(ECONNRESET); } return ret; } /* * Receive a vfio-user message. If "len" is set to non-zero, the message should * include data of that length, which is stored in the pre-allocated "data" * pointer. */ static int tran_pipe_recv(int fd, struct vfio_user_header *hdr, bool is_reply, uint16_t *msg_id, void *data, size_t *len) { int ret; /* FIXME if ret == -1 then fcntl can overwrite recv's errno */ ret = tran_pipe_get_msg(hdr, sizeof(*hdr), fd); if (ret < 0) { return ret; } if (is_reply) { if (msg_id != NULL && hdr->msg_id != *msg_id) { return ERROR_INT(EPROTO); } if ((hdr->flags & VFIO_USER_F_TYPE_MASK) != VFIO_USER_F_TYPE_REPLY) { return ERROR_INT(EINVAL); } if (hdr->flags & VFIO_USER_F_ERROR) { if (hdr->error_no <= 0) { hdr->error_no = EINVAL; } return ERROR_INT(hdr->error_no); } } else { if ((hdr->flags & VFIO_USER_F_TYPE_MASK) != VFIO_USER_F_TYPE_COMMAND) { return ERROR_INT(EINVAL); } if (msg_id != NULL) { *msg_id = hdr->msg_id; } } if (hdr->msg_size < sizeof(*hdr) || hdr->msg_size > SERVER_MAX_MSG_SIZE) { return ERROR_INT(EINVAL); } if (len != NULL && *len > 0 && hdr->msg_size > sizeof(*hdr)) { ret = read(fd, data, MIN(hdr->msg_size - sizeof(*hdr), *len)); if (ret < 0) { return -1; } else if (ret == 0) { return ERROR_INT(ENOMSG); } else if (*len != (size_t)ret) { return ERROR_INT(ECONNRESET); } *len = ret; } return 0; } /* * Like tran_pipe_recv(), but will automatically allocate reply data. */ static int tran_pipe_recv_alloc(int fd, struct vfio_user_header *hdr, bool is_reply, uint16_t *msg_id, void **datap, size_t *lenp) { void *data; size_t len; int ret; ret = tran_pipe_recv(fd, hdr, is_reply, msg_id, NULL, NULL); if (ret != 0) { return ret; } assert(hdr->msg_size >= sizeof(*hdr)); assert(hdr->msg_size <= SERVER_MAX_MSG_SIZE); len = hdr->msg_size - sizeof(*hdr); if (len == 0) { *datap = NULL; *lenp = 0; return 0; } data = calloc(1, len); if (data == NULL) { return -1; } ret = read(fd, data, len); if (ret < 0) { ret = errno; free(data); return ERROR_INT(ret); } else if (ret == 0) { free(data); return ERROR_INT(ENOMSG); } else if (len != (size_t)ret) { free(data); return ERROR_INT(ECONNRESET); } *datap = data; *lenp = len; return 0; } /* * FIXME: all these send/recv handlers need to be made robust against async * messages. */ static int tran_pipe_msg_iovec(tran_pipe_t *tp, uint16_t msg_id, enum vfio_user_command cmd, struct iovec *iovecs, size_t nr_iovecs, struct vfio_user_header *hdr, void *recv_data, size_t recv_len) { int ret = tran_pipe_send_iovec(tp->out_fd, msg_id, false, cmd, iovecs, nr_iovecs, 0); if (ret < 0) { return ret; } if (hdr == NULL) { hdr = alloca(sizeof(*hdr)); } return tran_pipe_recv(tp->in_fd, hdr, true, &msg_id, recv_data, &recv_len); } static int tran_pipe_init(vfu_ctx_t *vfu_ctx) { tran_pipe_t *tp = NULL; assert(vfu_ctx != NULL); tp = calloc(1, sizeof(tran_pipe_t)); if (tp == NULL) { return -1; } tp->in_fd = -1; tp->out_fd = -1; vfu_ctx->tran_data = tp; return 0; } static int tran_pipe_get_poll_fd(vfu_ctx_t *vfu_ctx) { tran_pipe_t *tp = vfu_ctx->tran_data; return tp->in_fd; } static int tran_pipe_attach(vfu_ctx_t *vfu_ctx) { tran_pipe_t *tp; int ret; assert(vfu_ctx != NULL); assert(vfu_ctx->tran_data != NULL); tp = vfu_ctx->tran_data; tp->in_fd = STDIN_FILENO; tp->out_fd = STDOUT_FILENO; ret = tran_negotiate(vfu_ctx, NULL); if (ret < 0) { ret = errno; tp->in_fd = -1; tp->out_fd = -1; return ERROR_INT(ret); } return 0; } static int tran_pipe_get_request_header(vfu_ctx_t *vfu_ctx, struct vfio_user_header *hdr, int *fds UNUSED, size_t *nr_fds) { tran_pipe_t *tp; assert(vfu_ctx != NULL); assert(vfu_ctx->tran_data != NULL); tp = vfu_ctx->tran_data; *nr_fds = 0; return tran_pipe_get_msg(hdr, sizeof(*hdr), tp->in_fd); } static int tran_pipe_recv_body(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) { tran_pipe_t *tp; int ret; assert(vfu_ctx != NULL); assert(vfu_ctx->tran_data != NULL); assert(msg != NULL); tp = vfu_ctx->tran_data; assert(msg->in.iov.iov_len <= SERVER_MAX_MSG_SIZE); msg->in.iov.iov_base = malloc(msg->in.iov.iov_len); if (msg->in.iov.iov_base == NULL) { return -1; } ret = read(tp->in_fd, msg->in.iov.iov_base, msg->in.iov.iov_len); if (ret < 0) { ret = errno; free(msg->in.iov.iov_base); msg->in.iov.iov_base = NULL; return ERROR_INT(ret); } else if (ret == 0) { free(msg->in.iov.iov_base); msg->in.iov.iov_base = NULL; return ERROR_INT(ENOMSG); } else if (ret != (int)msg->in.iov.iov_len) { vfu_log(vfu_ctx, LOG_ERR, "msg%#hx: short read: expected=%zu, actual=%d", msg->hdr.msg_id, msg->in.iov.iov_len, ret); free(msg->in.iov.iov_base); msg->in.iov.iov_base = NULL; return ERROR_INT(EINVAL); } return 0; } static int tran_pipe_recv_msg(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) { tran_pipe_t *tp; assert(vfu_ctx != NULL); assert(vfu_ctx->tran_data != NULL); assert(msg != NULL); tp = vfu_ctx->tran_data; if (tp->in_fd == -1) { vfu_log(vfu_ctx, LOG_ERR, "%s: not connected", __func__); return ERROR_INT(ENOTCONN); } return tran_pipe_recv_alloc(tp->in_fd, &msg->hdr, false, NULL, &msg->in.iov.iov_base, &msg->in.iov.iov_len); } static int tran_pipe_reply(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg, int err) { struct iovec *iovecs; size_t nr_iovecs; tran_pipe_t *tp; int ret; assert(vfu_ctx != NULL); assert(vfu_ctx->tran_data != NULL); assert(msg != NULL); tp = vfu_ctx->tran_data; /* First iovec entry is for msg header. */ nr_iovecs = (msg->nr_out_iovecs != 0) ? (msg->nr_out_iovecs + 1) : 2; iovecs = calloc(nr_iovecs, sizeof(*iovecs)); if (iovecs == NULL) { return -1; } if (msg->out_iovecs != NULL) { memcpy(iovecs + 1, msg->out_iovecs, msg->nr_out_iovecs * sizeof(*iovecs)); } else { iovecs[1].iov_base = msg->out.iov.iov_base; iovecs[1].iov_len = msg->out.iov.iov_len; } ret = tran_pipe_send_iovec(tp->out_fd, msg->hdr.msg_id, true, msg->hdr.cmd, iovecs, nr_iovecs, err); free(iovecs); return ret; } static int tran_pipe_send_msg(vfu_ctx_t *vfu_ctx, uint16_t msg_id, enum vfio_user_command cmd, void *send_data, size_t send_len, struct vfio_user_header *hdr, void *recv_data, size_t recv_len) { /* [0] is for the header. */ struct iovec iovecs[2] = { [1] = { .iov_base = send_data, .iov_len = send_len } }; tran_pipe_t *tp; assert(vfu_ctx != NULL); assert(vfu_ctx->tran_data != NULL); tp = vfu_ctx->tran_data; return tran_pipe_msg_iovec(tp, msg_id, cmd, iovecs, ARRAY_SIZE(iovecs), hdr, recv_data, recv_len); } static void tran_pipe_detach(vfu_ctx_t *vfu_ctx) { assert(vfu_ctx != NULL); } static void tran_pipe_fini(vfu_ctx_t *vfu_ctx) { assert(vfu_ctx != NULL); free(vfu_ctx->tran_data); vfu_ctx->tran_data = NULL; } struct transport_ops tran_pipe_ops = { .init = tran_pipe_init, .get_poll_fd = tran_pipe_get_poll_fd, .attach = tran_pipe_attach, .get_request_header = tran_pipe_get_request_header, .recv_body = tran_pipe_recv_body, .reply = tran_pipe_reply, .recv_msg = tran_pipe_recv_msg, .send_msg = tran_pipe_send_msg, .detach = tran_pipe_detach, .fini = tran_pipe_fini }; /* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */