M5: VFS mount support — mount table + forwarding router
Turn the flat-ramfs VFS into a router: a mount table maps an absolute path prefix (e.g. /mnt/usb) to a backend server's endpoint, and open/read/write/status/ readdir/close on a path under a mount are forwarded to that backend, which speaks the same vfs-protocol. This is what a FAT filesystem mounts into. - protocol: append readdir / mount / unmount operations, a NodeKind enum (the FSH file types) that now fills FileStatus.kind, a DirectoryEntry record, and a directory open flag. Appended values keep existing clients and tests unchanged. - vfs.zig: a mount table, longest-prefix routing, forwarding of every op on a backend handle, mount/unmount handlers (the backend arrives as the call's capability), and release-on-death that also closes the backend's handles. - path.zig: pure, host-tested mount-prefix matching that never captures a non-boundary like /mnt/usbextra. - unistd: mount(), opendir / readdir / closedir clients. Bare names still resolve in the flat ramfs — the backward-compat contract; the vfs and vfs-client-death tests pass unchanged. End-to-end mount+read is exercised by the FAT server (M6). Host units cover path matching and protocol sizes.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
//! Pure path utilities for the VFS mount router — no IPC, no state, so they are
|
||||
//! host-testable in isolation. The router uses these to decide whether an opened
|
||||
//! path lies under a mount point and, if so, what it looks like relative to that
|
||||
//! mount.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// If `path` lies under `mount_prefix` — equal to it, or the prefix followed by a
|
||||
/// path separator — return the path relative to the mount ("/" for an exact
|
||||
/// match, otherwise the tail beginning with '/'). Returns null when `path` is not
|
||||
/// under the mount, so a prefix like "/mnt/usb" never captures "/mnt/usbextra".
|
||||
pub fn underMount(path: []const u8, mount_prefix: []const u8) ?[]const u8 {
|
||||
if (path.len < mount_prefix.len) return null;
|
||||
if (!std.mem.eql(u8, path[0..mount_prefix.len], mount_prefix)) return null;
|
||||
if (path.len == mount_prefix.len) return "/";
|
||||
if (path[mount_prefix.len] != '/') return null;
|
||||
return path[mount_prefix.len..];
|
||||
}
|
||||
|
||||
/// Whether `path` is absolute (rooted at '/'). Bare names — what the flat ramfs
|
||||
/// uses — are relative and never route through a mount.
|
||||
pub fn isAbsolute(path: []const u8) bool {
|
||||
return path.len > 0 and path[0] == '/';
|
||||
}
|
||||
|
||||
test "underMount matches only at path boundaries" {
|
||||
try std.testing.expectEqualStrings("/", underMount("/mnt/usb", "/mnt/usb").?);
|
||||
try std.testing.expectEqualStrings("/system/kernel", underMount("/mnt/usb/system/kernel", "/mnt/usb").?);
|
||||
try std.testing.expect(underMount("/mnt/usbextra", "/mnt/usb") == null); // not a boundary
|
||||
try std.testing.expect(underMount("/mnt", "/mnt/usb") == null); // shorter than the prefix
|
||||
try std.testing.expect(underMount("/other", "/mnt/usb") == null);
|
||||
try std.testing.expect(underMount("greeting", "/mnt/usb") == null); // a bare name
|
||||
}
|
||||
|
||||
test "isAbsolute distinguishes paths from bare names" {
|
||||
try std.testing.expect(isAbsolute("/mnt/usb"));
|
||||
try std.testing.expect(!isAbsolute("greeting"));
|
||||
try std.testing.expect(!isAbsolute(""));
|
||||
}
|
||||
Reference in New Issue
Block a user