//! vfio — VFIO device bind orchestration for the PCI claim path (Tier-1 #7 /
//! FEEDBACK LINUX-01 / survivability D9). Slice 1: the container/group/device-fd
//! acquisition dance + its atomic unwind.
//!
//! Once `linux_iommu` has resolved a device's IOMMU group and declared it safely
//! bindable (the posture gate), the broker must actually hand the client a usable
//! device handle WITHOUT ever leaving the kernel in a half-bound state. This
//! module is that handoff: open the VFIO container, open the device's IOMMU
//! group, prove the group is VIABLE, attach the group to the container, set the
//! IOMMU type, and pull the per-device fd — and if ANY step fails, unwind every
//! descriptor opened so far before returning the error (Part 3.4: a claim either
//! fully succeeds or leaves no trace). The per-device fd is the resource the
//! client ultimately drives; `unbind` tears the whole stack down in reverse.
//!
//! ## Seam + the hardware gate (what's verified here, what needs a real device)
//!
//! Every kernel touch goes through the injected `VfioOps` seam, so the
//! orchestration — the bind SEQUENCE, the VIABLE gate, the errno→`Err` mapping,
//! and above all the atomic UNWIND (no fd leak on a partial bind) — is pinned
//! deterministically with a fake that can fail at any chosen stage. That logic is
//! the survivability-relevant part and it is fully tested.
//!
//! Slice 2 adds the host-driver detach (`detachHostDriver` / `reattachHostDriver`):
//! unbind the device from its host driver, route its next probe to vfio-pci via
//! `driver_override`, re-probe — and atomically put it back on any failure.
//! Together they are the full passthrough bind: detach the host driver, then run
//! the VFIO dance; the same atomic discipline (unwind / reattach) spans both.
//!
//! Slice 4 SHRINKS the hardware gate (see the test section): where the real ops
//! CAN run without a passthrough device, they now do — pinned against this host's
//! live kernel. The `realOps` container path (open `/dev/vfio/vfio` →
//! `VFIO_GET_API_VERSION` → `VFIO_CHECK_EXTENSION`) executes against the real
//! kernel; the UAPI constants + `GroupStatus` layout are cross-checked against the
//! installed `linux/vfio.h`; and the detach sysfs paths are verified to exist on
//! the live `/sys`. Each is a host-integration test that asserts the real path
//! where the facility is present and the HONEST-absence contract where it isn't.
//!
//! What REMAINS hardware-unverified (it CANNOT be checked without root + a
//! dedicated passthrough device): only `VFIO_GROUP_GET_DEVICE_FD` and the
//! destructive sysfs WRITES (unbind / `driver_override` / `drivers_probe`) — the
//! irreducible "take a real device away from its host driver and hand out its fd"
//! core. Those still only execute on a real host; here the privileged open/write
//! fails and the caller maps it to an honest `Err` — never a faked success.

const std = @import("std");
const model = @import("mgf_hrb_model");
const linux = std.os.linux;

const Err = model.Err;

// ===========================================================================
// VFIO UAPI constants — from linux/vfio.h. HARDWARE-UNVERIFIED on this host
// (no root / passthrough device to exercise the ioctls); written to the spec.
//
// Every VFIO ioctl is `_IO(VFIO_TYPE, VFIO_BASE + n)` with VFIO_TYPE = ';'
// (0x3B) and VFIO_BASE = 100, and direction/size 0 — so the request number is
// simply `(0x3B << 8) | (100 + n)`. They are constant across architectures
// because `_IO` encodes no size.
// ===========================================================================

const VFIO_TYPE: u32 = ';'; // 0x3B
const VFIO_BASE: u32 = 100;
fn vfioIo(n: u32) u32 {
    return (VFIO_TYPE << 8) | (VFIO_BASE + n);
}

pub const VFIO_GET_API_VERSION: u32 = vfioIo(0); // 0x3B64
pub const VFIO_CHECK_EXTENSION: u32 = vfioIo(1); // 0x3B65
pub const VFIO_SET_IOMMU: u32 = vfioIo(2); // 0x3B66
pub const VFIO_GROUP_GET_STATUS: u32 = vfioIo(3); // 0x3B67
pub const VFIO_GROUP_SET_CONTAINER: u32 = vfioIo(4); // 0x3B68
pub const VFIO_GROUP_UNSET_CONTAINER: u32 = vfioIo(5); // 0x3B69
pub const VFIO_GROUP_GET_DEVICE_FD: u32 = vfioIo(6); // 0x3B6A

pub const VFIO_API_VERSION: i64 = 0;
pub const VFIO_TYPE1_IOMMU: usize = 1;
pub const VFIO_GROUP_FLAGS_VIABLE: u32 = 1; // bit 0 of vfio_group_status.flags

/// `struct vfio_group_status { __u32 argsz; __u32 flags; }`.
pub const GroupStatus = extern struct {
    argsz: u32 = @sizeOf(GroupStatus),
    flags: u32 = 0,
};

