Android系统属性的初始化以及存储

2018-03-11  本文已影响0人  lenozhao

基于 Android 6.0 的源码,分析系统属性的初始化以及存储。

上一篇结尾有提到属性系统的框架,本文就针对系统属性的初始化以及存储分析一波

property_frame.png

说到属性系统就不得不提一下 init 进程,ini t是 Linux 系统中用户空间的第一个进程,进程号为1。Kernel 启动后,在用户空间,启动 init 进程,并调用 init 中的 main() 方法执行 init 进程的职责。对于 init 进程的功能分为4部分:

我们就从 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的属性是否已经存在属性内存区域中:

  1. 如果存在的话,那么就会得到一个类型为prop_info的结构体pi,表示接下来要做的是修改属性。这时候就会调用我们在上面分析的函数update_prop_info进指定的属性进行修改。
  2. 如果不存在的话,那么指针pi的值就会等于NULL。表示接下来要做的增加属性。这时候只需要在属性内存区域的属性值列表pa_info_array的最后增加一项即可。

增加或者修改完成属性之后,还要进行以下的检查:

  1. 如果属性的名称是以“net.”开头,但是又不等于“net.change”,那么就将名称为“net.change”的属性设置为1,表示网络属性发生了变化。
  2. 如果属性的名称是以“persist.”开头,那么就表示该属性应该是持久化储存到文件中去,因此就会调用函数write_persistent_property执行这个操作,以便系统下次启动后,可以将该属性的初始值设置为系统上次关闭时的值。
  3. 如果属性的名称等于“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

上一篇下一篇

猜你喜欢

热点阅读