属性系统源码分析二

前几天连续两次重感冒,停更了几天。今天感冒好得差不多了,电脑又坏了!

这是一个介绍 Android 属性系统的系列文章:

  • Android 属性系统入门
  • 属性文件生成过程分析
  • 如何添加系统属性
  • 属性与 SeLinux
  • 属性系统源码分析一
  • 属性系统源码分析二(本文)
  • 属性系统源码分析三

本文基于 AOSP android-10.0.0_r41 分支讲解

1.回顾

上一节说到,在 init 进程中,会调用到 property_init() 函数来完成属性系统的初始化工作:

cpp 复制代码
void property_init() {

    // 创建 /dev/__properties__ 文件夹
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);

    // 生成 /dev/__properties__/property_info 文件
    CreateSerializedPropertyInfo();

    // 生成属性文件
    if (__system_property_area_init()) {
        LOG(FATAL) << "Failed to initialize property area";
    }

    //这个应该是重复了,新版本已没有
    if (!property_info_area.LoadDefaultPath()) {
        LOG(FATAL) << "Failed to load serialized property info file";
    }
}

在上一节中我要主要分析了 CreateSerializedPropertyInfo() 函数的实现细节,总结一下就是:把各个分区的属性 SeLinux 配置文件解析后加载进内存,序列化处理后,存入 /dev/__properties__/property_info 文件中。可以通过 PropertyInfoArea 类来读写这个文件中的数据,用一张图表示就是:

2. 明确两个名称

为避免混淆,先明确两个名称

在手机上我们的属性大都保存在以下一些文件中:

bash 复制代码
/default.prop
/data/local.prop
/system/build.prop
/system/product/build.prop
/vendor/build.prop
/vendor/odm/etc/build.prop
/vendor/default.prop
/data/property/ 目录下

本文称这些文件叫属性配置文件,这些文件中的内容大概长下面这样:

bash 复制代码
ro.actionable_compatible_property.enabled=true
ro.postinstall.fstab.prefix=/system
ro.secure=0
ro.allow.mock.location=1
ro.debuggable=1
debug.atrace.tags.enableflags=0
dalvik.vm.image-dex2oat-Xms=64m
dalvik.vm.image-dex2oat-Xmx=64m

/dev/__properties__ 目录下,也有很多属性相关的文件:

bash 复制代码
cd /dev/__properties__

ls -lZ 

total 576

-r--r--r-- 1 root root u:object_r:properties_serial:s0                               131072 2023-10-31 10:46 properties_serial

-r--r--r-- 1 root root u:object_r:property_info:s0                                    34640 2023-10-31 10:46 property_info

-r--r--r-- 1 root root u:object_r:apexd_prop:s0                                      131072 2023-10-31 10:46 u:object_r:apexd_prop:s0

-r--r--r-- 1 root root u:object_r:audio_prop:s0                                      131072 2023-10-31 10:46 u:object_r:audio_prop:s0

-r--r--r-- 1 root root u:object_r:bluetooth_a2dp_offload_prop:s0                     131072 2023-10-31 10:46 u:object_r:bluetooth_a2dp_offload_prop:s0

-r--r--r-- 1 root root u:object_r:bluetooth_audio_hal_prop:s0                        131072 2023-10-31 10:46 u:object_r:bluetooth_audio_hal_prop:s0

-r--r--r-- 1 root root u:object_r:bluetooth_prop:s0                                  131072 2023-10-31 10:46 u:object_r:bluetooth_prop:s0

-r--r--r-- 1 root root u:object_r:bootloader_boot_reason_prop:s0                     131072 2023-10-31 10:46 u:object_r:bootloader_boot_reason_prop:s0

# ......

本文称 /dev/__properties__ 目录下的这些文件叫属性文件

properties_serial 与 property_info 存储一些管理信息,暂时不管。其他文件,文件名就是其属性安全上下文,内部存储的内容使用 cat 查看是乱码,分析过代码后,我们就会知道,文件的内容是一个序列化后的字典树,字典树用于存储属性的名字和值,同一个文件中保存的属性都有相同的安全上下文。

3. 源码分析

