1mod clear;
3mod cpuinfo;
4mod crypto;
5mod frame_meta;
6mod health;
7mod heap;
8mod reboot;
9mod scheduler;
10mod shutdown;
11mod silo_attach;
12#[path = "silo.rs"]
13mod silo_cmd;
14mod silo_limit;
15mod silos;
16mod strate;
17mod test_exec;
18mod test_mem;
19mod test_mem_region;
20mod test_mem_region_proc;
21mod test_mem_stressed;
22mod test_pid;
23mod test_syscalls;
24mod trace;
25mod version;
26mod wasm_run;
27pub use clear::cmd_clear;
28pub use cpuinfo::cmd_cpuinfo;
29pub use crypto::cmd_crypto;
30pub use frame_meta::cmd_frame_meta;
31pub use health::cmd_health;
32pub use heap::cmd_heap;
33pub use reboot::cmd_reboot;
34pub use scheduler::cmd_scheduler;
35pub use shutdown::cmd_shutdown;
36pub use silo_cmd::cmd_silo;
37pub use silos::cmd_silos;
38pub use strate::cmd_strate;
39pub use test_exec::cmd_test_exec;
40pub use test_mem::cmd_test_mem;
41pub use test_mem_region::cmd_test_mem_region;
42pub use test_mem_region_proc::cmd_test_mem_region_proc;
43pub use test_mem_stressed::cmd_test_mem_stressed;
44pub use test_pid::cmd_test_pid;
45pub use test_syscalls::cmd_test_syscalls;
46pub use trace::cmd_trace;
47pub use version::cmd_version;
48pub use wasm_run::cmd_wasm_run;
49
50use silo_attach::cmd_silo_attach;
51use silo_limit::cmd_silo_limit;
52
53use crate::{
54 arch::vga,
55 memory,
56 process::elf::load_and_run_elf,
57 shell::{
58 commands::top::Strat9RatatuiBackend,
59 output::{clear_screen, format_bytes},
60 ShellError,
61 },
62 shell_println, silo, vfs,
63};
64use alloc::{string::String, vec::Vec};
65use ratatui::{
66 layout::{Constraint, Direction, Layout},
67 style::{Color, Modifier, Style},
68 widgets::{Block, Borders, Cell, Paragraph, Row, Table},
69 Terminal,
70};
71
72const STRATE_USAGE: &str = "Usage: strate <list|spawn|start|stop|kill|destroy|rename|config|info|suspend|resume|events|pledge|unveil|sandbox|limit|attach|top|logs> ...";
73const SILO_USAGE: &str = "Usage: silo <list|spawn|start|stop|kill|destroy|rename|config|info|suspend|resume|events|pledge|unveil|sandbox|limit|attach|top|logs> ...";
74const DEFAULT_MANAGED_SILO_TOML: &str = r#"
75[[silos]]
76name = "console-admin"
77family = "SYS"
78mode = "700"
79sid = 42
80[[silos.strates]]
81name = "console-admin"
82binary = "/initfs/console-admin"
83type = "elf"
84
85[[silos]]
86name = "bus"
87family = "DRV"
88mode = "076"
89sid = 42
90[[silos.strates]]
91name = "strate-bus"
92binary = "/initfs/strate-bus"
93type = "elf"
94
95[[silos]]
96name = "network"
97family = "NET"
98mode = "076"
99sid = 42
100[[silos.strates]]
101name = "strate-net"
102binary = "/initfs/strate-net-silo"
103type = "elf"
104
105[[silos]]
106name = "dhcp-client"
107family = "NET"
108mode = "076"
109sid = 42
110[[silos.strates]]
111name = "dhcp-client"
112binary = "/initfs/dhcp-client"
113type = "elf"
114
115[[silos]]
116name = "telnet"
117family = "NET"
118mode = "076"
119sid = 42
120[[silos.strates]]
121name = "telnetd"
122binary = "/initfs/bin/telnetd"
123type = "elf"
124
125[[silos]]
126name = "web-admin"
127family = "NET"
128mode = "076"
129sid = 42
130graphics_enabled = true
131graphics_mode = "webrtc-native"
132graphics_max_sessions = 1
133graphics_session_ttl_sec = 1800
134graphics_turn_policy = "auto"
135[[silos.strates]]
136name = "web-admin"
137binary = "/initfs/bin/web-admin"
138type = "elf"
139
140[[silos]]
141name = "graphics-webrtc"
142family = "NET"
143mode = "076"
144sid = 42
145[[silos.strates]]
146name = "strate-webrtc"
147binary = "/initfs/strate-webrtc"
148type = "elf"
149"#;
150
151#[derive(Clone)]
152struct ManagedStrateDef {
153 name: String,
154 binary: String,
155 stype: String,
156 target: String,
157}
158
159#[derive(Clone)]
160struct ManagedSiloDef {
161 name: String,
162 sid: u32,
163 family: String,
164 mode: String,
165 cpu_features: String,
166 graphics_enabled: bool,
167 graphics_mode: String,
168 graphics_read_only: bool,
169 graphics_max_sessions: u16,
170 graphics_session_ttl_sec: u32,
171 graphics_turn_policy: String,
172 strates: Vec<ManagedStrateDef>,
173}
174
175fn parse_silo_toml(data: &str) -> Vec<ManagedSiloDef> {
177 #[derive(Clone, Copy)]
178 enum Section {
179 Silo,
180 Strate,
181 }
182
183 fn push_default_strate(silo: &mut ManagedSiloDef) {
185 silo.strates.push(ManagedStrateDef {
186 name: String::new(),
187 binary: String::new(),
188 stype: String::from("elf"),
189 target: String::from("default"),
190 });
191 }
192
193 let mut silos = Vec::new();
194 let mut current_silo: Option<ManagedSiloDef> = None;
195 let mut section = Section::Silo;
196
197 for raw_line in data.lines() {
198 let line = raw_line.trim();
199 if line.is_empty() || line.starts_with('#') {
200 continue;
201 }
202 if line == "[[silos]]" {
203 if let Some(s) = current_silo.take() {
204 silos.push(s);
205 }
206 current_silo = Some(ManagedSiloDef {
207 name: String::new(),
208 sid: 42,
209 family: String::from("USR"),
210 mode: String::from("000"),
211 cpu_features: String::new(),
212 graphics_enabled: false,
213 graphics_mode: String::new(),
214 graphics_read_only: false,
215 graphics_max_sessions: 0,
216 graphics_session_ttl_sec: 0,
217 graphics_turn_policy: String::from("auto"),
218 strates: Vec::new(),
219 });
220 section = Section::Silo;
221 continue;
222 }
223 if line == "[[silos.strates]]" {
224 if let Some(ref mut s) = current_silo {
225 push_default_strate(s);
226 }
227 section = Section::Strate;
228 continue;
229 }
230 if let Some(idx) = line.find('=') {
231 let key = line[..idx].trim();
232 let val = line[idx + 1..].trim().trim_matches('"');
233 if let Some(ref mut s) = current_silo {
234 match section {
235 Section::Silo => match key {
236 "name" => s.name = String::from(val),
237 "sid" => s.sid = val.parse().unwrap_or(42),
238 "family" => s.family = String::from(val),
239 "mode" => s.mode = String::from(val),
240 "cpu_features" => s.cpu_features = String::from(val),
241 "graphics_enabled" => {
242 s.graphics_enabled = matches!(val, "true" | "True" | "TRUE" | "1")
243 }
244 "graphics_mode" => s.graphics_mode = String::from(val),
245 "graphics_read_only" => {
246 s.graphics_read_only = matches!(val, "true" | "True" | "TRUE" | "1")
247 }
248 "graphics_max_sessions" => {
249 s.graphics_max_sessions = val.parse().unwrap_or(0)
250 }
251 "graphics_session_ttl_sec" => {
252 s.graphics_session_ttl_sec = val.parse().unwrap_or(0)
253 }
254 "graphics_turn_policy" => s.graphics_turn_policy = String::from(val),
255 _ => {}
256 },
257 Section::Strate => {
258 if s.strates.is_empty() {
259 push_default_strate(s);
260 }
261 if let Some(st) = s.strates.last_mut() {
262 match key {
263 "name" => st.name = String::from(val),
264 "binary" => st.binary = String::from(val),
265 "type" => st.stype = String::from(val),
266 "target_strate" => st.target = String::from(val),
267 _ => {}
268 }
269 }
270 }
271 }
272 }
273 }
274 }
275
276 if let Some(s) = current_silo {
277 silos.push(s);
278 }
279 silos
280}
281
282fn render_silo_toml(silos: &[ManagedSiloDef]) -> String {
284 use core::fmt::Write;
285 let mut out = String::new();
286 for (i, s) in silos.iter().enumerate() {
287 if i > 0 {
288 out.push('\n');
289 }
290 let _ = writeln!(out, "[[silos]]");
291 let _ = writeln!(out, "name = \"{}\"", s.name);
292 let _ = writeln!(out, "sid = {}", s.sid);
293 let _ = writeln!(out, "family = \"{}\"", s.family);
294 let _ = writeln!(out, "mode = \"{}\"", s.mode);
295 if !s.cpu_features.is_empty() {
296 let _ = writeln!(out, "cpu_features = \"{}\"", s.cpu_features);
297 }
298 if s.graphics_enabled {
299 let _ = writeln!(out, "graphics_enabled = true");
300 let mode = if s.graphics_mode.is_empty() {
301 "webrtc-native"
302 } else {
303 s.graphics_mode.as_str()
304 };
305 let _ = writeln!(out, "graphics_mode = \"{}\"", mode);
306 if s.graphics_read_only {
307 let _ = writeln!(out, "graphics_read_only = true");
308 }
309 if s.graphics_max_sessions != 0 {
310 let _ = writeln!(out, "graphics_max_sessions = {}", s.graphics_max_sessions);
311 }
312 if s.graphics_session_ttl_sec != 0 {
313 let _ = writeln!(
314 out,
315 "graphics_session_ttl_sec = {}",
316 s.graphics_session_ttl_sec
317 );
318 }
319 if s.graphics_turn_policy != "auto" && !s.graphics_turn_policy.is_empty() {
320 let _ = writeln!(out, "graphics_turn_policy = \"{}\"", s.graphics_turn_policy);
321 }
322 }
323 for st in &s.strates {
324 out.push('\n');
325 let _ = writeln!(out, "[[silos.strates]]");
326 let _ = writeln!(out, "name = \"{}\"", st.name);
327 let _ = writeln!(out, "binary = \"{}\"", st.binary);
328 let _ = writeln!(out, "type = \"{}\"", st.stype);
329 let _ = writeln!(out, "target_strate = \"{}\"", st.target);
330 }
331 }
332 out
333}
334
335fn read_silo_toml_from_initfs() -> Result<String, ShellError> {
337 let path = "/initfs/silo.toml";
338 match vfs::open(path, vfs::OpenFlags::READ) {
339 Ok(fd) => {
340 let data = vfs::read_all(fd).map_err(|_| ShellError::ExecutionFailed)?;
341 let _ = vfs::close(fd);
342 let text = core::str::from_utf8(&data).map_err(|_| ShellError::ExecutionFailed)?;
343 Ok(String::from(text))
344 }
345 Err(crate::syscall::error::SyscallError::NotFound) => Ok(String::new()),
346 Err(_) => Err(ShellError::ExecutionFailed),
347 }
348}
349
350fn load_managed_silos_with_source() -> (Vec<ManagedSiloDef>, &'static str) {
352 match read_silo_toml_from_initfs() {
353 Ok(text) => {
354 let parsed = parse_silo_toml(&text);
355 if parsed.is_empty() {
356 (
357 parse_silo_toml(DEFAULT_MANAGED_SILO_TOML),
358 "embedded-default",
359 )
360 } else {
361 (parsed, "/initfs/silo.toml")
362 }
363 }
364 Err(_) => (
365 parse_silo_toml(DEFAULT_MANAGED_SILO_TOML),
366 "embedded-default",
367 ),
368 }
369}
370
371fn family_uses_system_sid(family: &str) -> bool {
372 matches!(family, "SYS" | "DRV" | "NET" | "FS")
373}
374
375fn compute_managed_runtime_sids(managed: &[ManagedSiloDef]) -> Vec<(String, u32)> {
376 let mut ordered = managed.to_vec();
377 ordered.sort_by_key(|s| if s.name == "bus" { 0u8 } else { 1u8 });
378
379 let mut next_sys_sid = 100u32;
380 let mut next_usr_sid = 1000u32;
381 let mut mappings = Vec::new();
382
383 for silo in ordered {
384 let sid = if silo.sid == 42 {
385 if family_uses_system_sid(&silo.family) {
386 let id = next_sys_sid;
387 next_sys_sid += 1;
388 id
389 } else {
390 let id = next_usr_sid;
391 next_usr_sid += 1;
392 id
393 }
394 } else {
395 silo.sid
396 };
397 mappings.push((silo.name, sid));
398 }
399
400 mappings
401}
402
403fn managed_name_for_runtime_sid(
404 managed_runtime_sids: &[(String, u32)],
405 sid: u32,
406) -> Option<String> {
407 managed_runtime_sids
408 .iter()
409 .find(|(_, mapped_sid)| *mapped_sid == sid)
410 .map(|(name, _)| name.clone())
411}
412
413fn normalize_silo_selector(selector: &str, managed_runtime_sids: &[(String, u32)]) -> String {
414 if selector.parse::<u32>().is_ok() {
415 return String::from(selector);
416 }
417
418 managed_runtime_sids
419 .iter()
420 .find(|(name, _)| name == selector)
421 .map(|(_, sid)| alloc::format!("{}", sid))
422 .unwrap_or_else(|| String::from(selector))
423}
424
425fn normalize_current_silo_selector(selector: &str) -> String {
426 let (managed, _) = load_managed_silos_with_source();
427 let managed_runtime_sids = compute_managed_runtime_sids(&managed);
428 normalize_silo_selector(selector, &managed_runtime_sids)
429}
430
431fn push_unique(values: &mut Vec<String>, item: &str) {
433 if !values.iter().any(|v| v == item) {
434 values.push(String::from(item));
435 }
436}
437
438fn join_csv(values: &[String]) -> String {
440 if values.is_empty() {
441 return String::from("-");
442 }
443 let mut out = String::new();
444 for (i, v) in values.iter().enumerate() {
445 if i != 0 {
446 out.push_str(", ");
447 }
448 out.push_str(v);
449 }
450 out
451}
452
453struct SiloListRow {
454 sid: u32,
455 name: String,
456 state: String,
457 tasks: usize,
458 memory: String,
459 mode: u16,
460 label: String,
461 strates: String,
462}
463
464struct RuntimeStrateRow {
465 strate: String,
466 belongs_to: String,
467 status: String,
468}
469
470struct ConfigStrateRow {
471 strate: String,
472 belongs_to: String,
473}
474
475struct ConfigListRow {
476 sid: u32,
477 name: String,
478 family: String,
479 mode: String,
480 strates: String,
481}
482
483fn render_silo_table_ratatui(
485 runtime_rows: &[SiloListRow],
486 config_rows: &[ConfigListRow],
487 config_source: &str,
488) -> Result<bool, ShellError> {
489 if !vga::is_available() {
490 return Ok(false);
491 }
492
493 let backend = Strat9RatatuiBackend::new().map_err(|_| ShellError::ExecutionFailed)?;
494 let mut terminal = Terminal::new(backend).map_err(|_| ShellError::ExecutionFailed)?;
495 terminal.clear().map_err(|_| ShellError::ExecutionFailed)?;
496
497 let runtime_table_rows: Vec<Row> = runtime_rows
498 .iter()
499 .map(|r| {
500 let mut style = Style::default().fg(Color::White);
501 if r.strates == "-" {
502 style = style.fg(Color::LightRed);
503 } else {
504 style = style.fg(Color::LightGreen);
505 }
506 Row::new(alloc::vec![
507 Cell::from(alloc::format!("{}", r.sid)),
508 Cell::from(r.name.as_str()),
509 Cell::from(r.state.as_str()),
510 Cell::from(alloc::format!("{}", r.tasks)),
511 Cell::from(r.memory.as_str()),
512 Cell::from(alloc::format!("{:o}", r.mode)),
513 Cell::from(r.label.as_str()),
514 Cell::from(r.strates.as_str()),
515 ])
516 .style(style)
517 })
518 .collect();
519 let config_table_rows: Vec<Row> = config_rows
520 .iter()
521 .map(|r| {
522 Row::new(alloc::vec![
523 Cell::from(alloc::format!("{}", r.sid)),
524 Cell::from(r.name.as_str()),
525 Cell::from(r.family.as_str()),
526 Cell::from(r.mode.as_str()),
527 Cell::from(r.strates.as_str()),
528 ])
529 .style(Style::default().fg(Color::LightCyan))
530 })
531 .collect();
532
533 let frame_started = vga::begin_frame();
534 terminal
535 .draw(|f| {
536 let area = f.area();
537 let vertical = Layout::default()
538 .direction(Direction::Vertical)
539 .constraints([
540 Constraint::Length(2),
541 Constraint::Min(10),
542 Constraint::Length(10),
543 Constraint::Length(1),
544 ])
545 .split(area);
546
547 let title = Paragraph::new("Silo List")
548 .style(
549 Style::default()
550 .fg(Color::Cyan)
551 .add_modifier(Modifier::BOLD),
552 )
553 .block(Block::default().borders(Borders::BOTTOM).title("Strat9"));
554 f.render_widget(title, vertical[0]);
555
556 let widths = [
557 Constraint::Length(6),
558 Constraint::Length(12),
559 Constraint::Length(10),
560 Constraint::Length(7),
561 Constraint::Length(18),
562 Constraint::Length(6),
563 Constraint::Length(12),
564 Constraint::Min(20),
565 ];
566 let runtime_table = Table::new(runtime_table_rows, widths)
567 .header(
568 Row::new(alloc::vec![
569 Cell::from("SID"),
570 Cell::from("Name"),
571 Cell::from("State"),
572 Cell::from("Tasks"),
573 Cell::from("Memory"),
574 Cell::from("Mode"),
575 Cell::from("Label"),
576 Cell::from("Strates"),
577 ])
578 .style(
579 Style::default()
580 .fg(Color::Yellow)
581 .add_modifier(Modifier::BOLD),
582 ),
583 )
584 .block(
585 Block::default()
586 .borders(Borders::ALL)
587 .title("Runtime")
588 .border_style(Style::default().fg(Color::Green)),
589 )
590 .column_spacing(1);
591 f.render_widget(runtime_table, vertical[1]);
592
593 let config_widths = [
594 Constraint::Length(6),
595 Constraint::Length(14),
596 Constraint::Length(8),
597 Constraint::Length(8),
598 Constraint::Min(20),
599 ];
600 let config_table = Table::new(config_table_rows, config_widths)
601 .header(
602 Row::new(alloc::vec![
603 Cell::from("SID"),
604 Cell::from("Name"),
605 Cell::from("Family"),
606 Cell::from("Mode"),
607 Cell::from("Strates"),
608 ])
609 .style(
610 Style::default()
611 .fg(Color::Magenta)
612 .add_modifier(Modifier::BOLD),
613 ),
614 )
615 .block(
616 Block::default()
617 .borders(Borders::ALL)
618 .title(alloc::format!("Config ({})", config_source))
619 .border_style(Style::default().fg(Color::Magenta)),
620 )
621 .column_spacing(1);
622 f.render_widget(config_table, vertical[2]);
623
624 let footer = Paragraph::new("runtime vert=associe | runtime rouge=incomplet")
625 .style(Style::default().fg(Color::DarkGray));
626 f.render_widget(footer, vertical[3]);
627 })
628 .map_err(|_| ShellError::ExecutionFailed)?;
629 if frame_started {
630 vga::end_frame();
631 }
632 Ok(true)
633}
634
635fn render_strate_table_ratatui(
637 runtime_rows: &[RuntimeStrateRow],
638 config_rows: &[ConfigStrateRow],
639 config_source: &str,
640) -> Result<bool, ShellError> {
641 if !vga::is_available() {
642 return Ok(false);
643 }
644
645 let backend = Strat9RatatuiBackend::new().map_err(|_| ShellError::ExecutionFailed)?;
646 let mut terminal = Terminal::new(backend).map_err(|_| ShellError::ExecutionFailed)?;
647 terminal.clear().map_err(|_| ShellError::ExecutionFailed)?;
648
649 let runtime_table_rows: Vec<Row> = runtime_rows
650 .iter()
651 .map(|r| {
652 let style = if r.status == "config+runtime" {
653 Style::default().fg(Color::LightGreen)
654 } else {
655 Style::default().fg(Color::LightYellow)
656 };
657 Row::new(alloc::vec![
658 Cell::from(r.strate.as_str()),
659 Cell::from(r.belongs_to.as_str()),
660 Cell::from(r.status.as_str()),
661 ])
662 .style(style)
663 })
664 .collect();
665 let config_table_rows: Vec<Row> = config_rows
666 .iter()
667 .map(|r| {
668 Row::new(alloc::vec![
669 Cell::from(r.strate.as_str()),
670 Cell::from(r.belongs_to.as_str()),
671 ])
672 .style(Style::default().fg(Color::LightCyan))
673 })
674 .collect();
675
676 let frame_started = vga::begin_frame();
677 terminal
678 .draw(|f| {
679 let area = f.area();
680 let vertical = Layout::default()
681 .direction(Direction::Vertical)
682 .constraints([
683 Constraint::Length(2),
684 Constraint::Min(8),
685 Constraint::Length(8),
686 Constraint::Length(1),
687 ])
688 .split(area);
689
690 let title = Paragraph::new("Strate List")
691 .style(
692 Style::default()
693 .fg(Color::Cyan)
694 .add_modifier(Modifier::BOLD),
695 )
696 .block(Block::default().borders(Borders::BOTTOM).title("Strat9"));
697 f.render_widget(title, vertical[0]);
698
699 let runtime_widths = [
700 Constraint::Length(22),
701 Constraint::Min(24),
702 Constraint::Length(16),
703 ];
704 let runtime_table = Table::new(runtime_table_rows, runtime_widths)
705 .header(
706 Row::new(alloc::vec![
707 Cell::from("Strate"),
708 Cell::from("BelongsTo"),
709 Cell::from("Status"),
710 ])
711 .style(
712 Style::default()
713 .fg(Color::Yellow)
714 .add_modifier(Modifier::BOLD),
715 ),
716 )
717 .block(
718 Block::default()
719 .borders(Borders::ALL)
720 .title("Runtime")
721 .border_style(Style::default().fg(Color::Green)),
722 )
723 .column_spacing(2);
724 f.render_widget(runtime_table, vertical[1]);
725
726 let config_widths = [Constraint::Length(22), Constraint::Min(24)];
727 let config_table = Table::new(config_table_rows, config_widths)
728 .header(
729 Row::new(alloc::vec![Cell::from("Strate"), Cell::from("BelongsTo")]).style(
730 Style::default()
731 .fg(Color::Magenta)
732 .add_modifier(Modifier::BOLD),
733 ),
734 )
735 .block(
736 Block::default()
737 .borders(Borders::ALL)
738 .title(alloc::format!("Config ({})", config_source))
739 .border_style(Style::default().fg(Color::Magenta)),
740 )
741 .column_spacing(2);
742 f.render_widget(config_table, vertical[2]);
743
744 let footer = Paragraph::new("vert=config+runtime, jaune=runtime-only")
745 .style(Style::default().fg(Color::DarkGray));
746 f.render_widget(footer, vertical[3]);
747 })
748 .map_err(|_| ShellError::ExecutionFailed)?;
749 if frame_started {
750 vga::end_frame();
751 }
752 Ok(true)
753}
754
755fn write_silo_toml_to_initfs(text: &str) -> Result<(), ShellError> {
757 let path = "/initfs/silo.toml";
758 let fd = vfs::open(
759 path,
760 vfs::OpenFlags::WRITE | vfs::OpenFlags::CREATE | vfs::OpenFlags::TRUNCATE,
761 )
762 .map_err(|_| ShellError::ExecutionFailed)?;
763 let bytes = text.as_bytes();
764 let mut written = 0usize;
765 while written < bytes.len() {
766 let n = vfs::write(fd, &bytes[written..]).map_err(|_| ShellError::ExecutionFailed)?;
767 if n == 0 {
768 let _ = vfs::close(fd);
769 return Err(ShellError::ExecutionFailed);
770 }
771 written += n;
772 }
773 let _ = vfs::close(fd);
774 Ok(())
775}
776
777fn print_strate_state_for_sid(sid: u32) {
779 if let Some(s) = silo::list_silos_snapshot()
780 .into_iter()
781 .find(|s| s.id == sid)
782 {
783 shell_println!("state: {:?}", s.state);
784 } else {
785 shell_println!("state: <unknown>");
786 }
787}
788
789fn print_strate_usage() {
790 shell_println!("{}", STRATE_USAGE);
791 shell_println!(" strate list");
792 shell_println!(" strate spawn <path|type> [--label <l>] [--dev <p>] [--type elf|wasm]");
793 shell_println!(" strate start <id|label>");
794 shell_println!(" strate stop|kill|destroy <id|label>");
795 shell_println!(" strate rename <id|label> <new_label>");
796 shell_println!(" strate config show|add|remove ...");
797 shell_println!(" strate info <id|label>");
798 shell_println!(" strate suspend|resume <id|label>");
799 shell_println!(" strate events [id|label]");
800 shell_println!(" strate pledge <id|label> <octal_mode>");
801 shell_println!(" strate unveil <id|label> <path> <rwx>");
802 shell_println!(" strate sandbox <id|label>");
803 shell_println!(" strate top [--sort mem|tasks]");
804 shell_println!(" strate logs <id|label>");
805}
806
807fn print_silo_usage() {
808 shell_println!("{}", SILO_USAGE);
809 shell_println!(" silo list [--gui]");
810 shell_println!(" silo spawn <path|type> [--label <l>] [--dev <p>] [--type elf|wasm]");
811 shell_println!(" silo start <id|label>");
812 shell_println!(" silo stop|kill|destroy <id|label>");
813 shell_println!(" silo rename <id|label> <new_label>");
814 shell_println!(" silo config show|add|remove ...");
815 shell_println!(" silo info <id|label>");
816 shell_println!(" silo suspend|resume <id|label>");
817 shell_println!(" silo events [id|label]");
818 shell_println!(" silo pledge <id|label> <octal_mode>");
819 shell_println!(" silo unveil <id|label> <path> <rwx>");
820 shell_println!(" silo sandbox <id|label>");
821 shell_println!(" silo limit <id|label> <mem_max|mem_min|max_tasks|cpu_shares> <value>");
822 shell_println!(" silo attach <id|label>");
823 shell_println!(" silo top [--sort mem|tasks]");
824 shell_println!(" silo logs <id|label>");
825}
826
827pub(super) fn cmd_silo_impl(args: &[String]) -> Result<(), ShellError> {
828 if args.is_empty() {
829 print_silo_usage();
830 return Err(ShellError::InvalidArguments);
831 }
832 match args[0].as_str() {
833 "list" => cmd_silo_list(args),
834 "info" => cmd_silo_info(args),
835 "suspend" => cmd_silo_suspend(args),
836 "resume" => cmd_silo_resume(args),
837 "events" => cmd_silo_events(args),
838 "pledge" => cmd_silo_pledge(args),
839 "unveil" => cmd_silo_unveil(args),
840 "sandbox" => cmd_silo_sandbox(args),
841 "limit" => cmd_silo_limit(args),
842 "attach" => cmd_silo_attach(args),
843 "top" => cmd_silo_top(args),
844 "logs" => cmd_silo_logs(args),
845 "spawn" | "start" | "stop" | "kill" | "destroy" | "rename" | "config" => cmd_strate(args),
846 _ => {
847 print_silo_usage();
848 Err(ShellError::InvalidArguments)
849 }
850 }
851}
852
853pub(super) fn cmd_silos_impl(_args: &[String]) -> Result<(), ShellError> {
855 let args = [String::from("list")];
856 cmd_silo(&args)
857}
858
859pub(super) fn cmd_version_impl(_args: &[String]) -> Result<(), ShellError> {
861 shell_println!("Strat9-OS v0.1.0 (Bedrock)");
862 shell_println!("Build: x86_64-unknown-none");
863 shell_println!("Features: SMP, APIC, VirtIO, IPC, Schemes");
864 Ok(())
865}
866
867pub(super) fn cmd_clear_impl(_args: &[String]) -> Result<(), ShellError> {
869 clear_screen();
870 Ok(())
871}
872
873pub(super) fn cmd_cpuinfo_impl(_args: &[String]) -> Result<(), ShellError> {
875 shell_println!("CPU information:");
876
877 if crate::arch::apic::is_initialized() {
878 let lapic_id = crate::arch::apic::lapic_id();
879 let cpu_count = crate::arch::percpu::cpu_count();
880 shell_println!(" Current LAPIC ID: {}", lapic_id);
881 shell_println!(" CPU count: {}", cpu_count);
882 shell_println!(" APIC: Active");
883 } else {
884 shell_println!(" APIC: Not initialized");
885 shell_println!(" Mode: Legacy PIC");
886 }
887
888 shell_println!("");
889 Ok(())
890}
891
892pub(super) fn cmd_reboot_impl(_args: &[String]) -> Result<(), ShellError> {
894 shell_println!("Rebooting system...");
895 unsafe {
896 crate::arch::cli();
897 crate::arch::io::outb(0x64, 0xFE);
898 loop {
899 crate::arch::hlt();
900 }
901 }
902}
903
904pub(super) fn cmd_trace_impl(args: &[String]) -> Result<(), ShellError> {
906 if args.is_empty() || args[0].as_str() != "mem" {
907 shell_println!("Usage: trace mem on|off|dump [n]|clear|serial on|off|mask");
908 return Err(ShellError::InvalidArguments);
909 }
910
911 if args.len() < 2 {
912 shell_println!("Usage: trace mem on|off|dump [n]|clear|serial on|off|mask");
913 return Err(ShellError::InvalidArguments);
914 }
915
916 match args[1].as_str() {
917 "on" => {
918 crate::trace::enable(crate::trace::category::MEM_ALL);
919 shell_println!(
920 "trace mem: on (mask={:#x}, mode={})",
921 crate::trace::mask(),
922 crate::trace::mask_human(crate::trace::mask())
923 );
924 Ok(())
925 }
926 "off" => {
927 crate::trace::disable(crate::trace::category::MEM_ALL);
928 shell_println!(
929 "trace mem: off (mask={:#x}, mode={})",
930 crate::trace::mask(),
931 crate::trace::mask_human(crate::trace::mask())
932 );
933 Ok(())
934 }
935 "mask" => {
936 let stats = crate::trace::stats();
937 shell_println!(
938 "trace mem: mask={:#x} mode={} serial={} stored={} dropped={}",
939 crate::trace::mask(),
940 crate::trace::mask_human(crate::trace::mask()),
941 if crate::trace::serial_echo() {
942 "on"
943 } else {
944 "off"
945 },
946 stats.stored,
947 stats.dropped
948 );
949 Ok(())
950 }
951 "clear" => {
952 crate::trace::clear_all();
953 shell_println!("trace mem: buffers cleared");
954 Ok(())
955 }
956 "serial" => {
957 if args.len() != 3 {
958 shell_println!("Usage: trace mem serial on|off");
959 return Err(ShellError::InvalidArguments);
960 }
961 match args[2].as_str() {
962 "on" => {
963 crate::trace::set_serial_echo(true);
964 shell_println!("trace mem serial: on");
965 Ok(())
966 }
967 "off" => {
968 crate::trace::set_serial_echo(false);
969 shell_println!("trace mem serial: off");
970 Ok(())
971 }
972 _ => {
973 shell_println!("Usage: trace mem serial on|off");
974 Err(ShellError::InvalidArguments)
975 }
976 }
977 }
978 "dump" => {
979 let limit = if args.len() >= 3 {
980 args[2].parse::<usize>().unwrap_or(64)
981 } else {
982 64
983 };
984 let events = crate::trace::snapshot_all(limit);
985 let stats = crate::trace::stats();
986 shell_println!(
987 "trace mem dump: events={} stored={} dropped={}",
988 events.len(),
989 stats.stored,
990 stats.dropped
991 );
992 for e in events.iter() {
993 shell_println!(
994 " seq={} t={} cpu={} kind={} pid={} tid={} cr3={:#x} rip={:#x} vaddr={:#x} fl={:#x} a0={:#x} a1={:#x}",
995 e.seq,
996 e.ticks,
997 e.cpu,
998 crate::trace::kind_name(e.kind),
999 e.pid,
1000 e.tid,
1001 e.cr3,
1002 e.rip,
1003 e.vaddr,
1004 e.flags,
1005 e.arg0,
1006 e.arg1
1007 );
1008 }
1009 Ok(())
1010 }
1011 _ => {
1012 shell_println!("Usage: trace mem on|off|dump [n]|clear|serial on|off|mask");
1013 Err(ShellError::InvalidArguments)
1014 }
1015 }
1016}
1017
1018pub(super) fn cmd_test_pid_impl(_args: &[String]) -> Result<(), ShellError> {
1020 let path = "/initfs/test_pid";
1021 shell_println!("Launching {} ...", path);
1022
1023 let fd = match vfs::open(path, vfs::OpenFlags::READ) {
1024 Ok(fd) => fd,
1025 Err(e) => {
1026 shell_println!("open failed: {:?}", e);
1027 return Err(ShellError::ExecutionFailed);
1028 }
1029 };
1030
1031 let data = match vfs::read_all(fd) {
1032 Ok(d) => d,
1033 Err(e) => {
1034 let _ = vfs::close(fd);
1035 shell_println!("read failed: {:?}", e);
1036 return Err(ShellError::ExecutionFailed);
1037 }
1038 };
1039 let _ = vfs::close(fd);
1040
1041 shell_println!("ELF size: {} bytes", data.len());
1042 match load_and_run_elf(&data, "test_pid") {
1043 Ok(task_id) => {
1044 shell_println!("test_pid started (task id={})", task_id);
1045 Ok(())
1046 }
1047 Err(e) => {
1048 shell_println!("load_and_run_elf failed: {}", e);
1049 Err(ShellError::ExecutionFailed)
1050 }
1051 }
1052}
1053
1054pub(super) fn cmd_test_syscalls_impl(_args: &[String]) -> Result<(), ShellError> {
1056 let path = "/initfs/test_syscalls";
1057 shell_println!("Launching {} ...", path);
1058
1059 let fd = match vfs::open(path, vfs::OpenFlags::READ) {
1060 Ok(fd) => fd,
1061 Err(e) => {
1062 shell_println!("open failed: {:?}", e);
1063 return Err(ShellError::ExecutionFailed);
1064 }
1065 };
1066
1067 let data = match vfs::read_all(fd) {
1068 Ok(d) => d,
1069 Err(e) => {
1070 let _ = vfs::close(fd);
1071 shell_println!("read failed: {:?}", e);
1072 return Err(ShellError::ExecutionFailed);
1073 }
1074 };
1075 let _ = vfs::close(fd);
1076
1077 shell_println!("ELF size: {} bytes", data.len());
1078 match load_and_run_elf(&data, "test_syscalls") {
1079 Ok(task_id) => {
1080 shell_println!("test_syscalls started (task id={})", task_id);
1081 Ok(())
1082 }
1083 Err(e) => {
1084 shell_println!("load_and_run_elf failed: {}", e);
1085 Err(ShellError::ExecutionFailed)
1086 }
1087 }
1088}
1089
1090pub(super) fn cmd_test_mem_impl(_args: &[String]) -> Result<(), ShellError> {
1092 let path = "/initfs/test_mem";
1093 shell_println!("Launching {} ...", path);
1094
1095 let fd = match vfs::open(path, vfs::OpenFlags::READ) {
1096 Ok(fd) => fd,
1097 Err(e) => {
1098 shell_println!("open failed: {:?}", e);
1099 return Err(ShellError::ExecutionFailed);
1100 }
1101 };
1102
1103 let data = match vfs::read_all(fd) {
1104 Ok(d) => d,
1105 Err(e) => {
1106 let _ = vfs::close(fd);
1107 shell_println!("read failed: {:?}", e);
1108 return Err(ShellError::ExecutionFailed);
1109 }
1110 };
1111 let _ = vfs::close(fd);
1112
1113 shell_println!("ELF size: {} bytes", data.len());
1114 match load_and_run_elf(&data, "test_mem") {
1115 Ok(task_id) => {
1116 shell_println!("test_mem started (task id={})", task_id);
1117 Ok(())
1118 }
1119 Err(e) => {
1120 shell_println!("load_and_run_elf failed: {}", e);
1121 Err(ShellError::ExecutionFailed)
1122 }
1123 }
1124}
1125
1126pub(super) fn cmd_test_mem_stressed_impl(_args: &[String]) -> Result<(), ShellError> {
1128 let path = "/initfs/test_mem_stressed";
1129 shell_println!("Launching {} ...", path);
1130
1131 let fd = match vfs::open(path, vfs::OpenFlags::READ) {
1132 Ok(fd) => fd,
1133 Err(e) => {
1134 shell_println!("open failed: {:?}", e);
1135 return Err(ShellError::ExecutionFailed);
1136 }
1137 };
1138
1139 let data = match vfs::read_all(fd) {
1140 Ok(d) => d,
1141 Err(e) => {
1142 let _ = vfs::close(fd);
1143 shell_println!("read failed: {:?}", e);
1144 return Err(ShellError::ExecutionFailed);
1145 }
1146 };
1147 let _ = vfs::close(fd);
1148
1149 shell_println!("ELF size: {} bytes", data.len());
1150 match load_and_run_elf(&data, "test_mem_stressed") {
1151 Ok(task_id) => {
1152 shell_println!("test_mem_stressed started (task id={})", task_id);
1153 Ok(())
1154 }
1155 Err(e) => {
1156 shell_println!("load_and_run_elf failed: {}", e);
1157 Err(ShellError::ExecutionFailed)
1158 }
1159 }
1160}
1161
1162pub(super) fn cmd_test_mem_region_impl(_args: &[String]) -> Result<(), ShellError> {
1164 let path = "/initfs/test_mem_region";
1165 shell_println!("Launching {} ...", path);
1166
1167 let fd = match vfs::open(path, vfs::OpenFlags::READ) {
1168 Ok(fd) => fd,
1169 Err(e) => {
1170 shell_println!("open failed: {:?}", e);
1171 return Err(ShellError::ExecutionFailed);
1172 }
1173 };
1174
1175 let data = match vfs::read_all(fd) {
1176 Ok(d) => d,
1177 Err(e) => {
1178 let _ = vfs::close(fd);
1179 shell_println!("read failed: {:?}", e);
1180 return Err(ShellError::ExecutionFailed);
1181 }
1182 };
1183 let _ = vfs::close(fd);
1184
1185 shell_println!("ELF size: {} bytes", data.len());
1186 match load_and_run_elf(&data, "test_mem_region") {
1187 Ok(task_id) => {
1188 shell_println!("test_mem_region started (task id={})", task_id);
1189 Ok(())
1190 }
1191 Err(e) => {
1192 shell_println!("load_and_run_elf failed: {}", e);
1193 Err(ShellError::ExecutionFailed)
1194 }
1195 }
1196}
1197
1198pub(super) fn cmd_test_mem_region_proc_impl(_args: &[String]) -> Result<(), ShellError> {
1200 let path = "/initfs/test_mem_region_proc";
1201 shell_println!("Launching {} ...", path);
1202
1203 let fd = match vfs::open(path, vfs::OpenFlags::READ) {
1204 Ok(fd) => fd,
1205 Err(e) => {
1206 shell_println!("open failed: {:?}", e);
1207 return Err(ShellError::ExecutionFailed);
1208 }
1209 };
1210
1211 let data = match vfs::read_all(fd) {
1212 Ok(d) => d,
1213 Err(e) => {
1214 let _ = vfs::close(fd);
1215 shell_println!("read failed: {:?}", e);
1216 return Err(ShellError::ExecutionFailed);
1217 }
1218 };
1219 let _ = vfs::close(fd);
1220
1221 shell_println!("ELF size: {} bytes", data.len());
1222 match load_and_run_elf(&data, "test_mem_region_proc") {
1223 Ok(task_id) => {
1224 shell_println!("test_mem_region_proc started (task id={})", task_id);
1225 Ok(())
1226 }
1227 Err(e) => {
1228 shell_println!("load_and_run_elf failed: {}", e);
1229 Err(ShellError::ExecutionFailed)
1230 }
1231 }
1232}
1233
1234fn initfs_or_boot_module(path: &'static str, module: Option<(u64, u64)>) -> Option<&'static [u8]> {
1236 if let Some(bytes) = vfs::get_initfs_file_bytes(path) {
1237 return Some(bytes);
1238 }
1239
1240 let (base, size) = module?;
1241 if base == 0 || size == 0 {
1242 return None;
1243 }
1244
1245 let base_virt = memory::phys_to_virt(base) as *const u8;
1246 if vfs::register_initfs_file(path, base_virt, size as usize).is_err() {
1247 return None;
1248 }
1249
1250 vfs::get_initfs_file_bytes(path)
1251}
1252
1253pub(super) fn cmd_test_exec_impl(_args: &[String]) -> Result<(), ShellError> {
1255 let path = "/initfs/test_exec";
1256 shell_println!("Launching {} ...", path);
1257
1258 let boot_bytes = vfs::get_initfs_file_bytes(path);
1261
1262 if let Some(data) = boot_bytes {
1263 shell_println!("ELF size: {} bytes", data.len());
1264 return match load_and_run_elf(data, "test_exec") {
1265 Ok(task_id) => {
1266 shell_println!("test_exec started (task id={})", task_id);
1267 Ok(())
1268 }
1269 Err(e) => {
1270 shell_println!("load_and_run_elf failed: {}", e);
1271 Err(ShellError::ExecutionFailed)
1272 }
1273 };
1274 }
1275
1276 let fd = match vfs::open(path, vfs::OpenFlags::READ) {
1277 Ok(fd) => fd,
1278 Err(e) => {
1279 shell_println!("open failed: {:?}", e);
1280 shell_println!("test_exec not found; ensure it's in the FAT32 boot partition");
1281 return Err(ShellError::ExecutionFailed);
1282 }
1283 };
1284
1285 let data = match vfs::read_all(fd) {
1286 Ok(d) => d,
1287 Err(e) => {
1288 let _ = vfs::close(fd);
1289 shell_println!("read failed: {:?}", e);
1290 return Err(ShellError::ExecutionFailed);
1291 }
1292 };
1293 let _ = vfs::close(fd);
1294
1295 shell_println!("ELF size: {} bytes", data.len());
1296 match load_and_run_elf(&data, "test_exec") {
1297 Ok(task_id) => {
1298 shell_println!("test_exec started (task id={})", task_id);
1299 Ok(())
1300 }
1301 Err(e) => {
1302 shell_println!("load_and_run_elf failed: {}", e);
1303 Err(ShellError::ExecutionFailed)
1304 }
1305 }
1306}
1307
1308fn cmd_silo_list(args: &[String]) -> Result<(), ShellError> {
1310 let mut want_gui = false;
1311 for arg in args.iter().skip(1) {
1312 match arg.as_str() {
1313 "--gui" => want_gui = true,
1314 _ => {
1315 shell_println!("Usage: silo list [--gui]");
1316 return Err(ShellError::InvalidArguments);
1317 }
1318 }
1319 }
1320
1321 let (managed, managed_source) = load_managed_silos_with_source();
1322 let managed_runtime_sids = compute_managed_runtime_sids(&managed);
1323 let mut silos = silo::list_silos_snapshot();
1324 silos.sort_by_key(|s| s.id);
1325
1326 let mut rows: Vec<SiloListRow> = Vec::new();
1327 let mut config_rows: Vec<ConfigListRow> = Vec::new();
1328
1329 for m in &managed {
1330 let mut strates = Vec::new();
1331 for st in &m.strates {
1332 if !st.name.is_empty() {
1333 push_unique(&mut strates, &st.name);
1334 }
1335 }
1336 config_rows.push(ConfigListRow {
1337 sid: managed_runtime_sids
1338 .iter()
1339 .find(|(name, _)| *name == m.name)
1340 .map(|(_, sid)| *sid)
1341 .unwrap_or(m.sid),
1342 name: m.name.clone(),
1343 family: m.family.clone(),
1344 mode: m.mode.clone(),
1345 strates: join_csv(&strates),
1346 });
1347 }
1348
1349 for s in silos.iter() {
1350 let display_name = managed_name_for_runtime_sid(&managed_runtime_sids, s.id)
1351 .unwrap_or_else(|| s.name.clone());
1352 let label = s.strate_label.clone().unwrap_or_else(|| String::from("-"));
1353 let mut strates = Vec::new();
1354 for m in &managed {
1355 if managed_runtime_sids
1356 .iter()
1357 .any(|(name, sid)| *sid == s.id && *name == m.name)
1358 {
1359 for st in &m.strates {
1360 if !st.name.is_empty() {
1361 push_unique(&mut strates, &st.name);
1362 }
1363 }
1364 }
1365 }
1366 if strates.is_empty() && label != "-" {
1367 strates.push(alloc::format!("{} (kernel)", label));
1368 }
1369 let strates_cell = join_csv(&strates);
1370 let (used_val, used_unit) = format_bytes(s.mem_usage_bytes as usize);
1371 let mem_cell = if s.mem_max_bytes == 0 {
1372 alloc::format!("{} {} / unlimited", used_val, used_unit)
1373 } else {
1374 let (max_val, max_unit) = format_bytes(s.mem_max_bytes as usize);
1375 alloc::format!("{} {} / {} {}", used_val, used_unit, max_val, max_unit)
1376 };
1377 rows.push(SiloListRow {
1378 sid: s.id,
1379 name: display_name,
1380 state: alloc::format!("{:?}", s.state),
1381 tasks: s.task_count,
1382 memory: mem_cell,
1383 mode: s.mode,
1384 label,
1385 strates: strates_cell,
1386 });
1387 }
1388 if want_gui {
1389 if render_silo_table_ratatui(&rows, &config_rows, managed_source).unwrap_or(false) {
1390 return Ok(());
1391 }
1392 shell_println!("silo list: GUI unavailable, fallback console");
1393 }
1394
1395 shell_println!(
1396 "{:<6} {:<14} {:<10} {:<7} {:<18} {:<6} {:<12} {}",
1397 "SID",
1398 "Name",
1399 "State",
1400 "Tasks",
1401 "Memory",
1402 "Mode",
1403 "Label",
1404 "Strates"
1405 );
1406 shell_println!("====================================================================================================================================================================================");
1407 for r in rows {
1408 shell_println!(
1409 "{:<6} {:<12} {:<10} {:<7} {:<18} {:<6o} {:<12} {}",
1410 r.sid,
1411 r.name,
1412 r.state,
1413 r.tasks,
1414 r.memory,
1415 r.mode,
1416 r.label,
1417 r.strates
1418 );
1419 }
1420 Ok(())
1421}
1422
1423fn cmd_strate_list(_args: &[String]) -> Result<(), ShellError> {
1425 struct StrateEntry {
1426 name: String,
1427 belongs_to: Vec<String>,
1428 }
1429
1430 let (managed, managed_source) = load_managed_silos_with_source();
1431 let mut entries: Vec<StrateEntry> = Vec::new();
1432
1433 for s in &managed {
1434 for st in &s.strates {
1435 if st.name.is_empty() {
1436 continue;
1437 }
1438 if let Some(e) = entries.iter_mut().find(|e| e.name == st.name) {
1439 push_unique(&mut e.belongs_to, &s.name);
1440 } else {
1441 entries.push(StrateEntry {
1442 name: st.name.clone(),
1443 belongs_to: alloc::vec![s.name.clone()],
1444 });
1445 }
1446 }
1447 }
1448
1449 let mut runtime_entries: Vec<StrateEntry> = Vec::new();
1450 for runtime in silo::list_silos_snapshot() {
1451 let mut names: Vec<String> = Vec::new();
1452 for m in &managed {
1453 if m.name == runtime.name || m.sid == runtime.id {
1454 for st in &m.strates {
1455 if !st.name.is_empty() {
1456 push_unique(&mut names, &st.name);
1457 }
1458 }
1459 }
1460 }
1461 if names.is_empty() {
1462 if let Some(label) = runtime.strate_label {
1463 names.push(label);
1464 } else {
1465 continue;
1466 }
1467 }
1468
1469 for name in names {
1470 if let Some(e) = runtime_entries.iter_mut().find(|e| e.name == name) {
1471 push_unique(&mut e.belongs_to, &runtime.name);
1472 } else {
1473 runtime_entries.push(StrateEntry {
1474 name,
1475 belongs_to: alloc::vec![runtime.name.clone()],
1476 });
1477 }
1478 }
1479 }
1480
1481 entries.sort_by(|a, b| a.name.cmp(&b.name));
1482 runtime_entries.sort_by(|a, b| a.name.cmp(&b.name));
1483
1484 let config_rows: Vec<ConfigStrateRow> = entries
1485 .iter()
1486 .map(|e| ConfigStrateRow {
1487 strate: e.name.clone(),
1488 belongs_to: join_csv(&e.belongs_to),
1489 })
1490 .collect();
1491
1492 let runtime_rows: Vec<RuntimeStrateRow> = runtime_entries
1493 .iter()
1494 .map(|e| {
1495 let in_cfg = entries.iter().any(|cfg| cfg.name == e.name);
1496 RuntimeStrateRow {
1497 strate: e.name.clone(),
1498 belongs_to: join_csv(&e.belongs_to),
1499 status: if in_cfg {
1500 String::from("config+runtime")
1501 } else {
1502 String::from("runtime-only")
1503 },
1504 }
1505 })
1506 .collect();
1507
1508 if render_strate_table_ratatui(&runtime_rows, &config_rows, managed_source).unwrap_or(false) {
1509 return Ok(());
1510 }
1511
1512 shell_println!("Runtime:");
1513 shell_println!("{:<20} {:<24} {}", "Strate", "BelongsTo", "Status");
1514 shell_println!("====================");
1515 for r in runtime_rows {
1516 shell_println!("{:<20} {:<24} {}", r.strate, r.belongs_to, r.status);
1517 }
1518 shell_println!("");
1519 shell_println!("Config ({}):", managed_source);
1520 shell_println!("{:<20} {}", "Strate", "BelongsTo");
1521 shell_println!("====================");
1522 for r in config_rows {
1523 shell_println!("{:<20} {}", r.strate, r.belongs_to);
1524 }
1525 Ok(())
1526}
1527
1528fn cmd_strate_spawn(args: &[String]) -> Result<(), ShellError> {
1529 if args.len() < 2 {
1530 shell_println!(
1531 "Usage: strate spawn <path|type> [--label <l>] [--dev <p>] [--type elf|wasm]"
1532 );
1533 return Err(ShellError::InvalidArguments);
1534 }
1535 let target = args[1].as_str();
1536
1537 let mut label: Option<&str> = None;
1538 let mut dev: Option<&str> = None;
1539 let mut spawn_type: Option<&str> = None;
1540 let mut i = 2usize;
1541 while i < args.len() {
1542 match args[i].as_str() {
1543 "--label" => {
1544 if i + 1 >= args.len() {
1545 return Err(ShellError::InvalidArguments);
1546 }
1547 label = Some(args[i + 1].as_str());
1548 i += 2;
1549 }
1550 "--dev" => {
1551 if i + 1 >= args.len() {
1552 return Err(ShellError::InvalidArguments);
1553 }
1554 dev = Some(args[i + 1].as_str());
1555 i += 2;
1556 }
1557 "--type" => {
1558 if i + 1 >= args.len() {
1559 return Err(ShellError::InvalidArguments);
1560 }
1561 spawn_type = Some(args[i + 1].as_str());
1562 i += 2;
1563 }
1564 _ => {
1565 shell_println!("strate spawn: unknown option '{}'", args[i]);
1566 return Err(ShellError::InvalidArguments);
1567 }
1568 }
1569 }
1570
1571 let module_path: String = match target {
1572 "strate-fs-ext4" => String::from("/initfs/fs-ext4"),
1573 "ramfs" | "strate-fs-ramfs" => String::from("/initfs/strate-fs-ramfs"),
1574 path if path.starts_with('/') => String::from(path),
1575 name => {
1576 let mut p = String::from("/initfs/bin/");
1577 p.push_str(name);
1578 p
1579 }
1580 };
1581
1582 if spawn_type == Some("wasm") {
1583 shell_println!("strate spawn: delegating wasm to wasm-run...");
1584 return cmd_wasm_run(&[String::from(target)]);
1585 }
1586
1587 let fd = vfs::open(&module_path, vfs::OpenFlags::READ).map_err(|_| {
1588 shell_println!("strate spawn: cannot open '{}'", module_path);
1589 ShellError::ExecutionFailed
1590 })?;
1591 let data = match vfs::read_all(fd) {
1592 Ok(d) => d,
1593 Err(_) => {
1594 let _ = vfs::close(fd);
1595 shell_println!("strate spawn: cannot read '{}'", module_path);
1596 return Err(ShellError::ExecutionFailed);
1597 }
1598 };
1599 let _ = vfs::close(fd);
1600
1601 match silo::kernel_spawn_strate(&data, label, dev) {
1602 Ok(sid) => {
1603 shell_println!(
1604 "strate spawn: started (sid={}, path={}, label={})",
1605 sid,
1606 module_path,
1607 label.unwrap_or("-")
1608 );
1609 Ok(())
1610 }
1611 Err(e) => {
1612 shell_println!("strate spawn failed: {:?}", e);
1613 Err(ShellError::ExecutionFailed)
1614 }
1615 }
1616}
1617
1618fn cmd_strate_config_show(args: &[String]) -> Result<(), ShellError> {
1620 let existing = read_silo_toml_from_initfs()?;
1621 let silos = parse_silo_toml(&existing);
1622 if silos.is_empty() {
1623 shell_println!("strate config show: /initfs/silo.toml empty or missing");
1624 return Ok(());
1625 }
1626
1627 if args.len() == 3 {
1628 let name = args[2].as_str();
1629 let Some(s) = silos.iter().find(|s| s.name == name) else {
1630 shell_println!("strate config show: silo '{}' not found", name);
1631 return Err(ShellError::ExecutionFailed);
1632 };
1633 shell_println!(
1634 "silo '{}' sid={} family={} mode={} strates={}",
1635 s.name,
1636 s.sid,
1637 s.family,
1638 s.mode,
1639 s.strates.len()
1640 );
1641 for st in &s.strates {
1642 shell_println!(
1643 " - {}: binary={} type={} target={}",
1644 st.name,
1645 st.binary,
1646 st.stype,
1647 st.target
1648 );
1649 }
1650 return Ok(());
1651 }
1652
1653 for s in &silos {
1654 shell_println!(
1655 "silo '{}' sid={} family={} mode={} strates={}",
1656 s.name,
1657 s.sid,
1658 s.family,
1659 s.mode,
1660 s.strates.len()
1661 );
1662 }
1663 Ok(())
1664}
1665
1666fn cmd_strate_config_add(args: &[String]) -> Result<(), ShellError> {
1668 if args.len() < 5 {
1669 shell_println!("Usage: strate config add <silo> <name> <binary> [--type <t>] [--target <x>] [--family <F>] [--mode <ooo>] [--sid <n>]");
1670 return Err(ShellError::InvalidArguments);
1671 }
1672 let silo_name = args[2].as_str();
1673 let strate_name = args[3].as_str();
1674 let binary = args[4].as_str();
1675 if silo_name.is_empty() || strate_name.is_empty() || binary.is_empty() {
1676 shell_println!("strate config add: invalid empty argument");
1677 return Err(ShellError::InvalidArguments);
1678 }
1679
1680 let mut stype = String::from("elf");
1681 let mut target = String::from("default");
1682 let mut family: Option<String> = None;
1683 let mut mode: Option<String> = None;
1684 let mut sid: Option<u32> = None;
1685 let mut i = 5usize;
1686 while i < args.len() {
1687 match args[i].as_str() {
1688 "--type" => {
1689 if i + 1 >= args.len() {
1690 shell_println!("strate config add: missing value for --type");
1691 return Err(ShellError::InvalidArguments);
1692 }
1693 stype = args[i + 1].clone();
1694 i += 2;
1695 }
1696 "--target" => {
1697 if i + 1 >= args.len() {
1698 shell_println!("strate config add: missing value for --target");
1699 return Err(ShellError::InvalidArguments);
1700 }
1701 target = args[i + 1].clone();
1702 i += 2;
1703 }
1704 "--family" => {
1705 if i + 1 >= args.len() {
1706 shell_println!("strate config add: missing value for --family");
1707 return Err(ShellError::InvalidArguments);
1708 }
1709 family = Some(args[i + 1].clone());
1710 i += 2;
1711 }
1712 "--mode" => {
1713 if i + 1 >= args.len() {
1714 shell_println!("strate config add: missing value for --mode");
1715 return Err(ShellError::InvalidArguments);
1716 }
1717 mode = Some(args[i + 1].clone());
1718 i += 2;
1719 }
1720 "--sid" => {
1721 if i + 1 >= args.len() {
1722 shell_println!("strate config add: missing value for --sid");
1723 return Err(ShellError::InvalidArguments);
1724 }
1725 sid = args[i + 1].parse::<u32>().ok();
1726 if sid.is_none() {
1727 shell_println!("strate config add: invalid --sid");
1728 return Err(ShellError::InvalidArguments);
1729 }
1730 i += 2;
1731 }
1732 other => {
1733 shell_println!("strate config add: unknown option '{}'", other);
1734 return Err(ShellError::InvalidArguments);
1735 }
1736 }
1737 }
1738
1739 let existing = read_silo_toml_from_initfs()?;
1740 let mut silos = parse_silo_toml(&existing);
1741 let idx = match silos.iter().position(|s| s.name == silo_name) {
1742 Some(p) => p,
1743 None => {
1744 silos.push(ManagedSiloDef {
1745 name: String::from(silo_name),
1746 sid: sid.unwrap_or(42),
1747 family: family.clone().unwrap_or_else(|| String::from("USR")),
1748 mode: mode.clone().unwrap_or_else(|| String::from("000")),
1749 cpu_features: String::new(),
1750 graphics_enabled: false,
1751 graphics_mode: String::new(),
1752 graphics_read_only: false,
1753 graphics_max_sessions: 0,
1754 graphics_session_ttl_sec: 0,
1755 graphics_turn_policy: String::from("auto"),
1756 strates: Vec::new(),
1757 });
1758 silos.len() - 1
1759 }
1760 };
1761
1762 if let Some(f) = family {
1763 silos[idx].family = f;
1764 }
1765 if let Some(m) = mode {
1766 silos[idx].mode = m;
1767 }
1768 if let Some(s) = sid {
1769 silos[idx].sid = s;
1770 }
1771
1772 if let Some(st) = silos[idx]
1773 .strates
1774 .iter_mut()
1775 .find(|st| st.name == strate_name)
1776 {
1777 st.binary = String::from(binary);
1778 st.stype = stype;
1779 st.target = target;
1780 } else {
1781 silos[idx].strates.push(ManagedStrateDef {
1782 name: String::from(strate_name),
1783 binary: String::from(binary),
1784 stype,
1785 target,
1786 });
1787 }
1788
1789 let rendered = render_silo_toml(&silos);
1790 write_silo_toml_to_initfs(&rendered)?;
1791 shell_println!(
1792 "strate config add: wrote /initfs/silo.toml (silo='{}', strate='{}')",
1793 silo_name,
1794 strate_name
1795 );
1796 Ok(())
1797}
1798
1799fn cmd_strate_config_remove(args: &[String]) -> Result<(), ShellError> {
1801 if args.len() != 4 {
1802 shell_println!("Usage: strate config remove <silo> <name>");
1803 return Err(ShellError::InvalidArguments);
1804 }
1805 let silo_name = args[2].as_str();
1806 let strate_name = args[3].as_str();
1807 let existing = read_silo_toml_from_initfs()?;
1808 let mut silos = parse_silo_toml(&existing);
1809
1810 let Some(silo_idx) = silos.iter().position(|s| s.name == silo_name) else {
1811 shell_println!("strate config remove: silo '{}' not found", silo_name);
1812 return Err(ShellError::ExecutionFailed);
1813 };
1814 let Some(strate_idx) = silos[silo_idx]
1815 .strates
1816 .iter()
1817 .position(|st| st.name == strate_name)
1818 else {
1819 shell_println!(
1820 "strate config remove: strate '{}' not found in silo '{}'",
1821 strate_name,
1822 silo_name
1823 );
1824 return Err(ShellError::ExecutionFailed);
1825 };
1826
1827 silos[silo_idx].strates.remove(strate_idx);
1828 if silos[silo_idx].strates.is_empty() {
1829 silos.remove(silo_idx);
1830 }
1831
1832 let rendered = render_silo_toml(&silos);
1833 write_silo_toml_to_initfs(&rendered)?;
1834 shell_println!(
1835 "strate config remove: updated /initfs/silo.toml (silo='{}', strate='{}')",
1836 silo_name,
1837 strate_name
1838 );
1839 Ok(())
1840}
1841
1842fn cmd_strate_config(args: &[String]) -> Result<(), ShellError> {
1844 if args.len() < 2 {
1845 shell_println!("Usage: strate config <show|add|remove> ...");
1846 return Err(ShellError::InvalidArguments);
1847 }
1848 match args[1].as_str() {
1849 "show" => cmd_strate_config_show(args),
1850 "add" => cmd_strate_config_add(args),
1851 "remove" => cmd_strate_config_remove(args),
1852 _ => {
1853 shell_println!("Usage: strate config <show|add|remove> ...");
1854 Err(ShellError::InvalidArguments)
1855 }
1856 }
1857}
1858
1859fn cmd_strate_start(args: &[String]) -> Result<(), ShellError> {
1861 if args.len() != 2 {
1862 shell_println!("Usage: strate start <id|label|name>");
1863 return Err(ShellError::InvalidArguments);
1864 }
1865 let selector = normalize_current_silo_selector(args[1].as_str());
1866 match silo::kernel_start_silo(selector.as_str()) {
1867 Ok(sid) => {
1868 shell_println!("strate start: ok (sid={})", sid);
1869 print_strate_state_for_sid(sid);
1870 Ok(())
1871 }
1872 Err(e) => {
1873 shell_println!("strate start failed: {:?}", e);
1874 Err(ShellError::ExecutionFailed)
1875 }
1876 }
1877}
1878
1879fn cmd_strate_lifecycle(args: &[String]) -> Result<(), ShellError> {
1881 if args.len() != 2 {
1882 shell_println!("Usage: strate start|stop|kill|destroy <id|label|name>");
1883 return Err(ShellError::InvalidArguments);
1884 }
1885 let selector = normalize_current_silo_selector(args[1].as_str());
1886 let action = args[0].as_str();
1887 let result = match action {
1888 "stop" => silo::kernel_stop_silo(selector.as_str(), false),
1889 "kill" => silo::kernel_stop_silo(selector.as_str(), true),
1890 "destroy" => silo::kernel_destroy_silo(selector.as_str()),
1891 _ => unreachable!(),
1892 };
1893 match result {
1894 Ok(sid) => {
1895 shell_println!("strate {}: ok (sid={})", action, sid);
1896 if action == "stop" {
1897 print_strate_state_for_sid(sid);
1898 }
1899 Ok(())
1900 }
1901 Err(e) => {
1902 shell_println!("strate {} failed: {:?}", action, e);
1903 Err(ShellError::ExecutionFailed)
1904 }
1905 }
1906}
1907
1908fn cmd_strate_rename(args: &[String]) -> Result<(), ShellError> {
1910 if args.len() != 3 {
1911 shell_println!("Usage: strate rename <id|label|name> <new_label>");
1912 return Err(ShellError::InvalidArguments);
1913 }
1914 let selector = normalize_current_silo_selector(args[1].as_str());
1915 let new_label = args[2].as_str();
1916 match silo::kernel_rename_silo_label(selector.as_str(), new_label) {
1917 Ok(sid) => {
1918 shell_println!("strate rename: ok (sid={}, new_label={})", sid, new_label);
1919 Ok(())
1920 }
1921 Err(e) => {
1922 if matches!(e, crate::syscall::error::SyscallError::InvalidArgument) {
1923 shell_println!(
1924 "strate rename failed: strate is running or not in a renamable state (stop it first)"
1925 );
1926 } else {
1927 shell_println!("strate rename failed: {:?}", e);
1928 }
1929 Err(ShellError::ExecutionFailed)
1930 }
1931 }
1932}
1933
1934pub(super) fn cmd_strate_impl(args: &[String]) -> Result<(), ShellError> {
1935 if args.is_empty() {
1936 print_strate_usage();
1937 return Err(ShellError::InvalidArguments);
1938 }
1939
1940 match args[0].as_str() {
1941 "list" => cmd_strate_list(args),
1942 "spawn" => cmd_strate_spawn(args),
1943 "config" => cmd_strate_config(args),
1944 "start" => cmd_strate_start(args),
1945 "stop" | "kill" | "destroy" => cmd_strate_lifecycle(args),
1946 "rename" => cmd_strate_rename(args),
1947 "info" => cmd_silo_info(args),
1948 "suspend" => cmd_silo_suspend(args),
1949 "resume" => cmd_silo_resume(args),
1950 "events" => cmd_silo_events(args),
1951 "pledge" => cmd_silo_pledge(args),
1952 "unveil" => cmd_silo_unveil(args),
1953 "sandbox" => cmd_silo_sandbox(args),
1954 "limit" => cmd_silo_limit(args),
1955 "attach" => cmd_silo_attach(args),
1956 "top" => cmd_silo_top(args),
1957 "logs" => cmd_silo_logs(args),
1958 _ => {
1959 print_strate_usage();
1960 Err(ShellError::InvalidArguments)
1961 }
1962 }
1963}
1964
1965fn cmd_silo_info(args: &[String]) -> Result<(), ShellError> {
1969 if args.len() < 2 {
1970 shell_println!("Usage: silo info <id|label|name>");
1971 return Err(ShellError::InvalidArguments);
1972 }
1973 let selector = normalize_current_silo_selector(args[1].as_str());
1974 let detail = silo::silo_detail_snapshot(selector.as_str()).map_err(|e| {
1975 shell_println!("silo info: {:?}", e);
1976 ShellError::ExecutionFailed
1977 })?;
1978 let b = &detail.base;
1979 let (used_v, used_u) = format_bytes(b.mem_usage_bytes as usize);
1980 let (min_v, min_u) = format_bytes(b.mem_min_bytes as usize);
1981 let mem_max = if b.mem_max_bytes == 0 {
1982 String::from("unlimited")
1983 } else {
1984 let (v, u) = format_bytes(b.mem_max_bytes as usize);
1985 alloc::format!("{} {}", v, u)
1986 };
1987
1988 shell_println!("SID: {}", b.id);
1989 shell_println!("Name: {}", b.name);
1990 shell_println!("Label: {}", b.strate_label.as_deref().unwrap_or("-"));
1991 shell_println!("Tier: {:?}", b.tier);
1992 shell_println!("State: {:?}", b.state);
1993 shell_println!("Family: {:?}", detail.family);
1994 shell_println!("Mode: {:03o}", b.mode);
1995 shell_println!("Sandboxed: {}", detail.sandboxed);
1996 shell_println!("Tasks: {}", b.task_count);
1997 shell_println!(
1998 "Memory: {} {} / {} {} / {}",
1999 used_v,
2000 used_u,
2001 min_v,
2002 min_u,
2003 mem_max
2004 );
2005 shell_println!("CPU shares: {}", detail.cpu_shares);
2006 shell_println!("CPU mask: {:#x}", detail.cpu_affinity_mask);
2007 shell_println!("CPU req: {:#x}", detail.cpu_features_required);
2008 shell_println!("CPU allow: {:#x}", detail.cpu_features_allowed);
2009 shell_println!("XCR0 mask: {:#x}", detail.xcr0_mask);
2010 shell_println!("GFX flags: {:#x}", detail.graphics_flags);
2011 shell_println!(
2012 "GFX mode: {}",
2013 if (detail.graphics_flags & (1 << 2)) != 0 {
2014 "webrtc-native"
2015 } else if (detail.graphics_flags & (1 << 1)) != 0 {
2016 "graphics-raw"
2017 } else {
2018 "disabled"
2019 }
2020 );
2021 shell_println!(
2022 "GFX ro: {}",
2023 if (detail.graphics_flags & (1 << 3)) != 0 {
2024 "true"
2025 } else {
2026 "false"
2027 }
2028 );
2029 shell_println!("GFX sess: {}", detail.graphics_max_sessions);
2030 shell_println!("GFX ttl: {} sec", detail.graphics_session_ttl_sec);
2031 shell_println!(
2032 "Max tasks: {}",
2033 if detail.max_tasks == 0 {
2034 String::from("unlimited")
2035 } else {
2036 alloc::format!("{}", detail.max_tasks)
2037 }
2038 );
2039 shell_println!("Caps: {} granted", detail.granted_caps_count);
2040
2041 if !detail.task_ids.is_empty() {
2042 shell_println!("Task IDs: {:?}", detail.task_ids);
2043 }
2044
2045 if !detail.unveil_rules.is_empty() {
2046 shell_println!("Unveil rules:");
2047 for (path, bits) in &detail.unveil_rules {
2048 let r = if bits & 4 != 0 { 'r' } else { '-' };
2049 let w = if bits & 2 != 0 { 'w' } else { '-' };
2050 let x = if bits & 1 != 0 { 'x' } else { '-' };
2051 shell_println!(" {}{}{} {}", r, w, x, path);
2052 }
2053 }
2054 Ok(())
2055}
2056
2057fn cmd_silo_suspend(args: &[String]) -> Result<(), ShellError> {
2058 if args.len() < 2 {
2059 shell_println!("Usage: silo suspend <id|label|name>");
2060 return Err(ShellError::InvalidArguments);
2061 }
2062 let selector = normalize_current_silo_selector(args[1].as_str());
2063 match silo::kernel_suspend_silo(selector.as_str()) {
2064 Ok(sid) => {
2065 shell_println!("silo suspend: ok (sid={})", sid);
2066 Ok(())
2067 }
2068 Err(e) => {
2069 shell_println!("silo suspend failed: {:?}", e);
2070 Err(ShellError::ExecutionFailed)
2071 }
2072 }
2073}
2074
2075fn cmd_silo_resume(args: &[String]) -> Result<(), ShellError> {
2076 if args.len() < 2 {
2077 shell_println!("Usage: silo resume <id|label|name>");
2078 return Err(ShellError::InvalidArguments);
2079 }
2080 let selector = normalize_current_silo_selector(args[1].as_str());
2081 match silo::kernel_resume_silo(selector.as_str()) {
2082 Ok(sid) => {
2083 shell_println!("silo resume: ok (sid={})", sid);
2084 Ok(())
2085 }
2086 Err(e) => {
2087 shell_println!("silo resume failed: {:?}", e);
2088 Err(ShellError::ExecutionFailed)
2089 }
2090 }
2091}
2092
2093fn event_kind_str(kind: silo::SiloEventKind) -> &'static str {
2094 match kind {
2095 silo::SiloEventKind::Started => "Started",
2096 silo::SiloEventKind::Stopped => "Stopped",
2097 silo::SiloEventKind::Killed => "Killed",
2098 silo::SiloEventKind::Crashed => "Crashed",
2099 silo::SiloEventKind::Paused => "Paused",
2100 silo::SiloEventKind::Resumed => "Resumed",
2101 }
2102}
2103
2104fn cmd_silo_events(args: &[String]) -> Result<(), ShellError> {
2105 let events = if args.len() >= 2 {
2106 let selector = normalize_current_silo_selector(args[1].as_str());
2107 silo::list_events_for_silo(selector.as_str()).map_err(|e| {
2108 shell_println!("silo events: {:?}", e);
2109 ShellError::ExecutionFailed
2110 })?
2111 } else {
2112 silo::list_events_snapshot()
2113 };
2114
2115 if events.is_empty() {
2116 shell_println!("(no events)");
2117 return Ok(());
2118 }
2119
2120 shell_println!(
2121 "{:<8} {:<10} {:<12} {:<12} {}",
2122 "SID",
2123 "Kind",
2124 "Data0",
2125 "Data1",
2126 "Tick"
2127 );
2128 shell_println!("==========");
2129 for ev in &events {
2130 shell_println!(
2131 "{:<8} {:<10} {:#010x} {:#010x} {}",
2132 ev.silo_id,
2133 event_kind_str(ev.kind),
2134 ev.data0,
2135 ev.data1,
2136 ev.tick
2137 );
2138 }
2139 Ok(())
2140}
2141
2142fn cmd_silo_pledge(args: &[String]) -> Result<(), ShellError> {
2143 if args.len() < 3 {
2144 shell_println!("Usage: silo pledge <id|label|name> <octal_mode>");
2145 return Err(ShellError::InvalidArguments);
2146 }
2147 let mode_val = u16::from_str_radix(args[2].as_str(), 8).map_err(|_| {
2148 shell_println!("silo pledge: invalid octal mode '{}'", args[2]);
2149 ShellError::InvalidArguments
2150 })?;
2151 let selector = normalize_current_silo_selector(args[1].as_str());
2152 match silo::kernel_pledge_silo(selector.as_str(), mode_val) {
2153 Ok((old, new)) => {
2154 shell_println!("silo pledge: {:03o} -> {:03o}", old, new);
2155 Ok(())
2156 }
2157 Err(e) => {
2158 shell_println!("silo pledge failed: {:?}", e);
2159 Err(ShellError::ExecutionFailed)
2160 }
2161 }
2162}
2163
2164fn cmd_silo_unveil(args: &[String]) -> Result<(), ShellError> {
2165 if args.len() < 4 {
2166 shell_println!("Usage: silo unveil <id|label|name> <path> <rwx>");
2167 return Err(ShellError::InvalidArguments);
2168 }
2169 let selector = normalize_current_silo_selector(args[1].as_str());
2170 let path = args[2].as_str();
2171 let rights = args[3].as_str();
2172 match silo::kernel_unveil_silo(selector.as_str(), path, rights) {
2173 Ok(sid) => {
2174 shell_println!(
2175 "silo unveil: ok (sid={}, path={}, rights={})",
2176 sid,
2177 path,
2178 rights
2179 );
2180 Ok(())
2181 }
2182 Err(e) => {
2183 shell_println!("silo unveil failed: {:?}", e);
2184 Err(ShellError::ExecutionFailed)
2185 }
2186 }
2187}
2188
2189fn cmd_silo_sandbox(args: &[String]) -> Result<(), ShellError> {
2190 if args.len() < 2 {
2191 shell_println!("Usage: silo sandbox <id|label|name>");
2192 return Err(ShellError::InvalidArguments);
2193 }
2194 let selector = normalize_current_silo_selector(args[1].as_str());
2195 match silo::kernel_sandbox_silo(selector.as_str()) {
2196 Ok(sid) => {
2197 shell_println!("silo sandbox: ok (sid={})", sid);
2198 Ok(())
2199 }
2200 Err(e) => {
2201 shell_println!("silo sandbox failed: {:?}", e);
2202 Err(ShellError::ExecutionFailed)
2203 }
2204 }
2205}
2206
2207fn cmd_silo_top(_args: &[String]) -> Result<(), ShellError> {
2208 let mut silos = silo::list_silos_snapshot();
2209
2210 let sort_by_mem = _args.len() >= 3 && _args[1] == "--sort" && _args[2] == "mem";
2211 if sort_by_mem {
2212 silos.sort_by(|a, b| b.mem_usage_bytes.cmp(&a.mem_usage_bytes));
2213 } else {
2214 silos.sort_by(|a, b| {
2215 b.task_count
2216 .cmp(&a.task_count)
2217 .then(b.mem_usage_bytes.cmp(&a.mem_usage_bytes))
2218 });
2219 }
2220
2221 let total_tasks: usize = silos.iter().map(|s| s.task_count).sum();
2222 let total_mem: u64 = silos.iter().map(|s| s.mem_usage_bytes).sum();
2223 let (tm_v, tm_u) = format_bytes(total_mem as usize);
2224
2225 shell_println!(
2226 "Silos: {} Tasks: {} Memory: {} {}",
2227 silos.len(),
2228 total_tasks,
2229 tm_v,
2230 tm_u
2231 );
2232 shell_println!("");
2233 shell_println!(
2234 "{:<6} {:<14} {:<10} {:<7} {:<16} {:<6}",
2235 "SID",
2236 "Name",
2237 "State",
2238 "Tasks",
2239 "Memory",
2240 "Mode"
2241 );
2242 shell_println!("========================================");
2243 for s in &silos {
2244 let (mv, mu) = format_bytes(s.mem_usage_bytes as usize);
2245 let mem_str = alloc::format!("{} {}", mv, mu);
2246 shell_println!(
2247 "{:<6} {:<14} {:<10} {:<7} {:<16} {:03o}",
2248 s.id,
2249 s.name,
2250 alloc::format!("{:?}", s.state),
2251 s.task_count,
2252 mem_str,
2253 s.mode
2254 );
2255 }
2256 Ok(())
2257}
2258
2259fn cmd_silo_logs(args: &[String]) -> Result<(), ShellError> {
2260 if args.len() < 2 {
2261 shell_println!("Usage: silo logs <id|label|name>");
2262 return Err(ShellError::InvalidArguments);
2263 }
2264 let selector = normalize_current_silo_selector(args[1].as_str());
2265 let events = silo::list_events_for_silo(selector.as_str()).map_err(|e| {
2266 shell_println!("silo logs: {:?}", e);
2267 ShellError::ExecutionFailed
2268 })?;
2269 if events.is_empty() {
2270 shell_println!("(no log entries for this silo)");
2271 return Ok(());
2272 }
2273 for ev in &events {
2274 let tick_s = ev.tick / 100;
2275 let tick_cs = ev.tick % 100;
2276 shell_println!(
2277 "[{:>6}.{:02}] sid={} {}",
2278 tick_s,
2279 tick_cs,
2280 ev.silo_id,
2281 event_kind_str(ev.kind)
2282 );
2283 }
2284 Ok(())
2285}
2286
2287pub(super) fn cmd_wasm_run_impl(args: &[String]) -> Result<(), ShellError> {
2288 if args.len() < 1 {
2289 shell_println!("Usage: wasm-run <path>");
2290 return Err(ShellError::InvalidArguments);
2291 }
2292 let wasm_path = &args[0];
2293
2294 shell_println!("wasm-run: using running strate-wasm service...");
2295 let default_service_path = String::from("/srv/strate-wasm/default");
2296 let bootstrap_service_path = String::from("/srv/strate-wasm/bootstrap");
2297 shell_println!("wasm-run: waiting for service {} ...", default_service_path);
2298
2299 let mut selected_service_path: Option<String> = None;
2300 for _ in 0..100 {
2301 if crate::shell::is_interrupted() {
2302 shell_println!("wasm-run: cancelled");
2303 return Err(ShellError::ExecutionFailed);
2304 }
2305 if vfs::stat_path(&default_service_path).is_ok() {
2306 selected_service_path = Some(default_service_path.clone());
2307 break;
2308 }
2309 if vfs::stat_path(&bootstrap_service_path).is_ok() {
2310 selected_service_path = Some(bootstrap_service_path.clone());
2311 break;
2312 }
2313 crate::process::yield_task();
2314 }
2315
2316 let Some(service_path) = selected_service_path else {
2317 shell_println!("wasm-run: timed out waiting for /srv/strate-wasm/default");
2318 return Err(ShellError::ExecutionFailed);
2319 };
2320
2321 let (scheme, rel) = vfs::resolve(&service_path).map_err(|_| ShellError::ExecutionFailed)?;
2323 let open_res = scheme
2324 .open(&rel, vfs::OpenFlags::READ)
2325 .map_err(|_| ShellError::ExecutionFailed)?;
2326 let port_id = crate::ipc::PortId::from_u64(open_res.file_id);
2327 let port = crate::ipc::port::get_port(port_id).ok_or(ShellError::ExecutionFailed)?;
2328
2329 let mut load_msg = crate::ipc::IpcMessage::new(0x100);
2330 let path_bytes = wasm_path.as_bytes();
2331 let copy_len = core::cmp::min(path_bytes.len(), 63);
2332 load_msg.payload[0] = copy_len as u8;
2333 load_msg.payload[1..1 + copy_len].copy_from_slice(&path_bytes[..copy_len]);
2334
2335 shell_println!("wasm-run: loading {} ...", wasm_path);
2336 port.send(load_msg)
2337 .map_err(|_| ShellError::ExecutionFailed)?;
2338
2339 let load_ack = port.recv().map_err(|_| ShellError::ExecutionFailed)?;
2340 let load_status = u32::from_le_bytes([
2341 load_ack.payload[0],
2342 load_ack.payload[1],
2343 load_ack.payload[2],
2344 load_ack.payload[3],
2345 ]);
2346 if load_status != 0 {
2347 shell_println!("wasm-run: load failed (status={})", load_status);
2348 return Err(ShellError::ExecutionFailed);
2349 }
2350
2351 let run_msg = crate::ipc::IpcMessage::new(0x102);
2352 shell_println!("wasm-run: starting execution...");
2353 port.send(run_msg)
2354 .map_err(|_| ShellError::ExecutionFailed)?;
2355 let run_ack = port.recv().map_err(|_| ShellError::ExecutionFailed)?;
2356 let run_status = u32::from_le_bytes([
2357 run_ack.payload[0],
2358 run_ack.payload[1],
2359 run_ack.payload[2],
2360 run_ack.payload[3],
2361 ]);
2362 if run_status != 0 {
2363 shell_println!("wasm-run: execution failed (status={})", run_status);
2364 return Err(ShellError::ExecutionFailed);
2365 }
2366 shell_println!("wasm-run: done");
2367
2368 Ok(())
2369}
2370
2371pub(super) fn cmd_health_impl(_args: &[String]) -> Result<(), ShellError> {
2373 shell_println!("=== Strat9 Health Report ===\n");
2374
2375 shell_println!("-- VFS Mounts --");
2376 for m in vfs::list_mounts() {
2377 shell_println!(" {}", m);
2378 }
2379
2380 shell_println!("\n-- IPC Namespace --");
2381 let bindings = crate::namespace::list_all_bindings();
2382 if bindings.is_empty() {
2383 shell_println!(" (none)");
2384 } else {
2385 for (name, port_id) in &bindings {
2386 shell_println!(" {} -> port {}", name, port_id);
2387 }
2388 }
2389
2390 shell_println!("\n-- Active Silos --");
2391 let silo_list = silo::list_silos_snapshot();
2392 if silo_list.is_empty() {
2393 shell_println!(" (none)");
2394 } else {
2395 for info in &silo_list {
2396 shell_println!(
2397 " SID={} name={} state={:?} tasks={}",
2398 info.id,
2399 info.name,
2400 info.state,
2401 info.task_count
2402 );
2403 }
2404 }
2405
2406 shell_println!("\n=== End Health Report ===");
2407 Ok(())
2408}