Skip to main content

strate_init/
main.rs

1#![no_std]
2#![no_main]
3#![feature(alloc_error_handler)]
4
5extern crate alloc;
6
7use alloc::{string::String, vec::Vec};
8use core::{alloc::Layout, panic::PanicInfo};
9use strat9_syscall::{call, data::IpcMessage, number};
10
11const EAGAIN: usize = 11;
12const MAX_READ_BYTES: usize = 512 * 1024;
13const MAX_READ_ITERS: usize = 4096;
14const MAX_READ_EAGAIN: usize = 256;
15const SUPERVISOR_POLL_YIELDS: usize = 512;
16
17// ---------------------------------------------------------------------------
18// GLOBAL ALLOCATOR (BUMP + BRK)
19// ---------------------------------------------------------------------------
20
21alloc_freelist::define_freelist_brk_allocator!(
22    pub struct BumpAllocator;
23    brk = strat9_syscall::call::brk;
24    heap_max = 16 * 1024 * 1024;
25);
26
27#[global_allocator]
28static ALLOCATOR: BumpAllocator = BumpAllocator;
29
30#[alloc_error_handler]
31/// Implements alloc error.
32fn alloc_error(_layout: Layout) -> ! {
33    let _ = call::debug_log(b"[init] OOM Fatal\n");
34    call::exit(12);
35}
36
37// ---------------------------------------------------------------------------
38// SECURITY POLICY & PROFILES (From silo_security_model.md)
39// ---------------------------------------------------------------------------
40
41#[derive(Clone, Copy)]
42struct OctalMode(u16);
43
44impl OctalMode {
45    /// Returns whether subset of.
46    fn is_subset_of(&self, other: &OctalMode) -> bool {
47        let (s_c, s_h, s_r) = ((self.0 >> 6) & 0o7, (self.0 >> 3) & 0o7, self.0 & 0o7);
48        let (o_c, o_h, o_r) = ((other.0 >> 6) & 0o7, (other.0 >> 3) & 0o7, other.0 & 0o7);
49        (s_c & !o_c) == 0 && (s_h & !o_h) == 0 && (s_r & !o_r) == 0
50    }
51}
52
53struct FamilyProfile {
54    family: &'static str,
55    max_mode: OctalMode,
56}
57
58const FAMILY_PROFILES: &[FamilyProfile] = &[
59    FamilyProfile {
60        family: "SYS",
61        max_mode: OctalMode(0o777),
62    },
63    FamilyProfile {
64        family: "DRV",
65        max_mode: OctalMode(0o076),
66    },
67    FamilyProfile {
68        family: "FS",
69        max_mode: OctalMode(0o076),
70    },
71    FamilyProfile {
72        family: "NET",
73        max_mode: OctalMode(0o076),
74    },
75    FamilyProfile {
76        family: "WASM",
77        max_mode: OctalMode(0o006),
78    },
79    FamilyProfile {
80        family: "USR",
81        max_mode: OctalMode(0o004),
82    },
83];
84
85/// Returns family profile.
86fn get_family_profile(name: &str) -> &'static FamilyProfile {
87    for p in FAMILY_PROFILES {
88        if p.family == name {
89            return p;
90        }
91    }
92    &FAMILY_PROFILES[5] // Default to USR
93}
94
95// ---------------------------------------------------------------------------
96// UTILS
97// ---------------------------------------------------------------------------
98
99/// Implements log.
100fn log(msg: &str) {
101    let _ = call::debug_log(msg.as_bytes());
102}
103
104/// Reads file.
105fn read_file(path: &str) -> Result<Vec<u8>, &'static str> {
106    let fd = call::openat(0, path, 0x1, 0).map_err(|_| "open failed")?;
107    let mut out = Vec::new();
108    let mut chunk = [0u8; 4096];
109    let mut iters = 0usize;
110    let mut eagain = 0usize;
111    loop {
112        if out.len() >= MAX_READ_BYTES || iters >= MAX_READ_ITERS {
113            break;
114        }
115        iters += 1;
116        match call::read(fd as usize, &mut chunk) {
117            Ok(0) => break,
118            Ok(n) => {
119                let remain = MAX_READ_BYTES.saturating_sub(out.len());
120                let take = core::cmp::min(n, remain);
121                out.extend_from_slice(&chunk[..take]);
122                eagain = 0;
123                if take < n {
124                    break;
125                }
126            }
127            Err(e) if e.to_errno() == EAGAIN => {
128                eagain += 1;
129                if eagain > MAX_READ_EAGAIN {
130                    let _ = call::close(fd as usize);
131                    return Err("read timeout");
132                }
133                let _ = call::sched_yield();
134            }
135            Err(_) => {
136                let _ = call::close(fd as usize);
137                return Err("read failed");
138            }
139        }
140    }
141    let _ = call::close(fd as usize);
142    Ok(out)
143}
144
145// ---------------------------------------------------------------------------
146// HIERARCHICAL PARSER
147// ---------------------------------------------------------------------------
148
149struct StrateDef {
150    name: String,
151    binary: String,
152    stype: String,
153    target: String,
154}
155
156struct SiloDef {
157    name: String,
158    sid: u32,
159    family: String,
160    mode: String,
161    graphics_enabled: bool,
162    graphics_mode: String,
163    graphics_read_only: bool,
164    graphics_max_sessions: u16,
165    graphics_session_ttl_sec: u32,
166    graphics_turn_policy: String,
167    strates: Vec<StrateDef>,
168}
169
170/// Parses config.
171fn parse_config(data: &str) -> Vec<SiloDef> {
172    #[derive(Clone, Copy)]
173    enum Section {
174        Silo,
175        Strate,
176    }
177
178    /// Implements push default strate.
179    fn push_default_strate(silo: &mut SiloDef) {
180        silo.strates.push(StrateDef {
181            name: String::new(),
182            binary: String::new(),
183            stype: String::from("elf"),
184            target: String::from("default"),
185        });
186    }
187
188    let mut silos = Vec::new();
189    let mut current_silo: Option<SiloDef> = None;
190    let mut section = Section::Silo;
191
192    for raw_line in data.lines() {
193        let line = raw_line.trim();
194        if line.is_empty() || line.starts_with('#') {
195            continue;
196        }
197
198        if line == "[[silos]]" {
199            if let Some(s) = current_silo.take() {
200                silos.push(s);
201            }
202            current_silo = Some(SiloDef {
203                name: String::new(),
204                sid: 42,
205                family: String::from("USR"),
206                mode: String::from("000"),
207                graphics_enabled: false,
208                graphics_mode: String::new(),
209                graphics_read_only: false,
210                graphics_max_sessions: 0,
211                graphics_session_ttl_sec: 0,
212                graphics_turn_policy: String::from("auto"),
213                strates: Vec::new(),
214            });
215            section = Section::Silo;
216            continue;
217        }
218
219        if line == "[[silos.strates]]" {
220            if let Some(ref mut s) = current_silo {
221                push_default_strate(s);
222            }
223            section = Section::Strate;
224            continue;
225        }
226
227        if let Some(idx) = line.find('=') {
228            let key = line[..idx].trim();
229            let val = line[idx + 1..].trim().trim_matches('"');
230
231            if let Some(ref mut s) = current_silo {
232                match section {
233                    Section::Silo => match key {
234                        "name" => s.name = String::from(val),
235                        "sid" => s.sid = val.parse().unwrap_or(42),
236                        "family" => s.family = String::from(val),
237                        "mode" => s.mode = String::from(val),
238                        "graphics_enabled" => s.graphics_enabled = parse_toml_bool(val),
239                        "graphics_mode" => s.graphics_mode = String::from(val),
240                        "graphics_read_only" => s.graphics_read_only = parse_toml_bool(val),
241                        "graphics_max_sessions" => {
242                            s.graphics_max_sessions = val.parse().unwrap_or(0)
243                        }
244                        "graphics_session_ttl_sec" => {
245                            s.graphics_session_ttl_sec = val.parse().unwrap_or(0)
246                        }
247                        "graphics_turn_policy" => s.graphics_turn_policy = String::from(val),
248                        _ => {}
249                    },
250                    Section::Strate => {
251                        if s.strates.is_empty() {
252                            push_default_strate(s);
253                        }
254                        if let Some(strate) = s.strates.last_mut() {
255                            match key {
256                                "name" => strate.name = String::from(val),
257                                "binary" => strate.binary = String::from(val),
258                                "type" => strate.stype = String::from(val),
259                                "target_strate" => strate.target = String::from(val),
260                                _ => {}
261                            }
262                        }
263                    }
264                }
265            }
266        }
267    }
268    if let Some(s) = current_silo {
269        silos.push(s);
270    }
271    silos
272}
273
274/// Implements ensure required silos.
275fn ensure_required_silos(mut silos: Vec<SiloDef>) -> Vec<SiloDef> {
276    let has_bus = silos.iter().any(|s| s.name == "bus");
277    let has_network = silos.iter().any(|s| s.name == "network");
278    let has_dhcp = silos.iter().any(|s| s.name == "dhcp-client");
279
280    if !has_bus {
281        log("[init] Missing mandatory silo 'bus' in config, adding fallback\n");
282        silos.push(SiloDef {
283            name: String::from("bus"),
284            sid: 42,
285            family: String::from("DRV"),
286            mode: String::from("076"),
287            graphics_enabled: false,
288            graphics_mode: String::new(),
289            graphics_read_only: false,
290            graphics_max_sessions: 0,
291            graphics_session_ttl_sec: 0,
292            graphics_turn_policy: String::from("auto"),
293            strates: alloc::vec![StrateDef {
294                name: String::from("strate-bus"),
295                binary: String::from("/initfs/strate-bus"),
296                stype: String::from("elf"),
297                target: String::from("default"),
298            }],
299        });
300    }
301
302    if !has_network {
303        log("[init] Missing mandatory silo 'network' in config, adding fallback\n");
304        silos.push(SiloDef {
305            name: String::from("network"),
306            sid: 42,
307            family: String::from("NET"),
308            mode: String::from("076"),
309            graphics_enabled: false,
310            graphics_mode: String::new(),
311            graphics_read_only: false,
312            graphics_max_sessions: 0,
313            graphics_session_ttl_sec: 0,
314            graphics_turn_policy: String::from("auto"),
315            strates: alloc::vec![StrateDef {
316                name: String::from("strate-net"),
317                binary: String::from("/initfs/strate-net"),
318                stype: String::from("elf"),
319                target: String::from("default"),
320            }],
321        });
322    }
323
324    if !has_dhcp {
325        log("[init] Missing mandatory silo 'dhcp-client' in config, adding fallback\n");
326        silos.push(SiloDef {
327            name: String::from("dhcp-client"),
328            sid: 42,
329            family: String::from("NET"),
330            mode: String::from("076"),
331            graphics_enabled: false,
332            graphics_mode: String::new(),
333            graphics_read_only: false,
334            graphics_max_sessions: 0,
335            graphics_session_ttl_sec: 0,
336            graphics_turn_policy: String::from("auto"),
337            strates: alloc::vec![StrateDef {
338                name: String::from("dhcp-client"),
339                binary: String::from("/initfs/bin/dhcp-client"),
340                stype: String::from("elf"),
341                target: String::from("default"),
342            }],
343        });
344    }
345
346    silos
347}
348
349/// Implements load primary silos.
350fn load_primary_silos() -> Vec<SiloDef> {
351    log("[init] load_primary_silos: begin\n");
352    match read_file("/initfs/silo.toml") {
353        Ok(data_vec) => match core::str::from_utf8(&data_vec) {
354            Ok(data_str) => {
355                log("[init] load_primary_silos: parse /initfs/silo.toml\n");
356                let parsed = parse_config(data_str);
357                if parsed.is_empty() {
358                    log("[init] Empty /initfs/silo.toml, using embedded defaults\n");
359                    log("[init] load_primary_silos: parse embedded defaults\n");
360                    let parsed = parse_config(DEFAULT_SILO_TOML);
361                    log("[init] load_primary_silos: parsed embedded defaults count=");
362                    log_u32(parsed.len() as u32);
363                    log("\n");
364                    parsed
365                } else {
366                    log("[init] load_primary_silos: parsed file count=");
367                    log_u32(parsed.len() as u32);
368                    log("\n");
369                    parsed
370                }
371            }
372            Err(_) => {
373                log("[init] Invalid UTF-8 in /initfs/silo.toml, using embedded defaults\n");
374                log("[init] load_primary_silos: parse embedded defaults\n");
375                let parsed = parse_config(DEFAULT_SILO_TOML);
376                log("[init] load_primary_silos: parsed embedded defaults count=");
377                log_u32(parsed.len() as u32);
378                log("\n");
379                parsed
380            }
381        },
382        Err(_) => {
383            log("[init] Missing /initfs/silo.toml, using embedded defaults\n");
384            log("[init] load_primary_silos: parse embedded defaults\n");
385            let parsed = parse_config(DEFAULT_SILO_TOML);
386            log("[init] load_primary_silos: parsed embedded defaults count=");
387            log_u32(parsed.len() as u32);
388            log("\n");
389            parsed
390        }
391    }
392}
393
394/// Implements merge wasm test overlay.
395fn merge_wasm_test_overlay(silos: &mut Vec<SiloDef>) {
396    let data = match read_file("/initfs/wasm-test.toml") {
397        Ok(d) => d,
398        Err(_) => return,
399    };
400    let text = match core::str::from_utf8(&data) {
401        Ok(t) => t,
402        Err(_) => {
403            log("[init] Invalid UTF-8 in /initfs/wasm-test.toml, skipping overlay\n");
404            return;
405        }
406    };
407    let overlay = parse_config(text);
408    if overlay.is_empty() {
409        return;
410    }
411
412    let mut added = 0u32;
413    for o in overlay {
414        let exists = silos.iter().any(|s| s.name == o.name);
415        if exists {
416            continue;
417        }
418        silos.push(o);
419        added += 1;
420    }
421    if added > 0 {
422        log("[init] Applied wasm-test overlay silos: ");
423        log_u32(added);
424        log("\n");
425    }
426}
427
428// ---------------------------------------------------------------------------
429// EXECUTION LOGIC
430// ---------------------------------------------------------------------------
431
432#[repr(C)]
433struct SiloConfig {
434    mem_min: u64,
435    mem_max: u64,
436    cpu_shares: u32,
437    cpu_quota_us: u64,
438    cpu_period_us: u64,
439    cpu_affinity_mask: u64,
440    max_tasks: u32,
441    io_bw_read: u64,
442    io_bw_write: u64,
443    caps_ptr: u64,
444    caps_len: u64,
445    flags: u64,
446    sid: u32,
447    mode: u16,
448    family: u8,
449    cpu_features_required: u64,
450    cpu_features_allowed: u64,
451    xcr0_mask: u64,
452    graphics_max_sessions: u16,
453    graphics_session_ttl_sec: u32,
454    graphics_reserved: u16,
455}
456
457impl SiloConfig {
458    /// Creates a new instance.
459    const fn new(sid: u32, mode: u16, family: u8, flags: u64) -> Self {
460        Self {
461            mem_min: 0,
462            mem_max: 0,
463            cpu_shares: 0,
464            cpu_quota_us: 0,
465            cpu_period_us: 0,
466            cpu_affinity_mask: 0,
467            max_tasks: 0,
468            io_bw_read: 0,
469            io_bw_write: 0,
470            caps_ptr: 0,
471            caps_len: 0,
472            flags,
473            sid,
474            mode,
475            family,
476            cpu_features_required: 0,
477            cpu_features_allowed: u64::MAX,
478            xcr0_mask: 0,
479            graphics_max_sessions: 0,
480            graphics_session_ttl_sec: 0,
481            graphics_reserved: 0,
482        }
483    }
484}
485
486const SILO_FLAG_GRAPHICS: u64 = 1 << 1;
487const SILO_FLAG_WEBRTC_NATIVE: u64 = 1 << 2;
488const SILO_FLAG_GRAPHICS_READ_ONLY: u64 = 1 << 3;
489const SILO_FLAG_WEBRTC_TURN_FORCE: u64 = 1 << 4;
490
491/// Implements family to id.
492fn family_to_id(name: &str) -> Option<u8> {
493    match name {
494        "SYS" => Some(0),
495        "DRV" => Some(1),
496        "FS" => Some(2),
497        "NET" => Some(3),
498        "WASM" => Some(4),
499        "USR" => Some(5),
500        _ => None,
501    }
502}
503
504/// Parses mode octal.
505fn parse_mode_octal(s: &str) -> Option<u16> {
506    let trimmed = if let Some(rest) = s.strip_prefix("0o") {
507        rest
508    } else {
509        s
510    };
511    u16::from_str_radix(trimmed, 8).ok()
512}
513
514fn parse_toml_bool(s: &str) -> bool {
515    matches!(s, "true" | "True" | "TRUE" | "1" | "yes" | "on")
516}
517
518/// Implements log u32.
519fn log_u32(mut value: u32) {
520    let mut buf = [0u8; 10];
521    if value == 0 {
522        log("0");
523        return;
524    }
525    let mut i = buf.len();
526    while value > 0 {
527        i -= 1;
528        buf[i] = b'0' + (value % 10) as u8;
529        value /= 10;
530    }
531    let s = unsafe { core::str::from_utf8_unchecked(&buf[i..]) };
532    log(s);
533}
534
535/// Implements ipc call status.
536fn ipc_call_status(port: usize, msg: &mut IpcMessage) -> Result<u32, &'static str> {
537    call::ipc_call(port, msg).map_err(|_| "ipc_call failed")?;
538    Ok(u32::from_le_bytes([
539        msg.payload[0],
540        msg.payload[1],
541        msg.payload[2],
542        msg.payload[3],
543    ]))
544}
545
546/// Implements connect wasm service.
547fn connect_wasm_service(path: &str) -> Result<usize, &'static str> {
548    for _ in 0..256 {
549        if let Ok(h) = call::ipc_connect(path.as_bytes()) {
550            return Ok(h);
551        }
552        let _ = call::sched_yield();
553    }
554    Err("ipc_connect timeout")
555}
556
557/// Implements run wasm app.
558fn run_wasm_app(service_path: &str, wasm_path: &str) -> Result<(), u32> {
559    let port = connect_wasm_service(service_path).map_err(|_| 0xffff0000u32)?;
560
561    let mut load = IpcMessage::new(0x100);
562    let bytes = wasm_path.as_bytes();
563    let n = core::cmp::min(bytes.len(), load.payload.len().saturating_sub(1));
564    load.payload[0] = n as u8;
565    if n > 0 {
566        load.payload[1..1 + n].copy_from_slice(&bytes[..n]);
567    }
568    let load_status = ipc_call_status(port, &mut load).map_err(|_| 0xffff0001u32)?;
569    if load_status != 0 {
570        let _ = call::handle_close(port);
571        return Err(load_status);
572    }
573
574    let mut run = IpcMessage::new(0x102);
575    let run_status = ipc_call_status(port, &mut run).map_err(|_| 0xffff0002u32)?;
576    let _ = call::handle_close(port);
577    if run_status != 0 {
578        return Err(run_status);
579    }
580    Ok(())
581}
582
583/// Implements boot silos.
584fn boot_silos(silos: Vec<SiloDef>) {
585    let mut next_sys_sid = 100u32;
586    let mut next_usr_sid = 1000u32;
587    let mut silos = silos;
588
589    // Blocking launch order requirement:
590    // - "bus" must start first and expose /bus/pci/* before PCI-dependent silos.
591    // - other silos keep their relative order.
592    silos.sort_by_key(|s| if s.name == "bus" { 0u8 } else { 1u8 });
593
594    for s_def in silos {
595        let requested_mode = parse_mode_octal(&s_def.mode).unwrap_or(0);
596        let profile = get_family_profile(&s_def.family);
597
598        // Policy Validation
599        if !OctalMode(requested_mode).is_subset_of(&profile.max_mode) {
600            log("[init] SECURITY VIOLATION: silo ");
601            log(&s_def.name);
602            log(" exceeds family ceiling\n");
603            continue;
604        }
605        let family_id = match family_to_id(&s_def.family) {
606            Some(id) => id,
607            None => {
608                log("[init] Invalid family for silo ");
609                log(&s_def.name);
610                log("\n");
611                continue;
612            }
613        };
614
615        let final_sid = if s_def.sid == 42 {
616            match family_id {
617                0 | 1 | 2 | 3 => {
618                    let id = next_sys_sid;
619                    next_sys_sid += 1;
620                    id
621                }
622                _ => {
623                    let id = next_usr_sid;
624                    next_usr_sid += 1;
625                    id
626                }
627            }
628        } else {
629            s_def.sid
630        };
631
632        log(&alloc::format!(
633            "[init] Creating Silo: {} (SID={})\n",
634            s_def.name,
635            final_sid
636        ));
637
638        let mut flags = 0u64;
639        let graphics_mode = s_def.graphics_mode.as_str();
640        if s_def.graphics_enabled {
641            flags |= SILO_FLAG_GRAPHICS;
642            if graphics_mode == "webrtc-native" {
643                flags |= SILO_FLAG_WEBRTC_NATIVE;
644            }
645            if s_def.graphics_read_only {
646                flags |= SILO_FLAG_GRAPHICS_READ_ONLY;
647            }
648            if s_def.graphics_turn_policy == "force" {
649                flags |= SILO_FLAG_WEBRTC_TURN_FORCE;
650            }
651        }
652        let mut config = SiloConfig::new(final_sid, requested_mode, family_id, flags);
653        config.graphics_max_sessions = if s_def.graphics_enabled {
654            if s_def.graphics_max_sessions == 0 {
655                1
656            } else {
657                s_def.graphics_max_sessions
658            }
659        } else {
660            0
661        };
662        config.graphics_session_ttl_sec = if s_def.graphics_enabled {
663            if s_def.graphics_session_ttl_sec == 0 {
664                1800
665            } else {
666                s_def.graphics_session_ttl_sec
667            }
668        } else {
669            0
670        };
671
672        let silo_handle = match call::silo_create((&config as *const SiloConfig) as usize) {
673            Ok(h) => h,
674            Err(e) => {
675                log("[init] silo_create failed: ");
676                log(e.name());
677                log("\n");
678                continue;
679            }
680        };
681
682        if s_def.strates.is_empty() {
683            log("[init] No strates declared for silo ");
684            log(&s_def.name);
685            log("\n");
686            continue;
687        }
688
689        let mut runtime_targets: Vec<(String, String)> = Vec::new();
690
691        for str_def in s_def.strates {
692            match str_def.stype.as_str() {
693                "elf" | "wasm-runtime" => {
694                    log(&alloc::format!("[init]   -> Strate: {}\n", str_def.name));
695                    if let Ok(data) = read_file(&str_def.binary) {
696                        if data.len() >= 4 {
697                            log(&alloc::format!(
698                                "[init]     module magic {:02x}{:02x}{:02x}{:02x} size={}\n",
699                                data[0],
700                                data[1],
701                                data[2],
702                                data[3],
703                                data.len()
704                            ));
705                        } else {
706                            log(&alloc::format!(
707                                "[init]     module too small size={}\n",
708                                data.len()
709                            ));
710                        }
711                        let mod_h = match unsafe {
712                            strat9_syscall::syscall2(
713                                number::SYS_MODULE_LOAD,
714                                data.as_ptr() as usize,
715                                data.len(),
716                            )
717                        } {
718                            Ok(h) => h,
719                            Err(_) => {
720                                log(&alloc::format!(
721                                    "[init] module_load failed for {}\n",
722                                    str_def.binary
723                                ));
724                                continue;
725                            }
726                        };
727                        if let Err(e) = call::silo_attach_module(silo_handle, mod_h) {
728                            log(&alloc::format!(
729                                "[init] silo_attach_module failed: {}\n",
730                                e.name()
731                            ));
732                            continue;
733                        }
734                        match call::silo_start(silo_handle) {
735                            Err(e) => {
736                                log(&alloc::format!("[init] silo_start failed: {}\n", e.name()));
737                            }
738                            Ok(pid) => {
739                                register_supervised(&str_def.name, pid as u64);
740                                if str_def.stype == "wasm-runtime" {
741                                    runtime_targets
742                                        .push((str_def.name.clone(), str_def.target.clone()));
743                                }
744                            }
745                        }
746                    } else {
747                        log(&alloc::format!(
748                            "[init] failed to read binary {}\n",
749                            str_def.binary
750                        ));
751                    }
752                }
753                "wasm-app" => {
754                    log(&alloc::format!("[init]   -> Wasm-App: {}\n", str_def.name));
755                    let mut target_label = String::new();
756                    if !str_def.target.is_empty() {
757                        let mut found = false;
758                        for (runtime_name, runtime_label) in runtime_targets.iter() {
759                            if runtime_name == &str_def.target {
760                                target_label = runtime_label.clone();
761                                found = true;
762                                break;
763                            }
764                        }
765                        if !found {
766                            target_label = str_def.target.clone();
767                        }
768                    }
769                    if target_label.is_empty() {
770                        target_label = String::from("default");
771                    }
772
773                    let service_path = alloc::format!("/srv/strate-wasm/{}", target_label);
774                    match run_wasm_app(&service_path, &str_def.binary) {
775                        Ok(()) => {
776                            log(&alloc::format!(
777                                "[init]     wasm app started: {}\n",
778                                str_def.binary
779                            ));
780                        }
781                        Err(code) => {
782                            let line = alloc::format!(
783                                "[init]     wasm app failed: status=0x{:08x} (service={}, path={})\n",
784                                code,
785                                service_path,
786                                str_def.binary
787                            );
788                            log(&line);
789                        }
790                    }
791                }
792                _ => {}
793            }
794        }
795    }
796}
797
798const DEFAULT_SILO_TOML: &str = r#"
799[[silos]]
800name = "console-admin"
801family = "SYS"
802mode = "700"
803sid = 42
804[[silos.strates]]
805name = "console-admin"
806binary = "/initfs/console-admin"
807type = "elf"
808
809[[silos]]
810name = "bus"
811family = "DRV"
812mode = "076"
813sid = 42
814[[silos.strates]]
815name = "strate-bus"
816binary = "/initfs/strate-bus"
817type = "elf"
818probe_mode = "full"
819
820[[silos]]
821name = "network"
822family = "NET"
823mode = "076"
824sid = 42
825[[silos.strates]]
826name = "strate-net"
827binary = "/initfs/strate-net"
828type = "elf"
829
830[[silos]]
831name = "dhcp-client"
832family = "NET"
833mode = "076"
834sid = 42
835[[silos.strates]]
836name = "dhcp-client"
837binary = "/initfs/bin/dhcp-client"
838type = "elf"
839
840[[silos]]
841name = "telnet"
842family = "NET"
843mode = "076"
844sid = 42
845[[silos.strates]]
846name = "telnetd"
847binary = "/initfs/bin/telnetd"
848type = "elf"
849
850[[silos]]
851name = "ssh"
852family = "NET"
853mode = "076"
854sid = 42
855[[silos.strates]]
856name = "sshd"
857binary = "/initfs/bin/sshd"
858type = "elf"
859
860[[silos]]
861name = "web-admin"
862family = "NET"
863mode = "076"
864sid = 42
865graphics_enabled = true
866graphics_mode = "webrtc-native"
867graphics_max_sessions = 1
868graphics_session_ttl_sec = 1800
869graphics_turn_policy = "auto"
870[[silos.strates]]
871name = "web-admin"
872binary = "/initfs/bin/web-admin"
873type = "elf"
874
875[[silos]]
876name = "graphics-webrtc"
877family = "NET"
878mode = "076"
879sid = 42
880[[silos.strates]]
881name = "strate-webrtc"
882binary = "/initfs/strate-webrtc"
883type = "elf"
884"#;
885
886#[derive(Clone, Copy, PartialEq, Eq)]
887enum StrateHealth {
888    Ready,
889    Failed,
890}
891
892struct SupervisedChild {
893    name: [u8; 32],
894    name_len: u8,
895    pid: u64,
896    health: StrateHealth,
897    restart_count: u16,
898}
899
900impl SupervisedChild {
901    fn from_name(name: &str, pid: u64) -> Self {
902        let mut buf = [0u8; 32];
903        let n = core::cmp::min(name.len(), 32);
904        buf[..n].copy_from_slice(&name.as_bytes()[..n]);
905        Self {
906            name: buf,
907            name_len: n as u8,
908            pid,
909            health: StrateHealth::Ready,
910            restart_count: 0,
911        }
912    }
913
914    fn name_str(&self) -> &str {
915        unsafe { core::str::from_utf8_unchecked(&self.name[..self.name_len as usize]) }
916    }
917}
918
919static mut SUPERVISED: [Option<SupervisedChild>; 16] = [
920    None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
921];
922static mut SUPERVISED_COUNT: usize = 0;
923const SUPERVISED_CAPACITY: usize = 16;
924
925fn register_supervised(name: &str, pid: u64) {
926    unsafe {
927        if SUPERVISED_COUNT < SUPERVISED_CAPACITY {
928            let base = core::ptr::addr_of_mut!(SUPERVISED).cast::<Option<SupervisedChild>>();
929            let slot = base.add(SUPERVISED_COUNT);
930            slot.write(Some(SupervisedChild::from_name(name, pid)));
931            SUPERVISED_COUNT += 1;
932        }
933    }
934}
935
936fn supervisor_loop() -> ! {
937    log("[init] Supervisor: entering watch loop\n");
938    loop {
939        for _ in 0..SUPERVISOR_POLL_YIELDS {
940            let _ = call::sched_yield();
941        }
942
943        let mut wstatus: i32 = 0;
944        match call::waitpid(-1, Some(&mut wstatus), 1) {
945            // WNOHANG = 1
946            Ok(pid) if pid > 0 => {
947                let status = wstatus;
948                let mut found = false;
949                unsafe {
950                    let base =
951                        core::ptr::addr_of_mut!(SUPERVISED).cast::<Option<SupervisedChild>>();
952                    let count = SUPERVISED_COUNT;
953                    for idx in 0..count {
954                        let slot = base.add(idx);
955                        if let Some(child) = (*slot).as_mut() {
956                            if child.pid == pid as u64 {
957                                child.health = StrateHealth::Failed;
958                                found = true;
959                                log("[init] Supervisor: strate '");
960                                log(child.name_str());
961                                log("' exited (status=");
962                                log_u32(status as u32);
963                                log(", restarts=");
964                                log_u32(child.restart_count as u32);
965                                log(")\n");
966                                break;
967                            }
968                        }
969                    }
970                }
971                if !found {
972                    log("[init] Supervisor: unknown child pid=");
973                    log_u32(pid as u32);
974                    log(" exited status=");
975                    log_u32(status as u32);
976                    log("\n");
977                }
978            }
979            _ => {}
980        }
981    }
982}
983
984#[unsafe(no_mangle)]
985/// Implements start.
986pub unsafe extern "C" fn _start() -> ! {
987    log("[init] Strat9 Hierarchical Boot Starting\n");
988    log("[init] Stage: load primary silos\n");
989    let mut silos = load_primary_silos();
990    log("[init] Stage: merge wasm overlay\n");
991    merge_wasm_test_overlay(&mut silos);
992    log("[init] Stage: ensure required silos\n");
993    let silos = ensure_required_silos(silos);
994    log("[init] Stage: boot silos\n");
995    boot_silos(silos);
996    log("[init] Boot complete.\n");
997    supervisor_loop();
998}
999
1000#[panic_handler]
1001/// Implements panic.
1002fn panic(_info: &PanicInfo) -> ! {
1003    let _ = call::debug_log(b"[init] PANIC!\n");
1004    call::exit(255)
1005}