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