android中数据的触发器,事务,Android P on property属性无法trigger流程分析

前语:

马上就要过年了,在关机下班之际,写一篇文章,记录一下这两天踩的一个坑,也不枉别人放假回家过年了我还在坚持在一线解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(&service_list, subcontexts));

// 以on开头的行交给ActionParser解析

parser.AddSectionParser("on", std::make_unique(&action_manager, subcontexts));

// 以import开头的行交给ImportParser解析

parser.AddSectionParser("import", std::make_unique(&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 ParseSection(std::vector<:string>&& args,

const std::string& filename, int line) = 0;

virtual Result ParseLineSection(std::vector<:string>&&, int) { return Success(); };

virtual Result 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<: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(std::vector<: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 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<:string std::unique_ptr>> section_parsers_;

std::vector<:pair linecallback>> line_callbacks_;

};

} // namespace init

} // namespace android

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

void Parser::AddSectionParser(const std::string& name, std::unique_ptr 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 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<: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':

/* \ -> line continuation */

if (x[1] != '\n') {

x++;

continue;

}

case '\n':

/* \ -> 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* subcontexts)

: action_manager_(action_manager), subcontexts_(subcontexts), action_(nullptr) {}

Result ParseSection(std::vector<:string>&& args, const std::string& filename,

int line) override;

Result ParseLineSection(std::vector<:string>&& args, int line) override;

Result EndSection() override;

private:

ActionManager* action_manager_;

std::vector* subcontexts_;

std::unique_ptr action_;

};

} // namespace init

} // namespace android

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

Result ActionParser::ParseSection(std::vector<:string>&& args,

const std::string& filename, int line) {

std::vector<: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<: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(false, action_subcontext, filename, line, event_trigger,

property_triggers);

action_ = std::move(action);

return Success();

}

Result ParseTriggers(const std::vector<:string>& args, Subcontext* subcontext,

std::string* event_trigger,

std::map<: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 ParsePropertyTrigger(const std::string& trigger, Subcontext* subcontext,

std::map<: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<: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在白名单中,所以可以生效,而自己的添加的属性不生效

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

使用白名单中原本就有的属性或属性前缀

在白名单中加入自己的属性

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

on property:sys.boot_completed=1

# do my things

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

static const std::set<: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问题,要考量的因素比较多,印象深刻,故做此笔记

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值