diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/CMakeLists.txt | 9 | ||||
-rw-r--r-- | lib/libvfio-user.c | 15 | ||||
-rw-r--r-- | lib/private.h | 26 | ||||
-rw-r--r-- | lib/tran.c | 346 | ||||
-rw-r--r-- | lib/tran.h | 82 | ||||
-rw-r--r-- | lib/tran_pipe.c | 470 | ||||
-rw-r--r-- | lib/tran_pipe.h | 43 | ||||
-rw-r--r-- | lib/tran_sock.c | 332 | ||||
-rw-r--r-- | lib/tran_sock.h | 19 |
9 files changed, 1002 insertions, 340 deletions
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 5605a9f..968f911 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -44,14 +44,23 @@ set(LIBOBJS $<TARGET_OBJECTS:libvfio-user> $<TARGET_OBJECTS:migration> $<TARGET_OBJECTS:pci> + $<TARGET_OBJECTS:tran> $<TARGET_OBJECTS:tran_sock>) +if (WITH_TRAN_PIPE EQUAL 1) + set(LIBOBJS ${LIBOBJS} + $<TARGET_OBJECTS:tran_pipe>) + + add_library(tran_pipe OBJECT tran_pipe.c) +endif() + add_library(pci_caps OBJECT pci_caps.c) add_library(dma OBJECT dma.c) add_library(irq OBJECT irq.c) add_library(libvfio-user OBJECT libvfio-user.c) add_library(migration OBJECT migration.c) add_library(pci OBJECT pci.c) +add_library(tran OBJECT tran.c) add_library(tran_sock OBJECT tran_sock.c) add_library(vfio-user-shared SHARED ${LIBOBJS}) diff --git a/lib/libvfio-user.c b/lib/libvfio-user.c index 88e2350..5b95865 100644 --- a/lib/libvfio-user.c +++ b/lib/libvfio-user.c @@ -56,6 +56,7 @@ #include "migration.h" #include "pci.h" #include "private.h" +#include "tran_pipe.h" #include "tran_sock.h" static int @@ -1645,9 +1646,15 @@ vfu_create_ctx(vfu_trans_t trans, const char *path, int flags, void *pvt, return ERROR_PTR(EINVAL); } +#ifdef WITH_TRAN_PIPE + if (trans != VFU_TRANS_SOCK && trans != VFU_TRANS_PIPE) { + return ERROR_PTR(ENOTSUP); + } +#else if (trans != VFU_TRANS_SOCK) { return ERROR_PTR(ENOTSUP); } +#endif if (dev_type != VFU_DEV_TYPE_PCI) { return ERROR_PTR(ENOTSUP); @@ -1659,7 +1666,13 @@ vfu_create_ctx(vfu_trans_t trans, const char *path, int flags, void *pvt, } vfu_ctx->dev_type = dev_type; - vfu_ctx->tran = &tran_sock_ops; + if (trans == VFU_TRANS_SOCK) { + vfu_ctx->tran = &tran_sock_ops; + } else { +#ifdef WITH_TRAN_PIPE + vfu_ctx->tran = &tran_pipe_ops; +#endif + } vfu_ctx->tran_data = NULL; vfu_ctx->pvt = pvt; vfu_ctx->flags = flags; diff --git a/lib/private.h b/lib/private.h index 1d19e6f..4c483f2 100644 --- a/lib/private.h +++ b/lib/private.h @@ -65,7 +65,7 @@ * Outgoing requests are either stored in out.iov.iov_base, or out_iovecs. In * the latter case, the iovecs refer to data that should not be freed. */ -typedef struct { +typedef struct vfu_msg { /* in/out */ struct vfio_user_header hdr; @@ -81,30 +81,6 @@ typedef struct { size_t nr_out_iovecs; } vfu_msg_t; -struct transport_ops { - int (*init)(vfu_ctx_t *vfu_ctx); - - int (*get_poll_fd)(vfu_ctx_t *vfu_ctx); - - int (*attach)(vfu_ctx_t *vfu_ctx); - - int (*get_request_header)(vfu_ctx_t *vfu_ctx, struct vfio_user_header *hdr, - int *fds, size_t *nr_fds); - - int (*recv_body)(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg); - - int (*reply)(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg, int err); - - int (*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); - - void (*detach)(vfu_ctx_t *vfu_ctx); - void (*fini)(vfu_ctx_t *vfu_ctx); -}; - typedef struct { int err_efd; /* eventfd for irq err */ int req_efd; /* eventfd for irq req */ diff --git a/lib/tran.c b/lib/tran.c new file mode 100644 index 0000000..ba49fd6 --- /dev/null +++ b/lib/tran.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2020 Nutanix Inc. All rights reserved. + * + * Authors: Thanos Makatos <thanos@nutanix.com> + * Swapnil Ingle <swapnil.ingle@nutanix.com> + * Felipe Franciosi <felipe@nutanix.com> + * + * 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 <COPYRIGHT HOLDER> 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 <sys/param.h> +#include <sys/types.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <json.h> + +#include "libvfio-user.h" +#include "migration.h" +#include "tran.h" + +// FIXME: is this the value we want? +#define SERVER_MAX_FDS 8 + +/* + * Expected JSON is of the form: + * + * { + * "capabilities": { + * "max_msg_fds": 32, + * "max_data_xfer_size": 1048576 + * "migration": { + * "pgsize": 4096 + * } + * } + * } + * + * with everything being optional. Note that json_object_get_uint64() is only + * available in newer library versions, so we don't use it. + */ +int +tran_parse_version_json(const char *json_str, int *client_max_fdsp, + size_t *client_max_data_xfer_sizep, size_t *pgsizep) +{ + struct json_object *jo_caps = NULL; + struct json_object *jo_top = NULL; + struct json_object *jo = NULL; + int ret = EINVAL; + + if ((jo_top = json_tokener_parse(json_str)) == NULL) { + goto out; + } + + if (!json_object_object_get_ex(jo_top, "capabilities", &jo_caps)) { + ret = 0; + goto out; + } + + if (json_object_get_type(jo_caps) != json_type_object) { + goto out; + } + + if (json_object_object_get_ex(jo_caps, "max_msg_fds", &jo)) { + if (json_object_get_type(jo) != json_type_int) { + goto out; + } + + errno = 0; + *client_max_fdsp = (int)json_object_get_int64(jo); + + if (errno != 0) { + goto out; + } + } + + if (json_object_object_get_ex(jo_caps, "max_data_xfer_size", &jo)) { + if (json_object_get_type(jo) != json_type_int) { + goto out; + } + + errno = 0; + *client_max_data_xfer_sizep = (int)json_object_get_int64(jo); + + if (errno != 0) { + goto out; + } + } + if (json_object_object_get_ex(jo_caps, "migration", &jo)) { + struct json_object *jo2 = NULL; + + if (json_object_get_type(jo) != json_type_object) { + goto out; + } + + if (json_object_object_get_ex(jo, "pgsize", &jo2)) { + if (json_object_get_type(jo2) != json_type_int) { + goto out; + } + + errno = 0; + *pgsizep = (size_t)json_object_get_int64(jo2); + + if (errno != 0) { + goto out; + } + } + } + + ret = 0; + +out: + /* We just need to put our top-level object. */ + json_object_put(jo_top); + if (ret != 0) { + return ERROR_INT(ret); + } + return 0; +} + +static int +recv_version(vfu_ctx_t *vfu_ctx, uint16_t *msg_idp, + struct vfio_user_version **versionp) +{ + struct vfio_user_version *cversion = NULL; + vfu_msg_t msg = { { 0 } }; + int ret; + + *versionp = NULL; + + ret = vfu_ctx->tran->recv_msg(vfu_ctx, &msg); + + if (ret < 0) { + vfu_log(vfu_ctx, LOG_ERR, "failed to receive version: %m"); + return ret; + } + + *msg_idp = msg.hdr.msg_id; + + if (msg.hdr.cmd != VFIO_USER_VERSION) { + vfu_log(vfu_ctx, LOG_ERR, "msg%#hx: invalid cmd %hu (expected %u)", + *msg_idp, msg.hdr.cmd, VFIO_USER_VERSION); + ret = EINVAL; + goto out; + } + + if (msg.in.nr_fds != 0) { + vfu_log(vfu_ctx, LOG_ERR, + "msg%#hx: VFIO_USER_VERSION: sent with %zu fds", *msg_idp, + msg.in.nr_fds); + ret = EINVAL; + goto out; + } + + if (msg.in.iov.iov_len < sizeof(*cversion)) { + vfu_log(vfu_ctx, LOG_ERR, + "msg%#hx: VFIO_USER_VERSION: invalid size %lu", + *msg_idp, msg.in.iov.iov_len); + ret = EINVAL; + goto out; + } + + cversion = msg.in.iov.iov_base; + + if (cversion->major != LIB_VFIO_USER_MAJOR) { + vfu_log(vfu_ctx, LOG_ERR, "unsupported client major %hu (must be %u)", + cversion->major, LIB_VFIO_USER_MAJOR); + ret = EINVAL; + goto out; + } + + vfu_ctx->client_max_fds = 1; + vfu_ctx->client_max_data_xfer_size = VFIO_USER_DEFAULT_MAX_DATA_XFER_SIZE; + + if (msg.in.iov.iov_len > sizeof(*cversion)) { + const char *json_str = (const char *)cversion->data; + size_t len = msg.in.iov.iov_len - sizeof(*cversion); + size_t pgsize = 0; + + if (json_str[len - 1] != '\0') { + vfu_log(vfu_ctx, LOG_ERR, "ignoring invalid JSON from client"); + ret = EINVAL; + goto out; + } + + ret = tran_parse_version_json(json_str, &vfu_ctx->client_max_fds, + &vfu_ctx->client_max_data_xfer_size, + &pgsize); + + if (ret < 0) { + /* No client-supplied strings in the log for release build. */ +#ifdef DEBUG + vfu_log(vfu_ctx, LOG_ERR, "failed to parse client JSON \"%s\"", + json_str); +#else + vfu_log(vfu_ctx, LOG_ERR, "failed to parse client JSON"); +#endif + ret = errno; + goto out; + } + + if (vfu_ctx->migration != NULL && pgsize != 0) { + ret = migration_set_pgsize(vfu_ctx->migration, pgsize); + + if (ret != 0) { + vfu_log(vfu_ctx, LOG_ERR, "refusing client page size of %zu", + pgsize); + ret = errno; + goto out; + } + } + + // FIXME: is the code resilient against ->client_max_fds == 0? + if (vfu_ctx->client_max_fds < 0 || + vfu_ctx->client_max_fds > VFIO_USER_CLIENT_MAX_MSG_FDS_LIMIT) { + vfu_log(vfu_ctx, LOG_ERR, "refusing client max_msg_fds of %d", + vfu_ctx->client_max_fds); + ret = EINVAL; + goto out; + } + } + +out: + if (ret != 0) { + vfu_msg_t rmsg = { { 0 } }; + size_t i; + + rmsg.hdr = msg.hdr; + + (void) vfu_ctx->tran->reply(vfu_ctx, &rmsg, ret); + + for (i = 0; i < msg.in.nr_fds; i++) { + if (msg.in.fds[i] != -1) { + close(msg.in.fds[i]); + } + } + + free(msg.in.iov.iov_base); + + *versionp = NULL; + return ERROR_INT(ret); + } + + *versionp = cversion; + return 0; +} + +static int +send_version(vfu_ctx_t *vfu_ctx, uint16_t msg_id, + struct vfio_user_version *cversion) +{ + struct vfio_user_version sversion = { 0 }; + struct iovec iovecs[2] = { { 0 } }; + char server_caps[1024]; + vfu_msg_t msg = { { 0 } }; + int slen; + + if (vfu_ctx->migration == NULL) { + slen = snprintf(server_caps, sizeof(server_caps), + "{" + "\"capabilities\":{" + "\"max_msg_fds\":%u," + "\"max_data_xfer_size\":%u" + "}" + "}", SERVER_MAX_FDS, SERVER_MAX_DATA_XFER_SIZE); + } else { + slen = snprintf(server_caps, sizeof(server_caps), + "{" + "\"capabilities\":{" + "\"max_msg_fds\":%u," + "\"max_data_xfer_size\":%u," + "\"migration\":{" + "\"pgsize\":%zu" + "}" + "}" + "}", SERVER_MAX_FDS, SERVER_MAX_DATA_XFER_SIZE, + migration_get_pgsize(vfu_ctx->migration)); + } + + // FIXME: we should save the client minor here, and check that before trying + // to send unsupported things. + sversion.major = LIB_VFIO_USER_MAJOR; + sversion.minor = MIN(cversion->minor, LIB_VFIO_USER_MINOR); + + iovecs[0].iov_base = &sversion; + iovecs[0].iov_len = sizeof(sversion); + iovecs[1].iov_base = server_caps; + /* Include the NUL. */ + iovecs[1].iov_len = slen + 1; + + msg.hdr.cmd = VFIO_USER_VERSION; + msg.hdr.msg_id = msg_id; + msg.out_iovecs = iovecs; + msg.nr_out_iovecs = 2; + + return vfu_ctx->tran->reply(vfu_ctx, &msg, 0); +} + +int +tran_negotiate(vfu_ctx_t *vfu_ctx) +{ + struct vfio_user_version *client_version = NULL; + uint16_t msg_id = 0x0bad; + int ret; + + ret = recv_version(vfu_ctx, &msg_id, &client_version); + + if (ret < 0) { + vfu_log(vfu_ctx, LOG_ERR, "failed to recv version: %m"); + return ret; + } + + ret = send_version(vfu_ctx, msg_id, client_version); + + free(client_version); + + if (ret < 0) { + vfu_log(vfu_ctx, LOG_ERR, "failed to send version: %m"); + } + + return ret; +} + +/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/lib/tran.h b/lib/tran.h new file mode 100644 index 0000000..fee96e8 --- /dev/null +++ b/lib/tran.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Nutanix Inc. All rights reserved. + * + * Authors: Thanos Makatos <thanos@nutanix.com> + * Swapnil Ingle <swapnil.ingle@nutanix.com> + * Felipe Franciosi <felipe@nutanix.com> + * + * 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 <COPYRIGHT HOLDER> 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. + * + */ + +#ifndef LIB_VFIO_USER_TRAN_H +#define LIB_VFIO_USER_TRAN_H + +#include "libvfio-user.h" +#include "private.h" + +struct transport_ops { + int (*init)(vfu_ctx_t *vfu_ctx); + + int (*get_poll_fd)(vfu_ctx_t *vfu_ctx); + + int (*attach)(vfu_ctx_t *vfu_ctx); + + int (*get_request_header)(vfu_ctx_t *vfu_ctx, struct vfio_user_header *hdr, + int *fds, size_t *nr_fds); + + int (*recv_body)(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg); + + int (*reply)(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg, int err); + + int (*recv_msg)(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg); + + int (*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); + + void (*detach)(vfu_ctx_t *vfu_ctx); + void (*fini)(vfu_ctx_t *vfu_ctx); +}; + +/* The largest number of fd's we are prepared to receive. */ +// FIXME: value? +#define VFIO_USER_CLIENT_MAX_MSG_FDS_LIMIT (1024) + +/* + * Parse JSON supplied from the other side into the known parameters. Note: they + * will not be set if not found in the JSON. + */ +int +tran_parse_version_json(const char *json_str, int *client_max_fdsp, + size_t *client_max_data_xfer_sizep, size_t *pgsizep); + +int +tran_negotiate(vfu_ctx_t *vfu_ctx); + +#endif /* LIB_VFIO_USER_TRAN_H */ + +/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/lib/tran_pipe.c b/lib/tran_pipe.c new file mode 100644 index 0000000..5b47142 --- /dev/null +++ b/lib/tran_pipe.c @@ -0,0 +1,470 @@ +/* + * Copyright (c) 2021 Nutanix Inc. All rights reserved. + * + * Authors: Thanos Makatos <thanos@nutanix.com> + * Swapnil Ingle <swapnil.ingle@nutanix.com> + * Felipe Franciosi <felipe@nutanix.com> + * + * 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 <COPYRIGHT HOLDER> 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 <sys/param.h> +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <strings.h> + +#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.type = VFIO_USER_F_TYPE_REPLY; + hdr.cmd = cmd; + if (err != 0) { + hdr.flags.error = 1U; + hdr.error_no = err; + } + } else { + hdr.cmd = cmd; + hdr.flags.type = VFIO_USER_F_TYPE_COMMAND; + } + + iovecs[0].iov_base = &hdr; + iovecs[0].iov_len = sizeof(hdr); + + ret = writev(fd, iovecs, nr_iovecs); + + 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 < 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.type != VFIO_USER_F_TYPE_REPLY) { + return ERROR_INT(EINVAL); + } + + if (hdr->flags.error == 1U) { + if (hdr->error_no <= 0) { + hdr->error_no = EINVAL; + } + return ERROR_INT(hdr->error_no); + } + } else { + if (hdr->flags.type != 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; + int ret = 0; + + assert(vfu_ctx != NULL); + + tp = calloc(1, sizeof(tran_pipe_t)); + + if (tp == NULL) { + return -1; + } + + tp->in_fd = -1; + tp->out_fd = -1; + + if (ret != 0) { + free(tp); + return ERROR_INT(ret); + } + + 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); + 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) { + bcopy(msg->out_iovecs, iovecs + 1, + 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: */ diff --git a/lib/tran_pipe.h b/lib/tran_pipe.h new file mode 100644 index 0000000..11c1eaf --- /dev/null +++ b/lib/tran_pipe.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Nutanix Inc. All rights reserved. + * + * Authors: Thanos Makatos <thanos@nutanix.com> + * Swapnil Ingle <swapnil.ingle@nutanix.com> + * Felipe Franciosi <felipe@nutanix.com> + * + * 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 <COPYRIGHT HOLDER> 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. + * + */ + +#ifndef LIB_VFIO_USER_TRAN_PIPE_H +#define LIB_VFIO_USER_TRAN_PIPE_H + +#include "libvfio-user.h" +#include "tran.h" + +extern struct transport_ops tran_pipe_ops; + +#endif /* LIB_VFIO_USER_TRAN_PIPE_H */ + +/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/lib/tran_sock.c b/lib/tran_sock.c index 19bf370..b894fb3 100644 --- a/lib/tran_sock.c +++ b/lib/tran_sock.c @@ -30,27 +30,19 @@ * */ -#include <assert.h> -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <json.h> -#include <stdio.h> -#include <string.h> #include <sys/param.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/un.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> -#include "libvfio-user.h" -#include "migration.h" -#include "private.h" #include "tran_sock.h" -// FIXME: is this the value we want? -#define SERVER_MAX_FDS 8 - typedef struct { int listen_fd; int conn_fd; @@ -145,18 +137,6 @@ tran_sock_send(int sock, uint16_t msg_id, bool is_reply, ARRAY_SIZE(iovecs), NULL, 0, 0); } -/* - * Send an empty reply back to the other end with the given errno. - */ -static int -tran_sock_send_error(int sock, uint16_t msg_id, - enum vfio_user_command cmd, - int error) -{ - return tran_sock_send_iovec(sock, msg_id, true, cmd, - NULL, 0, NULL, 0, error); -} - static int get_msg(void *data, size_t len, int *fds, size_t *nr_fds, int sock_fd, int sock_flags) @@ -395,8 +375,7 @@ tran_sock_init(vfu_ctx_t *vfu_ctx) ts = calloc(1, sizeof(tran_sock_t)); if (ts == NULL) { - ret = errno; - goto out; + return -1; } ts->listen_fd = -1; @@ -462,277 +441,6 @@ tran_sock_get_poll_fd(vfu_ctx_t *vfu_ctx) return ts->listen_fd; } -/* - * Expected JSON is of the form: - * - * { - * "capabilities": { - * "max_msg_fds": 32, - * "max_data_xfer_size": 1048576 - * "migration": { - * "pgsize": 4096 - * } - * } - * } - * - * with everything being optional. Note that json_object_get_uint64() is only - * available in newer library versions, so we don't use it. - */ -int -tran_parse_version_json(const char *json_str, int *client_max_fdsp, - size_t *client_max_data_xfer_sizep, size_t *pgsizep) -{ - struct json_object *jo_caps = NULL; - struct json_object *jo_top = NULL; - struct json_object *jo = NULL; - int ret = EINVAL; - - if ((jo_top = json_tokener_parse(json_str)) == NULL) { - goto out; - } - - if (!json_object_object_get_ex(jo_top, "capabilities", &jo_caps)) { - ret = 0; - goto out; - } - - if (json_object_get_type(jo_caps) != json_type_object) { - goto out; - } - - if (json_object_object_get_ex(jo_caps, "max_msg_fds", &jo)) { - if (json_object_get_type(jo) != json_type_int) { - goto out; - } - - errno = 0; - *client_max_fdsp = (int)json_object_get_int64(jo); - - if (errno != 0) { - goto out; - } - } - - if (json_object_object_get_ex(jo_caps, "max_data_xfer_size", &jo)) { - if (json_object_get_type(jo) != json_type_int) { - goto out; - } - - errno = 0; - *client_max_data_xfer_sizep = (int)json_object_get_int64(jo); - - if (errno != 0) { - goto out; - } - } - if (json_object_object_get_ex(jo_caps, "migration", &jo)) { - struct json_object *jo2 = NULL; - - if (json_object_get_type(jo) != json_type_object) { - goto out; - } - - if (json_object_object_get_ex(jo, "pgsize", &jo2)) { - if (json_object_get_type(jo2) != json_type_int) { - goto out; - } - - errno = 0; - *pgsizep = (size_t)json_object_get_int64(jo2); - - if (errno != 0) { - goto out; - } - } - } - - ret = 0; - -out: - /* We just need to put our top-level object. */ - json_object_put(jo_top); - if (ret != 0) { - return ERROR_INT(ret); - } - return 0; -} - -static int -recv_version(vfu_ctx_t *vfu_ctx, int sock, uint16_t *msg_idp, - struct vfio_user_version **versionp) -{ - struct vfio_user_version *cversion = NULL; - struct vfio_user_header hdr; - size_t vlen = 0; - int ret; - - *versionp = NULL; - - ret = tran_sock_recv_alloc(sock, &hdr, false, msg_idp, - (void **)&cversion, &vlen); - - if (ret < 0) { - vfu_log(vfu_ctx, LOG_ERR, "failed to receive version: %m"); - return ret; - } - - if (hdr.cmd != VFIO_USER_VERSION) { - vfu_log(vfu_ctx, LOG_ERR, "msg%#hx: invalid cmd %hu (expected %u)", - *msg_idp, hdr.cmd, VFIO_USER_VERSION); - ret = EINVAL; - goto out; - } - - if (vlen < sizeof(*cversion)) { - vfu_log(vfu_ctx, LOG_ERR, - "msg%#hx: VFIO_USER_VERSION: invalid size %lu", *msg_idp, vlen); - ret = EINVAL; - goto out; - } - - if (cversion->major != LIB_VFIO_USER_MAJOR) { - vfu_log(vfu_ctx, LOG_ERR, "unsupported client major %hu (must be %u)", - cversion->major, LIB_VFIO_USER_MAJOR); - ret = EINVAL; - goto out; - } - - vfu_ctx->client_max_fds = 1; - vfu_ctx->client_max_data_xfer_size = VFIO_USER_DEFAULT_MAX_DATA_XFER_SIZE; - - if (vlen > sizeof(*cversion)) { - const char *json_str = (const char *)cversion->data; - size_t len = vlen - sizeof(*cversion); - size_t pgsize = 0; - - if (json_str[len - 1] != '\0') { - vfu_log(vfu_ctx, LOG_ERR, "ignoring invalid JSON from client"); - ret = EINVAL; - goto out; - } - - ret = tran_parse_version_json(json_str, &vfu_ctx->client_max_fds, - &vfu_ctx->client_max_data_xfer_size, - &pgsize); - - if (ret < 0) { - /* No client-supplied strings in the log for release build. */ -#ifdef DEBUG - vfu_log(vfu_ctx, LOG_ERR, "failed to parse client JSON \"%s\"", - json_str); -#else - vfu_log(vfu_ctx, LOG_ERR, "failed to parse client JSON"); -#endif - ret = errno; - goto out; - } - - if (vfu_ctx->migration != NULL && pgsize != 0) { - ret = migration_set_pgsize(vfu_ctx->migration, pgsize); - - if (ret != 0) { - vfu_log(vfu_ctx, LOG_ERR, "refusing client page size of %zu", - pgsize); - ret = errno; - goto out; - } - } - - // FIXME: is the code resilient against ->client_max_fds == 0? - if (vfu_ctx->client_max_fds < 0 || - vfu_ctx->client_max_fds > VFIO_USER_CLIENT_MAX_MSG_FDS_LIMIT) { - vfu_log(vfu_ctx, LOG_ERR, "refusing client max_msg_fds of %d", - vfu_ctx->client_max_fds); - ret = EINVAL; - goto out; - } - } - -out: - if (ret != 0) { - // FIXME: spec, is it OK to just have the header? - (void) tran_sock_send_error(sock, *msg_idp, hdr.cmd, ret); - free(cversion); - *versionp = NULL; - return ERROR_INT(ret); - } - - *versionp = cversion; - return 0; -} - -static int -send_version(vfu_ctx_t *vfu_ctx, int sock, uint16_t msg_id, - struct vfio_user_version *cversion) -{ - struct vfio_user_version sversion = { 0 }; - struct iovec iovecs[3] = { { 0 } }; - char server_caps[1024]; - int slen; - - if (vfu_ctx->migration == NULL) { - slen = snprintf(server_caps, sizeof(server_caps), - "{" - "\"capabilities\":{" - "\"max_msg_fds\":%u," - "\"max_data_xfer_size\":%u" - "}" - "}", SERVER_MAX_FDS, SERVER_MAX_DATA_XFER_SIZE); - } else { - slen = snprintf(server_caps, sizeof(server_caps), - "{" - "\"capabilities\":{" - "\"max_msg_fds\":%u," - "\"max_data_xfer_size\":%u," - "\"migration\":{" - "\"pgsize\":%zu" - "}" - "}" - "}", SERVER_MAX_FDS, SERVER_MAX_DATA_XFER_SIZE, - migration_get_pgsize(vfu_ctx->migration)); - } - - // FIXME: we should save the client minor here, and check that before trying - // to send unsupported things. - sversion.major = LIB_VFIO_USER_MAJOR; - sversion.minor = MIN(cversion->minor, LIB_VFIO_USER_MINOR); - - /* [0] is for the header. */ - iovecs[1].iov_base = &sversion; - iovecs[1].iov_len = sizeof(sversion); - iovecs[2].iov_base = server_caps; - /* Include the NUL. */ - iovecs[2].iov_len = slen + 1; - - return tran_sock_send_iovec(sock, msg_id, true, VFIO_USER_VERSION, - iovecs, ARRAY_SIZE(iovecs), NULL, 0, 0); -} - -static int -negotiate(vfu_ctx_t *vfu_ctx, int sock) -{ - struct vfio_user_version *client_version = NULL; - uint16_t msg_id = 0x0bad; - int ret; - - ret = recv_version(vfu_ctx, sock, &msg_id, &client_version); - - if (ret < 0) { - vfu_log(vfu_ctx, LOG_ERR, "failed to recv version: %m"); - return ret; - } - - ret = send_version(vfu_ctx, sock, msg_id, client_version); - - free(client_version); - - if (ret < 0) { - vfu_log(vfu_ctx, LOG_ERR, "failed to send version: %m"); - } - - return ret; -} - static int tran_sock_attach(vfu_ctx_t *vfu_ctx) { @@ -755,7 +463,7 @@ tran_sock_attach(vfu_ctx_t *vfu_ctx) return -1; } - ret = negotiate(vfu_ctx, ts->conn_fd); + ret = tran_negotiate(vfu_ctx); if (ret < 0) { ret = errno; close(ts->conn_fd); @@ -806,6 +514,11 @@ tran_sock_recv_body(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) ts = vfu_ctx->tran_data; + if (ts->conn_fd == -1) { + vfu_log(vfu_ctx, LOG_ERR, "%s: not connected", __func__); + return ERROR_INT(ENOTCONN); + } + assert(msg->in.iov.iov_len <= SERVER_MAX_MSG_SIZE); msg->in.iov.iov_base = malloc(msg->in.iov.iov_len); @@ -837,6 +550,26 @@ tran_sock_recv_body(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) } static int +tran_sock_recv_msg(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) +{ + tran_sock_t *ts; + + assert(vfu_ctx != NULL); + assert(vfu_ctx->tran_data != NULL); + assert(msg != NULL); + + ts = vfu_ctx->tran_data; + + if (ts->conn_fd == -1) { + vfu_log(vfu_ctx, LOG_ERR, "%s: not connected", __func__); + return ERROR_INT(ENOTCONN); + } + + return tran_sock_recv_alloc(ts->conn_fd, &msg->hdr, false, NULL, + &msg->in.iov.iov_base, &msg->in.iov.iov_len); +} + +static int tran_sock_reply(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg, int err) { struct iovec *iovecs; @@ -935,6 +668,7 @@ struct transport_ops tran_sock_ops = { .get_request_header = tran_sock_get_request_header, .recv_body = tran_sock_recv_body, .reply = tran_sock_reply, + .recv_msg = tran_sock_recv_msg, .send_msg = tran_sock_send_msg, .detach = tran_sock_detach, .fini = tran_sock_fini diff --git a/lib/tran_sock.h b/lib/tran_sock.h index efc5d60..084c0a6 100644 --- a/lib/tran_sock.h +++ b/lib/tran_sock.h @@ -34,28 +34,17 @@ #define LIB_VFIO_USER_TRAN_SOCK_H #include "libvfio-user.h" +#include "tran.h" + +extern struct transport_ops tran_sock_ops; /* * These are not public routines, but for convenience, they are used by the * sample/test code as well as privately within libvfio-user. * - * Note there is currently only one transport - talking over a UNIX socket. + * Note there is currently only one real transport - talking over a UNIX socket. */ -/* The largest number of fd's we are prepared to receive. */ -// FIXME: value? -#define VFIO_USER_CLIENT_MAX_MSG_FDS_LIMIT (1024) - -extern struct transport_ops tran_sock_ops; - -/* - * Parse JSON supplied from the other side into the known parameters. Note: they - * will not be set if not found in the JSON. - */ -int -tran_parse_version_json(const char *json_str, int *client_max_fdsp, - size_t *client_max_data_xfer_sizep, size_t *pgsizep); - /* * Send a message to the other end. The iovecs array should leave the first * entry empty, as it will be used for the header. |