Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/test_bgmgr.c
T
AskaEth 6b52740ff1 fix: 后台页杀按钮点不动 — app.json framework=1 被误当「不可杀」门控 + am kill 无前台会话不可执行
根因(2026-11-04 真机 dev.player.ncm pid=7520):
- bg_page.c bgm_app_meta 把 app.json 的 framework 字段映射进 bgmgr_row.framework,而
  bgmgr_can_kill 把它当「系统/框架 App 禁止 kill」。但 framework=1 在 MeowOS 里意为「用
  meow_ui.a 的 SDK 框架 App」(docs/APP_DEV_GUIDE.md §9,推荐值,真机 4 个 App 全写 1)
  → 后台页每行「杀」按钮被置灰,bgm_row_tap 命中后直接 return 1、不调 meow_bg_terminate;
  「一键清除后台」同理 0 行可杀。整条链路无日志,故只能靠翻 app.json 才定位(全部设备日志里
  "后台管理页终止后台App" 出现 0 次 = 4 次进页从未真正杀过)。
- 面板 killfg 正常是因为走 am kill → amsd → MSG_AMS_KILL,无 framework 门控;但它其实靠
  amsd 1200ms×2 超时兜底 kill(pid,SIGTERM) 才杀成:util.c 原本在无前台 App 会话时只回
  ACK -2((uint32_t)MEOW_ERR_NOENT),ACK 及时到达时 amsd 判「launcher refused」完全不杀
  (ams_20260915_031339.log 实证),ACK 迟到才侥幸成功(2026-11-04 真机)。

修复:
- meow_bgmgr: 门控位 framework → protected_app(系统级受保护标记;当前无 App 置位,launcher
  自身组件本就不在 App 表/后台表内)。bg_page 不再把 a->framework 送进门控。
- bg_page: 行副文本改存 ctx 持久缓冲(原 rbuf[].sub 指向 bgm_rows 栈帧 → 悬垂,副标题乱码);
  门控拒绝/未命中后台表各留一行 logmsg,「点了没反应」从此可查。
- util.c MSG_AMS_KILL: 先查后台保活表 → meow_bg_terminate 就地走真退序列(与页面「杀」按钮
  同一通道,故 am kill 与页面按钮行为对称),无前台会话也能杀;否则仍走 pending / ACK -2。
- amsd cmd_kill/cmd_start: ipc 失败原因细分(connect failed / timeout, no reply / ...),日志
  不再笼统写 "launcher offline"(响应串保留该字样,MeowPanel api_ams_ctl 依此映射 503)。
- meow_bgplay: meow_bg_terminate 注释/日志写明「后台管理页 / am kill」双入口同通道。

测试(宿主 gcc,产物在 $TMPDIR):
- test_bgpage 40 checks:新增 framework=1 仍判可杀且按钮真 terminate(本 bug 回归)、
  受保护行按钮不杀但留诊断日志、sub 持久化 + 栈抖动不变(悬垂回归)。
- test_bgmgr 30 checks:protected_app 门控矩阵 + framework=1 可杀回归。
- test_bgplay 92 PASS:新增 4b meow_bg_terminate 全链路(受理→term_at_ms→tick SIGTERM→
  on_bg_dead→清槽;空/不在表分别 INVAL/NOENT);TEST_SVC_PATH 改取 $TMPDIR
  (Termux 无 /tmp,原 fputs(NULL) 直接 abort)。
- amsd 宿主集成(fake launcher 桩,4 例):ACK 0→terminate 且不盲杀;ACK -2→refused 不盲杀;
  无 launcher→direct SIGTERM (connect failed);挂起不回→direct SIGTERM (timeout, no reply)。
0warning:util.c 与 main 同告警逐行一致(仅 SDK 头既有 typedef 重定义),其余文件干净。
2026-11-04 21:00:40 +08:00

