Snap for 10428683 from 23e8e5196456b787d5e78c4744b76946a8535732 to mainline-adbd-release

Change-Id: I69f1b6731b9c84176d9962f4239b557f0ebedf90
diff --git a/Android.bp b/Android.bp
index d6d7c64..1627f0f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -78,7 +78,7 @@
 }
 
 cc_defaults {
-    name: "libc_musl_defaults",
+    name: "libc_musl_base_defaults",
     host_supported: true,
     device_supported: false,
     system_shared_libs: [],
@@ -86,12 +86,10 @@
         // CFLAGS_C99FSE
         "-nostdinc",
         "-ffreestanding",
-        "-frounding-math",
         "-Wa,--noexecstack",
 
         // CFLAGS_AUTO
-        //"-Os",
-        "-O0",
+        "-Os",
         "-pipe",
         "-fomit-frame-pointer",
         "-fno-unwind-tables",
@@ -138,6 +136,21 @@
         "-Wno-unused-parameter",
     ],
 
+    arch: {
+        x86: {
+            cflags: ["-frounding-math"],
+        },
+        x86_64: {
+            cflags: ["-frounding-math"],
+        },
+        arm: {
+            cflags: ["-Wno-ignored-pragmas"],
+        },
+        arm64: {
+            cflags: ["-Wno-ignored-pragmas"],
+        },
+    },
+
     ldflags: [
         "-Wl,--sort-section,alignment",
         "-Wl,--sort-common",
@@ -157,15 +170,6 @@
 
     asflags: ["-Wno-unused-command-line-argument"],
 
-    header_libs: [
-        // The order here is very important, private headers like src/include/features.h override
-        // public headers like include/features.h, and arch headers like arch/x86_64/ksigaction.h
-        // override private headers like src/internal/ksigaction.h.
-        "libc_musl_arch_headers",
-        "libc_musl_private_headers",
-        "libc_musl_public_headers",
-    ],
-
     stl: "none",
     c_std: "c99",
     sanitize: {
@@ -184,6 +188,19 @@
     },
 }
 
+cc_defaults {
+    name: "libc_musl_defaults",
+    defaults: ["libc_musl_base_defaults"],
+    header_libs: [
+        // The order here is very important, private headers like src/include/features.h override
+        // public headers like include/features.h, and arch headers like arch/x86_64/ksigaction.h
+        // override private headers like src/internal/ksigaction.h.
+        "libc_musl_arch_headers",
+        "libc_musl_private_headers",
+        "libc_musl_public_headers",
+    ],
+}
+
 cc_library_headers {
     name: "libc_musl_headers",
     visibility: ["//bionic/libc"],
@@ -219,6 +236,14 @@
         "-nostdlib",
     ],
     dynamic_list: "dynamic.list",
+    export_header_lib_headers: [
+        "libc_musl_arch_headers",
+        "libc_musl_public_headers",
+        "libc_llndk_headers",
+    ],
+    header_libs: [
+        "libc_llndk_headers",
+    ],
 }
 
 // All the static parts of the main musl libc.  Don't use this directly, use
@@ -307,19 +332,6 @@
     ],
 }
 
-// Musl sources for the dynamic linker when used in the sysroot.
-cc_library_static {
-    name: "libc_musl_ldso_sysroot",
-    defaults: [
-        "libc_musl_defaults",
-        "libc_musl_ldso_sources",
-    ],
-    cflags: [
-        "-fno-stack-protector",
-        "-DLIBC_SONAME=libc.so",
-    ],
-}
-
 // An attempt to compile the dynamic linker as a standalone library separate from libc_musl.so.
 // Not used yet.
 cc_library_shared {
@@ -339,65 +351,55 @@
     ],
 }
 
