//! /system/services/init — the first user-space program, PID 1. Built as its own
//! freestanding binary (see build.zig), shipped on the boot volume at /system/services/init,
//! loaded by the bootloader, and started in ring 3 as a scheduled process by the
//! kernel (system/kernel/process.zig). It links against the shared user runtime
//! library `runtime` and talks to the kernel only through `runtime`'s system_call wrappers.
//!
//! It proves the C-convention heap works, then — as PID 1 — acts as the system's
//! **service supervisor**: it spawns the user-space services danos brings up at boot
//! (the VFS server, the device manager), and settles into an event loop as the root
//! of user space. Drivers are *not* its job: the device manager discovers the
//! hardware and spawns those. This is the service half of the service/driver spawn
//! split (docs/driver-model.md).
//!
//! M21: init also owns **orderly shutdown**. It supervises its children (keeping
//! their ids and an exit endpoint), subscribes to the power service, and on a
//! power-button event runs the stop sequence over its children in reverse order
//! before asking the power service to enter S5 — lifecycle (M17) and events (M21)
//! composing into a clean poweroff.
//!
//! P2: init is also the **registrar** — it serves `/protocol`, the namespace where
//! a program finds everything it talks to
//! (docs/os-development/protocol-namespace.md). It is the natural home: it already
//! spawns the services and already holds the supervision link to each, so it is the
//! process that *knows* which binary is which. Registry traffic rides the same
//! endpoint as supervision, because one thread can only wait in one place — the
//! loop below answers vfs `open`/`readdir`/`bind` alongside signals, timers, power
//! events, and children's deaths.
//!
//! Which sets the security posture of this file. `fs_resolve` installs a mounted
//! backend's endpoint capability in *any* caller's handle table, so sharing the
//! mailbox means **every ring-3 process can send into PID 1**. Two rules follow,
//! and both are structural here rather than remembered per branch:
//!
//! - **Privileged action requires an attested sender.** What arrives is a
//!   stranger's bytes; the only identity on it is the task id the kernel stamps.
//!   Content never authorizes (`onPowerEvent`), and neither does a name — the
//!   registrar attests a caller's supervision by task id (`supervisorSatisfies`).
//! - **Absence is the enforcement.** P3: `open` consults the manifest with the
//!   same attested identity a `bind` does, and a caller with no grant is told
//!   exactly what a caller asking for a name nobody bound is told — `-ENOENT`,
//!   and no capability (`onOpen`). Restriction stage one of
//!   docs/os-development/protocol-namespace.md: what a process cannot open does
//!   not exist for it, so there is no "permission denied" to distinguish.
//! - **A capability that arrives is closed unless it is claimed** (`Arrival`),
//!   because PID 1's thirty-two handle slots are a resource an unauthenticated
//!   caller would otherwise be able to spend.

const std = @import("std");
const ipc = @import("ipc");
const process = @import("process");
const time = @import("time");
const memory = @import("memory");
const logging = @import("logging");
const power_protocol = @import("power-protocol");
const vfs_protocol = @import("vfs-protocol");
const envelope = @import("envelope");
const build_options = @import("build_options");
const fs = @import("file-system");
const csv = @import("csv");

/// The system services init brings up at boot are init's policy, not the kernel's —
/// and that policy is now data: `/system/configuration/init.csv` (see `loadServices`), read at
/// startup instead of a hardcoded list. Drivers are absent on purpose: the device
/// manager owns those.
///
/// The most services `/system/configuration/init.csv` can list, and the most argv entries (beyond the
/// path) each may carry. Fixed caps because init parses the list into static storage —
/// the freestanding, no-allocator counterpart to the device manager's registry table.
const max_services = 16;
const max_service_args = 4;

/// One service init starts, parsed from a row of `/system/configuration/init.csv`: its binary path
/// and argv, both slices into `init_csv` (held for the life of the process).
const Service = struct {
    path: []const u8 = "",
    arg_buffer: [max_service_args][]const u8 = undefined,
    arg_count: usize = 0,
    fn arguments(self: *const Service) []const []const u8 {
        return self.arg_buffer[0..self.arg_count];
    }
};

/// The `/system/configuration/init.csv` bytes, held because the parsed services slice into them.
var init_csv: [4096]u8 = undefined;
var services: [max_services]Service = .{Service{}} ** max_services;
var service_count: usize = 0;

/// The live process id of each service (0 = not running) and its restart count,
/// indexed by position in `services`. init supervises these: it spawns them against
/// `supervision_endpoint` and, on a child's death, restarts it (up to
/// `maximum_restarts`) — the reincarnation half of resilience (docs/resilience.md),
/// the service-level counterpart to the device manager's driver restarts.
var child_ids: [max_services]u32 = .{0} ** max_services;
var restart_counts: [max_services]u32 = .{0} ** max_services;
var shutting_down = false;
var supervision_endpoint: ipc.Handle = 0;

/// Parse `/system/configuration/init.csv` into `services`, in file order (startup order; shutdown is
/// the reverse). Each row is a binary path followed by its argv, comma-separated;
/// `#` comments and blank lines are ignored. The file lives in the initial ramdisk,
/// which the kernel serves directly, so init — PID 1, running before any filesystem
/// service — reads it with a plain fs.open, the same mechanism the device manager
/// uses for /system/configuration/devices.csv. A missing file means no services (the no-ramdisk
/// isolation test): loud, but not fatal.
fn loadServices() void {
    const used = readConfiguration("/system/configuration/init.csv", &init_csv) orelse {
        _ = logging.write("/system/services/init: /system/configuration/init.csv missing — no services started\n");
        return;
    };
    var lines = std.mem.splitScalar(u8, init_csv[0..used], '\n');
    while (lines.next()) |line| {
        const body = csv.stripComment(line);
        if (body.len == 0) continue;
        if (service_count >= services.len) {
            _ = logging.write("/system/services/init: /system/configuration/init.csv has more services than the table holds\n");
            break;
        }
        var it = csv.fields(body);
        const path = it.next() orelse continue;
        if (path.len == 0) continue;
        var service: Service = .{ .path = path };
        while (it.next()) |argument| {
            if (argument.len == 0) continue; // padding, or a trailing comma
            if (service.arg_count >= max_service_args) break;
            service.arg_buffer[service.arg_count] = argument;
            service.arg_count += 1;
        }
        services[service_count] = service;
        service_count += 1;
    }
}

