Commit Graph
6 Commits
Author SHA1 Message Date
AskaEth 1ccb3c7a93 fix(daemon): M65 P1 三条 + P2-1 — popen 超时/共享缓冲、bt scan 有界收割与毫秒节流
来自 M59 内存与资源安全审计(这 3 条 P1 很可能就是 2026-11-11 18:44 设备用户态崩塌的
成因:当时 sshd 连 fork 都做不到、ICMP 仍应答)。执行顺序按审计要求 P1-3 → P2-1 → P1-1 → P1-2。

P1-1 launcherd net.c:popen_read_tmo —— 对持续输出的子进程永不超时 + 堆无限增长
- 新增 meow_launcherd/popen_tmo.h:读循环的 deadline 移到**循环顶部**,输出加 256KB 硬
  上限(超限返回 OVERFLOW,调用方 kill 子进程并按失败返回 NULL,绝不静默截断),缓冲交给
  调用方或由本函数持有(无 static)。
- net.c 收尸改为 popen_tmo_reap_bounded(300ms + SIGKILL + 300ms):旧实现 SIGKILL 之后是
  无条件 waitpid(…,0),子进程卡 D 态照样钉死 UI 线程。调用面(malloc 返回值 + free)不变,
  14 个调用点零改动。

P1-2 audiod popen_read_tmo 的 static char buf[4096] 被 4 个线程共用
- 改成调用方传缓冲(`char *popen_read_tmo(tmo, cmd, out, cap)`),函数内无共享状态、不加锁
  —— M55 刚把音量写路径做成无锁,音频线程上不能拿锁。7 个调用点各用自己的栈缓冲。
- 顺带修掉旧循环的 `remain` 不递减问题:旧写法每次 poll 都能重等满 tmo,最坏总时长
  ≈ 4095 次 × 1500ms;现在总时长就是一个 tmo。

P1-3 netmgrd bt_scan_on/off 走无超时 system()(单线程主循环 + 55 个残留 bluetoothctl 的形态)
- scan off:直接 run_cmd_ex(BT_SCAN_OFF_TMO_MS=600),超时如实回
  MEOW_ERR_NODEV 并计入既有 bt_circuit_note。
- scan on:fork 一个 setsid 的 scan worker(在飞上限 1),worker 内部用 run_cmd_ex 跑同一条
  pipeline(超时 + 输出上限);父进程只记账,bt_scan_reap() 在主循环每拍收割,过 deadline
  杀**整个进程组**(pipeline 的 bluetoothctl 是孙进程,只杀 sh 会留孤儿)。
- 超时计入既有熔断(连 3 次 → 30s 内不再 fork);熔断中 scan on 不再起进程。
- 不在主循环里同步跑整条 pipeline:它要 ~5s(sleep 1 + dwell 4),同步跑等于每 7s 把 DNS
  代理停 5s,是另一种挂死。

P2-1 扫描节流失效 7000 秒(mono_now() 返回秒,却与 7000 毫秒比较)
- 节流改用 64 位毫秒时钟(bt_scan_now_ms / 函数指针钩子供宿主单测注入可控时钟),抽成纯
  函数 bt_scan_throttle(now_ms);实际节流从 ~1.94 小时恢复到 7s。

测试(每条都有反向控制)
- 新增 meow_launcherd/test_popen_tmo.c(已接入 build_launcher.sh test):
  持续输出且永不退出的生产者 → 新循环 300ms 到点返回,旧循环 1500ms 内不返回;
  `yes`+64KB 上限 → 立刻 OVERFLOW;同一份 300KB 有界输入 → 旧循环照单全收 307200 字节、
  新循环在上限处报 OVERFLOW;两线程交错读 → 旧 static 缓冲 A 读到 B 的内容,调用方缓冲各
  读各的;收尸 313ms 有界且无残留子进程。
- meow_netmgrd/test_bt_ctl.c 增补:7s / 6.999s / 7.1s 边界(含走真实入口 bt_scan_on 的可控
  时钟用例,秒/毫秒单位回归会在这里翻车);wedge 桩(scan 一律 exec sleep 60)下
  scan off 611ms 有界返回且 ECHILD、scan on 的 worker 1200ms 窗口到期被整组收割、孙进程
  全收(ECHILD)、超时进熔断并最终熔断拒绝 fork。