-// Convert the linker (which is actually libc_musl.so) into a .s file for embedding in crtbegin.
-cc_genrule {
-    name: "musl_linker_asm",
-    host_supported: true,
-    device_supported: false,
-    tools: ["extract_linker"],
-    cmd: "$(location) -s $(out) $(in)",
-    srcs: [":libc_musl"],
-    out: ["linker.s"],
-    target: {
-        darwin: {
-            enabled: false,
-        },
-        bionic: {
-            enabled: false,
-        },
-        glibc: {
-            enabled: false,
-        },
-    },
-}
-
-// Convert the linker (which is actually libc_musl.so) into a linker script for embedding in
-// crtbegin.
-cc_genrule {
-    name: "musl_linker_script",
-    visibility: ["//visibility:public"],
-    host_supported: true,
-    device_supported: false,
-    tools: ["extract_linker"],
-    cmd: "$(location) -T $(out) $(in)",
-    srcs: [":libc_musl"],
-    out: ["linker.script"],
-    target: {
-        darwin: {
-            enabled: false,
-        },
-        bionic: {
-            enabled: false,
-        },
-        glibc: {
-            enabled: false,
-        },
-    },
-}
-
 //
 // The musl CRT objects
 //
 
 cc_defaults {
     name: "libc_musl_crt_defaults",
-    defaults: ["libc_musl_defaults"],
+    defaults: ["libc_musl_base_defaults"],
     cflags: [
         // These are required to make sure the C code in crt/*.c
         // doesn't have any dependencies on libc.
         "-fno-stack-protector",
         "-ftrivial-auto-var-init=uninitialized",
     ],
+    ldflags: [
+        "-Wl,--no-gc-sections",
+    ],
+
+    // The headers below are the same as the header_libs in
+    // libc_musl_defaults, but bazel considers the crt depending
+    // on a header lib to be a circular dependency (toolchain ->
+    // crt objects -> header lib -> toolchain).
+    // TODO(b/263407827): go back to using header_libs once bazel
+    // supports it without introducing a dependency on the toolchain.
+    arch: {
+        arm: {
+            local_include_dirs: ["arch/arm"],
+        },
+        arm64: {
+            local_include_dirs: ["arch/aarch64"],
+        },
+        x86: {
+            local_include_dirs: ["arch/i386"],
+        },
+        x86_64: {
+            local_include_dirs: ["arch/x86_64"],
+        },
+    },
+    generated_headers: [
+        "libc_musl_alltypes.h",
+        "libc_musl_syscall.h",
+        "libc_musl_version.h",
+    ],
+    local_include_dirs: [
+        "arch/generic",
+        "src/include",
+        "src/internal",
+        "android/include",
+        "include",
+    ],
 }
 
 cc_object {
@@ -447,28 +449,21 @@
 
 cc_object {
     name: "libc_musl_crtbegin_dynamic",
-    defaults: ["libc_musl_defaults"],
+    defaults: ["libc_musl_crt_defaults"],
     visibility: ["//visibility:public"],
     objs: [
-        "libc_musl_crt1",
         "libc_musl_crti",
         "clang_rt.crtbegin",
     ],
     srcs: [
-        ":musl_linker_asm",
-        "android/ldso_trampoline.cpp",
+        "android/relinterp.c",
     ],
-    cflags: [
-        // These are required to make sure the C code in ldso_trampoline.c
-        // doesn't have any dependencies on libc.
-        "-fno-stack-protector",
-        "-ftrivial-auto-var-init=uninitialized",
-    ],
+    cflags: ["-DLOADER_PATH=\"libc_musl.so\""],
 }
 
 cc_object {
     name: "libc_musl_crtbegin_static",
-    defaults: ["libc_musl_defaults"],
+    defaults: ["libc_musl_crt_defaults"],
     visibility: ["//visibility:public"],
     objs: [
         "libc_musl_Scrt1",
@@ -479,7 +474,7 @@
 
 cc_object {
     name: "libc_musl_crtend",
-    defaults: ["libc_musl_defaults"],
+    defaults: ["libc_musl_crt_defaults"],
     visibility: ["//visibility:public"],
     objs: [
         "clang_rt.crtend",
@@ -489,7 +484,7 @@
 
 cc_object {
     name: "libc_musl_crtbegin_so",
-    defaults: ["libc_musl_defaults"],
+    defaults: ["libc_musl_crt_defaults"],
     visibility: ["//visibility:public"],
     objs: [
         "libc_musl_crti",
@@ -499,7 +494,7 @@
 
 cc_object {
     name: "libc_musl_crtend_so",
-    defaults: ["libc_musl_defaults"],
+    defaults: ["libc_musl_crt_defaults"],
     visibility: ["//visibility:public"],
     objs: [
         "clang_rt.crtend",
@@ -554,7 +549,7 @@
         },
     },
     tool_files: ["tools/mkalltypes.sed"],
-    out: ["bits/alltypes.h"],
+    out: ["bits/arch/alltypes.h"],
     cmd: "sed -f $(location tools/mkalltypes.sed) $(in) > $(out)",
 }
 
@@ -602,22 +597,6 @@
 //
 
 
-// A copy of libc_musl that uses libc.so as its soname for putting in the sysroot.
-cc_library_shared {
-    name: "libc_musl_for_sysroot",
-    defaults: ["libc_musl_defaults"],
-    whole_static_libs: ["libc_musl_static"],
-    shared: {
-        whole_static_libs: ["libc_musl_ldso_sysroot"],
-    },
-    ldflags: [
-        "-Wl,-e,_dlstart",
-        "-nostdlib",
-        "-Wl,--soname,libc.so",
-    ],
-    dynamic_list: "dynamic.list",
-}
-
 // An empty static library that will be copied to libdl.a, etc. in the sysroot.
 // Shouldn't be used by anything else besides the sysroot cc_genrule.
 cc_library_static {
@@ -631,17 +610,11 @@
 // crt.
 cc_object {
     name: "libc_musl_linker_object",
-    defaults: ["libc_musl_defaults"],
+    defaults: ["libc_musl_crt_defaults"],
     srcs: [
-        ":musl_linker_asm",
-        "android/ldso_trampoline.cpp",
+        "android/relinterp.c",
     ],
-    cflags: [
-        // These are required to make sure the C code in ldso_trampoline.c
-        // doesn't have any dependencies on libc.
-        "-fno-stack-protector",
-        "-ftrivial-auto-var-init=uninitialized",
-    ],
+    cflags: ["-DLOADER_PATH=\"libc_musl.so\""],
 }
 
 // The architecture-specific bits have to be handled separately because the label varies based
@@ -687,6 +660,11 @@
         "chmod a+x $(out)",
 }
 
+cc_library_host_static {
+    name: "libz_static_for_sysroot",
+    whole_static_libs: ["libz"],
+}
+
 cc_genrule {
     name: "libc_musl_sysroot",
     host_supported: true,
@@ -716,14 +694,15 @@
         // libc++ headers
         ":libc_musl_sysroot_libc++_headers",
         ":libc_musl_sysroot_libc++abi_headers",
+        ":libc_musl_sysroot_zlib_headers",
 
         // Libraries
         ":libc_musl",
-        ":libc_musl_for_sysroot",
         ":libc_musl_static",
         ":libc++abi",
         ":libc++",
         ":libc++_static",
+        ":libz_static_for_sysroot",
 
         // Objects
         ":libc_musl_crti",
@@ -736,7 +715,7 @@
 
         // Embedded linker objects and linker scripts
         ":libc_musl_linker_object",
-        ":musl_linker_script",
+        "android/relinterp.script",
 
         // Wrapper scripts
         ":libc_musl_clang_wrapper",
@@ -755,7 +734,7 @@
         "bits=($(locations arch/generic/bits/*.h)) && " +
         "android_bits=($(locations android/include/bits/*.h)) && " +
         "ln -s libc_musl.so $(genDir)/ld-musl.so.1 && " +
-        "echo -e 'GROUP ( Scrt1-real.o libc_musl_linker_object.o )\nINCLUDE linker.script' > $(genDir)/Scrt1.ld && " +
+        "echo -e 'GROUP ( libc_musl_linker_object.o )\nINCLUDE relinterp.script' > $(genDir)/Scrt1.ld && " +
         "echo -e 'GROUP ( libc_musl.so )' > $(genDir)/libc.so && " +
         "$(location soong_zip) -o $(genDir)/sysroot.zip " +
         " -j " +
@@ -772,14 +751,9 @@
         "  -D $$(dirname $${android_bits[0]}) " +
         " -P include/sys -j " +
         "  -f $(location android/include/sys/cdefs.h) " +
-        // crt objects
-        " -P lib -j " +
-        "  -f $(location :libc_musl_crti) " +
-        "  -f $(location :libc_musl_crtn) " +
-        "  -f $(location :libc_musl_crt1) " +
-        "  -f $(location :libc_musl_rcrt1) " +
         // embedded linker crt objects
-        "  -f $(location :musl_linker_script) " +
+        " -P lib -j " +
+        "  -f $(location android/relinterp.script) " +
         "  -f $(location :libc_musl_linker_object) " +
         // libs
         "  -f $(location :libc_musl) " +
@@ -798,10 +772,15 @@
         "  -f $(location :libc_musl_static) " +
         "  -f $(location :libc_musl_sysroot_static_empty) " +
         "  -f $(genDir)/Scrt1.ld " +
+        "  -f $(location :libc_musl_crti) " +
+        "  -f $(location :libc_musl_crtn) " +
+        "  -f $(location :libc_musl_crt1) " +
+        "  -f $(location :libc_musl_rcrt1) " +
         "  -f $(location :libc_musl_Scrt1) " +
         "  -f $(location :libc++_static) " +
         "  -f $(location :clang_rt.crtbegin) " +
         "  -f $(location :clang_rt.crtend) " +
+        "  -f $(location :libz_static_for_sysroot) " +
         " && " +
         "$(location zip2zip) -i $(genDir)/libs.zip -o $(genDir)/libs_renamed.zip " +
         // rename libs from module names to desired names in sysroot
@@ -825,11 +804,19 @@
         " lib/clang_rt.crtbegin.o:lib/crtbeginT.o " +
         " lib/clang_rt.crtend.o:lib/crtend.o " +
         " lib/clang_rt.crtend.o:lib/crtendS.o " +
+        // rename crt objects
+        " lib/libc_musl_crti.o:lib/crti.o " +
+        " lib/libc_musl_crtn.o:lib/crtn.o " +
+        " lib/libc_musl_crt1.o:lib/crt1.o " +
+        " lib/libc_musl_rcrt1.o:lib/rcrt1.o " +
+        // rename static libz
+        " lib/libz_static_for_sysroot.a:lib/libz.a " +
         " && " +
         "$(location merge_zips) -ignore-duplicates $(out) " +
         " $(location :libc_musl_sysroot_bionic_headers) " +
         " $(location :libc_musl_sysroot_libc++_headers) " +
         " $(location :libc_musl_sysroot_libc++abi_headers) " +
+        " $(location :libc_musl_sysroot_zlib_headers) " +
         " $(location :libc_musl_sysroot_bits) " +
         " $(genDir)/sysroot.zip " +
         " $(genDir)/libs_renamed.zip",
diff --git a/android/include/signal.h b/android/include/signal.h
deleted file mode 100644
index 0d7d03c..0000000
--- a/android/include/signal.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#pragma once
-
-#include_next <signal.h>
-
-#define __SIGRTMIN 32
-#define __SIGRTMAX _NSIG-1
diff --git a/android/relinterp.c b/android/relinterp.c
new file mode 100644
index 0000000..c900e79
--- /dev/null
+++ b/android/relinterp.c
@@ -0,0 +1,930 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define SYSCALL_NO_TLS 1
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <link.h>
+#include <stdalign.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/syscall.h>
+#include <sys/user.h>
+#include <unistd.h>
+
+#include "reloc.h"
+#include "syscall.h"
+
+typedef void EntryFunc(void);
+
+// arm64 doesn't have a constant page size and has to use the value from AT_PAGESZ.
+#ifndef PAGE_SIZE
+#define PAGE_SIZE g_page_size
+#endif
+
+#define PAGE_START(x) ((x) & (~(PAGE_SIZE-1)))
+#define PAGE_END(x) PAGE_START((x) + (PAGE_SIZE - 1))
+
+#define START "_start"
+#include "crt_arch.h"
+
+int main();
+weak void _init();
+weak void _fini();
+int __libc_start_main(int (*)(), int, char **,
+  void (*)(), void(*)(), void(*)());
+
+static ElfW(Phdr) replacement_phdr_table[64];
+static char replacement_interp[PATH_MAX];
+
+static bool g_debug = false;
+static const char* g_prog_name = NULL;
+static uintptr_t g_page_size = 0;
+static int g_errno = 0;
+
+__attribute__((visibility("hidden"))) extern ElfW(Dyn) _DYNAMIC[];
+
+__attribute__((used))
+static long ri_set_errno(unsigned long val) {
+  if (val > -4096UL) {
+    g_errno = -val;
+    return -1;
+  }
+  return val;
+}
+
+#define ri_syscall(...) ri_set_errno(__syscall(__VA_ARGS__))
+
+static ssize_t ri_write(int fd, const void* buf, size_t amt) {
+  return ri_syscall(SYS_write, fd, buf, amt);
+}
+
+__attribute__((noreturn))
+static void ri_exit(int status) {
+  ri_syscall(SYS_exit, status);
+  __builtin_unreachable();
+}
+
+static int ri_open(const char* path, int flags, mode_t mode) {
+  return ri_syscall(SYS_openat, AT_FDCWD, path, flags, mode);
+}
+
+static int ri_close(int fd) {
+  return ri_syscall(SYS_close, fd);
+}
+
+static off_t ri_lseek(int fd, off_t offset, int whence) {
+  return ri_syscall(SYS_lseek, fd, offset, whence);
+}
+
+static ssize_t ri_readlink(const char* path, char* buf, size_t size) {
+  return ri_syscall(SYS_readlinkat, AT_FDCWD, path, buf, size);
+}
+
+static void* ri_mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) {
+#ifdef SYS_mmap2
+  return (void*)ri_syscall(SYS_mmap2, addr, length, prot, flags, fd, offset/SYSCALL_MMAP2_UNIT);
+#else
+  return (void*)ri_syscall(SYS_mmap, addr, length, prot, flags, fd, offset);
+#endif
+}
+
+static void* ri_munmap(void* addr, size_t length) {
+  return (void*)ri_syscall(SYS_munmap, addr, length);
+}
+
+static int ri_mprotect(void* addr, size_t len, int prot) {
+  return ri_syscall(SYS_mprotect, addr, len, prot);
+}
+
+static ssize_t ri_pread(int fd, void* buf, size_t size, off_t ofs) {
+  return ri_syscall(SYS_pread, fd, buf, size, __SYSCALL_LL_PRW(ofs));
+}
+
+static size_t ri_strlen(const char* src) {
+  for (size_t len = 0;; ++len) {
+    if (src[len] == '\0') return len;
+  }
+}
+
+static char* ri_strcpy(char* dst, const char* src) {
+  char* result = dst;
+  while ((*dst = *src) != '\0') {
+    ++dst;
+    ++src;
+  }
+  return result;
+}
+
+static char* ri_strcat(char* dst, const char* src) {
+  ri_strcpy(dst + ri_strlen(dst), src);
+  return dst;
+}
+
+static void* ri_memset(void* dst, int val, size_t len) {
+  for (size_t i = 0; i < len; ++i) {
+    ((char*)dst)[i] = val;
+  }
+  return dst;
+}
+
+__attribute__ ((unused))
+static void* ri_memcpy(void* dst, const void* src, size_t len) {
+  for (size_t i = 0; i < len; ++i) {
+    ((char*)dst)[i] = ((char*)src)[i];
+  }
+  return dst;
+}
+
+static int ri_strncmp(const char* x, const char *y, size_t maxlen) {
+  for (size_t i = 0;; ++i) {
+    if (i == maxlen) return 0;
+    int result = (unsigned char)x[i] - (unsigned char)y[i];
+    if (result != 0) return result;
+    if (x[i] == '\0') return 0;
+  }
+}
+
+static int ri_strcmp(const char* x, const char *y) {
+  return ri_strncmp(x, y, SIZE_MAX);
+}
+
+static char* ri_strrchr(const char* str, int ch) {
+  char* result = NULL;
+  while (true) {
+    if (*str == ch) result = (char*)str;
+    if (*str == '\0') break;
+    ++str;
+  }
+  return result;
+}
+
+static char* ri_strchr(const char* str, int ch) {
+  while (*str) {
+    if (*str == ch) return (char*)str;
+    ++str;
+  }
+  return NULL;
+}
+
+static void ri_dirname(char* path) {
+  char* last_slash = ri_strrchr(path, '/');
+  if (last_slash == NULL) {
+    path[0] = '.';   // returns "."
+    path[1] = '\0';
+  } else if (last_slash == path) {
+    path[1] = '\0';  // returns "/"
+  } else {
+    *last_slash = '\0';
+  }
+}
+
+static void out_str_n(const char* str, size_t n) {
+  ri_write(STDERR_FILENO, str, n);
+}
+
+static void out_str(const char* str) {
+  out_str_n(str, ri_strlen(str));
+}
+
+static char* ul_to_str(unsigned long i, char* out, unsigned char base) {
+  char buf[65];
+  char* cur = &buf[65];
+  *--cur = '\0';
+  do {
+    *--cur = "0123456789abcdef"[i % base];
+    i /= base;
+  } while (i > 0);
+  return ri_strcpy(out, cur);
+}
+
+static char* l_to_str(long i, char* out, unsigned char base) {
+  if (i < 0) {
+    *out = '-';
+    ul_to_str(-(unsigned long)i, out + 1, base);
+    return out;
+  } else {
+    return ul_to_str(i, out, base);
+  }
+}
+
+static const char* ri_strerror(int err) {
+  switch (err) {
+    case EPERM: return "Operation not permitted";
+    case ENOENT: return "No such file or directory";
+    case EIO: return "I/O error";
+    case ENXIO: return "No such device or address";
+    case EAGAIN: return "Try again";
+    case ENOMEM: return "Out of memory";
+    case EACCES: return "Permission denied";
+    case ENODEV: return "No such device";
+    case ENOTDIR: return "Not a directory";
+    case EINVAL: return "Invalid argument";
+    case ENFILE: return "File table overflow";
+    case EMFILE: return "Too many open files";
+    case ESPIPE: return "Illegal seek";
+    case ENAMETOOLONG: return "File name too long";
+    case ELOOP: return "Too many symbolic links encountered";
+  }
+  static char buf[64];
+  ri_strcpy(buf, "Unknown error ");
+  l_to_str(err, buf + ri_strlen(buf), 10);
+  return buf;
+}
+
+static void outv(const char *fmt, va_list ap) {
+  char buf[65];
+  while (true) {
+    if (fmt[0] == '\0') break;
+
+#define NUM_FMT(num_fmt, type, func, base)                  \
+    if (!ri_strncmp(fmt, num_fmt, sizeof(num_fmt) - 1)) {   \
+      out_str(func(va_arg(ap, type), buf, base));           \
+      fmt += sizeof(num_fmt) - 1;                           \
+      continue;                                             \
+    }
+    NUM_FMT("%d",  int,           l_to_str,  10);
+    NUM_FMT("%ld", long,          l_to_str,  10);
+    NUM_FMT("%u",  unsigned int,  ul_to_str, 10);
+    NUM_FMT("%lu", unsigned long, ul_to_str, 10);
+    NUM_FMT("%zu", size_t,        ul_to_str, 10);
+    NUM_FMT("%x",  unsigned int,  ul_to_str, 16);
+    NUM_FMT("%lx", unsigned long, ul_to_str, 16);
+    NUM_FMT("%zx", size_t,        ul_to_str, 16);
+#undef NUM_FMT
+
+    if (!ri_strncmp(fmt, "%p", 2)) {
+      out_str(ul_to_str((unsigned long)va_arg(ap, void*), buf, 16));
+      fmt += 2;
+    } else if (!ri_strncmp(fmt, "%s", 2)) {
+      const char* arg = va_arg(ap, const char*);
+      out_str(arg ? arg : "(null)");
+      fmt += 2;
+    } else if (!ri_strncmp(fmt, "%%", 2)) {
+      out_str("%");
+      fmt += 2;
+    } else if (fmt[0] == '%') {
+      buf[0] = fmt[1];
+      buf[1] = '\0';
+      out_str("relinterp error: unrecognized output specifier: '%");
+      out_str(buf);
+      out_str("'\n");
+      ri_exit(1);
+    } else {
+      size_t len = 0;
+      while (fmt[len] != '\0' && fmt[len] != '%') ++len;
+      out_str_n(fmt, len);
+      fmt += len;
+    }
+  }
+}
+
+__attribute__((format(printf, 1, 2)))
+static void debug(const char* fmt, ...) {
+  if (!g_debug) return;
+  out_str("relinterp: ");
+
+  va_list ap;
+  va_start(ap, fmt);
+  outv(fmt, ap);
+  va_end(ap);
+  out_str("\n");
+}
+
+__attribute__((format(printf, 1, 2), noreturn))
+static void fatal(const char* fmt, ...) {
+  out_str("relinterp: ");
+  if (g_prog_name) {
+    out_str(g_prog_name);
+    out_str(": ");
+  }
+  out_str("fatal error: ");
+
+  va_list ap;
+  va_start(ap, fmt);
+  outv(fmt, ap);
+  va_end(ap);
+  out_str("\n");
+  ri_exit(1);
+}
+
+static void* optimizer_barrier(void* val) {
+  __asm__ volatile ("nop" :: "r"(&val) : "memory");
+  return val;
+}
+
+typedef struct {
+  unsigned long key;
+  unsigned long value;
+} AuxEntry;
+
+typedef struct {
+  int argc;
+  char **argv;
+  char **envp;
+  size_t envp_count;
+  AuxEntry* auxv;
+  size_t auxv_count;
+} KernelArguments;
+
+static KernelArguments read_args(void* raw_args) {
+  KernelArguments result;
+  result.argc = *(long*)raw_args;
+  result.argv = (char**)((void**)raw_args + 1);
+  result.envp = result.argv + result.argc + 1;
+
+  char** envp = result.envp;
+  while (*envp != NULL) ++envp;
+  result.envp_count = envp - result.envp;
+  ++envp;
+
+  result.auxv = (AuxEntry*)envp;
+  size_t count = 0;
+  while (result.auxv[count].key != 0) {
+    ++count;
+  }
+  result.auxv_count = count;
+  return result;
+}
+
+static void dump_auxv(const KernelArguments* args) {
+  for (size_t i = 0; i < args->auxv_count; ++i) {
+    const char* name = "";
+    switch (args->auxv[i].key) {
+      case AT_BASE: name = " [AT_BASE]"; break;
+      case AT_EGID: name = " [AT_EGID]"; break;
+      case AT_ENTRY: name = " [AT_ENTRY]"; break;
+      case AT_EUID: name = " [AT_EUID]"; break;
+      case AT_GID: name = " [AT_GID]"; break;
+      case AT_PAGESZ: name = " [AT_PAGESZ]"; break;
+      case AT_PHDR: name = " [AT_PHDR]"; break;
+      case AT_PHENT: name = " [AT_PHENT]"; break;
+      case AT_PHNUM: name = " [AT_PHNUM]"; break;
+      case AT_SECURE: name = " [AT_SECURE]"; break;
+      case AT_SYSINFO: name = " [AT_SYSINFO]"; break;
+      case AT_SYSINFO_EHDR: name = " [AT_SYSINFO_EHDR]"; break;
+      case AT_UID: name = " [AT_UID]"; break;
+    }
+    debug("  %lu => 0x%lx%s", args->auxv[i].key, args->auxv[i].value, name);
+  }
+}
+
+static unsigned long ri_getauxval(const KernelArguments* args, unsigned long kind,
+                                  bool allow_missing) {
+  for (size_t i = 0; i < args->auxv_count; ++i) {
+    if (args->auxv[i].key == kind) return args->auxv[i].value;
+  }
+  if (!allow_missing) fatal("could not find aux vector entry %lu", kind);
+  return 0;
+}
+
+static int elf_flags_to_prot(int flags) {
+  int result = 0;
+  if (flags & PF_R) result |= PROT_READ;
+  if (flags & PF_W) result |= PROT_WRITE;
+  if (flags & PF_X) result |= PROT_EXEC;
+  return result;
+}
+
+typedef struct {
+  int fd;
+  char path[PATH_MAX];
+} OpenedLoader;
+
+typedef struct {
+  void* base_addr;
+  EntryFunc* entry;
+} LoadedInterp;
+
+static LoadedInterp load_interp(const OpenedLoader *loader, ElfW(Ehdr)* hdr) {
+  ElfW(Phdr)* phdr = (ElfW(Phdr)*)((char*)hdr + hdr->e_phoff);
+  size_t phdr_count = hdr->e_phnum;
+
+  size_t max_vaddr = 0;
+
+  // Find the virtual address extent.
+  for (size_t i = 0; i < phdr_count; ++i) {
+    if (phdr[i].p_type == PT_LOAD) {
+      max_vaddr = PAGE_END(MAX(max_vaddr, phdr[i].p_vaddr + phdr[i].p_memsz));
+    }
+  }
+
+  // Map an area to fit the loader.
+  void* loader_vaddr = ri_mmap(NULL, max_vaddr, PROT_READ | PROT_WRITE,
+                               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (loader_vaddr == (void*)MAP_FAILED) {
+    fatal("reservation mmap of 0x%zx bytes for %s failed: %s", max_vaddr, loader->path,
+          ri_strerror(g_errno));
+  }
+
+  // Map each PT_LOAD.
+  for (size_t i = 0; i < phdr_count; ++i) {
+    if (phdr[i].p_type == PT_LOAD) {
+      size_t start = PAGE_START(phdr[i].p_vaddr);
+      const size_t end = PAGE_END(phdr[i].p_vaddr + phdr[i].p_memsz);
+      if (phdr[i].p_filesz > 0) {
+        const size_t file_end = phdr[i].p_vaddr + phdr[i].p_filesz;
+        void* tmp = ri_mmap((char*)loader_vaddr + start,
+                            file_end - start,
+                            elf_flags_to_prot(phdr[i].p_flags),
+                            MAP_PRIVATE | MAP_FIXED, loader->fd, PAGE_START(phdr[i].p_offset));
+        if (tmp == (void*)MAP_FAILED) {
+          fatal("PT_LOAD mmap failed (%s segment #%zu): %s", loader->path, i,
+                ri_strerror(g_errno));
+        }
+        start = file_end;
+        if (phdr[i].p_flags & PF_W) {
+          // The bytes between p_filesz and PAGE_END(p_filesz) currently come from the file mapping,
+          // but they need to be zeroed. (Apparently this zeroing isn't necessary if the segment isn't
+          // writable, and zeroing a non-writable page would be inconvenient.)
+          ri_memset((char*)loader_vaddr + start, '\0', PAGE_END(start) - start);
+        }
+        start = PAGE_END(start);
+      }
+      if (start < end) {
+        // The memory is already zeroed, because it comes from an anonymous file mapping. Just set
+        // the protections correctly.
+        int result = ri_mprotect((char*)loader_vaddr + start, end - start,
+                                 elf_flags_to_prot(phdr[i].p_flags));
+        if (result != 0) {
+          fatal("mprotect of PT_LOAD failed (%s segment #%zu): %s", loader->path, i,
+                ri_strerror(g_errno));
+        }
+      }
+    }
+  }
+
+  return (LoadedInterp) {
+    .base_addr = loader_vaddr,
+    .entry = (EntryFunc*)((uintptr_t)loader_vaddr + hdr->e_entry),
+  };
+}
+
+typedef struct {
+  ElfW(Phdr)* phdr;
+  size_t phdr_count;
+  uintptr_t load_bias;
+  uintptr_t page_size;
+  char* search_paths;
+  ElfW(Ehdr)* ehdr;
+  ElfW(Phdr)* first_load;
+  bool secure;
+} ExeInfo;
+
+static ExeInfo get_exe_info(const KernelArguments* args) {
+  ExeInfo result = { 0 };
+  result.phdr = (ElfW(Phdr)*)ri_getauxval(args, AT_PHDR, false);
+  result.phdr_count = ri_getauxval(args, AT_PHNUM, false);
+  result.page_size = ri_getauxval(args, AT_PAGESZ, false);
+
+  unsigned long uid = ri_getauxval(args, AT_UID, false);
+  unsigned long euid = ri_getauxval(args, AT_EUID, false);
+  unsigned long gid = ri_getauxval(args, AT_GID, false);
+  unsigned long egid = ri_getauxval(args, AT_EGID, false);
+  unsigned long secure = ri_getauxval(args, AT_SECURE, true);
+  result.secure = uid != euid || gid != egid || secure;
+
+  debug("orig phdr     = %p", (void*)result.phdr);
+  debug("orig phnum    = %zu", result.phdr_count);
+
+  for (size_t i = 0; i < result.phdr_count; ++i) {
+    if (result.phdr[i].p_type == PT_DYNAMIC) {
+      result.load_bias = (uintptr_t)&_DYNAMIC - result.phdr[i].p_vaddr;
+    }
+  }
+  debug("load_bias     = 0x%lx", (unsigned long)result.load_bias);
+
+  for (size_t i = 0; i < result.phdr_count; ++i) {
+    ElfW(Phdr)* phdr = &result.phdr[i];
+    if (phdr->p_type != PT_LOAD) continue;
+    result.first_load = phdr;
+    if (phdr->p_offset != 0) {
+      fatal("expected zero p_offset for first PT_LOAD, found 0x%zx instead",
+            (size_t)phdr->p_offset);
+    }
+    result.ehdr = (ElfW(Ehdr)*)(phdr->p_vaddr + result.load_bias);
+    break;
+  }
+  debug("ehdr          = %p", (void*)result.ehdr);
+
+  ElfW(Word) runpath_offset = -1;
+  char* strtab = NULL;
+  for (ElfW(Dyn)* dyn = _DYNAMIC; dyn->d_tag != DT_NULL; dyn++) {
+    switch (dyn->d_tag) {
+    case DT_RUNPATH:
+      runpath_offset = dyn->d_un.d_val;
+      break;
+    case DT_RPATH:
+      if (runpath_offset == -1) runpath_offset = dyn->d_un.d_val;
+      break;
+    case DT_STRTAB:
+      strtab = (char*)(dyn->d_un.d_ptr + result.load_bias);
+      break;
+    }
+  }
+
+  if (strtab && runpath_offset != -1) {
+    result.search_paths = strtab + runpath_offset;
+    debug("dt_runpath    = %s", result.search_paths);
+  }
+  return result;
+}
+
+// Loaders typically read the PT_INTERP of the executable, e.g. to set a pathname on the loader.
+// glibc insists on the executable having PT_INTERP, and aborts if it's missing.  Musl passes it
+// to debuggers to find symbols for the loader, which includes all the libc symbols.
+//
+// Make a copy of the phdr table and insert PT_INTERP into the copy.
+//
+static void insert_pt_interp_into_phdr_table(const KernelArguments* args, const ExeInfo* exe,
+                                             const char* loader_realpath) {
+  // Reserve extra space for the inserted PT_PHDR and PT_INTERP segments and a null terminator.
+  if (exe->phdr_count + 3 > sizeof(replacement_phdr_table) / sizeof(replacement_phdr_table[0])) {
+    fatal("too many phdr table entries in executable");
+  }
+
+  ElfW(Phdr) newPhdr = {
+    .p_type = PT_PHDR,
+    // The replacement phdr is in the BSS section, which has no file location.
+    // Use 0 for the offset.  If this causes a problem the replacement phdr could
+    // be moved to the data section and the correct p_offset calculated.
+    .p_offset = 0,
+    .p_vaddr = (uintptr_t)&replacement_phdr_table - exe->load_bias,
+    .p_paddr = (uintptr_t)&replacement_phdr_table - exe->load_bias,
+    .p_memsz = (exe->phdr_count + 1) * sizeof(ElfW(Phdr)),
+    .p_filesz = (exe->phdr_count + 1) * sizeof(ElfW(Phdr)),
+    .p_flags = PF_R,
+    .p_align = alignof(ElfW(Phdr)),
+  };
+
+  ElfW(Phdr*) cur = replacement_phdr_table;
+  if (exe->phdr[0].p_type != PT_PHDR) {
+    // ld.bfd does not insert a PT_PHDR if there is no PT_INTERP, fake one.
+    // It has to be first.  We're adding an entry so increase memsz and filesz.
+    newPhdr.p_memsz += sizeof(ElfW(Phdr));
+    newPhdr.p_filesz += sizeof(ElfW(Phdr));
+    *cur = newPhdr;
+    ++cur;
+  }
+
+  for (size_t i = 0; i < exe->phdr_count; ++i) {
+    switch (exe->phdr[i].p_type) {
+    case 0:
+      fatal("unexpected null phdr entry at index %zu", i);
+      break;
+    case PT_PHDR:
+      *cur = newPhdr;
+      break;
+    default:
+      *cur = exe->phdr[i];
+    }
+    ++cur;
+  }
+
+  // Insert PT_INTERP at the end.
+  cur->p_type = PT_INTERP;
+  cur->p_offset = 0;
+  cur->p_vaddr = (uintptr_t)&replacement_interp - exe->load_bias;
+  cur->p_paddr = cur->p_vaddr;
+  cur->p_filesz = ri_strlen(replacement_interp) + 1;
+  cur->p_memsz = ri_strlen(replacement_interp) + 1;
+  cur->p_flags = PF_R;
+  cur->p_align = 1;
+  ++cur;
+
+  ri_strcpy(replacement_interp, loader_realpath);
+
+  debug("new phdr      = %p", (void*)&replacement_phdr_table);
+  debug("new phnum     = %zu", cur - replacement_phdr_table);
+
+  // Update the aux vector with the new phdr+phnum.
+  for (size_t i = 0; i < args->auxv_count; ++i) {
+    if (args->auxv[i].key == AT_PHDR) {
+      args->auxv[i].value = (unsigned long)&replacement_phdr_table;
+    } else if (args->auxv[i].key == AT_PHNUM) {
+      args->auxv[i].value = cur - replacement_phdr_table;
+    }
+  }
+
+  // AT_PHDR and AT_PHNUM are now updated to point to the replacement program
+  // headers, but the e_phoff and e_phnum in the ELF headers still point to the
+  // original program headers.  dynlink.c doesn't use e_phoff value from the
+  // main application's program headers.  The e_phoff and e_phnum values could
+  // be updated, but that would require using mprotect to allow modifications
+  // to the read-only first page.
+}
+
+static void realpath_fd(int fd, const char* orig_path, char* out, size_t len) {
+  char path[64];
+  ri_strcpy(path, "/proc/self/fd/");
+  ul_to_str(fd, path + ri_strlen(path), 10);
+  ssize_t result = ri_readlink(path, out, len);
+  if (result == -1) fatal("could not get realpath of %s: %s", orig_path, ri_strerror(g_errno));
+  if ((size_t)result >= len) fatal("realpath of %s too long", orig_path);
+}
+
+static int open_loader(const ExeInfo* exe, const char* path, OpenedLoader* loader) {
+  debug("trying to open '%s'", path);
+  loader->fd = ri_open(path, O_RDONLY, 0);
+  if (loader->fd < 0) {
+    debug("could not open loader %s: %s", path, ri_strerror(g_errno));
+    return -1;
+  }
+
+  ElfW(Ehdr) hdr;
+  ssize_t l = ri_pread(loader->fd, &hdr, sizeof(hdr), 0);
+  if (l < 0) {
+    debug("reading elf header from %s failed: %s", path, ri_strerror(g_errno));
+    return -1;
+  }
+  if (l != sizeof(hdr)) {
+    debug("file %s too short to contain elf header", path);
+    return -1;
+  }
+
+  if (hdr.e_ident[0] != ELFMAG0 ||
+      hdr.e_ident[1] != ELFMAG1 ||
+      hdr.e_ident[2] != ELFMAG2 ||
+      hdr.e_ident[3] != ELFMAG3) {
+    debug("file %s is not an elf file", path);
+    return -1;
+  }
+
+  if (hdr.e_machine != exe->ehdr->e_machine) {
+    debug("incorrect elf machine for loader %s, expected %d got %d",
+          path, exe->ehdr->e_machine, hdr.e_machine);
+    return -1;
+  }
+
+  if (hdr.e_ident[EI_CLASS] != exe->ehdr->e_ident[EI_CLASS]) {
+    debug("incorrect elf class for loader %s, expected %d got %d",
+          path, exe->ehdr->e_ident[EI_CLASS], hdr.e_ident[EI_CLASS]);
+    return -1;
+  }
+
+  realpath_fd(loader->fd, path, loader->path, sizeof(loader->path));
+
+  return 0;
+}
+
+static int open_rel_loader(const ExeInfo* exe, const char* dir, const char* rel, OpenedLoader* loader) {
+  char buf[PATH_MAX];
+
+  size_t dir_len = ri_strlen(dir);
+
+  if (dir_len + (dir_len == 0 ? 1 : 0) + ri_strlen(rel) + 2 > sizeof(buf)) {
+    debug("path to loader exceeds PATH_MAX: %s/%s", dir, rel);
+    return 1;
+  }
+
+  if (dir_len == 0) {
+    ri_strcpy(buf, ".");
+  } else {
+    ri_strcpy(buf, dir);
+    if (dir[dir_len-1] != '/') {
+      ri_strcat(buf, "/");
+    }
+  }
+  ri_strcat(buf, rel);
+
+  return open_loader(exe, buf, loader);
+}
+
+static void get_origin(char* buf, size_t buf_len) {
+  ssize_t len = ri_readlink("/proc/self/exe", buf, buf_len);
+  if (len <= 0 || (size_t)len >= buf_len) {
+    fatal("could not readlink /proc/self/exe: %s", ri_strerror(g_errno));
+  }
+  buf[len] = '\0';
+
+  ri_dirname(buf);
+}
+
+static int search_path_list_for_loader(const ExeInfo* exe, const char* loader_rel_path, const char* search_path,
+                                       const char* search_path_name, bool expand_origin, OpenedLoader *loader) {
+  char origin_buf[PATH_MAX];
+  char* origin = NULL;
+
+  const char* p = search_path;
+  while (p && p[0]) {
+    const char* start = p;
+    const char* end = ri_strchr(p, ':');
+    if (end == NULL) {
+      end = start + ri_strlen(p);
+      p = NULL;
+    } else {
+      p = end + 1;
+    }
+    size_t n = end - start;
+    char search_path_entry[PATH_MAX];
+    if (n >= sizeof(search_path_entry)) {
+      // Too long, skip.
+      debug("%s entry too long: %s", search_path_name, start);
+      continue;
+    }
+
+    ri_memcpy(search_path_entry, start, n);
+    search_path_entry[n] = '\0';
+
+    char buf[PATH_MAX];
+    char* d = NULL;
+    if (expand_origin) {
+      d = ri_strchr(search_path_entry, '$');
+    }
+    if (d && (!ri_strncmp(d, "$ORIGIN", 7) || !ri_strncmp(d, "${ORIGIN}", 9))) {
+      if (!origin) {
+        get_origin(origin_buf, sizeof(origin_buf));
+        origin = origin_buf;
+      }
+
+      size_t s = 7;
+      if (d[1] == '{') {
+        s += 2;
+      }
+      ri_memcpy(buf, search_path_entry, d - search_path_entry);
+      buf[d - search_path_entry] = '\0';
+      if (ri_strlen(buf) + ri_strlen(origin) + ri_strlen(d+s) >= sizeof(buf)) {
+        debug("path to loader %s%s%s too long", buf, origin, d+s);
+        continue;
+      }
+
+      ri_strcat(buf, origin);
+      ri_strcat(buf, d+s);
+    } else {
+      ri_strcpy(buf, search_path_entry);
+    }
+    debug("trying loader %s at %s", loader_rel_path, buf);
+    if (!open_rel_loader(exe, buf, loader_rel_path, loader)) {
+      debug("opened loader %s at %s", loader_rel_path, buf);
+      return 0;
+    }
+  }
+
+  return -1;
+}
+
+static int find_and_open_loader(const ExeInfo* exe, const char* ld_library_path, OpenedLoader* loader) {
+  const char* loader_rel_path = LOADER_PATH;
+
+  if (loader_rel_path[0] == '/') {
+    return open_loader(exe, loader_rel_path, loader);
+  }
+
+  if (exe->secure) {
+    fatal("relinterp not supported for secure executables");
+  }
+
+  if (!search_path_list_for_loader(exe, loader_rel_path, ld_library_path, "LD_LIBRARY_PATH", false, loader)) {
+    return 0;
+  }
+
+  if (!exe->search_paths || ri_strlen(exe->search_paths) == 0) {
+    // If no DT_RUNPATH search relative to the exe.
+    char origin[PATH_MAX];
+    get_origin(origin, sizeof(origin));
+    return open_rel_loader(exe, origin, loader_rel_path, loader);
+  }
+
+  if (!search_path_list_for_loader(exe, loader_rel_path, exe->search_paths, "rpath", true, loader)) {
+    return 0;
+  }
+
+  fatal("unable to find loader %s in rpath %s", loader_rel_path, exe->search_paths);
+}
+
+// Use a trick to determine whether the executable has been relocated yet. This variable points to
+// a variable in libc. It will be NULL if and only if the program hasn't been linked yet. This
+// should accommodate these situations:
+//  - The program was actually statically-linked instead.
+//  - Either a PIE or non-PIE dynamic executable.
+//  - Any situation where the loader calls the executable's _start:
+//     - In normal operation, the kernel calls the executable's _start, _start jumps to the loader's
+//       entry point, which jumps to _start again after linking it.
+//     - The executable actually has its PT_INTERP set after all.
+//     - The user runs the loader, passing it the path of the executable.
+// This C file must always be compiled as PIC, or else the linker will use a COPY relocation and
+// duplicate "environ" into the executable.
+static bool is_exe_relocated(void) {
+  // Use the GOT to get the address of environ.
+  extern char** environ;
+  void* read_environ = optimizer_barrier(&environ);
+  debug("read_environ = %p", read_environ);
+  return read_environ != NULL;
+}
+
+void _start_c(long* raw_args) {
+  const KernelArguments args = read_args(raw_args);
+  const char* ld_library_path = NULL;
+
+  for (size_t i = 0; i < args.envp_count; ++i) {
+    if (!ri_strcmp(args.envp[i], "RELINTERP_DEBUG=1")) {
+      g_debug = true;
+    }
+    if (!ri_strncmp(args.envp[i], "LD_LIBRARY_PATH=", 16)) {
+      ld_library_path = args.envp[i] + 16;
+    }
+  }
+  if (args.argc >= 1) {
+    g_prog_name = args.argv[0];
+  }
+
+  if (is_exe_relocated()) {
+    debug("exe is already relocated, starting main executable");
+    int argc = raw_args[0];
+    char **argv = (void *)(raw_args+1);
+    __libc_start_main(main, argc, argv, _init, _fini, 0);
+  }
+
+  debug("entering relinterp");
+
+  const ExeInfo exe = get_exe_info(&args);
+  g_page_size = exe.page_size;
+
+  OpenedLoader loader;
+  if (find_and_open_loader(&exe, ld_library_path, &loader)) {
+    fatal("failed to open loader");
+  }
+  off_t len = ri_lseek(loader.fd, 0, SEEK_END);
+  if (len == (off_t)-1) fatal("lseek on %s failed: %s", loader.path, ri_strerror(g_errno));
+
+  void* loader_data = ri_mmap(NULL, len, PROT_READ, MAP_PRIVATE, loader.fd, 0);
+  if (loader_data == (void*)MAP_FAILED) {
+    fatal("could not mmap %s: %s", loader.path, ri_strerror(g_errno));
+  }
+
+  LoadedInterp interp = load_interp(&loader, (ElfW(Ehdr)*)loader_data);
+  if (ri_munmap(loader_data, len) != 0) fatal("munmap failed: %s", ri_strerror(g_errno));
+
+  debug("original auxv:");
+  dump_auxv(&args);
+
+  // Create a virtual phdr table that includes PT_INTERP, for the benefit of loaders that read the
+  // executable PT_INTERP.
+  insert_pt_interp_into_phdr_table(&args, &exe, loader.path);
+  ri_close(loader.fd);
+
+  // TODO: /proc/pid/auxv isn't updated with the new auxv vector. Is it possible to update it?
+  // XXX: If we try to update it, we'd use prctl(PR_SET_MM, PR_SET_MM_AUXV, &vec, size, 0)
+  // Maybe updating it would be useful as a way to communicate the loader's base to a debugger.
+  // e.g. lldb uses AT_BASE in the aux vector, but it caches the values at process startup, so
+  // it wouldn't currently notice a changed value.
+
+  // The loader uses AT_BASE to locate itself, so search for the entry and update it. Even though
+  // its value is always zero, the kernel still includes the entry[0]. If this changes (or we want
+  // to make weaker assumptions about the kernel's behavior), then we can copy the kernel arguments
+  // onto the stack (e.g. using alloca) before jumping to the loader's entry point.
+  // [0] https://github.com/torvalds/linux/blob/v5.13/fs/binfmt_elf.c#L263
+  for (size_t i = 0; i < args.auxv_count; ++i) {
+    if (args.auxv[i].key == AT_BASE) {
+      args.auxv[i].value = (unsigned long)interp.base_addr;
+      debug("new auxv:");
+      dump_auxv(&args);
+      debug("transferring to real loader");
+      CRTJMP(interp.entry, raw_args);
+    }
+  }
+  fatal("AT_BASE not found in aux vector");
+}
+
+
+// Normally gdb and lldb look for a symbol named "_dl_debug_state" in the
+// interpreter to get notified when the dynamic loader has modified the
+// list of shared libraries.  When using relinterp, the debugger is not
+// aware of the interpreter (PT_INTERP is unset and auxv AT_BASE is 0) so it
+// doesn't know where to look for the symbol.  It falls back to looking in the
+// executable, so provide a symbol for it to find.  The dynamic loader will
+// need to forward its calls to its own _dl_debug_state symbol to this one.
+//
+// This has to be defined in a .c file because lldb looks for a symbol with
+// DWARF language type DW_LANG_C.
+extern void _dl_debug_state() {
+}
diff --git a/android/relinterp.script b/android/relinterp.script
new file mode 100644
index 0000000..9085231
--- /dev/null
+++ b/android/relinterp.script
@@ -0,0 +1,4 @@
+SECTIONS {
+  /DISCARD/ : { *(.interp) }
+}
+INSERT BEFORE .text;
diff --git a/arch/arm/syscall_arch.h b/arch/arm/syscall_arch.h
index a877b2c..624e992 100644
--- a/arch/arm/syscall_arch.h
+++ b/arch/arm/syscall_arch.h
@@ -101,3 +101,10 @@
 #define SYSCALL_FADVISE_6_ARG
 
 #define SYSCALL_IPC_BROKEN_MODE
+
+#define VDSO_USEFUL
+#define VDSO_CGT32_SYM "__vdso_clock_gettime"
+#define VDSO_CGT32_VER "LINUX_2.6"
+#define VDSO_CGT_SYM "__vdso_clock_gettime64"
+#define VDSO_CGT_VER "LINUX_2.6"
+#define VDSO_CGT_WORKAROUND 1
diff --git a/configure b/configure
index ca5cbc0..6f5453f 100755
--- a/configure
+++ b/configure
@@ -723,11 +723,6 @@
 test "$SUBARCH" \
 && printf "configured for %s variant: %s\n" "$ARCH" "$ARCH$SUBARCH"
 
-case "$ARCH$SUBARCH" in
-arm) ASMSUBARCH=el ;;
-*) ASMSUBARCH=$SUBARCH ;;
-esac
-
 #
 # Some archs (powerpc) have different possible long double formats
 # that the compiler can be configured for. The logic for whether this
diff --git a/include/elf.h b/include/elf.h
index 86e2f0b..9e980a2 100644
--- a/include/elf.h
+++ b/include/elf.h
@@ -385,7 +385,8 @@
 #define SHT_PREINIT_ARRAY 16
 #define SHT_GROUP	  17
 #define SHT_SYMTAB_SHNDX  18
-#define	SHT_NUM		  19
+#define SHT_RELR	  19
+#define	SHT_NUM		  20
 #define SHT_LOOS	  0x60000000
 #define SHT_GNU_ATTRIBUTES 0x6ffffff5
 #define SHT_GNU_HASH	  0x6ffffff6
@@ -754,7 +755,10 @@
 #define DT_PREINIT_ARRAY 32
 #define DT_PREINIT_ARRAYSZ 33
 #define DT_SYMTAB_SHNDX	34
-#define	DT_NUM		35
+#define DT_RELRSZ	35
+#define DT_RELR		36
+#define DT_RELRENT	37
+#define	DT_NUM		38
 #define DT_LOOS		0x6000000d
 #define DT_HIOS		0x6ffff000
 #define DT_LOPROC	0x70000000
diff --git a/include/signal.h b/include/signal.h
index b6711bc..c347f86 100644
--- a/include/signal.h
+++ b/include/signal.h
@@ -229,7 +229,6 @@
 
 int pthread_sigmask(int, const sigset_t *__restrict, sigset_t *__restrict);
 int pthread_kill(pthread_t, int);
-int pthread_sigqueue(pthread_t, int, union sigval);
 
 void psiginfo(const siginfo_t *, const char *);
 void psignal(int, const char *);
diff --git a/include/strings.h b/include/strings.h
index db0960b..b7a5ea0 100644
--- a/include/strings.h
+++ b/include/strings.h
@@ -5,6 +5,7 @@
 extern "C" {
 #endif
 
+#include <features.h>
 
 #define __NEED_size_t
 #define __NEED_locale_t
diff --git a/include/sys/sysinfo.h b/include/sys/sysinfo.h
index 3b7d0e3..6a3931e 100644
--- a/include/sys/sysinfo.h
+++ b/include/sys/sysinfo.h
@@ -8,7 +8,7 @@
 #define SI_LOAD_SHIFT 16
 
 struct sysinfo {
-	long uptime;
+	unsigned long uptime;
 	unsigned long loads[3];
 	unsigned long totalram;
 	unsigned long freeram;
diff --git a/include/unistd.h b/include/unistd.h
index 212263a..0e8149e 100644
--- a/include/unistd.h
+++ b/include/unistd.h
@@ -425,6 +425,8 @@
 #define _SC_XOPEN_STREAMS	246
 #define _SC_THREAD_ROBUST_PRIO_INHERIT	247
 #define _SC_THREAD_ROBUST_PRIO_PROTECT	248
+#define _SC_MINSIGSTKSZ	249
+#define _SC_SIGSTKSZ	250
 
 #define _CS_PATH	0
 #define _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS	1
@@ -467,6 +469,8 @@
 #define _CS_POSIX_V7_LPBIG_OFFBIG_LINTFLAGS	1147
 #define _CS_V6_ENV	1148
 #define _CS_V7_ENV	1149
+#define _CS_POSIX_V7_THREADS_CFLAGS	1150
+#define _CS_POSIX_V7_THREADS_LDFLAGS	1151
 
 #ifdef __cplusplus
 }
diff --git a/ldso/dlstart.c b/ldso/dlstart.c
index 20d50f2..259f5e1 100644
--- a/ldso/dlstart.c
+++ b/ldso/dlstart.c
@@ -140,6 +140,21 @@
 		size_t *rel_addr = (void *)(base + rel[0]);
 		*rel_addr = base + rel[2];
 	}
+
+	rel = (void *)(base+dyn[DT_RELR]);
+	rel_size = dyn[DT_RELRSZ];
+	size_t *relr_addr = 0;
+	for (; rel_size; rel++, rel_size-=sizeof(size_t)) {
+		if ((rel[0]&1) == 0) {
+			relr_addr = (void *)(base + rel[0]);
+			*relr_addr++ += base;
+		} else {
+			for (size_t i=0, bitmap=rel[0]; bitmap>>=1; i++)
+				if (bitmap&1)
+					relr_addr[i] += base;
+			relr_addr += 8*sizeof(size_t)-1;
+		}
+	}
 #endif
 
 	stage2_func dls2;
diff --git a/ldso/dynlink.c b/ldso/dynlink.c
index fd0d38e..d055bd2 100644
--- a/ldso/dynlink.c
+++ b/ldso/dynlink.c
@@ -32,7 +32,9 @@
 #define realloc __libc_realloc
 #define free __libc_free
 
-static void error(const char *, ...);
+static void error_impl(const char *, ...);
+static void error_noop(const char *, ...);
+static void (*error)(const char *, ...) = error_noop;
 
 #define MAXP2(a,b) (-(-(a)&-(b)))
 #define ALIGN(x,y) ((x)+(y)-1 & -(y))
@@ -63,6 +65,8 @@
 	size_t *dynv;
 	struct dso *next, *prev;
 
+	int elfmachine;
+	int elfclass;
 	Phdr *phdr;
 	int phnum;
 	size_t phentsize;
@@ -151,6 +155,7 @@
 static struct fdpic_dummy_loadmap app_dummy_loadmap;
 
 struct debug *_dl_debug_addr = &debug;
+static void (*exe_dl_debug_state)(void) = 0;
 
 extern hidden int __malloc_replaced;
 
@@ -211,7 +216,8 @@
 	size_t i;
 	for (i=0; i<cnt; i++) a[i] = 0;
 	for (; v[0]; v+=2) if (v[0]-1<cnt-1) {
-		a[0] |= 1UL<<v[0];
+		if (v[0] < 8*sizeof(long))
+			a[0] |= 1UL<<v[0];
 		a[v[0]] = v[1];
 	}
 }
@@ -516,6 +522,23 @@
 	}
 }
 
+static void do_relr_relocs(struct dso *dso, size_t *relr, size_t relr_size)
+{
+	unsigned char *base = dso->base;
+	size_t *reloc_addr;
+	for (; relr_size; relr++, relr_size-=sizeof(size_t))
+		if ((relr[0]&1) == 0) {
+			reloc_addr = laddr(dso, relr[0]);
+			*reloc_addr++ += (size_t)base;
+		} else {
+			int i = 0;
+			for (size_t bitmap=relr[0]; (bitmap>>=1); i++)
+				if (bitmap&1)
+					reloc_addr[i] += (size_t)base;
+			reloc_addr += 8*sizeof(size_t)-1;
+		}
+}
+
 static void redo_lazy_relocs()
 {
 	struct dso *p = lazy_head, *next;
@@ -623,6 +646,19 @@
 	}
 }
 
+static int verify_elf_magic(const Ehdr* eh) {
+	return eh->e_ident[0] == ELFMAG0 &&
+		eh->e_ident[1] == ELFMAG1 &&
+		eh->e_ident[2] == ELFMAG2 &&
+		eh->e_ident[3] == ELFMAG3;
+}
+
+/* Verifies that an elf header's machine and class match the loader */
+static int verify_elf_arch(const Ehdr* eh) {
+	return eh->e_machine == ldso.elfmachine &&
+		eh->e_ident[EI_CLASS] == ldso.elfclass;
+}
+
 static void *map_library(int fd, struct dso *dso)
 {
 	Ehdr buf[(896+sizeof(Ehdr))/sizeof(Ehdr)];
@@ -645,6 +681,10 @@
 	if (l<0) return 0;
 	if (l<sizeof *eh || (eh->e_type != ET_DYN && eh->e_type != ET_EXEC))
 		goto noexec;
+	if (!verify_elf_magic(eh)) goto noexec;
+	if (!verify_elf_arch(eh)) goto noexec;
+	dso->elfmachine = eh->e_machine;
+	dso->elfclass = eh->e_ident[EI_CLASS];
 	phsize = eh->e_phentsize * eh->e_phnum;
 	if (phsize > sizeof buf - sizeof *eh) {
 		allocated_buf = malloc(phsize);
@@ -809,29 +849,53 @@
 	return 0;
 }
 
-static int path_open(const char *name, const char *s, char *buf, size_t buf_size)
+static int path_open_library(const char *name, const char *s, char *buf, size_t buf_size)
 {
 	size_t l;
 	int fd;
+	const char *p;
 	for (;;) {
 		s += strspn(s, ":\n");
+		p = s;
 		l = strcspn(s, ":\n");
 		if (l-1 >= INT_MAX) return -1;
-		if (snprintf(buf, buf_size, "%.*s/%s", (int)l, s, name) < buf_size) {
-			if ((fd = open(buf, O_RDONLY|O_CLOEXEC))>=0) return fd;
-			switch (errno) {
-			case ENOENT:
-			case ENOTDIR:
-			case EACCES:
-			case ENAMETOOLONG:
-				break;
-			default:
-				/* Any negative value but -1 will inhibit
-				 * futher path search. */
+		s += l;
+		if (snprintf(buf, buf_size, "%.*s/%s", (int)l, p, name) < buf_size) {
+			fd = open(buf, O_RDONLY|O_CLOEXEC);
+			if (fd < 0) {
+				switch (errno) {
+				case ENOENT:
+				case ENOTDIR:
+				case EACCES:
+				case ENAMETOOLONG:
+					/* Keep searching in path list. */
+					continue;
+				default:
+					/* Any negative value but -1 will
+					 * inhibit further path search in
+					 * load_library. */
+					return -2;
+				}
+			}
+			Ehdr eh;
+			ssize_t n = pread(fd, &eh, sizeof eh, 0);
+			/* If the elf file is invalid return -2 to inhibit
+			 * further path search in load_library. */
+			if (n < 0 ||
+			    n != sizeof eh ||
+			    !verify_elf_magic(&eh)) {
+				close(fd);
 				return -2;
 			}
+			/* If the elf file has a valid header but is for the
+			 * wrong architecture ignore it and keep searching the
+			 * path list. */
+			if (!verify_elf_arch(&eh)) {
+				close(fd);
+				continue;
+			}
+			return fd;
 		}
-		s += l;
 	}
 }
 
@@ -869,7 +933,7 @@
 		case ENOENT:
 		case ENOTDIR:
 		case EACCES:
-			break;
+			return 0;
 		default:
 			return -1;
 		}
@@ -1011,7 +1075,7 @@
 	/* Catch and block attempts to reload the implementation itself */
 	if (name[0]=='l' && name[1]=='i' && name[2]=='b') {
 		static const char reserved[] =
-			"c.pthread.rt.m.dl.util.xnet.";
+			"c.c_musl.pthread.rt.m.dl.util.xnet.";
 		const char *rp, *next;
 		for (rp=reserved; *rp; rp=next) {
 			next = strchr(rp, '.') + 1;
@@ -1055,12 +1119,12 @@
 		}
 		if (strlen(name) > NAME_MAX) return 0;
 		fd = -1;
-		if (env_path) fd = path_open(name, env_path, buf, sizeof buf);
+		if (env_path) fd = path_open_library(name, env_path, buf, sizeof buf);
 		for (p=needed_by; fd == -1 && p; p=p->needed_by) {
 			if (fixup_rpath(p, buf, sizeof buf) < 0)
 				fd = -2; /* Inhibit further search. */
 			if (p->rpath)
-				fd = path_open(name, p->rpath, buf, sizeof buf);
+				fd = path_open_library(name, p->rpath, buf, sizeof buf);
 		}
 		if (fd == -1) {
 			if (!sys_path) {
@@ -1099,7 +1163,7 @@
 				}
 			}
 			if (!sys_path) sys_path = "/lib:/usr/local/lib:/usr/lib";
-			fd = path_open(name, sys_path, buf, sizeof buf);
+			fd = path_open_library(name, sys_path, buf, sizeof buf);
 		}
 		pathname = buf;
 	}
@@ -1358,13 +1422,17 @@
 			2+(dyn[DT_PLTREL]==DT_RELA));
 		do_relocs(p, laddr(p, dyn[DT_REL]), dyn[DT_RELSZ], 2);
 		do_relocs(p, laddr(p, dyn[DT_RELA]), dyn[DT_RELASZ], 3);
+		if (!DL_FDPIC)
+			do_relr_relocs(p, laddr(p, dyn[DT_RELR]), dyn[DT_RELRSZ]);
 
-		if (head != &ldso && p->relro_start != p->relro_end &&
-		    mprotect(laddr(p, p->relro_start), p->relro_end-p->relro_start, PROT_READ)
-		    && errno != ENOSYS) {
-			error("Error relocating %s: RELRO protection failed: %m",
-				p->name);
-			if (runtime) longjmp(*rtld_fail, 1);
+		if (head != &ldso && p->relro_start != p->relro_end) {
+			long ret = __syscall(SYS_mprotect, laddr(p, p->relro_start),
+				p->relro_end-p->relro_start, PROT_READ);
+			if (ret != 0 && ret != -ENOSYS) {
+				error("Error relocating %s: RELRO protection failed: %m",
+					p->name);
+				if (runtime) longjmp(*rtld_fail, 1);
+			}
 		}
 
 		p->relocated = 1;
@@ -1561,6 +1629,8 @@
 
 static void dl_debug_state(void)
 {
+	if (exe_dl_debug_state)
+		exe_dl_debug_state();
 }
 
 weak_alias(dl_debug_state, _dl_debug_state);
@@ -1667,6 +1737,8 @@
 	ldso.phnum = ehdr->e_phnum;
 	ldso.phdr = laddr(&ldso, ehdr->e_phoff);
 	ldso.phentsize = ehdr->e_phentsize;
+	ldso.elfmachine = ehdr->e_machine;
+	ldso.elfclass = ehdr->e_ident[EI_CLASS];
 	kernel_mapped_dso(&ldso);
 	decode_dyn(&ldso);
 
@@ -1759,6 +1831,9 @@
 		env_preload = getenv("LD_PRELOAD");
 	}
 
+	/* Activate error handler function */
+	error = error_impl;
+
 	/* If the main program was already loaded by the kernel,
 	 * AT_PHDR will point to some location other than the dynamic
 	 * linker's program headers. */
@@ -1983,6 +2058,12 @@
 	if (find_sym(head, "aligned_alloc", 1).dso != &ldso)
 		__aligned_alloc_replaced = 1;
 
+	/* Determine if another DSO is providing the _dl_debug_state symbol
+	 * and forward calls to it. */
+	struct symdef debug_sym = find_sym(head, "_dl_debug_state", 1);
+	if (debug_sym.dso != &ldso)
+		exe_dl_debug_state = (void (*)(void))laddr(debug_sym.dso, debug_sym.sym->st_value);
+
 	/* Switch to runtime mode: any further failures in the dynamic
 	 * linker are a reportable failure rather than a fatal startup
 	 * error. */
@@ -2348,7 +2429,7 @@
 	return ret;
 }
 
