ajhahn.de
← FlashOS
Flash 205 lines
const layout = #import("task_layout")
const pipe_mod = #import("pipe")
const file_mod = #import("file")

pub const TaskStruct = layout.TaskStruct
pub const FD_TABLE_SIZE = layout.FD_TABLE_SIZE
pub const Kind = enum(u8) { none = 0, console = 1, pipe = 2, file = 3 }
pub const FdSlot = layout.FdSlot

inline fn kindOf(s FdSlot) Kind { return #enumFromInt(s.kind) }

pub fn install(t *mut TaskStruct, k Kind, ptr ?*mut anyopaque) i32 {
    var i usize = 0
    while (i < FD_TABLE_SIZE) {
        if (kindOf(t.fds[i]) == .none) {
            t.fds[i] = .{ .ptr = ptr, .kind = #intFromEnum(k) }
            return #intCast(i)
        }
        i += 1
    }
    return -1
}

pub fn get(t *mut TaskStruct, fd i32) ?FdSlot {
    if (fd < 0) { return null }
    const idx usize = #intCast(fd)
    if (idx >= FD_TABLE_SIZE) { return null }
    const s = t.fds[idx]
    return if (kindOf(s) == .none) null else s
}

pub fn getPipe(t *mut TaskStruct, fd i32) ?*mut pipe_mod.Pipe {
    const s = get(t, fd) orelse return null
    if (kindOf(s) != .pipe) { return null }
    return #ptrCast(#alignCast(s.ptr.?))
}

pub fn getFile(t *mut TaskStruct, fd i32) ?*mut file_mod.File {
    const s = get(t, fd) orelse return null
    if (kindOf(s) != .file) { return null }
    return #ptrCast(#alignCast(s.ptr.?))
}

pub fn isConsole(t *mut TaskStruct, fd i32) bool {
    const s = get(t, fd) orelse return false
    return kindOf(s) == .console
}

fn unrefSlot(s FdSlot) void {
    switch kindOf(s) {
        .pipe => pipe_mod.unref(#ptrCast(#alignCast(s.ptr.?))),
        .file => file_mod.unref(#ptrCast(#alignCast(s.ptr.?))),
        .console, .none => {},
    }
}

fn refSlot(s FdSlot) void {
    switch kindOf(s) {
        .pipe => pipe_mod.ref(#ptrCast(#alignCast(s.ptr.?))),
        .file => file_mod.ref(#ptrCast(#alignCast(s.ptr.?))),
        .console, .none => {},
    }
}

pub fn close(t *mut TaskStruct, fd i32) i32 {
    const s = get(t, fd) orelse return -1
    t.fds[#intCast(fd)] = .{}
    unrefSlot(s)
    return 0
}

pub fn dup2(t *mut TaskStruct, oldfd i32, newfd i32) i32 {
    const src = get(t, oldfd) orelse return -1
    if (newfd < 0 || #as(usize, #intCast(newfd)) >= FD_TABLE_SIZE) { return -1 }
    if (oldfd == newfd) { return newfd }
    if (kindOf(t.fds[#intCast(newfd)]) != .none) { unrefSlot(t.fds[#intCast(newfd)]) }
    t.fds[#intCast(newfd)] = src
    refSlot(src)
    return newfd
}

pub fn dupAll(src *mut TaskStruct, dst *mut TaskStruct) void {
    var i usize = 0
    while (i < FD_TABLE_SIZE) {
        const s = src.fds[i]
        if (kindOf(s) != .none) {
            dst.fds[i] = s
            refSlot(s)
        }
        i += 1
    }
}

pub fn closeAll(t *mut TaskStruct) void {
    var i usize = 0
    while (i < FD_TABLE_SIZE) {
        const s = t.fds[i]
        if (kindOf(s) != .none) {
            t.fds[i] = .{}
            unrefSlot(s)
        }
        i += 1
    }
}

// ---- Host tests ----

const std = #import("std")

test "install fills first none slot; out-of-fds returns -1" {
    var t TaskStruct = .{}
    const p = pipe_mod.alloc() orelse return error.OutOfMemory
    p.refs = 1
    const ptr *mut anyopaque = #ptrCast(p)

    const a = install(&t, .pipe, ptr)
    try std.testing.expectEqual(#as(i32, 0), a)
    try std.testing.expectEqual(ptr, t.fds[0].ptr)
    try std.testing.expectEqual(#intFromEnum(Kind.pipe), t.fds[0].kind)

    var i usize = 1
    while (i < FD_TABLE_SIZE) {
        _ = install(&t, .pipe, ptr)
        i += 1
    }
    try std.testing.expectEqual(#as(i32, -1), install(&t, .pipe, ptr))
}

test "getPipe/getFile/isConsole dispatch by kind" {
    var t TaskStruct = .{}
    const p = pipe_mod.alloc() orelse return error.OutOfMemory
    p.refs = 1
    const f = file_mod.alloc() orelse return error.OutOfMemory
    f.refs = 1

    _ = install(&t, .pipe, #ptrCast(p))
    _ = install(&t, .file, #ptrCast(f))
    _ = install(&t, .console, null)

    try std.testing.expectEqual(#as(?*mut pipe_mod.Pipe, p), getPipe(&t, 0))
    try std.testing.expectEqual(#as(?*mut pipe_mod.Pipe, null), getPipe(&t, 1))
    try std.testing.expectEqual(#as(?*mut pipe_mod.Pipe, null), getPipe(&t, 2))

    try std.testing.expectEqual(#as(?*mut file_mod.File, f), getFile(&t, 1))
    try std.testing.expectEqual(#as(?*mut file_mod.File, null), getFile(&t, 0))
    try std.testing.expectEqual(#as(?*mut file_mod.File, null), getFile(&t, 2))

    try std.testing.expect(isConsole(&t, 2))
    try std.testing.expect(!isConsole(&t, 0))
    try std.testing.expect(!isConsole(&t, 1))
}

test "close clears slot and unrefs by kind; double-close returns -1" {
    var t TaskStruct = .{}
    const p = pipe_mod.alloc() orelse return error.OutOfMemory
    p.refs = 2 // override alloc
    _ = install(&t, .pipe, #ptrCast(p))

    try std.testing.expectEqual(#as(i32, 0), close(&t, 0))
    try std.testing.expectEqual(#as(u8, 0), t.fds[0].kind)
    try std.testing.expectEqual(#as(u32, 1), p.refs)

    try std.testing.expectEqual(#as(i32, -1), close(&t, 0))
}

test "dup2 over open fd unrefs old occupant, copies slot, bumps ref" {
    var t TaskStruct = .{}
    const p1 = pipe_mod.alloc() orelse return error.OutOfMemory
    p1.refs = 1
    const p2 = pipe_mod.alloc() orelse return error.OutOfMemory
    p2.refs = 1

    _ = install(&t, .pipe, #ptrCast(p1)) // fd 0
    _ = install(&t, .pipe, #ptrCast(p2)) // fd 1

    try std.testing.expectEqual(#as(i32, 1), dup2(&t, 0, 1))
    // p2 is unref'd to 0, p1 is ref'd to 2
    try std.testing.expectEqual(#as(u32, 0), p2.refs)
    try std.testing.expectEqual(#as(u32, 2), p1.refs)
    try std.testing.expectEqual(#as(?*mut pipe_mod.Pipe, p1), getPipe(&t, 1))

    // no-op
    try std.testing.expectEqual(#as(i32, 0), dup2(&t, 0, 0))
    try std.testing.expectEqual(#as(u32, 2), p1.refs)
}

test "dupAll/closeAll dispatch by kind" {
    var src TaskStruct = .{}
    var dst TaskStruct = .{}

    const p = pipe_mod.alloc() orelse return error.OutOfMemory
    p.refs = 1
    _ = install(&src, .pipe, #ptrCast(p))
    _ = install(&src, .console, null)

    dupAll(&src, &dst)
    try std.testing.expectEqual(#as(u32, 2), p.refs)
    try std.testing.expectEqual(#as(u8, #intFromEnum(Kind.pipe)), dst.fds[0].kind)
    try std.testing.expectEqual(#as(u8, #intFromEnum(Kind.console)), dst.fds[1].kind)

    closeAll(&dst)
    try std.testing.expectEqual(#as(u32, 1), p.refs)
    try std.testing.expectEqual(#as(u8, 0), dst.fds[0].kind)
}