验证:netmgrd 宿主回归 79 PASS / 0 FAIL;test_bt_ctl ALL PASS;launcherd
`build_launcher.sh test` ALL TESTS PASS(含新增用例);meow_netmgrd mipsel 交叉编译通过;
audiod host 构建通过,test_audiod_host 的失败集合与改动前**逐条相同**(41 条,T8 SEGV 是
测试自身 strstr(q8,"rms=")+4 的既有缺陷,见 M59 审计 §5)。
仅源码:无二进制、未改 scripts/build/fw_content.conf。
2026-11-12 09:02:07 +08:00
AskaEth 1e2604953d docs(bt): M54 r1 评审 P2 — 入站 hcitool con 样本内联 od -c 原始捕获(仅注释)
评审指出 S_CON_INBOUND 注释声称「od -c 逐字节校对」,但仓库与设备上都找不到
那份捕获件,traceability 不可审计。把 2026-11-11 20:53(0.8.50 / 10.58.215.50 /
耳机自主回连现场)`hcitool con 2>&1 | od -c` 的原样输出内联进注释块,并写清
逐字节读法、80 字节/末偏移 0000120 的自校验、以及复核命令(printf | od -c)。
同时如实注明该次采集未单独落盘(devdocs/evidence/ 只有 m50 那一份),出站样本
仍指 devdocs/evidence/m50_bt_result_20261111.log:7-8 的 cat -A 证据。

仅注释变化:meow_netmgrd.c 一字未动,测试断言未动(宿主单测 206 ok / 0 FAIL,
连跑 3 次 ALL PASS)。
2026-11-11 21:50:28 +08:00
AskaEth 6e2774ff5c fix(bt): M54 入站 ACL(> ACL)解析 — 耳机自主回连后 connected=0 而声音正常
bt_acl_parse 原先只认行内 "< ACL ",而 hcitool con 对**远端发起**的连接打的是
"> ACL "。耳机自动回连(最常见的连接方式)时于是:bt connected 空、bt status
connected=0、bt connect 报「未生效(hcitool con 无该 ACL)」,而 audiod 用
strstr("ACL") 认得出、声音正常从耳机出 —— 用户看到的就是「看不出到底连没连」。

改法:白名单取代裸子串匹配 —— 行首(可带空白)→ '<' 或 '>' → ' ' → "ACL" → ' '
→ 17 字符 BD_ADDR(且必须在此断词:再跟 hex/':' 不算)。因此 SCO 行、表头、
截断行都不收。MAC 统一大写(与 audiod bt_dev_acl_macs / bluetoothctl 一致)。
单一解析器:bt_acl_macs / bt_acl_has_tri(写后真态复查)/ bt_connected_text /
bt_status.connected_n 全部经 bt_acl_parse,无第二套。

单测用真机原始字节(od -c 校对):
  入站 0.8.50 设备 10.58.215.50 2026-11-11 20:53 "\t> ACL 33:DE:2E:21:AD:30
  handle 11 state 1 lm MASTER AUTH ENCRYPT \n"
  出站 devdocs/evidence/m50_bt_result_20261111.log:8 "\t< ACL ... handle 0
  state 5 lm MASTER \n"
覆盖 >/</>SCO/<SCO/表头/空/NULL/无方向符/截断(行中与行尾)/伪 MAC/大小写/CRLF/
max 裁剪,另加端到端:入站时 connected、status connected=1、connect 不报未生效、
SCO-only 不计连接、disconnect 假成功对入站 ACL 也报「仍有该 ACL」。
用旧解析器跑同一套测试 18 条 FAIL(证明用例不是自证)。

