1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
|
#
# zyginit — Init system for Zygaena/Hammerhead
#
# Requires: Reef 0.4.0+
#
REEF = reef
REEF_FLAGS =
BINDIR = build
TARGETS = $(BINDIR)/zyginit $(BINDIR)/zygctl
all: $(TARGETS)
$(BINDIR)/zyginit: src/zyginit.reef src/config.reef src/depgraph.reef \
src/contract.reef src/supervisor.reef src/socket.reef
mkdir -p $(BINDIR)
$(REEF) build $(REEF_FLAGS) src/zyginit.reef -o $@
$(BINDIR)/zygctl: src/zygctl.reef
mkdir -p $(BINDIR)
$(REEF) build $(REEF_FLAGS) src/zygctl.reef -o $@
install: all
install -m 755 $(BINDIR)/zyginit $(DESTDIR)/sbin/zyginit
install -m 755 $(BINDIR)/zygctl $(DESTDIR)/sbin/zygctl
clean:
rm -rf $(BINDIR)
test:
$(REEF) test tests/
.PHONY: all install clean test
// config.reef — TOML service definition parser
//
// Parses /etc/zyginit.d/*.toml service definitions into ServiceDef structs.
// Uses Reef's built-in TOML parser: import encoding.toml
//
// This module should be implemented first — it has no illumos dependencies
// and can be tested on any platform.
import encoding.toml
import io.fs
// Service types
enum ServiceType {
Daemon, // long-running process, restarted on exit
Oneshot, // runs once, considered "started" after exit 0
Transient, // runs once, not tracked after exit
}
// Restart policy
enum RestartPolicy {
Always,
OnFailure,
Never,
}
// Stop method
enum StopMethod {
Contract, // signal all processes in contract (default)
Exec, // run a stop command
}
// Service definition — parsed from TOML
struct ServiceDef {
name: String,
description: String,
service_type: ServiceType,
// exec
start_cmd: String,
stop_cmd: Option<String>,
// stop
stop_method: StopMethod,
stop_signal: String, // "TERM", "HUP", etc.
stop_timeout: i32, // seconds
// dependencies
requires: Vec<String>, // hard dependencies — must be running
after: Vec<String>, // ordering — start after these, but don't require
// contract
contract_param: Vec<String>,
contract_fatal: Vec<String>,
// restart
restart_policy: RestartPolicy,
restart_delay: i32, // seconds
max_retries: i32, // -1 = unlimited
}
// Parse a single TOML file into a ServiceDef
fn parse_service(path: String) -> Result<ServiceDef, String> {
// TODO: implement
// 1. Read file contents
// 2. Parse TOML
// 3. Extract fields with defaults
// 4. Validate required fields
Err("not yet implemented")
}
// Scan /etc/zyginit.d/enabled.d/ and return list of enabled service paths
fn scan_enabled(config_dir: String) -> Result<Vec<String>, String> {
// TODO: implement
// 1. Read symlinks in enabled.d/
// 2. Resolve each to its .toml file
// 3. Return list of absolute paths
Err("not yet implemented")
}
// Load all enabled services
fn load_enabled_services(config_dir: String) -> Result<Vec<ServiceDef>, String> {
// TODO: implement
// 1. scan_enabled()
// 2. parse_service() for each
// 3. Return Vec of ServiceDefs
Err("not yet implemented")
}
// contract.reef — illumos process contract FFI bindings
//
// Provides Reef wrappers around libcontract for managing service processes.
// Process contracts are an illumos kernel feature — this module ONLY works
// on illumos/Hammerhead.
//
// Reference: illumos libcontract(3LIB), contract(5), process(5)
// Source reference: hammerhead/usr/src/cmd/svc/startd/contract.c
// C FFI declarations for libcontract
extern "C" {
// Template management
fn ct_tmpl_activate(fd: i32) -> i32;
fn ct_tmpl_clear(fd: i32) -> i32;
fn ct_tmpl_set_informative(fd: i32, events: u32) -> i32;
fn ct_tmpl_set_critical(fd: i32, events: u32) -> i32;
// Process contract template
fn ct_pr_tmpl_set_fatal(fd: i32, events: u32) -> i32;
fn ct_pr_tmpl_set_param(fd: i32, params: u32) -> i32;
// Contract status
fn ct_status_read(fd: i32, detail: i32, statp: *mut CtStatus) -> i32;
fn ct_status_free(status: *mut CtStatus);
fn ct_status_get_id(status: *CtStatus) -> i32;
// Contract control
fn ct_ctl_abandon(fd: i32) -> i32;
fn ct_ctl_adopt(fd: i32) -> i32;
// POSIX
fn open(path: *const u8, flags: i32) -> i32;
fn close(fd: i32) -> i32;
fn fork() -> i32;
fn execvp(file: *const u8, argv: *const *const u8) -> i32;
fn setsid() -> i32;
fn sigsend(idtype: i32, id: i64, sig: i32) -> i32;
fn poll(fds: *mut PollFd, nfds: i32, timeout: i32) -> i32;
}
// Contract event flags
const CT_PR_EV_EMPTY: u32 = 0x01; // all processes exited
const CT_PR_EV_FORK: u32 = 0x02; // process forked
const CT_PR_EV_EXIT: u32 = 0x04; // process exited
const CT_PR_EV_CORE: u32 = 0x08; // process dumped core
const CT_PR_EV_SIGNAL: u32 = 0x10; // fatal signal
const CT_PR_EV_HWERR: u32 = 0x20; // hardware error
// Contract parameter flags
const CT_PR_INHERIT: u32 = 0x01; // inherit contract on fork
const CT_PR_NOORPHAN: u32 = 0x02; // kill orphans on contract abandon
// Contract template path
const CTFS_PROCESS_TEMPLATE: &str = "/system/contract/process/template";
const CTFS_PROCESS_LATEST: &str = "/system/contract/process/latest";
const CTFS_PROCESS_BUNDLE: &str = "/system/contract/process/bundle";
// Managed contract handle
struct Contract {
id: i32, // contract ID from kernel
service_name: String,
bundle_fd: i32, // fd for event monitoring
}
// Set up a contract template for forking a service
fn setup_template() -> Result<i32, String> {
// TODO: implement
// 1. open(CTFS_PROCESS_TEMPLATE, O_RDWR)
// 2. ct_tmpl_set_informative(fd, CT_PR_EV_EMPTY)
// 3. ct_tmpl_set_critical(fd, CT_PR_EV_EMPTY | CT_PR_EV_HWERR)
// 4. ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR)
// 5. ct_pr_tmpl_set_param(fd, CT_PR_INHERIT | CT_PR_NOORPHAN)
// 6. ct_tmpl_activate(fd)
// 7. Return fd
Err("not yet implemented")
}
// After fork(), retrieve the contract ID for the new child
fn get_latest_contract() -> Result<i32, String> {
// TODO: implement
// 1. open(CTFS_PROCESS_LATEST, O_RDONLY)
// 2. ct_status_read()
// 3. ct_status_get_id()
// 4. Return contract ID
Err("not yet implemented")
}
// Open the contract bundle fd for poll()-based event monitoring
fn open_bundle() -> Result<i32, String> {
// TODO: implement
Err("not yet implemented")
}
// Kill all processes in a contract
fn kill_contract(contract_id: i32, signal: i32) -> Result<(), String> {
// TODO: implement using sigsend(P_CTID, contract_id, signal)
Err("not yet implemented")
}
// Re-adopt an existing contract (after zyginit restart)
fn adopt_contract(contract_id: i32) -> Result<(), String> {
// TODO: implement
Err("not yet implemented")
}
// depgraph.reef — Dependency graph and topological sort
//
// Builds a directed acyclic graph from ServiceDef dependencies,
// performs topological sort to determine boot order, and identifies
// services that can start in parallel.
//
// This module has no illumos dependencies — can be tested on any platform.
import config.{ServiceDef}
// A node in the dependency graph
struct DepNode {
name: String,
service: ServiceDef,
edges: Vec<String>, // services this depends on (requires + after)
in_degree: i32, // number of unsatisfied dependencies
}
// The full dependency graph
struct DepGraph {
nodes: Map<String, DepNode>,
}
// Build a dependency graph from a list of service definitions
fn build_graph(services: Vec<ServiceDef>) -> Result<DepGraph, String> {
// TODO: implement
// 1. Create a DepNode for each service
// 2. Add edges for requires + after dependencies
// 3. Detect missing dependencies (warn, don't fail)
// 4. Detect cycles (fail with clear error message)
Err("not yet implemented")
}
// Topological sort — returns services grouped by startup tier
// Services within the same tier can start in parallel.
//
// Returns: Vec<Vec<String>> — each inner Vec is a parallel tier
// Example: [["root-fs"], ["filesystem", "loopback"], ["network", "syslog"], ["sshd", "cron"]]
fn topo_sort(graph: DepGraph) -> Result<Vec<Vec<String>>, String> {
// TODO: implement using Kahn's algorithm
// 1. Find all nodes with in_degree == 0 → first tier
// 2. Remove those nodes, decrement in_degree of dependents
// 3. Repeat until all nodes placed
// 4. If nodes remain with in_degree > 0 → cycle detected
Err("not yet implemented")
}
// Check for dependency cycles — returns the cycle path if found
fn detect_cycle(graph: DepGraph) -> Option<Vec<String>> {
// TODO: implement using DFS with coloring
None
}
// socket.reef — Unix domain socket server for zygctl communication
//
// Listens on /var/run/zyginit.sock for commands from zygctl.
// Protocol: newline-delimited text commands, JSON responses.
//
// Commands:
// status → list all services and their states
// status <name> → status of a single service
// start <name> → start a service (transient, not persisted)
// stop <name> → stop a service
// restart <name> → stop then start
// enable <name> → create symlink in enabled.d/ (persisted)
// disable <name> → remove symlink from enabled.d/
// list → list all known service definitions
const SOCKET_PATH: &str = "/var/run/zyginit.sock";
// Create and bind the Unix domain socket
fn create_socket() -> Result<i32, String> {
// TODO: implement
// 1. socket(AF_UNIX, SOCK_STREAM, 0)
// 2. bind() to SOCKET_PATH
// 3. listen()
// 4. Return fd for poll()
Err("not yet implemented")
}
// Handle a client connection — read command, dispatch, respond
fn handle_client(client_fd: i32) -> Result<(), String> {
// TODO: implement
// 1. Read command line from client
// 2. Parse command + args
// 3. Dispatch to appropriate handler
// 4. Send response (JSON)
// 5. Close client fd
Err("not yet implemented")
}
// supervisor.reef — Service lifecycle management
//
// Handles starting, stopping, and restarting services using process
// contracts. Implements restart policies (always, on-failure, never).
import config.{ServiceDef, ServiceType, RestartPolicy, StopMethod}
import contract.{Contract, setup_template, get_latest_contract, kill_contract}
// Runtime state of a service
enum ServiceState {
Disabled, // not enabled
Waiting, // waiting for dependencies
Starting, // exec'd, waiting for confirmation
Running, // running (has contract)
Stopping, // stop signal sent, waiting for exit
Stopped, // cleanly stopped
Failed, // exited with error
Maintenance, // exceeded max retries
}
// Runtime info for a managed service
struct ServiceRuntime {
def: ServiceDef,
state: ServiceState,
contract: Option<Contract>,
pid: Option<i32>,
restart_count: i32,
last_exit_code: Option<i32>,
last_start_time: i64, // unix timestamp
}
// Start a service — fork, set up contract, exec
fn start_service(svc: ServiceDef) -> Result<ServiceRuntime, String> {
// TODO: implement
// 1. setup_template() for contract
// 2. fork()
// 3. In child: setsid(), exec(svc.start_cmd)
// 4. In parent: get_latest_contract(), ct_tmpl_clear()
// 5. Return ServiceRuntime with Running state
Err("not yet implemented")
}
// Stop a service
fn stop_service(rt: ServiceRuntime) -> Result<ServiceRuntime, String> {
// TODO: implement
// Based on svc.stop_method:
// Contract: kill_contract(contract_id, signal)
// Exec: fork/exec the stop command
// Wait up to stop_timeout, then SIGKILL
Err("not yet implemented")
}
// Handle a contract event (service exited)
fn handle_exit(rt: ServiceRuntime, exit_code: i32) -> ServiceRuntime {
// TODO: implement
// Based on restart_policy:
// Always: schedule restart after delay
// OnFailure: restart only if exit_code != 0
// Never: mark as Stopped
// Check max_retries — enter Maintenance if exceeded
rt
}
// zygctl.reef — Administrative CLI for zyginit
//
// Communicates with the running zyginit daemon over Unix domain socket
// at /var/run/zyginit.sock.
//
// Usage:
// zygctl status # list all services
// zygctl status <name> # status of one service
// zygctl start <name> # start a service (transient)
// zygctl stop <name> # stop a service
// zygctl restart <name> # restart a service
// zygctl enable <name> # enable at boot (creates symlink)
// zygctl disable <name> # disable at boot (removes symlink)
// zygctl list # list all service definitions
const SOCKET_PATH: &str = "/var/run/zyginit.sock";
fn main() -> i32 {
// TODO: implement
// 1. Parse command-line arguments
// 2. Connect to Unix domain socket
// 3. Send command
// 4. Read and display response
// 5. For enable/disable: also manage symlinks in enabled.d/
0
}
// zyginit.reef — Main init daemon (PID 1)
//
// This is the entry point for the Zygaena init system.
// It replaces SMF's svc.startd + svc.configd + master restarter.
//
// Boot sequence:
// 1. Scan /etc/zyginit.d/enabled.d/ for enabled services
// 2. Parse TOML definitions → ServiceDef structs
// 3. Build dependency graph → topological sort
// 4. Start services in parallel, respecting dependency order
// 5. Mount tmpfs, create /var/run/zyginit.sock
// 6. Enter poll(2) event loop
import config.{load_enabled_services, ServiceDef}
import depgraph.{build_graph, topo_sort}
import contract.{open_bundle}
import supervisor.{start_service, handle_exit, ServiceRuntime, ServiceState}
import socket.{create_socket, handle_client}
const CONFIG_DIR: &str = "/etc/zyginit.d";
const DIAG_DIR: &str = "/var/run/zyginit";
fn main() -> i32 {
// TODO: implement
// Phase 1: Load configuration
// let services = load_enabled_services(CONFIG_DIR)?;
// Phase 2: Build dependency graph and compute boot order
// let graph = build_graph(services)?;
// let tiers = topo_sort(graph)?;
// Phase 3: Start services tier by tier
// for tier in tiers {
// // Start all services in this tier in parallel
// for svc_name in tier {
// start_service(services[svc_name]);
// }
// // Wait for all services in tier to reach Running state
// }
// Phase 4: Set up communication
// let bundle_fd = open_bundle()?;
// let socket_fd = create_socket()?;
// let signal_fd = setup_signal_pipe()?; // self-pipe trick for signals
// Phase 5: Event loop
// loop {
// poll([bundle_fd, socket_fd, signal_fd], -1);
// // Handle contract events (service exits)
// // Handle socket connections (zygctl commands)
// // Handle signals (SIGCHLD, SIGTERM, SIGHUP for reload)
// }
0
}
|