-static void error(const char *fmt, ...)
+static void error_impl(const char *fmt, ...)
 {
 	va_list ap;
 	va_start(ap, fmt);
@@ -2362,3 +2443,7 @@
 	__dl_vseterr(fmt, ap);
 	va_end(ap);
 }
+
+static void error_noop(const char *fmt, ...)
+{
+}
diff --git a/sources.bp b/sources.bp
index 0e63694..dd9fbda 100644
--- a/sources.bp
+++ b/sources.bp
@@ -1118,7 +1118,6 @@
         "src/thread/pthread_setschedprio.c",
         "src/thread/pthread_setspecific.c",
         "src/thread/pthread_sigmask.c",
-        "src/thread/pthread_sigqueue.c",
         "src/thread/pthread_spin_destroy.c",
         "src/thread/pthread_spin_init.c",
         "src/thread/pthread_spin_lock.c",
@@ -1353,6 +1352,7 @@
                 "src/math/aarch64/sqrtf.c",
                 "src/math/aarch64/trunc.c",
                 "src/math/aarch64/truncf.c",
+                "src/process/aarch64/vfork.s",
                 "src/setjmp/aarch64/longjmp.s",
                 "src/setjmp/aarch64/setjmp.s",
                 "src/signal/aarch64/restore.s",
