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