__system_property_area_init() 函数的基本流程:

  • /dev/__properties__/property_info 文件中加载信息
  • 根据加载到的信息,构建出一个 ContextNode 数组
  • /dev/__properties__ 目录下,创建属性文件

函数的具体实现如下:

cpp 复制代码
// bionic/libc/bionic/system_property_api.cpp

#define PROP_FILENAME "/dev/__properties__"

static SystemProperties system_properties;

__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;
}

system_properties 是在 bionic/libc/bionic/system_property_api.cpp 文件中定义的 static 全局变量。__system_property_area_init() 函数进一步调用到 SystemProperties 的 AreaInit 函数:

cpp 复制代码
// bionic/libc/system_properties/system_properties.cpp
bool SystemProperties::AreaInit(const char* filename, bool* fsetxattr_failed) {
  if (strlen(filename) >= PROP_FILENAME_MAX) {
    return false;
  }
  strcpy(property_filename_, filename);

  contexts_ = new (contexts_data_) ContextsSerialized();
  if (!contexts_->Initialize(true, property_filename_, fsetxattr_failed)) {
    return false;
  }
  initialized_ = true;
  return true;
}

AreaInit 函数中会将 SystemPropertiesContexts* contexts_ 成员初始化为一个 ContextsSerialized 对象,接着调用ContextsSerialized 对象的 Initialize 函数。

ContextsSerialized 类的定义如下:

cpp 复制代码
// bionic/libc/system_properties/include/system_properties/contexts_serialized.h
class ContextsSerialized : public Contexts {
 public:
  virtual ~ContextsSerialized() override {
  }

  virtual bool Initialize(bool writable, const char* filename, bool* fsetxattr_failed) override;
  virtual prop_area* GetPropAreaForName(const char* name) override;
  virtual prop_area* GetSerialPropArea() override {
    return serial_prop_area_;
  }
  virtual void ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) override;
  virtual void ResetAccess() override;
  virtual void FreeAndUnmap() override;

 private:
  bool InitializeContextNodes();
  bool InitializeProperties();
  bool MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed);

  const char* filename_;
  //  /dev/__properties__/property_info 文件对应的管理类 
  android::properties::PropertyInfoAreaFile property_info_area_file_;
  // 是一个 ContextNode 数组,用于保存从 property_info 文件中获取到的数据
  ContextNode* context_nodes_ = nullptr;
  size_t num_context_nodes_ = 0;
  size_t context_nodes_mmap_size_ = 0;
  
  prop_area* serial_prop_area_ = nullptr;
};

这里主要关注 ContextsSerialized 类的成员:

  • PropertyInfoAreaFile property_info_area_file_/dev/__properties__/property_info 文件对应的管理类,通过这个类,可以方便地从文件中读写数据
  • ContextNode* context_nodes 是一个数组,用于保存从 /dev/__properties__/property_info 文件中获取到的数据,ContextNode 有一个内部成员 prop_area* pa_, 是属性文件对应的管理类,可以方便地从属性文件中读写数据

接着看 ContextsSerialized 对象的 Initialize 函数:

cpp 复制代码
// writable:true
// filename:"/dev/__properties__"
bool ContextsSerialized::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
  filename_ = filename;
  if (!InitializeProperties()) {
    return false;
  }

  if (writable) { // 走这个分支
    mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);
    bool open_failed = false;
    if (fsetxattr_failed) {
      *fsetxattr_failed = false;
    }

    for (size_t i = 0; i < num_context_nodes_; ++i) {
      if (!context_nodes_[i].Open(true, fsetxattr_failed)) {
        open_failed = true;
      }
    }
    
    // 走这里 
    if (open_failed || !MapSerialPropertyArea(true, fsetxattr_failed)) {
      FreeAndUnmap();
      return false;
    }
  } else {
    if (!MapSerialPropertyArea(false, nullptr)) {
      FreeAndUnmap();
      return false;
    }
  }
  return true;
}

这个函数主要三部分功能:

  • InitializeProperties 函数,从 /dev/__properties__/property_info 中读取信息,创建一个 ContextNode 数组
  • 通过 ContextNodeopen 函数,在 /dev/__properties__/ 中创建新的属性文件
  • 调用 MapSerialPropertyArea 函数,构建一个特殊文件