真机验证:同一时刻 0.8.50 旧二进制 bt status connected=0,而按新源码交叉编译
(mipsel 静态)的探针在设备上对**实时 > ACL** 链路报 connected=1 且
bt_connected_text 列出 "Device 33:DE:2E:21:AD:30 MOONDROP Pudding"。
2026-11-11 21:50:28 +08:00
worker-m49 239cebae01 fix(bt): M49 r1 评审 — 写操作预算压回 1500ms 窗口内 + A2DP 总预算/TTL 缓存 + 测试可重复 + rfkill 顺序;rebase 到 main 保住 M43 time32
rebase 到 main(00e7271,M43 time32 已合入) 时解开 build_netmgrd.sh 冲突:**两者都保留**——
M43 的 `../sdk/meow_time32.c` + `-DMEOW_TIME32_LIBC_OVERRIDES`,以及我的 ROOT/ZIGCC/OUT
环境变量化。编译改在 mktemp 目录里做(产物名仍是字面量 `-o meow_netmgrd`,供
sdk/test_time32_abi.sh 的按名定位),编完再落到 OUT —— 设了 OUT 也不会覆盖/删除仓库里
那份被跟踪的二进制(r1 期间 mv 曾把它删掉过)。门禁自证:`--builds` → build meow_netmgrd
src=yes macro=yes;`--no-builds` → 4263=3 4403=0 16B-layout=OK ALL PASS。

P1-1(写操作越过客户端 1500ms 窗口):预算模型改成**每一层都受限**——
`BT_ACT_TMO_MS 800 / BT_ACT_SHORT_MS 600 / BT_VERIFY_BUDGET_MS 220 / gap 90`,
`bt_poll_probe` 把**剩余预算**传给探测函数(`bt_acl_has_tri`/`bt_info_flag` 新增 tmo 形参),
探测自身那次 run_cmd 也受同一个 deadline 约束;`bt_onoff` 同理(up 失败只在剩余预算内重试
一次);成功文案不再补设备名(省掉 1-2 次 fork,UI 本来就按 MAC join)。
探测改为三态:命令失败/超时 = -1(**未知**),不再被当成「没有 ACL」——否则 disconnect 会在
查不到的情况下报「已断开」,又是一次假成功。
实测(宿主桩驱动 bt_ctl_execute,命令卡死 5s 的形态):connect 1042-1047ms、pair 1042-1045ms、
bt on 831ms,全部 < 1450 且有 400ms+ 余量(r1 实测 1528/2424ms)。设备侧实测 fork 成本
≈25-30ms/次(bt a2dp 50ms、bt status 100ms),余量足够。

P2-1(A2DP 1+N 子进程塞进 status JSON):`bt_a2dp_query(buf,cap,budget)` 加**总预算**,
list-pcms 单命令 ≤400ms、每个 info ≤300ms、剩下端点超预算就不再查;`bt a2dp` 命令用 900ms。
status JSON 不在无连接时查(0 次 fork),有连接时走 10s TTL 缓存 + 500ms 预算;
`a2dp_codec`/`a2dp_rate` 字段保留(MeowPanel btCard 依赖,见 docs/plan/FUTURE_MEOWPANEL.md:48)。
实测 4 端点全卡死:911ms(r1 6053ms);status JSON 首次 350-384ms、缓存命中 31-49ms。

P2-2(test_bt_ctl 不可重复):开跑先 `rm -rf` 状态目录、退出再清;连跑 3 次 ALL PASS(贴了输出)。

P2-3(bt_onoff 顺序 + 多节点):开=先 rfkill unblock(**全部**同类型节点)再 hciconfig up,
up 未生效时剩余预算内重试一次;关=先 down 再 block。新增 `rfkill_set_block_all()`(旧
`rfkill_set_block` 仍只写首个节点,WiFi 路径行为不变)。桩改成「blocked 时 up 必败」,并加了
反向用例证明桩真的会失败(否则断言空转)。

P3:①info 的 RUNCMD_EMPTY(exit 0 无输出)不再当失败跳过;②`bt_pcm_paths_parse` 的
`/a2dpsrc` 搜索限制在行内 [p,e)(原来会跨 \n 命中下一行);③`bt_devices_text` 注释写明
「本轮扫描见到过的设备」而非「此刻在场」([DEL] 不删除、devices 块含 BlueZ 历史缓存)。

附带(塔 11:06 建议,BT 作用域内先落地):连续 3 次 RUNCMD_TIMEOUT 后熔断 30s,冷却期内
直接回「熔断中」不再 fork(真机 55 个挂死 bluetoothctl 吃光进程槽的事故);任何非超时返回清零。
单测覆盖:第 4 次调用 0ms 返回。范围只限 BT 写操作,WiFi/DNS 调用点未动。