/// Read a whole configuration file into `into`, returning the byte count. Both of
/// init's manifests live in the initial ramdisk the kernel serves directly, so this
/// works before any filesystem service exists. A file that fills the buffer exactly
/// is reported: a manifest silently losing its last rows is a policy change nobody
/// asked for, and the symptom (one service refused a name) points nowhere near it.
fn readConfiguration(path: []const u8, into: []u8) ?usize {
    var file = fs.open(path, .{}) orelse return null;
    defer file.close();
    var used: usize = 0;
    while (used < into.len) {
        const n = file.read(into[used..]) orelse break;
        if (n == 0) break;
        used += n;
    }
    if (used == into.len) std.log.info("{s} filled the read buffer — rows past {d} bytes are lost", .{ path, used });
    return used;
}

/// Give up restarting a service after this many crashes — a crash-loop cap, so a service
/// that faults immediately on every spawn doesn't respawn forever.
const maximum_restarts = 3;

// --- the registry: /protocol ------------------------------------------------

/// Longest contract name the namespace admits (`display`, `test/shared-memory`)
/// and the most that may be bound at once. Both static, like everything else
/// init holds.
const maximum_name = 64;
const maximum_bindings = 16;
const maximum_grants = 64;

/// One bound contract: the name, the provider's endpoint (a capability init
/// holds and hands to whoever opens the name), and the provenance a diagnostic
/// listing answers "who serves this?" with.
const Binding = struct {
    used: bool = false,
    name: [maximum_name]u8 = undefined,
    name_len: usize = 0,
    endpoint: ipc.Handle = 0,
    task: u32 = 0,
    binary: [64]u8 = undefined,
    binary_len: usize = 0,

    fn nameSlice(self: *const Binding) []const u8 {
        return self.name[0..self.name_len];
    }
    fn binarySlice(self: *const Binding) []const u8 {
        return self.binary[0..self.binary_len];
    }
};

var bindings: [maximum_bindings]Binding = .{Binding{}} ** maximum_bindings;

/// What a grant row permits.
///
///   - `bind`      — claim the name, i.e. provide the contract.
///   - `open`      — reach the name, i.e. speak the contract to whoever provides it.
///   - `supervise` — stand in a third task's supervision chain: a task running this
///     binary, under this supervisor, may be the supervising task named by an
///     `open` row for this contract. It grants the *delegate* nothing itself.
///
/// `supervise` exists because attestation is deliberately one hop deep
/// (`supervisorSatisfies`): init vouches only for tasks it or the kernel started.
/// The driver tree is deeper than that — the device manager starts the PS/2 bus,
/// and the bus starts the keyboard and mouse drivers — so without a way to say
/// "this task is an authorized supervisor", a legitimate grandchild would be
/// indistinguishable from a laundering deputy. Naming the delegate in the
/// manifest is what tells them apart, and it is the same shape as every other
/// row: a binary, the supervisor it must have, and the contract it concerns.
/// Deliberately `open`-only — a delegate may vouch for what its children may
/// *reach*, never for what they may *claim* — so the bind path's attestation is
/// exactly what P2 shipped and every refusal it makes still holds.
const Permission = enum { bind, open, supervise };

/// One row of `/system/configuration/protocol.csv`. Every field may end in `*`,
/// which matches any tail — the subtree scoping the design doc describes, and
/// what lets one row grant the whole `/test/` family its `test/...` names.
const Grant = struct {
    binary: []const u8 = "",
    supervisor: []const u8 = "",
    permission: Permission = .bind,
    name: []const u8 = "",
};

/// Roomier than init.csv's: this manifest carries a row per provider per spawn
/// path, a row per client per contract it reaches, and its own format
/// documentation — which is most of the bytes, and is the point of the file.
var protocol_csv: [16384]u8 = undefined;
var grants: [maximum_grants]Grant = .{Grant{}} ** maximum_grants;
var grant_count: usize = 0;

/// Parse `/system/configuration/protocol.csv` — the grant manifest. Separate from
/// init.csv because every field there after the path is argv, and overloading that
/// would be ambiguous; separate *files* also means a grant exists for binaries init
/// never spawns (the drivers, which the device manager owns).
fn loadGrants() void {
    const used = readConfiguration("/system/configuration/protocol.csv", &protocol_csv) orelse {
        _ = logging.write("/system/services/init: /system/configuration/protocol.csv missing — no protocol may be bound\n");
        return;
    };
    var lines = std.mem.splitScalar(u8, protocol_csv[0..used], '\n');
    while (lines.next()) |line| {
        const body = csv.stripComment(line);
        if (body.len == 0) continue;
        if (grant_count >= grants.len) {
            _ = logging.write("/system/services/init: /system/configuration/protocol.csv has more rows than the table holds\n");
            break;
        }
        var it = csv.fields(body);
        const binary = it.next() orelse continue;
        const supervisor = it.next() orelse continue;
        const permission = it.next() orelse continue;
        const name = it.next() orelse continue;
        if (binary.len == 0 or supervisor.len == 0 or name.len == 0) continue;
        const kind: Permission = if (std.mem.eql(u8, permission, "bind"))
            .bind
        else if (std.mem.eql(u8, permission, "open"))
            .open
        else if (std.mem.eql(u8, permission, "supervise"))
            .supervise
        else
            continue; // an unreadable row grants nothing rather than something wrong
        grants[grant_count] = .{ .binary = binary, .supervisor = supervisor, .permission = kind, .name = name };
        grant_count += 1;
    }
}