接下来逐步分析:

cpp 复制代码
bool ContextsSerialized::InitializeProperties() {
  if (!property_info_area_file_.LoadDefaultPath()) {
    return false;
  }

  if (!InitializeContextNodes()) {
    FreeAndUnmap();
    return false;
  }

  return true;
}
  • LoadDefaultPath/dev/__properties__/property_info 文件加载进内存
  • InitializeContextNodes 函数初始化一个 ContextNode 数组

我们先看看 LoadDefaultPath 的具体实现:

cpp 复制代码
bool PropertyInfoAreaFile::LoadDefaultPath() {
  return LoadPath("/dev/__properties__/property_info");
}

// 接着调用 LoadPath 函数
// 把 /dev/__properties__/property_info 映射到内存 mmap_base_ 中

// filename: "/dev/__properties__/property_info"
bool PropertyInfoAreaFile::LoadPath(const char* filename) {

  int fd = open(filename, O_CLOEXEC | O_NOFOLLOW | O_RDONLY);

  struct stat fd_stat;
  if (fstat(fd, &fd_stat) < 0) {
    close(fd);
    return false;
  }

  if ((fd_stat.st_uid != 0) || (fd_stat.st_gid != 0) ||
      ((fd_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) ||
      (fd_stat.st_size < static_cast<off_t>(sizeof(PropertyInfoArea)))) {
    close(fd);
    return false;
  }

  auto mmap_size = fd_stat.st_size;

  void* map_result = mmap(nullptr, mmap_size, PROT_READ, MAP_SHARED, fd, 0);
  if (map_result == MAP_FAILED) {
    close(fd);
    return false;
  }

  auto property_info_area = reinterpret_cast<PropertyInfoArea*>(map_result);
  if (property_info_area->minimum_supported_version() > 1 ||
      property_info_area->size() != mmap_size) {
    munmap(map_result, mmap_size);
    close(fd);
    return false;
  }

  close(fd);
  mmap_base_ = map_result;
  mmap_size_ = mmap_size;
  return true;
}

整体逻辑很简单:

  • 打开 /dev/__properties__/property_info 文件
  • mmap 映射这个文件到内存
  • 将映射区内存地址强转为管理类 PropertyInfoArea 的指针,通过这个指针就可以读写 /dev/__properties__/property_info 文件中的数据了
  • 把映射区地址保存到成员变量 mmap_base_ 中

接下来看 InitializeContextNodes() 函数:

cpp 复制代码
// 分配一块内存,里面保存 ContextNode 数组
// 把 /dev/__properties__/property_info 文件中的部分信息写入上面的数组
bool ContextsSerialized::InitializeContextNodes() {
  auto num_context_nodes = property_info_area_file_->num_contexts();
  auto context_nodes_mmap_size = sizeof(ContextNode) * num_context_nodes;
  // We want to avoid malloc in system properties, so we take an anonymous map instead (b/31659220).
  void* const map_result = mmap(nullptr, context_nodes_mmap_size, PROT_READ | PROT_WRITE,
                                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (map_result == MAP_FAILED) {
    return false;
  }

  prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, map_result, context_nodes_mmap_size,
        "System property context nodes");

  context_nodes_ = reinterpret_cast<ContextNode*>(map_result);
  num_context_nodes_ = num_context_nodes;
  contxt_nodes_mmap_size_ = context_nodes_mmap_size;
e
  for (size_t i = 0; i < num_context_nodes; ++i) {
    new (&context_nodes_[i]) ContextNode(property_info_area_file_->context(i), filename_);
  }

  return true;
}

这里通过 mmap 了一块匿名内存,然后通过一个 for 循环,将这块内存初始化为一个 ContextNode 的数组结构,每个 ContextNode 对象中的信息保存了一个安全上下文信息和文件路径信息,其中安全上下文信息来自 property_info_area_file_ 对象。

完成了上面的工作,就会通过一个 for 循环,调用每个 ContextNode 对象的 Open 函数了:

cpp 复制代码
bool ContextNode::Open(bool access_rw, bool* fsetxattr_failed) {
  lock_.lock();
  if (pa_) {
    lock_.unlock();
    return true;
  }

  char filename[PROP_FILENAME_MAX];
  // 构建文件的完整路径
  int len = async_safe_format_buffer(filename, sizeof(filename), "%s/%s", filename_, context_);
  if (len < 0 || len >= PROP_FILENAME_MAX) {
    lock_.unlock();
    return false;
  }

  if (access_rw) { // 走这个分支
    pa_ = prop_area::map_prop_area_rw(filename, context_, fsetxattr_failed);
  } else {
    pa_ = prop_area::map_prop_area(filename);
  }
  lock_.unlock();
  return pa_;
}

这里会构建一个文件路径 filename ,其结构就是 /dev/__properties__/ + 安全上下文,接着就调用 map_prop_area_rw 函数,map_prop_area_rw 函数是一个类中的 static 函数,直接通过类名调用:

cpp 复制代码
prop_area* prop_area::map_prop_area_rw(const char* filename, const char* context,
                                       bool* fsetxattr_failed) {
   // 创建文件
  const int fd = open(filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);

  if (fd < 0) {
    if (errno == EACCES) {
      /* for consistency with the case where the process has already
       * mapped the page in and segfaults when trying to write to it
       */
      abort();
    }
    return nullptr;
  }

    // 设置文件安全上下文
  if (context) {
    if (fsetxattr(fd, XATTR_NAME_SELINUX, context, strlen(context) + 1, 0) != 0) {
      async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                            "fsetxattr failed to set context (%s) for \"%s\"", context, filename);
      if (fsetxattr_failed) {
        *fsetxattr_failed = true;
      }
    }
  }

  if (ftruncate(fd, PA_SIZE) < 0) {
    close(fd);
    return nullptr;
  }

  pa_size_ = PA_SIZE;
  pa_data_size_ = pa_size_ - sizeof(prop_area);

    // 把文件映射到内存
  void* const memory_area = mmap(nullptr, pa_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (memory_area == MAP_FAILED) {
    close(fd);
    return nullptr;
  }

    // 使用 prop_area 管理文件映射的内存
  prop_area* pa = new (memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);

  close(fd);
  return pa;
}

map_prop_area_rw 函数中:

  • 创建一个文件,文件名是函数传入的 context 安全上下文
  • 将文件的安全上下文设置为函数参数 context,与文件名相同
  • 把新创建的文件映射到内存,并把这块内存转成这块内存对应的管理类 prop_area 的指针。函数返回后,该指针保存在 ContextNode 的成员 _pa 中。

prop_area 用于管理属性文件映射的内存区域,这里先简单了解一下。

cpp 复制代码
// 目标内存管理类
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 void unmap_prop_area(prop_area** pa) {
    if (*pa) {
      munmap(*pa, pa_size_);
      *pa = nullptr;
    }
  }

  prop_area(const uint32_t magic, const uint32_t version) : magic_(magic), version_(version) {
    atomic_init(&serial_, 0u);
    memset(reserved_, 0, sizeof(reserved_));
    // Allocate enough space for the root node.
    bytes_used_ = sizeof(prop_bt);
  }

  const prop_info* find(const char* name);
  bool add(const char* name, unsigned int namelen, const char* value, unsigned int valuelen);

  bool foreach (void (*propfn)(const prop_info* pi, void* cookie), void* cookie);

  atomic_uint_least32_t* serial() {
    return &serial_;
  }
  uint32_t magic() const {
    return magic_;
  }
  uint32_t version() const {
    return version_;
  }

 private:
  static prop_area* map_fd_ro(const int fd);

  void* allocate_obj(const size_t size, uint_least32_t* const off);
  prop_bt* new_prop_bt(const char* name, uint32_t namelen, uint_least32_t* const off);
  prop_info* new_prop_info(const char* name, uint32_t namelen, const char* value, uint32_t valuelen,
                           uint_least32_t* const off);
  void* to_prop_obj(uint_least32_t off);
  prop_bt* to_prop_bt(atomic_uint_least32_t* off_p);
  prop_info* to_prop_info(atomic_uint_least32_t* off_p);

  prop_bt* root_node();

  prop_bt* find_prop_bt(prop_bt* const bt, const char* name, uint32_t namelen, bool alloc_if_needed);

  const prop_info* find_property(prop_bt* const trie, const char* name, uint32_t namelen,
                                 const char* value, uint32_t valuelen, bool alloc_if_needed);

  bool foreach_property(prop_bt* const trie, void (*propfn)(const prop_info* pi, void* cookie),
                        void* cookie);

  // 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,
  // and thus we share these two variables among all instances.
  static size_t pa_size_;
  static size_t pa_data_size_;

  uint32_t bytes_used_;
  atomic_uint_least32_t serial_;
  uint32_t magic_;
  uint32_t version_;
  uint32_t reserved_[28];
  char data_[0];

  BIONIC_DISALLOW_COPY_AND_ASSIGN(prop_area);
};

先看成员变量,主要是一些头信息,最后是一个 0 长数组,代表一个地址,剩下的就是配套的函数操作了,这个一般用到的时候再分析。

最后会调用到 MapSerialPropertyArea 函数,创建一个特殊文件 /dev/__properties__/properties_serial

cpp 复制代码
bool ContextsSerialized::MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed) {
  char filename[PROP_FILENAME_MAX];
  // 一个特殊文件 /dev/__properties__/properties_serial
  int len = async_safe_format_buffer(filename, sizeof(filename), "%s/properties_serial", filename_);
  if (len < 0 || len >= PROP_FILENAME_MAX) {
    serial_prop_area_ = nullptr;
    return false;
  }

  if (access_rw) { //走这个分支
    serial_prop_area_ =
        prop_area::map_prop_area_rw(filename, "u:object_r:properties_serial:s0", fsetxattr_failed);
  } else {
    serial_prop_area_ = prop_area::map_prop_area(filename);
  }
  return serial_prop_area_;
}

目前为止,这个程序的结构如下图所示:

4. 属性 key value 对的加载过程

自此,属性文件都创建好并初始化完毕,那么属性的 key-value 对,怎么保存进属性文件呢?

Android 平台把属性文件通过 mmap 映射到内存中,使用二叉/字典混合树的结构来保存 key-value 对。

