Merge "Temporarily fall back to non-V memcmp" into main
diff --git a/benchmarks/Android.bp b/benchmarks/Android.bp
index ffb5921..75e607c 100644
--- a/benchmarks/Android.bp
+++ b/benchmarks/Android.bp
@@ -72,6 +72,7 @@
 
     target: {
         android: {
+            header_libs: ["bionic_libc_platform_headers"],
             static_libs: [
                 "libmeminfo",
                 "libprocinfo",
diff --git a/benchmarks/ScopedDecayTimeRestorer.h b/benchmarks/ScopedDecayTimeRestorer.h
new file mode 100644
index 0000000..5835b43
--- /dev/null
+++ b/benchmarks/ScopedDecayTimeRestorer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <malloc.h>
+
+#if defined(__BIONIC__)
+
+#include "platform/bionic/malloc.h"
+
+class ScopedDecayTimeRestorer {
+ public:
+  ScopedDecayTimeRestorer() {
+    bool value;
+    if (android_mallopt(M_GET_DECAY_TIME_ENABLED, &value, sizeof(value))) {
+      saved_value_ = value ? 1 : 0;
+    }
+  }
+
+  virtual ~ScopedDecayTimeRestorer() { mallopt(M_DECAY_TIME, saved_value_); }
+
+ private:
+  int saved_value_ = 0;
+};
+
+#endif
diff --git a/benchmarks/malloc_benchmark.cpp b/benchmarks/malloc_benchmark.cpp
index 258343f..8f467d2 100644
--- a/benchmarks/malloc_benchmark.cpp
+++ b/benchmarks/malloc_benchmark.cpp
@@ -36,11 +36,14 @@
 #include <vector>
 
 #include <benchmark/benchmark.h>
+#include "ScopedDecayTimeRestorer.h"
 #include "util.h"
 
 #if defined(__BIONIC__)
 
 static void RunMalloptPurge(benchmark::State& state, int purge_value) {
+  ScopedDecayTimeRestorer restorer;
+
   static size_t sizes[] = {8, 16, 32, 64, 128, 1024, 4096, 16384, 65536, 131072, 1048576};
   static int pagesize = getpagesize();
   mallopt(M_DECAY_TIME, 1);
@@ -69,7 +72,6 @@
 
     mallopt(purge_value, 0);
   }
-  mallopt(M_DECAY_TIME, 0);
 }
 
 static void RunThreadsThroughput(benchmark::State& state, size_t size, size_t num_threads) {
diff --git a/benchmarks/malloc_sql_benchmark.cpp b/benchmarks/malloc_sql_benchmark.cpp
index 383325c..d5b17f6 100644
--- a/benchmarks/malloc_sql_benchmark.cpp
+++ b/benchmarks/malloc_sql_benchmark.cpp
@@ -31,6 +31,7 @@
 #include <unistd.h>
 
 #include <benchmark/benchmark.h>
+#include "ScopedDecayTimeRestorer.h"
 #include "util.h"
 
 #if defined(__BIONIC__)
@@ -104,6 +105,8 @@
 #include "malloc_sql.h"
 
 static void BM_malloc_sql_trace_default(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 
@@ -115,14 +118,14 @@
 BIONIC_BENCHMARK(BM_malloc_sql_trace_default);
 
 static void BM_malloc_sql_trace_decay1(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   mallopt(M_DECAY_TIME, 1);
 
   for (auto _ : state) {
     BenchmarkMalloc(g_sql_entries, sizeof(g_sql_entries) / sizeof(MallocEntry),
                     kMaxSqlAllocSlots);
   }
-
-  mallopt(M_DECAY_TIME, 0);
 }
 BIONIC_BENCHMARK(BM_malloc_sql_trace_decay1);
 
diff --git a/benchmarks/stdlib_benchmark.cpp b/benchmarks/stdlib_benchmark.cpp
index 14b380a..9be72e7 100644
--- a/benchmarks/stdlib_benchmark.cpp
+++ b/benchmarks/stdlib_benchmark.cpp
@@ -22,6 +22,7 @@
 #include <unistd.h>
 
 #include <benchmark/benchmark.h>
+#include "ScopedDecayTimeRestorer.h"
 #include "util.h"
 
 static void MallocFree(benchmark::State& state) {
@@ -40,6 +41,8 @@
 
 static void BM_stdlib_malloc_free_default(benchmark::State& state) {
 #if defined(__BIONIC__)
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 #endif
@@ -50,11 +53,11 @@
 
 #if defined(__BIONIC__)
 static void BM_stdlib_malloc_free_decay1(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   mallopt(M_DECAY_TIME, 1);
 
   MallocFree(state);
-
-  mallopt(M_DECAY_TIME, 0);
 }
 BIONIC_BENCHMARK_WITH_ARG(BM_stdlib_malloc_free_decay1, "AT_COMMON_SIZES");
 #endif
@@ -75,6 +78,8 @@
 
 static void BM_stdlib_calloc_free_default(benchmark::State& state) {
 #if defined(__BIONIC__)
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 #endif
@@ -113,8 +118,9 @@
 }
 
 void BM_stdlib_malloc_forty_default(benchmark::State& state) {
-
 #if defined(__BIONIC__)
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 #endif
@@ -125,17 +131,19 @@
 
 #if defined(__BIONIC__)
 void BM_stdlib_malloc_forty_decay1(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   mallopt(M_DECAY_TIME, 1);
 
   MallocMultiple(state, state.range(0), 40);
-
-  mallopt(M_DECAY_TIME, 0);
 }
 BIONIC_BENCHMARK_WITH_ARG(BM_stdlib_malloc_forty_decay1, "AT_COMMON_SIZES");
 #endif
 
 void BM_stdlib_malloc_multiple_8192_allocs_default(benchmark::State& state) {
 #if defined(__BIONIC__)
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 #endif
@@ -146,11 +154,11 @@
 
 #if defined(__BIONIC__)
 void BM_stdlib_malloc_multiple_8192_allocs_decay1(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   mallopt(M_DECAY_TIME, 1);
 
   MallocMultiple(state, 8192, state.range(0));
-
-  mallopt(M_DECAY_TIME, 0);
 }
 BIONIC_BENCHMARK_WITH_ARG(BM_stdlib_malloc_multiple_8192_allocs_decay1, "AT_SMALL_SIZES");
 #endif
diff --git a/libc/bionic/malloc_common.cpp b/libc/bionic/malloc_common.cpp
index e159fdc..3c4884b 100644
--- a/libc/bionic/malloc_common.cpp
+++ b/libc/bionic/malloc_common.cpp
@@ -110,12 +110,27 @@
   if (param == M_BIONIC_ZERO_INIT) {
     return SetHeapZeroInitialize(value);
   }
+
   // The rest we pass on...
+  int retval;
   auto dispatch_table = GetDispatchTable();
   if (__predict_false(dispatch_table != nullptr)) {
-    return dispatch_table->mallopt(param, value);
+    retval = dispatch_table->mallopt(param, value);
+  } else {
+    retval = Malloc(mallopt)(param, value);
   }
-  return Malloc(mallopt)(param, value);
+
+  // Track the M_DECAY_TIME mallopt calls.
+  if (param == M_DECAY_TIME && retval == 1) {
+    __libc_globals.mutate([value](libc_globals* globals) {
+      if (value == 0) {
+        atomic_store(&globals->decay_time_enabled, false);
+      } else {
+        atomic_store(&globals->decay_time_enabled, true);
+      }
+    });
+  }
+  return retval;
 }
 
 extern "C" void* malloc(size_t bytes) {
@@ -341,6 +356,14 @@
     *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->memtag_stack);
     return true;
   }
+  if (opcode == M_GET_DECAY_TIME_ENABLED) {
+    if (arg == nullptr || arg_size != sizeof(bool)) {
+      errno = EINVAL;
+      return false;
+    }
+    *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->decay_time_enabled);
+    return true;
+  }
   errno = ENOTSUP;
   return false;
 }
diff --git a/libc/bionic/malloc_common_dynamic.cpp b/libc/bionic/malloc_common_dynamic.cpp
index 802a94f..792a114 100644
--- a/libc/bionic/malloc_common_dynamic.cpp
+++ b/libc/bionic/malloc_common_dynamic.cpp
@@ -543,6 +543,14 @@
     *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->memtag_stack);
     return true;
   }
+  if (opcode == M_GET_DECAY_TIME_ENABLED) {
+    if (arg == nullptr || arg_size != sizeof(bool)) {
+      errno = EINVAL;
+      return false;
+    }
+    *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->decay_time_enabled);
+    return true;
+  }
   // Try heapprofd's mallopt, as it handles options not covered here.
   return HeapprofdMallopt(opcode, arg, arg_size);
 }
