⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/services/
shell_utils.rs

1//! Additional Shell Utilities
2//!
3//! Implements find, grep, wc, head, tail, and other common Unix utilities.
4
5#![allow(unused_variables)]
6
7use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
8
9use super::shell::{BuiltinCommand, CommandResult, Shell};
10use crate::error::KernelError;
11
12/// Find command - search for files
13pub struct FindCommand;
14
15impl BuiltinCommand for FindCommand {
16    fn name(&self) -> &str {
17        "find"
18    }
19    fn description(&self) -> &str {
20        "Search for files in directory hierarchy"
21    }
22
23    fn execute(&self, args: &[String], shell: &Shell) -> CommandResult {
24        let start_path = if args.is_empty() {
25            shell.get_cwd()
26        } else {
27            args[0].clone()
28        };
29
30        let pattern = if args.len() > 1 { Some(&args[1]) } else { None };
31
32        match find_files(&start_path, pattern) {
33            Ok(files) => {
34                for file in files {
35                    crate::println!("{}", file);
36                }
37                CommandResult::Success(0)
38            }
39            Err(e) => CommandResult::Error(format!("find: {}", e)),
40        }
41    }
42}
43
44/// Grep command - search file contents
45pub struct GrepCommand;
46
47impl BuiltinCommand for GrepCommand {
48    fn name(&self) -> &str {
49        "grep"
50    }
51    fn description(&self) -> &str {
52        "Search for patterns in files"
53    }
54
55    fn execute(&self, args: &[String], _shell: &Shell) -> CommandResult {
56        if args.len() < 2 {
57            return CommandResult::Error(String::from("grep: missing pattern or file"));
58        }
59
60        let pattern = &args[0];
61        let file_path = &args[1];
62
63        match grep_file(pattern, file_path) {
64            Ok(matches) => {
65                for line in matches {
66                    crate::println!("{}", line);
67                }
68                CommandResult::Success(0)
69            }
70            Err(e) => CommandResult::Error(format!("grep: {}", e)),
71        }
72    }
73}
74
75/// Word count command
76pub struct WcCommand;
77
78impl BuiltinCommand for WcCommand {
79    fn name(&self) -> &str {
80        "wc"
81    }
82    fn description(&self) -> &str {
83        "Count lines, words, and characters"
84    }
85
86    fn execute(&self, args: &[String], _shell: &Shell) -> CommandResult {
87        if args.is_empty() {
88            return CommandResult::Error(String::from("wc: missing file operand"));
89        }
90
91        for file_path in args {
92            match count_file(file_path) {
93                Ok((lines, words, chars)) => {
94                    crate::println!("{:6} {:6} {:6} {}", lines, words, chars, file_path);
95                }
96                Err(e) => {
97                    return CommandResult::Error(format!("wc: {}: {}", file_path, e));
98                }
99            }
100        }
101
102        CommandResult::Success(0)
103    }
104}
105
106/// Head command - show first lines of file
107pub struct HeadCommand;
108
109impl BuiltinCommand for HeadCommand {
110    fn name(&self) -> &str {
111        "head"
112    }
113    fn description(&self) -> &str {
114        "Output the first part of files"
115    }
116
117    fn execute(&self, args: &[String], _shell: &Shell) -> CommandResult {
118        if args.is_empty() {
119            return CommandResult::Error(String::from("head: missing file operand"));
120        }
121
122        let num_lines = 10; // Default to 10 lines
123        let file_path = &args[0];
124
125        match head_file(file_path, num_lines) {
126            Ok(lines) => {
127                for line in lines {
128                    crate::println!("{}", line);
129                }
130                CommandResult::Success(0)
131            }
132            Err(e) => CommandResult::Error(format!("head: {}: {}", file_path, e)),
133        }
134    }
135}
136
137/// Tail command - show last lines of file
138pub struct TailCommand;
139
140impl BuiltinCommand for TailCommand {
141    fn name(&self) -> &str {
142        "tail"
143    }
144    fn description(&self) -> &str {
145        "Output the last part of files"
146    }
147
148    fn execute(&self, args: &[String], _shell: &Shell) -> CommandResult {
149        if args.is_empty() {
150            return CommandResult::Error(String::from("tail: missing file operand"));
151        }
152
153        let num_lines = 10; // Default to 10 lines
154        let file_path = &args[0];
155
156        match tail_file(file_path, num_lines) {
157            Ok(lines) => {
158                for line in lines {
159                    crate::println!("{}", line);
160                }
161                CommandResult::Success(0)
162            }
163            Err(e) => CommandResult::Error(format!("tail: {}: {}", file_path, e)),
164        }
165    }
166}
167
168/// Diff command - compare files
169pub struct DiffCommand;
170
171impl BuiltinCommand for DiffCommand {
172    fn name(&self) -> &str {
173        "diff"
174    }
175    fn description(&self) -> &str {
176        "Compare files line by line"
177    }
178
179    fn execute(&self, args: &[String], _shell: &Shell) -> CommandResult {
180        if args.len() < 2 {
181            return CommandResult::Error(String::from("diff: missing file operands"));
182        }
183
184        let file1 = &args[0];
185        let file2 = &args[1];
186
187        match diff_files(file1, file2) {
188            Ok(differences) => {
189                if differences.is_empty() {
190                    crate::println!("Files are identical");
191                } else {
192                    for diff in differences {
193                        crate::println!("{}", diff);
194                    }
195                }
196                CommandResult::Success(0)
197            }
198            Err(e) => CommandResult::Error(format!("diff: {}", e)),
199        }
200    }
201}
202
203/// Sort command - sort lines of text
204pub struct SortCommand;
205
206impl BuiltinCommand for SortCommand {
207    fn name(&self) -> &str {
208        "sort"
209    }
210    fn description(&self) -> &str {
211        "Sort lines of text files"
212    }
213
214    fn execute(&self, args: &[String], _shell: &Shell) -> CommandResult {
215        if args.is_empty() {
216            return CommandResult::Error(String::from("sort: missing file operand"));
217        }
218
219        match sort_file(&args[0]) {
220            Ok(lines) => {
221                for line in lines {
222                    crate::println!("{}", line);
223                }
224                CommandResult::Success(0)
225            }
226            Err(e) => CommandResult::Error(format!("sort: {}", e)),
227        }
228    }
229}
230
231/// Uniq command - report or omit repeated lines
232pub struct UniqCommand;
233
234impl BuiltinCommand for UniqCommand {
235    fn name(&self) -> &str {
236        "uniq"
237    }
238    fn description(&self) -> &str {
239        "Report or omit repeated lines"
240    }
241
242    fn execute(&self, args: &[String], _shell: &Shell) -> CommandResult {
243        if args.is_empty() {
244            return CommandResult::Error(String::from("uniq: missing file operand"));
245        }
246
247        match uniq_file(&args[0]) {
248            Ok(lines) => {
249                for line in lines {
250                    crate::println!("{}", line);
251                }
252                CommandResult::Success(0)
253            }
254            Err(e) => CommandResult::Error(format!("uniq: {}", e)),
255        }
256    }
257}
258
259// Helper functions
260
261fn find_files(start_path: &str, pattern: Option<&String>) -> Result<Vec<String>, KernelError> {
262    let mut results = Vec::new();
263
264    // Recursively search directory tree
265    fn search_dir(
266        path: &str,
267        pattern: Option<&String>,
268        results: &mut Vec<String>,
269    ) -> Result<(), KernelError> {
270        let vfs = crate::fs::get_vfs().read();
271        let node = vfs.resolve_path(path)?;
272
273        match node.readdir() {
274            Ok(entries) => {
275                for entry in entries {
276                    let full_path = if path.ends_with('/') {
277                        format!("{}{}", path, entry.name)
278                    } else {
279                        format!("{}/{}", path, entry.name)
280                    };
281
282                    // Check if matches pattern
283                    if let Some(pat) = pattern {
284                        if entry.name.contains(pat.as_str()) {
285                            results.push(full_path.clone());
286                        }
287                    } else {
288                        results.push(full_path.clone());
289                    }
290
291                    // Recurse into subdirectories
292                    if entry.node_type == crate::fs::NodeType::Directory {
293                        let _ = search_dir(&full_path, pattern, results);
294                    }
295                }
296                Ok(())
297            }
298            Err(_) => Ok(()), // Skip directories we can't read
299        }
300    }
301
302    search_dir(start_path, pattern, &mut results)?;
303    Ok(results)
304}
305
306fn grep_file(pattern: &str, file_path: &str) -> Result<Vec<String>, KernelError> {
307    let vfs = crate::fs::get_vfs().read();
308    let node = vfs.resolve_path(file_path)?;
309
310    let mut buffer = [0u8; 4096];
311    let bytes_read = node.read(0, &mut buffer)?;
312
313    let content =
314        core::str::from_utf8(&buffer[..bytes_read]).map_err(|_| KernelError::InvalidArgument {
315            name: "file content",
316            value: "invalid UTF-8",
317        })?;
318
319    let mut matches = Vec::new();
320    for line in content.lines() {
321        if line.contains(pattern) {
322            matches.push(String::from(line));
323        }
324    }
325
326    Ok(matches)
327}
328
329fn count_file(file_path: &str) -> Result<(usize, usize, usize), KernelError> {
330    let vfs = crate::fs::get_vfs().read();
331    let node = vfs.resolve_path(file_path)?;
332
333    let mut buffer = [0u8; 4096];
334    let bytes_read = node.read(0, &mut buffer)?;
335
336    let content =
337        core::str::from_utf8(&buffer[..bytes_read]).map_err(|_| KernelError::InvalidArgument {
338            name: "file content",
339            value: "invalid UTF-8",
340        })?;
341
342    let lines = content.lines().count();
343    let words = content.split_whitespace().count();
344    let chars = content.chars().count();
345
346    Ok((lines, words, chars))
347}
348
349fn head_file(file_path: &str, num_lines: usize) -> Result<Vec<String>, KernelError> {
350    let vfs = crate::fs::get_vfs().read();
351    let node = vfs.resolve_path(file_path)?;
352
353    let mut buffer = [0u8; 4096];
354    let bytes_read = node.read(0, &mut buffer)?;
355
356    let content =
357        core::str::from_utf8(&buffer[..bytes_read]).map_err(|_| KernelError::InvalidArgument {
358            name: "file content",
359            value: "invalid UTF-8",
360        })?;
361
362    let lines: Vec<String> = content.lines().take(num_lines).map(String::from).collect();
363
364    Ok(lines)
365}
366
367fn tail_file(file_path: &str, num_lines: usize) -> Result<Vec<String>, KernelError> {
368    let vfs = crate::fs::get_vfs().read();
369    let node = vfs.resolve_path(file_path)?;
370
371    let mut buffer = [0u8; 4096];
372    let bytes_read = node.read(0, &mut buffer)?;
373
374    let content =
375        core::str::from_utf8(&buffer[..bytes_read]).map_err(|_| KernelError::InvalidArgument {
376            name: "file content",
377            value: "invalid UTF-8",
378        })?;
379
380    let all_lines: Vec<String> = content.lines().map(String::from).collect();
381    let start = if all_lines.len() > num_lines {
382        all_lines.len() - num_lines
383    } else {
384        0
385    };
386
387    Ok(all_lines[start..].to_vec())
388}
389
390fn diff_files(file1: &str, file2: &str) -> Result<Vec<String>, KernelError> {
391    let vfs = crate::fs::get_vfs().read();
392
393    let node1 = vfs.resolve_path(file1)?;
394    let node2 = vfs.resolve_path(file2)?;
395
396    let mut buffer1 = [0u8; 4096];
397    let mut buffer2 = [0u8; 4096];
398
399    let bytes1 = node1.read(0, &mut buffer1)?;
400    let bytes2 = node2.read(0, &mut buffer2)?;
401
402    let content1 =
403        core::str::from_utf8(&buffer1[..bytes1]).map_err(|_| KernelError::InvalidArgument {
404            name: "file1 content",
405            value: "invalid UTF-8",
406        })?;
407    let content2 =
408        core::str::from_utf8(&buffer2[..bytes2]).map_err(|_| KernelError::InvalidArgument {
409            name: "file2 content",
410            value: "invalid UTF-8",
411        })?;
412
413    let lines1: Vec<&str> = content1.lines().collect();
414    let lines2: Vec<&str> = content2.lines().collect();
415
416    let mut differences = Vec::new();
417
418    let max_len = lines1.len().max(lines2.len());
419    for i in 0..max_len {
420        let line1 = lines1.get(i).copied();
421        let line2 = lines2.get(i).copied();
422
423        if line1 != line2 {
424            if let Some(l1) = line1 {
425                differences.push(format!("< {}", l1));
426            }
427            if let Some(l2) = line2 {
428                differences.push(format!("> {}", l2));
429            }
430        }
431    }
432
433    Ok(differences)
434}
435
436fn sort_file(file_path: &str) -> Result<Vec<String>, KernelError> {
437    let vfs = crate::fs::get_vfs().read();
438    let node = vfs.resolve_path(file_path)?;
439
440    let mut buffer = [0u8; 4096];
441    let bytes_read = node.read(0, &mut buffer)?;
442
443    let content =
444        core::str::from_utf8(&buffer[..bytes_read]).map_err(|_| KernelError::InvalidArgument {
445            name: "file content",
446            value: "invalid UTF-8",
447        })?;
448
449    let mut lines: Vec<String> = content.lines().map(String::from).collect();
450    lines.sort();
451
452    Ok(lines)
453}
454
455fn uniq_file(file_path: &str) -> Result<Vec<String>, KernelError> {
456    let vfs = crate::fs::get_vfs().read();
457    let node = vfs.resolve_path(file_path)?;
458
459    let mut buffer = [0u8; 4096];
460    let bytes_read = node.read(0, &mut buffer)?;
461
462    let content =
463        core::str::from_utf8(&buffer[..bytes_read]).map_err(|_| KernelError::InvalidArgument {
464            name: "file content",
465            value: "invalid UTF-8",
466        })?;
467
468    let mut unique_lines = Vec::new();
469    let mut last_line: Option<String> = None;
470
471    for line in content.lines() {
472        if Some(line) != last_line.as_deref() {
473            unique_lines.push(String::from(line));
474            last_line = Some(String::from(line));
475        }
476    }
477
478    Ok(unique_lines)
479}
480
481/// Register all utility commands with the shell
482pub fn register_utils(shell: &Shell) {
483    // Use the public API to register commands
484    let commands: Vec<Box<dyn BuiltinCommand>> = vec![
485        Box::new(FindCommand),
486        Box::new(GrepCommand),
487        Box::new(WcCommand),
488        Box::new(HeadCommand),
489        Box::new(TailCommand),
490        Box::new(DiffCommand),
491        Box::new(SortCommand),
492        Box::new(UniqCommand),
493    ];
494
495    shell.register_builtins_batch(commands);
496}