映射结构如下:

树的结构如下:

树有两种节点 prop_bt 和 prop_info:

cpp 复制代码
struct prop_bt {

  // name 长度
  uint32_t namelen;
  
  //各个指针,实际存储的是相对当前内存区域起点的偏移值
  atomic_uint_least32_t prop;
  atomic_uint_least32_t left;
  atomic_uint_least32_t right;
  atomic_uint_least32_t children;

  // 0 长数组,代表一个指针
  char name[0];

  prop_bt(const char* name, const uint32_t name_length) {
    this->namelen = name_length;
    memcpy(this->name, name, name_length);
    this->name[name_length] = '\0';
  }

 private:
  BIONIC_DISALLOW_COPY_AND_ASSIGN(prop_bt);
};

// 通常 prop_bt 的 prop 指针指向一个 prop_info 对象
// 主要用于保存完整的属性名与属性值
struct prop_info {
  constexpr static uint32_t kLongFlag = 1 << 16;
  constexpr static size_t kLongLegacyErrorBufferSize = 56;

 public:
  atomic_uint_least32_t serial;
  // 联合体用于保存属性名
  union {
    char value[PROP_VALUE_MAX];
    struct {
      char error_message[kLongLegacyErrorBufferSize];
      uint32_t offset;
    } long_property;
  };

  // 属性名,'/0' 结尾
  char name[0];

  bool is_long() const {
    return (load_const_atomic(&serial, memory_order_relaxed) & kLongFlag) != 0;
  }

  const char* long_value() const {
    return reinterpret_cast<const char*>(this) + long_property.offset;
  }

  prop_info(const char* name, uint32_t namelen, const char* value, uint32_t valuelen);
  prop_info(const char* name, uint32_t namelen, uint32_t long_offset);

 private:
  BIONIC_DISALLOW_IMPLICIT_CONSTRUCTORS(prop_info);
};

init 进程接下来的任务就是加载属性值到这些属性文件中,init 进程会调用 export_kernel_boot_props 和 property_load_boot_defaults 两个函数来完成这个任务,我们逐一分析:

