Merge "versioner: remove parsing of platforms."
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..2ea4125
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,11 @@
+enh@google.com
+
+cferris@google.com
+danalbert@google.com
+jmgao@google.com
+rprichard@google.com
+tomcherry@google.com
+yabinc@google.com
+
+# Still the best reviewer for changes related to the dynamic linker.
+dimitry@google.com
diff --git a/benchmarks/stdio_benchmark.cpp b/benchmarks/stdio_benchmark.cpp
index d547fed..872725d 100644
--- a/benchmarks/stdio_benchmark.cpp
+++ b/benchmarks/stdio_benchmark.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <err.h>
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdio_ext.h>
 #include <stdlib.h>
@@ -205,3 +206,33 @@
   }
 }
 BIONIC_BENCHMARK(BM_stdio_printf_1$s);
+
+static void BM_stdio_scanf_s(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    char s[BUFSIZ];
+    if (sscanf("file /etc/passwd", "file %s", s) != 1) abort();
+  }
+}
+BIONIC_BENCHMARK(BM_stdio_scanf_s);
+
+static void BM_stdio_scanf_d(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    int i;
+    if (sscanf("size 12345", "size %d", &i) != 1) abort();
+  }
+}
+BIONIC_BENCHMARK(BM_stdio_scanf_d);
+
+static void BM_stdio_scanf_maps(benchmark::State& state) {
+  while (state.KeepRunning()) {
+    uintptr_t start;
+    uintptr_t end;
+    uintptr_t offset;
+    char permissions[5];
+    int name_pos;
+    if (sscanf("6f000000-6f01e000 rwxp 00000000 00:0c 16389419   /system/lib/libcomposer.so",
+               "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n",
+               &start, &end, permissions, &offset, &name_pos) != 4) abort();
+  }
+}
+BIONIC_BENCHMARK(BM_stdio_scanf_maps);
diff --git a/benchmarks/suites/vdso.xml b/benchmarks/suites/vdso.xml
new file mode 100644
index 0000000..1f5958c
--- /dev/null
+++ b/benchmarks/suites/vdso.xml
@@ -0,0 +1,51 @@
+<fn>
+  <name>BM_time_clock_getres</name>
+</fn>
+<fn>
+  <name>BM_time_clock_getres_BOOTTIME</name>
+</fn>
+<fn>
+  <name>BM_time_clock_getres_MONOTONIC_COARSE</name>
+</fn>
+<fn>
+  <name>BM_time_clock_getres_MONOTONIC_RAW</name>
+</fn>
+<fn>
+  <name>BM_time_clock_getres_REALTIME</name>
+</fn>
+<fn>
+  <name>BM_time_clock_getres_REALTIME_COARSE</name>
+</fn>
+<fn>
+  <name>BM_time_clock_getres_syscall</name>
+</fn>
+<fn>
+  <name>BM_time_clock_gettime</name>
+</fn>
+<fn>
+  <name>BM_time_clock_gettime_BOOTTIME</name>
+</fn>
+<fn>
+  <name>BM_time_clock_gettime_MONOTONIC_COARSE</name>
+</fn>
+<fn>
+  <name>BM_time_clock_gettime_MONOTONIC_RAW</name>
+</fn>
+<fn>
+  <name>BM_time_clock_gettime_REALTIME</name>
+</fn>
+<fn>
+  <name>BM_time_clock_gettime_REALTIME_COARSE</name>
+</fn>
+<fn>
+  <name>BM_time_clock_gettime_syscall</name>
+</fn>
+<fn>
+  <name>BM_time_gettimeofday</name>
+</fn>
+<fn>
+  <name>BM_time_gettimeofday_syscall</name>
+</fn>
+<fn>
+  <name>BM_time_time</name>
+</fn>
diff --git a/benchmarks/tests/interface_test.cpp b/benchmarks/tests/interface_test.cpp
index 33e506e..64629e6 100644
--- a/benchmarks/tests/interface_test.cpp
+++ b/benchmarks/tests/interface_test.cpp
@@ -298,6 +298,9 @@
     "BM_stdio_printf_d/iterations:1\n"
     "BM_stdio_printf_literal/iterations:1\n"
     "BM_stdio_printf_s/iterations:1\n"
+    "BM_stdio_scanf_d/iterations:1\n"
+    "BM_stdio_scanf_maps/iterations:1\n"
+    "BM_stdio_scanf_s/iterations:1\n"
     "BM_stdlib_malloc_free/8/iterations:1\n"
     "BM_stdlib_malloc_free/64/iterations:1\n"
     "BM_stdlib_malloc_free/512/iterations:1\n"
@@ -435,7 +438,19 @@
     "BM_string_strstr/32768/0/0/iterations:1\n"
     "BM_string_strstr/65536/0/0/iterations:1\n"
     "BM_string_strstr/131072/0/0/iterations:1\n"
+    "BM_time_clock_getres/iterations:1\n"
+    "BM_time_clock_getres_BOOTTIME/iterations:1\n"
+    "BM_time_clock_getres_MONOTONIC_COARSE/iterations:1\n"
+    "BM_time_clock_getres_MONOTONIC_RAW/iterations:1\n"
+    "BM_time_clock_getres_REALTIME/iterations:1\n"
+    "BM_time_clock_getres_REALTIME_COARSE/iterations:1\n"
+    "BM_time_clock_getres_syscall/iterations:1\n"
     "BM_time_clock_gettime/iterations:1\n"
+    "BM_time_clock_gettime_BOOTTIME/iterations:1\n"
+    "BM_time_clock_gettime_MONOTONIC_COARSE/iterations:1\n"
+    "BM_time_clock_gettime_MONOTONIC_RAW/iterations:1\n"
+    "BM_time_clock_gettime_REALTIME/iterations:1\n"
+    "BM_time_clock_gettime_REALTIME_COARSE/iterations:1\n"
     "BM_time_clock_gettime_syscall/iterations:1\n"
     "BM_time_gettimeofday/iterations:1\n"
     "BM_time_gettimeofday_syscall/iterations:1\n"
