Skip to content

windows: use NtSetInformationFile in DeleteFile. #15316

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 5 commits into from
Apr 20, 2023

Conversation

xEgoist
Copy link
Contributor

@xEgoist xEgoist commented Apr 16, 2023

closes #15315
closes #5537

Using FILE_DELETE_ON_CLOSE can silently succeed without reporting any error on non-empty directory. One way to return an error is to add NtSetInformationFile which will cause DeleteFile to report DIRECTORY_NOT_EMPTY.

@squeek502
Copy link
Collaborator

Should also enable this test case:

zig/lib/std/fs/test.zig

Lines 490 to 494 in 397649f

// 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"));
}

xEgoist added 2 commits April 16, 2023 15:18
Using `FILE_DELETE_ON_CLOSE` can silently succeed without reporting any error
on non-empty directory. This commit adds usage of NtSetInformationFile
which will report `DIRECTORY_NOT_EMPTY`.
@xEgoist
Copy link
Contributor Author

xEgoist commented Apr 16, 2023

Should also enable this test case:

zig/lib/std/fs/test.zig

Lines 490 to 494 in 397649f

// 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"));
}

Will do, thanks for pointing that.

@@ -935,6 +936,22 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
.CANNOT_DELETE => return error.AccessDenied,
Copy link
Collaborator

@squeek502 squeek502 Apr 17, 2023

Choose a reason for hiding this comment

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

Because this NtCreateFile call does not use FILE_DELETE_ON_CLOSE anymore, it seems likely that this error is no longer possible here but likely is possible from the NtSetInformationFile call.

More context:

It'd probably be good to add a test case that intentionally tries to delete a read-only file so that we know this case continues to be handled in the same way. Maybe something like (totally untested):

test "deleting a read-only file on windows" {
    if (builtin.os.tag != .windows) return error.SkipZigTest;

    var tmp = tmpDir(.{});
    defer tmp.cleanup();

    // Create a file and make it read-only
    {
        const file = try tmp.dir.createFile("test_file", .{ .read = true });
        defer file.close();
        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
    {
        const file = try tmp.dir.openFile("test_file", .{});
        defer file.close();
        const metadata = try file.metadata();
        var permissions = metadata.permissions();
        permissions.setReadOnly(false);
        try file.setPermissions(permissions);
    }
    try tmp.dir.deleteFile("test_file");
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right. It should be moved to the set function return.
Also, while testing this, I found out that NtCreateFile can also result in AccessDenied, when the owner is different. So we can replace CANNOT_DELETE with ACCESS_DENIED and add CANNOT_DELETE to the second rc.

Copy link
Contributor Author

@xEgoist xEgoist Apr 18, 2023

Choose a reason for hiding this comment

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

Note on the test example:
I think you may have found another bug with that (if it doesn't already exist) 😄 . The test will fail on Access Denied for a different reason. The file is not open with FILE_WRITE_ATTRIBUTES as the only OpenMode are read_only (GENERIC_READ), write_only (GENERIC_WRITE), and read_write (both).

and since GENERIC_READ only have FILE_READ_ATTRIBUTES, it cannot change its attributes anymore after the read only is set. Sort of related to the second issue you linked, but specific to setPermissions rather than deleteFile

I am looking to see if there are more refined flags or functions that allows that, but from a quick search, i couldn't find one.

Copy link
Collaborator

@squeek502 squeek502 Apr 18, 2023

Choose a reason for hiding this comment

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

Looks like this is the relevant issue: #12377 (about Dir but seems like it might apply to File.OpenFlags too).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, that looks like the same behavior.
I am adding a modified test with no file closing/reopen. If it doesn't seem necessary to test right now due to this, I can remove it.

@squeek502
Copy link
Collaborator

Can also add "closes #5537" to OP

xEgoist added a commit to xEgoist/zig that referenced this pull request Apr 18, 2023
Deleting a read-only file should result in `AccessDenied` (`CANNOT_DELETE`).

Note: This test was observed to fail when the file is closed then reopened
before the change in permission due to the absence of
`FILE_WRITE_ATTRIBUTES` when re-opened. (see ziglang#15316).
Deleting a read-only file should result in `AccessDenied` (`CANNOT_DELETE`).

Note: This test was observed to fail when the file is closed then reopened
before the change in permission due to the absence of
`FILE_WRITE_ATTRIBUTES` when re-opened. (see ziglang#15316).
DELETE_PENDING can happen when the file is yet to be closed for deletion
or if it never get closed. In that case, DeleteFile should assume the
file deletion is succeeding (no CloseHandle is required as it's a "failure"). In case of `DELETE_PENDING` failure, the file
may still exist. In which case if it's part of `deleteTree`, it will
eventually fail on `error.DirNotEmpty`.
@andrewrk
Copy link
Member

Thank you for the implementation @xEgoist and thank you for the review @squeek502. I'm happy to merge this when @squeek502 gives the thumbs up.

@squeek502
Copy link
Collaborator

Looks good to me 👍

@andrewrk andrewrk merged commit a867599 into ziglang:master Apr 20, 2023
der-teufel-programming pushed a commit to der-teufel-programming/zig that referenced this pull request Apr 21, 2023
Deleting a read-only file should result in `AccessDenied` (`CANNOT_DELETE`).

Note: This test was observed to fail when the file is closed then reopened
before the change in permission due to the absence of
`FILE_WRITE_ATTRIBUTES` when re-opened. (see ziglang#15316).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants