Android P 属性服务property的初始化与启动

Android P init启动阶段干了啥中简单学习了下init进程在启动阶段都做了哪些工作,大概流程就这个,在这里我们来看下属性服务的初始化和启动

属性服务初始化

在init.cpp的main函数中,通过property_init()对属性服务进行初始化,看下property_init的代码(system\core\init\property_service.cpp)

void property_init() {
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);  // 1
    CreateSerializedPropertyInfo(); // 2 
    if (__system_property_area_init()) { // 3 
        LOG(FATAL) << "Failed to initialize property area";
    }
    if (!property_info_area.LoadDefaultPath()) { // 4
        LOG(FATAL) << "Failed to load serialized property info file";
    }
}

我们可以看到property_init就这几行代码,看着简单感觉还是蛮复杂的,我们就按注释的顺序查看吧

注释1

mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);  // 1

这里创建了一个节点,并且不同的组具有的权限也不一样,这个内核权限是定义在kernel里的stat.h文件,我们可以看到只有所有者才具有读写权限

define S_IRWXU 00700 
define S_IXGRP 00010 
S_IXOTH 00001

注释2

CreateSerializedPropertyInfo()的方法体:

void CreateSerializedPropertyInfo() {
    auto property_infos = std::vector<PropertyInfoEntry>();
    if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
        if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
                                      &property_infos)) {
            return;
        }
        // Don't check for failure here, so we always have a sane list of properties.
        // E.g. In case of recovery, the vendor partition will not have mounted and we
        // still need the system / platform properties to function.
        if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
                                      &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
                                     &property_infos);
        }
    } else {
        if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
            return;
        }
        if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
        }
    }

    auto serialized_contexts = std::string();
    auto error = std::string();
    if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
                   &error)) {
        LOG(ERROR) << "Unable to serialize property contexts: " << error;
        return;
    }

    constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
    if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
        PLOG(ERROR) << "Unable to write serialized property infos to file";
    }
    selinux_android_restorecon(kPropertyInfosPath, 0);
}

在这里首先创建了一个动态数组property_infos,然后通过access做一些权限判断,根据权限的不同会将不同的属性通过LoadPropertyInfoFromFile方法写入到property_infos之中,这里面涉及到selinux部分,这部分太麻烦了,以后有机会再研究研究

注释3

    if (__system_property_area_init()) {
        LOG(FATAL) << "Failed to initialize property area";
    }

来看下这个方法
/bionic/libc/bionic/system_property_api.cpp

__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的AreaInit方法
/bionic/libc/system_properties/system_properties.cpp

bool SystemProperties::Init(const char* filename) {
62  // This is called from __libc_init_common, and should leave errno at 0 (http://b/37248982).
63  ErrnoRestorer errno_restorer;
64
65  if (initialized_) {
66    contexts_->ResetAccess();
67    return true;
68  }
69
70  if (strlen(filename) > PROP_FILENAME_MAX) {
71    return false;
72  }
73  strcpy(property_filename_, filename);
74
75  if (is_dir(property_filename_)) {
76    if (access("/dev/__properties__/property_info", R_OK) == 0) {
77      contexts_ = new (contexts_data_) ContextsSerialized();
78      if (!contexts_->Initialize(false, property_filename_, nullptr)) {
79        return false;
80      }
81    } else {
82      contexts_ = new (contexts_data_) ContextsSplit();
83      if (!contexts_->Initialize(false, property_filename_, nullptr)) {
84        return false;
85      }
86    }
87  } else {
88    contexts_ = new (contexts_data_) ContextsPreSplit();
89    if (!contexts_->Initialize(false, property_filename_, nullptr)) {
90      return false;
91    }
92  }
93  initialized_ = true;
94  return true;
95}

在这里我们可以看到主要就是对属性服务的空间做一些初始化

注释4

    if (!property_info_area.LoadDefaultPath()) {
        LOG(FATAL) << "Failed to load serialized property info file";
    }

看下LoadDefaultPath的代码

bool PropertyInfoAreaFile::LoadDefaultPath() {
  return LoadPath("/dev/__properties__/property_info");
}

我们可以看到,这里就加载了之前保存到property_info中的内容

启动属性服务

在init.cpp中调用函数start_property_service()去启动属性服务,先看下方法的内容

void start_property_service() {
    selinux_callback cb;
    cb.func_audit = SelinuxAuditCallback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    property_set("ro.property_service.version", "2");

    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr);
    if (property_set_fd == -1) {
        PLOG(FATAL) << "start_property_service socket creation failed";
    }

    listen(property_set_fd, 8);

    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

