Merge "Split properties into their own class to make testing better"
diff --git a/benchmarks/Android.bp b/benchmarks/Android.bp
index c7c8a29..be61eda 100644
--- a/benchmarks/Android.bp
+++ b/benchmarks/Android.bp
@@ -61,6 +61,11 @@
     name: "bionic-benchmarks",
     defaults: ["bionic-benchmarks-defaults"],
     data: ["suites/*"],
+    static_libs: [
+        "libsystemproperties",
+        "libasync_safe",
+    ],
+    include_dirs: ["bionic/libc",],
 }
 
 // We don't build a static benchmark executable because it's not usually
diff --git a/benchmarks/property_benchmark.cpp b/benchmarks/property_benchmark.cpp
index 5760bf1..9868765 100644
--- a/benchmarks/property_benchmark.cpp
+++ b/benchmarks/property_benchmark.cpp
@@ -21,37 +21,28 @@
 
 #include <string>
 
+#include <android-base/test_utils.h>
+
+using namespace std::literals;
+
 #if defined(__BIONIC__)
 
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
 #include <sys/_system_properties.h>
 
 #include <benchmark/benchmark.h>
+#include <system_properties/system_properties.h>
 #include "util.h"
 
 struct LocalPropertyTestState {
-  explicit LocalPropertyTestState(int nprops) : nprops(nprops), valid(false) {
+  explicit LocalPropertyTestState(int nprops)
+      : nprops(nprops), valid(false), system_properties_(false) {
     static const char prop_name_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.";
 
-    const char* android_data = getenv("ANDROID_DATA");
-    if (android_data == NULL) {
-      printf("ANDROID_DATA environment variable not set\n");
+    valid = system_properties_.AreaInit(dir_.path, nullptr);
+    if (!valid) {
       return;
     }
-    char dir_template[PATH_MAX];
-    snprintf(dir_template, sizeof(dir_template), "%s/local/tmp/prop-XXXXXX", android_data);
-    char* dirname = mkdtemp(dir_template);
-    if (!dirname) {
-      printf("making temp file for test state failed (is %s/local/tmp writable?): %s\n",
-             android_data, strerror(errno));
-      return;
-    }
-
-    pa_dirname = dirname;
-    pa_filename = pa_dirname + "/__properties__";
-
-    __system_property_set_filename(pa_filename.c_str());
-    __system_property_area_init();
 
     names = new char* [nprops];
     name_lens = new int[nprops];
@@ -88,7 +79,7 @@
         values[i][j] = prop_name_chars[random() % (sizeof(prop_name_chars) - 1)];
       }
 
-      if (__system_property_add(names[i], name_lens[i], values[i], value_lens[i]) < 0) {
+      if (system_properties_.Add(names[i], name_lens[i], values[i], value_lens[i]) < 0) {
         printf("Failed to add a property, terminating...\n");
         printf("%s = %.*s\n", names[i], value_lens[i], values[i]);
         exit(1);
@@ -98,14 +89,16 @@
     valid = true;
   }
 
-  ~LocalPropertyTestState() {
-    if (!valid)
-      return;
+  SystemProperties& system_properties() {
+    return system_properties_;
+  }
 
-    __system_property_set_filename(PROP_FILENAME);
-    __system_property_area_init();
-    unlink(pa_filename.c_str());
-    rmdir(pa_dirname.c_str());
+  ~LocalPropertyTestState() {
+    if (!valid) {
+      return;
+    }
+
+    system_properties_.contexts()->FreeAndUnmap();
 
     for (int i = 0; i < nprops; i++) {
       delete names[i];
@@ -126,8 +119,8 @@
   bool valid;
 
  private:
-  std::string pa_dirname;
-  std::string pa_filename;
+  SystemProperties system_properties_;
+  TemporaryDir dir_;
 };
 
 static void BM_property_get(benchmark::State& state) {
@@ -138,7 +131,7 @@
 
   while (state.KeepRunning()) {
     char value[PROP_VALUE_MAX];
-    __system_property_get(pa.names[random() % nprops], value);
+    pa.system_properties().Get(pa.names[random() % nprops], value);
   }
 }
 BIONIC_BENCHMARK_WITH_ARG(BM_property_get, "NUM_PROPS");
@@ -150,7 +143,7 @@
   if (!pa.valid) return;
 
   while (state.KeepRunning()) {
-    __system_property_find(pa.names[random() % nprops]);
+    pa.system_properties().Find(pa.names[random() % nprops]);
   }
 }
 BIONIC_BENCHMARK_WITH_ARG(BM_property_find, "NUM_PROPS");
@@ -165,12 +158,12 @@
   char propvalue[PROP_VALUE_MAX];
 
   for (size_t i = 0; i < nprops; ++i) {
-    pinfo[i] = __system_property_find(pa.names[random() % nprops]);
+    pinfo[i] = pa.system_properties().Find(pa.names[random() % nprops]);
   }
 
   size_t i = 0;
   while (state.KeepRunning()) {
-    __system_property_read(pinfo[i], 0, propvalue);
+    pa.system_properties().Read(pinfo[i], 0, propvalue);
     i = (i + 1) % nprops;
   }
 
@@ -186,12 +179,12 @@
 
   const prop_info** pinfo = new const prop_info*[nprops];
   for (size_t i = 0; i < nprops; ++i) {
-    pinfo[i] = __system_property_find(pa.names[random() % nprops]);
+    pinfo[i] = pa.system_properties().Find(pa.names[random() % nprops]);
   }
 
   size_t i = 0;
   while (state.KeepRunning()) {
-    __system_property_serial(pinfo[i]);
+    pa.system_properties().Serial(pinfo[i]);
     i = (i + 1) % nprops;
   }
 
diff --git a/libc/Android.bp b/libc/Android.bp
index 1017fa5..4db4f56 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -1387,6 +1387,8 @@
         "bionic/sys_time.cpp",
         "bionic/sysinfo.cpp",
         "bionic/syslog.cpp",
+        "bionic/system_property_api.cpp",
+        "bionic/system_property_set.cpp",
         "bionic/tdestroy.cpp",
         "bionic/termios.cpp",
         "bionic/thread_private.cpp",
@@ -1400,13 +1402,6 @@
         "bionic/wctype.cpp",
         "bionic/wcwidth.cpp",
         "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",
-
 
         // This contains a weak stub implementation of __find_icu_symbol for wctype.cpp,
         // which will be overridden by the actual one in libc.so.
@@ -1424,7 +1419,7 @@
             cflags: ["-DTREBLE_LINKER_NAMESPACES"],
         },
     },
-    whole_static_libs: ["libpropertyinfoparser"],
+    whole_static_libs: ["libsystemproperties"],
     cppflags: ["-Wold-style-cast"],
     local_include_dirs: ["stdio"],
     include_dirs: ["bionic/libstdc++/include"],
diff --git a/libc/bionic/system_property_api.cpp b/libc/bionic/system_property_api.cpp
new file mode 100644
index 0000000..f2e1032
--- /dev/null
+++ b/libc/bionic/system_property_api.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <sys/_system_properties.h>
+
+#include <system_properties/prop_area.h>
+#include <system_properties/system_properties.h>
+
+#include "private/bionic_defs.h"
+
+static SystemProperties system_properties;
+
+// This is public because it was exposed in the NDK. As of 2017-01, ~60 apps reference this symbol.
+// It is set to nullptr and never modified.
+__BIONIC_WEAK_VARIABLE_FOR_NATIVE_BRIDGE
+prop_area* __system_property_area__ = nullptr;
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_properties_init() {
+  return system_properties.Init(PROP_FILENAME) ? 0 : -1;
+}
+
+// This was previously for testing, but now that SystemProperties is its own testable class,
+// there is never a reason to call this function.
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_property_set_filename(const char*) {
+  return -1;
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_property_area_init() {
+  bool fsetxattr_failed = false;
+  return system_properties.AreaInit(PROP_FILENAME, &fsetxattr_failed) && !fsetxattr_failed ? 0 : -1;
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+uint32_t __system_property_area_serial() {
+  return system_properties.AreaSerial();
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+const prop_info* __system_property_find(const char* name) {
+  return system_properties.Find(name);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_property_read(const prop_info* pi, char* name, char* value) {
+  return system_properties.Read(pi, name, value);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+void __system_property_read_callback(const prop_info* pi,
+                                     void (*callback)(void* cookie, const char* name,
+                                                      const char* value, uint32_t serial),
+                                     void* cookie) {
+  return system_properties.ReadCallback(pi, callback, cookie);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_property_get(const char* name, char* value) {
+  return system_properties.Get(name, value);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_property_update(prop_info* pi, const char* value, unsigned int len) {
+  return system_properties.Update(pi, value, len);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_property_add(const char* name, unsigned int namelen, const char* value,
+                          unsigned int valuelen) {
+  return system_properties.Add(name, namelen, value, valuelen);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+uint32_t __system_property_serial(const prop_info* pi) {
+  return system_properties.Serial(pi);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+uint32_t __system_property_wait_any(uint32_t old_serial) {
+  return system_properties.WaitAny(old_serial);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+bool __system_property_wait(const prop_info* pi, uint32_t old_serial, uint32_t* new_serial_ptr,
+                            const timespec* relative_timeout) {
+  return system_properties.Wait(pi, old_serial, new_serial_ptr, relative_timeout);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+const prop_info* __system_property_find_nth(unsigned n) {
+  return system_properties.FindNth(n);
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_property_foreach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) {
+  return system_properties.Foreach(propfn, cookie);
+}
diff --git a/libc/bionic/system_property_set.cpp b/libc/bionic/system_property_set.cpp
new file mode 100644
index 0000000..a70a376
--- /dev/null
+++ b/libc/bionic/system_property_set.cpp
@@ -0,0 +1,314 @@
+/*
+ * 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 <errno.h>
+#include <poll.h>
+#include <stdatomic.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <sys/_system_properties.h>
+#include <unistd.h>
+
+#include <async_safe/log.h>
+
+#include "private/bionic_defs.h"
+#include "private/bionic_macros.h"
+
+static const char property_service_socket[] = "/dev/socket/" PROP_SERVICE_NAME;
+static const char* kServiceVersionPropertyName = "ro.property_service.version";
+
+class PropertyServiceConnection {
+ public:
+  PropertyServiceConnection() : last_error_(0) {
+    socket_ = ::socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    if (socket_ == -1) {
+      last_error_ = errno;
+      return;
+    }
+
+    const size_t namelen = strlen(property_service_socket);
+    sockaddr_un addr;
+    memset(&addr, 0, sizeof(addr));
+    strlcpy(addr.sun_path, property_service_socket, sizeof(addr.sun_path));
+    addr.sun_family = AF_LOCAL;
+    socklen_t alen = namelen + offsetof(sockaddr_un, sun_path) + 1;
+
+    if (TEMP_FAILURE_RETRY(connect(socket_, reinterpret_cast<sockaddr*>(&addr), alen)) == -1) {
+      last_error_ = errno;
+      close(socket_);
+      socket_ = -1;
+    }
+  }
+
+  bool IsValid() {
+    return socket_ != -1;
+  }
+
+  int GetLastError() {
+    return last_error_;
+  }
+
+  bool RecvInt32(int32_t* value) {
+    int result = TEMP_FAILURE_RETRY(recv(socket_, value, sizeof(*value), MSG_WAITALL));
+    return CheckSendRecvResult(result, sizeof(*value));
+  }
+
+  int socket() {
+    return socket_;
+  }
+
+  ~PropertyServiceConnection() {
+    if (socket_ != -1) {
+      close(socket_);
+    }
+  }
+
+ private:
+  bool CheckSendRecvResult(int result, int expected_len) {
+    if (result == -1) {
+      last_error_ = errno;
+    } else if (result != expected_len) {
+      last_error_ = -1;
+    } else {
+      last_error_ = 0;
+    }
+
+    return last_error_ == 0;
+  }
+
+  int socket_;
+  int last_error_;
+
+  friend class SocketWriter;
+};
+
+class SocketWriter {
+ public:
+  explicit SocketWriter(PropertyServiceConnection* connection)
+      : connection_(connection), iov_index_(0), uint_buf_index_(0) {
+  }
+
+  SocketWriter& WriteUint32(uint32_t value) {
+    CHECK(uint_buf_index_ < kUintBufSize);
+    CHECK(iov_index_ < kIovSize);
+    uint32_t* ptr = uint_buf_ + uint_buf_index_;
+    uint_buf_[uint_buf_index_++] = value;
+    iov_[iov_index_].iov_base = ptr;
+    iov_[iov_index_].iov_len = sizeof(*ptr);
+    ++iov_index_;
+    return *this;
+  }
+
+  SocketWriter& WriteString(const char* value) {
+    uint32_t valuelen = strlen(value);
+    WriteUint32(valuelen);
+    if (valuelen == 0) {
+      return *this;
+    }
+
+    CHECK(iov_index_ < kIovSize);
+    iov_[iov_index_].iov_base = const_cast<char*>(value);
+    iov_[iov_index_].iov_len = valuelen;
+    ++iov_index_;
+
+    return *this;
+  }
+
+  bool Send() {
+    if (!connection_->IsValid()) {
+      return false;
+    }
+
+    if (writev(connection_->socket(), iov_, iov_index_) == -1) {
+      connection_->last_error_ = errno;
+      return false;
+    }
+
+    iov_index_ = uint_buf_index_ = 0;
+    return true;
+  }
+
+ private:
+  static constexpr size_t kUintBufSize = 8;
+  static constexpr size_t kIovSize = 8;
+
+  PropertyServiceConnection* connection_;
+  iovec iov_[kIovSize];
+  size_t iov_index_;
+  uint32_t uint_buf_[kUintBufSize];
+  size_t uint_buf_index_;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(SocketWriter);
+};
+
+struct prop_msg {
+  unsigned cmd;
+  char name[PROP_NAME_MAX];
+  char value[PROP_VALUE_MAX];
+};
+
+static int send_prop_msg(const prop_msg* msg) {
+  PropertyServiceConnection connection;
+  if (!connection.IsValid()) {
+    return connection.GetLastError();
+  }
+
+  int result = -1;
+  int s = connection.socket();
+
+  const int num_bytes = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0));
+  if (num_bytes == sizeof(prop_msg)) {
+    // We successfully wrote to the property server but now we
+    // wait for the property server to finish its work.  It
+    // acknowledges its completion by closing the socket so we
+    // poll here (on nothing), waiting for the socket to close.
+    // If you 'adb shell setprop foo bar' you'll see the POLLHUP
+    // once the socket closes.  Out of paranoia we cap our poll
+    // at 250 ms.
+    pollfd pollfds[1];
+    pollfds[0].fd = s;
+    pollfds[0].events = 0;
+    const int poll_result = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
+    if (poll_result == 1 && (pollfds[0].revents & POLLHUP) != 0) {
+      result = 0;
+    } else {
+      // Ignore the timeout and treat it like a success anyway.
+      // The init process is single-threaded and its property
+      // service is sometimes slow to respond (perhaps it's off
+      // starting a child process or something) and thus this
+      // times out and the caller thinks it failed, even though
+      // it's still getting around to it.  So we fake it here,
+      // mostly for ctl.* properties, but we do try and wait 250
+      // ms so callers who do read-after-write can reliably see
+      // what they've written.  Most of the time.
+      // TODO: fix the system properties design.
+      async_safe_format_log(ANDROID_LOG_WARN, "libc",
+                            "Property service has timed out while trying to set \"%s\" to \"%s\"",
+                            msg->name, msg->value);
+      result = 0;
+    }
+  }
+
+  return result;
+}
+
+static constexpr uint32_t kProtocolVersion1 = 1;
+static constexpr uint32_t kProtocolVersion2 = 2;  // current
+
+static atomic_uint_least32_t g_propservice_protocol_version = 0;
+
+static void detect_protocol_version() {
+  char value[PROP_VALUE_MAX];
+  if (__system_property_get(kServiceVersionPropertyName, value) == 0) {
+    g_propservice_protocol_version = kProtocolVersion1;
+    async_safe_format_log(ANDROID_LOG_WARN, "libc",
+                          "Using old property service protocol (\"%s\" is not set)",
+                          kServiceVersionPropertyName);
+  } else {
+    uint32_t version = static_cast<uint32_t>(atoll(value));
+    if (version >= kProtocolVersion2) {
+      g_propservice_protocol_version = kProtocolVersion2;
+    } else {
+      async_safe_format_log(ANDROID_LOG_WARN, "libc",
+                            "Using old property service protocol (\"%s\"=\"%s\")",
+                            kServiceVersionPropertyName, value);
+      g_propservice_protocol_version = kProtocolVersion1;
+    }
+  }
+}
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_property_set(const char* key, const char* value) {
+  if (key == nullptr) return -1;
+  if (value == nullptr) value = "";
+
+  if (g_propservice_protocol_version == 0) {
+    detect_protocol_version();
+  }
+
+  if (g_propservice_protocol_version == kProtocolVersion1) {
+    // Old protocol does not support long names or values
+    if (strlen(key) >= PROP_NAME_MAX) return -1;
+    if (strlen(value) >= PROP_VALUE_MAX) return -1;
+
+    prop_msg msg;
+    memset(&msg, 0, sizeof msg);
+    msg.cmd = PROP_MSG_SETPROP;
+    strlcpy(msg.name, key, sizeof msg.name);
+    strlcpy(msg.value, value, sizeof msg.value);
+
+    return send_prop_msg(&msg);
+  } else {
+    // New protocol only allows long values for ro. properties only.
+    if (strlen(value) >= PROP_VALUE_MAX && strncmp(key, "ro.", 3) != 0) return -1;
+    // Use proper protocol
+    PropertyServiceConnection connection;
+    if (!connection.IsValid()) {
+      errno = connection.GetLastError();
+      async_safe_format_log(
+          ANDROID_LOG_WARN, "libc",
+          "Unable to set property \"%s\" to \"%s\": connection failed; errno=%d (%s)", key, value,
+          errno, strerror(errno));
+      return -1;
+    }
+
+    SocketWriter writer(&connection);
+    if (!writer.WriteUint32(PROP_MSG_SETPROP2).WriteString(key).WriteString(value).Send()) {
+      errno = connection.GetLastError();
+      async_safe_format_log(ANDROID_LOG_WARN, "libc",
+                            "Unable to set property \"%s\" to \"%s\": write failed; errno=%d (%s)",
+                            key, value, errno, strerror(errno));
+      return -1;
+    }
+
+    int result = -1;
+    if (!connection.RecvInt32(&result)) {
+      errno = connection.GetLastError();
+      async_safe_format_log(ANDROID_LOG_WARN, "libc",
+                            "Unable to set property \"%s\" to \"%s\": recv failed; errno=%d (%s)",
+                            key, value, errno, strerror(errno));
+      return -1;
+    }
+
+    if (result != PROP_SUCCESS) {
+      async_safe_format_log(ANDROID_LOG_WARN, "libc",
+                            "Unable to set property \"%s\" to \"%s\": error code: 0x%x", key, value,
+                            result);
+      return -1;
+    }
+
+    return 0;
+  }
+}
diff --git a/libc/system_properties/Android.bp b/libc/system_properties/Android.bp
new file mode 100644
index 0000000..f94cda9
--- /dev/null
+++ b/libc/system_properties/Android.bp
@@ -0,0 +1,24 @@
+cc_library_static {
+    name: "libsystemproperties",
+    defaults: ["libc_defaults"],
+    srcs: [
+        "context_node.cpp",
+        "contexts_split.cpp",
+        "contexts_serialized.cpp",
+        "prop_area.cpp",
+        "prop_info.cpp",
+        "system_properties.cpp",
+    ],
+    whole_static_libs: [
+        "libpropertyinfoparser",
+    ],
+    static_libs: [
+        "libasync_safe",
+    ],
+
+    include_dirs: [
+        "bionic/libc",
+        "bionic/libstdc++/include",
+    ],
+    export_include_dirs: ["include"],
+}
diff --git a/libc/system_properties/context_node.cpp b/libc/system_properties/context_node.cpp
index 13cef75..5496b5a 100644
--- a/libc/system_properties/context_node.cpp
+++ b/libc/system_properties/context_node.cpp
@@ -26,13 +26,14 @@
  * SUCH DAMAGE.
  */
 
-#include "context_node.h"
+#include "system_properties/context_node.h"
 
+#include <limits.h>
 #include <unistd.h>
 
 #include <async_safe/log.h>
 
-#include "property_filename.h"
+#include "system_properties/system_properties.h"
 
 // pthread_mutex_lock() calls into system_properties in the case of contention.
 // This creates a risk of dead lock if any system_properties functions
@@ -49,8 +50,7 @@
   }
 
   char filename[PROP_FILENAME_MAX];
-  int len =
-      async_safe_format_buffer(filename, sizeof(filename), "%s/%s", property_filename, context_);
+  int len = async_safe_format_buffer(filename, sizeof(filename), "%s/%s", filename_, context_);
   if (len < 0 || len > PROP_FILENAME_MAX) {
     lock_.unlock();
     return false;
@@ -85,8 +85,7 @@
 
 bool ContextNode::CheckAccess() {
   char filename[PROP_FILENAME_MAX];
-  int len =
-      async_safe_format_buffer(filename, sizeof(filename), "%s/%s", property_filename, context_);
+  int len = async_safe_format_buffer(filename, sizeof(filename), "%s/%s", filename_, context_);
   if (len < 0 || len > PROP_FILENAME_MAX) {
     return false;
   }
diff --git a/libc/system_properties/contexts_serialized.cpp b/libc/system_properties/contexts_serialized.cpp
index 117b5cf..062e8a5 100644
--- a/libc/system_properties/contexts_serialized.cpp
+++ b/libc/system_properties/contexts_serialized.cpp
@@ -26,9 +26,10 @@
  * SUCH DAMAGE.
  */
 
-#include "contexts_serialized.h"
+#include "system_properties/contexts_serialized.h"
 
 #include <fcntl.h>
+#include <limits.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -38,7 +39,7 @@
 #include <async_safe/log.h>
 
 #include "private/bionic_prctl.h"
-#include "property_filename.h"
+#include "system_properties/system_properties.h"
 
 bool ContextsSerialized::InitializeContextNodes() {
   auto num_context_nodes = property_info_area_file_->num_contexts();
@@ -58,7 +59,7 @@
   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));
+    new (&context_nodes_[i]) ContextNode(property_info_area_file_->context(i), filename_);
   }
 
   return true;
@@ -66,8 +67,7 @@
 
 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);
+  int len = async_safe_format_buffer(filename, sizeof(filename), "%s/properties_serial", filename_);
   if (len < 0 || len > PROP_FILENAME_MAX) {
     serial_prop_area_ = nullptr;
     return false;
@@ -95,27 +95,28 @@
   return true;
 }
 
-bool ContextsSerialized::Initialize(bool writable) {
+bool ContextsSerialized::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
+  filename_ = filename;
   if (!InitializeProperties()) {
     return false;
   }
 
   if (writable) {
-    mkdir(property_filename, S_IRWXU | S_IXGRP | S_IXOTH);
+    mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);
     bool open_failed = false;
-    bool fsetxattr_failed = false;
+    if (fsetxattr_failed) {
+      *fsetxattr_failed = false;
+    }
 
     for (size_t i = 0; i < num_context_nodes_; ++i) {
-      if (!context_nodes_[i].Open(true, &fsetxattr_failed)) {
+      if (!context_nodes_[i].Open(true, fsetxattr_failed)) {
         open_failed = true;
       }
     }
-    if (open_failed || !MapSerialPropertyArea(true, &fsetxattr_failed)) {
+    if (open_failed || !MapSerialPropertyArea(true, fsetxattr_failed)) {
       FreeAndUnmap();
       return false;
     }
-
-    return !fsetxattr_failed;
   } else {
     if (!MapSerialPropertyArea(false, nullptr)) {
       FreeAndUnmap();
diff --git a/libc/system_properties/contexts_split.cpp b/libc/system_properties/contexts_split.cpp
index 77f2069..b8afa29 100644
--- a/libc/system_properties/contexts_split.cpp
+++ b/libc/system_properties/contexts_split.cpp
@@ -26,22 +26,23 @@
  * SUCH DAMAGE.
  */
 
-#include "contexts_split.h"
+#include "system_properties/contexts_split.h"
 
 #include <ctype.h>
+#include <limits.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 
 #include <async_safe/log.h>
 
-#include "context_node.h"
-#include "property_filename.h"
+#include "system_properties/context_node.h"
+#include "system_properties/system_properties.h"
 
 class ContextListNode : public ContextNode {
  public:
-  ContextListNode(ContextListNode* next, const char* context)
-      : ContextNode(strdup(context)), next(next) {
+  ContextListNode(ContextListNode* next, const char* context, const char* filename)
+      : ContextNode(strdup(context), filename), next(next) {
   }
 
   ~ContextListNode() {
@@ -194,8 +195,7 @@
 
 bool ContextsSplit::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);
+  int len = async_safe_format_buffer(filename, sizeof(filename), "%s/properties_serial", filename_);
   if (len < 0 || len > PROP_FILENAME_MAX) {
     serial_prop_area_ = nullptr;
     return false;
@@ -245,7 +245,7 @@
     if (old_context) {
       ListAddAfterLen(&prefixes_, prop_prefix, old_context);
     } else {
-      ListAdd(&contexts_, context);
+      ListAdd(&contexts_, context, filename_);
       ListAddAfterLen(&prefixes_, prop_prefix, contexts_);
     }
     free(prop_prefix);
@@ -285,26 +285,28 @@
   return true;
 }
 
-bool ContextsSplit::Initialize(bool writable) {
+bool ContextsSplit::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
+  filename_ = filename;
   if (!InitializeProperties()) {
     return false;
   }
 
   if (writable) {
-    mkdir(property_filename, S_IRWXU | S_IXGRP | S_IXOTH);
+    mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);
     bool open_failed = false;
-    bool fsetxattr_failed = false;
+    if (fsetxattr_failed) {
+      *fsetxattr_failed = false;
+    }
+
     ListForEach(contexts_, [&fsetxattr_failed, &open_failed](ContextListNode* l) {
-      if (!l->Open(true, &fsetxattr_failed)) {
+      if (!l->Open(true, fsetxattr_failed)) {
         open_failed = true;
       }
     });
-    if (open_failed || !MapSerialPropertyArea(true, &fsetxattr_failed)) {
+    if (open_failed || !MapSerialPropertyArea(true, fsetxattr_failed)) {
       FreeAndUnmap();
       return false;
     }
-
-    return !fsetxattr_failed;
   } else {
     if (!MapSerialPropertyArea(false, nullptr)) {
       FreeAndUnmap();
diff --git a/libc/system_properties/context_node.h b/libc/system_properties/include/system_properties/context_node.h
similarity index 87%
rename from libc/system_properties/context_node.h
rename to libc/system_properties/include/system_properties/context_node.h
index 769b853..6a3bbcd 100644
--- a/libc/system_properties/context_node.h
+++ b/libc/system_properties/include/system_properties/context_node.h
@@ -35,17 +35,15 @@
 
 class ContextNode {
  public:
-  ContextNode(const char* context) : context_(context), pa_(nullptr), no_access_(false) {
+  ContextNode(const char* context, const char* filename)
+      : context_(context), pa_(nullptr), no_access_(false), filename_(filename) {
     lock_.init(false);
   }
   ~ContextNode() {
     Unmap();
   }
 
-  ContextNode(const ContextNode&) = delete;
-  ContextNode(ContextNode&&) = delete;
-  void operator=(const ContextNode&) = delete;
-  void operator=(const ContextNode&&) = delete;
+  DISALLOW_COPY_AND_ASSIGN(ContextNode);
 
   bool Open(bool access_rw, bool* fsetxattr_failed);
   bool CheckAccessAndOpen();
@@ -66,6 +64,7 @@
   const char* context_;
   prop_area* pa_;
   bool no_access_;
+  const char* filename_;
 };
 
 #endif
diff --git a/libc/system_properties/contexts.h b/libc/system_properties/include/system_properties/contexts.h
similarity index 95%
rename from libc/system_properties/contexts.h
rename to libc/system_properties/include/system_properties/contexts.h
index 5df9d96..8f154ed 100644
--- a/libc/system_properties/contexts.h
+++ b/libc/system_properties/include/system_properties/contexts.h
@@ -37,7 +37,7 @@
   virtual ~Contexts() {
   }
 
-  virtual bool Initialize(bool writable) = 0;
+  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed) = 0;
   virtual prop_area* GetPropAreaForName(const char* name) = 0;
   virtual prop_area* GetSerialPropArea() = 0;
   virtual void ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) = 0;
diff --git a/libc/system_properties/contexts_pre_split.h b/libc/system_properties/include/system_properties/contexts_pre_split.h
similarity index 94%
rename from libc/system_properties/contexts_pre_split.h
rename to libc/system_properties/include/system_properties/contexts_pre_split.h
index bbc8529..14b00f1 100644
--- a/libc/system_properties/contexts_pre_split.h
+++ b/libc/system_properties/include/system_properties/contexts_pre_split.h
@@ -32,7 +32,6 @@
 #include "contexts.h"
 #include "prop_area.h"
 #include "prop_info.h"
-#include "property_filename.h"
 
 class ContextsPreSplit : public Contexts {
  public:
@@ -40,8 +39,8 @@
   }
 
   // We'll never initialize this legacy option as writable, so don't even check the arg.
-  virtual bool Initialize(bool) override {
-    pre_split_prop_area_ = prop_area::map_prop_area(property_filename);
+  virtual bool Initialize(bool, const char* filename, bool*) override {
+    pre_split_prop_area_ = prop_area::map_prop_area(filename);
     return pre_split_prop_area_ != nullptr;
   }
 
diff --git a/libc/system_properties/contexts_serialized.h b/libc/system_properties/include/system_properties/contexts_serialized.h
similarity index 94%
rename from libc/system_properties/contexts_serialized.h
rename to libc/system_properties/include/system_properties/contexts_serialized.h
index 52474c3..010730a 100644
--- a/libc/system_properties/contexts_serialized.h
+++ b/libc/system_properties/include/system_properties/contexts_serialized.h
@@ -39,7 +39,7 @@
   virtual ~ContextsSerialized() override {
   }
 
-  virtual bool Initialize(bool writable) override;
+  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed) override;
   virtual prop_area* GetPropAreaForName(const char* name) override;
   virtual prop_area* GetSerialPropArea() override {
     return serial_prop_area_;
@@ -53,6 +53,7 @@
   bool InitializeProperties();
   bool MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed);
 
+  const char* filename_;
   android::properties::PropertyInfoAreaFile property_info_area_file_;
   ContextNode* context_nodes_ = nullptr;
   size_t num_context_nodes_ = 0;
diff --git a/libc/system_properties/contexts_split.h b/libc/system_properties/include/system_properties/contexts_split.h
similarity index 94%
rename from libc/system_properties/contexts_split.h
rename to libc/system_properties/include/system_properties/contexts_split.h
index f98eb44..06c405a 100644
--- a/libc/system_properties/contexts_split.h
+++ b/libc/system_properties/include/system_properties/contexts_split.h
@@ -39,7 +39,7 @@
   virtual ~ContextsSplit() override {
   }
 
-  virtual bool Initialize(bool writable) override;
+  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed) override;
   virtual prop_area* GetPropAreaForName(const char* name) override;
   virtual prop_area* GetSerialPropArea() override {
     return serial_prop_area_;
@@ -56,6 +56,7 @@
   PrefixNode* prefixes_ = nullptr;
   ContextListNode* contexts_ = nullptr;
   prop_area* serial_prop_area_ = nullptr;
+  const char* filename_ = nullptr;
 };
 
 #endif
diff --git a/libc/system_properties/prop_area.h b/libc/system_properties/include/system_properties/prop_area.h
similarity index 99%
rename from libc/system_properties/prop_area.h
rename to libc/system_properties/include/system_properties/prop_area.h
index 10c1adb..a7d5d22 100644
--- a/libc/system_properties/prop_area.h
+++ b/libc/system_properties/include/system_properties/prop_area.h
@@ -103,7 +103,7 @@
   }
 
   prop_area(const uint32_t magic, const uint32_t version) : magic_(magic), version_(version) {
-    atomic_init(&serial_, 0);
+    atomic_init(&serial_, 0u);
     memset(reserved_, 0, sizeof(reserved_));
     // Allocate enough space for the root node.
     bytes_used_ = sizeof(prop_bt);
diff --git a/libc/system_properties/prop_info.h b/libc/system_properties/include/system_properties/prop_info.h
similarity index 100%
rename from libc/system_properties/prop_info.h
rename to libc/system_properties/include/system_properties/prop_info.h
diff --git a/libc/system_properties/include/system_properties/system_properties.h b/libc/system_properties/include/system_properties/system_properties.h
new file mode 100644
index 0000000..c74f875
--- /dev/null
+++ b/libc/system_properties/include/system_properties/system_properties.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 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_SYSTEM_PROPERTIES_H
+#define SYSTEM_PROPERTIES_SYSTEM_PROPERTIES_H
+
+#include <stdint.h>
+#include <sys/system_properties.h>
+
+#include "contexts.h"
+#include "contexts_pre_split.h"
+#include "contexts_serialized.h"
+#include "contexts_split.h"
+
+constexpr int PROP_FILENAME_MAX = 1024;
+
+class SystemProperties {
+ public:
+  // Note that system properties are initialized before libc calls static initializers, so
+  // doing any initialization in this constructor is an error.  Even a Constructor that zero
+  // initializes this class will clobber the previous property initialization.
+  // We rely on the static SystemProperties in libc to be placed in .bss and zero initialized.
+  SystemProperties() {
+  }
+  // Special constructor for testing that also zero initializes the important members.
+  SystemProperties(bool initialized) : initialized_(initialized) {
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(SystemProperties);
+
+  bool Init(const char* filename);
+  bool AreaInit(const char* filename, bool* fsetxattr_failed);
+  uint32_t AreaSerial();
+  const prop_info* Find(const char* name);
+  int Read(const prop_info* pi, char* name, char* value);
+  void ReadCallback(const prop_info* pi,
+                    void (*callback)(void* cookie, const char* name, const char* value,
+                                     uint32_t serial),
+                    void* cookie);
+  int Get(const char* name, char* value);
+  int Update(prop_info* pi, const char* value, unsigned int len);
+  int Add(const char* name, unsigned int namelen, const char* value, unsigned int valuelen);
+  uint32_t Serial(const prop_info* pi);
+  uint32_t WaitAny(uint32_t old_serial);
+  bool Wait(const prop_info* pi, uint32_t old_serial, uint32_t* new_serial_ptr,
+            const timespec* relative_timeout);
+  const prop_info* FindNth(unsigned n);
+  int Foreach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie);
+
+  Contexts* contexts() {
+    return reinterpret_cast<Contexts*>(&contexts_union_);
+  }
+
+ private:
+  // We don't want to use new or malloc in properties (b/31659220), and since these classes
+  // are small enough and we place them in a union.  See the above note about Constructors
+  // for why there is a no-op constructor here.
+  union ContextsUnion {
+    ContextsUnion() {
+    }
+    ~ContextsUnion() {
+    }
+    ContextsSerialized contexts_serialized;
+    ContextsSplit contexts_split;
+    ContextsPreSplit contexts_pre_split;
+  } contexts_union_;
+  bool initialized_;
+  char property_filename_[PROP_FILENAME_MAX];
+};
+
+#endif
diff --git a/libc/system_properties/prop_area.cpp b/libc/system_properties/prop_area.cpp
index 032fa4e..42bee9f 100644
--- a/libc/system_properties/prop_area.cpp
+++ b/libc/system_properties/prop_area.cpp
@@ -26,6 +26,8 @@
  * SUCH DAMAGE.
  */
 
+#include "system_properties/prop_area.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <stdlib.h>
@@ -39,8 +41,6 @@
 
 #include <async_safe/log.h>
 
-#include "prop_area.h"
-
 constexpr size_t PA_SIZE = 128 * 1024;
 constexpr uint32_t PROP_AREA_MAGIC = 0x504f5250;
 constexpr uint32_t PROP_AREA_VERSION = 0xfc6ed0ab;
diff --git a/libc/system_properties/prop_info.cpp b/libc/system_properties/prop_info.cpp
index 5123f92..890d1cf 100644
--- a/libc/system_properties/prop_info.cpp
+++ b/libc/system_properties/prop_info.cpp
@@ -26,7 +26,7 @@
  * SUCH DAMAGE.
  */
 
-#include "prop_info.h"
+#include "system_properties/prop_info.h"
 
 #include <string.h>
 
diff --git a/libc/system_properties/property_filename.h b/libc/system_properties/property_filename.h
deleted file mode 100644
index 0f7bcf7..0000000
--- a/libc/system_properties/property_filename.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2008 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_PROPERTY_FILENAME_H
-#define SYSTEM_PROPERTIES_PROPERTY_FILENAME_H
-
-// These are globals set by __system_property_set_filename().
-// There isn't huge benefit in refactoring them, so they're alone in this header.
-constexpr int PROP_FILENAME_MAX = 1024;
-extern char property_filename[PROP_FILENAME_MAX];
-
-#endif
diff --git a/libc/system_properties/system_properties.cpp b/libc/system_properties/system_properties.cpp
index f67fc4d..c610df2 100644
--- a/libc/system_properties/system_properties.cpp
+++ b/libc/system_properties/system_properties.cpp
@@ -26,246 +26,30 @@
  * SUCH DAMAGE.
  */
 
+#include "system_properties/system_properties.h"
+
 #include <errno.h>
-#include <fcntl.h>
-#include <netinet/in.h>
-#include <poll.h>
 #include <stdatomic.h>
-#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/select.h>
-#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <sys/uio.h>
-#include <sys/un.h>
 #include <unistd.h>
 
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
-#include <sys/system_properties.h>
-
 #include <new>
 
 #include <async_safe/log.h>
 
 #include "private/ErrnoRestorer.h"
-#include "private/bionic_defs.h"
 #include "private/bionic_futex.h"
-#include "private/bionic_macros.h"
-#include "private/bionic_sdk_version.h"
 
-#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"
-#include "property_filename.h"
-
-// We don't want to use new or malloc in properties (b/31659220), and since these classes are
-// small enough and we place them in a static union.  Note that system properties are initialized
-// before static initializers are called, so using a Constructor here is an error.  Even a
-// Constructor that zero initializes a class will clobber the previous property initialization.
-static union ContextsUnion {
-  ContextsUnion() {}
-  ~ContextsUnion() {}
-  ContextsSerialized contexts_serialized;
-  ContextsSplit contexts_split;
-  ContextsPreSplit contexts_pre_split;
-} contexts_union;
-static Contexts* contexts = nullptr;
+#include "system_properties/context_node.h"
+#include "system_properties/prop_area.h"
+#include "system_properties/prop_info.h"
 
 #define SERIAL_DIRTY(serial) ((serial)&1)
 #define SERIAL_VALUE_LEN(serial) ((serial) >> 24)
 
-static const char property_service_socket[] = "/dev/socket/" PROP_SERVICE_NAME;
-static const char* kServiceVersionPropertyName = "ro.property_service.version";
-
-// This is public because it was exposed in the NDK. As of 2017-01, ~60 apps reference this symbol.
-// It is set to nullptr and never modified.
-__BIONIC_WEAK_VARIABLE_FOR_NATIVE_BRIDGE
-prop_area* __system_property_area__ = nullptr;
-
-char property_filename[PROP_FILENAME_MAX] = PROP_FILENAME;
-
-class PropertyServiceConnection {
- public:
-  PropertyServiceConnection() : last_error_(0) {
-    socket_ = ::socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
-    if (socket_ == -1) {
-      last_error_ = errno;
-      return;
-    }
-
-    const size_t namelen = strlen(property_service_socket);
-    sockaddr_un addr;
-    memset(&addr, 0, sizeof(addr));
-    strlcpy(addr.sun_path, property_service_socket, sizeof(addr.sun_path));
-    addr.sun_family = AF_LOCAL;
-    socklen_t alen = namelen + offsetof(sockaddr_un, sun_path) + 1;
-
-    if (TEMP_FAILURE_RETRY(connect(socket_, reinterpret_cast<sockaddr*>(&addr), alen)) == -1) {
-      last_error_ = errno;
-      close(socket_);
-      socket_ = -1;
-    }
-  }
-
-  bool IsValid() {
-    return socket_ != -1;
-  }
-
-  int GetLastError() {
-    return last_error_;
-  }
-
-  bool RecvInt32(int32_t* value) {
-    int result = TEMP_FAILURE_RETRY(recv(socket_, value, sizeof(*value), MSG_WAITALL));
-    return CheckSendRecvResult(result, sizeof(*value));
-  }
-
-  int socket() {
-    return socket_;
-  }
-
-  ~PropertyServiceConnection() {
-    if (socket_ != -1) {
-      close(socket_);
-    }
-  }
-
- private:
-  bool CheckSendRecvResult(int result, int expected_len) {
-    if (result == -1) {
-      last_error_ = errno;
-    } else if (result != expected_len) {
-      last_error_ = -1;
-    } else {
-      last_error_ = 0;
-    }
-
-    return last_error_ == 0;
-  }
-
-  int socket_;
-  int last_error_;
-
-  friend class SocketWriter;
-};
-
-class SocketWriter {
- public:
-  explicit SocketWriter(PropertyServiceConnection* connection)
-      : connection_(connection), iov_index_(0), uint_buf_index_(0) {
-  }
-
-  SocketWriter& WriteUint32(uint32_t value) {
-    CHECK(uint_buf_index_ < kUintBufSize);
-    CHECK(iov_index_ < kIovSize);
-    uint32_t* ptr = uint_buf_ + uint_buf_index_;
-    uint_buf_[uint_buf_index_++] = value;
-    iov_[iov_index_].iov_base = ptr;
-    iov_[iov_index_].iov_len = sizeof(*ptr);
-    ++iov_index_;
-    return *this;
-  }
-
-  SocketWriter& WriteString(const char* value) {
-    uint32_t valuelen = strlen(value);
-    WriteUint32(valuelen);
-    if (valuelen == 0) {
-      return *this;
-    }
-
-    CHECK(iov_index_ < kIovSize);
-    iov_[iov_index_].iov_base = const_cast<char*>(value);
-    iov_[iov_index_].iov_len = valuelen;
-    ++iov_index_;
-
-    return *this;
-  }
-
-  bool Send() {
-    if (!connection_->IsValid()) {
-      return false;
-    }
-
-    if (writev(connection_->socket(), iov_, iov_index_) == -1) {
-      connection_->last_error_ = errno;
-      return false;
-    }
-
-    iov_index_ = uint_buf_index_ = 0;
-    return true;
-  }
-
- private:
-  static constexpr size_t kUintBufSize = 8;
-  static constexpr size_t kIovSize = 8;
-
-  PropertyServiceConnection* connection_;
-  iovec iov_[kIovSize];
-  size_t iov_index_;
-  uint32_t uint_buf_[kUintBufSize];
-  size_t uint_buf_index_;
-
-  DISALLOW_IMPLICIT_CONSTRUCTORS(SocketWriter);
-};
-
-struct prop_msg {
-  unsigned cmd;
-  char name[PROP_NAME_MAX];
-  char value[PROP_VALUE_MAX];
-};
-
-static int send_prop_msg(const prop_msg* msg) {
-  PropertyServiceConnection connection;
-  if (!connection.IsValid()) {
-    return connection.GetLastError();
-  }
-
-  int result = -1;
-  int s = connection.socket();
-
-  const int num_bytes = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0));
-  if (num_bytes == sizeof(prop_msg)) {
-    // We successfully wrote to the property server but now we
-    // wait for the property server to finish its work.  It
-    // acknowledges its completion by closing the socket so we
-    // poll here (on nothing), waiting for the socket to close.
-    // If you 'adb shell setprop foo bar' you'll see the POLLHUP
-    // once the socket closes.  Out of paranoia we cap our poll
-    // at 250 ms.
-    pollfd pollfds[1];
-    pollfds[0].fd = s;
-    pollfds[0].events = 0;
-    const int poll_result = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
-    if (poll_result == 1 && (pollfds[0].revents & POLLHUP) != 0) {
-      result = 0;
-    } else {
-      // Ignore the timeout and treat it like a success anyway.
-      // The init process is single-threaded and its property
-      // service is sometimes slow to respond (perhaps it's off
-      // starting a child process or something) and thus this
-      // times out and the caller thinks it failed, even though
-      // it's still getting around to it.  So we fake it here,
-      // mostly for ctl.* properties, but we do try and wait 250
-      // ms so callers who do read-after-write can reliably see
-      // what they've written.  Most of the time.
-      // TODO: fix the system properties design.
-      async_safe_format_log(ANDROID_LOG_WARN, "libc",
-                            "Property service has timed out while trying to set \"%s\" to \"%s\"",
-                            msg->name, msg->value);
-      result = 0;
-    }
-  }
-
-  return result;
-}
-
 static bool is_dir(const char* pathname) {
   struct stat info;
   if (stat(pathname, &info) == -1) {
@@ -274,71 +58,62 @@
   return S_ISDIR(info.st_mode);
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-int __system_properties_init() {
+bool SystemProperties::Init(const char* filename) {
   // This is called from __libc_init_common, and should leave errno at 0 (http://b/37248982).
   ErrnoRestorer errno_restorer;
 
-  if (contexts != nullptr) {
-    contexts->ResetAccess();
-    return 0;
+  if (initialized_) {
+    contexts()->ResetAccess();
+    return true;
   }
-  contexts = nullptr;
-  if (is_dir(property_filename)) {
+
+  if (strlen(filename) > PROP_FILENAME_MAX) {
+    return false;
+  }
+  strcpy(property_filename_, filename);
+
+  if (is_dir(property_filename_)) {
     if (access("/dev/__properties__/property_info", R_OK) == 0) {
-      new (&contexts_union.contexts_serialized) ContextsSerialized();
-      if (!contexts_union.contexts_serialized.Initialize(false)) {
-        return -1;
+      new (&contexts_union_.contexts_serialized) ContextsSerialized();
+      if (!contexts_union_.contexts_serialized.Initialize(false, property_filename_, nullptr)) {
+        return false;
       }
-      contexts = &contexts_union.contexts_serialized;
     } else {
-      new (&contexts_union.contexts_split) ContextsSplit();
-      if (!contexts_union.contexts_split.Initialize(false)) {
-        return -1;
+      new (&contexts_union_.contexts_split) ContextsSplit();
+      if (!contexts_union_.contexts_split.Initialize(false, property_filename_, nullptr)) {
+        return false;
       }
-      contexts = &contexts_union.contexts_split;
     }
   } else {
-    new (&contexts_union.contexts_pre_split) ContextsPreSplit();
-    if (!contexts_union.contexts_pre_split.Initialize(false)) {
-      return -1;
+    new (&contexts_union_.contexts_pre_split) ContextsPreSplit();
+    if (!contexts_union_.contexts_pre_split.Initialize(false, property_filename_, nullptr)) {
+      return false;
     }
-    contexts = &contexts_union.contexts_pre_split;
   }
-  return 0;
+  initialized_ = true;
+  return true;
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-int __system_property_set_filename(const char* filename) {
-  size_t len = strlen(filename);
-  if (len >= sizeof(property_filename)) return -1;
+bool SystemProperties::AreaInit(const char* filename, bool* fsetxattr_failed) {
+  if (strlen(filename) > PROP_FILENAME_MAX) {
+    return false;
+  }
+  strcpy(property_filename_, filename);
 
-  strcpy(property_filename, filename);
-  return 0;
+  new (&contexts_union_.contexts_serialized) ContextsSerialized();
+  if (!contexts_union_.contexts_serialized.Initialize(true, property_filename_, fsetxattr_failed)) {
+    return false;
+  }
+  initialized_ = true;
+  return true;
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-int __system_property_area_init() {
-  if (contexts != nullptr) {
-    contexts->FreeAndUnmap();
-  }
-  // We set this unconditionally as we want tests to continue on regardless of if this failed
-  // and property_service will abort on an error condition, so no harm done.
-  new (&contexts_union.contexts_serialized) ContextsSerialized;
-  contexts = &contexts_union.contexts_serialized;
-  if (!contexts_union.contexts_serialized.Initialize(true)) {
-    return -1;
-  }
-  return 0;
-}
-
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-uint32_t __system_property_area_serial() {
-  if (contexts == nullptr) {
+uint32_t SystemProperties::AreaSerial() {
+  if (!initialized_) {
     return -1;
   }
 
-  prop_area* pa = contexts->GetSerialPropArea();
+  prop_area* pa = contexts()->GetSerialPropArea();
   if (!pa) {
     return -1;
   }
@@ -347,13 +122,12 @@
   return atomic_load_explicit(pa->serial(), memory_order_acquire);
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-const prop_info* __system_property_find(const char* name) {
-  if (contexts == nullptr) {
+const prop_info* SystemProperties::Find(const char* name) {
+  if (!initialized_) {
     return nullptr;
   }
 
-  prop_area* pa = contexts->GetPropAreaForName(name);
+  prop_area* pa = contexts()->GetPropAreaForName(name);
   if (!pa) {
     async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Access denied finding property \"%s\"", name);
     return nullptr;
@@ -366,10 +140,9 @@
   return strncmp(name, "ro.", 3) == 0;
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-int __system_property_read(const prop_info* pi, char* name, char* value) {
+int SystemProperties::Read(const prop_info* pi, char* name, char* value) {
   while (true) {
-    uint32_t serial = __system_property_serial(pi);  // acquire semantics
+    uint32_t serial = Serial(pi);  // acquire semantics
     size_t len = SERIAL_VALUE_LEN(serial);
     memcpy(value, pi->value, len + 1);
     // TODO: Fix the synchronization scheme here.
@@ -405,15 +178,14 @@
   }
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-void __system_property_read_callback(const prop_info* pi,
-                                     void (*callback)(void* cookie, const char* name,
-                                                      const char* value, uint32_t serial),
-                                     void* cookie) {
+void SystemProperties::ReadCallback(const prop_info* pi,
+                                    void (*callback)(void* cookie, const char* name,
+                                                     const char* value, uint32_t serial),
+                                    void* cookie) {
   // Read only properties don't need to copy the value to a temporary buffer, since it can never
   // change.
   if (is_read_only(pi->name)) {
-    uint32_t serial = __system_property_serial(pi);
+    uint32_t serial = Serial(pi);
     if (pi->is_long()) {
       callback(cookie, pi->name, pi->long_value(), serial);
     } else {
@@ -423,14 +195,14 @@
   }
 
   while (true) {
-    uint32_t serial = __system_property_serial(pi);  // acquire semantics
+    uint32_t serial = Serial(pi);  // acquire semantics
     size_t len = SERIAL_VALUE_LEN(serial);
     char value_buf[len + 1];
 
     memcpy(value_buf, pi->value, len);
     value_buf[len] = '\0';
 
-    // TODO: see todo in __system_property_read function
+    // TODO: see todo in Read function
     atomic_thread_fence(memory_order_acquire);
     if (serial == load_const_atomic(&(pi->serial), memory_order_relaxed)) {
       callback(cookie, pi->name, value_buf, serial);
@@ -439,118 +211,27 @@
   }
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-int __system_property_get(const char* name, char* value) {
-  const prop_info* pi = __system_property_find(name);
+int SystemProperties::Get(const char* name, char* value) {
+  const prop_info* pi = Find(name);
 
   if (pi != 0) {
-    return __system_property_read(pi, nullptr, value);
+    return Read(pi, nullptr, value);
   } else {
     value[0] = 0;
     return 0;
   }
 }
 
-static constexpr uint32_t kProtocolVersion1 = 1;
-static constexpr uint32_t kProtocolVersion2 = 2;  // current
-
-static atomic_uint_least32_t g_propservice_protocol_version = 0;
-
-static void detect_protocol_version() {
-  char value[PROP_VALUE_MAX];
-  if (__system_property_get(kServiceVersionPropertyName, value) == 0) {
-    g_propservice_protocol_version = kProtocolVersion1;
-    async_safe_format_log(ANDROID_LOG_WARN, "libc",
-                          "Using old property service protocol (\"%s\" is not set)",
-                          kServiceVersionPropertyName);
-  } else {
-    uint32_t version = static_cast<uint32_t>(atoll(value));
-    if (version >= kProtocolVersion2) {
-      g_propservice_protocol_version = kProtocolVersion2;
-    } else {
-      async_safe_format_log(ANDROID_LOG_WARN, "libc",
-                            "Using old property service protocol (\"%s\"=\"%s\")",
-                            kServiceVersionPropertyName, value);
-      g_propservice_protocol_version = kProtocolVersion1;
-    }
-  }
-}
-
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-int __system_property_set(const char* key, const char* value) {
-  if (key == nullptr) return -1;
-  if (value == nullptr) value = "";
-
-  if (g_propservice_protocol_version == 0) {
-    detect_protocol_version();
-  }
-
-  if (g_propservice_protocol_version == kProtocolVersion1) {
-    // Old protocol does not support long names or values
-    if (strlen(key) >= PROP_NAME_MAX) return -1;
-    if (strlen(value) >= PROP_VALUE_MAX) return -1;
-
-    prop_msg msg;
-    memset(&msg, 0, sizeof msg);
-    msg.cmd = PROP_MSG_SETPROP;
-    strlcpy(msg.name, key, sizeof msg.name);
-    strlcpy(msg.value, value, sizeof msg.value);
-
-    return send_prop_msg(&msg);
-  } else {
-    // New protocol only allows long values for ro. properties only.
-    if (strlen(value) >= PROP_VALUE_MAX && !is_read_only(key)) return -1;
-    // Use proper protocol
-    PropertyServiceConnection connection;
-    if (!connection.IsValid()) {
-      errno = connection.GetLastError();
-      async_safe_format_log(
-          ANDROID_LOG_WARN, "libc",
-          "Unable to set property \"%s\" to \"%s\": connection failed; errno=%d (%s)", key, value,
-          errno, strerror(errno));
-      return -1;
-    }
-
-    SocketWriter writer(&connection);
-    if (!writer.WriteUint32(PROP_MSG_SETPROP2).WriteString(key).WriteString(value).Send()) {
-      errno = connection.GetLastError();
-      async_safe_format_log(ANDROID_LOG_WARN, "libc",
-                            "Unable to set property \"%s\" to \"%s\": write failed; errno=%d (%s)",
-                            key, value, errno, strerror(errno));
-      return -1;
-    }
-
-    int result = -1;
-    if (!connection.RecvInt32(&result)) {
-      errno = connection.GetLastError();
-      async_safe_format_log(ANDROID_LOG_WARN, "libc",
-                            "Unable to set property \"%s\" to \"%s\": recv failed; errno=%d (%s)",
-                            key, value, errno, strerror(errno));
-      return -1;
-    }
-
-    if (result != PROP_SUCCESS) {
-      async_safe_format_log(ANDROID_LOG_WARN, "libc",
-                            "Unable to set property \"%s\" to \"%s\": error code: 0x%x", key, value,
-                            result);
-      return -1;
-    }
-
-    return 0;
-  }
-}
-
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-int __system_property_update(prop_info* pi, const char* value, unsigned int len) {
+int SystemProperties::Update(prop_info* pi, const char* value, unsigned int len) {
   if (len >= PROP_VALUE_MAX) {
     return -1;
   }
 
-  if (contexts == nullptr) {
+  if (!initialized_) {
     return -1;
   }
 
-  prop_area* pa = contexts->GetSerialPropArea();
+  prop_area* pa = contexts()->GetSerialPropArea();
   if (!pa) {
     return -1;
   }
@@ -574,8 +255,7 @@
   return 0;
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-int __system_property_add(const char* name, unsigned int namelen, const char* value,
+int SystemProperties::Add(const char* name, unsigned int namelen, const char* value,
                           unsigned int valuelen) {
   if (valuelen >= PROP_VALUE_MAX && !is_read_only(name)) {
     return -1;
@@ -585,16 +265,16 @@
     return -1;
   }
 
-  if (contexts == nullptr) {
+  if (!initialized_) {
     return -1;
   }
 
-  prop_area* serial_pa = contexts->GetSerialPropArea();
+  prop_area* serial_pa = contexts()->GetSerialPropArea();
   if (serial_pa == nullptr) {
     return -1;
   }
 
-  prop_area* pa = contexts->GetPropAreaForName(name);
+  prop_area* pa = contexts()->GetPropAreaForName(name);
   if (!pa) {
     async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Access denied adding property \"%s\"", name);
     return -1;
@@ -615,8 +295,7 @@
 }
 
 // Wait for non-locked serial, and retrieve it with acquire semantics.
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-uint32_t __system_property_serial(const prop_info* pi) {
+uint32_t SystemProperties::Serial(const prop_info* pi) {
   uint32_t serial = load_const_atomic(&pi->serial, memory_order_acquire);
   while (SERIAL_DIRTY(serial)) {
     __futex_wait(const_cast<_Atomic(uint_least32_t)*>(&pi->serial), serial, nullptr);
@@ -625,24 +304,22 @@
   return serial;
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-uint32_t __system_property_wait_any(uint32_t old_serial) {
+uint32_t SystemProperties::WaitAny(uint32_t old_serial) {
   uint32_t new_serial;
-  __system_property_wait(nullptr, old_serial, &new_serial, nullptr);
+  Wait(nullptr, old_serial, &new_serial, nullptr);
   return new_serial;
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-bool __system_property_wait(const prop_info* pi, uint32_t old_serial, uint32_t* new_serial_ptr,
+bool SystemProperties::Wait(const prop_info* pi, uint32_t old_serial, uint32_t* new_serial_ptr,
                             const timespec* relative_timeout) {
   // Are we waiting on the global serial or a specific serial?
   atomic_uint_least32_t* serial_ptr;
   if (pi == nullptr) {
-    if (contexts == nullptr) {
+    if (!initialized_) {
       return -1;
     }
 
-    prop_area* serial_pa = contexts->GetSerialPropArea();
+    prop_area* serial_pa = contexts()->GetSerialPropArea();
     if (serial_pa == nullptr) {
       return -1;
     }
@@ -665,8 +342,7 @@
   return true;
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-const prop_info* __system_property_find_nth(unsigned n) {
+const prop_info* SystemProperties::FindNth(unsigned n) {
   struct find_nth {
     const uint32_t sought;
     uint32_t current;
@@ -679,17 +355,16 @@
       if (self->current++ == self->sought) self->result = pi;
     }
   } state(n);
-  __system_property_foreach(find_nth::fn, &state);
+  Foreach(find_nth::fn, &state);
   return state.result;
 }
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-int __system_property_foreach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) {
-  if (contexts == nullptr) {
+int SystemProperties::Foreach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) {
+  if (!initialized_) {
     return -1;
   }
 
-  contexts->ForEach(propfn, cookie);
+  contexts()->ForEach(propfn, cookie);
 
   return 0;
 }
diff --git a/tests/Android.bp b/tests/Android.bp
index 51a721a..c1e455d 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -173,7 +173,10 @@
 
     target: {
         bionic: {
-            whole_static_libs: ["libasync_safe"],
+            whole_static_libs: [
+                "libasync_safe",
+                "libsystemproperties",
+            ],
         },
     },
 
diff --git a/tests/system_properties_test.cpp b/tests/system_properties_test.cpp
index 69647bf..f2151e1 100644
--- a/tests/system_properties_test.cpp
+++ b/tests/system_properties_test.cpp
@@ -24,6 +24,8 @@
 #include <string>
 #include <thread>
 
+#include <android-base/test_utils.h>
+
 using namespace std::literals;
 
 #if defined(__BIONIC__)
@@ -31,41 +33,26 @@
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
 #include <sys/_system_properties.h>
 
-struct LocalPropertyTestState {
-    LocalPropertyTestState() : valid(false) {
-        const char* ANDROID_DATA = getenv("ANDROID_DATA");
-        char dir_template[PATH_MAX];
-        snprintf(dir_template, sizeof(dir_template), "%s/local/tmp/prop-XXXXXX", ANDROID_DATA);
-        char* dirname = mkdtemp(dir_template);
-        if (!dirname) {
-            fprintf(stderr, "making temp file for test state failed (is %s writable?): %s",
-                    dir_template, strerror(errno));
-            return;
-        }
+#include <system_properties/system_properties.h>
 
-        pa_dirname = dirname;
-        pa_filename = pa_dirname + "/__properties__";
-
-        __system_property_set_filename(pa_filename.c_str());
-        __system_property_area_init();
-        valid = true;
+class SystemPropertiesTest : public SystemProperties {
+ public:
+  SystemPropertiesTest() : SystemProperties(false) {
+    valid_ = AreaInit(dir_.path, nullptr);
+  }
+  ~SystemPropertiesTest() {
+    if (valid_) {
+      contexts()->FreeAndUnmap();
     }
+  }
 
-    ~LocalPropertyTestState() {
-        if (!valid) {
-            return;
-        }
+  bool valid() const {
+    return valid_;
+  }
 
-        __system_property_set_filename(PROP_FILENAME);
-        __system_properties_init();
-        unlink(pa_filename.c_str());
-        rmdir(pa_dirname.c_str());
-    }
-public:
-    bool valid;
-private:
-    std::string pa_dirname;
-    std::string pa_filename;
+ private:
+  TemporaryDir dir_;
+  bool valid_;
 };
 
 static void foreach_test_callback(const prop_info *pi, void* cookie) {
@@ -100,27 +87,16 @@
     ok[name_i][name_j][name_k] = true;
 }
 
-static void* PropertyWaitHelperFn(void* arg) {
-    int* flag = static_cast<int*>(arg);
-    prop_info* pi = const_cast<prop_info*>(__system_property_find("property"));
-    usleep(100000);
-
-    *flag = 1;
-    __system_property_update(pi, "value3", 6);
-
-    return nullptr;
-}
-
 #endif // __BIONIC__
 
 TEST(properties, __system_property_add) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
 
-    ASSERT_EQ(0, __system_property_add("property", 8, "value1", 6));
-    ASSERT_EQ(0, __system_property_add("other_property", 14, "value2", 6));
-    ASSERT_EQ(0, __system_property_add("property_other", 14, "value3", 6));
+    ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
+    ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
+    ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
 
     // check that there is no limit on property name length
     char name[PROP_NAME_MAX + 11];
@@ -130,19 +106,19 @@
     }
 
     name[sizeof(name)-1] = '\0';
-    ASSERT_EQ(0, __system_property_add(name, strlen(name), "value", 5));
+    ASSERT_EQ(0, system_properties.Add(name, strlen(name), "value", 5));
 
     char propvalue[PROP_VALUE_MAX];
-    ASSERT_EQ(6, __system_property_get("property", propvalue));
+    ASSERT_EQ(6, system_properties.Get("property", propvalue));
     ASSERT_STREQ(propvalue, "value1");
 
-    ASSERT_EQ(6, __system_property_get("other_property", propvalue));
+    ASSERT_EQ(6, system_properties.Get("other_property", propvalue));
     ASSERT_STREQ(propvalue, "value2");
 
-    ASSERT_EQ(6, __system_property_get("property_other", propvalue));
+    ASSERT_EQ(6, system_properties.Get("property_other", propvalue));
     ASSERT_STREQ(propvalue, "value3");
 
-    ASSERT_EQ(5, __system_property_get(name, propvalue));
+    ASSERT_EQ(5, system_properties.Get(name, propvalue));
     ASSERT_STREQ(propvalue, "value");
 #else // __BIONIC__
     GTEST_LOG_(INFO) << "This test does nothing.\n";
@@ -151,33 +127,33 @@
 
 TEST(properties, __system_property_update) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
 
-    ASSERT_EQ(0, __system_property_add("property", 8, "oldvalue1", 9));
-    ASSERT_EQ(0, __system_property_add("other_property", 14, "value2", 6));
-    ASSERT_EQ(0, __system_property_add("property_other", 14, "value3", 6));
+    ASSERT_EQ(0, system_properties.Add("property", 8, "oldvalue1", 9));
+    ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
+    ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
 
-    const prop_info* pi = __system_property_find("property");
+    const prop_info* pi = system_properties.Find("property");
     ASSERT_TRUE(pi != nullptr);
-    __system_property_update(const_cast<prop_info*>(pi), "value4", 6);
+    system_properties.Update(const_cast<prop_info*>(pi), "value4", 6);
 
-    pi = __system_property_find("other_property");
+    pi = system_properties.Find("other_property");
     ASSERT_TRUE(pi != nullptr);
-    __system_property_update(const_cast<prop_info*>(pi), "newvalue5", 9);
+    system_properties.Update(const_cast<prop_info*>(pi), "newvalue5", 9);
 
-    pi = __system_property_find("property_other");
+    pi = system_properties.Find("property_other");
     ASSERT_TRUE(pi != nullptr);
-    __system_property_update(const_cast<prop_info*>(pi), "value6", 6);
+    system_properties.Update(const_cast<prop_info*>(pi), "value6", 6);
 
     char propvalue[PROP_VALUE_MAX];
-    ASSERT_EQ(6, __system_property_get("property", propvalue));
+    ASSERT_EQ(6, system_properties.Get("property", propvalue));
     ASSERT_STREQ(propvalue, "value4");
 
-    ASSERT_EQ(9, __system_property_get("other_property", propvalue));
+    ASSERT_EQ(9, system_properties.Get("other_property", propvalue));
     ASSERT_STREQ(propvalue, "newvalue5");
 
-    ASSERT_EQ(6, __system_property_get("property_other", propvalue));
+    ASSERT_EQ(6, system_properties.Get("property_other", propvalue));
     ASSERT_STREQ(propvalue, "value6");
 #else // __BIONIC__
     GTEST_LOG_(INFO) << "This test does nothing.\n";
@@ -186,8 +162,9 @@
 
 TEST(properties, fill) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
+
     char prop_name[PROP_NAME_MAX];
     char prop_value[PROP_VALUE_MAX];
     char prop_value_ret[PROP_VALUE_MAX];
@@ -202,7 +179,7 @@
         prop_name[PROP_NAME_MAX - 1] = 0;
         prop_value[PROP_VALUE_MAX - 1] = 0;
 
-        ret = __system_property_add(prop_name, PROP_NAME_MAX - 1, prop_value, PROP_VALUE_MAX - 1);
+        ret = system_properties.Add(prop_name, PROP_NAME_MAX - 1, prop_value, PROP_VALUE_MAX - 1);
         if (ret < 0)
             break;
 
@@ -221,7 +198,7 @@
         prop_value[PROP_VALUE_MAX - 1] = 0;
         memset(prop_value_ret, '\0', PROP_VALUE_MAX);
 
-        ASSERT_EQ(PROP_VALUE_MAX - 1, __system_property_get(prop_name, prop_value_ret));
+        ASSERT_EQ(PROP_VALUE_MAX - 1, system_properties.Get(prop_name, prop_value_ret));
         ASSERT_EQ(0, memcmp(prop_value, prop_value_ret, PROP_VALUE_MAX));
     }
 #else // __BIONIC__
@@ -231,15 +208,15 @@
 
 TEST(properties, __system_property_foreach) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
 
-    ASSERT_EQ(0, __system_property_add("property", 8, "value1", 6));
-    ASSERT_EQ(0, __system_property_add("other_property", 14, "value2", 6));
-    ASSERT_EQ(0, __system_property_add("property_other", 14, "value3", 6));
+    ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
+    ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
+    ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
 
     size_t count = 0;
-    ASSERT_EQ(0, __system_property_foreach(foreach_test_callback, &count));
+    ASSERT_EQ(0, system_properties.Foreach(foreach_test_callback, &count));
     ASSERT_EQ(3U, count);
 #else // __BIONIC__
     GTEST_LOG_(INFO) << "This test does nothing.\n";
@@ -248,27 +225,27 @@
 
 TEST(properties, __system_property_find_nth) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
 
-    ASSERT_EQ(0, __system_property_add("property", 8, "value1", 6));
-    ASSERT_EQ(0, __system_property_add("other_property", 14, "value2", 6));
-    ASSERT_EQ(0, __system_property_add("property_other", 14, "value3", 6));
+    ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
+    ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
+    ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
 
     char name[PROP_NAME_MAX];
     char value[PROP_VALUE_MAX];
-    EXPECT_EQ(6, __system_property_read(__system_property_find_nth(0), name, value));
+    EXPECT_EQ(6, system_properties.Read(system_properties.FindNth(0), name, value));
     EXPECT_STREQ("property", name);
     EXPECT_STREQ("value1", value);
-    EXPECT_EQ(6, __system_property_read(__system_property_find_nth(1), name, value));
+    EXPECT_EQ(6, system_properties.Read(system_properties.FindNth(1), name, value));
     EXPECT_STREQ("other_property", name);
     EXPECT_STREQ("value2", value);
-    EXPECT_EQ(6, __system_property_read(__system_property_find_nth(2), name, value));
+    EXPECT_EQ(6, system_properties.Read(system_properties.FindNth(2), name, value));
     EXPECT_STREQ("property_other", name);
     EXPECT_STREQ("value3", value);
 
     for (unsigned i = 3; i < 1024; ++i) {
-      ASSERT_TRUE(__system_property_find_nth(i) == nullptr);
+      ASSERT_TRUE(system_properties.FindNth(i) == nullptr);
     }
 #else // __BIONIC__
     GTEST_LOG_(INFO) << "This test does nothing.\n";
@@ -277,8 +254,9 @@
 
 TEST(properties, fill_hierarchical) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
+
     char prop_name[PROP_NAME_MAX];
     char prop_value[PROP_VALUE_MAX];
     char prop_value_ret[PROP_VALUE_MAX];
@@ -294,7 +272,8 @@
                 prop_name[PROP_NAME_MAX - 1] = 0;
                 prop_value[PROP_VALUE_MAX - 1] = 0;
 
-                ASSERT_EQ(0, __system_property_add(prop_name, PROP_NAME_MAX - 1, prop_value, PROP_VALUE_MAX - 1));
+                ASSERT_EQ(0, system_properties.Add(
+                    prop_name, PROP_NAME_MAX - 1, prop_value, PROP_VALUE_MAX - 1));
             }
         }
     }
@@ -310,7 +289,7 @@
                 prop_value[PROP_VALUE_MAX - 1] = 0;
                 memset(prop_value_ret, '\0', PROP_VALUE_MAX);
 
-                ASSERT_EQ(PROP_VALUE_MAX - 1, __system_property_get(prop_name, prop_value_ret));
+                ASSERT_EQ(PROP_VALUE_MAX - 1, system_properties.Get(prop_name, prop_value_ret));
                 ASSERT_EQ(0, memcmp(prop_value, prop_value_ret, PROP_VALUE_MAX));
             }
         }
@@ -318,7 +297,7 @@
 
     bool ok[8][8][8];
     memset(ok, 0, sizeof(ok));
-    __system_property_foreach(hierarchical_test_callback, ok);
+    system_properties.Foreach(hierarchical_test_callback, ok);
 
     for (int i = 0; i < 8; i++) {
         for (int j = 0; j < 8; j++) {
@@ -334,19 +313,20 @@
 
 TEST(properties, errors) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
+
     char prop_value[PROP_NAME_MAX];
 
-    ASSERT_EQ(0, __system_property_add("property", 8, "value1", 6));
-    ASSERT_EQ(0, __system_property_add("other_property", 14, "value2", 6));
-    ASSERT_EQ(0, __system_property_add("property_other", 14, "value3", 6));
+    ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
+    ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
+    ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
 
-    ASSERT_EQ(0, __system_property_find("property1"));
-    ASSERT_EQ(0, __system_property_get("property1", prop_value));
+    ASSERT_EQ(0, system_properties.Find("property1"));
+    ASSERT_EQ(0, system_properties.Get("property1", prop_value));
 
-    ASSERT_EQ(-1, __system_property_add("name", 4, "value", PROP_VALUE_MAX));
-    ASSERT_EQ(-1, __system_property_update(NULL, "value", PROP_VALUE_MAX));
+    ASSERT_EQ(-1, system_properties.Add("name", 4, "value", PROP_VALUE_MAX));
+    ASSERT_EQ(-1, system_properties.Update(NULL, "value", PROP_VALUE_MAX));
 #else // __BIONIC__
     GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif // __BIONIC__
@@ -354,15 +334,15 @@
 
 TEST(properties, __system_property_serial) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
 
-    ASSERT_EQ(0, __system_property_add("property", 8, "value1", 6));
-    const prop_info* pi = __system_property_find("property");
+    ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
+    const prop_info* pi = system_properties.Find("property");
     ASSERT_TRUE(pi != nullptr);
-    unsigned serial = __system_property_serial(pi);
-    ASSERT_EQ(0, __system_property_update(const_cast<prop_info*>(pi), "value2", 6));
-    ASSERT_NE(serial, __system_property_serial(pi));
+    unsigned serial = system_properties.Serial(pi);
+    ASSERT_EQ(0, system_properties.Update(const_cast<prop_info*>(pi), "value2", 6));
+    ASSERT_NE(serial, system_properties.Serial(pi));
 #else // __BIONIC__
     GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif // __BIONIC__
@@ -370,25 +350,30 @@
 
 TEST(properties, __system_property_wait_any) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
 
-    ASSERT_EQ(0, __system_property_add("property", 8, "value1", 6));
-    unsigned serial = __system_property_wait_any(0);
+    ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
+    unsigned serial = system_properties.WaitAny(0);
 
-    prop_info* pi = const_cast<prop_info*>(__system_property_find("property"));
+    prop_info* pi = const_cast<prop_info*>(system_properties.Find("property"));
     ASSERT_TRUE(pi != nullptr);
-    __system_property_update(pi, "value2", 6);
-    serial = __system_property_wait_any(serial);
+    system_properties.Update(pi, "value2", 6);
+    serial = system_properties.WaitAny(serial);
 
     int flag = 0;
-    pthread_t t;
-    ASSERT_EQ(0, pthread_create(&t, nullptr, PropertyWaitHelperFn, &flag));
+    std::thread thread([&system_properties, &flag]() {
+        prop_info* pi = const_cast<prop_info*>(system_properties.Find("property"));
+        usleep(100000);
+
+        flag = 1;
+        system_properties.Update(pi, "value3", 6);
+    });
     ASSERT_EQ(flag, 0);
-    serial = __system_property_wait_any(serial);
+    serial = system_properties.WaitAny(serial);
     ASSERT_EQ(flag, 1);
 
-    ASSERT_EQ(0, pthread_join(t, nullptr));
+    thread.join();
 #else // __BIONIC__
     GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif // __BIONIC__
@@ -396,29 +381,29 @@
 
 TEST(properties, __system_property_wait) {
 #if defined(__BIONIC__)
-    LocalPropertyTestState pa;
-    ASSERT_TRUE(pa.valid);
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
 
-    ASSERT_EQ(0, __system_property_add("property", 8, "value1", 6));
+    ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
 
-    prop_info* pi = const_cast<prop_info*>(__system_property_find("property"));
+    prop_info* pi = const_cast<prop_info*>(system_properties.Find("property"));
     ASSERT_TRUE(pi != nullptr);
 
-    unsigned serial = __system_property_serial(pi);
+    unsigned serial = system_properties.Serial(pi);
 
-    std::thread thread([]() {
-        prop_info* pi = const_cast<prop_info*>(__system_property_find("property"));
+    std::thread thread([&system_properties]() {
+        prop_info* pi = const_cast<prop_info*>(system_properties.Find("property"));
         ASSERT_TRUE(pi != nullptr);
 
-        __system_property_update(pi, "value2", 6);
+        system_properties.Update(pi, "value2", 6);
     });
 
     uint32_t new_serial;
-    __system_property_wait(pi, serial, &new_serial, nullptr);
+    system_properties.Wait(pi, serial, &new_serial, nullptr);
     ASSERT_GT(new_serial, serial);
 
     char value[PROP_VALUE_MAX];
-    ASSERT_EQ(6, __system_property_get("property", value));
+    ASSERT_EQ(6, system_properties.Get("property", value));
     ASSERT_STREQ("value2", value);
 
     thread.join();
@@ -457,8 +442,8 @@
 
 TEST(properties, __system_property_extra_long_read_only) {
 #if defined(__BIONIC__)
-  LocalPropertyTestState pa;
-  ASSERT_TRUE(pa.valid);
+  SystemPropertiesTest system_properties;
+  ASSERT_TRUE(system_properties.valid());
 
   std::vector<std::pair<std::string, std::string>> short_properties = {
     { "ro.0char", std::string() },
@@ -475,33 +460,34 @@
   for (const auto& property : short_properties) {
     const std::string& name = property.first;
     const std::string& value = property.second;
-    ASSERT_EQ(0, __system_property_add(name.c_str(), name.size(), value.c_str(), value.size()));
+    ASSERT_EQ(0, system_properties.Add(name.c_str(), name.size(), value.c_str(), value.size()));
   }
 
   for (const auto& property : long_properties) {
     const std::string& name = property.first;
     const std::string& value = property.second;
-    ASSERT_EQ(0, __system_property_add(name.c_str(), name.size(), value.c_str(), value.size()));
+    ASSERT_EQ(0, system_properties.Add(name.c_str(), name.size(), value.c_str(), value.size()));
   }
 
-  auto check_with_legacy_read = [](const std::string& name, const std::string& expected_value) {
+  auto check_with_legacy_read = [&system_properties](const std::string& name,
+                                                     const std::string& expected_value) {
     char value[PROP_VALUE_MAX];
-    EXPECT_EQ(static_cast<int>(expected_value.size()), __system_property_get(name.c_str(), value))
+    EXPECT_EQ(static_cast<int>(expected_value.size()), system_properties.Get(name.c_str(), value))
         << name;
     EXPECT_EQ(expected_value, value) << name;
   };
 
-  auto check_with_read_callback = [](const std::string& name, const std::string& expected_value) {
-    const prop_info* pi = __system_property_find(name.c_str());
+  auto check_with_read_callback = [&system_properties](const std::string& name,
+                                                       const std::string& expected_value) {
+    const prop_info* pi = system_properties.Find(name.c_str());
     ASSERT_NE(nullptr, pi);
     std::string value;
-    __system_property_read_callback(pi,
-                                    [](void* cookie, const char*, const char* value, uint32_t) {
-                                      std::string* out_value =
-                                          reinterpret_cast<std::string*>(cookie);
-                                      *out_value = value;
-                                    },
-                                    &value);
+    system_properties.ReadCallback(pi,
+                                   [](void* cookie, const char*, const char* value, uint32_t) {
+                                     auto* out_value = reinterpret_cast<std::string*>(cookie);
+                                     *out_value = value;
+                                   },
+                                   &value);
     EXPECT_EQ(expected_value, value) << name;
   };
 
@@ -529,12 +515,12 @@
 // pa_size is 128 * 1024 currently, if a property is longer then we expect it to fail gracefully.
 TEST(properties, __system_property_extra_long_read_only_too_long) {
 #if defined(__BIONIC__)
-  LocalPropertyTestState pa;
-  ASSERT_TRUE(pa.valid);
+  SystemPropertiesTest system_properties;
+  ASSERT_TRUE(system_properties.valid());
 
   auto name = "ro.super_long_property"s;
   auto value = std::string(128 * 1024 + 1, 'x');
-  ASSERT_NE(0, __system_property_add(name.c_str(), name.size(), value.c_str(), value.size()));
+  ASSERT_NE(0, system_properties.Add(name.c_str(), name.size(), value.c_str(), value.size()));
 
 #else   // __BIONIC__
   GTEST_LOG_(INFO) << "This test does nothing.\n";