diff --git a/libc/bionic/system_property_api.cpp b/libc/bionic/system_property_api.cpp
index a641f12..8fdea59 100644
--- a/libc/bionic/system_property_api.cpp
+++ b/libc/bionic/system_property_api.cpp
@@ -29,6 +29,7 @@
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
 #include <sys/_system_properties.h>
 
+#include <async_safe/CHECK.h>
 #include <system_properties/prop_area.h>
 #include <system_properties/system_properties.h>
 
@@ -45,7 +46,7 @@
 
 __BIONIC_WEAK_FOR_NATIVE_BRIDGE
 int __system_properties_init() {
-  return system_properties.Init(PROP_FILENAME) ? 0 : -1;
+  return system_properties.Init(PROP_DIRNAME) ? 0 : -1;
 }
 
 __BIONIC_WEAK_FOR_NATIVE_BRIDGE
@@ -55,8 +56,8 @@
 
 __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;
+  bool fsetxattr_fail = false;
+  return system_properties.AreaInit(PROP_DIRNAME, &fsetxattr_fail) && !fsetxattr_fail ? 0 : -1;
 }
 
 __BIONIC_WEAK_FOR_NATIVE_BRIDGE
@@ -129,3 +130,9 @@
 int __system_property_foreach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) {
   return system_properties.Foreach(propfn, cookie);
 }
+
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
+int __system_properties_zygote_reload(void) {
+  CHECK(getpid() == gettid());
+  return system_properties.Reload(false) ? 0 : -1;
+}
diff --git a/libc/include/sys/_system_properties.h b/libc/include/sys/_system_properties.h
index 943d4c6..30dea89 100644
--- a/libc/include/sys/_system_properties.h
+++ b/libc/include/sys/_system_properties.h
@@ -41,7 +41,7 @@
 __BEGIN_DECLS
 
 #define PROP_SERVICE_NAME "property_service"
-#define PROP_FILENAME "/dev/__properties__"
+#define PROP_DIRNAME "/dev/__properties__"
 
 #define PROP_MSG_SETPROP 1
 #define PROP_MSG_SETPROP2 0x00020001
@@ -129,6 +129,18 @@
  */
 int __system_properties_init(void);
 