@@ -1395,6 +1395,7 @@
                 "src/math/sqrtf.c",
                 "src/math/trunc.c",
                 "src/math/truncf.c",
+                "src/process/vfork.c",
                 "src/setjmp/longjmp.c",
                 "src/setjmp/setjmp.c",
                 "src/signal/restore.c",
diff --git a/src/conf/confstr.c b/src/conf/confstr.c
index 02cb1aa..3d41728 100644
--- a/src/conf/confstr.c
+++ b/src/conf/confstr.c
@@ -7,7 +7,7 @@
 	const char *s = "";
 	if (!name) {
 		s = "/bin:/usr/bin";
-	} else if ((name&~4U)!=1 && name-_CS_POSIX_V6_ILP32_OFF32_CFLAGS>33U) {
+	} else if ((name&~4U)!=1 && name-_CS_POSIX_V6_ILP32_OFF32_CFLAGS>35U) {
 		errno = EINVAL;
 		return 0;
 	}
diff --git a/src/conf/sysconf.c b/src/conf/sysconf.c
index 3baaed3..60d3e74 100644
--- a/src/conf/sysconf.c
+++ b/src/conf/sysconf.c
@@ -4,6 +4,7 @@
 #include <sys/resource.h>
 #include <signal.h>
 #include <sys/sysinfo.h>
