From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.3 (2019-12-06) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-1.2 required=3.0 tests=DKIM_INVALID,DKIM_SIGNED, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H4,RCVD_IN_MSPIKE_WL,SPF_HELO_PASS autolearn=unavailable autolearn_force=no version=3.4.3 Received: by atuin.qyliss.net (Postfix, from userid 496) id B42FC1A267; Wed, 8 Jul 2020 15:16:40 +0000 (UTC) Received: from [127.0.0.1] (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id 365001A290; Wed, 8 Jul 2020 15:16:29 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 496) id 304511A236; Wed, 8 Jul 2020 15:16:26 +0000 (UTC) Received: from out3-smtp.messagingengine.com (out3-smtp.messagingengine.com [66.111.4.27]) by atuin.qyliss.net (Postfix) with ESMTPS id 38AB71A28B for ; Wed, 8 Jul 2020 15:16:21 +0000 (UTC) Received: from compute7.internal (compute7.nyi.internal [10.202.2.47]) by mailout.nyi.internal (Postfix) with ESMTP id 1CBE95C00CD for ; Wed, 8 Jul 2020 11:16:19 -0400 (EDT) Received: from mailfrontend1 ([10.202.2.162]) by compute7.internal (MEProxy); Wed, 08 Jul 2020 11:16:19 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=alyssa.is; h= from:to:subject:date:message-id:mime-version:content-type :content-transfer-encoding; s=fm3; bh=bzyQ/DM2UDa+hLLOuCcynqcW5M AMzV8CAVJadhg2hWQ=; b=OfC4B3jRmr7ShLqNYDGHUPC0r+bmleDsLSgHtJUPif +Fk3WGci9vZ6GvzwXpUrN+oGAWRtPjqeUkcXs20RJQJBIqdXLg99MaXbBuJT8BDa MLJyV/ka7epgIRxXc+8XFg3IOVXFPkxA7F9wOnPswq2zwsiEnfQJoxLkIAR6xpS+ oUPBX2RnsA8ktInazWn1B76FtbdAP0d10ZqK799RP6UJq/83r1F8VNBsYVywH2se XbwBlw6jjBAciu9jq0OXoYhcte65V1rXVXUODNi+ENDudIhw0eEpFMeNahsoRAr5 aYtlhkJ8F25Bub4ngNxuFM7tjbWG93yJjzFSYpTaz46g== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=content-transfer-encoding:content-type :date:from:message-id:mime-version:subject:to:x-me-proxy :x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; bh=bzyQ/D M2UDa+hLLOuCcynqcW5MAMzV8CAVJadhg2hWQ=; b=LOYX2wuTivFmX+z7hd+sZk JqPGm1TPhohDl7r+wK/cWdZPYn4uV4Yjwh3m4XlmT5T/z+Lj1JsfV+yfnkwJtwsR 1Ag7LrmgdxD1fHdixqfCzQ+MPnD+SHQVyz/JPCt08xPyImviQh6rG4fFDuBxTK4R mNYjy+OgxSH/qA+j7asOmWCYeqx150il8s0ce6kRFxS4B8HCxvhr+mvaUC7iH131 A2xw+qsocUg/zflcLw38w4qPSFXILgWEmEZBsTvHS8758yPCo9hWvIuc0nGEadUP Nm3cNUSV5p2+hPsCSX14ztFLvRMph/7QgNgnc+8OcvOtvhF81AOdzWlFWRPRdM8g == X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeduiedrudejgdekkecutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfghnecu uegrihhlohhuthemuceftddtnecunecujfgurhephffvufffkffogggtgfesthekredtre dtjeenucfhrhhomheptehlhihsshgrucftohhsshcuoehhihesrghlhihsshgrrdhisheq necuggftrfgrthhtvghrnhepheeitdeiuddtuddthfejfffhieevteejtdfgfeevfeeihe ffgeegtedvvddvleffnecuffhomhgrihhnpehmohiiihhllhgrrdhorhhgnecukfhppeej ledrvdefhedruddviedrudejleenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmh epmhgrihhlfhhrohhmpehqhihlihhsshesgidvvddtrdhqhihlihhsshdrnhgvth X-ME-Proxy: Received: from x220.qyliss.net (p4feb7eb3.dip0.t-ipconnect.de [79.235.126.179]) by mail.messagingengine.com (Postfix) with ESMTPA id E530B3280066 for ; Wed, 8 Jul 2020 11:16:17 -0400 (EDT) Received: by x220.qyliss.net (Postfix, from userid 1000) id 85EAC24A; Wed, 8 Jul 2020 15:16:16 +0000 (UTC) From: Alyssa Ross To: devel@spectrum-os.org Subject: [PATCH wayland] Support virtio_wl display sockets Date: Wed, 8 Jul 2020 15:16:00 +0000 Message-Id: <20200708151600.10607-1-hi@alyssa.is> X-Mailer: git-send-email 2.26.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Message-ID-Hash: R6BDBAIOVAJLAMVJRKZZJTJZBDAXCKXC X-Message-ID-Hash: R6BDBAIOVAJLAMVJRKZZJTJZBDAXCKXC X-MailFrom: qyliss@x220.qyliss.net X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header X-Mailman-Version: 3.3.1 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Post: List-Subscribe: List-Unsubscribe: This patch adds libvirtio_wl, which exposes reimplementations of the sendmsg(2) and recvmsg(2) for virtio_wl socket fds. Tests for as much libvirtio_wl functionality as is reasonably possible to test without requiring a working virtio_wl connection are included. (Testing further would require running tests in a VM so that they could talk to the virtio_wl kernel driver, which would be prohibitively complex. virtio_wl socket fds do not actually point to sockets, but to special virtio_wl files. Whenever a display socket operation calls sendmsg() or recvmsg() with a file descriptor and receives an ENOTSOCK error, it retries the operation with the equivalent libvirtio_wl function, in case the file descriptor is a virtio_wl. This is the least invasive way to implement virtio_wl support -- if a normal socket is being used, there will be no change in behaviour. Because virtio_wl doesn't implement every socket feature, some workarounds are currently required to accomodate everything Wayland expects of sockets: * virtio_wl_recvmsg implements the MSG_DONTWAIT flag by setting O_NONBLOCK on the fd, attempting the VIRTWL_IOCTL_RECV operation, and then restoring the fd's original flags. This is obviously race-prone, but there is no reasonable alternative at present. * virtio_wl_sendmsg requires MSG_NOSIGNAL to be set, and ignores it. This is because I think from looking at the code that virtio_wl does not generate SIGPIPE signals, nor does it ever return EPIPE. I could be wrong about this, though. * virtio_wl does not support credential passing -- what would it even mean, considering the other end of the connection is on another (virtual) machine? So wl_client's ucred member will have pid, uid, and gid all set to -1 for a client connected over virtio_wl. * virtio_wl sockets do not support accept(2), so a fallback is used. A proxy program on the host accept(2) on a host socket. When it receives a connection, it attaches the connection socket to the VM, then sends the name of the connected socket over the Wayland display socket. Wayland then receives this name, looks up the connection socket, and uses that as the client connection socket. Additionally, virtio_wl memfd-like file descriptors don't support mremap(2), so for virtio_wl sockets Wayland will munmap(2) the memfd, and then mmap(2) it again. This should be at least mostly okay because Wayland only ever calls mremap with MREMAP_MAYMOVE, but it is still race-prone. To be able to do this, memfds are no longer closed after being mmaped, but are kept around in the wl_shm_pool struct, so that they can be passed to mmap() if required. --- src/connection.c | 4 + src/meson.build | 16 +- src/virtio_wl.c | 344 +++++++++++++++++++++++ src/virtio_wl.h | 23 ++ src/wayland-os.c | 23 ++ src/wayland-os.h | 3 + src/wayland-server.c | 20 +- src/wayland-shm.c | 24 +- tests/meson.build | 1 + tests/virtio_wl-test.c | 615 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1064 insertions(+), 9 deletions(-) create mode 100644 src/virtio_wl.c create mode 100644 src/virtio_wl.h create mode 100644 tests/virtio_wl-test.c diff --git a/src/connection.c b/src/connection.c index d0c7d9f..1bfbd8c 100644 --- a/src/connection.c +++ b/src/connection.c @@ -40,6 +40,7 @@ #include #include =20 +#include "virtio_wl.h" #include "wayland-util.h" #include "wayland-private.h" #include "wayland-os.h" @@ -314,6 +315,9 @@ wl_connection_flush(struct wl_connection *connection) do { len =3D sendmsg(connection->fd, &msg, MSG_NOSIGNAL | MSG_DONTWAIT); + if (len =3D=3D -1 && errno =3D=3D ENOTSOCK) + len =3D virtio_wl_sendmsg(connection->fd, &msg, + MSG_NOSIGNAL | MSG_DONTWAIT); } while (len =3D=3D -1 && errno =3D=3D EINTR); =20 if (len =3D=3D -1) diff --git a/src/meson.build b/src/meson.build index 2d1485c..cca1d93 100644 --- a/src/meson.build +++ b/src/meson.build @@ -71,13 +71,26 @@ if get_option('libraries') mathlib_dep =3D cc.find_library('m', required: false) threads_dep =3D dependency('threads', required: false) =20 + virtio_wl =3D library( + 'virtio_wl', + sources: [ + 'virtio_wl.c', + ], + version: '0.1.0', + install: true, + ) + + virtio_wl_dep =3D declare_dependency( + link_with: virtio_wl, + ) + wayland_private =3D static_library( 'wayland-private', sources: [ 'connection.c', 'wayland-os.c' ], - dependencies: [ ffi_dep, ] + dependencies: [ ffi_dep, virtio_wl_dep ] ) =20 wayland_private_dep =3D declare_dependency( @@ -230,5 +243,6 @@ if get_option('libraries') 'wayland-server-core.h', 'wayland-client.h', 'wayland-client-core.h', + 'virtio_wl.h', ]) endif diff --git a/src/virtio_wl.c b/src/virtio_wl.c new file mode 100644 index 0000000..a1ee8ee --- /dev/null +++ b/src/virtio_wl.c @@ -0,0 +1,344 @@ +/* Copyright 2020 Alyssa Ross + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virtio_wl.h" + +// This is essentially vendored reusable library code, so I consider +// it exempt from the Wayland style guide. :) + +#if defined(__GNUC__) && __GNUC__ >=3D 4 +#define VIRTIO_WL_EXPORT __attribute__ ((visibility("default"))) +#else +#define VIRTIO_WL_EXPORT +#endif + +static int +set_nonblocking(int fd) +{ + int fl =3D fcntl(fd, F_GETFL); + if (fl =3D=3D -1) + return -1; + if (!(fl & O_NONBLOCK)) + if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) =3D=3D -1) + return -1; + return fl; +} + +// Returns the total size of all buffers in an iovec. +// A return value of -1 means that the total overflowed. +static ssize_t +iov_len(const struct iovec iov[], size_t n) +{ + size_t len =3D 0; + for (size_t i =3D 0; i < n; i++) { + if (SSIZE_MAX - len < iov[i].iov_len) + return -1; + len +=3D iov[i].iov_len; + } + return len; +} + +// Copies from an iovec array into a buffer. +// The buffer is assumed to be large enough to hold all the data. +// This length can be calculated with the iov_len function. +static size_t +iov_flatten(void *buf, size_t buflen, const struct iovec *iov, size_t io= vlen) +{ + size_t off =3D 0; + + for (size_t index =3D 0; index < iovlen && off < buflen; index++) { + const struct iovec *i =3D &iov[index]; + size_t rem =3D buflen - off; + size_t len =3D i->iov_len < rem ? i->iov_len : rem; + + memcpy((unsigned char *)buf + off, i->iov_base, len); + off +=3D len; + } + + return off; +} + +// Copies from a buffer into an iovec array. +// Returns number of bytes copied. +static size_t +iov_fill(struct iovec *iov, size_t iovlen, const void *buf, size_t bufle= n) +{ + size_t off =3D 0; + + for (size_t index =3D 0; index < iovlen && off < buflen; index++) { + struct iovec *i =3D &iov[index]; + size_t rem =3D buflen - off; + size_t len =3D i->iov_len < rem ? i->iov_len : rem; + + memcpy(i->iov_base, (const unsigned char *)buf + off, len); + off +=3D len; + } + + return off; +} + +static ssize_t +cmsg_to_fdbuf(int *buf, size_t buflen, const struct msghdr *msg) +{ + size_t next_fd =3D 0; + + for (struct cmsghdr *cmsg =3D CMSG_FIRSTHDR(msg); + cmsg !=3D NULL; cmsg =3D CMSG_NXTHDR(msg, cmsg)) { + size_t size =3D cmsg->cmsg_len - CMSG_LEN(0); + + // Check the cmsg can be handled. + if (cmsg->cmsg_level !=3D SOL_SOCKET || + cmsg->cmsg_type !=3D SCM_RIGHTS || + size % sizeof(int) !=3D 0) { + errno =3D EINVAL; + return -1; + } + + size_t rem =3D sizeof(int) * (buflen - next_fd); + size_t len =3D size > rem ? rem : size; + + // Copy the fds to the buffer. + memcpy(buf + next_fd, CMSG_DATA(cmsg), len); + next_fd +=3D len / sizeof(int); + + if (size > rem) + break; + } + + return next_fd > SSIZE_MAX ? SSIZE_MAX : next_fd; +} + +static size_t +fdbuf_to_cmsg(struct msghdr *msg, const int *buf, size_t buflen) +{ + // Check msg->msg_control is long enough to fit at least one fd. + if (msg->msg_controllen < CMSG_SPACE(sizeof(int))) { + + // If there's at least one fd in buf, set MSG_CTRUNC. + size_t i =3D 0; + while (i < buflen && !(msg->msg_flags & MSG_CTRUNC)) + if (buf[i++] !=3D -1) + msg->msg_flags |=3D MSG_CTRUNC; + + return 0; + } + + // cmsg(3): + // > When initializing a buffer that will contain a series of cmsghdr + // > structures (e.g., to be sent with sendmsg(2)), that buffer = should + // > first be zero-initialized to en=E2=80=90 sure the correct o= peration of + // > CMSG_NXTHDR(). + memset(msg->msg_control, 0, msg->msg_controllen); + + // Set up the cmsg. + struct cmsghdr *cmsg =3D CMSG_FIRSTHDR(msg); + cmsg->cmsg_level =3D SOL_SOCKET; + cmsg->cmsg_type =3D SCM_RIGHTS; + + // Copy as many fds as fit into cmsg. + size_t len =3D 0; + for (size_t i =3D 0; i < buflen; i++) { + if (buf[i] =3D=3D -1) + continue; + + if (CMSG_LEN((len + 1) * sizeof(int)) > msg->msg_controllen) { + msg->msg_flags |=3D MSG_CTRUNC; + break; + } + + memcpy(CMSG_DATA(cmsg) + sizeof(int) * len, &buf[i], sizeof(int)); + len++; + } + + cmsg->cmsg_len =3D CMSG_LEN(len * sizeof(int)); + return len; +} + +VIRTIO_WL_EXPORT int +virtio_wl_connect(const char *name, uint32_t flags) +{ + static int wl_fd =3D -1; + if (wl_fd < 0) + wl_fd =3D open("/dev/wl0", O_RDWR | O_CLOEXEC); + if (wl_fd < 0) + return wl_fd; + + struct virtwl_ioctl_new new_ctx =3D { + .type =3D name ? VIRTWL_IOCTL_NEW_CTX_NAMED : VIRTWL_IOCTL_NEW_CTX, + .fd =3D -1, + .flags =3D flags, + }; + // Device assumes name 32 bytes long if not null terminated. + if (name) + strncpy(new_ctx.name, name, sizeof(new_ctx.name)); + + if (ioctl(wl_fd, VIRTWL_IOCTL_NEW, &new_ctx)) + return -1; + + return new_ctx.fd; +} + +VIRTIO_WL_EXPORT int +virtio_wl_send_raw(int sockfd, struct virtwl_ioctl_txn *ioctl_txn) +{ + return ioctl(sockfd, VIRTWL_IOCTL_SEND, ioctl_txn); +} + +static int +msghdr_to_txn(const struct msghdr *msg, struct virtwl_ioctl_txn **txn) +{ + // Make sure that if there's an error and the caller tries to free *txn + // anyway it doesn't end up freeing an invalid address. + *txn =3D NULL; + + // Figure out how big the txn needs to be. + ssize_t len =3D iov_len(msg->msg_iov, msg->msg_iovlen); + if (len < 0 || len > UINT32_MAX) { + errno =3D ENOMEM; + return -1; + } + + // Allocate the txn. + *txn =3D malloc(sizeof(**txn) + len); + if (!*txn) + return -1; + + // Set the len member of the txn. + (*txn)->len =3D len; + + // Copy data from the iovec into the transaction. + iov_flatten((*txn)->data, len, msg->msg_iov, msg->msg_iovlen); + + // Copy file descriptors to the txn. + ssize_t fd_count =3D cmsg_to_fdbuf((*txn)->fds, VIRTWL_SEND_MAX_ALLOCS,= msg); + if (fd_count =3D=3D -1) { + free(*txn); + *txn =3D NULL; + return -1; + } + + // Fill the rest of the fd buffer with -1. + while (fd_count < VIRTWL_SEND_MAX_ALLOCS) + (*txn)->fds[fd_count++] =3D -1; + + return 0; +} + +static void +txn_to_msghdr(struct msghdr *msg, const struct virtwl_ioctl_txn *txn) +{ + // Copy txn data to iovecs. + iov_fill(msg->msg_iov, msg->msg_iovlen, txn->data, txn->len); + + // Copy fds to cmsg. + fdbuf_to_cmsg(msg, txn->fds, VIRTWL_SEND_MAX_ALLOCS); +} + +VIRTIO_WL_EXPORT ssize_t +virtio_wl_sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + int sockfl; + + if ((flags & ~MSG_DONTWAIT) !=3D MSG_NOSIGNAL) { + errno =3D EINVAL; + return -1; + } + + struct virtwl_ioctl_txn *txn; + if (msghdr_to_txn(msg, &txn) =3D=3D -1) + return -1; + + if (flags & MSG_DONTWAIT) { + sockfl =3D set_nonblocking(sockfd); + if (sockfl =3D=3D -1) { + free(txn); + return -1; + } + } + + int rv =3D virtio_wl_send_raw(sockfd, txn); + + if ((flags & MSG_DONTWAIT) && !(sockfl & O_NONBLOCK)) { + if (fcntl(sockfd, F_SETFL, sockfl) =3D=3D -1) { + free(txn); + return -1; + } + } + + if (rv !=3D -1) + rv =3D txn->len; + + free(txn); + return rv; +} + +VIRTIO_WL_EXPORT int +virtio_wl_recv_raw(int sockfd, struct virtwl_ioctl_txn *ioctl_txn) +{ + return ioctl(sockfd, VIRTWL_IOCTL_RECV, ioctl_txn); +} + +VIRTIO_WL_EXPORT ssize_t +virtio_wl_recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + int sockfl; + + if ((flags & ~MSG_DONTWAIT) !=3D MSG_CMSG_CLOEXEC) { + errno =3D EINVAL; + return -1; + } + + ssize_t len =3D iov_len(msg->msg_iov, msg->msg_iovlen); + if (len < 0 || len > UINT32_MAX) { + errno =3D ENOMEM; + return -1; + } + + struct virtwl_ioctl_txn *txn =3D malloc(sizeof(*txn) + len); + if (!txn) + return -1; + + txn->len =3D len; + + if (flags & MSG_DONTWAIT) { + sockfl =3D set_nonblocking(sockfd); + if (sockfl =3D=3D -1) { + free(txn); + return -1; + } + } + + ssize_t rv =3D virtio_wl_recv_raw(sockfd, txn); + + if ((flags & MSG_DONTWAIT) && !(sockfl & O_NONBLOCK)) + if (fcntl(sockfd, F_SETFL, sockfl) =3D=3D -1) + rv =3D -1; + + if (rv =3D=3D -1) { + for (size_t i =3D 0; i < VIRTWL_SEND_MAX_ALLOCS; i++) + if (txn->fds[i] =3D=3D -1) + close(txn->fds[i]); + } else { + txn_to_msghdr(msg, txn); + rv =3D txn->len; + } + + free(txn); + return rv; +} diff --git a/src/virtio_wl.h b/src/virtio_wl.h new file mode 100644 index 0000000..80bcd38 --- /dev/null +++ b/src/virtio_wl.h @@ -0,0 +1,23 @@ +/* Copyright 2020 Alyssa Ross + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef UTIL_VIRTIO_WL_H +#define UTIL_VIRTIO_WL_H + +#include + +struct virtwl_ioctl_txn; +struct msghdr; + +int virtio_wl_connect(const char *name, uint32_t flags); + +ssize_t virtio_wl_sendmsg(int sockfd, const struct msghdr *, int flags); +int virtio_wl_send_raw(int sockfd, struct virtwl_ioctl_txn *); + +ssize_t virtio_wl_recvmsg(int sockfd, struct msghdr *, int flags); +int virtio_wl_recv_raw(int sockfd, struct virtwl_ioctl_txn *); + +#endif diff --git a/src/wayland-os.c b/src/wayland-os.c index 93b6f5f..c92aaae 100644 --- a/src/wayland-os.c +++ b/src/wayland-os.c @@ -31,6 +31,9 @@ #include #include #include +#include +#include +#include =20 #include "../config.h" #include "wayland-os.h" @@ -126,6 +129,10 @@ wl_os_recvmsg_cloexec(int sockfd, struct msghdr *msg= , int flags) len =3D recvmsg(sockfd, msg, flags | MSG_CMSG_CLOEXEC); if (len >=3D 0) return len; + + if (errno =3D=3D ENOTSOCK) + return virtio_wl_recvmsg(sockfd, msg, flags | MSG_CMSG_CLOEXEC); + if (errno !=3D EINVAL) return -1; =20 @@ -165,3 +172,19 @@ wl_os_accept_cloexec(int sockfd, struct sockaddr *ad= dr, socklen_t *addrlen) fd =3D accept(sockfd, addr, addrlen); return set_cloexec_or_close(fd); } + +int +wl_virtio_accept_cloexec(int sockfd) +{ + char name[32]; + size_t offset =3D 0; + + while (offset < sizeof name) { + ssize_t size =3D read(sockfd, name + offset, sizeof name - offset); + if (size < 0) + return size; + offset +=3D size; + } + + return virtio_wl_connect(name, 0); +} diff --git a/src/wayland-os.h b/src/wayland-os.h index f51efaa..899ac8e 100644 --- a/src/wayland-os.h +++ b/src/wayland-os.h @@ -41,6 +41,9 @@ wl_os_epoll_create_cloexec(void); int wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrl= en); =20 +int +wl_virtio_accept_cloexec(int sockfd); + =20 /* * The following are for wayland-os.c and the unit tests. diff --git a/src/wayland-server.c b/src/wayland-server.c index 3f48dfe..0146ca8 100644 --- a/src/wayland-server.c +++ b/src/wayland-server.c @@ -531,8 +531,18 @@ wl_client_create(struct wl_display *display, int fd) =20 len =3D sizeof client->ucred; if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, - &client->ucred, &len) < 0) - goto err_source; + &client->ucred, &len) < 0 && errno !=3D ENOTSOCK) { + if (errno =3D=3D ENOTSOCK) { + // Probably a virtio_wl socket. + // We don't have credential information, so fill with + // values that should never match real ones. + client->ucred.pid =3D -1; + client->ucred.uid =3D -1; + client->ucred.gid =3D -1; + } else { + goto err_source; + } + } =20 client->connection =3D wl_connection_create(fd); if (client->connection =3D=3D NULL) @@ -1419,6 +1429,9 @@ socket_data(int fd, uint32_t mask, void *data) length =3D sizeof name; client_fd =3D wl_os_accept_cloexec(fd, (struct sockaddr *) &name, &length); + if (client_fd < 0 && errno =3D=3D ENOTSOCK) + client_fd =3D wl_virtio_accept_cloexec(fd); + if (client_fd < 0) wl_log("failed to accept: %s\n", strerror(errno)); else @@ -1603,7 +1616,8 @@ wl_display_add_socket_fd(struct wl_display *display= , int sock_fd) struct stat buf; =20 /* Require a valid fd or fail */ - if (sock_fd < 0 || fstat(sock_fd, &buf) < 0 || !S_ISSOCK(buf.st_mode)) = { + if (sock_fd < 0 || fstat(sock_fd, &buf) < 0 || + ((buf.st_mode & S_IFMT) && !S_ISSOCK(buf.st_mode))) { return -1; } =20 diff --git a/src/wayland-shm.c b/src/wayland-shm.c index b85e5a7..636ee7a 100644 --- a/src/wayland-shm.c +++ b/src/wayland-shm.c @@ -61,6 +61,7 @@ struct wl_shm_pool { int internal_refcount; int external_refcount; char *data; + int fd; int32_t size; int32_t new_size; bool sigbus_is_impossible; @@ -91,14 +92,27 @@ shm_pool_finish_resize(struct wl_shm_pool *pool) =20 data =3D mremap(pool->data, pool->size, pool->new_size, MREMAP_MAYMOVE)= ; if (data =3D=3D MAP_FAILED) { - wl_resource_post_error(pool->resource, - WL_SHM_ERROR_INVALID_FD, - "failed mremap"); - return; + if (errno !=3D EFAULT) + goto fail; + + if (munmap(pool->data, pool->size) =3D=3D -1) + goto fail; + + data =3D mmap(pool->data, pool->new_size, PROT_READ | PROT_WRITE, + MAP_SHARED, pool->fd, 0); + if (data =3D=3D MAP_FAILED) + goto fail; } =20 pool->data =3D data; pool->size =3D pool->new_size; + + return; + + fail: + wl_resource_post_error(pool->resource, + WL_SHM_ERROR_INVALID_FD, + "failed mremap"); } =20 static void @@ -291,6 +305,7 @@ shm_create_pool(struct wl_client *client, struct wl_r= esource *resource, pool->external_refcount =3D 0; pool->size =3D size; pool->new_size =3D size; + pool->fd =3D fd; pool->data =3D mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (pool->data =3D=3D MAP_FAILED) { @@ -300,7 +315,6 @@ shm_create_pool(struct wl_client *client, struct wl_r= esource *resource, strerror(errno)); goto err_free; } - close(fd); =20 pool->resource =3D wl_resource_create(client, &wl_shm_pool_interface, 1, id); diff --git a/tests/meson.build b/tests/meson.build index 224f48d..6ee2b49 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -148,6 +148,7 @@ tests =3D { 'headers-protocol-core-test.c', ], 'os-wrappers-test': [], + 'virtio_wl-test': [], } =20 foreach test_name, test_extra_sources: tests diff --git a/tests/virtio_wl-test.c b/tests/virtio_wl-test.c new file mode 100644 index 0000000..16c7a93 --- /dev/null +++ b/tests/virtio_wl-test.c @@ -0,0 +1,615 @@ +#define _GNU_SOURCE +#include + +#include "test-runner.h" +#include "virtio_wl.c" + +TEST(iov_len_fit) +{ + int f1[] =3D { 1, 2 }; + int f2[] =3D { 3 }; + + struct iovec iov[] =3D { + { .iov_base =3D f1, .iov_len =3D sizeof(f1) }, + { .iov_base =3D NULL, .iov_len =3D 0 }, + { .iov_base =3D f2, .iov_len =3D sizeof(f2) }, + }; + + assert(iov_len(iov, sizeof(iov) / sizeof(*iov)) =3D=3D 3 * sizeof(int))= ; +} + +TEST(iov_len_overflow) +{ + struct iovec iov[] =3D { + { .iov_base =3D NULL, .iov_len =3D SIZE_MAX }, + { .iov_base =3D NULL, .iov_len =3D SIZE_MAX }, + }; + + assert(iov_len(iov, sizeof(iov) / sizeof(*iov)) =3D=3D -1); +} + +TEST(iov_flatten_test) +{ + int f1[] =3D { 1 }; + int f2[] =3D { 2, 3 }; + + struct iovec iov[] =3D { + { .iov_base =3D f1, .iov_len =3D sizeof(f1) }, + { .iov_base =3D NULL, .iov_len =3D 0 }, + { .iov_base =3D f2, .iov_len =3D sizeof(f2) }, + }; + + int buf[2]; + iov_flatten(buf, sizeof(buf), iov, sizeof(iov) / sizeof(*iov)); + assert(buf[0] =3D=3D 1); + assert(buf[1] =3D=3D 2); +} + +TEST(iov_flatten_zero_iov) +{ + unsigned char buf[] =3D { 0xFF }; + assert(iov_flatten(buf, 1, NULL, 0) =3D=3D 0); +} + +TEST(iov_flatten_null_iov) +{ + unsigned char buf[] =3D { 0xFF }; + struct iovec iov[] =3D { { .iov_base =3D NULL, .iov_len =3D 0 } }; + + assert(iov_flatten(buf, sizeof buf, iov, sizeof(iov) / sizeof(*iov)) =3D= =3D 0); +} + +TEST(iov_flatten_zero_buf) +{ + unsigned char f1[] =3D { 0 }; + struct iovec iov[] =3D { { .iov_base =3D f1, .iov_len =3D sizeof f1 } }= ; + + assert(iov_flatten(NULL, 0, iov, sizeof(iov) / sizeof(*iov)) =3D=3D 0); +} + +TEST(iov_flatten_short_iov) +{ + int f1[] =3D { 1 }; + int f2[] =3D { 2, 3 }; + + struct iovec iov[] =3D { + { .iov_base =3D f1, .iov_len =3D sizeof f1 }, + { .iov_base =3D NULL, .iov_len =3D 0 }, + { .iov_base =3D f2, .iov_len =3D sizeof f2 }, + }; + + int buf[] =3D { 9, 8, 7, 6 }; + + assert(iov_flatten(buf, sizeof buf, iov, sizeof(iov) / sizeof(*iov)) =3D= =3D 12); + assert(buf[0] =3D=3D 1); + assert(buf[1] =3D=3D 2); + assert(buf[2] =3D=3D 3); + assert(buf[3] =3D=3D 6); +} + +TEST(iov_flatten_exact_iov) +{ + int f1[] =3D { 1 }; + int f2[] =3D { 2, 3 }; + + struct iovec iov[] =3D { + { .iov_base =3D f1, .iov_len =3D sizeof f1 }, + { .iov_base =3D NULL, .iov_len =3D 0 }, + { .iov_base =3D f2, .iov_len =3D sizeof f2 }, + }; + + int buf[] =3D { 9, 8, 7 }; + + assert(iov_flatten(buf, sizeof buf, iov, sizeof(iov) / sizeof(*iov)) =3D= =3D 12); + assert(f1[0] =3D=3D 1); + assert(f2[0] =3D=3D 2); + assert(f2[1] =3D=3D 3); +} + +TEST(iov_flatten_long_iov) +{ + int f1[] =3D { 1 }; + int f2[] =3D { 2, 3, 4 }; + + struct iovec iov[] =3D { + { .iov_base =3D f1, .iov_len =3D sizeof f1 }, + { .iov_base =3D NULL, .iov_len =3D 0 }, + { .iov_base =3D f2, .iov_len =3D sizeof f2 }, + }; + + int buf[] =3D { 9, 8, 7 }; + + assert(iov_flatten(buf, sizeof buf, iov, sizeof(iov) / sizeof(*iov)) =3D= =3D 12); + assert(buf[0] =3D=3D 1); + assert(buf[1] =3D=3D 2); + assert(buf[2] =3D=3D 3); +} + +TEST(iov_fill_zero_iov) +{ + unsigned char buf[] =3D { 0xFF }; + assert(iov_fill(NULL, 0, buf, 1) =3D=3D 0); +} + +TEST(iov_fill_null_iov) +{ + unsigned char buf[] =3D { 0xFF }; + struct iovec iov[] =3D { { .iov_base =3D NULL, .iov_len =3D 0 } }; + + assert(iov_fill(iov, sizeof(iov) / sizeof(*iov), buf, sizeof buf) =3D=3D= 0); +} + +TEST(iov_fill_zero_buf) +{ + unsigned char f1[] =3D { 0 }; + struct iovec iov[] =3D { { .iov_base =3D f1, .iov_len =3D sizeof f1 } }= ; + + assert(iov_fill(iov, sizeof(iov) / sizeof(*iov), NULL, 0) =3D=3D 0); +} + +TEST(iov_fill_short_iov) +{ + int f1[] =3D { 1 }; + int f2[] =3D { 2, 3 }; + + struct iovec iov[] =3D { + { .iov_base =3D f1, .iov_len =3D sizeof f1 }, + { .iov_base =3D NULL, .iov_len =3D 0 }, + { .iov_base =3D f2, .iov_len =3D sizeof f2 }, + }; + + int buf[] =3D { 9, 8, 7, 6 }; + + assert(iov_fill(iov, sizeof(iov) / sizeof(*iov), buf, sizeof buf) =3D=3D= 12); + assert(f1[0] =3D=3D 9); + assert(f2[0] =3D=3D 8); + assert(f2[1] =3D=3D 7); +} + +TEST(iov_fill_exact) +{ + int f1[] =3D { 1 }; + int f2[] =3D { 2, 3 }; + + struct iovec iov[] =3D { + { .iov_base =3D f1, .iov_len =3D sizeof f1 }, + { .iov_base =3D NULL, .iov_len =3D 0 }, + { .iov_base =3D f2, .iov_len =3D sizeof f2 }, + }; + + int buf[] =3D { 9, 8, 7 }; + + assert(iov_fill(iov, sizeof(iov) / sizeof(*iov), buf, sizeof buf) =3D=3D= 12); + assert(f1[0] =3D=3D 9); + assert(f2[0] =3D=3D 8); + assert(f2[1] =3D=3D 7); +} + +TEST(iov_fill_long_iov) +{ + int f1[] =3D { 1 }; + int f2[] =3D { 2, 3, 4 }; + + struct iovec iov[] =3D { + { .iov_base =3D f1, .iov_len =3D sizeof f1 }, + { .iov_base =3D NULL, .iov_len =3D 0 }, + { .iov_base =3D f2, .iov_len =3D sizeof f2 }, + }; + + int buf[] =3D { 9, 8, 7 }; + + assert(iov_fill(iov, sizeof(iov) / sizeof(*iov), buf, sizeof buf) =3D=3D= 12); + assert(f1[0] =3D=3D 9); + assert(f2[0] =3D=3D 8); + assert(f2[1] =3D=3D 7); + assert(f2[2] =3D=3D 4); +} + +TEST(cmsg_to_fdbuf_zero_buf) +{ + int fds[] =3D { 0 }; + + union { + char buf[CMSG_SPACE(sizeof fds)]; + struct cmsghdr align; + } u; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + struct cmsghdr *cmsg =3D CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level =3D SOL_SOCKET; + cmsg->cmsg_type =3D SCM_RIGHTS; + cmsg->cmsg_len =3D CMSG_LEN(sizeof fds); + memcpy(CMSG_DATA(cmsg), fds, sizeof fds); + + assert(cmsg_to_fdbuf(NULL, 0, &msg) =3D=3D 0); +} + +TEST(cmsg_to_fdbuf_zero_cmsg) +{ + int fds[2] =3D { 0 }; + struct msghdr msg =3D { 0 }; + assert(cmsg_to_fdbuf(fds, sizeof fds, &msg) =3D=3D 0); +} + +TEST(cmsg_to_fdbuf_small_buf) +{ + int fds[] =3D { 0, 1 }; + + union { + char buf[CMSG_SPACE(sizeof fds)]; + struct cmsghdr align; + } u; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + struct cmsghdr *cmsg =3D CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level =3D SOL_SOCKET; + cmsg->cmsg_type =3D SCM_RIGHTS; + cmsg->cmsg_len =3D CMSG_LEN(sizeof fds); + memcpy(CMSG_DATA(cmsg), fds, sizeof fds); + + int buf[] =3D { 0xFF }; + assert(cmsg_to_fdbuf(buf, sizeof(buf) / sizeof(*buf), &msg) =3D=3D 1); + assert(buf[0] =3D=3D 0); +} + +TEST(cmsg_to_fdbuf_exact) +{ + int fds[] =3D { 0, 1 }; + + union { + char buf[CMSG_SPACE(sizeof fds)]; + struct cmsghdr align; + } u; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + struct cmsghdr *cmsg =3D CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level =3D SOL_SOCKET; + cmsg->cmsg_type =3D SCM_RIGHTS; + cmsg->cmsg_len =3D CMSG_LEN(sizeof fds); + memcpy(CMSG_DATA(cmsg), fds, sizeof fds); + + int buf[] =3D { 0xFF, 0xFE }; + assert(cmsg_to_fdbuf(buf, sizeof(buf) / sizeof(*buf), &msg) =3D=3D 2); + assert(buf[0] =3D=3D 0); + assert(buf[1] =3D=3D 1); +} + +TEST(cmsg_to_fdbuf_small_cmsg) +{ + int fds[] =3D { 0 }; + + union { + char buf[CMSG_SPACE(sizeof fds)]; + struct cmsghdr align; + } u; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + struct cmsghdr *cmsg =3D CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level =3D SOL_SOCKET; + cmsg->cmsg_type =3D SCM_RIGHTS; + cmsg->cmsg_len =3D CMSG_LEN(sizeof fds); + memcpy(CMSG_DATA(cmsg), fds, sizeof fds); + + int buf[] =3D { 0xFF, 0xFE }; + assert(cmsg_to_fdbuf(buf, sizeof(buf) / sizeof(*buf), &msg) =3D=3D 1); + assert(buf[0] =3D=3D 0); + assert(buf[1] =3D=3D 0xFE); +} + +TEST(cmsg_to_fdbuf_multiple_cmsg) +{ + int f1[] =3D { 0 }; + int f2[] =3D { 1 }; + + union { + char buf[CMSG_SPACE(sizeof f1) + CMSG_SPACE(sizeof f2)]; + struct cmsghdr align; + } u; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + struct cmsghdr *cmsg1 =3D CMSG_FIRSTHDR(&msg); + cmsg1->cmsg_level =3D SOL_SOCKET; + cmsg1->cmsg_type =3D SCM_RIGHTS; + cmsg1->cmsg_len =3D CMSG_LEN(sizeof f1); + memcpy(CMSG_DATA(cmsg1), f1, sizeof f1); + + struct cmsghdr *cmsg2 =3D CMSG_NXTHDR(&msg, cmsg1); + cmsg2->cmsg_level =3D SOL_SOCKET; + cmsg2->cmsg_type =3D SCM_RIGHTS; + cmsg2->cmsg_len =3D CMSG_LEN(sizeof f2); + memcpy(CMSG_DATA(cmsg2), f2, sizeof f2); + + int buf[] =3D { 0xFF, 0xFE }; + assert(cmsg_to_fdbuf(buf, sizeof(buf) / sizeof(*buf), &msg) =3D=3D 2); + assert(buf[0] =3D=3D 0); + assert(buf[1] =3D=3D 1); +} + +TEST(cmsg_to_fdbuf_other_cmsg) +{ + struct ucred cred =3D { + .pid =3D getpid(), + .uid =3D getuid(), + .gid =3D getgid(), + }; + + int fds[] =3D { 0, 1 }; + + union { + char buf[CMSG_SPACE(sizeof cred) + CMSG_SPACE(sizeof fds)]; + struct cmsghdr align; + } u; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + struct cmsghdr *cmsg1 =3D CMSG_FIRSTHDR(&msg); + cmsg1->cmsg_level =3D SOL_SOCKET; + cmsg1->cmsg_type =3D SCM_CREDENTIALS; + cmsg1->cmsg_len =3D CMSG_LEN(sizeof cred); + memcpy(CMSG_DATA(cmsg1), &cred, sizeof cred); + + struct cmsghdr *cmsg2 =3D CMSG_NXTHDR(&msg, cmsg1); + cmsg2->cmsg_level =3D SOL_SOCKET; + cmsg2->cmsg_type =3D SCM_RIGHTS; + cmsg2->cmsg_len =3D CMSG_LEN(sizeof fds); + memcpy(CMSG_DATA(cmsg2), fds, sizeof fds); + + int buf[] =3D { 0xFF, 0xFE }; + assert(cmsg_to_fdbuf(buf, sizeof(buf) / sizeof(*buf), &msg) =3D=3D -1); + assert(errno =3D=3D EINVAL); +} + +TEST(cmsg_to_fdbuf_misaligned) +{ + int fds[] =3D { 0, 1 }; + + union { + char buf[CMSG_SPACE(sizeof fds + 1)]; + struct cmsghdr align; + } u; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + struct cmsghdr *cmsg =3D CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level =3D SOL_SOCKET; + cmsg->cmsg_type =3D SCM_RIGHTS; + cmsg->cmsg_len =3D CMSG_LEN(sizeof(fds) + 1); + memcpy(CMSG_DATA(cmsg), fds, sizeof fds); + CMSG_DATA(cmsg)[sizeof(fds)] =3D 0; + + int buf[] =3D { 0xFF, 0xFE }; + assert(cmsg_to_fdbuf(buf, sizeof(buf) / sizeof(*buf), &msg) =3D=3D -1); + assert(errno =3D=3D EINVAL); +} + +TEST(fdbuf_to_cmsg_null_cmsg) +{ + struct msghdr msg =3D { 0 }; + int fds[] =3D { 1 }; + assert(fdbuf_to_cmsg(&msg, fds, sizeof(fds) / sizeof(*fds)) =3D=3D 0); + assert(msg.msg_flags & MSG_CTRUNC); +} + +TEST(fdbuf_to_cmsg_empty_both) +{ + struct msghdr msg =3D { 0 }; + int fds[] =3D { -1 }; + assert(fdbuf_to_cmsg(&msg, fds, sizeof(fds) / sizeof(*fds)) =3D=3D 0); + assert(!(msg.msg_flags & MSG_CTRUNC)); +} + +TEST(fdbuf_to_cmsg_null_buf) +{ + union { + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u =3D { 0 }; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + assert(fdbuf_to_cmsg(&msg, NULL, 0) =3D=3D 0); + assert(!(msg.msg_flags & MSG_CTRUNC)); +} + +TEST(fdbuf_to_cmsg_tiny_msg) +{ + union { + char buf[1]; + struct cmsghdr align; + } u =3D { 0 }; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + int fds[] =3D { 1 }; + assert(fdbuf_to_cmsg(&msg, fds, sizeof(fds) / sizeof(*fds)) =3D=3D 0); + assert(msg.msg_flags & MSG_CTRUNC); +} + +TEST(fdbuf_to_cmsg_small_msg) +{ + union { + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u =3D { 0 }; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + int fds[] =3D { 1, 2, 3 }; + assert(CMSG_SPACE(sizeof fds) !=3D sizeof u.buf); + + // CMSG_SPACE(sizeof(int)) can equal CMSG_SPACE(sizeof(int) * 2) + size_t n =3D fdbuf_to_cmsg(&msg, fds, sizeof(fds) / sizeof(*fds)); + assert(CMSG_SPACE(n * sizeof(int)) =3D=3D sizeof u.buf); + assert(msg.msg_flags & MSG_CTRUNC); +} + +TEST(fdbuf_to_cmsg_equal) +{ + int fds[] =3D { 1, 2 }; + + union { + char buf[CMSG_SPACE(sizeof fds)]; + struct cmsghdr align; + } u =3D { 0 }; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + assert(fdbuf_to_cmsg(&msg, fds, sizeof(fds) / sizeof(*fds)) =3D=3D 2); + assert(!memcmp(CMSG_DATA(CMSG_FIRSTHDR(&msg)), fds, sizeof fds)); + assert(!(msg.msg_flags & MSG_CTRUNC)); +} + +TEST(fdbuf_to_cmsg_negative_one) +{ + int fds[] =3D { 1, -1, -1, 2 }; + + union { + char buf[CMSG_SPACE(2 * sizeof(int))]; + struct cmsghdr align; + } u =3D { 0 }; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + assert(fdbuf_to_cmsg(&msg, fds, sizeof(fds) / sizeof(*fds)) =3D=3D 2); + + unsigned char *data =3D CMSG_DATA(CMSG_FIRSTHDR(&msg)); + assert(!memcmp(data, &fds[0], sizeof(int))); + assert(!memcmp(data + sizeof(int), &fds[3], sizeof(int))); + assert(!(msg.msg_flags & MSG_CTRUNC)); +} + +TEST(fdbuf_to_cmsg_small_buf) +{ + int fds[] =3D { 1 }; + + union { + char buf[CMSG_SPACE(2 * sizeof fds)]; + struct cmsghdr align; + } u =3D { 0 }; + + struct msghdr msg =3D { 0 }; + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + assert(fdbuf_to_cmsg(&msg, fds, sizeof(fds) / sizeof(*fds)) =3D=3D 1); + assert(!memcmp(CMSG_DATA(CMSG_FIRSTHDR(&msg)), fds, sizeof fds)); + assert(!(msg.msg_flags & MSG_CTRUNC)); +} + +TEST(msghdr_to_txn_test) +{ + struct virtwl_ioctl_txn *txn; + + int f1[] =3D { 1 }; + int f2[] =3D { 2, 3 }; + + const int fds[] =3D { 0, 1, 2 }; + + struct iovec iov[] =3D { + { .iov_base =3D f1, .iov_len =3D sizeof f1 }, + { .iov_base =3D f2, .iov_len =3D sizeof f2 }, + }; + + union { + char buf[CMSG_SPACE(sizeof fds)]; + struct cmsghdr align; + } u; + + struct msghdr msg =3D { 0 }; + msg.msg_iov =3D iov; + msg.msg_iovlen =3D sizeof(iov) / sizeof(*iov); + msg.msg_control =3D u.buf; + msg.msg_controllen =3D sizeof u.buf; + + struct cmsghdr *cmsg =3D CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level =3D SOL_SOCKET; + cmsg->cmsg_type =3D SCM_RIGHTS; + cmsg->cmsg_len =3D CMSG_LEN(sizeof fds); + memcpy(CMSG_DATA(cmsg), fds, sizeof fds); + + assert(!msghdr_to_txn(&msg, &txn)); + + assert(txn->len =3D=3D sizeof(f1) + sizeof(f2)); + assert(!memcmp(txn->fds, fds, sizeof fds)); + for (size_t i =3D sizeof(fds) / sizeof(*fds); + i < VIRTWL_SEND_MAX_ALLOCS; i++) + assert(txn->fds[i] =3D=3D -1); + assert(!memcmp(txn->data, f1, sizeof f1)); + assert(!memcmp(txn->data + sizeof f1, f2, sizeof f2)); +} + +TEST(txn_to_msghdr_test) +{ + const int data[] =3D { 1, 2, 3 }; + + size_t len =3D sizeof(data); + struct virtwl_ioctl_txn *txn =3D malloc(sizeof(*txn) + len); + assert(txn); + + txn->len =3D len; + + const int fds[] =3D { 0, 1, 2 }; + memcpy(txn->fds, fds, sizeof fds); + for (int i =3D sizeof(fds) / sizeof(*fds); i < VIRTWL_SEND_MAX_ALLOCS; = i++) + txn->fds[i] =3D -1; + + memcpy(txn->data, data, sizeof data); + + int buf1[1], buf2[2]; + + struct iovec iov[] =3D { + { .iov_base =3D buf1, .iov_len =3D sizeof buf1 }, + { .iov_base =3D buf2, .iov_len =3D sizeof buf2 }, + }; + + unsigned char cmsg_buf[VIRTWL_SEND_MAX_ALLOCS]; + + struct msghdr msg =3D { 0 }; + msg.msg_iov =3D iov; + msg.msg_iovlen =3D sizeof(iov) / sizeof(*iov); + msg.msg_control =3D cmsg_buf; + msg.msg_controllen =3D sizeof cmsg_buf; + + txn_to_msghdr(&msg, txn); + + assert(!memcmp(buf1, data, sizeof buf1)); + assert(!memcmp(buf2, (unsigned char *)data + sizeof buf1, sizeof buf2))= ; + + struct cmsghdr *cmsg =3D CMSG_FIRSTHDR(&msg); + assert(cmsg->cmsg_level =3D=3D SOL_SOCKET); + assert(cmsg->cmsg_type =3D=3D SCM_RIGHTS); + assert(cmsg->cmsg_len =3D=3D CMSG_LEN(3 * sizeof(int))); + for (int i =3D 0; i < 3; i++) + assert(!memcmp(CMSG_DATA(cmsg), fds, sizeof fds)); + + assert(!CMSG_NXTHDR(&msg, cmsg)); +} --=20 2.26.2