block: reclaim range slots on death, and give confinement one controller
Two defects the V2 boundary review confirmed: 1. The per-badge range table was never reclaimed. usb-storage receives exit notifications (via the subscriber watch), but onNotification handled only the timer and dropped child-exits, so a dead filesystem left its range slot used forever. The medium-removal lifecycle churns filesystems, so after maximum_ranges confine/die cycles define_range would return ENOSPC and no volume could be confined again until reboot. onNotification now frees the dead badge's slot, mirroring fat's open-node sweep. 2. define_range checked only that the CALLER was unconfined, never that it owned the target badge — so any unconfined opener could install a range for another live client and silently redirect its I/O. Confinement now has a single controller: the first unconfined party to define a range (the volume manager, which confines every filesystem before handing it a channel). Only the controller may thereafter; the slot releases on its death so a restarted manager re-takes it. This is the mechanism half; V3 adds the grant half (only the volume manager gets an unconfined channel). Neutral: block-range (the fixture is the sole definer -> controller), fat-mount, usb-report all green. End-to-end exercise of both lands in V3/V4 (the VM+filesystem relationship and the remount churn).
This commit is contained in:
@@ -208,6 +208,14 @@ const maximum_ranges = 64;
|
||||
const Range = struct { used: bool = false, badge: u32 = 0, base: u64 = 0, count: u64 = 0 };
|
||||
var ranges = [_]Range{.{}} ** maximum_ranges;
|
||||
|
||||
/// The one party allowed to confine others — the first unconfined caller to
|
||||
/// define a range, which is the volume manager (it probes and confines every
|
||||
/// filesystem before handing it a channel). Without this, any unconfined
|
||||
/// opener could install a range for another live client's badge and silently
|
||||
/// redirect its I/O. Released on the controller's death so a restarted volume
|
||||
/// manager re-takes it.
|
||||
var range_controller: ?u32 = null;
|
||||
|
||||
fn rangeFor(badge: u32) ?*Range {
|
||||
for (&ranges) |*r| {
|
||||
if (r.used and r.badge == badge) return r;
|
||||
@@ -264,7 +272,17 @@ fn onWrite(_: void, invocation: Invocation(block_protocol.Transfer), answer: Ans
|
||||
/// Refused if the CALLER is itself confined — a filesystem cannot widen its own
|
||||
/// range or confine anyone; only an unconfined party (the volume manager) may.
|
||||
fn onDefineRange(_: void, invocation: Invocation(block_protocol.DefineRange), _: Answer(void)) isize {
|
||||
if (rangeFor(invocation.sender) != null) return -envelope.EPERM;
|
||||
const sender = invocation.sender;
|
||||
// A confined caller may never confine — no self-widening, no escape.
|
||||
if (rangeFor(sender) != null) return -envelope.EPERM;
|
||||
// Confinement has a single controller (the volume manager). Whoever defines
|
||||
// the first range takes it; only they may thereafter, so a second unconfined
|
||||
// opener cannot install a range for a badge it does not own.
|
||||
if (range_controller) |c| {
|
||||
if (sender != c) return -envelope.EPERM;
|
||||
} else {
|
||||
range_controller = sender;
|
||||
}
|
||||
const request = invocation.request;
|
||||
const slot = rangeFor(request.badge) orelse free: {
|
||||
for (&ranges) |*r| {
|
||||
@@ -336,8 +354,17 @@ fn onNotification(badge: u64) void {
|
||||
if (got.isTimer()) {
|
||||
pollPresence();
|
||||
_ = time.timerOnce(service_endpoint, presence_poll_ms);
|
||||
return;
|
||||
}
|
||||
// A client died. The harness sweep (Serve.hooks) covers only the SUBSCRIBER
|
||||
// table; the per-badge range table is ours to reclaim, or confine/die cycles
|
||||
// (the medium-removal lifecycle) would exhaust it. A dead controller also
|
||||
// releases confinement authority to its successor.
|
||||
if (got.isChildExit()) {
|
||||
const dead = got.childProcessId();
|
||||
if (rangeFor(dead)) |r| r.* = .{};
|
||||
if (range_controller == dead) range_controller = null;
|
||||
}
|
||||
// Subscriber deaths are swept by the harness (Serve.hooks); nothing else here.
|
||||
}
|
||||
|
||||
pub fn main(init: process.Init) void {
|
||||
|
||||
Reference in New Issue
Block a user