cpp 复制代码
static void export_kernel_boot_props() {
    constexpr const char* UNSET = "";
    struct {
        const char *src_prop;
        const char *dst_prop;
        const char *default_value;
    } prop_map[] = {
        { "ro.boot.serialno",   "ro.serialno",   UNSET, },
        { "ro.boot.mode",       "ro.bootmode",   "unknown", },
        { "ro.boot.baseband",   "ro.baseband",   "unknown", },
        { "ro.boot.bootloader", "ro.bootloader", "unknown", },
        { "ro.boot.hardware",   "ro.hardware",   "unknown", },
        { "ro.boot.revision",   "ro.revision",   "0", },
    };
    for (const auto& prop : prop_map) {
        std::string value = GetProperty(prop.src_prop, prop.default_value);
        if (value != UNSET)
            property_set(prop.dst_prop, value);
    }
}

export_kernel_boot_props() 函数的主要作用是把 Kernel 启动相关的属性写入属性系统。

这里先调用 GetProperty 获取属性值,如果没有值则调用 property_set 来添加值。系统初始化时,还没有写入数据,GetProperty 获取的属性值一定是空。所以我们先看 property_set 的具体实现:

cpp 复制代码
// system/core/init/property_service.cpp

const std::string kInitContext = "u:r:init:s0";

// property_set 是一个函数指针,指向 InitPropertySet
uint32_t (*property_set)(const std::string& name, const std::string& value) = InitPropertySet;

uint32_t InitPropertySet(const std::string& name, const std::string& value) {

    // 做一些具体判断
    if (StartsWith(name, "ctl.")) {
        LOG(ERROR) << "InitPropertySet: Do not set ctl. properties from init; call the Service "
                      "functions directly";
        return PROP_ERROR_INVALID_NAME;
    }
    if (name == "selinux.restorecon_recursive") {
        LOG(ERROR) << "InitPropertySet: Do not set selinux.restorecon_recursive from init; use the "
                      "restorecon builtin directly";
        return PROP_ERROR_INVALID_NAME;
    }

    uint32_t result = 0;
    ucred cr = {.pid = 1, .uid = 0, .gid = 0};
    std::string error;
    // 核心是这个
    result = HandlePropertySet(name, value, kInitContext.c_str(), cr, &error);
    if (result != PROP_SUCCESS) {
        LOG(ERROR) << "Init cannot set '" << name << "' to '" << value << "': " << error;
    }

    return result;
}

开始会对属性值做一些判断,遇到不合规的属性名,直接打 log 报错返回。核心功能实现最终会调用到 HandlePropertySet 函数:

cpp 复制代码
// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                           const std::string& source_context, const ucred& cr, std::string* error) {

    // selinux 的权限检查
    if (auto ret = CheckPermissions(name, value, source_context, cr, error); ret != PROP_SUCCESS) {
        return ret;
    }

    if (StartsWith(name, "ctl.")) {
        HandleControlMessage(name.c_str() + 4, value, cr.pid);
        return PROP_SUCCESS;
    }

    // sys.powerctl is a special property that is used to make the device reboot.  We want to log
    // any process that sets this property to be able to accurately blame the cause of a shutdown.
    if (name == "sys.powerctl") {
        std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
        std::string process_cmdline;
        std::string process_log_string;
        if (ReadFileToString(cmdline_path, &process_cmdline)) {
            // Since cmdline is null deliminated, .c_str() conveniently gives us just the process
            // path.
            process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
        }
        LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                  << process_log_string;
    }

    if (name == "selinux.restorecon_recursive") {
        return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
    }

    // 
    return PropertySet(name, value, error);
}

一堆的操作,最终核心是调用 PropertySet:

