Merge branch 'ps/writev' into seen

A compatibility wrapper for writev(3p) has been reintroduced,
including fixes for CMake build and 'MAX_IO_SIZE' limits on NonStop.
Calls to write(3p) in send_sideband() and cat_blob() have been
refactored to use writev(3p) wrappers to reduce syscall overhead.

* ps/writev:
  fast-import: use writev(3p) to send cat-blob responses
  sideband: use writev(3p) to send pktlines
  wrapper: properly handle MAX_IO_SIZE in writev(3p)
  wrapper: introduce writev(3p) wrappers
  compat/posix: introduce writev(3p) wrapper
This commit is contained in:
Junio C Hamano
2026-07-23 14:13:03 -07:00
12 changed files with 193 additions and 7 deletions

View File

@@ -2045,6 +2045,10 @@ ifdef NO_PREAD
COMPAT_CFLAGS += -DNO_PREAD
COMPAT_OBJS += compat/pread.o
endif
ifdef NO_WRITEV
COMPAT_CFLAGS += -DNO_WRITEV
COMPAT_OBJS += compat/writev.o
endif
ifdef NO_FAST_WORKING_DIRECTORY
BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY
endif

View File

@@ -3335,6 +3335,7 @@ static void cat_blob_write(const char *buf, unsigned long size)
static void cat_blob(struct object_entry *oe, struct object_id *oid)
{
struct strbuf line = STRBUF_INIT;
struct iovec iov[3];
unsigned long size;
enum object_type type = 0;
char *buf;
@@ -3368,10 +3369,21 @@ static void cat_blob(struct object_entry *oe, struct object_id *oid)
strbuf_reset(&line);
strbuf_addf(&line, "%s %s %"PRIuMAX"\n", oid_to_hex(oid),
type_name(type), (uintmax_t)size);
cat_blob_write(line.buf, line.len);
/*
* Write the header, the payload and the trailing newline with a
* single writev(3p) call instead of three separate write(3p) calls.
*/
iov[0].iov_base = line.buf;
iov[0].iov_len = line.len;
iov[1].iov_base = buf;
iov[1].iov_len = size;
iov[2].iov_base = (void *) "\n";
iov[2].iov_len = 1;
if (writev_in_full(cat_blob_fd, iov, ARRAY_SIZE(iov)) < 0)
die_errno(_("write to frontend failed"));
strbuf_release(&line);
cat_blob_write(buf, size);
cat_blob_write("\n", 1);
if (oe && oe->pack_id == pack_id) {
last_blob.offset = oe->idx.offset;
strbuf_attach(&last_blob.data, buf, size, size + 1);

View File

@@ -148,6 +148,9 @@
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/statvfs.h>
#ifndef NO_WRITEV
#include <sys/uio.h>
#endif
#include <termios.h>
#ifndef NO_SYS_SELECT_H
#include <sys/select.h>
@@ -334,6 +337,17 @@ int git_lstat(const char *, struct stat *);
ssize_t git_pread(int fd, void *buf, size_t count, off_t offset);
#endif
#ifdef NO_WRITEV
#define writev git_writev
#define iovec git_iovec
struct git_iovec {
void *iov_base;
size_t iov_len;
};
ssize_t git_writev(int fd, const struct iovec *iov, int iovcnt);
#endif
#ifdef NO_SETENV
#define setenv gitsetenv
int gitsetenv(const char *, const char *, int);

44
compat/writev.c Normal file
View File

@@ -0,0 +1,44 @@
#include "../git-compat-util.h"
#include "../wrapper.h"
ssize_t git_writev(int fd, const struct iovec *iov, int iovcnt)
{
size_t total_written = 0;
size_t sum = 0;
/*
* According to writev(3p), the syscall shall error with EINVAL in case
* the sum of `iov_len` overflows `ssize_t`.
*/
for (int i = 0; i < iovcnt; i++) {
if (iov[i].iov_len > maximum_signed_value_of_type(ssize_t) ||
iov[i].iov_len + sum > maximum_signed_value_of_type(ssize_t)) {
errno = EINVAL;
return -1;
}
sum += iov[i].iov_len;
}
for (int i = 0; i < iovcnt; i++) {
const char *bytes = iov[i].iov_base;
size_t iovec_written = 0;
while (iovec_written < iov[i].iov_len) {
ssize_t bytes_written = xwrite(fd, bytes + iovec_written,
iov[i].iov_len - iovec_written);
if (bytes_written < 0) {
if (total_written)
goto out;
return bytes_written;
}
if (!bytes_written)
goto out;
iovec_written += bytes_written;
total_written += bytes_written;
}
}
out:
return (ssize_t) total_written;
}

View File

@@ -483,6 +483,7 @@ ifeq ($(uname_S),Windows)
SANE_TOOL_PATH ?= $(msvc_bin_dir_msys)
HAVE_ALLOCA_H = YesPlease
NO_PREAD = YesPlease
NO_WRITEV = YesPlease
NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease
NO_POLL = YesPlease
@@ -697,6 +698,7 @@ ifeq ($(uname_S),MINGW)
pathsep = ;
HAVE_ALLOCA_H = YesPlease
NO_PREAD = YesPlease
NO_WRITEV = YesPlease
NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease
NO_POLL = YesPlease

View File

@@ -378,7 +378,7 @@ endif()
#function checks
set(function_checks
strcasestr memmem strlcpy strtoimax strtoumax strtoull
setenv mkdtemp poll pread memmem)
setenv mkdtemp poll pread memmem writev)
#unsetenv,hstrerror are incompatible with windows build
if(NOT WIN32)
@@ -423,6 +423,10 @@ if(NOT HAVE_MEMMEM)
list(APPEND compat_SOURCES compat/memmem.c)
endif()
if(NOT HAVE_WRITEV)
list(APPEND compat_SOURCES compat/writev.c)
endif()
if(NOT WIN32)
if(NOT HAVE_UNSETENV)
list(APPEND compat_SOURCES compat/unsetenv.c)

View File

@@ -1464,6 +1464,7 @@ checkfuncs = {
'initgroups' : [],
'strtoumax' : ['strtoumax.c', 'strtoimax.c'],
'pread' : ['pread.c'],
'writev' : ['writev.c'],
}
if host_machine.system() == 'windows'

View File

@@ -445,6 +445,7 @@ void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_ma
const char *p = data;
while (sz) {
struct iovec iov[2];
unsigned n;
char hdr[5];
@@ -454,12 +455,19 @@ void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_ma
if (0 <= band) {
xsnprintf(hdr, sizeof(hdr), "%04x", n + 5);
hdr[4] = band;
write_or_die(fd, hdr, 5);
iov[0].iov_base = hdr;
iov[0].iov_len = 5;
} else {
xsnprintf(hdr, sizeof(hdr), "%04x", n + 4);
write_or_die(fd, hdr, 4);
iov[0].iov_base = hdr;
iov[0].iov_len = 4;
}
write_or_die(fd, p, n);
iov[1].iov_base = (void *) p;
iov[1].iov_len = n;
writev_or_die(fd, iov, ARRAY_SIZE(iov));
p += n;
sz -= n;
}

View File

@@ -323,6 +323,84 @@ ssize_t write_in_full(int fd, const void *buf, size_t count)
return total;
}
ssize_t xwritev(int fd, struct iovec *iov, int iovcnt)
{
size_t allowed = MAX_IO_SIZE;
int i;
/*
* Some platforms define a comparatively small `MAX_IO_SIZE` that
* limits how many bytes can be written with a single call to
* write(3p) or writev(3p); exceeding that limit causes the syscall to
* fail with EINVAL. Just like xwrite() chomps overly large requests
* for write(3p), pretend that the underlying writev(3p) performed a
* short write by only passing along the leading iovec entries that
* fit into that limit.
*/
for (i = 0; i < iovcnt; i++) {
if (iov[i].iov_len > allowed) {
/*
* If the first buffer is larger than MAX_IO_SIZE,
* let xwrite() deal with it.
*/
if (!i)
return xwrite(fd, iov->iov_base, iov->iov_len);
break;
}
allowed -= iov[i].iov_len;
}
while (1) {
ssize_t bytes_written = writev(fd, iov, i);
if (bytes_written < 0) {
if (errno == EINTR)
continue;
if (handle_nonblock(fd, POLLOUT, errno))
continue;
}
return bytes_written;
}
}
ssize_t writev_in_full(int fd, struct iovec *iov, int iovcnt)
{
ssize_t total_written = 0;
while (iovcnt) {
ssize_t bytes_written = xwritev(fd, iov, iovcnt);
if (bytes_written < 0)
return -1;
if (!bytes_written) {
errno = ENOSPC;
return -1;
}
total_written += bytes_written;
/*
* We first need to discard any iovec entities that have been
* fully written.
*/
while (iovcnt && (size_t)bytes_written >= iov->iov_len) {
bytes_written -= iov->iov_len;
iov++;
iovcnt--;
}
/*
* Finally, we need to adjust the last iovec in case we have
* performed a partial write.
*/
if (iovcnt && bytes_written) {
iov->iov_base = (char *) iov->iov_base + bytes_written;
iov->iov_len -= bytes_written;
}
}
return total_written;
}
ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset)
{
char *p = buf;

View File

@@ -16,6 +16,7 @@ void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_
int xopen(const char *path, int flags, ...);
ssize_t xread(int fd, void *buf, size_t len);
ssize_t xwrite(int fd, const void *buf, size_t len);
ssize_t xwritev(int fd, struct iovec *iov, int iovcnt);
ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
int xdup(int fd);
FILE *xfopen(const char *path, const char *mode);
@@ -47,6 +48,15 @@ ssize_t read_in_full(int fd, void *buf, size_t count);
ssize_t write_in_full(int fd, const void *buf, size_t count);
ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset);
/*
* Try to write all iovecs. Returns -1 in case an error occurred with a proper
* errno set, the number of bytes written otherwise.
*
* Note that the iovec will be modified as a result of this call to adjust for
* partial writes!
*/
ssize_t writev_in_full(int fd, struct iovec *iov, int iovcnt);
static inline ssize_t write_str_in_full(int fd, const char *str)
{
return write_in_full(fd, str, strlen(str));

View File

@@ -96,6 +96,14 @@ void write_or_die(int fd, const void *buf, size_t count)
}
}
void writev_or_die(int fd, struct iovec *iov, int iovlen)
{
if (writev_in_full(fd, iov, iovlen) < 0) {
check_pipe(errno);
die_errno("writev error");
}
}
void fwrite_or_die(FILE *f, const void *buf, size_t count)
{
if (fwrite(buf, 1, count, f) != count)

View File

@@ -7,6 +7,7 @@ void fprintf_or_die(FILE *, const char *fmt, ...);
void fwrite_or_die(FILE *f, const void *buf, size_t count);
void fflush_or_die(FILE *f);
void write_or_die(int fd, const void *buf, size_t count);
void writev_or_die(int fd, struct iovec *iov, int iovlen);
/*
* These values are used to help identify parts of a repository to fsync.