diff --git a/benchmarks/time_benchmark.cpp b/benchmarks/time_benchmark.cpp
index 4c6d5dd..437dc78 100644
--- a/benchmarks/time_benchmark.cpp
+++ b/benchmarks/time_benchmark.cpp
@@ -23,6 +23,7 @@
 #include "util.h"
 
 static void BM_time_clock_gettime(benchmark::State& state) {
+  // CLOCK_MONOTONIC is required supported in vdso
   timespec t;
   while (state.KeepRunning()) {
     clock_gettime(CLOCK_MONOTONIC, &t);
@@ -31,6 +32,7 @@
 BIONIC_BENCHMARK(BM_time_clock_gettime);
 
 static void BM_time_clock_gettime_syscall(benchmark::State& state) {
+  // CLOCK_MONOTONIC is required supported in vdso
   timespec t;
   while (state.KeepRunning()) {
     syscall(__NR_clock_gettime, CLOCK_MONOTONIC, &t);
@@ -38,6 +40,114 @@
 }
 BIONIC_BENCHMARK(BM_time_clock_gettime_syscall);
 
+static void BM_time_clock_gettime_MONOTONIC_COARSE(benchmark::State& state) {
+  // CLOCK_MONOTONIC_COARSE is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_gettime(CLOCK_MONOTONIC_COARSE, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_gettime_MONOTONIC_COARSE);
+
+static void BM_time_clock_gettime_MONOTONIC_RAW(benchmark::State& state) {
+  // CLOCK_MONOTONIC_RAW is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_gettime(CLOCK_MONOTONIC_RAW, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_gettime_MONOTONIC_RAW);
+
+static void BM_time_clock_gettime_REALTIME(benchmark::State& state) {
+  // CLOCK_REALTIME is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_gettime(CLOCK_REALTIME, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_gettime_REALTIME);
+
+static void BM_time_clock_gettime_REALTIME_COARSE(benchmark::State& state) {
+  // CLOCK_REALTIME_COARSE is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_gettime(CLOCK_REALTIME_COARSE, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_gettime_REALTIME_COARSE);
+
+static void BM_time_clock_gettime_BOOTTIME(benchmark::State& state) {
+  // CLOCK_BOOTTIME is optionally supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_gettime(CLOCK_BOOTTIME, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_gettime_BOOTTIME);
+
+static void BM_time_clock_getres(benchmark::State& state) {
+  // CLOCK_MONOTONIC is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_getres(CLOCK_MONOTONIC, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_getres);
+
+static void BM_time_clock_getres_syscall(benchmark::State& state) {
+  // CLOCK_MONOTONIC is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    syscall(__NR_clock_getres, CLOCK_MONOTONIC, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_getres_syscall);
+
+static void BM_time_clock_getres_MONOTONIC_COARSE(benchmark::State& state) {
+  // CLOCK_MONOTONIC_COARSE is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_getres(CLOCK_MONOTONIC_COARSE, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_getres_MONOTONIC_COARSE);
+
+static void BM_time_clock_getres_MONOTONIC_RAW(benchmark::State& state) {
+  // CLOCK_MONOTONIC_RAW is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_getres(CLOCK_MONOTONIC_RAW, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_getres_MONOTONIC_RAW);
+
+static void BM_time_clock_getres_REALTIME(benchmark::State& state) {
+  // CLOCK_REALTIME is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_getres(CLOCK_REALTIME, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_getres_REALTIME);
+
+static void BM_time_clock_getres_REALTIME_COARSE(benchmark::State& state) {
+  // CLOCK_REALTIME_COARSE is required supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_getres(CLOCK_REALTIME_COARSE, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_getres_REALTIME_COARSE);
+
+static void BM_time_clock_getres_BOOTTIME(benchmark::State& state) {
+  // CLOCK_BOOTTIME is optionally supported in vdso
+  timespec t;
+  while (state.KeepRunning()) {
+    clock_getres(CLOCK_BOOTTIME, &t);
+  }
+}
+BIONIC_BENCHMARK(BM_time_clock_getres_BOOTTIME);
+
 static void BM_time_gettimeofday(benchmark::State& state) {
   timeval tv;
   while (state.KeepRunning()) {
diff --git a/libc/Android.bp b/libc/Android.bp
index 3ec75ee..52e53e2 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -1402,6 +1402,7 @@
         "bionic/wmempcpy.cpp",
         "system_properties/context_node.cpp",
         "system_properties/contexts_split.cpp",
+        "system_properties/contexts_serialized.cpp",
         "system_properties/prop_area.cpp",
         "system_properties/prop_info.cpp",
         "system_properties/system_properties.cpp",
@@ -1423,6 +1424,7 @@
             cflags: ["-D__ANDROID_TREBLE__"],
         },
     },
+    whole_static_libs: ["libpropertyinfoparser"],
     cppflags: ["-Wold-style-cast"],
     local_include_dirs: ["stdio"],
     include_dirs: ["bionic/libstdc++/include"],
@@ -2036,6 +2038,7 @@
     license: "NOTICE",
 }
 
+// Not actually used in the NDK, but needed to build AOSP for mips.
 ndk_headers {
     name: "libc_asm_mips",
     from: "kernel/uapi/asm-mips",
@@ -2044,6 +2047,7 @@
     license: "NOTICE",
 }
 
+// Not actually used in the NDK, but needed to build AOSP for mips64.
 ndk_headers {
     name: "libc_asm_mips64",
     from: "kernel/uapi/asm-mips",
diff --git a/libc/SYSCALLS.TXT b/libc/SYSCALLS.TXT
index f8be0b0..5c9a284 100644
--- a/libc/SYSCALLS.TXT
+++ b/libc/SYSCALLS.TXT
@@ -212,7 +212,6 @@
 clock_t       times(struct tms*)       all
 int           nanosleep(const struct timespec*, struct timespec*)   all
 int           clock_settime(clockid_t, const struct timespec*)  all
-int           clock_getres(clockid_t, struct timespec*)   all
 int           ___clock_nanosleep:clock_nanosleep(clockid_t, int, const struct timespec*, struct timespec*)  all
 int           getitimer(int, const struct itimerval*)   all
 int           setitimer(int, const struct itimerval*, struct itimerval*)  all
@@ -351,6 +350,7 @@
 int     __set_thread_area:set_thread_area(void*) x86
 
 # vdso stuff.
+int __clock_getres:clock_getres(clockid_t, struct timespec*) all
 int __clock_gettime:clock_gettime(clockid_t, timespec*) all
 int __gettimeofday:gettimeofday(timeval*, timezone*) all
 
diff --git a/libc/arch-arm/syscalls/clock_getres.S b/libc/arch-arm/syscalls/__clock_getres.S
similarity index 88%
rename from libc/arch-arm/syscalls/clock_getres.S
rename to libc/arch-arm/syscalls/__clock_getres.S
index e101127..439b5b8 100644
--- a/libc/arch-arm/syscalls/clock_getres.S
+++ b/libc/arch-arm/syscalls/__clock_getres.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(clock_getres)
+ENTRY(__clock_getres)
     mov     ip, r7
     .cfi_register r7, ip
     ldr     r7, =__NR_clock_getres
@@ -13,4 +13,4 @@
     bxls    lr
     neg     r0, r0
     b       __set_errno_internal
-END(clock_getres)
+END(__clock_getres)
diff --git a/libc/arch-arm64/syscalls/clock_getres.S b/libc/arch-arm64/syscalls/__clock_getres.S
similarity index 78%
rename from libc/arch-arm64/syscalls/clock_getres.S
rename to libc/arch-arm64/syscalls/__clock_getres.S
index 33fda8f..9d0deaa 100644
--- a/libc/arch-arm64/syscalls/clock_getres.S
+++ b/libc/arch-arm64/syscalls/__clock_getres.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(clock_getres)
+ENTRY(__clock_getres)
     mov     x8, __NR_clock_getres
     svc     #0
 
@@ -11,4 +11,5 @@
     b.hi    __set_errno_internal
 
     ret
-END(clock_getres)
+END(__clock_getres)
+.hidden __clock_getres
diff --git a/libc/arch-mips/syscalls/clock_getres.S b/libc/arch-mips/syscalls/__clock_getres.S
similarity index 87%
rename from libc/arch-mips/syscalls/clock_getres.S
rename to libc/arch-mips/syscalls/__clock_getres.S
index 153f629..d4e67c5 100644
--- a/libc/arch-mips/syscalls/clock_getres.S
+++ b/libc/arch-mips/syscalls/__clock_getres.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(clock_getres)
+ENTRY(__clock_getres)
     .set noreorder
     .cpload $t9
     li $v0, __NR_clock_getres
@@ -16,4 +16,4 @@
     j $t9
     nop
     .set reorder
-END(clock_getres)
+END(__clock_getres)
diff --git a/libc/arch-mips64/syscalls/clock_getres.S b/libc/arch-mips64/syscalls/__clock_getres.S
similarity index 85%
rename from libc/arch-mips64/syscalls/clock_getres.S
rename to libc/arch-mips64/syscalls/__clock_getres.S
index 32fac1a..fb871c9 100644
--- a/libc/arch-mips64/syscalls/clock_getres.S
+++ b/libc/arch-mips64/syscalls/__clock_getres.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(clock_getres)
+ENTRY(__clock_getres)
     .set push
     .set noreorder
     li $v0, __NR_clock_getres
@@ -22,4 +22,5 @@
     j $t9
     move $ra, $t0
     .set pop
-END(clock_getres)
+END(__clock_getres)
+.hidden __clock_getres
diff --git a/libc/arch-x86/syscalls/clock_getres.S b/libc/arch-x86/syscalls/__clock_getres.S
similarity index 93%
rename from libc/arch-x86/syscalls/clock_getres.S
rename to libc/arch-x86/syscalls/__clock_getres.S
index 9466e08..77b0aa9 100644
--- a/libc/arch-x86/syscalls/clock_getres.S
+++ b/libc/arch-x86/syscalls/__clock_getres.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(clock_getres)
+ENTRY(__clock_getres)
     pushl   %ebx
     .cfi_def_cfa_offset 8
     .cfi_rel_offset ebx, 0
@@ -31,4 +31,4 @@
     popl    %ecx
     popl    %ebx
     ret
-END(clock_getres)
+END(__clock_getres)
diff --git a/libc/arch-x86_64/syscalls/clock_getres.S b/libc/arch-x86_64/syscalls/__clock_getres.S
similarity index 80%
rename from libc/arch-x86_64/syscalls/clock_getres.S
rename to libc/arch-x86_64/syscalls/__clock_getres.S
index f65d127..aff974e 100644
--- a/libc/arch-x86_64/syscalls/clock_getres.S
+++ b/libc/arch-x86_64/syscalls/__clock_getres.S
@@ -2,7 +2,7 @@
 
 #include <private/bionic_asm.h>
 
-ENTRY(clock_getres)
+ENTRY(__clock_getres)
     movl    $__NR_clock_getres, %eax
     syscall
     cmpq    $-MAX_ERRNO, %rax
@@ -12,4 +12,5 @@
     call    __set_errno_internal
 1:
     ret
-END(clock_getres)
+END(__clock_getres)
+.hidden __clock_getres
diff --git a/libc/bionic/vdso.cpp b/libc/bionic/vdso.cpp
index 969c39f..00d6490 100644
--- a/libc/bionic/vdso.cpp
+++ b/libc/bionic/vdso.cpp
@@ -42,6 +42,15 @@
   return __clock_gettime(clock_id, tp);
 }
 
+int clock_getres(int clock_id, timespec* tp) {
+  auto vdso_clock_getres = reinterpret_cast<decltype(&clock_getres)>(
+    __libc_globals->vdso[VDSO_CLOCK_GETRES].fn);
+  if (__predict_true(vdso_clock_getres)) {
+    return vdso_return(vdso_clock_getres(clock_id, tp));
+  }
+  return __clock_getres(clock_id, tp);
+}
+
 int gettimeofday(timeval* tv, struct timezone* tz) {
   auto vdso_gettimeofday = reinterpret_cast<decltype(&gettimeofday)>(
     __libc_globals->vdso[VDSO_GETTIMEOFDAY].fn);
@@ -54,6 +63,7 @@
 void __libc_init_vdso(libc_globals* globals, KernelArgumentBlock& args) {
   auto&& vdso = globals->vdso;
   vdso[VDSO_CLOCK_GETTIME] = { VDSO_CLOCK_GETTIME_SYMBOL, nullptr };
+  vdso[VDSO_CLOCK_GETRES] = { VDSO_CLOCK_GETRES_SYMBOL, nullptr };
   vdso[VDSO_GETTIMEOFDAY] = { VDSO_GETTIMEOFDAY_SYMBOL, nullptr };
 
   // Do we have a vdso?
diff --git a/libc/private/bionic_vdso.h b/libc/private/bionic_vdso.h
index 2d11cd6..ed11501 100644
--- a/libc/private/bionic_vdso.h
+++ b/libc/private/bionic_vdso.h
@@ -33,13 +33,16 @@
 
 #if defined(__aarch64__)
 #define VDSO_CLOCK_GETTIME_SYMBOL "__kernel_clock_gettime"
+#define VDSO_CLOCK_GETRES_SYMBOL  "__kernel_clock_getres"
 #define VDSO_GETTIMEOFDAY_SYMBOL  "__kernel_gettimeofday"
 #else
 #define VDSO_CLOCK_GETTIME_SYMBOL "__vdso_clock_gettime"
+#define VDSO_CLOCK_GETRES_SYMBOL  "__vdso_clock_getres"
 #define VDSO_GETTIMEOFDAY_SYMBOL  "__vdso_gettimeofday"
 #endif
 
 extern "C" int __clock_gettime(int, timespec*);
+extern "C" int __clock_getres(int, timespec*);
 extern "C" int __gettimeofday(timeval*, struct timezone*);
 
 struct vdso_entry {
@@ -49,6 +52,7 @@
 
 enum {
   VDSO_CLOCK_GETTIME = 0,
+  VDSO_CLOCK_GETRES,
   VDSO_GETTIMEOFDAY,
   VDSO_END
 };
diff --git a/libc/stdio/vfscanf.c b/libc/stdio/vfscanf.c
index 853e0cf..c9e4385 100644
--- a/libc/stdio/vfscanf.c
+++ b/libc/stdio/vfscanf.c
@@ -83,9 +83,6 @@
 #define CT_INT 3    /* integer, i.e., strtoimax or strtoumax */
 #define CT_FLOAT 4  /* floating, i.e., strtod */
 
-#define u_char unsigned char
-#define u_long unsigned long
-
 static u_char* __sccl(char*, u_char*);
 
 /*
@@ -192,9 +189,6 @@
       /*
        * Conversions.
        * Those marked `compat' are for 4.[123]BSD compatibility.
-       *
-       * (According to ANSI, E and X formats are supposed
-       * to the same as e and x.  Sorry about that.)
        */
       case 'D': /* compat */
         flags |= LONG;
@@ -330,10 +324,7 @@
         /* scan arbitrary characters (sets NOSKIP) */
         if (width == 0) width = 1;
         if (flags & LONG) {
-          if ((flags & SUPPRESS) == 0)
-            wcp = va_arg(ap, wchar_t*);
-          else
-            wcp = NULL;
+          wcp = ((flags & SUPPRESS) == 0) ? va_arg(ap, wchar_t*) : NULL;
           n = 0;
           while (width != 0) {
             if (n == (int)MB_CUR_MAX) {
@@ -394,20 +385,17 @@
         break;
 
       case CT_CCL:
-        /* scan a (nonempty) character class (sets NOSKIP) */
-        if (width == 0) width = (size_t)~0; /* `infinity' */
-        /* take only those things in the class */
+      case CT_STRING:
+        // CT_CCL: scan a (nonempty) character class (sets NOSKIP).
+        // CT_STRING: like CCL, but zero-length string OK, & no NOSKIP.
+        if (width == 0) width = (size_t)~0; // 'infinity'.
         if (flags & LONG) {
           wchar_t twc;
-          int nchars;
+          int nchars = 0;
 
-          if ((flags & SUPPRESS) == 0)
-            wcp = va_arg(ap, wchar_t*);
-          else
-            wcp = &twc;
+          wcp = (flags & SUPPRESS) == 0 ? va_arg(ap, wchar_t*) : &twc;
           n = 0;
-          nchars = 0;
-          while (width != 0) {
+          while ((c == CT_CCL || !isspace(*fp->_p)) && width != 0) {
             if (n == (int)MB_CUR_MAX) {
               fp->_flags |= __SERR;
               goto input_failure;
@@ -423,7 +411,7 @@
             }
             if (nconv == 0) *wcp = L'\0';
             if (nconv != (size_t)-2) {
-              if (wctob(*wcp) != EOF && !ccltab[wctob(*wcp)]) {
+              if ((c == CT_CCL && wctob(*wcp) != EOF && !ccltab[wctob(*wcp)]) || (c == CT_STRING && iswspace(*wcp))) {
                 while (n != 0) {
                   n--;
                   ungetc(buf[n], fp);
@@ -444,121 +432,46 @@
               break;
             }
           }
-          if (n != 0) {
+          if (c == CT_CCL && n != 0) {
             fp->_flags |= __SERR;
             goto input_failure;
           }
           n = nchars;
-          if (n == 0) goto match_failure;
-          if (!(flags & SUPPRESS)) {
-            *wcp = L'\0';
-            nassigned++;
-          }
-        } else
-            /* take only those things in the class */
-            if (flags & SUPPRESS) {
+        } else if (flags & SUPPRESS) {
           n = 0;
-          while (ccltab[*fp->_p]) {
+          while ((c == CT_CCL && ccltab[*fp->_p]) || (c == CT_STRING && !isspace(*fp->_p))) {
             n++, fp->_r--, fp->_p++;
             if (--width == 0) break;
             if (fp->_r <= 0 && __srefill(fp)) {
-              if (n == 0) goto input_failure;
+              if (c == CT_CCL && n == 0) goto input_failure;
               break;
             }
           }
-          if (n == 0) goto match_failure;
         } else {
           p0 = p = va_arg(ap, char*);
-          while (ccltab[*fp->_p]) {
+          while ((c == CT_CCL && ccltab[*fp->_p]) || (c == CT_STRING && !isspace(*fp->_p))) {
             fp->_r--;
             *p++ = *fp->_p++;
             if (--width == 0) break;
             if (fp->_r <= 0 && __srefill(fp)) {
-              if (p == p0) goto input_failure;
+              if (c == CT_CCL && p == p0) goto input_failure;
               break;
             }
           }
           n = p - p0;
-          if (n == 0) goto match_failure;
-          *p = '\0';
-          nassigned++;
+        }
+        if (c == CT_CCL && n == 0) goto match_failure;
+        if (!(flags & SUPPRESS)) {
+          if (flags & LONG) {
+            *wcp = L'\0';
+          } else {
+            *p = '\0';
+          }
+          ++nassigned;
         }
         nread += n;
         break;
 
-      case CT_STRING:
-        /* like CCL, but zero-length string OK, & no NOSKIP */
-        if (width == 0) width = (size_t)~0;
-        if (flags & LONG) {
-          wchar_t twc;
-
-          if ((flags & SUPPRESS) == 0)
-            wcp = va_arg(ap, wchar_t*);
-          else
-            wcp = &twc;
-          n = 0;
-          while (!isspace(*fp->_p) && width != 0) {
-            if (n == (int)MB_CUR_MAX) {
-              fp->_flags |= __SERR;
-              goto input_failure;
-            }
-            buf[n++] = *fp->_p;
-            fp->_p++;
-            fp->_r--;
-            memset(&mbs, 0, sizeof(mbs));
-            nconv = mbrtowc(wcp, buf, n, &mbs);
-            if (nconv == (size_t)-1) {
-              fp->_flags |= __SERR;
-              goto input_failure;
-            }
-            if (nconv == 0) *wcp = L'\0';
-            if (nconv != (size_t)-2) {
-              if (iswspace(*wcp)) {
-                while (n != 0) {
-                  n--;
-                  ungetc(buf[n], fp);
-                }
-                break;
-              }
-              nread += n;
-              width--;
-              if (!(flags & SUPPRESS)) wcp++;
-              n = 0;
-            }
-            if (fp->_r <= 0 && __srefill(fp)) {
-              if (n != 0) {
-                fp->_flags |= __SERR;
-                goto input_failure;
-              }
-              break;
-            }
-          }
-          if (!(flags & SUPPRESS)) {
-            *wcp = L'\0';
-            nassigned++;
-          }
-        } else if (flags & SUPPRESS) {
-          n = 0;
-          while (!isspace(*fp->_p)) {
-            n++, fp->_r--, fp->_p++;
-            if (--width == 0) break;
-            if (fp->_r <= 0 && __srefill(fp)) break;
-          }
-          nread += n;
-        } else {
-          p0 = p = va_arg(ap, char*);
-          while (!isspace(*fp->_p)) {
-            fp->_r--;
-            *p++ = *fp->_p++;
-            if (--width == 0) break;
-            if (fp->_r <= 0 && __srefill(fp)) break;
-          }
-          *p = '\0';
-          nread += p - p0;
-          nassigned++;
-        }
-        continue;
-
       case CT_INT:
         /* scan an integer as if by strtoimax/strtoumax */
 #ifdef hardway
diff --git a/libc/stdio/vfwscanf.c b/libc/stdio/vfwscanf.c
index 206f4a2..71cd49b 100644
--- a/libc/stdio/vfwscanf.c
+++ b/libc/stdio/vfwscanf.c
@@ -84,12 +84,43 @@
 #define CT_INT 3    /* integer, i.e., strtoimax or strtoumax */
 #define CT_FLOAT 4  /* floating, i.e., strtod */
 
-#define u_char unsigned char
-#define u_long unsigned long
+// An interpretive version of __sccl from vfscanf.c --- a table of all wchar_t values would
+// be a little too expensive, and some kind of compressed version isn't worth the trouble.
+static inline bool in_ccl(wchar_t wc, const wchar_t* ccl) {
+  // Is this a negated set?
+  bool member_result = true;
+  if (*ccl == '^') {
+    member_result = false;
+    ++ccl;
+  }
 
-#define INCCL(_c)                                        \
-  (cclcompl ? (wmemchr(ccls, (_c), ccle - ccls) == NULL) \
-            : (wmemchr(ccls, (_c), ccle - ccls) != NULL))
+  // The first character may be ']' or '-' without being special.
+  if (*ccl == '-' || *ccl == ']') {
+    // A literal match?
+    if (*ccl == wc) return member_result;
+    ++ccl;
+  }
+
+  while (*ccl && *ccl != ']') {
+    // The last character may be '-' without being special.
+    if (*ccl == '-' && ccl[1] != '\0' && ccl[1] != ']') {
+      wchar_t first = *(ccl - 1);
+      wchar_t last = *(ccl + 1);
+      if (first <= last) {
+        // In the range?
+        if (wc >= first && wc <= last) return member_result;
+        ccl += 2;
+        continue;
+      }
+      // A '-' is not considered to be part of a range if the character after
+      // is not greater than the character before, so fall through...
+    }
+    // A literal match?
+    if (*ccl == wc) return member_result;
+    ++ccl;
+  }
+  return !member_result;
+}
 
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wframe-larger-than="
@@ -109,9 +140,7 @@
   int nread;              /* number of characters consumed from fp */
   int base;               /* base argument to strtoimax/strtouimax */
   wchar_t buf[BUF];       /* buffer for numeric conversions */
-  const wchar_t* ccls;    /* character class start */
-  const wchar_t* ccle;    /* character class end */
-  int cclcompl;           /* ccl is complemented? */
+  const wchar_t* ccl;
   wint_t wi;              /* handy wint_t */
   char* mbp;              /* multibyte string pointer for %c %s %[ */
   size_t nconv;           /* number of bytes in mb. conversion */
@@ -127,7 +156,6 @@
   nconversions = 0;
   nread = 0;
   base = 0; /* XXX just to keep gcc happy */
-  ccls = ccle = NULL;
   for (;;) {
     c = *fmt++;
     if (c == 0) {
@@ -154,7 +182,7 @@
         if ((wi = __fgetwc_unlock(fp)) == WEOF) goto input_failure;
         if (wi != c) {
           __ungetwc(wi, fp);
-          goto input_failure;
+          goto match_failure;
         }
         nread++;
         continue;
@@ -210,9 +238,6 @@
       /*
        * Conversions.
        * Those marked `compat' are for 4.[123]BSD compatibility.
-       *
-       * (According to ANSI, E and X formats are supposed
-       * to the same as e and x.  Sorry about that.)
        */
       case 'D': /* compat */
         flags |= LONG;
@@ -266,15 +291,10 @@
         break;
 
       case '[':
-        ccls = fmt;
-        if (*fmt == '^') {
-          cclcompl = 1;
-          fmt++;
-        } else
-          cclcompl = 0;
+        ccl = fmt;
+        if (*fmt == '^') fmt++;
         if (*fmt == ']') fmt++;
         while (*fmt != '\0' && *fmt != ']') fmt++;
-        ccle = fmt;
         fmt++;
         flags |= NOSKIP;
         c = CT_CCL;
@@ -382,28 +402,26 @@
         break;
 
       case CT_CCL:
-        /* scan a (nonempty) character class (sets NOSKIP) */
-        if (width == 0) width = (size_t)~0; /* `infinity' */
-        /* take only those things in the class */
+      case CT_STRING:
+        // CT_CCL: scan a (nonempty) character class (sets NOSKIP).
+        // CT_STRING: like CCL, but zero-length string OK, & no NOSKIP.
+        if (width == 0) width = (size_t)~0; // 'infinity'.
         if ((flags & SUPPRESS) && (flags & LONG)) {
           n = 0;
-          while ((wi = __fgetwc_unlock(fp)) != WEOF && width-- != 0 && INCCL(wi)) n++;
+          while ((wi = __fgetwc_unlock(fp)) != WEOF && width-- != 0 && ((c == CT_CCL && in_ccl(wi, ccl)) || (c == CT_STRING && !iswspace(wi)))) n++;
           if (wi != WEOF) __ungetwc(wi, fp);
-          if (n == 0) goto match_failure;
         } else if (flags & LONG) {
           p0 = p = va_arg(ap, wchar_t*);
-          while ((wi = __fgetwc_unlock(fp)) != WEOF && width-- != 0 && INCCL(wi))
+          while ((wi = __fgetwc_unlock(fp)) != WEOF && width-- != 0 && ((c == CT_CCL && in_ccl(wi, ccl)) || (c == CT_STRING && !iswspace(wi)))) {
             *p++ = (wchar_t)wi;
+          }
           if (wi != WEOF) __ungetwc(wi, fp);
           n = p - p0;
-          if (n == 0) goto match_failure;
-          *p = 0;
-          nassigned++;
         } else {
           if (!(flags & SUPPRESS)) mbp = va_arg(ap, char*);
           n = 0;
           memset(&mbs, 0, sizeof(mbs));
-          while ((wi = __fgetwc_unlock(fp)) != WEOF && width != 0 && INCCL(wi)) {
+          while ((wi = __fgetwc_unlock(fp)) != WEOF && width != 0 && ((c == CT_CCL && in_ccl(wi, ccl)) || (c == CT_STRING && !iswspace(wi)))) {
             if (width >= MB_CUR_MAX && !(flags & SUPPRESS)) {
               nconv = wcrtomb(mbp, wi, &mbs);
               if (nconv == (size_t)-1) goto input_failure;
@@ -418,56 +436,20 @@
             n++;
           }
           if (wi != WEOF) __ungetwc(wi, fp);
-          if (!(flags & SUPPRESS)) {
-            *mbp = 0;
-            nassigned++;
+        }
+        if (c == CT_CCL && n == 0) goto match_failure;
+        if (!(flags & SUPPRESS)) {
+          if (flags & LONG) {
+            *p = L'\0';
+          } else {
+            *mbp = '\0';
           }
+          ++nassigned;
         }
         nread += n;
         nconversions++;
         break;
 
-      case CT_STRING:
-        /* like CCL, but zero-length string OK, & no NOSKIP */
-        if (width == 0) width = (size_t)~0;
-        if ((flags & SUPPRESS) && (flags & LONG)) {
-          while ((wi = __fgetwc_unlock(fp)) != WEOF && width-- != 0 && !iswspace(wi)) nread++;
-          if (wi != WEOF) __ungetwc(wi, fp);
-        } else if (flags & LONG) {
-          p0 = p = va_arg(ap, wchar_t*);
-          while ((wi = __fgetwc_unlock(fp)) != WEOF && width-- != 0 && !iswspace(wi)) {
-            *p++ = (wchar_t)wi;
-            nread++;
-          }
-          if (wi != WEOF) __ungetwc(wi, fp);
-          *p = 0;
-          nassigned++;
-        } else {
-          if (!(flags & SUPPRESS)) mbp = va_arg(ap, char*);
-          memset(&mbs, 0, sizeof(mbs));
-          while ((wi = __fgetwc_unlock(fp)) != WEOF && width != 0 && !iswspace(wi)) {
-            if (width >= MB_CUR_MAX && !(flags & SUPPRESS)) {
-              nconv = wcrtomb(mbp, wi, &mbs);
-              if (nconv == (size_t)-1) goto input_failure;
-            } else {
-              nconv = wcrtomb(mbbuf, wi, &mbs);
-              if (nconv == (size_t)-1) goto input_failure;
-              if (nconv > width) break;
-              if (!(flags & SUPPRESS)) memcpy(mbp, mbbuf, nconv);
-            }
-            if (!(flags & SUPPRESS)) mbp += nconv;
-            width -= nconv;
-            nread++;
-          }
-          if (wi != WEOF) __ungetwc(wi, fp);
-          if (!(flags & SUPPRESS)) {
-            *mbp = 0;
-            nassigned++;
-          }
-        }
-        nconversions++;
-        continue;
-
       case CT_INT:
         /* scan an integer as if by strtoimax/strtoumax */
         if (width == 0 || width > sizeof(buf) / sizeof(*buf) - 1)
diff --git a/libc/system_properties/context_node.h b/libc/system_properties/context_node.h
index 1c6cbbb..769b853 100644
--- a/libc/system_properties/context_node.h
+++ b/libc/system_properties/context_node.h
@@ -41,9 +41,16 @@
   ~ContextNode() {
     Unmap();
   }
+
+  ContextNode(const ContextNode&) = delete;
+  ContextNode(ContextNode&&) = delete;
+  void operator=(const ContextNode&) = delete;
+  void operator=(const ContextNode&&) = delete;
+
   bool Open(bool access_rw, bool* fsetxattr_failed);
   bool CheckAccessAndOpen();
   void ResetAccess();
+  void Unmap();
 
   const char* context() const {
     return context_;
@@ -54,7 +61,6 @@
 
  private:
   bool CheckAccess();
-  void Unmap();
 
   Lock lock_;
   const char* context_;
diff --git a/libc/system_properties/contexts_serialized.cpp b/libc/system_properties/contexts_serialized.cpp
new file mode 100644
index 0000000..117b5cf
--- /dev/null
+++ b/libc/system_properties/contexts_serialized.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "contexts_serialized.h"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <new>
+
+#include <async_safe/log.h>
+
+#include "private/bionic_prctl.h"
+#include "property_filename.h"
+
+bool ContextsSerialized::InitializeContextNodes() {
+  auto num_context_nodes = property_info_area_file_->num_contexts();
+  auto context_nodes_mmap_size = sizeof(ContextNode) * num_context_nodes;
+  // We want to avoid malloc in system properties, so we take an anonymous map instead (b/31659220).
+  void* const map_result = mmap(nullptr, context_nodes_mmap_size, PROT_READ | PROT_WRITE,
+                                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (map_result == MAP_FAILED) {
+    return false;
+  }
+
+  prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, map_result, context_nodes_mmap_size,
+        "System property context nodes");
+
+  context_nodes_ = reinterpret_cast<ContextNode*>(map_result);
+  num_context_nodes_ = num_context_nodes;
+  context_nodes_mmap_size_ = context_nodes_mmap_size;
+
+  for (size_t i = 0; i < num_context_nodes; ++i) {
+    new (&context_nodes_[i]) ContextNode(property_info_area_file_->context(i));
+  }
+
+  return true;
+}
+
+bool ContextsSerialized::MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed) {
+  char filename[PROP_FILENAME_MAX];
+  int len = async_safe_format_buffer(filename, sizeof(filename), "%s/properties_serial",
+                                     property_filename);
+  if (len < 0 || len > PROP_FILENAME_MAX) {
+    serial_prop_area_ = nullptr;
+    return false;
+  }
+
+  if (access_rw) {
+    serial_prop_area_ =
+        prop_area::map_prop_area_rw(filename, "u:object_r:properties_serial:s0", fsetxattr_failed);
+  } else {
+    serial_prop_area_ = prop_area::map_prop_area(filename);
+  }
+  return serial_prop_area_;
+}
+
+bool ContextsSerialized::InitializeProperties() {
+  if (!property_info_area_file_.LoadDefaultPath()) {
+    return false;
+  }
+
+  if (!InitializeContextNodes()) {
+    FreeAndUnmap();
+    return false;
+  }
+
+  return true;
+}
+
+bool ContextsSerialized::Initialize(bool writable) {
+  if (!InitializeProperties()) {
+    return false;
+  }
+
+  if (writable) {
+    mkdir(property_filename, S_IRWXU | S_IXGRP | S_IXOTH);
+    bool open_failed = false;
+    bool fsetxattr_failed = false;
+
+    for (size_t i = 0; i < num_context_nodes_; ++i) {
+      if (!context_nodes_[i].Open(true, &fsetxattr_failed)) {
+        open_failed = true;
+      }
+    }
+    if (open_failed || !MapSerialPropertyArea(true, &fsetxattr_failed)) {
+      FreeAndUnmap();
+      return false;
+    }
+
+    return !fsetxattr_failed;
+  } else {
+    if (!MapSerialPropertyArea(false, nullptr)) {
+      FreeAndUnmap();
+      return false;
+    }
+  }
+  return true;
+}
+
+prop_area* ContextsSerialized::GetPropAreaForName(const char* name) {
+  uint32_t index;
+  property_info_area_file_->GetPropertyInfoIndexes(name, &index, nullptr);
+  if (index == ~0u || index >= num_context_nodes_) {
+    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Could not find context for property \"%s\"",
+                          name);
+    return nullptr;
+  }
+  auto* context_node = &context_nodes_[index];
+  if (!context_node->pa()) {
+    // We explicitly do not check no_access_ in this case because unlike the
+    // case of foreach(), we want to generate an selinux audit for each
+    // non-permitted property access in this function.
+    context_node->Open(false, nullptr);
+  }
+  return context_node->pa();
+}
+
+void ContextsSerialized::ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) {
+  for (size_t i = 0; i < num_context_nodes_; ++i) {
+    if (context_nodes_[i].CheckAccessAndOpen()) {
+      context_nodes_[i].pa()->foreach (propfn, cookie);
+    }
+  }
+}
+
+void ContextsSerialized::ResetAccess() {
+  for (size_t i = 0; i < num_context_nodes_; ++i) {
+    context_nodes_[i].ResetAccess();
+  }
+}
+
+void ContextsSerialized::FreeAndUnmap() {
+  property_info_area_file_.Reset();
+  if (context_nodes_ != nullptr) {
+    for (size_t i = 0; i < num_context_nodes_; ++i) {
+      context_nodes_[i].Unmap();
+    }
+    munmap(context_nodes_, context_nodes_mmap_size_);
+    context_nodes_ = nullptr;
+  }
+  prop_area::unmap_prop_area(&serial_prop_area_);
+  serial_prop_area_ = nullptr;
+}
diff --git a/libc/system_properties/contexts_serialized.h b/libc/system_properties/contexts_serialized.h
new file mode 100644
index 0000000..52474c3
--- /dev/null
+++ b/libc/system_properties/contexts_serialized.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef SYSTEM_PROPERTIES_CONTEXTS_SERIALIZED_H
+#define SYSTEM_PROPERTIES_CONTEXTS_SERIALIZED_H
+
+#include <property_info_parser/property_info_parser.h>
+
+#include "context_node.h"
+#include "contexts.h"
+
+class ContextsSerialized : public Contexts {
+ public:
+  virtual ~ContextsSerialized() override {
+  }
+
+  virtual bool Initialize(bool writable) override;
+  virtual prop_area* GetPropAreaForName(const char* name) override;
+  virtual prop_area* GetSerialPropArea() override {
+    return serial_prop_area_;
+  }
+  virtual void ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) override;
+  virtual void ResetAccess() override;
+  virtual void FreeAndUnmap() override;
+
+ private:
+  bool InitializeContextNodes();
+  bool InitializeProperties();
+  bool MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed);
+
+  android::properties::PropertyInfoAreaFile property_info_area_file_;
+  ContextNode* context_nodes_ = nullptr;
+  size_t num_context_nodes_ = 0;
+  size_t context_nodes_mmap_size_ = 0;
+  prop_area* serial_prop_area_ = nullptr;
+};
+
+#endif
diff --git a/libc/system_properties/prop_area.cpp b/libc/system_properties/prop_area.cpp
index e11f292..032fa4e 100644
--- a/libc/system_properties/prop_area.cpp
+++ b/libc/system_properties/prop_area.cpp
@@ -26,6 +26,7 @@
  * SUCH DAMAGE.
  */
 
+#include <errno.h>
 #include <fcntl.h>
 #include <stdlib.h>
 #include <sys/cdefs.h>
diff --git a/libc/system_properties/system_properties.cpp b/libc/system_properties/system_properties.cpp
index 89a6fe7..93184a3 100644
--- a/libc/system_properties/system_properties.cpp
+++ b/libc/system_properties/system_properties.cpp
@@ -59,6 +59,7 @@
 #include "context_node.h"
 #include "contexts.h"
 #include "contexts_pre_split.h"
+#include "contexts_serialized.h"
 #include "contexts_split.h"
 #include "prop_area.h"
 #include "prop_info.h"
@@ -71,6 +72,7 @@
 static union ContextsUnion {
   ContextsUnion() {}
   ~ContextsUnion() {}
+  ContextsSerialized contexts_serialized;
   ContextsSplit contexts_split;
   ContextsPreSplit contexts_pre_split;
 } contexts_union;
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 7bf3a5b..4ad44fa 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -328,9 +328,7 @@
   TRACE("name %s: freeing soinfo @ %p", si->get_realpath(), si);
 
   if (!solist_remove_soinfo(si)) {
-    // TODO (dimitry): revisit this - for now preserving the logic
-    // but it does not look right, abort if soinfo is not in the list instead?
-    return;
+    async_safe_fatal("soinfo=%p is not in soinfo_list (double unload?)", si);
   }
 
   // clear links to/from si
@@ -718,13 +716,11 @@
 // walk_dependencies_tree returns false if walk was terminated
 // by the action and true otherwise.
 template<typename F>
-static bool walk_dependencies_tree(soinfo* root_soinfos[], size_t root_soinfos_size, F action) {
+static bool walk_dependencies_tree(soinfo* root_soinfo, F action) {
   SoinfoLinkedList visit_list;
   SoinfoLinkedList visited;
 
-  for (size_t i = 0; i < root_soinfos_size; ++i) {
-    visit_list.push_back(root_soinfos[i]);
-  }
+  visit_list.push_back(root_soinfo);
 
   soinfo* si;
   while ((si = visit_list.pop_front()) != nullptr) {
@@ -760,7 +756,7 @@
   const ElfW(Sym)* result = nullptr;
   bool skip_lookup = skip_until != nullptr;
 
-  walk_dependencies_tree(&root, 1, [&](soinfo* current_soinfo) {
+  walk_dependencies_tree(root, [&](soinfo* current_soinfo) {
     if (skip_lookup) {
       skip_lookup = current_soinfo != skip_until;
       return kWalkContinue;
@@ -1163,6 +1159,27 @@
   return *candidate != nullptr;
 }
 
+static bool find_loaded_library_by_realpath(android_namespace_t* ns, const char* realpath,
+                                            bool search_linked_namespaces, soinfo** candidate) {
+  auto predicate = [&](soinfo* si) { return strcmp(realpath, si->get_realpath()) == 0; };
+
+  *candidate = ns->soinfo_list().find_if(predicate);
+
+  if (*candidate == nullptr && search_linked_namespaces) {
+    for (auto& link : ns->linked_namespaces()) {
+      android_namespace_t* linked_ns = link.linked_namespace();
+      soinfo* si = linked_ns->soinfo_list().find_if(predicate);
+
+      if (si != nullptr && link.is_accessible(si->get_soname())) {
+        *candidate = si;
+        return true;
+      }
+    }
+  }
+
+  return *candidate != nullptr;
+}
+
 static bool load_library(android_namespace_t* ns,
                          LoadTask* task,
                          LoadTaskList* load_tasks,
@@ -1457,7 +1474,6 @@
           if (load_library(linked_namespace.linked_namespace(), task, zip_archive_cache, load_tasks, rtld_flags, false)) {
             return true;
           }
-          // lib was not found in the namespace. Try next linked namespace.
         } else {
           // lib is already loaded
           return true;
@@ -1470,7 +1486,6 @@
 }
 
 static void soinfo_unload(soinfo* si);
-static void soinfo_unload(soinfo* soinfos[], size_t count);
 
 static void shuffle(std::vector<LoadTask*>* v) {
   for (size_t i = 0, size = v->size(); i < size; ++i) {
@@ -1495,9 +1510,9 @@
                     const android_dlextinfo* extinfo,
                     bool add_as_children,
                     bool search_linked_namespaces,
-                    std::unordered_map<const soinfo*, ElfReader>& readers_map,
                     std::vector<android_namespace_t*>* namespaces) {
   // Step 0: prepare.
+  std::unordered_map<const soinfo*, ElfReader> readers_map;
   LoadTaskList load_tasks;
 
   for (size_t i = 0; i < library_names_count; ++i) {
@@ -1527,11 +1542,6 @@
     }
   });
 
-  auto failure_guard = android::base::make_scope_guard([&]() {
-    // Housekeeping
-    soinfo_unload(soinfos, soinfos_count);
-  });
-
   ZipArchiveCache zip_archive_cache;
 
   // Step 1: expand the list of load_tasks to include
@@ -1544,7 +1554,6 @@
     task->set_extinfo(is_dt_needed ? nullptr : extinfo);
     task->set_dt_needed(is_dt_needed);
 
-    // try to find the load.
     // Note: start from the namespace that is stored in the LoadTask. This namespace
     // is different from the current namespace when the LoadTask is for a transitive
     // dependency and the lib that created the LoadTask is not found in the
@@ -1562,10 +1571,6 @@
 
     if (is_dt_needed) {
       needed_by->add_child(si);
-
-      if (si->is_linked()) {
-        si->increment_ref_count();
-      }
     }
 
     // When ld_preloads is not null, the first
@@ -1641,77 +1646,116 @@
     }
   }
 
-  // Step 5: link libraries that are not destined to this namespace.
-  // Do this by recursively calling find_libraries on the namespace where the lib
-  // was found during Step 1.
+  // Step 5: Collect roots of local_groups.
+  // Whenever needed_by->si link crosses a namespace boundary it forms its own local_group.
+  // Here we collect new roots to link them separately later on. Note that we need to avoid
+  // collecting duplicates. Also the order is important. They need to be linked in the same
+  // BFS order we link individual libraries.
+  std::vector<soinfo*> local_group_roots;
+  if (start_with != nullptr && add_as_children) {
+    local_group_roots.push_back(start_with);
+  } else {
+    CHECK(soinfos_count == 1);
+    local_group_roots.push_back(soinfos[0]);
+  }
+
   for (auto&& task : load_tasks) {
     soinfo* si = task->get_soinfo();
-    if (si->get_primary_namespace() != ns) {
-      const char* name = task->get_name();
-      if (find_libraries(si->get_primary_namespace(), task->get_needed_by(), &name, 1,
-                         nullptr /* soinfos */, nullptr /* ld_preloads */, 0 /* ld_preload_count */,
-                         rtld_flags, nullptr /* extinfo */, false /* add_as_children */,
-                         false /* search_linked_namespaces */, readers_map, namespaces)) {
-        // If this lib is directly needed by one of the libs in this namespace,
-        // then increment the count
-        soinfo* needed_by = task->get_needed_by();
-        if (needed_by != nullptr && needed_by->get_primary_namespace() == ns && si->is_linked()) {
-          si->increment_ref_count();
-        }
-      } else {
-        return false;
+    soinfo* needed_by = task->get_needed_by();
+    bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);
+    android_namespace_t* needed_by_ns =
+        is_dt_needed ? needed_by->get_primary_namespace() : ns;
+
+    if (!si->is_linked() && si->get_primary_namespace() != needed_by_ns) {
+      auto it = std::find(local_group_roots.begin(), local_group_roots.end(), si);
+      LD_LOG(kLogDlopen,
+             "Crossing namespace boundary (si=%s@%p, si_ns=%s@%p, needed_by=%s@%p, ns=%s@%p, needed_by_ns=%s@%p) adding to local_group_roots: %s",
+             si->get_realpath(),
+             si,
+             si->get_primary_namespace()->get_name(),
+             si->get_primary_namespace(),
+             needed_by == nullptr ? "(nullptr)" : needed_by->get_realpath(),
+             needed_by,
+             ns->get_name(),
+             ns,
+             needed_by_ns->get_name(),
+             needed_by_ns,
+             it == local_group_roots.end() ? "yes" : "no");
+
+      if (it == local_group_roots.end()) {
+        local_group_roots.push_back(si);
       }
     }
   }
 
-  // Step 6: link libraries in this namespace
-  soinfo_list_t local_group;
-  walk_dependencies_tree(
-      (start_with != nullptr && add_as_children) ? &start_with : soinfos,
-      (start_with != nullptr && add_as_children) ? 1 : soinfos_count,
+  // Step 6: Link all local groups
+  for (auto root : local_group_roots) {
+    soinfo_list_t local_group;
+    android_namespace_t* local_group_ns = root->get_primary_namespace();
+
+    walk_dependencies_tree(root,
       [&] (soinfo* si) {
-    if (ns->is_accessible(si)) {
-      local_group.push_back(si);
-      return kWalkContinue;
-    } else {
-      return kWalkSkip;
-    }
-  });
+        if (local_group_ns->is_accessible(si)) {
+          local_group.push_back(si);
+          return kWalkContinue;
+        } else {
+          return kWalkSkip;
+        }
+      });
 
-  soinfo_list_t global_group = ns->get_global_group();
-  bool linked = local_group.visit([&](soinfo* si) {
-    if (!si->is_linked()) {
-      if (!si->link_image(global_group, local_group, extinfo) ||
-          !get_cfi_shadow()->AfterLoad(si, solist_get_head())) {
-        return false;
+    soinfo_list_t global_group = local_group_ns->get_global_group();
+    bool linked = local_group.visit([&](soinfo* si) {
+      // Even though local group may contain accessible soinfos from other namesapces
+      // we should avoid linking them (because if they are not linked -> they
+      // are in the local_group_roots and will be linked later).
+      if (!si->is_linked() && si->get_primary_namespace() == local_group_ns) {
+        if (!si->link_image(global_group, local_group, extinfo) ||
+            !get_cfi_shadow()->AfterLoad(si, solist_get_head())) {
+          return false;
+        }
       }
-    }
 
-    return true;
-  });
-
-  if (linked) {
-    local_group.for_each([](soinfo* si) {
-      if (!si->is_linked()) {
-        si->set_linked();
-      }
+      return true;
     });
 
-    failure_guard.Disable();
+    if (!linked) {
+      return false;
+    }
   }
 
-  return linked;
+  // Step 7: Mark all load_tasks as linked and increment refcounts
+  // for references between load_groups (at this point it does not matter if
+  // referenced load_groups were loaded by previous dlopen or as part of this
+  // one on step 6)
+  if (start_with != nullptr && add_as_children) {
+    start_with->set_linked();
+  }
+
+  for (auto&& task : load_tasks) {
+    soinfo* si = task->get_soinfo();
+    si->set_linked();
+  }
+
+  for (auto&& task : load_tasks) {
+    soinfo* si = task->get_soinfo();
+    soinfo* needed_by = task->get_needed_by();
+    if (needed_by != nullptr &&
+        needed_by != start_with &&
+        needed_by->get_local_group_root() != si->get_local_group_root()) {
+      si->increment_ref_count();
+    }
+  }
+
+
+  return true;
 }
 
 static soinfo* find_library(android_namespace_t* ns,
                             const char* name, int rtld_flags,
                             const android_dlextinfo* extinfo,
                             soinfo* needed_by) {
-  soinfo* si;
+  soinfo* si = nullptr;
 
-  // readers_map is shared across recursive calls to find_libraries.
-  // However, the map is not shared across different threads.
-  std::unordered_map<const soinfo*, ElfReader> readers_map;
   if (name == nullptr) {
     si = solist_get_somain();
   } else if (!find_libraries(ns,
@@ -1724,8 +1768,10 @@
                              rtld_flags,
                              extinfo,
                              false /* add_as_children */,
-                             true /* search_linked_namespaces */,
-                             readers_map)) {
+                             true /* search_linked_namespaces */)) {
+    if (si != nullptr) {
+      soinfo_unload(si);
+    }
     return nullptr;
   }
 
@@ -1734,18 +1780,26 @@
   return si;
 }
 
-static void soinfo_unload(soinfo* si) {
-  soinfo* root = si->is_linked() ? si->get_local_group_root() : si;
+static void soinfo_unload(soinfo* unload_si) {
+  // Note that the library can be loaded but not linked;
+  // in which case there is no root but we still need
+  // to walk the tree and unload soinfos involved.
+  //
+  // This happens on unsuccessful dlopen, when one of
+  // the DT_NEEDED libraries could not be linked/found.
+  bool is_linked = unload_si->is_linked();
+  soinfo* root = is_linked ? unload_si->get_local_group_root() : unload_si;
 
   LD_LOG(kLogDlopen,
          "... dlclose(realpath=\"%s\"@%p) ... load group root is \"%s\"@%p",
-         si->get_realpath(),
-         si,
+         unload_si->get_realpath(),
+         unload_si,
          root->get_realpath(),
          root);
 
   ScopedTrace trace((std::string("unload ") + root->get_realpath()).c_str());
 
+  size_t ref_count = is_linked ? root->decrement_ref_count() : 0;
   if (!root->can_unload()) {
     LD_LOG(kLogDlopen,
            "... dlclose(root=\"%s\"@%p) ... not unloading - the load group is flagged with NODELETE",
@@ -1754,48 +1808,17 @@
     return;
   }
 
-  soinfo_unload(&root, 1);
-}
-
-static void soinfo_unload(soinfo* soinfos[], size_t count) {
-  // Note that the library can be loaded but not linked;
-  // in which case there is no root but we still need
-  // to walk the tree and unload soinfos involved.
-  //
-  // This happens on unsuccessful dlopen, when one of
-  // the DT_NEEDED libraries could not be linked/found.
-  if (count == 0) {
+  if (ref_count > 0) {
+    LD_LOG(kLogDlopen,
+           "... dlclose(root=\"%s\"@%p) ... not unloading - decrementing ref_count to %zd",
+           root->get_realpath(),
+           root,
+           ref_count);
     return;
   }
 
   soinfo_list_t unload_list;
-  for (size_t i = 0; i < count; ++i) {
-    soinfo* si = soinfos[i];
-
-    if (si->can_unload()) {
-      size_t ref_count = si->is_linked() ? si->decrement_ref_count() : 0;
-      if (ref_count == 0) {
-        unload_list.push_back(si);
-      } else {
-        LD_LOG(kLogDlopen,
-               "... dlclose(root=\"%s\"@%p) ... not unloading - decrementing ref_count to %zd",
-               si->get_realpath(),
-               si,
-               ref_count);
-      }
-    } else {
-      LD_LOG(kLogDlopen,
-             "... dlclose(root=\"%s\"@%p) ... not unloading - the load group is flagged with NODELETE",
-             si->get_realpath(),
-             si);
-      return;
-    }
-  }
-
-  // This is used to identify soinfos outside of the load-group
-  // note that we cannot have > 1 in the array and have any of them
-  // linked. This is why we can safely use the first one.
-  soinfo* root = soinfos[0];
+  unload_list.push_back(root);
 
   soinfo_list_t local_unload_list;
   soinfo_list_t external_unload_list;
@@ -1879,12 +1902,17 @@
     soinfo_free(si);
   }
 
-  while ((si = external_unload_list.pop_front()) != nullptr) {
-    LD_LOG(kLogDlopen,
-           "... dlclose: unloading external reference \"%s\"@%p ...",
-           si->get_realpath(),
-           si);
-    soinfo_unload(si);
+  if (is_linked) {
+    while ((si = external_unload_list.pop_front()) != nullptr) {
+      LD_LOG(kLogDlopen,
+             "... dlclose: unloading external reference \"%s\"@%p ...",
+             si->get_realpath(),
+             si);
+      soinfo_unload(si);
+    }
+  } else {
+      LD_LOG(kLogDlopen,
+             "... dlclose: unload_si was not linked - not unloading external references ...");
   }
 }
 
@@ -2015,12 +2043,18 @@
 
   const char* translated_name = name;
   if (g_is_asan && translated_name != nullptr && translated_name[0] == '/') {
-    char translated_path[PATH_MAX];
-    if (realpath(translated_name, translated_path) != nullptr) {
-      asan_name_holder = std::string(kAsanLibDirPrefix) + translated_path;
+    char original_path[PATH_MAX];
+    if (realpath(name, original_path) != nullptr) {
+      asan_name_holder = std::string(kAsanLibDirPrefix) + original_path;
       if (file_exists(asan_name_holder.c_str())) {
-        translated_name = asan_name_holder.c_str();
-        PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
+        soinfo* si = nullptr;
+        if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
+          PRINT("linker_asan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
+                asan_name_holder.c_str());
+        } else {
+          PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
+          translated_name = asan_name_holder.c_str();
+        }
       }
     }
   }
@@ -3341,6 +3375,10 @@
 
 bool soinfo::link_image(const soinfo_list_t& global_group, const soinfo_list_t& local_group,
                         const android_dlextinfo* extinfo) {
+  if (is_image_linked()) {
+    // already linked.
+    return true;
+  }
 
   local_group_root_ = local_group.front();
   if (local_group_root_ == nullptr) {
@@ -3483,6 +3521,7 @@
   }
 
   notify_gdb_of_load(this);
+  set_image_linked();
   return true;
 }
 
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index 317f0d2..dc1fa75 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -384,9 +384,6 @@
   const char** needed_library_names = &needed_library_name_list[0];
   size_t needed_libraries_count = needed_library_name_list.size();
 
-  // readers_map is shared across recursive calls to find_libraries so that we
-  // don't need to re-load elf headers.
-  std::unordered_map<const soinfo*, ElfReader> readers_map;
   if (needed_libraries_count > 0 &&
       !find_libraries(&g_default_namespace,
                       si,
@@ -399,7 +396,6 @@
                       nullptr,
                       true /* add_as_children */,
                       true /* search_linked_namespaces */,
-                      readers_map,
                       &namespaces)) {
     __linker_cannot_link(g_argv[0]);
   } else if (needed_libraries_count == 0) {
diff --git a/linker/linker_main.h b/linker/linker_main.h
index 2cf30c2..5641696 100644
--- a/linker/linker_main.h
+++ b/linker/linker_main.h
@@ -65,7 +65,6 @@
                     const android_dlextinfo* extinfo,
                     bool add_as_children,
                     bool search_linked_namespaces,
-                    std::unordered_map<const soinfo*, ElfReader>& readers_map,
                     std::vector<android_namespace_t*>* namespaces = nullptr);
 
 void solist_add_soinfo(soinfo* si);
diff --git a/linker/linker_soinfo.cpp b/linker/linker_soinfo.cpp
index fbff7cf..dd91752 100644
--- a/linker/linker_soinfo.cpp
+++ b/linker/linker_soinfo.cpp
@@ -657,6 +657,10 @@
   return (flags_ & FLAG_LINKED) != 0;
 }
 
+bool soinfo::is_image_linked() const {
+  return (flags_ & FLAG_IMAGE_LINKED) != 0;
+}
+
 bool soinfo::is_main_executable() const {
   return (flags_ & FLAG_EXE) != 0;
 }
@@ -669,6 +673,10 @@
   flags_ |= FLAG_LINKED;
 }
 
+void soinfo::set_image_linked() {
+  flags_ |= FLAG_IMAGE_LINKED;
+}
+
 void soinfo::set_linker_flag() {
   flags_ |= FLAG_LINKER;
 }
@@ -677,8 +685,8 @@
   flags_ |= FLAG_EXE;
 }
 
-void soinfo::increment_ref_count() {
-  local_group_root_->ref_count_++;
+size_t soinfo::increment_ref_count() {
+  return ++local_group_root_->ref_count_;
 }
 
 size_t soinfo::decrement_ref_count() {
diff --git a/linker/linker_soinfo.h b/linker/linker_soinfo.h
index 6f472b5..273c36c 100644
--- a/linker/linker_soinfo.h
+++ b/linker/linker_soinfo.h
@@ -41,6 +41,17 @@
 #define FLAG_GNU_HASH         0x00000040 // uses gnu hash
 #define FLAG_MAPPED_BY_CALLER 0x00000080 // the map is reserved by the caller
                                          // and should not be unmapped
+#define FLAG_IMAGE_LINKED     0x00000100 // Is image linked - this is a guard on link_image.
+                                         // The difference between this flag and
+                                         // FLAG_LINKED is that FLAG_LINKED
+                                         // means is set when load_group is
+                                         // successfully loaded whereas this
+                                         // flag is set to avoid linking image
+                                         // when link_image called for the
+                                         // second time. This situation happens
+                                         // when load group is crossing
+                                         // namespace boundary twice and second
+                                         // local group depends on the same libraries.
 #define FLAG_NEW_SOINFO       0x40000000 // new soinfo format
 
 #define SOINFO_VERSION 3
@@ -243,7 +254,7 @@
   void set_main_executable();
   void set_nodelete();
 
-  void increment_ref_count();
+  size_t increment_ref_count();
   size_t decrement_ref_count();
 
   soinfo* get_local_group_root() const;
@@ -273,6 +284,9 @@
   void* to_handle();
 
  private:
+  bool is_image_linked() const;
+  void set_image_linked();
+
   bool elf_lookup(SymbolName& symbol_name, const version_info* vi, uint32_t* symbol_index) const;
   ElfW(Sym)* elf_addr_lookup(const void* addr);
   bool gnu_lookup(SymbolName& symbol_name, const version_info* vi, uint32_t* symbol_index) const;
diff --git a/tests/Android.bp b/tests/Android.bp
index c045c1e..28df084 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -51,11 +51,13 @@
     name: "libBionicStandardTests",
     defaults: ["bionic_tests_defaults"],
     srcs: [
+        "alloca_test.cpp",
         "arpa_inet_test.cpp",
         "async_safe_test.cpp",
         "assert_test.cpp",
         "buffer_tests.cpp",
         "bug_26110743_test.cpp",
+        "byteswap_test.cpp",
         "complex_test.cpp",
         "complex_force_long_double_test.cpp",
         "ctype_test.cpp",
diff --git a/tests/alloca_test.cpp b/tests/alloca_test.cpp
new file mode 100644
index 0000000..192157d
--- /dev/null
+++ b/tests/alloca_test.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <alloca.h>
+
+#include <string.h>
+
+#include <gtest/gtest.h>
+
+TEST(alloca, alloca) {
+  // These days, alloca is usually a builtin, so we can't really assert much.
+  void* p = alloca(1024);
+  ASSERT_NE(nullptr, p);
+  memset(p, 0, 1024);
+}
diff --git a/tests/byteswap_test.cpp b/tests/byteswap_test.cpp
new file mode 100644
index 0000000..a18cfa6
--- /dev/null
+++ b/tests/byteswap_test.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <byteswap.h>
+
+#include <gtest/gtest.h>
+
+static constexpr uint16_t le16 = 0x1234;
+static constexpr uint32_t le32 = 0x12345678;
+static constexpr uint64_t le64 = 0x123456789abcdef0;
+
+static constexpr uint16_t be16 = 0x3412;
+static constexpr uint32_t be32 = 0x78563412;
+static constexpr uint64_t be64 = 0xf0debc9a78563412;
+
+TEST(byteswap, bswap_16) {
+  EXPECT_EQ(le16, bswap_16(be16));
+  EXPECT_EQ(be16, bswap_16(le16));
+}
+
+TEST(byteswap, bswap_32) {
+  EXPECT_EQ(le32, bswap_32(be32));
+  EXPECT_EQ(be32, bswap_32(le32));
+}
+
+TEST(byteswap, bswap_64) {
+  EXPECT_EQ(le64, bswap_64(be64));
+  EXPECT_EQ(be64, bswap_64(le64));
+}
+
diff --git a/tests/dlext_test.cpp b/tests/dlext_test.cpp
index 3f6da59..04b83f2 100644
--- a/tests/dlext_test.cpp
+++ b/tests/dlext_test.cpp
@@ -1051,6 +1051,87 @@
             "\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror());
 }
 
+TEST(dlext, ns_unload_between_namespaces_missing_symbol_direct) {
+  ASSERT_TRUE(android_init_anonymous_namespace(g_core_shared_libs.c_str(), nullptr));
+
+  const std::string public_ns_search_path =  get_testlib_root() + "/public_namespace_libs";
+  const std::string private_ns_search_path = get_testlib_root() + "/private_namespace_libs";
+
+  android_namespace_t* ns_public =
+          android_create_namespace("public",
+                                   nullptr,
+                                   public_ns_search_path.c_str(),
+                                   ANDROID_NAMESPACE_TYPE_ISOLATED,
+                                   nullptr,
+                                   nullptr);
+
+  ASSERT_TRUE(android_link_namespaces(ns_public, nullptr, g_core_shared_libs.c_str())) << dlerror();
+
+  android_namespace_t* ns_private =
+          android_create_namespace("private",
+                                   nullptr,
+                                   private_ns_search_path.c_str(),
+                                   ANDROID_NAMESPACE_TYPE_ISOLATED,
+                                   nullptr,
+                                   nullptr);
+
+  ASSERT_TRUE(android_link_namespaces(ns_private, ns_public, "libtest_missing_symbol.so")) << dlerror();
+  ASSERT_TRUE(android_link_namespaces(ns_private, nullptr, g_core_shared_libs.c_str())) << dlerror();
+
+  android_dlextinfo extinfo;
+  extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
+  extinfo.library_namespace = ns_private;
+
+  void* handle = android_dlopen_ext((public_ns_search_path + "/libtest_missing_symbol.so").c_str(),
+                                    RTLD_NOW,
+                                    &extinfo);
+  ASSERT_TRUE(handle == nullptr);
+  ASSERT_EQ(std::string("dlopen failed: cannot locate symbol \"dlopen_testlib_missing_symbol\" referenced by \"") +
+            public_ns_search_path + "/libtest_missing_symbol.so\"...",
+            dlerror());
+}
+
+TEST(dlext, ns_unload_between_namespaces_missing_symbol_indirect) {
+  ASSERT_TRUE(android_init_anonymous_namespace(g_core_shared_libs.c_str(), nullptr));
+
+  const std::string public_ns_search_path =  get_testlib_root() + "/public_namespace_libs";
+  const std::string private_ns_search_path = get_testlib_root() + "/private_namespace_libs";
+
+  android_namespace_t* ns_public =
+          android_create_namespace("public",
+                                   nullptr,
+                                   public_ns_search_path.c_str(),
+                                   ANDROID_NAMESPACE_TYPE_ISOLATED,
+                                   nullptr,
+                                   nullptr);
+
+  ASSERT_TRUE(android_link_namespaces(ns_public, nullptr, g_core_shared_libs.c_str())) << dlerror();
+
+  android_namespace_t* ns_private =
+          android_create_namespace("private",
+                                   nullptr,
+                                   private_ns_search_path.c_str(),
+                                   ANDROID_NAMESPACE_TYPE_ISOLATED,
+                                   nullptr,
+                                   nullptr);
+
+  ASSERT_TRUE(android_link_namespaces(ns_private,
+                                      ns_public,
+                                      "libnstest_public.so:libtest_missing_symbol_child_public.so")
+              ) << dlerror();
+  ASSERT_TRUE(android_link_namespaces(ns_private, nullptr, g_core_shared_libs.c_str())) << dlerror();
+
+  android_dlextinfo extinfo;
+  extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
+  extinfo.library_namespace = ns_private;
+
+  void* handle = android_dlopen_ext("libtest_missing_symbol_root.so", RTLD_NOW, &extinfo);
+  ASSERT_TRUE(handle == nullptr);
+  ASSERT_EQ(std::string("dlopen failed: cannot locate symbol \"dlopen_testlib_missing_symbol\" referenced by \"") +
+            private_ns_search_path + "/libtest_missing_symbol_root.so\"...",
+            dlerror());
+}
+
 TEST(dlext, ns_greylist_enabled) {
   ASSERT_TRUE(android_init_anonymous_namespace(g_core_shared_libs.c_str(), nullptr));
 
@@ -1270,29 +1351,49 @@
 
   std::string shared_libs = g_core_shared_libs + ":" + g_public_lib;
 
+  // create a parent namespace to use instead of the default namespace. This is
+  // to make this test be independent from the configuration of the default
+  // namespace.
+  android_namespace_t* ns_parent =
+          android_create_namespace("parent",
+                                   nullptr,
+                                   nullptr,
+                                   ANDROID_NAMESPACE_TYPE_REGULAR,
+                                   nullptr,
+                                   nullptr);
+  ASSERT_TRUE(ns_parent != nullptr) << dlerror();
+  ASSERT_TRUE(android_link_namespaces(ns_parent, nullptr, g_core_shared_libs.c_str())) << dlerror();
+
+  android_dlextinfo extinfo;
+  extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
+  extinfo.library_namespace = ns_parent;
+
   const std::string lib_public_path = get_testlib_root() + "/public_namespace_libs/" + g_public_lib;
-  void* handle_public = dlopen(lib_public_path.c_str(), RTLD_NOW);
+  void* handle_public = android_dlopen_ext(lib_public_path.c_str(), RTLD_NOW, &extinfo);
   ASSERT_TRUE(handle_public != nullptr) << dlerror();
 
   android_set_application_target_sdk_version(42U); // something > 23
 
   ASSERT_TRUE(android_init_anonymous_namespace(shared_libs.c_str(), nullptr)) << dlerror();
 
-  // preload this library to the default namespace to check if it
+  // preload this library to the parent namespace to check if it
   // is shared later on.
   void* handle_dlopened =
-          dlopen((get_testlib_root() + "/private_namespace_libs/libnstest_dlopened.so").c_str(), RTLD_NOW);
+          android_dlopen_ext((get_testlib_root() + "/private_namespace_libs/libnstest_dlopened.so").c_str(), RTLD_NOW, &extinfo);
   ASSERT_TRUE(handle_dlopened != nullptr) << dlerror();
 
+  // create two child namespaces of 'ns_parent'. One with regular, the other
+  // with isolated & shared.
   android_namespace_t* ns_not_isolated =
           android_create_namespace("private",
                                    nullptr,
                                    (get_testlib_root() + "/private_namespace_libs").c_str(),
                                    ANDROID_NAMESPACE_TYPE_REGULAR,
                                    nullptr,
-                                   nullptr);
+                                   ns_parent);
   ASSERT_TRUE(ns_not_isolated != nullptr) << dlerror();
-  ASSERT_TRUE(android_link_namespaces(ns_not_isolated, nullptr, shared_libs.c_str())) << dlerror();
+  ASSERT_TRUE(android_link_namespaces(ns_not_isolated, ns_parent, g_public_lib)) << dlerror();
+  ASSERT_TRUE(android_link_namespaces(ns_not_isolated, nullptr, g_core_shared_libs.c_str())) << dlerror();
 
   android_namespace_t* ns_isolated_shared =
           android_create_namespace("private_isolated_shared",
@@ -1300,23 +1401,22 @@
                                    (get_testlib_root() + "/private_namespace_libs").c_str(),
                                    ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_SHARED,
                                    nullptr,
-                                   nullptr);
+                                   ns_parent);
   ASSERT_TRUE(ns_isolated_shared != nullptr) << dlerror();
-  ASSERT_TRUE(android_link_namespaces(ns_isolated_shared, nullptr, shared_libs.c_str())) << dlerror();
+  ASSERT_TRUE(android_link_namespaces(ns_isolated_shared, ns_parent, g_public_lib)) << dlerror();
+  ASSERT_TRUE(android_link_namespaces(ns_isolated_shared, nullptr, g_core_shared_libs.c_str())) << dlerror();
 
-  ASSERT_TRUE(dlopen(root_lib, RTLD_NOW) == nullptr);
+  ASSERT_TRUE(android_dlopen_ext(root_lib, RTLD_NOW, &extinfo) == nullptr);
   ASSERT_STREQ("dlopen failed: library \"libnstest_root_not_isolated.so\" not found", dlerror());
 
   std::string lib_private_external_path =
       get_testlib_root() + "/private_namespace_libs_external/libnstest_private_external.so";
 
-  // Load lib_private_external_path to default namespace
+  // Load lib_private_external_path to the parent namespace
   // (it should remain invisible for the isolated namespaces after this)
-  void* handle = dlopen(lib_private_external_path.c_str(), RTLD_NOW);
+  void* handle = android_dlopen_ext(lib_private_external_path.c_str(), RTLD_NOW, &extinfo);
   ASSERT_TRUE(handle != nullptr) << dlerror();
 
-  android_dlextinfo extinfo;
-  extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
   extinfo.library_namespace = ns_not_isolated;
 
   void* handle1 = android_dlopen_ext(root_lib, RTLD_NOW, &extinfo);
diff --git a/tests/inttypes_test.cpp b/tests/inttypes_test.cpp
index da12ec4..08258ac 100644
--- a/tests/inttypes_test.cpp
+++ b/tests/inttypes_test.cpp
@@ -109,6 +109,18 @@
   EXPECT_EQ(L'x', *end);
 }
 
+TEST(inttypes, strtoimax_dec) {
+  char* p;
+  EXPECT_EQ(-18737357, strtoimax("-18737357foobar12", &p, 10));
+  EXPECT_STREQ("foobar12", p);
+}
+
+TEST(inttypes, strtoimax_hex) {
+  char* p;
+  EXPECT_EQ(-0x18737357f, strtoimax("-18737357foobar12", &p, 16));
+  EXPECT_STREQ("oobar12", p);
+}
+
 TEST(inttypes, strtoimax_EINVAL) {
   errno = 0;
   strtoimax("123", NULL, -1);
@@ -121,6 +133,24 @@
   ASSERT_EQ(EINVAL, errno);
 }
 
+TEST(inttypes, strtoumax_dec) {
+  char* p;
+  EXPECT_EQ(18737357U, strtoumax("18737357foobar12", &p, 10));
+  EXPECT_STREQ("foobar12", p);
+}
+
+TEST(inttypes, strtoumax_hex) {
+  char* p;
+  EXPECT_EQ(0x18737357fU, strtoumax("18737357foobar12", &p, 16));
+  EXPECT_STREQ("oobar12", p);
+}
+
+TEST(inttypes, strtoumax_negative) {
+  char* p;
+  EXPECT_EQ(UINTMAX_MAX - 18737357 + 1, strtoumax("-18737357foobar12", &p, 10));
+  EXPECT_STREQ("foobar12", p);
+}
+
 TEST(inttypes, strtoumax_EINVAL) {
   errno = 0;
   strtoumax("123", NULL, -1);
@@ -158,30 +188,88 @@
 }
 
 TEST(inttypes, div) {
-  div_t r = div(-5, 3);
-  ASSERT_EQ(-1, r.quot);
-  ASSERT_EQ(-2, r.rem);
+  div_t r;
+
+  r = div(5, 3);
+  EXPECT_EQ(1, r.quot);
+  EXPECT_EQ(2, r.rem);
+
+  r = div(5, -3);
+  EXPECT_EQ(-1, r.quot);
+  EXPECT_EQ(2, r.rem);
+
+  r = div(-5, 3);
+  EXPECT_EQ(-1, r.quot);
+  EXPECT_EQ(-2, r.rem);
+
+  r = div(-5, -3);
+  EXPECT_EQ(1, r.quot);
+  EXPECT_EQ(-2, r.rem);
 }
 
 TEST(inttypes, ldiv) {
-  ldiv_t r = ldiv(-5, 3);
-  ASSERT_EQ(-1, r.quot);
-  ASSERT_EQ(-2, r.rem);
+  ldiv_t r;
+
+  r = ldiv(5, 3);
+  EXPECT_EQ(1, r.quot);
+  EXPECT_EQ(2, r.rem);
+
+  r = ldiv(5, -3);
+  EXPECT_EQ(-1, r.quot);
+  EXPECT_EQ(2, r.rem);
+
+  r = ldiv(-5, 3);
+  EXPECT_EQ(-1, r.quot);
+  EXPECT_EQ(-2, r.rem);
+
+  r = ldiv(-5, -3);
+  EXPECT_EQ(1, r.quot);
+  EXPECT_EQ(-2, r.rem);
 }
 
 TEST(inttypes, lldiv) {
-  lldiv_t r = lldiv(-5, 3);
-  ASSERT_EQ(-1, r.quot);
-  ASSERT_EQ(-2, r.rem);
+  lldiv_t r;
+
+  r = lldiv(5, 3);
+  EXPECT_EQ(1, r.quot);
+  EXPECT_EQ(2, r.rem);
+
+  r = lldiv(5, -3);
+  EXPECT_EQ(-1, r.quot);
+  EXPECT_EQ(2, r.rem);
+
+  r = lldiv(-5, 3);
+  EXPECT_EQ(-1, r.quot);
+  EXPECT_EQ(-2, r.rem);
+
+  r = lldiv(-5, -3);
+  EXPECT_EQ(1, r.quot);
+  EXPECT_EQ(-2, r.rem);
 }
 
 TEST(inttypes, imaxdiv) {
-  imaxdiv_t r = imaxdiv(-5, 3);
-  ASSERT_EQ(-1, r.quot);
-  ASSERT_EQ(-2, r.rem);
+  imaxdiv_t r;
+
+  r = imaxdiv(5, 3);
+  EXPECT_EQ(1, r.quot);
+  EXPECT_EQ(2, r.rem);
+
+  r = imaxdiv(5, -3);
+  EXPECT_EQ(-1, r.quot);
+  EXPECT_EQ(2, r.rem);
+
+  r = imaxdiv(-5, 3);
+  EXPECT_EQ(-1, r.quot);
+  EXPECT_EQ(-2, r.rem);
+
+  r = imaxdiv(-5, -3);
+  EXPECT_EQ(1, r.quot);
+  EXPECT_EQ(-2, r.rem);
 }
 
 TEST(inttypes, imaxabs) {
-  ASSERT_EQ(INTMAX_MAX, imaxabs(-INTMAX_MAX));
-  ASSERT_EQ(INTMAX_MAX, imaxabs(INTMAX_MAX));
+  EXPECT_EQ(672423489, imaxabs(672423489));
+  EXPECT_EQ(672423489, imaxabs(-672423489));
+  EXPECT_EQ(INTMAX_MAX, imaxabs(-INTMAX_MAX));
+  EXPECT_EQ(INTMAX_MAX, imaxabs(INTMAX_MAX));
 }
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index 8d0271a..e45eb6e 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -113,6 +113,62 @@
 }
 
 // -----------------------------------------------------------------------------
+// Library used by dlext direct unload on the namespace boundary tests
+// -----------------------------------------------------------------------------
+cc_test_library {
+    name: "libtest_missing_symbol",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["dlopen_testlib_missing_symbol.cpp"],
+    allow_undefined_symbols: true,
+    relative_install_path: "bionic-loader-test-libs/public_namespace_libs",
+}
+
+
+// -----------------------------------------------------------------------------
+// Library used by dlext indirect unload on the namespace boundary tests
+//
+// These libraries produce following dependency graph:
+// libtest_missing_symbol_root (private ns)
+// +-> libbnstest_public (public ns)
+// +-> libtest_missing_symbol_child_public (public ns)
+//     +-> libnstest_public (public ns)
+// +-> libtest_missing_symbol_child_private (private_ns)
+//     +-> libnstest_public (public_ns)
+//
+// All libraries except libtest_missing_symbol are located in
+// private_namespace_libs/
+// -----------------------------------------------------------------------------
+cc_test_library {
+    name: "libtest_missing_symbol_child_public",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["empty.cpp"],
+    relative_install_path: "bionic-loader-test-libs/public_namespace_libs",
+    shared_libs: ["libnstest_public"],
+}
+
+cc_test_library {
+    name: "libtest_missing_symbol_child_private",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["empty.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+    shared_libs: ["libnstest_public"],
+}
+
+cc_test_library {
+    name: "libtest_missing_symbol_root",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["dlopen_testlib_missing_symbol.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+    allow_undefined_symbols: true,
+    shared_libs: [
+        "libnstest_public",
+        "libtest_missing_symbol_child_public",
+        "libtest_missing_symbol_child_private",
+    ],
+}
+
+// -----------------------------------------------------------------------------
+// -----------------------------------------------------------------------------
 // Library used by dlfcn nodelete tests
 // -----------------------------------------------------------------------------
 cc_test_library {
@@ -142,8 +198,65 @@
 
 // -----------------------------------------------------------------------------
 // Build test helper libraries for linker namespaces
+//
+// This set of libraries is used to verify linker namespaces.
+//
+// Test cases
+// 1. Check that private libraries loaded in different namespaces are
+//    different. Check that dlsym does not confuse them.
+// 2. Check that public libraries loaded in different namespaces are shared
+//    between them.
+// 3. Check that namespace sticks on dlopen
+// 4. Check that having access to shared library (libnstest_public.so)
+//    does not expose symbols from dependent library (libnstest_public_internal.so)
+//
+// Dependency tree (visibility)
+// libnstest_root.so (this should be local to the namespace)
+// +-> libnstest_public.so
+//     +-> libnstest_public_internal.so
+// +-> libnstest_private.so
+//
+// libnstest_dlopened.so (library in private namespace dlopened from libnstest_root.so)
 // -----------------------------------------------------------------------------
-// include $(LOCAL_PATH)/Android.build.linker_namespaces.mk
+cc_test_library {
+    name: "libnstest_root",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_root.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+    shared_libs: [
+        "libnstest_public",
+        "libnstest_private",
+    ],
+}
+
+cc_test_library {
+    name: "libnstest_public_internal",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_public_internal.cpp"],
+    relative_install_path: "bionic-loader-test-libs/public_namespace_libs",
+}
+
+cc_test_library {
+    name: "libnstest_public",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_public.cpp"],
+    relative_install_path: "bionic-loader-test-libs/public_namespace_libs",
+    shared_libs: ["libnstest_public_internal"],
+}
+
+cc_test_library {
+    name: "libnstest_private",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_private.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+}
+
+cc_test_library {
+    name: "libnstest_dlopened",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_dlopened.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+}
 
 // -----------------------------------------------------------------------------
 // Build DT_RUNPATH test helper libraries
diff --git a/tests/libs/Android.build.linker_namespaces.mk b/tests/libs/Android.build.linker_namespaces.mk
index cd9d7f1..e2b01a7 100644
--- a/tests/libs/Android.build.linker_namespaces.mk
+++ b/tests/libs/Android.build.linker_namespaces.mk
@@ -19,51 +19,6 @@
 # -----------------------------------------------------------------------------
 
 # -----------------------------------------------------------------------------
-# Test cases
-# 1. Check that private libraries loaded in different namespaces are
-#    different. Check that dlsym does not confuse them.
-# 2. Check that public libraries loaded in different namespaces are shared
-#    between them.
-# 3. Check that namespace sticks on dlopen
-# 4. Check that having access to shared library (libnstest_public.so)
-#    does not expose symbols from dependent library (libnstest_public_internal.so)
-#
-# Dependency tree (visibility)
-# libnstest_root.so (this should be local to the namespace)
-# +-> libnstest_public.so
-#     +-> libnstest_public_internal.so
-# +-> libnstest_private.so
-#
-# libnstest_dlopened.so (library in private namespace dlopened from libnstest_root.so)
-# -----------------------------------------------------------------------------
-libnstest_root_src_files := namespaces_root.cpp
-libnstest_root_shared_libraries := libnstest_public libnstest_private
-libnstest_root_relative_install_path := private_namespace_libs
-module := libnstest_root
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-libnstest_public_internal_src_files := namespaces_public_internal.cpp
-module := libnstest_public_internal
-libnstest_public_internal_relative_install_path := public_namespace_libs
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-libnstest_public_src_files := namespaces_public.cpp
-libnstest_public_shared_libraries := libnstest_public_internal
-module := libnstest_public
-libnstest_public_relative_install_path := public_namespace_libs
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-libnstest_private_src_files := namespaces_private.cpp
-libnstest_private_relative_install_path := private_namespace_libs
-module := libnstest_private
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-libnstest_dlopened_src_files := namespaces_dlopened.cpp
-libnstest_dlopened_relative_install_path := private_namespace_libs
-module := libnstest_dlopened
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-# -----------------------------------------------------------------------------
 # This set of libraries is to test isolated namespaces
 #
 # Isolated namespaces do not allow loading of the library outside of
diff --git a/tests/libs/dlopen_testlib_missing_symbol.cpp b/tests/libs/dlopen_testlib_missing_symbol.cpp
new file mode 100644
index 0000000..0f73c60
--- /dev/null
+++ b/tests/libs/dlopen_testlib_missing_symbol.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+extern "C" void dlopen_testlib_missing_symbol();
+
+extern "C" bool dlopen_testlib_simple_func() {
+  dlopen_testlib_missing_symbol();
+  return true;
+}
diff --git a/tests/pthread_test.cpp b/tests/pthread_test.cpp
old mode 100755
new mode 100644
diff --git a/tests/stdio_test.cpp b/tests/stdio_test.cpp
index 05a19d1..e060cd9 100644
--- a/tests/stdio_test.cpp
+++ b/tests/stdio_test.cpp
@@ -923,33 +923,188 @@
 TEST(STDIO_TEST, sscanf_swscanf) {
   struct stuff {
     char s1[123];
-    int i1;
+    int i1, i2;
+    char cs1[3];
+    char s2[3];
+    char c1;
     double d1;
     float f1;
-    char s2[123];
+    char s3[123];
 
     void Check() {
-      ASSERT_STREQ("hello", s1);
-      ASSERT_EQ(123, i1);
-      ASSERT_DOUBLE_EQ(1.23, d1);
-      ASSERT_FLOAT_EQ(9.0f, f1);
-      ASSERT_STREQ("world", s2);
+      EXPECT_STREQ("hello", s1);
+      EXPECT_EQ(123, i1);
+      EXPECT_EQ(456, i2);
+      EXPECT_EQ('a', cs1[0]);
+      EXPECT_EQ('b', cs1[1]);
+      EXPECT_EQ('x', cs1[2]); // No terminating NUL.
+      EXPECT_STREQ("AB", s2); // Terminating NUL.
+      EXPECT_EQ('!', c1);
+      EXPECT_DOUBLE_EQ(1.23, d1);
+      EXPECT_FLOAT_EQ(9.0f, f1);
+      EXPECT_STREQ("world", s3);
     }
   } s;
 
-  memset(&s, 0, sizeof(s));
-  ASSERT_EQ(5, sscanf("  hello 123 1.23 0x1.2p3 world",
-                      "%s %i %lf %f %s",
-                      s.s1, &s.i1, &s.d1, &s.f1, s.s2));
+  memset(&s, 'x', sizeof(s));
+  ASSERT_EQ(9, sscanf("  hello 123 456abAB! 1.23 0x1.2p3 world",
+                      "%s %i%i%2c%[A-Z]%c %lf %f %s",
+                      s.s1, &s.i1, &s.i2, s.cs1, s.s2, &s.c1, &s.d1, &s.f1, s.s3));
   s.Check();
 
-  memset(&s, 0, sizeof(s));
-  ASSERT_EQ(5, swscanf(L"  hello 123 1.23 0x1.2p3 world",
-                       L"%s %i %lf %f %s",
-                       s.s1, &s.i1, &s.d1, &s.f1, s.s2));
+  memset(&s, 'x', sizeof(s));
+  ASSERT_EQ(9, swscanf(L"  hello 123 456abAB! 1.23 0x1.2p3 world",
+                       L"%s %i%i%2c%[A-Z]%c %lf %f %s",
+                       s.s1, &s.i1, &s.i2, s.cs1, s.s2, &s.c1, &s.d1, &s.f1, s.s3));
   s.Check();
 }
 
+template <typename T>
+static void CheckScanf(int sscanf_fn(const T*, const T*, ...),
+                       const T* input, const T* fmt,
+                       int expected_count, const char* expected_string) {
+  char buf[256] = {};
+  ASSERT_EQ(expected_count, sscanf_fn(input, fmt, &buf)) << fmt;
+  ASSERT_STREQ(expected_string, buf) << fmt;
+}
+
+TEST(STDIO_TEST, sscanf_ccl) {
+  // `abc` is just those characters.
+  CheckScanf(sscanf, "abcd", "%[abc]", 1, "abc");
+  // `a-c` is the range 'a' .. 'c'.
+  CheckScanf(sscanf, "abcd", "%[a-c]", 1, "abc");
+  CheckScanf(sscanf, "-d", "%[a-c]", 0, "");
+  CheckScanf(sscanf, "ac-bAd", "%[a--c]", 1, "ac-bA");
+  // `a-c-e` is equivalent to `a-e`.
+  CheckScanf(sscanf, "abcdefg", "%[a-c-e]", 1, "abcde");
+  // `e-a` is equivalent to `ae-` (because 'e' > 'a').
+  CheckScanf(sscanf, "-a-e-b", "%[e-a]", 1, "-a-e-");
+  // An initial '^' negates the set.
+  CheckScanf(sscanf, "abcde", "%[^d]", 1, "abc");
+  CheckScanf(sscanf, "abcdefgh", "%[^c-d]", 1, "ab");
+  CheckScanf(sscanf, "hgfedcba", "%[^c-d]", 1, "hgfe");
+  // The first character may be ']' or '-' without being special.
+  CheckScanf(sscanf, "[[]]x", "%[][]", 1, "[[]]");
+  CheckScanf(sscanf, "-a-x", "%[-a]", 1, "-a-");
+  // The last character may be '-' without being special.
+  CheckScanf(sscanf, "-a-x", "%[a-]", 1, "-a-");
+  // X--Y is [X--] + Y, not [X--] + [--Y] (a bug in my initial implementation).
+  CheckScanf(sscanf, "+,-/.", "%[+--/]", 1, "+,-/");
+}
+
+TEST(STDIO_TEST, swscanf_ccl) {
+  // `abc` is just those characters.
+  CheckScanf(swscanf, L"abcd", L"%[abc]", 1, "abc");
+  // `a-c` is the range 'a' .. 'c'.
+  CheckScanf(swscanf, L"abcd", L"%[a-c]", 1, "abc");
+  CheckScanf(swscanf, L"-d", L"%[a-c]", 0, "");
+  CheckScanf(swscanf, L"ac-bAd", L"%[a--c]", 1, "ac-bA");
+  // `a-c-e` is equivalent to `a-e`.
+  CheckScanf(swscanf, L"abcdefg", L"%[a-c-e]", 1, "abcde");
+  // `e-a` is equivalent to `ae-` (because 'e' > 'a').
+  CheckScanf(swscanf, L"-a-e-b", L"%[e-a]", 1, "-a-e-");
+  // An initial '^' negates the set.
+  CheckScanf(swscanf, L"abcde", L"%[^d]", 1, "abc");
+  CheckScanf(swscanf, L"abcdefgh", L"%[^c-d]", 1, "ab");
+  CheckScanf(swscanf, L"hgfedcba", L"%[^c-d]", 1, "hgfe");
+  // The first character may be ']' or '-' without being special.
+  CheckScanf(swscanf, L"[[]]x", L"%[][]", 1, "[[]]");
+  CheckScanf(swscanf, L"-a-x", L"%[-a]", 1, "-a-");
+  // The last character may be '-' without being special.
+  CheckScanf(swscanf, L"-a-x", L"%[a-]", 1, "-a-");
+  // X--Y is [X--] + Y, not [X--] + [--Y] (a bug in my initial implementation).
+  CheckScanf(swscanf, L"+,-/.", L"%[+--/]", 1, "+,-/");
+}
+
+// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=202240
+TEST(STDIO_TEST, scanf_wscanf_EOF) {
+  EXPECT_EQ(0, sscanf("b", "ab"));
+  EXPECT_EQ(EOF, sscanf("", "a"));
+  EXPECT_EQ(0, swscanf(L"b", L"ab"));
+  EXPECT_EQ(EOF, swscanf(L"", L"a"));
+}
+
+TEST(STDIO_TEST, scanf_invalid_UTF8) {
+#if 0 // TODO: more tests invented during code review; no regressions, so fix later.
+  char buf[BUFSIZ];
+  wchar_t wbuf[BUFSIZ];
+
+  memset(buf, 0, sizeof(buf));
+  memset(wbuf, 0, sizeof(wbuf));
+  EXPECT_EQ(0, sscanf("\xc0" " foo", "%ls %s", wbuf, buf));
+#endif
+}
+
+TEST(STDIO_TEST, scanf_no_match_no_termination) {
+  char buf[4] = "x";
+  EXPECT_EQ(0, sscanf("d", "%[abc]", buf));
+  EXPECT_EQ('x', buf[0]);
+  EXPECT_EQ(0, swscanf(L"d", L"%[abc]", buf));
+  EXPECT_EQ('x', buf[0]);
+
+  wchar_t wbuf[4] = L"x";
+  EXPECT_EQ(0, swscanf(L"d", L"%l[abc]", wbuf));
+  EXPECT_EQ(L'x', wbuf[0]);
+
+  EXPECT_EQ(EOF, sscanf("", "%s", buf));
+  EXPECT_EQ('x', buf[0]);
+
+  EXPECT_EQ(EOF, swscanf(L"", L"%ls", wbuf));
+  EXPECT_EQ(L'x', wbuf[0]);
+}
+
+TEST(STDIO_TEST, scanf_wscanf_wide_character_class) {
+#if 0 // TODO: more tests invented during code review; no regressions, so fix later.
+  wchar_t buf[BUFSIZ];
+
+  // A wide character shouldn't match an ASCII-only class for scanf or wscanf.
+  memset(buf, 0, sizeof(buf));
+  EXPECT_EQ(1, sscanf("xĀyz", "%l[xy]", buf));
+  EXPECT_EQ(L"x"s, std::wstring(buf));
+  memset(buf, 0, sizeof(buf));
+  EXPECT_EQ(1, swscanf(L"xĀyz", L"%l[xy]", buf));
+  EXPECT_EQ(L"x"s, std::wstring(buf));
+
+  // Even if scanf has wide characters in a class, they won't match...
+  // TODO: is that a bug?
+  memset(buf, 0, sizeof(buf));
+  EXPECT_EQ(1, sscanf("xĀyz", "%l[xĀy]", buf));
+  EXPECT_EQ(L"x"s, std::wstring(buf));
+  // ...unless you use wscanf.
+  memset(buf, 0, sizeof(buf));
+  EXPECT_EQ(1, swscanf(L"xĀyz", L"%l[xĀy]", buf));
+  EXPECT_EQ(L"xĀy"s, std::wstring(buf));
+
+  // Negation only covers ASCII for scanf...
+  memset(buf, 0, sizeof(buf));
+  EXPECT_EQ(1, sscanf("xĀyz", "%l[^ab]", buf));
+  EXPECT_EQ(L"x"s, std::wstring(buf));
+  // ...but covers wide characters for wscanf.
+  memset(buf, 0, sizeof(buf));
+  EXPECT_EQ(1, swscanf(L"xĀyz", L"%l[^ab]", buf));
+  EXPECT_EQ(L"xĀyz"s, std::wstring(buf));
+
+  // We already determined that non-ASCII characters are ignored in scanf classes.
+  memset(buf, 0, sizeof(buf));
+  EXPECT_EQ(1, sscanf("x"
+                      "\xc4\x80" // Matches a byte from each wide char in the class.
+                      "\xc6\x82" // Neither byte is in the class.
+                      "yz",
+                      "%l[xy" "\xc5\x80" "\xc4\x81" "]", buf));
+  EXPECT_EQ(L"x", std::wstring(buf));
+  // bionic and glibc both behave badly for wscanf, so let's call it right for now...
+  memset(buf, 0, sizeof(buf));
+  EXPECT_EQ(1, swscanf(L"x"
+                       L"\xc4\x80"
+                       L"\xc6\x82"
+                       L"yz",
+                       L"%l[xy" L"\xc5\x80" L"\xc4\x81" L"]", buf));
+  // Note that this isn't L"xĀ" --- although the *bytes* matched, they're
+  // not put back together as a wide character.
+  EXPECT_EQ(L"x" L"\xc4" L"\x80", std::wstring(buf));
+#endif
+}
+
 TEST(STDIO_TEST, cantwrite_EBADF) {
   // If we open a file read-only...
   FILE* fp = fopen("/proc/version", "r");
diff --git a/tests/stdlib_test.cpp b/tests/stdlib_test.cpp
index 0c40380..ed5767c 100644
--- a/tests/stdlib_test.cpp
+++ b/tests/stdlib_test.cpp
@@ -138,6 +138,47 @@
   EXPECT_EQ(795539493, mrand48());
 }
 
+TEST(stdlib, jrand48_distribution) {
+  const int iterations = 4096;
+  const int pivot_low  = 1536;
+  const int pivot_high = 2560;
+
+  unsigned short xsubi[3];
+  int bits[32] = {};
+
+  for (int iter = 0; iter < iterations; ++iter) {
+    long rand_val = jrand48(xsubi);
+    for (int bit = 0; bit < 32; ++bit) {
+      bits[bit] += (static_cast<unsigned long>(rand_val) >> bit) & 0x01;
+    }
+  }
+
+  // Check that bit probability is uniform
+  for (int bit = 0; bit < 32; ++bit) {
+    EXPECT_TRUE((pivot_low <= bits[bit]) && (bits[bit] <= pivot_high));
+  }
+}
+
+TEST(stdlib, mrand48_distribution) {
+  const int iterations = 4096;
+  const int pivot_low  = 1536;
+  const int pivot_high = 2560;
+
+  int bits[32] = {};
+
+  for (int iter = 0; iter < iterations; ++iter) {
+    long rand_val = mrand48();
+    for (int bit = 0; bit < 32; ++bit) {
+      bits[bit] += (static_cast<unsigned long>(rand_val) >> bit) & 0x01;
+    }
+  }
+
+  // Check that bit probability is uniform
+  for (int bit = 0; bit < 32; ++bit) {
+    EXPECT_TRUE((pivot_low <= bits[bit]) && (bits[bit] <= pivot_high));
+  }
+}
+
 TEST(stdlib, posix_memalign_sweep) {
   void* ptr;
 
diff --git a/tests/time_test.cpp b/tests/time_test.cpp
index e1fae92..7a96760 100644
--- a/tests/time_test.cpp
+++ b/tests/time_test.cpp
@@ -43,6 +43,19 @@
   ASSERT_EQ(1970, broken_down->tm_year + 1900);
 }
 
+TEST(time, gmtime_r) {
+  struct tm tm = {};
+  time_t t = 0;
+  struct tm* broken_down = gmtime_r(&t, &tm);
+  ASSERT_EQ(broken_down, &tm);
+  ASSERT_EQ(0, broken_down->tm_sec);
+  ASSERT_EQ(0, broken_down->tm_min);
+  ASSERT_EQ(0, broken_down->tm_hour);
+  ASSERT_EQ(1, broken_down->tm_mday);
+  ASSERT_EQ(0, broken_down->tm_mon);
+  ASSERT_EQ(1970, broken_down->tm_year + 1900);
+}
+
 static void* gmtime_no_stack_overflow_14313703_fn(void*) {
   const char* original_tz = getenv("TZ");
   // Ensure we'll actually have to enter tzload by using a time zone that doesn't exist.
@@ -790,3 +803,29 @@
   // The BSDs agree with us, but glibc gets this wrong.
 #endif
 }
+
+TEST(time, asctime) {
+  const struct tm tm = {};
+  ASSERT_STREQ("Sun Jan  0 00:00:00 1900\n", asctime(&tm));
+}
+
+TEST(time, asctime_r) {
+  const struct tm tm = {};
+  char buf[256];
+  ASSERT_EQ(buf, asctime_r(&tm, buf));
+  ASSERT_STREQ("Sun Jan  0 00:00:00 1900\n", buf);
+}
+
+TEST(time, ctime) {
+  setenv("TZ", "UTC", 1);
+  const time_t t = 0;
+  ASSERT_STREQ("Thu Jan  1 00:00:00 1970\n", ctime(&t));
+}
+
+TEST(time, ctime_r) {
+  setenv("TZ", "UTC", 1);
+  const time_t t = 0;
+  char buf[256];
+  ASSERT_EQ(buf, ctime_r(&t, buf));
+  ASSERT_STREQ("Thu Jan  1 00:00:00 1970\n", buf);
+}