/// Map a `-errno` (the seam's failure convention) to the broker's typed `Err`,
/// so a privilege/availability failure on this sandbox reads honestly rather than
/// as a fake success. `errno` is the positive errno (callers pass `-rc`).
pub fn errToErr(errno: i64) Err {
    return switch (errno) {
        @intFromEnum(linux.E.PERM), @intFromEnum(linux.E.ACCES) => Err.NotPermitted,
        @intFromEnum(linux.E.NODEV), @intFromEnum(linux.E.NOENT) => Err.NotFound,
        @intFromEnum(linux.E.BUSY) => Err.Conflict,
        @intFromEnum(linux.E.NOTTY), @intFromEnum(linux.E.OPNOTSUPP) => Err.Unsupported,
        else => Err.HardwareFault,
    };
}

// ===========================================================================
// The injectable seam. Each op returns the raw-syscall convention: a
// non-negative result on success, or a negative `-errno` on failure. The fake
// in the tests records calls + can be scripted to fail at any stage; `realOps`
// wraps the actual Linux syscalls.
// ===========================================================================

pub const VfioOps = struct {
    ctx: *anyopaque,
    /// Open a node like "/dev/vfio/vfio" or "/dev/vfio/<group>". Returns an fd
    /// (>= 0) or `-errno`.
    openPath: *const fn (ctx: *anyopaque, path: [:0]const u8) i64,
    /// `ioctl(fd, request, arg)`; `arg` is a pointer-as-int or an inline value
    /// (e.g. the IOMMU type). Returns >= 0 on success, `-errno` on failure.
    ioctl: *const fn (ctx: *anyopaque, fd: i32, request: u32, arg: usize) i64,
    /// Close a descriptor opened via `openPath` / returned by an ioctl.
    closeFd: *const fn (ctx: *anyopaque, fd: i32) void,
};

/// The descriptors a successful bind owns. `unbind` closes them in reverse.
pub const BoundDevice = struct {
    container_fd: i32,
    group_fd: i32,
    device_fd: i32,
    group: u32,
};

/// Bind the device named `bdf` (its IOMMU `group` already resolved + posture-
/// checked by `linux_iommu`) and return its descriptors. ATOMIC: on any failure
/// every descriptor opened so far is closed before returning the error, so a
/// partial bind never leaks a container/group/device fd (Part 3.4).
///
/// Precondition (slice 2): the device must already be detached from its host
/// driver and bound to `vfio-pci`, else `GROUP_GET_DEVICE_FD` fails `ENODEV` —
/// which this surfaces honestly via `errToErr`.
pub fn bind(ops: VfioOps, bdf: [:0]const u8, group: u32) Err!BoundDevice {
    var group_path_buf: [64]u8 = undefined;
    const group_path = std.fmt.bufPrintZ(&group_path_buf, "/dev/vfio/{d}", .{group}) catch return Err.BadRequest;

    // 1. Open the container.
    const cont_rc = ops.openPath(ops.ctx, "/dev/vfio/vfio");
    if (cont_rc < 0) return errToErr(-cont_rc);
    const container_fd: i32 = @intCast(cont_rc);
    errdefer ops.closeFd(ops.ctx, container_fd);

    // 2. API-version + TYPE1 extension sanity (refuse on a kernel we don't speak).
    if (ops.ioctl(ops.ctx, container_fd, VFIO_GET_API_VERSION, 0) != VFIO_API_VERSION) {
        return Err.Unsupported;
    }
    if (ops.ioctl(ops.ctx, container_fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU) == 0) {
        return Err.Unsupported; // kernel lacks the TYPE1 IOMMU we bind against
    }

    // 3. Open the device's IOMMU group.
    const grp_rc = ops.openPath(ops.ctx, group_path);
    if (grp_rc < 0) return errToErr(-grp_rc);
    const group_fd: i32 = @intCast(grp_rc);
    errdefer ops.closeFd(ops.ctx, group_fd);

    // 4. The group must be VIABLE — every device in it bound to vfio-pci (or
    //    unbound). A non-viable group means another driver still holds a sibling
    //    function; binding would be unsafe, so refuse (Conflict).
    var status = GroupStatus{};
    const st_rc = ops.ioctl(ops.ctx, group_fd, VFIO_GROUP_GET_STATUS, @intFromPtr(&status));
    if (st_rc < 0) return errToErr(-st_rc);
    if (status.flags & VFIO_GROUP_FLAGS_VIABLE == 0) return Err.Conflict;

    // 5. Attach the group to the container.
    var cfd_arg: i32 = container_fd;
    const set_rc = ops.ioctl(ops.ctx, group_fd, VFIO_GROUP_SET_CONTAINER, @intFromPtr(&cfd_arg));
    if (set_rc < 0) return errToErr(-set_rc);

    // 6. Set the IOMMU model on the container (now that a group is attached).
    const iom_rc = ops.ioctl(ops.ctx, container_fd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);
    if (iom_rc < 0) return errToErr(-iom_rc);

    // 7. Pull the per-device fd — the resource the client drives.
    const dev_rc = ops.ioctl(ops.ctx, group_fd, VFIO_GROUP_GET_DEVICE_FD, @intFromPtr(bdf.ptr));
    if (dev_rc < 0) return errToErr(-dev_rc);
    const device_fd: i32 = @intCast(dev_rc);

    return .{
        .container_fd = container_fd,
        .group_fd = group_fd,
        .device_fd = device_fd,
        .group = group,
    };
}

