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) }