diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index a4604f9a3c..59b1addaaf 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -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); } diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index eaa859af06..26b7f5ad4b 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -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); diff --git a/src/coredump/coredump-backtrace.c b/src/coredump/coredump-backtrace.c index b8aa880f8e..9af7b5adb9 100644 --- a/src/coredump/coredump-backtrace.c +++ b/src/coredump/coredump-backtrace.c @@ -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; } diff --git a/src/report/report-upload.c b/src/report/report-upload.c index 218742f540..1744d0d91d 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -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 */ diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index 35b9396572..6764d2e78e 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -3,9 +3,66 @@ #include #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) {