/// Tear a bound device down — close the device, group, then container fd (the
/// reverse of `bind`). Total + idempotent: closing the group fd releases it from
/// the container, and closing the container releases the IOMMU mappings. Safe to
/// call once per successful `bind`.
pub fn unbind(ops: VfioOps, b: BoundDevice) void {
    ops.closeFd(ops.ctx, b.device_fd);
    ops.closeFd(ops.ctx, b.group_fd);
    ops.closeFd(ops.ctx, b.container_fd);
}

// ===========================================================================
// Host-driver detach (R5 slice 2). Before `GROUP_GET_DEVICE_FD` can hand out a
// device, the device must be off its host driver (e.g. nvme, e1000e) and on
// vfio-pci. The kernel exposes this through sysfs: unbind the device from its
// current driver, route its next probe to vfio-pci via `driver_override`, then
// `drivers_probe` it. This is the destructive step — it takes the device away
// from the host — so it must be ATOMIC: if any sysfs write fails partway, the
// device is put straight back on its original driver, never left orphaned on no
// driver or half-bound. `reattachHostDriver` is the matching teardown for the
// release path (and the unwind for a later bind failure).
//
// As with the VFIO ioctls, the sysfs PATHS are written to the kernel's stable
// ABI but the raw writes are HARDWARE-UNVERIFIED here (they need root + a real
// device); the orchestration + the atomic reattach are pinned via the seam.
// ===========================================================================

pub const DriverOps = struct {
    ctx: *anyopaque,
    /// The device's current driver name (basename of the `/sys/.../driver`
    /// symlink) into `buf`, or null if the device is unbound. Pure read.
    currentDriver: *const fn (ctx: *anyopaque, bdf: [:0]const u8, buf: []u8) ?[]const u8,
    /// Write `bytes` to a sysfs attribute (unbind / driver_override /
    /// drivers_probe). Returns true on success.
    writeAttr: *const fn (ctx: *anyopaque, path: [:0]const u8, bytes: []const u8) bool,
};

/// What `detachHostDriver` did, so the release path can reverse it and so the
/// original driver is on record. Copyable by value (no heap) for storage
/// alongside the bound device.
pub const DetachState = struct {
    had_driver: bool = false,
    drv_buf: [48]u8 = undefined,
    drv_len: usize = 0,
    pub fn driver(self: *const DetachState) []const u8 {
        return self.drv_buf[0..self.drv_len];
    }
};

fn unbindPath(buf: []u8, bdf: [:0]const u8) Err![:0]const u8 {
    return std.fmt.bufPrintZ(buf, "/sys/bus/pci/devices/{s}/driver/unbind", .{bdf}) catch Err.BadRequest;
}
fn overridePath(buf: []u8, bdf: [:0]const u8) Err![:0]const u8 {
    return std.fmt.bufPrintZ(buf, "/sys/bus/pci/devices/{s}/driver_override", .{bdf}) catch Err.BadRequest;
}
const DRIVERS_PROBE: [:0]const u8 = "/sys/bus/pci/drivers_probe";

/// Take the device `bdf` off its host driver and bind it to vfio-pci. ATOMIC: if
/// the override/probe steps fail after the unbind, the original driver is put
/// back (`errdefer reattachHostDriver`), so a failed detach never leaves a device
/// orphaned. Returns the state needed to reverse it on release.
pub fn detachHostDriver(ops: DriverOps, bdf: [:0]const u8) Err!DetachState {
    var st = DetachState{};
    // Capture the current driver name straight into the result's buffer (so its
    // bytes outlive this frame for the release-path log) — null means unbound.
    if (ops.currentDriver(ops.ctx, bdf, &st.drv_buf)) |drv| {
        st.had_driver = true;
        st.drv_len = drv.len;
        var ub: [128]u8 = undefined;
        if (!ops.writeAttr(ops.ctx, try unbindPath(&ub, bdf), bdf)) return Err.HardwareFault;
    }
    // From here the device is OFF its host driver — any failure must put it back.
    errdefer reattachHostDriver(ops, bdf, st);

    var ov: [128]u8 = undefined;
    if (!ops.writeAttr(ops.ctx, try overridePath(&ov, bdf), "vfio-pci\n")) return Err.HardwareFault;
    if (!ops.writeAttr(ops.ctx, DRIVERS_PROBE, bdf)) return Err.HardwareFault;
    return st;
}

