The NeoDOS VFS (Virtual File System) is a unified filesystem interface accessed through a global Mutex<Vfs> protected by helper functions in globals.rs.
The preferred pattern for accessing VFS in kernel code:
use crate::globals;
let result = crate::globals::with_vfs(|vfs| {
// vfs is &mut Vfs - perform operations here
vfs.resolve_path("C:\\path\\to\\file.txt")
});Location: neodos-kernel/src/infra/globals.rs
Definition:
pub fn with_vfs<F, R>(f: F) -> R
where
F: FnOnce(&mut crate::fs::vfs::Vfs) -> R
{
let mut lock = VFS.lock();
f(&mut lock)
}match crate::globals::VFS.lock().read_file(inode_num, 0, &mut buf) {
Ok(bytes_read) => { /* ... */ }
Err(_) => { /* ... */ }
}File: neodos-kernel/src/fs/vfs.rs
pub struct Vfs {
pub drives: [Option<Box<dyn FileSystem>>; 26], // A: through Z:
mounts: [Option<Mount>; MAX_MOUNTS], // Mount points (max 8)
mount_count: usize,
}pub struct VfsNode {
pub inode: u32, // Inode number
pub mode: u16, // Type and permissions (use MODE_FILE, MODE_DIR)
pub size: u32, // File size in bytes
}pub const MODE_DIR: u16 = 0x40; // Directory
pub const MODE_FILE: u16 = 0x80; // Regular filepub enum VfsError {
NotFound,
NotADirectory,
NotAFile,
AlreadyExists,
IOError,
InvalidPath,
PermissionDenied,
NotImplemented,
MountTableFull,
AlreadyMounted,
NotMounted,
DirectoryNotEmpty,
}Resolves a full path and returns drive index + file metadata.
Parameters:
path: Full path like"C:\\System\\Config\\system.cfg"
Returns:
Ok((drive_idx, VfsNode))- drive index (0-25) and file metadataErr(VfsError)- if path is invalid or file not found
Example:
crate::globals::with_vfs(|vfs| {
match vfs.resolve_path("C:\\readme.txt") {
Ok((drive_idx, node)) => {
println!("Inode: {}, Size: {}, IsFile: {}",
node.inode, node.size, node.mode & MODE_FILE != 0);
}
Err(e) => eprintln!("Path error: {:?}", e),
}
})Used in: Syscalls, shell commands, driver loader
Reads file content at specified offset.
Parameters:
drive_idx: Drive index (fromresolve_path)inode: Inode number (fromVfsNode)offset: Byte offset in filebuf: Mutable buffer to read into
Returns:
Ok(bytes_read)- number of bytes readErr(VfsError)- on I/O or permission error
Example:
crate::globals::with_vfs(|vfs| {
let (drive_idx, node) = vfs.resolve_path("C:\\test.nxe")?;
if node.mode & MODE_FILE == 0 {
return Err(VfsError::NotAFile);
}
let mut buf = vec![0u8; node.size as usize];
let bytes_read = vfs.read(drive_idx, node.inode, 0, &mut buf)?;
println!("Read {} bytes", bytes_read);
Ok(())
})Real example from codebase (neodos-kernel/src/drivers/nem/management/boot_loader/mod.rs):
fn read_nem_file(path: &str) -> Result<Vec<u8>, &'static str> {
crate::globals::with_vfs(|vfs| {
let (drive_idx, node) = vfs.resolve_path(path)
.map_err(|_| "VFS resolve failed")?;
if node.mode & MODE_FILE == 0 {
return Err("Not a file");
}
let size = node.size as usize;
if size == 0 || size > 65536 {
return Err("Bad size");
}
let mut buf = alloc::vec::Vec::with_capacity(size);
buf.resize(size, 0);
let read = vfs.read(drive_idx, node.inode, 0, &mut buf)
.map_err(|_| "Read error")?;
buf.truncate(read);
Ok(buf)
})
}Writes data to a file.
Parameters:
drive_idx: Drive indexinode: Inode numberoffset: Byte offsetbuf: Data to write
Returns:
Ok(bytes_written)- number of bytes writtenErr(VfsError)- on I/O or permission error
Lists directory entries.
Returns:
Ok(Some(DirEntry))- next directory entryOk(None)- end of directoryErr(VfsError)- if not a directory or I/O error
pub struct DirEntry {
pub name: String,
pub node: VfsNode,
}Gets file/directory information (size, type, etc).
Creates a new file.
Creates a new directory.
Deletes a file.
Deletes an empty directory.
Renames a file or directory.
Mounts a filesystem on a drive letter (A-Z).
Unmounts a filesystem.
Mounts a drive at a subdirectory.
Unmounts from a subdirectory.
Gets the volume label of a drive.
Sets the volume label.
Any filesystem implementation must implement this trait:
pub trait FileSystem: Send {
fn read(&mut self, inode: u32, offset: u64, buf: &mut [u8])
-> Result<usize, VfsError>;
fn write(&mut self, inode: u32, offset: u64, buf: &[u8])
-> Result<usize, VfsError>;
fn lookup(&mut self, dir_inode: u32, name: &str)
-> Result<VfsNode, VfsError>;
fn readdir(&mut self, dir_inode: u32, index: usize)
-> Result<Option<DirEntry>, VfsError>;
fn mkdir(&mut self, dir_inode: u32, name: &str)
-> Result<VfsNode, VfsError>;
fn create(&mut self, dir_inode: u32, name: &str)
-> Result<VfsNode, VfsError>;
fn stat(&mut self, inode: u32)
-> Result<VfsNode, VfsError>;
fn remove_file(&mut self, dir_inode: u32, name: &str)
-> Result<(), VfsError>;
fn remove_dir(&mut self, dir_inode: u32, name: &str)
-> Result<(), VfsError>;
fn rename(&mut self, dir_inode: u32, old_name: &str, new_name: &str)
-> Result<(), VfsError>;
fn volume_label(&self)
-> Result<String, VfsError>;
fn set_volume_label(&mut self, label: &str)
-> Result<(), VfsError>;
}Current implementations:
NeoDosFsV2- NeoDOS native filesystem (NE2, NeoFS v2)Fat32Driver- FAT32 (ESP boot partition)Iso9660Driver- ISO 9660 (CD-ROM) — planned; no implementation yet
crate::globals::with_vfs(|vfs| {
let (drive_idx, node) = vfs.resolve_path("C:\\file.txt")?;
if node.mode & MODE_FILE == 0 {
return Err(VfsError::NotAFile);
}
let size = node.size as usize;
let mut buf = vec![0u8; size];
let bytes_read = vfs.read(drive_idx, node.inode, 0, &mut buf)?;
buf.truncate(bytes_read);
Ok(buf)
})// From sys_readfile syscall
let result = crate::globals::with_vfs(|vfs| {
vfs.read(drive_idx, inode_num, offset, &mut temp_buf)
});
match result {
Ok(bytes_read) => {
unsafe {
core::ptr::copy_nonoverlapping(temp_buf.as_ptr(), buf_ptr, bytes_read);
}
}
Err(_) => return err_to_u64(SyscallError::IOError),
}crate::globals::with_vfs(|vfs| {
let (drive_idx, dir_node) = vfs.resolve_path("C:\\System")?;
let mut index = 0;
while let Ok(Some(entry)) = vfs.readdir(drive_idx, dir_node.inode, index) {
println!("{}: {} bytes", entry.name, entry.node.size);
index += 1;
}
Ok(())
})crate::globals::with_vfs(|vfs| {
let file_node = vfs.create("C:\\newfile.txt")?;
vfs.write(drive_idx, file_node.inode, 0, b"Hello, World!")?;
Ok(())
})The filesystem stack is protected by three global spinlocks declared in
infra/globals.rs:
pub static BLOCK_DEVICES: Mutex<BlockDeviceManager> = ...;
pub static PAGE_CACHE: Mutex<PageCache> = ...;
pub static VFS: Mutex<crate::fs::vfs::Vfs> = ...;The canonical acquisition order is enforced by the diagnostic lock-order
checker (infra/lock_order.rs) and must be respected by every caller:
VFS -> MOUNT_MANAGER -> PAGE_CACHE -> BLOCK_DEVICES
MOUNT_MANAGER (fs/vfs/mount.rs) is acquired only under VFS (unified
mount/unmount) or on its own.
Always acquire the locks through the helpers, never with a raw .lock():
globals::with_vfs(|vfs| ...)globals::with_page_cache(|pc| ...)globals::with_block_devices(|bdevs| ...)vfs::mount::with_mount_manager(|mgr| ...)
Each helper disables preemption for the critical section (required: the timer
must not deschedule a thread holding a filesystem spinlock — #376) and tags the
acquisition in the lock-order graph (#343). Use with_vfs_site(site, ...) to
tag a caller for the #345 VFS owner/waiter diagnostics.
- ✅ Use the guarded helpers (
with_vfs,with_page_cache,with_block_devices,with_mount_manager). - ✅ Keep the critical section bounded: do not hold
VFSacross heap allocation or across an unbounded number of block writes. - ❌ Do not acquire a lock in the inverse order (see the order above).
- ❌ Do not call a raw
VFS.lock()/PAGE_CACHE.lock()/BLOCK_DEVICES.lock()in production code; only boot-time one-shot setup and the in-kernel test harness may do so, and only before APs / interrupts can observe the lock. - ❌ Do not hold a filesystem lock across syscall boundaries or across a blocking wait.
The single VFS mutex is retained in v0.51 after review: the FileSystem
trait is &mut self and Vfs owns the drive objects, so per-drive locking
requires a with_drive API change plus compound-operation atomicity rules.
Moreover, every file operation still funnels through the single global
BLOCK_DEVICES and one synchronous device, so removing the VFS lock alone
would not increase throughput. The design and evidence are recorded in
docs/investigation/vfs-lock-contention-83-report.md; the per-drive split is
the v0.52 follow-up.
neodos-kernel/src/syscall/mod.rs line ~737
let result = crate::globals::with_vfs(|vfs| {
vfs.read(drive_idx, inode_num, offset, &mut temp_buf)
});neodos-kernel/src/drivers/nem/management/boot_loader/mod.rs line ~274
fn read_nem_file(path: &str) -> Result<Vec<u8>, &'static str> {
crate::globals::with_vfs(|vfs| {
let (drive_idx, node) = vfs.resolve_path(path).map_err(|_| "VFS resolve failed")?;
// ... validation ...
let mut buf = alloc::vec::Vec::with_capacity(size);
buf.resize(size, 0);
let read = vfs.read(drive_idx, node.inode, 0, &mut buf).map_err(|_| "Read error")?;
buf.truncate(read);
Ok(buf)
})
}crate::globals::with_vfs(|vfs| {
let (drive_idx, node) = vfs.resolve_path(path)?;
// ... read and display ...
})neodos-kernel/src/infra/boot_benchmark.rs line ~364
match crate::globals::VFS.lock().open_file(path) {
Ok(inode_num) => {
let mut buf = [0u8; 512];
match crate::globals::VFS.lock().read_file(inode_num, 0, &mut buf) {
Ok(bytes_read) => { /* ... */ }
}
}
}- Path-based API: Vfs provides high-level methods that take full paths
- Inode-based API: FileSystem trait works with inode numbers
- Drive abstraction: Drive letters (A-Z) map to mounted filesystems
- Error handling: All operations return
Result<T, VfsError> - Zero-copy reads: Data copied directly to user buffers where possible
- Mount points: Supports mounting drives at directory locations