Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs.Dir: Give the option to stat a symbolic link #20843

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
70 changes: 68 additions & 2 deletions lib/std/fs/Dir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,70 @@ pub fn atomicSymLink(

pub const ReadLinkError = posix.ReadLinkError;

/// Same as `Dir.statFile`, but if the path points to a symbolic link,
/// it will stat the link rather than the file it points to.
/// Note: if the target is a regular file, this function has the same
/// behaviour that `Dir.statFile`
pub fn statLink(self: Dir, sub_path: []const u8) StatFileError!Stat {
if (native_os == .windows) {
// note: it is possible to stat the file directly using `NtQueryInformatioByName`
// see PR #20843 for why it has not been implemented
const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
return self.statLinkW(path_w.span());
}
if (native_os == .wasi and !builtin.link_libc) {
const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = false });
return Stat.fromWasi(st);
}
squeek502 marked this conversation as resolved.
Show resolved Hide resolved
if (native_os == .linux) {
const sub_path_c = try posix.toPosixPath(sub_path);
var stx = std.mem.zeroes(linux.Statx);

const rc = linux.statx(
self.fd,
&sub_path_c,
linux.AT.NO_AUTOMOUNT | linux.AT.SYMLINK_NOFOLLOW,
linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
&stx,
);

return switch (linux.E.init(rc)) {
.SUCCESS => Stat.fromLinux(stx),
.ACCES => error.AccessDenied,
.BADF => unreachable,
.FAULT => unreachable,
.INVAL => unreachable,
.LOOP => error.SymLinkLoop,
.NAMETOOLONG => unreachable, // Handled by posix.toPosixPath() above.
.NOENT, .NOTDIR => error.FileNotFound,
.NOMEM => error.SystemResources,
else => |err| posix.unexpectedErrno(err),
};
}
const st = try posix.fstatat(self.fd, sub_path, posix.AT.SYMLINK_NOFOLLOW);
return Stat.fromPosix(st);
}

/// Same as `Dir.statLink`
pub fn statLinkZ(self: Dir, sub_path_c: [*:0]const u8) StatFileError!Stat {
return self.statLink(mem.sliceTo(sub_path_c, 0));
squeek502 marked this conversation as resolved.
Show resolved Hide resolved
}

/// Windows only. Same as `Dir.statLink`
pub fn statLinkW(self: Dir, sub_path_w: []const u16) StatFileError!Stat {
const file = File{
.handle = try windows.OpenFile(sub_path_w, .{
.dir = self.fd,
.access_mask = windows.GENERIC_READ,
.creation = windows.FILE_OPEN,
.follow_symlinks = false,
.filter = .any,
}),
};
defer file.close();
return file.stat();
}

/// Read value of a symbolic link.
/// The return value is a slice of `buffer`, from index `0`.
/// Asserts that the path parameter has no null bytes.
Expand Down Expand Up @@ -2681,9 +2745,11 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
if (native_os == .windows) {
var file = try self.openFile(sub_path, .{});
// note: it is possible to stat the file directly using `NtQueryInformatioByName`
// see PR #20843 for why it has not been implemented
squeek502 marked this conversation as resolved.
Show resolved Hide resolved
const file = try self.openFile(sub_path, .{});
defer file.close();
return file.stat();
return try file.stat();
}
if (native_os == .wasi and !builtin.link_libc) {
const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });
Expand Down
19 changes: 19 additions & 0 deletions lib/std/fs/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,25 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" {
}.impl);
}

test "Dir.statLink" {
samy-00007 marked this conversation as resolved.
Show resolved Hide resolved
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
const dir_target_path = try ctx.transformPath("test_file");
const file = try ctx.dir.createFile(dir_target_path, .{});
try file.writeAll("Some test content");
file.close();

try setupSymlink(ctx.dir, dir_target_path, "symlink", .{});

const file_stat = try ctx.dir.statLink("test_file");
const link_stat = try ctx.dir.statLink("symlink");

try testing.expectEqual(File.Kind.file, file_stat.kind);
try testing.expectEqual(File.Kind.sym_link, link_stat.kind);
}
}.impl);
}

test "openDir" {
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
Expand Down
7 changes: 7 additions & 0 deletions lib/std/os/windows/ntdll.zig
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ pub extern "ntdll" fn RtlVirtualUnwind(
EstablisherFrame: *DWORD64,
ContextPointers: ?*KNONVOLATILE_CONTEXT_POINTERS,
) callconv(WINAPI) *EXCEPTION_ROUTINE;
pub extern "ntdll" fn NtQueryInformationByName(
ObjectAttributes: *OBJECT_ATTRIBUTES,
IoStatusBlock: *IO_STATUS_BLOCK,
FileInformation: *anyopaque,
Length: ULONG,
FileInformationClass: FILE_INFORMATION_CLASS,
) callconv(WINAPI) NTSTATUS;
pub extern "ntdll" fn NtQueryInformationFile(
FileHandle: HANDLE,
IoStatusBlock: *IO_STATUS_BLOCK,
Expand Down
Loading