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