Skip to main content

strat9_kernel/shell/commands/sys/
mod.rs

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