+/*
+ * Reloads the system properties from disk.
+ * Not intended for use by any apps except the Zygote. Should only be called from the main thread.
+ *
+ * NOTE: Any pointers received from methods such as __system_property_find should be assumed to be
+ * invalid after this method is called.
+ *
+ * Returns 0 on success, -1 if the system properties failed to re-initialize (same conditions as
+ * __system properties_init)
+ */
+int __system_properties_zygote_reload(void); __INTRODUCED_IN(__ANDROID_API_V__)
+
 /* Deprecated: use __system_property_wait instead. */
 uint32_t __system_property_wait_any(uint32_t __old_serial);
 
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index 824682b..156e9ee 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1600,6 +1600,7 @@
     tzalloc;
     tzfree;
     wcsrtombs_l;
+    __system_properties_zygote_reload; # apex
 } LIBC_U;
 
 LIBC_PRIVATE {
diff --git a/libc/platform/bionic/malloc.h b/libc/platform/bionic/malloc.h
index 0a6546e..a06b8ee 100644
--- a/libc/platform/bionic/malloc.h
+++ b/libc/platform/bionic/malloc.h
@@ -104,6 +104,13 @@
   // Query whether memtag stack is enabled for this process.
   M_MEMTAG_STACK_IS_ON = 11,
 #define M_MEMTAG_STACK_IS_ON M_MEMTAG_STACK_IS_ON
+  // Query whether the current process has the decay time enabled so that
+  // the memory from allocations are not immediately released to the OS.
+  // Result is assigned to the arg pointer's destination.
+  //   arg = bool*
+  //   arg_size = sizeof(bool)
+  M_GET_DECAY_TIME_ENABLED = 12,
+#define M_GET_DECAY_TIME_ENABLED M_GET_DECAY_TIME_ENABLED
 };
 
 #pragma clang diagnostic push
diff --git a/libc/private/bionic_globals.h b/libc/private/bionic_globals.h
index d9c4234..15b570d 100644
--- a/libc/private/bionic_globals.h
+++ b/libc/private/bionic_globals.h
@@ -49,6 +49,7 @@
   long setjmp_cookie;
   uintptr_t heap_pointer_tag;
   _Atomic(bool) memtag_stack;
+  _Atomic(bool) decay_time_enabled;
 
   // In order to allow a complete switch between dispatch tables without
   // the need for copying each function by function in the structure,
diff --git a/libc/system_properties/contexts_serialized.cpp b/libc/system_properties/contexts_serialized.cpp
index f05aaa0..73c9136 100644
--- a/libc/system_properties/contexts_serialized.cpp
+++ b/libc/system_properties/contexts_serialized.cpp
@@ -38,6 +38,7 @@
 #include <new>
 
 #include <async_safe/log.h>
+#include <private/android_filesystem_config.h>
 
 #include "system_properties/system_properties.h"
 
@@ -59,25 +60,28 @@
   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), filename_);
+    new (&context_nodes_[i]) ContextNode(property_info_area_file_->context(i), dirname_);
   }
 
   return true;
 }
 
 bool ContextsSerialized::MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed) {
-  PropertiesFilename filename(filename_, "properties_serial");
   if (access_rw) {
     serial_prop_area_ = prop_area::map_prop_area_rw(
-        filename.c_str(), "u:object_r:properties_serial:s0", fsetxattr_failed);
+        serial_filename_.c_str(), "u:object_r:properties_serial:s0", fsetxattr_failed);
   } else {
-    serial_prop_area_ = prop_area::map_prop_area(filename.c_str());
+    serial_prop_area_ = prop_area::map_prop_area(serial_filename_.c_str());
   }
   return serial_prop_area_;
 }
 