+#include <sys/auxv.h>
 #include "syscall.h"
 #include "libc.h"
 
@@ -19,6 +20,8 @@
 #define JT_AVPHYS_PAGES JT(9)
 #define JT_ZERO JT(10)
 #define JT_DELAYTIMER_MAX JT(11)
+#define JT_MINSIGSTKSZ JT(12)
+#define JT_SIGSTKSZ JT(13)
 
 #define RLIM(x) (-32768|(RLIMIT_ ## x))
 
@@ -165,6 +168,9 @@
 		[_SC_XOPEN_STREAMS] = JT_ZERO,
 		[_SC_THREAD_ROBUST_PRIO_INHERIT] = -1,
 		[_SC_THREAD_ROBUST_PRIO_PROTECT] = -1,
+
+		[_SC_MINSIGSTKSZ] = JT_MINSIGSTKSZ,
+		[_SC_SIGSTKSZ] = JT_SIGSTKSZ,
 	};
 
 	if (name >= sizeof(values)/sizeof(values[0]) || !values[name]) {
@@ -212,6 +218,13 @@
 		mem *= si.mem_unit;
 		mem /= PAGE_SIZE;
 		return (mem > LONG_MAX) ? LONG_MAX : mem;
+	case JT_MINSIGSTKSZ & 255:
+	case JT_SIGSTKSZ & 255: ;
+		long val = __getauxval(AT_MINSIGSTKSZ);
+		if (val < MINSIGSTKSZ) val = MINSIGSTKSZ;
+		if (values[name] == JT_SIGSTKSZ)
+			val += SIGSTKSZ - MINSIGSTKSZ;
+		return val;
 	case JT_ZERO & 255:
 		return 0;
 	}