/// Match a manifest field against a value: exact, or a trailing `*` matching any
/// tail. The wildcard is how a subtree is granted whole (`/test/*` for every test
/// fixture, `test/*` for every name they may claim).
fn matches(pattern: []const u8, value: []const u8) bool {
    if (pattern.len != 0 and pattern[pattern.len - 1] == '*') {
        const prefix = pattern[0 .. pattern.len - 1];
        return value.len >= prefix.len and std.mem.eql(u8, value[0..prefix.len], prefix);
    }
    return std.mem.eql(u8, pattern, value);
}

/// A snapshot of the kernel's process records — the only identity in the system
/// that cannot be forged, because the kernel stamps it at spawn. Refreshed per
/// authorization; binds are rare, so the copy costs nothing that matters.
var process_table: [64]process.ProcessDescriptor = undefined;
var process_count: usize = 0;
var process_truncated = false;

fn refreshProcessTable() void {
    const total = process.processes(&process_table);
    process_count = @min(total, process_table.len);
    process_truncated = total > process_table.len;
}

/// Whether task `id` is still alive, as the last snapshot saw it. A snapshot that
/// did not fit answers "alive" for anything it did not list: refusing a bind is
/// recoverable, stealing a live provider's name is not.
fn taskAlive(id: u32) bool {
    return descriptorOf(id) != null or process_truncated;
}

fn descriptorOf(id: u32) ?*const process.ProcessDescriptor {
    for (process_table[0..process_count]) |*descriptor| {
        if (descriptor.id == id) return descriptor;
    }
    return null;
}

fn nameOf(descriptor: *const process.ProcessDescriptor) []const u8 {
    const length = @min(@as(usize, descriptor.name_length), descriptor.name.len);
    return descriptor.name[0..length];
}

/// The name a kernel task answers to in a grant row. Kernel tasks carry no
/// binary, so the manifest spells the harness's parentage `kernel`.
const kernel_supervisor = "kernel";

/// init's own task id, read once at startup. Ids are monotonic and never reused
/// (system/kernel/process.zig), so an id comparison is an *identity* test where a
/// name comparison is only a resemblance test — the whole basis of the
/// attestation below.
var own_task: u32 = 0;

/// Whether `id` is a process THIS init spawned: a lookup in its own child table,
/// which is the one record of "I started that one" nobody else can write.
fn spawnedByUs(id: u32) bool {
    if (id == 0) return false;
    for (child_ids[0..service_count]) |child| {
        if (child == id) return true;
    }
    return false;
}

/// Who is asking, attested by the kernel: the caller's binary, and the **task**
/// that spawned it — an id, not a name.
///
/// A name alone is not identity: `spawn` is ungated, so a hostile process can
/// start a granted binary itself and would inherit its grants. Neither is the
/// supervisor's *name* enough, and this is the trap the first cut fell into —
/// init and the device manager are ordinary bundled binaries, so an attacker
/// spawns its own `/system/services/init` and lets that instance spawn
/// `/system/services/input`. Both kernel-stamped names then match the grant row
/// exactly, and walking to the root of the chain does not help either: the
/// laundered chain still roots at the real PID 1. What refuses it is asking
/// *which task* the supervisor is, and only accepting one init can vouch for.
const Identity = struct {
    /// The caller's binary path, exactly as the kernel stamped it at spawn.
    binary: []const u8,
    /// The supervising task's id. 0 means the kernel spawned the caller, which
    /// no ring-3 process can arrange: every `system_spawn` stamps the caller as
    /// the child's supervisor (system/kernel/process.zig `systemSpawn`).
    supervisor_task: u32,
    /// The supervising task's kernel-stamped binary — the grant row's supervisor
    /// column is matched against this, and the refusal log prints it. `kernel`
    /// when there is no supervising task.
    supervisor_binary: []const u8,
    /// Whether init can vouch for how the supervising task came to exist: it is
    /// this init, a process this init spawned, or a process the KERNEL spawned.
    /// A supervisor init cannot vouch for satisfies no row, however well its
    /// name reads — that is the laundering deputy's refusal.
    supervisor_vouched: bool,
};

/// The process a task belongs to. A thread resolves to its leader: threads share
/// a binary (a thread's own record is named `thread`), and the supervision link
/// that matters is the process's.
fn leaderOf(descriptor: *const process.ProcessDescriptor) *const process.ProcessDescriptor {
    if (descriptor.leader == descriptor.id) return descriptor;
    return descriptorOf(descriptor.leader) orelse descriptor;
}

/// Resolve the badge on a request into an identity, one hop up the supervision
/// chain in the kernel's records — one hop is enough because the hop is attested
/// by id (see `supervisorSatisfies`), and every id in the chain init accepts is
/// one init or the kernel created.
fn identify(task: u32) ?Identity {
    const caller = descriptorOf(task) orelse return null;
    const leader = leaderOf(caller);
    if (leader.supervisor == 0) return .{
        .binary = nameOf(leader),
        .supervisor_task = 0,
        .supervisor_binary = kernel_supervisor,
        .supervisor_vouched = true, // the kernel is the root of trust, not a claimant
    };
    // The supervising *task* may be a worker thread of the supervising process;
    // its process is what the manifest names and what init recorded at spawn.
    const supervisor = leaderOf(descriptorOf(leader.supervisor) orelse return null); // unattestable: refuse
    return .{
        .binary = nameOf(leader),
        .supervisor_task = supervisor.id,
        .supervisor_binary = nameOf(supervisor),
        .supervisor_vouched = supervisor.id == own_task or
            spawnedByUs(supervisor.id) or
            supervisor.supervisor == 0,
    };
}

