Android系统属性的初始化以及存储
基于 Android 6.0 的源码,分析系统属性的初始化以及存储。
上一篇结尾有提到属性系统的框架,本文就针对系统属性的初始化以及存储分析一波
property_frame.png说到属性系统就不得不提一下 init 进程,ini t是 Linux 系统中用户空间的第一个进程,进程号为1。Kernel 启动后,在用户空间,启动 init 进程,并调用 init 中的 main() 方法执行 init 进程的职责。对于 init 进程的功能分为4部分:
- 分析和运行所有的 init.rc 文件;
- 生成设备驱动节点; (通过rc文件创建)
- 处理子进程的终止(signal 方式);
- 提供属性服务
我们就从 init 的 main() 方法开始 (已省略部分与系统属性无关的代码)
syste/core/init/init.cpp
int main(int argc, char** argv) {
......
// 创建一块共享的内存空间,用于属性服务
property_init();
......
// 加载default.prop文件
property_load_boot_defaults();
// 启动属性系统服务(通过Socket通讯)
start_property_service();
......
// 解析 init.rc 文件
init_parse_config_file("/init.rc");
......
// 根据属性的当前状态运行所有属性触发器。
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
......
while (true) {
......
epoll_event ev;
//循环 等待事件发生
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}
加载default.prop文件
syste/core/init/property_service.cpp
void property_load_boot_defaults() {
// PROP_PATH_RAMDISK_DEFAULT = "/default.prop"
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}
/*
* Filter is used to decide which properties to load: NULL loads all keys,
* "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
*/
static void load_properties_from_file(const char* filename, const char* filter) {
Timer t;
std::string data;
// 读取 filename 对应文件内容到 data
if (read_file(filename, &data)) {
data.push_back('\n');
// 解析 data
load_properties(&data[0], filter);
}
NOTICE("(Loading properties from %s took %.2fs.)\n", filename, t.duration());
}
/*
* Filter is used to decide which properties to load: NULL loads all keys,
* "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
*/
static void load_properties(char *data, const char *filter)
{
char *key, *value, *eol, *sol, *tmp, *fn;
size_t flen = 0;
if (filter) {
flen = strlen(filter);
}
sol = data;
// 逐行解析
while ((eol = strchr(sol, '\n'))) {
key = sol;
*eol++ = 0;
sol = eol;
while (isspace(*key)) key++;
// 如果是以 '#' 开头则跳过
if (*key == '#') continue;
tmp = eol - 2;
while ((tmp > key) && isspace(*tmp)) *tmp-- = 0;
// 如果是以 import 开头则继续解析该文件
if (!strncmp(key, "import ", 7) && flen == 0) {
fn = key + 7;
while (isspace(*fn)) fn++;
key = strchr(fn, ' ');
if (key) {
*key++ = 0;
while (isspace(*key)) key++;
}
// 解析被 import 的文件
load_properties_from_file(fn, key);
} else {
value = strchr(key, '=');
if (!value) continue;
*value++ = 0;
tmp = value - 2;
while ((tmp > key) && isspace(*tmp)) *tmp-- = 0;
while (isspace(*value)) value++;
if (flen > 0) {
if (filter[flen - 1] == '*') {
if (strncmp(key, filter, flen - 1)) continue;
} else {
if (strcmp(key, filter)) continue;
}
}
// 设置系统属性
property_set(key, value);
}
}
}
系统性的保存
这里又有 property_set,前面的文章也讲了设置系统属性,不过那是客户端,现在我们看一下服务端又做什么些什么。
syste/core/init/property_service.cpp
int property_set(const char* name, const char* value) {
int rc = property_set_impl(name, value);
if (rc == -1) {
ERROR("property_set(\"%s\", \"%s\") failed\n", name, value);
}
return rc;
}
static int property_set_impl(const char* name, const char* value) {
size_t namelen = strlen(name);
size_t valuelen = strlen(value);
// 合法性校验
if (!is_legal_property_name(name, namelen)) return -1;
if (valuelen >= PROP_VALUE_MAX) return -1;
// 如果属性的名称等于“selinux.reload_policy”,并且前面给它设置的值等于1
if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) {
// 重新加载SEAndroid策略
if (selinux_reload_policy() != 0) {
ERROR("Failed to reload policy\n");
}
} else if (strcmp("selinux.restorecon_recursive", name) == 0 && valuelen > 0) {
// 恢复SELinux文件属性即恢复文件的安全上下文
if (restorecon_recursive(value) != 0) {
ERROR("Failed to restorecon_recursive %s\n", value);
}
}
// 从属性内存区域中查询名称为name的属性
prop_info* pi = (prop_info*) __system_property_find(name);
// 如果查询结果不为空,即已经存在
if(pi != 0) {
/* ro.* properties may NEVER be modified once set */
// 如果 name 以 'ro.' 开头则直接返回
if(!strncmp(name, "ro.", 3)) return -1;
// 更新属性
__system_property_update(pi, value, valuelen);
} else {
// 如果不存在,则添加属性
int rc = __system_property_add(name, namelen, value, valuelen);
if (rc < 0) {
return rc;
}
}
/* If name starts with "net." treat as a DNS property. */
// 如果属性的名称是以“net.”开头,但是又不等于“net.change”,那么就将名称为“net.change”的属性设置为1,表示网络属性发生了变化
if (strncmp("net.", name, strlen("net.")) == 0) {
if (strcmp("net.change", name) == 0) {
return 0;
}
/*
* The 'net.change' property is a special property used track when any
* 'net.*' property name is updated. It is _ONLY_ updated here. Its value
* contains the last updated 'net.*' property.
*/
property_set("net.change", name);
}
// 如果属性的名称是以“persist.”开头,那么就表示该属性应该是持久化储存到文件中去
else if (persistent_properties_loaded &&
strncmp("persist.", name, strlen("persist.")) == 0) {
/*
* Don't write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
write_persistent_property(name, value);
}
// 发送一个名称为name的属性发生了变化的通知。以便init进程可以执行在启动脚本init.rc中配置的操作。
property_changed(name, value);
return 0;
}
static void write_persistent_property(const char *name, const char *value)
{
char tempPath[PATH_MAX];
char path[PATH_MAX];
int fd;
snprintf(tempPath, sizeof(tempPath), "%s/.temp.XXXXXX", PERSISTENT_PROPERTY_DIR);
fd = mkstemp(tempPath);
if (fd < 0) {
ERROR("Unable to write persistent property to temp file %s: %s\n", tempPath, strerror(errno));
return;
}
write(fd, value, strlen(value));
fsync(fd);
close(fd);
snprintf(path, sizeof(path), "%s/%s", PERSISTENT_PROPERTY_DIR, name);
if (rename(tempPath, path)) {
unlink(tempPath);
ERROR("Unable to rename persistent property file %s to %s\n", tempPath, path);
}
}
函数property_set首先是调用函数__system_property_find检查名称为name的属性是否已经存在属性内存区域中:
- 如果存在的话,那么就会得到一个类型为prop_info的结构体pi,表示接下来要做的是修改属性。这时候就会调用我们在上面分析的函数update_prop_info进指定的属性进行修改。
- 如果不存在的话,那么指针pi的值就会等于NULL。表示接下来要做的增加属性。这时候只需要在属性内存区域的属性值列表pa_info_array的最后增加一项即可。
增加或者修改完成属性之后,还要进行以下的检查:
- 如果属性的名称是以“net.”开头,但是又不等于“net.change”,那么就将名称为“net.change”的属性设置为1,表示网络属性发生了变化。
- 如果属性的名称是以“persist.”开头,那么就表示该属性应该是持久化储存到文件中去,因此就会调用函数write_persistent_property执行这个操作,以便系统下次启动后,可以将该属性的初始值设置为系统上次关闭时的值。
- 如果属性的名称等于“selinux.reload_policy”,并且前面给它设置的值等于1,那么就表示要重新加载SEAndroid策略,这是通过调用函数selinux_reload_policy来实现的。
最后,函数property_set调用另外一个函数property_changed发送一个名称为name的属性发生了变化的通知。以便init进程可以执行在启动脚本init.rc中配置的操作。
启动属性系统服务
属性服务就是通过 socket 接收客户端传来的设置系统属性的请求,
void start_property_service() {
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);
if (property_set_fd == -1) {
ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
exit(1);
}
listen(property_set_fd, 8);
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
static void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
char * source_ctx = NULL;
struct pollfd ufds[1];
const int timeout_ms = 2 * 1000; /* Default 2 sec timeout for caller to send property. */
int nr;
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
/* Check socket options here */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to receive socket options\n");
return;
}
ufds[0].fd = s;
ufds[0].events = POLLIN;
ufds[0].revents = 0;
nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
if (nr == 0) {
ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
close(s);
return;
} else if (nr < 0) {
ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
close(s);
return;
}
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
r, sizeof(prop_msg), strerror(errno));
close(s);
return;
}
switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
if (check_perms(msg.name, source_ctx)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
freecon(source_ctx);
break;
default:
close(s);
break;
}
}
加载其他属性
开头有讲到,init.cpp 的 main() 方法会去解析 init.rc。在 init.r 中,也会触发加载系统属性,代码如下:
on late-init
trigger early-fs
trigger fs
trigger post-fs
trigger post-fs-data
# Load properties from /system/ + /factory after fs mount. Place
# this in another action so that the load will be scheduled after the prior
# issued fs triggers have completed.
trigger load_all_props_action
# Remove a file to wake up anything waiting for firmware.
trigger firmware_mounts_complete
trigger early-boot
trigger boot
# Load properties from /system/ + /factory after fs mount.
on load_all_props_action
load_all_props
start logd
start logd-reinit
load_all_props 会调用到 property_service.cpp 中的 load_all_props 方法。
void load_all_props() {
load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
load_properties_from_file(PROP_PATH_FACTORY, "ro.*");
load_override_properties();
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
load_recovery_id_prop();
}
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
属性触发器
参考:
http://blog.csdn.net/luoshengyang/article/details/38102011
http://blog.csdn.net/kc58236582/article/details/51939322