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 重定义),其余文件干净。
This commit is contained in:
AskaEth committed 2026-11-04 21:00:40 +08:00
1 parent a73bcb9841
commit 6b52740ff1
11 files changed
+249 -84

No files matched your search

+56 -15
View File
@@ -784,11 +784,35 @@ static void out_append(const char *pkg, pid_t pid, long epoch, long up, const ch
}
/* ---- launcher IPC 转发(xchg 一帧,读回一帧) ---- */
/* 最近一次 ipc_xchg 的失败原因:区分「连不上」与「连上但没及时回」。此前两者都记成
* "launcher offline",2026-11-04 真机即误诊——launcher 活着且回了 ACK,只是晚于 1200ms 超时。 */
enum { IPC_ERR_NONE = 0, IPC_ERR_CONN, IPC_ERR_IO, IPC_ERR_TIMEOUT, IPC_ERR_PROTO };
static int g_ipc_err = IPC_ERR_NONE;
static const char *ipc_err_str(void)
{
switch (g_ipc_err) {
case IPC_ERR_CONN: return "connect failed";
case IPC_ERR_IO: return "io error";
case IPC_ERR_TIMEOUT: return "timeout, no reply";
case IPC_ERR_PROTO: return "unexpected reply";
default: return "ok";
}
}
static int ipc_fail(int fd, int err)
{
if (fd >= 0) close(fd);
g_ipc_err = err;
return -1;
}
static int ipc_xchg(uint16_t type, const void *payload, uint32_t len,
uint8_t *rbuf, uint32_t rcap)
{
g_ipc_err = IPC_ERR_NONE;
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
if (fd < 0) { g_ipc_err = IPC_ERR_IO; return -1; }
/* 收发加超时:launcher 刚刚重启/主循环未就绪(接受但不回包)时,amsd 的阻塞
* read 会永远挂起,冻结整个单线程守护。设 SO_SNDTIMEO/SO_RCVTIMEO 让单次转发
* 有界,超时即返回失败 → ams_request_ctl 退避重连,绝不无限阻塞。
@@ -802,7 +826,7 @@ static int ipc_xchg(uint16_t type, const void *payload, uint32_t len,
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, g_ipc_sock, sizeof a.sun_path - 1);
if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) { close(fd); return -1; }
if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) return ipc_fail(fd, IPC_ERR_CONN);
uint8_t hdr[MEOW_IPC_HDR_LEN];
uint32_t magic = MEOW_IPC_MAGIC, rmagic, rlen;
uint32_t reqid = 0; /* amsd→launcher 控制请求无需回显 */
@@ -813,31 +837,35 @@ static int ipc_xchg(uint16_t type, const void *payload, uint32_t len,
size_t off = 0;
while (off < sizeof hdr) {
ssize_t w = write(fd, hdr + off, sizeof hdr - off);
if (w <= 0) { close(fd); return -1; }
if (w <= 0) return ipc_fail(fd, (w < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
? IPC_ERR_TIMEOUT : IPC_ERR_IO);
off += (size_t)w;
}
if (len && payload) {
size_t p = 0;
while (p < len) {
ssize_t w = write(fd, (const uint8_t *)payload + p, len - p);
if (w <= 0) { close(fd); return -1; }
if (w <= 0) return ipc_fail(fd, (w < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
? IPC_ERR_TIMEOUT : IPC_ERR_IO);
p += (size_t)w;
}
}
off = 0;
while (off < sizeof hdr) {
ssize_t r = read(fd, hdr + off, sizeof hdr - off);
if (r <= 0) { close(fd); return -1; }
if (r <= 0) return ipc_fail(fd, (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
? IPC_ERR_TIMEOUT : IPC_ERR_IO);
off += (size_t)r;
}
memcpy(&rmagic, hdr, 4); memcpy(&rver, hdr + 4, 2);
memcpy(&rtype, hdr + 6, 2); memcpy(&rlen, hdr + 12, 4);
if (rmagic != MEOW_IPC_MAGIC || rver != MEOW_IPC_VERSION) { close(fd); return -1; }
if (rmagic != MEOW_IPC_MAGIC || rver != MEOW_IPC_VERSION) return ipc_fail(fd, IPC_ERR_PROTO);
if (rlen > rcap) rlen = rcap;
off = 0;
while (off < rlen) {
ssize_t r = read(fd, rbuf + off, rlen - off);
if (r <= 0) { close(fd); return -1; }
if (r <= 0) return ipc_fail(fd, (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
? IPC_ERR_TIMEOUT : IPC_ERR_IO);
off += (size_t)r;
}
close(fd);
@@ -858,11 +886,12 @@ static int ams_request_ctl(uint16_t type, const void *payload, uint32_t len, uin
if (code) *code = a.code;
return 0;
}
if (rt == MSG_ERR) return MEOW_ERR_INTERNAL;
if (rt == MSG_ERR) { g_ipc_err = IPC_ERR_PROTO; return MEOW_ERR_INTERNAL; }
/* 一次连接失败/超时(launcher 未就绪)→ 短退避再试;仍失败则返回 -1。 */
if (attempt + 1 < AMS_IPC_RETRY)
usleep((useconds_t)(AMS_IPC_BACKOFF_MS * (attempt + 1)) * 1000);
}
if (g_ipc_err == IPC_ERR_NONE) g_ipc_err = IPC_ERR_PROTO; /* 收到非 ACK/ERR 帧:不是「无回执」 */
return -1;
}
@@ -1228,9 +1257,14 @@ static int cmd_start(const char *spec, char *resp, int rn)
/* launcher ACK ack.code 携带正幅度(busy=8,见 util.c `rc<0?-rc:MEOW_OK`),非二补。 */
snprintf(detail, sizeof detail, "busy (app running)");
emit_log("AMS start %s -> BUSY (app running)", pkg);
} else if (rt == 0) {
snprintf(detail, sizeof detail, "launcher refused code=%d", (int)code);
emit_log("AMS start %s -> launcher refused code=%u(%d)", pkg, code, (int)code);
} else {
snprintf(detail, sizeof detail, "launcher offline");
emit_log("AMS start %s -> launcher offline", pkg);
/* rt!=0 只代表「这次没拿到回执」,不等于 launcher 死了(同 cmd_kill 注释)。
* 响应串保留 "launcher offline"(MeowPanel 据此映射 503),日志补真实原因。 */
snprintf(detail, sizeof detail, "launcher offline (%s)", ipc_err_str());
emit_log("AMS start %s -> launcher offline (%s)", pkg, ipc_err_str());
}
snprintf(resp, rn, "%s %s/%s\n", strstr(detail, "ok forwarded") ? "OK" : "ERR",
pkg, act[0] ? act : "(default)");
@@ -1251,14 +1285,21 @@ static int cmd_kill(const char *pkg, char *resp, int rn)
uint32_t code = 0;
int rt = ams_request_ctl(MSG_AMS_KILL, &k, sizeof k, &code);
if (rt != 0 && a->pid > 0) {
/* 兜底:launcher 不在,直接 SIGTERM(与 launcher 的 SIGTERM 语义一致) */
/* 兜底:launcher 没给回执 → 直接 SIGTERM(与 launcher 的 SIGTERM 语义一致)。
* 注意 rt!=0 **不等于 launcher 死了**:主循环被 IPC 洪泛拖慢时 ACK 会晚于
* AMS_IPC_TIMEOUT_MS,故日志写实际原因(connect failed / timeout, no reply / ...),
* 而不是笼统的 "launcher offline"(2026-11-04 真机即此误判)。 */
kill(a->pid, SIGTERM);
emit_log("AMS kill %s -> direct SIGTERM (launcher offline)", pkg);
emit_log("AMS kill %s -> direct SIGTERM (launcher %s)", pkg, ipc_err_str());
} else if (rt != 0) {
return snprintf(resp, rn, "ERR launcher offline: %s\n", pkg);
/* 响应串保留 "launcher offline" 字样(MeowPanel api_ams_ctl 依此映射 503 AMS_UNAVAILABLE),
* 只在括号里补真实原因,便于区分「连不上」与「连上但超时」。 */
return snprintf(resp, rn, "ERR launcher offline (%s): %s\n", ipc_err_str(), pkg);
} else if (code != MEOW_OK) {
emit_log("AMS kill %s -> launcher refused code=%u", pkg, code);
return snprintf(resp, rn, "ERR launcher refused: %s (code=%u)\n", pkg, code);
/* launcher 明确拒绝:不改判为直杀(pid 可能已被回收,盲杀会打错进程),如实回错。
* code 是负错误码的 uint32 呈现(-2 → 4294967294),括号里给有符号值便于对照。 */
emit_log("AMS kill %s -> launcher refused code=%u(%d)", pkg, code, (int)code);
return snprintf(resp, rn, "ERR launcher refused: %s (code=%d)\n", pkg, (int)code);
}
hist_push(a, "ams_kill", "terminate (via launcher)", (long)time(NULL), read_uptime_sec());
int m = snprintf(resp, rn, "OK terminate %s\n", pkg);