真机(本轮只读,设备已恢复在线):新件 md5 e62e7512 上卡只读验收 status/paired/connected/
devices/a2dp 全通;`ps w | grep -c bluetoothctl` 前后均为 2(≤3);Music du 199164544;
临时目录/独立实例清理干净,/usr/bin 未动。
2026-11-11 19:15:00 +08:00
worker-m49 370c290160 fix(bt): M49 蓝牙控制面重建 — paired-devices/a2dp list-pcms+info/run_cmd 四态+写后真态复查+扫描 ANSI 解析
真机 0.8.49 现场 /usr/bin/meow_netmgrd 是 P1(eb07415) 之前的旧件:`bt status` 无
connected= 字段、`bt connected|devices|a2dp` 连子命令都不存在(被当成写级拒绝)、
`bt paired` 落到 `bluetoothctl paired` → "Invalid command in menu main"。上游只有
NETMGR_UNREACH 才回退直连,INVAL/PERM 直接返回 → 三个列表恒空。

本单修掉「部署新件后依然存在」的三个真 bug + 一个解析缺陷:

1. `bluetoothctl paired` → `paired-devices`(bt_status 计数、bt paired 子命令两处);
   `devices` 只用于补名,不再当配对权威。
2. A2DP:v4.1.1 没有 `a2dp list`。改为 `list-pcms` 取端点 → 每端点 `info <path>`;
   MAC 只从 D-Bus 路径的 `dev_XX_..` 段解(真机路径带 `/a2dpsrc/source` 段,
   按「最后一段取 token」会拿到 "source")。info 查不到就跳过,不编造。
3. `run_cmd` 四态(成功有输出/成功无输出/非 0 退出/超时/起不来)+ stderr 有界抓取
   (≤256B):旧实现只回字节数、失败也返回 0 → on/off/connect/disconnect/pair 恒报成功。
   写操作改为下发后复查真态(hcitool con / bluetoothctl info Paired: / hciconfig
   power),超时或未收敛如实报 MEOW_ERR_NODEV。兼容外壳 run_cmd() 契约不变。
4. 扫描缓存:实时 `[NEW] Device` 行以 ANSI ESC 开头(真机 od -c 证据),旧解析器只认
   行首 → 实时行全丢。新增 bt_clean_ansi()/bt_scan_line_parse():剥 ESC/CSI/控制符、
   去 `[bluetooth]# ` 提示符、只认 [NEW] 与裸 Device、排除 [CHG]/[DEL];bt_scan_on 改用
   g_bluetoothctl(原来硬编码,桩对 scan 无效)。

解析器全部抽成纯函数并用真机原始字节做单测(test_bt_ctl.c 扩充:四态、ANSI 样本、
截断/空输出、list-pcms/info、hcitool con)。test_netmgrd.sh 桩改 list-pcms/info +
真机 ANSI 缓存样本;顺带修掉两处宿主可移植性问题(/tmp 不可写→$TMPDIR、桩脚本在
PATH=/bin:/usr/bin 下用了 cat/rm→改 shell 内建,test_wifi_connect 的 g_wpa_cli 悬垂
栈指针)。

按塔要求:本 commit 不含二进制、不改 fw_content.conf(netmgrd 产物由 M51 统一重编)。
build_netmgrd.sh 只做 ROOT/ZIGCC/OUT 环境变量化(旧硬编码 $HOME/Project/R3ProII),
M51 复用;time32 接入仍归 M43。
2026-11-11 18:56:06 +08:00
AskaEth eb07415f7b feat(bt): 蓝牙P1 — netmgrd BT_CTL权威收口(hcitool con判连接/paired-devices补名/a2dp详情/connect-disconnect-pair-unpair-scan)+launcher蓝牙子页sub_page_t重做(8分组,数据全走netmgrd)+设置键audio.output_bt/bt.codec;修act冲突+sub悬垂;宿主82PASS+bt_ctl/bt_page全过 2026-09-03 20:01:13 +08:00