131 lines
5.3 KiB
C
131 lines
5.3 KiB
C
/* 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;
|
||
}
|