@@ -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 ) {
@@ -297,6 +300,8 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
297
300
}
298
301
299
302
test "link with relative paths" {
303
+ if (! supports_fstat ) return error .SkipZigTest ;
304
+
300
305
switch (native_os ) {
301
306
.wasi , .linux , .solaris , .illumos = > {},
302
307
else = > return error .SkipZigTest ,
@@ -339,6 +344,8 @@ test "link with relative paths" {
339
344
}
340
345
341
346
test "linkat with different directories" {
347
+ if (! supports_fstat ) return error .SkipZigTest ;
348
+
342
349
switch (native_os ) {
343
350
.wasi , .linux , .solaris , .illumos = > {},
344
351
else = > return error .SkipZigTest ,
@@ -380,9 +387,8 @@ test "linkat with different directories" {
380
387
}
381
388
}
382
389
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 ;
386
392
387
393
var tmp = tmpDir (.{});
388
394
defer tmp .cleanup ();
@@ -394,14 +400,66 @@ test "fstatat" {
394
400
// fetch file's info on the opened fd directly
395
401
const file = try tmp .dir .openFile ("file.txt" , .{});
396
402
const stat = try posix .fstat (file .handle );
397
- defer file .close ();
403
+ file .close ();
398
404
399
405
// 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 );
402
407
try expectEqual (stat , statat );
403
408
}
404
409
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
+
405
463
test "readlinkat" {
406
464
var tmp = tmpDir (.{});
407
465
defer tmp .cleanup ();
@@ -1247,6 +1305,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
1247
1305
1248
1306
test "fchmodat smoke test" {
1249
1307
if (! std .fs .has_executable_bit ) return error .SkipZigTest ;
1308
+ if (! supports_fstat ) return error .SkipZigTest ; // for expectMode()
1250
1309
1251
1310
var tmp = tmpDir (.{});
1252
1311
defer tmp .cleanup ();
0 commit comments