fix(launcherd): M42 包安装入口改委托 nyai — 删内联解包/验签/部署,复用 M35 安全链

- app_launcher.c 删掉 meow_pkg_install 旧内联实现(文件名推包名、硬要 .mpk 后缀、
  rm -rf 目标目录直解、失败不回滚 .bak_、不拒 ./..、系统包大小写敏感比较);
  导出保留签名逻辑不动(验签仍只 shell-out meow_verify)。
- 新增 pkg_install.c:fork+exec /usr/bin/nyai install <路径>(argv 直传不经 shell;
  MEOW_NYAI_BIN 覆写,缺省不走 PATH)。包身份/暂存/清理/防降级/大小写全由 nyai 闭环。
- stdout/stderr 分两条管道收:nyai 进度是缓冲 stdout,错误是即时 stderr,合流会把
  真错误顶掉(真机首测即踩到);错误文案只取 stderr 尾行。
- start/poll 两段式(+busy 查询)供 UI 主循环按帧驱动不假死;同步封装 20ms 切片轮询,
  180s 超时 SIGTERM→SIGKILL,不用时钟 ABI(M40 time32 教训)。
- main_menu --pkg-install 失败打 stderr 可读文案、退出码统一 0/1。
- test_pkg_install.c:44 断言(路径预检含任意文件名可装/目录/不可读/控制字符/超长、
  错误文案矩阵、argv 直传、start 立即返回、并发拒装、超时、nyai 缺失)。
- 重链 meow_launcherd 894940B/cea0a735(链 meow_ui.a 86800633,0 warning);
  fw_content.conf 同步钉值。
This commit is contained in:
AskaEth committed 2026-11-11 16:37:00 +08:00
1 parent ebd64d07e1
commit 58cdb73bf8
8 files changed
+654 -108

No files matched your search

