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
66 changes: 64 additions & 2 deletions lib/std/fs/Dir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1898,6 +1898,68 @@ 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) {
const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
return self.statLinkW(path_w.span());
}
const sub_path_c = try posix.toPosixPath(sub_path);
return self.statLinkZ(&sub_path_c);
}

/// Same as `Dir.statLink`
pub fn statLinkZ(self: Dir, sub_path_c: [*:0]const u8) StatFileError!Stat {
if (native_os == .windows) {
const path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
return self.statLinkW(path_w.span());
}
if (native_os == .linux) {
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 => error.NameTooLong,
.NOENT, .NOTDIR => error.FileNotFound,
.NOMEM => error.SystemResources,
else => |err| posix.unexpectedErrno(err),
};
}
const st = try posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW);
return Stat.fromPosix(st);
}

/// 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 @@ -2683,9 +2745,9 @@ 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, .{});
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
22 changes: 22 additions & 0 deletions lib/std/fs/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,28 @@ 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
// https://github.com/ziglang/zig/issues/20890
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;

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