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(),