1#![no_std]
2#![no_main]
3#![feature(alloc_error_handler)]
4
5extern crate alloc;
6
7use 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
27alloc_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]
41fn alloc_error(_layout: Layout) -> ! {
43 let _ = call::debug_log(b"[init] OOM Fatal\n");
44 call::exit(12);
45}
46
47use phf::phf_map;
50
51static FAMILY_PROFILES: phf::Map<&'static str, SiloMode> = phf_map! {
52 "SYS" => SiloMode(0o777), "DRV" => SiloMode(0o076), "FS" => SiloMode(0o076), "NET" => SiloMode(0o076), "WASM" => SiloMode(0o006), };
58
59fn get_family_profile(name: &str) -> SiloMode {
62 FAMILY_PROFILES
63 .get(name)
64 .copied()
65 .unwrap_or(SiloMode(0o004))
66}
67
68fn log(msg: &str) {
76 let _ = call::debug_log(msg.as_bytes());
77}
78
79fn 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]; 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#[derive(Clone)]
129struct StrateDef {
130 name: String,
131 binary: String,
132 stype: String,
133 target: String,
134}
135
136#[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
154fn parse_config(data: &str) -> Vec<SiloDef> {
156 #[derive(Clone, Copy)]
157 enum Section {
158 Silo,
159 Strate,
160 }
161
162 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
257fn ensure_required_silos(mut silos: Vec<SiloDef>) -> Vec<SiloDef> {
259 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
344fn 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 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
366fn 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
408fn 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
442const 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
451fn 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
464fn 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
478fn 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
489fn 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
504fn 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
530fn boot_silos(mut silos: Vec<SiloDef>) {
532 let mut next_sys_sid = 100u32;
533 let mut next_usr_sid = 1000u32;
534
535 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 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 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 let _ = call::sched_yield();
981 }
982 }
983 }
984}
985
986#[unsafe(no_mangle)]
987pub 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}