| ... | ... | @@ -156,23 +156,80 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo |
| 156 | 156 | try testing.expectEqualStrings(target_path, given); |
| 157 | 157 | } |
| 158 | 158 | |
| 159 | | test "stat on a symlink returns Kind.sym_link" { |
| 159 | test "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 | |
| 160 | 167 | try testWithAllSupportedPathTypes(struct { |
| 161 | 168 | fn impl(ctx: *TestContext) !void { |
| 162 | 169 | const dir_target_path = try ctx.transformPath("subdir"); |
| 163 | 170 | try ctx.dir.makeDir(dir_target_path); |
| 164 | 171 | |
| 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 | | |
| 169 | 172 | ctx.dir.symLink(dir_target_path, "symlink", .{ .is_directory = true }) catch |err| switch (err) { |
| 170 | 173 | // Symlink requires admin privileges on windows, so this test can legitimately fail. |
| 171 | 174 | error.AccessDenied => return error.SkipZigTest, |
| 172 | 175 | else => return err, |
| 173 | 176 | }; |
| 174 | 177 | |
| 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 | }; |
| 176 | 233 | defer symlink.close(); |
| 177 | 234 | |
| 178 | 235 | const stat = try symlink.stat(); |