From c9f0cbaab64d5ea2c23c85245ea461787af82df4 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 6 Nov 2026 12:49:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(ntp):=20M22=20=E7=BD=91=E7=BB=9C=E6=A0=A1?= =?UTF-8?q?=E6=97=B6=20=E2=80=94=20=E5=A4=8D=E7=94=A8=E5=9B=BA=E4=BB=B6?= =?UTF-8?q?=E8=87=AA=E5=B8=A6=20ntpd=20+=20hwclock=20=E5=9B=9E=E5=86=99=20?= =?UTF-8?q?RTC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机实测(0.8.47,2026-11-06)定位到的根因不是「没有 NTP 客户端」: 固件自带 ntp-4.2.8p14(/usr/sbin/ntpd)和 /etc/ntp.conf(4 个 pool.ntp.org), 但没有任何 init 脚本/进程启动它 —— 有客户端、没启动。 实测证据: - RTC 存在且可读(/dev/rtc0,10003000.rtc),内核开机 hctosys=1, 冷启动不回到 1970 → 掉电保持;RTC 存 UTC(since_epoch == date +%s)。 - 但 RTC 会走慢且无人纠正:实测系统时间比 NTP 慢 49 s;把系统时间人为 拨错 4 h 后观察 65 s 无人纠正,RTC 也不跟随系统时间。 - UDP/123 出网正常(ntpd -q 一次成功 time set +48.877s);DNS/HTTP 正常。 - udhcpc 的 default.script 不处理 option 42,实测热点也不下发。 - ntpd -q 不会回写 RTC(步进后 sys-rtc 仍差 49 s)→ 必须 hwclock -w -u。 - ntpd 没有超时参数,无服务器时永久存活 → 必须自带看门狗。 - -p 形式的 server 首个 poll 要 ~64 s;配置里加 iburst 后 8 s 内返回。 实现(新增,不改任何基线文件): - /usr/bin/meow_ntpsync:一次性校时器。复用 ntpd -q -g + 运行时生成的 iburst 配置;内置 IP 兜底(阿里/腾讯/Cloudflare)→ DNS 名兜底; 单次 15 s 看门狗、默认路由轮询、1 h 短路、进程锁、失败静默。 成功严格校时后 hwclock -w -u 回写 RTC(RTC 存 UTC,必须 -u)。 - /etc/init.d/S45meow_ntpsync:开机只 setsid 后台拉起并立即返回 (关键路径 +0 秒;后台最长约 150 s 自行退出),失败不阻断 init。 - /usr/share/udhcpc/default.script.d/50meow_ntpsync:只在 bound/renew 触发(设备上唯一可靠的「网络就绪」事件),后台拉起后立即 exit 0; 有 $ntpsrv(option 42)就优先用。基线 default.script 本就有 HOOK_DIR 循环,目录原本为空。 - tests/run_tests.sh:宿主沙箱单测(假 ntpd/hwclock/网络探测)。 验证:dash 与 busybox sh 各 sh -n 通过;单测两套 shell 各 23/23 通过; 真机(/tmp 下运行,未装系统目录)首跑 8 s rc=0、ntpd slew 成功、 RTC 由 1793940395 写成 1793940452(49 s 偏差被纠正)、hwclock -u -r 吻合; 二次运行 0 s 短路;-b -t 3 无网络 5 s 退出 rc=1; DHCP 灌入不可达服务器 192.0.2.x 仍靠 IP 兜底在 ~10 s 内成功。 --- patch_v2/ntpsync/50meow_ntpsync | 33 +++ patch_v2/ntpsync/S45meow_ntpsync | 40 ++++ patch_v2/ntpsync/meow_ntpsync | 313 ++++++++++++++++++++++++++++ patch_v2/ntpsync/tests/run_tests.sh | 162 ++++++++++++++ scripts/build/README.md | 2 +- scripts/build/fw_content.conf | 11 + 6 files changed, 560 insertions(+), 1 deletion(-) create mode 100755 patch_v2/ntpsync/50meow_ntpsync create mode 100755 patch_v2/ntpsync/S45meow_ntpsync create mode 100755 patch_v2/ntpsync/meow_ntpsync create mode 100644 patch_v2/ntpsync/tests/run_tests.sh diff --git a/patch_v2/ntpsync/50meow_ntpsync b/patch_v2/ntpsync/50meow_ntpsync new file mode 100755 index 0000000..fbf8d93 --- /dev/null +++ b/patch_v2/ntpsync/50meow_ntpsync @@ -0,0 +1,33 @@ +#!/bin/sh +# +# 50meow_ntpsync — udhcpc 钩子(M22) +# +# /usr/share/udhcpc/default.script 结尾会执行 default.script.d/*, +# 参数是本轮 DHCP 事件(deconfig|bound|renew|leasefail|nak)。 +# 本钩子只在拿到租约(bound/renew)时动作——这是设备上唯一可靠的 +# 「网络真的就绪」事件(S40network 阶段 WiFi 还没起来)。 +# +# 关键:**绝不能阻塞**。default.script 是 udhcpc 同步调用的, +# 这里把校时丢后台后立即 exit 0,否则会拖住 DHCP 脚本。 +# +# $ntpsrv = DHCP option 42(NTP 服务器列表)。 +# 实测 2026-11-06 热点不下发(租约环境变量里没有 ntpsrv),企业网可能有; +# 有就优先用,没有就走 /usr/bin/meow_ntpsync 里的内置 IP 兜底。 +# +# 重复触发由 meow_ntpsync 自己的 1 h 短路 + 锁兜住:续租每 30 min 一次, +# 最多 1 h 真同步一次,开销可忽略。 +# +case "$1" in + bound|renew) ;; + *) exit 0 ;; +esac + +[ -x /usr/bin/meow_ntpsync ] || exit 0 + +if [ -n "$ntpsrv" ]; then + setsid /usr/bin/meow_ntpsync -s "$ntpsrv" >/dev/null 2>&1 & +else + setsid /usr/bin/meow_ntpsync >/dev/null 2>&1 & +fi + +exit 0 diff --git a/patch_v2/ntpsync/S45meow_ntpsync b/patch_v2/ntpsync/S45meow_ntpsync new file mode 100755 index 0000000..5f23f2e --- /dev/null +++ b/patch_v2/ntpsync/S45meow_ntpsync @@ -0,0 +1,40 @@ +#!/bin/sh +# +# S45meow_ntpsync — 开机网络校时(M22) +# +# 只负责把同步器丢到后台并**立即返回**,开机关键路径增加 0 秒。 +# 真正的「等网络就绪 + 有限重试 + 总超时」全在 /usr/bin/meow_ntpsync 里。 +# +# 为什么排 S45:S40network 只 ifup 了 lo,WiFi 由 S43 之后才起来,租约更晚, +# 所以本脚本启动时大概率还没有网络——同步器自己轮询默认路由(最多 -t 秒)。 +# WiFi 在更晚(用户手动连)才就绪的情况,由 udhcpc 钩子 +# /usr/share/udhcpc/default.script.d/50meow_ntpsync 在拿到租约时再触发一次。 +# +# 失败不阻断 init:所有分支都 exit 0(除非参数错误)。 +# +case "$1" in + start) + printf "Starting meow_ntpsync: " + [ -x /usr/bin/meow_ntpsync ] || { echo "SKIP (not installed)"; exit 0; } + setsid /usr/bin/meow_ntpsync -b -t 120 >/dev/null 2>&1 & + echo "OK" + ;; + 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 + echo "OK" + ;; + restart|reload) + "$0" stop + "$0" start + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 + ;; +esac + +exit 0 diff --git a/patch_v2/ntpsync/meow_ntpsync b/patch_v2/ntpsync/meow_ntpsync new file mode 100755 index 0000000..210c01b --- /dev/null +++ b/patch_v2/ntpsync/meow_ntpsync @@ -0,0 +1,313 @@ +#!/bin/sh +# +# meow_ntpsync — 网络校时(一次性,不常驻)M22 +# +# ---- 为什么需要它(2026-11-06 固件 0.8.47 实测)---- +# * 设备有 RTC:/dev/rtc0(驱动 10003000.rtc),内核开机 hctosys=1, +# 即系统时间来自 RTC;RTC 掉电保持(冷启动后不是 1970)。 +# * 但 RTC 走时偏慢且**没有任何东西纠正**:实测系统时间比 NTP 慢 49 s, +# 把系统时间人为拨错 4 h 后观察 65 s,无人纠正,RTC 也不受系统时间影响。 +# * 固件其实自带 ntp-4.2.8p14(/usr/sbin/ntpd)和 /etc/ntp.conf +# (4 个 pool.ntp.org),但**没有任何 init 脚本/进程启动它** +# → 根因是「有客户端、没启动」,不是「没有 NTP 客户端」。 +# * udhcpc 的 default.script 不处理 option 42,实测热点也不下发。 +# +# ---- 三条路线的取舍 ---- +# ① 自写轻量 SNTP:要新增/交叉编译一个二进制,且 UDP 123 收发、超时、 +# 时间戳换算都要自己写;收益与 ntpd 相同 → 放弃。 +# ② 复用固件自带 ntpd(本脚本):零新增依赖、零体积增长,协议实现成熟, +# -g 允许开机大步进跳时。唯一坑是 ntpd 没有超时参数(无响应会一直等), +# 所以本脚本自带看门狗 kill -9。 +# ③ 依赖 DHCP option 42:实测热点不下发,且 udhcpc 的 default.script +# 根本不读 ntpsrv → 不能作为唯一来源;但企业网可能有, +# 所以 dhcp 钩子把它当「网络就绪事件 + 可选服务器源」,而非唯一依赖。 +# +# ---- 设计约束 ---- +# * 不阻塞开机:调用方(S45 / udhcpc 钩子)一律后台拉起本脚本后立即返回。 +# * 严格有界:单次 ntpd 上限 ATTEMPT_TIMEOUT 秒,-b 模式总时长受 -t 限制, +# 进程生命周期最长 ≈ -t + 2×ATTEMPT_TIMEOUT,之后自杀。 +# * 网络未就绪不硬等:先轮询默认路由,出现才尝试校时。 +# * 失败静默:只写 /var/log(tmpfs,不磨 NAND),不占 CPU、不留常驻进程。 +# * 成功必须回写 RTC(hwclock -w -u),否则下次开机电又错了。 +# +# 用法: +# meow_ntpsync -b [-t 秒] [-s "服务器 ..."] 开机模式:等默认路由 + 重试 +# meow_ntpsync [-s "服务器 ..."] 立即试一次(udhcpc bound/renew) +# meow_ntpsync -S 打印上次同步记录 +# meow_ntpsync -f ... 忽略「1 h 内已同步」短路 +# +# 退出码: 0 = 时间可信(本次同步成功,或 1 h 内已同步过);1 = 失败/超时;2 = 用法错误 +# +# 以下变量可由环境覆盖——仅供宿主单测注入,设备上不设置,走真实路径: +# MEOW_NTPD / MEOW_HWCLOCK / MEOW_NETCHECK / MEOW_NTPSYNC_ATTEMPT_TIMEOUT +# MEOW_NTPSYNC_LOG / MEOW_NTPSYNC_STATE / MEOW_NTPSYNC_LOCK + +NTPD="${MEOW_NTPD:-/usr/sbin/ntpd}" +HWCLOCK="${MEOW_HWCLOCK:-/sbin/hwclock}" +NETCHECK="${MEOW_NETCHECK:-}" + +LOG_FILE="${MEOW_NTPSYNC_LOG:-/var/log/meow_ntpsync.log}" +STATE_FILE="${MEOW_NTPSYNC_STATE:-/tmp/meow_ntpsync.state}" +LOCK_DIR="${MEOW_NTPSYNC_LOCK:-/tmp/meow_ntpsync.lock}" + +# 内置 IP 兜底(不依赖 DNS,国内可达):阿里 / 腾讯 / Cloudflare +FALLBACK_IP="203.107.6.88 120.25.115.20 162.159.200.1" +# DNS 名兜底:内置 IP 全废(被墙/下线)时最后再试一次 +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 内回) +BOOT_TOTAL=120 # -b 模式总时长上限 + +usage() { + cat <>"$LOG_FILE" 2>/dev/null + return 0 +} + +# 网络就绪 = 存在默认路由。不 ping、不解析 DNS,只查内核路由表(零网络开销)。 +net_ready() { + if [ -n "$NETCHECK" ]; then + $NETCHECK >/dev/null 2>&1 + return $? + fi + if [ -r /proc/net/route ]; then + awk 'NR>1 && $2=="00000000" {found=1; exit} END{exit !found}' \ + /proc/net/route 2>/dev/null && return 0 + fi + if command -v ip >/dev/null 2>&1; then + ip route 2>/dev/null | grep -q '^default ' && return 0 + fi + return 1 +} + +synced_recently() { + [ -f "$STATE_FILE" ] || return 1 + s_epoch=$(sed -n 's/^epoch=//p' "$STATE_FILE" 2>/dev/null | head -n 1) + case "$s_epoch" in + ''|*[!0-9]*) return 1 ;; + esac + now=$(date +%s) + age=$((now - s_epoch)) + # 时钟被往回拨时 age<0,视为未同步 + [ "$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 + return 0 + fi + other=$(cat "$LOCK_DIR/pid" 2>/dev/null) + 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 + return 0 +} + +clock_sane() { + y=$(date +%Y 2>/dev/null) + case "$y" in + ''|*[!0-9]*) return 1 ;; + esac + [ "$y" -ge 2020 ] && [ "$y" -le 2100 ] +} + +record_success() { + now=$(date +%s) + { + echo "epoch=$now" + echo "date=$(date '+%Y-%m-%d %H:%M:%S')" + echo "servers=$1" + } >"$STATE_FILE" 2>/dev/null +} + +# RTC 存的是 UTC(实测 /sys/class/rtc/rtc0/since_epoch == date +%s),必须 -u +persist_rtc() { + if "$HWCLOCK" -w -u >>"$LOG_FILE" 2>&1; then + log "rtc written (hwclock -w -u)" + return 0 + fi + log "rtc write FAILED (hwclock -w -u)" + return 1 +} + +# 生成 ntpd 临时配置。iburst 是必须的:实测不加 iburst 时首个 poll 要等 ~64 s, +# -q 根本来不及取到时间(加 iburst 后 8 s 内返回)。 +gen_conf() { + for s in $1; do + echo "server $s iburst" + done >"$LOCK_DIR/ntp.conf" 2>/dev/null +} + +# 单次尝试。ntpd 无响应时不会自己退出(实测无服务器时永久存活), +# 所以用轮询 + kill -9 兜底;成功与否只看退出码。 +attempt() { + servers="$1" + [ -n "$servers" ] || return 1 + gen_conf "$servers" || return 1 + + "$NTPD" -n -q -g -c "$LOCK_DIR/ntp.conf" >"$LOG_FILE" 2>&1 & + pid=$! + waited=0 + while [ "$waited" -lt "$ATTEMPT_TIMEOUT" ]; do + kill -0 "$pid" 2>/dev/null || break + sleep 1 + waited=$((waited + 1)) + done + + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null + wait "$pid" 2>/dev/null + log "attempt timeout after ${waited}s (servers: $servers)" + return 1 + fi + wait "$pid" 2>/dev/null + rc=$? + if [ "$rc" -ne 0 ]; then + log "attempt failed rc=$rc (servers: $servers)" + return 1 + fi + return 0 +} + +trim_log() { + [ -f "$LOG_FILE" ] || return 0 + sz=$(wc -c <"$LOG_FILE" 2>/dev/null) + case "$sz" in + ''|*[!0-9]*) return 0 ;; + esac + # /var/log 是 tmpfs,只防无限增长;保留最后 200 行 + [ "$sz" -gt 65536 ] && tail -n 200 "$LOG_FILE" >"$LOG_FILE.tmp" 2>/dev/null && + mv -f "$LOG_FILE.tmp" "$LOG_FILE" 2>/dev/null + return 0 +} + +# 一轮 = 先打「DHCP 服务器 + 内置 IP」(不需要 DNS),再打 DNS 名 +sync_round() { + servers="$EXTRA $FALLBACK_IP" + trim_log + + if attempt "$servers"; then + if clock_sane; then + record_success "$servers" + log "sync OK (servers: $servers)" + persist_rtc + return 0 + fi + log "sync reported ok but clock still insane, ignore" + fi + + if attempt "$FALLBACK_DNS"; then + if clock_sane; then + record_success "$FALLBACK_DNS" + log "sync OK (dns: $FALLBACK_DNS)" + persist_rtc + return 0 + fi + log "dns sync reported ok but clock still insane, ignore" + fi + + return 1 +} + +MODE=single +TOTAL=$BOOT_TOTAL +EXTRA="" +FORCE=0 +QUIET="" + +while [ $# -gt 0 ]; do + case "$1" in + -b) MODE=boot ;; + -t) TOTAL="$2"; shift ;; + -s) EXTRA="$2"; shift ;; + -f) FORCE=1 ;; + -q) QUIET=1 ;; + -S) MODE=status ;; + -h|--help) usage; exit 0 ;; + *) + echo "meow_ntpsync: unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + esac + shift +done + +case "$TOTAL" in + ''|*[!0-9]*) + echo "meow_ntpsync: -t 必须是整数秒" >&2 + exit 2 + ;; +esac + +if [ "$MODE" = status ]; then + if [ -f "$STATE_FILE" ]; then + cat "$STATE_FILE" + else + echo "never synced" + fi + exit 0 +fi + +if [ "$FORCE" -eq 0 ] && synced_recently; then + log "skip: synced within ${SYNC_TTL}s" + exit 0 +fi + +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 + +# 已经有常驻 ntpd 在跑(用户/第三方装的)→ 它会自己校时,别抢 123 端口 +if pidof ntpd >/dev/null 2>&1; then + log "skip: an ntpd is already running" + exit 0 +fi + +if [ "$MODE" = boot ]; then + deadline=$(( $(date +%s) + TOTAL )) + while :; do + if net_ready; then + log "network ready, syncing" + sync_round && exit 0 + fi + [ "$(date +%s)" -ge "$deadline" ] && break + sleep 5 + done + log "give up: no sync within ${TOTAL}s" + exit 1 +fi + +# 单次模式(udhcpc 钩子):此刻租约刚拿到,路由已装好,直接试 +if ! net_ready; then + log "single: network not ready, skip" + exit 1 +fi +sync_round && exit 0 +exit 1 diff --git a/patch_v2/ntpsync/tests/run_tests.sh b/patch_v2/ntpsync/tests/run_tests.sh new file mode 100644 index 0000000..d654b32 --- /dev/null +++ b/patch_v2/ntpsync/tests/run_tests.sh @@ -0,0 +1,162 @@ +#!/bin/sh +# +# run_tests.sh — meow_ntpsync 宿主沙箱单测(M22) +# +# 不碰真机、不联网:用假的 ntpd / hwclock / 网络探测注入, +# 验证「成功 / 超时 / 失败 / 网络未就绪 / 短路 / 返回码」这些分支。 +# +# 用法: sh patch_v2/ntpsync/tests/run_tests.sh [meow_ntpsync 路径] +# +set -u + +HERE=$(cd "$(dirname "$0")" && pwd) +SUT=${1:-"$HERE/../meow_ntpsync"} +W=$(mktemp -d) +trap 'rm -rf "$W"' EXIT + +PASS=0 +FAIL=0 + +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 决定 ---- +cat >"$W/ntpd" <<'EOF' +#!/bin/sh +echo "invoked $(date +%s)" >> "$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 ;; + slew) echo "ntpd: time slew +0.000042 s"; exit 0 ;; +esac +exit 1 +EOF +chmod +x "$W/ntpd" + +# ---- 假 hwclock ---- +cat >"$W/hwclock" <<'EOF' +#!/bin/sh +echo "$*" >> "$FAKE_HWCLOCK_CALLS" +[ "$FAKE_HWCLOCK_MODE" = fail ] && exit 1 +exit 0 +EOF +chmod +x "$W/hwclock" + +# 每种模式都跑在一个干净的沙箱里 +run_case() { # $1=名字 其余=meow_ntpsync 参数;通过环境变量控制 + name=$1 + shift + CALLS="$W/calls.$name" + HW="$W/hw.$name" + : >"$CALLS" + : >"$HW" + export FAKE_NTPD_CALLS="$CALLS" FAKE_HWCLOCK_CALLS="$HW" + export MEOW_NTPD="$W/ntpd" MEOW_HWCLOCK="$W/hwclock" + export MEOW_NTPSYNC_STATE="$W/state.$name" MEOW_NTPSYNC_LOCK="$W/lock.$name" + export MEOW_NTPSYNC_LOG="$W/log.$name" + export MEOW_NTPSYNC_ATTEMPT_TIMEOUT="${CASE_TIMEOUT:-2}" +} + +echo "== meow_ntpsync 沙箱单测 ($SUT)" + +# 1) 正常同步 +rm -f "$W/state.1" +run_case 1 +FAKE_NTPD_MODE=ok MEOW_NETCHECK=true sh "$SUT"; rc=$? +check "同步成功返回 0" "$rc" 0 +check "hwclock 被调用一次" "$(wc -l <"$W/hw.1" | tr -d ' ')" 1 +check "hwclock 参数为 -w -u" "$(cat "$W/hw.1")" "-w -u" +check "写了 state(epoch)" "$(grep -c '^epoch=' "$W/state.1" 2>/dev/null || echo 0)" 1 + +# 1b) 单独验证 gen_conf 的内容(截获 -c 指向的文件) +cat >"$W/ntpd2" <<'EOF' +#!/bin/sh +while [ $# -gt 0 ]; do [ "$1" = "-c" ] && cp "$2" "$FAKE_CONF_DUMP"; shift; done +exit 0 +EOF +chmod +x "$W/ntpd2" +rm -f "$W/state.1b" +run_case 1b +FAKE_CONF_DUMP="$W/conf.1b" MEOW_NTPD="$W/ntpd2" FAKE_NTPD_MODE=ok MEOW_NETCHECK=true sh "$SUT" +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 +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 +check "挂死不写 RTC" "$(wc -l <"$W/hw.2" | tr -d ' ')" 0 + +# 3) ntpd 明确失败 → 返回 1,不写 RTC +run_case 3 +FAKE_NTPD_MODE=fail MEOW_NETCHECK=true sh "$SUT"; rc=$? +check "失败返回 1" "$rc" 1 +check "失败不写 RTC" "$(wc -l <"$W/hw.3" | tr -d ' ')" 0 + +# 4) 单次模式网络未就绪 → 立刻返回 1,且不去打 ntpd +run_case 4 +t0=$(date +%s) +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 + +# 5) 开机模式:网络一直不来,-t 3 → 按时退出 +run_case 5 +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)" +else bad "开机模式退出时长异常 (${el}s)"; fi + +# 6) 开机模式:网络晚到 → 仍能同步成功 +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 ] +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=$? +check "网络晚到仍返回 0" "$rc" 0 + +# 7) 非法参数 +sh "$SUT" --bogus >/dev/null 2>&1; check "未知参数返回 2" "$?" 2 +sh "$SUT" -t abc >/dev/null 2>&1; check "非数字 -t 返回 2" "$?" 2 + +# 8) 1h 内已同步 → 短路,不调 ntpd +run_case 8 +echo "epoch=$(date +%s)" >"$W/state.8" +FAKE_NTPD_MODE=ok MEOW_NETCHECK=true sh "$SUT"; rc=$? +check "已同步短路返回 0" "$rc" 0 +check "短路不调 ntpd" "$(wc -l <"$W/calls.8" | tr -d ' ')" 0 + +# 9) RTC 写失败:时间已对,返回 0,但日志要留痕 +rm -f "$W/state.9" +run_case 9 +FAKE_NTPD_MODE=slew FAKE_HWCLOCK_MODE=fail MEOW_NETCHECK=true sh "$SUT"; rc=$? +check "RTC 写失败仍返回 0" "$rc" 0 +check "日志记录 RTC 写失败" "$(grep -c 'rtc write FAILED' "$W/log.9" 2>/dev/null)" 1 + +# 10) -S 打印状态 +run_case 10 +echo "epoch=12345" >"$W/state.10" +out=$(sh "$SUT" -S) +check "-S 打印上次记录" "$(echo "$out" | grep -c '^epoch=12345$')" 1 + +echo +echo "== 结果: PASS=$PASS FAIL=$FAIL" +[ "$FAIL" -eq 0 ] || exit 1 diff --git a/scripts/build/README.md b/scripts/build/README.md index abeae2f..91a73d1 100644 --- a/scripts/build/README.md +++ b/scripts/build/README.md @@ -66,7 +66,7 @@ bash scripts/build/build_fw_apps.sh -v 0.7.68 -o r3proii_m2_73.upt -b fw_ssh/squ 规则: - `#` 开头为注释,空行忽略。 -- 目标目录自动创建;`usr/bin` / `etc/init.d` / `etc/profile.d` 一律 `chmod 755`。 +- 目标目录自动创建;**每条**条目装入后一律 `chmod 755`(`usr/bin` / `etc/init.d` / `etc/profile.d` 之外同理,例如 udhcpc 钩子 `usr/share/udhcpc/default.script.d/*` 必须可执行才会被调用)。 - 多重配置并存:复制 `fw_content.conf` 改名,用 `-c` 指定,互不干扰。 ### 覆盖基线已有文件(清单 `` 与基线同名) diff --git a/scripts/build/fw_content.conf b/scripts/build/fw_content.conf index 3c66acc..b6b1532 100644 --- a/scripts/build/fw_content.conf +++ b/scripts/build/fw_content.conf @@ -44,6 +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;自带看门狗/总超时/默认路由轮询,不自常驻、不阻塞调用方) # ---- 开机 init 脚本(etc/init.d,数字决定启动顺序,按文件名自然序)---- # M21 开机亮度链(顺序依赖:S11 < S21mount_ubifs < S22): @@ -54,6 +55,10 @@ patch_v2/meow_netmgrd/meow_netmgrd|/usr/bin/meow_net|20243ea1a800b29badefebb3956 # S11 是**覆盖基线同名文件**(基线已有,md5 249220f2…);S22 是**新增**文件。 patch_v2/bootmenu/S11jpeg_display_shell|/etc/init.d/S11jpeg_display_shell|3ad33301fb9f0884102a47e17a1985ed|覆盖基线 S11(基线 md5 249220f2…):背光写入从「画完 logo 后写 60」改为「画 logo 前写 20(只降不升;cur=0/空 照写,仅 1..20 的有效值跳过)」,删掉写死 60;用户亮度交给 S22 patch_v2/bootmenu/S22brightness|/etc/init.d/S22brightness|1a0aa33d6f806f3f1648168cc1d8b953|新增:S21mount_ubifs 之后按 /usr/data/meow/settings.conf 的 display.brightness 恢复背光(clamp [brightness_min, max_brightness];读不到不改、失败不阻断 init) +# 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/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 状态供批③后台管理页) @@ -62,5 +67,11 @@ patch_v2/bootmenu/S90meow_bootmenu|/etc/init.d/S90meow_bootmenu|e0ace111ce5ee446 patch_v2/bootmenu/S92_03_start_music_player|/etc/init.d/S92_03_start_music_player|c17257aa3d93f75da72903b57cbe1034|§4.3 兜底 patch(rcS 正常路径被 S90 阻塞不执行) patch_v2/bootmenu/S95shutdown_logo|/etc/init.d/S95shutdown_logo|96db7b844d7c26f44835d4681824f578|rcK 关机时显示 /etc/shutdown_logo.jpeg +# ---- udhcpc 钩子(usr/share/udhcpc/default.script.d,default.script 末尾自动执行)---- +# M22:baseline 的 default.script 已有 HOOK_DIR 循环(第 99-103 行),目录原本为空。 +# 钩子只在 bound/renew(拿到租约)时动作——这是设备上唯一可靠的「网络真的就绪」事件; +# 投递后台后立即 exit 0,绝不阻塞 DHCP 脚本。$ntpsrv(DHCP option 42)有就优先用。 +patch_v2/ntpsync/50meow_ntpsync|/usr/share/udhcpc/default.script.d/50meow_ntpsync|4774c08194a2e0232960927252f252f2|新增:udhcpc 租约钩子(bound/renew 时后台触发校时;deconfig/leasefail/nak 直接返回) + # ---- SSH 会话工具 PATH(etc/profile.d)---- patch_v2/meow_launcherd/meow_card_path.sh|/etc/profile.d/meow_card_path.sh|ac1963c75190611f14a2d46d217e640a|SSH 会话 PATH(M2.12 起):指向 TF 卡 MeowOS/opt/bin