1mod cat;
3mod cd;
4mod cp;
5mod df;
6mod ls;
7mod mkdir;
8mod mount;
9mod mv;
10mod rm;
11mod scheme;
12mod stat;
13mod touch;
14mod umount;
15mod write;
16
17use crate::{
18 shell::ShellError,
19 shell_println,
20 vfs::{self, scheme::DT_DIR, OpenFlags},
21};
22use alloc::{string::String, vec::Vec};
23use spin::Lazy;
24
25pub use cat::cmd_cat;
26pub use cd::cmd_cd;
27pub use cp::cmd_cp;
28pub use df::cmd_df;
29pub use ls::cmd_ls;
30pub use mkdir::cmd_mkdir;
31pub use mount::cmd_mount;
32pub use mv::cmd_mv;
33pub use rm::cmd_rm;
34pub use scheme::cmd_scheme;
35pub use stat::cmd_stat;
36pub use touch::cmd_touch;
37pub use umount::cmd_umount;
38pub use write::cmd_write;
39
40static SHELL_CWD: Lazy<crate::sync::SpinLock<String>> =
47 Lazy::new(|| crate::sync::SpinLock::new(String::from("/")));
48
49pub fn get_cwd() -> String {
51 SHELL_CWD.lock().clone()
52}
53
54fn set_cwd(path: String) {
56 *SHELL_CWD.lock() = path;
57}
58
59fn normalize_path(path: &str) -> String {
61 let mut parts: Vec<&str> = Vec::new();
62 for component in path.split('/') {
63 match component {
64 "" | "." => {}
65 ".." => {
66 let _ = parts.pop();
67 }
68 other => parts.push(other),
69 }
70 }
71 if parts.is_empty() {
72 return String::from("/");
73 }
74 let mut result = String::from("/");
75 for (i, part) in parts.iter().enumerate() {
76 if i > 0 {
77 result.push('/');
78 }
79 result.push_str(part);
80 }
81 result
82}
83
84pub fn resolve_shell_path(path: &str) -> String {
90 if path.is_empty() {
91 return get_cwd();
92 }
93 if path.starts_with('/') {
94 return normalize_path(path);
95 }
96 let cwd = get_cwd();
97 let combined = if cwd.ends_with('/') {
98 alloc::format!("{}{}", cwd, path)
99 } else {
100 alloc::format!("{}/{}", cwd, path)
101 };
102 normalize_path(&combined)
103}
104
105pub(super) fn cmd_cd_impl(args: &[String]) -> Result<(), ShellError> {
109 let target = if args.is_empty() {
110 String::from("/")
111 } else {
112 resolve_shell_path(&args[0])
113 };
114
115 match vfs::open(&target, OpenFlags::READ | OpenFlags::DIRECTORY) {
117 Ok(fd) => {
118 let _ = vfs::close(fd);
119 set_cwd(target);
120 }
121 Err(e) => {
122 let arg = args.first().map(|s| s.as_str()).unwrap_or("/");
123 shell_println!("cd: {}: {:?}", arg, e);
124 }
125 }
126 Ok(())
127}
128
129pub(super) fn cmd_ls_impl(args: &[String]) -> Result<(), ShellError> {
133 let long = args.iter().any(|a| a == "-l");
134 let path = args
135 .iter()
136 .find(|a| !a.starts_with('-'))
137 .map(|a| resolve_shell_path(a))
138 .unwrap_or_else(|| resolve_shell_path(""));
139
140 if path == "/" && !long {
142 shell_println!("Mount points:");
143 for m in vfs::list_mounts() {
144 shell_println!(" {}", m);
145 }
146 return Ok(());
147 }
148
149 match vfs::open(&path, OpenFlags::READ | OpenFlags::DIRECTORY) {
150 Ok(fd) => {
151 match vfs::getdents(fd) {
152 Ok(entries) => {
153 if entries.is_empty() {
154 shell_println!("(empty)");
155 } else if long {
156 for e in &entries {
157 let full_path = if path.ends_with('/') {
158 alloc::format!("{}{}", path, e.name)
159 } else {
160 alloc::format!("{}/{}", path, e.name)
161 };
162 let suffix = if e.file_type == DT_DIR { "/" } else { "" };
163 match vfs::stat_path(&full_path) {
164 Ok(st) => {
165 let mode_str = format_mode(st.st_mode);
166 shell_println!(
167 "{} {:>3} {:>4} {:>4} {:>8} {} {}{}",
168 mode_str,
169 st.st_nlink,
170 st.st_uid,
171 st.st_gid,
172 st.st_size,
173 format_mtime(st.st_mtime),
174 e.name,
175 suffix
176 );
177 }
178 Err(_) => {
179 let type_char = if e.file_type == DT_DIR { 'd' } else { '-' };
180 shell_println!(" {}{}", type_char, e.name);
181 }
182 }
183 }
184 } else {
185 for e in &entries {
186 let type_char = if e.file_type == DT_DIR { 'd' } else { '-' };
187 shell_println!(" {}{}", type_char, e.name);
188 }
189 }
190 }
191 Err(_) => {
192 let mut buf = [0u8; 4096];
193 match vfs::read(fd, &mut buf) {
194 Ok(n) if n > 0 => {
195 let s = core::str::from_utf8(&buf[..n]).unwrap_or("(binary)");
196 shell_println!("{}", s.trim_end());
197 }
198 _ => shell_println!("(empty)"),
199 }
200 }
201 }
202 let _ = vfs::close(fd);
203 }
204 Err(e) => shell_println!("ls: {}: {:?}", path, e),
205 }
206
207 Ok(())
208}
209
210fn format_mode(mode: u32) -> alloc::string::String {
212 let ft = mode & 0o170000;
213 let type_char = match ft {
214 0o040000 => 'd',
215 0o120000 => 'l',
216 0o060000 => 'b',
217 0o020000 => 'c',
218 0o010000 => 'p',
219 0o140000 => 's',
220 _ => '-',
221 };
222 let perms = mode & 0o7777;
223 let mut buf = [0u8; 10];
224 buf[0] = type_char as u8;
225 buf[1] = if perms & 0o400 != 0 { b'r' } else { b'-' };
227 buf[2] = if perms & 0o200 != 0 { b'w' } else { b'-' };
228 buf[3] = if perms & 0o100 != 0 {
229 if perms & 0o4000 != 0 {
230 b's'
231 } else {
232 b'x'
233 }
234 } else {
235 if perms & 0o4000 != 0 {
236 b'S'
237 } else {
238 b'-'
239 }
240 };
241 buf[4] = if perms & 0o40 != 0 { b'r' } else { b'-' };
243 buf[5] = if perms & 0o20 != 0 { b'w' } else { b'-' };
244 buf[6] = if perms & 0o10 != 0 {
245 if perms & 0o2000 != 0 {
246 b's'
247 } else {
248 b'x'
249 }
250 } else {
251 if perms & 0o2000 != 0 {
252 b'S'
253 } else {
254 b'-'
255 }
256 };
257 buf[7] = if perms & 0o4 != 0 { b'r' } else { b'-' };
259 buf[8] = if perms & 0o2 != 0 { b'w' } else { b'-' };
260 buf[9] = if perms & 0o1 != 0 {
261 if perms & 0o1000 != 0 {
262 b't'
263 } else {
264 b'x'
265 }
266 } else {
267 if perms & 0o1000 != 0 {
268 b'T'
269 } else {
270 b'-'
271 }
272 };
273 unsafe { alloc::string::String::from_utf8_unchecked(buf.to_vec()) }
274}
275
276fn format_mtime(mtime: strat9_abi::data::TimeSpec) -> alloc::string::String {
278 let secs = mtime.tv_sec as u64;
279 let days = secs / 86400;
280 let rem = secs % 86400;
281 let hours = rem / 3600;
282 let mins = (rem % 3600) / 60;
283
284 let month_days: &[(u32, &str)] = &[
286 (31, "Jan"),
287 (28, "Feb"),
288 (31, "Mar"),
289 (30, "Apr"),
290 (31, "May"),
291 (30, "Jun"),
292 (31, "Jul"),
293 (31, "Aug"),
294 (30, "Sep"),
295 (31, "Oct"),
296 (30, "Nov"),
297 (31, "Dec"),
298 ];
299 let mut day_of_year = (days % 365) as u32;
300 let mut month_idx = 0;
301 for &(days_in_month, _) in month_days {
302 if day_of_year < days_in_month {
303 break;
304 }
305 day_of_year -= days_in_month;
306 month_idx += 1;
307 }
308 if month_idx >= 12 {
309 month_idx = 11;
310 day_of_year = 30;
311 }
312 let month_name = month_days[month_idx].1;
313
314 alloc::format!(
315 "{} {:>2} {:02}:{:02}",
316 month_name,
317 day_of_year + 1,
318 hours,
319 mins
320 )
321}
322
323pub(super) fn cmd_cat_impl(args: &[String]) -> Result<(), ShellError> {
331 if let Some(piped) = crate::shell::output::take_pipe_input() {
332 if args.is_empty() {
333 let s = core::str::from_utf8(&piped).unwrap_or("(non-UTF8 data)");
334 crate::shell_print!("{}", s);
335 if !s.ends_with('\n') {
336 shell_println!();
337 }
338 return Ok(());
339 }
340 }
341
342 if args.is_empty() {
343 shell_println!("Usage: cat <path>");
344 return Ok(());
345 }
346
347 let path = resolve_shell_path(&args[0]);
348 match vfs::open(&path, OpenFlags::READ) {
349 Ok(fd) => {
350 let mut buf = [0u8; 1024];
351 loop {
352 match vfs::read(fd, &mut buf) {
353 Ok(0) => break,
354 Ok(n) => {
355 let s = core::str::from_utf8(&buf[..n]).unwrap_or("(non-UTF8 data)");
356 crate::shell_print!("{}", s);
357 }
358 Err(e) => {
359 shell_println!("\nError reading file: {:?}", e);
360 break;
361 }
362 }
363 }
364 shell_println!("");
365 let _ = vfs::close(fd);
366 }
367 Err(e) => shell_println!("cat: {}: {:?}", path, e),
368 }
369 Ok(())
370}
371
372pub(super) fn cmd_scheme_impl(args: &[String]) -> Result<(), ShellError> {
376 if args.is_empty() || args[0] != "ls" {
377 shell_println!("Usage: scheme ls");
378 return Ok(());
379 }
380
381 shell_println!("Registered schemes:");
382 shell_println!("{:<14} {}", "Name", "Type");
383 shell_println!("======================================================");
384 for scheme in vfs::list_schemes() {
385 shell_println!(" {:<12} Kernel/IPC", scheme);
386 }
387 shell_println!("");
388 Ok(())
389}
390
391pub(super) fn cmd_mount_impl(args: &[String]) -> Result<(), ShellError> {
393 if args.is_empty() || args[0] == "ls" {
394 shell_println!("Mount points:");
395 for m in vfs::list_mounts() {
396 shell_println!(" {}", m);
397 }
398 shell_println!("");
399 shell_println!("Usage: mount <source> <target>");
400 return Ok(());
401 }
402 if args.len() != 2 {
403 shell_println!("Usage: mount <source> <target>");
404 return Ok(());
405 }
406
407 let source = resolve_shell_path(&args[0]);
408 let target = resolve_shell_path(&args[1]);
409
410 let (scheme, rel) = match vfs::resolve(&source) {
411 Ok(v) => v,
412 Err(e) => {
413 shell_println!("mount: source {} unavailable: {:?}", source, e);
414 return Ok(());
415 }
416 };
417 if !rel.is_empty() {
418 shell_println!("mount: source must be a mount root: {}", source);
419 return Ok(());
420 }
421
422 match vfs::mount(&target, scheme) {
423 Ok(()) => shell_println!("mount: {} mounted on {}", source, target),
424 Err(e) => shell_println!("mount: {} -> {} failed: {:?}", source, target, e),
425 }
426 Ok(())
427}
428
429pub(super) fn cmd_umount_impl(args: &[String]) -> Result<(), ShellError> {
431 if args.len() != 1 {
432 shell_println!("Usage: umount <target>");
433 return Ok(());
434 }
435
436 let target = resolve_shell_path(&args[0]);
437 match vfs::unmount(&target) {
438 Ok(()) => shell_println!("umount: {}", target),
439 Err(e) => shell_println!("umount: {}: {:?}", target, e),
440 }
441 Ok(())
442}
443
444pub(super) fn cmd_mkdir_impl(args: &[String]) -> Result<(), ShellError> {
448 if args.is_empty() {
449 shell_println!("Usage: mkdir <path>");
450 return Ok(());
451 }
452 let path = resolve_shell_path(&args[0]);
453 match vfs::mkdir(&path, 0o755) {
454 Ok(()) => shell_println!("mkdir: {}", path),
455 Err(e) => shell_println!("mkdir: {}: {:?}", path, e),
456 }
457 Ok(())
458}
459
460pub(super) fn cmd_touch_impl(args: &[String]) -> Result<(), ShellError> {
464 if args.is_empty() {
465 shell_println!("Usage: touch <path>");
466 return Ok(());
467 }
468 let path = resolve_shell_path(&args[0]);
469 match vfs::create_file(&path, 0o644) {
470 Ok(()) => shell_println!("touch: {}", path),
471 Err(e) => shell_println!("touch: {}: {:?}", path, e),
472 }
473 Ok(())
474}
475
476pub(super) fn cmd_rm_impl(args: &[String]) -> Result<(), ShellError> {
480 if args.is_empty() {
481 shell_println!("Usage: rm <path>");
482 return Ok(());
483 }
484 let path = resolve_shell_path(&args[0]);
485 match vfs::unlink(&path) {
486 Ok(()) => shell_println!("rm: {}", path),
487 Err(e) => shell_println!("rm: {}: {:?}", path, e),
488 }
489 Ok(())
490}
491
492pub(super) fn cmd_write_impl(args: &[String]) -> Result<(), ShellError> {
495 if args.len() < 2 {
496 shell_println!("Usage: write <path> <text>");
497 return Ok(());
498 }
499 let path = resolve_shell_path(&args[0]);
500 let text = &args[1];
501
502 match vfs::open(&path, OpenFlags::WRITE | OpenFlags::CREATE) {
503 Ok(fd) => {
504 match vfs::write(fd, text.as_bytes()) {
505 Ok(n) => shell_println!("write: {} bytes -> {}", n, path),
506 Err(e) => shell_println!("write: {}: {:?}", path, e),
507 }
508 let _ = vfs::close(fd);
509 }
510 Err(e) => shell_println!("write: {}: {:?}", path, e),
511 }
512 Ok(())
513}
514
515pub(super) fn cmd_stat_impl(args: &[String]) -> Result<(), ShellError> {
518 if args.is_empty() {
519 shell_println!("Usage: stat <path>");
520 return Err(ShellError::InvalidArguments);
521 }
522 let path = resolve_shell_path(&args[0]);
523 match vfs::stat_path(&path) {
524 Ok(st) => {
525 let ftype = match st.st_mode & 0xF000 {
526 0x4000 => "directory",
527 0x8000 => "regular file",
528 0xA000 => "symbolic link",
529 0x1000 => "FIFO",
530 0x6000 => "block device",
531 0x2000 => "character device",
532 _ => "unknown",
533 };
534 shell_println!(" File: {}", path);
535 shell_println!(" Type: {}", ftype);
536 shell_println!(" Size: {} bytes", st.st_size);
537 shell_println!(" Mode: {:04o}", st.st_mode & 0o7777);
538 shell_println!(" Links: {}", st.st_nlink);
539 shell_println!(" Inode: {}", st.st_ino);
540 }
541 Err(e) => shell_println!("stat: {}: {:?}", path, e),
542 }
543 Ok(())
544}
545
546pub(super) fn cmd_cp_impl(args: &[String]) -> Result<(), ShellError> {
549 if args.len() < 2 {
550 shell_println!("Usage: cp <src> <dst>");
551 return Err(ShellError::InvalidArguments);
552 }
553 let src = resolve_shell_path(&args[0]);
554 let dst = resolve_shell_path(&args[1]);
555
556 let fd_src = vfs::open(&src, OpenFlags::READ).map_err(|e| {
557 shell_println!("cp: cannot open '{}': {:?}", src, e);
558 ShellError::ExecutionFailed
559 })?;
560 let data = match vfs::read_all(fd_src) {
561 Ok(d) => d,
562 Err(e) => {
563 let _ = vfs::close(fd_src);
564 shell_println!("cp: cannot read '{}': {:?}", src, e);
565 return Err(ShellError::ExecutionFailed);
566 }
567 };
568 let _ = vfs::close(fd_src);
569
570 let fd_dst = vfs::open(
571 &dst,
572 OpenFlags::WRITE | OpenFlags::CREATE | OpenFlags::TRUNCATE,
573 )
574 .map_err(|e| {
575 shell_println!("cp: cannot create '{}': {:?}", dst, e);
576 ShellError::ExecutionFailed
577 })?;
578 match vfs::write(fd_dst, &data) {
579 Ok(n) => shell_println!("cp: {} -> {} ({} bytes)", src, dst, n),
580 Err(e) => shell_println!("cp: write to '{}': {:?}", dst, e),
581 }
582 let _ = vfs::close(fd_dst);
583 Ok(())
584}
585
586pub(super) fn cmd_mv_impl(args: &[String]) -> Result<(), ShellError> {
589 if args.len() < 2 {
590 shell_println!("Usage: mv <src> <dst>");
591 return Err(ShellError::InvalidArguments);
592 }
593 let src = resolve_shell_path(&args[0]);
594 let dst = resolve_shell_path(&args[1]);
595
596 match vfs::rename(&src, &dst) {
597 Ok(()) => {
598 shell_println!("mv: {} -> {}", src, dst);
599 return Ok(());
600 }
601 Err(crate::syscall::error::SyscallError::NotSupported) => {
602 }
604 Err(e) => {
605 shell_println!("mv: {:?}", e);
606 return Err(ShellError::ExecutionFailed);
607 }
608 }
609
610 cmd_cp(args)?;
611 match vfs::unlink(&src) {
612 Ok(()) => shell_println!("mv: removed {}", src),
613 Err(e) => shell_println!("mv: could not remove source '{}': {:?}", src, e),
614 }
615 Ok(())
616}
617
618pub(super) fn cmd_df_impl(_args: &[String]) -> Result<(), ShellError> {
621 let mounts = vfs::list_mounts();
622 shell_println!("{:<20} {}", "Mount", "Status");
623 shell_println!("=======================================================================================================");
624 for m in &mounts {
625 let status = if vfs::open(m, OpenFlags::READ | OpenFlags::DIRECTORY)
626 .map(|fd| {
627 let _ = vfs::close(fd);
628 })
629 .is_ok()
630 {
631 "accessible"
632 } else {
633 "unavailable"
634 };
635 shell_println!("{:<20} {}", m, status);
636 }
637 shell_println!("{} mount(s)", mounts.len());
638 Ok(())
639}