//! 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("")); }