Bound installer service checks

This commit is contained in:
ushan0v
2026-07-14 01:38:42 +03:00
parent 2ddc26988a
commit c05752fd5f
2 changed files with 514 additions and 33 deletions
+369 -34
View File
@@ -123,24 +123,145 @@ run_with_deadline() {
forkop_deadline_seconds="$1"
shift
"$@" &
forkop_deadline_command_pid=$!
(
trap 'kill "$forkop_deadline_sleep_pid" 2>/dev/null || true; exit 0' TERM INT
sleep "$forkop_deadline_seconds" &
forkop_deadline_sleep_pid=$!
wait "$forkop_deadline_sleep_pid"
kill "$forkop_deadline_command_pid" 2>/dev/null || true
) &
forkop_deadline_watchdog_pid=$!
forkop_deadline_helper="${FORKOP_DEADLINE_HELPER_PATH:-}"
if [ -z "$forkop_deadline_helper" ]; then
forkop_deadline_helper="$(install_deadline_helper_path)" || return 1
fi
wait "$forkop_deadline_command_pid"
forkop_deadline_result="$TMP_DIR/deadline-result.$$"
"$forkop_deadline_helper" run "$forkop_deadline_seconds" "$forkop_deadline_result" "$@"
forkop_deadline_status=$?
kill "$forkop_deadline_watchdog_pid" 2>/dev/null || true
wait "$forkop_deadline_watchdog_pid" 2>/dev/null || true
rm -f "$forkop_deadline_result.output" "$forkop_deadline_result.error" \
"$forkop_deadline_result.status" "$forkop_deadline_result.timeout"
return "$forkop_deadline_status"
}
install_deadline_helper_path() {
deadline_helper_path="$TMP_DIR/install-deadline.sh"
if [ ! -s "$deadline_helper_path" ]; then
cat > "$deadline_helper_path" <<'EOF'
#!/bin/sh
process_starttime() {
local pid="$1"
local stat rest
[ -r "/proc/$pid/stat" ] || return 1
IFS= read -r stat < "/proc/$pid/stat" || return 1
rest="${stat##*) }"
set -- $rest
[ "$#" -ge 20 ] || return 1
shift 19
printf '%s\n' "$1"
}
child_pids() {
local parent="$1"
local status key value pid ppid
for status in /proc/[0-9]*/status; do
[ -r "$status" ] || continue
pid=""
ppid=""
while IFS=: read -r key value; do
case "$key" in
Pid)
set -- $value
pid="${1:-}"
;;
PPid)
set -- $value
ppid="${1:-}"
;;
esac
done < "$status"
[ "$ppid" = "$parent" ] && [ -n "$pid" ] && printf '%s\n' "$pid"
done
}
kill_descendants() {
local parent="$1"
local signal="$2"
local child
for child in $(child_pids "$parent"); do
kill_descendants "$child" "$signal"
kill "-$signal" "$child" 2>/dev/null || true
done
}
kill_process_tree() {
local root="$1"
local expected_starttime="$2"
local current_starttime
current_starttime="$(process_starttime "$root" 2>/dev/null || true)"
[ -n "$current_starttime" ] && [ "$current_starttime" = "$expected_starttime" ] || return 0
kill -STOP "$root" 2>/dev/null || return 0
kill_descendants "$root" TERM
sleep 1
kill_descendants "$root" KILL
kill -KILL "$root" 2>/dev/null || true
}
run_command() {
local seconds="$1"
local result="$2"
local command_pid command_starttime watchdog_pid status
shift 2
rm -f "$result.output" "$result.error" "$result.status" "$result.timeout"
umask 077
"$@" >"$result.output" 2>"$result.error" &
command_pid=$!
command_starttime="$(process_starttime "$command_pid" 2>/dev/null || true)"
(
local sleep_pid current_starttime
trap 'kill "$sleep_pid" 2>/dev/null || true; wait "$sleep_pid" 2>/dev/null || true; exit 0' TERM INT
sleep "$seconds" &
sleep_pid=$!
wait "$sleep_pid" || exit 0
current_starttime="$(process_starttime "$command_pid" 2>/dev/null || true)"
[ -n "$command_starttime" ] && [ "$current_starttime" = "$command_starttime" ] || exit 0
: > "$result.timeout"
kill_process_tree "$command_pid" "$command_starttime"
) >/dev/null 2>&1 &
watchdog_pid=$!
wait "$command_pid"
status=$?
kill "$watchdog_pid" 2>/dev/null || true
wait "$watchdog_pid" 2>/dev/null || true
[ ! -e "$result.timeout" ] || status=124
printf '%s\n' "$status" > "$result.status"
cat "$result.output"
cat "$result.error" >&2
return "$status"
}
case "${1:-}" in
run)
shift
run_command "$@"
;;
kill-tree)
shift
kill_process_tree "$1" "$2"
;;
*)
exit 2
;;
esac
EOF
chmod 0700 "$deadline_helper_path" || return 1
fi
printf '%s\n' "$deadline_helper_path"
}
http_get() {
case "$FETCHER" in
wget)
@@ -429,6 +550,24 @@ function command_output(args) {
return data == null ? "" : data;
}
function read_text_file(path) {
let handle = fs.open(as_string(path), "r");
if (!handle)
return "";
let data = handle.read("all");
handle.close();
return data == null ? "" : data;
}
function unlink_file(path) {
try {
fs.unlink(as_string(path));
}
catch (e) {
}
}
function env(name, fallback) {
let value = getenv(name);
if (value == null || value == "")
@@ -479,6 +618,44 @@ const INSTALLER_LEGACY_PERSISTENT_DIR = env("FORKOP_INSTALLER_LEGACY_PERSISTENT_
const INSTALLER_LEGACY_RUNTIME_DIR = env("FORKOP_INSTALLER_LEGACY_RUNTIME_DIR", "/var/run/" + LEGACY_BACKEND_PACKAGE);
const INSTALLER_LEGACY_TMP_DIR = env("FORKOP_INSTALLER_LEGACY_TMP_DIR", "/tmp/" + LEGACY_BACKEND_PACKAGE);
const INSTALLER_LEGACY_TMP_ALT_DIR = env("FORKOP_INSTALLER_LEGACY_TMP_ALT_DIR", "/tmp/" + LEGACY_CONFIG_PACKAGE_ALT);
const INSTALLER_DEADLINE_HELPER = env("FORKOP_INSTALLER_DEADLINE_HELPER", "");
const INSTALLER_COMMAND_RESULT = env("FORKOP_INSTALLER_COMMAND_RESULT", "/tmp/forkop-installer-command");
const INSTALLER_RC_DIR = env("FORKOP_INSTALLER_RC_DIR", "/etc/rc.d");
const INSTALLER_START_RETRY_FILE = env("FORKOP_INSTALLER_START_RETRY_FILE", "/var/run/forkop/start.retry");
const INSTALLER_START_RETRY_PID_FILE = env("FORKOP_INSTALLER_START_RETRY_PID_FILE", "/var/run/forkop/start-retry.pid");
const INSTALLER_ORPHAN_PPID = env("FORKOP_INSTALLER_ORPHAN_PPID", "1");
const INSTALLER_SERVICE_PROBE_TIMEOUT = int(env("FORKOP_INSTALLER_SERVICE_PROBE_TIMEOUT", "6")) || 6;
const INSTALLER_SERVICE_ACTION_TIMEOUT = int(env("FORKOP_INSTALLER_SERVICE_ACTION_TIMEOUT", "60")) || 60;
let installer_command_sequence = 0;
function installer_command_result(args, timeout_seconds) {
installer_command_sequence++;
let result = INSTALLER_COMMAND_RESULT + "." + installer_command_sequence;
let helper_args = [
INSTALLER_DEADLINE_HELPER,
"run",
as_string(timeout_seconds),
result
];
for (let arg in args)
push(helper_args, arg);
let shell_status = normalize_status(system(command_from_args(helper_args) + " >/dev/null 2>&1"));
let status_text = trim(read_text_file(result + ".status"));
let complete = match(status_text, /^[0-9]+$/) != null;
let status = complete ? int(status_text) : shell_status;
let output = read_text_file(result + ".output");
for (let suffix in [ ".output", ".error", ".status", ".timeout" ])
unlink_file(result + suffix);
return {
status,
output,
complete,
timed_out: status == 124
};
}
let dns_owner_config = "forkop";
let dns_owner_section = "forkop";
@@ -640,23 +817,150 @@ function installer_confirm_remove_https_dns_proxy() {
}
}
function installer_service_enabled(init_script) {
return path_executable(init_script) && run_args([ init_script, "enabled" ]);
function path_basename(path) {
let parts = split(as_string(path), "/");
return length(parts) > 0 ? parts[length(parts) - 1] : "";
}
function installer_service_running(init_script) {
if (!path_executable(init_script))
return false;
function installer_process_starttime(pid) {
let stat = read_text_file("/proc/" + as_string(pid) + "/stat");
let matched = match(stat, /^[0-9]+ \(.*\) [^ ]+ (.*)$/);
if (!matched)
return "";
if (trim(command_output([ init_script, "status" ])) == "running")
let fields = words(matched[1]);
return length(fields) > 18 ? as_string(fields[18]) : "";
}
function installer_process_ppid(pid) {
let matched = match(read_text_file("/proc/" + as_string(pid) + "/status"), /(^|\n)PPid:[ \t]*([0-9]+)/);
return matched ? as_string(matched[2]) : "";
}
function installer_process_args(pid) {
let args = [];
for (let arg in split(read_text_file("/proc/" + as_string(pid) + "/cmdline"), "\0"))
if (arg != "")
push(args, arg);
return args;
}
function installer_args_have_exact(args, value) {
for (let arg in args)
if (arg == value)
return true;
return run_args([ init_script, "running" ]);
return false;
}
function installer_backend_status_running(bin_path) {
if (!path_executable(bin_path))
function installer_args_contain(args, value) {
for (let arg in args)
if (index(arg, value) >= 0)
return true;
return false;
return index(command_output([ bin_path, "get_status" ]), "\"running\":1") >= 0;
}
function installer_kill_process_tree(pid) {
let starttime = installer_process_starttime(pid);
if (starttime == "" || INSTALLER_DEADLINE_HELPER == "")
return false;
return normalize_status(system(command_from_args([
INSTALLER_DEADLINE_HELPER,
"kill-tree",
as_string(pid),
starttime
]) + " >/dev/null 2>&1")) == 0;
}
function installer_cancel_stale_start_retry() {
let pid = trim(read_text_file(INSTALLER_START_RETRY_PID_FILE));
if (match(pid, /^[0-9]+$/)) {
let args = installer_process_args(pid);
if (installer_args_contain(args, INSTALLER_FORKOP_INIT) &&
installer_args_contain(args, "retry_start_on_wan_up"))
installer_kill_process_tree(pid);
}
unlink_file(INSTALLER_START_RETRY_PID_FILE);
unlink_file(INSTALLER_START_RETRY_FILE);
}
function installer_recover_interrupted_cleanup(init_scripts) {
installer_cancel_stale_start_retry();
for (let status_path in fs.glob("/proc/[0-9]*/status")) {
let parts = split(status_path, "/");
let pid = length(parts) > 2 ? parts[2] : "";
if (pid == "" || installer_process_ppid(pid) != INSTALLER_ORPHAN_PPID)
continue;
let args = installer_process_args(pid);
if (length(args) == 0)
continue;
let action = args[length(args) - 1];
let stale = false;
if (action == "installer-cleanup-legacy") {
for (let arg in args)
if (ends_with(arg, "/install-json.uc") || arg == "install-json.uc")
stale = true;
}
else if (action == "enabled" || action == "status" || action == "running") {
for (let init_script in init_scripts)
if (init_script != "" && installer_args_have_exact(args, init_script))
stale = true;
}
if (stale)
installer_kill_process_tree(pid);
}
}
function installer_service_enabled_state(init_script) {
if (!path_executable(init_script))
return { known: true, value: false };
let result = installer_command_result([ init_script, "enabled" ], INSTALLER_SERVICE_PROBE_TIMEOUT);
if (result.complete && !result.timed_out)
return { known: true, value: result.status == 0 };
let service_name = path_basename(init_script);
if (service_name != "" && length(fs.glob(INSTALLER_RC_DIR + "/S??" + service_name)) > 0)
return { known: true, value: true };
return { known: false, value: false };
}
function installer_service_running_state(init_script) {
if (!path_executable(init_script))
return { known: true, value: false };
let status = installer_command_result([ init_script, "status" ], INSTALLER_SERVICE_PROBE_TIMEOUT);
if (status.complete && !status.timed_out && trim(status.output) == "running")
return { known: true, value: true };
let running = installer_command_result([ init_script, "running" ], INSTALLER_SERVICE_PROBE_TIMEOUT);
if (running.complete && !running.timed_out)
return { known: true, value: running.status == 0 };
return { known: false, value: false };
}
function installer_backend_status_running_state(bin_path) {
if (!path_executable(bin_path))
return { known: true, value: false };
let result = installer_command_result([ bin_path, "get_status" ], INSTALLER_SERVICE_PROBE_TIMEOUT);
if (!result.complete || result.timed_out)
return { known: false, value: false };
return { known: true, value: index(result.output, "\"running\":1") >= 0 };
}
function installer_service_action(init_script, action) {
let result = installer_command_result([ init_script, action ], INSTALLER_SERVICE_ACTION_TIMEOUT);
if (!result.complete || result.timed_out) {
warn("Timed out while running " + init_script + " " + action + ".\n");
return false;
}
return true;
}
function select_dns_owner(legacy) {
@@ -682,17 +986,27 @@ function installer_restore_dnsmasq(bin_path, legacy) {
function installer_deactivate_legacy_base() {
if (!path_executable(INSTALLER_LEGACY_BASE_INIT))
return;
return true;
if (installer_service_running(INSTALLER_LEGACY_BASE_INIT)) {
let running = installer_service_running_state(INSTALLER_LEGACY_BASE_INIT);
let enabled = installer_service_enabled_state(INSTALLER_LEGACY_BASE_INIT);
if (!running.known || !enabled.known) {
warn("Unable to determine the legacy service state before installation.\n");
return false;
}
if (running.value) {
warn("Detected a running legacy service. Stopping it before installing Forkop.\n");
run_args([ INSTALLER_LEGACY_BASE_INIT, "stop" ]);
if (!installer_service_action(INSTALLER_LEGACY_BASE_INIT, "stop"))
return false;
}
if (installer_service_enabled(INSTALLER_LEGACY_BASE_INIT)) {
if (enabled.value) {
warn("Detected an enabled legacy autostart. Disabling it before installing Forkop.\n");
run_args([ INSTALLER_LEGACY_BASE_INIT, "disable" ]);
if (!installer_service_action(INSTALLER_LEGACY_BASE_INIT, "disable"))
return false;
}
return true;
}
function installer_cleanup_legacy() {
@@ -700,19 +1014,38 @@ function installer_cleanup_legacy() {
let legacy_installed = LEGACY_BRAND != "" && installer_package_installed(LEGACY_BACKEND_PACKAGE);
let active_init = legacy_installed ? INSTALLER_LEGACY_INIT : INSTALLER_FORKOP_INIT;
let active_bin = legacy_installed ? INSTALLER_LEGACY_BIN : INSTALLER_FORKOP_BIN;
let was_enabled = installer_service_enabled(active_init);
let was_running = installer_service_running(active_init) || installer_backend_status_running(active_bin);
installer_recover_interrupted_cleanup([
active_init,
INSTALLER_FORKOP_INIT,
INSTALLER_LEGACY_INIT,
INSTALLER_LEGACY_BASE_INIT
]);
let enabled = installer_service_enabled_state(active_init);
let running = installer_service_running_state(active_init);
let backend_running = running.known && running.value ?
{ known: true, value: false } :
installer_backend_status_running_state(active_bin);
if (!enabled.known || (!running.known && !backend_running.known)) {
warn("Unable to determine the Forkop service state before installation.\n");
return false;
}
let was_enabled = enabled.value;
let was_running = running.value || backend_running.value;
if (!installer_confirm_remove_https_dns_proxy())
return false;
if (legacy_installed)
installer_deactivate_legacy_base();
if (legacy_installed && !installer_deactivate_legacy_base())
return false;
if (path_executable(active_init)) {
run_args([ active_init, "stop" ]);
if (!installer_service_action(active_init, "stop"))
return false;
installer_restore_dnsmasq(active_bin, legacy_installed);
run_args([ active_init, "disable" ]);
if (!installer_service_action(active_init, "disable"))
return false;
}
let packages_removed = true;
@@ -1094,6 +1427,8 @@ install_json_ucode() {
FORKOP_INSTALLER_LEGACY_BRAND="$LEGACY_BRAND" \
FORKOP_INSTALLER_LEGACY_BACKEND="$LEGACY_BACKEND_PACKAGE" \
FORKOP_INSTALLER_LEGACY_CONFIG_ALT="$LEGACY_CONFIG_PACKAGE_ALT" \
FORKOP_INSTALLER_DEADLINE_HELPER="$(install_deadline_helper_path)" \
FORKOP_INSTALLER_COMMAND_RESULT="$TMP_DIR/installer-command" \
ucode "$(install_json_helper_path)" "$@"
}
+146
View File
@@ -9,6 +9,18 @@ LEGACY_BACKEND="${LEGACY_BRAND}-plus"
LEGACY_CONFIG_ALT="${LEGACY_BRAND}_plus"
cleanup() {
if [ -n "${RETRY_PID:-}" ]; then
kill -KILL "$RETRY_PID" 2>/dev/null || true
wait "$RETRY_PID" 2>/dev/null || true
fi
if [ -n "${ORPHAN_PROBE_PID:-}" ]; then
kill -KILL "$ORPHAN_PROBE_PID" 2>/dev/null || true
fi
if [ -n "${HANG_PID_LOG:-}" ] && [ -r "$HANG_PID_LOG" ]; then
while IFS= read -r pid; do
kill -KILL "$pid" 2>/dev/null || true
done < "$HANG_PID_LOG"
fi
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
@@ -20,6 +32,19 @@ fail() {
[ -r "$INSTALLER" ] || fail "install.sh is missing"
deadline_helper="$WORK_DIR/install-deadline.sh"
awk '
/cat > "\$deadline_helper_path" <<'\''EOF'\''/ { capture = 1; next }
capture && /^EOF$/ { exit }
capture { print }
' "$INSTALLER" > "$deadline_helper"
[ -s "$deadline_helper" ] || fail "failed to extract installer deadline helper"
chmod 0700 "$deadline_helper"
export FORKOP_DEADLINE_HELPER_PATH="$deadline_helper"
export FORKOP_INSTALLER_DEADLINE_HELPER="$deadline_helper"
export FORKOP_INSTALLER_COMMAND_RESULT="$WORK_DIR/installer-command"
export TMP_DIR="$WORK_DIR"
grep -Fq 'run_with_deadline "$METADATA_TIMEOUT_SECONDS" wget -T "$CONNECT_TIMEOUT_SECONDS" -qO-' "$INSTALLER" ||
fail "installer wget metadata requests must have portable connect and total timeouts"
grep -Fq 'run_with_deadline "$DOWNLOAD_TIMEOUT_SECONDS" wget -T "$CONNECT_TIMEOUT_SECONDS" -q -O' "$INSTALLER" ||
@@ -37,6 +62,20 @@ deadline_elapsed="$(($(date +%s) - deadline_started))"
fail "installer deadline watchdog did not stop the command promptly"
run_with_deadline 3 sh -c 'exit 0' ||
fail "installer deadline watchdog must preserve successful command status"
deadline_child_pid="$WORK_DIR/deadline-child.pid"
if run_with_deadline 1 sh -c '
trap "" TERM
sh -c '\''trap "" TERM; while :; do sleep 30; done'\'' &
child=$!
printf "%s\n" "$child" > "$1"
wait "$child"
' sh "$deadline_child_pid"; then
fail "installer deadline watchdog must fail a stubborn process tree"
fi
[ -s "$deadline_child_pid" ] || fail "deadline process-tree fixture did not record its child"
if kill -0 "$(cat "$deadline_child_pid")" 2>/dev/null; then
fail "installer deadline watchdog left a descendant running"
fi
grep -Fq 'curl --connect-timeout "$CONNECT_TIMEOUT_SECONDS" --max-time "$METADATA_TIMEOUT_SECONDS"' "$INSTALLER" ||
fail "installer curl metadata requests must have connect and total timeouts"
grep -Fq 'curl --connect-timeout "$CONNECT_TIMEOUT_SECONDS" --max-time "$DOWNLOAD_TIMEOUT_SECONDS"' "$INSTALLER" ||
@@ -165,6 +204,113 @@ exit 0
SH
chmod 0755 "$WORK_DIR/opkg"
cat >"$WORK_DIR/hanging-init" <<'SH'
#!/usr/bin/env sh
printf '%s\n' "$1" >> "$FORKOP_INSTALLER_INIT_LOG"
case "$1" in
enabled|status|running)
printf '%s\n' "$$" >> "$FORKOP_INSTALLER_HANG_PID_LOG"
trap '' TERM
sh -c '
trap "" TERM
printf "%s\n" "$$" >> "$FORKOP_INSTALLER_HANG_PID_LOG"
while :; do sleep 30; done
' &
wait "$!"
;;
stop|disable)
exit 0
;;
esac
exit 0
SH
chmod 0755 "$WORK_DIR/hanging-init"
cat >"$WORK_DIR/hanging-bin" <<'SH'
#!/usr/bin/env sh
case "$1" in
get_status) printf '%s\n' '{"running":0}' ;;
restore_dnsmasq) exit 0 ;;
esac
exit 0
SH
chmod 0755 "$WORK_DIR/hanging-bin"
mkdir -p "$WORK_DIR/rc.d"
ln -s "$WORK_DIR/hanging-init" "$WORK_DIR/rc.d/S99hanging-init"
HANG_PID_LOG="$WORK_DIR/hanging-pids.log"
: > "$HANG_PID_LOG"
: > "$WORK_DIR/hanging-init.log"
ORPHAN_PROBE_PID="$(
FORKOP_INSTALLER_INIT_LOG="$WORK_DIR/hanging-init.log" \
FORKOP_INSTALLER_HANG_PID_LOG="$HANG_PID_LOG" \
sh -c '"$1" running >/dev/null 2>&1 & printf "%s\n" "$!"' sh "$WORK_DIR/hanging-init"
)"
ORPHAN_PROBE_PPID=""
for _ in $(seq 1 20); do
ORPHAN_PROBE_PPID="$(awk '/^PPid:/ { print $2 }' "/proc/$ORPHAN_PROBE_PID/status" 2>/dev/null || true)"
[ -n "$ORPHAN_PROBE_PPID" ] && break
sleep 0.05
done
[ -n "$ORPHAN_PROBE_PPID" ] ||
fail "failed to create an orphaned init.d probe fixture"
sh -c 'trap "" TERM; while :; do sleep 30; done' \
"$WORK_DIR/hanging-init" retry_start_on_wan_up &
RETRY_PID=$!
printf '%s\n' "$RETRY_PID" > "$WORK_DIR/start-retry.pid"
printf '%s\n' pending > "$WORK_DIR/start.retry"
hanging_started="$(date +%s)"
PATH="$WORK_DIR:$PATH" \
FORKOP_INSTALLER_OPKG_LOG="$WORK_DIR/opkg.log" \
FORKOP_INSTALLER_INIT="$WORK_DIR/hanging-init" \
FORKOP_INSTALLER_BIN="$WORK_DIR/hanging-bin" \
FORKOP_INSTALLER_LIB="$WORK_DIR/missing-forkop-lib" \
FORKOP_INSTALLER_UCI_DEFAULTS="$WORK_DIR/missing-uci-defaults" \
FORKOP_INSTALLER_LUCI_VIEW="$WORK_DIR/missing-luci-view" \
FORKOP_INSTALLER_MENU_JSON="$WORK_DIR/missing-menu.json" \
FORKOP_INSTALLER_ACL_JSON="$WORK_DIR/missing-acl.json" \
FORKOP_INSTALLER_RU_LMO="$WORK_DIR/missing-ru.lmo" \
FORKOP_INSTALLER_EN_LMO="$WORK_DIR/missing-en.lmo" \
FORKOP_INSTALLER_RU_LUA="$WORK_DIR/missing-ru.lua" \
FORKOP_INSTALLER_EN_LUA="$WORK_DIR/missing-en.lua" \
FORKOP_INSTALLER_LEGACY_BASE_INIT="$WORK_DIR/missing-original-init" \
FORKOP_INSTALLER_LEGACY_BRAND="$LEGACY_BRAND" \
FORKOP_INSTALLER_LEGACY_BACKEND="$LEGACY_BACKEND" \
FORKOP_INSTALLER_RC_DIR="$WORK_DIR/rc.d" \
FORKOP_INSTALLER_START_RETRY_FILE="$WORK_DIR/start.retry" \
FORKOP_INSTALLER_START_RETRY_PID_FILE="$WORK_DIR/start-retry.pid" \
FORKOP_INSTALLER_ORPHAN_PPID="$ORPHAN_PROBE_PPID" \
FORKOP_INSTALLER_SERVICE_PROBE_TIMEOUT=1 \
FORKOP_INSTALLER_SERVICE_ACTION_TIMEOUT=3 \
FORKOP_INSTALLER_INIT_LOG="$WORK_DIR/hanging-init.log" \
FORKOP_INSTALLER_HANG_PID_LOG="$HANG_PID_LOG" \
FORKOP_UCI_STATE_FILE="$WORK_DIR/empty-uci.state" \
ucode "$helper" installer-cleanup-legacy > "$WORK_DIR/hanging-state.env"
hanging_elapsed="$(($(date +%s) - hanging_started))"
[ "$hanging_elapsed" -lt 15 ] ||
fail "installer cleanup did not bound hanging init.d probes (${hanging_elapsed}s)"
grep -Fxq 'FORKOP_WAS_ENABLED=1' "$WORK_DIR/hanging-state.env" ||
fail "installer cleanup must recover enabled state from rc.d after a probe timeout"
for action in enabled status running stop disable; do
grep -Fxq "$action" "$WORK_DIR/hanging-init.log" ||
fail "installer cleanup did not run the expected bounded service action: $action"
done
if [ -e "$WORK_DIR/start.retry" ] || [ -e "$WORK_DIR/start-retry.pid" ]; then
fail "installer cleanup left scheduled retry state behind"
fi
wait "$RETRY_PID" 2>/dev/null || true
RETRY_PID=""
if kill -0 "$ORPHAN_PROBE_PID" 2>/dev/null; then
fail "installer cleanup left an orphaned init.d probe running"
fi
ORPHAN_PROBE_PID=""
while IFS= read -r pid; do
if kill -0 "$pid" 2>/dev/null; then
fail "installer cleanup left a timed-out service process running: $pid"
fi
done < "$HANG_PID_LOG"
: >"$WORK_DIR/opkg.log"
printf '%s\n' '1' |
PATH="$WORK_DIR:$PATH" \