/// Whether the caller's supervising task satisfies a grant row's supervisor
/// column. The column names *the authorized supervising task*, matched by
/// identity — the binary it must be, plus proof that this instance of that
/// binary is the authorized one:
///
///   - `kernel` is satisfied only by a genuinely kernel-spawned caller
///     (supervisor id 0). A ring-3 process cannot manufacture that: user
///     `system_spawn` always stamps the caller (system/kernel/process.zig).
///   - init's own binary is satisfied only when the supervising task IS this
///     init (`own_task`).
///   - any other binary — the device manager, a test fixture spawning another —
///     is satisfied only when the supervising task is one init spawned itself
///     (its own child table) or one the kernel spawned. Everything init and the
///     kernel start is therefore reachable; a chain that passes through a
///     process *neither* of them started is not.
fn supervisorSatisfies(column: []const u8, identity: Identity) bool {
    if (std.mem.eql(u8, column, kernel_supervisor)) return identity.supervisor_task == 0;
    if (identity.supervisor_task == 0) return false; // a kernel task answers to no binary column
    if (!matches(column, identity.supervisor_binary)) return false;
    return identity.supervisor_vouched;
}

/// Whether `identity` is granted `permission` on `name`.
fn granted(identity: Identity, permission: Permission, name: []const u8) bool {
    for (grants[0..grant_count]) |grant| {
        if (grant.permission != permission) continue;
        if (!matches(grant.binary, identity.binary)) continue;
        if (!supervisorSatisfies(grant.supervisor, identity)) continue;
        if (!matches(grant.name, name)) continue;
        return true;
    }
    return false;
}

/// Whether `identity` may reach `name` — `granted(.open, …)`, plus the one hop
/// `open` takes that `bind` does not (`Permission.supervise`).
///
/// The hop is needed because the driver tree is three deep and attestation is
/// one: the PS/2 keyboard driver's supervising task is the PS/2 bus driver,
/// which the device manager started, which init started. Init cannot vouch for
/// the bus by acquaintance — it never met it — so the manifest says so instead,
/// and says it per contract: `ps2-bus` may be the supervisor named in an `open`
/// grant for `ps2-bus` and for `input`, and for nothing else.
fn mayOpen(identity: Identity, name: []const u8) bool {
    if (granted(identity, .open, name)) return true;
    return delegatedOpen(identity, name);
}

/// The delegated `open`: the row's supervisor column names the caller's actual
/// supervising task by binary, that task is one init cannot vouch for directly,
/// and a `supervise` row authorizes it for exactly this contract.
///
/// The delegate itself is attested the ordinary way (`granted` → strict
/// `supervisorSatisfies`), so the chain is still anchored one hop above it in
/// init or the kernel and the recursion stops there. Two hops of manifest, never
/// an unbounded walk — a laundering deputy is refused at the first hop nobody
/// wrote a row for.
fn delegatedOpen(identity: Identity, name: []const u8) bool {
    if (identity.supervisor_task == 0) return false; // a kernel-spawned caller needs no delegate
    if (identity.supervisor_vouched) return false; // already answered by `granted` above
    const delegate = identify(identity.supervisor_task) orelse return false;
    if (!granted(delegate, .supervise, name)) return false;
    for (grants[0..grant_count]) |grant| {
        if (grant.permission != .open) continue;
        if (!matches(grant.binary, identity.binary)) continue;
        if (!matches(grant.supervisor, identity.supervisor_binary)) continue;
        if (!matches(grant.name, name)) continue;
        return true;
    }
    return false;
}

fn findBinding(name: []const u8) ?*Binding {
    for (&bindings) |*binding| {
        if (binding.used and std.mem.eql(u8, binding.nameSlice(), name)) return binding;
    }
    return null;
}

/// Release a binding: the provider's endpoint capability goes back to the handle
/// table, and the name is free for the next claimant. init's own cached power
/// channel goes with it — a closed handle number is reused by the next capability
/// that arrives, and a stale copy would quietly aim the shutdown call at a
/// stranger. So does the authorized power *task*: nothing may speak for a
/// contract nobody holds.
fn releaseBinding(binding: *Binding) void {
    if (std.mem.eql(u8, binding.nameSlice(), power_contract)) {
        power_endpoint = null;
        power_task = null;
        power_pending = false;
    }
    _ = ipc.close(binding.endpoint);
    binding.* = .{};
}

/// Drop every name a dead process held. Called when a supervised child dies (so
/// the restarted instance can bind again) and whenever a bind finds the current
/// owner gone — providers init does not supervise need the second path.
fn unbindTask(task: u32) void {
    for (&bindings) |*binding| {
        if (binding.used and binding.task == task) {
            std.log.info("/protocol/{s} released ({s} is gone)", .{ binding.nameSlice(), binding.binarySlice() });
            releaseBinding(binding);
        }
    }
}

/// A contract name as the namespace spells it: the mount-relative path a resolve
/// hands us ("/display") and the name a bind sends ("display") are the same thing
/// with and without a leading slash, so one normaliser serves both. Empty or
/// longer than the namespace admits is not a name.
fn contractName(raw: []const u8) ?[]const u8 {
    const name = if (raw.len != 0 and raw[0] == '/') raw[1..] else raw;
    if (name.len == 0 or name.len > maximum_name) return null;
    return name;
}

/// The provider's endpoint that will ride the *next* reply, when the request was
/// an `open` that found its contract.
var pending_capability: ?ipc.Handle = null;

/// The ownership rule for a capability that arrives with a turn of the loop —
/// **the turn owns it until a handler takes it, and closes whatever is left** —
/// lives in `ipc.Arrival`, next to `replyWait`, because it is not PID 1's rule:
/// the service harness every other service runs (library/kernel/service.zig) had
/// the identical hole and now states the identical contract.
const Arrival = ipc.Arrival;

/// The one contract init is itself a client of. It never resolves the name — it
/// *is* the registry, so it reads its own table; the binding is what hands it the
/// channel.
const power_contract = "power";

