Commit 11a398af3e
Changed files (3)
lib
std
lib/std/fs/File.zig
@@ -389,7 +389,26 @@ pub fn stat(self: File) StatError!Stat {
.inode = info.InternalInformation.IndexNumber,
.size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
.mode = 0,
- .kind = if (info.StandardInformation.Directory == 0) .file else .directory,
+ .kind = if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) reparse_point: {
+ var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
+ const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
+ switch (tag_rc) {
+ .SUCCESS => {},
+ // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
+ // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
+ .INFO_LENGTH_MISMATCH => unreachable,
+ .ACCESS_DENIED => return error.AccessDenied,
+ else => return windows.unexpectedStatus(rc),
+ }
+ if (tag_info.ReparseTag & windows.reparse_tag_name_surrogate_bit != 0) {
+ break :reparse_point .sym_link;
+ }
+ // Unknown reparse point
+ break :reparse_point .unknown;
+ } else if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0)
+ .directory
+ else
+ .file,
.atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
.mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
.ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
@@ -791,7 +810,7 @@ pub const MetadataWindows = struct {
/// Can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
pub fn kind(self: Self) Kind {
if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
- if (self.reparse_tag & 0x20000000 != 0) {
+ if (self.reparse_tag & windows.reparse_tag_name_surrogate_bit != 0) {
return .sym_link;
}
} else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) {
@@ -842,10 +861,17 @@ pub fn metadata(self: File) MetadataError!Metadata {
const reparse_tag: windows.DWORD = reparse_blk: {
if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
- var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
- try windows.DeviceIoControl(self.handle, windows.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]);
- const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
- break :reparse_blk reparse_struct.ReparseTag;
+ var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
+ const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
+ switch (tag_rc) {
+ .SUCCESS => {},
+ // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
+ // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
+ .INFO_LENGTH_MISMATCH => unreachable,
+ .ACCESS_DENIED => return error.AccessDenied,
+ else => return windows.unexpectedStatus(rc),
+ }
+ break :reparse_blk tag_info.ReparseTag;
}
break :reparse_blk 0;
};
lib/std/fs/test.zig
@@ -156,6 +156,31 @@ 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" {
+ 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 });
+ defer symlink.close();
+
+ const stat = try symlink.stat();
+ try testing.expectEqual(File.Kind.sym_link, stat.kind);
+ }
+ }.impl);
+}
+
test "relative symlink to parent directory" {
var tmp = tmpDir(.{});
defer tmp.cleanup();
lib/std/os/windows.zig
@@ -2972,6 +2972,15 @@ pub const FILE_INFORMATION_CLASS = enum(c_int) {
FileMaximumInformation,
};
+pub const FILE_ATTRIBUTE_TAG_INFO = extern struct {
+ FileAttributes: DWORD,
+ ReparseTag: DWORD,
+};
+
+/// "If this bit is set, the file or directory represents another named entity in the system."
+/// https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags
+pub const reparse_tag_name_surrogate_bit = 0x20000000;
+
pub const FILE_DISPOSITION_INFORMATION = extern struct {
DeleteFile: BOOLEAN,
};