From 6f4594d5dc61bb67978c44cd6eeb0e7bfb621c9b Mon Sep 17 00:00:00 2001
From: Elliott Hughes <enh@google.com>
Date: Wed, 26 Aug 2015 13:27:43 -0700
Subject: [PATCH] Add preadv/pwritev.

Bug: http://b/12612572
Change-Id: I38ff2684d69bd0fe3f21b1d371b88fa60d5421cb
---
 libc/SYSCALLS.TXT                     | 10 +++++
 libc/arch-arm/syscalls/__preadv64.S   | 22 +++++++++++
 libc/arch-arm/syscalls/__pwritev64.S  | 22 +++++++++++
 libc/arch-arm64/syscalls/preadv.S     | 16 ++++++++
 libc/arch-arm64/syscalls/pwritev.S    | 16 ++++++++
 libc/arch-mips/syscalls/__preadv64.S  | 19 ++++++++++
 libc/arch-mips/syscalls/__pwritev64.S | 19 ++++++++++
 libc/arch-mips64/syscalls/preadv.S    | 27 ++++++++++++++
 libc/arch-mips64/syscalls/pwritev.S   | 27 ++++++++++++++
 libc/arch-x86/syscalls/__preadv64.S   | 41 ++++++++++++++++++++
 libc/arch-x86/syscalls/__pwritev64.S  | 41 ++++++++++++++++++++
 libc/arch-x86_64/syscalls/preadv.S    | 18 +++++++++
 libc/arch-x86_64/syscalls/pwritev.S   | 18 +++++++++
 libc/bionic/legacy_32_bit_support.cpp | 20 ++++++++++
 libc/include/sys/uio.h                | 12 ++++++
 libc/libc.map                         |  4 ++
 tests/sys_uio_test.cpp                | 54 ++++++++++++++++++++++++++-
 17 files changed, 384 insertions(+), 2 deletions(-)
 create mode 100644 libc/arch-arm/syscalls/__preadv64.S
 create mode 100644 libc/arch-arm/syscalls/__pwritev64.S
 create mode 100644 libc/arch-arm64/syscalls/preadv.S
 create mode 100644 libc/arch-arm64/syscalls/pwritev.S
 create mode 100644 libc/arch-mips/syscalls/__preadv64.S
 create mode 100644 libc/arch-mips/syscalls/__pwritev64.S
 create mode 100644 libc/arch-mips64/syscalls/preadv.S
 create mode 100644 libc/arch-mips64/syscalls/pwritev.S
 create mode 100644 libc/arch-x86/syscalls/__preadv64.S
 create mode 100644 libc/arch-x86/syscalls/__pwritev64.S
 create mode 100644 libc/arch-x86_64/syscalls/preadv.S
 create mode 100644 libc/arch-x86_64/syscalls/pwritev.S

diff --git a/libc/SYSCALLS.TXT b/libc/SYSCALLS.TXT
index ac8db62e0..4188c1518 100644
--- a/libc/SYSCALLS.TXT
+++ b/libc/SYSCALLS.TXT
@@ -94,6 +94,16 @@ ssize_t     pread64(int, void*, size_t, off64_t) arm,mips,x86
 ssize_t     pread64|pread(int, void*, size_t, off_t) arm64,mips64,x86_64
 ssize_t     pwrite64(int, void*, size_t, off64_t) arm,mips,x86
 ssize_t     pwrite64|pwrite(int, void*, size_t, off_t) arm64,mips64,x86_64
+
+# On LP32, preadv/pwritev don't use off64_t --- they use pairs of 32-bit
+# arguments to avoid problems on architectures like ARM where 64-bit arguments
+# must be in a register pair starting with an even-numbered register.
+# See linux/fs/read_write.c and https://lwn.net/Articles/311630/.
+ssize_t     __preadv64:preadv(int, const struct iovec*, int, long, long) arm,mips,x86
+ssize_t     preadv|preadv64(int, const struct iovec*, int, off_t) arm64,mips64,x86_64
+ssize_t     __pwritev64:pwritev(int, const struct iovec*, int, long, long) arm,mips,x86
+ssize_t     pwritev|pwritev64(int, const struct iovec*, int, off_t) arm64,mips64,x86_64
+
 int         ___close:close(int)  all
 pid_t       __getpid:getpid()  all
 int         munmap(void*, size_t)  all
