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