/// Set when `power` is bound: init subscribes to it on the next turn of the loop,
/// never inside the bind — the provider is blocked on our reply until then, so
/// calling it here would deadlock the pair.
var power_pending = false;
var power_endpoint: ?ipc.Handle = null;

/// The one task authorized to deliver power events: whoever holds the `power`
/// binding. Recorded at the bind and cleared with the binding, so a provider that
/// dies and rebinds re-derives it with no further ceremony.
///
/// This is the *authentication* for the shutdown path. init's registry endpoint
/// is its supervision endpoint, and `fs_resolve("/protocol")` installs a sendable
/// handle to it in any caller's table — so after P2 every ring-3 process can post
/// into PID 1's mailbox. A power event may therefore never be believed on the
/// strength of its payload; it is believed because the kernel stamped the
/// sender's task id on it and that id is the provider's.
var power_task: ?u32 = null;

/// Whether the heartbeat's re-arming timer is running. A timer landing carries no
/// identity, so the loop cannot tell one timer from another — which means exactly
/// one may ever be in flight, or every landing re-arms and the beat doubles. (It
/// did: two beats a second is enough extra chatter to cut a driver's echoed line
/// in half on the shared serial stream.) So the deferred power subscribe borrows
/// the heartbeat's tick when there is one, and arms its own only when there is not.
var heartbeat_running = false;

/// Answer one registry request. Writes a vfs-protocol reply into `reply` and
/// returns its length; a capability the reply must carry lands in
/// `pending_capability`. `arrived` is the capability the *request* carried, owned
/// by the turn — nothing here has to close it, only `bind` has to claim it.
fn serveRegistry(request_bytes: []const u8, reply: []u8, sender: u32, arrived: *Arrival) usize {
    if (request_bytes.len < envelope.prefix_size)
        return answer(reply, -envelope.EPROTO, 0);
    // The header is read field by field rather than reinterpreted whole, and the
    // verb is compared as a number rather than decoded into the generated
    // `Operation`: the bytes come from anyone at all, so a value outside the enum
    // must be a refusal, never an `@enumFromInt`. This is deliberately NOT
    // `Protocol.Provider.dispatch` for the same reason — PID 1 reads a stranger's
    // packet, and it reads it by hand.
    const operation = std.mem.readInt(u32, request_bytes[0..4], .little);
    const body = request_bytes[envelope.prefix_size..];

    if (operation == @intFromEnum(vfs_protocol.Operation.bind))
        return answer(reply, onBind(sender, body, arrived), 0);
    // Only `bind` claims a capability; one attached to anything else is closed by
    // the turn's `defer` in the loop, along with the ones sent to a request that
    // was too short to name a verb at all.
    if (operation == @intFromEnum(vfs_protocol.Operation.open)) {
        // `open`'s fixed part is the flags word, which means nothing to a
        // namespace; the name follows it as the packet's tail.
        if (body.len < @sizeOf(vfs_protocol.Open)) return answer(reply, -envelope.EPROTO, 0);
        return onOpen(reply, sender, body[@sizeOf(vfs_protocol.Open)..]);
    }
    if (operation == @intFromEnum(vfs_protocol.Operation.readdir)) {
        if (body.len < @sizeOf(vfs_protocol.Readdir)) return answer(reply, -envelope.EPROTO, 0);
        return onReaddir(reply, std.mem.readInt(u64, body[0..8], .little));
    }
    // Everything else a filesystem answers is meaningless here: `/protocol` holds
    // contracts, not bytes.
    return answer(reply, -envelope.ENOSYS, 0);
}

/// Lay down the envelope's reply prefix (and say how many payload bytes the
/// caller has already written after it).
fn answer(reply: []u8, status: i32, payload_len: usize) usize {
    const header = envelope.Status{ .status = status, .len = @intCast(payload_len) };
    @memcpy(reply[0..envelope.prefix_size], std.mem.asBytes(&header));
    return envelope.prefix_size + payload_len;
}

/// `bind(name, capability = the provider's endpoint)`. The capability is the
/// point of the call, so a bind without one is malformed. Every refusal below
/// simply returns: the endpoint stays the turn's, and the turn closes it — which
/// is why there is not one `ipc.close` on the way out of any of the six of them.
/// The success path is the only one that says anything about ownership, because
/// it is the only one that keeps the capability.
fn onBind(sender: u32, raw_name: []const u8, arrived: *Arrival) i32 {
    if (arrived.peek() == null) return -envelope.EPROTO;
    const name = contractName(raw_name) orelse return -envelope.ENOENT;
    refreshProcessTable();
    const identity = identify(sender) orelse return -envelope.EPERM;
    if (!granted(identity, .bind, name)) {
        std.log.info("refused bind of /protocol/{s} by {s} (pid {d}, supervisor {s} pid {d})", .{
            name,
            identity.binary,
            sender,
            identity.supervisor_binary,
            identity.supervisor_task,
        });
        return -envelope.EPERM;
    }
    if (findBinding(name)) |existing| {
        // Collision is an error — never last-writer-wins — unless the incumbent
        // is dead, which is how a restarted provider retakes its own name.
        if (taskAlive(existing.task)) {
            std.log.info("refused bind of /protocol/{s}: held by {s} (pid {d})", .{ name, existing.binarySlice(), existing.task });
            return -envelope.EBUSY;
        }
        releaseBinding(existing);
    }
    const slot = for (&bindings) |*binding| {
        if (!binding.used) break binding;
    } else return -envelope.ENOSPC;

    // Claimed: the binding owns the endpoint from here, and `releaseBinding` is
    // what closes it.
    const endpoint = arrived.take().?;
    slot.* = .{ .used = true, .endpoint = endpoint, .task = sender };
    @memcpy(slot.name[0..name.len], name);
    slot.name_len = name.len;
    const binary_len = @min(identity.binary.len, slot.binary.len);
    @memcpy(slot.binary[0..binary_len], identity.binary[0..binary_len]);
    slot.binary_len = binary_len;

    // Provenance, at the moment it becomes true: name -> pid -> binary path.
    std.log.info("/protocol/{s} -> pid {d} {s}", .{ name, sender, slot.binarySlice() });
    if (std.mem.eql(u8, name, power_contract)) {
        power_endpoint = endpoint;
        // The bind is also the authentication: whoever holds `power` is the one
        // task whose power events init will act on (see `onPowerEvent`).
        power_task = sender;
        power_pending = true;
        // Wake ourselves once the reply has gone out; the subscribe call cannot
        // happen while the power service is still blocked on it. The heartbeat's
        // tick is that wake when it is running — see `heartbeat_running`.
        if (!heartbeat_running) _ = time.timerOnce(supervision_endpoint, 1);
    }
    return 0;
}

