Phase 2c: rename, wired through the VFS to runtime.fs
Completes the Phase 2 FAT mutation set (truncate, mkdir, unlink, rename). - engine: rename(dir, old_name, new_name) rewrites an existing entry's 8.3 name in place within the same directory. Refuses a missing source, a non-8.3 target, or a name that already exists; drops any long-name entries on the old file (it takes its new 8.3 name), LFN-aware like removeFile. Cross-directory and long-name- preserving rename are noted limitations. Host-tested (rename keeps contents; collision, non-8.3, and missing-source are refused). - vfs protocol: a `rename` operation whose payload is old-path, a 0x00 separator, then new-path. - VFS router: a forwardRename helper + a `.rename` case that requires both paths under the same mount (cross-filesystem rename is refused) and forwards the mount-relative old+new. - fat server: a `.rename` handler that requires the same parent directory and calls engine.rename. - runtime.fs: rename(old_path, new_path). - fat-test now renames the file it created (before removing it) and asserts the old name is gone, behind a new `fat-rename` QEMU case. Verified: zig build, zig build test (the engine rename unit test), zig build check-fat-image, and a sequential QEMU sweep — fat-mount, fat-mutations, fat-rename, vfs, vfs-client-death, log-flush, orderly-shutdown, initial-ramdisk, smoke — green.
This commit is contained in:
@@ -25,6 +25,9 @@ pub const Operation = enum(u32) {
|
||||
// payload); a mounted backend handles them, the flat ramfs refuses them.
|
||||
mkdir, // mkdir(path payload) -> status
|
||||
unlink, // unlink(path payload) -> status
|
||||
// rename: the payload is the old path, a single 0x00 separator, then the new
|
||||
// path. Same-directory rename only (the router requires both under one mount).
|
||||
rename, // rename(old\0new payload) -> status
|
||||
};
|
||||
|
||||
/// The type of a filesystem node, aligned to the FSH file-type table
|
||||
|
||||
@@ -177,6 +177,28 @@ fn forwardPath(out: []u8, backend: ipc.Handle, operation: protocol.Operation, re
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// Forward a rename to its backend: the payload is the mount-relative old path, a
|
||||
/// 0x00 separator, then the mount-relative new path. Relays the backend's reply.
|
||||
fn forwardRename(out: []u8, backend: ipc.Handle, old_relative: []const u8, new_relative: []const u8) usize {
|
||||
const total = old_relative.len + 1 + new_relative.len;
|
||||
var message: [protocol.message_maximum]u8 = undefined;
|
||||
if (protocol.request_size + total > message.len) return fail(out);
|
||||
const request = protocol.Request{ .operation = .rename, .node = 0, .offset = 0, .len = @intCast(total), .flags = 0 };
|
||||
@memcpy(message[0..protocol.request_size], std.mem.asBytes(&request));
|
||||
var p = protocol.request_size;
|
||||
@memcpy(message[p..][0..old_relative.len], old_relative);
|
||||
p += old_relative.len;
|
||||
message[p] = 0;
|
||||
p += 1;
|
||||
@memcpy(message[p..][0..new_relative.len], new_relative);
|
||||
p += new_relative.len;
|
||||
var reply: [protocol.message_maximum]u8 = undefined;
|
||||
const n = ipc.call(backend, message[0..p], &reply) catch return fail(out);
|
||||
const copy = @min(n, out.len);
|
||||
@memcpy(out[0..copy], reply[0..copy]);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// Best-effort close of a backend node (used when a dead client's forwarding
|
||||
/// handles are swept — the backend must not leak the vfs's opens).
|
||||
fn forwardClose(backend: ipc.Handle, backend_node: u64) void {
|
||||
@@ -328,6 +350,18 @@ fn handle(message: []const u8, out: []u8, sender: u32, capability: ?ipc.Handle)
|
||||
// create or remove them (and a bare-name path is not a mount target).
|
||||
return fail(out);
|
||||
},
|
||||
.rename => {
|
||||
const both = payload[0..@min(payload.len, request.len)];
|
||||
const sep = std.mem.indexOfScalar(u8, both, 0) orelse return fail(out);
|
||||
const old_path = both[0..sep];
|
||||
const new_path = both[sep + 1 ..];
|
||||
const mo = longestMount(old_path) orelse return fail(out);
|
||||
const mn = longestMount(new_path) orelse return fail(out);
|
||||
// Both paths must live under the same mount — cross-filesystem rename is
|
||||
// not supported.
|
||||
if (mo.backend != mn.backend) return fail(out);
|
||||
return forwardRename(out, mo.backend, mo.relative, mn.relative);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user