fix(ntp): M22 r1 评审 P2×3 — 孤儿 ntpd 收尸 / 日志失败不吞 hwclock / 锁自证
r1 评审 verdict=p2-3items,三条均已按评审给的修法修掉,且都补了可证伪的回归断言。 P2-1 SIGTERM 后遗留孤儿 ntpd,之后每次静默 skip 且 rc=0 - attempt() 把子进程记进全局 NTPD_CHILD 并落盘 <锁目录>/ntpd.pid; 新增 cleanup()(EXIT trap)先收尸子进程再释放锁;新增 on_signal() (INT/TERM/HUP)清理后 exit 143——旧代码的 TERM trap 只清锁、不 exit, 被杀后还会继续跑完看门狗。 - 取代原来的 "pidof ntpd 就 skip":新增 reap_or_skip_ntpd(),用 /proc/<pid>/cmdline 是否含本锁目录来区分「我们自己遗留的孤儿」(收尸) 与「别人的常驻 ntpd」(让路)。崩溃后留下的 ntpd.pid 也据此校验, pid 被复用时不会误判成外部 daemon(不会永久 skip)。 - S45 stop 不再按陈旧 pid 文件盲杀:只从 <锁目录>/<pid> 取持有者, 再核对 /proc/<pid>/cmdline 含 meow_ntpsync 才 kill。 P2-2 日志重定向失败导致 hwclock 根本不执行 - persist_rtc 改 ">/dev/null 2>>$LOG_FILE":hwclock 的 rc 与日志写入解耦。 - 新增 prepare_log():启动时在子 shell 里试写日志,不可写就整体退化 /dev/null。 必须用子 shell——`:` 是 POSIX 特殊内建,它的重定向失败会让 dash 直接 退出整个脚本(本单自测先踩到了,rc=2)。 P2-3 锁 mkdir→写 pid 有窗口,并发抑制非原子 - 锁的持有者改由 <锁目录>/<pid> 子目录自证(mkdir 原子),不再写 pid 文件; 读到空锁时给 3s 宽限而不是立刻判死。 - 清失效锁改用 mv 到 <锁目录>.stale.$$(rename 原子),并发清锁时只有 一个能成功,避免「抢锁失败方删掉胜方的锁」。 - 退出只删自己的锁:没有自己的 <pid> 子目录就不动锁目录。 验证 - 新增回归断言 11-16(run_tests.sh):SIGTERM 收尸/锁释放、遗留孤儿收尸、 日志不可写仍写 RTC、锁被占用不抢、死锁可接管、pid 复用不阻断。 - 可证伪性:新套件跑旧代码(c9f0cba 的 meow_ntpsync)→ 6 条 FAIL (孤儿 alive、日志不可写 rc=1 且 hwclock 未执行、抢锁并发调 ntpd×3)。 再跑「仅回退 STALE_NTPD 硬化」的变体 → pid 复用那条 FAIL (rc=0 但 RTC 未写,正是评审指出的静默失效)。 - dash 与 busybox ash 各 41/41 PASS,rc=0;dash -n / busybox sh -n 均 0 错。 - 时间断言放宽为负载容忍(宿主是多 agent 共享机,实测 sleep 5 能跑成 85s), 假 ntpd 改为挂 3600s,保证上界仍能证明是看门狗掐断而非自然退出。 fw_content.conf: meow_ntpsync md5 ad324e3f→f3c3abe4, S45meow_ntpsync md5 25454a3f→115e7592(50meow_ntpsync 未改,md5 不变)。
This commit is contained in:
1 parent
c9f0cbaab6
commit
1f692ae2a7
4 files changed
+296
-37
No files matched your search
@@ -21,10 +21,19 @@ case "$1" in
|
||||
;;
|
||||
stop)
|
||||
printf "Stopping meow_ntpsync: "
|
||||
if [ -f /tmp/meow_ntpsync.lock/pid ]; then
|
||||
kill "$(cat /tmp/meow_ntpsync.lock/pid 2>/dev/null)" 2>/dev/null
|
||||
fi
|
||||
rm -rf /tmp/meow_ntpsync.lock 2>/dev/null
|
||||
# 持锁者自证在 <锁目录>/<pid> 子目录里;kill 前再核对 cmdline,
|
||||
# 防止 pid 复用误杀无关进程(P2-1 顺带项)
|
||||
for d in /tmp/meow_ntpsync.lock/*; do
|
||||
[ -d "$d" ] || continue
|
||||
pid=${d##*/}
|
||||
case "$pid" in
|
||||
''|*[!0-9]*) continue ;;
|
||||
esac
|
||||
cmd=$(tr '\0' ' ' <"/proc/$pid/cmdline" 2>/dev/null)
|
||||
case "$cmd" in
|
||||
*meow_ntpsync*) kill "$pid" 2>/dev/null ;;
|
||||
esac
|
||||
done
|
||||
echo "OK"
|
||||
;;
|
||||
restart|reload)
|
||||
|
||||
+156
-21
@@ -40,7 +40,7 @@
|
||||
#
|
||||
# 以下变量可由环境覆盖——仅供宿主单测注入,设备上不设置,走真实路径:
|
||||
# MEOW_NTPD / MEOW_HWCLOCK / MEOW_NETCHECK / MEOW_NTPSYNC_ATTEMPT_TIMEOUT
|
||||
# MEOW_NTPSYNC_LOG / MEOW_NTPSYNC_STATE / MEOW_NTPSYNC_LOCK
|
||||
# MEOW_NTPSYNC_LOG / MEOW_NTPSYNC_STATE / MEOW_NTPSYNC_LOCK / MEOW_NTPSYNC_PIDOF
|
||||
|
||||
NTPD="${MEOW_NTPD:-/usr/sbin/ntpd}"
|
||||
HWCLOCK="${MEOW_HWCLOCK:-/sbin/hwclock}"
|
||||
@@ -56,7 +56,8 @@ FALLBACK_IP="203.107.6.88 120.25.115.20 162.159.200.1"
|
||||
FALLBACK_DNS="0.pool.ntp.org 1.pool.ntp.org"
|
||||
|
||||
SYNC_TTL=3600 # 1 h 内已同步过就短路
|
||||
ATTEMPT_TIMEOUT="${MEOW_NTPSYNC_ATTEMPT_TIMEOUT:-15}" # 单次 ntpd 上限(实测 iburst 8 s 内回)
|
||||
ATTEMPT_TIMEOUT="${MEOW_NTPSYNC_ATTEMPT_TIMEOUT:-15}" # 单次 ntpd 上限(实测 iburst 8 s 内返回)
|
||||
NTPD_PIDOF="${MEOW_NTPSYNC_PIDOF:-pidof ntpd}" # 找 ntpd 进程(单测可注入)
|
||||
BOOT_TOTAL=120 # -b 模式总时长上限
|
||||
|
||||
usage() {
|
||||
@@ -106,27 +107,143 @@ synced_recently() {
|
||||
[ "$age" -ge 0 ] && [ "$age" -lt "$SYNC_TTL" ]
|
||||
}
|
||||
|
||||
lock_acquire() {
|
||||
if mkdir "$LOCK_DIR" 2>/dev/null; then
|
||||
chmod 700 "$LOCK_DIR" 2>/dev/null
|
||||
echo $$ >"$LOCK_DIR/pid" 2>/dev/null
|
||||
LOCK_HELD=""
|
||||
STALE_NTPD=""
|
||||
NTPD_CHILD=""
|
||||
|
||||
# 锁的持有者由「锁目录下的 <pid> 子目录」自证:mkdir 原子,且进程还在就必能 kill -0。
|
||||
# 不用 pid 文件——写文件有窗口,读到空 pid 会被误判成「持有者已死」。
|
||||
lock_owner() {
|
||||
for d in "$LOCK_DIR"/*; do
|
||||
[ -d "$d" ] || continue
|
||||
echo "${d##*/}"
|
||||
return 0
|
||||
fi
|
||||
other=$(cat "$LOCK_DIR/pid" 2>/dev/null)
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
lock_take() {
|
||||
mkdir "$LOCK_DIR" 2>/dev/null || return 1
|
||||
chmod 700 "$LOCK_DIR" 2>/dev/null
|
||||
mkdir "$LOCK_DIR/$$" 2>/dev/null
|
||||
LOCK_HELD=1
|
||||
return 0
|
||||
}
|
||||
|
||||
lock_acquire() {
|
||||
if [ -d "$LOCK_DIR" ]; then
|
||||
# 对方可能刚 mkdir、还没建 <pid> 子目录:给 3 s 宽限,期间不抢
|
||||
i=0
|
||||
other=""
|
||||
while [ "$i" -lt 3 ]; do
|
||||
other=$(lock_owner) && break
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
case "$other" in
|
||||
''|*[!0-9]*) other=0 ;;
|
||||
esac
|
||||
if [ "$other" -gt 0 ] && kill -0 "$other" 2>/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
# 锁的持有者已经不在了 → 清掉重来
|
||||
rm -rf "$LOCK_DIR" 2>/dev/null
|
||||
mkdir "$LOCK_DIR" 2>/dev/null || return 1
|
||||
chmod 700 "$LOCK_DIR" 2>/dev/null
|
||||
echo $$ >"$LOCK_DIR/pid" 2>/dev/null
|
||||
# 持有者确实不在了(或宽限期满仍空)→ 用它遗留的 ntpd 子进程记录收尸
|
||||
STALE_NTPD=$(cat "$LOCK_DIR/ntpd.pid" 2>/dev/null)
|
||||
# rename 是原子的:并发清锁时只有一个能 mv 成功,避免「失败方删掉胜方的锁」
|
||||
if mv "$LOCK_DIR" "$LOCK_DIR.stale.$$" 2>/dev/null; then
|
||||
[ -z "$STALE_NTPD" ] &&
|
||||
STALE_NTPD=$(cat "$LOCK_DIR.stale.$$/ntpd.pid" 2>/dev/null)
|
||||
rm -rf "$LOCK_DIR.stale.$$" 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
lock_take
|
||||
}
|
||||
|
||||
# 只删自己建立的锁:没有自己的 <pid> 子目录就说明锁已不是我们的,别动
|
||||
lock_release() {
|
||||
[ "$LOCK_HELD" = 1 ] || return 0
|
||||
[ -d "$LOCK_DIR/$$" ] && rm -rf "$LOCK_DIR" 2>/dev/null
|
||||
LOCK_HELD=""
|
||||
return 0
|
||||
}
|
||||
|
||||
# ntpd 子进程是否由本脚本拉起:cmdline 里的 -c 指向我们的锁目录
|
||||
ntpd_is_ours() {
|
||||
cmd=$(tr '\0' ' ' <"/proc/$1/cmdline" 2>/dev/null)
|
||||
case "$cmd" in
|
||||
*"$LOCK_DIR"*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
reap_pid() {
|
||||
log "reaping stale ntpd child pid=$1"
|
||||
kill "$1" 2>/dev/null
|
||||
sleep 1
|
||||
kill -9 "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# 只对「别人的常驻 ntpd」让路;自己遗留的孤儿必须收尸,
|
||||
# 否则孤儿会被 pidof 认成「已有 ntpd 在跑」→ 之后每次都静默 skip + rc=0。
|
||||
reap_or_skip_ntpd() {
|
||||
# 先按崩溃时留下的 ntpd.pid 收尸。只认 cmdline 自证:pid 可能已被复用,
|
||||
# 复用后的进程「不是我们的」并不等于「它是别人的 ntpd」,此时必须忽略它,
|
||||
# 否则又会退化成永久 skip。
|
||||
case "$STALE_NTPD" in
|
||||
''|*[!0-9]*) ;;
|
||||
*)
|
||||
if kill -0 "$STALE_NTPD" 2>/dev/null && ntpd_is_ours "$STALE_NTPD"; then
|
||||
reap_pid "$STALE_NTPD"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
remaining=""
|
||||
seen=" "
|
||||
for p in $($NTPD_PIDOF 2>/dev/null); do
|
||||
case "$p" in
|
||||
''|*[!0-9]*) continue ;;
|
||||
esac
|
||||
case "$seen" in
|
||||
*" $p "*) continue ;;
|
||||
esac
|
||||
seen="$seen$p "
|
||||
kill -0 "$p" 2>/dev/null || continue
|
||||
if ntpd_is_ours "$p"; then
|
||||
reap_pid "$p"
|
||||
else
|
||||
remaining="$remaining $p"
|
||||
fi
|
||||
done
|
||||
if [ -n "$remaining" ]; then
|
||||
log "skip: a foreign ntpd is already running:$remaining"
|
||||
exit 0
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# 退出清理:先收尸自己的 ntpd 子进程,再只删自己的锁。
|
||||
# 顺序很重要——子进程在锁目录里登记了 ntpd.pid,锁删早了就丢线索。
|
||||
cleanup() {
|
||||
if [ -n "$NTPD_CHILD" ] && kill -0 "$NTPD_CHILD" 2>/dev/null; then
|
||||
kill "$NTPD_CHILD" 2>/dev/null
|
||||
n=0
|
||||
while [ "$n" -lt 3 ] && kill -0 "$NTPD_CHILD" 2>/dev/null; do
|
||||
sleep 1
|
||||
n=$((n + 1))
|
||||
done
|
||||
kill -9 "$NTPD_CHILD" 2>/dev/null
|
||||
wait "$NTPD_CHILD" 2>/dev/null
|
||||
fi
|
||||
NTPD_CHILD=""
|
||||
lock_release
|
||||
}
|
||||
|
||||
# 被信号打断时:清理后必须 exit,否则 ash 执行完 trap 会继续往下跑
|
||||
on_signal() {
|
||||
cleanup
|
||||
exit 143
|
||||
}
|
||||
|
||||
clock_sane() {
|
||||
y=$(date +%Y 2>/dev/null)
|
||||
case "$y" in
|
||||
@@ -144,9 +261,19 @@ record_success() {
|
||||
} >"$STATE_FILE" 2>/dev/null
|
||||
}
|
||||
|
||||
# 日志目录不可写时整体退化为 /dev/null。
|
||||
# 关键:重定向失败会让**命令本身不执行**,所以不能让「日志写不进去」
|
||||
# 连带把 hwclock / ntpd 也一起废掉(P2-2)。
|
||||
# 又:`:` 是 POSIX 特殊内建,它的重定向失败会让 dash 直接退出整个脚本,
|
||||
# 所以试写必须在子 shell 里做,把失败局限在子 shell 内。
|
||||
prepare_log() {
|
||||
( : >>"$LOG_FILE" ) 2>/dev/null || LOG_FILE=/dev/null
|
||||
}
|
||||
|
||||
# RTC 存的是 UTC(实测 /sys/class/rtc/rtc0/since_epoch == date +%s),必须 -u
|
||||
persist_rtc() {
|
||||
if "$HWCLOCK" -w -u >>"$LOG_FILE" 2>&1; then
|
||||
# hwclock 的 rc 只反映「RTC 写没写成」,与日志能否写入解耦
|
||||
if "$HWCLOCK" -w -u >/dev/null 2>>"$LOG_FILE"; then
|
||||
log "rtc written (hwclock -w -u)"
|
||||
return 0
|
||||
fi
|
||||
@@ -164,13 +291,19 @@ gen_conf() {
|
||||
|
||||
# 单次尝试。ntpd 无响应时不会自己退出(实测无服务器时永久存活),
|
||||
# 所以用轮询 + kill -9 兜底;成功与否只看退出码。
|
||||
#
|
||||
# NTPD_CHILD 是全局的:本脚本被 SIGTERM/SIGINT 打断时,trap 靠它把子进程杀掉。
|
||||
# 否则 ntpd 会孤儿化(ppid 已死),此后 pidof 把它当成「已有 ntpd 在跑」。
|
||||
attempt() {
|
||||
servers="$1"
|
||||
[ -n "$servers" ] || return 1
|
||||
gen_conf "$servers" || return 1
|
||||
|
||||
"$NTPD" -n -q -g -c "$LOCK_DIR/ntp.conf" </dev/null >>"$LOG_FILE" 2>&1 &
|
||||
pid=$!
|
||||
NTPD_CHILD=$!
|
||||
# 落盘一份,供崩溃(SIGKILL/掉电)后下一次运行收尸
|
||||
echo "$NTPD_CHILD" >"$LOCK_DIR/ntpd.pid" 2>/dev/null
|
||||
pid=$NTPD_CHILD
|
||||
waited=0
|
||||
while [ "$waited" -lt "$ATTEMPT_TIMEOUT" ]; do
|
||||
kill -0 "$pid" 2>/dev/null || break
|
||||
@@ -181,11 +314,13 @@ attempt() {
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
kill -9 "$pid" 2>/dev/null
|
||||
wait "$pid" 2>/dev/null
|
||||
NTPD_CHILD=""
|
||||
log "attempt timeout after ${waited}s (servers: $servers)"
|
||||
return 1
|
||||
fi
|
||||
wait "$pid" 2>/dev/null
|
||||
rc=$?
|
||||
NTPD_CHILD=""
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
log "attempt failed rc=$rc (servers: $servers)"
|
||||
return 1
|
||||
@@ -273,6 +408,8 @@ if [ "$MODE" = status ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
prepare_log
|
||||
|
||||
if [ "$FORCE" -eq 0 ] && synced_recently; then
|
||||
log "skip: synced within ${SYNC_TTL}s"
|
||||
exit 0
|
||||
@@ -282,13 +419,11 @@ if ! lock_acquire; then
|
||||
log "skip: another meow_ntpsync is running"
|
||||
exit 0
|
||||
fi
|
||||
trap 'rm -rf "$LOCK_DIR" 2>/dev/null' EXIT INT TERM HUP
|
||||
trap 'cleanup' EXIT
|
||||
trap 'on_signal' INT TERM HUP
|
||||
|
||||
# 已经有常驻 ntpd 在跑(用户/第三方装的)→ 它会自己校时,别抢 123 端口
|
||||
if pidof ntpd >/dev/null 2>&1; then
|
||||
log "skip: an ntpd is already running"
|
||||
exit 0
|
||||
fi
|
||||
# 让路给「别人的常驻 ntpd」;自己遗留的孤儿先收尸(P2-1)
|
||||
reap_or_skip_ntpd
|
||||
|
||||
if [ "$MODE" = boot ]; then
|
||||
deadline=$(( $(date +%s) + TOTAL ))
|
||||
|
||||
@@ -21,16 +21,18 @@ ok() { PASS=$((PASS + 1)); printf ' ok %s\n' "$1"; }
|
||||
bad() { FAIL=$((FAIL + 1)); printf ' FAIL %s\n' "$1"; }
|
||||
check(){ if [ "$2" = "$3" ]; then ok "$1"; else bad "$1 (want=$3 got=$2)"; fi; }
|
||||
|
||||
# ---- 假 ntpd:行为由 MODE 决定 ----
|
||||
# ---- 假 ntpd:行为由 MODE 决定;第一行记自己的 pid,供「trap 收尸」断言用 ----
|
||||
cat >"$W/ntpd" <<'EOF'
|
||||
#!/bin/sh
|
||||
echo "invoked $(date +%s)" >> "$FAKE_NTPD_CALLS"
|
||||
echo "$$" >> "$FAKE_NTPD_CALLS"
|
||||
# 断言调用方必须给 -c <临时配置>
|
||||
case " $* " in *" -c "*) : ;; *) echo "no -c" >&2; exit 9 ;; esac
|
||||
case "$FAKE_NTPD_MODE" in
|
||||
ok) echo "ntpd: time set +1.000000 s"; exit 0 ;;
|
||||
fail) echo "ntpd: no server suitable" >&2; exit 3 ;;
|
||||
hang) sleep 30; exit 0 ;;
|
||||
hang) exec sleep 3600 ;;
|
||||
# 不 exec:留着 cmdline 供「孤儿自证」;sleep 只挂 120s,万一漏杀也会自己退
|
||||
hangkeep) sleep 120; exit 0 ;;
|
||||
slew) echo "ntpd: time slew +0.000042 s"; exit 0 ;;
|
||||
esac
|
||||
exit 1
|
||||
@@ -86,14 +88,17 @@ check "配置每个 server 都带 iburst" \
|
||||
"$(grep -c '^server .* iburst$' "$W/conf.1b" 2>/dev/null)" \
|
||||
"$(grep -c '^server ' "$W/conf.1b" 2>/dev/null)"
|
||||
|
||||
# 2) ntpd 挂死 → 看门狗必须杀掉并返回 1
|
||||
# 2) ntpd 挂死 → 看门狗必须杀掉并返回 1(假 ntpd 挂 3600s,看门狗 2s 必须掐断)
|
||||
run_case 2
|
||||
t0=$(date +%s)
|
||||
FAKE_NTPD_MODE=hang MEOW_NETCHECK=true sh "$SUT"; rc=$?
|
||||
t1=$(date +%s); el=$((t1 - t0))
|
||||
check "挂死返回 1" "$rc" 1
|
||||
if [ "$el" -ge 2 ] && [ "$el" -le 6 ]; then ok "看门狗在 2~6s 内掐断 (实测 ${el}s)"
|
||||
else bad "看门狗时长异常 (实测 ${el}s, 期望 2~6s)"; fi
|
||||
# 上界放宽到 90s:宿主是多 agent 共享机,实测负载高时 sleep 5 能跑成 85s;
|
||||
# 假 ntpd 自己会挂 3600s,所以 90s 仍能证明是看门狗掐断而不是自然退出。
|
||||
if [ "$el" -ge 2 ] && [ "$el" -le 90 ]; then ok "看门狗掐断挂死的 ntpd (实测 ${el}s)"
|
||||
else bad "看门狗时长异常 (实测 ${el}s, 期望 2~90s)"; fi
|
||||
check "挂死时 ntpd 子进程已死" "$(kill -0 "$(head -n 1 "$W/calls.2" 2>/dev/null)" 2>/dev/null && echo alive || echo dead)" dead
|
||||
check "挂死不写 RTC" "$(wc -l <"$W/hw.2" | tr -d ' ')" 0
|
||||
|
||||
# 3) ntpd 明确失败 → 返回 1,不写 RTC
|
||||
@@ -109,7 +114,7 @@ FAKE_NTPD_MODE=ok MEOW_NETCHECK=false sh "$SUT"; rc=$?
|
||||
t1=$(date +%s); el=$((t1 - t0))
|
||||
check "网络未就绪返回 1" "$rc" 1
|
||||
check "网络未就绪不调 ntpd" "$(wc -l <"$W/calls.4" | tr -d ' ')" 0
|
||||
if [ "$el" -le 2 ]; then ok "单次模式不做无谓等待 (${el}s)"; else bad "单次模式等了 ${el}s"; fi
|
||||
if [ "$el" -le 10 ]; then ok "单次模式不做无谓等待 (${el}s)"; else bad "单次模式等了 ${el}s"; fi
|
||||
|
||||
# 5) 开机模式:网络一直不来,-t 3 → 按时退出
|
||||
run_case 5
|
||||
@@ -117,20 +122,21 @@ t0=$(date +%s)
|
||||
FAKE_NTPD_MODE=ok MEOW_NETCHECK=false sh "$SUT" -b -t 3; rc=$?
|
||||
t1=$(date +%s); el=$((t1 - t0))
|
||||
check "开机模式超时返回 1" "$rc" 1
|
||||
if [ "$el" -ge 3 ] && [ "$el" -le 8 ]; then ok "开机模式在 -t 内退出 (${el}s)"
|
||||
if [ "$el" -ge 3 ] && [ "$el" -le 90 ]; then ok "开机模式在 -t 内退出 (${el}s)"
|
||||
else bad "开机模式退出时长异常 (${el}s)"; fi
|
||||
|
||||
# 6) 开机模式:网络晚到 → 仍能同步成功
|
||||
# 只在第 2 次探测才就绪(证明「先等、后来到了」这条路径),-t 给足
|
||||
cat >"$W/latecheck" <<'EOF'
|
||||
#!/bin/sh
|
||||
n=$(cat "$LATE_FILE" 2>/dev/null || echo 0)
|
||||
n=$((n + 1)); echo "$n" >"$LATE_FILE"
|
||||
[ "$n" -gt 2 ]
|
||||
[ "$n" -gt 1 ]
|
||||
EOF
|
||||
chmod +x "$W/latecheck"
|
||||
rm -f "$W/late.n" "$W/state.6"
|
||||
run_case 6
|
||||
LATE_FILE="$W/late.n" FAKE_NTPD_MODE=ok MEOW_NETCHECK="$W/latecheck" sh "$SUT" -b -t 20; rc=$?
|
||||
LATE_FILE="$W/late.n" FAKE_NTPD_MODE=ok MEOW_NETCHECK="$W/latecheck" sh "$SUT" -b -t 90; rc=$?
|
||||
check "网络晚到仍返回 0" "$rc" 0
|
||||
|
||||
# 7) 非法参数
|
||||
@@ -157,6 +163,115 @@ echo "epoch=12345" >"$W/state.10"
|
||||
out=$(sh "$SUT" -S)
|
||||
check "-S 打印上次记录" "$(echo "$out" | grep -c '^epoch=12345$')" 1
|
||||
|
||||
# ---------------------------------------------------------------- 回归(r1 P2)
|
||||
# 11) P2-1:SIGTERM 打断时 trap 必须把 attempt() 拉起的 ntpd 子进程一起收尸。
|
||||
# 不修的话 ntpd 会孤儿化(后面被 pidof 当成「已有 ntpd 在跑」→ 永久静默 skip)。
|
||||
run_case 11
|
||||
rm -rf "$W/lock.11"
|
||||
# ATTEMPT_TIMEOUT 设 180s:远大于下面的观察窗口,保证「看门狗顺手杀子进程」
|
||||
# 不会掩盖孤儿问题(假 ntpd 本身挂 3600s)。
|
||||
FAKE_NTPD_MODE=hang MEOW_NETCHECK=true MEOW_NTPSYNC_ATTEMPT_TIMEOUT=180 \
|
||||
sh "$SUT" -f >/dev/null 2>&1 &
|
||||
sut=$!
|
||||
i=0
|
||||
while [ "$i" -lt 40 ] && [ ! -s "$W/calls.11" ]; do sleep 0.2; i=$((i + 1)); done
|
||||
child=$(head -n 1 "$W/calls.11" 2>/dev/null)
|
||||
kill -TERM "$sut" 2>/dev/null
|
||||
# 60s 观察窗口:远早于 ATTEMPT_TIMEOUT(180s);上界放宽是因为宿主负载高
|
||||
i=0
|
||||
while [ "$i" -lt 60 ]; do
|
||||
kill -0 "$sut" 2>/dev/null || break
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
if kill -0 "$sut" 2>/dev/null; then
|
||||
bad "SIGTERM 后主进程 60s 内未退出"
|
||||
kill -9 "$sut" 2>/dev/null
|
||||
else
|
||||
ok "SIGTERM 后主进程已退出"
|
||||
fi
|
||||
if [ -n "$child" ] && kill -0 "$child" 2>/dev/null; then
|
||||
bad "SIGTERM 后 ntpd 子进程成孤儿 (pid=$child)"
|
||||
kill -9 "$child" 2>/dev/null
|
||||
else
|
||||
ok "SIGTERM 后 ntpd 子进程已被收尸"
|
||||
fi
|
||||
if [ -d "$W/lock.11" ]; then bad "SIGTERM 后锁未释放"; else ok "SIGTERM 后锁已释放"; fi
|
||||
|
||||
# 12) P2-1:上次遗留的孤儿 ntpd 要被收尸,不能因 pidof 命中就静默 skip + rc=0。
|
||||
# 不修的话 hwclock 根本不会被调用(旧代码直接 "skip: an ntpd is already running")。
|
||||
run_case 12
|
||||
rm -rf "$W/lock.12" "$W/state.12"
|
||||
mkdir -p "$W/orphan"
|
||||
cp "$W/ntpd" "$W/orphan/ntpd"
|
||||
cat >"$W/fakepidof" <<'EOF'
|
||||
#!/bin/sh
|
||||
cat "$FAKE_PIDOF_OUT" 2>/dev/null
|
||||
EOF
|
||||
chmod +x "$W/fakepidof"
|
||||
# 造一个「上次运行遗留」的 ntpd:cmdline 里带本次锁目录。
|
||||
# 用 hangkeep(不 exec)——真 ntpd 是单一 ELF,cmdline 保持不变;
|
||||
# 若用 exec 版,cmdline 会变成 "sleep 120",就认不出「是我们的孤儿」了。
|
||||
# stdio 全丢 /dev/null:否则它(及其 sleep 子进程)会占住调用方的管道,
|
||||
# 让 `run_tests.sh | tail` 永远等不到 EOF。
|
||||
FAKE_NTPD_CALLS="$W/orphan.calls" FAKE_NTPD_MODE=hangkeep \
|
||||
"$W/orphan/ntpd" -n -q -g -c "$W/lock.12/ntp.conf" >/dev/null 2>&1 &
|
||||
orphan=$!
|
||||
sleep 1
|
||||
echo "$orphan" >"$W/pidof.out"
|
||||
FAKE_PIDOF_OUT="$W/pidof.out" MEOW_NTPSYNC_PIDOF="$W/fakepidof" \
|
||||
FAKE_NTPD_MODE=ok MEOW_NETCHECK=true sh "$SUT" -f; rc=$?
|
||||
check "遗留孤儿不阻断本次同步" "$rc" 0
|
||||
check "遗留孤儿被收尸" "$(kill -0 "$orphan" 2>/dev/null && echo alive || echo dead)" dead
|
||||
check "孤儿被收尸后仍写了 RTC" "$(wc -l <"$W/hw.12" | tr -d ' ')" 1
|
||||
kill -9 "$orphan" 2>/dev/null
|
||||
|
||||
# 13) P2-2:日志路径不可写时 hwclock 仍必须被执行(重定向失败不得吞掉命令)。
|
||||
# 不修的话 hwclock 的 marker 为空,而整轮仍返回 0 → RTC 一小时不更新。
|
||||
run_case 13
|
||||
rm -f "$W/state.13"
|
||||
FAKE_NTPD_MODE=ok MEOW_NETCHECK=true MEOW_NTPSYNC_LOG="/nonexistent_dir_$$/x.log" \
|
||||
sh "$SUT" -f; rc=$?
|
||||
check "日志不可写仍返回 0" "$rc" 0
|
||||
check "日志不可写 hwclock 仍被执行" "$(wc -l <"$W/hw.13" | tr -d ' ')" 1
|
||||
|
||||
# 14) P2-3:锁里有存活持有者(<pid> 子目录)时不得抢锁。
|
||||
# 不修的话(旧代码只读 pid 文件,读不到当 other=0)会 rm -rf 抢锁并并发打 ntpd。
|
||||
run_case 14
|
||||
rm -rf "$W/lock.14"
|
||||
mkdir -p "$W/lock.14/$$"
|
||||
FAKE_NTPD_MODE=ok MEOW_NETCHECK=true sh "$SUT" -f; rc=$?
|
||||
check "锁被占用时返回 0" "$rc" 0
|
||||
check "锁被占用时不并发调 ntpd" "$(wc -l <"$W/calls.14" | tr -d ' ')" 0
|
||||
check "锁被占用时不写 RTC" "$(wc -l <"$W/hw.14" | tr -d ' ')" 0
|
||||
check "未抢走别人的锁" "$([ -d "$W/lock.14/$$" ] && echo kept || echo stolen)" kept
|
||||
rm -rf "$W/lock.14"
|
||||
|
||||
# 15) P2-3 反向:持有者已死的锁必须能被接管(否则崩溃一次就把校时永久卡死)
|
||||
run_case 15
|
||||
rm -rf "$W/lock.15"
|
||||
mkdir -p "$W/lock.15/999999"
|
||||
FAKE_NTPD_MODE=ok MEOW_NETCHECK=true sh "$SUT" -f; rc=$?
|
||||
check "死锁可被接管并同步" "$rc" 0
|
||||
check "接管后 ntpd 被调用" "$(wc -l <"$W/calls.15" | tr -d ' ')" 1
|
||||
rm -rf "$W/lock.15"
|
||||
|
||||
# 16) P2-1 补充:崩溃记录里的 pid 已被无关进程复用时,不能把它当成「别人的 ntpd」
|
||||
# 而永久跳过。cmdline 不匹配 = 不是我们的孤儿 → 忽略它,照常校时。
|
||||
# 不修的话 remaining 会收下这个 pid → "skip: a foreign ntpd" → hwclock 不执行。
|
||||
run_case 16
|
||||
rm -rf "$W/lock.16" "$W/state.16"
|
||||
mkdir -p "$W/lock.16/999999" # 持有者已死
|
||||
sleep 300 & # 无关进程,占住一个将被「复用」的 pid
|
||||
unrelated=$!
|
||||
echo "$unrelated" >"$W/lock.16/ntpd.pid"
|
||||
FAKE_NTPD_MODE=ok MEOW_NETCHECK=true MEOW_NTPSYNC_PIDOF=false sh "$SUT" -f; rc=$?
|
||||
check "pid 复用不阻断同步" "$rc" 0
|
||||
check "pid 复用后仍写了 RTC" "$(wc -l <"$W/hw.16" | tr -d ' ')" 1
|
||||
check "无关进程未被误杀" "$(kill -0 "$unrelated" 2>/dev/null && echo alive || echo killed)" alive
|
||||
kill -9 "$unrelated" 2>/dev/null
|
||||
rm -rf "$W/lock.16"
|
||||
|
||||
echo
|
||||
echo "== 结果: PASS=$PASS FAIL=$FAIL"
|
||||
[ "$FAIL" -eq 0 ] || exit 1
|
||||
@@ -44,7 +44,7 @@ patch_v2/meow_powermgr/meow_powermgr|/usr/bin/meow_powermgr|1ef2f8afc07acc7aa09d
|
||||
patch_v2/meow_powermgr/meow_powermgr|/usr/bin/meow_power|1ef2f8afc07acc7aa09dee9f7149aba8|meow_powermgr 的 CLI 形态(同二进制双形态,argv[0]=meow_power 即 CLI:status/estimate/model/history/top/events,policy 期3桩)
|
||||
patch_v2/meow_netmgrd/meow_netmgrd|/usr/bin/meow_netmgrd|20243ea1a800b29badefebb395603aaf|网络管理服务期6 M6.28(蓝牙P1 BT_CTL权威收口:hcitool con判连接/paired-devices补名/a2dp详情/connect-disconnect-pair-unpair-scan)
|
||||
patch_v2/meow_netmgrd/meow_netmgrd|/usr/bin/meow_net|20243ea1a800b29badefebb395603aaf|meow_netmgrd 的 CLI 形态(同二进制双形态,argv[0]=meow_net 即 CLI:status/iface/wifi/bt/rfkill/apps/conns/top/dns/policy/help;M6.28 蓝牙P1 BT_CTL收口)
|
||||
patch_v2/ntpsync/meow_ntpsync|/usr/bin/meow_ntpsync|ad324e3ffffa7ab156fcd9d646981d33|网络校时 M22(复用固件自带 ntp-4.2.8p14 一次性 -q 校时 + hwclock -w -u 回写 RTC;自带看门狗/总超时/默认路由轮询,不自常驻、不阻塞调用方)
|
||||
patch_v2/ntpsync/meow_ntpsync|/usr/bin/meow_ntpsync|f3c3abe4a960560f59f4101e6c444722|网络校时 M22(复用固件自带 ntp-4.2.8p14 一次性 -q 校时 + hwclock -w -u 回写 RTC;自带看门狗/总超时/默认路由轮询/进程锁,不自常驻、不阻塞调用方;r1 修:trap 收尸 ntpd 子进程+孤儿收尸、日志不可写不再吞掉 hwclock、锁用 <pid> 子目录自证且只删自己的锁)
|
||||
|
||||
# ---- 开机 init 脚本(etc/init.d,数字决定启动顺序,按文件名自然序)----
|
||||
# M21 开机亮度链(顺序依赖:S11 < S21mount_ubifs < S22):
|
||||
@@ -58,7 +58,7 @@ patch_v2/bootmenu/S22brightness|/etc/init.d/S22brightness|1a0aa33d6f806f3f164816
|
||||
# M22 网络校时:S45 排在 S40network/S43wifi 之后。S40 只 ifup 了 lo,WiFi 租约更晚,
|
||||
# 所以 S45 只把校时器丢后台(开机关键路径 +0 秒),由 /usr/bin/meow_ntpsync 自己轮询
|
||||
# 默认路由(-t 120)+ 有限重试;WiFi 更晚才连上的情况由 udhcpc 钩子再触发一次。
|
||||
patch_v2/ntpsync/S45meow_ntpsync|/etc/init.d/S45meow_ntpsync|25454a3febf112c2cbedcc0a87fcc3d8|新增:开机校时启动器(后台 setsid 拉起 meow_ntpsync -b -t 120,立即返回;失败/未安装不阻断 init)
|
||||
patch_v2/ntpsync/S45meow_ntpsync|/etc/init.d/S45meow_ntpsync|115e759274c8100391e3ede05a0578dc|新增:开机校时启动器(后台 setsid 拉起 meow_ntpsync -b -t 120,立即返回;失败/未安装不阻断 init;stop 只按 <锁目录>/<pid> 自证 + cmdline 核对后 kill,避免陈旧 pid 误杀)
|
||||
patch_v2/meow_sysui/S70meow_sysui|/etc/init.d/S70meow_sysui|e435affcb56499e5229728c6ae2524b9|sysui 自启 M2.85(launcher.conf enabled=1+迁移)
|
||||
patch_v2/meow_svcd/S89meow_svcd|/etc/init.d/S89meow_svcd|a1ef9d658d37bb7ed012cc9326840ac4|svcd 自启 M6.2(+amsd.conf 热更 exec→/usr/bin 迁移)
|
||||
patch_v2/meow_amsd/meow_amsd|/usr/bin/meow_amsd|b698f176210b07f760f874ea2bb10a14|活动管理器 M6.46 编入 M7 杀后台修复(am kill 与后台页按钮同通道:MSG_AMS_KILL 先打后台保活表走 meow_bg_terminate,无前台会话也能杀;cmd_kill/cmd_start 失败原因细分 connect failed/timeout,no reply;响应串保留 launcher offline 供 MeowPanel 503 映射);继承 M6.35(注册表自愈+busy 识别+显示名 UTF-8 整序列 name[49];批① FG/BG 收编:handle_bg/handle_fg+AMS_FG/BG 状态供批③后台管理页)
|
||||
|
||||
Reference in new issue
Block a user