library: the harness keeps the subscribers, and an id belongs to whoever opened it
Three services had each written the same thing and got it three different ways: input polled the process list to notice a dead subscriber, and only when someone else subscribed; the power service never noticed at all; the device manager noticed drivers but not subscribers. The harness owns the table now, driven by the events a protocol declares — it registers on the reserved verb, frames each event once, posts to everyone interested without waiting on any of them, and reclaims a slot when the kernel says its owner died. Interest masks moved to the envelope, so a subscriber that wants only mice asks the same way everywhere. Two consequences the plan had not foreseen. The device manager now hears a supervised child's death twice, once as its supervisor and once as a subscriber, so restart backoff counted every crash twice and gave up after half as many; it retires the id before counting. And the kernel's published exit table had eight slots for what is now six subscriptions in a plain boot, so it holds sixteen. The other half is a hole the design named early and left standing: a backend handed out a small integer and then honoured it from anyone. A process that guessed a file's node id read another client's file; a display layer had no owner at all, so any client could reconfigure or destroy any layer; a USB device token was never checked against the client that opened it. Each is now bound to the task that opened it, and a wrong owner gets exactly what an unknown id gets — the refusal must not become the oracle the identical answers elsewhere were designed to remove. Closing a file changed with it: it used to succeed unconditionally, which would have told a caller which ids existed. Suite 111/111, with a new case in which one process holds a file and a layer, hands both ids to a second process, and finds them untouched after that process has tried everything with them.
This commit is contained in:
@@ -10,9 +10,9 @@ pub fn build(b: *std.Build) void {
|
||||
.name = "display",
|
||||
.root_source_file = b.path("display.zig"),
|
||||
.imports = &.{
|
||||
"channel", "display-client", "display-protocol", "driver", "envelope", "input-client",
|
||||
"ipc", "logging", "memory", "scanout-protocol", "service",
|
||||
"thread", "time",
|
||||
"channel", "display-client", "display-protocol", "driver", "envelope", "input-client",
|
||||
"ipc", "logging", "memory", "process", "scanout-protocol",
|
||||
"service", "thread", "time",
|
||||
},
|
||||
.threaded = true, // real atomics/TLS (docs/threading.md)
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ const std = @import("std");
|
||||
const channel = @import("channel");
|
||||
const ipc = @import("ipc");
|
||||
const input = @import("input-client");
|
||||
const process = @import("process");
|
||||
const Thread = @import("thread").Thread;
|
||||
const service = @import("service");
|
||||
const time = @import("time");
|
||||
@@ -74,8 +75,22 @@ var background: u32 = 0;
|
||||
/// small copies rather than one huge bounding box (see compositor.DamageList).
|
||||
const maximum_layers = 16;
|
||||
|
||||
/// A layer belongs to the client that created it. `owner` is the kernel-stamped
|
||||
/// badge of that task, and `service_owned` (0, an id no task wears) marks the
|
||||
/// compositor's own layers — the cursor sprite and the self-check pair — which
|
||||
/// this file creates by direct call rather than over the protocol.
|
||||
///
|
||||
/// Layer ids are slots in a sixteen-entry table: small, dense, and guessable, so
|
||||
/// before this field any client could configure, draw into, or destroy any
|
||||
/// other's layer — including the cursor. The check lives in the protocol handlers
|
||||
/// (docs/os-development/protocol-namespace.md: handles validated against the
|
||||
/// badge); the internal helpers stay unscoped precisely so the compositor can
|
||||
/// still drive its own.
|
||||
const service_owned: u32 = 0;
|
||||
|
||||
const Layer = struct {
|
||||
used: bool = false,
|
||||
owner: u32 = service_owned,
|
||||
x: i32 = 0,
|
||||
y: i32 = 0,
|
||||
z: u32 = 0,
|
||||
@@ -189,7 +204,8 @@ fn layerAt(id: u32) ?*Layer {
|
||||
return &layers[id];
|
||||
}
|
||||
|
||||
fn createLayer(x: i32, y: i32, w: u32, h: u32, z: u32, visible: bool) ?u32 {
|
||||
/// Create a layer for `owner` — `service_owned` for the compositor's own.
|
||||
fn createLayer(owner: u32, x: i32, y: i32, w: u32, h: u32, z: u32, visible: bool) ?u32 {
|
||||
if (w == 0 or h == 0) return null;
|
||||
const slot = freeLayer() orelse return null;
|
||||
const len = @as(usize, w) * h * 4;
|
||||
@@ -197,6 +213,7 @@ fn createLayer(x: i32, y: i32, w: u32, h: u32, z: u32, visible: bool) ?u32 {
|
||||
if (memory.mmapFailed(base)) return null;
|
||||
layers[slot] = .{
|
||||
.used = true,
|
||||
.owner = owner,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.z = z,
|
||||
@@ -420,8 +437,8 @@ fn selfCheck() void {
|
||||
const format = backend.info().format;
|
||||
const red = display_protocol.pack(format, 0xC0, 0x20, 0x20);
|
||||
const green = display_protocol.pack(format, 0x20, 0xC0, 0x20);
|
||||
const bottom = createLayer(100, 100, 80, 80, 0, true) orelse return fail_check("create");
|
||||
const top = createLayer(140, 140, 80, 80, 1, true) orelse return fail_check("create");
|
||||
const bottom = createLayer(service_owned, 100, 100, 80, 80, 0, true) orelse return fail_check("create");
|
||||
const top = createLayer(service_owned, 140, 140, 80, 80, 1, true) orelse return fail_check("create");
|
||||
_ = fillLayer(bottom, Rect.init(0, 0, 80, 80), red);
|
||||
_ = fillLayer(top, Rect.init(0, 0, 80, 80), green);
|
||||
present();
|
||||
@@ -586,7 +603,7 @@ fn startCursorTracking() void {
|
||||
const mode = backend.info();
|
||||
cursor_origin_x = @divTrunc(@as(i32, @intCast(mode.width)), 2);
|
||||
cursor_origin_y = @divTrunc(@as(i32, @intCast(mode.height)), 2);
|
||||
const id = createLayer(cursor_origin_x, cursor_origin_y, cursor_size, cursor_size, cursor_z, true) orelse {
|
||||
const id = createLayer(service_owned, cursor_origin_x, cursor_origin_y, cursor_size, cursor_size, cursor_z, true) orelse {
|
||||
_ = logging.write("display: could not create cursor layer\n");
|
||||
return;
|
||||
};
|
||||
@@ -603,6 +620,9 @@ fn startCursorTracking() void {
|
||||
|
||||
fn initialise(endpoint: ipc.Handle) bool {
|
||||
service_endpoint = endpoint;
|
||||
// Layers are per-client state, so the compositor needs deaths: a client that
|
||||
// crashes leaves its surfaces on screen and its slots spent otherwise.
|
||||
_ = process.subscribeExits(endpoint);
|
||||
|
||||
// Pick the scanout backend (GOP today). It logs the reason on failure.
|
||||
backend = backend_mod.select() orelse return false;
|
||||
@@ -635,9 +655,35 @@ fn initialise(endpoint: ipc.Handle) bool {
|
||||
// id a u32: a value that does not fit is not a layer of ours, and `layerAt`
|
||||
// refuses it the same way an out-of-range one is refused.
|
||||
|
||||
fn targetLayer(target: u64) ?u32 {
|
||||
if (target > std.math.maxInt(u32)) return null;
|
||||
return @intCast(target);
|
||||
/// The layer a packet addresses, **for the task that sent it**: null unless the
|
||||
/// target names a used slot this sender created. A layer that is somebody else's
|
||||
/// is refused exactly as one that never existed, so a client cannot use the
|
||||
/// refusal to learn which ids are live (P3's refusal-equals-absence, applied to
|
||||
/// ids rather than names).
|
||||
fn targetLayer(target: u64, sender: u32) ?u32 {
|
||||
if (target > std.math.maxInt(u32)) return null; // a layer id is a u32
|
||||
const id: u32 = @intCast(target);
|
||||
const layer = layerAt(id) orelse return null;
|
||||
if (layer.owner != sender) return null;
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Destroy every layer a dead client left behind — its surface is pages nobody
|
||||
/// will ever draw into again, and its slot is one of sixteen. The published
|
||||
/// exit events are the notice, the same sweep idiom the FAT server uses for open
|
||||
/// files and the harness uses for subscribers.
|
||||
fn releaseLayersOf(dead: u32) void {
|
||||
var released: u32 = 0;
|
||||
for (&layers, 0..) |*layer, id| {
|
||||
if (layer.used and layer.owner == dead) {
|
||||
_ = destroyLayer(@intCast(id));
|
||||
released += 1;
|
||||
}
|
||||
}
|
||||
if (released != 0) {
|
||||
std.log.info("released {d} layer(s) for dead client {d}", .{ released, dead });
|
||||
schedulePresent(); // the screen still shows what they painted
|
||||
}
|
||||
}
|
||||
|
||||
fn onInfo(_: void, _: Invocation(void), answer: Answer(display_protocol.Info)) isize {
|
||||
@@ -648,37 +694,37 @@ fn onInfo(_: void, _: Invocation(void), answer: Answer(display_protocol.Info)) i
|
||||
|
||||
fn onCreateLayer(_: void, invocation: Invocation(display_protocol.CreateLayer), answer: Answer(display_protocol.Created)) isize {
|
||||
const request = invocation.request;
|
||||
const slot = createLayer(request.x, request.y, request.width, request.height, request.z, request.visible != 0) orelse return refused;
|
||||
const slot = createLayer(invocation.sender, request.x, request.y, request.width, request.height, request.z, request.visible != 0) orelse return refused;
|
||||
answer.set(.{ .layer = slot });
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn onConfigureLayer(_: void, invocation: Invocation(display_protocol.ConfigureLayer), _: Answer(void)) isize {
|
||||
const id = targetLayer(invocation.target) orelse return refused;
|
||||
const id = targetLayer(invocation.target, invocation.sender) orelse return refused;
|
||||
const request = invocation.request;
|
||||
return if (configureLayer(id, request.x, request.y, request.z, request.visible != 0)) 0 else refused;
|
||||
}
|
||||
|
||||
fn onDestroyLayer(_: void, invocation: Invocation(void), _: Answer(void)) isize {
|
||||
const id = targetLayer(invocation.target) orelse return refused;
|
||||
const id = targetLayer(invocation.target, invocation.sender) orelse return refused;
|
||||
return if (destroyLayer(id)) 0 else refused;
|
||||
}
|
||||
|
||||
fn onFillRect(_: void, invocation: Invocation(display_protocol.FillRect), _: Answer(void)) isize {
|
||||
const id = targetLayer(invocation.target) orelse return refused;
|
||||
const id = targetLayer(invocation.target, invocation.sender) orelse return refused;
|
||||
const request = invocation.request;
|
||||
const local = Rect.init(request.x, request.y, @intCast(request.width), @intCast(request.height));
|
||||
return if (fillLayer(id, local, request.colour)) 0 else refused;
|
||||
}
|
||||
|
||||
fn onBlitTile(_: void, invocation: Invocation(display_protocol.BlitTile), _: Answer(void)) isize {
|
||||
const id = targetLayer(invocation.target) orelse return refused;
|
||||
const id = targetLayer(invocation.target, invocation.sender) orelse return refused;
|
||||
const request = invocation.request;
|
||||
return if (blitLayer(id, request.x, request.y, request.width, request.height, invocation.tail)) 0 else refused;
|
||||
}
|
||||
|
||||
fn onDamage(_: void, invocation: Invocation(display_protocol.Damage), _: Answer(void)) isize {
|
||||
const id = targetLayer(invocation.target) orelse return refused;
|
||||
const id = targetLayer(invocation.target, invocation.sender) orelse return refused;
|
||||
const l = layerAt(id) orelse return refused;
|
||||
const request = invocation.request;
|
||||
const screen = Rect{ .x = l.x + request.x, .y = l.y + request.y, .w = @intCast(request.width), .h = @intCast(request.height) };
|
||||
@@ -733,13 +779,18 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, arrived: *ipc.Arriva
|
||||
return Serve.dispatch({}, handlers, message, sender, arrived.peek(), reply);
|
||||
}
|
||||
|
||||
/// Two notification sources reach the compositor, and one coalesced badge can carry
|
||||
/// both, so each bit is handled independently. A **message-notification** is a poke from
|
||||
/// the mouse-listener thread (a buffered self-`ipc.send`, `notify_message_bit`): fold the
|
||||
/// newest cursor position into the scene. A **timer** (`notify_timer_bit`) is the frame
|
||||
/// clock — or the deferred first native present after `attach_scanout` — either way,
|
||||
/// present the accumulated damage.
|
||||
/// Three notification sources reach the compositor, and one coalesced badge can carry
|
||||
/// more than one, so each bit is handled independently. A **message-notification** is a
|
||||
/// poke from the mouse-listener thread (a buffered self-`ipc.send`, `notify_message_bit`):
|
||||
/// fold the newest cursor position into the scene. A **timer** (`notify_timer_bit`) is the
|
||||
/// frame clock — or the deferred first native present after `attach_scanout` — either way,
|
||||
/// present the accumulated damage. A **published exit** (`notify_exit_bit`) is a client
|
||||
/// gone: release the layers it left.
|
||||
fn onNotification(badge: u64) void {
|
||||
if (badge & ipc.notify_exit_bit != 0) {
|
||||
releaseLayersOf(@intCast(badge & ~(ipc.notify_badge_bit | ipc.notify_exit_bit)));
|
||||
return;
|
||||
}
|
||||
if (badge & ipc.notify_message_bit != 0) renderCursor();
|
||||
if (badge & ipc.notify_timer_bit != 0) frameTick();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user