Make 'stat of a symlink' test case not rely on OpenDirOptions.no_follow behavior

The `no_follow` behavior happened to allow opening a file descriptor of a symlink itself on Windows, but that behavior may change in the future. Instead, we implement the opening of the symlink as a file descriptor manually (and per-platform) in the test case.
This commit is contained in:
Ryan Liptak 2023-12-21 03:02:22 -08:00
parent 11a398af3e
commit f5d0664e78

View File

@ -156,23 +156,80 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
try testing.expectEqualStrings(target_path, given);
}
test "stat on a symlink returns Kind.sym_link" {
test "File.stat on a File that is a symlink returns Kind.sym_link" {
// This test requires getting a file descriptor of a symlink which
// is not possible on all targets
switch (builtin.target.os.tag) {
.windows, .linux => {},
else => return error.SkipZigTest,
}
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
const dir_target_path = try ctx.transformPath("subdir");
try ctx.dir.makeDir(dir_target_path);
// TODO: Also test a symlink to a file.
// There's currently no way to avoid following symlinks when opening files.
// https://github.com/ziglang/zig/issues/18327
ctx.dir.symLink(dir_target_path, "symlink", .{ .is_directory = true }) catch |err| switch (err) {
// Symlink requires admin privileges on windows, so this test can legitimately fail.
error.AccessDenied => return error.SkipZigTest,
else => return err,
};
var symlink = try ctx.dir.openDir("symlink", .{ .no_follow = true });
var symlink = switch (builtin.target.os.tag) {
.windows => windows_symlink: {
const w = std.os.windows;
const sub_path_w = try std.os.windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink");
var result = Dir{
.fd = undefined,
};
const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2));
var nt_name = w.UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.Buffer = @constCast(&sub_path_w.data),
};
var attr = w.OBJECT_ATTRIBUTES{
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
.RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.fd,
.Attributes = 0,
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var io: w.IO_STATUS_BLOCK = undefined;
const rc = w.ntdll.NtCreateFile(
&result.fd,
w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE,
&attr,
&io,
null,
w.FILE_ATTRIBUTE_NORMAL,
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
w.FILE_OPEN,
// FILE_OPEN_REPARSE_POINT is the important thing here
w.FILE_OPEN_REPARSE_POINT | w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT,
null,
0,
);
switch (rc) {
.SUCCESS => break :windows_symlink result,
else => return w.unexpectedStatus(rc),
}
},
.linux => linux_symlink: {
const sub_path_c = try os.toPosixPath("symlink");
// the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink
// note that if O_DIRECTORY is set, then this will error with ENOTDIR
const flags = os.O.NOFOLLOW | os.O.PATH | os.O.RDONLY | os.O.CLOEXEC;
const fd = try os.openatZ(ctx.dir.fd, &sub_path_c, flags, 0);
break :linux_symlink Dir{ .fd = fd };
},
else => unreachable,
};
defer symlink.close();
const stat = try symlink.stat();