Skip to content

std.windows: use posix semantics to delete files, if available #15501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions lib/std/fs/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1416,23 +1416,42 @@ test "File.PermissionsUnix" {
try testing.expect(!permissions_unix.unixHas(.other, .execute));
}

test "delete a read-only file on windows" {
if (builtin.os.tag != .windows) return error.SkipZigTest;
test "delete a read-only file on windows with file pending semantics" {
if (builtin.os.tag != .windows or builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1))
return error.SkipZigTest;

var tmp = tmpDir(.{});
defer tmp.cleanup();
{
const file = try tmp.dir.createFile("test_file", .{ .read = true });
defer file.close();
// 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);
}
try tmp.dir.deleteFile("test_file");
}

test "delete a read-only file on windows with posix semantis" {
if (builtin.os.tag != .windows or !builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1))
return error.SkipZigTest;

var tmp = tmpDir(.{});
defer tmp.cleanup();
const file = try tmp.dir.createFile("test_file", .{ .read = true });
defer file.close();
// 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");
try tmp.dir.deleteFile("test_file"); // file is unmapped and deleted once last handle closed
}

test "delete a setAsCwd directory on Windows" {
Expand Down
57 changes: 45 additions & 12 deletions lib/std/os/windows.zig
Original file line number Diff line number Diff line change
Expand Up @@ -937,19 +937,40 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
.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);
defer CloseHandle(tmp_handle);

if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs1)) {
// Deletion with posix semantics.
var info = FILE_DISPOSITION_INFORMATION_EX{
.Flags = FILE_DISPOSITION_DELETE |
FILE_DISPOSITION_POSIX_SEMANTICS |
FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
};

rc = ntdll.NtSetInformationFile(
tmp_handle,
&io,
&info,
@sizeOf(FILE_DISPOSITION_INFORMATION_EX),
.FileDispositionInformationEx,
);
} else {
// Deletion with file pending semantics, which requires waiting or moving
// files to get them removed (from here).
var file_dispo = FILE_DISPOSITION_INFORMATION{
.DeleteFile = TRUE,
};

rc = ntdll.NtSetInformationFile(
tmp_handle,
&io,
&file_dispo,
@sizeOf(FILE_DISPOSITION_INFORMATION),
.FileDispositionInformation,
);
}
switch (rc) {
.SUCCESS => return,
.SUCCESS => {},
.DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
.INVALID_PARAMETER => unreachable,
.CANNOT_DELETE => return error.AccessDenied,
Expand Down Expand Up @@ -2397,6 +2418,18 @@ pub const FILE_NAME_INFORMATION = extern struct {
FileName: [1]WCHAR,
};

pub const FILE_DISPOSITION_INFORMATION_EX = extern struct {
/// combination of FILE_DISPOSITION_* flags
Flags: ULONG,
};

const FILE_DISPOSITION_DO_NOT_DELETE: ULONG = 0x00000000;
Copy link
Contributor Author

@matu3ba matu3ba Apr 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I namespace these?

const FILE_DISPOSITION_DELETE: ULONG = 0x00000001;
const FILE_DISPOSITION_POSIX_SEMANTICS: ULONG = 0x00000002;
const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004;
const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008;
const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010;

pub const FILE_RENAME_INFORMATION = extern struct {
ReplaceIfExists: BOOLEAN,
RootDirectory: ?HANDLE,
Expand Down