Move delete file logic into windows.DeleteFile fn
This way, we can remove more `kernel32` calls such as `RemoveDirectoryW` or `DeleteFileW`, and use `std.os.windows.DeleteFile` instead which is purely NT-based.master
parent
66bbe4ec4c
commit
8981b18fee
|
@ -1117,7 +1117,7 @@ pub const Dir = struct {
|
|||
pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
||||
return self.deleteFileW(sub_path_w.span().ptr);
|
||||
return self.deleteFileW(sub_path_w.span());
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
|
||||
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
|
||||
|
@ -1151,7 +1151,7 @@ pub const Dir = struct {
|
|||
}
|
||||
|
||||
/// Same as `deleteFile` except the parameter is WTF-16 encoded.
|
||||
pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void {
|
||||
pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
|
||||
os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
|
||||
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
|
||||
else => |e| return e,
|
||||
|
@ -1180,7 +1180,7 @@ pub const Dir = struct {
|
|||
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
||||
return self.deleteDirW(sub_path_w.span().ptr);
|
||||
return self.deleteDirW(sub_path_w.span());
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) {
|
||||
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
|
||||
|
@ -1202,7 +1202,7 @@ pub const Dir = struct {
|
|||
|
||||
/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
|
||||
/// This function is Windows-only.
|
||||
pub fn deleteDirW(self: Dir, sub_path_w: [*:0]const u16) DeleteDirError!void {
|
||||
pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
|
||||
os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
|
||||
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
|
||||
else => |e| return e,
|
||||
|
@ -1939,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
|
|||
return walker;
|
||||
}
|
||||
|
||||
pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError;
|
||||
pub const OpenSelfExeError = error{
|
||||
SharingViolation,
|
||||
PathAlreadyExists,
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
PipeBusy,
|
||||
NameTooLong,
|
||||
/// On Windows, file paths must be valid Unicode.
|
||||
InvalidUtf8,
|
||||
/// On Windows, file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
Unexpected,
|
||||
} || os.OpenError || SelfExePathError || os.FlockError;
|
||||
|
||||
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
|
||||
if (builtin.os.tag == .linux) {
|
||||
|
|
|
@ -47,7 +47,20 @@ pub const File = struct {
|
|||
else => 0o666,
|
||||
};
|
||||
|
||||
pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError;
|
||||
pub const OpenError = error{
|
||||
SharingViolation,
|
||||
PathAlreadyExists,
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
PipeBusy,
|
||||
NameTooLong,
|
||||
/// On Windows, file paths must be valid Unicode.
|
||||
InvalidUtf8,
|
||||
/// On Windows, file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
Unexpected,
|
||||
} || os.OpenError || os.FlockError;
|
||||
|
||||
pub const Lock = enum { None, Shared, Exclusive };
|
||||
|
||||
|
|
|
@ -379,7 +379,7 @@ pub fn Watch(comptime V: type) type {
|
|||
.access_mask = windows.FILE_LIST_DIRECTORY,
|
||||
.creation = windows.FILE_OPEN,
|
||||
.io_mode = .blocking,
|
||||
.expect_dir = true,
|
||||
.open_dir = true,
|
||||
});
|
||||
var dir_handle_consumed = false;
|
||||
defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
|
||||
|
|
|
@ -1683,7 +1683,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
|
|||
@compileError("unlink is not supported in WASI; use unlinkat instead");
|
||||
} else if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||||
return windows.DeleteFileW(file_path_w.span().ptr);
|
||||
return unlinkW(file_path_w.span());
|
||||
} else {
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return unlinkZ(&file_path_c);
|
||||
|
@ -1696,7 +1696,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ");
|
|||
pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
|
||||
return windows.DeleteFileW(file_path_w.span().ptr);
|
||||
return unlinkW(file_path_w.span());
|
||||
}
|
||||
switch (errno(system.unlink(file_path))) {
|
||||
0 => return,
|
||||
|
@ -1717,6 +1717,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
|
|||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
|
||||
pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
|
||||
return windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd });
|
||||
}
|
||||
|
||||
pub const UnlinkatError = UnlinkError || error{
|
||||
/// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
|
||||
DirNotEmpty,
|
||||
|
@ -1727,7 +1732,7 @@ pub const UnlinkatError = UnlinkError || error{
|
|||
pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||||
return unlinkatW(dirfd, file_path_w.span().ptr, flags);
|
||||
return unlinkatW(dirfd, file_path_w.span(), flags);
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
return unlinkatWasi(dirfd, file_path, flags);
|
||||
} else {
|
||||
|
@ -1774,7 +1779,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
|
|||
pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
|
||||
return unlinkatW(dirfd, file_path_w.span().ptr, flags);
|
||||
return unlinkatW(dirfd, file_path_w.span(), flags);
|
||||
}
|
||||
switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
|
||||
0 => return,
|
||||
|
@ -1800,67 +1805,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
|
|||
}
|
||||
|
||||
/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
|
||||
pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void {
|
||||
const w = windows;
|
||||
|
||||
const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
|
||||
const create_options_flags = if (want_rmdir_behavior)
|
||||
@as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT)
|
||||
else
|
||||
@as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT); // would we ever want to delete the target instead?
|
||||
|
||||
const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
|
||||
var nt_name = w.UNICODE_STRING{
|
||||
.Length = path_len_bytes,
|
||||
.MaximumLength = path_len_bytes,
|
||||
// The Windows API makes this mutable, but it will not mutate here.
|
||||
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
|
||||
};
|
||||
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
|
||||
// Windows does not recognize this, but it does work with empty string.
|
||||
nt_name.Length = 0;
|
||||
}
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
|
||||
// Can't remove the parent directory with an open handle.
|
||||
return error.FileBusy;
|
||||
}
|
||||
|
||||
var attr = w.OBJECT_ATTRIBUTES{
|
||||
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
|
||||
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: w.IO_STATUS_BLOCK = undefined;
|
||||
var tmp_handle: w.HANDLE = undefined;
|
||||
var rc = w.ntdll.NtCreateFile(
|
||||
&tmp_handle,
|
||||
w.SYNCHRONIZE | w.DELETE,
|
||||
&attr,
|
||||
&io,
|
||||
null,
|
||||
0,
|
||||
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
|
||||
w.FILE_OPEN,
|
||||
create_options_flags,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
if (rc == .SUCCESS) {
|
||||
rc = w.ntdll.NtClose(tmp_handle);
|
||||
}
|
||||
switch (rc) {
|
||||
.SUCCESS => return,
|
||||
.OBJECT_NAME_INVALID => unreachable,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
||||
.NOT_A_DIRECTORY => return error.NotDir,
|
||||
else => return w.unexpectedStatus(rc),
|
||||
}
|
||||
pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
|
||||
const remove_dir = (flags & AT_REMOVEDIR) != 0;
|
||||
return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
|
||||
}
|
||||
|
||||
const RenameError = error{
|
||||
|
@ -2256,7 +2203,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
|
|||
@compileError("rmdir is not supported in WASI; use unlinkat instead");
|
||||
} else if (builtin.os.tag == .windows) {
|
||||
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
|
||||
return windows.RemoveDirectoryW(dir_path_w.span().ptr);
|
||||
return rmdirW(dir_path_w.span());
|
||||
} else {
|
||||
const dir_path_c = try toPosixPath(dir_path);
|
||||
return rmdirZ(&dir_path_c);
|
||||
|
@ -2269,7 +2216,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ");
|
|||
pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
|
||||
return windows.RemoveDirectoryW(dir_path_w.span().ptr);
|
||||
return rmdirW(dir_path_w.span());
|
||||
}
|
||||
switch (errno(system.rmdir(dir_path))) {
|
||||
0 => return,
|
||||
|
@ -2290,6 +2237,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
|
|||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `rmdir` except the parameter is null-terminated, WTF16 encoded.
|
||||
pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
|
||||
return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
|
||||
error.IsDir => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const ChangeCurDirError = error{
|
||||
AccessDenied,
|
||||
FileSystem,
|
||||
|
|
|
@ -25,30 +25,6 @@ pub usingnamespace @import("windows/bits.zig");
|
|||
|
||||
pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
|
||||
|
||||
pub const CreateFileError = error{
|
||||
SharingViolation,
|
||||
PathAlreadyExists,
|
||||
|
||||
/// When any of the path components can not be found or the file component can not
|
||||
/// be found. Some operating systems distinguish between path components not found and
|
||||
/// file components not found, but they are collapsed into FileNotFound to gain
|
||||
/// consistency across operating systems.
|
||||
FileNotFound,
|
||||
|
||||
AccessDenied,
|
||||
PipeBusy,
|
||||
NameTooLong,
|
||||
|
||||
/// On Windows, file paths must be valid Unicode.
|
||||
InvalidUtf8,
|
||||
|
||||
/// On Windows, file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub const OpenError = error{
|
||||
IsDir,
|
||||
FileNotFound,
|
||||
|
@ -729,24 +705,69 @@ pub const DeleteFileError = error{
|
|||
NameTooLong,
|
||||
FileBusy,
|
||||
Unexpected,
|
||||
NotDir,
|
||||
IsDir,
|
||||
};
|
||||
|
||||
pub fn DeleteFile(filename: []const u8) DeleteFileError!void {
|
||||
const filename_w = try sliceToPrefixedFileW(filename);
|
||||
return DeleteFileW(filename_w.span().ptr);
|
||||
}
|
||||
pub const DeleteFileOptions = struct {
|
||||
dir: ?HANDLE,
|
||||
remove_dir: bool = false,
|
||||
};
|
||||
|
||||
pub fn DeleteFileW(filename: [*:0]const u16) DeleteFileError!void {
|
||||
if (kernel32.DeleteFileW(filename) == 0) {
|
||||
switch (kernel32.GetLastError()) {
|
||||
.FILE_NOT_FOUND => return error.FileNotFound,
|
||||
.PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.FILENAME_EXCED_RANGE => return error.NameTooLong,
|
||||
.INVALID_PARAMETER => return error.NameTooLong,
|
||||
.SHARING_VIOLATION => return error.FileBusy,
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
|
||||
const create_options_flags: ULONG = if (options.remove_dir)
|
||||
FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT
|
||||
else
|
||||
FILE_DELETE_ON_CLOSE | FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead?
|
||||
|
||||
const path_len_bytes = @intCast(u16, sub_path_w.len * 2);
|
||||
var nt_name = UNICODE_STRING{
|
||||
.Length = path_len_bytes,
|
||||
.MaximumLength = path_len_bytes,
|
||||
// The Windows API makes this mutable, but it will not mutate here.
|
||||
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
|
||||
};
|
||||
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
|
||||
// Windows does not recognize this, but it does work with empty string.
|
||||
nt_name.Length = 0;
|
||||
}
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
|
||||
// Can't remove the parent directory with an open handle.
|
||||
return error.FileBusy;
|
||||
}
|
||||
|
||||
var attr = OBJECT_ATTRIBUTES{
|
||||
.Length = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
|
||||
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: IO_STATUS_BLOCK = undefined;
|
||||
var tmp_handle: HANDLE = undefined;
|
||||
var rc = ntdll.NtCreateFile(
|
||||
&tmp_handle,
|
||||
SYNCHRONIZE | DELETE,
|
||||
&attr,
|
||||
&io,
|
||||
null,
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
FILE_OPEN,
|
||||
create_options_flags,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => return CloseHandle(tmp_handle),
|
||||
.OBJECT_NAME_INVALID => unreachable,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
||||
.NOT_A_DIRECTORY => return error.NotDir,
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -766,29 +787,6 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW
|
|||
}
|
||||
}
|
||||
|
||||
pub const RemoveDirectoryError = error{
|
||||
FileNotFound,
|
||||
DirNotEmpty,
|
||||
Unexpected,
|
||||
NotDir,
|
||||
};
|
||||
|
||||
pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void {
|
||||
const dir_path_w = try sliceToPrefixedFileW(dir_path);
|
||||
return RemoveDirectoryW(dir_path_w.span().ptr);
|
||||
}
|
||||
|
||||
pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void {
|
||||
if (kernel32.RemoveDirectoryW(dir_path_w) == 0) {
|
||||
switch (kernel32.GetLastError()) {
|
||||
.PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.DIR_NOT_EMPTY => return error.DirNotEmpty,
|
||||
.DIRECTORY => return error.NotDir,
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const GetStdHandleError = error{
|
||||
NoStandardHandleAttached,
|
||||
Unexpected,
|
||||
|
|
Loading…
Reference in New Issue