[package] name = "contract_probe" version = "0.1.0" author = "Chris Tusa " description = "FFI validation probe for zyginit Hammerhead libcontract bindings" license = "CDDL-1.0" [build] entry = "src/main.reef" output = "contract_probe" output_dir = "build" source_dirs = ["src"] # This probe exercises each libcontract FFI call in isolation to validate # the bindings match the real kernel interface on Hammerhead. # # On Linux (dev) — stubs return -1, build only validates compile/link: # clang -c ../../src/contract_linux_stubs.c -o build/contract_linux_stubs.o # reefc build --obj build/contract_linux_stubs.o # # On Hammerhead — real libcontract, actual validation: # reefc build -l contract /****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025-2026, Leafscale, LLC - https://www.leafscale.com Project: zyginit Filename: main.reef Authors: Chris Tusa License: Description: libcontract FFI validation probe — exercises each kernel contract call used by zyginit, in isolation, with PASS/FAIL output per probe. Run on Hammerhead after build with -l contract to verify zyginit's libcontract bindings match the kernel interface. ******************************************************************************/ module contract_probe import sys.process as process import sys.signal as signal import sys.poll as poll import time.clock as clock import core.str import io.dir as dir // ============================================================================ // FFI declarations — libcontract and POSIX // ============================================================================ extern "C" fn ct_tmpl_activate(fd: int): int extern "C" fn ct_tmpl_clear(fd: int): int extern "C" fn ct_tmpl_set_informative(fd: int, events: int): int extern "C" fn ct_tmpl_set_critical(fd: int, events: int): int extern "C" fn ct_pr_tmpl_set_fatal(fd: int, events: int): int extern "C" fn ct_pr_tmpl_set_param(fd: int, params: int): int extern "C" fn ct_status_read(fd: int, detail: int, statp: pointer): int extern "C" proc ct_status_free(stathdl: pointer) extern "C" fn ct_status_get_id(stathdl: pointer): int extern "C" fn ct_status_get_holder(stathdl: pointer): int extern "C" fn ct_status_get_state(stathdl: pointer): int extern "C" fn ct_ctl_abandon(fd: int): int extern "C" fn ct_ctl_adopt(fd: int): int extern "C" fn ct_event_read(fd: int, evtp: pointer): int extern "C" proc ct_event_free(evthdl: pointer) extern "C" fn ct_event_get_ctid(evthdl: pointer): int extern "C" fn ct_event_get_type(evthdl: pointer): int extern "C" fn sigsend(idtype: int, id: int, sig: int): int extern "C" fn open(path: string, flags: int): int extern "C" fn close(fd: int): int // From probe_helpers.c — errno and strerror bridges extern "C" fn probe_errno(): int extern "C" fn probe_strerror(err: int): string // ============================================================================ // Constants (mirror sys/contract.h, sys/contract/process.h, sys/procset.h) // ============================================================================ fn O_RDONLY(): int return 0 end O_RDONLY fn O_WRONLY(): int return 1 end O_WRONLY fn O_RDWR(): int return 2 end O_RDWR fn CT_PR_EV_EMPTY(): int return 1 end CT_PR_EV_EMPTY fn CT_PR_EV_HWERR(): int return 32 end CT_PR_EV_HWERR fn CT_PR_INHERIT(): int return 1 end CT_PR_INHERIT fn CT_PR_NOORPHAN(): int return 2 end CT_PR_NOORPHAN fn CTD_COMMON(): int return 0 end CTD_COMMON fn P_CTID(): int return 13 end P_CTID // ctstate_t enum (sys/contract.h) fn CTS_OWNED(): int return 0 end CTS_OWNED fn CTS_INHERITED(): int return 1 end CTS_INHERITED fn CTS_ORPHAN(): int return 2 end CTS_ORPHAN fn CTS_DEAD(): int return 3 end CTS_DEAD fn state_name(state: int): string if state == 0 return "OWNED" elif state == 1 return "INHERITED" elif state == 2 return "ORPHAN" elif state == 3 return "DEAD" end if return "UNKNOWN" end state_name fn TEMPLATE_PATH(): string return "/system/contract/process/template" end TEMPLATE_PATH fn LATEST_PATH(): string return "/system/contract/process/latest" end LATEST_PATH fn BUNDLE_PATH(): string return "/system/contract/process/bundle" end BUNDLE_PATH // ============================================================================ // Result tracking // ============================================================================ mut g_passed: int = 0 mut g_failed: int = 0 proc section(title: string) println("") println("=== " + title + " ===") end section proc pass(name: string) println(" PASS: " + name) g_passed = g_passed + 1 end pass proc fail(name: string, detail: string) println(" FAIL: " + name + " — " + detail) g_failed = g_failed + 1 end fail // Parse a non-negative int from a string. Returns -1 on any non-digit. fn str_to_int(s: string): int let len = str.length(s) if len <= 0 return 0 - 1 end if mut value = 0 mut i = 0 while i < len let ch = str.substring(s, i, 1) let digit = str.index_of("0123456789", ch) if digit < 0 return 0 - 1 end if value = value * 10 + digit i = i + 1 end while return value end str_to_int // Convert int to string (for error messages and path building) fn int_to_str(n: int): string if n == 0 return "0" end if mut value = n if n < 0 value = 0 - n end if mut result = "" while value > 0 let digit = value % 10 result = str.concat(str.substring("0123456789", digit, 1), result) value = value / 10 end while if n < 0 result = str.concat("-", result) end if return result end int_to_str // ============================================================================ // Shared helpers // ============================================================================ // Set up a contract template exactly as zyginit does. // Returns template fd on success, -1 on error. fn setup_template(): int let tmpl_fd = open(TEMPLATE_PATH(), O_RDWR()) if tmpl_fd < 0 return 0 - 1 end if let rc1 = ct_tmpl_set_informative(tmpl_fd, CT_PR_EV_EMPTY()) let rc2 = ct_tmpl_set_critical(tmpl_fd, CT_PR_EV_EMPTY() + CT_PR_EV_HWERR()) let rc3 = ct_pr_tmpl_set_fatal(tmpl_fd, CT_PR_EV_HWERR()) let rc4 = ct_pr_tmpl_set_param(tmpl_fd, CT_PR_INHERIT() + CT_PR_NOORPHAN()) if rc1 != 0 or rc2 != 0 or rc3 != 0 or rc4 != 0 close(tmpl_fd) return 0 - 1 end if if ct_tmpl_activate(tmpl_fd) != 0 close(tmpl_fd) return 0 - 1 end if return tmpl_fd end setup_template // Read the contract ID of the most recently created contract. fn get_latest_contract(): int let latest_fd = open(LATEST_PATH(), O_RDONLY()) if latest_fd < 0 return 0 - 1 end if unsafe let stathdl_buf = new [pointer](1) let rc = ct_status_read(latest_fd, CTD_COMMON(), stathdl_buf) close(latest_fd) if rc != 0 return 0 - 1 end if let stathdl = stathdl_buf[0] let ctid = ct_status_get_id(stathdl) ct_status_free(stathdl) return ctid end unsafe end get_latest_contract // Exec /usr/bin/sleep in the child. Seconds given as string. // Never returns on success — this is a one-way door for the child. proc child_sleep_and_exit(seconds: string) let argv = new [string](2) argv[0] = "sleep" argv[1] = seconds let _ = process.process_exec("/bin/sleep", argv) // If we got here, exec failed — exit cleanly with an unusual code process.exit_now(127) end child_sleep_and_exit // ============================================================================ // Probe 1: setup_template + open_bundle // ============================================================================ proc probe_1_template_and_bundle() section("Probe 1: setup_template + open_bundle") let tmpl_fd = setup_template() if tmpl_fd < 0 fail("setup_template", "returned -1 (check /system/contract/process/template perms)") return end if pass("setup_template returned valid fd (" + int_to_str(tmpl_fd) + ")") let bundle_fd = open(BUNDLE_PATH(), O_RDONLY()) if bundle_fd < 0 fail("open_bundle", "could not open " + BUNDLE_PATH()) let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) return end if pass("open_bundle returned valid fd (" + int_to_str(bundle_fd) + ")") let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) close(bundle_fd) end probe_1_template_and_bundle // ============================================================================ // Probe 2: fork under active template + get_latest_contract // ============================================================================ proc probe_2_fork_and_latest() section("Probe 2: fork + get_latest_contract") let tmpl_fd = setup_template() if tmpl_fd < 0 fail("setup (precondition)", "template failed") return end if let pid = process.process_fork() if pid < 0 fail("process_fork", "returned -1") let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) return end if if pid == 0 child_sleep_and_exit("1") end if let ctid = get_latest_contract() let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) if ctid <= 0 fail("get_latest_contract", "returned non-positive CTID (" + int_to_str(ctid) + ")") else pass("get_latest_contract returned CTID=" + int_to_str(ctid)) end if let _ = process.process_wait(pid) end probe_2_fork_and_latest // ============================================================================ // Probe 3: read_event receives CT_PR_EV_EMPTY when child exits // ============================================================================ proc probe_3_read_event_empty() section("Probe 3: read_event CT_PR_EV_EMPTY on child exit") let tmpl_fd = setup_template() if tmpl_fd < 0 fail("setup (precondition)", "template failed") return end if let bundle_fd = open(BUNDLE_PATH(), O_RDONLY()) if bundle_fd < 0 fail("setup (precondition)", "bundle failed") let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) return end if let pid = process.process_fork() if pid == 0 child_sleep_and_exit("1") end if let expected_ctid = get_latest_contract() let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) // Wait for child to exit so the contract transitions to empty let _ = process.process_wait(pid) // Poll for the resulting event (5s ceiling) poll.poll_clear() let idx = poll.poll_add(bundle_fd, poll.POLLIN()) let ready = poll.poll_wait(5000) if ready <= 0 fail("poll bundle_fd", "no event within 5s") close(bundle_fd) return end if if not poll.poll_readable(idx) fail("poll bundle_fd", "not readable") close(bundle_fd) return end if unsafe let evthdl_buf = new [pointer](1) let rc = ct_event_read(bundle_fd, evthdl_buf) if rc != 0 fail("ct_event_read", "returned " + int_to_str(rc)) close(bundle_fd) return end if let evthdl = evthdl_buf[0] let got_ctid = ct_event_get_ctid(evthdl) let got_type = ct_event_get_type(evthdl) ct_event_free(evthdl) if got_ctid == expected_ctid pass("ct_event_get_ctid matches expected (CTID=" + int_to_str(got_ctid) + ")") else fail("ct_event_get_ctid mismatch", "expected " + int_to_str(expected_ctid) + ", got " + int_to_str(got_ctid)) end if if got_type == CT_PR_EV_EMPTY() pass("ct_event_get_type is CT_PR_EV_EMPTY") else fail("ct_event_get_type", "got " + int_to_str(got_type) + ", expected " + int_to_str(CT_PR_EV_EMPTY())) end if end unsafe close(bundle_fd) end probe_3_read_event_empty // ============================================================================ // Probe 4: kill_contract actually signals the child // ============================================================================ proc probe_4_kill_contract() section("Probe 4: kill_contract (sigsend P_CTID)") let tmpl_fd = setup_template() if tmpl_fd < 0 fail("setup (precondition)", "template failed") return end if let pid = process.process_fork() if pid == 0 child_sleep_and_exit("30") end if let ctid = get_latest_contract() let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) if ctid <= 0 fail("kill_contract precondition", "no CTID") let _ = process.process_kill(pid, signal.SIGKILL()) let _ = process.process_wait(pid) return end if // Give the child time to reach sleep(2), then signal its contract clock.sleep_millis(200) let rc = sigsend(P_CTID(), ctid, signal.SIGTERM()) if rc != 0 let err = probe_errno() fail("sigsend(P_CTID, ctid=" + int_to_str(ctid) + ", SIGTERM)", "returned " + int_to_str(rc) + " errno=" + int_to_str(err) + " (" + probe_strerror(err) + ")") // Diagnostic: does sigsend work at all with P_PID? let rc2 = sigsend(0, pid, 0) // 0=P_PID, sig 0 = permission probe if rc2 == 0 println(" diag: sigsend(P_PID, pid, 0) works — issue is with P_CTID or ctid") else let err2 = probe_errno() println(" diag: sigsend(P_PID, pid, 0) also fails: errno=" + int_to_str(err2) + " (" + probe_strerror(err2) + ")") end if let _ = process.process_kill(pid, signal.SIGKILL()) let _ = process.process_wait(pid) return end if pass("sigsend(P_CTID, ctid=" + int_to_str(ctid) + ", SIGTERM) returned 0") let _ = process.process_wait(pid) if process.process_was_signaled() pass("child terminated by signal (" + int_to_str(process.process_term_signal()) + ")") else fail("child exit state", "expected signaled, got normal exit code=" + int_to_str(process.process_exit_code())) end if end probe_4_kill_contract // ============================================================================ // Probe 5: abandon_contract via ctl fd // ============================================================================ proc probe_5_abandon_contract() section("Probe 5: abandon_contract (ct_ctl_abandon)") let tmpl_fd = setup_template() if tmpl_fd < 0 fail("setup (precondition)", "template failed") return end if let pid = process.process_fork() if pid == 0 child_sleep_and_exit("1") end if let ctid = get_latest_contract() let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) if ctid <= 0 fail("abandon precondition", "no CTID") let _ = process.process_wait(pid) return end if let ctl_path = "/system/contract/process/" + int_to_str(ctid) + "/ctl" let ctl_fd = open(ctl_path, O_WRONLY()) if ctl_fd < 0 fail("open ctl fd", "could not open " + ctl_path) let _ = process.process_wait(pid) return end if pass("opened ctl fd for " + ctl_path) let rc = ct_ctl_abandon(ctl_fd) close(ctl_fd) if rc == 0 pass("ct_ctl_abandon returned 0") else fail("ct_ctl_abandon", "returned " + int_to_str(rc)) end if // Reap the child — still our child at the process level let _ = process.process_wait(pid) end probe_5_abandon_contract // ============================================================================ // Probe 6: enumerate CTFS + read full status (id, holder, state) // ============================================================================ proc probe_6_enumerate_contracts() section("Probe 6: enumerate /system/contract/process + read status fields") // Establish a known-live contract so the scan has a ground truth to find. let tmpl_fd = setup_template() if tmpl_fd < 0 fail("setup (precondition)", "template failed") return end if let pid = process.process_fork() if pid == 0 child_sleep_and_exit("3") end if let known_ctid = get_latest_contract() let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) if known_ctid <= 0 fail("enum precondition", "no known CTID") let _ = process.process_wait(pid) return end if // Let the child finish exec so its contract state is stable. clock.sleep_millis(200) let max_entries = 512 mut entries = new [string](max_entries) let count = dir.list_dir("/system/contract/process", entries, max_entries) if count < 0 fail("list_dir", "returned " + int_to_str(count)) let _ = process.process_kill(pid, signal.SIGTERM()) let _ = process.process_wait(pid) return end if pass("list_dir returned " + int_to_str(count) + " raw entries") mut scanned = 0 mut owned = 0 mut inherited = 0 mut orphan = 0 mut dead = 0 mut found_known = false mut known_state = 0 - 1 mut known_holder = 0 - 1 mut i = 0 while i < count let name = entries[i] if name != "." and name != ".." let parsed = str_to_int(name) if parsed > 0 let status_path = "/system/contract/process/" + name + "/status" let status_fd = open(status_path, O_RDONLY()) if status_fd >= 0 unsafe let stathdl_buf = new [pointer](1) let rc = ct_status_read(status_fd, CTD_COMMON(), stathdl_buf) close(status_fd) if rc == 0 let stathdl = stathdl_buf[0] let got_ctid = ct_status_get_id(stathdl) let holder = ct_status_get_holder(stathdl) let state = ct_status_get_state(stathdl) ct_status_free(stathdl) scanned = scanned + 1 if state == CTS_OWNED() owned = owned + 1 elif state == CTS_INHERITED() inherited = inherited + 1 elif state == CTS_ORPHAN() orphan = orphan + 1 else dead = dead + 1 end if if got_ctid == known_ctid found_known = true known_state = state known_holder = holder end if end if end unsafe end if end if end if i = i + 1 end while pass("read status on " + int_to_str(scanned) + " contracts") println(" state breakdown: OWNED=" + int_to_str(owned) + " INHERITED=" + int_to_str(inherited) + " ORPHAN=" + int_to_str(orphan) + " DEAD=" + int_to_str(dead)) if not found_known fail("locate known contract in CTFS", "CTID=" + int_to_str(known_ctid) + " not found among scanned entries") else pass("located known contract CTID=" + int_to_str(known_ctid) + " (state=" + state_name(known_state) + " holder=" + int_to_str(known_holder) + ")") let our_pid = process.getpid() if known_state == CTS_OWNED() and known_holder == our_pid pass("known contract: state=CTS_OWNED, holder=our PID (" + int_to_str(our_pid) + ")") else fail("known contract fields", "expected state=CTS_OWNED + holder=" + int_to_str(our_pid) + ", got state=" + state_name(known_state) + " holder=" + int_to_str(known_holder)) end if end if // Cleanup let _ = process.process_kill(pid, signal.SIGTERM()) let _ = process.process_wait(pid) end probe_6_enumerate_contracts // ============================================================================ // Probe 7: ct_ctl_adopt FFI linkage smoke test on an unowned contract // ============================================================================ proc probe_7_adopt_linkage() section("Probe 7: ct_ctl_adopt linkage smoke") // Part A: kernel denies ctl access to contracts we don't own. // Contract 1 is typically owned by sched / init. Opening its ctl // fd from an unrelated process should be denied (EACCES). let unowned_fd = open("/system/contract/process/1/ctl", O_WRONLY()) if unowned_fd >= 0 let _ = close(unowned_fd) fail("unowned ctl open", "unexpectedly opened /system/contract/process/1/ctl — expected EACCES") else let err = probe_errno() if err == 13 // EACCES pass("unowned ctl open correctly denied (EACCES)") else pass("unowned ctl open denied with errno=" + int_to_str(err) + " (" + probe_strerror(err) + ")") end if end if // Part B: ct_ctl_adopt FFI call path on one of our own contracts. // Create an owned contract, open its ctl fd (should succeed), then // try ct_ctl_adopt on it. Adopt must fail because the contract is // in state CTS_OWNED, not CTS_INHERITED — confirming the FFI call // reaches the kernel and the kernel enforces adopt semantics. let tmpl_fd = setup_template() if tmpl_fd < 0 fail("setup (precondition)", "template failed") return end if let pid = process.process_fork() if pid == 0 child_sleep_and_exit("2") end if let ctid = get_latest_contract() let _ = ct_tmpl_clear(tmpl_fd) close(tmpl_fd) if ctid <= 0 fail("adopt precondition", "no CTID") let _ = process.process_wait(pid) return end if let ctl_path = "/system/contract/process/" + int_to_str(ctid) + "/ctl" let ctl_fd = open(ctl_path, O_WRONLY()) if ctl_fd < 0 let err = probe_errno() fail("own ctl open", ctl_path + " errno=" + int_to_str(err) + " (" + probe_strerror(err) + ")") let _ = process.process_wait(pid) return end if pass("opened own ctl fd for CTID=" + int_to_str(ctid)) let rc = ct_ctl_adopt(ctl_fd) let err = probe_errno() close(ctl_fd) if rc == 0 fail("ct_ctl_adopt(owned)", "unexpectedly succeeded on CTS_OWNED contract") else pass("ct_ctl_adopt(owned) returned " + int_to_str(rc) + " errno=" + int_to_str(err) + " (" + probe_strerror(err) + ")") println(" — FFI linkage confirmed; adopt refused on non-inherited state") end if let _ = process.process_wait(pid) end probe_7_adopt_linkage // ============================================================================ // Entry point // ============================================================================ proc main() println("zyginit libcontract FFI probe") println("=============================") println("pid=" + int_to_str(process.getpid())) probe_1_template_and_bundle() probe_2_fork_and_latest() probe_3_read_event_empty() probe_4_kill_contract() probe_5_abandon_contract() probe_6_enumerate_contracts() probe_7_adopt_linkage() println("") println("=============================") println("Results: " + int_to_str(g_passed) + " passed, " + int_to_str(g_failed) + " failed") println("=============================") if g_failed > 0 process.exit_now(1) end if end main end module /* * probe_helpers.c — tiny helpers for the FFI probe * * Exposes errno readout and a strerror bridge so the Reef probe can report * the real kernel error when an FFI call fails. */ #include #include int probe_errno(void) { return errno; } const char *probe_strerror(int err) { return strerror(err); } [package] name = "replace_probe" version = "0.1.0" author = "Chris Tusa " description = "Unit-test probe for zyginit live-replace state-file format" license = "CDDL-1.0" [build] entry = "src/main.reef" output = "replace_probe" output_dir = "build" source_dirs = ["src", "../../src"] # This probe round-trips state.toml serialization without needing a # running zyginit. Models utils/contract_probe. # # NOTE: reefc does not use source_dirs from reef.toml for module resolution. # Module resolution falls back to src/ in CWD. Build from the zyginit root: # # Linux (from zyginit/): # clang -c src/helpers.c -o build/helpers.o # clang -c src/contract_linux_stubs.c -o build/contract_linux_stubs.o # reefc utils/replace_probe/src/main.reef \ # -o utils/replace_probe/build/replace_probe \ # --obj build/helpers.o --obj build/contract_linux_stubs.o /****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025-2026, Leafscale, LLC - https://www.leafscale.com Project: zyginit Filename: main.reef Authors: Chris Tusa License: Description: Probe — round-trip the state.toml format through replace.reef ******************************************************************************/ import supervisor import replace import io.dir as dir import io.file import core.str import sys.env as sysenv import sys.process as process mut g_pass: int = 0 mut g_fail: int = 0 proc check(label: string, ok: bool) if ok g_pass = g_pass + 1 println(" PASS: " + label) else g_fail = g_fail + 1 println(" FAIL: " + label) end if end check fn build_synthetic_table(): supervisor.ServiceTable let table = supervisor.new_service_table(8) // sshd: RUNNING with restart_count=2 and last_exit_code=0 let def_sshd = supervisor.make_def_with_name("sshd") let idx_sshd = supervisor.add_service(table, def_sshd) supervisor.adopt_runtime(table, idx_sshd, supervisor.STATE_RUNNING(), 100, 23, 2, 0, 1746204051) // syslogd: RUNNING with no restarts, last_exit_code = -1 (never exited) let def_syslog = supervisor.make_def_with_name("syslogd") let idx_syslog = supervisor.add_service(table, def_syslog) supervisor.adopt_runtime(table, idx_syslog, supervisor.STATE_RUNNING(), 101, 17, 0, 0 - 1, 1746204047) // cron: STOPPED — must NOT be serialized let def_cron = supervisor.make_def_with_name("cron") let idx_cron = supervisor.add_service(table, def_cron) supervisor.adopt_runtime(table, idx_cron, supervisor.STATE_STOPPED(), 0 - 1, 0 - 1, 0, 0, 0) return table end build_synthetic_table proc test_round_trip() println("== round-trip ==") let tmpdir = sysenv.get_env_or("TMPDIR", "/tmp") let probe_dir = str.concat(tmpdir, "/replace_probe") let _ = sysenv.set_env("ZYGINIT_RUN_DIR", probe_dir) if not dir.dir_exists(probe_dir) let _ = dir.create_dir_all(probe_dir) end if let table = build_synthetic_table() let serialized = replace.serialize_state(table, 1746204000) check("serialize returns true", serialized) let recovered = replace.recover_state(1746204000) check("recover boot_time matches", replace.rs_boot_time(recovered) == 1746204000) check("recover count == 2 (running only)", replace.rs_count(recovered) == 2) // Find services by name (order is implementation-defined) mut found_sshd = false mut found_syslog = false mut i = 0 while i < replace.rs_count(recovered) let svc = replace.rs_service(recovered, i) let name = replace.svc_name(svc) if name == "sshd" found_sshd = true check("sshd contract_id = 23", replace.svc_contract_id(svc) == 23) check("sshd restart_count = 2", replace.svc_restart_count(svc) == 2) check("sshd last_exit_code = 0", replace.svc_last_exit_code(svc) == 0) check("sshd last_start_time = 1746204051", replace.svc_last_start_time(svc) == 1746204051) elif name == "syslogd" found_syslog = true check("syslogd contract_id = 17", replace.svc_contract_id(svc) == 17) check("syslogd last_exit_code = -1", replace.svc_last_exit_code(svc) == 0 - 1) end if i = i + 1 end while check("found sshd", found_sshd) check("found syslogd", found_syslog) // After recover_state, state.toml is deleted check("state.toml deleted after recovery", not file.fileExists(replace.STATE_FILE_PATH())) end test_round_trip proc test_stale_boot_time() println("== stale state file ==") let tmpdir = sysenv.get_env_or("TMPDIR", "/tmp") let probe_dir = str.concat(tmpdir, "/replace_probe") let _ = sysenv.set_env("ZYGINIT_RUN_DIR", probe_dir) if not dir.dir_exists(probe_dir) let _ = dir.create_dir_all(probe_dir) end if let table = build_synthetic_table() let _ = replace.serialize_state(table, 1746204000) // Recover with DIFFERENT boot_time -> stale -> empty + deleted let recovered = replace.recover_state(1746999999) check("stale file -> empty state", replace.rs_is_empty(recovered)) check("stale file deleted", not file.fileExists(replace.STATE_FILE_PATH())) end test_stale_boot_time proc test_missing_file() println("== missing state file ==") let tmpdir = sysenv.get_env_or("TMPDIR", "/tmp") // Unique per-run dir to guarantee no leftover state.toml let probe_dir = str.concat(tmpdir, "/replace_probe_missing") let _ = sysenv.set_env("ZYGINIT_RUN_DIR", probe_dir) if not dir.dir_exists(probe_dir) let _ = dir.create_dir_all(probe_dir) end if // No need to unlink — the dir is fresh per invocation; if a prior // run left a state.toml here, the boot_time will differ and recovery // will treat it as stale (exercising a different code path). Either // way the test asserts "empty state" which holds. let recovered = replace.recover_state(1746204000) check("missing-or-stale file -> empty state", replace.rs_is_empty(recovered)) end test_missing_file proc test_preconditions() println("== preconditions ==") let table = supervisor.new_service_table(8) // sshd RUNNING — stable let def_sshd = supervisor.make_def_with_name("sshd") let idx_sshd = supervisor.add_service(table, def_sshd) supervisor.adopt_runtime(table, idx_sshd, supervisor.STATE_RUNNING(), 100, 23, 0, 0 - 1, 1746204051) let r1 = replace.check_preconditions(table) check("all-RUNNING -> empty report", str.length(r1) == 0) // Add a STOPPING service let def_cron = supervisor.make_def_with_name("cron") let idx_cron = supervisor.add_service(table, def_cron) supervisor.adopt_runtime(table, idx_cron, supervisor.STATE_STOPPING(), 101, 24, 0, 0 - 1, 1746204060) let r2 = replace.check_preconditions(table) check("STOPPING -> non-empty report", str.length(r2) > 0) check("report mentions cron", str.index_of(r2, "cron") >= 0) check("report mentions stopping", str.index_of(r2, "stopping") >= 0) // Add a WAITING service — should also be flagged transient let def_syslogd = supervisor.make_def_with_name("syslogd") let idx_syslogd = supervisor.add_service(table, def_syslogd) supervisor.adopt_runtime(table, idx_syslogd, supervisor.STATE_WAITING(), 0 - 1, 0 - 1, 1, 1, 0) let r3 = replace.check_preconditions(table) check("WAITING also flagged", str.index_of(r3, "syslogd") >= 0) check("multiple entries comma-separated", str.index_of(r3, ", ") >= 0) end test_preconditions proc main() test_round_trip() test_stale_boot_time() test_missing_file() test_preconditions() println("") println("--- " + int_to_str(g_pass) + " passed, " + int_to_str(g_fail) + " failed ---") if g_fail > 0 process.exit_now(1) end if end main fn int_to_str(n: int): string if n == 0 return "0" end if mut value = n mut neg = false if n < 0 value = 0 - n neg = true end if mut result = "" while value > 0 let digit = value % 10 result = str.concat(str.substring("0123456789", digit, 1), result) value = value / 10 end while if neg result = str.concat("-", result) end if return result end int_to_str