/// `open(name)` -> the provider's endpoint, delivered as the reply's capability.
///
/// **A refusal and an absence are the same answer, and that is the whole point.**
/// The namespace is the restriction (docs/os-development/protocol-namespace.md):
/// what a process may open is what exists for it, so "you may not have this" and
/// "there is no such thing" collapse into one reply — `-ENOENT`, no payload, no
/// capability. A caller therefore has no oracle: it cannot use `open` to learn
/// that a contract it lacks is bound, and — the reason this matters beyond
/// tidiness — stage two's supervisor can refuse, stall for a human, or substitute
/// a fake without the child being able to tell which happened.
///
/// Indistinguishable is a claim about *work done*, not only about the bytes, so
/// both questions are asked on every open whatever the first one answers: the
/// process table is refreshed, the caller identified, the grants scanned and the
/// bindings scanned, and only then is the single verdict formed. Nothing here
/// logs, either — `klog_read` is ungated (system/kernel/process.zig), so a line
/// written on one branch is a line the refused caller can read, and a serial line
/// costs milliseconds it could time. The operator's diagnosis is the pair the
/// namespace already publishes on purpose: `readdir` over `/protocol` says what is
/// bound, `/system/configuration/protocol.csv` says who may reach it, and the
/// client's own retry loop says which one it wanted.
///
/// (Not constant-time in the cryptographic sense, and not claimed to be: the two
/// scans stop at the row they match, and the optimiser is free to sink a pure
/// table walk past a branch that discards it. What is removed is the difference a
/// caller could actually measure or read — a syscall on one branch and not the
/// other, a line in a world-readable log ring, or a serial write costing
/// milliseconds.)
fn onOpen(reply: []u8, sender: u32, raw_name: []const u8) usize {
    const name = contractName(raw_name) orelse return answer(reply, -envelope.ENOENT, 0);
    refreshProcessTable();
    const identity = identify(sender);
    const permitted = if (identity) |who| mayOpen(who, name) else false;
    const binding = findBinding(name);
    if (!permitted) return answer(reply, -envelope.ENOENT, 0);
    const found = binding orelse return answer(reply, -envelope.ENOENT, 0);
    pending_capability = found.endpoint;
    // A contract node has no node id — the capability is the whole answer — but
    // the protocol says an `open` reply carries one, so it carries a zero.
    const opened = vfs_protocol.Opened{ .node = 0 };
    @memcpy(reply[envelope.prefix_size..][0..@sizeOf(vfs_protocol.Opened)], std.mem.asBytes(&opened));
    return answer(reply, 0, @sizeOf(vfs_protocol.Opened));
}

/// `readdir(cursor)` — the namespace, browsable. One entry per turn, as the vfs
/// protocol lists any directory: kind `protocol`, the contract's name, and the
/// provider's task id in `size`, so a plain listing answers "who serves this?".
fn onReaddir(reply: []u8, cursor: u64) usize {
    var index: u64 = 0;
    for (&bindings) |*binding| {
        if (!binding.used) continue;
        if (index != cursor) {
            index += 1;
            continue;
        }
        const name = binding.nameSlice();
        return writeEntry(reply, .{
            .kind = @intFromEnum(vfs_protocol.NodeKind.protocol),
            .name_len = @intCast(name.len),
            .size = binding.task,
        }, name);
    }
    // End of directory, which the envelope spells as an entry with no name: the
    // reply's own length cannot say it any more, because the fixed reply part
    // always travels.
    return writeEntry(reply, .{}, &.{});
}

/// One `readdir` reply: the entry, then its name inline.
fn writeEntry(reply: []u8, entry: vfs_protocol.DirectoryEntry, name: []const u8) usize {
    const total = vfs_protocol.directory_entry_size + name.len;
    if (envelope.prefix_size + total > reply.len) return answer(reply, -envelope.EPROTO, 0);
    @memcpy(reply[envelope.prefix_size..][0..vfs_protocol.directory_entry_size], std.mem.asBytes(&entry));
    @memcpy(reply[envelope.prefix_size + vfs_protocol.directory_entry_size ..][0..name.len], name);
    return answer(reply, 0, total);
}