diff --git a/src/include/sys/stat.h b/src/include/sys/stat.h
new file mode 100644
index 0000000..59339be
--- /dev/null
+++ b/src/include/sys/stat.h
@@ -0,0 +1,9 @@
+#ifndef SYS_STAT_H
+#define SYS_STAT_H
+
+#include "../../../include/sys/stat.h"
+
+hidden int __fstat(int, struct stat *);
+hidden int __fstatat(int, const char *restrict, struct stat *restrict, int);
+
+#endif
diff --git a/src/internal/dynlink.h b/src/internal/dynlink.h
index 51c0639..830354e 100644
--- a/src/internal/dynlink.h
+++ b/src/internal/dynlink.h
@@ -93,7 +93,7 @@
 #endif
 
 #define AUX_CNT 32
-#define DYN_CNT 32
+#define DYN_CNT 37
 
 typedef void (*stage2_func)(unsigned char *, size_t *);
 
diff --git a/src/internal/syscall.h b/src/internal/syscall.h
index d5f294d..4a44615 100644
--- a/src/internal/syscall.h
+++ b/src/internal/syscall.h
@@ -58,7 +58,7 @@
 #define __syscall_cp(...) __SYSCALL_DISP(__syscall_cp,__VA_ARGS__)
 #define syscall_cp(...) __syscall_ret(__syscall_cp(__VA_ARGS__))
 
-static inline long __alt_socketcall(int sys, int sock, int cp, long a, long b, long c, long d, long e, long f)
+static inline long __alt_socketcall(int sys, int sock, int cp, syscall_arg_t a, syscall_arg_t b, syscall_arg_t c, syscall_arg_t d, syscall_arg_t e, syscall_arg_t f)
 {
 	long r;
 	if (cp) r = __syscall_cp(sys, a, b, c, d, e, f);
@@ -71,9 +71,9 @@
 	return r;
 }
 #define __socketcall(nm, a, b, c, d, e, f) __alt_socketcall(SYS_##nm, __SC_##nm, 0, \
-	(long)(a), (long)(b), (long)(c), (long)(d), (long)(e), (long)(f))
+	__scc(a), __scc(b), __scc(c), __scc(d), __scc(e), __scc(f))
 #define __socketcall_cp(nm, a, b, c, d, e, f) __alt_socketcall(SYS_##nm, __SC_##nm, 1, \
-	(long)(a), (long)(b), (long)(c), (long)(d), (long)(e), (long)(f))
+	__scc(a), __scc(b), __scc(c), __scc(d), __scc(e), __scc(f))
 
 /* fixup legacy 16-bit junk */
 
@@ -201,43 +201,43 @@
 #define SYS_sendfile SYS_sendfile64
 #endif
 
-#ifndef SYS_timer_settime
+#ifdef SYS_timer_settime32
 #define SYS_timer_settime SYS_timer_settime32
 #endif
 
-#ifndef SYS_timer_gettime
+#ifdef SYS_timer_gettime32
 #define SYS_timer_gettime SYS_timer_gettime32
 #endif
 
-#ifndef SYS_timerfd_settime
+#ifdef SYS_timerfd_settime32
 #define SYS_timerfd_settime SYS_timerfd_settime32
 #endif
 
-#ifndef SYS_timerfd_gettime
+#ifdef SYS_timerfd_gettime32
 #define SYS_timerfd_gettime SYS_timerfd_gettime32
 #endif
 
-#ifndef SYS_clock_settime
+#ifdef SYS_clock_settime32
 #define SYS_clock_settime SYS_clock_settime32
 #endif
 
-#ifndef SYS_clock_gettime
+#ifdef SYS_clock_gettime32
 #define SYS_clock_gettime SYS_clock_gettime32
 #endif
 
-#ifndef SYS_clock_getres
+#ifdef SYS_clock_getres_time32
 #define SYS_clock_getres SYS_clock_getres_time32
 #endif
 
-#ifndef SYS_clock_nanosleep
+#ifdef SYS_clock_nanosleep_time32
 #define SYS_clock_nanosleep SYS_clock_nanosleep_time32
 #endif
 
-#ifndef SYS_gettimeofday
+#ifdef SYS_gettimeofday_time32
 #define SYS_gettimeofday SYS_gettimeofday_time32
 #endif
 
-#ifndef SYS_settimeofday
+#ifdef SYS_settimeofday_time32
 #define SYS_settimeofday SYS_settimeofday_time32
 #endif
 
diff --git a/src/linux/epoll.c b/src/linux/epoll.c
index 93baa81..e56e8f4 100644
--- a/src/linux/epoll.c
+++ b/src/linux/epoll.c
@@ -5,6 +5,7 @@
 
 int epoll_create(int size)
 {
+	if (size<=0) return __syscall_ret(-EINVAL);
 	return epoll_create1(0);
 }
 
diff --git a/src/linux/membarrier.c b/src/linux/membarrier.c
index 343f736..f64fe7e 100644
--- a/src/linux/membarrier.c
+++ b/src/linux/membarrier.c
@@ -35,7 +35,7 @@
 		__tl_lock();
 		sem_init(&barrier_sem, 0, 0);
 		struct sigaction sa = {
-			.sa_flags = SA_RESTART,
+			.sa_flags = SA_RESTART | SA_ONSTACK,
 			.sa_handler = bcast_barrier
 		};
 		memset(&sa.sa_mask, -1, sizeof sa.sa_mask);
diff --git a/src/misc/getopt.c b/src/misc/getopt.c
index c3f6699..f8df7ae 100644
--- a/src/misc/getopt.c
+++ b/src/misc/getopt.c
@@ -25,7 +25,7 @@
 	FUNLOCK(f);
 }
 
-int getopt(int argc, char * const argv[], const char *optstring)
+int __posix_getopt(int argc, char * const argv[], const char *optstring)
 {
 	int i;
 	wchar_t c, d;
@@ -101,5 +101,3 @@
 	}
 	return c;
 }
-
-weak_alias(getopt, __posix_getopt);
diff --git a/src/misc/getopt_long.c b/src/misc/getopt_long.c
index 6949ab1..117d1a1 100644
--- a/src/misc/getopt_long.c
+++ b/src/misc/getopt_long.c
@@ -9,6 +9,8 @@
 
 extern int __optpos, __optreset;
 
+int __posix_getopt(int argc, char * const argv[], const char *optstring);
+
 static void permute(char *const *argv, int dest, int src)
 {
 	char **av = (char **)argv;
@@ -134,7 +136,7 @@
 			return '?';
 		}
 	}
