veridian_kernel/services/
shell_utils.rs1#![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
12pub 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
44pub 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
75pub 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
106pub 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; 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
137pub 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; 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
168pub 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
203pub 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
231pub 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
259fn find_files(start_path: &str, pattern: Option<&String>) -> Result<Vec<String>, KernelError> {
262 let mut results = Vec::new();
263
264 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 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 if entry.node_type == crate::fs::NodeType::Directory {
293 let _ = search_dir(&full_path, pattern, results);
294 }
295 }
296 Ok(())
297 }
298 Err(_) => Ok(()), }
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
481pub fn register_utils(shell: &Shell) {
483 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}