+2 -102
View File
@@ -1044,7 +1044,8 @@ void cont_player(void)
/* ================================================================== */
/* M2.12 MeowAPPs 包数据目录 + .mpk 接口 */
/* M2.12 MeowAPPs 包数据目录 + .mpk 导出接口 */
/* 安装入口 meow_pkg_install M42 起移到 pkg_install.c(委托 nyai)。 */
/* exFAT 无符号链接 → 数据目录用环境变量(MEOW_DATA_DIR/CACHE_DIR) */
/* 传址给应用,不做 symlink(APPS.md 附录 v2 修订)。 */
/* ================================================================== */
@@ -1127,16 +1128,6 @@ static int pkg_json_field_mpk(const char *mpk, const char *key, char *out, int n
return 0;
}
/* 安装成功后,把原签 .mpk 另存 MeowData/<pkg>/data/<pkg>.mpk.src(导出保留签名)。 */
static void pkg_save_src(const char *mpk, const char *pkg)
{
char dst[320], cmd[720];
snprintf(dst, sizeof dst, "/usr/data/mnt/sd_0/MeowData/%s/data/%s.mpk.src", pkg, pkg);
snprintf(cmd, sizeof cmd, "mkdir -p '/usr/data/mnt/sd_0/MeowData/%s/data' 2>/dev/null && "
"cp '%s' '%s' 2>/dev/null", pkg, mpk, dst);
if (system(cmd) != 0) logmsg("pkg_install: save .mpk.src fail for %s", pkg);
}
/* 导出保留签名:缓存 .mpk.src 存在 + 验签通过 + 其 app.json 版本 == 已装 → 直接复制(不二次签名)。
* 成功 0;否则 -1(调用方现场重打包,无签名)。 */
static int pkg_export_preserve_sig(const char *pkg, const char *dst)
@@ -1157,97 +1148,6 @@ static int pkg_export_preserve_sig(const char *pkg, const char *dst)
return 0;
}
/* .mpk 安装(本期接口/CLI + 文档;UI 后期):解压 .mpk → MeowAPPs/<pkg>/。
* 约定 .mpk = tar.gz,顶层即包目录(含 app.json);包名 = 文件名茎 <pkg>.mpk。
* 同名先备份;解压后校验 app.json;随后数据 init(MeowData/<pkg>/{data,cache})。
* 返回 0 ok / 负=错误。 */
int meow_pkg_install(const char *mpk_path)
{
if (!mpk_path || access(mpk_path, R_OK) != 0) { logmsg("pkg_install: bad mpk %s", mpk_path?mpk_path:"(null)"); return -1; }
if (!sd_device_present()) { logmsg("pkg_install: no card"); return -1; }
if (strpbrk(mpk_path, " ;`$'\"&|<>") != NULL) { logmsg("pkg_install: unsafe path"); return -2; }
/* 包名 = 文件名茎(去掉 .mpk) */
const char *bn = strrchr(mpk_path, '/');
bn = bn ? bn + 1 : mpk_path;
const char *dot = strrchr(bn, '.');
char pkg[64];
if (!dot || strcmp(dot, ".mpk") != 0) { logmsg("pkg_install: need <pkg>.mpk"); return -1; }
int len = (int)(dot - bn);
if (len <= 0 || len >= (int)sizeof pkg) { logmsg("pkg_install: bad pkg name"); return -1; }
memcpy(pkg, bn, len); pkg[len] = 0;
/* 目标 MeowAPPs/<pkg>/;同名先备份(.bak_<pid>),再解压,再校验 app.json */
char dst[256], bak[256], cmd[760];
snprintf(dst, sizeof dst, "/usr/data/mnt/sd_0/MeowAPPs/%s", pkg);
snprintf(bak, sizeof bak, "%s.bak_%d", dst, (int)getpid());
int replacing = (access(dst, F_OK) == 0);
/* II 期:签名分级先于解包(MPK_SIGNATURE_DESIGN §5.2:先验签再解包,失败不落盘)。
* 有签名必须验签(失败拒装);os.meow.* 必须有有效签名;无签名第三方放行(告警)。 */
int vr = pkg_verify_sig(mpk_path, pkg);
int sysp = (strncmp(pkg, "os.meow.", 8) == 0);
if (vr == 1) { logmsg("pkg_install: sig invalid, reject %s", pkg); return -3; }
else if (vr == 2) {
if (sysp) { logmsg("pkg_install: sys pkg %s must be signed, rejected", pkg); return -3; }
logmsg("pkg_install: %s unsigned (3rd party allowed)", pkg);
}
else if (vr != 0) {
if (sysp) { logmsg("pkg_install: meow_verify unavailable rc=%d, sys pkg reject", vr); return -3; }
logmsg("pkg_install: meow_verify unavailable rc=%d, %s unverified (3rd party)", vr, pkg);
}
/* 防降级门禁(与 nyai 同规范,APPS.md §3.8):仅按 version_code 比较。
* 新版 code < 已装 → 拒;相等 → 允许重装;任一侧缺 version_code → 拒。 */
if (replacing) {
char nc[32], oc[32];
long long ncode = -1, ocode = -1;
int nok = 0, ok2 = 0;
if (pkg_json_field_mpk(mpk_path, "version_code", nc, sizeof nc) == 0) {
char *ep; long long v = strtoll(nc, &ep, 10);
if (ep != nc && *ep == '\0') { ncode = v; nok = 1; }
}
char instj[280];
snprintf(instj, sizeof instj, "/usr/data/mnt/sd_0/MeowAPPs/%s/app.json", pkg);
if (pkg_json_field(instj, "version_code", oc, sizeof oc) == 0) {
char *ep; long long v = strtoll(oc, &ep, 10);
if (ep != oc && *ep == '\0') { ocode = v; ok2 = 1; }
}
if (!nok || !ok2) { logmsg("pkg_install: %s version_code missing, reject", pkg); return -4; }
if (ncode < ocode) { logmsg("pkg_install: downgrade %s new %lld < old %lld, reject", pkg, ncode, ocode); return -4; }
}
/* BusyBox tar 无 -z:用 gzip -dc | tar -x 处理 tar.gz。 */
snprintf(cmd, sizeof cmd,
"rm -rf '%s' 2>/dev/null; "
"[ -d '%s' ] && mv '%s' '%s' 2>/dev/null; "
"mkdir -p '%s' && gzip -dc '%s' | tar -x -C '%s' 2>/dev/null && "
"[ -f '%s/app.json' ]", bak, dst, dst, bak, dst, mpk_path, dst, dst);
if (system(cmd) != 0) { logmsg("pkg_install: extract/validate fail for %s", pkg); return -1; }
/* II 期:app.json.package 绑定(== 包名,禁改包名/目录改名;防第三方面具系统包)。 */
{
char appj[280], ph[64];
snprintf(appj, sizeof appj, "/usr/data/mnt/sd_0/MeowAPPs/%s/app.json", pkg);
if (pkg_json_field(appj, "package", ph, sizeof ph) == 0 && strcmp(ph, pkg) != 0) {
logmsg("pkg_install: app.json.package %s != %s, reject", ph, pkg);
char rb[760];
snprintf(rb, sizeof rb, "rm -rf '%s' 2>/dev/null; [ -d '%s' ] && mv '%s' '%s' 2>/dev/null",
dst, bak, bak, dst);
system(rb);
return -3;
}
}
/* 数据 init:MeowData/<pkg>/{data,cache} */
snprintf(cmd, sizeof cmd, "mkdir -p '/usr/data/mnt/sd_0/MeowData/%s/data' "
"'/usr/data/mnt/sd_0/MeowData/%s/cache' 2>/dev/null", pkg, pkg);
system(cmd);
/* 有效签 → 原签 .mpk 另存 MeowData/<pkg>/data/<pkg>.mpk.src(导出保留签名) */
if (vr == 0) pkg_save_src(mpk_path, pkg);
logmsg("pkg_install: %s installed (old backed up %s)", pkg, bak);
return 0;
}
/* .mpk 导出:打包 MeowAPPs/<pkg>/(数据目录本就由 env 指向 MeowData,MeowAPPs/<pkg> 下无
* data/cache 实体,直接全打包)→ <pkg>.mpk。返回 0 ok。 */
int meow_pkg_export(const char *pkg, const char *out_path)