volume-manager: load the mount map; pick binary by signature, compose the id-path (S2)
The volume manager reads its policy from configuration at boot (loadTables,
mirroring the device-manager registry load): filesystems.csv (content signature
-> service binary) and volumes.csv (optional id -> mount-prefix override), each
held in a static source buffer with declared bounds. On probe it picks the
binary from the volume's signature (unserved + logged if no row matches, like an
unbound device) and composes the mount path — a volumes.csv override, else the
default /volumes/<id> from volume-map.idString — then spawns that binary with
argv {volume-id, mount-prefix}. Behavior-preserving: fat still ignores argv[2..]
and uses its hardcoded mounts, the binary resolves to /system/services/fat, so
the FULL suite stays green (127/127); the flip to argv-driven mounts and the
/volumes/usb -> id-path migration land in step 5.
This commit is contained in:
@@ -317,6 +317,11 @@ pub fn build(b: *std.Build) void {
|
|||||||
// out of the same read-only initrd, before it spawns anything — the registrar
|
// out of the same read-only initrd, before it spawns anything — the registrar
|
||||||
// has to know its policy before the first provider asks.
|
// has to know its policy before the first provider asks.
|
||||||
bundled_list.append(b.allocator, .{ .path = "system/configuration/protocol.csv", .binary = b.path("system/configuration/protocol.csv") }) catch @panic("OOM");
|
bundled_list.append(b.allocator, .{ .path = "system/configuration/protocol.csv", .binary = b.path("system/configuration/protocol.csv") }) catch @panic("OOM");
|
||||||
|
// The storage mount map (docs/file-system-development/storage-architecture.md):
|
||||||
|
// filesystems.csv (content signature -> service binary) and volumes.csv (the
|
||||||
|
// optional id -> mount-prefix override), both read by the volume manager.
|
||||||
|
bundled_list.append(b.allocator, .{ .path = "system/configuration/filesystems.csv", .binary = b.path("system/configuration/filesystems.csv") }) catch @panic("OOM");
|
||||||
|
bundled_list.append(b.allocator, .{ .path = "system/configuration/volumes.csv", .binary = b.path("system/configuration/volumes.csv") }) catch @panic("OOM");
|
||||||
// A no-option build assumes neither -Dtest-case nor -Ddiagnose: it ships the
|
// A no-option build assumes neither -Dtest-case nor -Ddiagnose: it ships the
|
||||||
// production set only. The userspace test fixtures under /test join in only
|
// production set only. The userspace test fixtures under /test join in only
|
||||||
// for a test build — which the QEMU harness signals by passing
|
// for a test build — which the QEMU harness signals by passing
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# The filesystem map: a probed volume's content signature -> the service binary
|
||||||
|
# that serves it (docs/file-system-development/storage-architecture.md). The
|
||||||
|
# volume manager reads this (the policy); a signature no row matches goes
|
||||||
|
# unserved, never guessed. Adding a filesystem adds a row.
|
||||||
|
#
|
||||||
|
# signature, binary
|
||||||
|
fat, /system/services/fat
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
# The mount map (danos's fstab): a volume's content id -> a chosen mount prefix.
|
||||||
|
# This is an OPTIONAL override, read by the volume manager. A volume with no row
|
||||||
|
# mounts at its default /volumes/<id>, where <id> is the manager's rendered
|
||||||
|
# content identity (e.g. fat-12345678, gpt-<guid>, mbr-<sig>-<index>) — stable,
|
||||||
|
# unique, and never a port or a label. The label is display metadata, not here:
|
||||||
|
# query it via the volume manager's `volumes` verb.
|
||||||
|
#
|
||||||
|
# id, mount_prefix
|
||||||
|
@@ -10,9 +10,10 @@ pub fn build(b: *std.Build) void {
|
|||||||
.name = "volume-manager",
|
.name = "volume-manager",
|
||||||
.root_source_file = b.path("volume-manager.zig"),
|
.root_source_file = b.path("volume-manager.zig"),
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
"block", "channel", "device-manager-protocol", "driver",
|
"block", "channel", "csv", "device-manager-protocol",
|
||||||
"envelope", "ipc", "logging", "memory",
|
"driver", "envelope", "file-system", "ipc",
|
||||||
"process", "service", "time", "volume-manager-protocol",
|
"logging", "memory", "process", "service",
|
||||||
|
"time", "volume-manager-protocol",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ const process = @import("process");
|
|||||||
const service = @import("service");
|
const service = @import("service");
|
||||||
const time = @import("time");
|
const time = @import("time");
|
||||||
const envelope = @import("envelope");
|
const envelope = @import("envelope");
|
||||||
|
const fs = @import("file-system");
|
||||||
const partition = @import("partition.zig");
|
const partition = @import("partition.zig");
|
||||||
|
const filesystem_map = @import("filesystem-map.zig");
|
||||||
|
const volume_map = @import("volume-map.zig");
|
||||||
|
|
||||||
const Serve = volume_manager_protocol.Protocol.Provider(void);
|
const Serve = volume_manager_protocol.Protocol.Provider(void);
|
||||||
const Invocation = envelope.Invocation;
|
const Invocation = envelope.Invocation;
|
||||||
@@ -42,15 +45,53 @@ const Volume = struct {
|
|||||||
block_count: u64,
|
block_count: u64,
|
||||||
identity: partition.Identity,
|
identity: partition.Identity,
|
||||||
id: u64,
|
id: u64,
|
||||||
|
binary: []const u8, // the service binary, from filesystems.csv by signature
|
||||||
|
mount_prefix: []const u8, // the volume-root mount path (its id-path, or a volumes.csv override)
|
||||||
filesystem_pid: u32 = 0,
|
filesystem_pid: u32 = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The filesystem binary a probed volume is served by. The signature->binary
|
|
||||||
/// map (filesystems.csv) lands with the identity ladder; for now every FAT-shaped
|
|
||||||
/// volume gets the FAT service.
|
|
||||||
const filesystem_binary = "/system/services/fat";
|
|
||||||
const volume_id: u64 = 1;
|
const volume_id: u64 = 1;
|
||||||
|
|
||||||
|
// The mount map, read from configuration at boot (the policy home, storage-
|
||||||
|
// architecture.md): filesystems.csv (content signature -> service binary) and
|
||||||
|
// volumes.csv (an optional id -> mount-prefix override). The sources are held
|
||||||
|
// for the process life so the parsed rules' slices into them stay valid.
|
||||||
|
/// bound: bytes of filesystems.csv / volumes.csv the manager reads
|
||||||
|
/// decided-by: ours
|
||||||
|
/// protects: the config source buffers below
|
||||||
|
/// at-limit: truncate - a longer file is cut; a row split by the cut is malformed
|
||||||
|
/// observed-by: the per-file "malformed/truncated" log line
|
||||||
|
const config_source_bytes = 2048;
|
||||||
|
var filesystems_source: [config_source_bytes]u8 = undefined;
|
||||||
|
var volumes_source: [config_source_bytes]u8 = undefined;
|
||||||
|
/// bound: filesystem-map rules held (one per content signature)
|
||||||
|
/// decided-by: ours
|
||||||
|
/// protects: the filesystem_rules table
|
||||||
|
/// at-limit: truncate - extra rows are dropped and the "truncated" note logged
|
||||||
|
/// observed-by: the "truncated" log line
|
||||||
|
const maximum_filesystem_rules = 8;
|
||||||
|
/// bound: volumes.csv override rows held (one per pinned volume id)
|
||||||
|
/// decided-by: ours
|
||||||
|
/// protects: the volume_rules table
|
||||||
|
/// at-limit: truncate - extra rows are dropped and the "truncated" note logged
|
||||||
|
/// observed-by: the "truncated" log line
|
||||||
|
const maximum_volume_rules = 64;
|
||||||
|
var filesystem_rules: [maximum_filesystem_rules]filesystem_map.Rule = undefined;
|
||||||
|
var filesystem_rule_count: usize = 0;
|
||||||
|
var volume_rules: [maximum_volume_rules]volume_map.Override = undefined;
|
||||||
|
var volume_rule_count: usize = 0;
|
||||||
|
/// The composed default mount path (/volumes/<id>) for the current volume; a
|
||||||
|
/// volumes.csv override is used in place and needs no buffer (it is already a
|
||||||
|
/// slice into volumes_source). One buffer suffices while the manager serves one
|
||||||
|
/// volume (multi-volume gives each its own in S3).
|
||||||
|
/// bound: bytes of a composed /volumes/<id> mount path
|
||||||
|
/// decided-by: ours
|
||||||
|
/// protects: the mount_prefix_buf below
|
||||||
|
/// at-limit: truncate - bufPrint fails; the volume mounts at a fallback path (logged)
|
||||||
|
/// observed-by: the fallback path in the log
|
||||||
|
const mount_path_maximum = 64;
|
||||||
|
var mount_prefix_buf: [mount_path_maximum]u8 = undefined;
|
||||||
|
|
||||||
var service_endpoint: ipc.Handle = 0;
|
var service_endpoint: ipc.Handle = 0;
|
||||||
var manager_handle: ?ipc.Handle = null;
|
var manager_handle: ?ipc.Handle = null;
|
||||||
var bounce: memory.DmaRegion = undefined;
|
var bounce: memory.DmaRegion = undefined;
|
||||||
@@ -156,7 +197,7 @@ fn isDevicePresent(device_id: u64) bool {
|
|||||||
/// confinement controller (it defines the first range on the device).
|
/// confinement controller (it defines the first range on the device).
|
||||||
fn spawnFilesystem(v: *Volume) void {
|
fn spawnFilesystem(v: *Volume) void {
|
||||||
if (fs_failed) return;
|
if (fs_failed) return;
|
||||||
const pid = process.spawnSupervised(filesystem_binary, &.{"1"}, service_endpoint) orelse {
|
const pid = process.spawnSupervised(v.binary, &.{ "1", v.mount_prefix }, service_endpoint) orelse {
|
||||||
_ = logging.write("volume-manager: could not spawn the filesystem; retrying\n");
|
_ = logging.write("volume-manager: could not spawn the filesystem; retrying\n");
|
||||||
armRestart();
|
armRestart();
|
||||||
return;
|
return;
|
||||||
@@ -169,7 +210,7 @@ fn spawnFilesystem(v: *Volume) void {
|
|||||||
}
|
}
|
||||||
v.filesystem_pid = pid;
|
v.filesystem_pid = pid;
|
||||||
fs_spawn_ns = time.clock();
|
fs_spawn_ns = time.clock();
|
||||||
std.log.info("volume 0x{x} -> {s} (pid {d}), lba {d}, {d} blocks", .{ v.identity.key, filesystem_binary, pid, v.base_lba, v.block_count });
|
std.log.info("volume 0x{x} -> {s} (pid {d}), lba {d}, {d} blocks", .{ v.identity.key, v.binary, pid, v.base_lba, v.block_count });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedule a fat restart after backoff; the poll loop performs it once due.
|
/// Schedule a fat restart after backoff; the poll loop performs it once due.
|
||||||
@@ -224,11 +265,29 @@ fn bringUpVolume() void {
|
|||||||
_ = ipc.close(device.endpoint);
|
_ = ipc.close(device.endpoint);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// Pick the service binary from the volume's content signature. A signature
|
||||||
|
// no filesystems.csv row serves goes unserved (logged), like an unbound
|
||||||
|
// device — the manager does not guess.
|
||||||
|
const binary = filesystem_map.match(filesystem_rules[0..filesystem_rule_count], found.signature) orelse {
|
||||||
|
if (!logged_no_volume) {
|
||||||
|
_ = logging.write("volume-manager: no filesystem serves this volume's content; unserved\n");
|
||||||
|
logged_no_volume = true;
|
||||||
|
}
|
||||||
|
_ = ipc.close(device.endpoint);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// The mount path is the volume's identity id (/volumes/<id>), or a
|
||||||
|
// volumes.csv override pinning it to a chosen path. The id is content-derived,
|
||||||
|
// so the path is stable and never a port or a label.
|
||||||
|
var id_buf: [volume_map.id_maximum]u8 = undefined;
|
||||||
|
const id = volume_map.idString(found.identity, &id_buf);
|
||||||
|
const mount_prefix = volume_map.overrideFor(volume_rules[0..volume_rule_count], id) orelse
|
||||||
|
(std.fmt.bufPrint(&mount_prefix_buf, "/volumes/{s}", .{id}) catch "/volumes/unknown");
|
||||||
logged_no_volume = false;
|
logged_no_volume = false;
|
||||||
fs_restarts = 0;
|
fs_restarts = 0;
|
||||||
fs_failed = false;
|
fs_failed = false;
|
||||||
restart_pending = false;
|
restart_pending = false;
|
||||||
volume = .{ .storage = device, .storage_device_id = opened.device_id, .base_lba = found.base_lba, .block_count = found.block_count, .identity = found.identity, .id = volume_id };
|
volume = .{ .storage = device, .storage_device_id = opened.device_id, .base_lba = found.base_lba, .block_count = found.block_count, .identity = found.identity, .id = volume_id, .binary = binary, .mount_prefix = mount_prefix };
|
||||||
spawnFilesystem(&volume.?);
|
spawnFilesystem(&volume.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,9 +355,42 @@ fn onMessage(message: []const u8, out: []u8, sender: u32, arrived: *ipc.Arrival)
|
|||||||
return Serve.dispatch({}, handlers, message, sender, arrived.peek(), out);
|
return Serve.dispatch({}, handlers, message, sender, arrived.peek(), out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a config file into `buf`, returning the byte count (0 if missing).
|
||||||
|
fn readConfig(path: []const u8, buf: []u8) usize {
|
||||||
|
var file = fs.open(path, .{}) orelse {
|
||||||
|
std.log.info("volume-manager: {s} missing", .{path});
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
defer file.close();
|
||||||
|
var used: usize = 0;
|
||||||
|
while (used < buf.len) {
|
||||||
|
const n = file.read(buf[used..]) orelse break;
|
||||||
|
if (n == 0) break;
|
||||||
|
used += n;
|
||||||
|
}
|
||||||
|
return used;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the mount map from configuration once at boot (mirrors the device
|
||||||
|
/// manager's registry load). A missing or empty filesystems.csv means no volume
|
||||||
|
/// is served; volumes.csv is optional — no rows means every volume takes its
|
||||||
|
/// default /volumes/<id> path.
|
||||||
|
fn loadTables() void {
|
||||||
|
const fs_used = readConfig("/system/configuration/filesystems.csv", &filesystems_source);
|
||||||
|
const fr = filesystem_map.parse(filesystems_source[0..fs_used], &filesystem_rules);
|
||||||
|
filesystem_rule_count = fr.count;
|
||||||
|
if (fr.malformed != 0 or fr.truncated) std.log.info("filesystems.csv: {d} malformed, truncated={}", .{ fr.malformed, fr.truncated });
|
||||||
|
|
||||||
|
const vol_used = readConfig("/system/configuration/volumes.csv", &volumes_source);
|
||||||
|
const vr = volume_map.parse(volumes_source[0..vol_used], &volume_rules);
|
||||||
|
volume_rule_count = vr.count;
|
||||||
|
if (vr.malformed != 0 or vr.truncated) std.log.info("volumes.csv: {d} malformed, truncated={}", .{ vr.malformed, vr.truncated });
|
||||||
|
}
|
||||||
|
|
||||||
fn initialise(endpoint: ipc.Handle) bool {
|
fn initialise(endpoint: ipc.Handle) bool {
|
||||||
service_endpoint = endpoint;
|
service_endpoint = endpoint;
|
||||||
_ = logging.write("volume-manager: starting, waiting for a storage device\n");
|
_ = logging.write("volume-manager: starting, waiting for a storage device\n");
|
||||||
|
loadTables();
|
||||||
_ = process.subscribeExits(endpoint);
|
_ = process.subscribeExits(endpoint);
|
||||||
pollTick();
|
pollTick();
|
||||||
_ = time.timerOnce(endpoint, poll_interval_ms); // the poll runs for the life of the boot
|
_ = time.timerOnce(endpoint, poll_interval_ms); // the poll runs for the life of the boot
|
||||||
|
|||||||
Reference in New Issue
Block a user