From 6d5fdd26103862810f646c8c42b72b0a90d67e1f Mon Sep 17 00:00:00 2001 From: Jan Philipp Hafer Date: Sun, 6 Aug 2023 23:34:26 +0200 Subject: [PATCH 1/4] std.windows: use atomic rename, if possible Mitigates #14978. --- lib/std/os.zig | 47 ++++++++++++++++++++++++++++++------------ lib/std/os/windows.zig | 15 +++++++++++++- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index f2b2393b541d..a43d414eb007 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2636,23 +2636,44 @@ pub fn renameatW( const rename_info = @as(*windows.FILE_RENAME_INFORMATION, @ptrCast(&rename_info_buf)); - rename_info.* = .{ - .ReplaceIfExists = ReplaceIfExists, - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd, - .FileNameLength = @as(u32, @intCast(new_path_w.len * 2)), // already checked error.NameTooLong - .FileName = undefined, - }; + if (builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) { + 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 (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, + }; + } else { + rename_info.* = .{ + .Flags = ReplaceIfExists, + .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(@as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w); var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const rc = windows.ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info, - @as(u32, @intCast(struct_len)), // already checked for error.NameTooLong - .FileRenameInformation, - ); + const rc = if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) { + windows.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformationEx, + ); + } else { + windows.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformation, + ); + }; switch (rc) { .SUCCESS => return, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 9fa00c6edac1..7fd98ac3de4a 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2856,8 +2856,21 @@ const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004; const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008; const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010; +// FILE_RENAME_INFORMATION.Flags +pub const FILE_RENAME_REPLACE_IF_EXISTS = 0x00000001; +pub const FILE_RENAME_POSIX_SEMANTICS = 0x00000002; +pub const FILE_RENAME_SUPPRESS_PIN_STATE_INHERITANCE = 0x00000004; +pub const FILE_RENAME_SUPPRESS_STORAGE_RESERVE_INHERITANCE = 0x00000008; +pub const FILE_RENAME_NO_INCREASE_AVAILABLE_SPACE = 0x00000010; +pub const FILE_RENAME_NO_DECREASE_AVAILABLE_SPACE = 0x00000020; +pub const FILE_RENAME_PRESERVE_AVAILABLE_SPACE = 0x00000030; +pub const FILE_RENAME_IGNORE_READONLY_ATTRIBUTE = 0x00000040; +pub const FILE_RENAME_FORCE_RESIZE_TARGET_SR = 0x00000080; +pub const FILE_RENAME_FORCE_RESIZE_SOURCE_SR = 0x00000100; +pub const FILE_RENAME_FORCE_RESIZE_SR = 0x00000180; + pub const FILE_RENAME_INFORMATION = extern struct { - ReplaceIfExists: BOOLEAN, + Flags: if (builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) ULONG else BOOLEAN, RootDirectory: ?HANDLE, FileNameLength: ULONG, FileName: [1]WCHAR, From 3ad5bebb207ae4fd7aaf9de4c352520cb521b112 Mon Sep 17 00:00:00 2001 From: Jan Philipp Hafer Date: Mon, 14 Aug 2023 01:19:44 +0200 Subject: [PATCH 2/4] use same strategy as in #16499 suggested by @squeek502 Also add optimization for happy path with early return. No idea, why this worked before. --- lib/std/os.zig | 84 +++++++++++++++++++++++++----------------- lib/std/os/windows.zig | 11 +++++- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index a43d414eb007..4758c846570a 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2629,14 +2629,16 @@ pub fn renameatW( }; defer windows.CloseHandle(src_fd); - 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 = @as(*windows.FILE_RENAME_INFORMATION, @ptrCast(&rename_info_buf)); + var need_fallback = true; + if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) { + 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 = @as(*windows.FILE_RENAME_INFORMATION_EX, @ptrCast(&rename_info_buf)); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; - if (builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) { 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.* = .{ @@ -2645,47 +2647,61 @@ pub fn renameatW( .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong .FileName = undefined, }; - } else { + @memcpy(@as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w); + const rc = windows.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformationEx, + ); + switch (rc) { + .SUCCESS => return, + // INVALID_PARAMETER here means that the filesystem does not support FileRenameInformationEx + .INVALID_PARAMETER => {}, + // For all other statuses, fall down to the switch below to handle them. + else => 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 = @as(*windows.FILE_RENAME_INFORMATION, @ptrCast(&rename_info_buf)); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + rename_info.* = .{ .Flags = ReplaceIfExists, .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(@as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w); - - var io_status_block: windows.IO_STATUS_BLOCK = undefined; + @memcpy(@as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w); - const rc = if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) { - windows.ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info, - @intCast(struct_len), // already checked for error.NameTooLong - .FileRenameInformationEx, - ); - } else { - windows.ntdll.NtSetInformationFile( + const rc = + windows.ntdll.NtSetInformationFile( src_fd, &io_status_block, rename_info, @intCast(struct_len), // already checked for error.NameTooLong .FileRenameInformation, ); - }; - switch (rc) { - .SUCCESS => return, - .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, - else => return windows.unexpectedStatus(rc), + 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, + else => return windows.unexpectedStatus(rc), + } } } diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 7fd98ac3de4a..d9cb6b6b27cd 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1009,6 +1009,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil .FileDispositionInformationEx, ); switch (rc) { + .SUCCESS => return, // 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. @@ -2870,7 +2871,15 @@ pub const FILE_RENAME_FORCE_RESIZE_SOURCE_SR = 0x00000100; pub const FILE_RENAME_FORCE_RESIZE_SR = 0x00000180; pub const FILE_RENAME_INFORMATION = extern struct { - Flags: if (builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) ULONG else BOOLEAN, + Flags: BOOLEAN, + RootDirectory: ?HANDLE, + FileNameLength: ULONG, + FileName: [1]WCHAR, +}; + +// FileRenameInformationEx (since .win10_rs1) +pub const FILE_RENAME_INFORMATION_EX = extern struct { + Flags: ULONG, RootDirectory: ?HANDLE, FileNameLength: ULONG, FileName: [1]WCHAR, From ac9195e2041108f445af8c6d14b36ef0f16234f4 Mon Sep 17 00:00:00 2001 From: Jan Philipp Hafer Date: Sun, 20 Aug 2023 13:20:23 +0200 Subject: [PATCH 3/4] fixup switch case --- lib/std/os.zig | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 4758c846570a..75e8a9de5eb1 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2630,6 +2630,7 @@ pub fn renameatW( defer windows.CloseHandle(src_fd); var need_fallback = true; + var rc: windows.NTSTATUS = undefined; if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) { 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; @@ -2648,7 +2649,7 @@ pub fn renameatW( .FileName = undefined, }; @memcpy(@as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w); - const rc = windows.ntdll.NtSetInformationFile( + rc = windows.ntdll.NtSetInformationFile( src_fd, &io_status_block, rename_info, @@ -2681,7 +2682,7 @@ pub fn renameatW( }; @memcpy(@as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w); - const rc = + rc = windows.ntdll.NtSetInformationFile( src_fd, &io_status_block, @@ -2689,19 +2690,19 @@ pub fn renameatW( @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, - else => return windows.unexpectedStatus(rc), - } + 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, + else => return windows.unexpectedStatus(rc), } } From 146671240cfe0b74e17adc217712d5ebfb2543a1 Mon Sep 17 00:00:00 2001 From: Jan Philipp Hafer Date: Mon, 21 Aug 2023 16:27:12 +0200 Subject: [PATCH 4/4] add posix-like errors --- lib/std/os.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/os.zig b/lib/std/os.zig index 75e8a9de5eb1..fbd0fd492f9c 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2660,6 +2660,9 @@ pub fn renameatW( .SUCCESS => return, // INVALID_PARAMETER here means that the filesystem does not support FileRenameInformationEx .INVALID_PARAMETER => {}, + .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, + .FILE_IS_A_DIRECTORY => return error.IsDir, + .NOT_A_DIRECTORY => return error.NotDir, // For all other statuses, fall down to the switch below to handle them. else => need_fallback = false, }