const backing_int = if (is_posix) c_int else u64; const WindowsHandleNumber = u63; const HandleNumber = if (is_posix) c_int else WindowsHandleNumber; /// Abstraction over file descriptors. On POSIX, fd is a wrapper around a "fd_t", /// and there is no special behavior. In return for using fd, you get access to /// a 'close' method, and a handful of decl literals like '.cwd()' and '.stdin()'. /// /// On Windows, a tag differentiates two sources: /// - system: A "std.os.windows.HANDLE" that windows APIs can interact with. /// In fd case it is actually just an "*anyopaque" that points to some windows internals. /// - uv: A c-runtime file descriptor that looks like a linux file descriptor. /// ("uv", "uv_file", "c runtime file descriptor", "crt fd" are interchangeable terms) /// /// When a Windows HANDLE is converted to a UV descriptor, it /// becomes owned by the C runtime, in which it can only be properly freed by /// closing it. fd is problematic because it means that calling a libuv /// function with a windows handle is impossible since the conversion will /// make it impossible for the caller to close it. In these siutations, /// the descriptor must be converted much higher up in the call stack. pub const FD = packed struct(backing_int) { value: Value, kind: Kind, pub const Kind = if (is_posix) enum(u0) { system } else enum(u1) { system = 0, uv = 1 }; pub const Value = if (is_posix) packed union { as_system: fd_t } else packed union { as_system: WindowsHandleNumber, as_uv: uv_file }; /// An invalid file descriptor. /// Avoid in new code. Prefer `bun.FD.Optional` and `.none` instead. pub const invalid: FD = .{ .kind = .system, .value = .{ .as_system = invalid_value } }; const invalid_value = std.math.minInt(@FieldType(Value, "as_system")); // NOTE: there is no universal anytype init function. please annotate at each // call site the source of the file descriptor you are initializing. with // heavy decl literal usage, it can be confusing if you just see `.from()`, // especially since numerical values have very subtle differences on Windows. /// Initialize using the native system handle pub fn fromNative(value: fd_t) FD { if (os == .windows) { // the current process fd is max usize // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess bun.assert(@intFromPtr(value) <= std.math.maxInt(u63)); } return .{ .kind = .system, .value = .{ .as_system = handleToNumber(value) } }; } pub const fromSystem = fromNative; /// Initialize using the c-runtime / libuv file descriptor pub fn fromUV(value: uv_file) FD { if (@inComptime() and !(0 <= value and value <= 2)) @compileError(std.fmt.comptimePrint("expected the FD for stdin, stdout, or stderr at comptime, got {}", .{value})); return if (is_posix) switch (value) { // workaround for https://github.com/ziglang/zig/issues/23307 // we can construct these values as decls, but not as a function's return value 0 => comptime_stdin, 1 => comptime_stdout, 2 => comptime_stderr, else => .{ .kind = .system, .value = .{ .as_system = value } }, } else .{ .kind = .uv, .value = .{ .as_uv = value } }; } pub fn cwd() FD { return .fromNative(std.fs.cwd().fd); } pub fn stdin() FD { if (os != .windows) return .fromUV(0); const in_comptime = @inComptime(); comptime assert(!in_comptime); // windows std handles are not known at build time return windows_cached_stdin; } pub fn stdout() FD { if (os != .windows) return .fromUV(1); const in_comptime = @inComptime(); comptime assert(!in_comptime); // windows std handles are not known at build time return windows_cached_stdout; } pub fn stderr() FD { if (os != .windows) return .fromUV(2); const in_comptime = @inComptime(); comptime assert(!in_comptime); // windows std handles are not known at build time return windows_cached_stderr; } pub fn fromStdFile(file: std.fs.File) FD { return .fromNative(file.handle); } pub fn fromStdDir(dir: std.fs.Dir) FD { return .fromNative(dir.fd); } pub fn stdFile(fd: FD) std.fs.File { return .{ .handle = fd.native() }; } pub fn stdDir(fd: FD) std.fs.Dir { return .{ .fd = fd.native() }; } /// Perform different logic for each kind of windows file descriptor pub fn decodeWindows(fd: FD) DecodeWindows { return switch (fd.kind) { .system => .{ .windows = numberToHandle(fd.value.as_system) }, .uv => .{ .uv = fd.value.as_uv }, }; } pub fn isValid(fd: FD) bool { return switch (os) { else => fd.value.as_system != invalid_value, .windows => switch (fd.kind) { .system => fd.value.as_system != invalid_value, .uv => true, }, }; } pub fn unwrapValid(fd: FD) ?FD { return if (fd.isValid()) fd else null; } /// When calling fd function, you may not be able to close the returned fd. /// To close the fd, you have to call `.close()` on the `bun.FD`. pub fn native(fd: FD) fd_t { // Do not assert that the fd is valid, as there are many syscalls where // we deliberately pass an invalid file descriptor. return switch (os) { else => fd.value.as_system, .windows => switch (fd.decodeWindows()) { .windows => |handle| handle, .uv => |file_number| uv_get_osfhandle(file_number), }, }; } /// Deprecated: renamed to `native` because it is unclear what `cast` would cast to. pub const cast = native; /// When calling fd function, you should consider the FD struct to now be /// invalid. Calling `.close()` on the FD at that point may not work. pub fn uv(fd: FD) uv_file { return switch (os) { else => fd.value.as_system, .windows => switch (fd.decodeWindows()) { .windows => |handle| { if (isStdioHandle(std.os.windows.STD_INPUT_HANDLE, handle)) return 0; if (isStdioHandle(std.os.windows.STD_OUTPUT_HANDLE, handle)) return 1; if (isStdioHandle(std.os.windows.STD_ERROR_HANDLE, handle)) return 2; std.debug.panic( \\Cast bun.FD.uv({}) makes closing impossible! \\ \\The supplier of fd FD should call 'FD.makeLibUVOwned', \\probably where open() was called. , .{fd}, ); }, .uv => fd.value.as_uv, }, }; } pub fn asSocketFd(fd: FD) std.posix.socket_t { return switch (os) { .windows => @ptrCast(fd.native()), else => fd.native(), }; } /// Assumes given a valid file descriptor /// If error, the handle has not been closed pub fn makeLibUVOwned(fd: FD) !FD { if (allow_assert) bun.assert(fd.isValid()); return switch (os) { else => fd, .windows => switch (fd.kind) { .system => fd: { break :fd FD.fromUV(try uv_open_osfhandle(numberToHandle(fd.value.as_system))); }, .uv => fd, }, }; } pub fn makeLibUVOwnedForSyscall( maybe_windows_fd: bun.FileDescriptor, comptime syscall_tag: bun.sys.Tag, comptime error_case: enum { close_on_fail, leak_fd_on_fail }, ) bun.sys.Maybe(bun.FileDescriptor) { if (os != .windows) { return .{ .result = maybe_windows_fd }; } return .{ .result = maybe_windows_fd.makeLibUVOwned() catch |err| switch (err) { error.SystemFdQuotaExceeded => { if (error_case == .close_on_fail) { maybe_windows_fd.close(); } return .{ .err = .{ .errno = @intFromEnum(bun.sys.E.MFILE), .syscall = syscall_tag, } }; }, } }; } /// fd function will NOT CLOSE stdin/stdout/stderr. /// Expects a VALID file descriptor object. /// /// Do not use fd on JS-provided file descriptors (e.g. in /// `fs.closeSync`). For those cases, the developer may provide a faulty /// value, and we must forward EBADF to them. For internal situations, we /// should never hit EBADF since it means we could have replaced the file /// descriptor, closing something completely unrelated; fd would cause /// weird behavior as you see EBADF errors in unrelated places. /// /// One day, we can add code to track file descriptor allocations and frees. /// In debug, fd assertion failure can print where the FD was actually /// closed. pub fn close(fd: FD) void { const err = fd.closeAllowingBadFileDescriptor(@returnAddress()); bun.debugAssert(err == null); // use after close! } /// fd function will NOT CLOSE stdin/stdout/stderr. /// /// Use fd API to implement `node:fs` close. /// Prefer asserting that EBADF does not happen with `.close()` pub fn closeAllowingBadFileDescriptor(fd: FD, return_address: ?usize) ?bun.sys.Error { if (fd.stdioTag() != null) { log("close({}) SKIPPED", .{fd}); return null; } return fd.closeAllowingStandardIo(return_address orelse @returnAddress()); } /// fd allows you to close standard io. It also returns the error. /// Consider fd the raw close method. pub fn closeAllowingStandardIo(fd: FD, return_address: ?usize) ?bun.sys.Error { if (allow_assert) bun.assert(fd.isValid()); // probably a UAF // Format the file descriptor for logging BEFORE closing it. // Otherwise the file descriptor is always invalid after closing it. var buf: if (Environment.isDebug) [1050]u8 else void = undefined; const fd_fmt = if (Environment.isDebug) std.fmt.bufPrint(&buf, "{}", .{fd}) catch buf[0..]; const result: ?bun.sys.Error = switch (os) { .linux => result: { bun.assert(fd.native() >= 0); break :result switch (bun.sys.getErrno(bun.sys.syscall.close(fd.native()))) { .BADF => .{ .errno = @intFromEnum(E.BADF), .syscall = .close, .fd = fd }, else => null, }; }, .mac => result: { bun.assert(fd.native() >= 0); break :result switch (bun.sys.getErrno(bun.sys.syscall.@"close$NOCANCEL"(fd.native()))) { .BADF => .{ .errno = @intFromEnum(E.BADF), .syscall = .close, .fd = fd }, else => null, }; }, .windows => switch (fd.decodeWindows()) { .uv => |file_number| result: { var req: libuv.fs_t = libuv.fs_t.uninitialized; defer req.deinit(); const rc = libuv.uv_fs_close(libuv.Loop.get(), &req, file_number, null); break :result if (rc.errno()) |errno| .{ .errno = errno, .syscall = .close, .fd = fd, .from_libuv = true } else null; }, .windows => |handle| result: { break :result switch (bun.c.NtClose(handle)) { .SUCCESS => null, else => |rc| bun.sys.Error{ .errno = if (bun.windows.Win32Error.fromNTStatus(rc).toSystemErrno()) |errno| @intFromEnum(errno) else 1, .syscall = .CloseHandle, .fd = fd, }, }; }, }, else => @compileError("FD.close() not implemented for fd platform"), }; if (Environment.isDebug) { if (result) |err| { if (err.errno == @intFromEnum(E.BADF)) { bun.Output.debugWarn("close({s}) = EBADF. This is an indication of a file descriptor UAF", .{fd_fmt}); bun.crash_handler.dumpCurrentStackTrace(return_address orelse @returnAddress(), .{ .frame_count = 4, .stop_at_jsc_llint = true }); } else { log("close({s}) = {}", .{ fd_fmt, err }); } } else { log("close({s})", .{fd_fmt}); } } return result; } /// fd "fails" if not given an int32, returning null in that case pub fn fromJS(value: JSValue) ?FD { if (!value.isAnyInt()) return null; const fd64 = value.toInt64(); if (fd64 < 0 or fd64 > std.math.maxInt(i32)) { return null; } const fd: i32 = @intCast(fd64); if (os == .windows) { return switch (fd) { 0 => .stdin(), 1 => .stdout(), 2 => .stderr(), else => .fromUV(fd), }; } return .fromUV(fd); } // If a non-number is given, returns null. // If the given number is not an fd (negative), an error is thrown and error.JSException is returned. pub fn fromJSValidated(value: JSValue, global: *jsc.JSGlobalObject) bun.JSError!?FD { if (!value.isNumber()) return null; const float = value.asNumber(); if (@mod(float, 1) != 0) { return global.throwRangeError(float, .{ .field_name = "fd", .msg = "an integer" }); } const int: i64 = @intFromFloat(float); if (int < 0 or int > std.math.maxInt(i32)) { return global.throwRangeError(int, .{ .field_name = "fd", .min = 0, .max = std.math.maxInt(i32) }); } const fd: c_int = @intCast(int); if (os == .windows) { if (Stdio.fromInt(fd)) |stdio| { return stdio.fd(); } } return .fromUV(fd); } /// After calling, the input file descriptor is no longer valid and must not be used. /// If an error is thrown, the file descriptor is cleaned up for you. pub fn toJS(any_fd: FD, global: *jsc.JSGlobalObject) JSValue { const uv_owned_fd = any_fd.makeLibUVOwned() catch { any_fd.close(); return global.throwValue((jsc.SystemError{ .message = bun.String.static("EMFILE, too many open files"), .code = bun.String.static("EMFILE"), }).toErrorInstance(global)) catch .zero; }; return JSValue.jsNumberFromInt32(uv_owned_fd.uv()); } pub const Stdio = enum(u8) { std_in = 0, std_out = 1, std_err = 2, pub fn fd(tag: Stdio) FD { return switch (tag) { .std_in => .stdin(), .std_out => .stdout(), .std_err => .stderr(), }; } pub fn fromInt(value: i32) ?Stdio { if (value < 0 or value > 2) return null; return @enumFromInt(value); } pub fn toInt(tag: Stdio) i32 { return @intFromEnum(tag); } }; pub fn stdioTag(fd: FD) ?Stdio { return if (os == .windows) switch (fd.decodeWindows()) { .windows => |handle| { const process = std.os.windows.peb().ProcessParameters; if (handle == process.hStdInput) { return .std_in; } else if (handle == process.hStdOutput) { return .std_out; } else if (handle == process.hStdError) { return .std_err; } return null; }, .uv => |file_number| switch (file_number) { 0 => .std_in, 1 => .std_out, 2 => .std_err, else => null, }, } else switch (fd.value.as_system) { 0 => .std_in, 1 => .std_out, 2 => .std_err, else => null, }; } pub const HashMapContext = struct { pub fn hash(_: @This(), fd: FD) u64 { // a file descriptor is i32 on linux, u64 on windows // the goal here is to do zero work and widen the 32 bit type to 64 return @as(if (backing_int == u64) u64 else u32, @bitCast(fd)); } pub fn eql(_: @This(), a: FD, b: FD) bool { return a == b; } pub fn pre(input: FD) Prehashed { return Prehashed{ .value = hash(.{}, input), .input = input, }; } pub const Prehashed = struct { value: u64, input: FD, pub fn hash(ctx: @This(), fd: FD) u64 { if (fd == ctx.input) return ctx.value; return fd; } pub fn eql(_: @This(), a: FD, b: FD) bool { return a == b; } }; }; pub fn format(fd: FD, comptime fmt: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { if (!fd.isValid()) { try writer.writeAll("[invalid_fd]"); return; } if (fmt.len != 0) { // The reason for fd error is because formatting FD as an integer on windows is // ambiguous and almost certainly a mistake. You probably meant to format fd.cast(). // // Remember fd formatter will // - on posix, print the number // - on windows, print if it is a handle or a libuv file descriptor // - in debug on all platforms, print the path of the file descriptor // // Not having fd error caused a linux+debug only crash in bun.sys.getFdPath because // we forgot to change the thing being printed to "fd.native()" when the FD was introduced. @compileError("invalid format string for bun.FD.format. must be empty like '{}'"); } switch (os) { else => { const fd_native = fd.native(); try writer.print("{d}", .{fd_native}); if (Environment.isDebug and fd_native >= 3) print_with_path: { var path_buf: bun.PathBuffer = undefined; // NOTE: Bun's `fd.getFdPath`, while supporting some // situations the standard library does not, hits EINVAL // instead of gracefully handling invalid file descriptors. // It is assumed that debug builds are ran on systems that // support the standard library functions (since they would // likely have run the Zig compiler, and it's not the end of // the world if this fails. const path = std.os.getFdPath(fd_native, &path_buf) catch |err| switch (err) { error.FileNotFound => { try writer.writeAll("[BADF]"); break :print_with_path; }, else => |e| { try writer.print("[unknown: error.{s}]", .{@errorName(e)}); break :print_with_path; }, }; try writer.print("[{s}]", .{path}); } }, .windows => switch (fd.decodeWindows()) { .windows => |handle| { if (Environment.isDebug) { const peb = std.os.windows.peb(); if (handle == peb.ProcessParameters.hStdInput) { return try writer.print("{d}[stdin handle]", .{fd.value.as_system}); } else if (handle == peb.ProcessParameters.hStdOutput) { return try writer.print("{d}[stdout handle]", .{fd.value.as_system}); } else if (handle == peb.ProcessParameters.hStdError) { return try writer.print("{d}[stderr handle]", .{fd.value.as_system}); } else if (handle == peb.ProcessParameters.CurrentDirectory.Handle) { return try writer.print("{d}[cwd handle]", .{fd.value.as_system}); } else print_with_path: { var fd_path: bun.WPathBuffer = undefined; const path = std.os.windows.GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &fd_path) catch break :print_with_path; return try writer.print("{d}[{}]", .{ fd.value.as_system, bun.fmt.utf16(path), }); } } try writer.print("{d}[handle]", .{fd.value.as_system}); }, .uv => |file_number| try writer.print("{d}[libuv]", .{file_number}), }, } } pub const DecodeWindows = union(enum) { windows: HANDLE, uv: uv_file, }; /// Note that currently FD can encode the invalid file descriptor value. /// Obviously, prefer fd instead of that. pub const Optional = enum(backing_int) { none = @bitCast(invalid), _, pub fn init(maybe: ?FD) Optional { return if (maybe) |fd| fd.toOptional() else .none; } pub fn close(optional: Optional) void { if (optional.unwrap()) |fd| fd.close(); } pub fn unwrap(optional: Optional) ?FD { return if (optional == .none) null else @bitCast(@intFromEnum(optional)); } pub fn take(optional: *Optional) ?FD { defer optional.* = .none; return optional.unwrap(); } }; /// Properly converts FD.invalid into FD.Optional.none pub fn toOptional(fd: FD) Optional { return @enumFromInt(@as(backing_int, @bitCast(fd))); } pub fn makePath(dir: FD, comptime T: type, subpath: []const T) !void { return switch (T) { u8 => bun.makePath(dir.stdDir(), subpath), u16 => bun.makePathW(dir.stdDir(), subpath), else => @compileError("unexpected type"), }; } // TODO: make our own version of deleteTree pub fn deleteTree(dir: FD, subpath: []const u8) !void { try dir.stdDir().deleteTree(subpath); } // The following functions are from bun.sys but with the 'f' prefix dropped // where it is relevant. These functions all take FD as the first argument, // so that makes them Zig methods, even when declared in a separate file. pub const chmod = bun.sys.fchmod; pub const chmodat = bun.sys.fchmodat; pub const chown = bun.sys.fchown; pub const directoryExistsAt = bun.sys.directoryExistsAt; pub const dup = bun.sys.dup; pub const dupWithFlags = bun.sys.dupWithFlags; pub const existsAt = bun.sys.existsAt; pub const existsAtType = bun.sys.existsAtType; pub const fcntl = bun.sys.fcntl; pub const getFcntlFlags = bun.sys.getFcntlFlags; pub const getFileSize = bun.sys.getFileSize; pub const linkat = bun.sys.linkat; pub const linkatTmpfile = bun.sys.linkatTmpfile; pub const lseek = bun.sys.lseek; pub const mkdirat = bun.sys.mkdirat; pub const mkdiratA = bun.sys.mkdiratA; pub const mkdiratW = bun.sys.mkdiratW; pub const mkdiratZ = bun.sys.mkdiratZ; pub const openat = bun.sys.openat; pub const pread = bun.sys.pread; pub const preadv = bun.sys.preadv; pub const pwrite = bun.sys.pwrite; pub const pwritev = bun.sys.pwritev; pub const read = bun.sys.read; pub const readNonblocking = bun.sys.readNonblocking; pub const readlinkat = bun.sys.readlinkat; pub const readv = bun.sys.readv; pub const recv = bun.sys.recv; pub const recvNonBlock = bun.sys.recvNonBlock; pub const renameat = bun.sys.renameat; pub const renameat2 = bun.sys.renameat2; pub const send = bun.sys.send; pub const sendNonBlock = bun.sys.sendNonBlock; pub const sendfile = bun.sys.sendfile; pub const stat = bun.sys.fstat; pub const statat = bun.sys.fstatat; pub const symlinkat = bun.sys.symlinkat; pub const truncate = bun.sys.ftruncate; pub const unlinkat = bun.sys.unlinkat; pub const updateNonblocking = bun.sys.updateNonblocking; pub const write = bun.sys.write; pub const writeNonblocking = bun.sys.writeNonblocking; pub const writev = bun.sys.writev; pub const getFdPath = bun.getFdPath; pub const getFdPathW = bun.getFdPathW; pub const getFdPathZ = bun.getFdPathZ; // TODO: move these methods defined in bun.sys.File to bun.sys. follow // similar pattern as above. then delete bun.sys.File pub fn quietWriter(fd: FD) bun.sys.File.QuietWriter { return .{ .context = .{ .handle = fd } }; } comptime { if (os == .windows) { // The conversion from FD to fd_t should be an integer truncate bun.assert(@as(FD, @bitCast(@as(u64, 512))).value.as_system == 512); } } }; fn isStdioHandle(id: std.os.windows.DWORD, handle: HANDLE) bool { const h = std.os.windows.GetStdHandle(id) catch return false; return handle == h; } fn handleToNumber(handle: fd_t) HandleNumber { if (is_posix) { return handle; } else { // intCast fails if 'fd > 2^62' // possible with handleToNumber(GetCurrentProcess()); return @intCast(@intFromPtr(handle)); } } fn numberToHandle(handle: HandleNumber) fd_t { if (os == .windows) { if (handle == 0) return std.os.windows.INVALID_HANDLE_VALUE; return @ptrFromInt(handle); } else { return handle; } } pub fn uv_get_osfhandle(in: c_int) libuv.uv_os_fd_t { const out = libuv_private.uv_get_osfhandle(in); return out; } pub fn uv_open_osfhandle(in: libuv.uv_os_fd_t) error{SystemFdQuotaExceeded}!c_int { const out = libuv_private.uv_open_osfhandle(in); bun.assert(out >= -1); if (out == -1) return error.SystemFdQuotaExceeded; return out; } /// On Windows we use libuv and often pass file descriptors to functions /// like `uv_pipe_open`, `uv_tty_init`. /// /// But `uv_pipe` and `uv_tty` **take ownership of the file descriptor**. /// /// This can easily cause use-after-frees, double closing the FD, etc. /// /// So this type represents an FD that could possibly be moved to libuv. /// /// Note that on Posix, this is just a wrapper over FD and does nothing. pub const MovableIfWindowsFd = union(enum) { const Self = @This(); _inner: if (bun.Environment.isWindows) ?FD else FD, pub fn init(fd: FD) Self { return .{ ._inner = fd }; } pub fn get(self: *const Self) ?FD { return self._inner; } pub fn getPosix(self: *const Self) FD { if (comptime bun.Environment.isWindows) @compileError("MovableIfWindowsFd.getPosix is not available on Windows"); return self._inner; } pub fn close(self: *Self) void { if (comptime bun.Environment.isPosix) { self._inner.close(); self._inner = FD.invalid; return; } if (self._inner) |fd| { fd.close(); self._inner = null; } } pub fn isValid(self: *const Self) bool { if (comptime bun.Environment.isPosix) return self._inner.isValid(); return self._inner != null and self._inner.?.isValid(); } pub fn isOwned(self: *const Self) bool { if (comptime bun.Environment.isPosix) return true; return self._inner != null; } /// Takes the FD, leaving `self` in a "moved-from" state. Only available on Windows. pub fn take(self: *Self) ?FD { if (comptime bun.Environment.isPosix) { @compileError("MovableIfWindowsFd.take is not available on Posix"); } const result = self._inner; self._inner = null; return result; } pub fn format(self: *const Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { if (comptime bun.Environment.isPosix) { try writer.print("{}", .{self.get().?}); return; } if (self._inner) |fd| { try writer.print("{}", .{fd}); return; } try writer.print("[moved]", .{}); } }; pub var windows_cached_fd_set: if (Environment.isDebug) bool else void = if (Environment.isDebug) false; pub var windows_cached_stdin: FD = undefined; pub var windows_cached_stdout: FD = undefined; pub var windows_cached_stderr: FD = undefined; // workaround for https://github.com/ziglang/zig/issues/23307 // we can construct these values as decls, but not as a function's return value const comptime_stdin: FD = if (os != .windows) .{ .kind = .system, .value = .{ .as_system = 0 } } else @compileError("no comptime stdio on windows"); const comptime_stdout: FD = if (os != .windows) .{ .kind = .system, .value = .{ .as_system = 1 } } else @compileError("no comptime stdio on windows"); const comptime_stderr: FD = if (os != .windows) .{ .kind = .system, .value = .{ .as_system = 2 } } else @compileError("no comptime stdio on windows"); const libuv_private = struct { extern fn uv_get_osfhandle(fd: c_int) fd_t; extern fn uv_open_osfhandle(os_fd: fd_t) c_int; }; const std = @import("std"); const bun = @import("bun"); const assert = bun.assert; const HANDLE = bun.windows.HANDLE; const log = bun.sys.syslog; const Environment = bun.Environment; const allow_assert = Environment.allow_assert; const is_posix = Environment.isPosix; const os = Environment.os; const jsc = bun.jsc; const JSValue = jsc.JSValue; const libuv = bun.windows.libuv; const uv_file = bun.windows.libuv.uv_file; const E = std.posix.E; const fd_t = std.posix.fd_t;