diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 1fbd7dbd63..c953b529fa 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -488,10 +488,7 @@ test "deleteDir" { dir.close(); // deleting a non-empty directory - // TODO: Re-enable this check on Windows, see https://github.com/ziglang/zig/issues/5537 - if (builtin.os.tag != .windows) { - try testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir")); - } + try testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir")); dir = try tmp_dir.dir.openDir("test_dir", .{}); try dir.deleteFile("test_file"); @@ -1418,3 +1415,22 @@ test "File.PermissionsUnix" { try testing.expect(permissions_unix.unixHas(.user, .execute)); try testing.expect(!permissions_unix.unixHas(.other, .execute)); } + +test "delete a read-only file on windows" { + if (builtin.os.tag != .windows) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + const file = try tmp.dir.createFile("test_file", .{ .read = true }); + // Create a file and make it read-only + const metadata = try file.metadata(); + var permissions = metadata.permissions(); + permissions.setReadOnly(true); + try file.setPermissions(permissions); + try testing.expectError(error.AccessDenied, tmp.dir.deleteFile("test_file")); + // Now make the file not read-only + permissions.setReadOnly(false); + try file.setPermissions(permissions); + file.close(); + try tmp.dir.deleteFile("test_file"); +} diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index f8672bc58d..7a2d75f345 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -870,6 +870,7 @@ pub const DeleteFileError = error{ Unexpected, NotDir, IsDir, + DirNotEmpty, }; pub const DeleteFileOptions = struct { @@ -879,9 +880,9 @@ pub const DeleteFileOptions = struct { 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 + 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? + 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{ @@ -924,7 +925,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil 0, ); switch (rc) { - .SUCCESS => return CloseHandle(tmp_handle), + .SUCCESS => {}, .OBJECT_NAME_INVALID => unreachable, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, @@ -932,7 +933,28 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil .FILE_IS_A_DIRECTORY => return error.IsDir, .NOT_A_DIRECTORY => return error.NotDir, .SHARING_VIOLATION => return error.FileBusy, + .ACCESS_DENIED => return error.AccessDenied, + .DELETE_PENDING => return, + else => return unexpectedStatus(rc), + } + var file_dispo = FILE_DISPOSITION_INFORMATION{ + .DeleteFile = TRUE, + }; + rc = ntdll.NtSetInformationFile( + tmp_handle, + &io, + &file_dispo, + @sizeOf(FILE_DISPOSITION_INFORMATION), + .FileDispositionInformation, + ); + CloseHandle(tmp_handle); + switch (rc) { + .SUCCESS => return, + .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, + .INVALID_PARAMETER => unreachable, .CANNOT_DELETE => return error.AccessDenied, + .MEDIA_WRITE_PROTECTED => return error.AccessDenied, + .ACCESS_DENIED => return error.AccessDenied, else => return unexpectedStatus(rc), } } @@ -2470,6 +2492,10 @@ pub const FILE_INFORMATION_CLASS = enum(c_int) { FileMaximumInformation, }; +pub const FILE_DISPOSITION_INFORMATION = extern struct { + DeleteFile: BOOLEAN, +}; + pub const FILE_FS_DEVICE_INFORMATION = extern struct { DeviceType: DEVICE_TYPE, Characteristics: ULONG,