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