@@ -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 ) {
@@ -297,6 +300,8 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
297300}
298301
299302test "link with relative paths" {
303+ if (! supports_fstat ) return error .SkipZigTest ;
304+
300305 switch (native_os ) {
301306 .wasi , .linux , .solaris , .illumos = > {},
302307 else = > return error .SkipZigTest ,
@@ -339,6 +344,8 @@ test "link with relative paths" {
339344}
340345
341346test "linkat with different directories" {
347+ if (! supports_fstat ) return error .SkipZigTest ;
348+
342349 switch (native_os ) {
343350 .wasi , .linux , .solaris , .illumos = > {},
344351 else = > return error .SkipZigTest ,
@@ -380,9 +387,8 @@ test "linkat with different directories" {
380387 }
381388}
382389
383- test "fstatat" {
384- // enable when `fstat` and `fstatat` are implemented on Windows
385- if (native_os == .windows ) return error .SkipZigTest ;
390+ test "fstat(at) file" {
391+ if (! supports_fstat ) return error .SkipZigTest ;
386392
387393 var tmp = tmpDir (.{});
388394 defer tmp .cleanup ();
@@ -394,14 +400,66 @@ test "fstatat" {
394400 // fetch file's info on the opened fd directly
395401 const file = try tmp .dir .openFile ("file.txt" , .{});
396402 const stat = try posix .fstat (file .handle );
397- defer file .close ();
403+ file .close ();
398404
399405 // now repeat but using `fstatat` instead
400- const flags = posix .AT .SYMLINK_NOFOLLOW ;
401- const statat = try posix .fstatat (tmp .dir .fd , "file.txt" , flags );
406+ const statat = try posix .fstatat (tmp .dir .fd , "file.txt" , 0 );
402407 try expectEqual (stat , statat );
403408}
404409
410+ test "fstat(at) symlink" {
411+ if (! supports_fstat ) return error .SkipZigTest ;
412+
413+ var tmp = testing .tmpDir (.{});
414+ defer tmp .cleanup ();
415+
416+ try tmp .dir .writeFile (.{ .sub_path = "target.txt" , .data = "irrelevant" });
417+
418+ const target = try tmp .dir .openFile ("target.txt" , .{});
419+ const statTarget = try posix .fstat (target .handle );
420+ target .close ();
421+
422+ // Set up symlink
423+ try tmp .dir .symLink ("target.txt" , "sym.lnk" , .{});
424+
425+ // Openat (+follow) + fstat() the symlink
426+ const linkFollowFd = try posix .openat (tmp .dir .fd , "sym.lnk" , .{}, default_mode );
427+ defer posix .close (linkFollowFd );
428+ const statLinkFollow = try posix .fstat (linkFollowFd );
429+
430+ // fstatat (with and without follow) the symlink
431+ const statatLinkFollow = try posix .fstatat (tmp .dir .fd , "sym.lnk" , 0 );
432+ const statatLinkNoFollow = try posix .fstatat (tmp .dir .fd , "sym.lnk" , posix .AT .SYMLINK_NOFOLLOW );
433+
434+ if (@hasField (posix .O , "PATH" )) {
435+ // Can only openat() a symlink with NOFOLLOW if O.PATH is
436+ // supported. Result should exactly match result from the
437+ // no-follow fstatat() call.
438+
439+ const linkNoFollowFd = try posix .openat (tmp .dir .fd , "sym.lnk" , .{ .NOFOLLOW = true , .PATH = true }, default_mode );
440+ defer posix .close (linkNoFollowFd );
441+
442+ const statLinkNoFollow = try posix .fstat (linkNoFollowFd );
443+ try testing .expectEqual (statLinkNoFollow , statatLinkNoFollow );
444+ }
445+
446+ // Link following should have followed the link
447+ try testing .expectEqual (statTarget , statLinkFollow );
448+ try testing .expectEqual (statTarget , statatLinkFollow );
449+
450+ // symlink and target are different:
451+ try testing .expect (statTarget .ino != statatLinkNoFollow .ino );
452+ try testing .expect (statTarget .mode != statatLinkNoFollow .mode );
453+
454+ // target is a regular, non-link file:
455+ try testing .expect (posix .S .ISREG (statTarget .mode ));
456+ try testing .expect (! posix .S .ISLNK (statTarget .mode ));
457+
458+ // symlink is a non-regular, link file:
459+ try testing .expect (! posix .S .ISREG (statatLinkNoFollow .mode ));
460+ try testing .expect (posix .S .ISLNK (statatLinkNoFollow .mode ));
461+ }
462+
405463test "readlinkat" {
406464 var tmp = tmpDir (.{});
407465 defer tmp .cleanup ();
@@ -1247,6 +1305,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
12471305
12481306test "fchmodat smoke test" {
12491307 if (! std .fs .has_executable_bit ) return error .SkipZigTest ;
1308+ if (! supports_fstat ) return error .SkipZigTest ; // for expectMode()
12501309
12511310 var tmp = tmpDir (.{});
12521311 defer tmp .cleanup ();
0 commit comments