From caf80e90b85a2a7855c369887dce163cf036ff7e Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Sat, 19 Jul 2025 20:31:13 -0700 Subject: [PATCH 1/2] linux/mips.zig: Use `i32` for stat nsec fields The `atime()`, etc wrappers here expect to create a `std.linux.timespec` (defined in `linux.zig` to have `isize` fields), so the u32 causes errors: error: expected type 'isize', found 'u32' .nsec = self.atim_nsec, Make the nsec fields signed for consistency with all the other structs, with and with `std.linux.timespec`. Also looks like the comment on `__pad1` was copied from `__pad0`, but it only applies to `__pad0`. --- lib/std/os/linux/mips.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/os/linux/mips.zig b/lib/std/os/linux/mips.zig index d7225eb7b354c4d3ae719ca28b709723bcf075c1..0293fb83e852c7d110c402555470a6fbd6743a46 100644 --- a/lib/std/os/linux/mips.zig +++ b/lib/std/os/linux/mips.zig @@ -317,14 +317,14 @@ pub const Stat = extern struct { uid: uid_t, gid: gid_t, rdev: dev_t, - __pad1: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). + __pad1: [2]u32, size: off_t, atim: i32, - atim_nsec: u32, + atim_nsec: i32, mtim: i32, - mtim_nsec: u32, + mtim_nsec: i32, ctim: i32, - ctim_nsec: u32, + ctim_nsec: i32, blksize: blksize_t, __pad3: u32, blocks: blkcnt_t, -- 2.54.0 From cf47d283d101e5d8e0d1ca7ec427d40efc40f358 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Sat, 19 Jul 2025 09:34:04 -0700 Subject: [PATCH 2/2] lib/std/posix/test.zig: don't compare blksize in "fstatat" In trying to reproduce the race in #24380, my system tripped over the stat "blocks" field changing in this test. The value was almost always 8 (effectively 4k) or very infrequently 0 (I saw the 0 from both `fstat` and `fstatat`). I believe the underlying filesystem is free to asynchronously change this value. For example, if it migrates a file between some "inline" or maybe journal storage, and actual on-disk blocks. So it seems plausible that its allowed to change between stat calls. Breaking up the struct comparison this way means we also don't compare any of the padding or "reserved" fields, too. And we can narrow down the s390x-linux work-around. --- lib/std/posix/test.zig | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 8199be65aa3feb4220e6231d5d100c61f01c9c59..55a53518d974e410725c64a2ec04f53ca8173168 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -395,11 +395,27 @@ test "fstatat" { // now repeat but using `fstatat` instead const statat = try posix.fstatat(tmp.dir.fd, "file.txt", posix.AT.SYMLINK_NOFOLLOW); - // s390x-linux does not have nanosecond precision for fstat(), but it does for fstatat(). As a - // result, comparing the two structures is doomed to fail. - if (builtin.cpu.arch == .s390x and builtin.os.tag == .linux) return error.SkipZigTest; + try expectEqual(stat.dev, statat.dev); + try expectEqual(stat.ino, statat.ino); + try expectEqual(stat.nlink, statat.nlink); + try expectEqual(stat.mode, statat.mode); + try expectEqual(stat.uid, statat.uid); + try expectEqual(stat.gid, statat.gid); + try expectEqual(stat.rdev, statat.rdev); + try expectEqual(stat.size, statat.size); + try expectEqual(stat.blksize, statat.blksize); - try expectEqual(stat, statat); + // The stat.blocks/statat.blocks count is managed by the filesystem and may + // change if the file is stored in a journal or "inline". + // try expectEqual(stat.blocks, statat.blocks); + + // s390x-linux does not have nanosecond precision for fstat(), but it does for + // fstatat(). As a result, comparing the timestamps isn't worth the effort + if (!(builtin.cpu.arch == .s390x and builtin.os.tag == .linux)) { + try expectEqual(stat.atime(), statat.atime()); + try expectEqual(stat.mtime(), statat.mtime()); + try expectEqual(stat.ctime(), statat.ctime()); + } } test "readlinkat" { -- 2.54.0