Skip to main content

strat9_kernel/shell/commands/sys/
mod.rs

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