-	return getopt(argc, argv, optstring);
+	return __posix_getopt(argc, argv, optstring);
 }
 
 int getopt_long(int argc, char *const *argv, const char *optstring, const struct option *longopts, int *idx)
@@ -146,3 +148,20 @@
 {
 	return __getopt_long(argc, argv, optstring, longopts, idx, 1);
 }
+
+/* ANDROID CHANGE: implement getopt via getopt_long to continue parsing options
+ * after the first non-option argument to match the user visible behavior of
+ * glibc.
+ */
+int getopt(int argc, char * const argv[], const char *optstring)
+{
+	static int posixly_correct = -1;
+
+	if (posixly_correct == -1 || __optreset)
+		posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);
+
+	if (posixly_correct)
+		return __posix_getopt(argc, argv, optstring);
+	else
+		return __getopt_long(argc, argv, optstring, NULL, NULL, 0);
+}
diff --git a/src/misc/getrlimit.c b/src/misc/getrlimit.c
index 2ab2f0f..bf67630 100644
--- a/src/misc/getrlimit.c
+++ b/src/misc/getrlimit.c
@@ -6,12 +6,13 @@
 
 int getrlimit(int resource, struct rlimit *rlim)
 {
-	unsigned long k_rlim[2];
 	int ret = syscall(SYS_prlimit64, 0, resource, 0, rlim);
 	if (!ret) {
 		FIX(rlim->rlim_cur);
 		FIX(rlim->rlim_max);
 	}
+#ifdef SYS_getrlimit
+	unsigned long k_rlim[2];
 	if (!ret || errno != ENOSYS)
 		return ret;
 	if (syscall(SYS_getrlimit, resource, k_rlim) < 0)
@@ -21,6 +22,9 @@
 	FIX(rlim->rlim_cur);
 	FIX(rlim->rlim_max);
 	return 0;
+#else
+	return ret;
+#endif
 }
 
 weak_alias(getrlimit, getrlimit64);
diff --git a/src/misc/mntent.c b/src/misc/mntent.c
index eabb820..d404fbe 100644
--- a/src/misc/mntent.c
+++ b/src/misc/mntent.c
@@ -2,6 +2,7 @@
 #include <string.h>
 #include <mntent.h>
 #include <errno.h>
+#include <limits.h>
 
 static char *internal_buf;
 static size_t internal_bufsize;
@@ -21,7 +22,8 @@
 
 struct mntent *getmntent_r(FILE *f, struct mntent *mnt, char *linebuf, int buflen)
 {
-	int cnt, n[8], use_internal = (linebuf == SENTINEL);
+	int n[8], use_internal = (linebuf == SENTINEL);
+	size_t len, i;
 
 	mnt->mnt_freq = 0;
 	mnt->mnt_passno = 0;
@@ -39,10 +41,14 @@
 			errno = ERANGE;
 			return 0;
 		}
-		cnt = sscanf(linebuf, " %n%*s%n %n%*s%n %n%*s%n %n%*s%n %d %d",
+
+		len = strlen(linebuf);
+		if (len > INT_MAX) continue;
+		for (i = 0; i < sizeof n / sizeof *n; i++) n[i] = len;
+		sscanf(linebuf, " %n%*s%n %n%*s%n %n%*s%n %n%*s%n %d %d",
 			n, n+1, n+2, n+3, n+4, n+5, n+6, n+7,
 			&mnt->mnt_freq, &mnt->mnt_passno);
-	} while (cnt < 2 || linebuf[n[0]] == '#');
+	} while (linebuf[n[0]] == '#' || n[1]==len);
 
 	linebuf[n[1]] = 0;
 	linebuf[n[3]] = 0;
diff --git a/src/misc/setrlimit.c b/src/misc/setrlimit.c
index 8340aee..5b713cf 100644
--- a/src/misc/setrlimit.c
+++ b/src/misc/setrlimit.c
@@ -12,12 +12,14 @@
 	int err;
 };
 
+#ifdef SYS_setrlimit
 static void do_setrlimit(void *p)
 {
 	struct ctx *c = p;
 	if (c->err>0) return;
 	c->err = -__syscall(SYS_setrlimit, c->res, c->lim);
 }
+#endif
 
 int setrlimit(int resource, const struct rlimit *rlim)
 {
@@ -29,6 +31,7 @@
 		rlim = &tmp;
 	}
 	int ret = __syscall(SYS_prlimit64, 0, resource, rlim, 0);
+#ifdef SYS_setrlimit
 	if (ret != -ENOSYS) return __syscall_ret(ret);
 
 	struct ctx c = {
@@ -42,6 +45,9 @@
 		return -1;
 	}
 	return 0;
+#else
+	return __syscall_ret(ret);
+#endif
 }
 
 weak_alias(setrlimit, setrlimit64);
diff --git a/src/network/getaddrinfo.c b/src/network/getaddrinfo.c
index efaab30..9df045f 100644
--- a/src/network/getaddrinfo.c
+++ b/src/network/getaddrinfo.c
@@ -66,9 +66,11 @@
 				pthread_setcancelstate(
 					PTHREAD_CANCEL_DISABLE, &cs);
 				int r = connect(s, ta[i], tl[i]);
+				int saved_errno = errno;
 				pthread_setcancelstate(cs, 0);
 				close(s);
 				if (!r) continue;
+				errno = saved_errno;
 			}
 			switch (errno) {
 			case EADDRNOTAVAIL:
diff --git a/src/network/gethostbyaddr_r.c b/src/network/gethostbyaddr_r.c
index 0f1e61a..ceaf393 100644
--- a/src/network/gethostbyaddr_r.c
+++ b/src/network/gethostbyaddr_r.c
@@ -54,10 +54,11 @@
 	case EAI_OVERFLOW:
 		return ERANGE;
 	default:
-	case EAI_MEMORY:
-	case EAI_SYSTEM:
 	case EAI_FAIL:
 		*err = NO_RECOVERY;
+		return EBADMSG;
+	case EAI_SYSTEM:
+		*err = NO_RECOVERY;
 		return errno;
 	case 0:
 		break;
diff --git a/src/network/gethostbyname2_r.c b/src/network/gethostbyname2_r.c
index fc89487..c9f3acc 100644
--- a/src/network/gethostbyname2_r.c
+++ b/src/network/gethostbyname2_r.c
@@ -22,7 +22,7 @@
 	if (cnt<0) switch (cnt) {
 	case EAI_NONAME:
 		*err = HOST_NOT_FOUND;
-		return ENOENT;
+		return 0;
 	case EAI_AGAIN:
 		*err = TRY_AGAIN;
 		return EAGAIN;
@@ -30,7 +30,6 @@
 	case EAI_FAIL:
 		*err = NO_RECOVERY;
 		return EBADMSG;
-	case EAI_MEMORY:
 	case EAI_SYSTEM:
 		*err = NO_RECOVERY;
 		return errno;
diff --git a/src/network/lookup_name.c b/src/network/lookup_name.c
index aa558c1..bec6ba2 100644
--- a/src/network/lookup_name.c
+++ b/src/network/lookup_name.c
@@ -153,8 +153,11 @@
 			qlens[nq] = __res_mkquery(0, name, 1, afrr[i].rr,
 				0, 0, 0, qbuf[nq], sizeof *qbuf);
 			if (qlens[nq] == -1)
-				return EAI_NONAME;
+				return 0;
 			qbuf[nq][3] = 0; /* don't need AD flag */
+			/* Ensure query IDs are distinct. */
+			if (nq && qbuf[nq][0] == qbuf[0][0])
+				qbuf[nq][0]++;
 			nq++;
 		}
 	}
diff --git a/src/network/netlink.h b/src/network/netlink.h
index 38acb17..873fabe 100644
--- a/src/network/netlink.h
+++ b/src/network/netlink.h
@@ -86,7 +86,7 @@
 #define RTA_DATALEN(rta)	((rta)->rta_len-sizeof(struct rtattr))
 #define RTA_DATAEND(rta)	((char*)(rta)+(rta)->rta_len)
 #define RTA_NEXT(rta)		(struct rtattr*)((char*)(rta)+NETLINK_ALIGN((rta)->rta_len))
-#define RTA_OK(nlh,end)		((char*)(end)-(char*)(rta) >= sizeof(struct rtattr))
+#define RTA_OK(rta,end)		((char*)(end)-(char*)(rta) >= sizeof(struct rtattr))
 
 #define NLMSG_RTA(nlh,len)	((void*)((char*)(nlh)+sizeof(struct nlmsghdr)+NETLINK_ALIGN(len)))
 #define NLMSG_RTAOK(rta,nlh)	RTA_OK(rta,NLMSG_DATAEND(nlh))
diff --git a/src/network/res_mkquery.c b/src/network/res_mkquery.c
index 33f50cb..614bf78 100644
--- a/src/network/res_mkquery.c
+++ b/src/network/res_mkquery.c
@@ -13,6 +13,7 @@
 	int n;
 
 	if (l && dname[l-1]=='.') l--;
+	if (l && dname[l-1]=='.') return -1;
 	n = 17+l+!!l;
 	if (l>253 || buflen<n || op>15u || class>255u || type>255u)
 		return -1;
diff --git a/src/network/res_msend.c b/src/network/res_msend.c
index 3e01800..9adaea1 100644
--- a/src/network/res_msend.c
+++ b/src/network/res_msend.c
@@ -68,14 +68,20 @@
 	}
 
 	/* Get local address and open/bind a socket */
-	sa.sin.sin_family = family;
 	fd = socket(family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
 
 	/* Handle case where system lacks IPv6 support */
 	if (fd < 0 && family == AF_INET6 && errno == EAFNOSUPPORT) {
+		for (i=0; i<nns && conf->ns[nns].family == AF_INET6; i++);
+		if (i==nns) {
+			pthread_setcancelstate(cs, 0);
+			return -1;
+		}
 		fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
 		family = AF_INET;
+		sl = sizeof sa.sin;
 	}
+	sa.sin.sin_family = family;
 	if (fd < 0 || bind(fd, (void *)&sa, sl) < 0) {
 		if (fd >= 0) close(fd);
 		pthread_setcancelstate(cs, 0);
diff --git a/src/process/aarch64/vfork.s b/src/process/aarch64/vfork.s
new file mode 100644
index 0000000..429bec8
--- /dev/null
+++ b/src/process/aarch64/vfork.s
@@ -0,0 +1,9 @@
+.global vfork
+.type vfork,%function
+vfork:
+	mov x8, 220    // SYS_clone
+	mov x0, 0x4111 // SIGCHLD | CLONE_VM | CLONE_VFORK
+	mov x1, 0
+	svc 0
+	.hidden __syscall_ret
+	b __syscall_ret
diff --git a/src/stat/fchmodat.c b/src/stat/fchmodat.c
index 4ee00b0..bc58105 100644
--- a/src/stat/fchmodat.c
+++ b/src/stat/fchmodat.c
@@ -2,7 +2,6 @@
 #include <fcntl.h>
 #include <errno.h>
 #include "syscall.h"
-#include "kstat.h"
 
 int fchmodat(int fd, const char *path, mode_t mode, int flag)
 {
@@ -11,12 +10,12 @@
 	if (flag != AT_SYMLINK_NOFOLLOW)
 		return __syscall_ret(-EINVAL);
 
-	struct kstat st;
+	struct stat st;
 	int ret, fd2;
 	char proc[15+3*sizeof(int)];
 
-	if ((ret = __syscall(SYS_fstatat, fd, path, &st, flag)))
-		return __syscall_ret(ret);
+	if (fstatat(fd, path, &st, flag))
+		return -1;
 	if (S_ISLNK(st.st_mode))
 		return __syscall_ret(-EOPNOTSUPP);
 
@@ -27,12 +26,12 @@
 	}
 
 	__procfdname(proc, fd2);
-	ret = __syscall(SYS_fstatat, AT_FDCWD, proc, &st, 0);
+	ret = stat(proc, &st);
 	if (!ret) {
-		if (S_ISLNK(st.st_mode)) ret = -EOPNOTSUPP;
-		else ret = __syscall(SYS_fchmodat, AT_FDCWD, proc, mode);
+		if (S_ISLNK(st.st_mode)) ret = __syscall_ret(-EOPNOTSUPP);
+		else ret = syscall(SYS_fchmodat, AT_FDCWD, proc, mode);
 	}
 
 	__syscall(SYS_close, fd2);
-	return __syscall_ret(ret);
+	return ret;
 }
diff --git a/src/stat/fstat.c b/src/stat/fstat.c
index 9bbb46d..27db0cc 100644
--- a/src/stat/fstat.c
+++ b/src/stat/fstat.c
@@ -4,12 +4,14 @@
 #include <fcntl.h>
 #include "syscall.h"
 
-int fstat(int fd, struct stat *st)
+int __fstat(int fd, struct stat *st)
 {
 	if (fd<0) return __syscall_ret(-EBADF);
-	return fstatat(fd, "", st, AT_EMPTY_PATH);
+	return __fstatat(fd, "", st, AT_EMPTY_PATH);
 }
 
+weak_alias(__fstat, fstat);
+
 #if !_REDIR_TIME64
 weak_alias(fstat, fstat64);
 #endif
diff --git a/src/stat/fstatat.c b/src/stat/fstatat.c
index de165b5..74c51cf 100644
--- a/src/stat/fstatat.c
+++ b/src/stat/fstatat.c
@@ -6,7 +6,6 @@
 #include <stdint.h>
 #include <sys/sysmacros.h>
 #include "syscall.h"
-#include "kstat.h"
 
 struct statx {
 	uint32_t stx_mask;
@@ -69,6 +68,10 @@
 	return 0;
 }
 
+#ifdef SYS_fstatat
+
+#include "kstat.h"
+
 static int fstatat_kstat(int fd, const char *restrict path, struct stat *restrict st, int flag)
 {
 	int ret;
@@ -130,18 +133,25 @@
 
 	return 0;
 }
+#endif
 
-int fstatat(int fd, const char *restrict path, struct stat *restrict st, int flag)
+int __fstatat(int fd, const char *restrict path, struct stat *restrict st, int flag)
 {
 	int ret;
+#ifdef SYS_fstatat
 	if (sizeof((struct kstat){0}.st_atime_sec) < sizeof(time_t)) {
 		ret = fstatat_statx(fd, path, st, flag);
 		if (ret!=-ENOSYS) return __syscall_ret(ret);
 	}
 	ret = fstatat_kstat(fd, path, st, flag);
+#else
+	ret = fstatat_statx(fd, path, st, flag);
+#endif
 	return __syscall_ret(ret);
 }
 
+weak_alias(__fstatat, fstatat);
+
 #if !_REDIR_TIME64
 weak_alias(fstatat, fstatat64);
 #endif
diff --git a/src/stdio/freopen.c b/src/stdio/freopen.c
index 615d4b4..96bfbb4 100644
--- a/src/stdio/freopen.c
+++ b/src/stdio/freopen.c
@@ -40,6 +40,8 @@
 		fclose(f2);
 	}
 
