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:
1 parent
ebd64d07e1
commit
58cdb73bf8
8 files changed
+654
-108
No files matched your search
@@ -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)
|
||||
|
||||
@@ -24,7 +24,7 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
|
||||
[ -f "../sdk/meow_ui.a" ] || { echo "!! 缺 ../sdk/meow_ui.a,先跑 sdk/build_sdk.sh"; exit 1; }
|
||||
"$CC" -I"../sdk" -Os -static -Wall -Wextra \
|
||||
main_menu.c statusbar.c fb_compositor.c settings_page.c power_menu.c \
|
||||
notif_center.c app_launcher.c audio.c backlight.c net.c about.c \
|
||||
notif_center.c app_launcher.c pkg_install.c audio.c backlight.c net.c about.c \
|
||||
subpage.c wifi_page.c bt_page.c netmgr_ipc.c now_playing.c bg_page.c \
|
||||
font_theme.c keys.c util.c meow_dep.c key_router.c \
|
||||
meow_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \
|
||||
|
||||
@@ -2132,7 +2132,7 @@ static void usage(void)
|
||||
" [--sim-exit-tap N] [--sim-power-hold N]\n"
|
||||
" [--sim-power-pick N] [--sim-no-exec]\n"
|
||||
" [--dumpkeys N] [--scan] [--sysdump]\n"
|
||||
" [--pkg-install <pkg.mpk>] [--pkg-export <pkg> <out.mpk>]\n"
|
||||
" [--pkg-install <file>] [--pkg-export <pkg> <out.mpk>]\n"
|
||||
" [--ipc-only N] [--ipc-sock <path>] [--appinfo <app.json>]\n"
|
||||
" [--fontscan <card_root>]\n");
|
||||
}
|
||||
@@ -2253,10 +2253,12 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
if (pkg_install_mpk) {
|
||||
/* M2.12 .mpk 安装接口(CLI 钩子;UI 后期)。安全、离开主循环。 */
|
||||
/* M42:安装委托 nyai(唯一权威实现;包身份只看包内 app.json,文件名/后缀不参与)。
|
||||
* 失败必须给可读原因(nyai stderr 尾行),不能静默;退出码统一 0/1。 */
|
||||
int r = meow_pkg_install(pkg_install_mpk);
|
||||
printf("pkg_install %s -> %d\n", pkg_install_mpk, r);
|
||||
return r;
|
||||
if (r != 0) fprintf(stderr, "pkg_install failed: %s\n", meow_pkg_install_error());
|
||||
return r == 0 ? 0 : 1;
|
||||
}
|
||||
if (pkg_export_name && pkg_export_out) {
|
||||
/* M2.12 .mpk 导出接口(CLI 钩子;UI 后期)。 */
|
||||
|
||||
@@ -787,7 +787,23 @@ void meow_panel_off(void);
|
||||
void meow_panel_on(void);
|
||||
void meow_parse_app_json(FILE *f, struct app *a);
|
||||
int meow_pkg_export(const char *pkg, const char *out_path);
|
||||
int meow_pkg_install(const char *mpk_path);
|
||||
/* ---- M42:包安装(唯一实现 = /usr/bin/nyai,launcherd 只做入口预检 + 异步执行 + 错误文案)----
|
||||
* 安装流的解包/清单/footer/验签(meow_verify)/防降级/部署/清理全在 nyai;本模块不解析包内容。
|
||||
* nyai 路径 env MEOW_NYAI_BIN 覆写(缺省 /usr/bin/nyai,不依赖 PATH)。 */
|
||||
#define MEOW_PKG_E_PATH (-1) /* 入口路径预检失败(文案见 meow_pkg_install_error) */
|
||||
#define MEOW_PKG_E_NYAI (-2) /* nyai 不可执行 / pipe/fork 失败 */
|
||||
#define MEOW_PKG_E_FAIL (-3) /* nyai 返回非 0 */
|
||||
#define MEOW_PKG_E_TIMEOUT (-4) /* 超时并已终止子进程 */
|
||||
#define MEOW_PKG_E_BUSY (-5) /* 已有一次安装在跑 */
|
||||
#define MEOW_PKG_INSTALL_TIMEOUT_MS 180000
|
||||
int meow_pkg_install(const char *path); /* 同步:0=成功;<0=MEOW_PKG_E_* */
|
||||
int meow_pkg_install_start(const char *path); /* 异步启动:0=已启动;<0 同左 */
|
||||
int meow_pkg_install_poll(int *rc); /* 1=运行中 0=结束(*rc) -1=无任务 */
|
||||
int meow_pkg_install_busy(void); /* 1=有安装在跑(UI 据此禁双击) */
|
||||
const char *meow_pkg_install_error(void); /* 最近一次失败的可读文案(永不 NULL) */
|
||||
/* 纯逻辑(宿主单测 test_pkg_install.c):路径预检 / 退出码→可读文案 */
|
||||
int meow_pkg_install_check_path(const char *path, char *err, int n);
|
||||
void meow_pkg_install_error_text(int rc, const char *detail, char *out, int n);
|
||||
void meow_sys_light(void);
|
||||
void meow_sys_refresh(void);
|
||||
void meow_toggle_panel(void);
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,329 @@
|
||||
/* pkg_install.c — M42: launcherd 侧包安装入口 = fork+exec `nyai install <包路径>`。
|
||||
*
|
||||
* 背景:M35 把 nyai 改成「包身份以已签名 app.json 清单为准 + SD 暂存 + 必清理」后,
|
||||
* launcherd 的 meow_pkg_install() 仍是旧内联实现(文件名推包名、硬要 .mpk 后缀、
|
||||
* 解压前 rm -rf 目标目录、失败不回滚备份、不拒 . / .. 、系统包大小写敏感比较),
|
||||
* 会在 launcherd 入口 100% 复现原事故。本模块删掉那套内联解包,改委托 nyai:
|
||||
* 解包→读清单→footer 一致性→验签(唯一权威 meow_verify)→防降级→rename 部署→清理
|
||||
* 全部只有 nyai 一份实现,launcherd 只负责:
|
||||
* ① 入口路径预检(纯逻辑,宿主可测);
|
||||
* ② 启动子进程并把 stdout/stderr 分别收进有界缓冲(失败给 UI 可读文案);
|
||||
* ③ 两段式 start/poll,安装期间调用方(UI 主循环)可按帧驱动、不假死。
|
||||
*
|
||||
* stdout/stderr 必须分开收:nyai 的进度走 stdout(管道 → 全缓冲,进程退出时才刷),
|
||||
* 错误走 stderr(无缓冲,立即出)。合成一路时「最后一行」会被刷出的旧进度覆盖,
|
||||
* 真机首测即踩到(降到 code 8 的包报错被「签名校验通过」顶掉)。错误文案只看 stderr。
|
||||
*
|
||||
* nyai 二进制:env MEOW_NYAI_BIN 覆写(宿主测试/非常规部署),缺省 /usr/bin/nyai
|
||||
* (固件 fw_content.conf 安装点,与 MEOW_VERIFY_BIN 同口径,不依赖 PATH)。
|
||||
*/
|
||||
#include "meow_launcher_internal.h"
|
||||
|
||||
#define NYAI_BIN_DEFAULT "/usr/bin/nyai"
|
||||
#define OUT_KEEP 1024 /* 每条流保留尾部字节数(够放 nyai 的最后几条信息) */
|
||||
|
||||
static pid_t g_pid = -1; /* >0 = 安装子进程在跑 */
|
||||
static int g_fd_out = -1; /* stdout 读端 */
|
||||
static int g_fd_err = -1; /* stderr 读端 */
|
||||
static char g_buf_out[OUT_KEEP + 1];
|
||||
static int g_len_out;
|
||||
static char g_buf_err[OUT_KEEP + 1];
|
||||
static int g_len_err;
|
||||
static char g_err[256]; /* 最近一次失败的可读文案 */
|
||||
|
||||
/* ---------------- 纯逻辑(宿主单测:test_pkg_install.c) ---------------- */
|
||||
|
||||
/* 取字符串里最后一条非空行(去尾部空白;超长取尾部)。0=有内容 -1=空。 */
|
||||
static int last_line_of(const char *s, char *out, int n)
|
||||
{
|
||||
if (!s || !*s || !out || n <= 0) return -1;
|
||||
size_t L = strlen(s);
|
||||
while (L > 0 && (s[L-1] == '\n' || s[L-1] == '\r' || s[L-1] == ' ' || s[L-1] == '\t')) L--;
|
||||
if (L == 0) { out[0] = 0; return -1; }
|
||||
size_t start = L;
|
||||
while (start > 0 && s[start-1] != '\n') start--;
|
||||
size_t len = L - start;
|
||||
if (len >= (size_t)n) { start += len - (size_t)(n - 1); len = (size_t)(n - 1); }
|
||||
memcpy(out, s + start, len);
|
||||
out[len] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 入口路径预检:0=可以交给 nyai;<0=拒装(err 填可读文案)。纯逻辑,宿主单测。
|
||||
* 只判「有没有这个文件、能不能读、有没有控制字符」——包身份一律由 nyai 从包内
|
||||
* app.json 取,**不要求 .mpk 后缀、不看文件名**(M35 定案)。 */
|
||||
int meow_pkg_install_check_path(const char *path, char *err, int n)
|
||||
{
|
||||
char dummy[8];
|
||||
if (!err || n <= 0) { err = dummy; n = (int)sizeof dummy; }
|
||||
err[0] = 0;
|
||||
if (!path || !*path) { snprintf(err, n, "包路径为空"); return -1; }
|
||||
if (strlen(path) >= 480) { snprintf(err, n, "包路径过长"); return -1; }
|
||||
for (const char *p = path; *p; p++) {
|
||||
unsigned char c = (unsigned char)*p;
|
||||
if (c < 0x20 || c == 0x7f) { snprintf(err, n, "包路径含控制字符"); return -1; }
|
||||
}
|
||||
struct stat st;
|
||||
if (stat(path, &st) != 0) { snprintf(err, n, "找不到包文件 %s", path); return -1; }
|
||||
if (!S_ISREG(st.st_mode)) { snprintf(err, n, "不是普通文件 %s", path); return -1; }
|
||||
if (access(path, R_OK) != 0) { snprintf(err, n, "包文件不可读 %s", path); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 退出码 + 输出尾行 → 一条可读文案。rc 可为 nyai 原始退出码(>0 / >=128)或本模块
|
||||
* 的 MEOW_PKG_E_* 负码。纯逻辑,宿主单测(文案不许为空、不许越界)。 */
|
||||
void meow_pkg_install_error_text(int rc, const char *detail, char *out, int n)
|
||||
{
|
||||
if (!out || n <= 0) return;
|
||||
out[0] = 0;
|
||||
if (rc == 0) return;
|
||||
char line[192];
|
||||
if (last_line_of(detail, line, sizeof line) != 0) line[0] = 0;
|
||||
|
||||
if (rc == MEOW_PKG_E_BUSY) {
|
||||
snprintf(out, n, "已有安装在运行,请稍后再试");
|
||||
} else if (rc == MEOW_PKG_E_PATH) {
|
||||
snprintf(out, n, "%s", line[0] ? line : "安装参数无效");
|
||||
} else if (rc == MEOW_PKG_E_NYAI) {
|
||||
snprintf(out, n, "包管理器 nyai 不可用%s%s", line[0] ? ": " : "", line);
|
||||
} else if (rc == MEOW_PKG_E_TIMEOUT) {
|
||||
snprintf(out, n, "安装超时(nyai 未在限时内结束)%s%s", line[0] ? ": " : "", line);
|
||||
} else if (rc >= 128) {
|
||||
snprintf(out, n, "安装失败(nyai 被信号 %d 终止)", rc - 128);
|
||||
} else if (line[0]) {
|
||||
snprintf(out, n, "%s", line);
|
||||
} else if (rc > 0) {
|
||||
snprintf(out, n, "安装失败(nyai 退出码 %d)", rc);
|
||||
} else {
|
||||
snprintf(out, n, "安装失败(错误码 %d)", rc);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 输出缓冲 ---------------- */
|
||||
|
||||
static void buf_put(char *buf, int *len, const char *s, int n)
|
||||
{
|
||||
if (n <= 0) return;
|
||||
if (n >= OUT_KEEP) {
|
||||
memcpy(buf, s + n - OUT_KEEP, OUT_KEEP - 1);
|
||||
buf[OUT_KEEP - 1] = 0;
|
||||
*len = OUT_KEEP - 1;
|
||||
return;
|
||||
}
|
||||
if (*len + n > OUT_KEEP - 1) {
|
||||
int drop = *len + n - (OUT_KEEP - 1);
|
||||
memmove(buf, buf + drop, (size_t)(*len - drop));
|
||||
*len -= drop;
|
||||
}
|
||||
memcpy(buf + *len, s, (size_t)n);
|
||||
*len += n;
|
||||
buf[*len] = 0;
|
||||
}
|
||||
|
||||
/* 读干一条流当前可用字节;*eof=1 表示写端已关。非阻塞读,调用方按帧驱动。 */
|
||||
static void drain_fd(int *fd, char *buf, int *len, int *eof)
|
||||
{
|
||||
if (*fd < 0) { if (eof) *eof = 1; return; }
|
||||
for (;;) {
|
||||
char b[256];
|
||||
ssize_t r = read(*fd, b, sizeof b);
|
||||
if (r > 0) { buf_put(buf, len, b, (int)r); continue; }
|
||||
if (r == 0) { if (eof) *eof = 1; break; }
|
||||
if (errno == EINTR) continue;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
|
||||
if (eof) *eof = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 子进程退出后把残留数据读干(此时无写端,读空即 EOF),然后关 fd。 */
|
||||
static void drain_finish(int *fd, char *buf, int *len)
|
||||
{
|
||||
if (*fd < 0) return;
|
||||
for (;;) {
|
||||
char b[256];
|
||||
ssize_t r = read(*fd, b, sizeof b);
|
||||
if (r > 0) { buf_put(buf, len, b, (int)r); continue; }
|
||||
if (r < 0 && errno == EINTR) continue;
|
||||
break;
|
||||
}
|
||||
close(*fd);
|
||||
*fd = -1;
|
||||
}
|
||||
|
||||
/* ---------------- 启动 / 轮询 / 同步封装 ---------------- */
|
||||
|
||||
const char *meow_pkg_install_error(void)
|
||||
{
|
||||
return g_err;
|
||||
}
|
||||
|
||||
int meow_pkg_install_busy(void)
|
||||
{
|
||||
return g_pid > 0;
|
||||
}
|
||||
|
||||
/* 启动一次安装(不等待)。0=已启动;<0=MEOW_PKG_E_*(g_err 已填)。 */
|
||||
int meow_pkg_install_start(const char *path)
|
||||
{
|
||||
char perr[256];
|
||||
if (g_pid > 0) {
|
||||
meow_pkg_install_error_text(MEOW_PKG_E_BUSY, NULL, g_err, sizeof g_err);
|
||||
return MEOW_PKG_E_BUSY;
|
||||
}
|
||||
if (meow_pkg_install_check_path(path, perr, sizeof perr) != 0) {
|
||||
snprintf(g_err, sizeof g_err, "%s", perr);
|
||||
logmsg("pkg_install: rejected path: %s", perr);
|
||||
return MEOW_PKG_E_PATH;
|
||||
}
|
||||
|
||||
const char *bin = getenv("MEOW_NYAI_BIN");
|
||||
if (!bin || !*bin) bin = NYAI_BIN_DEFAULT;
|
||||
if (access(bin, X_OK) != 0) {
|
||||
snprintf(g_err, sizeof g_err, "包管理器 nyai 不可用: 无法执行 %s", bin);
|
||||
logmsg("pkg_install: nyai unavailable at %s", bin);
|
||||
return MEOW_PKG_E_NYAI;
|
||||
}
|
||||
|
||||
int po[2], pe[2];
|
||||
if (pipe(po) != 0) {
|
||||
snprintf(g_err, sizeof g_err, "安装无法启动: pipe 失败 (%s)", strerror(errno));
|
||||
return MEOW_PKG_E_NYAI;
|
||||
}
|
||||
if (pipe(pe) != 0) {
|
||||
close(po[0]); close(po[1]);
|
||||
snprintf(g_err, sizeof g_err, "安装无法启动: pipe 失败 (%s)", strerror(errno));
|
||||
return MEOW_PKG_E_NYAI;
|
||||
}
|
||||
/* 读端不进子进程,且轮询按帧读不阻塞主循环。 */
|
||||
fcntl(po[0], F_SETFD, FD_CLOEXEC);
|
||||
fcntl(po[0], F_SETFL, O_NONBLOCK);
|
||||
fcntl(pe[0], F_SETFD, FD_CLOEXEC);
|
||||
fcntl(pe[0], F_SETFL, O_NONBLOCK);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
close(po[0]); close(po[1]); close(pe[0]); close(pe[1]);
|
||||
snprintf(g_err, sizeof g_err, "安装无法启动: fork 失败 (%s)", strerror(errno));
|
||||
logmsg("pkg_install: fork failed");
|
||||
return MEOW_PKG_E_NYAI;
|
||||
}
|
||||
if (pid == 0) {
|
||||
/* 子进程:stdout/stderr 分两条管道,再 exec nyai(argv 直传,不经 shell)。 */
|
||||
dup2(po[1], 1);
|
||||
dup2(pe[1], 2);
|
||||
if (po[1] > 2) close(po[1]);
|
||||
if (pe[1] > 2) close(pe[1]);
|
||||
execl(bin, "nyai", "install", path, (char *)0);
|
||||
{
|
||||
char m[160];
|
||||
int l = snprintf(m, sizeof m, "nyai: 无法执行 %s\n", bin);
|
||||
if (l > 0) { ssize_t w = write(2, m, (size_t)l); (void)w; }
|
||||
}
|
||||
_exit(127);
|
||||
}
|
||||
close(po[1]);
|
||||
close(pe[1]);
|
||||
g_pid = pid;
|
||||
g_fd_out = po[0];
|
||||
g_fd_err = pe[0];
|
||||
g_buf_out[0] = g_buf_err[0] = 0;
|
||||
g_len_out = g_len_err = 0;
|
||||
g_err[0] = 0;
|
||||
logmsg("pkg_install: exec %s install '%s' (pid %d)", bin, path, (int)pid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 轮询:1=运行中;0=已结束(*rc = 退出码,信号为 128+sig);-1=没有安装任务。 */
|
||||
int meow_pkg_install_poll(int *rc)
|
||||
{
|
||||
if (g_pid <= 0) return -1;
|
||||
int eo = 0, ee = 0;
|
||||
drain_fd(&g_fd_out, g_buf_out, &g_len_out, &eo);
|
||||
drain_fd(&g_fd_err, g_buf_err, &g_len_err, &ee);
|
||||
if (eo) { close(g_fd_out); g_fd_out = -1; }
|
||||
if (ee) { close(g_fd_err); g_fd_err = -1; }
|
||||
|
||||
int st = 0;
|
||||
pid_t w = waitpid(g_pid, &st, WNOHANG);
|
||||
if (w < 0 && errno == EINTR) return 1;
|
||||
if (w == 0) return 1;
|
||||
|
||||
drain_finish(&g_fd_out, g_buf_out, &g_len_out);
|
||||
drain_finish(&g_fd_err, g_buf_err, &g_len_err);
|
||||
|
||||
int code;
|
||||
if (w < 0) {
|
||||
code = -1;
|
||||
} else if (WIFEXITED(st)) {
|
||||
code = WEXITSTATUS(st);
|
||||
} else if (WIFSIGNALED(st)) {
|
||||
code = 128 + WTERMSIG(st);
|
||||
} else {
|
||||
code = -1;
|
||||
}
|
||||
g_pid = -1;
|
||||
if (rc) *rc = code;
|
||||
|
||||
if (code != 0) {
|
||||
/* 错误文案只看 stderr(stdout 是缓冲进度,退出时才刷,会把真错误顶掉);
|
||||
* stderr 空才退回 stdout 尾行。 */
|
||||
meow_pkg_install_error_text(code, g_buf_err[0] ? g_buf_err : g_buf_out,
|
||||
g_err, sizeof g_err);
|
||||
logmsg("pkg_install: failed rc=%d (%s)", code, g_err);
|
||||
} else {
|
||||
char line[192];
|
||||
if (last_line_of(g_buf_out, line, sizeof line) == 0)
|
||||
logmsg("pkg_install: ok (%s)", line);
|
||||
else
|
||||
logmsg("pkg_install: ok");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 同步安装(CLI 入口用):start + 轮询到结束。0=成功;<0=MEOW_PKG_E_*。
|
||||
* 超时默认 180s(MEOW_NYAI_TIMEOUT_MS 覆写,仅测试/调试),超时先 SIGTERM 再 SIGKILL。
|
||||
* 轮询切片用固定 20ms usleep 计数,不依赖时钟 ABI(M40:mipsel time32/time64 混用教训)。 */
|
||||
int meow_pkg_install(const char *path)
|
||||
{
|
||||
int r = meow_pkg_install_start(path);
|
||||
if (r != 0) return r;
|
||||
|
||||
int timeout_ms = MEOW_PKG_INSTALL_TIMEOUT_MS;
|
||||
const char *tv = getenv("MEOW_NYAI_TIMEOUT_MS");
|
||||
if (tv && *tv) { int v = atoi(tv); if (v > 0) timeout_ms = v; }
|
||||
int ticks = timeout_ms / 20;
|
||||
if (ticks < 1) ticks = 1;
|
||||
|
||||
for (int i = 0; ; i++) {
|
||||
int rc = 0;
|
||||
int p = meow_pkg_install_poll(&rc);
|
||||
if (p == 0) return (rc == 0) ? 0 : MEOW_PKG_E_FAIL;
|
||||
if (p < 0) {
|
||||
if (!g_err[0]) snprintf(g_err, sizeof g_err, "安装失败(无安装任务)");
|
||||
return MEOW_PKG_E_FAIL;
|
||||
}
|
||||
if (i >= ticks) break;
|
||||
usleep(20000);
|
||||
}
|
||||
|
||||
/* 超时:SIGTERM 给 nyai 走自身清理(stg_cleanup 的 signal 钩子),2s 后 SIGKILL 兜底。 */
|
||||
int finished = 0;
|
||||
if (g_pid > 0) kill(g_pid, SIGTERM);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int rc = 0;
|
||||
if (meow_pkg_install_poll(&rc) == 0) { finished = 1; break; }
|
||||
usleep(20000);
|
||||
}
|
||||
if (!finished && g_pid > 0) {
|
||||
kill(g_pid, SIGKILL);
|
||||
for (int i = 0; i < 100 && g_pid > 0; i++) {
|
||||
int rc = 0;
|
||||
meow_pkg_install_poll(&rc);
|
||||
if (g_pid > 0) usleep(20000);
|
||||
}
|
||||
}
|
||||
snprintf(g_err, sizeof g_err, "安装超时(超过 %d 秒未完成,已终止)", timeout_ms / 1000);
|
||||
logmsg("pkg_install: timeout after %d ms, child killed", timeout_ms);
|
||||
return MEOW_PKG_E_TIMEOUT;
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
# ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)----
|
||||
patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M6.25(开机菜单/第二屏亮度按用户设置恢复:显示前读 settings.conf 写回,方案A)
|
||||
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|605e13a9d154d179f4131e0c5e85c90f|M40 电源键/锁屏链修复(App 前台电源菜单补齐 §10.5:notif 全抑制+帧恢复+SIGCONT 排空+标记翻回,关闭后不再停留菜单画面;show_power_menu 恢复进入前背景帧+重绘挪到首轮按键采样后;activity 标记排空窗+会话入口清理+manual_off 去单向闩锁;锁屏电源改按下边沿+进场 300ms,熄屏退出也恢复底层帧;SIGCONT 前写 /tmp/meow_app_repaint), 链 meow_ui.a 86800633;继承 M39 点击输入门(M39 触摸守卫/MENU_TOUCH_GUARD_MS 150ms + SDK 启动排空);继承 M6.46 杀后台修复+SDK 时钟 ABI 修复(M11/M40);0warning
|
||||
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|cea0a73514f28f2aca419fe7835f78d6|M42 包安装入口改委托 nyai(删 app_launcher.c 内联解包/验签/部署,新增 pkg_install.c:fork+exec /usr/bin/nyai install <路径>,包身份只来自包内 app.json、不强制 .mpk 后缀、SD 暂存+rename+必清理/拒 ./..、大小写绕过全由 nyai 一站闭合;MEOW_NYAI_BIN 覆写;stdout/stderr 分流抓取取 stderr 尾行做可读错误;start/poll 两段式不阻塞主循环+180s 超时;CLI 失败退 1 并打 stderr 文案), 链 meow_ui.a 86800633;继承 M40 电源键/锁屏链修复(App 前台电源菜单补齐 §10.5:notif 全抑制+帧恢复+SIGCONT 排空+标记翻回,关闭后不再停留菜单画面;show_power_menu 恢复进入前背景帧+重绘挪到首轮按键采样后;activity 标记排空窗+会话入口清理+manual_off 去单向闩锁;锁屏电源改按下边沿+进场 300ms,熄屏退出也恢复底层帧;SIGCONT 前写 /tmp/meow_app_repaint);继承 M39 点击输入门(M39 触摸守卫/MENU_TOUCH_GUARD_MS 150ms + SDK 启动排空);继承 M6.46 杀后台修复+SDK 时钟 ABI 修复(M11/M40);0warning
|
||||
patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|37c57e0feb9cd220978bfd5deff75a1f|应用看门狗 M40:o32 time32 取时/睡眠(修 held 天文数字→单击误判长按)、电源阈值读 power.longpress_menu_ms/forceoff_ms(缺省 3000/8000,与 launcher 主屏统一);继承 M20 亮屏亮度按 settings 恢复(四支决策不再回退固定 60)
|
||||
patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187)
|
||||
mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194)
|
||||
|
||||
Reference in new issue
Block a user