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