diff --git a/libc/arch-arm/syscalls/__preadv64.S b/libc/arch-arm/syscalls/__preadv64.S
new file mode 100644
index 000000000..19eaaa54e
--- /dev/null
+++ b/libc/arch-arm/syscalls/__preadv64.S
@@ -0,0 +1,22 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(__preadv64)
+    mov     ip, sp
+    stmfd   sp!, {r4, r5, r6, r7}
+    .cfi_def_cfa_offset 16
+    .cfi_rel_offset r4, 0
+    .cfi_rel_offset r5, 4
+    .cfi_rel_offset r6, 8
+    .cfi_rel_offset r7, 12
+    ldmfd   ip, {r4, r5, r6}
+    ldr     r7, =__NR_preadv
+    swi     #0
+    ldmfd   sp!, {r4, r5, r6, r7}
+    .cfi_def_cfa_offset 0
+    cmn     r0, #(MAX_ERRNO + 1)
+    bxls    lr
+    neg     r0, r0
+    b       __set_errno_internal
+END(__preadv64)
diff --git a/libc/arch-arm/syscalls/__pwritev64.S b/libc/arch-arm/syscalls/__pwritev64.S
new file mode 100644
index 000000000..afcbe403e
--- /dev/null
+++ b/libc/arch-arm/syscalls/__pwritev64.S
@@ -0,0 +1,22 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(__pwritev64)
+    mov     ip, sp
+    stmfd   sp!, {r4, r5, r6, r7}
+    .cfi_def_cfa_offset 16
+    .cfi_rel_offset r4, 0
+    .cfi_rel_offset r5, 4
+    .cfi_rel_offset r6, 8
+    .cfi_rel_offset r7, 12
+    ldmfd   ip, {r4, r5, r6}
+    ldr     r7, =__NR_pwritev
+    swi     #0
+    ldmfd   sp!, {r4, r5, r6, r7}
+    .cfi_def_cfa_offset 0
+    cmn     r0, #(MAX_ERRNO + 1)
+    bxls    lr
+    neg     r0, r0
+    b       __set_errno_internal
+END(__pwritev64)
diff --git a/libc/arch-arm64/syscalls/preadv.S b/libc/arch-arm64/syscalls/preadv.S
new file mode 100644
index 000000000..cb8300d51
--- /dev/null
+++ b/libc/arch-arm64/syscalls/preadv.S
@@ -0,0 +1,16 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(preadv)
+    mov     x8, __NR_preadv
+    svc     #0
+
+    cmn     x0, #(MAX_ERRNO + 1)
+    cneg    x0, x0, hi
+    b.hi    __set_errno_internal
+
+    ret
+END(preadv)
+
+ALIAS_SYMBOL(preadv64, preadv)
diff --git a/libc/arch-arm64/syscalls/pwritev.S b/libc/arch-arm64/syscalls/pwritev.S
new file mode 100644
index 000000000..621466a6a
--- /dev/null
+++ b/libc/arch-arm64/syscalls/pwritev.S
@@ -0,0 +1,16 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(pwritev)
+    mov     x8, __NR_pwritev
+    svc     #0
+
+    cmn     x0, #(MAX_ERRNO + 1)
+    cneg    x0, x0, hi
+    b.hi    __set_errno_internal
+
+    ret
+END(pwritev)
+
+ALIAS_SYMBOL(pwritev64, pwritev)
diff --git a/libc/arch-mips/syscalls/__preadv64.S b/libc/arch-mips/syscalls/__preadv64.S
new file mode 100644
index 000000000..a46b86983
--- /dev/null
+++ b/libc/arch-mips/syscalls/__preadv64.S
@@ -0,0 +1,19 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(__preadv64)
+    .set noreorder
+    .cpload t9
+    li v0, __NR_preadv
+    syscall
+    bnez a3, 1f
+    move a0, v0
+    j ra
+    nop
+1:
+    la t9,__set_errno_internal
+    j t9
+    nop
+    .set reorder
+END(__preadv64)
diff --git a/libc/arch-mips/syscalls/__pwritev64.S b/libc/arch-mips/syscalls/__pwritev64.S
new file mode 100644
index 000000000..1222942f2
--- /dev/null
+++ b/libc/arch-mips/syscalls/__pwritev64.S
@@ -0,0 +1,19 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(__pwritev64)
+    .set noreorder
+    .cpload t9
+    li v0, __NR_pwritev
+    syscall
+    bnez a3, 1f
+    move a0, v0
+    j ra
+    nop
+1:
+    la t9,__set_errno_internal
+    j t9
+    nop
+    .set reorder
+END(__pwritev64)
diff --git a/libc/arch-mips64/syscalls/preadv.S b/libc/arch-mips64/syscalls/preadv.S
new file mode 100644
index 000000000..df43a8dc1
--- /dev/null
+++ b/libc/arch-mips64/syscalls/preadv.S
@@ -0,0 +1,27 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(preadv)
+    .set push
+    .set noreorder
+    li v0, __NR_preadv
+    syscall
+    bnez a3, 1f
+    move a0, v0
+    j ra
+    nop
+1:
+    move t0, ra
+    bal     2f
+    nop
+2:
+    .cpsetup ra, t1, 2b
+    LA t9,__set_errno_internal
+    .cpreturn
+    j t9
+    move ra, t0
+    .set pop
+END(preadv)
+
+ALIAS_SYMBOL(preadv64, preadv)
diff --git a/libc/arch-mips64/syscalls/pwritev.S b/libc/arch-mips64/syscalls/pwritev.S
new file mode 100644
index 000000000..f55cec02b
--- /dev/null
+++ b/libc/arch-mips64/syscalls/pwritev.S
@@ -0,0 +1,27 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(pwritev)
+    .set push
+    .set noreorder
+    li v0, __NR_pwritev
+    syscall
+    bnez a3, 1f
+    move a0, v0
+    j ra
+    nop
+1:
+    move t0, ra
+    bal     2f
+    nop
+2:
+    .cpsetup ra, t1, 2b
+    LA t9,__set_errno_internal
+    .cpreturn
+    j t9
+    move ra, t0
+    .set pop
+END(pwritev)
+
+ALIAS_SYMBOL(pwritev64, pwritev)
diff --git a/libc/arch-x86/syscalls/__preadv64.S b/libc/arch-x86/syscalls/__preadv64.S
new file mode 100644
index 000000000..3ce4fc3d9
--- /dev/null
+++ b/libc/arch-x86/syscalls/__preadv64.S
@@ -0,0 +1,41 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(__preadv64)
+    pushl   %ebx
+    .cfi_def_cfa_offset 8
+    .cfi_rel_offset ebx, 0
+    pushl   %ecx
+    .cfi_adjust_cfa_offset 4
+    .cfi_rel_offset ecx, 0
+    pushl   %edx
+    .cfi_adjust_cfa_offset 4
+    .cfi_rel_offset edx, 0
+    pushl   %esi
+    .cfi_adjust_cfa_offset 4
+    .cfi_rel_offset esi, 0
+    pushl   %edi
+    .cfi_adjust_cfa_offset 4
+    .cfi_rel_offset edi, 0
+    mov     24(%esp), %ebx
+    mov     28(%esp), %ecx
+    mov     32(%esp), %edx
+    mov     36(%esp), %esi
+    mov     40(%esp), %edi
+    movl    $__NR_preadv, %eax
+    int     $0x80
+    cmpl    $-MAX_ERRNO, %eax
+    jb      1f
+    negl    %eax
+    pushl   %eax
+    call    __set_errno_internal
+    addl    $4, %esp
+1:
+    popl    %edi
+    popl    %esi
+    popl    %edx
+    popl    %ecx
+    popl    %ebx
+    ret
+END(__preadv64)
diff --git a/libc/arch-x86/syscalls/__pwritev64.S b/libc/arch-x86/syscalls/__pwritev64.S
new file mode 100644
index 000000000..2ce5b0ee5
--- /dev/null
+++ b/libc/arch-x86/syscalls/__pwritev64.S
@@ -0,0 +1,41 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(__pwritev64)
+    pushl   %ebx
+    .cfi_def_cfa_offset 8
+    .cfi_rel_offset ebx, 0
+    pushl   %ecx
+    .cfi_adjust_cfa_offset 4
+    .cfi_rel_offset ecx, 0
+    pushl   %edx
+    .cfi_adjust_cfa_offset 4
+    .cfi_rel_offset edx, 0
+    pushl   %esi
+    .cfi_adjust_cfa_offset 4
+    .cfi_rel_offset esi, 0
+    pushl   %edi
+    .cfi_adjust_cfa_offset 4
+    .cfi_rel_offset edi, 0
+    mov     24(%esp), %ebx
+    mov     28(%esp), %ecx
+    mov     32(%esp), %edx
+    mov     36(%esp), %esi
+    mov     40(%esp), %edi
+    movl    $__NR_pwritev, %eax
+    int     $0x80
+    cmpl    $-MAX_ERRNO, %eax
+    jb      1f
+    negl    %eax
+    pushl   %eax
+    call    __set_errno_internal
+    addl    $4, %esp
+1:
+    popl    %edi
+    popl    %esi
+    popl    %edx
+    popl    %ecx
+    popl    %ebx
+    ret
+END(__pwritev64)
diff --git a/libc/arch-x86_64/syscalls/preadv.S b/libc/arch-x86_64/syscalls/preadv.S
new file mode 100644
index 000000000..7771a4447
--- /dev/null
+++ b/libc/arch-x86_64/syscalls/preadv.S
@@ -0,0 +1,18 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(preadv)
+    movq    %rcx, %r10
+    movl    $__NR_preadv, %eax
+    syscall
+    cmpq    $-MAX_ERRNO, %rax
+    jb      1f
+    negl    %eax
+    movl    %eax, %edi
+    call    __set_errno_internal
+1:
+    ret
+END(preadv)
+
+ALIAS_SYMBOL(preadv64, preadv)
diff --git a/libc/arch-x86_64/syscalls/pwritev.S b/libc/arch-x86_64/syscalls/pwritev.S
new file mode 100644
index 000000000..d3bd8facb
--- /dev/null
+++ b/libc/arch-x86_64/syscalls/pwritev.S
@@ -0,0 +1,18 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(pwritev)
+    movq    %rcx, %r10
+    movl    $__NR_pwritev, %eax
+    syscall
+    cmpq    $-MAX_ERRNO, %rax
+    jb      1f
+    negl    %eax
+    movl    %eax, %edi
+    call    __set_errno_internal
+1:
+    ret
+END(pwritev)
+
+ALIAS_SYMBOL(pwritev64, pwritev)
diff --git a/libc/bionic/legacy_32_bit_support.cpp b/libc/bionic/legacy_32_bit_support.cpp
index a10766471..20d258d7d 100644
--- a/libc/bionic/legacy_32_bit_support.cpp
+++ b/libc/bionic/legacy_32_bit_support.cpp
@@ -33,6 +33,7 @@
 #include <stdarg.h>
 #include <sys/resource.h>
 #include <sys/types.h>
