Skip to main content

strat9_kernel/shell/commands/sys/
mod.rs

1//! System management commands
2mod 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
147/// Parses silo toml.
148fn parse_silo_toml(data: &str) -> Vec<ManagedSiloDef> {
149    #[derive(Clone, Copy)]
150    enum Section {
151        Silo,
152        Strate,
153    }
154
155    /// Performs the push default strate operation.
156    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
254/// Performs the render silo toml operation.
255fn 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
307/// Reads silo toml from initfs.
308fn 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
322/// Performs the load managed silos with source operation.
323fn 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
343/// Performs the push unique operation.
344fn 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
350/// Performs the join csv operation.
351fn 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
395/// Performs the render silo table ratatui operation.
396fn 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
547/// Performs the render strate table ratatui operation.
548fn 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
667/// Writes silo toml to initfs.
668fn 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
689/// Performs the print strate state for sid operation.
690fn 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
765/// Performs the cmd silos operation.
766pub(super) fn cmd_silos_impl(_args: &[String]) -> Result<(), ShellError> {
767    let args = [String::from("list")];
768    cmd_silo(&args)
769}
770
771/// Display kernel version
772pub(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
779/// Clear the screen
780pub(super) fn cmd_clear_impl(_args: &[String]) -> Result<(), ShellError> {
781    clear_screen();
782    Ok(())
783}
784
785/// Display CPU information
786pub(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
804/// Reboot the system.
805pub(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
816/// trace mem on|off|dump [n]|clear|serial on|off|mask
817pub(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
930/// Launch the userspace PID test binary from initfs.
931pub(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
967/// Launch the userspace syscall integration test binary from initfs.
968pub(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
1004/// Launch the userspace memory test binary from initfs.
1005pub(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
1041/// Launch the userspace stressed memory test binary from initfs.
1042pub(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
1078/// Performs the cmd silo list operation.
1079fn 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
1183/// Performs the cmd strate list operation.
1184fn 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
1378/// Performs the cmd strate config show operation.
1379fn 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
1426/// Performs the cmd strate config add operation.
1427fn 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
1559/// Performs the cmd strate config remove operation.
1560fn 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
1602/// Performs the cmd strate config operation.
1603fn 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
1619/// Performs the cmd strate start operation.
1620fn 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
1639/// Performs the cmd strate lifecycle operation.
1640fn 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
1668/// Performs the cmd strate rename operation.
1669fn 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
1725// ============================================================================
1726// silo info / suspend / resume / events / pledge / unveil / sandbox / top / logs
1727// ============================================================================
1728
1729fn 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    // Connect and send LOAD then RUN
2073    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
2122/// `health` — system health diagnostic (boot graph, strates, IPC, VFS mounts).
2123pub(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}