#include <sys/param.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <limits.h>
#include <errno.h>
#include "iov.h"
struct iovec *
split_iov(struct iovec *iov, size_t *niov1, size_t offset, size_t *niov2)
{
size_t count, resid;
resid = offset;
for (count = 0; count < *niov1; count++) {
if (resid < iov[count].iov_len)
break;
resid -= iov[count].iov_len;
}
if (resid == 0 || count == *niov1) {
*niov2 = *niov1 - count;
*niov1 = count;
if (*niov2 == 0)
return (NULL);
return (&iov[count]);
}
*niov1 = count + 1;
*niov2 = *niov1 - count;
memmove(&iov[count + 1], &iov[count], sizeof(struct iovec) * (*niov2));
iov[count].iov_len = resid;
iov[count + 1].iov_base = (char *)iov[count].iov_base + resid;
iov[count + 1].iov_len -= resid;
return (&iov[count + 1]);
}
size_t
count_iov(const struct iovec *iov, size_t niov)
{
size_t total = 0;
size_t i;
for (i = 0; i < niov; i++) {
assert(total <= SIZE_MAX - iov[i].iov_len);
total += iov[i].iov_len;
}
return (total);
}
bool
check_iov_len(const struct iovec *iov, size_t niov, size_t len)
{
size_t total = 0;
size_t i;
for (i = 0; i < niov; i++) {
assert(total <= SIZE_MAX - iov[i].iov_len);
total += iov[i].iov_len;
if (total >= len)
return (true);
}
return (false);
}
size_t
iov_to_buf(const struct iovec *iov, size_t niov, void **buf)
{
size_t ptr, total;
size_t i;
total = count_iov(iov, niov);
*buf = reallocf(*buf, total);
if (*buf == NULL)
return (0);
for (i = 0, ptr = 0; i < niov; i++) {
memcpy((uint8_t *)*buf + ptr, iov[i].iov_base, iov[i].iov_len);
ptr += iov[i].iov_len;
}
return (total);
}
size_t
buf_to_iov(const void *buf, size_t buflen, const struct iovec *iov, size_t niov)
{
size_t off = 0, len;
size_t i;
for (i = 0; i < niov && off < buflen; i++) {
len = MIN(iov[i].iov_len, buflen - off);
memcpy(iov[i].iov_base, (const uint8_t *)buf + off, len);
off += len;
}
return (off);
}
size_t
iov_bunch_init(iov_bunch_t *iob, struct iovec *iov, int niov)
{
bzero(iob, sizeof (*iob));
iob->ib_iov = iov;
iob->ib_remain = count_iov(iov, niov);
return (iob->ib_remain);
}
bool
iov_bunch_copy(iov_bunch_t *iob, void *dst, size_t sz)
{
if (sz > iob->ib_remain)
return (false);
if (sz == 0)
return (true);
caddr_t dest = dst;
do {
struct iovec *iov = iob->ib_iov;
ASSERT3U(iov->iov_len, !=, 0);
const size_t iov_avail = iov->iov_len - iob->ib_offset;
const size_t to_copy = MIN(sz, iov_avail);
if (to_copy != 0 && dest != NULL) {
bcopy((caddr_t)iov->iov_base + iob->ib_offset, dest,
to_copy);
dest += to_copy;
}
sz -= to_copy;
iob->ib_remain -= to_copy;
iob->ib_offset += to_copy;
ASSERT3U(iob->ib_offset, <=, iov->iov_len);
if (iob->ib_offset == iov->iov_len) {
iob->ib_iov++;
iob->ib_offset = 0;
}
} while (sz > 0);
return (true);
}
bool
iov_bunch_skip(iov_bunch_t *iob, size_t sz)
{
return (iov_bunch_copy(iob, NULL, sz));
}
bool
iov_bunch_next_chunk(iov_bunch_t *iob, caddr_t *chunk, size_t *chunk_sz)
{
if (iob->ib_remain == 0) {
*chunk = NULL;
*chunk_sz = 0;
return (false);
}
*chunk_sz = iob->ib_iov->iov_len - iob->ib_offset;
*chunk = (caddr_t)iob->ib_iov->iov_base + iob->ib_offset;
iob->ib_remain -= *chunk_sz;
iob->ib_iov++;
iob->ib_offset = 0;
return (true);
}
void
iov_bunch_to_iov(iov_bunch_t *iob, struct iovec *iov, int *niov, uint_t size)
{
*niov = 0;
while (size-- > 0) {
caddr_t chunk;
size_t sz;
if (!iov_bunch_next_chunk(iob, &chunk, &sz))
break;
iov->iov_base = chunk;
iov->iov_len = sz;
iov++;
(*niov)++;
}
}
ssize_t
iov_bunch_to_buf(iov_bunch_t *iob, void **buf)
{
size_t total = iob->ib_remain;
if (total == 0) {
free(*buf);
*buf = NULL;
return (0);
}
*buf = reallocf(*buf, total);
if (*buf == NULL)
return (-1);
if (!iov_bunch_copy(iob, buf, total))
return (-1);
if (total > SSIZE_MAX) {
errno = EOVERFLOW;
return (-1);
}
return (total);
}
bool
buf_to_iov_bunch(iov_bunch_t *iob, const void *buf, size_t len)
{
const char *src = buf;
if (iob->ib_remain < len)
return (false);
do {
struct iovec *iov = iob->ib_iov;
const size_t iov_avail = iov->iov_len - iob->ib_offset;
const size_t to_copy = MIN(len, iov_avail);
if (to_copy != 0) {
bcopy(src, (caddr_t)iov->iov_base + iob->ib_offset,
to_copy);
}
src += to_copy;
len -= to_copy;
iob->ib_remain -= to_copy;
iob->ib_offset += to_copy;
ASSERT3U(iob->ib_offset, <=, iov->iov_len);
if (iob->ib_offset == iov->iov_len) {
iob->ib_iov++;
iob->ib_offset = 0;
}
} while (len > 0);
return (true);
}