@@ -306,22 +306,6 @@ pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) C
306306 wr .* = write ;
307307}
308308
309- pub fn CreateEventEx (attributes : ? * SECURITY_ATTRIBUTES , name : []const u8 , flags : DWORD , desired_access : DWORD ) ! HANDLE {
310- const nameW = try sliceToPrefixedFileW (null , name );
311- return CreateEventExW (attributes , nameW .span ().ptr , flags , desired_access );
312- }
313-
314- pub fn CreateEventExW (attributes : ? * SECURITY_ATTRIBUTES , nameW : ? LPCWSTR , flags : DWORD , desired_access : DWORD ) ! HANDLE {
315- const handle = kernel32 .CreateEventExW (attributes , nameW , flags , desired_access );
316- if (handle ) | h | {
317- return h ;
318- } else {
319- switch (GetLastError ()) {
320- else = > | err | return unexpectedError (err ),
321- }
322- }
323- }
324-
325309pub const DeviceIoControlError = error {
326310 AccessDenied ,
327311 /// The volume does not contain a recognized file system. File system
@@ -598,10 +582,6 @@ pub fn CloseHandle(hObject: HANDLE) void {
598582 assert (ntdll .NtClose (hObject ) == .SUCCESS );
599583}
600584
601- pub fn FindClose (hFindFile : HANDLE ) void {
602- assert (kernel32 .FindClose (hFindFile ) != 0 );
603- }
604-
605585pub const ReadFileError = error {
606586 BrokenPipe ,
607587 /// The specified network name is no longer available.
@@ -1104,21 +1084,135 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
11041084 }
11051085}
11061086
1107- pub const MoveFileError = error { FileNotFound , AccessDenied , Unexpected };
1087+ pub const RenameError = error {
1088+ IsDir ,
1089+ NotDir ,
1090+ FileNotFound ,
1091+ NoDevice ,
1092+ AccessDenied ,
1093+ PipeBusy ,
1094+ PathAlreadyExists ,
1095+ Unexpected ,
1096+ NameTooLong ,
1097+ NetworkNotFound ,
1098+ AntivirusInterference ,
1099+ BadPathName ,
1100+ RenameAcrossMountPoints ,
1101+ } || UnexpectedError ;
11081102
1109- pub fn MoveFileEx (old_path : []const u8 , new_path : []const u8 , flags : DWORD ) (MoveFileError || Wtf8ToPrefixedFileWError )! void {
1110- const old_path_w = try sliceToPrefixedFileW (null , old_path );
1111- const new_path_w = try sliceToPrefixedFileW (null , new_path );
1112- return MoveFileExW (old_path_w .span ().ptr , new_path_w .span ().ptr , flags );
1113- }
1103+ pub fn RenameFile (
1104+ /// May only be `null` if `old_path_w` is a fully-qualified absolute path.
1105+ old_dir_fd : ? HANDLE ,
1106+ old_path_w : []const u16 ,
1107+ /// May only be `null` if `new_path_w` is a fully-qualified absolute path,
1108+ /// or if the file is not being moved to a different directory.
1109+ new_dir_fd : ? HANDLE ,
1110+ new_path_w : []const u16 ,
1111+ replace_if_exists : bool ,
1112+ ) RenameError ! void {
1113+ const src_fd = OpenFile (old_path_w , .{
1114+ .dir = old_dir_fd ,
1115+ .access_mask = SYNCHRONIZE | GENERIC_WRITE | DELETE ,
1116+ .creation = FILE_OPEN ,
1117+ .filter = .any , // This function is supposed to rename both files and directories.
1118+ .follow_symlinks = false ,
1119+ }) catch | err | switch (err ) {
1120+ error .WouldBlock = > unreachable , // Not possible without `.share_access_nonblocking = true`.
1121+ else = > | e | return e ,
1122+ };
1123+ defer CloseHandle (src_fd );
11141124
1115- pub fn MoveFileExW (old_path : [* :0 ]const u16 , new_path : [* :0 ]const u16 , flags : DWORD ) MoveFileError ! void {
1116- if (kernel32 .MoveFileExW (old_path , new_path , flags ) == 0 ) {
1117- switch (GetLastError ()) {
1118- .FILE_NOT_FOUND = > return error .FileNotFound ,
1119- .ACCESS_DENIED = > return error .AccessDenied ,
1120- else = > | err | return unexpectedError (err ),
1125+ var rc : NTSTATUS = undefined ;
1126+ // FileRenameInformationEx has varying levels of support:
1127+ // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
1128+ // (INVALID_INFO_CLASS is returned if not supported)
1129+ // - Requires the NTFS filesystem
1130+ // (on filesystems like FAT32, INVALID_PARAMETER is returned)
1131+ // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1
1132+ // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
1133+ // (NOT_SUPPORTED is returned if a flag is unsupported)
1134+ //
1135+ // The strategy here is just to try using FileRenameInformationEx and fall back to
1136+ // FileRenameInformation if the return value lets us know that some aspect of it is not supported.
1137+ const need_fallback = need_fallback : {
1138+ const struct_buf_len = @sizeOf (FILE_RENAME_INFORMATION_EX ) + (PATH_MAX_WIDE * 2 );
1139+ var rename_info_buf : [struct_buf_len ]u8 align (@alignOf (FILE_RENAME_INFORMATION_EX )) = undefined ;
1140+ const struct_len = @sizeOf (FILE_RENAME_INFORMATION_EX ) + new_path_w .len * 2 ;
1141+ if (struct_len > struct_buf_len ) return error .NameTooLong ;
1142+
1143+ const rename_info : * FILE_RENAME_INFORMATION_EX = @ptrCast (& rename_info_buf );
1144+ var io_status_block : IO_STATUS_BLOCK = undefined ;
1145+
1146+ var flags : ULONG = FILE_RENAME_POSIX_SEMANTICS | FILE_RENAME_IGNORE_READONLY_ATTRIBUTE ;
1147+ if (replace_if_exists ) flags |= FILE_RENAME_REPLACE_IF_EXISTS ;
1148+ rename_info .* = .{
1149+ .Flags = flags ,
1150+ .RootDirectory = if (std .fs .path .isAbsoluteWindowsWtf16 (new_path_w )) null else new_dir_fd ,
1151+ .FileNameLength = @intCast (new_path_w .len * 2 ), // already checked error.NameTooLong
1152+ .FileName = undefined ,
1153+ };
1154+ @memcpy ((& rename_info .FileName ).ptr , new_path_w );
1155+ rc = ntdll .NtSetInformationFile (
1156+ src_fd ,
1157+ & io_status_block ,
1158+ rename_info ,
1159+ @intCast (struct_len ), // already checked for error.NameTooLong
1160+ .FileRenameInformationEx ,
1161+ );
1162+ switch (rc ) {
1163+ .SUCCESS = > return ,
1164+ // The filesystem does not support FileDispositionInformationEx
1165+ .INVALID_PARAMETER ,
1166+ // The operating system does not support FileDispositionInformationEx
1167+ .INVALID_INFO_CLASS ,
1168+ // The operating system does not support one of the flags
1169+ .NOT_SUPPORTED ,
1170+ = > break :need_fallback true ,
1171+ // For all other statuses, fall down to the switch below to handle them.
1172+ else = > break :need_fallback false ,
11211173 }
1174+ };
1175+
1176+ if (need_fallback ) {
1177+ const struct_buf_len = @sizeOf (FILE_RENAME_INFORMATION ) + (PATH_MAX_WIDE * 2 );
1178+ var rename_info_buf : [struct_buf_len ]u8 align (@alignOf (FILE_RENAME_INFORMATION )) = undefined ;
1179+ const struct_len = @sizeOf (FILE_RENAME_INFORMATION ) + new_path_w .len * 2 ;
1180+ if (struct_len > struct_buf_len ) return error .NameTooLong ;
1181+
1182+ const rename_info : * FILE_RENAME_INFORMATION = @ptrCast (& rename_info_buf );
1183+ var io_status_block : IO_STATUS_BLOCK = undefined ;
1184+
1185+ rename_info .* = .{
1186+ .Flags = @intFromBool (replace_if_exists ),
1187+ .RootDirectory = if (std .fs .path .isAbsoluteWindowsWtf16 (new_path_w )) null else new_dir_fd ,
1188+ .FileNameLength = @intCast (new_path_w .len * 2 ), // already checked error.NameTooLong
1189+ .FileName = undefined ,
1190+ };
1191+ @memcpy ((& rename_info .FileName ).ptr , new_path_w );
1192+
1193+ rc = ntdll .NtSetInformationFile (
1194+ src_fd ,
1195+ & io_status_block ,
1196+ rename_info ,
1197+ @intCast (struct_len ), // already checked for error.NameTooLong
1198+ .FileRenameInformation ,
1199+ );
1200+ }
1201+
1202+ switch (rc ) {
1203+ .SUCCESS = > {},
1204+ .INVALID_HANDLE = > unreachable ,
1205+ .INVALID_PARAMETER = > unreachable ,
1206+ .OBJECT_PATH_SYNTAX_BAD = > unreachable ,
1207+ .ACCESS_DENIED = > return error .AccessDenied ,
1208+ .OBJECT_NAME_NOT_FOUND = > return error .FileNotFound ,
1209+ .OBJECT_PATH_NOT_FOUND = > return error .FileNotFound ,
1210+ .NOT_SAME_DEVICE = > return error .RenameAcrossMountPoints ,
1211+ .OBJECT_NAME_COLLISION = > return error .PathAlreadyExists ,
1212+ .DIRECTORY_NOT_EMPTY = > return error .PathAlreadyExists ,
1213+ .FILE_IS_A_DIRECTORY = > return error .IsDir ,
1214+ .NOT_A_DIRECTORY = > return error .NotDir ,
1215+ else = > return unexpectedStatus (rc ),
11221216 }
11231217}
11241218
@@ -1515,30 +1609,6 @@ pub fn GetFileSizeEx(hFile: HANDLE) GetFileSizeError!u64 {
15151609 return @as (u64 , @bitCast (file_size ));
15161610}
15171611
1518- pub const GetFileAttributesError = error {
1519- FileNotFound ,
1520- AccessDenied ,
1521- Unexpected ,
1522- };
1523-
1524- pub fn GetFileAttributes (filename : []const u8 ) (GetFileAttributesError || Wtf8ToPrefixedFileWError )! DWORD {
1525- const filename_w = try sliceToPrefixedFileW (null , filename );
1526- return GetFileAttributesW (filename_w .span ().ptr );
1527- }
1528-
1529- pub fn GetFileAttributesW (lpFileName : [* :0 ]const u16 ) GetFileAttributesError ! DWORD {
1530- const rc = kernel32 .GetFileAttributesW (lpFileName );
1531- if (rc == INVALID_FILE_ATTRIBUTES ) {
1532- switch (GetLastError ()) {
1533- .FILE_NOT_FOUND = > return error .FileNotFound ,
1534- .PATH_NOT_FOUND = > return error .FileNotFound ,
1535- .ACCESS_DENIED = > return error .AccessDenied ,
1536- else = > | err | return unexpectedError (err ),
1537- }
1538- }
1539- return rc ;
1540- }
1541-
15421612pub fn getpeername (s : ws2_32.SOCKET , name : * ws2_32.sockaddr , namelen : * ws2_32.socklen_t ) i32 {
15431613 return ws2_32 .getpeername (s , name , @as (* i32 , @ptrCast (namelen )));
15441614}
@@ -1657,6 +1727,7 @@ pub const NtFreeVirtualMemoryError = error{
16571727};
16581728
16591729pub fn NtFreeVirtualMemory (hProcess : HANDLE , addr : ? * PVOID , size : * SIZE_T , free_type : ULONG ) NtFreeVirtualMemoryError ! void {
1730+ // TODO: If the return value is .INVALID_PAGE_PROTECTION, call RtlFlushSecureMemoryCache and try again.
16601731 return switch (ntdll .NtFreeVirtualMemory (hProcess , addr , size , free_type )) {
16611732 .SUCCESS = > return ,
16621733 .ACCESS_DENIED = > NtFreeVirtualMemoryError .AccessDenied ,
@@ -1665,20 +1736,6 @@ pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_
16651736 };
16661737}
16671738
1668- pub const VirtualAllocError = error {Unexpected };
1669-
1670- pub fn VirtualAlloc (addr : ? LPVOID , size : usize , alloc_type : DWORD , flProtect : DWORD ) VirtualAllocError ! LPVOID {
1671- return kernel32 .VirtualAlloc (addr , size , alloc_type , flProtect ) orelse {
1672- switch (GetLastError ()) {
1673- else = > | err | return unexpectedError (err ),
1674- }
1675- };
1676- }
1677-
1678- pub fn VirtualFree (lpAddress : ? LPVOID , dwSize : usize , dwFreeType : DWORD ) void {
1679- assert (kernel32 .VirtualFree (lpAddress , dwSize , dwFreeType ) != 0 );
1680- }
1681-
16821739pub const VirtualProtectError = error {
16831740 InvalidAddress ,
16841741 Unexpected ,
@@ -1713,19 +1770,6 @@ pub fn VirtualProtectEx(handle: HANDLE, addr: ?LPVOID, size: SIZE_T, new_prot: D
17131770 }
17141771}
17151772
1716- pub const VirtualQueryError = error {Unexpected };
1717-
1718- pub fn VirtualQuery (lpAddress : ? LPVOID , lpBuffer : PMEMORY_BASIC_INFORMATION , dwLength : SIZE_T ) VirtualQueryError ! SIZE_T {
1719- const rc = kernel32 .VirtualQuery (lpAddress , lpBuffer , dwLength );
1720- if (rc == 0 ) {
1721- switch (GetLastError ()) {
1722- else = > | err | return unexpectedError (err ),
1723- }
1724- }
1725-
1726- return rc ;
1727- }
1728-
17291773pub const SetConsoleTextAttributeError = error {Unexpected };
17301774
17311775pub fn SetConsoleTextAttribute (hConsoleOutput : HANDLE , wAttributes : WORD ) SetConsoleTextAttributeError ! void {
@@ -2628,16 +2672,6 @@ test ntToWin32Namespace {
26282672 try std .testing .expectError (error .NameTooLong , ntToWin32Namespace (L ("\\ ??\\ C:\\ test" ), & too_small_buf ));
26292673}
26302674
2631- fn getFullPathNameW (path : [* :0 ]const u16 , out : []u16 ) ! usize {
2632- const result = kernel32 .GetFullPathNameW (path , @as (u32 , @intCast (out .len )), out .ptr , null );
2633- if (result == 0 ) {
2634- switch (GetLastError ()) {
2635- else = > | err | return unexpectedError (err ),
2636- }
2637- }
2638- return result ;
2639- }
2640-
26412675inline fn MAKELANGID (p : c_ushort , s : c_ushort ) LANGID {
26422676 return (s << 10 ) | p ;
26432677}
0 commit comments