Skip to content

Commit

Permalink
wasi: fstat{at,} support
Browse files Browse the repository at this point in the history
Add a test for fstat{,at} with symlinks (for everyone, not just wasi)

Fix the wasi-libc Stat.mode field, and add the expected accessors to the S
struct.

Fixes ziglang#20890
  • Loading branch information
rootbeer committed Aug 9, 2024
1 parent 106866e commit d4b0601
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 7 deletions.
30 changes: 29 additions & 1 deletion lib/std/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1712,6 +1712,34 @@ pub const S = switch (native_os) {
pub const IFREG = 0x8000;
pub const IFSOCK = 0xc000;
pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK;

pub fn ISBLK(m: u32) bool {
return m & IFMT == IFBLK;
}

pub fn ISCHR(m: u32) bool {
return m & IFMT == IFCHR;
}

pub fn ISDIR(m: u32) bool {
return m & IFMT == IFDIR;
}

pub fn ISFIFO(m: u32) bool {
return m & IFMT == IFIFO;
}

pub fn ISLNK(m: u32) bool {
return m & IFMT == IFLNK;
}

pub fn ISREG(m: u32) bool {
return m & IFMT == IFREG;
}

pub fn ISSOCK(m: u32) bool {
return m & IFMT == IFSOCK;
}
},
.macos, .ios, .tvos, .watchos, .visionos => struct {
pub const IFMT = 0o170000;
Expand Down Expand Up @@ -6365,7 +6393,7 @@ pub const Stat = switch (native_os) {
dev: dev_t,
ino: ino_t,
nlink: nlink_t,
mode: mode_t,
mode: c_uint, // only the file-type bits (S.IFMT), no permission bits
uid: uid_t,
gid: gid_t,
__pad0: c_uint = 0,
Expand Down
71 changes: 65 additions & 6 deletions lib/std/posix/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const supports_chdir = (native_os != .wasi);
// Filter to skip tests on platforms that don't support absolute paths
const supports_absolute_paths = (native_os != .wasi);

// Filter to skip tests on platforms that don't (yet) suppport fstat/fstatat
const supports_fstat = (native_os != .windows);

test "check WASI CWD" {
if (native_os == .wasi) {
if (std.options.wasiCwd() != 3) {
Expand Down Expand Up @@ -299,6 +302,8 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
}

test "link with relative paths" {
if (!supports_fstat) return error.SkipZigTest;

switch (native_os) {
.wasi, .linux, .solaris, .illumos => {},
else => return error.SkipZigTest,
Expand Down Expand Up @@ -341,6 +346,8 @@ test "link with relative paths" {
}

test "linkat with different directories" {
if (!supports_fstat) return error.SkipZigTest;

switch (native_os) {
.wasi, .linux, .solaris, .illumos => {},
else => return error.SkipZigTest,
Expand Down Expand Up @@ -382,9 +389,8 @@ test "linkat with different directories" {
}
}

test "fstatat" {
// enable when `fstat` and `fstatat` are implemented on Windows
if (native_os == .windows) return error.SkipZigTest;
test "fstat(at) file" {
if (!supports_fstat) return error.SkipZigTest;

var tmp = tmpDir(.{});
defer tmp.cleanup();
Expand All @@ -396,14 +402,66 @@ test "fstatat" {
// fetch file's info on the opened fd directly
const file = try tmp.dir.openFile("file.txt", .{});
const stat = try posix.fstat(file.handle);
defer file.close();
file.close();

// now repeat but using `fstatat` instead
const flags = posix.AT.SYMLINK_NOFOLLOW;
const statat = try posix.fstatat(tmp.dir.fd, "file.txt", flags);
const statat = try posix.fstatat(tmp.dir.fd, "file.txt", 0);
try expectEqual(stat, statat);
}

test "fstat(at) symlink" {
if (!supports_fstat) return error.SkipZigTest;

var tmp = testing.tmpDir(.{});
defer tmp.cleanup();

try tmp.dir.writeFile(.{ .sub_path = "target.txt", .data = "irrelevant" });

const target = try tmp.dir.openFile("target.txt", .{});
const statTarget = try posix.fstat(target.handle);
target.close();

// Set up symlink
try tmp.dir.symLink("target.txt", "sym.lnk", .{});

// Openat (+follow) + fstat() the symlink
const linkFollowFd = try posix.openat(tmp.dir.fd, "sym.lnk", .{}, default_mode);
defer posix.close(linkFollowFd);
const statLinkFollow = try posix.fstat(linkFollowFd);

// fstatat (with and without follow) the symlink
const statatLinkFollow = try posix.fstatat(tmp.dir.fd, "sym.lnk", 0);
const statatLinkNoFollow = try posix.fstatat(tmp.dir.fd, "sym.lnk", posix.AT.SYMLINK_NOFOLLOW);

if (@hasField(posix.O, "PATH")) {
// Can only openat() a symlink with NOFOLLOW if O.PATH is
// supported. Result should exactly match result from the
// no-follow fstatat() call.

const linkNoFollowFd = try posix.openat(tmp.dir.fd, "sym.lnk", .{ .NOFOLLOW = true, .PATH = true }, default_mode);
defer posix.close(linkNoFollowFd);

const statLinkNoFollow = try posix.fstat(linkNoFollowFd);
try testing.expectEqual(statLinkNoFollow, statatLinkNoFollow);
}

// Link following should have followed the link
try testing.expectEqual(statTarget, statLinkFollow);
try testing.expectEqual(statTarget, statatLinkFollow);

// symlink and target are different:
try testing.expect(statTarget.ino != statatLinkNoFollow.ino);
try testing.expect(statTarget.mode != statatLinkNoFollow.mode);

// target is a regular, non-link file:
try testing.expect(posix.S.ISREG(statTarget.mode));
try testing.expect(!posix.S.ISLNK(statTarget.mode));

// symlink is a non-regular, link file:
try testing.expect(!posix.S.ISREG(statatLinkNoFollow.mode));
try testing.expect(posix.S.ISLNK(statatLinkNoFollow.mode));
}

test "readlinkat" {
var tmp = tmpDir(.{});
defer tmp.cleanup();
Expand Down Expand Up @@ -1249,6 +1307,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {

test "fchmodat smoke test" {
if (!std.fs.has_executable_bit) return error.SkipZigTest;
if (!supports_fstat) return error.SkipZigTest; // for expectMode()

var tmp = tmpDir(.{});
defer tmp.cleanup();
Expand Down

0 comments on commit d4b0601

Please sign in to comment.