前语:
马上就要过年了,在关机下班之际,写一篇文章,记录一下这两天踩的一个坑,也不枉别人放假回家过年了我还在坚持在一线解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_
,是负责解析语法的,在初始化的时候添加了三个解析类ServiceParser
、ActionParser
、ImportParser
,通过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在白名单中,所以可以生效,而自己的添加的属性不生效
那找到原因了也就好修改了,修改方式有两种:
- 使用白名单中原本就有的属性或属性前缀
- 在白名单中加入自己的属性
修改方式要根据自己的场景进行选择,我在实际项目中采用了第一种,使用了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问题,要考量的因素比较多,印象深刻,故做此笔记
六点一到,关机下班,回家过年!
网友评论