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