feat(m53): 自写 timeout(1) 并接入 devkit 本地源码构建路径

设备没有 timeout,busybox 1.31 也无该 applet;2026-11-11 18:44 一条挂在
硬件上的 bluetoothctl 累积 55 个残留进程耗尽进程表,dropbear 无法 fork,
整机在线却不可登录。本单给设备一个可用的 timeout。

- scripts/build/devkit-src/timeout.c(新,Apache-2.0,489 行)
  独立实现,未拷贝 busybox/GPL 代码;fork+setpgid(0,0) 后 kill(-pgid),
  孙进程一并终止;waitpid 回收直接子进程不留僵尸;-s/-k 两阶段终止;
  退出码 124/137/128+N/125/126/127 对齐 coreutils 语义。
  计时不用 timerfd(musl 的 timerfd_settime 在 4.4 上静默失效,实测
  timeout 2 sleep 30 等满 30s 才返回),改用 setitimer(ITIMER_REAL)+SIGALRM,
  与 SIGCHLD 一起进 signalfd,由一次 ppoll 等待,零轮询、不读时钟。
- build_devkit.sh:新增 build_local(B 路径,不碰上游 procps/lsof/ncurses
  流程);TOOLS/TOOL_NAMES/MANIFEST/licenses/stamp/--with-sources 接入;
  产物同时标注 Apache-2.0 并随附许可证原文。
- test_build_devkit.sh:T9(产物静态/架构/零 NEEDED/MANIFEST/license)、
  T10(源码纪律:独立实现、不调 clock_gettime、不用 timerfd、进程组模型)。
- DEVKIT.md/README.md:产物表、PATH 实测(ssh 非登录 shell 无 opt/bin,
  且设备无 bash → 用 sh -lc 或绝对路径)、禁裸跑纪律与 18:44 事故时间线、
  timerfd 静默失效的第二类 time64 坑、2026-11-11 真机验收原始输出。
This commit is contained in:
AskaEth committed 2026-11-11 21:23:52 +08:00
1 parent a41b61198e
commit 80c0bd4eb4
5 files changed
+855 -22

No files matched your search