cpp 复制代码
static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {

    size_t valuelen = value.size();

    if (!IsLegalPropertyName(name)) {
        *error = "Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
        *error = "Property value too long";
        return PROP_ERROR_INVALID_VALUE;
    }

    if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
        *error = "Value is not a UTF8 encoded string";
        return PROP_ERROR_INVALID_VALUE;
    }

    // 查找属性
    prop_info* pi = (prop_info*) __system_property_find(name.c_str());
    if (pi != nullptr) {
        // ro.* properties are actually "write-once".
        if (StartsWith(name, "ro.")) {
            *error = "Read-only property was already set";
            return PROP_ERROR_READ_ONLY_PROPERTY;
        }

        __system_property_update(pi, value.c_str(), valuelen);
    } else { // 初始化时,查找属性是空,走这里
        int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
        if (rc < 0) {
            *error = "__system_property_add failed";
            return PROP_ERROR_SET_FAILED;
        }
    }

    // Don't write properties to disk until after we have read all default
    // properties to prevent them from being overwritten by default values.
    if (persistent_properties_loaded && StartsWith(name, "persist.")) {
        WritePersistentProperty(name, value);
    }
    property_changed(name, value);
    return PROP_SUCCESS;
}

先通过 __system_property_find 去找,有没有这个属性值,显然这里是没有的,返回空,走 else 分支,调用 __system_property_add 添加属性值:

cpp 复制代码
__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int __system_property_add(const char* name, unsigned int namelen, const char* value,
                          unsigned int valuelen) {
  return system_properties.Add(name, namelen, value, valuelen);
}

int SystemProperties::Add(const char* name, unsigned int namelen, const char* value,
                          unsigned int valuelen) {
  if (valuelen >= PROP_VALUE_MAX && !is_read_only(name)) {
    return -1;
  }

  if (namelen < 1) {
    return -1;
  }

  if (!initialized_) {
    return -1;
  }

  // 获取 /dev/__properties__/properties_serial 文件对应的管理类 prop_area
  prop_area* serial_pa = contexts_->GetSerialPropArea();
  if (serial_pa == nullptr) {
    return -1;
  }

  // ContextsSerialized
  prop_area* pa = contexts_->GetPropAreaForName(name);
  if (!pa) {
    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Access denied adding property \"%s\"", name);
    return -1;
  }

  bool ret = pa->add(name, namelen, value, valuelen);
  if (!ret) {
    return -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(),
                        atomic_load_explicit(serial_pa->serial(), memory_order_relaxed) + 1,
                        memory_order_release);
  __futex_wake(serial_pa->serial(), INT32_MAX);
  return 0;
}

这里通过 ContextsSerialized 对象的 GetPropAreaForName 函数获取到属性对应的 prop_area 对象,其内部实现很简单,先通过 name 获取到对应的 index,在通过 index 找到对应的 ContextNode 对象,ContextNode 对象中保存着有属性文件对应的 prop_area 对象。通过 prop_area 对象就可以操作对应的属性文件了。

cpp 复制代码
prop_area* ContextsSerialized::GetPropAreaForName(const char* name) {
  // name 和 安全上下文值相同,从 /dev/__properties__/property_info 文件中的 contexts_ 中拿到 index
  uint32_t index;
  property_info_area_file_->GetPropertyInfoIndexes(name, &index, nullptr);
  if (index == ~0u || index >= num_context_nodes_) {
    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Could not find context for property \"%s\"",
                          name);
    return nullptr;
  }

  // 拿到 index 对应的 ContextNode
  auto* context_node = &context_nodes_[index];
  if (!context_node->pa()) {
    // We explicitly do not check no_access_ in this case because unlike the
    // case of foreach(), we want to generate an selinux audit for each
    // non-permitted property access in this function.
    context_node->Open(false, nullptr);
  }
  // 拿到属性  文件对应的 prop_area
  return context_node->pa();
}

接着再调用 prop_area 对象的 add 函数添加属性值:

cpp 复制代码
bool prop_area::add(const char* name, unsigned int namelen, const char* value,
                    unsigned int valuelen) {
  return find_property(root_node(), name, namelen, value, valuelen, true);
}

// data_ 数据区 0 位置,强转为 prop_bt*
// prop_bt 结构简单,直接写,省去序列化操作
inline prop_bt* prop_area::root_node() {
  return reinterpret_cast<prop_bt*>(to_prop_obj(0));
}

进一步调用 find_property 函数

