Skip to content

Service解析

追踪 service 段从首行、option keyword map 到 ServiceList 提交的生命周期与失败边界。

基于android-17.0.0_r1
AndroidinitServiceParserServiceServiceList

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_
bodyServiceParser::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

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

cpp
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

cpp
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

cpp
{"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

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 选项分类 ​

选项写入字段典型失败
classclassnames_缺少 class 名
user/groupproc_attr_uid/gid 无法解析
socket/filedescriptors类型、权限、重复名称
namespacenamespace flags非 pid/mnt
critical/oneshotservice flags组合约束在段结束检查
onrestartService::onrestart_builtin 不存在或参数错误

4. 身份资源 ​

4.1 class ​

ParseClass() 将 option 后全部 token 放入 std::set。set 的所有权在 Service;class 启动发生在对象提交之后,由 ServiceList 遍历。

源码文件:system/core/init/service_parser.cpp

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

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

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

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

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

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

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:

text
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。