Write appcompat_override system properties

Create a second set of system properties, that can be overlaid over the
real ones if necessary, for appcompat purposes.

Bug: 291814949
Ignore-AOSP-First: Aosp -> internal merge conflict
Test: manual, treehugger, system_properties_test

Change-Id: I884a78b67679c1f0b90a6c0159b17ab007f8cc60
diff --git a/libc/bionic/system_property_api.cpp b/libc/bionic/system_property_api.cpp
index a641f12..847473a 100644
--- a/libc/bionic/system_property_api.cpp
+++ b/libc/bionic/system_property_api.cpp
@@ -45,7 +45,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 +55,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 +129,8 @@
 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_reload() {
+  return system_properties.Reload(false) ? 0 : -1;
+}
diff --git a/libc/include/sys/_system_properties.h b/libc/include/sys/_system_properties.h
index 1afb000..0821e24 100644
--- a/libc/include/sys/_system_properties.h
+++ b/libc/include/sys/_system_properties.h
@@ -42,7 +42,7 @@
 
 #define PROP_SERVICE_NAME "property_service"
 #define PROP_SERVICE_FOR_SYSTEM_NAME "property_service_for_system"
-#define PROP_FILENAME "/dev/__properties__"
+#define PROP_DIRNAME "/dev/__properties__"
 
 #define PROP_MSG_SETPROP 1
 #define PROP_MSG_SETPROP2 0x00020001
@@ -130,6 +130,16 @@
  */
 int __system_properties_init(void);
 
+/*
+ * Reloads the system properties from disk.
+ *
+ * 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 otherwise
+ */
+int __system_properties_reload();
+
 /* 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..d737946 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1806,6 +1806,7 @@
     __system_property_area_init;
     __system_property_set_filename;
     __system_property_update;
+    __system_properties_reload;
     android_fdsan_get_fd_table;
     android_fdtrack_compare_exchange_hook; # llndk
     android_fdtrack_get_enabled; # llndk
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..e0b771c 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(getpid() == 1);
+      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(),