//! mgf-hrb-linux — the first real KernelBackend (Part 6.5). Implements the nine
//! abstract seam operations against ACTUAL Linux interfaces.
//!
//! HONESTY ABOUT WHAT'S EXERCISED (this is a sandbox without /dev/vfio):
//!   • enumerate / query_topology  — REAL: reads genuine PCI devices from
//!     /sys/bus/pci/devices, parses real vendor/device IDs. Runs against the
//!     actual hardware present.
//!   • acquire / release / reset / map_region / raw_channel — STRUCTURALLY
//!     COMPLETE but HARDWARE-UNTESTED here: the VFIO/usbfs code path is written
//!     to the contract, but cannot bind a real device without /dev/vfio + IOMMU.
//!     These return Unsupported when the VFIO substrate is absent — an HONEST
//!     "I can't do this here" (G2), never a lie.
//!
//! DEADLINES (Part 12 §2.2 / D-029). Every op signature now carries a
//! `Deadline`. This skeleton's ops are synchronous sysfs reads with no
//! cancellation path — they run to completion against healthy hardware
//! within a few ms each, which is well under any reasonable deadline. The
//! contract obligation is therefore TRIVIALLY MET on the success path. The
//! HARD obligation — "abandon a wedged syscall within the deadline" —
//! requires running ops in a killable context (separate thread or worker
//! process, per Part 12 §2.2). That second half of the enforcement story
//! is separate work (the watchdog + the privileged-helper IPC). Same
//! honest-incompleteness pattern as `enforce_cap` (D-026): the contract is
//! complete; the implementation states what it covers.
//!
//! This is the "Level 2: direct syscalls/ioctls" path (the how-low-we-go answer):
//! sysfs reads now; VFIO ioctls where hardware allows. No libpci/libusb.

const std = @import("std");
const model = @import("mgf_hrb_model");
const kabi = @import("mgf_hrb_kabi");
const sysfs_walk = @import("mgf_hrb_sysfs_walk");
const linux_iommu = @import("mgf_hrb_linux_iommu");
const vfio = @import("mgf_hrb_vfio");

const Urn = model.Urn;
const Resource = model.Resource;
const ClaimMode = model.ClaimMode;
const Err = model.Err;
const Deadline = kabi.Deadline;
const linux = std.os.linux;

/// Raw-syscall sysfs helpers. This IS the design's "Level 2: direct syscalls,
/// no convenience libraries" path (the how-low-we-go answer) — and using raw
/// syscalls here, rather than std.Io, keeps Linux specifics contained in this
/// backend (Part 6.5) exactly as the design intends.
fn sysOpen(path: [:0]const u8, flags: u32) ?i32 {
    const rc = linux.open(path, .{ .ACCMODE = .RDONLY, .DIRECTORY = (flags & linux.O.DIRECTORY) != 0 }, 0);
    if (linux.errno(rc) != .SUCCESS) return null;
    return @intCast(rc);
}
fn sysExists(path: [:0]const u8) bool {
    const rc = linux.access(path, linux.F_OK);
    return linux.errno(rc) == .SUCCESS;
}
/// Read a sysfs symlink and return the basename of its target.
///
/// Used for the `driver` + `driver/module` attrs on a bound device:
/// `.../driver` is a symlink to `/sys/bus/<subsys>/drivers/<name>`, so
/// the basename is the driver name. `.../driver/module` is a symlink to
/// `/sys/module/<name>`, so the basename is the loaded kernel-module name.
///
/// Returns null when the symlink doesn't exist (device is unbound) — the
/// caller emits no attr in that case (honest emptiness, G2). The buffer
/// must be large enough for the full symlink target (256 bytes is plenty
/// for both `driver` and `driver/module`).
fn sysReadSymlinkBasename(buf: []u8, path: [:0]const u8) ?[]const u8 {
    const rc = linux.readlink(path, buf.ptr, buf.len);
    if (linux.errno(rc) != .SUCCESS) return null;
    const n: usize = rc;
    if (n == 0 or n > buf.len) return null;
    return std.fs.path.basename(buf[0..n]);
}

