Skip to main content

strat9_kernel/shell/commands/util/
mod.rs

1//! Utility commands: uptime, dmesg, echo, env, whoami, grep, setenv, unsetenv
2mod audit;
3mod date;
4mod dmesg;
5mod echo;
6mod env;
7mod grep;
8mod ntpdate;
9mod uptime;
10mod watch;
11mod whoami;
12
13use crate::{
14    shell::{is_interrupted, ShellError},
15    shell_println, vfs,
16};
17use alloc::string::String;
18
19pub use audit::cmd_audit;
20pub use date::cmd_date;
21pub use dmesg::cmd_dmesg;
22pub use echo::cmd_echo;
23pub use env::{
24    cmd_env, cmd_setenv, cmd_unsetenv, init_shell_env, shell_getenv, shell_setenv, shell_unsetenv,
25};
26pub use grep::cmd_grep;
27pub use ntpdate::cmd_ntpdate;
28pub use uptime::cmd_uptime;
29pub use watch::cmd_watch;
30pub use whoami::cmd_whoami;
31
32pub(super) fn cmd_uptime_impl(_args: &[String]) -> Result<(), ShellError> {
33    let ticks = crate::process::scheduler::ticks();
34    let hz = crate::arch::x86_64::timer::TIMER_HZ;
35    let total_secs = ticks / hz;
36    let hours = total_secs / 3600;
37    let minutes = (total_secs % 3600) / 60;
38    let secs = total_secs % 60;
39
40    let task_count = crate::process::get_all_tasks()
41        .map(|t| t.len())
42        .unwrap_or(0);
43    let silos = crate::silo::list_silos_snapshot().len();
44
45    shell_println!(
46        "up {:02}:{:02}:{:02}  ({} ticks @ {} Hz)  {} tasks, {} silos",
47        hours,
48        minutes,
49        secs,
50        ticks,
51        hz,
52        task_count,
53        silos
54    );
55
56    // Perf counters (TSC-based)
57    let tsc_khz = crate::arch::x86_64::boot_timestamp::tsc_khz();
58    let stats = crate::process::scheduler::perf_counters::snapshot();
59    shell_println!(
60        "perf: {}",
61        stats
62            .iter()
63            .map(|s| {
64                let avg = s.avg_us(tsc_khz);
65                alloc::format!("{} avg={}us ({})", s.name, avg, s.count)
66            })
67            .collect::<alloc::vec::Vec<_>>()
68            .join("  ")
69    );
70
71    Ok(())
72}
73
74static KLOG: crate::sync::SpinLock<KernelLogBuffer> =
75    crate::sync::SpinLock::new(KernelLogBuffer::new());
76
77const KLOG_CAPACITY: usize = 256;
78
79struct KernelLogBuffer {
80    entries: [KlogEntry; KLOG_CAPACITY],
81    head: usize,
82    count: usize,
83}
84
85#[derive(Clone, Copy)]
86struct KlogEntry {
87    tick: u64,
88    len: u8,
89    data: [u8; 120],
90}
91
92impl KlogEntry {
93    const fn empty() -> Self {
94        Self {
95            tick: 0,
96            len: 0,
97            data: [0; 120],
98        }
99    }
100}
101
102impl KernelLogBuffer {
103    const fn new() -> Self {
104        Self {
105            entries: [KlogEntry::empty(); KLOG_CAPACITY],
106            head: 0,
107            count: 0,
108        }
109    }
110
111    fn push(&mut self, msg: &str) {
112        let tick = crate::process::scheduler::ticks();
113        let bytes = msg.as_bytes();
114        let copy_len = core::cmp::min(bytes.len(), 120);
115        let idx = (self.head + self.count) % KLOG_CAPACITY;
116        if self.count < KLOG_CAPACITY {
117            self.count += 1;
118        } else {
119            self.head = (self.head + 1) % KLOG_CAPACITY;
120        }
121        self.entries[idx].tick = tick;
122        self.entries[idx].len = copy_len as u8;
123        self.entries[idx].data[..copy_len].copy_from_slice(&bytes[..copy_len]);
124    }
125
126    fn iter(&self) -> impl Iterator<Item = &KlogEntry> {
127        let h = self.head;
128        let c = self.count;
129        (0..c).map(move |i| &self.entries[(h + i) % KLOG_CAPACITY])
130    }
131}
132
133pub fn klog_write(msg: &str) {
134    KLOG.lock().push(msg);
135}
136
137pub(super) fn cmd_dmesg_impl(args: &[String]) -> Result<(), ShellError> {
138    let limit: usize = if !args.is_empty() {
139        args[0].parse().unwrap_or(50)
140    } else {
141        50
142    };
143
144    let log = KLOG.lock();
145    let entries: alloc::vec::Vec<_> = log.iter().collect();
146    let start = if entries.len() > limit {
147        entries.len() - limit
148    } else {
149        0
150    };
151    let hz = crate::arch::x86_64::timer::TIMER_HZ;
152
153    if entries.is_empty() {
154        shell_println!("(kernel log empty)");
155        return Ok(());
156    }
157
158    for entry in &entries[start..] {
159        let secs = entry.tick / hz;
160        let cs = (entry.tick % hz) * 100 / hz;
161        let text = core::str::from_utf8(&entry.data[..entry.len as usize]).unwrap_or("???");
162        shell_println!("[{:>6}.{:02}] {}", secs, cs, text);
163    }
164    Ok(())
165}
166
167pub(super) fn cmd_echo_impl(args: &[String]) -> Result<(), ShellError> {
168    let mut first = true;
169    for arg in args {
170        if !first {
171            crate::shell_print!(" ");
172        }
173        crate::shell_print!("{}", arg);
174        first = false;
175    }
176    shell_println!("");
177    Ok(())
178}
179
180pub(super) fn cmd_whoami_impl(_args: &[String]) -> Result<(), ShellError> {
181    if let Some(label) = crate::silo::current_task_silo_label() {
182        shell_println!("silo: {}", label);
183    } else {
184        shell_println!("silo: kernel (no silo context)");
185    }
186
187    if let Some(task) = crate::process::current_task_clone() {
188        shell_println!("task: {} (pid={}, tid={})", task.name, task.pid, task.tid);
189    }
190
191    Ok(())
192}
193
194/// Search for lines matching a pattern in a file or piped input.
195///
196/// Usage: `grep <pattern> [path]`
197///
198/// When invoked as the right-hand side of a pipe (`cmd | grep pat`),
199/// reads from pipe input instead of a file.
200pub(super) fn cmd_grep_impl(args: &[String]) -> Result<(), ShellError> {
201    if args.is_empty() {
202        shell_println!("Usage: grep <pattern> [path]");
203        return Err(ShellError::InvalidArguments);
204    }
205    let pattern = args[0].as_str();
206
207    let (data, label) = if let Some(piped) = crate::shell::output::take_pipe_input() {
208        (piped, String::from("(pipe)"))
209    } else if args.len() >= 2 {
210        let path = args[1].as_str();
211        let fd = vfs::open(path, vfs::OpenFlags::READ).map_err(|_| {
212            shell_println!("grep: cannot open '{}'", path);
213            ShellError::ExecutionFailed
214        })?;
215        let d = match vfs::read_all(fd) {
216            Ok(d) => d,
217            Err(_) => {
218                let _ = vfs::close(fd);
219                shell_println!("grep: cannot read '{}'", path);
220                return Err(ShellError::ExecutionFailed);
221            }
222        };
223        let _ = vfs::close(fd);
224        (d, String::from(path))
225    } else {
226        shell_println!("Usage: grep <pattern> <path>");
227        return Err(ShellError::InvalidArguments);
228    };
229
230    let text = core::str::from_utf8(&data).unwrap_or("");
231    let mut found = 0u32;
232    for line in text.split('\n') {
233        if is_interrupted() {
234            shell_println!("(grep cancelled after {} matches)", found);
235            return Ok(());
236        }
237        if line.contains(pattern) {
238            shell_println!("{}", line);
239            found += 1;
240        }
241    }
242    if found == 0 {
243        shell_println!("(no match in {})", label);
244    }
245    Ok(())
246}