+	f->mode = 0;
+	f->locale = 0;
 	FUNLOCK(f);
 	return f;
 
diff --git a/src/stdio/open_wmemstream.c b/src/stdio/open_wmemstream.c
index ed1b561..b8ae4a7 100644
--- a/src/stdio/open_wmemstream.c
+++ b/src/stdio/open_wmemstream.c
@@ -40,8 +40,12 @@
 static size_t wms_write(FILE *f, const unsigned char *buf, size_t len)
 {
 	struct cookie *c = f->cookie;
-	size_t len2;
+	size_t len2 = f->wpos - f->wbase;
 	wchar_t *newbuf;
+	if (len2) {
+		f->wpos = f->wbase;
+		if (wms_write(f, f->wbase, len2) < len2) return 0;
+	}
 	if (len + c->pos >= c->space) {
 		len2 = 2*c->space+1 | c->pos+len+1;
 		if (len2 > SSIZE_MAX/4) return 0;
diff --git a/src/stdio/tempnam.c b/src/stdio/tempnam.c
index 565df6b..0c65b1f 100644
--- a/src/stdio/tempnam.c
+++ b/src/stdio/tempnam.c
@@ -6,7 +6,6 @@
 #include <string.h>
 #include <stdlib.h>
 #include "syscall.h"
-#include "kstat.h"
 
 #define MAXTRIES 100
 
@@ -37,11 +36,10 @@
 
 	for (try=0; try<MAXTRIES; try++) {
 		__randname(s+l-6);
-#ifdef SYS_lstat
-		r = __syscall(SYS_lstat, s, &(struct kstat){0});
+#ifdef SYS_readlink
+		r = __syscall(SYS_readlink, s, (char[1]){0}, 1);
 #else
-		r = __syscall(SYS_fstatat, AT_FDCWD, s,
-			&(struct kstat){0}, AT_SYMLINK_NOFOLLOW);
+		r = __syscall(SYS_readlinkat, AT_FDCWD, s, (char[1]){0}, 1);
 #endif
 		if (r == -ENOENT) return strdup(s);
 	}
diff --git a/src/stdio/tmpnam.c b/src/stdio/tmpnam.c
index d667a83..71dc8bb 100644
--- a/src/stdio/tmpnam.c
+++ b/src/stdio/tmpnam.c
@@ -5,7 +5,6 @@
 #include <string.h>
 #include <stdlib.h>
 #include "syscall.h"
-#include "kstat.h"
 
 #define MAXTRIES 100
 
@@ -17,11 +16,10 @@
 	int r;
 	for (try=0; try<MAXTRIES; try++) {
 		__randname(s+12);
-#ifdef SYS_lstat
-		r = __syscall(SYS_lstat, s, &(struct kstat){0});
+#ifdef SYS_readlink
+		r = __syscall(SYS_readlink, s, (char[1]){0}, 1);
 #else
-		r = __syscall(SYS_fstatat, AT_FDCWD, s,
-			&(struct kstat){0}, AT_SYMLINK_NOFOLLOW);
+		r = __syscall(SYS_readlinkat, AT_FDCWD, s, (char[1]){0}, 1);
 #endif
 		if (r == -ENOENT) return strcpy(buf ? buf : internal, s);
 	}
diff --git a/src/stdlib/qsort_nr.c b/src/stdlib/qsort_nr.c
index efe7cce..8ffe71d 100644
--- a/src/stdlib/qsort_nr.c
+++ b/src/stdlib/qsort_nr.c
@@ -10,5 +10,5 @@
 
 void qsort(void *base, size_t nel, size_t width, cmpfun cmp)
 {
-	__qsort_r(base, nel, width, wrapper_cmp, cmp);
+	__qsort_r(base, nel, width, wrapper_cmp, (void *)cmp);
 }
diff --git a/src/temp/__randname.c b/src/temp/__randname.c
index 2bce37a..e9b970f 100644
--- a/src/temp/__randname.c
+++ b/src/temp/__randname.c
@@ -1,5 +1,6 @@
 #include <time.h>
 #include <stdint.h>
+#include "pthread_impl.h"
 
 /* This assumes that a check for the
    template size has already been made */
@@ -10,7 +11,7 @@
 	unsigned long r;
 
 	__clock_gettime(CLOCK_REALTIME, &ts);
-	r = ts.tv_nsec*65537 ^ (uintptr_t)&ts / 16 + (uintptr_t)template;
+	r = ts.tv_sec + ts.tv_nsec + __pthread_self()->tid * 65537UL;
 	for (i=0; i<6; i++, r>>=5)
 		template[i] = 'A'+(r&15)+(r&16)*2;
 
diff --git a/src/thread/pthread_cancel.c b/src/thread/pthread_cancel.c
index 2f9d5e9..2d3a98e 100644
--- a/src/thread/pthread_cancel.c
+++ b/src/thread/pthread_cancel.c
@@ -77,7 +77,7 @@
 static void init_cancellation()
 {
 	struct sigaction sa = {
-		.sa_flags = SA_SIGINFO | SA_RESTART,
+		.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK,
 		.sa_sigaction = cancel_handler
 	};
 	memset(&sa.sa_mask, -1, _NSIG/8);
diff --git a/src/thread/pthread_sigqueue.c b/src/thread/pthread_sigqueue.c
deleted file mode 100644
index 9c34793..0000000
--- a/src/thread/pthread_sigqueue.c
+++ /dev/null
@@ -1,22 +0,0 @@
-#include <signal.h>
-#include <string.h>
-#include <unistd.h>
-#include "pthread_impl.h"
-#include "lock.h"
-
-int pthread_sigqueue(pthread_t t, int sig, const union sigval value)
-{
-	siginfo_t si;
-	int r;
-	memset(&si, 0, sizeof si);
-	si.si_signo = sig;
-	si.si_code = SI_QUEUE;
-	si.si_value = value;
-	si.si_uid = getuid();
-	si.si_pid = getpid();
-	LOCK(t->killlock);
-	r = t->tid ? -__syscall(SYS_rt_tgsigqueueinfo, si.si_pid, t->tid, sig, &si)
-		: (sig+0U >= _NSIG ? EINVAL : 0);
-	UNLOCK(t->killlock);
-	return r;
-}
diff --git a/src/thread/synccall.c b/src/thread/synccall.c
index d58c851..a6b177c 100644
--- a/src/thread/synccall.c
+++ b/src/thread/synccall.c
@@ -45,7 +45,7 @@
 {
 	sigset_t oldmask;
 	int cs, i, r;
-	struct sigaction sa = { .sa_flags = SA_RESTART, .sa_handler = handler };
+	struct sigaction sa = { .sa_flags = SA_RESTART | SA_ONSTACK, .sa_handler = handler };
 	pthread_t self = __pthread_self(), td;
 	int count = 0;
 
diff --git a/src/time/__map_file.c b/src/time/__map_file.c
index d3cefa8..c2b29fe 100644
--- a/src/time/__map_file.c
+++ b/src/time/__map_file.c
@@ -2,15 +2,14 @@
 #include <fcntl.h>
 #include <sys/stat.h>
 #include "syscall.h"
-#include "kstat.h"
 
 const char unsigned *__map_file(const char *pathname, size_t *size)
 {
-	struct kstat st;
+	struct stat st;
 	const unsigned char *map = MAP_FAILED;
 	int fd = sys_open(pathname, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
 	if (fd < 0) return 0;
-	if (!syscall(SYS_fstat, fd, &st)) {
+	if (!__fstat(fd, &st)) {
 		map = __mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
 		*size = st.st_size;
 	}
diff --git a/src/time/clock_getcpuclockid.c b/src/time/clock_getcpuclockid.c
index 8a0e2d4..bce1e8a 100644
--- a/src/time/clock_getcpuclockid.c
+++ b/src/time/clock_getcpuclockid.c
@@ -8,6 +8,7 @@
 	struct timespec ts;
 	clockid_t id = (-pid-1)*8U + 2;
 	int ret = __syscall(SYS_clock_getres, id, &ts);
+	if (ret == -EINVAL) ret = -ESRCH;
 	if (ret) return -ret;
 	*clk = id;
 	return 0;
diff --git a/src/time/clock_gettime.c b/src/time/clock_gettime.c
index 3e1d097..4d2ec22 100644
--- a/src/time/clock_gettime.c
+++ b/src/time/clock_gettime.c
@@ -42,6 +42,9 @@
 			p = cgt_time32_wrap;
 		}
 	}
+#ifdef VDSO_CGT_WORKAROUND
+	if (!__vdsosym(VDSO_CGT32_VER, VDSO_CGT32_SYM)) p = 0;
+#endif
 #endif
 	int (*f)(clockid_t, struct timespec *) =
 		(int (*)(clockid_t, struct timespec *))p;
@@ -80,10 +83,12 @@
 		return __syscall_ret(r);
 	long ts32[2];
 	r = __syscall(SYS_clock_gettime, clk, ts32);
+#ifdef SYS_gettimeofday
 	if (r==-ENOSYS && clk==CLOCK_REALTIME) {
 		r = __syscall(SYS_gettimeofday, ts32, 0);
 		ts32[1] *= 1000;
 	}
+#endif
 	if (!r) {
 		ts->tv_sec = ts32[0];
 		ts->tv_nsec = ts32[1];
@@ -92,6 +97,7 @@
 	return __syscall_ret(r);
 #else
 	r = __syscall(SYS_clock_gettime, clk, ts);
+#ifdef SYS_gettimeofday
 	if (r == -ENOSYS) {
 		if (clk == CLOCK_REALTIME) {
 			__syscall(SYS_gettimeofday, ts, 0);
@@ -100,6 +106,7 @@
 		}
 		r = -EINVAL;
 	}
+#endif
 	return __syscall_ret(r);
 #endif
 }
diff --git a/src/time/timer_create.c b/src/time/timer_create.c
index 4bef239..cd32c94 100644
--- a/src/time/timer_create.c
+++ b/src/time/timer_create.c
@@ -43,6 +43,8 @@
 	union sigval val = args->sev->sigev_value;
 
 	pthread_barrier_wait(&args->b);
+	if (self->cancel)
+		return 0;
 	for (;;) {
 		siginfo_t si;
 		while (sigwaitinfo(SIGTIMER_SET, &si) < 0);
@@ -113,8 +115,10 @@
 		ksev.sigev_signo = SIGTIMER;
 		ksev.sigev_notify = SIGEV_THREAD_ID;
 		ksev.sigev_tid = td->tid;
-		if (syscall(SYS_timer_create, clk, &ksev, &timerid) < 0)
+		if (syscall(SYS_timer_create, clk, &ksev, &timerid) < 0) {
 			timerid = -1;
+			td->cancel = 1;
+		}
 		td->timer_id = timerid;
 		pthread_barrier_wait(&args.b);
 		if (timerid < 0) return -1;