@@ -25,6 +25,9 @@ const supports_chdir = (native_os != .wasi);
2525// Filter to skip tests on platforms that don't support absolute paths
2626const 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+
2831test "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
301304test "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
343348test "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+
407465test "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
12501308test "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