/// Reverse a detach: unbind from vfio-pci, clear the override, and re-probe so
/// the kernel re-matches the device's natural host driver. Total + best-effort
/// (each step is independent; a missing device is fine) — the teardown for the
/// release path and the unwind for a later bind failure.
pub fn reattachHostDriver(ops: DriverOps, bdf: [:0]const u8, st: DetachState) void {
    _ = st; // the driver name is on record for logging; clearing override + a
    // re-probe is what actually re-binds the natural driver.
    var ub: [128]u8 = undefined;
    if (unbindPath(&ub, bdf)) |p| {
        _ = ops.writeAttr(ops.ctx, p, bdf);
    } else |_| {}
    var ov: [128]u8 = undefined;
    if (overridePath(&ov, bdf)) |p| {
        _ = ops.writeAttr(ops.ctx, p, "\n"); // empty override → natural matching
    } else |_| {}
    _ = ops.writeAttr(ops.ctx, DRIVERS_PROBE, bdf);
}

// ===========================================================================
// realOps — the raw-syscall implementation. HARDWARE-UNVERIFIED on this host:
// the open/ioctl/close calls are correct to the UAPI but only EXECUTE against a
// real passthrough device with root; here they fail at the first privileged
// open, which the caller maps to an honest Err.
// ===========================================================================

fn realOpen(_: *anyopaque, path: [:0]const u8) i64 {
    const rc = linux.open(path, .{ .ACCMODE = .RDWR }, 0);
    const e = linux.errno(rc);
    if (e != .SUCCESS) return -@as(i64, @intFromEnum(e));
    return @intCast(rc);
}
fn realIoctl(_: *anyopaque, fd: i32, request: u32, arg: usize) i64 {
    const rc = linux.ioctl(fd, request, arg);
    const e = linux.errno(rc);
    if (e != .SUCCESS) return -@as(i64, @intFromEnum(e));
    return @intCast(@as(isize, @bitCast(rc)));
}
fn realClose(_: *anyopaque, fd: i32) void {
    _ = linux.close(fd);
}

var real_ctx: u8 = 0;

/// The production seam (raw Linux syscalls).
pub fn realOps() VfioOps {
    return .{
        .ctx = &real_ctx,
        .openPath = realOpen,
        .ioctl = realIoctl,
        .closeFd = realClose,
    };
}

fn realCurrentDriver(_: *anyopaque, bdf: [:0]const u8, buf: []u8) ?[]const u8 {
    var pbuf: [128]u8 = undefined;
    const path = std.fmt.bufPrintZ(&pbuf, "/sys/bus/pci/devices/{s}/driver", .{bdf}) catch return null;
    var link: [256]u8 = undefined;
    const rc = linux.readlink(path, &link, link.len);
    if (linux.errno(rc) != .SUCCESS) return null; // no driver symlink → unbound
    const target = link[0..rc];
    // basename of the symlink target (".../drivers/nvme" → "nvme")
    const start = if (std.mem.lastIndexOfScalar(u8, target, '/')) |i| i + 1 else 0;
    const name = target[start..];
    const n = @min(name.len, buf.len);
    @memcpy(buf[0..n], name[0..n]);
    return buf[0..n];
}
fn realWriteAttr(_: *anyopaque, path: [:0]const u8, bytes: []const u8) bool {
    const rc = linux.open(path, .{ .ACCMODE = .WRONLY }, 0);
    if (linux.errno(rc) != .SUCCESS) return false;
    const fd: i32 = @intCast(rc);
    defer _ = linux.close(fd);
    const w = linux.write(fd, bytes.ptr, bytes.len);
    return linux.errno(w) == .SUCCESS;
}

/// The production driver-detach seam (raw sysfs syscalls).
pub fn realDriverOps() DriverOps {
    return .{
        .ctx = &real_ctx,
        .currentDriver = realCurrentDriver,
        .writeAttr = realWriteAttr,
    };
}

// ===========================================================================
// Tests — the orchestration + atomic unwind, via an injected fake. (slice 1)
// ===========================================================================

const testing = std.testing;

/// A scriptable fake: it hands out fds in order, records every close, and can be
/// told to fail a chosen step. For `GROUP_GET_STATUS` it writes a configurable
/// VIABLE flag into the caller's struct so the viability gate is exercised.
const Fake = struct {
    next_fd: i32 = 10,
    opens: usize = 0,
    closes: usize = 0,
    // a multiset of the fds that were closed (small, fixed cap is plenty)
    closed: [16]i32 = [_]i32{-1} ** 16,
    closed_n: usize = 0,
    // scripting
    fail_open_on: usize = 0, // Nth openPath call (1-based) returns -errno; 0 = never
    fail_ioctl_req: u32 = 0, // this ioctl request returns -errno; 0 = never
    fail_errno: i64 = @intFromEnum(linux.E.PERM),
    api_version: i64 = VFIO_API_VERSION,
    has_type1: i64 = 1,
    viable: bool = true,

    fn open(ctx: *anyopaque, path: [:0]const u8) i64 {
        _ = path;
        const self: *Fake = @ptrCast(@alignCast(ctx));
        self.opens += 1;
        if (self.fail_open_on == self.opens) return -self.fail_errno;
        const fd = self.next_fd;
        self.next_fd += 1;
        return fd;
    }
    fn ioctl(ctx: *anyopaque, fd: i32, request: u32, arg: usize) i64 {
        _ = fd;
        const self: *Fake = @ptrCast(@alignCast(ctx));
        if (self.fail_ioctl_req == request and request != 0) return -self.fail_errno;
        switch (request) {
            VFIO_GET_API_VERSION => return self.api_version,
            VFIO_CHECK_EXTENSION => return self.has_type1,
            VFIO_GROUP_GET_STATUS => {
                const st: *GroupStatus = @ptrFromInt(arg);
                st.flags = if (self.viable) VFIO_GROUP_FLAGS_VIABLE else 0;
                return 0;
            },
            VFIO_GROUP_GET_DEVICE_FD => {
                const fd_out = self.next_fd;
                self.next_fd += 1;
                return fd_out;
            },
            else => return 0, // SET_CONTAINER, SET_IOMMU
        }
    }
    fn close(ctx: *anyopaque, fd: i32) void {
        const self: *Fake = @ptrCast(@alignCast(ctx));
        self.closes += 1;
        if (self.closed_n < self.closed.len) {
            self.closed[self.closed_n] = fd;
            self.closed_n += 1;
        }
    }
    fn ops(self: *Fake) VfioOps {
        return .{ .ctx = self, .openPath = Fake.open, .ioctl = Fake.ioctl, .closeFd = Fake.close };
    }
    fn wasClosed(self: *Fake, fd: i32) bool {
        for (self.closed[0..self.closed_n]) |c| if (c == fd) return true;
        return false;
    }
};

