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)
This commit is contained in:
Daan De Meyer
2026-10-16 17:24:51 +01:00
committed by Luca Boccassi
parent fcaf217ab2
commit 8b9fb1bbec
3 changed files with 102 additions and 0 deletions
+50
View File
@@ -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;
+3
View File
@@ -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);
+49
View File
@@ -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);