feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘) - OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复 - ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640 - 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫) - 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
This commit is contained in:
1 parent
c2ee7626a4
commit
3d70a3dd2c
176 files changed
+15507
-597
No files matched your search
@@ -385,7 +385,12 @@ static void jv_free(jv_t *v)
|
||||
{
|
||||
if (!v) return;
|
||||
jv_t *c = v->child;
|
||||
while (c) { jv_t *nx = c->next; jv_free(c->child); free(c->str); free(c); c = nx; }
|
||||
while (c) {
|
||||
jv_t *nx = c->next;
|
||||
if (v->type == J_ARR) { jv_free(c); } /* 数组元素是完整值(可为对象/嵌套数组),须整体递归释放 */
|
||||
else { jv_free(c->child); free(c->str); free(c); } /* 对象:pair 链 → 释放 value + key + pair 节点 */
|
||||
c = nx;
|
||||
}
|
||||
free(v->str);
|
||||
free(v);
|
||||
}
|
||||
@@ -548,6 +553,37 @@ static jv_t *jv_obj_get(const jv_t *obj, const char *key)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 将 jv 树序列化回 JSON 文本(追加到 sbuf)。覆盖解析器支持的 6 类型;字符串经 sA_str 转义+UTF-8 消毒。
|
||||
* 注意 J_OBJ 的 child 链是「键值对」节点(str=key、child=value),此处按键值对展开,不递归其自身。 */
|
||||
static void jv_to_json(sbuf_t *s, const jv_t *v)
|
||||
{
|
||||
if (!v) { sA(s, "null"); return; }
|
||||
switch (v->type) {
|
||||
case J_NULL: sA(s, "null"); break;
|
||||
case J_BOOL: sA(s, v->bval ? "true" : "false"); break;
|
||||
case J_NUM:
|
||||
if (v->dnum == (double)v->inum) sA(s, "%lld", v->inum);
|
||||
else sA(s, "%g", v->dnum);
|
||||
break;
|
||||
case J_STR: sA_str(s, v->str ? v->str : ""); break;
|
||||
case J_ARR:
|
||||
sA(s, "[");
|
||||
{ int f = 1; for (const jv_t *e = v->child; e; e = e->next) { if (!f) sA(s, ","); f = 0; jv_to_json(s, e); } }
|
||||
sA(s, "]");
|
||||
break;
|
||||
case J_OBJ:
|
||||
sA(s, "{");
|
||||
{ int f2 = 1;
|
||||
for (const jv_t *p = v->child; p; p = p->next) {
|
||||
if (!f2) { sA(s, ","); }
|
||||
f2 = 0;
|
||||
sA_str(s, p->str); sA(s, ":"); jv_to_json(s, p->child);
|
||||
} }
|
||||
sA(s, "}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 设置表(权威来源:meow_settings.c g_defs,逐键一致) ----------------
|
||||
* 特别说明(与 MEOWPANEL_PROTOCOL/实现出入记录):
|
||||
* - ui.style 值域源码为 "md3,miui",但 SYSTEM_SETTINGS §1 定案 + MEOWPANEL_PROTOCOL §5
|
||||
@@ -1236,6 +1272,130 @@ static int api_power(int sock)
|
||||
return send_json(sock, 200, js);
|
||||
}
|
||||
|
||||
/* ---------------- powermgr 期2/期3 扩展消息号(POWER_MANAGER_DESIGN §6.1;本文件局部定,同 meow_ipc.h 号段)----------------
|
||||
* meow_ipc.h 只在注释里预留 0x00D2-0x00D7,未定义宏;与 meow_powermgr.c 本地 `#ifndef` 同值对齐。 */
|
||||
#ifndef MSG_PM_POLICY_GET
|
||||
#define MSG_PM_POLICY_GET 0x00D2 /* ↑ 策略读(空负载 → MSG_PM_RESULT policy JSON;读级任意调用者) */
|
||||
#define MSG_PM_POLICY_SET 0x00D3 /* ↑ 策略写(req: m_pm_policy_set_t;动作级=root+系统身份) */
|
||||
#define MSG_PM_HISTORY 0x00D4 /* ↑ 历史采样查询(req: u32 count,0=默认) */
|
||||
#define MSG_PM_HISTORY_RESULT 0x00D5 /* ↓ 历史 JSON */
|
||||
#define MSG_PM_EVENTS 0x00D6 /* ↑ 事件流查询(req: u32 count,0=默认) */
|
||||
#define MSG_PM_EVENTS_RESULT 0x00D7 /* ↓ 事件 JSON */
|
||||
#define MSG_PM_CALIB_START 0x00D8 /* ↑ 标定启动(动作级;本期面板不暴露,预留) */
|
||||
#define MSG_PM_CALIB_STOP 0x00D9 /* ↑ 标定停止(动作级;预留) */
|
||||
#endif
|
||||
/* 期 3 · 策略写负载(item="profile"/"low-pct"/"crit-pct"/"bri-cap"/"scr-cap")。 */
|
||||
typedef struct m_pm_policy_set { char item[16]; char val[32]; } m_pm_policy_set_t; /* 48 B */
|
||||
|
||||
/* 通用 powermgr IPC:发 req_type + 可选 payload,收任意响应帧。返回值:
|
||||
* >0 = json 字节长度(out 已填,*rtype 置响应帧类型;帧为 m_pm_result_t 布局 code+subset+json_len+json);
|
||||
* <=0 = 负 MEOW_ERR_*(含连不上 NODEV / MSG_ERR 的 code),*rtype 置收到的帧类型(MSG_ERR=0x0003)。
|
||||
* 兼容服务端「紧凑负载」(json 从偏移 12 起,json_len 在偏移 8)。 */
|
||||
static int ipc_pm_send(uint16_t req_type, const void *pl, uint32_t plen,
|
||||
uint16_t *rtype, char *out, int cap)
|
||||
{
|
||||
int fd = ipc_connect_tmo(g_sock_pm, IPC_TIMEOUT_MS);
|
||||
if (fd < 0) return MEOW_ERR_NODEV;
|
||||
int rc = MEOW_ERR_INTERNAL;
|
||||
if (rtype) *rtype = 0;
|
||||
if (ipc_hello(fd) < 0) { rc = MEOW_ERR_INTERNAL; goto done; }
|
||||
if (send_frame(fd, req_type, 0x41, pl, plen) < 0) { rc = MEOW_ERR_IO; goto done; }
|
||||
uint8_t buf[MEOW_IPC_MAX];
|
||||
uint16_t type; uint32_t rid;
|
||||
int n = recv_frame(fd, &type, &rid, buf, sizeof buf);
|
||||
if (n <= 0) { rc = MEOW_ERR_IO; goto done; }
|
||||
if (rtype) *rtype = type;
|
||||
if (type == MSG_ERR) {
|
||||
m_err_t e; memcpy(&e, buf, sizeof e);
|
||||
rc = (int)e.code; goto done;
|
||||
}
|
||||
if (n < 12) { rc = MEOW_ERR_IO; goto done; } /* code(4)+subset(4)+json_len(2)+pad(2) */
|
||||
uint32_t c; memcpy(&c, buf, 4);
|
||||
if (c != MEOW_OK) { rc = (int)c; goto done; }
|
||||
uint16_t jl; memcpy(&jl, buf + 8, 2);
|
||||
if (jl > MEOW_IPC_MAX - 12) jl = (uint16_t)(MEOW_IPC_MAX - 12);
|
||||
if ((size_t)jl >= (size_t)cap) jl = (uint16_t)(cap - 1);
|
||||
memcpy(out, buf + 12, jl); out[jl] = 0;
|
||||
rc = (int)jl;
|
||||
done:
|
||||
close(fd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* GET/POST /api/power/policy → powermgr 期3 策略档位(GET 读级;POST 动作级,前端需二次确认)。
|
||||
* GET:MSG_PM_POLICY_GET → policy JSON(profile 四档/低电阈值/亮度息屏上限,默认全关只透传)。 */
|
||||
static int api_power_policy_get(int sock)
|
||||
{
|
||||
char js[MEOW_IPC_MAX];
|
||||
uint16_t rt = 0;
|
||||
int rc = ipc_pm_send(MSG_PM_POLICY_GET, NULL, 0, &rt, js, sizeof js);
|
||||
if (rc <= 0)
|
||||
return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务不可用(powermgr 未运行/IPC 不可达)");
|
||||
if (rt != MSG_PM_RESULT || !utf8_valid_n(js, strlen(js)))
|
||||
return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务应答包含非法字节");
|
||||
return send_json(sock, 200, js);
|
||||
}
|
||||
|
||||
/* POST /api/power/policy:body {"item":"profile|low-pct|crit-pct|bri-cap|scr-cap","value":<枚举|数字>}。
|
||||
* 经 MSG_PM_POLICY_SET 写 settings.conf power.* 键(powermgr 只写键不直接动系统)。
|
||||
* 服务端对非法值回 200 + {"kind":"policy","ok":false,"why":..};权限不足回 MSG_ERR(MEOW_ERR_PERM)。 */
|
||||
static int api_power_policy_set(int sock, const char *body, size_t body_len)
|
||||
{
|
||||
if (!body || !body[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 body");
|
||||
jv_t *root = jv_parse(body, (int)body_len);
|
||||
if (!root) return send_err(sock, 400, "BAD_REQUEST", "body 非合法 JSON");
|
||||
jv_t *ji = jv_obj_get(root, "item"), *jv = jv_obj_get(root, "value");
|
||||
char item[20] = "", val[40] = "";
|
||||
if (ji && ji->type == J_STR && ji->str) snprintf(item, sizeof item, "%s", ji->str);
|
||||
if (jv && jv->type == J_STR && jv->str) snprintf(val, sizeof val, "%s", jv->str);
|
||||
else if (jv && jv->type == J_NUM) snprintf(val, sizeof val, "%lld", jv->inum);
|
||||
jv_free(root);
|
||||
if (!item[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 item(profile/low-pct/crit-pct/bri-cap/scr-cap)");
|
||||
if (!val[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 value");
|
||||
|
||||
m_pm_policy_set_t s; memset(&s, 0, sizeof s);
|
||||
snprintf(s.item, sizeof s.item, "%.15s", item); /* item[16] 留一个 NUL;防越界截断告警 */
|
||||
snprintf(s.val, sizeof s.val, "%.31s", val); /* val[32] 同理 */
|
||||
char js[MEOW_IPC_MAX];
|
||||
uint16_t rt = 0;
|
||||
int rc = ipc_pm_send(MSG_PM_POLICY_SET, &s, sizeof s, &rt, js, sizeof js);
|
||||
if (rc <= 0) {
|
||||
if (rt == MSG_ERR) /* 权限不足:powermgr 拒绝(面板 root 仅当被白名单才放行;如实回传) */
|
||||
return send_err(sock, 403, "FORBIDDEN", rc == MEOW_ERR_PERM ? "设备身份不允许写电源策略" : "电源策略写入被拒绝");
|
||||
return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务不可用(powermgr 未运行/IPC 不可达)");
|
||||
}
|
||||
if (rt != MSG_PM_RESULT || !utf8_valid_n(js, strlen(js)))
|
||||
return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务应答包含非法字节");
|
||||
/* 服务端对非法值回 ok:false(HTTP 层 200,code 仍 OK);按其 why 映射 422。 */
|
||||
if (strstr(js, "\"ok\":false")) {
|
||||
char why[96] = "";
|
||||
jv_t *jr = jv_parse(js, (int)strlen(js));
|
||||
if (jr) { jv_t *w = jv_obj_get(jr, "why"); if (w && w->type == J_STR && w->str) snprintf(why, sizeof why, "%s", w->str); jv_free(jr); }
|
||||
return send_err(sock, 422, "UNSUPPORTED", why[0] ? why : "非法电源策略值");
|
||||
}
|
||||
return send_json(sock, 200, js);
|
||||
}
|
||||
|
||||
/* GET /api/power/history → MSG_PM_HISTORY 采样环(30s 环形 4h,不落盘)。
|
||||
* GET /api/power/events → MSG_PM_EVENTS 异常耗电/充放电/亮灭屏/背光跳变事件(内存环形)。
|
||||
* 两者语义对齐 powermgr 期 2;count=0(默认)由服务端选合理上限,读级任意调用者。 */
|
||||
static int api_power_window(int sock, uint16_t req_type, uint16_t want_type, const char *unavail)
|
||||
{
|
||||
uint32_t count = 0;
|
||||
char js[MEOW_IPC_MAX];
|
||||
uint16_t rt = 0;
|
||||
int rc = ipc_pm_send(req_type, &count, sizeof count, &rt, js, sizeof js);
|
||||
if (rc <= 0)
|
||||
return send_err(sock, 503, "POWER_UNAVAILABLE", unavail);
|
||||
if (rt != want_type || !utf8_valid_n(js, strlen(js)))
|
||||
return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务应答包含非法字节");
|
||||
return send_json(sock, 200, js);
|
||||
}
|
||||
static int api_power_history(int sock)
|
||||
{ return api_power_window(sock, MSG_PM_HISTORY, MSG_PM_HISTORY_RESULT, "电源历史采样不可用(powermgr 未运行)"); }
|
||||
static int api_power_events(int sock)
|
||||
{ return api_power_window(sock, MSG_PM_EVENTS, MSG_PM_EVENTS_RESULT, "电源事件不可用(powermgr 未运行)"); }
|
||||
|
||||
/* ---- am/meow_svcd 二进制解析(popen 薄壳,复用有界收割 run_cmd) ----
|
||||
* env MEOW_AMS_BIN / MEOW_SVCD_BIN 可覆盖(宿主/qemu 测试用;设备默认 /usr/bin,回退卡上路径)。 */
|
||||
static const char *ams_bin(void)
|
||||
@@ -1514,6 +1674,211 @@ static int api_net_log(int sock)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ---------------- netmgrd 期2/期3:接口列表 + 管控策略(只读展示 / 写级经 require_auth) ----------------
|
||||
* GET /api/net/ifaces → 0xE2(name 空=全部)接口数组 + 0xE0 状态里的 rssi(WiFi 信号)+ rfkill + dns。
|
||||
* 上游任一源坏字节 → available:false(绝不伪造)。netmgrd 不在 → available:false(HTTP 200,不报错)。
|
||||
* GET /api/net/policy → 0xEA SHOW(只读)→ 策略文本 parse 成 JSON(enforce=dns_only 如实透传)。
|
||||
* POST /api/net/policy → 0xEA 写级(SET_NET/SET_DOMAIN/DEL);HTTP 层已过 require_auth,IPC 层由 netmgrd
|
||||
* 依 SO_PEERCRED 判系统身份(面板精确登记 os.meow.panel),拒绝时如实回传错误码,绝不掩盖。 */
|
||||
static int api_net_ifaces(int sock)
|
||||
{
|
||||
m_net_iface_req_t q; memset(&q, 0, sizeof q);
|
||||
uint16_t t; uint32_t code; char arr[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_IFACE_REQ, &q, sizeof q, &t, &code, NULL, arr, sizeof arr);
|
||||
if (n <= 0 || t != MSG_NET_IFACE_RESULT || code != MEOW_OK || !arr[0])
|
||||
return send_json(sock, 200, "{\"available\":false}");
|
||||
/* 上游接口数组由 netmgrd iface_json_obj 直出(name/kind/ip/gw/rx/tx,ASCII,无 ssid),仍校验坏字节。 */
|
||||
if (!utf8_valid_n(arr, strlen(arr)))
|
||||
return send_json(sock, 200, "{\"available\":false}");
|
||||
|
||||
/* 取 rssi + rfkill + dns:用 0xE0 状态 JSON(单一权威,含 wifi.rssi/rfkill[]/bt.dns[]),jv 提取。 */
|
||||
uint16_t st; uint32_t scode; char sj[MEOW_IPC_MAX];
|
||||
int sn2 = net_ipc(MSG_NET_STATUS_REQ, NULL, 0, &st, &scode, NULL, sj, sizeof sj);
|
||||
long long rssi = -9999; int got_rssi = 0;
|
||||
sbuf_t rfkill = {0}, dns = {0};
|
||||
if (sn2 > 0 && st == MSG_NET_STATUS_RESULT && scode == MEOW_OK && utf8_valid_n(sj, strlen(sj))) {
|
||||
jv_t *jr = jv_parse(sj, (int)strlen(sj));
|
||||
if (jr) {
|
||||
jv_t *w = jv_obj_get(jr, "wifi");
|
||||
jv_t *r = w ? jv_obj_get(w, "rssi") : NULL;
|
||||
if (r && r->type == J_NUM) { rssi = r->inum; got_rssi = 1; }
|
||||
jv_t *rf = jv_obj_get(jr, "rfkill");
|
||||
if (rf && rf->type == J_ARR) jv_to_json(&rfkill, rf);
|
||||
jv_t *bt = jv_obj_get(jr, "bt");
|
||||
jv_t *dn = bt ? jv_obj_get(bt, "dns") : NULL;
|
||||
if (dn && dn->type == J_ARR) jv_to_json(&dns, dn);
|
||||
jv_free(jr);
|
||||
}
|
||||
}
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"available\":true,\"rssi\":");
|
||||
if (got_rssi) sA(&s, "%lld", rssi); else sA(&s, "null");
|
||||
sA(&s, ",\"rfkill\":%s,\"dns\":%s,\"ifaces\":%s}", rfkill.b ? rfkill.b : "[]", dns.b ? dns.b : "[]", arr);
|
||||
sbuf_free(&rfkill); sbuf_free(&dns);
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 策略 SHOW 文本 → JSON。netmgrd 首行:`# 策略(NETMGR_DESIGN §6)log_level=%s enforce=dns_only(...)`;
|
||||
* 其后每实体一行:`<entity> net=block domain=+h1,-h2` 或 `(allow)`(无规则)。 */
|
||||
static void net_policy_parse(const char *txt, sbuf_t *s)
|
||||
{
|
||||
char lvl[24] = "", enf[24] = "dns_only";
|
||||
const char *p = txt;
|
||||
sA(s, "{\"available\":true,\"enforce\":");
|
||||
/* 首行找 enforce=... */
|
||||
/* enforce 后面紧跟全角「(」多字节说明,须在非 ASCII 字节处停(否则会吞「(SOCK_DESTROY...)」)。 */
|
||||
const char *en = strstr(p, "enforce=");
|
||||
if (en) {
|
||||
en += 8;
|
||||
int i = 0; while (en[i] && en[i] != ' ' && en[i] != '\n' && (unsigned char)en[i] < 0x80 && i < (int)sizeof enf - 1) { enf[i] = en[i]; i++; }
|
||||
enf[i] = 0;
|
||||
}
|
||||
const char *lg = strstr(p, "log_level=");
|
||||
if (lg) {
|
||||
lg += 10;
|
||||
int i = 0; while (lg[i] && lg[i] != ' ' && lg[i] != '\n' && (unsigned char)lg[i] < 0x80 && i < (int)sizeof lvl - 1) { lvl[i] = lg[i]; i++; }
|
||||
lvl[i] = 0;
|
||||
}
|
||||
sA_str(s, enf);
|
||||
sA(s, ",\"log_level\":");
|
||||
sA_str(s, lvl);
|
||||
sA(s, ",\"rules\":[");
|
||||
int first = 1;
|
||||
const char *ln = txt;
|
||||
while (ln && *ln) {
|
||||
const char *nl = strchr(ln, '\n');
|
||||
size_t ll = nl ? (size_t)(nl - ln) : strlen(ln);
|
||||
if (ll == 0) { ln = nl ? nl + 1 : ln + ll; continue; }
|
||||
/* 跳注释行(以 # 开头)与空;数据行为 `<entity> net=... domain=...`(entity 含冒号无空格) */
|
||||
const char *c = ln;
|
||||
while (c < ln + ll && (*c == ' ' || *c == '\t')) c++;
|
||||
int is_comment = (c < ln + ll && *c == '#');
|
||||
int is_none = (strncmp(c, "(none)", 6) == 0 || strncmp(c, "(allow)", 7) == 0);
|
||||
if (!is_comment && !is_none && c < ln + ll) {
|
||||
/* entity = 首个空白符之前 */
|
||||
char entity[64] = ""; size_t ei = 0;
|
||||
const char *e = c;
|
||||
while (e < ln + ll && *e != ' ' && *e != '\t' && ei < sizeof entity - 1) { entity[ei++] = *e++; }
|
||||
entity[ei] = 0;
|
||||
/* net=block|allow 与 domain=+h1,-h2 从 entity 之后扫 */
|
||||
char net[8] = "allow"; int has_net = 0;
|
||||
char allow_h[512] = "", deny_h[512] = "";
|
||||
const char *k = e;
|
||||
if (k < ln + ll) {
|
||||
const char *seg = k;
|
||||
while (seg < ln + ll && (*seg == ' ' || *seg == '\t')) seg++;
|
||||
const char *nh = strstr(seg, "net=");
|
||||
if (nh) { nh += 4; int i2 = 0; while (nh[i2] && nh[i2] != ' ' && nh[i2] != '\n' && i2 < 7) { net[i2] = nh[i2]; i2++; } net[i2] = 0; has_net = 1; }
|
||||
const char *dh = strstr(seg, "domain=");
|
||||
if (dh) {
|
||||
dh += 7;
|
||||
/* 逗号分隔;+ 前缀=allow,- 前缀=deny。主机列表内部以 ',' 拼接(供拆分回数组)。 */
|
||||
size_t ao = 0, do2 = 0;
|
||||
const char *h = dh;
|
||||
while (*h && *h != ' ' && *h != '\n' && *h != '\r') {
|
||||
if (*h == '+') {
|
||||
h++;
|
||||
if (ao && ao < sizeof allow_h - 1) allow_h[ao++] = ',';
|
||||
while (*h && *h != ',' && *h != ' ' && *h != '\n' && ao < sizeof allow_h - 1) allow_h[ao++] = *h++;
|
||||
} else if (*h == '-') {
|
||||
h++;
|
||||
if (do2 && do2 < sizeof deny_h - 1) deny_h[do2++] = ',';
|
||||
while (*h && *h != ',' && *h != ' ' && *h != '\n' && do2 < sizeof deny_h - 1) deny_h[do2++] = *h++;
|
||||
} else h++;
|
||||
if (*h == ',') h++;
|
||||
}
|
||||
allow_h[ao] = 0; deny_h[do2] = 0;
|
||||
}
|
||||
if (!first) sA(s, ",");
|
||||
first = 0;
|
||||
sA(s, "{\"entity\":"); sA_str(s, entity);
|
||||
sA(s, ",\"net\":"); sA_str(s, has_net ? net : "");
|
||||
sA(s, ",\"has_net\":%s", has_net ? "true" : "false");
|
||||
sA(s, ",\"allow_hosts\":[");
|
||||
{ const char *hp = allow_h; int hf = 1;
|
||||
while (hp && *hp) { const char *comma = strchr(hp, ','); size_t hl = comma ? (size_t)(comma - hp) : strlen(hp);
|
||||
if (!hf) { sA(s, ","); }
|
||||
hf = 0; sA_str_n(s, hp, hl);
|
||||
hp = comma ? comma + 1 : hp + hl; } }
|
||||
sA(s, "],\"deny_hosts\":[");
|
||||
{ const char *hp = deny_h; int hf = 1;
|
||||
while (hp && *hp) { const char *comma = strchr(hp, ','); size_t hl = comma ? (size_t)(comma - hp) : strlen(hp);
|
||||
if (!hf) { sA(s, ","); }
|
||||
hf = 0; sA_str_n(s, hp, hl);
|
||||
hp = comma ? comma + 1 : hp + hl; } }
|
||||
sA(s, "]}");
|
||||
}
|
||||
}
|
||||
ln = nl ? nl + 1 : ln + ll;
|
||||
}
|
||||
sA(s, "]}");
|
||||
}
|
||||
|
||||
/* GET /api/net/policy(只读):0xEA SHOW → JSON(rules + enforce=dns_only 如实)。 */
|
||||
static int api_net_policy_get(int sock)
|
||||
{
|
||||
m_net_policy_t q; memset(&q, 0, sizeof q);
|
||||
q.op = NET_POLICY_SHOW;
|
||||
uint16_t t; uint32_t code; char txt[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_POLICY, &q, sizeof q, &t, &code, NULL, txt, sizeof txt);
|
||||
if (n <= 0 || t != MSG_NET_POLICY_RESULT || code != MEOW_OK)
|
||||
return send_json(sock, 200, "{\"available\":false}");
|
||||
if (!utf8_valid_n(txt, strlen(txt))) /* 实体名/域名坏字节 → 降级 */
|
||||
return send_json(sock, 200, "{\"available\":false}");
|
||||
sbuf_t s = {0};
|
||||
net_policy_parse(txt, &s);
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* POST /api/net/policy:body {"op":"set_net|set_domain|del","entity":"<app:..|svc:..|proc:..|global>","arg":"<allow|block|+host|-host>"}。
|
||||
* op 缺失默认 set_net;op 非法 → 422。arg 校验交给 netmgrd(防自锁硬拒在 netmgrd 服务端)。 */
|
||||
static int api_net_policy_set(int sock, const char *body, size_t body_len)
|
||||
{
|
||||
if (!body || !body[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 body");
|
||||
jv_t *root = jv_parse(body, (int)body_len);
|
||||
if (!root) return send_err(sock, 400, "BAD_REQUEST", "body 非合法 JSON");
|
||||
jv_t *jo = jv_obj_get(root, "op"), *je = jv_obj_get(root, "entity"), *ja = jv_obj_get(root, "arg");
|
||||
char op[16] = "set_net", entity[64] = "", arg[64] = "";
|
||||
if (jo && jo->type == J_STR && jo->str) snprintf(op, sizeof op, "%s", jo->str);
|
||||
if (je && je->type == J_STR && je->str) snprintf(entity, sizeof entity, "%s", je->str);
|
||||
if (ja && ja->type == J_STR && ja->str) snprintf(arg, sizeof arg, "%s", ja->str);
|
||||
jv_free(root);
|
||||
if (!entity[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 entity(app:<pkg>/svc:<svc>/proc:<comm>)");
|
||||
if (!entity[0] || strchr(entity, '/')) return send_err(sock, 422, "UNSUPPORTED", "entity 非法");
|
||||
int nop = 0;
|
||||
if (!strcmp(op, "set_net")) nop = NET_POLICY_SET_NET;
|
||||
else if (!strcmp(op, "set_domain")) nop = NET_POLICY_SET_DOMAIN;
|
||||
else if (!strcmp(op, "del")) nop = NET_POLICY_DEL;
|
||||
else return send_err(sock, 422, "UNSUPPORTED", "op 须为 set_net|set_domain|del");
|
||||
if (nop != NET_POLICY_DEL && !arg[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 arg");
|
||||
|
||||
m_net_policy_t q; memset(&q, 0, sizeof q);
|
||||
q.op = (uint32_t)nop;
|
||||
snprintf(q.entity, sizeof q.entity, "%.47s", entity); /* entity[48] 留一个 NUL;防截断告警 */
|
||||
snprintf(q.arg, sizeof q.arg, "%.63s", arg); /* arg[64] 同理 */
|
||||
uint16_t t; uint32_t code; char txt[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_POLICY, &q, sizeof q, &t, &code, NULL, txt, sizeof txt);
|
||||
if (n <= 0 || t != MSG_NET_POLICY_RESULT)
|
||||
return send_err(sock, 503, "NET_UNAVAILABLE", "网络管理服务不可用");
|
||||
if (code == (uint32_t)MEOW_ERR_PERM)
|
||||
return send_err(sock, 403, "FORBIDDEN", txt[0] ? txt : "策略写入需系统身份,面板未被白名单?");
|
||||
if (code == (uint32_t)MEOW_ERR_INVAL)
|
||||
return send_err(sock, 422, "UNSUPPORTED", txt[0] ? txt : "策略参数非法");
|
||||
if (code != MEOW_OK)
|
||||
return send_err(sock, 503, "NET_NODEV", txt[0] ? txt : "策略写入失败");
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"accepted\":true,\"message\":");
|
||||
sA_str(&s, txt);
|
||||
sA(&s, "}");
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ---------------- amsd / svcd 服务表 + 首页聚合(批1 只读) ---------------- */
|
||||
|
||||
/* 就地去掉 ANSI 转义序列(\033[...final);svcd status all 走管道时 CLI 会去色,此处双保险)。 */
|
||||
@@ -1559,6 +1924,8 @@ static void join_tokens(char **toks, int from, int to, char *out, size_t cap)
|
||||
out[o] = 0;
|
||||
}
|
||||
|
||||
static void ams_fg_fill(sbuf_t *s); /* 定义于下;ams_fill 内引用其前台摘要(前向声明) */
|
||||
|
||||
/* GET /api/ams:`am list`(注册表+状态)parse → App 表。amsd 不在 → available:false(绝无假数据)。 */
|
||||
static void ams_fill(sbuf_t *s)
|
||||
{
|
||||
@@ -1597,8 +1964,57 @@ static void ams_fill(sbuf_t *s)
|
||||
}
|
||||
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
|
||||
}
|
||||
sA(s, "{\"available\":true,\"count\":%d,\"apps\":[%s]}", count, apps.b ? apps.b : "");
|
||||
sA(s, "{\"available\":true,\"count\":%d,\"apps\":[%s]", count, apps.b ? apps.b : "");
|
||||
sbuf_free(&apps);
|
||||
ams_fg_fill(s); /* 追加 `,"fg":{..},"running":[..]`(首页前台 App 高亮;additive) */
|
||||
sA(s, "}");
|
||||
}
|
||||
|
||||
/* `am status` → `,"fg":{...},"running":[...]`(供 /api/ams 与 overview 的 ams 块复用;仅前台摘要,轻)。
|
||||
* am status 行:`RUN %s %s %ld %llu %s\n` = pkg state pid dur activity(可含空格)。
|
||||
* activity=join(tok[5..n));fg=首个 state=="fg"。amsd 不在 → fg:null,running:[](绝无假数据)。 */
|
||||
static void ams_fg_fill(sbuf_t *s)
|
||||
{
|
||||
char out[16384];
|
||||
char cmd[300];
|
||||
snprintf(cmd, sizeof cmd, "%s status 2>/dev/null", ams_bin());
|
||||
int code = run_cmd(cmd, out, sizeof out);
|
||||
if (code != 0 || !strstr(out, "RUN")) { sA(s, ",\"fg\":null,\"running\":[]"); return; }
|
||||
panel_strip_ansi(out);
|
||||
char *toks[256];
|
||||
char fg_pkg[128] = "", fg_act[160] = ""; int fg_pid = 0;
|
||||
sbuf_t run = {0}; int first = 1;
|
||||
char *ln = out;
|
||||
while (*ln) {
|
||||
char *nl = strchr(ln, '\n');
|
||||
if (nl) *nl = 0;
|
||||
if (strncmp(ln, "RUN ", 4) == 0) {
|
||||
int n = txt_tokens(ln, toks, 256);
|
||||
if (n >= 6) { /* RUN pkg state pid dur activity... */
|
||||
char act[160];
|
||||
join_tokens(toks, 5, n, act, sizeof act);
|
||||
const char *st = toks[2];
|
||||
if (!first) sA(&run, ",");
|
||||
first = 0;
|
||||
sA(&run, "{\"pkg\":"); sA_str(&run, toks[1]);
|
||||
sA(&run, ",\"state\":"); sA_str(&run, st);
|
||||
sA(&run, ",\"pid\":%d", atoi(toks[3]));
|
||||
sA(&run, ",\"activity\":"); sA_str(&run, act);
|
||||
sA(&run, "}");
|
||||
if (strcmp(st, "fg") == 0 && !fg_pkg[0]) {
|
||||
snprintf(fg_pkg, sizeof fg_pkg, "%s", toks[1]);
|
||||
snprintf(fg_act, sizeof fg_act, "%s", act);
|
||||
fg_pid = atoi(toks[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
|
||||
}
|
||||
sA(s, ",\"fg\":");
|
||||
if (fg_pkg[0]) { sA(s, "{\"pkg\":"); sA_str(s, fg_pkg); sA(s, ",\"activity\":"); sA_str(s, fg_act); sA(s, ",\"pid\":%d}", fg_pid); }
|
||||
else sA(s, "null");
|
||||
sA(s, ",\"running\":[%s]", run.b ? run.b : "");
|
||||
sbuf_free(&run);
|
||||
}
|
||||
|
||||
static int api_ams(int sock)
|
||||
@@ -1610,6 +2026,100 @@ static int api_ams(int sock)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 包名合法性校验(ams history/start/kill 共用):反向域名命名字符集,拒 /、: 与空白。 */
|
||||
static int ams_pkg_ok(const char *pkg)
|
||||
{
|
||||
if (!pkg || !pkg[0]) return 0;
|
||||
if (strlen(pkg) >= MEOW_APP_ID_MAX) return 0;
|
||||
for (const char *p = pkg; *p; p++) {
|
||||
unsigned char c = (unsigned char)*p;
|
||||
if (c == '/' || c == ':' || c == ' ' || c == '\t' || c == '\n' || c == '\r') return 0;
|
||||
if (c < 0x21 || c > 0x7e) return 0; /* 包名仅 ASCII 可见字符 */
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* GET /api/ams/:pkg → `am history <pkg>`(amsd 现成:incident 登记 + 崩溃根因 detail)。
|
||||
* 输出行:`EVT <epoch> <up> <kind> <pkg> <detail...>`(detail 可含空格,为崩溃根因/事件文本)。
|
||||
* 上游 text 含崩溃 detail,须 utf8 校验坏字节降级 available:false(绝不发非法 JSON)。
|
||||
* 解析部分独立成 ams_history_parse(供 selftest 喂截断 detail 断言合法 JSON)。 */
|
||||
static void ams_history_parse(char *out, sbuf_t *s, const char *pkg) /* out 被 panel_strip_ansi 就地改 */
|
||||
{
|
||||
if (!utf8_valid_n(out, strlen(out))) { sA(s, "{\"available\":false}"); return; } /* 崩溃 detail 坏字节 → 降级 */
|
||||
panel_strip_ansi(out);
|
||||
char *toks[256];
|
||||
sbuf_t ev = {0};
|
||||
int first = 1, count = 0;
|
||||
char *ln = out;
|
||||
while (*ln) {
|
||||
char *nl = strchr(ln, '\n');
|
||||
if (nl) *nl = 0;
|
||||
if (strncmp(ln, "EVT ", 4) == 0) {
|
||||
int n = txt_tokens(ln, toks, 256);
|
||||
if (n >= 5) { /* EVT epoch up kind pkg detail... */
|
||||
char det[256];
|
||||
join_tokens(toks, 5, n, det, sizeof det);
|
||||
if (!first) sA(&ev, ",");
|
||||
first = 0; count++;
|
||||
sA(&ev, "{\"ts\":%ld,\"uptime_sec\":%ld,\"kind\":", atol(toks[1]), atol(toks[2]));
|
||||
sA_str(&ev, toks[3]);
|
||||
sA(&ev, ",\"detail\":"); sA_str(&ev, det);
|
||||
sA(&ev, "}");
|
||||
}
|
||||
}
|
||||
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
|
||||
}
|
||||
sA(s, "{\"available\":true,\"pkg\":"); sA_str(s, pkg);
|
||||
sA(s, ",\"count\":%d,\"events\":[%s]}", count, ev.b ? ev.b : "");
|
||||
sbuf_free(&ev);
|
||||
}
|
||||
static void ams_history_fill(sbuf_t *s, const char *pkg)
|
||||
{
|
||||
char out[16384];
|
||||
char cmd[320];
|
||||
snprintf(cmd, sizeof cmd, "%s history %s 2>/dev/null", ams_bin(), pkg);
|
||||
int code = run_cmd(cmd, out, sizeof out);
|
||||
if (code != 0 || !strstr(out, "EVT")) { sA(s, "{\"available\":false}"); return; }
|
||||
ams_history_parse(out, s, pkg);
|
||||
}
|
||||
static int api_ams_history(int sock, const char *pkg)
|
||||
{
|
||||
if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
|
||||
sbuf_t s = {0};
|
||||
ams_history_fill(&s, pkg);
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* POST /api/ams/:pkg/start → `am start <pkg>`(amsd→launcher MSG_AMS_START 拉起;launcher 离线回 ERR)。
|
||||
* POST /api/ams/:pkg/kill → `am kill <pkg>`(SIGTERM 语义,前台需前端二次确认)。均 require_auth 之后。 */
|
||||
static int api_ams_ctl(int sock, const char *pkg, int kill)
|
||||
{
|
||||
if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
|
||||
char out[256];
|
||||
char cmd[320];
|
||||
snprintf(cmd, sizeof cmd, "%s %s %s 2>/dev/null", ams_bin(), kill ? "kill" : "start", pkg);
|
||||
int code = run_cmd(cmd, out, sizeof out);
|
||||
if (code == 0 && strstr(out, "OK ")) {
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"accepted\":true,\"op\":\"%s\",\"pkg\":", kill ? "kill" : "start");
|
||||
sA_str(&s, pkg);
|
||||
sA(&s, ",\"message\":"); sA_str(&s, out);
|
||||
sA(&s, "}");
|
||||
int rc2 = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc2;
|
||||
}
|
||||
/* am CLI 以文本报告失败(ERR ...);按内容映射 HTTP 码(如实,绝不掩盖)。 */
|
||||
const char *detail = out[0] ? out : (kill ? "am kill 失败" : "am start 失败");
|
||||
if (strstr(detail, "not registered")) return send_err(sock, 404, "NOT_FOUND", detail);
|
||||
if (strstr(detail, "busy")) return send_err(sock, 409, "CONFLICT", detail);
|
||||
if (strstr(detail, "not running")) return send_err(sock, 409, "CONFLICT", detail);
|
||||
if (strstr(detail, "launcher offline")) return send_err(sock, 503, "AMS_UNAVAILABLE", "amsd/launcher 不可达");
|
||||
return send_err(sock, 500, "INTERNAL", detail);
|
||||
}
|
||||
|
||||
/* GET /api/svcd:`meow_svcd status all` parse → 服务表(state/pid/restart 计数/exec/来源)。
|
||||
* svcd 未运行(exit!=0)→ available:false(绝无假数据)。 */
|
||||
static void svcd_fill(sbuf_t *s)
|
||||
@@ -1691,6 +2201,17 @@ static int api_overview(int sock)
|
||||
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)
|
||||
@@ -5496,12 +6017,22 @@ static int handle(int sock, req_t *r)
|
||||
|
||||
/* ---- 批1 只读增强:功耗/进程服务/首页聚合(均走 PIN 鉴权读级) ---- */
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power")) return api_power(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power/policy")) return api_power_policy_get(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power/history")) return api_power_history(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power/events")) return api_power_events(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/ams")) return api_ams(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/svcd")) return api_svcd(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/overview")) return api_overview(sock);
|
||||
if (!strcmp(r->method, "GET")) {
|
||||
char seg[256];
|
||||
if (path_segment("/api/ams/", p, seg, sizeof seg) && seg[0])
|
||||
return api_ams_history(sock, seg); /* GET /api/ams/:pkg → am history(崩溃根因) */
|
||||
}
|
||||
|
||||
/* ---- 网络管理(meow_netmgrd 期2 透传:读级任意调用者;flush 写级走 require_auth) ---- */
|
||||
/* ---- 网络管理(meow_netmgrd 期2/期3 透传:读级任意调用者;flush/policy 写级走 require_auth) ---- */
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/status")) return api_net_status(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/ifaces")) return api_net_ifaces(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/policy")) return api_net_policy_get(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/conns")) return api_net_conns(sock, r->query);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/dns")) return api_net_dns(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/log")) return api_net_log(sock);
|
||||
@@ -5533,6 +6064,8 @@ static int handle(int sock, req_t *r)
|
||||
if (!strcmp(p, "/api/apps/install")) return api_apps_install(sock, r);
|
||||
if (!strcmp(p, "/api/notifications/dismiss_all") || !strcmp(p, "/api/notifications/clear")) return api_notification_dismiss_all(sock);
|
||||
if (!strcmp(p, "/api/net/dns/flush")) return api_net_dns_flush(sock);
|
||||
if (!strcmp(p, "/api/net/policy")) return api_net_policy_set(sock, r->body, r->body_len);
|
||||
if (!strcmp(p, "/api/power/policy")) return api_power_policy_set(sock, r->body, r->body_len);
|
||||
if (!strcmp(p, "/api/audio/route")) return api_audio_route(sock, r->body, r->body_len);
|
||||
if (!strcmp(p, "/api/audio/volume")) return api_audio_volume(sock, r->body, r->body_len);
|
||||
if (!strcmp(p, "/api/key")) return api_key_batch(sock, r);
|
||||
@@ -5540,6 +6073,19 @@ static int handle(int sock, req_t *r)
|
||||
char seg[256];
|
||||
if (path_segment("/api/key/", p, seg, sizeof seg))
|
||||
return api_key_single(sock, atoi(seg), r);
|
||||
/* POST /api/ams/<pkg>/start|kill(管启/停;require_auth 已护,kill 前台前端二次确认) */
|
||||
if (!strncmp(p, "/api/ams/", 9)) {
|
||||
size_t pl = strlen(p);
|
||||
if (pl > 5 && !strcmp(p + pl - 6, "/start")) {
|
||||
char pkg[256];
|
||||
if (path_segment("/api/ams/", p, pkg, sizeof pkg))
|
||||
return api_ams_ctl(sock, pkg, 0);
|
||||
} else if (pl > 4 && !strcmp(p + pl - 5, "/kill")) {
|
||||
char pkg[256];
|
||||
if (path_segment("/api/ams/", p, pkg, sizeof pkg))
|
||||
return api_ams_ctl(sock, pkg, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 设置 ---- */
|
||||
@@ -5697,7 +6243,43 @@ static int panel_selftest(void)
|
||||
jv_free(ar);
|
||||
sbuf_free(&a);
|
||||
|
||||
printf("SELFTEST PASS: 截断 UTF-8「流媒体客户\\xE7」→ 合法 JSON + 无非法字节\n");
|
||||
/* 4) net_policy_parse:喂含坏字节域名(截断「段」字)+ allow/deny 混合 → 产物合法 JSON、无非法字节。
|
||||
* 域名/实体名都走 sA_str_n 消毒(坏字节→'?'),故解析器产物必为合法 JSON。 */
|
||||
{
|
||||
static const char ptext[] =
|
||||
"# 策略(NETMGR_DESIGN §6)log_level=debug enforce=dns_only(SOCK_DESTROY 在本内核为 no-op)\n"
|
||||
"app:com.example.trunc net=block domain=+good.example,-bad\xE7\n"
|
||||
"svc:meowpanel net=allow domain=+svc.local\n";
|
||||
sbuf_t p = {0};
|
||||
net_policy_parse(ptext, &p);
|
||||
if (!p.b || !utf8_valid_n(p.b, p.n)) { printf("SELFTEST FAIL: net_policy_parse 产物含非法字节/为空\n"); sbuf_free(&p); return 1; }
|
||||
jv_t *pr = jv_parse(p.b, (int)p.n);
|
||||
if (!pr) { printf("SELFTEST FAIL: net_policy_parse 产物 JSON 无法解析\n"); sbuf_free(&p); return 1; }
|
||||
jv_t *rules = jv_obj_get(pr, "rules");
|
||||
if (!rules || rules->type != J_ARR || !rules->child) { printf("SELFTEST FAIL: net_policy_parse 缺 rules 数组\n"); jv_free(pr); sbuf_free(&p); return 1; }
|
||||
jv_t *enf = jv_obj_get(pr, "enforce");
|
||||
if (!enf || enf->type != J_STR || !enf->str || strcmp(enf->str, "dns_only") != 0) { printf("SELFTEST FAIL: net_policy_parse enforce 未如实透传 dns_only\n"); jv_free(pr); sbuf_free(&p); return 1; }
|
||||
jv_free(pr);
|
||||
sbuf_free(&p);
|
||||
}
|
||||
|
||||
/* 5) ams_history_parse:喂 EVT 行 + 截断崩溃 detail(含坏字节)→ available:false(降级,绝不发非法 JSON)。
|
||||
* detail 合法时 → 合法 JSON 可解析。 */
|
||||
{
|
||||
char bad[] = "# amd history com.example.trunc\nEVT 1786523400 123 crash com.example.trunc 崩溃在函数 foo\xE7\n";
|
||||
sbuf_t h = {0};
|
||||
ams_history_parse(bad, &h, "com.example.trunc");
|
||||
if (!h.b || strstr(h.b, "available\":false") == NULL) { printf("SELFTEST FAIL: ams_history_parse 截断 detail 未降级 available:false\n"); sbuf_free(&h); return 1; }
|
||||
sbuf_free(&h);
|
||||
|
||||
char good[] = "# amd history com.example.trunc\nEVT 1786523400 123 crash com.example.trunc SIGSEGV at addr 0x10\nEVT 1786523500 124 ams_start com.example.trunc ok forwarded\n";
|
||||
sbuf_t h2 = {0};
|
||||
ams_history_parse(good, &h2, "com.example.trunc");
|
||||
if (!h2.b || !utf8_valid_n(h2.b, h2.n) || !jv_parse(h2.b, (int)h2.n)) { printf("SELFTEST FAIL: ams_history_parse 合法 detail 无法解析/含非法字节\n"); sbuf_free(&h2); return 1; }
|
||||
sbuf_free(&h2);
|
||||
}
|
||||
|
||||
printf("SELFTEST PASS: 截断 UTF-8「流媒体客户\\xE7」+ net/power/ams 新增端点池 → 合法 JSON + 无非法字节\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user