@@ -25,6 +25,9 @@ const supports_chdir = (native_os != .wasi);
25
25
// Filter to skip tests on platforms that don't support absolute paths
26
26
const supports_absolute_paths = (native_os != .wasi );
27
27
28
+ // Filter to skip tests on platforms that don't (yet) suppport fstat/fstatat
29
+ const supports_fstat = (native_os != .windows );
30
+
28
31
test "check WASI CWD" {
29
32
if (native_os == .wasi ) {
30
33
if (std .options .wasiCwd () != 3 ) {
@@ -305,6 +308,8 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
305
308
}
306
309
307
310
test "link with relative paths" {
311
+ if (! supports_fstat ) return error .SkipZigTest ;
312
+
308
313
switch (native_os ) {
309
314
.wasi , .linux , .solaris , .illumos = > {},
310
315
else = > return error .SkipZigTest ,
@@ -347,6 +352,8 @@ test "link with relative paths" {
347
352
}
348
353
349
354
test "linkat with different directories" {
355
+ if (! supports_fstat ) return error .SkipZigTest ;
356
+
350
357
switch (native_os ) {
351
358
.wasi , .linux , .solaris , .illumos = > {},
352
359
else = > return error .SkipZigTest ,
@@ -388,9 +395,8 @@ test "linkat with different directories" {
388
395
}
389
396
}
390
397
391
- test "fstatat" {
392
- // enable when `fstat` and `fstatat` are implemented on Windows
393
- if (native_os == .windows ) return error .SkipZigTest ;
398
+ test "fstat(at) file" {
399
+ if (! supports_fstat ) return error .SkipZigTest ;
394
400
395
401
var tmp = tmpDir (.{});
396
402
defer tmp .cleanup ();
@@ -402,14 +408,66 @@ test "fstatat" {
402
408
// fetch file's info on the opened fd directly
403
409
const file = try tmp .dir .openFile ("file.txt" , .{});
404
410
const stat = try posix .fstat (file .handle );
405
- defer file .close ();
411
+ file .close ();
406
412
407
413
// now repeat but using `fstatat` instead
408
- const flags = posix .AT .SYMLINK_NOFOLLOW ;
409
- const statat = try posix .fstatat (tmp .dir .fd , "file.txt" , flags );
414
+ const statat = try posix .fstatat (tmp .dir .fd , "file.txt" , 0 );
410
415
try expectEqual (stat , statat );
411
416
}
412
417
418
+ test "fstat(at) symlink" {
419
+ if (! supports_fstat ) return error .SkipZigTest ;
420
+
421
+ var tmp = testing .tmpDir (.{});
422
+ defer tmp .cleanup ();
423
+
424
+ try tmp .dir .writeFile (.{ .sub_path = "target.txt" , .data = "irrelevant" });
425
+
426
+ const target = try tmp .dir .openFile ("target.txt" , .{});
427
+ const statTarget = try posix .fstat (target .handle );
428
+ target .close ();
429
+
430
+ // Set up symlink
431
+ try tmp .dir .symLink ("target.txt" , "sym.lnk" , .{});
432
+
433
+ // Openat (+follow) + fstat() the symlink
434
+ const linkFollowFd = try posix .openat (tmp .dir .fd , "sym.lnk" , .{}, default_mode );
435
+ defer posix .close (linkFollowFd );
436
+ const statLinkFollow = try posix .fstat (linkFollowFd );
437
+
438
+ // fstatat (with and without follow) the symlink
439
+ const statatLinkFollow = try posix .fstatat (tmp .dir .fd , "sym.lnk" , 0 );
440
+ const statatLinkNoFollow = try posix .fstatat (tmp .dir .fd , "sym.lnk" , posix .AT .SYMLINK_NOFOLLOW );
441
+
442
+ if (@hasField (posix .O , "PATH" )) {
443
+ // Can only openat() a symlink with NOFOLLOW if O.PATH is
444
+ // supported. Result should exactly match result from the
445
+ // no-follow fstatat() call.
446
+
447
+ const linkNoFollowFd = try posix .openat (tmp .dir .fd , "sym.lnk" , .{ .NOFOLLOW = true , .PATH = true }, default_mode );
448
+ defer posix .close (linkNoFollowFd );
449
+
450
+ const statLinkNoFollow = try posix .fstat (linkNoFollowFd );
451
+ try testing .expectEqual (statLinkNoFollow , statatLinkNoFollow );
452
+ }
453
+
454
+ // Link following should have followed the link
455
+ try testing .expectEqual (statTarget , statLinkFollow );
456
+ try testing .expectEqual (statTarget , statatLinkFollow );
457
+
458
+ // symlink and target are different:
459
+ try testing .expect (statTarget .ino != statatLinkNoFollow .ino );
460
+ try testing .expect (statTarget .mode != statatLinkNoFollow .mode );
461
+
462
+ // target is a regular, non-link file:
463
+ try testing .expect (posix .S .ISREG (statTarget .mode ));
464
+ try testing .expect (! posix .S .ISLNK (statTarget .mode ));
465
+
466
+ // symlink is a non-regular, link file:
467
+ try testing .expect (! posix .S .ISREG (statatLinkNoFollow .mode ));
468
+ try testing .expect (posix .S .ISLNK (statatLinkNoFollow .mode ));
469
+ }
470
+
413
471
test "readlinkat" {
414
472
var tmp = tmpDir (.{});
415
473
defer tmp .cleanup ();
@@ -1255,6 +1313,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
1255
1313
1256
1314
test "fchmodat smoke test" {
1257
1315
if (! std .fs .has_executable_bit ) return error .SkipZigTest ;
1316
+ if (! supports_fstat ) return error .SkipZigTest ; // for expectMode()
1258
1317
1259
1318
var tmp = tmpDir (.{});
1260
1319
defer tmp .cleanup ();
0 commit comments