Service解析
本文面向已经读过 rc语法 的读者。前文介绍了 Parser 如何把 section body 交给具体 parser;本文只追踪 service <name> <program> [args...]:ServiceParser 如何创建临时 Service,option 如何写入字段,何时进入 ServiceList,以及为什么重复定义、错误 uid、非法 socket 和 critical oneshot 会在不同阶段失败。
本文不讲 Service::Start() 的 fork/exec,也不讲每个资源字段在子进程中的最终系统调用。读完后,读者应能从一个真实 rc service 首行走到 ServiceList,判断 option 的参数数量和 owner,解释 onrestart 为什么先保存成 Action,以及用测试输入区分“对象未提交”和“已提交但运行失败”。
1. 对象边界
1.1 三个阶段
| 阶段 | 所有者 | 生效点 |
|---|---|---|
| 首行 | ServiceParser::ParseSection() | 创建临时 service_ |
| body | ServiceParser::ParseLineSection() 与成员 ParseXxx() | 修改 service_ 字段 |
| 段结束 | ServiceParser::EndSection() | 加入或替换 ServiceList |
临时对象在 EndSection() 返回错误时不会进入列表。因而“rc 文件被读取”不等于“服务已注册”;后续 class_start 遍历的是 ServiceList,不是 Parser 读过的文本。
1.2 字段去向
1.3 名称约束
服务名要通过 IsValidName(),因为 init 会把它用于 init.svc.<name> 控制属性。非法名称不是运行时才发现的路径错误,而是 section 首行解析错误。
源码文件:system/core/init/service_parser.cpp
if (args.size() < 3) {
return Error() << "services must have a name and a program";
}
const std::string& name = args[1];
if (!IsValidName(name)) {
return Error() << "invalid service name '" << name << "'";
}2. 首行创建
2.1 argv切分
ParseSection() 将 token 0 视为 service,token 1 是 name,token 2 及以后全部进入 Service 的 args_。所以 /system/bin/app_process64 和 --zygote 都是程序 argv,不是 option。
源码文件:system/core/init/service_parser.cpp
相关函数/类型:ServiceParser::ParseSection
std::vector<std::string> str_args(args.begin() + 2, args.end());
service_ = std::make_unique<Service>(
name, restart_action_subcontext, filename, str_args);2.2 兼容分支
源码仍保留按 vendor Android API level 判断的旧路径迁移:低版本把 /sbin/watchdogd 和 /charger 改为 system 路径。这是构建/设备条件分支,不能从 Android 17 的普通 service 示例推导为所有设备都会改写。
源码文件:system/core/init/service_parser.cpp
相关函数/类型:ServiceParser::ParseSection
if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_P__) {
if (str_args[0] == "/sbin/watchdogd") str_args[0] = "/system/bin/watchdogd";
}
if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_Q__) {
if (str_args[0] == "/charger") str_args[0] = "/system/bin/charger";
}2.3 上下文
若源文件路径匹配 subcontext,Service 会保存该 subcontext,用于后续 restart action 和服务命令的安全上下文;不匹配时保存 null。Parser 不在这里 fork 进程。
3. 选项路由
3.1 KeywordMap
body 每行先经过 GetParserMap().Find(args)。map 同时保存最小参数数、最大参数数和成员函数指针;参数数量不合格时,具体 ParseXxx() 根本不会被调用。
源码文件:system/core/init/service_parser.cpp
相关函数/类型:ServiceParser::GetParserMap
{"class", {1, kMax, &ServiceParser::ParseClass}},
{"disabled", {0, 0, &ServiceParser::ParseDisabled}},
{"onrestart", {1, kMax, &ServiceParser::ParseOnrestart}},
{"socket", {3, 6, &ServiceParser::ParseSocket}},
{"user", {1, 1, &ServiceParser::ParseUser}},
{"task_profiles", {1, kMax, &ServiceParser::ParseTaskProfiles}},3.2 分派入口
ParseLineSection() 在首行失败后不再拥有有效 service,因此直接返回空结果;正常情况下,std::invoke() 调用成员 parser,字段修改仍属于临时 Service。
源码文件:system/core/init/service_parser.cpp
Result<void> ServiceParser::ParseLineSection(
std::vector<std::string>&& args, int line) {
if (!service_) return {};
auto parser = GetParserMap().Find(args);
if (!parser.ok()) return parser.error();
return std::invoke(*parser, this, std::move(args));
}3.3 选项分类
| 选项 | 写入字段 | 典型失败 |
|---|---|---|
class | classnames_ | 缺少 class 名 |
user/group | proc_attr_ | uid/gid 无法解析 |
socket/file | descriptors | 类型、权限、重复名称 |
namespace | namespace flags | 非 pid/mnt |
critical/oneshot | service flags | 组合约束在段结束检查 |
onrestart | Service::onrestart_ | builtin 不存在或参数错误 |
4. 身份资源
4.1 class
ParseClass() 将 option 后全部 token 放入 std::set。set 的所有权在 Service;class 启动发生在对象提交之后,由 ServiceList 遍历。
源码文件:system/core/init/service_parser.cpp
Result<void> ServiceParser::ParseClass(std::vector<std::string>&& args) {
service_->classnames_ = std::set<std::string>(args.begin() + 1, args.end());
return {};
}4.2 user与group
user 写入 parsed_uid,group 的第一个参数写主 gid,剩余参数追加 supplementary gids。名称和数字都交给 DecodeUid(),因此解析失败不会留下一个“默认 root 但看似成功”的字段。
源码文件:system/core/init/service_parser.cpp
auto uid = DecodeUid(args[1]);
if (!uid.ok()) {
return Error() << "Unable to find UID for '" << args[1] << "': " << uid.error();
}
service_->proc_attr_.parsed_uid = *uid;主 gid 和 supplementary gids 使用同一套名称/数字解码,但分别写入不同字段;因此附加组不会覆盖主组。
源码文件:system/core/init/service_parser.cpp
auto gid = DecodeUid(args[1]);
if (!gid.ok()) return Error() << "Unable to find GID for '" << args[1] << "': " << gid.error();
service_->proc_attr_.gid = *gid;
for (std::size_t n = 2; n < args.size(); n++) {
gid = DecodeUid(args[n]);
if (!gid.ok()) return Error() << "Unable to find GID for '" << args[n] << "': " << gid.error();
service_->proc_attr_.supp_gids.emplace_back(*gid);
}4.3 capabilities
ParseCapabilities() 先确认内核支持 ambient capabilities,再逐项通过 LookupCap() 解析并检查不超过 CAP_LAST_CAP。这里保存 capability 位图;实际施加到子进程属于后续启动路径。
5. 进程策略
5.1 namespace
namespace pid 会同时设置 CLONE_NEWPID 和 CLONE_NEWNS,因为 PID namespace 需要 mount namespace;namespace mnt 只设置 mount。其他字符串立即失败。
源码文件:system/core/init/service_parser.cpp
if (args[i] == "pid") {
service_->namespaces_.flags |= CLONE_NEWPID;
service_->namespaces_.flags |= CLONE_NEWNS;
} else if (args[i] == "mnt") {
service_->namespaces_.flags |= CLONE_NEWNS;
} else {
return Error() << "namespace must be 'pid' or 'mnt'";
}5.2 priority与ioprio
priority 通过 ParseInt() 限制 Android 线程优先级范围;ioprio 只接受 rt、be、idle 三种 class,优先级必须在 0 到 7。字符串合法但数值越界,仍属于解析失败。
5.3 互斥选项
console 与 stdio_to_kmsg 互斥。解析第二个选项时检查已有 flag,因此错误取决于 option 出现顺序,但不会把冲突状态提交到 ServiceList。
6. 描述符
6.1 socket
socket 语法为 socket name type perm [uid [gid [seclabel]]]。type 先按 + 拆分基础类型和装饰:基础值是 stream、dgram 或 seqpacket,装饰只允许 passcred、listen。
源码文件:system/core/init/service_parser.cpp
auto types = Split(args[2], "+");
if (types[0] == "stream") socket.type = SOCK_STREAM;
else if (types[0] == "dgram") socket.type = SOCK_DGRAM;
else if (types[0] == "seqpacket") socket.type = SOCK_SEQPACKET;
else return Error() << "socket type must be 'dgram', 'stream' or 'seqpacket'";
for (size_t i = 1; i < types.size(); i++) {
if (types[i] == "passcred") socket.passcred = true;
else if (types[i] == "listen") socket.listen = true;
else return Error() << "Unknown socket type decoration";
}权限用八进制 strtol 解析,uid/gid 仍走 DecodeUid();同一 Service 的 socket name 重复会失败,而不会覆盖旧 descriptor。
6.2 file
file 只接受 r、w、rw,路径先 ExpandProps(),随后必须是绝对路径且不能含 ../。同名 file descriptor 也会被拒绝。
6.3 消费时机
socket/file descriptor 仍只是 Service 字段。它们何时创建、以什么 SELinux label 创建,由 Service::Start() 使用这些描述符时决定;解析成功不能证明 /dev/socket 已存在。
7. 重启策略
7.1 onrestart
ParseOnrestart() 去掉 option 名,把剩余 token 作为一条命令加入 service_->onrestart_。这条 Action 不进入全局 ActionManager,而是由 Service 作为自己的重启回调保存。
源码文件:system/core/init/service_parser.cpp
args.erase(args.begin());
int line = service_->onrestart_.NumCommands() + 1;
if (auto result = service_->onrestart_.AddCommand(std::move(args), line); !result.ok()) {
return Error() << "cannot add Onrestart command: " << result.error();
}7.2 flags
disabled、oneshot、critical、override 等 option 只设置 flags 或布尔字段;它们的具体生命周期由 Service 后续管理。critical 还可带 window 和 target,属性展开和整数解析在 parser 中完成。
7.3 失败边界
critical 与 oneshot 在 Android R+ 被禁止组合,检查位于 EndSection(),所以两个 option 单独解析都可能成功,段结束时才整体失败。这是字段间不变量,不能只看 KeywordMap 的参数数量。
8. 段提交
8.1 user检查
Android 17 在段结束时检查是否显式指定 user;依据构建条件和 ro.vendor.api_level,缺失 user 可能报错,旧兼容路径则记录 warning 并保留 root 语义。
8.2 重复定义
同名 Service 没有 override 会返回 ignored duplicate definition,但 Parser 会继续读取后续 section。已有 Service 保持不变,后续合法 Service 仍可加入列表。
8.3 override
override 先移除旧对象再添加新对象,但 APEX 不能更新非 updatable service,且新旧 subcontext 必须相同,否则返回 Treble boundary 错误。
源码文件:system/core/init/service_parser.cpp
Service* old_service = service_list_->FindService(service_->name());
if (old_service) {
if (!service_->is_override()) {
return Error() << "ignored duplicate definition of service '"
<< service_->name() << "'";
}
if (StartsWith(filename_, "/apex/") && !old_service->is_updatable()) {
return Error() << "cannot update a non-updatable service '" << service_->name() << "'";
}
if (context != old_context) {
return Error() << "service '" << service_->name()
<< "' overrides another service across the Treble boundary.";
}
service_list_->RemoveService(*old_service);
}
service_list_->AddService(std::move(service_));9. 解析拒绝
9.1 正常输入
service_test.cpp 和 init_test.cpp 使用最小 service 文本,检查 ServiceList 中的 name、args、class、uid 和 override 结果。它们证明的是 parser 对象字段和列表提交,不证明目标二进制存在或服务已运行。
9.2 失败输入
IgnoreDuplicateService 构造两个同名 service,再追加一个不同名 service,断言第一个保持不变且后一个仍被解析;这证明 duplicate 是当前段错误,不会让 Parser 放弃整个文件。OverrideService 则断言带 override 的第二个对象替换旧 class。
9.3 可执行检查
读者可选择一个真实 rc service,按以下链路标注字段 owner:
service 首行
-> Service::args_ / filename_
option keyword
-> proc_attr_ / flags_ / sockets_ / onrestart_
EndSection
-> ServiceList
class_start 或 start
-> Service::Start 消费字段若设备上 init.svc.foo 不存在,先查 EndSection() 是否因 user、duplicate 或 option 错误拒绝;若属性存在但 socket 不可用,再转到 Service::Start 和 SELinux,而不要继续修改 parser。
9.4 边界
本文没有证明所有 option 的运行时系统调用、完整 capability 继承、socket 创建时序或厂商 rc 的 subcontext policy;这些属于服务启动和安全专题。本文证明的是 Android 17 通用 ServiceParser 如何验证、组装并提交 Service。
