From 8b9fb1bbec97dddeee68c5060b06ec9d45e168f3 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 1 Oct 2026 09:24:45 +0200 Subject: [PATCH] io-util: add pwritev_full() and pwrite_full() loop_write() writes one buffer at the file position. To write at an offset, callers call pwrite() or pwritev() directly and handle short writes themselves. Add pwritev_full(), which writes all iovecs at an offset, retries on EINTR and short writes, splits batches larger than IOV_MAX, and reports the number of bytes written, also on failure. Add pwrite_full(), which does the same for one buffer. (cherry picked from commit 4d36be491a568180dc6336b91b0c496bb8cb6601) --- src/basic/io-util.c | 50 +++++++++++++++++++++++++++++++++++++++++ src/basic/io-util.h | 3 +++ src/test/test-io-util.c | 49 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/src/basic/io-util.c b/src/basic/io-util.c index d4a95128c8..0b584a638a 100644 --- a/src/basic/io-util.c +++ b/src/basic/io-util.c @@ -11,6 +11,7 @@ #include "errno-util.h" #include "fiber-ops.h" #include "io-util.h" +#include "iovec-util.h" #include "time-util.h" uint32_t poll_events_to_epoll(uint32_t events) { @@ -249,6 +250,55 @@ int loop_write_full(int fd, const void *buf, size_t nbytes, usec_t timeout) { return 0; } +int pwritev_full(int fd, struct iovec *iovec, size_t n, uint64_t offset, uint64_t *ret_written) { + uint64_t written = 0; + int r = 0; + + assert(fd >= 0); + assert(iovec || n == 0); + + /* Writes all of the iovecs at the offset. The iovecs are advanced past what was written, and + * 'ret_written' tells how much that was, also if the write fails halfway. */ + + for (size_t i = 0;;) { + size_t m; + ssize_t k; + + while (i < n && !iovec_is_set(iovec + i)) + i++; + if (i >= n) + break; + + m = MIN(n - i, (size_t) IOV_MAX); + k = pwritev(fd, iovec + i, m, offset + written); + if (k < 0) { + if (errno == EINTR) + continue; + + r = -errno; + break; + } + if (k == 0) { + r = -EIO; + break; + } + + written += k; + (void) iovec_inc_many(iovec + i, m, k); + } + + if (ret_written) + *ret_written = written; + return r; +} + +int pwrite_full(int fd, const void *buf, size_t nbytes, uint64_t offset) { + assert(fd >= 0); + assert(buf || nbytes == 0); + + return pwritev_full(fd, &IOVEC_MAKE((void*) buf, nbytes), 1, offset, /* ret_written= */ NULL); +} + int pipe_eof(int fd) { int r; diff --git a/src/basic/io-util.h b/src/basic/io-util.h index 0b3d7fff8d..8ae8e29bb2 100644 --- a/src/basic/io-util.h +++ b/src/basic/io-util.h @@ -20,6 +20,9 @@ static inline int loop_write(int fd, const void *buf, size_t nbytes) { return loop_write_full(fd, buf, nbytes, 0); } +int pwritev_full(int fd, struct iovec *iovec, size_t n, uint64_t offset, uint64_t *ret_written); +int pwrite_full(int fd, const void *buf, size_t nbytes, uint64_t offset) _nonnull_if_nonzero_(2, 3); + int pipe_eof(int fd); int ppoll_usec_full(struct pollfd *fds, size_t n_fds, usec_t timeout, const sigset_t *ss) _nonnull_if_nonzero_(1, 2); diff --git a/src/test/test-io-util.c b/src/test/test-io-util.c index dbf3495ab9..694cf01885 100644 --- a/src/test/test-io-util.c +++ b/src/test/test-io-util.c @@ -6,7 +6,9 @@ #include "fd-util.h" #include "io-util.h" +#include "iovec-util.h" #include "tests.h" +#include "tmpfile-util.h" static void test_sparse_write_one(int fd, const char *buffer, size_t n) { char check[n]; @@ -43,4 +45,51 @@ TEST(sparse_write) { test_sparse_write_one(fd, test_e, sizeof(test_e)); } +TEST(pwritev_full) { + _cleanup_close_ int fd = -EBADF; + struct iovec iovec[2 + 2 * IOV_MAX]; + char expected[3 + 2 * IOV_MAX], buf[sizeof(expected)]; + uint64_t written; + size_t n = 0; + + ASSERT_OK(fd = open_tmpfile_unlinkable(NULL, O_RDWR|O_CLOEXEC)); + + /* Nothing, or only empty iovecs, writes nothing */ + ASSERT_OK(pwritev_full(fd, NULL, 0, 0, &written)); + ASSERT_EQ(written, 0U); + iovec[0] = (struct iovec) {}; + ASSERT_OK(pwritev_full(fd, iovec, 1, 0, &written)); + ASSERT_EQ(written, 0U); + + /* More iovecs than one pwritev() takes, some of them empty */ + iovec[n++] = IOVEC_MAKE_STRING("abc"); + iovec[n++] = (struct iovec) {}; + memcpy(expected, "abc", 3); + for (size_t k = 0; k < 2 * IOV_MAX; k++) { + expected[3 + k] = 'a' + k % 26; + iovec[n++] = IOVEC_MAKE(expected + 3 + k, 1); + } + + ASSERT_OK(pwritev_full(fd, iovec, n, 7, &written)); + ASSERT_EQ(written, sizeof(expected)); + ASSERT_EQ(pread(fd, buf, sizeof(buf), 7), (ssize_t) sizeof(buf)); + ASSERT_EQ(memcmp(buf, expected, sizeof(expected)), 0); + + /* Written iovecs are advanced past the data */ + FOREACH_ARRAY(i, iovec, n) + ASSERT_FALSE(iovec_is_set(i)); +} + +TEST(pwrite_full) { + _cleanup_close_ int fd = -EBADF; + char buf[3]; + + ASSERT_OK(fd = open_tmpfile_unlinkable(NULL, O_RDWR|O_CLOEXEC)); + + ASSERT_OK(pwrite_full(fd, NULL, 0, 0)); + ASSERT_OK(pwrite_full(fd, "abc", 3, 5)); + ASSERT_EQ(pread(fd, buf, sizeof(buf), 5), (ssize_t) sizeof(buf)); + ASSERT_EQ(memcmp(buf, "abc", 3), 0); +} + DEFINE_TEST_MAIN(LOG_INFO);