/// True iff `s` is the kernel's sysfs name for a PCI root complex:
/// `pci<domain>:<bus>`, e.g. `pci0000:00`. Exactly 10 bytes — three
/// literal (`p`, `c`, `i`), four hex (domain), `:`, two hex (bus).
/// Used by `vDumpChain` to spot the top-of-tree component in a
/// `/sys/bus/pci/devices/<bdf>` symlink target.
pub fn isPciRootComplex(s: []const u8) bool {
    if (s.len != 10) return false;
    if (s[0] != 'p' or s[1] != 'c' or s[2] != 'i') return false;
    if (s[7] != ':') return false;
    inline for ([_]usize{ 3, 4, 5, 6, 8, 9 }) |idx| {
        const c = s[idx];
        const ok = (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f');
        if (!ok) return false;
    }
    return true;
}

/// True iff `s` looks like a PCI BDF: `DDDD:BB:DD.F` (lowercase hex).
/// Used by the parent-chain walker (`vDumpChain`) — and reused by
/// `linux_usb.zig` to find the USB host controller's BDF inside the
/// `/sys/bus/usb/devices/usb<N>` symlink target. Strict shape: exactly
/// 12 bytes, four-segment `4:2:2.1`. The kernel emits BDFs in this
/// canonical form on every sysfs path; anything else is not a BDF.
pub fn isPciBdf(s: []const u8) bool {
    if (s.len != 12) return false;
    if (s[4] != ':' or s[7] != ':' or s[10] != '.') return false;
    inline for ([_]usize{ 0, 1, 2, 3, 5, 6, 8, 9, 11 }) |idx| {
        const c = s[idx];
        const ok = (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f');
        if (!ok) return false;
    }
    return true;
}

/// One-line summary of a PCI function — `vendor:device class=<c>`.
/// Used by `dump_chain` (both PCI's own chain + USB's controller
/// rung). Writes into `buf` and returns the populated slice; on any
/// read failure returns a placeholder rather than failing the walk
/// (G2: the rung is still useful as a topology marker even without
/// the IDs).
pub fn pciSummary(bdf: []const u8, buf: []u8) []const u8 {
    var path_buf: [256]u8 = undefined;
    var vendor_buf: [16]u8 = undefined;
    var device_buf: [16]u8 = undefined;
    var class_buf: [16]u8 = undefined;
    const vp = std.fmt.bufPrintZ(
        &path_buf,
        "/sys/bus/pci/devices/{s}/vendor",
        .{bdf},
    ) catch return std.fmt.bufPrint(buf, "pci {s}", .{bdf}) catch buf[0..0];
    const vendor = sysReadAttr(&vendor_buf, vp) orelse "?";
    const dp = std.fmt.bufPrintZ(
        &path_buf,
        "/sys/bus/pci/devices/{s}/device",
        .{bdf},
    ) catch return std.fmt.bufPrint(buf, "pci {s}", .{bdf}) catch buf[0..0];
    const device = sysReadAttr(&device_buf, dp) orelse "?";
    const cp = std.fmt.bufPrintZ(
        &path_buf,
        "/sys/bus/pci/devices/{s}/class",
        .{bdf},
    ) catch return std.fmt.bufPrint(buf, "pci {s}", .{bdf}) catch buf[0..0];
    const class = sysReadAttr(&class_buf, cp) orelse "?";
    return std.fmt.bufPrint(buf, "{s}:{s} class={s}", .{ vendor, device, class }) catch buf[0..0];
}

fn sysReadAttr(buf: []u8, path: [:0]const u8) ?[]const u8 {
    const rc = linux.open(path, .{ .ACCMODE = .RDONLY }, 0);
    if (linux.errno(rc) != .SUCCESS) return null;
    const fd: i32 = @intCast(rc);
    defer _ = linux.close(fd);
    const n = linux.read(fd, buf.ptr, buf.len);
    if (linux.errno(n) != .SUCCESS) return null;
    var len: usize = n;
    while (len > 0 and (buf[len - 1] == '\n' or buf[len - 1] == '\r')) len -= 1;
    return buf[0..len];
}

/// One VFIO-bound device, keyed by the token we handed the client. Populated
/// only on a successful real bind (root + a passthrough device); `vRelease`
/// looks it up to tear the device down (unbind the VFIO fds + reattach the host
/// driver). Accessed under `Core.lock` (the linux backend has no unlocked reader
/// path), so it needs no lock of its own.
const BoundEntry = struct {
    id: u64,
    dev: vfio.BoundDevice,
    detach: vfio.DetachState, // to reattach the host driver on release
    bdf_buf: [32]u8 = undefined,
    bdf_len: usize = 0,
};

pub const LinuxBackend = struct {
    alloc: std.mem.Allocator,
    /// whether the VFIO substrate is available (/dev/vfio present). When false,
    /// acquisition honestly returns Unsupported instead of pretending.
    vfio_available: bool,
    /// Live VFIO binds, by token. Empty until a real bind succeeds (so on a
    /// sandbox without root it never allocates — `.empty` is allocation-free).
    bound: std.ArrayListUnmanaged(BoundEntry) = .empty,
    next_token: u64 = 1,
    /// The VFIO + driver-detach seams (DIP). Set to the raw-syscall impls in
    /// `init`; a test overrides them with fakes to exercise the full
    /// acquire → fd_for_token → revoke lifecycle without root or hardware.
    vfio_ops: vfio.VfioOps,
    driver_ops: vfio.DriverOps,

    pub fn init(alloc: std.mem.Allocator) LinuxBackend {
        // detect VFIO substrate honestly via a raw access() syscall
        const have_vfio = sysExists("/dev/vfio/vfio");
        return .{
            .alloc = alloc,
            .vfio_available = have_vfio,
            .vfio_ops = vfio.realOps(),
            .driver_ops = vfio.realDriverOps(),
        };
    }

    /// Tear down any still-bound devices (unbind + reattach the host driver) +
    /// free the table. Best-effort; the kernel also drops the fds on process exit.
    pub fn deinit(self: *LinuxBackend) void {
        for (self.bound.items) |e| {
            vfio.unbind(self.vfio_ops, e.dev);
            var nb: [32]u8 = undefined;
            if (std.fmt.bufPrintZ(&nb, "{s}", .{e.bdf_buf[0..e.bdf_len]})) |bdf_z| {
                vfio.reattachHostDriver(self.driver_ops, bdf_z, e.detach);
            } else |_| {}
        }
        self.bound.deinit(self.alloc);
    }

    // --- helpers: read a small sysfs attribute file (REAL hardware facts) ---
    fn readAttr(buf: []u8, path: [:0]const u8) ?[]const u8 {
        return sysReadAttr(buf, path);
    }

    // ---- vtable implementations ----

    fn vEnumerate(ctx: *anyopaque, scope: []const u8, sink: *const kabi.FactSink, deadline: Deadline) Err!void {
        const self: *LinuxBackend = @ptrCast(@alignCast(ctx));
        _ = self;
        _ = deadline; // synchronous sysfs walk; see module-level note on enforcement
        // REAL enumeration via raw syscalls: open + getdents64 on the PCI dir.
        const dfd_rc = linux.open("/sys/bus/pci/devices", .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, 0);
        if (linux.errno(dfd_rc) != .SUCCESS) return; // no PCI sysfs → empty, not error
        const dfd: i32 = @intCast(dfd_rc);
        defer _ = linux.close(dfd);

        var dbuf: [4096]u8 = undefined;
        while (true) {
            const nread = linux.getdents64(dfd, &dbuf, dbuf.len);
            if (linux.errno(nread) != .SUCCESS) return;
            if (nread == 0) break; // end of directory
            var off: usize = 0;
            while (off + 19 <= nread) {
                // Parse dirent64 by byte offset (records are NOT struct-aligned
                // at arbitrary buffer offsets — casting the struct panics).
                // layout: ino:u64(0) off:u64(8) reclen:u16(16) type:u8(18) name(19..)
                const reclen = std.mem.readInt(u16, dbuf[off + 16 ..][0..2], .little);
                if (reclen == 0) break; // malformed guard
                const name_start = off + 19;
                const name_ptr: [*:0]const u8 = @ptrCast(&dbuf[name_start]);
                const name = std.mem.span(name_ptr);
                off += reclen;
                if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;

                // name is a BDF like "0000:01:00.0"
                var urn_buf: [128]u8 = undefined;
                const urn_str = std.fmt.bufPrint(&urn_buf, "hw:/pci/{s}/fn0", .{name}) catch continue;
                const urn = Urn.from(urn_str);
                if (!urn.matches(scope)) continue;

                var res = Resource{ .urn = urn, .family = .claimable, .kind = .pcie_function };
                // read real vendor id to set fidelity honestly (live vs unknown)
                var vbuf: [32]u8 = undefined;
                var vpath_buf: [256]u8 = undefined;
                const vpath = std.fmt.bufPrintZ(&vpath_buf, "/sys/bus/pci/devices/{s}/vendor", .{name}) catch {
                    res.fidelity = .unknown;
                    sink.push(sink.ctx, res);
                    continue;
                };
                res.fidelity = if (sysReadAttr(&vbuf, vpath) != null) .live else .unknown;
                sink.push(sink.ctx, res);
            }
        }
    }

    fn vQueryTopology(ctx: *anyopaque, sink: *const kabi.FactSink, deadline: Deadline) Err!void {
        return vEnumerate(ctx, "hw:/**", sink, deadline);
    }
    fn vWatch(ctx: *anyopaque, scope: []const u8, sink: *const kabi.EventSink, deadline: Deadline) Err!kabi.WatchHandle {
        _ = ctx;
        _ = scope;
        _ = sink;
        _ = deadline;
        // REAL impl would open a netlink uevent socket (Part 6.5). Honest stub:
        return Err.Unsupported;
    }

    fn vAcquire(ctx: *anyopaque, urn: *const Urn, mode: ClaimMode, deadline: Deadline) Err!kabi.AcquireToken {
        const self: *LinuxBackend = @ptrCast(@alignCast(ctx));
        _ = deadline; // VFIO ioctl path will honour this; see module-level note
        // Observe needs no hardware claim — it's a read-only handle. Always OK.
        // (matches the contract: observe never acquires exclusivity)
        if (!self.vfio_available) {
            // HONEST: cannot bind a device without the VFIO substrate. Not a lie,
            // not a fake success — Unsupported (G2). The contract permits this;
            // the conformance suite's acquire tests skip when unsupported.
            return Err.Unsupported;
        }
        // #7 / D9 (FEEDBACK LINUX-01) — IOMMU safety for DMA-capable claims. A
        // shared/exclusive grant hands the client a device that can DMA; if it
        // is not behind an IOMMU, it could DMA anywhere in host memory ("silent
        // total compromise"). The broker must never SILENTLY grant that — it
        // refuses, declaring the degraded posture by saying no (G2). `observe`
        // is read-only (no DMA) and is exempt.
        if (mode != .observe and linux_iommu.postureFor(urn) == .no_iommu) {
            std.debug.print(
                "pci-acquire REFUSED {s}: device is not behind an IOMMU group — a " ++
                    "DMA-capable grant would be structurally unsafe (D9 / LINUX-01 #7)\n",
                .{urn.slice()},
            );
            return Err.NotPermitted;
        }
        // `observe` is a read-only handle — no DMA, so no VFIO bind. It stays
        // gated as before (the read-only-handle path is a separate concern);
        // returning Unsupported preserves the existing honest behavior.
        if (mode == .observe) return Err.Unsupported;

        // DMA-capable claim on an IOMMU-protected device: detach the host driver
        // (slice 2), then run the VFIO container/group/device dance (slice 1).
        // The whole thing is ATOMIC (Part 3.4): if the bind fails after the
        // detach, the errdefer puts the device back on its host driver; if
        // tracking the bind fails, the device is unbound AND reattached. On THIS
        // sandbox the privileged sysfs/`/dev/vfio` writes fail (no root) and the
        // caller gets an honest `Err` — never a faked success; with root + a
        // dedicated passthrough device it binds for real.
        const bdf = linux_iommu.bdfFromPciUrn(urn) orelse return Err.NotFound;
        const group = linux_iommu.iommuGroupFor(bdf) orelse return Err.Unsupported;
        var bdf_buf: [32]u8 = undefined;
        const bdf_z = std.fmt.bufPrintZ(&bdf_buf, "{s}", .{bdf}) catch return Err.BadRequest;

        const detach = try vfio.detachHostDriver(self.driver_ops, bdf_z);
        errdefer vfio.reattachHostDriver(self.driver_ops, bdf_z, detach);

        const dev = try vfio.bind(self.vfio_ops, bdf_z, group);
        errdefer vfio.unbind(self.vfio_ops, dev);

        const id = self.next_token;
        self.next_token += 1;
        var entry = BoundEntry{ .id = id, .dev = dev, .detach = detach };
        const bl = @min(bdf_z.len, entry.bdf_buf.len - 1);
        @memcpy(entry.bdf_buf[0..bl], bdf_z[0..bl]);
        entry.bdf_buf[bl] = 0;
        entry.bdf_len = bl;
        self.bound.append(self.alloc, entry) catch return Err.Overloaded; // errdefers unbind + reattach
        return .{ .id = id };
    }

    fn vRelease(ctx: *anyopaque, token: kabi.AcquireToken, deadline: Deadline) void {
        const self: *LinuxBackend = @ptrCast(@alignCast(ctx));
        _ = deadline;
        // total + idempotent: find the bound device for this token, drop it from
        // the table, tear the VFIO stack down (device→group→container fd), and
        // reattach the host driver. An unknown token (observe handle, or already
        // released) is a clean no-op.
        for (self.bound.items, 0..) |e, i| {
            if (e.id == token.id) {
                self.teardownAt(i);
                return;
            }
        }
    }

    /// Tier-0 #4 / survivability §3.4 — revoke below the fd. The broker forcibly
    /// reclaims `urn`: unbind the VFIO device (close the device/group/container
    /// fds → the kernel tears down the IOMMU mappings, so the client's dup'd
    /// device fd goes inert) and reattach the host driver. Idempotent — no bind
    /// for this URN means the post-condition ("the broker no longer holds it")
    /// already holds, so OK. This is the same teardown as `vRelease`, but
    /// keyed on the URN (a forced reclaim) rather than the owner's token.
    fn vRevoke(ctx: *anyopaque, urn: *const Urn, deadline: Deadline) Err!void {
        _ = deadline;
        const self: *LinuxBackend = @ptrCast(@alignCast(ctx));
        const want = linux_iommu.bdfFromPciUrn(urn) orelse return; // not PCI → nothing here
        for (self.bound.items, 0..) |e, i| {
            if (std.mem.eql(u8, e.bdf_buf[0..e.bdf_len], want)) {
                self.teardownAt(i);
                return;
            }
        }
    }

    /// Return the VFIO device fd for `token` (for SCM_RIGHTS passing to the
    /// client — the "delegate usage" half of the bind, G4). `-1` when the token
    /// is unknown / already released.
    fn vFdForToken(ctx: *anyopaque, token: kabi.AcquireToken) i32 {
        const self: *LinuxBackend = @ptrCast(@alignCast(ctx));
        for (self.bound.items) |e| {
            if (e.id == token.id) return e.dev.device_fd;
        }
        return -1;
    }

    /// Tear down the bound device at `idx` (unbind the VFIO fds + reattach the
    /// host driver) and drop it from the table. Shared by `vRelease`/`vRevoke`.
    fn teardownAt(self: *LinuxBackend, idx: usize) void {
        const e = self.bound.items[idx];
        var nb: [32]u8 = undefined;
        const bdf_z = std.fmt.bufPrintZ(&nb, "{s}", .{e.bdf_buf[0..e.bdf_len]}) catch null;
        _ = self.bound.swapRemove(idx);
        vfio.unbind(self.vfio_ops, e.dev);
        if (bdf_z) |b| vfio.reattachHostDriver(self.driver_ops, b, e.detach);
    }
    fn vReset(ctx: *anyopaque, urn: *const Urn, level: kabi.ResetLevel, deadline: Deadline) Err!void {
        const self: *LinuxBackend = @ptrCast(@alignCast(ctx));
        _ = urn;
        _ = level;
        _ = deadline;
        if (!self.vfio_available) return Err.Unsupported; // honest
        return; // real path: VFIO_DEVICE_RESET ioctl / FLR
    }
    fn vMapRegion(ctx: *anyopaque, token: kabi.AcquireToken, region: u32, deadline: Deadline) Err!kabi.Mapping {
        _ = ctx;
        _ = deadline;
        return .{ .token = token, .region = region };
    }
    fn vRawChannel(ctx: *anyopaque, token: kabi.AcquireToken, deadline: Deadline) Err!kabi.RawHandle {
        _ = ctx;
        _ = deadline;
        return .{ .token = token };
    }

    fn vInspect(ctx: *anyopaque, urn: *const Urn, attrs: *const kabi.AttrSink, deadline: Deadline) Err!Resource {
        _ = ctx;
        _ = deadline;
        // URN looks like `hw:/pci/<domain>:<bus>:<dev>.<fn>/fn<n>`.
        // Extract the BDF segment and read sysfs attrs from
        // /sys/bus/pci/devices/<bdf>/.
        const prefix = "hw:/pci/";
        const s = urn.slice();
        if (!std.mem.startsWith(u8, s, prefix)) return Err.NotFound;
        const after = s[prefix.len..];
        const slash = std.mem.indexOfScalar(u8, after, '/') orelse return Err.NotFound;
        const bdf = after[0..slash];

        var path_buf: [256]u8 = undefined;
        var attr_buf: [64]u8 = undefined;

        // Verify the device exists by reading `vendor` (always present).
        // Each attr has its own stack buffer below; pushing the local
        // slice directly is safe because `AttrSink.push` consumes the
        // value synchronously, and no buffer here is reused after its
        // push (so the lifetime contract — "value lives for the inspect
        // call" — is satisfied without copying).
        const vendor_path = std.fmt.bufPrintZ(&path_buf, "/sys/bus/pci/devices/{s}/vendor", .{bdf}) catch return Err.NotFound;
        const vendor = sysReadAttr(&attr_buf, vendor_path) orelse return Err.NotFound;
        attrs.push(attrs.ctx, .{ .key = "vendor", .value = vendor });

        var product_buf: [16]u8 = undefined;
        const product_path = std.fmt.bufPrintZ(&path_buf, "/sys/bus/pci/devices/{s}/device", .{bdf}) catch return .{ .urn = urn.*, .family = .claimable, .kind = .pcie_function };
        if (sysReadAttr(&product_buf, product_path)) |v| {
            attrs.push(attrs.ctx, .{ .key = "device", .value = v });
        }

        var class_buf: [16]u8 = undefined;
        const class_path = std.fmt.bufPrintZ(&path_buf, "/sys/bus/pci/devices/{s}/class", .{bdf}) catch return .{ .urn = urn.*, .family = .claimable, .kind = .pcie_function };
        if (sysReadAttr(&class_buf, class_path)) |v| {
            attrs.push(attrs.ctx, .{ .key = "class", .value = v });
        }

        var bdf_buf: [32]u8 = undefined;
        @memcpy(bdf_buf[0..bdf.len], bdf);
        attrs.push(attrs.ctx, .{ .key = "bdf", .value = bdf_buf[0..bdf.len] });

        // Driver-ownership attrs (Part 13 §5.2.1): which kernel driver
        // currently owns this PCI function, and which loaded module
        // does the driver come from? Both attrs are honestly absent
        // when the function is unbound (no driver symlink exists) —
        // a common case for PCI functions not currently in use.
        var drv_buf: [256]u8 = undefined;
        const drv_path = std.fmt.bufPrintZ(&path_buf, "/sys/bus/pci/devices/{s}/driver", .{bdf}) catch return .{ .urn = urn.*, .family = .claimable, .kind = .pcie_function, .fidelity = .live };
        if (sysReadSymlinkBasename(&drv_buf, drv_path)) |name| {
            attrs.push(attrs.ctx, .{ .key = "driver", .value = name });
        }
        var mod_buf: [256]u8 = undefined;
        const mod_path = std.fmt.bufPrintZ(&path_buf, "/sys/bus/pci/devices/{s}/driver/module", .{bdf}) catch return .{ .urn = urn.*, .family = .claimable, .kind = .pcie_function, .fidelity = .live };
        if (sysReadSymlinkBasename(&mod_buf, mod_path)) |name| {
            attrs.push(attrs.ctx, .{ .key = "module", .value = name });
        }

        return .{ .urn = urn.*, .family = .claimable, .kind = .pcie_function, .fidelity = .live };
    }

    /// Part 13 §5.5.1 — bulk introspection via tree walk. Resolves the
    /// PCI URN to its `/sys/bus/pci/devices/<bdf>/` dir and delegates
    /// to the shared `sysfs_walk` module. The walker handles file/
    /// symlink/subdir dispatch + bounded recursion + binary detection.
    /// Per-URN errors (URN doesn't resolve to a real device) surface
    /// as `Err.NotFound`; the walker itself never raises — sub-entry
    /// failures degrade silently and the global walk continues (per
    /// design's "partial results are honest" stance).
    fn vInspectFull(
        ctx: *anyopaque,
        urn: *const Urn,
        depth: model.InspectDepth,
        sink: *const kabi.TreeSink,
        deadline: kabi.Deadline,
    ) Err!model.Resource {
        _ = ctx;
        _ = deadline; // sync sysfs walk; bounded by the walker's caps

        const prefix = "hw:/pci/";
        const s = urn.slice();
        if (!std.mem.startsWith(u8, s, prefix)) return Err.NotFound;
        const after = s[prefix.len..];
        const slash = std.mem.indexOfScalar(u8, after, '/') orelse return Err.NotFound;
        const bdf = after[0..slash];

        var path_buf: [256]u8 = undefined;
        const dir_path = std.fmt.bufPrintZ(
            &path_buf,
            "/sys/bus/pci/devices/{s}",
            .{bdf},
        ) catch return Err.NotFound;

        // Verify the device exists by reading `vendor` (always present
        // on a real PCI device). Honest NotFound for an absent URN.
        var vendor_buf: [16]u8 = undefined;
        var vendor_path_buf: [256]u8 = undefined;
        const vendor_path = std.fmt.bufPrintZ(
            &vendor_path_buf,
            "{s}/vendor",
            .{dir_path},
        ) catch return Err.NotFound;
        if (sysReadAttr(&vendor_buf, vendor_path) == null) return Err.NotFound;

        const mode: sysfs_walk.WalkMode = switch (depth) {
            .shallow => .shallow,
            .all => .full,
        };
        var walker = sysfs_walk.Walker.init(sink);
        sysfs_walk.walkSubtree(dir_path, 0, mode, &walker);

        return .{
            .urn = urn.*,
            .family = .claimable,
            .kind = .pcie_function,
            .fidelity = .live,
        };
    }

    /// Part 13 §5.5.2 — parent-chain walk for a PCI function. Walks
    /// the chain leaf-first: the queried device → upstream PCI
    /// bridges (if any) → the root complex (`pci<domain>:<bus>`).
    ///
    /// Mechanism: readlink the sysfs symlink at
    /// `/sys/bus/pci/devices/<bdf>` — its target is the device's
    /// canonical path under `/sys/devices/`, e.g.
    /// `../../../devices/pci0000:00/0000:00:1c.5/0000:01:00.0`.
    /// Each path component matching the BDF regex is a PCI bridge;
    /// the `pci<domain>:<bus>` component is the root complex.
    ///
    /// The bridges appear ROOT→LEAF in the symlink target — the
    /// walker iterates in reverse so the chain is leaf-first per
    /// contract. Sub-entry failures (a bridge that's vanished
    /// between readlink and summary-read) degrade silently — the
    /// chain still emits with whatever rungs the walker could
    /// resolve (G2: a partial truth beats a silent dropout).
    fn vDumpChain(
        ctx: *anyopaque,
        urn: *const Urn,
        sink: *const kabi.ChainSink,
        deadline: Deadline,
    ) Err!void {
        _ = ctx;
        _ = deadline; // bounded by sysfs readlink + small per-rung reads

        // Parse the BDF segment out of `hw:/pci/<bdf>/fn<n>`.
        const prefix = "hw:/pci/";
        const s = urn.slice();
        if (!std.mem.startsWith(u8, s, prefix)) return Err.NotFound;
        const after = s[prefix.len..];
        const slash = std.mem.indexOfScalar(u8, after, '/') orelse return Err.NotFound;
        const leaf_bdf = after[0..slash];

        // Verify the device exists. `vendor` is present on every real PCI
        // function — its absence is the canonical "not on this host" tell.
        var path_buf: [256]u8 = undefined;
        var attr_buf: [64]u8 = undefined;
        const vendor_path = std.fmt.bufPrintZ(
            &path_buf,
            "/sys/bus/pci/devices/{s}/vendor",
            .{leaf_bdf},
        ) catch return Err.NotFound;
        if (sysReadAttr(&attr_buf, vendor_path) == null) return Err.NotFound;

        // 1) leaf device.
        var leaf_sum_buf: [96]u8 = undefined;
        const leaf_sum = pciSummary(leaf_bdf, &leaf_sum_buf);
        sink.push(sink.ctx, .{
            .urn = s,
            .level = "device",
            .summary = leaf_sum,
        });

        // Read the `/sys/bus/pci/devices/<bdf>` symlink target —
        // gives us the canonical `/sys/devices/pci.../<bdf>` path
        // we walk to recover bridges + root complex.
        var sym_path_buf: [256]u8 = undefined;
        const sym_path = std.fmt.bufPrintZ(
            &sym_path_buf,
            "/sys/bus/pci/devices/{s}",
            .{leaf_bdf},
        ) catch return;
        var link_buf: [512]u8 = undefined;
        const link_rc = linux.readlink(sym_path, &link_buf, link_buf.len);
        if (linux.errno(link_rc) != .SUCCESS) return;
        const link_len: usize = link_rc;
        const link_target = link_buf[0..link_len];

        // Collect bridges (BDFs in the path, excluding the leaf) +
        // remember the root-complex component (`pci<domain>:<bus>`).
        var bridge_bdfs: [16][]const u8 = undefined;
        var bridge_count: usize = 0;
        var root_complex: ?[]const u8 = null;
        var it = std.mem.splitScalar(u8, link_target, '/');
        while (it.next()) |comp| {
            if (isPciBdf(comp)) {
                if (std.mem.eql(u8, comp, leaf_bdf)) continue;
                if (bridge_count < bridge_bdfs.len) {
                    bridge_bdfs[bridge_count] = comp;
                    bridge_count += 1;
                }
            } else if (isPciRootComplex(comp)) {
                root_complex = comp;
            }
        }

        // 2) bridges, leaf-first (reverse of root→leaf path order).
        var i: usize = bridge_count;
        while (i > 0) {
            i -= 1;
            const bdf = bridge_bdfs[i];
            var bu_buf: [64]u8 = undefined;
            const bu = std.fmt.bufPrint(
                &bu_buf,
                "hw:/pci/{s}/fn0",
                .{bdf},
            ) catch continue;
            var bs_buf: [96]u8 = undefined;
            const bs = pciSummary(bdf, &bs_buf);
            sink.push(sink.ctx, .{
                .urn = bu,
                .level = "bridge",
                .summary = bs,
            });
        }

        // 3) root complex (synthetic URN — the root complex itself
        // is not a claimable PCI function, but a topology label).
        if (root_complex) |rc_name| {
            var ru_buf: [64]u8 = undefined;
            const ru = std.fmt.bufPrint(
                &ru_buf,
                "hw:/pci/root/{s}",
                .{rc_name},
            ) catch return;
            var rs_buf: [64]u8 = undefined;
            const rs = std.fmt.bufPrint(
                &rs_buf,
                "PCI root complex {s}",
                .{rc_name},
            ) catch return;
            sink.push(sink.ctx, .{
                .urn = ru,
                .level = "root_complex",
                .summary = rs,
            });
        }
    }

    fn vCapabilities(ctx: *anyopaque) kabi.BackendCapabilities {
        const self: *LinuxBackend = @ptrCast(@alignCast(ctx));
        // PCI without VFIO can do everything EXCEPT acquire (which would
        // bind a device for passthrough). Reset is also gated on VFIO.
        // Mode tells the operator at a glance which path they're on.
        return .{
            .name = "mgf-hrb-linux",
            .version = "0.1.0",
            .op_enumerate = true,
            .op_query_topology = true,
            .op_watch = false, // netlink-uevent watch lives in LinuxUsbBackend; PCI hot-add is rare and unwired
            .op_acquire = self.vfio_available,
            .op_release = true, // contractually total; no-op when nothing's acquired
            .op_reset = self.vfio_available,
            .op_map_region = self.vfio_available,
            .op_raw_channel = self.vfio_available,
            .op_enforce_cap = false,
            .mode = if (self.vfio_available) .full else .read_only,
            .mode_reason = if (self.vfio_available) "" else "no /dev/vfio — acquire returns Unsupported",
        };
    }

    const vtable = kabi.KernelBackend.VTable{
        .enumerate = vEnumerate,
        .query_topology = vQueryTopology,
        .watch = vWatch,
        .acquire = vAcquire,
        .release = vRelease,
        .reset = vReset,
        .map_region = vMapRegion,
        .raw_channel = vRawChannel,
        .enforce_cap = null, // Linux can't enforce our caps (Part 6.2) — userspace does
        .capabilities = vCapabilities,
        .fd_for_token = vFdForToken, // hand the client the bound VFIO device fd (G4)
        .inspect = vInspect,
        .inspect_full = vInspectFull, // Part 13 §5.5.1
        .dump_chain = vDumpChain, // Part 13 §5.5.2
        // Tier-0 #4 / survivability §3.4 — revoke below the fd: unbind the VFIO
        // device (the IOMMU mappings drop with the container fd) + reattach the
        // host driver. Idempotent when nothing is bound at the URN.
        .revoke = vRevoke,
        .acquire_detach = null, // PCI detach (kernel driver unbind) is the future U-vfio scope
        .drain_events = null, // PCI hot-add netlink wiring is the future U-pci-watch scope
        .control = null, // PCI control verbs (rescan/remove on a slot) is the future U-pci-control scope
    };

    pub fn backend(self: *LinuxBackend) kabi.KernelBackend {
        return .{ .ctx = self, .vtable = &vtable };
    }
};
