iovec-wrapper: fix memleak, rename functions for consistency, and introduce several helper functions (#41689)

This commit is contained in:
Yu Watanabe
2026-04-19 07:29:03 +09:00
committed by GitHub
5 changed files with 380 additions and 81 deletions
+139 -40
View File
@@ -23,6 +23,28 @@ void iovw_done_free(struct iovec_wrapper *iovw) {
iovw_done(iovw);
}
int iovw_compare(const struct iovec_wrapper *a, const struct iovec_wrapper *b) {
int r;
if (a == b)
return 0;
if (!a || !b)
return CMP(!!a, !!b);
/* Note, this performs structural (element-by-element) comparison, not content-based comparison.
* Two wrappers with identical concatenated content but different element boundaries
* (e.g., ["fo","o"] vs ["f","oo"]) will not compare as equal. */
for (size_t i = 0, n = MIN(a->count, b->count); i < n; i++) {
r = iovec_memcmp(a->iovec + i, b->iovec + i);
if (r != 0)
return r;
}
return CMP(a->count, b->count);
}
int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) {
assert(iovw);
@@ -38,6 +60,36 @@ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) {
return -ENOMEM;
iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len);
return 1;
}
int iovw_put_iov(struct iovec_wrapper *iovw, const struct iovec *iov) {
assert(iovw);
if (!iov)
return 0;
return iovw_put(iovw, iov->iov_base, iov->iov_len);
}
int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) {
assert(iovw);
if (iovw_isempty(source))
return 0;
/* We will reallocate iovw->iovec, hence the source cannot point to the same object. */
if (iovw == source)
return -EINVAL;
if (iovw->count > SIZE_MAX - source->count)
return -E2BIG;
if (iovw->count + source->count > IOV_MAX)
return -E2BIG;
if (!GREEDY_REALLOC_APPEND(iovw->iovec, iovw->count, source->iovec, source->count))
return -ENOMEM;
return 0;
}
@@ -46,13 +98,32 @@ int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) {
int r;
r = iovw_put(iovw, data, len);
if (r < 0)
if (r <= 0)
free(data);
return r;
}
int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len) {
int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov) {
int r;
assert(iovw);
if (!iov)
return 0;
r = iovw_put_iov(iovw, iov);
if (r <= 0)
iovec_done(iov);
else
/* On success, iov->iov_base is now owned by iovw. Let's emptify iov, but do not call
* iovec_done(), of course. */
*iov = (struct iovec) {};
return r;
}
int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len) {
if (len == 0)
return 0;
@@ -63,6 +134,52 @@ int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len) {
return iovw_consume(iovw, c, len);
}
int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov) {
assert(iovw);
if (!iovec_is_set(iov))
return 0;
return iovw_extend(iovw, iov->iov_base, iov->iov_len);
}
int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) {
int r;
assert(iovw);
/* This duplicates the source and merges it into the iovw. */
if (iovw_isempty(source))
return 0;
/* iovw->iovec will be reallocated in the loop below, hence source cannot point to the same object. */
if (iovw == source)
return -EINVAL;
if (iovw->count > SIZE_MAX - source->count)
return -E2BIG;
if (iovw->count + source->count > IOV_MAX)
return -E2BIG;
size_t original_count = iovw->count;
FOREACH_ARRAY(iovec, source->iovec, source->count) {
r = iovw_extend_iov(iovw, iovec);
if (r < 0)
goto rollback;
}
return 0;
rollback:
for (size_t i = original_count; i < iovw->count; i++)
iovec_done(iovw->iovec + i);
iovw->count = original_count;
return r;
}
int iovw_put_string_field_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *value) {
_cleanup_free_ char *x = NULL;
int r;
@@ -125,59 +242,41 @@ size_t iovw_size(const struct iovec_wrapper *iovw) {
return iovec_total_size(iovw->iovec, iovw->count);
}
int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source) {
int r;
int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret) {
assert(iovw);
assert(ret);
assert(target);
/* Squish a series of iovecs into a single iovec. */
/* This duplicates the source and merges it into the target. */
size_t len = iovw_size(iovw);
if (len == SIZE_MAX)
return -E2BIG; /* Prevent theoretical overflow */
if (iovw_isempty(source))
return 0;
/* Always allocate one more byte to make the result usable as a NUL-terminated string. */
_cleanup_free_ uint8_t *buf = malloc(len + 1);
if (!buf)
return -ENOMEM;
size_t original_count = target->count;
uint8_t *p = buf;
FOREACH_ARRAY(i, iovw->iovec, iovw->count)
p = mempcpy(p, i->iov_base, i->iov_len);
FOREACH_ARRAY(iovec, source->iovec, source->count) {
r = iovw_append(target, iovec->iov_base, iovec->iov_len);
if (r < 0)
goto rollback;
}
*p = 0;
*ret = IOVEC_MAKE(TAKE_PTR(buf), len);
return 0;
rollback:
for (size_t i = original_count; i < target->count; i++)
iovec_done(target->iovec + i);
target->count = original_count;
return r;
}
char* iovw_to_cstring(const struct iovec_wrapper *iovw) {
size_t size;
char *p, *ans;
assert(iovw);
/* Squish a series of iovecs into a C string. Embedded NULs are not allowed.
* The caller is expected to filter them out when populating the data. */
size = iovw_size(iovw);
if (size == SIZE_MAX)
return NULL; /* Prevent theoretical overflow */
size ++;
p = ans = new(char, size);
if (!ans)
_cleanup_(iovec_done) struct iovec iov = {};
if (iovw_concat(iovw, &iov) < 0)
return NULL;
FOREACH_ARRAY(iovec, iovw->iovec, iovw->count) {
assert(!memchr(iovec->iov_base, 0, iovec->iov_len));
p = mempcpy(p, iovec->iov_base, iovec->iov_len);
}
*p = '\0';
return ans;
assert(!memchr(iov.iov_base, 0, iov.iov_len));
return TAKE_PTR(iov.iov_base);
}
+12 -2
View File
@@ -11,9 +11,19 @@ struct iovec_wrapper {
void iovw_done_free(struct iovec_wrapper *iovw);
void iovw_done(struct iovec_wrapper *iovw);
int iovw_compare(const struct iovec_wrapper *a, const struct iovec_wrapper *b) _pure_;
static inline bool iovw_equal(const struct iovec_wrapper *a, const struct iovec_wrapper *b) {
return iovw_compare(a, b) == 0;
}
int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len);
int iovw_put_iov(struct iovec_wrapper *iovw, const struct iovec *iov);
int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source);
int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len);
int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len);
int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov);
int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len);
int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov);
int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source);
static inline bool iovw_isempty(const struct iovec_wrapper *iovw) {
return !iovw || iovw->count == 0;
@@ -32,5 +42,5 @@ int iovw_put_string_fieldf_full(struct iovec_wrapper *iovw, bool replace, const
int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value);
void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new);
size_t iovw_size(const struct iovec_wrapper *iovw);
int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source);
int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret);
char* iovw_to_cstring(const struct iovec_wrapper *iovw);
+1 -1
View File
@@ -50,7 +50,7 @@ int coredump_backtrace(int argc, char *argv[]) {
} else {
/* The imported iovecs are not supposed to be freed by us so let's copy and merge them at the
* end of the array. */
r = iovw_append_iovw(&context.iovw, &importer.iovw);
r = iovw_extend_iovw(&context.iovw, &importer.iovw);
if (r < 0)
return r;
}
+1 -1
View File
@@ -41,7 +41,7 @@ static size_t output_callback(char *buf,
return 0;
}
r = iovw_append(&context->upload_answer, buf, nmemb);
r = iovw_extend(&context->upload_answer, buf, nmemb);
if (r < 0) {
log_warning("Failed to store server answer (%zu bytes): out of memory", nmemb);
return 0; /* Returning < nmemb signals failure */
+227 -37
View File
@@ -3,9 +3,66 @@
#include <sys/uio.h>
#include "alloc-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "tests.h"
TEST(iovw_compare) {
_cleanup_(iovw_done) struct iovec_wrapper a1 = {}, a2 = {}, b = {}, c = {}, d = {}, e = {};
ASSERT_OK(iovw_put(&a1, (char*) "foo", 3));
ASSERT_OK(iovw_put(&a1, (char*) "aaaaa", 5));
ASSERT_OK(iovw_put(&a2, (char*) "foo", 3));
ASSERT_OK(iovw_put(&a2, (char*) "aaaaa", 5));
ASSERT_OK(iovw_put(&b, (char*) "foo", 3));
ASSERT_OK(iovw_put(&b, (char*) "bbbbb", 5));
ASSERT_OK(iovw_put(&c, (char*) "foo", 3));
ASSERT_OK(iovw_put(&d, (char*) "fooaa", 5));
ASSERT_OK(iovw_put(&d, (char*) "aaa", 3));
ASSERT_EQ(iovw_compare(&a1, &a1), 0);
ASSERT_EQ(iovw_compare(&a1, &a2), 0);
ASSERT_EQ(iovw_compare(&a2, &a1), 0);
ASSERT_LT(iovw_compare(&a1, &b), 0);
ASSERT_GT(iovw_compare(&b, &a1), 0);
ASSERT_EQ(iovw_compare(&b, &b), 0);
ASSERT_GT(iovw_compare(&a1, &c), 0);
ASSERT_LT(iovw_compare(&c, &a1), 0);
ASSERT_EQ(iovw_compare(&c, &c), 0);
ASSERT_LT(iovw_compare(&a1, &d), 0);
ASSERT_GT(iovw_compare(&d, &a1), 0);
ASSERT_EQ(iovw_compare(&d, &d), 0);
ASSERT_GT(iovw_compare(&a1, &e), 0);
ASSERT_LT(iovw_compare(&e, &a1), 0);
ASSERT_EQ(iovw_compare(&e, &e), 0);
ASSERT_GT(iovw_compare(&a1, NULL), 0);
ASSERT_LT(iovw_compare(NULL, &a1), 0);
ASSERT_EQ(iovw_compare(NULL, NULL), 0);
ASSERT_TRUE(iovw_equal(&a1, &a1));
ASSERT_TRUE(iovw_equal(&a1, &a2));
ASSERT_TRUE(iovw_equal(&a2, &a1));
ASSERT_FALSE(iovw_equal(&a1, &b));
ASSERT_FALSE(iovw_equal(&b, &a1));
ASSERT_TRUE(iovw_equal(&b, &b));
ASSERT_FALSE(iovw_equal(&a1, &c));
ASSERT_FALSE(iovw_equal(&c, &a1));
ASSERT_TRUE(iovw_equal(&c, &c));
ASSERT_FALSE(iovw_equal(&a1, &d));
ASSERT_FALSE(iovw_equal(&d, &a1));
ASSERT_TRUE(iovw_equal(&d, &d));
ASSERT_FALSE(iovw_equal(&a1, &e));
ASSERT_FALSE(iovw_equal(&e, &a1));
ASSERT_TRUE(iovw_equal(&e, &e));
ASSERT_FALSE(iovw_equal(&a1, NULL));
ASSERT_FALSE(iovw_equal(NULL, &a1));
ASSERT_TRUE(iovw_equal(NULL, NULL));
}
TEST(iovw_put) {
_cleanup_(iovw_done) struct iovec_wrapper iovw = {};
@@ -27,18 +84,84 @@ TEST(iovw_put) {
ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "q", 1), 0);
}
TEST(iovw_append) {
TEST(iovw_put_iov) {
/* iovw_put_iov() does not copy the input, hence do not use iovw_done_free */
_cleanup_(iovw_done) struct iovec_wrapper iovw = {};
/* Appending an empty/NULL source is a no-op */
ASSERT_OK_ZERO(iovw_put_iov(&iovw, NULL));
ASSERT_OK_ZERO(iovw_put_iov(&iovw, &(struct iovec) {}));
ASSERT_EQ(iovw.count, 0U);
ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("aaa")));
ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("bbb")));
ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("ccc")));
ASSERT_EQ(iovw.count, 3U);
ASSERT_EQ(iovec_memcmp(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa")), 0);
ASSERT_EQ(iovec_memcmp(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb")), 0);
ASSERT_EQ(iovec_memcmp(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc")), 0);
}
TEST(iovw_put_iovw) {
_cleanup_(iovw_done) struct iovec_wrapper target = {}, source = {};
/* Appending an empty/NULL source is a no-op */
ASSERT_OK_ZERO(iovw_put_iovw(&target, NULL));
ASSERT_OK_ZERO(iovw_put_iovw(&target, &source));
ASSERT_EQ(target.count, 0U);
ASSERT_OK(iovw_put_iov(&source, &IOVEC_MAKE_STRING("aaa")));
ASSERT_OK(iovw_put_iov(&source, &IOVEC_MAKE_STRING("bbb")));
ASSERT_OK(iovw_put_iov(&source, &IOVEC_MAKE_STRING("ccc")));
ASSERT_EQ(source.count, 3U);
/* Pre-seed target with one entry to check that append adds on top rather than replacing */
ASSERT_OK(iovw_put_iov(&target, &IOVEC_MAKE_STRING("xxx")));
ASSERT_OK(iovw_put_iov(&target, &IOVEC_MAKE_STRING("yyy")));
ASSERT_OK(iovw_put_iov(&target, &IOVEC_MAKE_STRING("zzz")));
ASSERT_EQ(target.count, 3U);
ASSERT_OK(iovw_put_iovw(&target, &source));
ASSERT_EQ(target.count, 6U);
ASSERT_EQ(iovec_memcmp(&target.iovec[0], &IOVEC_MAKE_STRING("xxx")), 0);
ASSERT_EQ(iovec_memcmp(&target.iovec[1], &IOVEC_MAKE_STRING("yyy")), 0);
ASSERT_EQ(iovec_memcmp(&target.iovec[2], &IOVEC_MAKE_STRING("zzz")), 0);
ASSERT_EQ(iovec_memcmp(&target.iovec[3], &IOVEC_MAKE_STRING("aaa")), 0);
ASSERT_EQ(iovec_memcmp(&target.iovec[4], &IOVEC_MAKE_STRING("bbb")), 0);
ASSERT_EQ(iovec_memcmp(&target.iovec[5], &IOVEC_MAKE_STRING("ccc")), 0);
/* iovw_put_iovw() does not copy data, hence the pointers must be equal */
ASSERT_PTR_EQ(target.iovec[3].iov_base, source.iovec[0].iov_base);
ASSERT_PTR_EQ(target.iovec[4].iov_base, source.iovec[1].iov_base);
ASSERT_PTR_EQ(target.iovec[5].iov_base, source.iovec[2].iov_base);
/* Source is unchanged */
ASSERT_EQ(source.count, 3U);
ASSERT_EQ(iovec_memcmp(&source.iovec[0], &IOVEC_MAKE_STRING("aaa")), 0);
ASSERT_EQ(iovec_memcmp(&source.iovec[1], &IOVEC_MAKE_STRING("bbb")), 0);
ASSERT_EQ(iovec_memcmp(&source.iovec[2], &IOVEC_MAKE_STRING("ccc")), 0);
/* Cannot pass the same objects */
ASSERT_ERROR(iovw_put_iovw(&target, &target), EINVAL);
}
TEST(iovw_extend) {
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
/* iovw_append copies the data; the wrapper owns the copies. */
/* Appending an empty/NULL source is a no-op */
ASSERT_OK_ZERO(iovw_extend(&iovw, NULL, 0));
ASSERT_OK_ZERO(iovw_extend(&iovw, "foo", 0));
ASSERT_EQ(iovw.count, 0U);
/* iovw_extend() copies the data; the wrapper owns the copies. */
char buf[4] = { 'o', 'n', 'e', '\0' };
ASSERT_OK(iovw_append(&iovw, buf, 3));
ASSERT_OK(iovw_extend(&iovw, buf, 3));
ASSERT_EQ(iovw.count, 1U);
ASSERT_EQ(iovw.iovec[0].iov_len, 3U);
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0);
/* Insert with a NUL */
ASSERT_OK_ZERO(iovw_append(&iovw, buf, 4));
ASSERT_OK(iovw_extend(&iovw, buf, 4));
ASSERT_EQ(iovw.count, 2U);
ASSERT_EQ(iovw.iovec[1].iov_len, 4U);
ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "one\0", 4), 0);
@@ -48,6 +171,60 @@ TEST(iovw_append) {
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0);
}
TEST(iovw_extend_iov) {
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
/* Appending an empty/NULL source is a no-op */
ASSERT_OK_ZERO(iovw_extend_iov(&iovw, NULL));
ASSERT_OK_ZERO(iovw_extend_iov(&iovw, &(struct iovec) {}));
ASSERT_EQ(iovw.count, 0U);
ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("aaa")));
ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("bbb")));
ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("ccc")));
ASSERT_EQ(iovw.count, 3U);
ASSERT_EQ(iovec_memcmp(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa")), 0);
ASSERT_EQ(iovec_memcmp(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb")), 0);
ASSERT_EQ(iovec_memcmp(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc")), 0);
}
TEST(iovw_extend_iovw) {
_cleanup_(iovw_done_free) struct iovec_wrapper target = {};
_cleanup_(iovw_done) struct iovec_wrapper source = {};
/* Appending an empty/NULL source is a no-op */
ASSERT_OK_ZERO(iovw_extend_iovw(&target, NULL));
ASSERT_OK_ZERO(iovw_extend_iovw(&target, &source));
ASSERT_EQ(target.count, 0U);
ASSERT_OK(iovw_put(&source, (char*) "one", 3));
ASSERT_OK(iovw_put(&source, (char*) "twotwo", 6));
ASSERT_EQ(source.count, 2U);
/* Pre-seed target with one entry to check that append adds on top rather than replacing */
char *seed = strdup("zero");
ASSERT_NOT_NULL(seed);
ASSERT_OK(iovw_put(&target, seed, strlen(seed)));
ASSERT_OK(iovw_extend_iovw(&target, &source));
ASSERT_EQ(target.count, 3U);
/* Appended entries must be fresh copies, not aliases of the source entries */
ASSERT_TRUE(target.iovec[1].iov_base != source.iovec[0].iov_base);
ASSERT_TRUE(target.iovec[2].iov_base != source.iovec[1].iov_base);
ASSERT_EQ(target.iovec[1].iov_len, 3U);
ASSERT_EQ(memcmp(target.iovec[1].iov_base, "one", 3), 0);
ASSERT_EQ(target.iovec[2].iov_len, 6U);
ASSERT_EQ(memcmp(target.iovec[2].iov_base, "twotwo", 6), 0);
/* Source is unchanged */
ASSERT_EQ(source.count, 2U);
/* Cannot pass the same objects */
ASSERT_ERROR(iovw_extend_iovw(&target, &target), EINVAL);
}
TEST(iovw_consume) {
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
@@ -58,14 +235,43 @@ TEST(iovw_consume) {
/* iovw_consume moves ownership in place, no copy */
ASSERT_PTR_EQ(iovw.iovec[0].iov_base, p);
/* Zero-length: iovw_put returns 0 without adding anything, and does not free the payload.
* Confirm by strdup'ing something and explicitly freeing it afterwards. */
_cleanup_free_ char *q = strdup("");
ASSERT_NOT_NULL(q);
/* Zero-length: iovw_put returns 0 without adding anything. Even in that case, iovw_consume() frees
* the payload. Confirm by strdup'ing something to verify that when running with sanitizer/valgrind. */
char *q = ASSERT_NOT_NULL(strdup(""));
ASSERT_OK_ZERO(iovw_consume(&iovw, q, 0));
ASSERT_EQ(iovw.count, 1U);
}
TEST(iovw_consume_iov) {
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
ASSERT_OK_ZERO(iovw_consume_iov(&iovw, NULL));
ASSERT_EQ(iovw.count, 0U);
ASSERT_OK_ZERO(iovw_consume_iov(&iovw, &(struct iovec) {}));
ASSERT_EQ(iovw.count, 0U);
struct iovec iov = {
.iov_base = ASSERT_NOT_NULL(strdup("consumed")),
.iov_len = strlen("consumed"),
};
ASSERT_OK(iovw_consume_iov(&iovw, &iov));
ASSERT_EQ(iovw.count, 1U);
/* iovw_consume_iov takes the ownership of the buffer, and emptifies the iovec. */
ASSERT_NULL(iov.iov_base);
ASSERT_EQ(iov.iov_len, 0U);
iov = (struct iovec) {
.iov_base = ASSERT_NOT_NULL(strdup("")),
.iov_len = 0,
};
ASSERT_OK_ZERO(iovw_consume_iov(&iovw, &iov));
ASSERT_EQ(iovw.count, 1U);
/* zero length iovec is also freed */
ASSERT_NULL(iov.iov_base);
ASSERT_EQ(iov.iov_len, 0U);
}
TEST(iovw_isempty) {
ASSERT_TRUE(iovw_isempty(NULL));
@@ -189,38 +395,22 @@ TEST(iovw_size) {
ASSERT_EQ(iovw_size(&iovw), 12U);
}
TEST(iovw_append_iovw) {
_cleanup_(iovw_done_free) struct iovec_wrapper target = {};
_cleanup_(iovw_done) struct iovec_wrapper source = {};
TEST(iovw_concat) {
_cleanup_(iovw_done) struct iovec_wrapper iovw = {};
/* Appending an empty/NULL source is a no-op */
ASSERT_OK_ZERO(iovw_append_iovw(&target, NULL));
ASSERT_OK_ZERO(iovw_append_iovw(&target, &source));
ASSERT_EQ(target.count, 0U);
/* Empty wrapper -> empty string with 0 length */
_cleanup_(iovec_done) struct iovec iov = {};
ASSERT_OK(iovw_concat(&iovw, &iov));
ASSERT_FALSE(iovec_is_set(&iov));
ASSERT_STREQ(iov.iov_base, "");
iovec_done(&iov);
ASSERT_OK(iovw_put(&source, (char*) "one", 3));
ASSERT_OK(iovw_put(&source, (char*) "twotwo", 6));
ASSERT_EQ(source.count, 2U);
ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3));
ASSERT_OK(iovw_put(&iovw, (char*) "\0", 1));
ASSERT_OK(iovw_put(&iovw, (char*) "bar", 4));
/* Pre-seed target with one entry to check that append adds on top rather than replacing */
char *seed = strdup("zero");
ASSERT_NOT_NULL(seed);
ASSERT_OK(iovw_put(&target, seed, strlen(seed)));
ASSERT_OK(iovw_append_iovw(&target, &source));
ASSERT_EQ(target.count, 3U);
/* Appended entries must be fresh copies, not aliases of the source entries */
ASSERT_TRUE(target.iovec[1].iov_base != source.iovec[0].iov_base);
ASSERT_TRUE(target.iovec[2].iov_base != source.iovec[1].iov_base);
ASSERT_EQ(target.iovec[1].iov_len, 3U);
ASSERT_EQ(memcmp(target.iovec[1].iov_base, "one", 3), 0);
ASSERT_EQ(target.iovec[2].iov_len, 6U);
ASSERT_EQ(memcmp(target.iovec[2].iov_base, "twotwo", 6), 0);
/* Source is unchanged */
ASSERT_EQ(source.count, 2U);
ASSERT_OK(iovw_concat(&iovw, &iov));
ASSERT_EQ(iovec_memcmp(&iov, &IOVEC_MAKE("foo\0bar\0", 8)), 0);
}
TEST(iovw_to_cstring) {