+52 -2
View File
@@ -9,6 +9,9 @@
# r2 P2-1 —— licenses/README 被删后 --with-sources 必须恢复,
# --verify-only 必须拒绝缺许可证的交付
# MANIFEST 内容漂移检测、src/ 缺文件与内容漂移自愈
# M53 —— 本地源码路径:timeout 产物存在/静态/MIPS32r2/零 NEEDED、
# MANIFEST 有 Apache-2.0 行、license 原文随附、
# 源码纪律(独立实现、不调 clock_gettime、不用 timerfd、setpgid+kill(-pgid))
#
# 前置:MEOW_DEVKIT_WORK 里已有一次构建(脚本会先跑一次增量构建,命中则秒回;
# 缓存缺失时会联网下载/编译,第一次运行约 1 分钟)。
@@ -60,7 +63,7 @@ rm -rf "$OUT/src"
run "普通重跑(应命中 stamp 且不建 src/)" bash "$SCRIPT" || fail "T1a 普通重跑"
[ ! -d "$OUT/src" ] && pass "T1a 普通重跑不创建 src/(符合预期)" || fail "T1a 普通重跑意外创建了 src/"
run "--with-sources(stamp 命中路径)" bash "$SCRIPT" --with-sources || fail "T1b --with-sources 退出码非 0"
for f in ncurses-6.5.tar.gz procps-ng-4.0.4.tar.xz lsof-4.99.4.tar.xz build_devkit.sh DEVKIT.md; do
for f in ncurses-6.5.tar.gz procps-ng-4.0.4.tar.xz lsof-4.99.4.tar.xz build_devkit.sh DEVKIT.md timeout.c; do
[ -s "$OUT/src/$f" ] && pass "T1b src/$f 存在且非空" || fail "T1b 缺 src/$f(P1 未修好)"
done
@@ -94,7 +97,7 @@ cp "$TMP/MANIFEST.txt.bak" "$OUT/MANIFEST.txt"
run "--verify-only(恢复后)" bash "$SCRIPT" --verify-only && pass "T5b 恢复后重新通过" || fail "T5b 恢复后仍失败"
echo "== 6) 交付完整性:删 licenses/ 后 --with-sources 必须恢复"
LICENSES="procps-ng-4.0.4-COPYING procps-ng-4.0.4-COPYING.LIB ncurses-6.5-COPYING lsof-4.99.4-COPYING musl-COPYRIGHT"
LICENSES="procps-ng-4.0.4-COPYING procps-ng-4.0.4-COPYING.LIB ncurses-6.5-COPYING lsof-4.99.4-COPYING musl-COPYRIGHT meowos-timeout-LICENSE"
rm -rf "$OUT/licenses"
run "--with-sources 补 licenses" bash "$SCRIPT" --with-sources || fail "T6 --with-sources 退出码非 0"
for f in $LICENSES; do
@@ -116,6 +119,53 @@ rm -f "$OUT/README.txt"
run "--with-sources 补 README" bash "$SCRIPT" --with-sources || fail "T8 --with-sources 退出码非 0"
[ -s "$OUT/README.txt" ] && pass "T8 README.txt 已恢复" || fail "T8 README.txt 未恢复"
echo "== 9) timeout(本地源码路径):产物存在 + 静态/架构/零 NEEDED"
if [ -f "$OUT/bin/timeout" ]; then
pass "T9a bin/timeout 存在"
DESC="$(file -b "$OUT/bin/timeout")"
case "$DESC" in *"statically linked"*) pass "T9b 静态链接" ;; *) fail "T9b 非静态: $DESC" ;; esac
case "$DESC" in *"MIPS32 rel2"*) pass "T9c MIPS32 rel2" ;; *) fail "T9c 架构不符: $DESC" ;; esac
[ "$(readelf -d "$OUT/bin/timeout" 2>/dev/null | grep -c NEEDED)" = 0 ] \
&& pass "T9d readelf -d 零 NEEDED" || fail "T9d 有 NEEDED 依赖"
readelf -h "$OUT/bin/timeout" | grep -q mips32r2 \
&& pass "T9e ELF Flags 含 mips32r2" || fail "T9e ELF Flags 无 mips32r2"
else
fail "T9a 缺 $OUT/bin/timeout(本地源码没编进来)"
fi
ROW="$(awk '$1=="timeout" && $(NF-1) ~ /^[0-9]+$/ && length($NF)==64' "$OUT/MANIFEST.txt")"
if [ -n "$ROW" ]; then
pass "T9f MANIFEST 有 timeout 表格行(sha256 + 体积)"
case "$ROW" in *Apache-2.0*) pass "T9g MANIFEST 标注 Apache-2.0" ;; *) fail "T9g MANIFEST 未标 Apache-2.0: $ROW" ;; esac
else
fail "T9f MANIFEST 缺 timeout 表格行"
fi
head -2 "$OUT/licenses/meowos-timeout-LICENSE" 2>/dev/null | grep -q 'Apache License' \
&& pass "T9h licenses/meowos-timeout-LICENSE 是 Apache-2.0 原文" \
|| fail "T9h 缺 Apache-2.0 许可证原文"
echo "== 10) timeout 源码纪律:本仓独立实现,且不碰坏掉的 clock_gettime"
SRC_T="$ROOT/scripts/build/devkit-src/timeout.c"
grep -q 'SPDX-License-Identifier: Apache-2.0' "$SRC_T" \
&& pass "T10a 源码带 Apache-2.0 SPDX 头" || fail "T10a 源码缺 Apache-2.0 SPDX 头"
grep -q '独立实现,未拷贝' "$SRC_T" \
&& pass "T10b 源码声明独立实现、未拷贝 GPL 代码" || fail "T10b 缺独立实现声明"
if grep -nE 'clock_gettime|gettimeofday' "$SRC_T" | grep -qvE '^[0-9]+:[[:space:]]*(/\*|\*|//)'; then
fail "T10c 源码调用了 clock_gettime/gettimeofday(4.4 内核上 time64 ABI 错配)"
else
pass "T10c 源码不调用 clock_gettime/gettimeofday(只在注释里提及)"
fi
if grep -nE 'timerfd_settime|timerfd_create' "$SRC_T" | grep -qvE '^[0-9]+:[[:space:]]*(/\*|\*|//)'; then
fail "T10e 源码用了 timerfd(musl 的 timerfd_settime 在 4.4 上静默失效,必须用 setitimer)"
else
pass "T10e 源码不用 timerfd(超时走 setitimer+SIGALRM)"
fi
grep -q 'setitimer(ITIMER_REAL' "$SRC_T" && grep -q 'SIGALRM' "$SRC_T" \
&& pass "T10f 超时机制是 setitimer(ITIMER_REAL)+SIGALRM" \
|| fail "T10f 缺 setitimer/SIGALRM 超时机制"
grep -q 'setpgid' "$SRC_T" && grep -q 'kill(-' "$SRC_T" \
&& pass "T10d 进程组模型到位(setpgid + kill(-pgid))" \
|| fail "T10d 缺进程组模型(孙进程会漏杀)"
echo
printf '== 结果: %d 通过, %d 失败\n' "$PASS" "$FAILN"
[ "$FAILN" = 0 ]