cpp 复制代码
const prop_info* prop_area::find_property(prop _bt* const trie, const char* name, uint32_t namelen,
                                          const char* value, uint32_t valuelen,
                                          bool alloc_if_needed) {
  if (!trie) return nullptr;

  const char* remaining_name = name;
  prop_bt* current = trie;
  while (true) {
    const char* sep = strchr(remaining_name, '.');
    const bool want_subtree = (sep != nullptr);
    const uint32_t substr_size = (want_subtree) ? sep - remaining_name : strlen(remaining_name);

    if (!substr_size) {
      return nullptr;
    }

    prop_bt* root = nullptr;
    uint_least32_t children_offset = atomic_load_explicit(&current->children, memory_order_relaxed);
    if (children_offset != 0) {
      root = to_prop_bt(&current->children);
    } else if (alloc_if_needed) { 
      uint_least32_t new_offset;
      root = new_prop_bt(remaining_name, substr_size, &new_offset);
      if (root) {
        atomic_store_explicit(&current->children, new_offset, memory_order_release);
      }
    }

    if (!root) {
      return nullptr;
    }

    // 实际插入数据
    current = find_prop_bt(root, remaining_name, substr_size, alloc_if_needed);
    if (!current) {
      return nullptr;
    }

    if (!want_subtree) break;

    remaining_name = sep + 1;
  }

  uint_least32_t prop_offset = atomic_load_explicit(&current->prop, memory_order_relaxed);
  if (prop_offset != 0) {
    return to_prop_info(&current->prop);
  } else if (alloc_if_needed) {
    uint_least32_t new_offset;
    prop_info* new_info = new_prop_info( , namelen, value, valuelen, &new_offset);
    if (new_info) {
      atomic_store_explicit(&current->prop, new_offset, memory_order_release);
    }

    return new_info;
  } else {
    return nullptr;
  }
}

prop_bt* prop_area::find_prop_bt(prop_bt* const bt, const char* name, uint32_t namelen,
                                 bool alloc_if_needed) {
  prop_bt* current = bt;
  while (true) {
    if (!current) {
      return nullptr;
    }

    const int ret = cmp_prop_name(name, namelen, current->name, current->namelen);
    if (ret == 0) {
      return current;
    }

    if (ret < 0) {
      uint_least32_t left_offset = atomic_load_explicit(&current->left, memory_order_relaxed);
      if (left_offset != 0) {
        current = to_prop_bt(&current->left);
      } else {
        if (!alloc_if_needed) {
          return nullptr;
        }
 
        uint_least32_t new_offset;
        prop_bt* new_bt = new_prop_bt(name, namelen, &new_offset);
        if (new_bt) {
          atomic_store_explicit(&current->left, new_offset, memory_order_release);
        }
        return new_bt;
      }
    } else {
      uint_least32_t right_offset = atomic_load_explicit(&current->right, memory_order_relaxed);
      if (right_offset != 0) {
        current = to_prop_bt(&current->right);
      } else {
        if (!alloc_if_needed) {
          return nullptr;
        }

        uint_least32_t new_offset;
        prop_bt* new_bt = new_prop_bt(name, namelen, &new_offset);
        if (new_bt) {
          atomic_store_explicit(&current->right, new_offset, memory_order_release);
        }
        return new_bt;
      }
    }
  }
}

这里实际会按照本节一开头给出的混合树的结构来创建结构体并做树的插入操作,这个不是我们的重点,有兴趣的同学可以自己捋一下代码。

相关推荐
丘狸尾1 小时前
[cisco 模拟器] ftp服务器配置
android·运维·服务器
van叶~3 小时前
探索未来编程:仓颉语言的优雅设计与无限可能
android·java·数据库·仓颉
Crossoads7 小时前
【汇编语言】端口 —— 「从端口到时间:一文了解CMOS RAM与汇编指令的交汇」
android·java·汇编·深度学习·网络协议·机器学习·汇编语言
li_liuliu8 小时前
Android4.4 在系统中添加自己的System Service
android
C4rpeDime10 小时前
自建MD5解密平台-续
android
鲤籽鲲12 小时前
C# Random 随机数 全面解析
android·java·c#
m0_5485147715 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php
凤邪摩羯16 小时前
Android-性能优化-03-启动优化-启动耗时
android
凤邪摩羯16 小时前
Android-性能优化-02-内存优化-LeakCanary原理解析
android
喀什酱豆腐16 小时前
Handle
android