From 17ecc77fc46727f3979abe904ec00b79f036270e Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sun, 23 Nov 2025 19:44:28 -0800 Subject: [PATCH 1/2] os.windows: Delete unused functions and kernel32 bindings --- lib/std/heap.zig | 2 +- lib/std/os.zig | 15 ------ lib/std/os/windows.zig | 82 +-------------------------------- lib/std/os/windows/kernel32.zig | 56 ---------------------- lib/std/posix.zig | 2 +- 5 files changed, 3 insertions(+), 154 deletions(-) diff --git a/lib/std/heap.zig b/lib/std/heap.zig index c35e6f6684..445b5da455 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -46,7 +46,7 @@ pub var next_mmap_addr_hint: ?[*]align(page_size_min) u8 = null; /// comptime-known minimum page size of the target. /// -/// All pointers from `mmap` or `VirtualAlloc` are aligned to at least +/// All pointers from `mmap` or `NtAllocateVirtualMemory` are aligned to at least /// `page_size_min`, but their actual alignment may be bigger. /// /// This value can be overridden via `std.options.page_size_min`. diff --git a/lib/std/os.zig b/lib/std/os.zig index 5efca5899c..110c92da2b 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -56,21 +56,6 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o else => undefined, }; -/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string. -/// Otherwise use `access`. -pub fn accessW(path: [*:0]const u16) windows.GetFileAttributesError!void { - const ret = try windows.GetFileAttributesW(path); - if (ret != windows.INVALID_FILE_ATTRIBUTES) { - return; - } - switch (windows.GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .ACCESS_DENIED => return error.AccessDenied, - else => |err| return windows.unexpectedError(err), - } -} - pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool { return switch (os.tag) { .windows, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index a855a58d4f..3265dc4074 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -306,22 +306,6 @@ pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) C wr.* = write; } -pub fn CreateEventEx(attributes: ?*SECURITY_ATTRIBUTES, name: []const u8, flags: DWORD, desired_access: DWORD) !HANDLE { - const nameW = try sliceToPrefixedFileW(null, name); - return CreateEventExW(attributes, nameW.span().ptr, flags, desired_access); -} - -pub fn CreateEventExW(attributes: ?*SECURITY_ATTRIBUTES, nameW: ?LPCWSTR, flags: DWORD, desired_access: DWORD) !HANDLE { - const handle = kernel32.CreateEventExW(attributes, nameW, flags, desired_access); - if (handle) |h| { - return h; - } else { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } -} - pub const DeviceIoControlError = error{ AccessDenied, /// The volume does not contain a recognized file system. File system @@ -598,10 +582,6 @@ pub fn CloseHandle(hObject: HANDLE) void { assert(ntdll.NtClose(hObject) == .SUCCESS); } -pub fn FindClose(hFindFile: HANDLE) void { - assert(kernel32.FindClose(hFindFile) != 0); -} - pub const ReadFileError = error{ BrokenPipe, /// The specified network name is no longer available. @@ -1515,30 +1495,6 @@ pub fn GetFileSizeEx(hFile: HANDLE) GetFileSizeError!u64 { return @as(u64, @bitCast(file_size)); } -pub const GetFileAttributesError = error{ - FileNotFound, - AccessDenied, - Unexpected, -}; - -pub fn GetFileAttributes(filename: []const u8) (GetFileAttributesError || Wtf8ToPrefixedFileWError)!DWORD { - const filename_w = try sliceToPrefixedFileW(null, filename); - return GetFileAttributesW(filename_w.span().ptr); -} - -pub fn GetFileAttributesW(lpFileName: [*:0]const u16) GetFileAttributesError!DWORD { - const rc = kernel32.GetFileAttributesW(lpFileName); - if (rc == INVALID_FILE_ATTRIBUTES) { - switch (GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .ACCESS_DENIED => return error.AccessDenied, - else => |err| return unexpectedError(err), - } - } - return rc; -} - pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 { return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen))); } @@ -1657,6 +1613,7 @@ pub const NtFreeVirtualMemoryError = error{ }; pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_type: ULONG) NtFreeVirtualMemoryError!void { + // TODO: If the return value is .INVALID_PAGE_PROTECTION, call RtlFlushSecureMemoryCache and try again. return switch (ntdll.NtFreeVirtualMemory(hProcess, addr, size, free_type)) { .SUCCESS => return, .ACCESS_DENIED => NtFreeVirtualMemoryError.AccessDenied, @@ -1665,20 +1622,6 @@ pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_ }; } -pub const VirtualAllocError = error{Unexpected}; - -pub fn VirtualAlloc(addr: ?LPVOID, size: usize, alloc_type: DWORD, flProtect: DWORD) VirtualAllocError!LPVOID { - return kernel32.VirtualAlloc(addr, size, alloc_type, flProtect) orelse { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - }; -} - -pub fn VirtualFree(lpAddress: ?LPVOID, dwSize: usize, dwFreeType: DWORD) void { - assert(kernel32.VirtualFree(lpAddress, dwSize, dwFreeType) != 0); -} - pub const VirtualProtectError = error{ InvalidAddress, Unexpected, @@ -1713,19 +1656,6 @@ pub fn VirtualProtectEx(handle: HANDLE, addr: ?LPVOID, size: SIZE_T, new_prot: D } } -pub const VirtualQueryError = error{Unexpected}; - -pub fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASIC_INFORMATION, dwLength: SIZE_T) VirtualQueryError!SIZE_T { - const rc = kernel32.VirtualQuery(lpAddress, lpBuffer, dwLength); - if (rc == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } - - return rc; -} - pub const SetConsoleTextAttributeError = error{Unexpected}; pub fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) SetConsoleTextAttributeError!void { @@ -2628,16 +2558,6 @@ test ntToWin32Namespace { try std.testing.expectError(error.NameTooLong, ntToWin32Namespace(L("\\??\\C:\\test"), &too_small_buf)); } -fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize { - const result = kernel32.GetFullPathNameW(path, @as(u32, @intCast(out.len)), out.ptr, null); - if (result == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } - return result; -} - inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID { return (s << 10) | p; } diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index 98d72a04e1..c32294c125 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -86,23 +86,6 @@ pub extern "kernel32" fn CreateNamedPipeW( lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES, ) callconv(.winapi) HANDLE; -pub extern "kernel32" fn FindFirstFileW( - lpFileName: LPCWSTR, - lpFindFileData: *WIN32_FIND_DATAW, -) callconv(.winapi) HANDLE; - -pub extern "kernel32" fn FindClose( - hFindFile: HANDLE, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around RtlGetFullPathName_UEx -pub extern "kernel32" fn GetFullPathNameW( - lpFileName: LPCWSTR, - nBufferLength: DWORD, - lpBuffer: LPWSTR, - lpFilePart: ?*?LPWSTR, -) callconv(.winapi) DWORD; - // TODO: Matches `STD_*_HANDLE` to peb().ProcessParameters.Standard* pub extern "kernel32" fn GetStdHandle( nStdHandle: DWORD, @@ -162,11 +145,6 @@ pub extern "kernel32" fn GetCurrentDirectoryW( lpBuffer: ?[*]WCHAR, ) callconv(.winapi) DWORD; -// TODO: RtlDosPathNameToNtPathNameU_WithStatus + NtQueryAttributesFile. -pub extern "kernel32" fn GetFileAttributesW( - lpFileName: LPCWSTR, -) callconv(.winapi) DWORD; - pub extern "kernel32" fn ReadFile( hFile: HANDLE, lpBuffer: LPVOID, @@ -182,14 +160,6 @@ pub extern "kernel32" fn GetSystemDirectoryW( // I/O - Kernel Objects -// TODO: Wrapper around NtCreateEvent. -pub extern "kernel32" fn CreateEventExW( - lpEventAttributes: ?*SECURITY_ATTRIBUTES, - lpName: ?LPCWSTR, - dwFlags: DWORD, - dwDesiredAccess: DWORD, -) callconv(.winapi) ?HANDLE; - // TODO: Wrapper around GetStdHandle + NtDuplicateObject. pub extern "kernel32" fn DuplicateHandle( hSourceProcessHandle: HANDLE, @@ -318,9 +288,6 @@ pub extern "kernel32" fn GetExitCodeProcess( lpExitCode: *DWORD, ) callconv(.winapi) BOOL; -// TODO: Already a wrapper for this, see `windows.GetCurrentProcess`. -pub extern "kernel32" fn GetCurrentProcess() callconv(.winapi) HANDLE; - // TODO: Wrapper around RtlSetEnvironmentVar. pub extern "kernel32" fn SetEnvironmentVariableW( lpName: LPCWSTR, @@ -465,29 +432,6 @@ pub extern "kernel32" fn HeapValidate( lpMem: ?*const anyopaque, ) callconv(.winapi) BOOL; -// TODO: Wrapper around NtAllocateVirtualMemory. -pub extern "kernel32" fn VirtualAlloc( - lpAddress: ?LPVOID, - dwSize: SIZE_T, - flAllocationType: DWORD, - flProtect: DWORD, -) callconv(.winapi) ?LPVOID; - -// TODO: Wrapper around NtFreeVirtualMemory. -// If the return value is .INVALID_PAGE_PROTECTION, calls RtlFlushSecureMemoryCache and try again. -pub extern "kernel32" fn VirtualFree( - lpAddress: ?LPVOID, - dwSize: SIZE_T, - dwFreeType: DWORD, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around NtQueryVirtualMemory. -pub extern "kernel32" fn VirtualQuery( - lpAddress: ?LPVOID, - lpBuffer: PMEMORY_BASIC_INFORMATION, - dwLength: SIZE_T, -) callconv(.winapi) SIZE_T; - // TODO: Getter for peb.ProcessHeap pub extern "kernel32" fn GetProcessHeap() callconv(.winapi) ?HANDLE; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 13d16ea324..2d33eb92b8 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -4409,7 +4409,7 @@ pub fn mmap( /// Note that while POSIX allows unmapping a region in the middle of an existing mapping, /// Zig's munmap function does not, for two reasons: /// * It violates the Zig principle that resource deallocation must succeed. -/// * The Windows function, VirtualFree, has this restriction. +/// * The Windows function, NtFreeVirtualMemory, has this restriction. pub fn munmap(memory: []align(page_size_min) const u8) void { switch (errno(system.munmap(memory.ptr, memory.len))) { .SUCCESS => return, From bf25816067844efbdb7fe26e8ca7a96341fb919a Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sun, 23 Nov 2025 20:02:16 -0800 Subject: [PATCH 2/2] Move Windows rename implementation from std.posix to windows.RenameFile This also unifies the rename implementations, since previously `posix.renameW` used `MoveFileEx` while `posix.renameatW` used `NtOpenFile`/`NtSetInformationFile`. This, in turn, allows the `MoveFileEx` bindings to be deleted as `posix.renameW` was the only usage. --- lib/std/os/windows.zig | 138 +++++++++++++++++++++++++++++--- lib/std/os/windows/kernel32.zig | 6 -- lib/std/posix.zig | 109 +------------------------ 3 files changed, 129 insertions(+), 124 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 3265dc4074..86655051fd 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1084,21 +1084,135 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil } } -pub const MoveFileError = error{ FileNotFound, AccessDenied, Unexpected }; +pub const RenameError = error{ + IsDir, + NotDir, + FileNotFound, + NoDevice, + AccessDenied, + PipeBusy, + PathAlreadyExists, + Unexpected, + NameTooLong, + NetworkNotFound, + AntivirusInterference, + BadPathName, + RenameAcrossMountPoints, +} || UnexpectedError; -pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) (MoveFileError || Wtf8ToPrefixedFileWError)!void { - const old_path_w = try sliceToPrefixedFileW(null, old_path); - const new_path_w = try sliceToPrefixedFileW(null, new_path); - return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags); -} +pub fn RenameFile( + /// May only be `null` if `old_path_w` is a fully-qualified absolute path. + old_dir_fd: ?HANDLE, + old_path_w: []const u16, + /// May only be `null` if `new_path_w` is a fully-qualified absolute path, + /// or if the file is not being moved to a different directory. + new_dir_fd: ?HANDLE, + new_path_w: []const u16, + replace_if_exists: bool, +) RenameError!void { + const src_fd = OpenFile(old_path_w, .{ + .dir = old_dir_fd, + .access_mask = SYNCHRONIZE | GENERIC_WRITE | DELETE, + .creation = FILE_OPEN, + .filter = .any, // This function is supposed to rename both files and directories. + .follow_symlinks = false, + }) catch |err| switch (err) { + error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. + else => |e| return e, + }; + defer CloseHandle(src_fd); -pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { - if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { - switch (GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .ACCESS_DENIED => return error.AccessDenied, - else => |err| return unexpectedError(err), + var rc: NTSTATUS = undefined; + // FileRenameInformationEx has varying levels of support: + // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 + // (INVALID_INFO_CLASS is returned if not supported) + // - Requires the NTFS filesystem + // (on filesystems like FAT32, INVALID_PARAMETER is returned) + // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 + // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 + // (NOT_SUPPORTED is returned if a flag is unsupported) + // + // The strategy here is just to try using FileRenameInformationEx and fall back to + // FileRenameInformation if the return value lets us know that some aspect of it is not supported. + const need_fallback = need_fallback: { + const struct_buf_len = @sizeOf(FILE_RENAME_INFORMATION_EX) + (PATH_MAX_WIDE * 2); + var rename_info_buf: [struct_buf_len]u8 align(@alignOf(FILE_RENAME_INFORMATION_EX)) = undefined; + const struct_len = @sizeOf(FILE_RENAME_INFORMATION_EX) + new_path_w.len * 2; + if (struct_len > struct_buf_len) return error.NameTooLong; + + const rename_info: *FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf); + var io_status_block: IO_STATUS_BLOCK = undefined; + + var flags: ULONG = FILE_RENAME_POSIX_SEMANTICS | FILE_RENAME_IGNORE_READONLY_ATTRIBUTE; + if (replace_if_exists) flags |= FILE_RENAME_REPLACE_IF_EXISTS; + rename_info.* = .{ + .Flags = flags, + .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, + .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong + .FileName = undefined, + }; + @memcpy((&rename_info.FileName).ptr, new_path_w); + rc = ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformationEx, + ); + switch (rc) { + .SUCCESS => return, + // The filesystem does not support FileDispositionInformationEx + .INVALID_PARAMETER, + // The operating system does not support FileDispositionInformationEx + .INVALID_INFO_CLASS, + // The operating system does not support one of the flags + .NOT_SUPPORTED, + => break :need_fallback true, + // For all other statuses, fall down to the switch below to handle them. + else => break :need_fallback false, } + }; + + if (need_fallback) { + const struct_buf_len = @sizeOf(FILE_RENAME_INFORMATION) + (PATH_MAX_WIDE * 2); + var rename_info_buf: [struct_buf_len]u8 align(@alignOf(FILE_RENAME_INFORMATION)) = undefined; + const struct_len = @sizeOf(FILE_RENAME_INFORMATION) + new_path_w.len * 2; + if (struct_len > struct_buf_len) return error.NameTooLong; + + const rename_info: *FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf); + var io_status_block: IO_STATUS_BLOCK = undefined; + + rename_info.* = .{ + .Flags = @intFromBool(replace_if_exists), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, + .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong + .FileName = undefined, + }; + @memcpy((&rename_info.FileName).ptr, new_path_w); + + rc = ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformation, + ); + } + + switch (rc) { + .SUCCESS => {}, + .INVALID_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + .ACCESS_DENIED => return error.AccessDenied, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, + .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, + .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, + .FILE_IS_A_DIRECTORY => return error.IsDir, + .NOT_A_DIRECTORY => return error.NotDir, + else => return unexpectedStatus(rc), } } diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index c32294c125..7f746057a9 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -91,12 +91,6 @@ pub extern "kernel32" fn GetStdHandle( nStdHandle: DWORD, ) callconv(.winapi) ?HANDLE; -pub extern "kernel32" fn MoveFileExW( - lpExistingFileName: LPCWSTR, - lpNewFileName: LPCWSTR, - dwFlags: DWORD, -) callconv(.winapi) BOOL; - // TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`. // `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END` pub extern "kernel32" fn SetFilePointerEx( diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 2d33eb92b8..3e80466cd3 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -2470,8 +2470,8 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi /// Same as `rename` except the parameters are null-terminated and WTF16LE encoded. /// Assumes target is Windows. pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void { - const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; - return windows.MoveFileExW(old_path, new_path, flags); + const cwd_handle = std.fs.cwd().fd; + return windows.RenameFile(cwd_handle, mem.span(old_path), cwd_handle, mem.span(new_path), true); } /// Change the name or location of a file based on an open directory handle. @@ -2588,110 +2588,7 @@ pub fn renameatW( new_path_w: []const u16, ReplaceIfExists: windows.BOOLEAN, ) RenameError!void { - const src_fd = windows.OpenFile(old_path_w, .{ - .dir = old_dir_fd, - .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE, - .creation = windows.FILE_OPEN, - .filter = .any, // This function is supposed to rename both files and directories. - .follow_symlinks = false, - }) catch |err| switch (err) { - error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. - else => |e| return e, - }; - defer windows.CloseHandle(src_fd); - - var rc: windows.NTSTATUS = undefined; - // FileRenameInformationEx has varying levels of support: - // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 - // (INVALID_INFO_CLASS is returned if not supported) - // - Requires the NTFS filesystem - // (on filesystems like FAT32, INVALID_PARAMETER is returned) - // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 - // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 - // (NOT_SUPPORTED is returned if a flag is unsupported) - // - // The strategy here is just to try using FileRenameInformationEx and fall back to - // FileRenameInformation if the return value lets us know that some aspect of it is not supported. - const need_fallback = need_fallback: { - const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (max_path_bytes - 1); - var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined; - const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2; - if (struct_len > struct_buf_len) return error.NameTooLong; - - const rename_info: *windows.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf); - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - - var flags: windows.ULONG = windows.FILE_RENAME_POSIX_SEMANTICS | windows.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE; - if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS; - rename_info.* = .{ - .Flags = flags, - .RootDirectory = if (fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, - .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong - .FileName = undefined, - }; - @memcpy((&rename_info.FileName).ptr, new_path_w); - rc = windows.ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info, - @intCast(struct_len), // already checked for error.NameTooLong - .FileRenameInformationEx, - ); - switch (rc) { - .SUCCESS => return, - // The filesystem does not support FileDispositionInformationEx - .INVALID_PARAMETER, - // The operating system does not support FileDispositionInformationEx - .INVALID_INFO_CLASS, - // The operating system does not support one of the flags - .NOT_SUPPORTED, - => break :need_fallback true, - // For all other statuses, fall down to the switch below to handle them. - else => break :need_fallback false, - } - }; - - if (need_fallback) { - const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (max_path_bytes - 1); - var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined; - const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path_w.len * 2; - if (struct_len > struct_buf_len) return error.NameTooLong; - - const rename_info: *windows.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf); - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - - rename_info.* = .{ - .Flags = ReplaceIfExists, - .RootDirectory = if (fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, - .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong - .FileName = undefined, - }; - @memcpy((&rename_info.FileName).ptr, new_path_w); - - rc = windows.ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info, - @intCast(struct_len), // already checked for error.NameTooLong - .FileRenameInformation, - ); - } - - switch (rc) { - .SUCCESS => {}, - .INVALID_HANDLE => unreachable, - .INVALID_PARAMETER => unreachable, - .OBJECT_PATH_SYNTAX_BAD => unreachable, - .ACCESS_DENIED => return error.AccessDenied, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, - .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, - .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, - .FILE_IS_A_DIRECTORY => return error.IsDir, - .NOT_A_DIRECTORY => return error.NotDir, - else => return windows.unexpectedStatus(rc), - } + return windows.RenameFile(old_dir_fd, old_path_w, new_dir_fd, new_path_w, ReplaceIfExists != 0); } /// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).