Skip to content

Commit 7a834e2

Browse files
author
matu3ba
authored
std.windows: use atomic rename, if possible (#16717)
Mitigates #14978. Uses the same strategy as in #16499 suggested by @squeek502
1 parent 6780a6b commit 7a834e2

File tree

2 files changed

+87
-24
lines changed

2 files changed

+87
-24
lines changed

lib/std/os.zig

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2629,33 +2629,74 @@ pub fn renameatW(
26292629
};
26302630
defer windows.CloseHandle(src_fd);
26312631

2632-
const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (MAX_PATH_BYTES - 1);
2633-
var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined;
2634-
const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path_w.len * 2;
2635-
if (struct_len > struct_buf_len) return error.NameTooLong;
2636-
2637-
const rename_info = @as(*windows.FILE_RENAME_INFORMATION, @ptrCast(&rename_info_buf));
2638-
2639-
rename_info.* = .{
2640-
.ReplaceIfExists = ReplaceIfExists,
2641-
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd,
2642-
.FileNameLength = @as(u32, @intCast(new_path_w.len * 2)), // already checked error.NameTooLong
2643-
.FileName = undefined,
2644-
};
2645-
@memcpy(@as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w);
2632+
var need_fallback = true;
2633+
var rc: windows.NTSTATUS = undefined;
2634+
if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) {
2635+
const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (MAX_PATH_BYTES - 1);
2636+
var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined;
2637+
const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2;
2638+
if (struct_len > struct_buf_len) return error.NameTooLong;
2639+
2640+
const rename_info = @as(*windows.FILE_RENAME_INFORMATION_EX, @ptrCast(&rename_info_buf));
2641+
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
26462642

2647-
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
2643+
var flags: windows.ULONG = windows.FILE_RENAME_POSIX_SEMANTICS | windows.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE;
2644+
if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS;
2645+
rename_info.* = .{
2646+
.Flags = flags,
2647+
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd,
2648+
.FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
2649+
.FileName = undefined,
2650+
};
2651+
@memcpy(@as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w);
2652+
rc = windows.ntdll.NtSetInformationFile(
2653+
src_fd,
2654+
&io_status_block,
2655+
rename_info,
2656+
@intCast(struct_len), // already checked for error.NameTooLong
2657+
.FileRenameInformationEx,
2658+
);
2659+
switch (rc) {
2660+
.SUCCESS => return,
2661+
// INVALID_PARAMETER here means that the filesystem does not support FileRenameInformationEx
2662+
.INVALID_PARAMETER => {},
2663+
.DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
2664+
.FILE_IS_A_DIRECTORY => return error.IsDir,
2665+
.NOT_A_DIRECTORY => return error.NotDir,
2666+
// For all other statuses, fall down to the switch below to handle them.
2667+
else => need_fallback = false,
2668+
}
2669+
}
26482670

2649-
const rc = windows.ntdll.NtSetInformationFile(
2650-
src_fd,
2651-
&io_status_block,
2652-
rename_info,
2653-
@as(u32, @intCast(struct_len)), // already checked for error.NameTooLong
2654-
.FileRenameInformation,
2655-
);
2671+
if (need_fallback) {
2672+
const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (MAX_PATH_BYTES - 1);
2673+
var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined;
2674+
const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path_w.len * 2;
2675+
if (struct_len > struct_buf_len) return error.NameTooLong;
2676+
2677+
const rename_info = @as(*windows.FILE_RENAME_INFORMATION, @ptrCast(&rename_info_buf));
2678+
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
2679+
2680+
rename_info.* = .{
2681+
.Flags = ReplaceIfExists,
2682+
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd,
2683+
.FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
2684+
.FileName = undefined,
2685+
};
2686+
@memcpy(@as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w);
2687+
2688+
rc =
2689+
windows.ntdll.NtSetInformationFile(
2690+
src_fd,
2691+
&io_status_block,
2692+
rename_info,
2693+
@intCast(struct_len), // already checked for error.NameTooLong
2694+
.FileRenameInformation,
2695+
);
2696+
}
26562697

26572698
switch (rc) {
2658-
.SUCCESS => return,
2699+
.SUCCESS => {},
26592700
.INVALID_HANDLE => unreachable,
26602701
.INVALID_PARAMETER => unreachable,
26612702
.OBJECT_PATH_SYNTAX_BAD => unreachable,

lib/std/os/windows.zig

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
10091009
.FileDispositionInformationEx,
10101010
);
10111011
switch (rc) {
1012+
.SUCCESS => return,
10121013
// INVALID_PARAMETER here means that the filesystem does not support FileDispositionInformationEx
10131014
.INVALID_PARAMETER => {},
10141015
// For all other statuses, fall down to the switch below to handle them.
@@ -2856,8 +2857,29 @@ const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004;
28562857
const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008;
28572858
const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010;
28582859

2860+
// FILE_RENAME_INFORMATION.Flags
2861+
pub const FILE_RENAME_REPLACE_IF_EXISTS = 0x00000001;
2862+
pub const FILE_RENAME_POSIX_SEMANTICS = 0x00000002;
2863+
pub const FILE_RENAME_SUPPRESS_PIN_STATE_INHERITANCE = 0x00000004;
2864+
pub const FILE_RENAME_SUPPRESS_STORAGE_RESERVE_INHERITANCE = 0x00000008;
2865+
pub const FILE_RENAME_NO_INCREASE_AVAILABLE_SPACE = 0x00000010;
2866+
pub const FILE_RENAME_NO_DECREASE_AVAILABLE_SPACE = 0x00000020;
2867+
pub const FILE_RENAME_PRESERVE_AVAILABLE_SPACE = 0x00000030;
2868+
pub const FILE_RENAME_IGNORE_READONLY_ATTRIBUTE = 0x00000040;
2869+
pub const FILE_RENAME_FORCE_RESIZE_TARGET_SR = 0x00000080;
2870+
pub const FILE_RENAME_FORCE_RESIZE_SOURCE_SR = 0x00000100;
2871+
pub const FILE_RENAME_FORCE_RESIZE_SR = 0x00000180;
2872+
28592873
pub const FILE_RENAME_INFORMATION = extern struct {
2860-
ReplaceIfExists: BOOLEAN,
2874+
Flags: BOOLEAN,
2875+
RootDirectory: ?HANDLE,
2876+
FileNameLength: ULONG,
2877+
FileName: [1]WCHAR,
2878+
};
2879+
2880+
// FileRenameInformationEx (since .win10_rs1)
2881+
pub const FILE_RENAME_INFORMATION_EX = extern struct {
2882+
Flags: ULONG,
28612883
RootDirectory: ?HANDLE,
28622884
FileNameLength: ULONG,
28632885
FileName: [1]WCHAR,

0 commit comments

Comments
 (0)