Files
NeoTachyon/tachyon/files/usr/lib/service/initd.uc
T
Dushnilin 03c51d7606 fix(service): prevent recursive flock deadlock and crontab wipe; increase component RPC timeout
- initd.uc: retry_start_on_wan_up now calls BIN_PATH directly instead of
  SERVICE_INIT to avoid the rc.common flock-1000 self-deadlock that was
  hanging Backend CI for the full 1-hour runner timeout
- initd.uc: schedule_start_retry uses background_command() so inherited
  fd 1000 (procd lock) is closed before the sleep starts
- updates.uc, parental_quota.uc: read /etc/crontabs/root via fs.readfile
  instead of crontab -l — BusyBox exits 1 on an empty file causing
  command_output_from_args to return '' and wipe all user cron entries
- shell/index.ts: COMPONENT_ACTION_RPC_TIMEOUT_MS 10s -> 45s to prevent
  false RPC timeout errors during opkg installs on slow routers
2026-09-15 11:16:50 +03:00

856 lines
31 KiB
Ucode

#!/usr/bin/env ucode
let fs = require("fs");
let constants = require("core.constants");
let uci_core = require("core.uci");
let common = require("core.common");
let as_string = common.as_string;
function constant_value(name, fallback) {
let value = constants[name];
return value == null ? as_string(fallback) : as_string(value);
}
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || constant_value("TACHYON_CONFIG_NAME", "tachyon");
const LIB_DIR = getenv("TACHYON_LIB") || "/usr/lib/tachyon";
const BIN_PATH = getenv("TACHYON_BIN") || constant_value("TACHYON_BIN", "/usr/bin/tachyon");
const SERVICE_INIT = getenv("TACHYON_SERVICE_INIT") || constant_value("TACHYON_SERVICE_INIT", "/etc/init.d/tachyon");
const SERVICE_NAME = getenv("TACHYON_SERVICE_NAME") || constant_value("TACHYON_SERVICE_NAME", "tachyon");
const CONFIG_FILE = getenv("TACHYON_CONFIG_FILE") || "/etc/config/" + CONFIG_NAME;
const RELOAD_LOCK_DIR = getenv("TACHYON_RELOAD_LOCK_DIR") || "/var/run/tachyon.reload.lock";
const RUNTIME_STATE_DIR = getenv("TACHYON_RUNTIME_STATE_DIR") || "/var/run/tachyon";
const PENDING_RELOAD_FILE = getenv("TACHYON_PENDING_RELOAD_FILE") || RUNTIME_STATE_DIR + "/reload.pending";
const START_RETRY_FILE = getenv("TACHYON_START_RETRY_FILE") || RUNTIME_STATE_DIR + "/start.retry";
const START_RETRY_PID_FILE = getenv("TACHYON_START_RETRY_PID_FILE") || RUNTIME_STATE_DIR + "/start-retry.pid";
const START_RETRY_DELAY_SECONDS = getenv("TACHYON_START_RETRY_DELAY_SECONDS") || "30";
const SERVICE_TRIGGER_SYNC_FILE = getenv("TACHYON_SERVICE_TRIGGER_SYNC_FILE") || RUNTIME_STATE_DIR + "/service-triggers.sync";
const INTERNAL_CONFIG_TRIGGER_GUARD = getenv("TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD") || "/var/run/tachyon.internal-config-change";
const CONFIG_CHANGE_REASON = getenv("TACHYON_CONFIG_CHANGE_REASON") || "on_config_change";
const DNS_APPLY_UC = LIB_DIR + "/dns/apply.uc";
const UI_UC = LIB_DIR + "/service/ui.uc";
let shell_quote = common.shell_quote;
function shell_assignment(name, value) {
print(as_string(name), "=", shell_quote(value), "\n");
}
let command_from_args = common.command_from_args;
// Local copy of core/common.uc's background_command(). This module runs on the
// early-boot path and deliberately carries its own helpers rather than pulling
// in core.common; see the rationale there for why the descriptors have to be
// closed by hand — including why the ceiling must stay above procd's fd 1000.
function background_command(command) {
return "{ if ( eval \"exec 10<&-\" ) 2>/dev/null; then __tfd=1048576; else __tfd=9; fi; " +
"for f in /proc/self/fd/*; do i=${f##*/}; case $i in 0|1|2) continue;; esac; " +
"[ \"$i\" -le $__tfd ] 2>/dev/null || continue; " +
"eval \"exec $i<&-\" 2>/dev/null || true; done; " +
as_string(command) + "; } </dev/null >/dev/null 2>&1 &";
}
function normalize_status(status) {
status = int(status);
if (status == -1)
return 255;
let signal = status & 127;
if (signal != 0)
return 128 + signal;
return (status >> 8) & 255;
}
let command_status = common.command_status;
function command_capture(command) {
let pipe = fs.popen(command, "r");
if (!pipe)
return { status: 1, output: "" };
let data = pipe.read("all");
let status = normalize_status(pipe.close());
return { status, output: data == null ? "" : as_string(data) };
}
let command_output = common.command_output;
let command_output_from_args = common.command_output_from_args;
let command_success_from_args = common.command_success_from_args;
let command_status_from_args = common.command_status_from_args;
function module_args(module_path, args) {
let result = [ "ucode", "-L", LIB_DIR, module_path ];
for (let arg in (type(args) == "array" ? args : []))
push(result, arg);
return result;
}
function module_command(module_path, args) {
return command_from_args(module_args(module_path, args));
}
function module_status(module_path, args) {
return command_status(module_command(module_path, args));
}
function module_output(module_path, args) {
let result = command_capture(module_command(module_path, args));
return result.status == 0 ? result.output : "";
}
function trim(value) {
return replace(as_string(value), /^[ \t\r\n]+|[ \t\r\n]+$/g, "");
}
function numeric_text(value) {
return match(as_string(value), /^[0-9]+$/) != null;
}
function current_epoch() {
return as_string(int(clock()[0]));
}
let bool_text = common.bool_value;
function object_or_empty(value) {
return type(value) == "object" ? value : {};
}
function option(section, key, fallback) {
if (fallback == null)
fallback = "";
let value = object_or_empty(section)[key];
if (value == null)
return as_string(fallback);
if (type(value) == "array")
return join(" ", value);
return as_string(value);
}
let file_exists = common.file_exists;
function file_executable(path) {
return command_success_from_args([ "test", "-x", as_string(path) ]);
}
let unlink_file = common.remove_file;
function ensure_parent_dir(path) {
let dir = replace(as_string(path), /\/[^\/]*$/, "");
if (dir == "" || dir == path)
return true;
return fs.mkdir(dir, 0755) || fs.stat(dir) != null;
}
function write_text_file(path, text) {
return fs.writefile(as_string(path), as_string(text));
}
function first_line_value(path) {
let data = fs.readfile(path);
if (data == null)
return "";
let newline = index(data, "\n");
return newline >= 0 ? substr(data, 0, newline) : data;
}
function config_file_hash(path) {
let fields = split(trim(command_output_from_args([ "md5sum", as_string(path) ])), " ");
return length(fields) > 0 ? as_string(fields[0]) : "";
}
function pid_alive(pid) {
pid = as_string(pid);
if (match(pid, /^[0-9]+$/) == null || !command_success_from_args([ "kill", "-0", pid ]))
return false;
let cmd = fs.readfile("/proc/" + pid + "/cmdline");
if (cmd != null && match(cmd, /ucode|tachyon|sh|sing-box/) == null)
return false;
return true;
}
function lock_dir_write_owner(lock_dir, owner_pid) {
return write_text_file(as_string(lock_dir) + "/pid", as_string(owner_pid) + "\n");
}
function release_runtime_dir_lock(lock_dir) {
lock_dir = as_string(lock_dir);
if (lock_dir == "")
return;
command_success_from_args([ "rm", "-f", lock_dir + "/pid" ]);
command_success_from_args([ "rmdir", lock_dir ]);
}
function acquire_runtime_dir_lock(lock_dir, owner_pid) {
lock_dir = as_string(lock_dir);
owner_pid = as_string(owner_pid);
if (lock_dir == "" || owner_pid == "")
return false;
if (command_success_from_args([ "mkdir", lock_dir ])) {
if (lock_dir_write_owner(lock_dir, owner_pid))
return true;
release_runtime_dir_lock(lock_dir);
return false;
}
let lock_stat = fs.stat(lock_dir + "/pid") || fs.stat(lock_dir);
let lock_age = (lock_stat && lock_stat.mtime) ? (int(clock()[0]) - lock_stat.mtime) : 9999;
let recorded_pid = first_line_value(lock_dir + "/pid");
if (pid_alive(recorded_pid) && lock_age < 300)
return false;
// pid file missing entirely: either another waiter is mid-takeover or a
// previous owner crashed before writing it. Only steal once the
// directory is old enough that a live holder cannot still be starting.
if (recorded_pid == "" && lock_age < 300)
return false;
// Takeover must be atomic: rm+rmdir+mkdir lets two concurrent waiters
// each believe they own the lock. Renaming the directory away succeeds
// for exactly one contender; the losers re-race on mkdir and either win
// a fresh lock or observe the winner's pid file.
let stale_dir = lock_dir + ".stale." + owner_pid + "." + int(clock()[0]);
if (!command_success_from_args([ "mv", lock_dir, stale_dir ]))
return false;
command_success_from_args([ "rm", "-rf", stale_dir ]);
if (command_success_from_args([ "mkdir", lock_dir ])) {
if (lock_dir_write_owner(lock_dir, owner_pid))
return true;
release_runtime_dir_lock(lock_dir);
return false;
}
// Another contender re-created the lock between our rename and mkdir:
// they own it now.
return false;
}
function mark_pending_reload(path, reason) {
path = as_string(path || PENDING_RELOAD_FILE);
reason = as_string(reason || "pending");
if (!ensure_parent_dir(path))
return false;
return write_text_file(path, "reason=" + reason + "\nupdated_at=" + current_epoch() + "\n");
}
function mark_start_retry(path, reason) {
path = as_string(path || START_RETRY_FILE);
reason = as_string(reason || "start_failed");
if (!ensure_parent_dir(path))
return false;
return write_text_file(path, "reason=" + reason + "\nupdated_at=" + current_epoch() + "\n");
}
function clear_start_retry(path) {
path = as_string(path || START_RETRY_FILE);
if (file_exists(path))
unlink_file(path);
}
function start_retry_pending(path) {
return file_exists(as_string(path || START_RETRY_FILE));
}
function cancel_scheduled_start_retry(path) {
path = as_string(path || START_RETRY_PID_FILE);
let pid = first_line_value(path);
if (pid_alive(pid))
command_success_from_args([ "kill", pid ]);
if (file_exists(path))
unlink_file(path);
}
function schedule_start_retry(path, delay_seconds) {
path = as_string(path || START_RETRY_PID_FILE);
delay_seconds = as_string(delay_seconds || START_RETRY_DELAY_SECONDS);
if (!numeric_text(delay_seconds))
delay_seconds = "30";
let scheduled_pid = first_line_value(path);
if (pid_alive(scheduled_pid))
return true;
if (file_exists(path))
unlink_file(path);
if (!ensure_parent_dir(path))
return false;
// Build the retry worker. The inner command sleeps, removes the pid file,
// then runs the retry action. We wrap everything in background_command() so
// that all inherited descriptors (in particular procd's flock fd 1000) are
// closed before the sleep starts — without that the background subshell
// would pin the lock file and prevent any subsequent service operation that
// also tries to acquire flock 1000.
let worker = command_from_args([ "sleep", delay_seconds ]) +
"; " + command_from_args([ "rm", "-f", path ]) +
"; " + command_from_args([ SERVICE_INIT, "retry_start_on_wan_up" ]);
let bg = background_command(worker);
// background_command() already appends " &"; we need $! so run it inside a
// subshell that prints the pid back to us.
let result = command_capture("{ " + bg + " } ; echo $!");
let pid = trim(result.output);
if (result.status != 0 || !numeric_text(pid))
return false;
return write_text_file(path, pid + "\n");
}
function consume_pending_reload(path) {
path = as_string(path || PENDING_RELOAD_FILE);
if (!file_exists(path))
return false;
unlink_file(path);
return true;
}
function run_pending_reload_if_requested(path, init_script) {
path = as_string(path || PENDING_RELOAD_FILE);
init_script = as_string(init_script || SERVICE_INIT);
if (!consume_pending_reload(path))
return;
command_success_from_args([ "logger", "-t", SERVICE_NAME, "[info] Applying pending Tachyon reload" ]);
system(background_command(shell_quote(init_script) + " reload pending"));
}
function uci_settings() {
return object_or_empty(uci_core.get_all(CONFIG_NAME, "settings"));
}
function settings_from_fixture(path) {
let data = fs.readfile(path);
if (data == null)
return {};
try {
data = json(data);
}
catch (e) {
return {};
}
return object_or_empty(object_or_empty(data).settings);
}
function initd_service_trigger_sync_requested(path) {
path = as_string(path);
if (!file_exists(path))
return false;
let value = first_line_value(path);
unlink_file(path);
return value == "1";
}
function initd_guard_matches_current_config(guard_path, config_path, now_value) {
let guard_timestamp = first_line_value(guard_path);
if (!numeric_text(guard_timestamp))
return false;
now_value = now_value == null ? current_epoch() : as_string(now_value);
if (!numeric_text(now_value))
return true;
if (int(now_value, 10) - int(guard_timestamp, 10) > 30)
return false;
let data = fs.readfile(guard_path);
if (data == null)
return false;
let lines = split(data, "\n");
let guard_hash = length(lines) > 1 ? as_string(lines[1]) : "";
if (guard_hash == "")
return false;
return guard_hash == config_file_hash(config_path);
}
function initd_should_skip_internal_config_reload(reason, guard_path, config_path, expected_reason, now_value) {
if (as_string(reason) != as_string(expected_reason || CONFIG_CHANGE_REASON))
return false;
if (!file_exists(as_string(guard_path)))
return false;
let matches = initd_guard_matches_current_config(guard_path, config_path, now_value);
unlink_file(guard_path);
return matches;
}
function initd_should_restore_dnsmasq_on_start_from_value(reason, shutdown_correctly) {
if (as_string(reason) == "triggered")
return false;
shutdown_correctly = as_string(shutdown_correctly);
if (shutdown_correctly == "")
shutdown_correctly = "1";
return shutdown_correctly == "0";
}
function initd_should_ignore_config_change_reload(reason, expected_reason, runtime_running, service_enabled) {
return as_string(reason) == as_string(expected_reason || CONFIG_CHANGE_REASON) &&
!bool_text(runtime_running);
}
function initd_should_queue_config_change_reload(reason, expected_reason, runtime_running, active_service_action) {
return as_string(reason) == as_string(expected_reason || CONFIG_CHANGE_REASON) &&
!bool_text(runtime_running) &&
as_string(active_service_action) != "";
}
function initd_should_sync_service_triggers(reason, expected_reason, sync_file) {
if (as_string(reason) != as_string(expected_reason || CONFIG_CHANGE_REASON))
return false;
return initd_service_trigger_sync_requested(sync_file);
}
function restore_dnsmasq_failsafe() {
if (!file_exists(DNS_APPLY_UC))
return 0;
return module_status(DNS_APPLY_UC, [ "failsafe-restore" ]);
}
function owner_pid_value() {
let pid = trim(command_output_from_args([ "sh", "-c", "echo $PPID" ]));
return match(pid, /^[0-9]+$/) != null ? pid : "0";
}
function begin_external_service_action(action, owner_pid, source) {
action = as_string(action);
if (action == "")
return "";
if (!file_exists(UI_UC))
return "";
let job_id = trim(module_output(UI_UC, [ "service-action-begin-if-idle", action, source || "initd" ]));
if (job_id != "")
module_status(UI_UC, [ "service-action-update-pid", job_id, owner_pid || owner_pid_value() ]);
return job_id;
}
function finish_external_service_action(action, job_id, status) {
if (as_string(job_id) == "" || !file_exists(UI_UC))
return 0;
return module_status(UI_UC, [ "service-action-finish-after-command", action, job_id, as_string(status) ]);
}
function runtime_status_object() {
let data = command_output_from_args([ BIN_PATH, "get_status" ]);
try {
data = json(data);
}
catch (e) {
return {};
}
return object_or_empty(data);
}
function runtime_is_running() {
return bool_text(runtime_status_object().running);
}
function status_service() {
if (runtime_is_running()) {
print("running\n");
return 0;
}
print("not running\n");
return 1;
}
function service_is_enabled() {
return file_exists("/etc/rc.d/S99" + SERVICE_NAME);
}
function retry_start_on_wan_up_action(runtime_running_value, service_enabled_value, retry_pending_value) {
if (bool_text(runtime_running_value))
return "skip_running";
if (!bool_text(service_enabled_value))
return "skip_disabled";
if (!bool_text(retry_pending_value))
return "skip_no_retry";
return "restart";
}
function retry_start_on_wan_up(owner_pid) {
let running = runtime_is_running();
let enabled = service_is_enabled();
let action = retry_start_on_wan_up_action(
running ? "1" : "0",
enabled ? "1" : "0",
start_retry_pending(START_RETRY_FILE) ? "1" : "0"
);
if (action == "skip_running" || action == "skip_disabled") {
clear_start_retry(START_RETRY_FILE);
if (!running && !enabled)
restore_dnsmasq_failsafe();
return 0;
}
if (action != "restart")
return 0;
command_success_from_args([ "logger", "-t", SERVICE_NAME, "[info] Retrying failed Tachyon start after WAN came up" ]);
// Do NOT call SERVICE_INIT here: rc.common holds flock 1000 for the whole
// duration of the init.d script, so a nested /etc/init.d/tachyon call would
// block forever waiting for that same lock (recursive flock self-deadlock).
// Call BIN_PATH directly, the same way start_service and stop_service do.
return command_status_from_args([ BIN_PATH, "restart" ]);
}
function active_service_action_value() {
if (!file_exists(UI_UC))
return "";
return trim(module_output(UI_UC, [ "active-service-action" ]));
}
function ui_action_tracked() {
return as_string(getenv("TACHYON_UI_ACTION_TRACKED") || "0") == "1";
}
function start_plan_value(reason, owner_pid, settings, bin_ok) {
settings = object_or_empty(settings);
let job_id = begin_external_service_action("start", "initd", owner_pid);
bin_ok = bin_ok == null ? file_executable(BIN_PATH) : bool_text(bin_ok);
if (!bin_ok) {
restore_dnsmasq_failsafe();
finish_external_service_action("start", job_id, 1);
}
else if (initd_should_restore_dnsmasq_on_start_from_value(reason, option(settings, "shutdown_correctly", "1"))) {
restore_dnsmasq_failsafe();
}
let badwan_enabled = option(settings, "enable_badwan_interface_monitoring", "") == "1";
let badwan_interfaces = option(settings, "badwan_monitored_interfaces", "");
return {
job_id,
bin_ok,
badwan_netdev: badwan_enabled ? badwan_interfaces : ""
};
}
function start_plan(reason, owner_pid, settings, bin_ok) {
let plan = start_plan_value(reason, owner_pid, settings, bin_ok);
shell_assignment("INITD_UI_JOB_ID", plan.job_id);
shell_assignment("INITD_BIN_OK", plan.bin_ok ? "1" : "0");
shell_assignment("INITD_BADWAN_NETDEV", plan.badwan_netdev);
}
const BOOT_START_UPTIME_SECONDS = int(getenv("TACHYON_BOOT_START_UPTIME_SECONDS") || "120");
const BOOT_START_VERIFY_TIMEOUT = getenv("TACHYON_BOOT_START_VERIFY_TIMEOUT") || "45";
// Seconds since boot, or null when /proc/uptime cannot be read.
function system_uptime_seconds() {
let data = fs.readfile("/proc/uptime");
if (data == null)
return null;
let field = split(trim(as_string(data)), /[ \t]+/)[0];
if (field == null || match(field, /^[0-9]+(\.[0-9]+)?$/) == null)
return null;
return int(field);
}
// Cold boot and WAN-up retries race the network coming up, so sing-box can take far
// longer to reach a stable state than on a start the user asked for. rc.common calls
// boot() as a plain argument-less start, so the reason alone cannot tell the two
// apart: a start early in uptime is treated as cold. Those paths keep the generous
// verification window, everything else uses the shorter default from lifecycle.uc.
function start_verify_timeout(reason, uptime_seconds) {
if (as_string(reason) == "triggered")
return BOOT_START_VERIFY_TIMEOUT;
if (uptime_seconds == null)
uptime_seconds = system_uptime_seconds();
if (uptime_seconds == null)
return "";
return int(uptime_seconds) <= BOOT_START_UPTIME_SECONDS ? BOOT_START_VERIFY_TIMEOUT : "";
}
function start_service(reason, owner_pid) {
print("Start Tachyon\n");
let plan = start_plan_value(reason, owner_pid, uci_settings(), null);
if (!plan.bin_ok)
return 1;
let verify_timeout = start_verify_timeout(reason, null);
let start_command = command_from_args([ BIN_PATH, "start" ]);
if (verify_timeout != "")
start_command = "TACHYON_SING_BOX_START_VERIFY_TIMEOUT=" +
shell_quote(verify_timeout) + " " + start_command;
let status = command_status(start_command);
if (status == 0) {
clear_start_retry(START_RETRY_FILE);
cancel_scheduled_start_retry(START_RETRY_PID_FILE);
}
else {
mark_start_retry(START_RETRY_FILE, as_string(reason) == "triggered" ? "wan_retry_failed" : "start_failed");
schedule_start_retry(START_RETRY_PID_FILE, START_RETRY_DELAY_SECONDS);
command_success_from_args([ "logger", "-t", SERVICE_NAME, "[warn] Tachyon start failed; scheduled an automatic retry" ]);
}
finish_external_service_action("start", plan.job_id, status);
return status;
}
function stop_plan(owner_pid, bin_ok) {
let job_id = begin_external_service_action("stop", "initd", owner_pid);
bin_ok = bin_ok == null ? file_executable(BIN_PATH) : bool_text(bin_ok);
if (!bin_ok) {
restore_dnsmasq_failsafe();
finish_external_service_action("stop", job_id, 1);
}
shell_assignment("INITD_UI_JOB_ID", job_id);
shell_assignment("INITD_BIN_OK", bin_ok ? "1" : "0");
}
function stop_finish(job_id, status) {
status = int(status || 0);
if (status != 0)
restore_dnsmasq_failsafe();
finish_external_service_action("stop", job_id, status);
return status;
}
function stop_service(owner_pid) {
clear_start_retry(START_RETRY_FILE);
cancel_scheduled_start_retry(START_RETRY_PID_FILE);
let job_id = begin_external_service_action("stop", "initd", owner_pid);
if (!file_executable(BIN_PATH)) {
restore_dnsmasq_failsafe();
finish_external_service_action("stop", job_id, 1);
return 1;
}
let status = command_status_from_args([ BIN_PATH, "stop" ]);
// These survive tachyon stop because background subprocesses (reload_firewall,
// restart) inherit the logread pipe read-end, preventing SIGPIPE delivery.
// Use pkill to target only logread in follow mode, not one-shot logread calls.
system("pkill -f 'logread -f$' 2>/dev/null; true");
return stop_finish(job_id, status);
}
function reload_begin_value(reason, owner_pid, runtime_running_value, service_enabled_value, active_service_action) {
reason = as_string(reason);
if (initd_should_skip_internal_config_reload(
reason,
INTERNAL_CONFIG_TRIGGER_GUARD,
CONFIG_FILE,
CONFIG_CHANGE_REASON,
null
)) {
return { action: "skip", job_id: "" };
}
active_service_action = active_service_action == null ? active_service_action_value() : as_string(active_service_action);
if (reason == "pending" && active_service_action != "" && !ui_action_tracked()) {
mark_pending_reload(PENDING_RELOAD_FILE, reason);
return { action: "skip", job_id: "" };
}
if (reason == "pending") {
if (!acquire_runtime_dir_lock(RELOAD_LOCK_DIR, owner_pid || owner_pid_value())) {
mark_pending_reload(PENDING_RELOAD_FILE, reason || "reload_busy");
return { action: "skip", job_id: "" };
}
unlink_file(SERVICE_TRIGGER_SYNC_FILE);
let job_id = begin_external_service_action("reload", "initd", owner_pid);
return { action: "run", job_id };
}
let running = runtime_running_value == null ? runtime_is_running() : bool_text(runtime_running_value);
let enabled = service_enabled_value == null ? service_is_enabled() : bool_text(service_enabled_value);
if (initd_should_queue_config_change_reload(reason, CONFIG_CHANGE_REASON, running, active_service_action)) {
mark_pending_reload(PENDING_RELOAD_FILE, reason || "reload_queued");
return { action: "skip", job_id: "" };
}
if (initd_should_ignore_config_change_reload(reason, CONFIG_CHANGE_REASON, running, enabled)) {
return { action: "skip", job_id: "" };
}
if (!acquire_runtime_dir_lock(RELOAD_LOCK_DIR, owner_pid || owner_pid_value())) {
mark_pending_reload(PENDING_RELOAD_FILE, reason || "reload_busy");
return { action: "skip", job_id: "" };
}
unlink_file(SERVICE_TRIGGER_SYNC_FILE);
let job_id = begin_external_service_action("reload", "initd", owner_pid);
return { action: "run", job_id };
}
function reload_begin(reason, owner_pid, runtime_running_value, service_enabled_value) {
let plan = reload_begin_value(reason, owner_pid, runtime_running_value, service_enabled_value, null);
shell_assignment("INITD_RELOAD_ACTION", plan.action);
if (plan.action == "run")
shell_assignment("INITD_UI_JOB_ID", plan.job_id);
return 0;
}
function reload_finish_value(reason, job_id, status) {
status = int(status || 0);
finish_external_service_action("reload", job_id, status);
let sync = status == 0 && initd_should_sync_service_triggers(reason, CONFIG_CHANGE_REASON, SERVICE_TRIGGER_SYNC_FILE);
release_runtime_dir_lock(RELOAD_LOCK_DIR);
if (active_service_action_value() == "")
run_pending_reload_if_requested(PENDING_RELOAD_FILE, SERVICE_INIT);
return { status, sync };
}
function reload_finish(reason, job_id, status) {
let plan = reload_finish_value(reason, job_id, status);
shell_assignment("INITD_SYNC_SERVICE_TRIGGERS", plan.sync ? "1" : "0");
return plan.status;
}
function reload_service(reason, owner_pid) {
let plan = reload_begin_value(reason, owner_pid, null, null);
if (plan.action != "run")
return 0;
let status = command_status(command_from_args([ "env", "TACHYON_UI_ACTION_TRACKED=1", BIN_PATH, "reload", reason ]) + " >/dev/null 2>&1");
let finish = reload_finish_value(reason, plan.job_id, status);
if (finish.sync)
print("sync\n");
return finish.status;
}
function reload_release() {
release_runtime_dir_lock(RELOAD_LOCK_DIR);
return 0;
}
function trigger_plan(settings) {
settings = object_or_empty(settings);
let badwan_enabled = option(settings, "enable_badwan_interface_monitoring", "") == "1";
let badwan_interfaces = split(replace(trim(option(settings, "badwan_monitored_interfaces", "")), /[ \t\r\n]+/g, " "), " ");
let delay = option(settings, "badwan_reload_delay", "2000");
if (delay == "")
delay = "2000";
print("delay\t", delay, "\n");
print("config\tconfig.change\t", CONFIG_NAME, "\t", SERVICE_INIT, "\treload\t", CONFIG_CHANGE_REASON, "\n");
let retry_ifaces = [ "wan", "wwan", "lte", "modem" ];
for (let iface in retry_ifaces)
print("interface\tinterface.*.up\t", iface, "\t", SERVICE_INIT, "\tretry_start_on_wan_up\t\n");
if (badwan_enabled) {
for (let iface in badwan_interfaces) {
iface = trim(iface);
if (iface == "" || iface == "wan")
continue;
print("interface\tinterface.*.up\t", iface, "\t", SERVICE_INIT, "\treload\t\n");
}
}
}
let mode = ARGV[0] || "";
if (mode == "restore-dnsmasq-failsafe")
exit(restore_dnsmasq_failsafe());
else if (mode == "runtime-running")
exit(runtime_is_running() ? 0 : 1);
else if (mode == "status-service")
exit(status_service());
else if (mode == "retry-start-on-wan-up")
exit(retry_start_on_wan_up(ARGV[1]));
else if (mode == "retry-start-on-wan-up-action")
print(retry_start_on_wan_up_action(ARGV[1], ARGV[2], ARGV[3]), "\n");
else if (mode == "service-enabled")
exit(service_is_enabled() ? 0 : 1);
else if (mode == "mark-start-retry")
exit(mark_start_retry(ARGV[1], ARGV[2]) ? 0 : 1);
else if (mode == "clear-start-retry")
clear_start_retry(ARGV[1]);
else if (mode == "start-retry-pending")
exit(start_retry_pending(ARGV[1]) ? 0 : 1);
else if (mode == "schedule-start-retry")
exit(schedule_start_retry(ARGV[1], ARGV[2]) ? 0 : 1);
else if (mode == "cancel-scheduled-start-retry")
cancel_scheduled_start_retry(ARGV[1]);
else if (mode == "begin-action") {
let job_id = begin_external_service_action(ARGV[1], ARGV[2] || "initd", ARGV[3]);
if (job_id != "")
print(job_id, "\n");
}
else if (mode == "finish-action")
exit(finish_external_service_action(ARGV[1], ARGV[2], ARGV[3]));
else if (mode == "start-plan")
start_plan(ARGV[1], ARGV[2], uci_settings(), null);
else if (mode == "start-service")
exit(start_service(ARGV[1], ARGV[2]));
else if (mode == "start-verify-timeout-fixture")
print(start_verify_timeout(ARGV[1], ARGV[2] == null || ARGV[2] == "" ? null : int(ARGV[2])), "\n");
else if (mode == "start-plan-fixture") {
let settings = {
shutdown_correctly: ARGV[2],
enable_badwan_interface_monitoring: ARGV[3],
badwan_monitored_interfaces: ARGV[4]
};
start_plan(ARGV[1], ARGV[5] || "0", settings, ARGV[6] == null ? "1" : ARGV[6]);
}
else if (mode == "stop-plan")
stop_plan(ARGV[1], null);
else if (mode == "stop-plan-fixture")
stop_plan(ARGV[1] || "0", ARGV[2] == null ? "1" : ARGV[2]);
else if (mode == "stop-finish")
exit(stop_finish(ARGV[1], ARGV[2]));
else if (mode == "stop-service")
exit(stop_service(ARGV[1]));
else if (mode == "reload-begin")
exit(reload_begin(ARGV[1], ARGV[2], null, null));
else if (mode == "reload-begin-fixture") {
let plan = reload_begin_value(ARGV[1], ARGV[2] || "0", ARGV[3], ARGV[4], ARGV[5]);
shell_assignment("INITD_RELOAD_ACTION", plan.action);
if (plan.action == "run")
shell_assignment("INITD_UI_JOB_ID", plan.job_id);
exit(plan.action == "run" ? 0 : 1);
}
else if (mode == "reload-finish")
exit(reload_finish(ARGV[1], ARGV[2], ARGV[3]));
else if (mode == "reload-service")
exit(reload_service(ARGV[1], ARGV[2]));
else if (mode == "reload-release")
exit(reload_release());
else if (mode == "trigger-plan")
trigger_plan(uci_settings());
else if (mode == "trigger-plan-fixture")
trigger_plan(settings_from_fixture(ARGV[1]));
else if (mode == "initd-service-trigger-sync-requested")
exit(initd_service_trigger_sync_requested(ARGV[1]) ? 0 : 1);
else if (mode == "initd-guard-matches-current-config")
exit(initd_guard_matches_current_config(ARGV[1], ARGV[2], ARGV[3]) ? 0 : 1);
else if (mode == "initd-should-skip-internal-config-reload")
exit(initd_should_skip_internal_config_reload(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]) ? 0 : 1);
else if (mode == "initd-should-restore-dnsmasq-on-start-fixture")
exit(initd_should_restore_dnsmasq_on_start_from_value(ARGV[1], ARGV[2]) ? 0 : 1);
else if (mode == "initd-should-ignore-config-change-reload")
exit(initd_should_ignore_config_change_reload(ARGV[1], ARGV[2], ARGV[3], ARGV[4]) ? 0 : 1);
else if (mode == "initd-should-queue-config-change-reload")
exit(initd_should_queue_config_change_reload(ARGV[1], ARGV[2], ARGV[3], ARGV[4]) ? 0 : 1);
else if (mode == "initd-should-sync-service-triggers")
exit(initd_should_sync_service_triggers(ARGV[1], ARGV[2], ARGV[3]) ? 0 : 1);
else {
warn("Usage: service/initd.uc <operation> ...\n");
exit(1);
}