Skip to main content

strate_init/
main.rs

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