pub fn main(startup: process.Init) void {
    // `registry` is the scenario mode: serve /protocol and nothing else. The
    // kernel test harness spawns its own providers directly, so it wants the
    // naming layer up without init's whole service list underneath it
    // (docs/security-track-plan.md, decision 9).
    const registry_only = if (startup.arguments.get(1)) |role| std.mem.eql(u8, role, "registry") else false;

    // Prove the heap end to end: allocate through the runtime allocator (which
    // mmaps pages from the kernel and carves them with the free list), write into
    // that heap buffer (exercising the widened debug_write bounds check), and
    // free it. A fault here would kill init before it heartbeats — so the init
    // test doubles as the heap regression test. (C code links the same heap via
    // the extern malloc/free symbols; Zig code uses this allocator.)
    const gpa = memory.allocator();
    if (gpa.alloc(u8, 64)) |buffer| {
        const message = "/system/services/init: heap ok\n";
        @memcpy(buffer[0..message.len], message);
        _ = logging.write(buffer[0..message.len]);
        gpa.free(buffer);
    } else |_| {}

    // One endpoint carries everything init waits on: children's exit
    // notifications (they are spawned supervised against it), init's own
    // signals, power events it subscribes to, and — since PID 1 is the registrar
    // — every /protocol request. One thread can wait in one place, so they share
    // a mailbox and the loop below tells them apart.
    supervision_endpoint = ipc.createIpcEndpoint() orelse {
        _ = logging.write("/system/services/init: no endpoint\n");
        return;
    };
    _ = process.bindSignals(supervision_endpoint);

    // Our own id, before anything can ask us a question. It is half of the
    // registrar's authority: a grant row naming init as the supervisor is
    // satisfied by *this* task and no other instance of this binary
    // (`supervisorSatisfies`).
    own_task = process.taskId();

    // The namespace goes up BEFORE anything is spawned, so a service's first
    // bind lands rather than retrying. The kernel reserves the prefix: this
    // mount is the only one it will ever hold.
    loadGrants();
    if (!fs.mount("/protocol", supervision_endpoint)) {
        _ = logging.write("/system/services/init: /protocol already mounted — not the registrar\n");
    }

    // Load the service list, then bring each up supervised so init can stop them
    // cleanly. Best-effort and silent: each service announces its own readiness,
    // and with no /system/configuration/init.csv (an isolation test) the loop starts nothing.
    if (!registry_only) {
        loadServices();
        for (services[0..service_count], 0..) |*service, i| {
            if (process.spawnSupervised(service.path, service.arguments(), supervision_endpoint)) |id| child_ids[i] = id;
        }
    }

    // A re-arming timer drives the liveness heartbeat — proof PID 1 is alive (the
    // init test's marker) and a -Dserial diagnostic. It is a serial/test-build-only
    // concern: a flashable (serial-off) image runs a purely event-driven PID 1 that
    // wakes only for real work (signals, power events, children's exits), never for a
    // periodic beat. `build_options.serial` is comptime, so the heartbeat — its timer
    // and the handler below — folds away entirely when serial is off.
    heartbeat_running = build_options.serial and !registry_only;
    if (heartbeat_running) _ = time.timerOnce(supervision_endpoint, 1000);

    var receive: [vfs_protocol.message_maximum]u8 = undefined;
    var reply_buffer: [vfs_protocol.message_maximum]u8 = undefined;
    var reply_len: usize = 0;
    var reply_capability: ?ipc.Handle = null;
    while (true) {
        const got = ipc.replyWait(supervision_endpoint, reply_buffer[0..reply_len], &receive, reply_capability);
        reply_len = 0; // nothing owed until this turn's request says otherwise
        reply_capability = null;

        // Whatever capability came with this turn is the turn's, and the turn
        // closes it unless a handler claims it. Structural on purpose — see
        // `Arrival`; it is what keeps a zero-length call from spending a handle
        // slot of PID 1's per call.
        var arrived: Arrival = .{ .handle = got.cap };
        defer arrived.release();

        if (got.isNotification()) {
            // Every badge on this branch is stamped by the KERNEL, and a stranger
            // cannot stamp one: `ipc_send` — the only way a ring-3 process puts
            // something in this mailbox with no reply owed — sets exactly
            // `notify_badge_bit | notify_message_bit` and fills the low bits with
            // the sender's own task id (system/kernel/ipc-synchronous.zig,
            // `sendLocked`).
            //
            // That is a statement about `ipc_send`, and on its own it proved far
            // too little: an attacker does not use `ipc_send` to forge a signal,
            // it asks the kernel to deliver a real one *here*. `fs_resolve`
            // hands any process a sendable handle to this endpoint, and
            // `signal_bind`/`timer_bind`/`process_subscribe`/spawn's exit
            // endpoint all used to accept any handle the caller held — so a
            // stranger could point its own signal delivery at PID 1 and signal
            // itself, and the terminate badge landing here was genuine in every
            // bit. What makes these branches trustworthy is therefore in the
            // KERNEL, not in this comment: binding a kernel notification to an
            // endpoint now requires *owning* that endpoint (`ipc.ownedBy`), so a
            // signal here comes only from our supervisor or our own group, a
            // timer landing only from a timer we armed, and a child-exit notice
            // only from a child we spawned. The buffered-message branch below is
            // the one still carrying a stranger's bytes, and it is the one that
            // authenticates its sender.
            if (process.signalsFrom(got.badge)) |signals| {
                if (signals.has(.terminate)) shutDown();
                continue;
            }
            if (got.isTimer()) {
                // The pending power subscription rides any timer landing: by the
                // time one arrives, the bind's reply has left and the power
                // service is serving again.
                if (power_pending) {
                    power_pending = false;
                    subscribePower();
                }
                if (heartbeat_running) {
                    _ = logging.write("/system/services/init: heartbeat\n");
                    _ = time.timerOnce(supervision_endpoint, 1000);
                }
                continue;
            }
            if (got.isMessage()) {
                // A buffered message: the only thing here an anonymous stranger
                // can put in front of PID 1. Authenticated by sender, never by
                // payload — see `onPowerEvent`.
                onPowerEvent(got.senderTaskId(), receive[0..got.len]);
                continue;
            }
            if (got.isChildExit()) {
                restartChild(got.childProcessId());
                continue;
            }
            continue; // anything else: keep waiting
        }
        // The universal ping, answered by the empty reply. A ping may still carry
        // a capability — the kernel installs one regardless of length — and this
        // `continue` disposes of it through the turn's `defer`, which is exactly
        // what it failed to do when the close lived in the branches.
        if (got.len == 0) continue;
        reply_len = serveRegistry(receive[0..got.len], &reply_buffer, got.senderTaskId(), &arrived);
        reply_capability = pending_capability;
        pending_capability = null;
    }
}

