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