test "vfio bind: the happy path returns the device fd and leaks nothing" {
    var fake = Fake{};
    const b = try bind(fake.ops(), "0000:01:00.0", 7);
    // container=10, group=11, device=12 in fd-handout order.
    try testing.expectEqual(@as(i32, 10), b.container_fd);
    try testing.expectEqual(@as(i32, 11), b.group_fd);
    try testing.expectEqual(@as(i32, 12), b.device_fd);
    try testing.expectEqual(@as(u32, 7), b.group);
    // A successful bind closes NOTHING (the caller owns all three fds).
    try testing.expectEqual(@as(usize, 0), fake.closes);

    // unbind closes exactly the three, and that's it.
    unbind(fake.ops(), b);
    try testing.expectEqual(@as(usize, 3), fake.closes);
    try testing.expect(fake.wasClosed(10) and fake.wasClosed(11) and fake.wasClosed(12));
}

test "vfio bind: a non-viable group is refused (Conflict) and unwinds cleanly" {
    var fake = Fake{ .viable = false };
    try testing.expectError(Err.Conflict, bind(fake.ops(), "0000:01:00.0", 7));
    // container (10) + group (11) were opened before the viability check; BOTH
    // must be closed by the unwind — no leak.
    try testing.expectEqual(@as(usize, 2), fake.closes);
    try testing.expect(fake.wasClosed(10) and fake.wasClosed(11));
}

test "vfio bind: a failure opening the group unwinds the container" {
    // 2nd openPath call is the group → fail it.
    var fake = Fake{ .fail_open_on = 2, .fail_errno = @intFromEnum(linux.E.BUSY) };
    try testing.expectError(Err.Conflict, bind(fake.ops(), "0000:01:00.0", 7)); // EBUSY → Conflict
    // Only the container (10) was open; it must be closed. The group never opened.
    try testing.expectEqual(@as(usize, 1), fake.closes);
    try testing.expect(fake.wasClosed(10));
}

test "vfio bind: a failure at GET_DEVICE_FD unwinds BOTH container and group" {
    var fake = Fake{ .fail_ioctl_req = VFIO_GROUP_GET_DEVICE_FD, .fail_errno = @intFromEnum(linux.E.NODEV) };
    try testing.expectError(Err.NotFound, bind(fake.ops(), "0000:01:00.0", 7)); // ENODEV → NotFound
    // Container (10) + group (11) were both open at the final step; both closed.
    try testing.expectEqual(@as(usize, 2), fake.closes);
    try testing.expect(fake.wasClosed(10) and fake.wasClosed(11));
}

test "vfio bind: an unknown VFIO API version is refused before any group opens" {
    var fake = Fake{ .api_version = 999 };
    try testing.expectError(Err.Unsupported, bind(fake.ops(), "0000:01:00.0", 7));
    // Only the container opened; it unwinds. The group was never reached.
    try testing.expectEqual(@as(usize, 1), fake.closes);
    try testing.expect(fake.wasClosed(10));
}

test "vfio bind: a kernel without the TYPE1 IOMMU extension is refused" {
    var fake = Fake{ .has_type1 = 0 };
    try testing.expectError(Err.Unsupported, bind(fake.ops(), "0000:01:00.0", 7));
    try testing.expectEqual(@as(usize, 1), fake.closes); // container unwound
}

test "vfio: errno mapping is honest (privilege/availability never reads as success)" {
    try testing.expectEqual(Err.NotPermitted, errToErr(@intFromEnum(linux.E.PERM)));
    try testing.expectEqual(Err.NotPermitted, errToErr(@intFromEnum(linux.E.ACCES)));
    try testing.expectEqual(Err.NotFound, errToErr(@intFromEnum(linux.E.NODEV)));
    try testing.expectEqual(Err.Conflict, errToErr(@intFromEnum(linux.E.BUSY)));
    try testing.expectEqual(Err.Unsupported, errToErr(@intFromEnum(linux.E.NOTTY)));
    try testing.expectEqual(Err.HardwareFault, errToErr(@intFromEnum(linux.E.IO)));
}

