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