mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Compare commits
1 Commits
bun-v1.3.3
...
jarred/rm-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67024a445a |
@@ -423,6 +423,8 @@ extern "C" void Bun__setCTRLHandler(BOOL add)
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" int32_t bun_is_stdio_null[3] = { 0, 0, 0 };
|
||||
|
||||
extern "C" void bun_initialize_process()
|
||||
{
|
||||
// Disable printf() buffering. We buffer it ourselves.
|
||||
@@ -443,6 +445,7 @@ extern "C" void bun_initialize_process()
|
||||
bool anyTTYs = false;
|
||||
|
||||
const auto setDevNullFd = [&](int target_fd) -> void {
|
||||
bun_is_stdio_null[target_fd] = 1;
|
||||
if (devNullFd_ == -1) {
|
||||
do {
|
||||
devNullFd_ = open("/dev/null", O_RDWR | O_CLOEXEC, 0);
|
||||
@@ -510,6 +513,7 @@ extern "C" void bun_initialize_process()
|
||||
// Ignore _close result. If it fails or not depends on used Windows
|
||||
// version. We will just check _open result.
|
||||
_close(fd);
|
||||
bun_is_stdio_null[fd] = 1;
|
||||
if (fd != _open("nul", O_RDWR)) {
|
||||
RELEASE_ASSERT_NOT_REACHED();
|
||||
} else {
|
||||
|
||||
@@ -220,7 +220,9 @@ pub fn Maybe(comptime ReturnTypeT: type, comptime ErrorTypeT: type) type {
|
||||
|
||||
pub inline fn errnoSys(rc: anytype, syscall: Syscall.Tag) ?@This() {
|
||||
if (comptime Environment.isWindows) {
|
||||
if (rc != 0) return null;
|
||||
if (comptime @TypeOf(rc) == std.os.windows.NTSTATUS) {} else {
|
||||
if (rc != 0) return null;
|
||||
}
|
||||
}
|
||||
return switch (Syscall.getErrno(rc)) {
|
||||
.SUCCESS => null,
|
||||
|
||||
@@ -214,6 +214,20 @@ pub const Source = struct {
|
||||
};
|
||||
|
||||
pub const Stdio = struct {
|
||||
extern "C" var bun_is_stdio_null: [3]i32;
|
||||
|
||||
pub fn isStderrNull() bool {
|
||||
return bun_is_stdio_null[2] == 1;
|
||||
}
|
||||
|
||||
pub fn isStdoutNull() bool {
|
||||
return bun_is_stdio_null[1] == 1;
|
||||
}
|
||||
|
||||
pub fn isStdinNull() bool {
|
||||
return bun_is_stdio_null[0] == 1;
|
||||
}
|
||||
|
||||
pub fn init() void {
|
||||
bun.C.bun_initialize_process();
|
||||
|
||||
|
||||
@@ -1143,7 +1143,7 @@ pub const Interpreter = struct {
|
||||
}
|
||||
|
||||
log("Duping stdin", .{});
|
||||
const stdin_fd = switch (ShellSyscall.dup(shell.STDIN_FD)) {
|
||||
const stdin_fd = switch (if (bun.Output.Source.Stdio.isStdinNull()) bun.sys.openNullDevice() else ShellSyscall.dup(shell.STDIN_FD)) {
|
||||
.result => |fd| fd,
|
||||
.err => |err| return .{ .err = .{ .sys = err.toSystemError() } },
|
||||
};
|
||||
@@ -1329,13 +1329,13 @@ pub const Interpreter = struct {
|
||||
const event_loop = this.event_loop;
|
||||
|
||||
log("Duping stdout", .{});
|
||||
const stdout_fd = switch (ShellSyscall.dup(shell.STDOUT_FD)) {
|
||||
const stdout_fd = switch (if (bun.Output.Source.Stdio.isStdoutNull()) bun.sys.openNullDevice() else ShellSyscall.dup(bun.STDOUT_FD)) {
|
||||
.result => |fd| fd,
|
||||
.err => |err| return .{ .err = err },
|
||||
};
|
||||
|
||||
log("Duping stderr", .{});
|
||||
const stderr_fd = switch (ShellSyscall.dup(shell.STDERR_FD)) {
|
||||
const stderr_fd = switch (if (bun.Output.Source.Stdio.isStderrNull()) bun.sys.openNullDevice() else ShellSyscall.dup(bun.STDERR_FD)) {
|
||||
.result => |fd| fd,
|
||||
.err => |err| return .{ .err = err },
|
||||
};
|
||||
@@ -9218,8 +9218,8 @@ pub const Interpreter = struct {
|
||||
JSC.WorkPool.schedule(&subtask.task);
|
||||
}
|
||||
|
||||
pub fn getcwd(this: *ShellRmTask) if (bun.Environment.isWindows) CwdPath else bun.FileDescriptor {
|
||||
return if (bun.Environment.isWindows) this.cwd_path.? else bun.toFD(this.cwd);
|
||||
pub fn getcwd(this: *ShellRmTask) bun.FileDescriptor {
|
||||
return this.cwd;
|
||||
}
|
||||
|
||||
pub fn verboseDeleted(this: *@This(), dir_task: *DirTask, path: [:0]const u8) Maybe(void) {
|
||||
@@ -9554,8 +9554,7 @@ pub const Interpreter = struct {
|
||||
}
|
||||
};
|
||||
const dirfd = bun.toFD(this.cwd);
|
||||
_ = dirfd; // autofix
|
||||
switch (ShellSyscall.unlinkatWithFlags(this.getcwd(), path, 0)) {
|
||||
switch (ShellSyscall.unlinkatWithFlags(dirfd, path, 0)) {
|
||||
.result => return this.verboseDeleted(parent_dir_task, path),
|
||||
.err => |e| {
|
||||
print("unlinkatWithFlags({s}) = {s}", .{ path, @tagName(e.getErrno()) });
|
||||
@@ -11180,6 +11179,8 @@ pub const IOWriterChildPtr = struct {
|
||||
/// - Sometimes windows doesn't have `*at()` functions like `rmdirat` so we have to join the directory path with the target path
|
||||
/// - Converts Posix absolute paths to Windows absolute paths on Windows
|
||||
const ShellSyscall = struct {
|
||||
pub const unlinkatWithFlags = Syscall.unlinkatWithFlags;
|
||||
pub const rmdirat = Syscall.rmdirat;
|
||||
fn getPath(dirfd: anytype, to: [:0]const u8, buf: *[bun.MAX_PATH_BYTES]u8) Maybe([:0]const u8) {
|
||||
if (bun.Environment.isPosix) @compileError("Don't use this");
|
||||
if (bun.strings.eqlComptime(to[0..to.len], "/dev/null")) {
|
||||
@@ -11293,58 +11294,6 @@ const ShellSyscall = struct {
|
||||
}
|
||||
return Syscall.dup(fd);
|
||||
}
|
||||
|
||||
pub fn unlinkatWithFlags(dirfd: anytype, to: [:0]const u8, flags: c_uint) Maybe(void) {
|
||||
if (bun.Environment.isWindows) {
|
||||
if (flags & std.os.AT.REMOVEDIR != 0) return ShellSyscall.rmdirat(dirfd, to);
|
||||
|
||||
var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
|
||||
const path = brk: {
|
||||
switch (ShellSyscall.getPath(dirfd, to, &buf)) {
|
||||
.err => |e| return .{ .err = e },
|
||||
.result => |p| break :brk p,
|
||||
}
|
||||
};
|
||||
|
||||
return switch (Syscall.unlink(path)) {
|
||||
.result => return Maybe(void).success,
|
||||
.err => |e| {
|
||||
log("unlinkatWithFlags({s}) = {s}", .{ path, @tagName(e.getErrno()) });
|
||||
return .{ .err = e.withPath(bun.default_allocator.dupe(u8, path) catch bun.outOfMemory()) };
|
||||
},
|
||||
};
|
||||
}
|
||||
if (@TypeOf(dirfd) != bun.FileDescriptor) {
|
||||
@compileError("Bad type: " ++ @typeName(@TypeOf(dirfd)));
|
||||
}
|
||||
return Syscall.unlinkatWithFlags(dirfd, to, flags);
|
||||
}
|
||||
|
||||
pub fn rmdirat(dirfd: anytype, to: [:0]const u8) Maybe(void) {
|
||||
if (bun.Environment.isWindows) {
|
||||
var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
|
||||
const path: []const u8 = brk: {
|
||||
switch (getPath(dirfd, to, &buf)) {
|
||||
.result => |p| break :brk p,
|
||||
.err => |e| return .{ .err = e },
|
||||
}
|
||||
};
|
||||
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
|
||||
const wpath = bun.strings.toWPath(&wide_buf, path);
|
||||
while (true) {
|
||||
if (windows.RemoveDirectoryW(wpath) == 0) {
|
||||
const errno = Syscall.getErrno(420);
|
||||
if (errno == .INTR) continue;
|
||||
log("rmdirat({s}) = {d}: {s}", .{ path, @intFromEnum(errno), @tagName(errno) });
|
||||
return .{ .err = Syscall.Error.fromCode(errno, .rmdir) };
|
||||
}
|
||||
log("rmdirat({s}) = {d}", .{ path, 0 });
|
||||
return Maybe(void).success;
|
||||
}
|
||||
}
|
||||
|
||||
return Syscall.rmdirat(dirfd, to);
|
||||
}
|
||||
};
|
||||
|
||||
/// A task that can write to stdout and/or stderr
|
||||
|
||||
50
src/sys.zig
50
src/sys.zig
@@ -593,6 +593,10 @@ pub fn fcntl(fd: bun.FileDescriptor, cmd: i32, arg: usize) Maybe(usize) {
|
||||
|
||||
pub fn getErrno(rc: anytype) bun.C.E {
|
||||
if (comptime Environment.isWindows) {
|
||||
if (comptime @TypeOf(rc) == bun.windows.NTSTATUS) {
|
||||
return bun.windows.translateNTStatusToErrno(rc);
|
||||
}
|
||||
|
||||
if (bun.windows.Win32Error.get().toSystemErrno()) |e| {
|
||||
return e.toE();
|
||||
}
|
||||
@@ -815,14 +819,24 @@ pub fn openFileAtWindowsNtPath(
|
||||
disposition: w.ULONG,
|
||||
options: w.ULONG,
|
||||
) Maybe(bun.FileDescriptor) {
|
||||
var result: windows.HANDLE = undefined;
|
||||
|
||||
// Another problem re: normalization is that you can use relative paths, but no leading '.\' or './''
|
||||
// this path is probably already backslash normalized so we're only going to check for '.\'
|
||||
// const path = if (bun.strings.hasPrefixComptimeUTF16(path_maybe_leading_dot, ".\\")) path_maybe_leading_dot[2..] else path_maybe_leading_dot;
|
||||
// std.debug.assert(!bun.strings.hasPrefixComptimeUTF16(path_maybe_leading_dot, "./"));
|
||||
assertIsValidWindowsPath(u16, path);
|
||||
|
||||
return openFileAtWindowsNtPathWithoutAssertion(dir, path, access_mask, disposition, options);
|
||||
}
|
||||
|
||||
fn openFileAtWindowsNtPathWithoutAssertion(
|
||||
dir: bun.FileDescriptor,
|
||||
path: []const u16,
|
||||
access_mask: w.ULONG,
|
||||
disposition: w.ULONG,
|
||||
options: w.ULONG,
|
||||
) Maybe(bun.FileDescriptor) {
|
||||
var result: windows.HANDLE = undefined;
|
||||
|
||||
const path_len_bytes = std.math.cast(u16, path.len * 2) orelse return .{
|
||||
.err = .{
|
||||
.errno = @intFromEnum(bun.C.E.NOMEM),
|
||||
@@ -1855,22 +1869,22 @@ pub fn unlink(from: [:0]const u8) Maybe(void) {
|
||||
}
|
||||
|
||||
pub fn rmdirat(dirfd: bun.FileDescriptor, to: anytype) Maybe(void) {
|
||||
if (Environment.isWindows) {
|
||||
return Maybe(void).todo();
|
||||
}
|
||||
while (true) {
|
||||
if (Maybe(void).errnoSys(sys.unlinkat(dirfd.cast(), to, std.os.AT.REMOVEDIR), .rmdir)) |err| {
|
||||
if (err.getErrno() == .INTR) continue;
|
||||
return err;
|
||||
}
|
||||
return Maybe(void).success;
|
||||
}
|
||||
return unlinkatWithFlags(dirfd, to, std.os.AT.REMOVEDIR);
|
||||
}
|
||||
|
||||
pub fn unlinkatWithFlags(dirfd: bun.FileDescriptor, to: anytype, flags: c_uint) Maybe(void) {
|
||||
if (Environment.isWindows) {
|
||||
return Maybe(void).todo();
|
||||
if (comptime std.meta.Elem(@TypeOf(to)) == u8) {
|
||||
var w_buf: bun.WPathBuffer = undefined;
|
||||
return unlinkatWithFlags(dirfd, bun.strings.toNTPath(&w_buf, bun.span(to)), flags);
|
||||
}
|
||||
|
||||
return bun.windows.DeleteFileBun(to, .{
|
||||
.dir = if (dirfd != bun.invalid_fd) dirfd.cast() else null,
|
||||
.remove_dir = flags & std.os.AT.REMOVEDIR != 0,
|
||||
});
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (Maybe(void).errnoSys(sys.unlinkat(dirfd.cast(), to, flags), .unlink)) |err| {
|
||||
if (err.getErrno() == .INTR) continue;
|
||||
@@ -1887,7 +1901,7 @@ pub fn unlinkatWithFlags(dirfd: bun.FileDescriptor, to: anytype, flags: c_uint)
|
||||
|
||||
pub fn unlinkat(dirfd: bun.FileDescriptor, to: anytype) Maybe(void) {
|
||||
if (Environment.isWindows) {
|
||||
return Maybe(void).todo();
|
||||
return unlinkatWithFlags(dirfd, to, 0);
|
||||
}
|
||||
while (true) {
|
||||
if (Maybe(void).errnoSys(sys.unlinkat(dirfd.cast(), to, 0), .unlink)) |err| {
|
||||
@@ -2270,6 +2284,14 @@ pub fn pipe() Maybe([2]bun.FileDescriptor) {
|
||||
return .{ .result = .{ bun.toFD(fds[0]), bun.toFD(fds[1]) } };
|
||||
}
|
||||
|
||||
pub fn openNullDevice() Maybe(bun.FileDescriptor) {
|
||||
if (comptime Environment.isWindows) {
|
||||
return sys_uv.open("nul", 0, 0);
|
||||
}
|
||||
|
||||
return open("/dev/null", os.O.RDWR, 0);
|
||||
}
|
||||
|
||||
pub fn dupWithFlags(fd: bun.FileDescriptor, flags: i32) Maybe(bun.FileDescriptor) {
|
||||
if (comptime Environment.isWindows) {
|
||||
var target: windows.HANDLE = undefined;
|
||||
|
||||
113
src/windows.zig
113
src/windows.zig
@@ -3044,7 +3044,7 @@ pub fn translateNTStatusToErrno(err: win32.NTSTATUS) bun.C.E {
|
||||
.OBJECT_NAME_NOT_FOUND => .NOENT,
|
||||
.NOT_A_DIRECTORY => .NOTDIR,
|
||||
.RETRY => .AGAIN,
|
||||
.DIRECTORY_NOT_EMPTY => .EXIST,
|
||||
.DIRECTORY_NOT_EMPTY => .NOTEMPTY,
|
||||
.FILE_TOO_LARGE => .@"2BIG",
|
||||
.SHARING_VIOLATION => if (comptime Environment.isDebug) brk: {
|
||||
bun.Output.debugWarn("Received SHARING_VIOLATION, indicates file handle should've been opened with FILE_SHARE_DELETE", .{});
|
||||
@@ -3375,3 +3375,114 @@ pub extern fn SetConsoleMode(console_handle: *anyopaque, mode: u32) u32;
|
||||
pub extern fn SetStdHandle(nStdHandle: u32, hHandle: *anyopaque) u32;
|
||||
pub extern fn GetConsoleOutputCP() u32;
|
||||
pub extern "kernel32" fn SetConsoleCP(wCodePageID: std.os.windows.UINT) callconv(std.os.windows.WINAPI) std.os.windows.BOOL;
|
||||
|
||||
pub const DeleteFileOptions = struct {
|
||||
dir: ?HANDLE,
|
||||
remove_dir: bool = false,
|
||||
};
|
||||
|
||||
const FILE_DISPOSITION_DELETE: ULONG = 0x00000001;
|
||||
const FILE_DISPOSITION_POSIX_SEMANTICS: ULONG = 0x00000002;
|
||||
const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004;
|
||||
const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008;
|
||||
const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010;
|
||||
|
||||
// Copy-paste of the standard library function except without unreachable.
|
||||
pub fn DeleteFileBun(sub_path_w: []const u16, options: DeleteFileOptions) bun.JSC.Maybe(void) {
|
||||
const create_options_flags: ULONG = if (options.remove_dir)
|
||||
FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT
|
||||
else
|
||||
windows.FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead?
|
||||
|
||||
const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2));
|
||||
var nt_name = UNICODE_STRING{
|
||||
.Length = path_len_bytes,
|
||||
.MaximumLength = path_len_bytes,
|
||||
// The Windows API makes this mutable, but it will not mutate here.
|
||||
.Buffer = @constCast(sub_path_w.ptr),
|
||||
};
|
||||
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
|
||||
// Windows does not recognize this, but it does work with empty string.
|
||||
nt_name.Length = 0;
|
||||
}
|
||||
|
||||
var attr = OBJECT_ATTRIBUTES{
|
||||
.Length = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
|
||||
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: IO_STATUS_BLOCK = undefined;
|
||||
var tmp_handle: HANDLE = undefined;
|
||||
var rc = ntdll.NtCreateFile(
|
||||
&tmp_handle,
|
||||
windows.SYNCHRONIZE | windows.DELETE,
|
||||
&attr,
|
||||
&io,
|
||||
null,
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
windows.FILE_OPEN,
|
||||
create_options_flags,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
bun.sys.syslog("NtCreateFile({}, DELETE) = {}", .{ bun.fmt.fmtPath(u16, sub_path_w, .{}), rc });
|
||||
if (bun.JSC.Maybe(void).errnoSys(rc, .open)) |err| {
|
||||
return err;
|
||||
}
|
||||
defer _ = bun.windows.CloseHandle(tmp_handle);
|
||||
|
||||
// FileDispositionInformationEx (and therefore FILE_DISPOSITION_POSIX_SEMANTICS and FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE)
|
||||
// are only supported on NTFS filesystems, so the version check on its own is only a partial solution. To support non-NTFS filesystems
|
||||
// like FAT32, we need to fallback to FileDispositionInformation if the usage of FileDispositionInformationEx gives
|
||||
// us INVALID_PARAMETER.
|
||||
// The same reasoning for win10_rs5 as in os.renameatW() applies (FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5).
|
||||
var need_fallback = true;
|
||||
// Deletion with posix semantics if the filesystem supports it.
|
||||
var info = windows.FILE_DISPOSITION_INFORMATION_EX{
|
||||
.Flags = FILE_DISPOSITION_DELETE |
|
||||
FILE_DISPOSITION_POSIX_SEMANTICS |
|
||||
FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
|
||||
};
|
||||
|
||||
rc = ntdll.NtSetInformationFile(
|
||||
tmp_handle,
|
||||
&io,
|
||||
&info,
|
||||
@sizeOf(windows.FILE_DISPOSITION_INFORMATION_EX),
|
||||
.FileDispositionInformationEx,
|
||||
);
|
||||
bun.sys.syslog("NtSetInformationFile({}, DELETE) = {}", .{ bun.fmt.fmtPath(u16, sub_path_w, .{}), rc });
|
||||
switch (rc) {
|
||||
.SUCCESS => return .{ .result = {} },
|
||||
// INVALID_PARAMETER here means that the filesystem does not support FileDispositionInformationEx
|
||||
.INVALID_PARAMETER => {},
|
||||
// For all other statuses, fall down to the switch below to handle them.
|
||||
else => need_fallback = false,
|
||||
}
|
||||
if (need_fallback) {
|
||||
// Deletion with file pending semantics, which requires waiting or moving
|
||||
// files to get them removed (from here).
|
||||
var file_dispo = windows.FILE_DISPOSITION_INFORMATION{
|
||||
.DeleteFile = TRUE,
|
||||
};
|
||||
|
||||
rc = ntdll.NtSetInformationFile(
|
||||
tmp_handle,
|
||||
&io,
|
||||
&file_dispo,
|
||||
@sizeOf(windows.FILE_DISPOSITION_INFORMATION),
|
||||
.FileDispositionInformation,
|
||||
);
|
||||
bun.sys.syslog("NtSetInformationFile({}, DELETE) = {}", .{ bun.fmt.fmtPath(u16, sub_path_w, .{}), rc });
|
||||
}
|
||||
if (bun.JSC.Maybe(void).errnoSys(rc, .NtSetInformationFile)) |err| {
|
||||
return err;
|
||||
}
|
||||
|
||||
return .{ .result = {} };
|
||||
}
|
||||
|
||||
@@ -1269,13 +1269,6 @@ pub fn renameAtW(
|
||||
new_path_w: []const u16,
|
||||
replace_if_exists: bool,
|
||||
) Maybe(void) {
|
||||
if (comptime bun.Environment.allow_assert) {
|
||||
// if the directories are the same and the destination path is absolute, the old path name is kept
|
||||
if (old_dir_fd == new_dir_fd) {
|
||||
std.debug.assert(!std.fs.path.isAbsoluteWindowsWTF16(new_path_w));
|
||||
}
|
||||
}
|
||||
|
||||
const src_fd = brk: {
|
||||
switch (bun.sys.openFileAtWindows(
|
||||
old_dir_fd,
|
||||
|
||||
Reference in New Issue
Block a user