Phase 2b: wire mkdir + unlink through the VFS to runtime.fs
The engine gained mkdir/unlink in Phase 2a; this exposes them as first-class filesystem operations so programs can use them. - vfs protocol: two new path-based operations, mkdir and unlink (appended, so existing opcodes/offsets are unchanged). - VFS router: a forwardPath helper relays a path-based op under a mount to its backend; the mkdir/unlink cases forward to the mounted filesystem (the flat ramfs refuses them — it has no directories). - fat server: mkdir -> engine.createDirectory, unlink -> engine.removeFile, each resolving the parent via a shared splitParent helper (also used by open-create). - runtime.fs: makeDirectory(path) and remove(path). - fat-test now exercises the whole path — mkdir /mnt/usb/TESTDIR, create + write + read a file inside it, then remove it — behind a new `fat-mutations` QEMU case. Verified: zig build, zig build test, zig build check-fat-image, and a sequential QEMU sweep — fat-mount, fat-mutations (mkdir/write/read/unlink through the mount), vfs, vfs-client-death, log-flush, orderly-shutdown, initial-ramdisk, smoke — green.
This commit is contained in:
@@ -53,6 +53,34 @@ pub fn main(init: runtime.process.Init) void {
|
||||
}
|
||||
}
|
||||
|
||||
// Exercise directory + file mutation through the mount: mkdir, create a file
|
||||
// inside it, read it back, then remove it — proof mkdir/unlink reach the engine.
|
||||
if (fs.makeDirectory("/mnt/usb/TESTDIR")) {
|
||||
var wrote = false;
|
||||
if (fs.open("/mnt/usb/TESTDIR/HELLO.TXT", .{ .create = true, .truncate = true })) |created| {
|
||||
var f = created;
|
||||
wrote = (f.writeAll("mutation-ok") orelse 0) == "mutation-ok".len;
|
||||
f.close();
|
||||
}
|
||||
var readback = false;
|
||||
if (fs.open("/mnt/usb/TESTDIR/HELLO.TXT", .{})) |reopened| {
|
||||
var f = reopened;
|
||||
var buf: [16]u8 = undefined;
|
||||
const got = f.read(&buf) orelse 0;
|
||||
f.close();
|
||||
readback = std.mem.eql(u8, buf[0..got], "mutation-ok");
|
||||
}
|
||||
const removed = fs.remove("/mnt/usb/TESTDIR/HELLO.TXT");
|
||||
const gone = !fs.exists("/mnt/usb/TESTDIR/HELLO.TXT");
|
||||
if (wrote and readback and removed and gone) {
|
||||
_ = runtime.system.write("fat-test: mutations ok\n");
|
||||
} else {
|
||||
writeLine("fat-test: mutations FAILED (wrote={} read={} removed={} gone={})\n", .{ wrote, readback, removed, gone });
|
||||
}
|
||||
} else {
|
||||
_ = runtime.system.write("fat-test: mkdir /mnt/usb/TESTDIR failed\n");
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
while (true) {
|
||||
_ = runtime.system.write("fat-test: ok\n");
|
||||
|
||||
@@ -115,14 +115,24 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
return true; // still serve directly, even if the namespace mount didn't take
|
||||
}
|
||||
|
||||
const ParentLeaf = struct { parent: []const u8, leaf: []const u8 };
|
||||
|
||||
// Split a path into its parent directory and final component: "/a/b" -> ("/a",
|
||||
// "b"); "/b" -> ("/", "b"); "b" -> ("/", "b").
|
||||
fn splitParent(path: []const u8) ParentLeaf {
|
||||
const slash = std.mem.lastIndexOfScalar(u8, path, '/');
|
||||
return .{
|
||||
.parent = if (slash) |s| (if (s == 0) "/" else path[0..s]) else "/",
|
||||
.leaf = if (slash) |s| path[s + 1 ..] else path,
|
||||
};
|
||||
}
|
||||
|
||||
fn handleOpen(out: []u8, path: []const u8, flags: u32) usize {
|
||||
var node = filesystem.resolve(path);
|
||||
if (node == null and flags & protocol.create != 0) {
|
||||
const slash = std.mem.lastIndexOfScalar(u8, path, '/');
|
||||
const parent_path = if (slash) |s| (if (s == 0) "/" else path[0..s]) else "/";
|
||||
const leaf = if (slash) |s| path[s + 1 ..] else path;
|
||||
const parent = filesystem.resolve(parent_path) orelse return fail(out);
|
||||
node = filesystem.createFile(parent, leaf);
|
||||
const split = splitParent(path);
|
||||
const parent = filesystem.resolve(split.parent) orelse return fail(out);
|
||||
node = filesystem.createFile(parent, split.leaf);
|
||||
}
|
||||
var resolved = node orelse return fail(out);
|
||||
// O_TRUNC: replace an existing file's contents rather than overwriting in place
|
||||
@@ -180,6 +190,18 @@ fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.i
|
||||
if (openAt(request.node)) |o| o.used = false;
|
||||
return writeReply(out, .{ .status = 0 }, &.{});
|
||||
},
|
||||
.mkdir => {
|
||||
const split = splitParent(payload[0..@min(payload.len, request.len)]);
|
||||
const parent = filesystem.resolve(split.parent) orelse return fail(out);
|
||||
if (filesystem.createDirectory(parent, split.leaf) == null) return fail(out);
|
||||
return writeReply(out, .{ .status = 0 }, &.{});
|
||||
},
|
||||
.unlink => {
|
||||
const split = splitParent(payload[0..@min(payload.len, request.len)]);
|
||||
const parent = filesystem.resolve(split.parent) orelse return fail(out);
|
||||
if (!filesystem.removeFile(parent, split.leaf)) return fail(out);
|
||||
return writeReply(out, .{ .status = 0 }, &.{});
|
||||
},
|
||||
// A backend is never itself a mount target.
|
||||
.mount, .unmount => return fail(out),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user