Commit 8981b18fee

Jakub Konka <kubkon@jakubkonka.com>
2020-07-30 22:47:30
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.
1 parent 66bbe4e
Changed files (5)
lib/std/fs/file.zig
@@ -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 };
 
lib/std/fs/watch.zig
@@ -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);
lib/std/os/windows.zig
@@ -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,
lib/std/fs.zig
@@ -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) {
lib/std/os.zig
@@ -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,