strat9_kernel/shell/commands/vfs/
mod.rs1mod 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 path = if args.is_empty() {
134 resolve_shell_path("")
135 } else {
136 resolve_shell_path(&args[0])
137 };
138
139 if path == "/" {
141 shell_println!("Mount points:");
142 for m in vfs::list_mounts() {
143 shell_println!(" {}", m);
144 }
145 return Ok(());
146 }
147
148 match vfs::open(&path, OpenFlags::READ | OpenFlags::DIRECTORY) {
149 Ok(fd) => {
150 match vfs::getdents(fd) {
152 Ok(entries) => {
153 if entries.is_empty() {
154 shell_println!("(empty)");
155 } else {
156 for e in &entries {
157 let type_char = if e.file_type == DT_DIR { 'd' } else { '-' };
158 shell_println!(" {}{}", type_char, e.name);
159 }
160 }
161 }
162 Err(_) => {
163 let mut buf = [0u8; 4096];
165 match vfs::read(fd, &mut buf) {
166 Ok(n) if n > 0 => {
167 let s = core::str::from_utf8(&buf[..n]).unwrap_or("(binary)");
168 shell_println!("{}", s.trim_end());
169 }
170 _ => shell_println!("(empty)"),
171 }
172 }
173 }
174 let _ = vfs::close(fd);
175 }
176 Err(e) => shell_println!("ls: {}: {:?}", path, e),
177 }
178
179 Ok(())
180}
181
182pub(super) fn cmd_cat_impl(args: &[String]) -> Result<(), ShellError> {
190 if let Some(piped) = crate::shell::output::take_pipe_input() {
191 if args.is_empty() {
192 let s = core::str::from_utf8(&piped).unwrap_or("(non-UTF8 data)");
193 crate::shell_print!("{}", s);
194 if !s.ends_with('\n') {
195 shell_println!();
196 }
197 return Ok(());
198 }
199 }
200
201 if args.is_empty() {
202 shell_println!("Usage: cat <path>");
203 return Ok(());
204 }
205
206 let path = resolve_shell_path(&args[0]);
207 match vfs::open(&path, OpenFlags::READ) {
208 Ok(fd) => {
209 let mut buf = [0u8; 1024];
210 loop {
211 match vfs::read(fd, &mut buf) {
212 Ok(0) => break,
213 Ok(n) => {
214 let s = core::str::from_utf8(&buf[..n]).unwrap_or("(non-UTF8 data)");
215 crate::shell_print!("{}", s);
216 }
217 Err(e) => {
218 shell_println!("\nError reading file: {:?}", e);
219 break;
220 }
221 }
222 }
223 shell_println!("");
224 let _ = vfs::close(fd);
225 }
226 Err(e) => shell_println!("cat: {}: {:?}", path, e),
227 }
228 Ok(())
229}
230
231pub(super) fn cmd_scheme_impl(args: &[String]) -> Result<(), ShellError> {
235 if args.is_empty() || args[0] != "ls" {
236 shell_println!("Usage: scheme ls");
237 return Ok(());
238 }
239
240 shell_println!("Registered schemes:");
241 shell_println!("{:<14} {}", "Name", "Type");
242 shell_println!("────────────────────────────────────");
243 for scheme in vfs::list_schemes() {
244 shell_println!(" {:<12} Kernel/IPC", scheme);
245 }
246 shell_println!("");
247 Ok(())
248}
249
250pub(super) fn cmd_mount_impl(args: &[String]) -> Result<(), ShellError> {
252 if args.is_empty() || args[0] == "ls" {
253 shell_println!("Mount points:");
254 for m in vfs::list_mounts() {
255 shell_println!(" {}", m);
256 }
257 shell_println!("");
258 shell_println!("Usage: mount <source> <target>");
259 return Ok(());
260 }
261 if args.len() != 2 {
262 shell_println!("Usage: mount <source> <target>");
263 return Ok(());
264 }
265
266 let source = resolve_shell_path(&args[0]);
267 let target = resolve_shell_path(&args[1]);
268
269 let (scheme, rel) = match vfs::resolve(&source) {
270 Ok(v) => v,
271 Err(e) => {
272 shell_println!("mount: source {} unavailable: {:?}", source, e);
273 return Ok(());
274 }
275 };
276 if !rel.is_empty() {
277 shell_println!("mount: source must be a mount root: {}", source);
278 return Ok(());
279 }
280
281 match vfs::mount(&target, scheme) {
282 Ok(()) => shell_println!("mount: {} mounted on {}", source, target),
283 Err(e) => shell_println!("mount: {} -> {} failed: {:?}", source, target, e),
284 }
285 Ok(())
286}
287
288pub(super) fn cmd_umount_impl(args: &[String]) -> Result<(), ShellError> {
290 if args.len() != 1 {
291 shell_println!("Usage: umount <target>");
292 return Ok(());
293 }
294
295 let target = resolve_shell_path(&args[0]);
296 match vfs::unmount(&target) {
297 Ok(()) => shell_println!("umount: {}", target),
298 Err(e) => shell_println!("umount: {}: {:?}", target, e),
299 }
300 Ok(())
301}
302
303pub(super) fn cmd_mkdir_impl(args: &[String]) -> Result<(), ShellError> {
307 if args.is_empty() {
308 shell_println!("Usage: mkdir <path>");
309 return Ok(());
310 }
311 let path = resolve_shell_path(&args[0]);
312 match vfs::mkdir(&path, 0o755) {
313 Ok(()) => shell_println!("mkdir: {}", path),
314 Err(e) => shell_println!("mkdir: {}: {:?}", path, e),
315 }
316 Ok(())
317}
318
319pub(super) fn cmd_touch_impl(args: &[String]) -> Result<(), ShellError> {
323 if args.is_empty() {
324 shell_println!("Usage: touch <path>");
325 return Ok(());
326 }
327 let path = resolve_shell_path(&args[0]);
328 match vfs::create_file(&path, 0o644) {
329 Ok(()) => shell_println!("touch: {}", path),
330 Err(e) => shell_println!("touch: {}: {:?}", path, e),
331 }
332 Ok(())
333}
334
335pub(super) fn cmd_rm_impl(args: &[String]) -> Result<(), ShellError> {
339 if args.is_empty() {
340 shell_println!("Usage: rm <path>");
341 return Ok(());
342 }
343 let path = resolve_shell_path(&args[0]);
344 match vfs::unlink(&path) {
345 Ok(()) => shell_println!("rm: {}", path),
346 Err(e) => shell_println!("rm: {}: {:?}", path, e),
347 }
348 Ok(())
349}
350
351pub(super) fn cmd_write_impl(args: &[String]) -> Result<(), ShellError> {
354 if args.len() < 2 {
355 shell_println!("Usage: write <path> <text>");
356 return Ok(());
357 }
358 let path = resolve_shell_path(&args[0]);
359 let text = &args[1];
360
361 match vfs::open(&path, OpenFlags::WRITE | OpenFlags::CREATE) {
362 Ok(fd) => {
363 match vfs::write(fd, text.as_bytes()) {
364 Ok(n) => shell_println!("write: {} bytes -> {}", n, path),
365 Err(e) => shell_println!("write: {}: {:?}", path, e),
366 }
367 let _ = vfs::close(fd);
368 }
369 Err(e) => shell_println!("write: {}: {:?}", path, e),
370 }
371 Ok(())
372}
373
374pub(super) fn cmd_stat_impl(args: &[String]) -> Result<(), ShellError> {
377 if args.is_empty() {
378 shell_println!("Usage: stat <path>");
379 return Err(ShellError::InvalidArguments);
380 }
381 let path = resolve_shell_path(&args[0]);
382 match vfs::stat_path(&path) {
383 Ok(st) => {
384 let ftype = match st.st_mode & 0xF000 {
385 0x4000 => "directory",
386 0x8000 => "regular file",
387 0xA000 => "symbolic link",
388 0x1000 => "FIFO",
389 0x6000 => "block device",
390 0x2000 => "character device",
391 _ => "unknown",
392 };
393 shell_println!(" File: {}", path);
394 shell_println!(" Type: {}", ftype);
395 shell_println!(" Size: {} bytes", st.st_size);
396 shell_println!(" Mode: {:04o}", st.st_mode & 0o7777);
397 shell_println!(" Links: {}", st.st_nlink);
398 shell_println!(" Inode: {}", st.st_ino);
399 }
400 Err(e) => shell_println!("stat: {}: {:?}", path, e),
401 }
402 Ok(())
403}
404
405pub(super) fn cmd_cp_impl(args: &[String]) -> Result<(), ShellError> {
408 if args.len() < 2 {
409 shell_println!("Usage: cp <src> <dst>");
410 return Err(ShellError::InvalidArguments);
411 }
412 let src = resolve_shell_path(&args[0]);
413 let dst = resolve_shell_path(&args[1]);
414
415 let fd_src = vfs::open(&src, OpenFlags::READ).map_err(|e| {
416 shell_println!("cp: cannot open '{}': {:?}", src, e);
417 ShellError::ExecutionFailed
418 })?;
419 let data = match vfs::read_all(fd_src) {
420 Ok(d) => d,
421 Err(e) => {
422 let _ = vfs::close(fd_src);
423 shell_println!("cp: cannot read '{}': {:?}", src, e);
424 return Err(ShellError::ExecutionFailed);
425 }
426 };
427 let _ = vfs::close(fd_src);
428
429 let fd_dst = vfs::open(
430 &dst,
431 OpenFlags::WRITE | OpenFlags::CREATE | OpenFlags::TRUNCATE,
432 )
433 .map_err(|e| {
434 shell_println!("cp: cannot create '{}': {:?}", dst, e);
435 ShellError::ExecutionFailed
436 })?;
437 match vfs::write(fd_dst, &data) {
438 Ok(n) => shell_println!("cp: {} -> {} ({} bytes)", src, dst, n),
439 Err(e) => shell_println!("cp: write to '{}': {:?}", dst, e),
440 }
441 let _ = vfs::close(fd_dst);
442 Ok(())
443}
444
445pub(super) fn cmd_mv_impl(args: &[String]) -> Result<(), ShellError> {
448 if args.len() < 2 {
449 shell_println!("Usage: mv <src> <dst>");
450 return Err(ShellError::InvalidArguments);
451 }
452 let src = resolve_shell_path(&args[0]);
453 let dst = resolve_shell_path(&args[1]);
454
455 match vfs::rename(&src, &dst) {
456 Ok(()) => {
457 shell_println!("mv: {} -> {}", src, dst);
458 return Ok(());
459 }
460 Err(crate::syscall::error::SyscallError::NotSupported) => {
461 }
463 Err(e) => {
464 shell_println!("mv: {:?}", e);
465 return Err(ShellError::ExecutionFailed);
466 }
467 }
468
469 cmd_cp(args)?;
470 match vfs::unlink(&src) {
471 Ok(()) => shell_println!("mv: removed {}", src),
472 Err(e) => shell_println!("mv: could not remove source '{}': {:?}", src, e),
473 }
474 Ok(())
475}
476
477pub(super) fn cmd_df_impl(_args: &[String]) -> Result<(), ShellError> {
480 let mounts = vfs::list_mounts();
481 shell_println!("{:<20} {}", "Mount", "Status");
482 shell_println!("────────────────────────────────────────");
483 for m in &mounts {
484 let status = if vfs::open(m, OpenFlags::READ | OpenFlags::DIRECTORY)
485 .map(|fd| {
486 let _ = vfs::close(fd);
487 })
488 .is_ok()
489 {
490 "accessible"
491 } else {
492 "unavailable"
493 };
494 shell_println!("{:<20} {}", m, status);
495 }
496 shell_println!("{} mount(s)", mounts.len());
497 Ok(())
498}