Skip to main content

strat9_kernel/vfs/
mod.rs

1//! Virtual File System (VFS) - Plan 9-inspired namespace.
2//!
3//! The VFS provides:
4//! - Scheme abstraction: Pluggable backends (IPC, kernel, devices)
5//! - Mount table: Map path prefixes to schemes
6//! - File descriptors: Per-process FD tables
7//! - Path resolution: Navigate the namespace hierarchy
8//!
9//! ## Architecture
10//!
11//! ```text
12//! User syscall (open "/net/tcp/0")
13//!      ↓
14//! VFS::open() : path resolution
15//!      ↓
16//! MountTable::resolve() → ("/net" → IpcScheme, "tcp/0")
17//!      ↓
18//! IpcScheme::open("tcp/0") → IPC message to network stack
19//!      ↓
20//! OpenFile created with scheme reference + file_id
21//!      ↓
22//! FD allocated in process FD table
23//!      ↓
24//! Returns FD to userspace
25//! ```
26
27pub mod blkdev_scheme;
28pub mod console_scheme;
29pub mod fd;
30pub mod file;
31pub mod ipcfs;
32pub mod mount;
33pub mod pipe;
34pub mod procfs;
35pub mod pty_scheme;
36pub mod ramfs_scheme;
37pub mod scheme;
38pub mod scheme_router;
39
40use crate::{process::current_task_clone, sync::SpinLock, syscall::error::SyscallError};
41use alloc::{boxed::Box, string::String, sync::Arc};
42use core::fmt::Write;
43
44pub use blkdev_scheme::BlkDevScheme;
45pub use fd::{FileDescriptorTable, STDERR, STDIN, STDOUT};
46pub use file::OpenFile;
47pub use mount::{list_mounts, mount, resolve, unmount, Namespace};
48pub use pipe::PipeScheme;
49pub use procfs::ProcScheme;
50pub use ramfs_scheme::RamfsScheme;
51pub use scheme::{
52    DirEntry, DynScheme, FileFlags, FileStat, IpcScheme, KernelScheme, OpenFlags, Scheme,
53};
54pub use scheme_router::{
55    get_initfs_file_bytes, init_builtin_schemes, list_schemes, mount_scheme, register_initfs_file,
56    register_scheme,
57};
58
59use crate::memory::{UserSliceRead, UserSliceWrite};
60
61// Re-export AT_FDCWD from the ABI so VFS callers can use it.
62pub use crate::syscall::numbers::AT_FDCWD;
63
64// ============================================================================
65// High-level VFS API
66// ============================================================================
67
68/// Open a file and return a file descriptor.
69///
70/// This is the main entry point for opening files from userspace.
71pub fn open(path: &str, flags: OpenFlags) -> Result<u32, SyscallError> {
72    // Resolve path to (scheme, relative_path)
73    let (scheme, relative_path) = mount::resolve(path)?;
74
75    // Open the file via the scheme
76    let open_result = scheme.open(&relative_path, flags)?;
77
78    // Create OpenFile wrapper
79    let open_file = Arc::new(OpenFile::new(
80        scheme,
81        open_result.file_id,
82        String::from(path),
83        flags,
84        open_result.flags,
85        open_result.size,
86    ));
87
88    // Insert into current task's FD table
89    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
90    // SAFETY: We're in syscall context, have exclusive access to FD table
91    let fd = unsafe { (&mut *task.process.fd_table.get()).insert(open_file) };
92
93    Ok(fd)
94}
95
96/// Open a file relative to a directory FD.
97///
98/// - If `dir_fd == AT_FDCWD`, resolve against the process CWD (equivalent to `open()`).
99/// - Otherwise, resolve against the path of the given directory FD.
100///
101/// This is the foundation for capability-based path resolution: the directory
102/// FD acts as the root of the namespace for this operation, naturally
103/// preventing `../` escapes beyond the FD's subtree.
104pub fn open_at(dir_fd: u64, path: &str, flags: OpenFlags) -> Result<u32, SyscallError> {
105    if dir_fd == AT_FDCWD as u64 {
106        // Fall back to process CWD : original open() behavior.
107        let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
108        let cwd = unsafe { (&*task.process.cwd.get()).clone() };
109        let abs = resolve_path(path, &cwd);
110        crate::silo::enforce_path_for_current_task(
111            &abs,
112            flags.contains(OpenFlags::READ) || flags.contains(OpenFlags::DIRECTORY),
113            flags.contains(OpenFlags::WRITE) || flags.contains(OpenFlags::CREATE),
114            false,
115        )?;
116        open(&abs, flags)
117    } else {
118        // Resolve relative to the directory FD.
119        let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
120        let fd_table = unsafe { &*task.process.fd_table.get() };
121        let dir_file = fd_table.get(dir_fd as u32)?;
122        if !dir_file.flags().contains(FileFlags::DIRECTORY) {
123            return Err(SyscallError::NotADirectory);
124        }
125        let dir_path = dir_file.path();
126        let abs = resolve_path(path, dir_path);
127        crate::silo::enforce_path_for_current_task(
128            &abs,
129            flags.contains(OpenFlags::READ) || flags.contains(OpenFlags::DIRECTORY),
130            flags.contains(OpenFlags::WRITE) || flags.contains(OpenFlags::CREATE),
131            false,
132        )?;
133        open(&abs, flags)
134    }
135}
136
137/// Stat a file relative to a directory FD.
138///
139/// Same resolution semantics as `open_at`.
140pub fn fstat_at(dir_fd: u64, path: &str) -> Result<FileStat, SyscallError> {
141    if dir_fd == AT_FDCWD as u64 {
142        let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
143        let cwd = unsafe { (&*task.process.cwd.get()).clone() };
144        let abs = resolve_path(path, &cwd);
145        stat_path(&abs)
146    } else {
147        let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
148        let fd_table = unsafe { &*task.process.fd_table.get() };
149        let dir_file = fd_table.get(dir_fd as u32)?;
150        if !dir_file.flags().contains(FileFlags::DIRECTORY) {
151            return Err(SyscallError::NotADirectory);
152        }
153        let dir_path = dir_file.path();
154        let abs = resolve_path(path, dir_path);
155        stat_path(&abs)
156    }
157}
158
159/// Create a directory.
160pub fn mkdir(path: &str, mode: u32) -> Result<(), SyscallError> {
161    let (scheme, relative_path) = mount::resolve(path)?;
162    scheme.create_directory(&relative_path, mode)?;
163    Ok(())
164}
165
166/// Create an empty regular file.
167pub fn create_file(path: &str, mode: u32) -> Result<(), SyscallError> {
168    let (scheme, relative_path) = mount::resolve(path)?;
169    scheme.create_file(&relative_path, mode)?;
170    Ok(())
171}
172
173/// Remove a file or directory.
174pub fn unlink(path: &str) -> Result<(), SyscallError> {
175    let (scheme, relative_path) = mount::resolve(path)?;
176    scheme.unlink(&relative_path)?;
177    Ok(())
178}
179
180/// Rename a file or directory (must be within the same mount).
181pub fn rename(old_path: &str, new_path: &str) -> Result<(), SyscallError> {
182    let (scheme, old_rel) = mount::resolve(old_path)?;
183    let (scheme2, new_rel) = mount::resolve(new_path)?;
184    if !Arc::ptr_eq(&scheme, &scheme2) {
185        return Err(SyscallError::NotSupported);
186    }
187    scheme.rename(&old_rel, &new_rel)
188}
189
190/// Change permission bits on a path.
191pub fn chmod(path: &str, mode: u32) -> Result<(), SyscallError> {
192    let (scheme, relative_path) = mount::resolve(path)?;
193    scheme.chmod(&relative_path, mode)
194}
195
196/// Change permission bits on an open fd.
197pub fn fchmod(fd: u32, mode: u32) -> Result<(), SyscallError> {
198    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
199    let fd_table = unsafe { &*task.process.fd_table.get() };
200    let file = fd_table.get(fd)?;
201    file.scheme().fchmod(file.file_id(), mode)
202}
203
204/// Truncate a file by path.
205pub fn truncate(path: &str, length: u64) -> Result<(), SyscallError> {
206    let (scheme, relative_path) = mount::resolve(path)?;
207    let res = scheme.open(&relative_path, OpenFlags::WRITE)?;
208    let r = scheme.truncate(res.file_id, length);
209    let _ = scheme.close(res.file_id);
210    r
211}
212
213/// Truncate an open fd.
214pub fn ftruncate(fd: u32, length: u64) -> Result<(), SyscallError> {
215    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
216    let fd_table = unsafe { &*task.process.fd_table.get() };
217    let file = fd_table.get(fd)?;
218    file.scheme().truncate(file.file_id(), length)
219}
220
221/// Create a hard link (must be within the same mount).
222pub fn link(old_path: &str, new_path: &str) -> Result<(), SyscallError> {
223    let (scheme, old_rel) = mount::resolve(old_path)?;
224    let (scheme2, new_rel) = mount::resolve(new_path)?;
225    if !Arc::ptr_eq(&scheme, &scheme2) {
226        return Err(SyscallError::NotSupported);
227    }
228    scheme.link(&old_rel, &new_rel)
229}
230
231/// Create a symbolic link.
232pub fn symlink(target: &str, link_path: &str) -> Result<(), SyscallError> {
233    let (scheme, link_rel) = mount::resolve(link_path)?;
234    scheme.symlink(target, &link_rel)
235}
236
237/// Read the target of a symbolic link.
238pub fn readlink(path: &str) -> Result<String, SyscallError> {
239    let (scheme, relative_path) = mount::resolve(path)?;
240    scheme.readlink(&relative_path)
241}
242
243/// Read from a file descriptor.
244pub fn read(fd: u32, buf: &mut [u8]) -> Result<usize, SyscallError> {
245    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
246    // SAFETY: Syscall context
247    let fd_table = unsafe { &*task.process.fd_table.get() };
248    let file = fd_table.get(fd)?;
249    file.read(buf)
250}
251
252/// Write to a file descriptor.
253pub fn write(fd: u32, buf: &[u8]) -> Result<usize, SyscallError> {
254    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
255    // SAFETY: Syscall context
256    let fd_table = unsafe { &*task.process.fd_table.get() };
257    let file = fd_table.get(fd)?;
258    file.write(buf)
259}
260
261/// Close a file descriptor.
262///
263/// Removes the fd from the table.  If this was the last Arc<OpenFile> reference
264/// (no dup'd / fork'd copies remain) the Drop impl will call scheme.close().
265pub fn close(fd: u32) -> Result<(), SyscallError> {
266    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
267    // SAFETY: Syscall context
268    let fd_table = unsafe { &mut *task.process.fd_table.get() };
269    let _file = fd_table.remove(fd)?;
270    Ok(())
271    // _file (Arc<OpenFile>) is dropped here; if refcount → 0, Drop fires → scheme.close()
272}
273
274/// Seek within a file.
275pub fn seek(fd: u32, offset: u64) -> Result<u64, SyscallError> {
276    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
277    // SAFETY: Syscall context
278    let fd_table = unsafe { &*task.process.fd_table.get() };
279    let file = fd_table.get(fd)?;
280    file.seek(offset)
281}
282
283/// Get current offset in a file.
284pub fn tell(fd: u32) -> Result<u64, SyscallError> {
285    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
286    // SAFETY: Syscall context
287    let fd_table = unsafe { &*task.process.fd_table.get() };
288    let file = fd_table.get(fd)?;
289    Ok(file.tell())
290}
291
292/// Get file size.
293pub fn fsize(fd: u32) -> Result<u64, SyscallError> {
294    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
295    // SAFETY: Syscall context
296    let fd_table = unsafe { &*task.process.fd_table.get() };
297    let file = fd_table.get(fd)?;
298    file.size()
299}
300
301/// Sync file to storage.
302pub fn fsync(fd: u32) -> Result<(), SyscallError> {
303    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
304    // SAFETY: Syscall context
305    let fd_table = unsafe { &*task.process.fd_table.get() };
306    let file = fd_table.get(fd)?;
307    file.sync()
308}
309
310/// POSIX lseek on a file descriptor.
311pub fn lseek(fd: u32, offset: i64, whence: u32) -> Result<u64, SyscallError> {
312    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
313    let fd_table = unsafe { &*task.process.fd_table.get() };
314    let file = fd_table.get(fd)?;
315    file.lseek(offset, whence)
316}
317
318/// fstat on an open file descriptor.
319pub fn fstat(fd: u32) -> Result<FileStat, SyscallError> {
320    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
321    let fd_table = unsafe { &*task.process.fd_table.get() };
322    let file = fd_table.get(fd)?;
323    file.stat()
324}
325
326/// stat by path (opens, stats, closes).
327pub fn stat_path(path: &str) -> Result<FileStat, SyscallError> {
328    let (scheme, relative_path) = mount::resolve(path)?;
329    let open_result = scheme.open(&relative_path, OpenFlags::READ)?;
330    let result = scheme.stat(open_result.file_id);
331    let _ = scheme.close(open_result.file_id);
332    result
333}
334
335/// Read directory entries from an open directory fd.
336pub fn getdents(fd: u32) -> Result<alloc::vec::Vec<DirEntry>, SyscallError> {
337    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
338    let fd_table = unsafe { &*task.process.fd_table.get() };
339    let file = fd_table.get(fd)?;
340    file.readdir()
341}
342
343/// Create a background stdin: a pipe read-end whose write end is immediately
344/// closed.  Any `read()` on the returned file will return 0 (EOF) at once,
345/// preventing processes launched in the background from blocking on stdin or
346/// spinning on EBADF.
347pub fn create_background_stdin() -> Arc<OpenFile> {
348    let pipe_scheme = get_pipe_scheme();
349    let (base_id, pipe) = pipe_scheme.create_pipe();
350
351    // Close write end now (refcount → 0 → write_closed = true).
352    // Subsequent reads on the read end will return EOF immediately.
353    pipe.close_write();
354
355    let dyn_scheme: DynScheme = pipe_scheme as Arc<dyn Scheme>;
356    Arc::new(OpenFile::new(
357        dyn_scheme,
358        base_id, // even = read end
359        String::from("pipe:[bg-stdin]"),
360        OpenFlags::READ,
361        FileFlags::PIPE,
362        None,
363    ))
364}
365
366/// Create a pipe, returning (read_fd, write_fd).
367pub fn pipe() -> Result<(u32, u32), SyscallError> {
368    let pipe_scheme = get_pipe_scheme();
369    let (base_id, _pipe) = pipe_scheme.create_pipe();
370
371    let dyn_scheme: DynScheme = pipe_scheme as Arc<dyn Scheme>;
372
373    let read_file = Arc::new(OpenFile::new(
374        dyn_scheme.clone(),
375        base_id,
376        String::from("pipe:[read]"),
377        OpenFlags::READ,
378        FileFlags::PIPE,
379        None,
380    ));
381    let write_file = Arc::new(OpenFile::new(
382        dyn_scheme.clone(),
383        base_id + 1,
384        String::from("pipe:[write]"),
385        OpenFlags::WRITE,
386        FileFlags::PIPE,
387        None,
388    ));
389
390    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
391    let fd_table = unsafe { &mut *task.process.fd_table.get() };
392    let read_fd = fd_table.insert(read_file);
393    let write_fd = fd_table.insert(write_file);
394
395    Ok((read_fd, write_fd))
396}
397
398/// Duplicate a file descriptor (POSIX dup).
399pub fn dup(old_fd: u32) -> Result<u32, SyscallError> {
400    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
401    let fd_table = unsafe { &mut *task.process.fd_table.get() };
402    fd_table.duplicate(old_fd)
403}
404
405/// Duplicate a file descriptor to a specific number (POSIX dup2).
406pub fn dup2(old_fd: u32, new_fd: u32) -> Result<u32, SyscallError> {
407    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
408    let fd_table = unsafe { &mut *task.process.fd_table.get() };
409    fd_table.duplicate_to(old_fd, new_fd)
410}
411
412/// Read all remaining bytes from a file descriptor.
413pub fn read_all(fd: u32) -> Result<alloc::vec::Vec<u8>, SyscallError> {
414    let mut out = alloc::vec::Vec::new();
415    let mut buf = [0u8; 4096];
416    loop {
417        let n = read(fd, &mut buf)?;
418        if n == 0 {
419            break;
420        }
421        out.extend_from_slice(&buf[..n]);
422    }
423    Ok(out)
424}
425
426// ============================================================================
427// Syscall Handlers (Native ABI)
428// ============================================================================
429
430/// Syscall handler for opening a file.
431pub fn sys_open(path_ptr: u64, path_len: u64, flags: u64) -> Result<u64, SyscallError> {
432    const MAX_PATH_LEN: usize = 4096;
433    if path_len == 0 || path_len as usize > MAX_PATH_LEN {
434        return Err(SyscallError::InvalidArgument);
435    }
436
437    let raw = read_user_path(path_ptr, path_len)?;
438    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
439    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
440    let path = resolve_path(&raw, &cwd);
441
442    let open_flags = OpenFlags::from_bits_truncate(flags as u32);
443
444    let want_read =
445        open_flags.contains(OpenFlags::READ) || open_flags.contains(OpenFlags::DIRECTORY);
446    let want_write = open_flags.contains(OpenFlags::WRITE)
447        || open_flags.contains(OpenFlags::CREATE)
448        || open_flags.contains(OpenFlags::TRUNCATE)
449        || open_flags.contains(OpenFlags::APPEND);
450    crate::silo::enforce_path_for_current_task(&path, want_read, want_write, false)?;
451
452    let fd = open(&path, open_flags)?;
453    Ok(fd as u64)
454}
455
456/// SYS_OPENAT (462): Open a file relative to a directory FD.
457pub fn sys_openat(
458    dir_fd: u64,
459    path_ptr: u64,
460    path_len: u64,
461    flags: u64,
462) -> Result<u64, SyscallError> {
463    const MAX_PATH_LEN: usize = 4096;
464    if path_len == 0 || path_len as usize > MAX_PATH_LEN {
465        return Err(SyscallError::InvalidArgument);
466    }
467    let raw = read_user_path(path_ptr, path_len)?;
468    let open_flags = OpenFlags::from_bits_truncate(flags as u32);
469    let fd = open_at(dir_fd, &raw, open_flags)?;
470    Ok(fd as u64)
471}
472
473/// SYS_FSTATAT (463): Stat a file relative to a directory FD.
474pub fn sys_fstatat(
475    dir_fd: u64,
476    path_ptr: u64,
477    path_len: u64,
478    _flags: u64,
479) -> Result<u64, SyscallError> {
480    const MAX_PATH_LEN: usize = 4096;
481    if path_len == 0 || path_len as usize > MAX_PATH_LEN {
482        return Err(SyscallError::InvalidArgument);
483    }
484    let raw = read_user_path(path_ptr, path_len)?;
485    let st = fstat_at(dir_fd, &raw)?;
486    // TODO: copy stat struct to userspace : for now return error as placeholder
487    let _ = st;
488    Err(SyscallError::NotImplemented)
489}
490
491/// Syscall handler for reading from a file.
492pub fn sys_read(fd: u32, buf_ptr: u64, buf_len: u64) -> Result<u64, SyscallError> {
493    if buf_len == 0 {
494        return Ok(0);
495    }
496
497    // Read directly into chunks to avoid large kernel allocations
498    let mut kbuf = [0u8; 4096];
499    let mut total_read = 0;
500
501    while total_read < buf_len as usize {
502        let to_read = core::cmp::min(kbuf.len(), buf_len as usize - total_read);
503        let n = read(fd, &mut kbuf[..to_read])?;
504        if n == 0 {
505            break;
506        }
507
508        let chunk_user = UserSliceWrite::new(buf_ptr + total_read as u64, n)?;
509        chunk_user.copy_from(&kbuf[..n]);
510
511        total_read += n;
512        if n < to_read {
513            break;
514        }
515    }
516
517    Ok(total_read as u64)
518}
519
520/// Syscall handler for writing to a file.
521pub fn sys_write(fd: u32, buf_ptr: u64, buf_len: u64) -> Result<u64, SyscallError> {
522    if buf_len == 0 {
523        return Ok(0);
524    }
525
526    // For stdout/stderr, fall back to direct console output only when no
527    // FD entry exists (early boot).  Once the FD table is populated (or
528    // after dup2 redirection) the normal VFS path is used.
529    if fd == 1 || fd == 2 {
530        let use_console = match current_task_clone() {
531            Some(t) => {
532                let fd_table = unsafe { &*t.process.fd_table.get() };
533                !fd_table.contains(fd)
534            }
535            None => true,
536        };
537        if use_console {
538            crate::silo::enforce_console_access()?;
539            let len = core::cmp::min(buf_len as usize, 16 * 1024);
540            let mut kbuf = [0u8; 4096];
541            let mut total_written = 0;
542            while total_written < len {
543                let to_write = core::cmp::min(kbuf.len(), len - total_written);
544                let chunk = UserSliceRead::new(buf_ptr + total_written as u64, to_write)?;
545                let n = chunk.copy_to(&mut kbuf[..to_write]);
546                if crate::arch::x86_64::vga::is_available() {
547                    if let Ok(s) = core::str::from_utf8(&kbuf[..n]) {
548                        crate::serial_print!("{}", s);
549                        crate::vga_print!("{}", s);
550                    } else {
551                        for &byte in &kbuf[..n] {
552                            crate::serial_print!("{}", byte as char);
553                        }
554                    }
555                } else {
556                    for &byte in &kbuf[..n] {
557                        crate::serial_print!("{}", byte as char);
558                    }
559                }
560                total_written += n;
561            }
562            return Ok(total_written as u64);
563        }
564    }
565
566    let mut kbuf = [0u8; 4096];
567    let mut total_written = 0;
568
569    while total_written < buf_len as usize {
570        let to_write = core::cmp::min(kbuf.len(), buf_len as usize - total_written);
571        let chunk_user = UserSliceRead::new(buf_ptr + total_written as u64, to_write)?;
572        chunk_user.copy_to(&mut kbuf[..to_write]);
573
574        let n = write(fd, &kbuf[..to_write])?;
575        total_written += n;
576        if n < to_write {
577            break;
578        }
579    }
580
581    Ok(total_written as u64)
582}
583
584/// Syscall handler for closing a file.
585pub fn sys_close(fd: u32) -> Result<u64, SyscallError> {
586    close(fd)?;
587    Ok(0)
588}
589
590/// Syscall handler for lseek.
591pub fn sys_lseek(fd: u32, offset: i64, whence: u32) -> Result<u64, SyscallError> {
592    lseek(fd, offset, whence)
593}
594
595/// Syscall handler for fstat.
596pub fn sys_fstat(fd: u32, stat_ptr: u64) -> Result<u64, SyscallError> {
597    let st = fstat(fd)?;
598    let user = UserSliceWrite::new(stat_ptr, core::mem::size_of::<FileStat>())?;
599    let bytes = unsafe {
600        core::slice::from_raw_parts(
601            &st as *const FileStat as *const u8,
602            core::mem::size_of::<FileStat>(),
603        )
604    };
605    user.copy_from(bytes);
606    Ok(0)
607}
608
609/// Syscall handler for stat (by path).
610pub fn sys_stat(path_ptr: u64, path_len: u64, stat_ptr: u64) -> Result<u64, SyscallError> {
611    const MAX_PATH_LEN: usize = 4096;
612    if path_len == 0 || path_len as usize > MAX_PATH_LEN {
613        return Err(SyscallError::InvalidArgument);
614    }
615    let raw = read_user_path(path_ptr, path_len)?;
616    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
617    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
618    let path = resolve_path(&raw, &cwd);
619    crate::silo::enforce_path_for_current_task(&path, true, false, false)?;
620
621    let st = stat_path(&path)?;
622    let user_out = UserSliceWrite::new(stat_ptr, core::mem::size_of::<FileStat>())?;
623    let out_bytes = unsafe {
624        core::slice::from_raw_parts(
625            &st as *const FileStat as *const u8,
626            core::mem::size_of::<FileStat>(),
627        )
628    };
629    user_out.copy_from(out_bytes);
630    Ok(0)
631}
632
633/// Syscall handler for getdents.
634///
635/// Writes a packed array of `KernelDirent` entries into the user buffer.
636/// Returns the number of bytes written.
637pub fn sys_getdents(fd: u32, buf_ptr: u64, buf_len: u64) -> Result<u64, SyscallError> {
638    use strat9_abi::data::DirentHeader;
639
640    let entries = getdents(fd)?;
641    let mut offset: usize = 0;
642    let buf_size = buf_len as usize;
643
644    for entry in &entries {
645        let name_bytes = entry.name.as_bytes();
646        let name_len = core::cmp::min(name_bytes.len(), 255) as u16;
647        let entry_size = DirentHeader::SIZE + name_len as usize + 1;
648
649        if offset + entry_size > buf_size {
650            break;
651        }
652
653        let user = UserSliceWrite::new(buf_ptr + offset as u64, entry_size)?;
654        let mut kbuf = [0u8; 268];
655        kbuf[0..8].copy_from_slice(&entry.ino.to_le_bytes());
656        kbuf[8] = entry.file_type;
657        kbuf[9..11].copy_from_slice(&name_len.to_le_bytes());
658        kbuf[11] = 0; // DirentHeader::_padding
659        kbuf[12..12 + name_len as usize].copy_from_slice(&name_bytes[..name_len as usize]);
660        kbuf[12 + name_len as usize] = 0;
661        user.copy_from(&kbuf[..entry_size]);
662
663        offset += entry_size;
664    }
665
666    Ok(offset as u64)
667}
668
669/// Syscall handler for pipe.
670pub fn sys_pipe(fds_ptr: u64) -> Result<u64, SyscallError> {
671    let (read_fd, write_fd) = pipe()?;
672    let user = UserSliceWrite::new(fds_ptr, 8)?; // 2 x u32
673    let mut buf = [0u8; 8];
674    buf[0..4].copy_from_slice(&read_fd.to_le_bytes());
675    buf[4..8].copy_from_slice(&write_fd.to_le_bytes());
676    user.copy_from(&buf);
677    Ok(0)
678}
679
680/// Syscall handler for dup.
681pub fn sys_dup(old_fd: u32) -> Result<u64, SyscallError> {
682    let new_fd = dup(old_fd)?;
683    Ok(new_fd as u64)
684}
685
686/// Syscall handler for dup2.
687pub fn sys_dup2(old_fd: u32, new_fd: u32) -> Result<u64, SyscallError> {
688    let fd = dup2(old_fd, new_fd)?;
689    Ok(fd as u64)
690}
691
692// ========== Path helpers ==============================
693
694/// Read a NUL-terminated or length-bounded path from user space.
695///
696/// `path_ptr` and `path_len` come directly from syscall arguments.
697/// If `path_len` is 0 the string is assumed to be NUL-terminated up to 4096 bytes.
698fn read_user_path(path_ptr: u64, path_len: u64) -> Result<alloc::string::String, SyscallError> {
699    const MAX_PATH: usize = 4096;
700    let len = if path_len == 0 || path_len as usize > MAX_PATH {
701        MAX_PATH
702    } else {
703        path_len as usize
704    };
705    let user = UserSliceRead::new(path_ptr, len)?;
706    let bytes = user.read_to_vec();
707    // Trim at first NUL byte if present.
708    let trimmed = bytes.split(|&b| b == 0).next().unwrap_or(&bytes);
709    if trimmed.is_empty() {
710        return Err(SyscallError::InvalidArgument);
711    }
712    core::str::from_utf8(trimmed)
713        .map(|s| alloc::string::String::from(s))
714        .map_err(|_| SyscallError::InvalidArgument)
715}
716
717/// Resolve `path` relative to the current working directory when it is not
718/// absolute. Returns the normalized absolute path.
719fn resolve_path(path: &str, cwd: &str) -> alloc::string::String {
720    let raw = if path.starts_with('/') {
721        alloc::string::String::from(path)
722    } else if cwd.ends_with('/') {
723        alloc::format!("{}{}", cwd, path)
724    } else {
725        alloc::format!("{}/{}", cwd, path)
726    };
727    normalize_path(&raw)
728}
729
730/// Collapse `.`, `..` and duplicate `/` in an absolute path.
731fn normalize_path(path: &str) -> alloc::string::String {
732    let mut parts: alloc::vec::Vec<&str> = alloc::vec::Vec::new();
733    for seg in path.split('/') {
734        match seg {
735            "" | "." => {}
736            ".." => {
737                parts.pop();
738            }
739            other => parts.push(other),
740        }
741    }
742    let mut out = alloc::string::String::with_capacity(path.len());
743    if parts.is_empty() {
744        out.push('/');
745    } else {
746        for p in &parts {
747            out.push('/');
748            out.push_str(p);
749        }
750    }
751    out
752}
753
754// ========== New VFS syscall handlers ================================================================================================================================================================
755
756/// SYS_CHDIR (440): Change current working directory.
757pub fn sys_chdir(path_ptr: u64, path_len: u64) -> Result<u64, SyscallError> {
758    let raw = read_user_path(path_ptr, path_len)?;
759    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
760    let cwd = unsafe { &*task.process.cwd.get() };
761    let abs = resolve_path(&raw, cwd);
762    crate::silo::enforce_path_for_current_task(&abs, true, false, false)?;
763
764    let (scheme, rel) = mount::resolve(&abs)?;
765    let res = scheme.open(&rel, OpenFlags::READ | OpenFlags::DIRECTORY)?;
766    let _ = scheme.close(res.file_id);
767
768    unsafe { *task.process.cwd.get() = abs };
769    Ok(0)
770}
771
772/// SYS_FCHDIR (441): Change cwd using an open file descriptor.
773pub fn sys_fchdir(fd: u32) -> Result<u64, SyscallError> {
774    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
775    let path = {
776        let fd_table = unsafe { &*task.process.fd_table.get() };
777        let file = fd_table.get(fd)?;
778        alloc::string::String::from(file.path())
779    };
780    unsafe { *task.process.cwd.get() = path };
781    Ok(0)
782}
783
784/// SYS_GETCWD (442): Write the current working directory into a user buffer.
785pub fn sys_getcwd(buf_ptr: u64, buf_len: u64) -> Result<u64, SyscallError> {
786    if buf_len == 0 {
787        return Err(SyscallError::InvalidArgument);
788    }
789    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
790    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
791    let bytes = cwd.as_bytes();
792    let needed = bytes.len() + 1; // include NUL terminator
793    if needed > buf_len as usize {
794        return Err(SyscallError::Range);
795    }
796    let out = UserSliceWrite::new(buf_ptr, needed)?;
797    let mut tmp = alloc::vec![0u8; needed];
798    tmp[..bytes.len()].copy_from_slice(bytes);
799    tmp[bytes.len()] = 0;
800    out.copy_from(&tmp);
801    Ok(needed as u64) // Like Linux: returns byte count written (including NUL)
802}
803
804/// SYS_IOCTL (443): I/O control : stub.
805///
806/// Returns ENOTTY for all file descriptors that are not character devices.
807/// Terminal / PTY support will be added when a TTY driver is implemented.
808pub fn sys_ioctl(_fd: u32, _request: u64, _arg: u64) -> Result<u64, SyscallError> {
809    Err(SyscallError::NotATty)
810}
811
812/// SYS_UMASK (444): Set file creation mask; return the old mask.
813pub fn sys_umask(mask: u64) -> Result<u64, SyscallError> {
814    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
815    let old = task
816        .process
817        .umask
818        .swap(mask as u32 & 0o777, core::sync::atomic::Ordering::Relaxed);
819    Ok(old as u64)
820}
821
822/// SYS_UNLINK (445): Remove a file.
823pub fn sys_unlink(path_ptr: u64, path_len: u64) -> Result<u64, SyscallError> {
824    let raw = read_user_path(path_ptr, path_len)?;
825    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
826    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
827    let abs = resolve_path(&raw, &cwd);
828    crate::silo::enforce_path_for_current_task(&abs, false, true, false)?;
829    unlink(&abs)?;
830    Ok(0)
831}
832
833/// SYS_RMDIR (446): Remove an empty directory.
834pub fn sys_rmdir(path_ptr: u64, path_len: u64) -> Result<u64, SyscallError> {
835    let raw = read_user_path(path_ptr, path_len)?;
836    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
837    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
838    let abs = resolve_path(&raw, &cwd);
839    crate::silo::enforce_path_for_current_task(&abs, false, true, false)?;
840    unlink(&abs)?;
841    Ok(0)
842}
843
844/// SYS_MKDIR (447): Create a directory.
845pub fn sys_mkdir(path_ptr: u64, path_len: u64, mode: u64) -> Result<u64, SyscallError> {
846    let raw = read_user_path(path_ptr, path_len)?;
847    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
848    let umask = task
849        .process
850        .umask
851        .load(core::sync::atomic::Ordering::Relaxed);
852    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
853    let abs = resolve_path(&raw, &cwd);
854    crate::silo::enforce_path_for_current_task(&abs, false, true, false)?;
855    let effective_mode = (mode as u32) & !umask;
856    mkdir(&abs, effective_mode)?;
857    Ok(0)
858}
859
860/// SYS_RENAME (448): Rename a file or directory.
861pub fn sys_rename(
862    old_ptr: u64,
863    old_len: u64,
864    new_ptr: u64,
865    new_len: u64,
866) -> Result<u64, SyscallError> {
867    let old = read_user_path(old_ptr, old_len)?;
868    let new = read_user_path(new_ptr, new_len)?;
869    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
870    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
871    let old_abs = resolve_path(&old, &cwd);
872    let new_abs = resolve_path(&new, &cwd);
873    crate::silo::enforce_path_for_current_task(&old_abs, true, true, false)?;
874    crate::silo::enforce_path_for_current_task(&new_abs, false, true, false)?;
875    rename(&old_abs, &new_abs)?;
876    Ok(0)
877}
878
879/// SYS_LINK (449): Create a hard link.
880pub fn sys_link(
881    old_ptr: u64,
882    old_len: u64,
883    new_ptr: u64,
884    new_len: u64,
885) -> Result<u64, SyscallError> {
886    let old = read_user_path(old_ptr, old_len)?;
887    let new = read_user_path(new_ptr, new_len)?;
888    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
889    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
890    let old_abs = resolve_path(&old, &cwd);
891    let new_abs = resolve_path(&new, &cwd);
892    crate::silo::enforce_path_for_current_task(&old_abs, true, false, false)?;
893    crate::silo::enforce_path_for_current_task(&new_abs, false, true, false)?;
894    link(&old_abs, &new_abs)?;
895    Ok(0)
896}
897
898/// SYS_SYMLINK (450): Create a symbolic link.
899pub fn sys_symlink(
900    target_ptr: u64,
901    target_len: u64,
902    linkpath_ptr: u64,
903    linkpath_len: u64,
904) -> Result<u64, SyscallError> {
905    let target = read_user_path(target_ptr, target_len)?;
906    let linkpath = read_user_path(linkpath_ptr, linkpath_len)?;
907    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
908    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
909    let link_abs = resolve_path(&linkpath, &cwd);
910    crate::silo::enforce_path_for_current_task(&link_abs, false, true, false)?;
911    symlink(&target, &link_abs)?;
912    Ok(0)
913}
914
915/// SYS_READLINK (451): Read a symbolic link.
916pub fn sys_readlink(
917    path_ptr: u64,
918    path_len: u64,
919    buf_ptr: u64,
920    buf_len: u64,
921) -> Result<u64, SyscallError> {
922    let path = read_user_path(path_ptr, path_len)?;
923    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
924    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
925    let abs = resolve_path(&path, &cwd);
926    crate::silo::enforce_path_for_current_task(&abs, true, false, false)?;
927    let target = readlink(&abs)?;
928    let bytes = target.as_bytes();
929    let n = bytes.len().min(buf_len as usize);
930    let user = UserSliceWrite::new(buf_ptr, n)?;
931    user.copy_from(&bytes[..n]);
932    Ok(n as u64)
933}
934
935/// SYS_CHMOD (452): Change file mode bits.
936pub fn sys_chmod(path_ptr: u64, path_len: u64, mode: u64) -> Result<u64, SyscallError> {
937    let path = read_user_path(path_ptr, path_len)?;
938    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
939    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
940    let abs = resolve_path(&path, &cwd);
941    crate::silo::enforce_path_for_current_task(&abs, false, true, false)?;
942    chmod(&abs, mode as u32)?;
943    Ok(0)
944}
945
946/// SYS_FCHMOD (453): Change file mode bits on open fd.
947pub fn sys_fchmod(fd: u32, mode: u64) -> Result<u64, SyscallError> {
948    fchmod(fd, mode as u32)?;
949    Ok(0)
950}
951
952/// SYS_TRUNCATE (454): Truncate file to given length.
953pub fn sys_truncate(path_ptr: u64, path_len: u64, length: u64) -> Result<u64, SyscallError> {
954    let path = read_user_path(path_ptr, path_len)?;
955    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
956    let cwd = unsafe { (&*task.process.cwd.get()).clone() };
957    let abs = resolve_path(&path, &cwd);
958    crate::silo::enforce_path_for_current_task(&abs, false, true, false)?;
959    truncate(&abs, length)?;
960    Ok(0)
961}
962
963/// SYS_FTRUNCATE (455): Truncate open fd to given length.
964pub fn sys_ftruncate(fd: u32, length: u64) -> Result<u64, SyscallError> {
965    ftruncate(fd, length)?;
966    Ok(0)
967}
968
969/// SYS_PREAD (456): Read at offset without changing fd position.
970pub fn sys_pread(fd: u32, buf_ptr: u64, buf_len: u64, offset: u64) -> Result<u64, SyscallError> {
971    if buf_len == 0 {
972        return Ok(0);
973    }
974    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
975    let fd_table = unsafe { &*task.process.fd_table.get() };
976    let file = fd_table.get(fd)?;
977    let mut kbuf = [0u8; 4096];
978    let mut total = 0usize;
979    let mut off = offset;
980    while total < buf_len as usize {
981        let to_read = core::cmp::min(kbuf.len(), buf_len as usize - total);
982        let n = file.pread(off, &mut kbuf[..to_read])?;
983        if n == 0 {
984            break;
985        }
986        let user = UserSliceWrite::new(buf_ptr + total as u64, n)?;
987        user.copy_from(&kbuf[..n]);
988        total += n;
989        off += n as u64;
990        if n < to_read {
991            break;
992        }
993    }
994    Ok(total as u64)
995}
996
997/// SYS_PWRITE (457): Write at offset without changing fd position.
998pub fn sys_pwrite(fd: u32, buf_ptr: u64, buf_len: u64, offset: u64) -> Result<u64, SyscallError> {
999    if buf_len == 0 {
1000        return Ok(0);
1001    }
1002    let task = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
1003    let fd_table = unsafe { &*task.process.fd_table.get() };
1004    let file = fd_table.get(fd)?;
1005    let mut kbuf = [0u8; 4096];
1006    let mut total = 0usize;
1007    let mut off = offset;
1008    while total < buf_len as usize {
1009        let to_write = core::cmp::min(kbuf.len(), buf_len as usize - total);
1010        let user = UserSliceRead::new(buf_ptr + total as u64, to_write)?;
1011        user.copy_to(&mut kbuf[..to_write]);
1012        let n = file.pwrite(off, &kbuf[..to_write])?;
1013        total += n;
1014        off += n as u64;
1015        if n < to_write {
1016            break;
1017        }
1018    }
1019    Ok(total as u64)
1020}
1021
1022// ============================================================================
1023// Global PipeScheme singleton
1024// ============================================================================
1025
1026static PIPE_SCHEME: SpinLock<Option<Arc<PipeScheme>>> = SpinLock::new(None);
1027
1028/// Returns pipe scheme.
1029fn get_pipe_scheme() -> Arc<PipeScheme> {
1030    let mut guard = PIPE_SCHEME.lock();
1031    if let Some(ref scheme) = *guard {
1032        return scheme.clone();
1033    }
1034    let scheme = Arc::new(PipeScheme::new());
1035    *guard = Some(scheme.clone());
1036    scheme
1037}
1038
1039/// Performs the build pci inventory text operation.
1040fn build_pci_inventory_text() -> String {
1041    #[cfg(target_arch = "x86_64")]
1042    {
1043        let devices = crate::hardware::pci_client::all_devices();
1044        let mut out = String::new();
1045        out.push_str("bus dev fn vendor device class subclass prog_if irq\n");
1046        for dev in devices.iter() {
1047            let _ = writeln!(
1048                out,
1049                "{:02x} {:02x} {} {:04x} {:04x} {:02x} {:02x} {:02x} {}",
1050                dev.address.bus,
1051                dev.address.device,
1052                dev.address.function,
1053                dev.vendor_id,
1054                dev.device_id,
1055                dev.class_code,
1056                dev.subclass,
1057                dev.prog_if,
1058                dev.interrupt_line
1059            );
1060        }
1061        return out;
1062    }
1063
1064    #[cfg(not(target_arch = "x86_64"))]
1065    {
1066        String::from("unsupported-arch\n")
1067    }
1068}
1069
1070// ============================================================================
1071// Initialization
1072// ============================================================================
1073
1074/// Initialize the VFS with default mounts.
1075pub fn init() {
1076    log::info!("[VFS] Initializing virtual file system");
1077
1078    //  Root filesystem (RamFS on "/") ========================================================================================================================
1079    // Must be mounted before any other scheme so that longest-prefix resolution
1080    // falls back to "/" for paths not covered by a more specific mount point.
1081    let rootfs = alloc::sync::Arc::new(RamfsScheme::new());
1082    if let Err(e) = mount::mount("/", rootfs.clone()) {
1083        log::error!("[VFS] Failed to mount /: {:?}", e);
1084    } else {
1085        // Populate the standard POSIX directory skeleton.
1086        for dir in &[
1087            "bin", "sbin", "etc", "tmp", "usr", "lib", "lib64", "home", "root", "run", "var",
1088            "mnt", "opt", "srv", "dev", "proc", "sys",
1089        ] {
1090            rootfs.ensure_dir(dir);
1091        }
1092        // Nested standard directories
1093        rootfs.ensure_dir("usr/bin");
1094        rootfs.ensure_dir("usr/sbin");
1095        rootfs.ensure_dir("usr/lib");
1096        rootfs.ensure_dir("var/log");
1097        rootfs.ensure_dir("var/tmp");
1098        rootfs.ensure_dir("run/lock");
1099        log::info!("[VFS] Mounted / (ramfs) with standard directory tree");
1100    }
1101
1102    // Initialize scheme router
1103    if let Err(e) = scheme_router::init_builtin_schemes() {
1104        log::error!("[VFS] Failed to init builtin schemes: {:?}", e);
1105    }
1106
1107    // Create and mount kernel scheme for /sys
1108    let kernel_scheme = KernelScheme::new();
1109
1110    // Register some basic kernel files
1111    static VERSION: &[u8] = b"Strat9-OS v0.1.0 (Bedrock)\n";
1112    kernel_scheme.register("version", VERSION.as_ptr(), VERSION.len());
1113
1114    static CMDLINE: &[u8] = b"quiet loglevel=debug\n";
1115    kernel_scheme.register("cmdline", CMDLINE.as_ptr(), CMDLINE.len());
1116
1117    let pci_inventory = build_pci_inventory_text().into_bytes().into_boxed_slice();
1118    let pci_inventory = Box::leak(pci_inventory);
1119    kernel_scheme.register("pci/inventory", pci_inventory.as_ptr(), pci_inventory.len());
1120
1121    let pci_count = pci_inventory
1122        .split(|b| *b == b'\n')
1123        .skip(1)
1124        .filter(|line| !line.is_empty())
1125        .count();
1126    let mut pci_count_str = String::new();
1127    let _ = writeln!(pci_count_str, "{}", pci_count);
1128    let pci_count = Box::leak(pci_count_str.into_bytes().into_boxed_slice());
1129    kernel_scheme.register("pci/count", pci_count.as_ptr(), pci_count.len());
1130
1131    // /sys/cpu/* : CPU information scheme (Plan9-style)
1132    {
1133        let host = crate::arch::x86_64::cpuid::host();
1134        // VFS initializes before SMP/percpu registration is complete.
1135        // Expose at least 1 CPU (BSP) instead of showing 0.
1136        let cpu_count = crate::arch::x86_64::percpu::get_cpu_count().max(1);
1137
1138        let count_s = Box::leak(
1139            alloc::format!("{}\n", cpu_count)
1140                .into_bytes()
1141                .into_boxed_slice(),
1142        );
1143        kernel_scheme.register("cpu/count", count_s.as_ptr(), count_s.len());
1144
1145        let vendor_s = Box::leak(
1146            alloc::format!("{}\n", host.vendor_string())
1147                .into_bytes()
1148                .into_boxed_slice(),
1149        );
1150        kernel_scheme.register("cpu/vendor", vendor_s.as_ptr(), vendor_s.len());
1151
1152        let model_s = Box::leak(
1153            alloc::format!("{}\n", host.model_name_str())
1154                .into_bytes()
1155                .into_boxed_slice(),
1156        );
1157        kernel_scheme.register("cpu/model", model_s.as_ptr(), model_s.len());
1158
1159        let features_s = Box::leak(
1160            alloc::format!(
1161                "{}\n",
1162                crate::arch::x86_64::cpuid::features_to_flags_string(host.features)
1163            )
1164            .into_bytes()
1165            .into_boxed_slice(),
1166        );
1167        kernel_scheme.register("cpu/features", features_s.as_ptr(), features_s.len());
1168
1169        let xcr0_s = Box::leak(
1170            alloc::format!("{:#x}\n", host.max_xcr0)
1171                .into_bytes()
1172                .into_boxed_slice(),
1173        );
1174        kernel_scheme.register("cpu/xcr0", xcr0_s.as_ptr(), xcr0_s.len());
1175
1176        let xsave_s = Box::leak(
1177            alloc::format!("{}\n", host.xsave_size)
1178                .into_bytes()
1179                .into_boxed_slice(),
1180        );
1181        kernel_scheme.register("cpu/xsave_size", xsave_s.as_ptr(), xsave_s.len());
1182    }
1183
1184    let kernel_scheme = Arc::new(kernel_scheme);
1185
1186    // Mount /sys
1187    if let Err(e) = mount::mount("/sys", kernel_scheme.clone()) {
1188        log::error!("[VFS] Failed to mount /sys: {:?}", e);
1189    } else {
1190        log::info!("[VFS] Mounted /sys (kernel scheme)");
1191    }
1192
1193    // Register and mount procfs
1194    let proc_scheme = Arc::new(ProcScheme::new());
1195    if let Err(e) = register_scheme("proc", proc_scheme.clone()) {
1196        log::error!("[VFS] Failed to register proc scheme: {:?}", e);
1197    } else {
1198        log::info!("[VFS] Registered proc scheme");
1199    }
1200
1201    if let Err(e) = mount::mount("/proc", proc_scheme) {
1202        log::error!("[VFS] Failed to mount /proc: {:?}", e);
1203    } else {
1204        log::info!("[VFS] Mounted /proc (procfs)");
1205    }
1206
1207    let ipc_scheme = Arc::new(ipcfs::IpcControlScheme::new());
1208    if let Err(e) = mount::mount("/ipc", ipc_scheme) {
1209        log::error!("[VFS] Failed to mount /ipc: {:?}", e);
1210    } else {
1211        log::info!("[VFS] Mounted /ipc (kernel ipc control scheme)");
1212    }
1213
1214    // Mount /dev : raw block-device scheme backed by AHCI.
1215    // The scheme is registered regardless of whether a disk is present:
1216    // device files appear dynamically when the hardware is available.
1217    let dev_scheme = Arc::new(BlkDevScheme::new());
1218    if let Err(e) = mount::mount("/dev", dev_scheme) {
1219        log::error!("[VFS] Failed to mount /dev: {:?}", e);
1220    } else {
1221        log::info!("[VFS] Mounted /dev (block-device scheme)");
1222    }
1223
1224    // Console scheme (/dev/console) : backs stdin/stdout/stderr for ELF processes
1225    let console = console_scheme::init_console_scheme();
1226    if let Err(e) = mount::mount("/dev/console", console) {
1227        log::error!("[VFS] Failed to mount /dev/console: {:?}", e);
1228    } else {
1229        log::info!("[VFS] Mounted /dev/console (serial + keyboard)");
1230    }
1231
1232    // PTY scheme (/dev/pts) : pseudo-terminals for interactive programs
1233    pty_scheme::init_pty_scheme();
1234    log::info!("[VFS] Mounted /dev/pts (PTY scheme)");
1235
1236    log::info!("[VFS] VFS ready");
1237}