187 lines
8.5 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_bgmgr.c — 批③ 后台管理页 逻辑层宿主单测(meow_bgmgr)。
*
* 验证:
* 1) bgmgr_collect:enumerate 后台表 → app_meta 补 name/icon/protected_app/is_fg → proc_sample 补
* pcpu/rss_kb;行 killable 预判正确;name 回退 app_id。
* 2) bgmgr_can_kill 门控矩阵:protected_app=1 → 0(**受保护 App 不 kill**);is_fg=1 → 0(当前前台不 kill);
* pid<=0 → 0(非后台/无进程);普通后台(非受保护/非前台/pid>0)→ 1。
* 3) bgmgr_kill_all 一键清:受保护 App 不杀、当前前台不杀、非后台不杀;其余逐一 kill;返回 kill 数。
* 3b) 回归:app.json framework=1(SDK 框架 App 标志,现有 App 全写 1)**不影响**可杀性。
* 4) bgmgr_status_str / bgmgr_mem_str 文案。
* 只依赖 meow_bgmgr.c + meow_ipc.h(纯 C);宿主 gcc 直编:
* gcc -Wall -Wextra -std=gnu99 test_bgmgr.c meow_bgmgr.c -o test_bgmgr
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include "meow_bgmgr.h"
static int g_fails = 0;
static int g_checks = 0;
#define CHECK(cond, msg) do { \
g_checks++; \
if (cond) printf("PASS: %s\n", msg); \
else { printf("FAIL: %s\n", msg); g_fails++; } } while (0)
/* ---- 假件(enumerate/app_meta/proc_sample 注入)---- */
static const char g_meta_app[][24] = {
"os.meow.hello", "os.meow.reader", "os.meow.sysguard", "os.meow.fgapp",
};
static int g_killed_n = 0;
static char g_killed_app[8][40];
static int g_meta_use_unknown = 0; /* 第 4 行 meta 返回 -1(未注册),测 name 回退 */
static int fake_enumerate(void *ctx, bgmgr_row_t *rows, int max)
{
(void)ctx;
if (max < 4) return 0;
/* 4 个后台行:hello=普通、reader=常驻、sysguard=受保护、fgapp=当前前台 */
memset(&rows[0], 0, sizeof rows[0]);
snprintf(rows[0].app_id, sizeof rows[0].app_id, "%s", g_meta_app[0]);
rows[0].pid = 1001; rows[0].keepalive = 0; rows[0].on_card = 0; rows[0].bg_since_ms = 1000;
memset(&rows[1], 0, sizeof rows[1]);
snprintf(rows[1].app_id, sizeof rows[1].app_id, "%s", g_meta_app[1]);
rows[1].pid = 1002; rows[1].keepalive = 1; rows[1].on_card = 0; rows[1].bg_since_ms = 2000;
memset(&rows[2], 0, sizeof rows[2]);
snprintf(rows[2].app_id, sizeof rows[2].app_id, "%s", g_meta_app[2]);
rows[2].pid = 1003; rows[2].keepalive = 0; rows[2].on_card = 0; rows[2].bg_since_ms = 3000;
memset(&rows[3], 0, sizeof rows[3]);
snprintf(rows[3].app_id, sizeof rows[3].app_id, "%s", g_meta_app[3]);
rows[3].pid = 1004; rows[3].keepalive = 0; rows[3].on_card = 0; rows[3].bg_since_ms = 4000;
return 4;
}
/* meta:hello/reader/sysguard/fgapp 各给名字;sysguard 标受保护;pid==1004 标前台。 */
static int fake_app_meta(void *ctx, const char *app_id, pid_t pid,
char *name, int nl, char *icon, int il,
char *protected_app, int *is_fg)
{
(void)ctx;
if (!strcmp(app_id, "os.meow.hello")) snprintf(name, nl, "播放");
else if (!strcmp(app_id, "os.meow.reader")) snprintf(name, nl, "阅读");
else if (!strcmp(app_id, "os.meow.sysguard")) snprintf(name, nl, "系统守护");
else if (!strcmp(app_id, "os.meow.fgapp")) snprintf(name, nl, "前台应用");
else return -1; /* 未注册 → name 回退 app_id */
snprintf(icon, il, "/tmp/icon_%s.png", app_id);
*protected_app = (!strcmp(app_id, "os.meow.sysguard")) ? '1' : 0;
*is_fg = (pid == 1004) ? 1 : 0; /* fgapp 标前台 */
return 0;
}
static int fake_proc_sample(void *ctx, pid_t pid, double *pcpu, long *rss_kb)
{
(void)ctx;
if (pid <= 0) return -1;
*pcpu = (double)(pid % 100) * 0.5; /* 1001->0.5% ... 1004->2.0% */
*rss_kb = (long)(pid % 10) * 2048; /* 1001->2048 ... 1004->8192 */
return 0;
}
static void fake_log(const char *fmt, ...) { (void)fmt; }
static void fake_kill(const char *app_id)
{
if (g_killed_n < 8) snprintf(g_killed_app[g_killed_n++], 40, "%s", app_id);
}
/* enumerate 只回一行:未注册 app_id,测 name 回退。 */
static int fake_enumerate_unknown(void *ctx, bgmgr_row_t *rows, int max)
{
(void)ctx;
if (max < 1) return 0;
(void)g_meta_use_unknown;
memset(&rows[0], 0, sizeof rows[0]);
snprintf(rows[0].app_id, sizeof rows[0].app_id, "os.meow.noreg");
rows[0].pid = 2001; rows[0].keepalive = 0;
return 1;
}
int main(void)
{
bgmgr_ops_t ops;
memset(&ops, 0, sizeof ops);
ops.enumerate = fake_enumerate;
ops.app_meta = fake_app_meta;
ops.proc_sample = fake_proc_sample;
ops.log = fake_log;
bgmgr_row_t rows[MEOW_BGMR_ROW_MAX];
int n = bgmgr_collect(&ops, rows, MEOW_BGMR_ROW_MAX);
printf("=== 1) collect ===\n");
CHECK(n == 4, "collect returns 4 rows");
CHECK(!strcmp(rows[0].name, "播放") && rows[0].pid == 1001, "row0 hello name/pid");
CHECK(rows[0].protected_app == 0, "row0 hello not protected");
CHECK(!strcmp(rows[1].name, "阅读") && rows[1].keepalive == 1, "row1 reader keepalive");
CHECK(rows[2].protected_app == '1', "row2 sysguard protected");
CHECK(rows[3].is_fg == 1, "row3 fgapp is_fg");
CHECK(rows[0].pcpu > 0 && rows[0].rss_kb > 0, "row0 pcpu/rss populated");
CHECK(rows[0].killable == 1 && rows[2].killable == 0, "killable prefilled by can_kill");
CHECK(!strcmp(rows[2].icon, "/tmp/icon_os.meow.sysguard.png"), "icon populated");
/* name 回退:未注册 app_id(app_meta 返回 -1)→ name==app_id */
printf("=== 1b) name fallback ===\n");
{
bgmgr_ops_t o2 = ops; o2.enumerate = fake_enumerate_unknown;
bgmgr_row_t r1[1]; memset(r1, 0, sizeof r1);
int n2 = bgmgr_collect(&o2, r1, 1);
CHECK(n2 == 1, "unknown row collected");
CHECK(!strcmp(r1[0].name, "os.meow.noreg"), "name falls back to app_id");
CHECK(bgmgr_can_kill(&r1[0]) == 1, "unknown bg app still killable");
}
printf("=== 2) can_kill gating ===\n");
CHECK(bgmgr_can_kill(&rows[2]) == 0, "protected app NOT killable");
CHECK(bgmgr_can_kill(&rows[3]) == 0, "fg app NOT killable (is_fg)");
CHECK(bgmgr_can_kill(&rows[0]) == 1, "normal bg app killable");
CHECK(bgmgr_can_kill(&rows[1]) == 1, "keepalive bg app killable");
{
bgmgr_row_t dead; memset(&dead, 0, sizeof dead);
snprintf(dead.app_id, sizeof dead.app_id, "x");
dead.pid = 0;
CHECK(bgmgr_can_kill(&dead) == 0, "pid<=0 NOT killable");
CHECK(bgmgr_can_kill(NULL) == 0, "NULL NOT killable");
}
printf("=== 3) kill_all (one-key clear) ===\n");
g_killed_n = 0;
int k = bgmgr_kill_all(&ops, rows, n, fake_kill);
CHECK(k == 2, "kill_all kills 2 (hello+reader)");
CHECK(g_killed_n == 2, "kill_all actually invoked kill_fn 2x");
int killed_fw = 0, killed_fg = 0, killed_bg = 0;
for (int i = 0; i < g_killed_n; i++) {
if (!strcmp(g_killed_app[i], "os.meow.sysguard")) killed_fw = 1;
if (!strcmp(g_killed_app[i], "os.meow.fgapp")) killed_fg = 1;
if (!strcmp(g_killed_app[i], "os.meow.hello")) killed_bg = 1;
}
CHECK(killed_fw == 0, "kill_all did NOT kill protected app");
CHECK(killed_fg == 0, "kill_all did NOT kill fg app");
CHECK(killed_bg == 1, "kill_all killed a real bg app");
CHECK(bgmgr_kill_all(&ops, rows, n, NULL) == 0, "kill_all NULL kill_fn -> 0");
/* 回归(2026-11-04 真机):app.json framework=1 是「SDK 框架 App」标志(现有 App 全写 1),
* **不是**受保护位。旧实现把它当门控 → dev.player.ncm 等所有后台 App 都不可杀、按钮置灰。 */
printf("=== 3b) framework(SDK) ≠ 受保护 ===\n");
{
bgmgr_row_t sdk; memset(&sdk, 0, sizeof sdk);
snprintf(sdk.app_id, sizeof sdk.app_id, "dev.player.ncm");
sdk.pid = 7520; sdk.protected_app = 0;
CHECK(bgmgr_can_kill(&sdk) == 1, "SDK 框架 App(framework=1)后台仍可杀");
}
printf("=== 4) status/mem str ===\n");
CHECK(!strcmp(bgmgr_status_str(&rows[1]), "常驻"), "status_str keepalive=常驻");
CHECK(!strcmp(bgmgr_status_str(&rows[0]), "后台"), "status_str normal=后台");
CHECK(!strcmp(bgmgr_status_str(&rows[3]), "前台"), "status_str is_fg=前台");
CHECK(!strcmp(bgmgr_mem_str(512), "512KB"), "mem_str(<1MB)=KB");
CHECK(!strcmp(bgmgr_mem_str(2048), "2.0MB"), "mem_str(>=1MB)=MB");
if (g_fails == 0) printf("\nALL PASS (%d checks)\n", g_checks);
else printf("\n%d FAILED\n", g_fails);
return g_fails ? 1 : 0;
}