authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-30 22:47:30+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-31 16:31:51+02:00
log8981b18fee9dc1db7faa8343d0d2151c3c0671fd
tree57fccb7375a3ef130b153ed5c12755d1f1d85de9
parent66bbe4ec4c11839217d6a9d65771d60d45cd6bc1

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.

5 files changed, 115 insertions(+), 136 deletions(-)

lib/std/fs.zig+18-5
......@@ -1117,7 +1117,7 @@ pub const Dir = struct {
11171117 pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
11181118 if (builtin.os.tag == .windows) {
11191119 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
1120 return self.deleteFileW(sub_path_w.span().ptr);
1120 return self.deleteFileW(sub_path_w.span());
11211121 } else if (builtin.os.tag == .wasi) {
11221122 os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
11231123 error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
......@@ -1151,7 +1151,7 @@ pub const Dir = struct {
11511151 }
11521152
11531153 /// Same as `deleteFile` except the parameter is WTF-16 encoded.
1154 pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void {
1154 pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
11551155 os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
11561156 error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
11571157 else => |e| return e,
......@@ -1180,7 +1180,7 @@ pub const Dir = struct {
11801180 pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
11811181 if (builtin.os.tag == .windows) {
11821182 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
1183 return self.deleteDirW(sub_path_w.span().ptr);
1183 return self.deleteDirW(sub_path_w.span());
11841184 } else if (builtin.os.tag == .wasi) {
11851185 os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) {
11861186 error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
......@@ -1202,7 +1202,7 @@ pub const Dir = struct {
12021202
12031203 /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
12041204 /// This function is Windows-only.
1205 pub fn deleteDirW(self: Dir, sub_path_w: [*:0]const u16) DeleteDirError!void {
1205 pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
12061206 os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
12071207 error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
12081208 else => |e| return e,
......@@ -1939,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
19391939 return walker;
19401940}
19411941
1942pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError;
1942pub const OpenSelfExeError = error{
1943 SharingViolation,
1944 PathAlreadyExists,
1945 FileNotFound,
1946 AccessDenied,
1947 PipeBusy,
1948 NameTooLong,
1949 /// On Windows, file paths must be valid Unicode.
1950 InvalidUtf8,
1951 /// On Windows, file paths cannot contain these characters:
1952 /// '/', '*', '?', '"', '<', '>', '|'
1953 BadPathName,
1954 Unexpected,
1955} || os.OpenError || SelfExePathError || os.FlockError;
19431956
19441957pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
19451958 if (builtin.os.tag == .linux) {
lib/std/fs/file.zig+14-1
......@@ -47,7 +47,20 @@ pub const File = struct {
4747 else => 0o666,
4848 };
4949
50 pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError;
50 pub const OpenError = error{
51 SharingViolation,
52 PathAlreadyExists,
53 FileNotFound,
54 AccessDenied,
55 PipeBusy,
56 NameTooLong,
57 /// On Windows, file paths must be valid Unicode.
58 InvalidUtf8,
59 /// On Windows, file paths cannot contain these characters:
60 /// '/', '*', '?', '"', '<', '>', '|'
61 BadPathName,
62 Unexpected,
63 } || os.OpenError || os.FlockError;
5164
5265 pub const Lock = enum { None, Shared, Exclusive };
5366
lib/std/fs/watch.zig+1-1
......@@ -379,7 +379,7 @@ pub fn Watch(comptime V: type) type {
379379 .access_mask = windows.FILE_LIST_DIRECTORY,
380380 .creation = windows.FILE_OPEN,
381381 .io_mode = .blocking,
382 .expect_dir = true,
382 .open_dir = true,
383383 });
384384 var dir_handle_consumed = false;
385385 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
lib/std/os.zig+22-67
......@@ -1683,7 +1683,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
16831683 @compileError("unlink is not supported in WASI; use unlinkat instead");
16841684 } else if (builtin.os.tag == .windows) {
16851685 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
1686 return windows.DeleteFileW(file_path_w.span().ptr);
1686 return unlinkW(file_path_w.span());
16871687 } else {
16881688 const file_path_c = try toPosixPath(file_path);
16891689 return unlinkZ(&file_path_c);
......@@ -1696,7 +1696,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ");
16961696pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
16971697 if (builtin.os.tag == .windows) {
16981698 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
1699 return windows.DeleteFileW(file_path_w.span().ptr);
1699 return unlinkW(file_path_w.span());
17001700 }
17011701 switch (errno(system.unlink(file_path))) {
17021702 0 => return,
......@@ -1717,6 +1717,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
17171717 }
17181718}
17191719
1720/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
1721pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
1722 return windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd });
1723}
1724
17201725pub const UnlinkatError = UnlinkError || error{
17211726 /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
17221727 DirNotEmpty,
......@@ -1727,7 +1732,7 @@ pub const UnlinkatError = UnlinkError || error{
17271732pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
17281733 if (builtin.os.tag == .windows) {
17291734 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
1730 return unlinkatW(dirfd, file_path_w.span().ptr, flags);
1735 return unlinkatW(dirfd, file_path_w.span(), flags);
17311736 } else if (builtin.os.tag == .wasi) {
17321737 return unlinkatWasi(dirfd, file_path, flags);
17331738 } else {
......@@ -1774,7 +1779,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
17741779pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
17751780 if (builtin.os.tag == .windows) {
17761781 const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
1777 return unlinkatW(dirfd, file_path_w.span().ptr, flags);
1782 return unlinkatW(dirfd, file_path_w.span(), flags);
17781783 }
17791784 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
17801785 0 => return,
......@@ -1800,67 +1805,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
18001805}
18011806
18021807/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
1803pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void {
1804 const w = windows;
1805
1806 const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
1807 const create_options_flags = if (want_rmdir_behavior)
1808 @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT)
1809 else
1810 @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?
1811
1812 const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
1813 var nt_name = w.UNICODE_STRING{
1814 .Length = path_len_bytes,
1815 .MaximumLength = path_len_bytes,
1816 // The Windows API makes this mutable, but it will not mutate here.
1817 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
1818 };
1819
1820 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
1821 // Windows does not recognize this, but it does work with empty string.
1822 nt_name.Length = 0;
1823 }
1824 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
1825 // Can't remove the parent directory with an open handle.
1826 return error.FileBusy;
1827 }
1828
1829 var attr = w.OBJECT_ATTRIBUTES{
1830 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1831 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
1832 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1833 .ObjectName = &nt_name,
1834 .SecurityDescriptor = null,
1835 .SecurityQualityOfService = null,
1836 };
1837 var io: w.IO_STATUS_BLOCK = undefined;
1838 var tmp_handle: w.HANDLE = undefined;
1839 var rc = w.ntdll.NtCreateFile(
1840 &tmp_handle,
1841 w.SYNCHRONIZE | w.DELETE,
1842 &attr,
1843 &io,
1844 null,
1845 0,
1846 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
1847 w.FILE_OPEN,
1848 create_options_flags,
1849 null,
1850 0,
1851 );
1852 if (rc == .SUCCESS) {
1853 rc = w.ntdll.NtClose(tmp_handle);
1854 }
1855 switch (rc) {
1856 .SUCCESS => return,
1857 .OBJECT_NAME_INVALID => unreachable,
1858 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1859 .INVALID_PARAMETER => unreachable,
1860 .FILE_IS_A_DIRECTORY => return error.IsDir,
1861 .NOT_A_DIRECTORY => return error.NotDir,
1862 else => return w.unexpectedStatus(rc),
1863 }
1808pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
1809 const remove_dir = (flags & AT_REMOVEDIR) != 0;
1810 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
18641811}
18651812
18661813const RenameError = error{
......@@ -2256,7 +2203,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
22562203 @compileError("rmdir is not supported in WASI; use unlinkat instead");
22572204 } else if (builtin.os.tag == .windows) {
22582205 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
2259 return windows.RemoveDirectoryW(dir_path_w.span().ptr);
2206 return rmdirW(dir_path_w.span());
22602207 } else {
22612208 const dir_path_c = try toPosixPath(dir_path);
22622209 return rmdirZ(&dir_path_c);
......@@ -2269,7 +2216,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ");
22692216pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
22702217 if (builtin.os.tag == .windows) {
22712218 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
2272 return windows.RemoveDirectoryW(dir_path_w.span().ptr);
2219 return rmdirW(dir_path_w.span());
22732220 }
22742221 switch (errno(system.rmdir(dir_path))) {
22752222 0 => return,
......@@ -2290,6 +2237,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
22902237 }
22912238}
22922239
2240/// Windows-only. Same as `rmdir` except the parameter is null-terminated, WTF16 encoded.
2241pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
2242 return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
2243 error.IsDir => unreachable,
2244 else => |e| return e,
2245 };
2246}
2247
22932248pub const ChangeCurDirError = error{
22942249 AccessDenied,
22952250 FileSystem,
lib/std/os/windows.zig+60-62
......@@ -25,30 +25,6 @@ pub usingnamespace @import("windows/bits.zig");
2525
2626pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
2727
28pub const CreateFileError = error{
29 SharingViolation,
30 PathAlreadyExists,
31
32 /// When any of the path components can not be found or the file component can not
33 /// be found. Some operating systems distinguish between path components not found and
34 /// file components not found, but they are collapsed into FileNotFound to gain
35 /// consistency across operating systems.
36 FileNotFound,
37
38 AccessDenied,
39 PipeBusy,
40 NameTooLong,
41
42 /// On Windows, file paths must be valid Unicode.
43 InvalidUtf8,
44
45 /// On Windows, file paths cannot contain these characters:
46 /// '/', '*', '?', '"', '<', '>', '|'
47 BadPathName,
48
49 Unexpected,
50};
51
5228pub const OpenError = error{
5329 IsDir,
5430 FileNotFound,
......@@ -729,24 +705,69 @@ pub const DeleteFileError = error{
729705 NameTooLong,
730706 FileBusy,
731707 Unexpected,
708 NotDir,
709 IsDir,
732710};
733711
734pub fn DeleteFile(filename: []const u8) DeleteFileError!void {
735 const filename_w = try sliceToPrefixedFileW(filename);
736 return DeleteFileW(filename_w.span().ptr);
737}
712pub const DeleteFileOptions = struct {
713 dir: ?HANDLE,
714 remove_dir: bool = false,
715};
738716
739pub fn DeleteFileW(filename: [*:0]const u16) DeleteFileError!void {
740 if (kernel32.DeleteFileW(filename) == 0) {
741 switch (kernel32.GetLastError()) {
742 .FILE_NOT_FOUND => return error.FileNotFound,
743 .PATH_NOT_FOUND => return error.FileNotFound,
744 .ACCESS_DENIED => return error.AccessDenied,
745 .FILENAME_EXCED_RANGE => return error.NameTooLong,
746 .INVALID_PARAMETER => return error.NameTooLong,
747 .SHARING_VIOLATION => return error.FileBusy,
748 else => |err| return unexpectedError(err),
749 }
717pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
718 const create_options_flags: ULONG = if (options.remove_dir)
719 FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT
720 else
721 FILE_DELETE_ON_CLOSE | FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead?
722
723 const path_len_bytes = @intCast(u16, sub_path_w.len * 2);
724 var nt_name = UNICODE_STRING{
725 .Length = path_len_bytes,
726 .MaximumLength = path_len_bytes,
727 // The Windows API makes this mutable, but it will not mutate here.
728 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
729 };
730
731 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
732 // Windows does not recognize this, but it does work with empty string.
733 nt_name.Length = 0;
734 }
735 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
736 // Can't remove the parent directory with an open handle.
737 return error.FileBusy;
738 }
739
740 var attr = OBJECT_ATTRIBUTES{
741 .Length = @sizeOf(OBJECT_ATTRIBUTES),
742 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
743 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
744 .ObjectName = &nt_name,
745 .SecurityDescriptor = null,
746 .SecurityQualityOfService = null,
747 };
748 var io: IO_STATUS_BLOCK = undefined;
749 var tmp_handle: HANDLE = undefined;
750 var rc = ntdll.NtCreateFile(
751 &tmp_handle,
752 SYNCHRONIZE | DELETE,
753 &attr,
754 &io,
755 null,
756 0,
757 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
758 FILE_OPEN,
759 create_options_flags,
760 null,
761 0,
762 );
763 switch (rc) {
764 .SUCCESS => return CloseHandle(tmp_handle),
765 .OBJECT_NAME_INVALID => unreachable,
766 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
767 .INVALID_PARAMETER => unreachable,
768 .FILE_IS_A_DIRECTORY => return error.IsDir,
769 .NOT_A_DIRECTORY => return error.NotDir,
770 else => return unexpectedStatus(rc),
750771 }
751772}
752773
......@@ -766,29 +787,6 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW
766787 }
767788}
768789
769pub const RemoveDirectoryError = error{
770 FileNotFound,
771 DirNotEmpty,
772 Unexpected,
773 NotDir,
774};
775
776pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void {
777 const dir_path_w = try sliceToPrefixedFileW(dir_path);
778 return RemoveDirectoryW(dir_path_w.span().ptr);
779}
780
781pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void {
782 if (kernel32.RemoveDirectoryW(dir_path_w) == 0) {
783 switch (kernel32.GetLastError()) {
784 .PATH_NOT_FOUND => return error.FileNotFound,
785 .DIR_NOT_EMPTY => return error.DirNotEmpty,
786 .DIRECTORY => return error.NotDir,
787 else => |err| return unexpectedError(err),
788 }
789 }
790}
791
792790pub const GetStdHandleError = error{
793791 NoStandardHandleAttached,
794792 Unexpected,