在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方法去更新这个值