test "vfio: the ioctl request numbers match the _IO(';', 100+n) UAPI encoding" {
    try testing.expectEqual(@as(u32, 0x3B64), VFIO_GET_API_VERSION);
    try testing.expectEqual(@as(u32, 0x3B67), VFIO_GROUP_GET_STATUS);
    try testing.expectEqual(@as(u32, 0x3B6A), VFIO_GROUP_GET_DEVICE_FD);
}

// --- R5 slice 2: host-driver detach, via an injected fake -------------------

/// A scriptable DriverOps fake. It classifies each sysfs write by the attribute
/// it targets and appends a single char to `log` — 'u' (unbind), 'o'
/// (driver_override), 'p' (drivers_probe) — so a test reads the exact sequence
/// off as a string. It can be told the device is unbound and can fail the Nth
/// write to drive the atomic-reattach path.
const FakeDriver = struct {
    has_driver: bool = true,
    fail_on: usize = 0, // Nth writeAttr call (1-based) returns false; 0 = never
    n: usize = 0,
    log: [32]u8 = [_]u8{0} ** 32,
    log_n: usize = 0,

    fn classify(path: []const u8) u8 {
        if (std.mem.indexOf(u8, path, "unbind") != null) return 'u';
        if (std.mem.indexOf(u8, path, "driver_override") != null) return 'o';
        if (std.mem.indexOf(u8, path, "drivers_probe") != null) return 'p';
        return '?';
    }
    fn currentDriver(ctx: *anyopaque, bdf: [:0]const u8, buf: []u8) ?[]const u8 {
        _ = bdf;
        const self: *FakeDriver = @ptrCast(@alignCast(ctx));
        if (!self.has_driver) return null;
        const name: []const u8 = "nvme";
        std.mem.copyForwards(u8, buf[0..name.len], name);
        return buf[0..name.len];
    }
    fn writeAttr(ctx: *anyopaque, path: [:0]const u8, bytes: []const u8) bool {
        _ = bytes;
        const self: *FakeDriver = @ptrCast(@alignCast(ctx));
        self.n += 1;
        if (self.log_n < self.log.len) {
            self.log[self.log_n] = classify(path);
            self.log_n += 1;
        }
        return self.fail_on != self.n;
    }
    fn ops(self: *FakeDriver) DriverOps {
        return .{ .ctx = self, .currentDriver = FakeDriver.currentDriver, .writeAttr = FakeDriver.writeAttr };
    }
    fn seq(self: *FakeDriver) []const u8 {
        return self.log[0..self.log_n];
    }
};

test "vfio detach: a bound device is unbound, overridden to vfio-pci, then probed" {
    var fd = FakeDriver{};
    const st = try detachHostDriver(fd.ops(), "0000:01:00.0");
    try testing.expect(st.had_driver);
    try testing.expectEqualStrings("nvme", st.driver());
    try testing.expectEqualStrings("uop", fd.seq()); // unbind, override, probe
}

test "vfio detach: an already-unbound device skips the unbind" {
    var fd = FakeDriver{ .has_driver = false };
    const st = try detachHostDriver(fd.ops(), "0000:01:00.0");
    try testing.expect(!st.had_driver);
    try testing.expectEqualStrings("op", fd.seq()); // no leading unbind
}

test "vfio detach: a failure mid-detach atomically puts the device back on its driver" {
    // Fail the override write (the 2nd writeAttr: after the host-driver unbind).
    var fd = FakeDriver{ .fail_on = 2 };
    try testing.expectError(Err.HardwareFault, detachHostDriver(fd.ops(), "0000:01:00.0"));
    // 'u' (unbind) + 'o' (failed override) THEN the reattach 'uop' — the device
    // is restored, never left orphaned off its host driver. The trailing "uop"
    // is the atomic rollback; without the errdefer the sequence would be "uo".
    try testing.expectEqualStrings("uouop", fd.seq());
}

test "vfio reattach: reverses a detach (unbind vfio-pci, clear override, re-probe)" {
    var fd = FakeDriver{};
    reattachHostDriver(fd.ops(), "0000:01:00.0", .{});
    try testing.expectEqualStrings("uop", fd.seq());
}

// ===========================================================================
// R5 slice 4 — shrink the hardware gate. The slices above pin the orchestration
// + atomic unwind via fakes; what stayed "written-to-spec but never run" was the
// raw `realOps`/`realDriverOps` against the real kernel. These host-integration
// tests close as much of that as is possible WITHOUT a passthrough device:
//
//   A. the real container ioctls (`realOps` → GET_API_VERSION + CHECK_EXTENSION)
//      actually execute against this host's live `/dev/vfio/vfio`;
//   B. every UAPI constant + the `GroupStatus` layout is cross-checked against the
//      installed `linux/vfio.h` (a drift detector tied to ground truth on disk);
//   C. the detach sysfs paths we WRITE to are verified to exist on the live `/sys`
//      (a path-typo catcher), on top of an exact-string construction pin.
//
// In the repo's house style they NEVER hard-skip: where the facility is present
// (this host) they exercise the real path; where it's absent (a bare CI box) they
// assert the honest-absence contract. After slice 4 the only VFIO surface left
// hardware-unverified is GROUP_GET_DEVICE_FD + the destructive sysfs writes — the
// irreducible "bind a real passthrough device" core.
// ===========================================================================

