mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
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.
This commit is contained in:
parent
17ecc77fc4
commit
bf25816067
@ -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 {
|
pub fn RenameFile(
|
||||||
const old_path_w = try sliceToPrefixedFileW(null, old_path);
|
/// May only be `null` if `old_path_w` is a fully-qualified absolute path.
|
||||||
const new_path_w = try sliceToPrefixedFileW(null, new_path);
|
old_dir_fd: ?HANDLE,
|
||||||
return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags);
|
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 {
|
var rc: NTSTATUS = undefined;
|
||||||
if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
|
// FileRenameInformationEx has varying levels of support:
|
||||||
switch (GetLastError()) {
|
// - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
|
||||||
.FILE_NOT_FOUND => return error.FileNotFound,
|
// (INVALID_INFO_CLASS is returned if not supported)
|
||||||
.ACCESS_DENIED => return error.AccessDenied,
|
// - Requires the NTFS filesystem
|
||||||
else => |err| return unexpectedError(err),
|
// (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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -91,12 +91,6 @@ pub extern "kernel32" fn GetStdHandle(
|
|||||||
nStdHandle: DWORD,
|
nStdHandle: DWORD,
|
||||||
) callconv(.winapi) ?HANDLE;
|
) callconv(.winapi) ?HANDLE;
|
||||||
|
|
||||||
pub extern "kernel32" fn MoveFileExW(
|
|
||||||
lpExistingFileName: LPCWSTR,
|
|
||||||
lpNewFileName: LPCWSTR,
|
|
||||||
dwFlags: DWORD,
|
|
||||||
) callconv(.winapi) BOOL;
|
|
||||||
|
|
||||||
// TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`.
|
// TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`.
|
||||||
// `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END`
|
// `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END`
|
||||||
pub extern "kernel32" fn SetFilePointerEx(
|
pub extern "kernel32" fn SetFilePointerEx(
|
||||||
|
|||||||
@ -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.
|
/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded.
|
||||||
/// Assumes target is Windows.
|
/// Assumes target is Windows.
|
||||||
pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
|
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;
|
const cwd_handle = std.fs.cwd().fd;
|
||||||
return windows.MoveFileExW(old_path, new_path, flags);
|
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.
|
/// 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,
|
new_path_w: []const u16,
|
||||||
ReplaceIfExists: windows.BOOLEAN,
|
ReplaceIfExists: windows.BOOLEAN,
|
||||||
) RenameError!void {
|
) RenameError!void {
|
||||||
const src_fd = windows.OpenFile(old_path_w, .{
|
return windows.RenameFile(old_dir_fd, old_path_w, new_dir_fd, new_path_w, ReplaceIfExists != 0);
|
||||||
.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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user