-bool ContextsSerialized::InitializeProperties() {
-  if (!property_info_area_file_.LoadDefaultPath()) {
+// Note: load_default_path is only used for testing, as it will cause properties to be loaded from
+// one file (specified by PropertyInfoAreaFile.LoadDefaultPath), but be written to "filename".
+bool ContextsSerialized::InitializeProperties(bool load_default_path) {
+  if (load_default_path && !property_info_area_file_.LoadDefaultPath()) {
+    return false;
+  } else if (!load_default_path && !property_info_area_file_.LoadPath(tree_filename_.c_str())) {
     return false;
   }
 
@@ -89,14 +93,20 @@
   return true;
 }
 
-bool ContextsSerialized::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
-  filename_ = filename;
-  if (!InitializeProperties()) {
+// Note: load_default_path is only used for testing, as it will cause properties to be loaded from
+// one file (specified by PropertyInfoAreaFile.LoadDefaultPath), but be written to "filename".
+bool ContextsSerialized::Initialize(bool writable, const char* dirname, bool* fsetxattr_failed,
+                                    bool load_default_path) {
+  dirname_ = dirname;
+  tree_filename_ = PropertiesFilename(dirname, "property_info");
+  serial_filename_ = PropertiesFilename(dirname, "properties_serial");
+
+  if (!InitializeProperties(load_default_path)) {
     return false;
   }
 
   if (writable) {
-    mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);
+    mkdir(dirname_, S_IRWXU | S_IXGRP | S_IXOTH);
     bool open_failed = false;
     if (fsetxattr_failed) {
       *fsetxattr_failed = false;
diff --git a/libc/system_properties/contexts_split.cpp b/libc/system_properties/contexts_split.cpp
index 3579f55..78bdc64 100644
--- a/libc/system_properties/contexts_split.cpp
+++ b/libc/system_properties/contexts_split.cpp
@@ -281,7 +281,7 @@
   return true;
 }
 
-bool ContextsSplit::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
+bool ContextsSplit::Initialize(bool writable, const char* filename, bool* fsetxattr_failed, bool) {
   filename_ = filename;
   if (!InitializeProperties()) {
     return false;
diff --git a/libc/system_properties/include/system_properties/contexts.h b/libc/system_properties/include/system_properties/contexts.h
index 670f808..df8c5a2 100644
--- a/libc/system_properties/include/system_properties/contexts.h
+++ b/libc/system_properties/include/system_properties/contexts.h
@@ -36,7 +36,8 @@
   virtual ~Contexts() {
   }
 
-  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed) = 0;
+  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed,
+                          bool load_default_path = false) = 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/include/system_properties/contexts_pre_split.h b/libc/system_properties/include/system_properties/contexts_pre_split.h
index 6e695e9..a6cd039 100644
--- a/libc/system_properties/include/system_properties/contexts_pre_split.h
+++ b/libc/system_properties/include/system_properties/contexts_pre_split.h
@@ -38,7 +38,7 @@
   }
 
   // We'll never initialize this legacy option as writable, so don't even check the arg.
-  virtual bool Initialize(bool, const char* filename, bool*) override {
+  virtual bool Initialize(bool, const char* filename, bool*, bool) override {
     pre_split_prop_area_ = prop_area::map_prop_area(filename);
     return pre_split_prop_area_ != nullptr;
   }
diff --git a/libc/system_properties/include/system_properties/contexts_serialized.h b/libc/system_properties/include/system_properties/contexts_serialized.h
index 93d6ac1..8bb0b11 100644
--- a/libc/system_properties/include/system_properties/contexts_serialized.h
+++ b/libc/system_properties/include/system_properties/contexts_serialized.h
@@ -32,13 +32,15 @@
 
 #include "context_node.h"
 #include "contexts.h"
+#include "properties_filename.h"
 
 class ContextsSerialized : public Contexts {
  public:
   virtual ~ContextsSerialized() override {
   }
 
-  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed) override;
+  virtual bool Initialize(bool writable, const char* dirname, bool* fsetxattr_failed,
+                          bool load_default_path) override;
   virtual prop_area* GetPropAreaForName(const char* name) override;
   virtual prop_area* GetSerialPropArea() override {
     return serial_prop_area_;
@@ -49,10 +51,12 @@
 
  private:
   bool InitializeContextNodes();
-  bool InitializeProperties();
+  bool InitializeProperties(bool load_default_path);
   bool MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed);
 
-  const char* filename_;
+  const char* dirname_;
+  PropertiesFilename tree_filename_;
+  PropertiesFilename serial_filename_;
   android::properties::PropertyInfoAreaFile property_info_area_file_;
   ContextNode* context_nodes_ = nullptr;
   size_t num_context_nodes_ = 0;
diff --git a/libc/system_properties/include/system_properties/contexts_split.h b/libc/system_properties/include/system_properties/contexts_split.h
index 1d954cc..321cfd2 100644
--- a/libc/system_properties/include/system_properties/contexts_split.h
+++ b/libc/system_properties/include/system_properties/contexts_split.h
@@ -38,7 +38,8 @@
   virtual ~ContextsSplit() override {
   }
 
-  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed) override;
+  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed,
+                          bool) override;
   virtual prop_area* GetPropAreaForName(const char* name) override;
   virtual prop_area* GetSerialPropArea() override {
     return serial_prop_area_;
diff --git a/libc/system_properties/include/system_properties/properties_filename.h b/libc/system_properties/include/system_properties/properties_filename.h
new file mode 100644
index 0000000..d686f20
--- /dev/null
+++ b/libc/system_properties/include/system_properties/properties_filename.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+class PropertiesFilename {
+ public:
+  PropertiesFilename() = default;
+  PropertiesFilename(const char* dir, const char* file) {
+    if (snprintf(filename_, sizeof(filename_), "%s/%s", dir, file) >=
+        static_cast<int>(sizeof(filename_))) {
+      abort();
+    }
+  }
+  void operator=(const char* value) {
+    if (strlen(value) >= sizeof(filename_)) abort();
+    strcpy(filename_, value);
+  }
+  const char* c_str() { return filename_; }
+
+ private:
+  // Typically something like "/dev/__properties__/properties_serial".
+  char filename_[128];
+};
diff --git a/libc/system_properties/include/system_properties/system_properties.h b/libc/system_properties/include/system_properties/system_properties.h
index 4d84b39..ea4f339 100644
--- a/libc/system_properties/include/system_properties/system_properties.h
+++ b/libc/system_properties/include/system_properties/system_properties.h
@@ -28,7 +28,6 @@
 
 #pragma once
 
-#include <stdint.h>
 #include <sys/param.h>
 #include <sys/system_properties.h>
 
@@ -37,26 +36,6 @@
 #include "contexts_serialized.h"
 #include "contexts_split.h"
 
-class PropertiesFilename {
- public:
-  PropertiesFilename() = default;
-  PropertiesFilename(const char* dir, const char* file) {
-    if (snprintf(filename_, sizeof(filename_), "%s/%s", dir, file) >=
-        static_cast<int>(sizeof(filename_))) {
-      abort();
-    }
-  }
-  void operator=(const char* value) {
-    if (strlen(value) >= sizeof(filename_)) abort();
-    strcpy(filename_, value);
-  }
-  const char* c_str() { return filename_; }
-
- private:
-  // Typically something like "/dev/__properties__/properties_serial".
-  char filename_[128];
-};
-
 class SystemProperties {
  public:
   friend struct LocalPropertyTestState;
@@ -73,7 +52,9 @@
   BIONIC_DISALLOW_COPY_AND_ASSIGN(SystemProperties);
 
   bool Init(const char* filename);
+  bool Reload(bool load_default_path);
   bool AreaInit(const char* filename, bool* fsetxattr_failed);
+  bool AreaInit(const char* filename, bool* fsetxattr_failed, bool load_default_path);
   uint32_t AreaSerial();
   const prop_info* Find(const char* name);
   int Read(const prop_info* pi, char* name, char* value);
@@ -101,8 +82,14 @@
   static constexpr size_t kMaxContextsSize =
       MAX(sizeof(ContextsSerialized), MAX(sizeof(ContextsSplit), sizeof(ContextsPreSplit)));
   alignas(kMaxContextsAlign) char contexts_data_[kMaxContextsSize];
+  alignas(kMaxContextsAlign) char appcompat_override_contexts_data_[kMaxContextsSize];
   Contexts* contexts_;
+  // See http://b/291816546#comment#3 for more explanation of appcompat_override
+  Contexts* appcompat_override_contexts_;
+
+  bool InitContexts(bool load_default_path);
 
   bool initialized_;
   PropertiesFilename properties_filename_;
+  PropertiesFilename appcompat_filename_;
 };
diff --git a/libc/system_properties/system_properties.cpp b/libc/system_properties/system_properties.cpp
index 049236f..9dd5e35 100644
--- a/libc/system_properties/system_properties.cpp
+++ b/libc/system_properties/system_properties.cpp
@@ -29,6 +29,7 @@
 #include "system_properties/system_properties.h"
 
 #include <errno.h>
+#include <private/android_filesystem_config.h>
 #include <stdatomic.h>
 #include <stdlib.h>
 #include <string.h>
@@ -38,6 +39,7 @@
 
 #include <new>
 
+#include <async_safe/CHECK.h>
 #include <async_safe/log.h>
 
 #include "private/ErrnoRestorer.h"
@@ -49,6 +51,7 @@
 
 #define SERIAL_DIRTY(serial) ((serial)&1)
 #define SERIAL_VALUE_LEN(serial) ((serial) >> 24)
+#define APPCOMPAT_PREFIX "ro.appcompat_override."
 
 static bool is_dir(const char* pathname) {
   struct stat info;
@@ -69,10 +72,21 @@
 
   properties_filename_ = filename;
 
+  if (!InitContexts(false)) {
+    return false;
+  }
+
+  initialized_ = true;
+  return true;
+}
+
+bool SystemProperties::InitContexts(bool load_default_path) {
   if (is_dir(properties_filename_.c_str())) {
-    if (access("/dev/__properties__/property_info", R_OK) == 0) {
-      contexts_ = new (contexts_data_) ContextsSerialized();
-      if (!contexts_->Initialize(false, properties_filename_.c_str(), nullptr)) {
+    if (access(PROP_TREE_FILE, R_OK) == 0) {
+      auto serial_contexts = new (contexts_data_) ContextsSerialized();
+      contexts_ = serial_contexts;
+      if (!serial_contexts->Initialize(false, properties_filename_.c_str(), nullptr,
+                                       load_default_path)) {
         return false;
       }
     } else {
@@ -87,20 +101,46 @@
       return false;
     }
   }
-  initialized_ = true;
   return true;
 }
 
 bool SystemProperties::AreaInit(const char* filename, bool* fsetxattr_failed) {
+  return AreaInit(filename, fsetxattr_failed, false);
+}
+
+// Note: load_default_path is only used for testing, as it will cause properties to be loaded from
+// one file (specified by PropertyInfoAreaFile.LoadDefaultPath), but be written to "filename".
+bool SystemProperties::AreaInit(const char* filename, bool* fsetxattr_failed,
+                                bool load_default_path) {
   properties_filename_ = filename;
-  contexts_ = new (contexts_data_) ContextsSerialized();
-  if (!contexts_->Initialize(true, properties_filename_.c_str(), fsetxattr_failed)) {
+  auto serial_contexts = new (contexts_data_) ContextsSerialized();
+  contexts_ = serial_contexts;
+  if (!serial_contexts->Initialize(true, properties_filename_.c_str(), fsetxattr_failed,
+                                   load_default_path)) {
     return false;
   }
+
+  auto* appcompat_contexts = new (appcompat_override_contexts_data_) ContextsSerialized();
+  appcompat_filename_ = PropertiesFilename(properties_filename_.c_str(), "appcompat_override");
+  if (!appcompat_contexts->Initialize(true, appcompat_filename_.c_str(), fsetxattr_failed,
+                                      load_default_path)) {
+    appcompat_override_contexts_ = nullptr;
+    return false;
+  }
+  appcompat_override_contexts_ = appcompat_contexts;
+
   initialized_ = true;
   return true;
 }
 
+bool SystemProperties::Reload(bool load_default_path) {
+  if (!initialized_) {
+    return true;
+  }
+
+  return InitContexts(load_default_path);
+}
+
 uint32_t SystemProperties::AreaSerial() {
   if (!initialized_) {
     return -1;
@@ -129,6 +169,10 @@
   return pa->find(name);
 }
 
+static bool is_appcompat_override(const char* name) {
+  return strncmp(name, APPCOMPAT_PREFIX, strlen(APPCOMPAT_PREFIX)) == 0;
+}
+
 static bool is_read_only(const char* name) {
   return strncmp(name, "ro.", 3) == 0;
 }
@@ -227,16 +271,24 @@
   if (!initialized_) {
     return -1;
   }
+  bool have_override = appcompat_override_contexts_ != nullptr;
 
   prop_area* serial_pa = contexts_->GetSerialPropArea();
+  prop_area* override_serial_pa =
+      have_override ? appcompat_override_contexts_->GetSerialPropArea() : nullptr;
   if (!serial_pa) {
     return -1;
   }
   prop_area* pa = contexts_->GetPropAreaForName(pi->name);
+  prop_area* override_pa =
+      have_override ? appcompat_override_contexts_->GetPropAreaForName(pi->name) : nullptr;
   if (__predict_false(!pa)) {
     async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Could not find area for \"%s\"", pi->name);
     return -1;
   }
+  CHECK(!have_override || (override_pa && override_serial_pa));
+
+  auto* override_pi = const_cast<prop_info*>(have_override ? override_pa->find(pi->name) : nullptr);
 
   uint32_t serial = atomic_load_explicit(&pi->serial, memory_order_relaxed);
   unsigned int old_len = SERIAL_VALUE_LEN(serial);
@@ -246,18 +298,34 @@
   // that we publish our dirty area update before allowing readers to see a
   // dirty serial.
   memcpy(pa->dirty_backup_area(), pi->value, old_len + 1);
+  if (have_override) {
+    memcpy(override_pa->dirty_backup_area(), override_pi->value, old_len + 1);
+  }
   atomic_thread_fence(memory_order_release);
   serial |= 1;
   atomic_store_explicit(&pi->serial, serial, memory_order_relaxed);
   strlcpy(pi->value, value, len + 1);
+  if (have_override) {
+    atomic_store_explicit(&override_pi->serial, serial, memory_order_relaxed);
+    strlcpy(override_pi->value, value, len + 1);
+  }
   // Now the primary value property area is up-to-date. Let readers know that they should
   // look at the property value instead of the backup area.
   atomic_thread_fence(memory_order_release);
-  atomic_store_explicit(&pi->serial, (len << 24) | ((serial + 1) & 0xffffff), memory_order_relaxed);
+  int new_serial = (len << 24) | ((serial + 1) & 0xffffff);
+  atomic_store_explicit(&pi->serial, new_serial, memory_order_relaxed);
+  if (have_override) {
+    atomic_store_explicit(&override_pi->serial, new_serial, memory_order_relaxed);
+  }
   __futex_wake(&pi->serial, INT32_MAX);  // Fence by side effect
   atomic_store_explicit(serial_pa->serial(),
                         atomic_load_explicit(serial_pa->serial(), memory_order_relaxed) + 1,
                         memory_order_release);
+  if (have_override) {
+    atomic_store_explicit(override_serial_pa->serial(),
+                          atomic_load_explicit(serial_pa->serial(), memory_order_relaxed) + 1,
+                          memory_order_release);
+  }
   __futex_wake(serial_pa->serial(), INT32_MAX);
 
   return 0;
@@ -293,6 +361,34 @@
     return -1;
   }
 
+  if (appcompat_override_contexts_ != nullptr) {
+    bool is_override = is_appcompat_override(name);
+    const char* override_name = name;
+    if (is_override) override_name += strlen(APPCOMPAT_PREFIX);
+    prop_area* other_pa = appcompat_override_contexts_->GetPropAreaForName(override_name);
+    prop_area* other_serial_pa = appcompat_override_contexts_->GetSerialPropArea();
+    CHECK(other_pa && other_serial_pa);
+    // We may write a property twice to overrides, once for the ro.*, and again for the
+    // ro.appcompat_override.ro.* property. If we've already written, then we should essentially
+    // perform an Update, not an Add.
+    auto other_pi = const_cast<prop_info*>(other_pa->find(override_name));
+    if (!other_pi) {
+      if (other_pa->add(override_name, strlen(override_name), value, valuelen)) {
+        atomic_store_explicit(
+            other_serial_pa->serial(),
+            atomic_load_explicit(other_serial_pa->serial(), memory_order_relaxed) + 1,
+            memory_order_release);
+      }
+    } else if (is_override) {
+      // We already wrote the ro.*, but appcompat_override.ro.* should override that. We don't
+      // need to do the usual dirty bit setting, as this only happens during the init process,
+      // before any readers are started. Check that only init or root can write appcompat props.
+      CHECK(getpid() == 1 || getuid() == 0);
+      atomic_thread_fence(memory_order_release);
+      strlcpy(other_pi->value, value, valuelen + 1);
+    }
+  }
+
   // There is only a single mutator, but we want to make sure that
   // updates are visible to a reader waiting for the update.
   atomic_store_explicit(serial_pa->serial(),
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp
index 2411753..14a426f 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -1734,3 +1734,36 @@
     }
   }
 }
+
+TEST(android_mallopt, get_decay_time_enabled_errors) {
+#if defined(__BIONIC__)
+  errno = 0;
+  EXPECT_FALSE(android_mallopt(M_GET_DECAY_TIME_ENABLED, nullptr, sizeof(bool)));
+  EXPECT_ERRNO(EINVAL);
+
+  errno = 0;
+  int value;
+  EXPECT_FALSE(android_mallopt(M_GET_DECAY_TIME_ENABLED, &value, sizeof(value)));
+  EXPECT_ERRNO(EINVAL);
+#else
+  GTEST_SKIP() << "bionic-only test";
+#endif
+}
+
+TEST(android_mallopt, get_decay_time_enabled) {
+#if defined(__BIONIC__)
+  SKIP_WITH_HWASAN << "hwasan does not implement mallopt";
+
+  EXPECT_EQ(1, mallopt(M_DECAY_TIME, 0));
+
+  bool value;
+  EXPECT_TRUE(android_mallopt(M_GET_DECAY_TIME_ENABLED, &value, sizeof(value)));
+  EXPECT_FALSE(value);
+
+  EXPECT_EQ(1, mallopt(M_DECAY_TIME, 1));
+  EXPECT_TRUE(android_mallopt(M_GET_DECAY_TIME_ENABLED, &value, sizeof(value)));
+  EXPECT_TRUE(value);
+#else
+  GTEST_SKIP() << "bionic-only test";
+#endif
+}
diff --git a/tests/system_properties_test.cpp b/tests/system_properties_test.cpp
index b1dfe0b..0b7f5ae 100644
--- a/tests/system_properties_test.cpp
+++ b/tests/system_properties_test.cpp
@@ -25,31 +25,53 @@
 
 #include <android-base/file.h>
 #include <android-base/silent_death_test.h>
+#include <android-base/stringprintf.h>
+
+#include "utils.h"
 
 using namespace std::literals;
 
 #if defined(__BIONIC__)
 
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <stdlib.h>
 #include <sys/_system_properties.h>
+#include <sys/mount.h>
 
 #include <system_properties/system_properties.h>
 
 class SystemPropertiesTest : public SystemProperties {
  public:
   SystemPropertiesTest() : SystemProperties(false) {
-    valid_ = AreaInit(dir_.path, nullptr);
+    appcompat_path = android::base::StringPrintf("%s/appcompat_override", dir_.path);
+    mount_path = android::base::StringPrintf("%s/__properties__", dir_.path);
+    mkdir(appcompat_path.c_str(), S_IRWXU | S_IXGRP | S_IXOTH);
+    valid_ = AreaInit(dir_.path, nullptr, true);
   }
   ~SystemPropertiesTest() {
     if (valid_) {
       contexts_->FreeAndUnmap();
     }
+    umount2(dir_.path, MNT_DETACH);
+    umount2(real_sysprop_dir.c_str(), MNT_DETACH);
   }
 
   bool valid() const {
     return valid_;
   }
 
+  const char* get_path() const { return dir_.path; }
+
+  const char* get_appcompat_path() const { return appcompat_path.c_str(); }
+
+  const char* get_mount_path() const { return mount_path.c_str(); }
+
+  const char* get_real_sysprop_dir() const { return real_sysprop_dir.c_str(); }
+
+  std::string appcompat_path;
+  std::string mount_path;
+  std::string real_sysprop_dir = "/dev/__properties__";
+
  private:
   TemporaryDir dir_;
   bool valid_;
@@ -125,6 +147,58 @@
 #endif // __BIONIC__
 }
 
+TEST(properties, __system_property_add_appcompat) {
+#if defined(__BIONIC__)
+    if (getuid() != 0) GTEST_SKIP() << "test requires root";
+    SystemPropertiesTest system_properties;
+    ASSERT_TRUE(system_properties.valid());
+
+    char name[] = "ro.property";
+    char override_name[] = "ro.appcompat_override.ro.property";
+    char name_not_written[] = "ro.property_other";
+    char override_with_no_real[] = "ro.appcompat_override.ro.property_other";
+    ASSERT_EQ(0, system_properties.Add(name, strlen(name), "value1", 6));
+    ASSERT_EQ(0, system_properties.Add(override_name, strlen(override_name), "value2", 6));
+    ASSERT_EQ(0, system_properties.Add(override_with_no_real, strlen(override_with_no_real),
+                                       "value3", 6));
+
+    char propvalue[PROP_VALUE_MAX];
+    ASSERT_EQ(6, system_properties.Get(name, propvalue));
+    ASSERT_STREQ(propvalue, "value1");
+
+    ASSERT_EQ(6, system_properties.Get(override_name, propvalue));
+    ASSERT_STREQ(propvalue, "value2");
+
+    ASSERT_EQ(0, system_properties.Get(name_not_written, propvalue));
+    ASSERT_STREQ(propvalue, "");
+
+    ASSERT_EQ(6, system_properties.Get(override_with_no_real, propvalue));
+    ASSERT_STREQ(propvalue, "value3");
+
+    int ret = mount(system_properties.get_appcompat_path(), system_properties.get_path(), nullptr,
+                    MS_BIND | MS_REC, nullptr);
+    if (ret != 0) {
+      ASSERT_ERRNO(0);
+    }
+    system_properties.Reload(true);
+
+    ASSERT_EQ(6, system_properties.Get(name, propvalue));
+    ASSERT_STREQ(propvalue, "value2");
+
+    ASSERT_EQ(0, system_properties.Get(override_name, propvalue));
+    ASSERT_STREQ(propvalue, "");
+
+    ASSERT_EQ(6, system_properties.Get(name_not_written, propvalue));
+    ASSERT_STREQ(propvalue, "value3");
+
+    ASSERT_EQ(0, system_properties.Get(override_with_no_real, propvalue));
+    ASSERT_STREQ(propvalue, "");
+
+#else   // __BIONIC__
+    GTEST_SKIP() << "bionic-only test";
+#endif  // __BIONIC__
+}
+
 TEST(properties, __system_property_update) {
 #if defined(__BIONIC__)
     SystemPropertiesTest system_properties;
@@ -432,7 +506,7 @@
 
   // This test only makes sense if we're talking to the real system property service.
   struct stat sb;
-  ASSERT_FALSE(stat(PROP_FILENAME, &sb) == -1 && errno == ENOENT);
+  ASSERT_FALSE(stat(PROP_DIRNAME, &sb) == -1 && errno == ENOENT);
 
   ASSERT_EXIT(__system_property_add("property", 8, "value", 5), KilledByFault(), "");
 #else // __BIONIC__
@@ -526,3 +600,93 @@
   GTEST_SKIP() << "bionic-only test";
 #endif  // __BIONIC__
 }
+
+// Note that this test affects global state of the system
+// this tests tries to mitigate this by using utime+pid
+// prefix for the property name. It is still results in
+// pollution of property service since properties cannot
+// be removed.
+//
+// Note that there is also possibility to run into "out-of-memory"
+// if this test if it is executed often enough without reboot.
+TEST(properties, __system_property_reload_no_op) {
+#if defined(__BIONIC__)
+  std::string property_name =
+      android::base::StringPrintf("debug.test.%d.%" PRId64 ".property", getpid(), NanoTime());
+  ASSERT_EQ(0, __system_property_find(property_name.c_str()));
+  ASSERT_EQ(0, __system_property_set(property_name.c_str(), "test value"));
+  ASSERT_EQ(0, __system_properties_zygote_reload());
+  const prop_info* readptr = __system_property_find(property_name.c_str());
+  std::string expected_name = property_name;
+  __system_property_read_callback(
+      readptr,
+      [](void*, const char*, const char* value, unsigned) { ASSERT_STREQ("test value", value); },
+      &expected_name);
+#else   // __BIONIC__
+  GTEST_SKIP() << "bionic-only test";
+#endif  // __BIONIC__
+}
+
+TEST(properties, __system_property_reload_invalid) {
+#if defined(__BIONIC__)
+  if (getuid() != 0) GTEST_SKIP() << "test requires root";
+  SystemPropertiesTest system_properties;
+
+  // Create an invalid property_info file, so the system will attempt to initialize a
+  // ContextSerialized
+  std::string property_info_file =
+      android::base::StringPrintf("%s/property_info", system_properties.get_path());
+  fclose(fopen(property_info_file.c_str(), "w"));
+  int ret = mount(system_properties.get_path(), system_properties.get_real_sysprop_dir(), nullptr,
+                  MS_BIND | MS_REC, nullptr);
+  if (ret != 0) {
+    ASSERT_ERRNO(0);
+  }
+
+  ASSERT_EQ(-1, __system_properties_zygote_reload());
+#else   // __BIONIC__
+  GTEST_SKIP() << "bionic-only test";
+#endif  // __BIONIC__
+}
+
+// Note that this test affects global state of the system
+// this tests tries to mitigate this by using utime+pid
+// prefix for the property name. It is still results in
+// pollution of property service since properties cannot
+// be removed.
+//
+// Note that there is also possibility to run into "out-of-memory"
+// if this test if it is executed often enough without reboot.
+TEST(properties, __system_property_reload_valid) {
+#if defined(__BIONIC__)
+  if (getuid() != 0) GTEST_SKIP() << "test requires root";
+  SystemPropertiesTest system_properties;
+
+  // Copy the system properties files into the temp directory
+  std::string shell_cmd = android::base::StringPrintf(
+      "cp -r %s %s", system_properties.get_real_sysprop_dir(), system_properties.get_path());
+  system(shell_cmd.c_str());
+
+  // Write a system property to the current set of system properties
+  std::string property_name =
+      android::base::StringPrintf("debug.test.%d.%" PRId64 ".property", getpid(), NanoTime());
+  ASSERT_EQ(0, __system_property_find(property_name.c_str()));
+  ASSERT_EQ(0, __system_property_set(property_name.c_str(), "test value"));
+
+  // Mount the temp directory (which doesn't have the property we just wrote) in place of the
+  // real one
+  int ret = mount(system_properties.get_mount_path(), system_properties.get_real_sysprop_dir(),
+                  nullptr, MS_BIND | MS_REC, nullptr);
+  if (ret != 0) {
+    ASSERT_ERRNO(0);
+  }
+
+  // reload system properties in the new dir, and verify the property we wrote after we copied the
+  // files isn't there
+  ASSERT_EQ(0, __system_properties_zygote_reload());
+  ASSERT_EQ(0, __system_property_find(property_name.c_str()));
+
+#else   // __BIONIC__
+  GTEST_SKIP() << "bionic-only test";
+#endif  // __BIONIC__
+}
diff --git a/tests/system_properties_test2.cpp b/tests/system_properties_test2.cpp
index 0953bde..0795ccd 100644
--- a/tests/system_properties_test2.cpp
+++ b/tests/system_properties_test2.cpp
@@ -28,10 +28,6 @@
 
 #if defined(__BIONIC__)
 #include <sys/system_properties.h>
-int64_t NanoTime() {
-  auto t = std::chrono::time_point_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now());
-  return t.time_since_epoch().count();
-}
 #endif
 
 // Note that this test affects global state of the system
diff --git a/tests/utils.cpp b/tests/utils.cpp
index 0c7c552..e123b42 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -89,6 +89,11 @@
   }
 }
 
+int64_t NanoTime() {
+  auto t = std::chrono::time_point_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now());
+  return t.time_since_epoch().count();
+}
+
 bool operator==(const Errno& lhs, const Errno& rhs) {
   return lhs.errno_ == rhs.errno_;
 }
diff --git a/tests/utils.h b/tests/utils.h
index f6b7174..dcb08f5 100644
--- a/tests/utils.h
+++ b/tests/utils.h
@@ -316,6 +316,8 @@
 
 bool IsLowRamDevice();
 
+int64_t NanoTime();
+
 class Errno {
  public:
   Errno(int e) : errno_(e) {}