最小系统模式(Mini System Mode)

适用范围:mc::app(mcapp)应用框架。 相关代码:libraries/mcapp/include/mc/app/mini_system_mode.h、libraries/mcapp/src/mini_system_mode.cpp, 启动管理入口 libraries/mcapp/src/service.cpp(service::start())。

1. 背景与目标

最小系统模式是系统进入「恢复 / 配置还原」流程时使用的一种受限启动模式:本次启动只运行 mini_system.json 白名单内的组件,白名单之外的组件不允许启动。

进入最小系统模式时,/data 会被临时挂载为空的 tmpfs(见 rc.sysinit),持久化数据分区 暂不可用。此时若拉起白名单之外的业务组件,它们可能读不到 / 写坏正在恢复中的状态, 因此必须在启动阶段被挡在门外。

2. 触发与退出

  • 触发:恢复流程(如 maca 的 sysctl/reset,写入 /dev/shm/trust/recovery_mode_flag)结束后 系统重启。rc.sysinit 检测到该标志时,以目录形式创建标记 /dev/shm/.recovery_mode_on (chmod 744),并临时挂载 /data,本次启动即进入最小系统模式。
  • 退出:完成恢复后关闭最小系统开关并重启操作系统。重启后 /dev/shm 清空、 标记消失,系统进入正常启动流程。

因此最小系统模式下,标记会持续存在整个启动周期;非白名单组件不会在本周期内启动。

3. 白名单 mini_system.json

  • 位置:/opt/bmc/trust/mini_system.json(部署根可用环境变量 MC_TRUST_DIR 覆盖,测试用)。

  • 结构:

    {
      "applications": ["maca", "persistence", "redfish", "..."]
    }
    
  • 名单为程序区只读文件,不涉及动态修改;匹配粒度是组件短名 (D-Bus 全名 bmc.kepler.xxx 的末段)。

名单的另一处既有消费方是本地调试配置持久化路径的选择 (debug.cpp 的 get_local_db_path:白名单组件落到 trust/persistence.local, 其余落到 opt/bmc/persistence.local),两者统一走 is_mini_system_app(),避免两套名单解析。

4. libmcpp 实现

4.1 对外接口(mc::app)

接口 说明
bool is_mini_system_mode() 是否处于最小系统模式:标记是否存在。默认 /dev/shm/.recovery_mode_on(产品侧沿用恢复场景命名);可用 MC_MINI_SYSTEM_MODE_MARKER 覆盖(测试)。
bool is_mini_system_app(mc::string_view app_name) 组件短名或 D-Bus 全名(统一取末段)是否在 mini_system.json 的 applications 名单中。
bool is_allowed_to_start_in_mini_system_mode(mc::string_view component_name) 启动判定入口:未处于最小系统模式放行;处于该模式时仅白名单组件放行。组件名取末段短名后匹配。

4.2 启动管理:按 service 粒度

一个 application(进程)可以包含多个 service(例如 devmon 进程承载 devmon / hwproxy / hwdiscovery 三个 service),因此启动管理落在 service 粒度而不是 application 粒度:只阻止 名单外的 service 启动,同进程内名单内的 service 照常启动。

启动管理位于 mc::app::service::start() 入口(建立 D-Bus 连接、初始化引擎、注册微组件之前):

service 身份 行为
无 context(如 application 的内部 root service) 不参与启动管理,正常启动
未处于最小系统模式 正常启动
属于白名单 正常启动
处于最小系统模式 且不在白名单 不启动:置为 stopped 并直接返回,不建立 D-Bus/引擎连接、不注册微组件;同进程其他 service 不受影响

被跳过的 service 不进入 running:其 state() 为 stopped、has_dbus() 为 false、 micro_component() 为 nullptr,on_start() 不会被调用。service::stop() 对未启动(stopped) 的 service 保持幂等,避免对从未启动的 service 触发 on_stop()/on_cleanup()。

调用约定:被跳过时 service::start() 返回 true(不触发上层回滚),但该 service 并未运行。调用方若需区分「已运行」与「被跳过」,应检查 state()(为 stopped), 而不是仅依据 start() 的返回值。

