authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2023-12-21 03:02:22-08:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2023-12-21 17:47:32-08:00
logf5d0664e78211fbc366801868d59d9f909cd0471
tree180ca9e517ad4dde13cef761667378929a6d33af
parent11a398af3e12f0d1da0a5f95a17e334a063890a9

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.

1 files changed, 63 insertions(+), 6 deletions(-)

lib/std/fs/test.zig+63-6
......@@ -156,23 +156,80 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
156156 try testing.expectEqualStrings(target_path, given);
157157}
158158
159test "stat on a symlink returns Kind.sym_link" {
159test "File.stat on a File that is a symlink returns Kind.sym_link" {
160 // This test requires getting a file descriptor of a symlink which
161 // is not possible on all targets
162 switch (builtin.target.os.tag) {
163 .windows, .linux => {},
164 else => return error.SkipZigTest,
165 }
166
160167 try testWithAllSupportedPathTypes(struct {
161168 fn impl(ctx: *TestContext) !void {
162169 const dir_target_path = try ctx.transformPath("subdir");
163170 try ctx.dir.makeDir(dir_target_path);
164171
165 // TODO: Also test a symlink to a file.
166 // There's currently no way to avoid following symlinks when opening files.
167 // https://github.com/ziglang/zig/issues/18327
168
169172 ctx.dir.symLink(dir_target_path, "symlink", .{ .is_directory = true }) catch |err| switch (err) {
170173 // Symlink requires admin privileges on windows, so this test can legitimately fail.
171174 error.AccessDenied => return error.SkipZigTest,
172175 else => return err,
173176 };
174177
175 var symlink = try ctx.dir.openDir("symlink", .{ .no_follow = true });
178 var symlink = switch (builtin.target.os.tag) {
179 .windows => windows_symlink: {
180 const w = std.os.windows;
181
182 const sub_path_w = try std.os.windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink");
183
184 var result = Dir{
185 .fd = undefined,
186 };
187
188 const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2));
189 var nt_name = w.UNICODE_STRING{
190 .Length = path_len_bytes,
191 .MaximumLength = path_len_bytes,
192 .Buffer = @constCast(&sub_path_w.data),
193 };
194 var attr = w.OBJECT_ATTRIBUTES{
195 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
196 .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.fd,
197 .Attributes = 0,
198 .ObjectName = &nt_name,
199 .SecurityDescriptor = null,
200 .SecurityQualityOfService = null,
201 };
202 var io: w.IO_STATUS_BLOCK = undefined;
203 const rc = w.ntdll.NtCreateFile(
204 &result.fd,
205 w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE,
206 &attr,
207 &io,
208 null,
209 w.FILE_ATTRIBUTE_NORMAL,
210 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
211 w.FILE_OPEN,
212 // FILE_OPEN_REPARSE_POINT is the important thing here
213 w.FILE_OPEN_REPARSE_POINT | w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT,
214 null,
215 0,
216 );
217
218 switch (rc) {
219 .SUCCESS => break :windows_symlink result,
220 else => return w.unexpectedStatus(rc),
221 }
222 },
223 .linux => linux_symlink: {
224 const sub_path_c = try os.toPosixPath("symlink");
225 // the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink
226 // note that if O_DIRECTORY is set, then this will error with ENOTDIR
227 const flags = os.O.NOFOLLOW | os.O.PATH | os.O.RDONLY | os.O.CLOEXEC;
228 const fd = try os.openatZ(ctx.dir.fd, &sub_path_c, flags, 0);
229 break :linux_symlink Dir{ .fd = fd };
230 },
231 else => unreachable,
232 };
176233 defer symlink.close();
177234
178235 const stat = try symlink.stat();