compat/posix: introduce writev(3p) wrapper

In a subsequent commit we're going to add the first caller to writev(3p). Introduce a compatibility wrapper for this syscall that we can use on systems that don't have this syscall. The syscall exists on modern Unixes like Linux and macOS, and seemingly even for NonStop according to [1]. It doesn't seem to exist on Windows though. [1]: http://nonstoptools.com/manuals/OSS-SystemCalls.pdf [2]: https://www.gnu.org/software/gnulib/manual/html_node/writev.html Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jul 16, 2026 at 09:52 UTC 1ed0bc4e3b297c9dcfaf697760a8a12c1253c091
6 files changed +70 -1
Makefile
+4
@@ -2033,6 +2033,10 @@ ifdef NO_PREAD
2033 COMPAT_CFLAGS += -DNO_PREAD
2034 COMPAT_OBJS += compat/pread.o
2035 endif
2036 +ifdef NO_WRITEV
2037 + COMPAT_CFLAGS += -DNO_WRITEV
2038 + COMPAT_OBJS += compat/writev.o
2039 +endif
2040 ifdef NO_FAST_WORKING_DIRECTORY
2041 BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY
2042 endif
compat/posix.h
+14
@@ -148,6 +148,9 @@
148 #include <sys/socket.h>
149 #include <sys/ioctl.h>
150 #include <sys/statvfs.h>
151 +#ifndef NO_WRITEV
152 +#include <sys/uio.h>
153 +#endif
154 #include <termios.h>
155 #ifndef NO_SYS_SELECT_H
156 #include <sys/select.h>
@@ -334,6 +337,17 @@ int git_lstat(const char *, struct stat *);
337 ssize_t git_pread(int fd, void *buf, size_t count, off_t offset);
338 #endif
339
340 +#ifdef NO_WRITEV
341 +#define writev git_writev
342 +#define iovec git_iovec
343 +struct git_iovec {
344 + void *iov_base;
345 + size_t iov_len;
346 +};
347 +
348 +ssize_t git_writev(int fd, const struct iovec *iov, int iovcnt);
349 +#endif
350 +
351 #ifdef NO_SETENV
352 #define setenv gitsetenv
353 int gitsetenv(const char *, const char *, int);
compat/writev.c new
+44
@@ -0,0 +1,44 @@
1 +#include "../git-compat-util.h"
2 +#include "../wrapper.h"
3 +
4 +ssize_t git_writev(int fd, const struct iovec *iov, int iovcnt)
5 +{
6 + size_t total_written = 0;
7 + size_t sum = 0;
8 +
9 + /*
10 + * According to writev(3p), the syscall shall error with EINVAL in case
11 + * the sum of `iov_len` overflows `ssize_t`.
12 + */
13 + for (int i = 0; i < iovcnt; i++) {
14 + if (iov[i].iov_len > maximum_signed_value_of_type(ssize_t) ||
15 + iov[i].iov_len + sum > maximum_signed_value_of_type(ssize_t)) {
16 + errno = EINVAL;
17 + return -1;
18 + }
19 +
20 + sum += iov[i].iov_len;
21 + }
22 +
23 + for (int i = 0; i < iovcnt; i++) {
24 + const char *bytes = iov[i].iov_base;
25 + size_t iovec_written = 0;
26 +
27 + while (iovec_written < iov[i].iov_len) {
28 + ssize_t bytes_written = xwrite(fd, bytes + iovec_written,
29 + iov[i].iov_len - iovec_written);
30 + if (bytes_written < 0) {
31 + if (total_written)
32 + goto out;
33 + return bytes_written;
34 + }
35 + if (!bytes_written)
36 + goto out;
37 + iovec_written += bytes_written;
38 + total_written += bytes_written;
39 + }
40 + }
41 +
42 +out:
43 + return (ssize_t) total_written;
44 +}
config.mak.uname
+2
@@ -483,6 +483,7 @@ ifeq ($(uname_S),Windows)
483 SANE_TOOL_PATH ?= $(msvc_bin_dir_msys)
484 HAVE_ALLOCA_H = YesPlease
485 NO_PREAD = YesPlease
486 + NO_WRITEV = YesPlease
487 NEEDS_CRYPTO_WITH_SSL = YesPlease
488 NO_LIBGEN_H = YesPlease
489 NO_POLL = YesPlease
@@ -697,6 +698,7 @@ ifeq ($(uname_S),MINGW)
698 pathsep = ;
699 HAVE_ALLOCA_H = YesPlease
700 NO_PREAD = YesPlease
701 + NO_WRITEV = YesPlease
702 NEEDS_CRYPTO_WITH_SSL = YesPlease
703 NO_LIBGEN_H = YesPlease
704 NO_POLL = YesPlease
contrib/buildsystems/CMakeLists.txt
+5 -1
@@ -378,7 +378,7 @@ endif()
378 #function checks
379 set(function_checks
380 strcasestr memmem strlcpy strtoimax strtoumax strtoull
381 - setenv mkdtemp poll pread memmem)
381 + setenv mkdtemp poll pread memmem writev)
382
383 #unsetenv,hstrerror are incompatible with windows build
384 if(NOT WIN32)
@@ -423,6 +423,10 @@ if(NOT HAVE_MEMMEM)
423 list(APPEND compat_SOURCES compat/memmem.c)
424 endif()
425
426 +if(NOT HAVE_WRITEV)
427 + list(APPEND compat_SOURCES compat/writev.c)
428 +endif()
429 +
430 if(NOT WIN32)
431 if(NOT HAVE_UNSETENV)
432 list(APPEND compat_SOURCES compat/unsetenv.c)
meson.build
+1
@@ -1448,6 +1448,7 @@ checkfuncs = {
1448 'initgroups' : [],
1449 'strtoumax' : ['strtoumax.c', 'strtoimax.c'],
1450 'pread' : ['pread.c'],
1451 + 'writev' : ['writev.c'],
1452 }
1453
1454 if host_machine.system() == 'windows'