/* mp_svcd.c — 服务表域:svcd 状态解析 + /api/svcd + 首页聚合 /api/overview。 */ #include "mp.h" const char *svcd_bin(void) { const char *e = getenv("MEOW_SVCD_BIN"); if (e && e[0]) return e; if (!access(SVCD_BIN, X_OK)) return SVCD_BIN; return SVCD_BIN_CARD; } void svcd_fill(sbuf_t *s) { char out[16384]; char cmd[300]; snprintf(cmd, sizeof cmd, "%s status all 2>/dev/null", svcd_bin()); int code = run_cmd(cmd, out, sizeof out); if (code != 0) { sA(s, "{\"available\":false}"); return; } panel_strip_ansi(out); char *toks[256]; sbuf_t svcs = {0}; int first = 1, count = 0; char *ln = out; while (*ln) { char *nl = strchr(ln, '\n'); if (nl) *nl = 0; int n = txt_tokens(ln, toks, 256); /* 数据行:NAME STATE PID RESTART BACKOFF TRIGGER SRC EXIT EXIT_TIME EXEC... * state=tok[1] 须为合法状态;exec=join(tok[9..n))。 */ if (n >= 10) { const char *st = toks[1]; int is_known = (!strcmp(st,"running") || !strcmp(st,"stopped") || !strcmp(st,"stopping") || !strcmp(st,"disabled") || !strcmp(st,"failed")); if (is_known) { char exec[512]; join_tokens(toks, 9, n, exec, sizeof exec); int restarts = 0; { const char *slash = strchr(toks[3], '/'); (void)slash; restarts = atoi(toks[3]); } if (!first) sA(&svcs, ","); first = 0; count++; sA(&svcs, "{\"name\":"); sA_str(&svcs, toks[0]); sA(&svcs, ",\"state\":"); sA_str(&svcs, toks[1]); sA(&svcs, ",\"pid\":%d", atoi(toks[2])); sA(&svcs, ",\"restart\":%d", restarts); sA(&svcs, ",\"restart_str\":"); sA_str(&svcs, toks[3]); sA(&svcs, ",\"backoff_ms\":%d", atoi(toks[4])); sA(&svcs, ",\"trigger\":"); sA_str(&svcs, toks[5]); sA(&svcs, ",\"src\":"); sA_str(&svcs, toks[6]); sA(&svcs, ",\"exit\":"); sA_str(&svcs, toks[7]); sA(&svcs, ",\"exit_time\":"); sA_str(&svcs, toks[8]); sA(&svcs, ",\"exec\":"); sA_str(&svcs, exec); sA(&svcs, "}"); } } if (nl) { *nl = '\n'; ln = nl + 1; } else break; } sA(s, "{\"available\":true,\"count\":%d,\"services\":[%s]}", count, svcs.b ? svcs.b : ""); sbuf_free(&svcs); } int api_svcd(int sock) { sbuf_t s = {0}; svcd_fill(&s); int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}"); sbuf_free(&s); return rc; } int api_overview(int sock) { sbuf_t s = {0}; sA(&s, "{\"ts\":%lld,", (long long)time(NULL)); char pb[4096]; if (ipc_perf_query(0, pb, sizeof pb) == MEOW_OK && utf8_valid_n(pb, strlen(pb))) sA(&s, "\"perf\":%s,", pb); else sA(&s, "\"perf\":{\"available\":false},"); char pw[MEOW_IPC_MAX]; if (ipc_pm_query(0, pw, sizeof pw) == MEOW_OK && utf8_valid_n(pw, strlen(pw))) sA(&s, "\"power\":%s,", pw); else sA(&s, "\"power\":{\"available\":false},"); /* 最近异常耗电/充放电/亮灭屏/背光跳变事件(摘要 top2 进 overview,完整列表走 /api/power/events)。 */ { uint32_t ecnt = 2; char ej[MEOW_IPC_MAX]; uint16_t ert = 0; int ert_rc = ipc_pm_send(MSG_PM_EVENTS, &ecnt, sizeof ecnt, &ert, ej, sizeof ej); if (ert_rc > 0 && ert == MSG_PM_EVENTS_RESULT && utf8_valid_n(ej, strlen(ej))) sA(&s, "\"events\":%s,", ej); else sA(&s, "\"events\":{\"available\":false},"); } sA(&s, "\"audio\":"); audio_fill(&s); if (net_block_fill(&s) == 0) ; /* net 块已追加(状态 + 合并的 dns 子键) */ else sA(&s, ",\"net\":{\"available\":false}"); sA(&s, ",\"ams\":"); ams_fill(&s); sA(&s, ",\"svcd\":"); svcd_fill(&s); sA(&s, "}"); /* 服务端自校验:发出前 jv_parse 自查,坏了留证+回 500(防「非 JSON 响应」破页,2026-09-02 间歇性事故) * 加严:内嵌 NUL(strlen 截断浏览器收残体)/非法 UTF-8(浏览器 JSON.parse 拒收,宽松解析器放行)*/ jv_t *chk = jv_parse(s.b ? s.b : "{}", (int)(s.b ? s.n : 2)); size_t slen = s.b ? strlen(s.b) : 0; int nul_inside = s.b && (slen != s.n); /* 内嵌 NUL:strlen < s.n */ int utf8_bad = s.b && !utf8_valid_n(s.b, s.n); int unbalanced = s.b && !json_strict_balanced(s.b, s.n); /* 尾随垃圾/多括号(jv_parse 容忍,浏览器不容忍) */ if (s.b && (nul_inside || utf8_bad || unbalanced)) { chk = NULL; } if (!chk && s.b) { FILE *df = fopen("/tmp/overview_bad.json", "w"); if (df) { fwrite(s.b, 1, s.n, df); fclose(df); } panel_log("overview: SELF-CHECK fail (nul=%d utf8_bad=%d unbalanced=%d n=%zu strlen=%zu), raw dumped /tmp/overview_bad.json", nul_inside, utf8_bad, unbalanced, s.n, slen); sbuf_free(&s); return send_err(sock, 500, "INTERNAL", "overview 聚合输出未通过 JSON 自校验(原始数据已留 /tmp/overview_bad.json)"); } if (chk) jv_free(chk); int rc = send_json(sock, 200, s.b ? s.b : "{}"); sbuf_free(&s); return rc; }