144 lines
7.3 KiB
Bash
144 lines
7.3 KiB
Bash
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
FORKOP_BIN="$ROOT_DIR/forkop/files/usr/bin/forkop"
|
|
FORKOP_INIT="$ROOT_DIR/forkop/files/etc/init.d/forkop"
|
|
CLI_UC="$FORKOP_BIN"
|
|
LIFECYCLE_UC="$ROOT_DIR/forkop/files/usr/lib/service/lifecycle.uc"
|
|
INITD_UC="$ROOT_DIR/forkop/files/usr/lib/service/initd.uc"
|
|
STATE_UC="$ROOT_DIR/forkop/files/usr/lib/service/state.uc"
|
|
UI_UC="$ROOT_DIR/forkop/files/usr/lib/service/ui.uc"
|
|
UPDATES_UC="$ROOT_DIR/forkop/files/usr/lib/components/updates.uc"
|
|
SUBSCRIPTION_CACHE_UC="$ROOT_DIR/forkop/files/usr/lib/subscription/cache.uc"
|
|
NFQUEUE_RUNTIME_UC="$ROOT_DIR/forkop/files/usr/lib/providers/nfqueue/runtime.uc"
|
|
BYEDPI_RUNTIME_UC="$ROOT_DIR/forkop/files/usr/lib/providers/byedpi/runtime.uc"
|
|
PRIORITY_UC="$ROOT_DIR/forkop/files/usr/lib/singbox/priority.uc"
|
|
|
|
fail() {
|
|
printf 'FAIL: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_pattern() {
|
|
local pattern="$1"
|
|
local label="$2"
|
|
|
|
grep -Fq "$pattern" "$LIFECYCLE_UC" || fail "$label"
|
|
}
|
|
|
|
reject_pattern() {
|
|
local pattern="$1"
|
|
local label="$2"
|
|
|
|
! grep -Fq "$pattern" "$FORKOP_BIN" || fail "$label"
|
|
}
|
|
|
|
require_file_pattern() {
|
|
local file="$1"
|
|
local pattern="$2"
|
|
local label="$3"
|
|
|
|
grep -Fq "$pattern" "$file" || fail "$label"
|
|
}
|
|
|
|
[ -r "$FORKOP_BIN" ] || fail "forkop binary source is missing"
|
|
[ -r "$FORKOP_BIN" ] || fail "forkop entrypoint is missing"
|
|
[ -r "$LIFECYCLE_UC" ] || fail "service/lifecycle.uc is missing"
|
|
|
|
grep -Fq '#!/usr/bin/ucode' "$FORKOP_BIN" ||
|
|
fail "forkop entrypoint must be a direct ucode executable"
|
|
grep -Fq 'service/lifecycle.uc' "$CLI_UC" ||
|
|
fail "service/cli.uc must dispatch startup through service/lifecycle.uc"
|
|
reject_pattern "trap " \
|
|
"forkop shell entrypoint must not own startup fail-safe traps"
|
|
reject_pattern "clear_startup_failsafe_trap" \
|
|
"forkop shell entrypoint must not keep old trap helper"
|
|
require_pattern 'module_background(UPDATES_UC, [ "list-update" ])' \
|
|
"startup list_update background job must be owned by service/lifecycle.uc"
|
|
require_pattern 'module_background(DIAGNOSTICS_UC, [ "get-system-info" ])' \
|
|
"startup system-info background job must be owned by service/lifecycle.uc"
|
|
require_pattern 'startup_config_fingerprint = external_config_fingerprint();' \
|
|
"startup must snapshot runtime-relevant config after validation"
|
|
require_pattern 'mark_pending_reload_if_config_changed(startup_config_fingerprint, "config_changed_during_start")' \
|
|
"startup must queue reload when config changes while service is starting"
|
|
awk '
|
|
/function restart_runtime_for_reload\(\)/ { in_restart = 1 }
|
|
in_restart && /status = start_impl\(\);/ { saw_start_impl = 1 }
|
|
in_restart && /dnsmasq_configure\(true\)/ { duplicated_dns = 1 }
|
|
in_restart && /write-current-reload-state-clean/ { duplicated_state = 1 }
|
|
in_restart && /DNS_FAILOVER_UC.*start-runtime/ { duplicated_failover = 1 }
|
|
in_restart && /^}/ { done = 1; exit }
|
|
END { exit done && saw_start_impl && !duplicated_dns && !duplicated_state && !duplicated_failover ? 0 : 1 }
|
|
' "$LIFECYCLE_UC" ||
|
|
fail "reload fallback restart must reuse the complete start_impl path without duplicating finalization"
|
|
require_pattern 'function abort_reload(status, runtime_changed)' \
|
|
"reload failures must share one cleanup decision owner"
|
|
require_pattern 'return abort_reload(status, true);' \
|
|
"reload failures after runtime mutation must clean the partial runtime"
|
|
cleanup_function_line="$(grep -nF 'function cleanup_failed_runtime()' "$LIFECYCLE_UC" | head -n1 | cut -d: -f1)"
|
|
abort_function_line="$(grep -nF 'function abort_reload(status, runtime_changed)' "$LIFECYCLE_UC" | head -n1 | cut -d: -f1)"
|
|
[ -n "$cleanup_function_line" ] && [ -n "$abort_function_line" ] &&
|
|
[ "$cleanup_function_line" -lt "$abort_function_line" ] ||
|
|
fail "cleanup_failed_runtime must be declared before abort_reload for ucode runtime calls"
|
|
require_pattern 'pending_reload_log_context(reason)' \
|
|
"startup queued reload must be visible in logs"
|
|
require_pattern 'return "current reload";' \
|
|
"reload-time queued reload must be distinguishable in logs"
|
|
require_file_pattern "$LIFECYCLE_UC" '>/dev/null 2>&1 1000>&- &' \
|
|
"service/lifecycle.uc background modules must close inherited procd lock fd"
|
|
require_file_pattern "$INITD_UC" 'reload pending >/dev/null 2>&1 1000>&- &' \
|
|
"service/initd.uc pending reload worker must close inherited procd lock fd"
|
|
require_file_pattern "$STATE_UC" 'reload pending >/dev/null 2>&1 1000>&- &' \
|
|
"service/state.uc pending reload worker must close inherited procd lock fd"
|
|
require_file_pattern "$FORKOP_INIT" 'FORKOP_LAST_START_STATUS="$?"' \
|
|
"init.d start_service must preserve backend start status for rc.common"
|
|
require_file_pattern "$FORKOP_INIT" 'service_started()' \
|
|
"init.d must return preserved start status through rc.common service_started hook"
|
|
require_file_pattern "$UI_UC" '>/dev/null 2>&1 1000>&- & echo $!' \
|
|
"service/ui.uc service action workers must close inherited procd lock fd"
|
|
require_file_pattern "$UPDATES_UC" '>/dev/null 2>&1 1000>&- & echo $!' \
|
|
"components/updates.uc async workers must close inherited procd lock fd"
|
|
require_file_pattern "$SUBSCRIPTION_CACHE_UC" '>/dev/null 2>&1 1000>&- & echo $!' \
|
|
"subscription/cache.uc retry worker must close inherited procd lock fd"
|
|
require_file_pattern "$NFQUEUE_RUNTIME_UC" '>>" + shell_quote(logfile) + " 2>&1 1000>&- & echo $!' \
|
|
"nfqueue provider supervisors must close inherited procd lock fd"
|
|
require_file_pattern "$BYEDPI_RUNTIME_UC" '>>" + shell_quote(logfile) + " 2>&1 1000>&- & echo $!' \
|
|
"byedpi supervisor must close inherited procd lock fd"
|
|
require_file_pattern "$PRIORITY_UC" '>/dev/null 2>&1 1000>&- & echo $!' \
|
|
"priority worker must close inherited procd lock fd"
|
|
awk '
|
|
/function cleanup_failed_runtime\(\)/ { in_cleanup = 1 }
|
|
in_cleanup && /stop_main\(\);/ { saw_stop = 1 }
|
|
in_cleanup && /dnsmasq_restore_fail_safe\(\);/ && saw_stop { saw_dns = 1 }
|
|
in_cleanup && /mark_runtime_stopped_clean\(\);/ && saw_dns { ok = 1; exit }
|
|
in_cleanup && /^}/ { exit }
|
|
END { exit ok ? 0 : 1 }
|
|
' "$LIFECYCLE_UC" ||
|
|
fail "failed runtime cleanup must stop partial runtime, roll back DNS, and mark clean shutdown"
|
|
grep -Fq 'cleanup_failed_runtime();' "$LIFECYCLE_UC" ||
|
|
fail "service failure paths must use failed runtime cleanup"
|
|
require_pattern "tproxy-marking-rule4-present" \
|
|
"service stop must use direct ucode IPv4 tproxy marking rule check"
|
|
require_pattern "tproxy-marking-rule6-present" \
|
|
"service stop must use direct ucode IPv6 tproxy marking rule check"
|
|
require_pattern "tproxy-route4-present" \
|
|
"service stop must use direct ucode IPv4 tproxy route check"
|
|
require_pattern "tproxy-route6-present" \
|
|
"service stop must use direct ucode IPv6 tproxy route check"
|
|
grep -Fq 'require("core.uci")' "$LIFECYCLE_UC" ||
|
|
fail "service/lifecycle.uc must use core.uci for lifecycle UCI state"
|
|
if grep -n -E 'uci -q|require\("uci"\)\.cursor|function uci_|uci_set\(' "$LIFECYCLE_UC" >/dev/null 2>&1; then
|
|
fail "service/lifecycle.uc must not own direct UCI CLI/cursor helpers"
|
|
fi
|
|
reject_pattern "tproxy_marking_rule_present" \
|
|
"service stop must not call the removed shell tproxy marking helper"
|
|
reject_pattern "tproxy_route_present" \
|
|
"service stop must not call the removed shell tproxy route helper"
|
|
reject_pattern "has_tproxy_marking_rule" \
|
|
"service stop must not call the removed shell tproxy marking helper"
|
|
reject_pattern "has_tproxy_route" \
|
|
"service stop must not call the removed shell tproxy route helper"
|
|
|
|
printf 'service start trap checks passed\n'
|