Compare commits

...

1 Commits

Author SHA1 Message Date
Jarred Sumner
67024a445a Get rm tests to pass on Windows 2024-04-10 01:44:48 -07:00
7 changed files with 177 additions and 82 deletions

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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();

View File

@@ -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

View File

@@ -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;

View File

@@ -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 = {} };
}

View File

@@ -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,