说明:本实现不再在 application 层「死等」。application 层的整体阻塞会导致同一进程内名单内的 service 也无法启动(如 devmon 进程内 hwproxy 需要随最小系统启动),与「最小系统模式只启动 名单内组件」的语义不符。

4.3 身份来源与名单异常的取舍

service 身份取 D-Bus 服务名的末段短名(bmc.kepler.devmon → devmon),与 configure()/start() 日志作用域、dispatcher 派发所用短名一致——直接复用 mc::engine::service_short_name(),不再自行实现末段截取。

名单不可用(文件缺失、不可读、非 JSON 对象、无 applications 数组、解析异常)时的取舍为 fail-open:启动管理 is_allowed_to_start_in_mini_system_mode() 无法判定时空降级放行 并打印 wlog 告警,避免名单异常把整个恢复流程(含恢复链路组件)挡死;只有名单可用且组件 确实不在其中时才拦截。

注意区分两处语义:

  • 启动管理(本模块 is_allowed_to_start_in_mini_system_mode):名单不可用 → 放行(fail-open)。
  • 白名单成员判定(is_mini_system_app,亦被本地持久化路径选择复用):名单不可用 → 返回 false(fail-closed),即"不属于白名单"。两者面向不同用途,异常取舍不同。

5. 与既有实现的关系

实现 行为
老 skynet / Lua(ibmc 仓库) is_recovery_mode()(旧命名,语义即最小系统模式)+ is_mini_system_app();app_mgmt/base.lua、app_preloader.lua 把非名单组件过滤 / 退出
产品 mcc(C 框架) mcc_start() 中 mcc_check_recovery_mode() 在进程启动处拦截;is_mini_system_app() 静态缓存名单
本实现(C++ / mcapp) 框架在 service::start() 入口自动执行启动管理,按 service 粒度跳过名单外组件,同一 application 内名单内 service 不受影响

6. 关键设计取舍

  1. service 粒度而非 application 粒度:一个进程多个 service 时,按 application 整体拦截会 误伤同进程内的名单组件。
  2. 「不启动」而非「死等」:被跳过的 service 直接不进入启动流程,进程内其余 service 照常工作; 避免 application 级阻塞。
  3. 不引入进程级全局解析缓存:MC_TRUST_DIR 在测试中按用例重定向,全局缓存易命中过期 目录的旧名单;保持逐次读取的既有行为,语义简单、可测试。
  4. stop() 幂等:跳过(未启动)的 service 在应用停止时不应触发 on_stop()/on_cleanup()。
  5. 名单不可用时启动管理 fail-open:名单缺失/损坏无法判定时降级放行(告警),避免把恢复流程 挡死;成员判定 is_mini_system_app 仍保持 fail-closed(不属于白名单),两处语义在 §4.3 说明。
  6. 短名截取复用既有工具:统一走 mc::engine::service_short_name(),两个公共接口对同一输入 (短名/全名)判定一致,避免多处重复实现导致口径漂移。

7. 部署 / 配置要求

  • 需要在最小系统模式下运行的组件,其 service 名末段(组件短名,如 devmon)必须出现在 /opt/bmc/trust/mini_system.json 名单中;多服务单进程组件按各 service 短名分别判定, 名单外的 service 不启动、名单内的照常启动。
  • 恢复流程创建标记沿用产品侧路径 /dev/shm/.recovery_mode_on;测试通过 MC_MINI_SYSTEM_MODE_MARKER / MC_TRUST_DIR 重定向,不触碰部署路径。

8. 单元测试

libraries/mcapp/tests/test_mini_system_mode.cpp 覆盖:

  • 白名单匹配(含空串、非法 JSON、无 applications 字段等);
  • is_mini_system_mode() 随标记创建 / 清除翻转;
  • 启动管理判定 is_allowed_to_start_in_mini_system_mode():未处于最小系统模式放行、该模式下仅白名单 (含全名末段匹配)放行、名单缺失/损坏时 fail-open 降级放行;
  • service 启动管理:最小系统模式下名单外 service 不启动(stopped、on_start 未调用、无 D-Bus/微组件), 且跳过后 stop() 幂等。