mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
### What does this PR do? ### How did you verify your code works? --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
776 lines
30 KiB
Zig
776 lines
30 KiB
Zig
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;
|