diff --git a/Android.mk b/Android.mk new file mode 100644 index 000000000..5e989df73 --- /dev/null +++ b/Android.mk @@ -0,0 +1,20 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE:= libsystemproperties +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) +LOCAL_STATIC_LIBRARIES := libcxx +LOCAL_CFLAGS := -std=c++17 -Wno-unused-function +LOCAL_SRC_FILES := \ + context_node.cpp \ + contexts_serialized.cpp \ + contexts_split.cpp \ + prop_area.cpp \ + prop_info.cpp \ + system_properties.cpp \ + system_property_api.cpp \ + system_property_set.cpp \ + property_info_parser.cpp + +include $(BUILD_STATIC_LIBRARY) diff --git a/context_node.cpp b/context_node.cpp index 572bf97ce..c5b67d065 100644 --- a/context_node.cpp +++ b/context_node.cpp @@ -31,8 +31,6 @@ #include #include -#include - #include "system_properties/system_properties.h" // pthread_mutex_lock() calls into system_properties in the case of contention. @@ -53,7 +51,7 @@ bool ContextNode::Open(bool access_rw, bool* fsetxattr_failed) { if (access_rw) { pa_ = prop_area::map_prop_area_rw(filename.c_str(), context_, fsetxattr_failed); } else { - pa_ = prop_area::map_prop_area(filename.c_str()); + pa_ = prop_area::map_prop_area(filename.c_str(), nullptr); } lock_.unlock(); return pa_; diff --git a/contexts_serialized.cpp b/contexts_serialized.cpp index 73c913640..d566ad86e 100644 --- a/contexts_serialized.cpp +++ b/contexts_serialized.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -37,9 +38,6 @@ #include -#include -#include - #include "system_properties/system_properties.h" bool ContextsSerialized::InitializeContextNodes() { @@ -71,7 +69,7 @@ bool ContextsSerialized::MapSerialPropertyArea(bool access_rw, bool* fsetxattr_f serial_prop_area_ = prop_area::map_prop_area_rw( serial_filename_.c_str(), "u:object_r:properties_serial:s0", fsetxattr_failed); } else { - serial_prop_area_ = prop_area::map_prop_area(serial_filename_.c_str()); + serial_prop_area_ = prop_area::map_prop_area(serial_filename_.c_str(), &rw_); } return serial_prop_area_; } @@ -148,6 +146,12 @@ prop_area* ContextsSerialized::GetPropAreaForName(const char* name) { return context_node->pa(); } +const char* ContextsSerialized::GetContextForName(const char* name) { + const char* context; + property_info_area_file_->GetPropertyInfo(name, &context, nullptr); + return context; +} + void ContextsSerialized::ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) { for (size_t i = 0; i < num_context_nodes_; ++i) { if (context_nodes_[i].CheckAccessAndOpen()) { @@ -156,6 +160,37 @@ void ContextsSerialized::ForEach(void (*propfn)(const prop_info* pi, void* cooki } } +bool ContextsSerialized::Compact() { + for (size_t i = 0; i < num_context_nodes_; ++i) { + if (!context_nodes_[i].CheckAccessAndOpen() || !context_nodes_[i].pa()->compact()) { + return false; + } + } + return !serial_prop_area_ || serial_prop_area_->compact(); +} + +bool ContextsSerialized::CompactContext(const char* context, bool* found) { + bool ret = true; + *found = false; + + for (size_t i = 0; i < num_context_nodes_; ++i) { + if (!strcmp(context_nodes_[i].context(), context)) { + *found = true; + if (!context_nodes_[i].CheckAccessAndOpen() || !context_nodes_[i].pa()->compact()) { + ret = false; + } + } + } + + if (!*found && serial_prop_area_ && + !strcmp(context, "u:object_r:properties_serial:s0")) { + *found = true; + ret = serial_prop_area_->compact(); + } + + return *found && ret; +} + void ContextsSerialized::ResetAccess() { for (size_t i = 0; i < num_context_nodes_; ++i) { context_nodes_[i].ResetAccess(); diff --git a/contexts_split.cpp b/contexts_split.cpp index 78bdc6436..cc15a4b8b 100644 --- a/contexts_split.cpp +++ b/contexts_split.cpp @@ -34,8 +34,6 @@ #include #include -#include - #include "system_properties/context_node.h" #include "system_properties/system_properties.h" @@ -197,7 +195,7 @@ bool ContextsSplit::MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed serial_prop_area_ = prop_area::map_prop_area_rw( 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(filename.c_str(), &rw_); } return serial_prop_area_; } @@ -268,6 +266,9 @@ bool ContextsSplit::InitializeProperties() { // still need the system / platform properties to function. if (access("/vendor/etc/selinux/vendor_property_contexts", R_OK) != -1) { InitializePropertiesFromFile("/vendor/etc/selinux/vendor_property_contexts"); + } else { + // Fallback to nonplat_* if vendor_* doesn't exist. + InitializePropertiesFromFile("/vendor/etc/selinux/nonplat_property_contexts"); } } else { if (!InitializePropertiesFromFile("/plat_property_contexts")) { @@ -275,6 +276,9 @@ bool ContextsSplit::InitializeProperties() { } if (access("/vendor_property_contexts", R_OK) != -1) { InitializePropertiesFromFile("/vendor_property_contexts"); + } else { + // Fallback to nonplat_* if vendor_* doesn't exist. + InitializePropertiesFromFile("/nonplat_property_contexts"); } } @@ -336,6 +340,14 @@ prop_area* ContextsSplit::GetPropAreaForName(const char* name) { return cnode->pa(); } +const char* ContextsSplit::GetContextForName(const char* name) { + auto entry = GetPrefixNodeForName(name); + if (!entry) { + return nullptr; + } + return entry->context->context(); +} + void ContextsSplit::ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) { ListForEach(contexts_, [propfn, cookie](ContextListNode* l) { if (l->CheckAccessAndOpen()) { @@ -344,6 +356,34 @@ void ContextsSplit::ForEach(void (*propfn)(const prop_info* pi, void* cookie), v }); } +bool ContextsSplit::Compact() { + bool ret = true; + ListForEach(contexts_, [&ret](ContextListNode* l) { + ret = ret && l->CheckAccessAndOpen() && l->pa()->compact(); + }); + return ret && (!serial_prop_area_ || serial_prop_area_->compact()); +} + +bool ContextsSplit::CompactContext(const char* context, bool* found) { + bool ret = true; + *found = false; + + ListForEach(contexts_, [&ret, found, context](ContextListNode* l) { + if (!strcmp(l->context(), context)) { + *found = true; + ret = ret && l->CheckAccessAndOpen() && l->pa()->compact(); + } + }); + + if (!*found && serial_prop_area_ && + !strcmp(context, "u:object_r:properties_serial:s0")) { + *found = true; + ret = serial_prop_area_->compact(); + } + + return *found && ret; +} + void ContextsSplit::ResetAccess() { ListForEach(contexts_, [](ContextListNode* l) { l->ResetAccess(); }); } diff --git a/include/api/hacks.h b/include/api/hacks.h new file mode 100644 index 000000000..8638d1c1a --- /dev/null +++ b/include/api/hacks.h @@ -0,0 +1,32 @@ +#pragma once + +#undef __INTRODUCED_IN +#define __INTRODUCED_IN(...) + +#undef __BIONIC_AVAILABILITY_GUARD +#define __BIONIC_AVAILABILITY_GUARD(...) 1 + +// +#define CHECK(x) /* NOP */ + +// +#define async_safe_format_buffer snprintf +#define async_safe_format_log(...) /* NOP */ + +// Rename symbols +#pragma redefine_extname __system_property_set __system_property_set2 +#pragma redefine_extname __system_property_find __system_property_find2 +#pragma redefine_extname __system_property_read_callback __system_property_read_callback2 +#pragma redefine_extname __system_property_foreach __system_property_foreach2 +#pragma redefine_extname __system_property_wait __system_property_wait2 +#pragma redefine_extname __system_property_read __system_property_read2 +#pragma redefine_extname __system_property_get __system_property_get2 +#pragma redefine_extname __system_property_find_nth __system_property_find_nth2 +#pragma redefine_extname __system_property_set_filename __system_property_set_filename2 +#pragma redefine_extname __system_property_area_init __system_property_area_init2 +#pragma redefine_extname __system_property_area_serial __system_property_area_serial2 +#pragma redefine_extname __system_property_add __system_property_add2 +#pragma redefine_extname __system_property_update __system_property_update2 +#pragma redefine_extname __system_property_serial __system_property_serial2 +#pragma redefine_extname __system_properties_init __system_properties_init2 +#pragma redefine_extname __system_property_wait_any __system_property_wait_any2 diff --git a/include/api/system_properties.h b/include/api/system_properties.h index 1303079f0..e6a1afd93 100644 --- a/include/api/system_properties.h +++ b/include/api/system_properties.h @@ -38,6 +38,8 @@ #include #include +#include "hacks.h" + __BEGIN_DECLS /** An opaque structure representing a system property. */ @@ -229,6 +231,34 @@ int __system_property_add(const char* _Nonnull __name, unsigned int __name_lengt */ int __system_property_update(prop_info* _Nonnull __pi, const char* _Nonnull __value, unsigned int __value_length); +/** + * Delete a system property. + * + * Returns 0 on success, -1 if the property area is full. + */ +int __system_property_delete(const char* _Nonnull __name, bool __prune); + +/** + * Compact the property area to preserve dictionary tree structure after deletions. + * + * Returns 0 on success, -1 on failure. + */ +bool __system_property_compact(void); + +/** + * Compact the property area for a specific context label. + * + * Returns true on success, false on failure or if the context was not found. + */ +bool __system_property_compact_context(const char* _Nonnull __context); + +/** + * Get context of a property. + * + * Returns the context on success, nullptr if fail. + */ +const char* _Nullable __system_property_get_context(const char* _Nonnull __name); + /** * Reloads the system properties from disk. * Not intended for use by any apps except the Zygote. @@ -247,11 +277,4 @@ int __system_properties_zygote_reload(void) __INTRODUCED_IN(35); #endif /* __BIONIC_AVAILABILITY_GUARD(35) */ -/** - * Deprecated: previously for testing, but now that SystemProperties is its own - * testable class, there is never a reason to call this function and its - * implementation simply returns -1. - */ -int __system_property_set_filename(const char* _Nullable __unused __filename); - __END_DECLS diff --git a/include/system_properties/contexts.h b/include/system_properties/contexts.h index df8c5a2cd..3d1f26935 100644 --- a/include/system_properties/contexts.h +++ b/include/system_properties/contexts.h @@ -40,7 +40,11 @@ class Contexts { bool load_default_path = false) = 0; virtual prop_area* GetPropAreaForName(const char* name) = 0; virtual prop_area* GetSerialPropArea() = 0; + virtual const char* GetContextForName(const char* name) = 0; virtual void ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) = 0; + virtual bool Compact() = 0; + virtual bool CompactContext(const char* context, bool* found) = 0; virtual void ResetAccess() = 0; virtual void FreeAndUnmap() = 0; + bool rw_ = false; }; diff --git a/include/system_properties/contexts_pre_split.h b/include/system_properties/contexts_pre_split.h index a6cd039b0..fd34b8e0f 100644 --- a/include/system_properties/contexts_pre_split.h +++ b/include/system_properties/contexts_pre_split.h @@ -28,6 +28,8 @@ #pragma once +#include + #include "contexts.h" #include "prop_area.h" #include "prop_info.h" @@ -39,7 +41,7 @@ class ContextsPreSplit : public Contexts { // We'll never initialize this legacy option as writable, so don't even check the arg. virtual bool Initialize(bool, const char* filename, bool*, bool) override { - pre_split_prop_area_ = prop_area::map_prop_area(filename); + pre_split_prop_area_ = prop_area::map_prop_area(filename, &rw_); return pre_split_prop_area_ != nullptr; } @@ -51,10 +53,27 @@ class ContextsPreSplit : public Contexts { return pre_split_prop_area_; } + virtual const char* GetContextForName(const char*) override { + return "u:object_r:properties_device:s0"; + } + virtual void ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) override { pre_split_prop_area_->foreach (propfn, cookie); } + virtual bool Compact() override { + return pre_split_prop_area_->compact(); + } + + virtual bool CompactContext(const char* context, bool* found) override { + if (context && !strcmp(context, GetContextForName(""))) { + *found = true; + return pre_split_prop_area_->compact(); + } + *found = false; + return false; + } + // This is a no-op for pre-split properties as there is only one property file and it is // accessible by all domains virtual void ResetAccess() override { diff --git a/include/system_properties/contexts_serialized.h b/include/system_properties/contexts_serialized.h index 8bb0b1136..91a4420d0 100644 --- a/include/system_properties/contexts_serialized.h +++ b/include/system_properties/contexts_serialized.h @@ -45,7 +45,10 @@ class ContextsSerialized : public Contexts { virtual prop_area* GetSerialPropArea() override { return serial_prop_area_; } + virtual const char* GetContextForName(const char* name) override; virtual void ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) override; + virtual bool Compact() override; + virtual bool CompactContext(const char* context, bool* found) override; virtual void ResetAccess() override; virtual void FreeAndUnmap() override; diff --git a/include/system_properties/contexts_split.h b/include/system_properties/contexts_split.h index 321cfd25d..1eb5cbc92 100644 --- a/include/system_properties/contexts_split.h +++ b/include/system_properties/contexts_split.h @@ -44,7 +44,10 @@ class ContextsSplit : public Contexts { virtual prop_area* GetSerialPropArea() override { return serial_prop_area_; } + virtual const char* GetContextForName(const char* name) override; virtual void ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) override; + virtual bool Compact() override; + virtual bool CompactContext(const char* context, bool* found) override; virtual void ResetAccess() override; virtual void FreeAndUnmap() override; diff --git a/include/system_properties/prop_area.h b/include/system_properties/prop_area.h index 089cf5274..996ff8e57 100644 --- a/include/system_properties/prop_area.h +++ b/include/system_properties/prop_area.h @@ -93,7 +93,7 @@ class prop_area { public: static prop_area* map_prop_area_rw(const char* filename, const char* context, bool* fsetxattr_failed); - static prop_area* map_prop_area(const char* filename); + static prop_area* map_prop_area(const char* filename, bool *is_rw); static void unmap_prop_area(prop_area** pa) { if (*pa) { munmap(*pa, pa_size_); @@ -124,6 +124,8 @@ class prop_area { const prop_info* find(const char* name); bool add(const char* name, unsigned int namelen, const char* value, unsigned int valuelen); + bool remove(const char* name, bool prune); + bool compact(); bool foreach (void (*propfn)(const prop_info* pi, void* cookie), void* cookie); @@ -139,7 +141,7 @@ class prop_area { char* dirty_backup_area() { return data_ + sizeof(prop_trie_node); } private: - static prop_area* map_fd_ro(const int fd); + static prop_area* map_fd_ro(const int fd, bool rw); void* allocate_obj(const size_t size, uint_least32_t* const off); prop_trie_node* new_prop_trie_node(const char* name, uint32_t namelen, uint_least32_t* const off); @@ -153,6 +155,7 @@ class prop_area { prop_trie_node* find_prop_trie_node(prop_trie_node* const trie, const char* name, uint32_t namelen, bool alloc_if_needed); + prop_trie_node* traverse_trie(prop_trie_node* const trie, const char* name, bool alloc_if_needed); const prop_info* find_property(prop_trie_node* const trie, const char* name, uint32_t namelen, const char* value, uint32_t valuelen, bool alloc_if_needed); @@ -160,6 +163,8 @@ class prop_area { bool foreach_property(prop_trie_node* const trie, void (*propfn)(const prop_info* pi, void* cookie), void* cookie); + bool prune_trie(prop_trie_node* const node); + // The original design doesn't include pa_size or pa_data_size in the prop_area struct itself. // Since we'll need to be backwards compatible with that design, we don't gain much by adding it // now, especially since we don't have any plans to make different property areas different sizes, diff --git a/include/system_properties/prop_info.h b/include/system_properties/prop_info.h index e2236b0cc..efa5c8d54 100644 --- a/include/system_properties/prop_info.h +++ b/include/system_properties/prop_info.h @@ -30,7 +30,7 @@ #include #include -#include +#include #include "platform/bionic/macros.h" diff --git a/include/system_properties/system_properties.h b/include/system_properties/system_properties.h index ea4f3392a..b3308239b 100644 --- a/include/system_properties/system_properties.h +++ b/include/system_properties/system_properties.h @@ -29,7 +29,7 @@ #pragma once #include -#include +#include #include "contexts.h" #include "contexts_pre_split.h" @@ -65,6 +65,10 @@ class SystemProperties { 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); + int Delete(const char* name, bool prune); + bool Compact(); + bool Compact(const char* context); + const char* GetContext(const char* name); 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); diff --git a/prop_area.cpp b/prop_area.cpp index faa3edf00..ef557d2e5 100644 --- a/prop_area.cpp +++ b/prop_area.cpp @@ -37,9 +37,9 @@ #include #include +#include #include - -#include +#include #ifdef LARGE_SYSTEM_PROPERTY_NODE constexpr size_t PA_SIZE = 1024 * 1024; @@ -108,7 +108,7 @@ prop_area* prop_area::map_prop_area_rw(const char* filename, const char* context return pa; } -prop_area* prop_area::map_fd_ro(const int fd) { +prop_area* prop_area::map_fd_ro(const int fd, bool rw) { struct stat fd_stat; if (fstat(fd, &fd_stat) < 0) { return nullptr; @@ -123,7 +123,8 @@ prop_area* prop_area::map_fd_ro(const int fd) { pa_size_ = fd_stat.st_size; pa_data_size_ = pa_size_ - sizeof(prop_area); - void* const map_result = mmap(nullptr, pa_size_, PROT_READ, MAP_SHARED, fd, 0); + int prot = rw ? PROT_READ | PROT_WRITE : PROT_READ; + void* const map_result = mmap(nullptr, pa_size_, prot, MAP_SHARED, fd, 0); if (map_result == MAP_FAILED) { return nullptr; } @@ -137,13 +138,22 @@ prop_area* prop_area::map_fd_ro(const int fd) { return pa; } -prop_area* prop_area::map_prop_area(const char* filename) { - int fd = open(filename, O_CLOEXEC | O_NOFOLLOW | O_RDONLY); - if (fd == -1) return nullptr; +prop_area* prop_area::map_prop_area(const char* filename, bool *is_rw) { + bool rw = false; + int fd = open(filename, O_CLOEXEC | O_NOFOLLOW | O_RDWR); + if (fd == -1) { + fd = open(filename, O_CLOEXEC | O_NOFOLLOW | O_RDONLY); + if (fd == -1) { + return nullptr; + } + } else { + rw = true; + } - prop_area* map_result = map_fd_ro(fd); + prop_area* map_result = map_fd_ro(fd, rw); close(fd); + if (is_rw) *is_rw = rw; return map_result; } @@ -275,9 +285,8 @@ prop_trie_node* prop_area::find_prop_trie_node(prop_trie_node* const trie, const } } -const prop_info* prop_area::find_property(prop_trie_node* const trie, const char* name, - uint32_t namelen, const char* value, uint32_t valuelen, - bool alloc_if_needed) { +prop_trie_node* prop_area::traverse_trie(prop_trie_node *const trie, const char *name, + bool alloc_if_needed) { if (!trie) return nullptr; const char* remaining_name = name; @@ -317,6 +326,15 @@ const prop_info* prop_area::find_property(prop_trie_node* const trie, const char remaining_name = sep + 1; } + return current; +} + +const prop_info* prop_area::find_property(prop_trie_node* const trie, const char* name, + uint32_t namelen, const char* value, uint32_t valuelen, + bool alloc_if_needed) { + prop_trie_node* current = traverse_trie(trie, name, alloc_if_needed); + if (!current) return nullptr; + uint_least32_t prop_offset = atomic_load_explicit(¤t->prop, memory_order_relaxed); if (prop_offset != 0) { return to_prop_info(¤t->prop); @@ -371,3 +389,251 @@ bool prop_area::add(const char* name, unsigned int namelen, const char* value, bool prop_area::foreach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) { return foreach_property(root_node(), propfn, cookie); } + +#define get_offset(ptr) atomic_load_explicit(ptr, memory_order_relaxed) +#define set_offset(ptr, val) atomic_store_explicit(ptr, val, memory_order_release) + +namespace { + +struct LiveObject { + uint_least32_t old_offset; + uint32_t size; + uint32_t aligned_size; + uint_least32_t new_offset; +}; + +struct LongValueRef { + uint_least32_t prop_old_offset; + uint_least32_t long_old_offset; +}; + +inline uint_least32_t ptr_to_offset(const char* base, const void* ptr) { + return static_cast(reinterpret_cast(ptr) - base); +} + +inline prop_trie_node* offset_to_node(char* base, uint_least32_t off) { + return reinterpret_cast(base + off); +} + +inline prop_info* offset_to_info(char* base, uint_least32_t off) { + return reinterpret_cast(base + off); +} + +void collect_live_objects(char* base, prop_trie_node* node, bool skip_node, + std::vector& objects, + std::vector& node_offsets, + std::vector& prop_offsets, + std::vector& long_refs) { + if (!node) return; + + if (!skip_node) { + uint_least32_t node_off = ptr_to_offset(base, node); + uint32_t node_size = sizeof(prop_trie_node) + node->namelen + 1; + objects.push_back({node_off, node_size, + static_cast( + __BIONIC_ALIGN(node_size, sizeof(uint_least32_t))), + 0u}); + node_offsets.push_back(node_off); + } + + uint_least32_t prop_off = get_offset(&node->prop); + if (prop_off != 0) { + prop_info* info = offset_to_info(base, prop_off); + uint32_t name_len = strlen(info->name); + uint32_t info_size = sizeof(prop_info) + name_len + 1; + objects.push_back({prop_off, info_size, + static_cast( + __BIONIC_ALIGN(info_size, sizeof(uint_least32_t))), + 0u}); + prop_offsets.push_back(prop_off); + + if (info->is_long()) { + const char* long_val = info->long_value(); + uint32_t long_len = strlen(long_val); + uint_least32_t long_off = ptr_to_offset(base, long_val); + uint32_t long_size = long_len + 1; + objects.push_back({long_off, long_size, + static_cast( + __BIONIC_ALIGN(long_size, sizeof(uint_least32_t))), + 0u}); + long_refs.push_back({prop_off, long_off}); + } + } + + uint_least32_t left_offset = get_offset(&node->left); + if (left_offset != 0) { + collect_live_objects(base, offset_to_node(base, left_offset), false, objects, + node_offsets, prop_offsets, long_refs); + } + uint_least32_t children_offset = get_offset(&node->children); + if (children_offset != 0) { + collect_live_objects(base, offset_to_node(base, children_offset), false, objects, + node_offsets, prop_offsets, long_refs); + } + uint_least32_t right_offset = get_offset(&node->right); + if (right_offset != 0) { + collect_live_objects(base, offset_to_node(base, right_offset), false, objects, + node_offsets, prop_offsets, long_refs); + } +} + +} // namespace + +// After removing a property, it is possible that redundant nodes will appear in the trie. +// DFS through the data structure, remove leaf nodes that do not hold properties, remove +// them from the trie, then backtrack recursively and remove redundant parent nodes. +// When this method returns true, detach the node from the parent. +bool prop_area::prune_trie(prop_trie_node *const node) { + bool is_leaf = true; + if (get_offset(&node->children) != 0) { + if (prune_trie(to_prop_trie_node(&node->children))) { + set_offset(&node->children, 0u); + } else { + is_leaf = false; + } + } + if (get_offset(&node->left) != 0) { + if (prune_trie(to_prop_trie_node(&node->left))) { + set_offset(&node->left, 0u); + } else { + is_leaf = false; + } + } + if (get_offset(&node->right) != 0) { + if (prune_trie(to_prop_trie_node(&node->right))) { + set_offset(&node->right, 0u); + } else { + is_leaf = false; + } + } + + if (is_leaf && get_offset(&node->prop) == 0) { + // Wipe the node + memset(node->name, 0, node->namelen); + memset(node, 0, sizeof(*node)); + // Then return true to detach the node from parent + return true; + } + return false; +} + +bool prop_area::remove(const char *name, bool prune) { + prop_trie_node *node = traverse_trie(root_node(), name, false); + if (!node) return false; + + uint_least32_t prop_offset = get_offset(&node->prop); + if (prop_offset == 0) return false; + + prop_info *prop = to_prop_info(&node->prop); + + // Detach the property from trie ASAP + set_offset(&node->prop, 0u); + + // Then wipe out the property from memory + if (prop->is_long()) { + char *value = const_cast(prop->long_value()); + memset(value, 0, strlen(value)); + } + memset(prop->name, 0, strlen(prop->name)); + memset(prop, 0, sizeof(*prop)); + + if (prune) { + prune_trie(root_node()); + } + + return true; +} + +bool prop_area::compact() { + const uint32_t data_start = sizeof(prop_trie_node) + + __BIONIC_ALIGN(PROP_VALUE_MAX, sizeof(uint_least32_t)); + const uint32_t old_bytes_used = bytes_used_; + if (old_bytes_used < data_start) { + return false; + } + + std::vector objects; + std::vector node_offsets; + std::vector prop_offsets; + std::vector long_refs; + + collect_live_objects(data_, root_node(), true, objects, node_offsets, prop_offsets, long_refs); + + if (objects.empty()) { + if (old_bytes_used != data_start) { + memset(data_ + data_start, 0, old_bytes_used - data_start); + bytes_used_ = data_start; + } + return true; + } + + std::sort(objects.begin(), objects.end(), + [](const LiveObject& a, const LiveObject& b) { return a.old_offset < b.old_offset; }); + std::sort(long_refs.begin(), long_refs.end(), + [](const LongValueRef& a, const LongValueRef& b) { + return a.prop_old_offset < b.prop_old_offset; + }); + + uint32_t next = data_start; + for (auto& obj : objects) { + obj.new_offset = next; + next += obj.aligned_size; + } + + for (const auto& obj : objects) { + if (obj.new_offset != obj.old_offset) { + memmove(data_ + obj.new_offset, data_ + obj.old_offset, obj.aligned_size); + } + } + + if (next < old_bytes_used) { + memset(data_ + next, 0, old_bytes_used - next); + } + bytes_used_ = next; + + auto remap_offset = [&objects](uint_least32_t old_off) -> uint_least32_t { + if (old_off == 0) return 0; + auto it = std::lower_bound( + objects.begin(), objects.end(), old_off, + [](const LiveObject& obj, uint_least32_t off) { return obj.old_offset < off; }); + if (it == objects.end() || it->old_offset != old_off) { + return old_off; + } + return it->new_offset; + }; + + auto update_node = [&remap_offset](prop_trie_node* node) { + uint_least32_t left = get_offset(&node->left); + if (left != 0) set_offset(&node->left, remap_offset(left)); + uint_least32_t right = get_offset(&node->right); + if (right != 0) set_offset(&node->right, remap_offset(right)); + uint_least32_t children = get_offset(&node->children); + if (children != 0) set_offset(&node->children, remap_offset(children)); + uint_least32_t prop = get_offset(&node->prop); + if (prop != 0) set_offset(&node->prop, remap_offset(prop)); + }; + + update_node(root_node()); + for (uint_least32_t old_off : node_offsets) { + uint_least32_t new_off = remap_offset(old_off); + update_node(offset_to_node(data_, new_off)); + } + + for (uint_least32_t prop_old_off : prop_offsets) { + uint_least32_t prop_new_off = remap_offset(prop_old_off); + prop_info* info = offset_to_info(data_, prop_new_off); + if (!info->is_long()) continue; + + auto it = std::lower_bound( + long_refs.begin(), long_refs.end(), prop_old_off, + [](const LongValueRef& ref, uint_least32_t off) { return ref.prop_old_offset < off; }); + if (it == long_refs.end() || it->prop_old_offset != prop_old_off) { + continue; + } + + uint_least32_t long_new_off = remap_offset(it->long_old_offset); + info->long_property.offset = long_new_off - prop_new_off; + } + + return true; +} diff --git a/system_properties.cpp b/system_properties.cpp index e0d38a822..43eac5076 100644 --- a/system_properties.cpp +++ b/system_properties.cpp @@ -29,7 +29,6 @@ #include "system_properties/system_properties.h" #include -#include #include #include #include @@ -39,9 +38,6 @@ #include -#include -#include - #include "private/ErrnoRestorer.h" #include "private/bionic_futex.h" @@ -101,6 +97,19 @@ bool SystemProperties::InitContexts(bool load_default_path) { return false; } } + + appcompat_filename_ = PropertiesFilename(properties_filename_.c_str(), "appcompat_override"); + appcompat_override_contexts_ = nullptr; + if (access(appcompat_filename_.c_str(), F_OK) != -1) { + auto* appcompat_contexts = new (appcompat_override_contexts_data_) ContextsSerialized(); + if (!appcompat_contexts->Initialize(false, appcompat_filename_.c_str(), nullptr, load_default_path)) { + // The appcompat folder exists, but initializing it failed + return false; + } else { + appcompat_override_contexts_ = appcompat_contexts; + } + } + return true; } @@ -190,9 +199,9 @@ uint32_t SystemProperties::ReadMutablePropertyValue(const prop_info* pi, char* v serial = new_serial; len = SERIAL_VALUE_LEN(serial); if (__predict_false(SERIAL_DIRTY(serial))) { - // See the comment in the prop_area constructor. - prop_area* pa = contexts_->GetPropAreaForName(pi->name); - memcpy(value, pa->dirty_backup_area(), len + 1); + __futex_wait(const_cast(&pi->serial), serial, nullptr); + new_serial = load_const_atomic(&pi->serial, memory_order_relaxed); + continue; } else { memcpy(value, pi->value, len + 1); } @@ -277,6 +286,10 @@ int SystemProperties::Update(prop_info* pi, const char* value, unsigned int len) } bool have_override = appcompat_override_contexts_ != nullptr; + if (!contexts_->rw_) { + return -1; + } + prop_area* serial_pa = contexts_->GetSerialPropArea(); prop_area* override_serial_pa = have_override ? appcompat_override_contexts_->GetSerialPropArea() : nullptr; @@ -295,23 +308,10 @@ int SystemProperties::Update(prop_info* pi, const char* value, unsigned int len) auto* override_pi = const_cast(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); - - // The contract with readers is that whenever the dirty bit is set, an undamaged copy - // of the pre-dirty value is available in the dirty backup area. The fence ensures - // 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); + strncpy(pi->value, value, PROP_VALUE_MAX); if (have_override) { - atomic_store_explicit(&override_pi->serial, serial, memory_order_relaxed); - strlcpy(override_pi->value, value, len + 1); + strncpy(override_pi->value, value, PROP_VALUE_MAX); } // 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. @@ -332,6 +332,15 @@ int SystemProperties::Update(prop_info* pi, const char* value, unsigned int len) } __futex_wake(serial_pa->serial(), INT32_MAX); + // Now that the serial value has been updated so waits on that serial has been unblocked, + // we restore the serial number back to the original value to hide traces of modification. + atomic_thread_fence(memory_order_release); + 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); + } + return 0; } @@ -356,6 +365,10 @@ int SystemProperties::Add(const char* name, unsigned int namelen, const char* va return -1; } + if (!contexts_->rw_) { + return -1; + } + prop_area* serial_pa = contexts_->GetSerialPropArea(); if (serial_pa == nullptr) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", @@ -413,6 +426,110 @@ int SystemProperties::Add(const char* name, unsigned int namelen, const char* va return 0; } +int SystemProperties::Delete(const char *name, bool prune) { + if (!initialized_) { + return -1; + } + + if (!contexts_->rw_) { + return -1; + } + + prop_area* serial_pa = contexts_->GetSerialPropArea(); + if (serial_pa == nullptr) { + return -1; + } + + prop_area* pa = contexts_->GetPropAreaForName(name); + if (!pa) { + return -1; + } + + if (!pa->remove(name, prune)) { + 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); + if (other_pa->remove(override_name, prune)) { + atomic_store_explicit( + other_serial_pa->serial(), + atomic_load_explicit(other_serial_pa->serial(), memory_order_relaxed) + 1, + memory_order_release); + } + } + + // 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(), + atomic_load_explicit(serial_pa->serial(), memory_order_relaxed) + 1, + memory_order_release); + __futex_wake(serial_pa->serial(), INT32_MAX); + return 0; +} + +bool SystemProperties::Compact() { + if (!initialized_) { + return false; + } + + if (!contexts_->rw_) { + return false; + } + + bool ret = contexts_->Compact(); + if (appcompat_override_contexts_ != nullptr) { + ret &= appcompat_override_contexts_->Compact(); + } + + return ret; +} + +bool SystemProperties::Compact(const char* context) { + if (!initialized_) { + return false; + } + + if (!contexts_->rw_) { + return false; + } + + if (context == nullptr || context[0] == '\0') { + return Compact(); + } + + bool found_main = false; + bool ret = contexts_->CompactContext(context, &found_main); + bool found_any = found_main; + if (!found_main) { + ret = true; + } + + if (appcompat_override_contexts_ != nullptr) { + bool found_override = false; + bool ret_override = appcompat_override_contexts_->CompactContext(context, &found_override); + if (found_override) { + found_any = true; + ret &= ret_override; + } + } + + return found_any && ret; +} + +const char* SystemProperties::GetContext(const char* name) { + if (!initialized_) { + return nullptr; + } + + return contexts_->GetContextForName(name); +} + uint32_t SystemProperties::WaitAny(uint32_t old_serial) { uint32_t new_serial; Wait(nullptr, old_serial, &new_serial, nullptr); diff --git a/system_property_api.cpp b/system_property_api.cpp index ed30fc2a6..e967ed209 100644 --- a/system_property_api.cpp +++ b/system_property_api.cpp @@ -26,9 +26,8 @@ * SUCH DAMAGE. */ -#include +#include -#include #include #include @@ -48,11 +47,6 @@ int __system_properties_init() { return system_properties.Init(PROP_DIRNAME) ? 0 : -1; } -__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_fail = false; @@ -92,6 +86,26 @@ 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_delete(const char* name, bool prune) { + return system_properties.Delete(name, prune); +} + +__BIONIC_WEAK_FOR_NATIVE_BRIDGE +bool __system_property_compact() { + return system_properties.Compact(); +} + +__BIONIC_WEAK_FOR_NATIVE_BRIDGE +bool __system_property_compact_context(const char* context) { + return system_properties.Compact(context); +} + +__BIONIC_WEAK_FOR_NATIVE_BRIDGE +const char* __system_property_get_context(const char *name) { + return system_properties.GetContext(name); +} + __BIONIC_WEAK_FOR_NATIVE_BRIDGE int __system_property_add(const char* name, unsigned int namelen, const char* value, unsigned int valuelen) { diff --git a/system_property_set.cpp b/system_property_set.cpp index 9d734456d..43dd4012a 100644 --- a/system_property_set.cpp +++ b/system_property_set.cpp @@ -34,15 +34,12 @@ #include #include #include -#include +#include #include #include #include #include -#include -#include - #include "private/bionic_defs.h" #include "platform/bionic/macros.h" #include "private/ScopedFd.h"