+#include <sys/uio.h>
 #include <sys/vfs.h>
 #include <unistd.h>
 
@@ -43,6 +44,8 @@
 // System calls we need.
 extern "C" int __fcntl64(int, int, void*);
 extern "C" int __llseek(int, unsigned long, unsigned long, off64_t*, int);
+extern "C" int __preadv64(int, const struct iovec*, int, long, long);
+extern "C" int __pwritev64(int, const struct iovec*, int, long, long);
 
 // For fcntl we use the fcntl64 system call to signal that we're using struct flock64.
 int fcntl(int fd, int cmd, ...) {
@@ -77,6 +80,23 @@ ssize_t pwrite(int fd, const void* buf, size_t byte_count, off_t offset) {
   return pwrite64(fd, buf, byte_count, static_cast<off64_t>(offset));
 }
 
+// On LP32, there is no off_t preadv/pwritev, and even the 64-bit preadv/pwritev
+// don't use off64_t (see SYSCALLS.TXT for more). Here, this means that we need
+// to implement all four functions because the two system calls don't match any
+// of the userspace functions. Unlike llseek, the pair is split lo-hi, not hi-lo.
+ssize_t preadv(int fd, const struct iovec* ios, int count, off_t offset) {
+  return __preadv64(fd, ios, count, offset, 0);
+}
+ssize_t preadv64(int fd, const struct iovec* ios, int count, off64_t offset) {
+  return __preadv64(fd, ios, count, offset, offset >> 32);
+}
+ssize_t pwritev(int fd, const struct iovec* ios, int count, off_t offset) {
+  return __pwritev64(fd, ios, count, offset, 0);
+}
+ssize_t pwritev64(int fd, const struct iovec* ios, int count, off64_t offset) {
+  return __pwritev64(fd, ios, count, offset, offset >> 32);
+}
+
 // There is no fallocate for 32-bit off_t, so we need to widen and call fallocate64.
 int fallocate(int fd, int mode, off_t offset, off_t length) {
   return fallocate64(fd, mode, static_cast<off64_t>(offset), static_cast<off64_t>(length));
diff --git a/libc/include/sys/uio.h b/libc/include/sys/uio.h
index 187ec22a4..72675d127 100644
--- a/libc/include/sys/uio.h
+++ b/libc/include/sys/uio.h
@@ -37,6 +37,18 @@ __BEGIN_DECLS
 int readv(int, const struct iovec*, int);
 int writev(int, const struct iovec*, int);
 
+#if defined(__USE_GNU)
+#if defined(__USE_FILE_OFFSET64)
+ssize_t preadv(int, const struct iovec*, int, off_t) __RENAME(preadv64);
+ssize_t pwritev(int, const struct iovec*, int, off_t) __RENAME(pwritev64);
+#else
+ssize_t preadv(int, const struct iovec*, int, off_t);
+ssize_t pwritev(int, const struct iovec*, int, off_t);
+#endif
+ssize_t preadv64(int, const struct iovec*, int, off64_t);
+ssize_t pwritev64(int, const struct iovec*, int, off64_t);
+#endif
+
 #if defined(__USE_GNU)
 ssize_t process_vm_readv(pid_t, const struct iovec*, unsigned long, const struct iovec*, unsigned long, unsigned long);
 ssize_t process_vm_writev(pid_t, const struct iovec*, unsigned long, const struct iovec*, unsigned long, unsigned long);
diff --git a/libc/libc.map b/libc/libc.map
index 945692050..fbceb832a 100644
--- a/libc/libc.map
+++ b/libc/libc.map
@@ -1342,6 +1342,10 @@ LIBC_N {
     __write_chk;
     getgrgid_r;
     getgrnam_r;
+    preadv;
+    preadv64;
+    pwritev;
+    pwritev64;
     strchrnul;
 } LIBC;
 
diff --git a/tests/sys_uio_test.cpp b/tests/sys_uio_test.cpp
index c7af8a77c..569d4fbad 100644
--- a/tests/sys_uio_test.cpp
+++ b/tests/sys_uio_test.cpp
@@ -18,10 +18,60 @@
 
 #include <sys/uio.h>
 
-TEST(sys_uio, process_vm_readv_ESRCH) {
+#include "TemporaryFile.h"
+
+TEST(sys_uio, readv_writev) {
+  TemporaryFile tf;
+
+  char buf1[] = "hello";
+  char buf2[] = "world";
+  iovec ios[] = { { buf1, 5 }, { buf2, 5 } };
+
+  ASSERT_EQ(10, writev(tf.fd, ios, 2));
+
+  ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET));
+
+  memset(buf1, '1', sizeof(buf1));
+  memset(buf2, '2', sizeof(buf2));
+
+  ASSERT_EQ(10, readv(tf.fd, ios, 2));
+  buf1[5] = buf2[5] = '\0';
+  ASSERT_STREQ("hello", buf1);
+  ASSERT_STREQ("world", buf2);
+}
+
+template <typename ReadFn, typename WriteFn>
+void TestPreadVPwriteV(ReadFn read_fn, WriteFn write_fn) {
+  TemporaryFile tf;
+
+  char buf[] = "world";
+  iovec ios[] = { { buf, 5 } };
+
+  ASSERT_EQ(5, write_fn(tf.fd, ios, 1, 5));
+  ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_CUR));
+
+  strcpy(buf, "hello");
+  ASSERT_EQ(5, write_fn(tf.fd, ios, 1, 0));
+  ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_CUR));
+
+  ASSERT_EQ(5, read_fn(tf.fd, ios, 1, 5));
+  ASSERT_STREQ("world", buf);
+  ASSERT_EQ(5, read_fn(tf.fd, ios, 1, 0));
+  ASSERT_STREQ("hello", buf);
+}
+
+TEST(sys_uio, preadv_pwritev) {
+  TestPreadVPwriteV(preadv, pwritev);
+}
+
+TEST(sys_uio, preadv64_pwritev64) {
+  TestPreadVPwriteV(preadv64, pwritev64);
+}
+
+TEST(sys_uio, process_vm_readv) {
   ASSERT_EQ(0, process_vm_readv(0, nullptr, 0, nullptr, 0, 0));
 }
 
-TEST(sys_uio, process_vm_writev_ESRCH) {
+TEST(sys_uio, process_vm_writev) {
   ASSERT_EQ(0, process_vm_writev(0, nullptr, 0, nullptr, 0, 0));
 }