/// Slurp a whole file via the module's raw-syscall idiom (cf. linux_irq's
/// `readProcInterrupts`). Returns null if the file can't be opened — the caller
/// treats that as "this facility isn't on this host" and asserts honest-absence.
/// Caller owns + frees the returned slice.
fn readWholeFile(alloc: std.mem.Allocator, path: [:0]const u8) ?[]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);
    var list: std.ArrayListUnmanaged(u8) = .empty;
    var chunk: [8192]u8 = undefined;
    while (list.items.len < (1 << 20)) {
        const n = linux.read(fd, &chunk, chunk.len);
        if (linux.errno(n) != .SUCCESS) {
            list.deinit(alloc);
            return null;
        }
        if (n == 0) break;
        list.appendSlice(alloc, chunk[0..n]) catch {
            list.deinit(alloc);
            return null;
        };
    }
    return list.toOwnedSlice(alloc) catch null;
}

/// Compact `s` in place, dropping ALL ASCII whitespace, so a `#define` match is
/// insensitive to the header's tab alignment AND line breaks. Returns the prefix.
fn stripWs(s: []u8) []u8 {
    var w: usize = 0;
    for (s) |c| {
        if (c == ' ' or c == '\t' or c == '\n' or c == '\r') continue;
        s[w] = c;
        w += 1;
    }
    return s[0..w];
}

test "vfio slice4 (host integration): realOps drives the REAL container ioctls" {
    const ops = realOps();
    const rc = ops.openPath(ops.ctx, "/dev/vfio/vfio");
    if (rc < 0) {
        // No VFIO container on this host (a bare CI box, no vfio in-kernel). The
        // honest contract: the open fails and maps to a typed Err — never a faked
        // success. This is the same honesty `bind` relies on at step 1.
        const e = errToErr(-rc);
        try testing.expect(e == Err.NotFound or e == Err.NotPermitted or e == Err.Unsupported);
        std.debug.print("  [vfio] no /dev/vfio/vfio here — honest {s}\n", .{@errorName(e)});
        return;
    }
    const container_fd: i32 = @intCast(rc);
    defer ops.closeFd(ops.ctx, container_fd);
    // The REAL kernel answers these two ioctls — the first half of bind()'s real
    // path actually executing, no longer fake-only. A wrong request number would
    // come back -ENOTTY (negative), failing the exact-API-version match below.
    const api = ops.ioctl(ops.ctx, container_fd, VFIO_GET_API_VERSION, 0);
    try testing.expectEqual(VFIO_API_VERSION, api); // kernel's API version == what we bind to
    const type1 = ops.ioctl(ops.ctx, container_fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU);
    try testing.expect(type1 >= 0); // request recognised (0 = unsupported, 1 = supported)
    std.debug.print("  [vfio] REAL container ioctls: API={d} TYPE1={d}\n", .{ api, type1 });
}

