Skip to content

Commit c045747

Browse files
committed
wasi: fstat{at,} support
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 #20890
1 parent 49666ec commit c045747

File tree

2 files changed

+94
-7
lines changed

2 files changed

+94
-7
lines changed

lib/std/c.zig

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,34 @@ pub const S = switch (native_os) {
17121712
pub const IFREG = 0x8000;
17131713
pub const IFSOCK = 0xc000;
17141714
pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK;
1715+
1716+
pub fn ISBLK(m: u32) bool {
1717+
return m & IFMT == IFBLK;
1718+
}
1719+
1720+
pub fn ISCHR(m: u32) bool {
1721+
return m & IFMT == IFCHR;
1722+
}
1723+
1724+
pub fn ISDIR(m: u32) bool {
1725+
return m & IFMT == IFDIR;
1726+
}
1727+
1728+
pub fn ISFIFO(m: u32) bool {
1729+
return m & IFMT == IFIFO;
1730+
}
1731+
1732+
pub fn ISLNK(m: u32) bool {
1733+
return m & IFMT == IFLNK;
1734+
}
1735+
1736+
pub fn ISREG(m: u32) bool {
1737+
return m & IFMT == IFREG;
1738+
}
1739+
1740+
pub fn ISSOCK(m: u32) bool {
1741+
return m & IFMT == IFSOCK;
1742+
}
17151743
},
17161744
.macos, .ios, .tvos, .watchos, .visionos => struct {
17171745
pub const IFMT = 0o170000;
@@ -6365,7 +6393,7 @@ pub const Stat = switch (native_os) {
63656393
dev: dev_t,
63666394
ino: ino_t,
63676395
nlink: nlink_t,
6368-
mode: mode_t,
6396+
mode: c_uint, // only the file-type bits (S.IFMT), no permission bits
63696397
uid: uid_t,
63706398
gid: gid_t,
63716399
__pad0: c_uint = 0,

lib/std/posix/test.zig

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ const supports_chdir = (native_os != .wasi);
2525
// Filter to skip tests on platforms that don't support absolute paths
2626
const supports_absolute_paths = (native_os != .wasi);
2727

28+
// Filter to skip tests on platforms that don't (yet) suppport fstat/fstatat
29+
const supports_fstat = (native_os != .windows);
30+
2831
test "check WASI CWD" {
2932
if (native_os == .wasi) {
3033
if (std.options.wasiCwd() != 3) {
@@ -299,6 +302,8 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
299302
}
300303

301304
test "link with relative paths" {
305+
if (!supports_fstat) return error.SkipZigTest;
306+
302307
switch (native_os) {
303308
.wasi, .linux, .solaris, .illumos => {},
304309
else => return error.SkipZigTest,
@@ -341,6 +346,8 @@ test "link with relative paths" {
341346
}
342347

343348
test "linkat with different directories" {
349+
if (!supports_fstat) return error.SkipZigTest;
350+
344351
switch (native_os) {
345352
.wasi, .linux, .solaris, .illumos => {},
346353
else => return error.SkipZigTest,
@@ -382,9 +389,8 @@ test "linkat with different directories" {
382389
}
383390
}
384391

385-
test "fstatat" {
386-
// enable when `fstat` and `fstatat` are implemented on Windows
387-
if (native_os == .windows) return error.SkipZigTest;
392+
test "fstat(at) file" {
393+
if (!supports_fstat) return error.SkipZigTest;
388394

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

401407
// now repeat but using `fstatat` instead
402-
const flags = posix.AT.SYMLINK_NOFOLLOW;
403-
const statat = try posix.fstatat(tmp.dir.fd, "file.txt", flags);
408+
const statat = try posix.fstatat(tmp.dir.fd, "file.txt", 0);
404409
try expectEqual(stat, statat);
405410
}
406411

412+
test "fstat(at) symlink" {
413+
if (!supports_fstat) return error.SkipZigTest;
414+
415+
var tmp = testing.tmpDir(.{});
416+
defer tmp.cleanup();
417+
418+
try tmp.dir.writeFile(.{ .sub_path = "target.txt", .data = "irrelevant" });
419+
420+
const target = try tmp.dir.openFile("target.txt", .{});
421+
const statTarget = try posix.fstat(target.handle);
422+
target.close();
423+
424+
// Set up symlink
425+
try tmp.dir.symLink("target.txt", "sym.lnk", .{});
426+
427+
// Openat (+follow) + fstat() the symlink
428+
const linkFollowFd = try posix.openat(tmp.dir.fd, "sym.lnk", .{}, default_mode);
429+
defer posix.close(linkFollowFd);
430+
const statLinkFollow = try posix.fstat(linkFollowFd);
431+
432+
// fstatat (with and without follow) the symlink
433+
const statatLinkFollow = try posix.fstatat(tmp.dir.fd, "sym.lnk", 0);
434+
const statatLinkNoFollow = try posix.fstatat(tmp.dir.fd, "sym.lnk", posix.AT.SYMLINK_NOFOLLOW);
435+
436+
if (@hasField(posix.O, "PATH")) {
437+
// Can only openat() a symlink with NOFOLLOW if O.PATH is
438+
// supported. Result should exactly match result from the
439+
// no-follow fstatat() call.
440+
441+
const linkNoFollowFd = try posix.openat(tmp.dir.fd, "sym.lnk", .{ .NOFOLLOW = true, .PATH = true }, default_mode);
442+
defer posix.close(linkNoFollowFd);
443+
444+
const statLinkNoFollow = try posix.fstat(linkNoFollowFd);
445+
try testing.expectEqual(statLinkNoFollow, statatLinkNoFollow);
446+
}
447+
448+
// Link following should have followed the link
449+
try testing.expectEqual(statTarget, statLinkFollow);
450+
try testing.expectEqual(statTarget, statatLinkFollow);
451+
452+
// symlink and target are different:
453+
try testing.expect(statTarget.ino != statatLinkNoFollow.ino);
454+
try testing.expect(statTarget.mode != statatLinkNoFollow.mode);
455+
456+
// target is a regular, non-link file:
457+
try testing.expect(posix.S.ISREG(statTarget.mode));
458+
try testing.expect(!posix.S.ISLNK(statTarget.mode));
459+
460+
// symlink is a non-regular, link file:
461+
try testing.expect(!posix.S.ISREG(statatLinkNoFollow.mode));
462+
try testing.expect(posix.S.ISLNK(statatLinkNoFollow.mode));
463+
}
464+
407465
test "readlinkat" {
408466
var tmp = tmpDir(.{});
409467
defer tmp.cleanup();
@@ -1249,6 +1307,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
12491307

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

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

0 commit comments

Comments
 (0)