在P版本上写属性值时会涉及selinux权限的问题,在这里我们也能看到一些关于selinux的操作,这里还是先撇开不看selinux,只看属性服务的代码

property_set("ro.property_service.version", "2");

设置属性服务的版本为2

 property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr);
    if (property_set_fd == -1) {
        PLOG(FATAL) << "start_property_service socket creation failed";
    }

    listen(property_set_fd, 8);

这里面创建了一个socket,连接数为8,下面看下这个socket有什么作用

register_epoll_handler(property_set_fd, handle_property_set_fd);

这里注册了epoll事件,在property_set_fd发生改变时调用handle_property_set_fd,其实到这里属性服务已经起来了,不过我们还是看下handle_property_set_fd这个方法做了什么

static void handle_property_set_fd() {
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */

    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
    if (s == -1) {
        return;
    }

    ucred cr;
    socklen_t cr_size = sizeof(cr);
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
        return;
    }

    SocketConnection socket(s, cr);
    uint32_t timeout_ms = kDefaultSocketTimeout;

    uint32_t cmd = 0;
    if (!socket.RecvUint32(&cmd, &timeout_ms)) {
        PLOG(ERROR) << "sys_prop: error while reading command from the socket";
        socket.SendUint32(PROP_ERROR_READ_CMD);
        return;
    }

    switch (cmd) {
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];

        if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
            !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
          return;
        }

        prop_name[PROP_NAME_MAX-1] = 0;
        prop_value[PROP_VALUE_MAX-1] = 0;

        const auto& cr = socket.cred();
        std::string error;
        uint32_t result =
            HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
        if (result != PROP_SUCCESS) {
            LOG(ERROR) << "Unable to set property '" << prop_name << "' to '" << prop_value
                       << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
                       << error;
        }

        break;
      }

    case PROP_MSG_SETPROP2: {
        std::string name;
        std::string value;
        if (!socket.RecvString(&name, &timeout_ms) ||
            !socket.RecvString(&value, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
          socket.SendUint32(PROP_ERROR_READ_DATA);
          return;
        }

        const auto& cr = socket.cred();
        std::string error;
        uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
        if (result != PROP_SUCCESS) {
            LOG(ERROR) << "Unable to set property '" << name << "' to '" << value
                       << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
                       << error;
        }
        socket.SendUint32(result);
        break;
      }

    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break;
    }
}

这段代码我们可以看出是我们改变系统属性值时会被调用,通过调用HandlePropertySet去改变属性值

uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                           const std::string& source_context, const ucred& cr, std::string* error) {
    if (!IsLegalPropertyName(name)) {
        *error = "Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (StartsWith(name, "ctl.")) {
        if (!CheckControlPropertyPerms(name, value, source_context, cr)) {
            *error = StringPrintf("Invalid permissions to perform '%s' on '%s'", name.c_str() + 4,
                                  value.c_str());
            return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
        }

        HandleControlMessage(name.c_str() + 4, value, cr.pid);
        return PROP_SUCCESS;
    }

    const char* target_context = nullptr;
    const char* type = nullptr;
    property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);

    if (!CheckMacPerms(name, target_context, source_context.c_str(), cr)) {
        *error = "SELinux permission check failed";
        return PROP_ERROR_PERMISSION_DENIED;
    }

    if (type == nullptr || !CheckType(type, value)) {
        *error = StringPrintf("Property type check failed, value doesn't match expected type '%s'",
                              (type ?: "(null)"));
        return PROP_ERROR_INVALID_VALUE;
    }

    // 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);
}

系统会首先检测这个属性命名是否合法,如果合法才会去检查属性的开头,对以ctl.开头会交给init去处理。名字为sys.powerctl selinux.restorecon_recursive的做特殊处理,其他的则会调用PropertySet方法

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

我们可以看到如果时ro开头则会直接返回PROP_ERROR_READ_ONLY_PROPERTY,告诉我们是只读的无法修改,对于像长度不符合,编码不匹配的也会提示我们是无效的,

 prop_info* pi = (prop_info*) __system_property_find(name.c_str());

这边会去检测这个属性是否存在,对于存在且可以更改的会调用__system_property_update,没有的则调用__system_property_add,这两个方法并没有去改变属性值,借用方法内部的描述是这样的

288 // There is only a single mutator, but we want to make sure that
289 // updates are visible to a reader waiting for the update.

真正的修改还是调用property_changed方法去更新这个值


版权声明:本文为yangfan1571397878原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。