/// A buffered message claiming to be a power event.
///
/// **Privileged control traffic is authenticated by sender, never by content.**
/// init's registry endpoint is its supervision endpoint, and `fs_resolve` installs
/// a sendable handle to any mount's backend in *any* caller's table
/// (system/kernel/process.zig), so after P2 every ring-3 process holds a handle it
/// can `ipc_send` into. Two payload bytes were once enough to reach `shutDown()`
/// from here — which stops every service and parks PID 1 in its final sleep,
/// destroying the registry for the rest of the boot, and does it for any process
/// that cares to ask.
///
/// The sender's task id is the fix, because it is not the sender's to choose: the
/// kernel stamps it into the badge's low bits as it copies the message into the
/// ring. Init is the registry, so it knows exactly which task holds `power`, and
/// that task alone is believed. A provider that dies and rebinds moves the
/// authorization with the binding; a name nothing holds authorizes nobody. Task
/// ids are never reused, so even a dead provider's id cannot be inherited.
///
/// (One task, not one process: the ACPI service is single-threaded and publishes
/// from the same task that bound the name. A threaded provider would want its
/// leader compared instead — which is a change to make when one appears, not a
/// looser rule to leave lying around for it.)
fn onPowerEvent(sender: u32, payload: []const u8) void {
    const authorized = power_task orelse {
        std.log.info("ignored a power event from pid {d}: nothing holds /protocol/power", .{sender});
        return;
    };
    if (sender != authorized) {
        std.log.info("ignored a power event from pid {d}: /protocol/power is pid {d}", .{ sender, authorized });
        return;
    }
    // Read as an envelope packet, never by byte offset: the kind IS the packet's
    // verb, so a power event is decoded exactly the way every other event in the
    // system is. A packet whose operation is not one of this protocol's events —
    // anything else that lands in this mailbox — decodes to null and is dropped.
    const kind = power_protocol.Protocol.eventOf(payload) orelse return;
    if (kind == .power_button) shutDown();
}

/// A supervised boot service died. Find which one and restart it — unless it exited
/// cleanly (it chose to stop, e.g. a driver with no hardware) or has hit the crash-loop
/// cap. Reclaiming the dead process is already the kernel's job (docs/process-lifecycle.md
/// iron rule 1); init only decides whether to bring it back.
fn restartChild(id: u32) void {
    // Whatever it served, it serves no longer: the name goes back before the
    // replacement asks for it, so the restarted instance binds rather than
    // colliding with its own corpse.
    unbindTask(id);
    if (shutting_down) return; // deaths during the stop sequence are expected, not crashes
    for (services[0..service_count], 0..) |*service, i| {
        if (child_ids[i] != id) continue;
        child_ids[i] = 0;
        // An unknown reason (the record aged out) is treated as a crash worth restarting.
        const reason = process.exitReason(id) orelse .fault;
        if (reason == .exited) {
            std.log.info("{s} exited cleanly; not restarting", .{service.path});
            return;
        }
        restart_counts[i] += 1;
        if (restart_counts[i] > maximum_restarts) {
            std.log.info("{s} keeps crashing; giving up after {d} restarts", .{ service.path, maximum_restarts });
            return;
        }
        std.log.info("{s} died ({s}); restarting ({d}/{d})", .{ service.path, @tagName(reason), restart_counts[i], maximum_restarts });
        if (process.spawnSupervised(service.path, service.arguments(), supervision_endpoint)) |new_id| child_ids[i] = new_id;
        return;
    }
    // An untracked child (e.g. the log-flush one-shot): nothing to restart.
}

/// Subscribe our endpoint (handed over as the call's capability) to the power
/// service, so events arrive as buffered messages here. init is the registry, so
/// it never resolves `/protocol/power` — it reads its own table, which is also
/// what makes this reachable at all: the subscription is armed by the bind that
/// put the endpoint there.
///
/// **This is the only place PID 1 blocks on another process, and it is the one
/// hazard the registrar has.** One thread serves both the namespace and this
/// call, so while it is outstanding init answers nobody: if the callee were
/// itself blocked asking init to resolve a name, the pair would never move. Two
/// things keep that from happening — the call is deferred to the next turn of
/// the loop (so the provider has its bind reply and is on its way to
/// `replyWait`), and the power provider resolves every name it needs *before* it
/// binds (system/services/acpi/acpi.zig, `manager_channel`). Any future service
/// init calls owes the same discipline.
fn subscribePower() void {
    const handle = power_endpoint orelse return;
    // The reserved `subscribe` verb: nothing but the header, with our own
    // endpoint riding as the call's capability.
    const header = envelope.Header{ .operation = envelope.operation_subscribe };
    var reply: [power_protocol.message_maximum]u8 = undefined;
    _ = ipc.callCap(handle, std.mem.asBytes(&header), &reply, supervision_endpoint) catch {};
}

/// The stop sequence: persist the log while storage is still up, then terminate
/// each child in reverse spawn order (vfs last — other services may flush through
/// it), waiting up to a deadline for each to exit before killing it, then ask the
/// power service to enter S5.
fn shutDown() void {
    shutting_down = true; // the stop loop below kills children — those deaths aren't crashes
    _ = logging.write("/system/services/init: shutting down\n");
    // Log persistence is the logger service's job: it is the LAST boot service,
    // so the reverse-order stop below terminates it first and its final drain
    // runs while the whole storage chain is still alive.
    var i = service_count;
    while (i > 0) {
        i -= 1;
        if (child_ids[i] != 0) process.stop(child_ids[i], 2000, supervision_endpoint);
    }
    if (power_endpoint) |h| {
        var packet: [power_protocol.message_maximum]u8 = undefined;
        if (power_protocol.Protocol.encodeRequest(.shutdown, 0, {}, &.{}, &packet)) |framed| {
            var reply: [power_protocol.message_maximum]u8 = undefined;
            _ = ipc.call(h, framed, &reply) catch {};
        }
    }
    // If S5 did not take, init has nothing left to do but idle.
    while (true) time.sleepMillis(1000);
}
