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    Ok(())
53}
54
55static KLOG: crate::sync::SpinLock<KernelLogBuffer> =
56    crate::sync::SpinLock::new(KernelLogBuffer::new());
57
58const KLOG_CAPACITY: usize = 256;
59
60struct KernelLogBuffer {
61    entries: [KlogEntry; KLOG_CAPACITY],
62    head: usize,
63    count: usize,
64}
65
66#[derive(Clone, Copy)]
67struct KlogEntry {
68    tick: u64,
69    len: u8,
70    data: [u8; 120],
71}
72
73impl KlogEntry {
74    const fn empty() -> Self {
75        Self {
76            tick: 0,
77            len: 0,
78            data: [0; 120],
79        }
80    }
81}
82
83impl KernelLogBuffer {
84    const fn new() -> Self {
85        Self {
86            entries: [KlogEntry::empty(); KLOG_CAPACITY],
87            head: 0,
88            count: 0,
89        }
90    }
91
92    fn push(&mut self, msg: &str) {
93        let tick = crate::process::scheduler::ticks();
94        let bytes = msg.as_bytes();
95        let copy_len = core::cmp::min(bytes.len(), 120);
96        let idx = (self.head + self.count) % KLOG_CAPACITY;
97        if self.count < KLOG_CAPACITY {
98            self.count += 1;
99        } else {
100            self.head = (self.head + 1) % KLOG_CAPACITY;
101        }
102        self.entries[idx].tick = tick;
103        self.entries[idx].len = copy_len as u8;
104        self.entries[idx].data[..copy_len].copy_from_slice(&bytes[..copy_len]);
105    }
106
107    fn iter(&self) -> impl Iterator<Item = &KlogEntry> {
108        let h = self.head;
109        let c = self.count;
110        (0..c).map(move |i| &self.entries[(h + i) % KLOG_CAPACITY])
111    }
112}
113
114pub fn klog_write(msg: &str) {
115    KLOG.lock().push(msg);
116}
117
118pub(super) fn cmd_dmesg_impl(args: &[String]) -> Result<(), ShellError> {
119    let limit: usize = if !args.is_empty() {
120        args[0].parse().unwrap_or(50)
121    } else {
122        50
123    };
124
125    let log = KLOG.lock();
126    let entries: alloc::vec::Vec<_> = log.iter().collect();
127    let start = if entries.len() > limit {
128        entries.len() - limit
129    } else {
130        0
131    };
132    let hz = crate::arch::x86_64::timer::TIMER_HZ;
133
134    if entries.is_empty() {
135        shell_println!("(kernel log empty)");
136        return Ok(());
137    }
138
139    for entry in &entries[start..] {
140        let secs = entry.tick / hz;
141        let cs = (entry.tick % hz) * 100 / hz;
142        let text = core::str::from_utf8(&entry.data[..entry.len as usize]).unwrap_or("???");
143        shell_println!("[{:>6}.{:02}] {}", secs, cs, text);
144    }
145    Ok(())
146}
147
148pub(super) fn cmd_echo_impl(args: &[String]) -> Result<(), ShellError> {
149    let mut first = true;
150    for arg in args {
151        if !first {
152            crate::shell_print!(" ");
153        }
154        crate::shell_print!("{}", arg);
155        first = false;
156    }
157    shell_println!("");
158    Ok(())
159}
160
161pub(super) fn cmd_whoami_impl(_args: &[String]) -> Result<(), ShellError> {
162    if let Some(label) = crate::silo::current_task_silo_label() {
163        shell_println!("silo: {}", label);
164    } else {
165        shell_println!("silo: kernel (no silo context)");
166    }
167
168    if let Some(task) = crate::process::current_task_clone() {
169        shell_println!("task: {} (pid={}, tid={})", task.name, task.pid, task.tid);
170    }
171
172    Ok(())
173}
174
175/// Search for lines matching a pattern in a file or piped input.
176///
177/// Usage: `grep <pattern> [path]`
178///
179/// When invoked as the right-hand side of a pipe (`cmd | grep pat`),
180/// reads from pipe input instead of a file.
181pub(super) fn cmd_grep_impl(args: &[String]) -> Result<(), ShellError> {
182    if args.is_empty() {
183        shell_println!("Usage: grep <pattern> [path]");
184        return Err(ShellError::InvalidArguments);
185    }
186    let pattern = args[0].as_str();
187
188    let (data, label) = if let Some(piped) = crate::shell::output::take_pipe_input() {
189        (piped, String::from("(pipe)"))
190    } else if args.len() >= 2 {
191        let path = args[1].as_str();
192        let fd = vfs::open(path, vfs::OpenFlags::READ).map_err(|_| {
193            shell_println!("grep: cannot open '{}'", path);
194            ShellError::ExecutionFailed
195        })?;
196        let d = match vfs::read_all(fd) {
197            Ok(d) => d,
198            Err(_) => {
199                let _ = vfs::close(fd);
200                shell_println!("grep: cannot read '{}'", path);
201                return Err(ShellError::ExecutionFailed);
202            }
203        };
204        let _ = vfs::close(fd);
205        (d, String::from(path))
206    } else {
207        shell_println!("Usage: grep <pattern> <path>");
208        return Err(ShellError::InvalidArguments);
209    };
210
211    let text = core::str::from_utf8(&data).unwrap_or("");
212    let mut found = 0u32;
213    for line in text.split('\n') {
214        if line.contains(pattern) {
215            shell_println!("{}", line);
216            found += 1;
217        }
218    }
219    if found == 0 {
220        shell_println!("(no match in {})", label);
221    }
222    Ok(())
223}