xref: /illumos-gate/usr/src/cmd/bhyve/common/iov.c (revision e8d3efa1c56e5f2b5368600a2baeb7b1d54a07f8)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2016 Jakub Klama <jceel@FreeBSD.org>.
5  * Copyright (c) 2018 Alexander Motin <mav@FreeBSD.org>
6  * Copyright (c) 2026 Hans Rosenfeld
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer
14  *    in this position and unchanged.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 /*
32  * This file and its contents are supplied under the terms of the
33  * Common Development and Distribution License ("CDDL"), version 1.0.
34  * You may only use this file in accordance with the terms of version
35  * 1.0 of the CDDL.
36  *
37  * A full copy of the text of the CDDL should have accompanied this
38  * source.  A copy of the CDDL is also available via the Internet at
39  * http://www.illumos.org/license/CDDL.
40  */
41 /* This file is dual-licensed; see usr/src/contrib/bhyve/LICENSE */
42 
43 /*
44  * Copyright 2025 Oxide Computer Company
45  */
46 
47 #include <sys/param.h>
48 #include <sys/types.h>
49 #include <sys/uio.h>
50 #include <sys/debug.h>
51 #include <sys/sysmacros.h>
52 
53 #include <assert.h>
54 #include <stdbool.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <strings.h>
58 #include <limits.h>
59 #include <errno.h>
60 #include "iov.h"
61 
62 /*
63  * Given an iovec and an offset, split the iovec into two at the offset and
64  * return a pointer to the beginning of the second iovec.
65  *
66  * The caller is responsible for providing an extra iovec in the array for the
67  * split.  That is, there should be space for *niov1 + 1 iovecs in the array.
68  */
69 struct iovec *
70 split_iov(struct iovec *iov, size_t *niov1, size_t offset, size_t *niov2)
71 {
72 	size_t count, resid;
73 
74 	/* Find the iovec entry that contains the offset. */
75 	resid = offset;
76 	for (count = 0; count < *niov1; count++) {
77 		if (resid < iov[count].iov_len)
78 			break;
79 		resid -= iov[count].iov_len;
80 	}
81 
82 	if (resid == 0 || count == *niov1) {
83 		/* Easy case, we don't have to split an iovec entry. */
84 		*niov2 = *niov1 - count;
85 		*niov1 = count;
86 		if (*niov2 == 0)
87 			return (NULL);
88 		return (&iov[count]);
89 	}
90 
91 	/* The entry iov[count] needs to be split. */
92 	*niov1 = count + 1;
93 	*niov2 = *niov1 - count;
94 	memmove(&iov[count + 1], &iov[count], sizeof(struct iovec) * (*niov2));
95 	iov[count].iov_len = resid;
96 	iov[count + 1].iov_base = (char *)iov[count].iov_base + resid;
97 	iov[count + 1].iov_len -= resid;
98 	return (&iov[count + 1]);
99 }
100 
101 size_t
102 count_iov(const struct iovec *iov, size_t niov)
103 {
104 	size_t total = 0;
105 	size_t i;
106 
107 	for (i = 0; i < niov; i++) {
108 		assert(total <= SIZE_MAX - iov[i].iov_len);
109 		total += iov[i].iov_len;
110 	}
111 
112 	return (total);
113 }
114 
115 bool
116 check_iov_len(const struct iovec *iov, size_t niov, size_t len)
117 {
118 	size_t total = 0;
119 	size_t i;
120 
121 	for (i = 0; i < niov; i++) {
122 		assert(total <= SIZE_MAX - iov[i].iov_len);
123 		total += iov[i].iov_len;
124 		if (total >= len)
125 			return (true);
126 	}
127 
128 	return (false);
129 }
130 
131 size_t
132 iov_to_buf(const struct iovec *iov, size_t niov, void **buf)
133 {
134 	size_t ptr, total;
135 	size_t i;
136 
137 	total = count_iov(iov, niov);
138 	*buf = reallocf(*buf, total);
139 	if (*buf == NULL)
140 		return (0);
141 
142 	for (i = 0, ptr = 0; i < niov; i++) {
143 		memcpy((uint8_t *)*buf + ptr, iov[i].iov_base, iov[i].iov_len);
144 		ptr += iov[i].iov_len;
145 	}
146 
147 	return (total);
148 }
149 
150 size_t
151 buf_to_iov(const void *buf, size_t buflen, const struct iovec *iov, size_t niov)
152 {
153 	size_t off = 0, len;
154 	size_t  i;
155 
156 	for (i = 0; i < niov && off < buflen; i++) {
157 		len = MIN(iov[i].iov_len, buflen - off);
158 		memcpy(iov[i].iov_base, (const uint8_t *)buf + off, len);
159 		off += len;
160 	}
161 
162 	return (off);
163 }
164 
165 size_t
166 iov_bunch_init(iov_bunch_t *iob, struct iovec *iov, int niov)
167 {
168 	bzero(iob, sizeof (*iob));
169 	iob->ib_iov = iov;
170 	iob->ib_remain = count_iov(iov, niov);
171 
172 	return (iob->ib_remain);
173 }
174 
175 /*
176  * Copy `sz` bytes from iovecs contained in `iob` to `dst`.
177  *
178  * Returns `true` if copy was successful (implying adequate data was remaining
179  * in the iov_bunch_t).
180  */
181 bool
182 iov_bunch_copy(iov_bunch_t *iob, void *dst, size_t sz)
183 {
184 	if (sz > iob->ib_remain)
185 		return (false);
186 	if (sz == 0)
187 		return (true);
188 
189 	caddr_t dest = dst;
190 	do {
191 		struct iovec *iov = iob->ib_iov;
192 
193 		ASSERT3U(iov->iov_len, !=, 0);
194 
195 		/* ib_offset is the offset within the current head of ib_iov */
196 		const size_t iov_avail = iov->iov_len - iob->ib_offset;
197 		const size_t to_copy = MIN(sz, iov_avail);
198 
199 		if (to_copy != 0 && dest != NULL) {
200 			bcopy((caddr_t)iov->iov_base + iob->ib_offset, dest,
201 			    to_copy);
202 			dest += to_copy;
203 		}
204 
205 		sz -= to_copy;
206 		iob->ib_remain -= to_copy;
207 		iob->ib_offset += to_copy;
208 
209 		ASSERT3U(iob->ib_offset, <=, iov->iov_len);
210 
211 		if (iob->ib_offset == iov->iov_len) {
212 			iob->ib_iov++;
213 			iob->ib_offset = 0;
214 		}
215 	} while (sz > 0);
216 
217 	return (true);
218 }
219 
220 /*
221  * Skip `sz` bytes from iovecs contained in `iob`.
222  *
223  * Returns `true` if the skip was successful (implying adequate data was
224  * remaining in the iov_bunch_t).
225  */
226 bool
227 iov_bunch_skip(iov_bunch_t *iob, size_t sz)
228 {
229 	return (iov_bunch_copy(iob, NULL, sz));
230 }
231 
232 /*
233  * Get the data pointer and length of the current head iovec, less any
234  * offsetting from prior copy operations. This will advance the iov_bunch_t as
235  * if the caller had performed a copy of that chunk length.
236  *
237  * Returns `true` if the iov_bunch_t had at least one iovec (unconsumed bytes)
238  * remaining, setting `chunk` and `chunk_sz` to the chunk pointer and size,
239  * respectively.
240  */
241 bool
242 iov_bunch_next_chunk(iov_bunch_t *iob, caddr_t *chunk, size_t *chunk_sz)
243 {
244 	if (iob->ib_remain == 0) {
245 		*chunk = NULL;
246 		*chunk_sz = 0;
247 		return (false);
248 	}
249 
250 	*chunk_sz = iob->ib_iov->iov_len - iob->ib_offset;
251 	*chunk = (caddr_t)iob->ib_iov->iov_base + iob->ib_offset;
252 	iob->ib_remain -= *chunk_sz;
253 	iob->ib_iov++;
254 	iob->ib_offset = 0;
255 	return (true);
256 }
257 
258 /*
259  * Extract the remaining data in an iov_bunch_t into a new iovec.
260  */
261 void
262 iov_bunch_to_iov(iov_bunch_t *iob, struct iovec *iov, int *niov, uint_t size)
263 {
264 	*niov = 0;
265 
266 	while (size-- > 0) {
267 		caddr_t chunk;
268 		size_t sz;
269 
270 		if (!iov_bunch_next_chunk(iob, &chunk, &sz))
271 			break;
272 
273 		iov->iov_base = chunk;
274 		iov->iov_len = sz;
275 		iov++;
276 		(*niov)++;
277 	}
278 }
279 
280 /*
281  * Extract the remaining data in an iov_bunch_t into a buffer, reallocating it
282  * to the required size. If there are no bytes remaining in the iov_bunch_t any
283  * supplied buffer will be freed and this function will return 0.
284  */
285 ssize_t
286 iov_bunch_to_buf(iov_bunch_t *iob, void **buf)
287 {
288 	size_t total = iob->ib_remain;
289 
290 	if (total == 0) {
291 		free(*buf);
292 		*buf = NULL;
293 		return (0);
294 	}
295 
296 	*buf = reallocf(*buf, total);
297 	if (*buf == NULL)
298 		return (-1);
299 
300 	if (!iov_bunch_copy(iob, buf, total))
301 		return (-1);
302 
303 	if (total > SSIZE_MAX) {
304 		errno = EOVERFLOW;
305 		return (-1);
306 	}
307 
308 	return (total);
309 }
310 
311 /*
312  * Copy data from a buffer into an iob_bunch_t. Returns true if there was
313  * sufficient space for the data, false otherwise.
314  */
315 bool
316 buf_to_iov_bunch(iov_bunch_t *iob, const void *buf, size_t len)
317 {
318 	const char *src = buf;
319 
320 	if (iob->ib_remain < len)
321 		return (false);
322 
323 	do {
324 		struct iovec *iov = iob->ib_iov;
325 
326 		/* ib_offset is the offset within the current head of ib_iov */
327 		const size_t iov_avail = iov->iov_len - iob->ib_offset;
328 		const size_t to_copy = MIN(len, iov_avail);
329 
330 		if (to_copy != 0) {
331 			bcopy(src, (caddr_t)iov->iov_base + iob->ib_offset,
332 			    to_copy);
333 		}
334 
335 		src += to_copy;
336 		len -= to_copy;
337 		iob->ib_remain -= to_copy;
338 		iob->ib_offset += to_copy;
339 
340 		ASSERT3U(iob->ib_offset, <=, iov->iov_len);
341 
342 		if (iob->ib_offset == iov->iov_len) {
343 			iob->ib_iov++;
344 			iob->ib_offset = 0;
345 		}
346 	} while (len > 0);
347 
348 	return (true);
349 }
350