0 引言
回顾一下下面这张图,属性初始化过程中,会调用CreateSerializedPropertyInfo()函数。这个函数内部会读取一系列的属性文件,例如:/system/etc/selinux/plat_property_contexts等等。这些文件会通过读取并解析到一个数组Vector中保存起来。接下来就是把这些个数组的数据构建成字典树,然后将字典树序列化为一个std::string,最终写入到/dev/properties/property_info文件中。我们在android中cat读取这个文件,会发现它的乱码的。
1 字典树的构建
字典树的构建和序列化将由BuildTrie()这一个函数完成,如下图所示property_infos就是准备进行字典树构建的数据,serialized_contexts则是字典树序列化之后得到的字符串数据。
进入该函数,可以看到两个关键的工具:字典树构建器和字典树序列化器。
字典树构建器由字典树节点、SeLinux上下文信息和类型组成,它是实际保存数据的位置,字典树节点想要获取到它的SeLinux上下文信息和类型都是通过指针来获取的。下图是它的数据结构图:
TrieBuilder的初始化将根据默认SeLinux上下文信息和默认类型信息构建一个根节点root,然后遍历property_info来将所有的属性文件中数据加入到字典树中。加入字典树的规则如下面的代码所示:
bool TrieBuilder::AddToTrie(const std::string& name, const std::string* context,
const std::string* type, bool exact, std::string* error) {
TrieBuilderNode* current_node = &builder_root_;
auto name_pieces = Split(name, ".");
bool ends_with_dot = false;
if (name_pieces.back().empty()) {
ends_with_dot = true;
name_pieces.pop_back();
}
// Move us to the final node that we care about, adding incremental nodes if necessary.
while (name_pieces.size() > 1) {
auto child = current_node->FindChild(name_pieces.front());
if (child == nullptr) {
child = current_node->AddChild(name_pieces.front());
}
if (child == nullptr) {
*error = "Unable to allocate Trie node";
return false;
}
current_node = child;
name_pieces.erase(name_pieces.begin());
}
// Store our context based on what type of match it is.
if (exact) {
if (!current_node->AddExactMatchContext(name_pieces.front(), context, type)) {
*error = "Duplicate exact match detected for '" + name + "'";
return false;
}
} else if (!ends_with_dot) {
if (!current_node->AddPrefixContext(name_pieces.front(), context, type)) {
*error = "Duplicate prefix match detected for '" + name + "'";
return false;
}
} else {
auto child = current_node->FindChild(name_pieces.front());
if (child == nullptr) {
child = current_node->AddChild(name_pieces.front());
}
if (child == nullptr) {
*error = "Unable to allocate Trie node";
return false;
}
if (child->context() != nullptr || child->type() != nullptr) {
*error = "Duplicate prefix match detected for '" + name + "'";
return false;
}
child->set_context(context);
child->set_type(type);
}
return true;
}
这部分代码的主要目的是通过构建字典树(Trie)的方式,将属性名称及其关联信息(如 SELinux 上下文和类型)插入到合适的节点中。属性名称通过 . 分隔成多个层级,并根据最后一个层级的匹配方式(精确匹配、前缀匹配或模糊匹配)分别处理。分三个步骤进行解析:
a. 将属性名称分割成层级
- 属性名称被
.分割成多个部分,每部分代表属性名称中的一个层级。例如:"service.adb.tcp.port" → ["service", "adb", "tcp", "port"] "service.adb.transport" → ["service", "adb", "transport"] - 如果属性名称以
.结尾(如service.adb.),会特殊处理,将最后的空字符串部分移除并标记为“模糊匹配”。
b. 逐层查找或创建节点
- 从根节点开始,逐层遍历
name_pieces:- 如果当前层级的子节点不存在,则创建新的子节点。
- 移动到下一层节点,直至处理到属性名称的最后一个部分。
c. 根据匹配方式处理最后一个层级
- 对于属性名称的最后一个层级(如
"port"),根据匹配方式分为以下三种情况:- 精确匹配(exact = true):
- 将该属性加入当前节点的
exact_matches_列表中。 - 如果
exact_matches_中已存在同名属性,报错:Duplicate exact match detected。
- 将该属性加入当前节点的
- 前缀匹配(名称不以
.结尾):- 将该属性加入当前节点的
prefixes_列表中。 - 如果
prefixes_中已存在同名前缀,报错:Duplicate prefix match detected。
- 将该属性加入当前节点的
- 模糊匹配(名称以
.结尾):- 查找当前节点下是否已有对应的子节点。如果不存在,则创建新的子节点。
- 为该节点设置 SELinux 上下文和类型信息。如果节点已有相关信息,报错:
Duplicate prefix match detected。
- 精确匹配(exact = true):
最后得到的字典树示意图如下:
2 字典树的序列化
字典树的序列化需要依赖一个序列化器TrieSerializer,它和一个内存分配器TrieNodeArena配合使用。它们之间的关系如下图所示:
下面是TrieNodeArena分配数据的函数,它的核心目标是保证分配的内存块是4字节的倍数,在空间不足时按需扩展data_的大小,避免手动管理堆内存。
void* AllocateData(size_t size, uint32_t* offset) {
size_t aligned_size = size + (sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1);
if (current_data_pointer_ + aligned_size > data_.size()) {
auto new_size = (current_data_pointer_ + aligned_size + data_.size()) * 2;
data_.resize(new_size, '\0');
}
if (offset) *offset = current_data_pointer_;
uint32_t return_offset = current_data_pointer_;
current_data_pointer_ += aligned_size;
return &data_[0] + return_offset;
}
字典树序列化器通过SerializeTrie函数依次将内存管理头部PropertyInfoAreaHeader、SeLinux上下文信息、类型信息、字典树节点写入到内存(实际就是个可以自动扩容的字符串)中去。字典树节点写入的时候会采取递归的方式写入。这部分的代码如下:
std::string TrieSerializer::SerializeTrie(const TrieBuilder& trie_builder) {
arena_.reset(new TrieNodeArena());
auto header = arena_->AllocateObject<PropertyInfoAreaHeader>(nullptr);
header->current_version = 1;
header->minimum_supported_version = 1;
// Store where we're about to write the contexts.
header->contexts_offset = arena_->size();
SerializeStrings(trie_builder.contexts());
// Store where we're about to write the types.
header->types_offset = arena_->size();
SerializeStrings(trie_builder.types());
// We need to store size() up to this point now for Find*Offset() to work.
header->size = arena_->size();
uint32_t root_trie_offset = WriteTrieNode(trie_builder.builder_root());
header->root_offset = root_trie_offset;
// Record the real size now that we've written everything
header->size = arena_->size();
return arena_->truncated_data();
}
3 总结
到这一步之后,整个字典树的构建以及字典树的序列化就已经完成了。也就是完成这样一个工作,把读取到的一系列属性文件读取到Vector后,将它解析并构建为一个字典树,方便查询和修改,然后将字典树的数据写入到内存中(一个自动扩容的std::string)。