# sysv-wrapper Makefile. # # Build: make # Install: make install DESTDIR= (defaults to /) # Clean: make clean # # Installs the wrapper at /sbin/sysv-wrapper and symlinks the legacy # tool names to it. /sbin/init is NOT symlinked here — that path is # the zyginit daemon binary. If you want sysv-wrapper to handle # `init ` too, move zyginit aside (e.g. to /sbin/zyginit) and # symlink /sbin/init -> sysv-wrapper instead. CC ?= gcc CFLAGS ?= -O2 -Wall DESTDIR ?= SBIN = $(DESTDIR)/sbin WRAPPER = $(SBIN)/sysv-wrapper all: sysv-wrapper sysv-wrapper: wrapper.c version.h $(CC) $(CFLAGS) -o $@ wrapper.c install: sysv-wrapper install -d $(SBIN) install -m 0755 sysv-wrapper $(WRAPPER) ln -sf sysv-wrapper $(SBIN)/halt ln -sf sysv-wrapper $(SBIN)/reboot ln -sf sysv-wrapper $(SBIN)/poweroff ln -sf sysv-wrapper $(SBIN)/telinit clean: rm -f sysv-wrapper .PHONY: all install clean ELF>@@8@8@ @@@pp-- -==-==PPP ppp$$###StdPPP Ptd!!!44QtdRtd-==88GNUGNU-ATb䎸n /lib64/ld-linux-x86-64.so.2emi <b; [ R.4C"freestrdup__xpg_basename__libc_start_mainexeclstderrfprintf__cxa_finalizestrerrorfwritestrcmp__errno_locationlibc.so.6GLIBC_2.34GLIBC_2.2.5_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTablezui = =X@X@???? ??@@@@ @(@ 0@ 8@ @@ H@HH/HtH5/%/@%/h%/h%/h%/h%/h%/h%/h%/hp%/h`%/h P%/fAVSPHHH5H11H.L008H5HLHI1H[A^4@1I^HHPTE11H=S.f.H=.H.H9tH6.Ht H=.H5.H)HH?HHHtH.HtfD=u.u+UH=-Ht H=V.dM.]wUAWAVATSIH>[HIH'HÃ|UMfH5LtA<$-u7A|$Vu/A|$u'H=H1H1L_H5HtH5jHu~EH,H8IH51tH,HH=oINЃCH HcHH=DH5 HuH= gH5 Hu*H= CqH= 5cH= 'UH5 HuH=y 4H+H8H5j HH+H8IH5 1`[A\A^A_]HHiEEEout of memory --version%s %s 0.1.2telinithaltrebootpoweroffsysv-wrapper: unknown invocation '%s' usage: %s singlemultireload%s: unknown runlevel '%s' /sbin/zygctl%s: cannot exec %s: %s sysv-wrapper;4(hHP8zRx &D$4FJ w?;*3$"\@8tABB B(A0(B BBA$\BAA PABGNU`~FDO{"type":"deb","os":"ubuntu","name":"glibc","version":"2.42-0ubuntu3.1","architecture":"amd64"} z  ==op ? ooxooPo=6FVfvX@GCC: (Ubuntu 15.2.0-4ubuntu4) 15.2.0Ubuntu clang version 21.1.2 (2ubuntu6) # \(3p5H^`@j= =(#=!? *G r P@c`@j pP@} X@ h@v@&`@0`@ 9Las" Scrt1.o__abi_tagwrapper.crun_zygctlcrtstuff.cderegister_tm_clones__do_global_dtors_auxcompleted.0__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entry__FRAME_END___DYNAMIC__GNU_EH_FRAME_HDR_GLOBAL_OFFSET_TABLE_free@GLIBC_2.2.5__libc_start_main@GLIBC_2.34__errno_location@GLIBC_2.2.5_ITM_deregisterTMCloneTable_edata_fini__data_startstrcmp@GLIBC_2.2.5fprintf@GLIBC_2.2.5__gmon_start____dso_handle_IO_stdin_used__xpg_basename@GLIBC_2.2.5_end__bss_startmainfwrite@GLIBC_2.2.5__TMC_END___ITM_registerTMCloneTablestrdup@GLIBC_2.2.5strerror@GLIBC_2.2.5execl@GLIBC_2.2.5__cxa_finalize@GLIBC_2.2.5_initstderr@GLIBC_2.2.5.symtab.strtab.shstrtab.note.gnu.property.note.gnu.build-id.interp.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.got.text.fini.rodata.eh_frame_hdr.eh_frame.note.ABI-tag.note.package.init_array.fini_array.dynamic.got.plt.data.bss.commentPP .pp$AIo$S [ppcoPP"poxx0B  @    !!40"0"## (#(#p=-=-=-?/0 ?/hP@P0`@`00`0L0h 57(/****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025-2026, Leafscale, LLC - https://www.leafscale.com Project: zyginit Filename: version.h Authors: Chris Tusa License: Description: Generated version constant. Do not edit by hand — regenerated by scripts/bump-version.sh. ******************************************************************************/ #ifndef ZYGINIT_VERSION_H #define ZYGINIT_VERSION_H #define ZYGINIT_VERSION "0.2.7" #endif /* * sysv-wrapper — translate traditional sysv-init commands to zygctl calls. * * Installed as multiple symlinks pointing at the same binary; we dispatch * on argv[0] (basename) so each link behaves like the legacy tool. * * /sbin/init map runlevel char to a zygctl verb * /sbin/telinit same; telinit is the historical client name * /sbin/halt zygctl halt * /sbin/reboot zygctl reboot * /sbin/poweroff zygctl poweroff * * The wrapper itself does not connect to zyginit's socket — it just exec's * /sbin/zygctl with the right verb. zygctl handles the socket protocol. * * Note: when /sbin/init is run by the kernel as PID 1, it is the zyginit * daemon binary, NOT this wrapper. This wrapper lives at a different path * (or replaces /sbin/init only on systems where zyginit is at /sbin/zyginit). * On the current Hammerhead deploy /sbin/init IS zyginit; the wrapper * is therefore meant for /sbin/halt, /sbin/reboot, /sbin/poweroff, * /sbin/telinit. If you DO want /sbin/init to be this wrapper, install * zyginit at /sbin/zyginit and point the kernel boot path there. */ #include #include #include #include #include #include #include "version.h" #define ZYGCTL "/sbin/zygctl" static int run_zygctl(const char *verb) { execl(ZYGCTL, "zygctl", verb, (char *)NULL); /* execl returns only on failure */ (void) fprintf(stderr, "%s: cannot exec %s: %s\n", "sysv-wrapper", ZYGCTL, strerror(errno)); return (1); } static int init_dispatch(int argc, char **argv) { if (argc < 2) { (void) fprintf(stderr, "usage: %s \n", argv[0]); return (1); } switch (argv[1][0]) { case '0': return (run_zygctl("halt")); case '5': return (run_zygctl("poweroff")); case '6': return (run_zygctl("reboot")); case '1': case 's': case 'S': return (run_zygctl("single")); case '2': case '3': case '4': return (run_zygctl("multi")); case 'q': case 'Q': return (run_zygctl("reload")); default: (void) fprintf(stderr, "%s: unknown runlevel '%s'\n", argv[0], argv[1]); return (1); } } int main(int argc, char **argv) { const char *me; char *argv0_copy; /* basename(3) on illumos modifies its argument, so duplicate first */ argv0_copy = strdup(argv[0]); if (argv0_copy == NULL) { (void) fprintf(stderr, "out of memory\n"); return (1); } me = basename(argv0_copy); if (argc >= 2 && (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)) { (void) printf("%s %s\n", me, ZYGINIT_VERSION); free(argv0_copy); return (0); } if (strcmp(me, "init") == 0 || strcmp(me, "telinit") == 0) return (init_dispatch(argc, argv)); if (strcmp(me, "halt") == 0) return (run_zygctl("halt")); if (strcmp(me, "reboot") == 0) return (run_zygctl("reboot")); if (strcmp(me, "poweroff") == 0) return (run_zygctl("poweroff")); (void) fprintf(stderr, "sysv-wrapper: unknown invocation '%s'\n", me); return (1); } [package] name = "zygctl" version = "0.2.7" author = "Chris Tusa " description = "Administrative CLI for zyginit" license = "CDDL-1.0" [build] entry = "src/main.reef" output = "zygctl" output_dir = "build" source_dirs = ["src"] /****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025-2026, Leafscale, LLC - https://www.leafscale.com Project: zygctl Filename: main.reef Authors: Chris Tusa License: Description: Administrative CLI for zyginit service manager ******************************************************************************/ module zygctl import sys.args as args import sys.env as env import sys.process as process import net.unix as unix import io.file import io.dir import core.str import core.result as res import version extern "C" fn zyginit_symlink(target: string, linkpath: string): int extern "C" fn zyginit_isatty(fd: int): int fn client_should_be_plain(): bool if env.has_env("NO_COLOR") return true end if if zyginit_isatty(1) == 0 return true end if let term = env.get_env_or("TERM", "") if str.length(term) == 0 return true end if if term == "dumb" return true end if return false end client_should_be_plain fn SOCKET_PATH(): string return env.get_env_or("ZYGINIT_SOCKET", "/var/run/zyginit.sock") end SOCKET_PATH fn CONFIG_DIR(): string return env.get_env_or("ZYGINIT_CONFIG_DIR", "/etc/zyginit") end CONFIG_DIR proc main() let argc = args.count() if argc < 2 print_usage() return end if let command = args.get(1) // Local commands (no daemon needed) if command == "help" or command == "--help" or command == "-h" print_usage() return end if if command == "version" or command == "--version" println("zygctl " + version.VERSION()) return end if // Local commands that operate on the filesystem directly if command == "enable" if argc < 3 println("error: usage: zygctl enable ") return end if cmd_enable(args.get(2)) return end if if command == "disable" if argc < 3 println("error: usage: zygctl disable ") return end if cmd_disable(args.get(2)) return end if // single/multi are forwarded to zyginit's socket — handled there. // (Earlier versions of zygctl had local stubs for these; removed // now that the daemon implements runlevel transitions.) // TTY-aware dispatch for status and list: // When stdout is not a TTY (piped/redirected), or when NO_COLOR is set, // append PLAIN so the daemon sends plain text without ANSI escapes. if command == "status" mut req = "status" if client_should_be_plain() req = "status plain" end if if argc >= 3 req = str.concat(req, " ") req = str.concat(req, args.get(2)) end if req = str.concat(req, "\n") let connect_r = unix.unix_connect(SOCKET_PATH()) if res.is_err(connect_r) println("zygctl: cannot connect to zyginit at " + SOCKET_PATH()) println("zygctl: is zyginit running?") return end if let fd = res.unwrap_ok(connect_r) unix.unix_send(fd, req) let resp_r = unix.unix_recv(fd, 4096) unix.unix_close(fd) let resp = res.unwrap_or(resp_r, "") if str.length(resp) > 0 print(resp) end if return end if if command == "list" mut req = "list" if client_should_be_plain() req = "list plain" end if req = str.concat(req, "\n") let connect_r = unix.unix_connect(SOCKET_PATH()) if res.is_err(connect_r) println("zygctl: cannot connect to zyginit at " + SOCKET_PATH()) println("zygctl: is zyginit running?") return end if let fd = res.unwrap_ok(connect_r) unix.unix_send(fd, req) let resp_r = unix.unix_recv(fd, 4096) unix.unix_close(fd) let resp = res.unwrap_or(resp_r, "") if str.length(resp) > 0 print(resp) end if return end if // Build the message to send to zyginit mut message = command if argc >= 3 message = str.concat(message, " ") message = str.concat(message, args.get(2)) end if message = str.concat(message, "\n") // Connect to zyginit socket let sock_path = SOCKET_PATH() let connect_r = unix.unix_connect(sock_path) if res.is_err(connect_r) println("zygctl: cannot connect to zyginit at " + sock_path) println("zygctl: is zyginit running?") return end if let fd = res.unwrap_ok(connect_r) // Send command unix.unix_send(fd, message) // Read response let response_r = unix.unix_recv(fd, 4096) unix.unix_close(fd) let response = res.unwrap_or(response_r, "") if str.length(response) > 0 print(response) end if end main // ============================================================================ // Local commands — enable/disable (filesystem operations, no daemon needed) // ============================================================================ proc cmd_enable(name: string) let config_dir = CONFIG_DIR() let svc_dir = str.concat(str.concat(config_dir, "/services/"), name) if not dir.dir_exists(svc_dir) println("error: no service definition found: " + svc_dir) return end if let enabled_dir = str.concat(config_dir, "/enabled.d") // Create enabled.d/ if it does not exist if not dir.dir_exists(enabled_dir) if res.is_err(dir.create_dir(enabled_dir)) println("error: cannot create " + enabled_dir) return end if end if let link_path = str.concat(str.concat(enabled_dir, "/"), name) if file.fileExists(link_path) println(name + " is already enabled") return end if // Create symlink: enabled.d/ -> ../services/ let target = str.concat("../services/", name) let rc = zyginit_symlink(target, link_path) if rc < 0 println("error: failed to create symlink for " + name) return end if println(name + " enabled") end cmd_enable proc cmd_disable(name: string) let config_dir = CONFIG_DIR() let enabled_dir = str.concat(config_dir, "/enabled.d") let link_path = str.concat(str.concat(enabled_dir, "/"), name) if not file.fileExists(link_path) println(name + " is not enabled") return end if if res.is_ok(file.deleteFile(link_path)) println(name + " disabled") else println("error: failed to remove " + link_path) end if end cmd_disable // ============================================================================ // Usage // ============================================================================ proc print_usage() println("zygctl — zyginit service control") println("") println("Usage: zygctl [argument]") println("") println("Commands:") println(" status [name] Show service status (all or one)") println(" start Start a service") println(" stop Stop a service") println(" restart Restart a service") println(" enable Enable a service (symlink in enabled.d/)") println(" disable Disable a service (remove from enabled.d/)") println(" reload Reload service configuration") println(" replace [--wait=N]") println(" Replace running zyginit via execve(/sbin/init);") println(" keeps services alive across the swap") println(" log Show service output log") println(" list List all service definitions") println(" halt Cleanly stop all services and halt the system") println(" reboot Cleanly stop all services and reboot") println(" poweroff Cleanly stop all services and power off") println(" single Transition to single-user (recovery) mode") println(" multi Transition to multi-user mode") println(" version Show version") println(" help Show this help") println("") println("Environment:") println(" ZYGINIT_SOCKET Socket path (default: /var/run/zyginit.sock)") println(" ZYGINIT_CONFIG_DIR Config directory (default: /etc/zyginit)") end print_usage end module /* * symlink_wrapper.c — Wrapper for symlink() to avoid const char* FFI conflict * * Reef generates char* for string parameters, but POSIX symlink() expects * const char*. This thin wrapper bridges the two declarations. */ #include int zyginit_symlink(char *target, char *linkpath) { return symlink(target, linkpath); } int zyginit_isatty(int fd) { return isatty(fd) ? 1 : 0; } /****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025-2026, Leafscale, LLC - https://www.leafscale.com Project: zygctl Filename: version.reef Authors: Chris Tusa License: Description: Generated version constant. Do not edit by hand — regenerated by scripts/bump-version.sh. ******************************************************************************/ module version export fn VERSION(): string end export fn VERSION(): string return "0.2.7" end VERSION end module