test "vfio slice4 (host integration): UAPI constants + struct match linux/vfio.h" {
    const blob = readWholeFile(testing.allocator, "/usr/include/linux/vfio.h") orelse {
        std.debug.print("  [vfio] no linux/vfio.h installed — ABI cross-check skipped honestly\n", .{});
        return;
    };
    defer testing.allocator.free(blob);
    const norm = stripWs(blob); // whitespace-insensitive #define matching
    // Each constant + the struct, exactly as the kernel header declares them.
    const needles = [_][]const u8{
        "#defineVFIO_TYPE(';')",
        "#defineVFIO_BASE100",
        "#defineVFIO_API_VERSION0",
        "#defineVFIO_TYPE1_IOMMU1",
        "#defineVFIO_GROUP_FLAGS_VIABLE(1<<0)",
        "#defineVFIO_GET_API_VERSION_IO(VFIO_TYPE,VFIO_BASE+0)",
        "#defineVFIO_CHECK_EXTENSION_IO(VFIO_TYPE,VFIO_BASE+1)",
        "#defineVFIO_SET_IOMMU_IO(VFIO_TYPE,VFIO_BASE+2)",
        "#defineVFIO_GROUP_GET_STATUS_IO(VFIO_TYPE,VFIO_BASE+3)",
        "#defineVFIO_GROUP_SET_CONTAINER_IO(VFIO_TYPE,VFIO_BASE+4)",
        "#defineVFIO_GROUP_UNSET_CONTAINER_IO(VFIO_TYPE,VFIO_BASE+5)",
        "#defineVFIO_GROUP_GET_DEVICE_FD_IO(VFIO_TYPE,VFIO_BASE+6)",
        "structvfio_group_status{__u32argsz;__u32flags;",
    };
    for (needles) |needle| {
        if (std.mem.indexOf(u8, norm, needle) == null) {
            std.debug.print("  [vfio] linux/vfio.h is missing or has drifted from: {s}\n", .{needle});
            try testing.expect(false);
        }
    }
    // Bridge: our COMPUTED request numbers are exactly `_IO(';', 100+n)` (dir/size
    // 0), the same expression the header above confirms the kernel uses — so our
    // numbers equal the kernel's numbers, not just "look plausible".
    const IoEnc = struct {
        fn at(n: u32) u32 {
            return (@as(u32, ';') << 8) | (100 + n);
        }
    };
    try testing.expectEqual(IoEnc.at(0), VFIO_GET_API_VERSION);
    try testing.expectEqual(IoEnc.at(1), VFIO_CHECK_EXTENSION);
    try testing.expectEqual(IoEnc.at(2), VFIO_SET_IOMMU);
    try testing.expectEqual(IoEnc.at(3), VFIO_GROUP_GET_STATUS);
    try testing.expectEqual(IoEnc.at(4), VFIO_GROUP_SET_CONTAINER);
    try testing.expectEqual(IoEnc.at(5), VFIO_GROUP_UNSET_CONTAINER);
    try testing.expectEqual(IoEnc.at(6), VFIO_GROUP_GET_DEVICE_FD);
    // And our GroupStatus mirrors `struct vfio_group_status` byte-for-byte.
    try testing.expectEqual(@as(usize, 8), @sizeOf(GroupStatus));
    try testing.expectEqual(@as(usize, 0), @offsetOf(GroupStatus, "argsz"));
    try testing.expectEqual(@as(usize, 4), @offsetOf(GroupStatus, "flags"));
    std.debug.print("  [vfio] UAPI constants + GroupStatus cross-checked vs linux/vfio.h\n", .{});
}

test "vfio slice4: detach sysfs paths are constructed exactly (pure, host-free)" {
    var buf: [128]u8 = undefined;
    try testing.expectEqualStrings(
        "/sys/bus/pci/devices/0000:01:00.0/driver/unbind",
        try unbindPath(&buf, "0000:01:00.0"),
    );
    try testing.expectEqualStrings(
        "/sys/bus/pci/devices/0000:01:00.0/driver_override",
        try overridePath(&buf, "0000:01:00.0"),
    );
    try testing.expectEqualStrings("/sys/bus/pci/drivers_probe", DRIVERS_PROBE);
}

test "vfio slice4 (host integration): the detach sysfs paths EXIST on this host" {
    // The global probe attribute is on every PCI-capable host; its absence means
    // no PCI sysfs here → assert nothing, skip honestly.
    if (linux.errno(linux.access(DRIVERS_PROBE, linux.F_OK)) != .SUCCESS) {
        std.debug.print("  [vfio] no {s} — sysfs path check skipped honestly\n", .{DRIVERS_PROBE});
        return;
    }
    const rc = linux.open("/sys/bus/pci/devices", .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, 0);
    if (linux.errno(rc) != .SUCCESS) return;
    const dfd: i32 = @intCast(rc);
    defer _ = linux.close(dfd);

    var buf: [4096]u8 align(@alignOf(linux.dirent64)) = undefined;
    var saw_device = false;
    var checked_override = false;
    var checked_unbind = false;
    outer: while (true) {
        const got = linux.getdents64(dfd, &buf, buf.len);
        if (linux.errno(got) != .SUCCESS or got == 0) break;
        var pos: usize = 0;
        while (pos < got) {
            const dent: *linux.dirent64 = @ptrCast(@alignCast(&buf[pos]));
            pos += dent.reclen;
            const name = std.mem.span(@as([*:0]const u8, @ptrCast(&dent.name)));
            if (name.len == 0 or name[0] == '.') continue; // skip "." / ".."
            saw_device = true;
            // Build the exact path detachHostDriver writes, and prove it exists —
            // catching a path typo BEFORE we ever write to it on real hardware.
            var pbuf: [128]u8 = undefined;
            const ovr = overridePath(&pbuf, name) catch continue;
            if (linux.errno(linux.access(ovr, linux.F_OK)) == .SUCCESS) checked_override = true;
            // The unbind attribute exists only when the device is driver-bound.
            var ubuf: [128]u8 = undefined;
            const ub = unbindPath(&ubuf, name) catch continue;
            if (linux.errno(linux.access(ub, linux.F_OK)) == .SUCCESS) checked_unbind = true;
            if (checked_override and checked_unbind) break :outer;
        }
    }
    if (!saw_device) {
        std.debug.print("  [vfio] no PCI devices enumerated — sysfs path check skipped honestly\n", .{});
        return;
    }
    // driver_override is present on every modern PCI device → must have found one.
    try testing.expect(checked_override);
    std.debug.print(
        "  [vfio] live sysfs detach paths verified (driver_override=yes, driver/unbind={s})\n",
        .{if (checked_unbind) "yes" else "none-bound"},
    );
}
