美文网首页Android精选
Android P on property属性无法trigger

Android P on property属性无法trigger

作者: 逸先森 | 来源:发表于2019-02-02 17:13 被阅读0次

    前语:

    马上就要过年了,在关机下班之际,写一篇文章,记录一下这两天踩的一个坑,也不枉别人放假回家过年了我还在坚持在一线解bug

    正文:

    因为一个需求,需要修改init.target.rc文件,在某个属性生效的时候触发某些流程,这里简化代码流程,以一个简单的例子说明:

    on property:persist.test=test
        setprop persist.test.result "ok"
    

    正常情况下,在系统其他地方设置了属性persist.test的值为test之后就会触发下面那个设置属性的动作,然而实际上并没有生效,测试步骤如下:

    $ adb shell setprop persist.test test
    $ adb shell getprop | grep test
    [persist.test]:[test]
    

    获取到了persist.test属性,然而persist.test.result属性并没有设置成功

    为排查selinux权限问题,先把selinux权限关闭再去设置属性的值,排查selinux权限的方法可以通过setenforce 0的方式快速验证:

    $ adb shell setenforce 0
    $ adb shell getenforce
    $ adb shell setprop persist.test test1 // 需要变化一次属性值,不然同样的值不会触发
    $ adb shell setprop persist.test test
    $ adb shell getprop | grep test
    [persist.test]:[test]
    

    要确认getenforce的值返回的Permissive才可以,这时候看到属性还是设置不成功的,这里不确定是因为on property:persist.test=test这个动作没执行,还是说setprop persist.test.result "ok"有权限问题。这里再做一个测试,把设置属性的动作放到on boot阶段:

    on boot
        ...
        setprop persist.test.result "ok"
        ...
    

    结果是放到on boot也是设置不成功的,那应该是有存在权限问题了,需要慢慢排查。

    第一步:排查代码有没有加载到这个文件

    检查的方法是修改这个文件的其他地方,看是否生效,这次依然是修改on boot

    on boot
        ...
        # write /dev/cpuset/camera-daemon/cpus 0-3
        # 这里原本是0-3,修改成0-2,重启后去查看这个节点的值是否有写入成功
        write /dev/cpuset/camera-daemon/cpus 0-2
        setprop persist.test.result "ok"
        ...
    

    修改后验证,节点的值是有写入的,属性还是设置不成功

    (PS:这个文件其实能比较肯定的是有加载到的,因为分区的挂载动作就在这个文件中,如果没有加载到那不能开机了,这里只是多做一次确认)

    第二步:排查selinux权限

    为了方便验证,这次把属性放到另外一个地方:

    on property:vold.decrypt=trigger_restart_framework
        ...
        setprop persist.test.result "ok"
        ...
    

    修改后验证方法:

    $ adb shell setprop vold.decrypt trigger_restart_framework
    $ adb shell getprop persist.test.result
    

    设置属性后可以看到设备上层服务已经重启,现象是有开机动画,但是去查属性后还是没有设置成功,这时候把selinux权限关闭再去测试:

    $ adb shell setenforce 0
    $ adb shell getenforce
    $ adb shell setprop vold.decrypt trigger_restart_framework
    $ adb shell getprop persist.test.result
    [persist.test.result]:[ok]
    

    这时候就可以看到属性已经设置成功了。那么,问题就来了,为什么一开始关闭权限去设置的时候还是不成功呢?

    既然setprop persist.test.result "ok"是可以成功的,那么问题就应该是出在on property:persist.test=test这个动作上了,至于是什么原因,那么就需要去跟代码分析流程了

    第三步:分析on property实现流程

    init.rc文件是在init进程解析的,那要分析流程得从init进程开始分析,Android P的init初始化代码较之前的版本还是有很大的不同的,这是还是要从init.cpp的main函数开始分析:

    init.cpp

    int main() {
        ...
        // 初始化init.rc解析动作的管理类
        ActionManager& am = ActionManager::GetInstance();
        ServiceList& sm = ServiceList::GetInstance();
        LoadBootScripts(am, sm);  // 加载解析init.rc
        ...
    }
    
    Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
        Parser parser;
    
        // 以service开头的行交给ServiceParser解析
        parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));
        // 以on开头的行交给ActionParser解析
        parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
        // 以import开头的行交给ImportParser解析
        parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
    
        return parser;
    }
    
    static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
        Parser parser = CreateParser(action_manager, service_list); // 创建一个Parser对象
    
        std::string bootscript = GetProperty("ro.boot.init_rc", "");
        if (bootscript.empty()) {
            parser.ParseConfig("/init.rc"); // 解析根目录的init.rc文件
            if (!parser.ParseConfig("/system/etc/init")) {
                late_import_paths.emplace_back("/system/etc/init");
            }
            if (!parser.ParseConfig("/product/etc/init")) {
                late_import_paths.emplace_back("/product/etc/init");
            }
            if (!parser.ParseConfig("/odm/etc/init")) {
                late_import_paths.emplace_back("/odm/etc/init");
            }
            if (!parser.ParseConfig("/vendor/etc/init")) {
                late_import_paths.emplace_back("/vendor/etc/init");
            }
        } else {
            parser.ParseConfig(bootscript);
        }
    }
    

    这里需要看一下Parser类的设计:

    Parser类定义在parser.h

    namespace android {
    namespace init {
    
    class SectionParser {
      public:
        virtual ~SectionParser() {}
        virtual Result<Success> ParseSection(std::vector<std::string>&& args,
                                             const std::string& filename, int line) = 0;
        virtual Result<Success> ParseLineSection(std::vector<std::string>&&, int) { return Success(); };
        virtual Result<Success> EndSection() { return Success(); };
        virtual void EndFile(){};
    };
    
    class Parser {
      public:
        //  LineCallback is the type for callbacks that can parse a line starting with a given prefix.
        //
        //  They take the form of bool Callback(std::vector<std::string>&& args, std::string* err)
        //
        //  Similar to ParseSection() and ParseLineSection(), this function returns bool with false
        //  indicating a failure and has an std::string* err parameter into which an error string can
        //  be written.
        using LineCallback = std::function<Result<Success>(std::vector<std::string>&&)>;
    
        Parser();
    
        bool ParseConfig(const std::string& path);
        bool ParseConfig(const std::string& path, size_t* parse_errors);
        void AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser);
        void AddSingleLineParser(const std::string& prefix, LineCallback callback);
    
      private:
        void ParseData(const std::string& filename, const std::string& data, size_t* parse_errors);
        bool ParseConfigFile(const std::string& path, size_t* parse_errors);
        bool ParseConfigDir(const std::string& path, size_t* parse_errors);
    
        std::map<std::string, std::unique_ptr<SectionParser>> section_parsers_;
        std::vector<std::pair<std::string, LineCallback>> line_callbacks_;
    };
    
    }  // namespace init
    }  // namespace android
    

    Parser包含两个成员变量,一个是section_parsers_,是负责解析语法的,在初始化的时候添加了三个解析类ServiceParserActionParserImportParser,通过AddSectionParser函数添加:

    void Parser::AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser) {
        section_parsers_[name] = std::move(parser);
    }
    

    line_callbacks_是判断当前行的前缀是否匹配prefix,如果匹配则取消当前的section的配置,perfix主要通过AddSingleLineParser添加:

    void Parser::AddSingleLineParser(const std::string& prefix, LineCallback callback) {
        line_callbacks_.emplace_back(prefix, callback);
    }
    

    目前用到这个函数的是uevent.cpp:

    DeviceHandler CreateDeviceHandler() {
        parser.AddSingleLineParser("/sys/",
                                   std::bind(ParsePermissionsLine, _1, &sysfs_permissions, nullptr));
        parser.AddSingleLineParser("/dev/",
                                   std::bind(ParsePermissionsLine, _1, nullptr, &dev_permissions));
    }
    

    在Parser类里面,公开的成员函数ParseConfig是外部调用解析的入口,调用流程如下:

    ParseConfig 
      |
       --> ParseConfigFile
        |
         --> ParseData
    

    ParseData的实现如下:

    void Parser::ParseData(const std::string& filename, const std::string& data, size_t* parse_errors) {
        // TODO: Use a parser with const input and remove this copy
        std::vector<char> data_copy(data.begin(), data.end());
        data_copy.push_back('\0');
    
        parse_state state;
        state.line = 0;
        state.ptr = &data_copy[0];
        state.nexttoken = 0;
    
        SectionParser* section_parser = nullptr;
        int section_start_line = -1;
        std::vector<std::string> args;
    
        // 一个lambda表达式,可以理解为内部函数,作用是判断这个section是否解析到最后
        auto end_section = [&] {
            if (section_parser == nullptr) return;
    
            if (auto result = section_parser->EndSection(); !result) {
                (*parse_errors)++;
                LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();
            }
    
            section_parser = nullptr;
            section_start_line = -1;
        };
    
        for (;;) {
            switch (next_token(&state)) {  // 语法解析器
                case T_EOF:  // 解析结束
                    end_section();
                    return;
                case T_NEWLINE:  // 解析新行
                    state.line++;
                    if (args.empty()) break;
                    // If we have a line matching a prefix we recognize, call its callback and unset any
                    // current section parsers.  This is meant for /sys/ and /dev/ line entries for
                    // uevent.
                    for (const auto& [prefix, callback] : line_callbacks_) {  // line_callbacks_ 匹配规则
                        if (android::base::StartsWith(args[0], prefix)) {
                            end_section();
    
                            if (auto result = callback(std::move(args)); !result) {
                                (*parse_errors)++;
                                LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                            }
                            break;
                        }
                    }
                    if (section_parsers_.count(args[0])) { // section_parsers_ 匹配规则
                        end_section();
                        section_parser = section_parsers_[args[0]].get(); // 根据参数获取解析器
                        section_start_line = state.line;
                        if (auto result =
                                section_parser->ParseSection(std::move(args), filename, state.line);  // 解析内容
                            !result) {
                            (*parse_errors)++;
                            LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                            section_parser = nullptr;
                        }
                    } else if (section_parser) {
                        if (auto result = section_parser->ParseLineSection(std::move(args), state.line);
                            !result) {
                            (*parse_errors)++;
                            LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                        }
                    }
                    args.clear();
                    break;
                case T_TEXT:  // 加载文件内容
                    args.emplace_back(state.text);
                    break;
            }
        }
    }
    

    parseData主要是通过parse_state控制状态从而解析不同的规则,parse_state是一个结构体,定义在tokenizer.h:

    namespace android {
    namespace init {
    
    struct parse_state
    {
        char *ptr;
        char *text;
        int line;
        int nexttoken;
    };
    
    int next_token(struct parse_state *state);
    
    }  // namespace init
    }  // namespace android
    

    next_token是主要语法解析函数,在tokenizer.cpp实现:

    int next_token(struct parse_state *state)
    {
        char *x = state->ptr;
        char *s;
    
        if (state->nexttoken) {
            int t = state->nexttoken;
            state->nexttoken = 0;
            return t;
        }
    
        for (;;) {
            switch (*x) {
            case 0:
                state->ptr = x;
                return T_EOF;
            case '\n':
                x++;
                state->ptr = x;
                return T_NEWLINE;
            case ' ':
            case '\t':
            case '\r':
                x++;
                continue;
            case '#':
                while (*x && (*x != '\n')) x++;
                if (*x == '\n') {
                    state->ptr = x+1;
                    return T_NEWLINE;
                } else {
                    state->ptr = x;
                    return T_EOF;
                }
            default:
                goto text;
            }
        }
    
    textdone:
        state->ptr = x;
        *s = 0;
        return T_TEXT;
    text:
        state->text = s = x;
    textresume:
        for (;;) {
            switch (*x) {
            case 0:
                goto textdone;
            case ' ':
            case '\t':
            case '\r':
                x++;
                goto textdone;
            case '\n':
                state->nexttoken = T_NEWLINE;
                x++;
                goto textdone;
            case '"':
                x++;
                for (;;) {
                    switch (*x) {
                    case 0:
                            /* unterminated quoted thing */
                        state->ptr = x;
                        return T_EOF;
                    case '"':
                        x++;
                        goto textresume;
                    default:
                        *s++ = *x++;
                    }
                }
                break;
            case '\\':
                x++;
                switch (*x) {
                case 0:
                    goto textdone;
                case 'n':
                    *s++ = '\n';
                    break;
                case 'r':
                    *s++ = '\r';
                    break;
                case 't':
                    *s++ = '\t';
                    break;
                case '\\':
                    *s++ = '\\';
                    break;
                case '\r':
                        /* \ <cr> <lf> -> line continuation */
                    if (x[1] != '\n') {
                        x++;
                        continue;
                    }
                case '\n':
                        /* \ <lf> -> line continuation */
                    state->line++;
                    x++;
                        /* eat any extra whitespace */
                    while((*x == ' ') || (*x == '\t')) x++;
                    continue;
                default:
                        /* unknown escape -- just copy */
                    *s++ = *x++;
                }
                continue;
            default:
                *s++ = *x++;
            }
        }
        return T_EOF;
    }
    

    这里不细究代码实现,主要分析思路,next_token函数通过解析文件的行来返回状态给parseData函数进行处理:

    // 解析到行时 on property:persist.test=test
    // 此时section_parser是ActionParser
    section_parser = section_parsers_[args[0]].get();
    // 再调用section_parser的ParseSection函数也就是ActionParser的ParseSection函数
    section_parser->ParseSection(std::move(args), filename, state.line)
    

    这里看一下ActionParser的定义(action_parser.h):

    namespace android {
    namespace init {
    
    class ActionParser : public SectionParser {
      public:
        ActionParser(ActionManager* action_manager, std::vector<Subcontext>* subcontexts)
            : action_manager_(action_manager), subcontexts_(subcontexts), action_(nullptr) {}
        Result<Success> ParseSection(std::vector<std::string>&& args, const std::string& filename,
                                     int line) override;
        Result<Success> ParseLineSection(std::vector<std::string>&& args, int line) override;
        Result<Success> EndSection() override;
    
      private:
        ActionManager* action_manager_;
        std::vector<Subcontext>* subcontexts_;
        std::unique_ptr<Action> action_;
    };
    
    }  // namespace init
    }  // namespace android
    

    实现在action_parser.cpp,这里主要看ParseSection的实现:

    Result<Success> ActionParser::ParseSection(std::vector<std::string>&& args,
                                               const std::string& filename, int line) {
        std::vector<std::string> triggers(args.begin() + 1, args.end());
        if (triggers.size() < 1) {
            return Error() << "Actions must have a trigger";
        }
    
        Subcontext* action_subcontext = nullptr;
        if (subcontexts_) {
            for (auto& subcontext : *subcontexts_) {
                if (StartsWith(filename, subcontext.path_prefix())) {
                    action_subcontext = &subcontext;
                    break;
                }
            }
        }
    
        std::string event_trigger;
        std::map<std::string, std::string> property_triggers;
        // 解析和触发动作
        if (auto result = ParseTriggers(triggers, action_subcontext, &event_trigger, &property_triggers);
            !result) {
            return Error() << "ParseTriggers() failed: " << result.error();
        }
    
        auto action = std::make_unique<Action>(false, action_subcontext, filename, line, event_trigger,
                                               property_triggers);
    
        action_ = std::move(action);
        return Success();
    }
    
    Result<Success> ParseTriggers(const std::vector<std::string>& args, Subcontext* subcontext,
                                  std::string* event_trigger,
                                  std::map<std::string, std::string>* property_triggers) {
        const static std::string prop_str("property:");
        for (std::size_t i = 0; i < args.size(); ++i) {
            if (args[i].empty()) {
                return Error() << "empty trigger is not valid";
            }
    
            if (i % 2) {
                if (args[i] != "&&") {
                    return Error() << "&& is the only symbol allowed to concatenate actions";
                } else {
                    continue;
                }
            }
    
            // 判断是不是 on property: 类型
            if (!args[i].compare(0, prop_str.length(), prop_str)) {
                // 符合条件的调用ParsePropertyTrigger解析
                if (auto result = ParsePropertyTrigger(args[i], subcontext, property_triggers);
                    !result) {
                    return result;
                }
            } else {
                if (!event_trigger->empty()) {
                    return Error() << "multiple event triggers are not allowed";
                }
    
                *event_trigger = args[i];
            }
        }
    
        return Success();
    }
    
    Result<Success> ParsePropertyTrigger(const std::string& trigger, Subcontext* subcontext,
                                         std::map<std::string, std::string>* property_triggers) {
        const static std::string prop_str("property:");
        std::string prop_name(trigger.substr(prop_str.length()));
        size_t equal_pos = prop_name.find('=');
        if (equal_pos == std::string::npos) {
            return Error() << "property trigger found without matching '='";
        }
    
        std::string prop_value(prop_name.substr(equal_pos + 1));
        prop_name.erase(equal_pos);
        // 判断属性是否合法
        if (!IsActionableProperty(subcontext, prop_name)) {
            return Error() << "unexported property tigger found: " << prop_name;
        }
    
        if (auto [it, inserted] = property_triggers->emplace(prop_name, prop_value); !inserted) {
            return Error() << "multiple property triggers found for same property";
        }
        return Success();
    }
    
    bool IsActionableProperty(Subcontext* subcontext, const std::string& prop_name) {
        static bool enabled = GetBoolProperty("ro.actionable_compatible_property.enabled", false);
    
        if (subcontext == nullptr || !enabled) {
            return true;
        }
        // 判断属性是否在kExportedActionableProperties数组,prop_name出现次数==1说明在数组里
        if (kExportedActionableProperties.count(prop_name) == 1) {
            return true;
        }
        // 遍历kPartnerPrefixes这个set,判断prop_name是不是符合set中要求的前缀
        for (const auto& prefix : kPartnerPrefixes) {
            if (android::base::StartsWith(prop_name, prefix)) {
                return true;
            }
        }
        return false;
    }
    

    kExportedActionableProperties和kPartnerPrefixes定义在stable_properties.h:

    static constexpr const char* kPartnerPrefixes[] = {
        "init.svc.vendor.", "ro.vendor.", "persist.vendor.", "vendor.", "init.svc.odm.", "ro.odm.",
        "persist.odm.",     "odm.",       "ro.boot.",
    };
    
    static const std::set<std::string> kExportedActionableProperties = {
        "dev.bootcomplete",
        "init.svc.console",
        "init.svc.mediadrm",
        "init.svc.surfaceflinger",
        "init.svc.zygote",
        "persist.bluetooth.btsnoopenable",
        "persist.sys.crash_rcu",
        "persist.sys.usb.usbradio.config",
        "persist.sys.zram_enabled",
        "ro.board.platform",
        "ro.bootmode",
        "ro.build.type",
        "ro.crypto.state",
        "ro.crypto.type",
        "ro.debuggable",
        "sys.boot_completed",
        "sys.boot_from_charger_mode",
        "sys.retaildemo.enabled",
        "sys.shutdown.requested",
        "sys.usb.config",
        "sys.usb.configfs",
        "sys.usb.ffs.mtp.ready",
        "sys.usb.ffs.ready",
        "sys.user.0.ce_available",
        "sys.vdso",
        "vold.decrypt",
        "vold.post_fs_data_done",
        "vts.native_server.on",
        "wlan.driver.status",
    };
    

    到这里流程就跟完了,问题原因也找到了,属性trigger是有一个类似于白名单的机制,不在白名单的属性是无法触发动作的,前面尝试的vold.decrypt在白名单中,所以可以生效,而自己的添加的属性不生效

    那找到原因了也就好修改了,修改方式有两种:

    1. 使用白名单中原本就有的属性或属性前缀
    2. 在白名单中加入自己的属性

    修改方式要根据自己的场景进行选择,我在实际项目中采用了第一种,使用了sys.boot_completed这个属性:

    on property:sys.boot_completed=1
        # do my things
    

    后来因为场景有些复杂,只由sys.boot_completed(开机完成后由AMS设置的一个属性)控制可能会出问题,后来加入了自己的属性到白名单里面,测试也是没有问题的

    static const std::set<std::string> kExportedActionableProperties = {
        "dev.bootcomplete",
        "init.svc.console",
        "init.svc.mediadrm",
        "init.svc.surfaceflinger",
        "init.svc.zygote",
        "persist.bluetooth.btsnoopenable",
        "persist.sys.crash_rcu",
        "persist.sys.usb.usbradio.config",
        "persist.sys.zram_enabled",
        "ro.board.platform",
        "ro.bootmode",
        "ro.build.type",
        "ro.crypto.state",
        "ro.crypto.type",
        "ro.debuggable",
        "sys.boot_completed",
        "sys.boot_from_charger_mode",
        "sys.retaildemo.enabled",
        "sys.shutdown.requested",
        "sys.usb.config",
        "sys.usb.configfs",
        "sys.usb.ffs.mtp.ready",
        "sys.usb.ffs.ready",
        "sys.user.0.ce_available",
        "sys.vdso",
        "vold.decrypt",
        "vold.post_fs_data_done",
        "vts.native_server.on",
        "wlan.driver.status",
        "persist.test"  // 添加自己的属性
    };
    
    on property:persist.test=test
        # do my things
    

    一开始担心修改了白名单会引起CTS、VTS问题,在跑了一整轮的CTS和VTS测试后,并没有报出相关问题,也就是说这两种修改方式都是可控的,没什么大的问题。

    后话:

    在解决实际问题的时候还是比较复杂的,不只是说流程弄清除后简单修改就行,在分析这个问题的时候,遇到的selinux权限问题还会触发neverallow机制,规避neverallow机制的同时又不能引起CTS问题,要考量的因素比较多,印象深刻,故做此笔记

    六点一到,关机下班,回家过年!

    相关文章

      网友评论

        本文标题:Android P on property属性无法trigger

        本文链接:https://www.haomeiwen.com/subject/lsnssqtx.html