Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/test_pkg_install.c
T
AskaEth 58cdb73bf8 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 同步钉值。
2026-11-11 16:37:00 +08:00

300 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* test_pkg_install.c — M42 宿主单测:安装入口路径预检 + 错误文案 + exec 合同(假 nyai)。
*
* 编译运行(在 patch_v2/meow_launcherd 下,需 -I../sdk 因内部头含 SDK 声明):
* gcc -Wall -Wextra -Os -I../sdk test_pkg_install.c pkg_install.c -o test_pkg_install && ./test_pkg_install
* 全 PASS -> exit 0。
*
* 覆盖:
* A. 路径预检:任意文件名/后缀可装(M35 定案:身份只来自包内 app.json)、目录/缺失/不可读/
* 控制字符/超长一律拒。
* B. 错误文案:nyai 退出码 + 输出尾行 → 单条可读文案(不空、不越界)。
* C. exec 合同:argv 直传(不经 shell,带空格/引号的路径原样到达)、退出码映射、
* start/poll 非阻塞(start 立即返回)、并发拒装、超时kill。
* 假 nyai 用 MEOW_NYAI_BIN 注入(与 MEOW_VERIFY_BIN 同口径),不碰真机/真包。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "meow_launcher_internal.h"
/* pkg_install.c 依赖的 launcher 日志:宿主打桩(宿主无 util.c)。 */
void logmsg(const char *fmt, ...)
{
(void)fmt;
}
static int g_pass = 0, g_fail = 0;
static void check(const char *name, int ok)
{
if (ok) { g_pass++; printf("PASS %s\n", name); }
else { g_fail++; printf("FAIL %s\n", name); }
}
static long long now_ms_mono(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static char g_dir[512];
/* 在临时目录写一个文件,返回其路径(buf)。 */
static const char *mkfile(const char *name, const char *content, char *buf, int n)
{
snprintf(buf, n, "%s/%s", g_dir, name);
FILE *f = fopen(buf, "w");
if (!f) return NULL;
if (content) fputs(content, f);
fclose(f);
return buf;
}
/* 写一个可执行假 nyai。 */
static const char *mkscript(const char *name, const char *body, char *buf, int n)
{
if (!mkfile(name, body, buf, n)) return NULL;
chmod(buf, 0755);
return buf;
}
/* ---------------- A. 路径预检 ---------------- */
static void test_path_check(void)
{
char err[256], f[512];
check("A1 null -> reject", meow_pkg_install_check_path(NULL, err, sizeof err) != 0);
check("A1 null -> has text", err[0] != 0);
err[0] = 0;
check("A2 empty -> reject", meow_pkg_install_check_path("", err, sizeof err) != 0 && err[0]);
err[0] = 0;
check("A3 missing -> reject", meow_pkg_install_check_path("/nonexistent/x.mpk", err, sizeof err) != 0);
check("A3 missing -> 找不到", strstr(err, "找不到") != NULL);
err[0] = 0;
check("A4 directory -> reject", meow_pkg_install_check_path(g_dir, err, sizeof err) != 0);
check("A4 directory -> 不是普通文件", strstr(err, "不是普通文件") != NULL);
err[0] = 0;
check("A5 dot -> reject", meow_pkg_install_check_path(".", err, sizeof err) != 0);
check("A6 dotdot -> reject", meow_pkg_install_check_path("..", err, sizeof err) != 0);
err[0] = 0;
check("A7 control char -> reject",
meow_pkg_install_check_path("/tmp/a\x01" "b", err, sizeof err) != 0 &&
strstr(err, "控制字符") != NULL);
{
char longp[700];
memset(longp, 'a', sizeof longp - 1);
longp[0] = '/'; longp[sizeof longp - 1] = 0;
err[0] = 0;
check("A8 too long -> reject",
meow_pkg_install_check_path(longp, err, sizeof err) != 0 &&
strstr(err, "过长") != NULL);
}
/* 关键回归:任意文件名/无 .mpk 后缀都可装(m35 事故:改名后装不上)。 */
if (mkfile("weird name-v0.1.7.bin", "x", f, sizeof f))
check("A9 any name/no .mpk -> accept",
meow_pkg_install_check_path(f, err, sizeof err) == 0);
else check("A9 any name/no .mpk -> accept", 0);
if (mkfile("OS.MEOW.READER", "x", f, sizeof f))
check("A10 uppercase name -> accept (nyai 拒,入口不按名字判)",
meow_pkg_install_check_path(f, err, sizeof err) == 0);
else check("A10 uppercase name -> accept (nyai 拒,入口不按名字判)", 0);
if (mkfile("noread.bin", "x", f, sizeof f)) {
chmod(f, 0000);
err[0] = 0;
check("A11 unreadable -> reject",
meow_pkg_install_check_path(f, err, sizeof err) != 0 &&
strstr(err, "不可读") != NULL);
chmod(f, 0644);
} else check("A11 unreadable -> reject", 0);
}
/* ---------------- B. 错误文案 ---------------- */
static void test_error_text(void)
{
char out[256];
meow_pkg_install_error_text(0, "whatever", out, sizeof out);
check("B1 rc=0 -> empty", out[0] == 0);
meow_pkg_install_error_text(1,
"Reading package lists... Done\r\nnyai: install: 不允许降级: 新版本 1 < 已装 2\n\n",
out, sizeof out);
check("B2 last non-empty line", strcmp(out, "nyai: install: 不允许降级: 新版本 1 < 已装 2") == 0);
meow_pkg_install_error_text(7, "", out, sizeof out);
check("B3 no output -> exit code text", strstr(out, "退出码 7") != NULL);
meow_pkg_install_error_text(137, "x", out, sizeof out);
check("B4 signal -> 128+sig text", strstr(out, "信号 9") != NULL);
meow_pkg_install_error_text(MEOW_PKG_E_TIMEOUT, NULL, out, sizeof out);
check("B5 timeout text", strstr(out, "超时") != NULL);
meow_pkg_install_error_text(MEOW_PKG_E_NYAI, "无法执行 /usr/bin/nyai", out, sizeof out);
check("B6 nyai unavailable text", strstr(out, "不可用") && strstr(out, "/usr/bin/nyai"));
meow_pkg_install_error_text(MEOW_PKG_E_BUSY, NULL, out, sizeof out);
check("B7 busy text", strstr(out, "已有安装") != NULL);
meow_pkg_install_error_text(MEOW_PKG_E_PATH, "找不到包文件 /x", out, sizeof out);
check("B8 path text", strcmp(out, "找不到包文件 /x") == 0);
{
char tiny[24];
memset(tiny, 0x5a, sizeof tiny);
meow_pkg_install_error_text(1,
"nyai: 这是一个非常非常长的错误信息,用来验证截断不会越界写坏栈\n", tiny, 16);
int guard = 1;
for (int i = 16; i < (int)sizeof tiny; i++) if (tiny[i] != 0x5a) guard = 0;
check("B9 truncation bounded", strlen(tiny) < 16 && guard);
}
meow_pkg_install_error_text(1, " \n\t\n", out, sizeof out);
check("B10 whitespace-only detail -> generic", out[0] != 0 && strstr(out, "退出码 1"));
}
/* ---------------- C. exec 合同(假 nyai) ---------------- */
static void test_exec(void)
{
char script[512], pkg[512], log[512], err[256];
/* C1: 失败透传 —— stderr 尾行进错误文案,退出码映射 MEOW_PKG_E_FAIL */
if (mkscript("nyai_fail.sh",
"#!/bin/sh\n"
"echo \"Reading package lists... Done\"\n"
"echo \"nyai: 假失败:测试包损坏\" >&2\n"
"exit 7\n", script, sizeof script) &&
mkfile("pkg-fail.bin", "x", pkg, sizeof pkg)) {
setenv("MEOW_NYAI_BIN", script, 1);
int r = meow_pkg_install(pkg);
check("C1 exit!=0 -> MEOW_PKG_E_FAIL", r == MEOW_PKG_E_FAIL);
check("C1 stderr tail surfaced", strstr(meow_pkg_install_error(), "假失败") != NULL);
check("C1 not busy after finish", meow_pkg_install_busy() == 0);
} else check("C1 exit!=0 -> MEOW_PKG_E_FAIL", 0);
/* C1b: 真机回归 —— nyai 的 stdout 是管道全缓冲(退出时才刷),错误走 stderr 先到。
* 合成一路时「最后一行」会把真错误顶成旧进度;错误文案必须取 stderr。 */
if (mkscript("nyai_buforder.sh",
"#!/bin/sh\n"
"echo \"nyai: install: 不允许降级: 新版本 code 8 < 已装 code 9\" >&2\n"
"echo \"Reading package lists... Done\"\n"
"echo \"nyai: 签名校验通过 (package=os.meow.reader)\"\n"
"exit 1\n", script, sizeof script) &&
mkfile("pkg-buforder.bin", "x", pkg, sizeof pkg)) {
setenv("MEOW_NYAI_BIN", script, 1);
int r = meow_pkg_install(pkg);
check("C1b fail rc", r == MEOW_PKG_E_FAIL);
check("C1b stderr beats buffered stdout",
strstr(meow_pkg_install_error(), "不允许降级") != NULL &&
strstr(meow_pkg_install_error(), "签名校验通过") == NULL);
} else check("C1b stderr beats buffered stdout", 0);
/* C2: argv 直传(不经 shell):带空格/单引号的路径原样到 $2;$1 必须是 install */
snprintf(log, sizeof log, "%s/argv.log", g_dir);
if (mkscript("nyai_argv.sh",
"#!/bin/sh\n"
"printf '%s|%s\\n' \"$1\" \"$2\" > \"$M42_ARGV_LOG\"\n"
"exit 0\n", script, sizeof script) &&
mkfile("name with space 'q'.bin", "x", pkg, sizeof pkg)) {
setenv("MEOW_NYAI_BIN", script, 1);
setenv("M42_ARGV_LOG", log, 1);
int r = meow_pkg_install(pkg);
check("C2 success rc=0", r == 0);
FILE *f = fopen(log, "r");
char line[600] = "";
if (f) { if (!fgets(line, sizeof line, f)) line[0] = 0; fclose(f); }
char want[700];
snprintf(want, sizeof want, "install|%s\n", pkg);
check("C2 argv passed verbatim (no shell)", strcmp(line, want) == 0);
unsetenv("M42_ARGV_LOG");
} else check("C2 success rc=0", 0);
/* C3: start 立即返回 + 并发拒装(UI 不假死的关键合同) */
if (mkscript("nyai_slow.sh", "#!/bin/sh\nsleep 0.4\nexit 0\n", script, sizeof script) &&
mkfile("pkg-slow.bin", "x", pkg, sizeof pkg)) {
setenv("MEOW_NYAI_BIN", script, 1);
long long t0 = now_ms_mono();
int r = meow_pkg_install_start(pkg);
long long dt = now_ms_mono() - t0;
check("C3 start returns fast", r == 0 && dt < 150);
check("C3 busy while running", meow_pkg_install_busy() == 1);
int r2 = meow_pkg_install_start(pkg);
check("C3 second start -> BUSY", r2 == MEOW_PKG_E_BUSY);
check("C3 busy error text", strstr(meow_pkg_install_error(), "已有安装") != NULL);
int rc = -1, p = 1, spins = 0;
while ((p = meow_pkg_install_poll(&rc)) == 1 && spins++ < 500) usleep(10000);
check("C3 poll finish rc=0", p == 0 && rc == 0);
check("C3 no longer busy", meow_pkg_install_busy() == 0);
} else check("C3 start returns fast", 0);
/* C4: 超时 —— 子进程被终止,返回 TIMEOUT 且不拖住调用方 */
if (mkscript("nyai_hang.sh", "#!/bin/sh\nexec sleep 5\n", script, sizeof script) &&
mkfile("pkg-hang.bin", "x", pkg, sizeof pkg)) {
setenv("MEOW_NYAI_BIN", script, 1);
setenv("MEOW_NYAI_TIMEOUT_MS", "100", 1);
long long t0 = now_ms_mono();
int r = meow_pkg_install(pkg);
long long dt = now_ms_mono() - t0;
check("C4 timeout -> MEOW_PKG_E_TIMEOUT", r == MEOW_PKG_E_TIMEOUT);
check("C4 timeout bounded (<3s)", dt < 3000);
check("C4 timeout text", strstr(meow_pkg_install_error(), "超时") != NULL);
unsetenv("MEOW_NYAI_TIMEOUT_MS");
} else check("C4 timeout -> MEOW_PKG_E_TIMEOUT", 0);
/* C5: nyai 不存在 → 明确报错,不回落旧内联实现、不静默 */
setenv("MEOW_NYAI_BIN", "/nonexistent/nyai", 1);
if (mkfile("pkg-ok.bin", "x", pkg, sizeof pkg)) {
err[0] = 0;
int r = meow_pkg_install(pkg);
check("C5 missing nyai -> MEOW_PKG_E_NYAI", r == MEOW_PKG_E_NYAI);
check("C5 missing nyai -> readable", strstr(meow_pkg_install_error(), "不可用") != NULL);
} else check("C5 missing nyai -> MEOW_PKG_E_NYAI", 0);
/* C6: nyai 存在但不可执行 → 同 E_NYAI */
if (mkfile("nyai_noexec.sh", "#!/bin/sh\nexit 0\n", script, sizeof script) &&
mkfile("pkg-ok2.bin", "x", pkg, sizeof pkg)) {
chmod(script, 0644);
setenv("MEOW_NYAI_BIN", script, 1);
check("C6 non-executable nyai -> MEOW_PKG_E_NYAI",
meow_pkg_install(pkg) == MEOW_PKG_E_NYAI);
chmod(script, 0755);
} else check("C6 non-executable nyai -> MEOW_PKG_E_NYAI", 0);
/* C7: 入口路径预检先于 exec(坏路径不启动子进程) */
unsetenv("MEOW_NYAI_BIN");
check("C7 bad path -> MEOW_PKG_E_PATH",
meow_pkg_install("/nonexistent/x.mpk") == MEOW_PKG_E_PATH);
}
int main(void)
{
const char *tmp = getenv("TMPDIR");
if (!tmp || !*tmp) tmp = "/tmp";
snprintf(g_dir, sizeof g_dir, "%s/m42pkg-XXXXXX", tmp);
if (!mkdtemp(g_dir)) {
/* 退路:当前目录(宿主 CI 也能跑) */
snprintf(g_dir, sizeof g_dir, "./m42pkg-XXXXXX");
if (!mkdtemp(g_dir)) { printf("FAIL cannot create temp dir\n"); return 1; }
}
printf("tmp=%s\n", g_dir);
test_path_check();
test_error_text();
test_exec();
/* 清理(先恢复文件权限,避免删不掉) */
{
char cmd[700];
snprintf(cmd, sizeof cmd, "chmod -R u+rwx '%s' 2>/dev/null; rm -rf '%s'", g_dir, g_dir);
if (system(cmd) != 0) printf("warn: cleanup %s failed\n", g_dir);
}
printf("\n%d PASS, %d FAIL\n", g_pass, g_fail);
return g_fail == 0 ? 0 : 1;
}