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
17alloc_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]
31fn alloc_error(_layout: Layout) -> ! {
33 let _ = call::debug_log(b"[init] OOM Fatal\n");
34 call::exit(12);
35}
36
37#[derive(Clone, Copy)]
42struct OctalMode(u16);
43
44impl OctalMode {
45 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
85fn 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] }
94
95fn log(msg: &str) {
101 let _ = call::debug_log(msg.as_bytes());
102}
103
104fn 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
146struct 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
171fn parse_config(data: &str) -> Vec<SiloDef> {
173 #[derive(Clone, Copy)]
174 enum Section {
175 Silo,
176 Strate,
177 }
178
179 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
275fn 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
350fn 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
395fn 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#[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 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
492fn 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
505fn 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
519fn 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
536fn 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
547fn 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
558fn 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
584fn 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 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 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 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)]
1028pub 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]
1044fn panic(_info: &PanicInfo) -> ! {
1046 let _ = call::debug_log(b"[init] PANIC!\n");
1047 call::exit(255)
1048}