Skip to content

Commit 152a988

Browse files
Linux: Nuke Stat bits in favour of statx
Maintaining the POSIX `stat` bits for Zig is a pain. Not only is `struct stat` incompatable between architectures, but maddingly annoying so; timestamps are specified as machine longs or fixed-width ints, members can be signed or unsigned. The libcs deal with this by introducing the own version of `struct stat` and copying the kernel structure members to it. In the case of glibc, they did it twice thanks to the largefile transition! In practice, the project needs to maintain three versions of `struct stat`: - What the kernel defines. - What musl wants for `struct stat`. - What glibc wants for `struct stat64`. Make sure to use `fstatat64`! And it's not as simple as running `zig translate-c`. In #21440 I had to create test programs in C and use `pahole` to dump the structure of `stat` for each arch, and I was constantly running into issue regarding padding and signed/unsigned ints. The fact that so many target checks in the `linux` and `posix` tests exist is most likely due to writing to padding bits and failing later. The solution to this madness is `statx(2)`: - It takes a single structure that is the same for all arches AND libcs. - It uses a custom timestamp format, but it is 64-bit ready. - It gives the same info as `fstatat(2)` and more! - Unlike `fstatat(2)`, you can request a subset of the info required based on passing a mask. It's so good that modern Linux arches (e.g. riscv) don't even implement `stat`, with the libcs using a generic `struct stat` and copying from `struct statx`. Therefore, this commit rips out all the `stat` bits from `std.os.linux` and `std.c`. `std.posix.Stat` is now `void`, and calling `std.posix.*stat` is an compile-time error. A wrapper around `statx` has been added to `std.os.linux`, and callers have been upgraded to use it. Tests have also been updated to use `statx` where possible. While I was here, I converted the mask and file attributes to be packed struct bitfields. A nice side effect is checking that you actually recieved the members you asked for via `Statx.mask`, which I have used by adding `assert`s at specific callsites. In the future I expect types like `mode_t`/`dev_t` to be audited and removed, as they aren't being used to define members of `struct stat`.
1 parent 2e26fbd commit 152a988

30 files changed

+303
-1011
lines changed

lib/std/c.zig

Lines changed: 5 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -7463,166 +7463,6 @@ pub const EAI = if (builtin.abi.isAndroid()) enum(c_int) {
74637463
pub const dl_iterate_phdr_callback = *const fn (info: *dl_phdr_info, size: usize, data: ?*anyopaque) callconv(.c) c_int;
74647464

74657465
pub const Stat = switch (native_os) {
7466-
.linux => switch (native_arch) {
7467-
.sparc64 => extern struct {
7468-
dev: u64,
7469-
__pad1: u16,
7470-
ino: ino_t,
7471-
mode: u32,
7472-
nlink: u32,
7473-
7474-
uid: u32,
7475-
gid: u32,
7476-
rdev: u64,
7477-
__pad2: u16,
7478-
7479-
size: off_t,
7480-
blksize: isize,
7481-
blocks: i64,
7482-
7483-
atim: timespec,
7484-
mtim: timespec,
7485-
ctim: timespec,
7486-
__reserved: [2]usize,
7487-
7488-
pub fn atime(self: @This()) timespec {
7489-
return self.atim;
7490-
}
7491-
7492-
pub fn mtime(self: @This()) timespec {
7493-
return self.mtim;
7494-
}
7495-
7496-
pub fn ctime(self: @This()) timespec {
7497-
return self.ctim;
7498-
}
7499-
},
7500-
.mips, .mipsel => if (builtin.target.abi.isMusl()) extern struct {
7501-
dev: dev_t,
7502-
__pad0: [2]i32,
7503-
ino: ino_t,
7504-
mode: mode_t,
7505-
nlink: nlink_t,
7506-
uid: uid_t,
7507-
gid: gid_t,
7508-
rdev: dev_t,
7509-
__pad1: [2]i32,
7510-
size: off_t,
7511-
atim: timespec,
7512-
mtim: timespec,
7513-
ctim: timespec,
7514-
blksize: blksize_t,
7515-
__pad3: i32,
7516-
blocks: blkcnt_t,
7517-
__pad4: [14]i32,
7518-
7519-
pub fn atime(self: @This()) timespec {
7520-
return self.atim;
7521-
}
7522-
7523-
pub fn mtime(self: @This()) timespec {
7524-
return self.mtim;
7525-
}
7526-
7527-
pub fn ctime(self: @This()) timespec {
7528-
return self.ctim;
7529-
}
7530-
} else extern struct {
7531-
dev: u32,
7532-
__pad0: [3]u32,
7533-
ino: ino_t,
7534-
mode: mode_t,
7535-
nlink: nlink_t,
7536-
uid: uid_t,
7537-
gid: gid_t,
7538-
rdev: u32,
7539-
__pad1: [3]u32,
7540-
size: off_t,
7541-
atim: timespec,
7542-
mtim: timespec,
7543-
ctim: timespec,
7544-
blksize: blksize_t,
7545-
__pad3: u32,
7546-
blocks: blkcnt_t,
7547-
__pad4: [14]u32,
7548-
7549-
pub fn atime(self: @This()) timespec {
7550-
return self.atim;
7551-
}
7552-
7553-
pub fn mtime(self: @This()) timespec {
7554-
return self.mtim;
7555-
}
7556-
7557-
pub fn ctime(self: @This()) timespec {
7558-
return self.ctim;
7559-
}
7560-
},
7561-
.mips64, .mips64el => if (builtin.target.abi.isMusl()) extern struct {
7562-
dev: dev_t,
7563-
__pad0: [3]i32,
7564-
ino: ino_t,
7565-
mode: mode_t,
7566-
nlink: nlink_t,
7567-
uid: uid_t,
7568-
gid: gid_t,
7569-
rdev: dev_t,
7570-
__pad1: [2]u32,
7571-
size: off_t,
7572-
__pad2: i32,
7573-
atim: timespec,
7574-
mtim: timespec,
7575-
ctim: timespec,
7576-
blksize: blksize_t,
7577-
__pad3: u32,
7578-
blocks: blkcnt_t,
7579-
__pad4: [14]i32,
7580-
7581-
pub fn atime(self: @This()) timespec {
7582-
return self.atim;
7583-
}
7584-
7585-
pub fn mtime(self: @This()) timespec {
7586-
return self.mtim;
7587-
}
7588-
7589-
pub fn ctime(self: @This()) timespec {
7590-
return self.ctim;
7591-
}
7592-
} else extern struct {
7593-
dev: dev_t,
7594-
__pad0: [3]u32,
7595-
ino: ino_t,
7596-
mode: mode_t,
7597-
nlink: nlink_t,
7598-
uid: uid_t,
7599-
gid: gid_t,
7600-
rdev: dev_t,
7601-
__pad1: [3]u32,
7602-
size: off_t,
7603-
atim: timespec,
7604-
mtim: timespec,
7605-
ctim: timespec,
7606-
blksize: blksize_t,
7607-
__pad3: u32,
7608-
blocks: blkcnt_t,
7609-
__pad4: [14]i32,
7610-
7611-
pub fn atime(self: @This()) timespec {
7612-
return self.atim;
7613-
}
7614-
7615-
pub fn mtime(self: @This()) timespec {
7616-
return self.mtim;
7617-
}
7618-
7619-
pub fn ctime(self: @This()) timespec {
7620-
return self.ctim;
7621-
}
7622-
},
7623-
7624-
else => std.os.linux.Stat, // libc stat is the same as kernel stat.
7625-
},
76267466
.emscripten => emscripten.Stat,
76277467
.wasi => extern struct {
76287468
// Match wasi-libc's `struct stat` in lib/libc/include/wasm-wasi-musl/__struct_stat.h
@@ -10260,6 +10100,7 @@ pub const fstat = switch (native_os) {
1026010100
else => private.fstat,
1026110101
},
1026210102
.netbsd => private.__fstat50,
10103+
.linux => {},
1026310104
else => private.fstat,
1026410105
};
1026510106

@@ -10268,8 +10109,12 @@ pub const fstatat = switch (native_os) {
1026810109
.x86_64 => private.@"fstatat$INODE64",
1026910110
else => private.fstatat,
1027010111
},
10112+
.linux => {},
1027110113
else => private.fstatat,
1027210114
};
10115+
10116+
pub extern "c" fn statx(dirfd: fd_t, path: [*:0]const u8, flags: u32, mask: linux.STATX, buf: *linux.Statx) c_int;
10117+
1027310118
pub extern "c" fn getpwent() ?*passwd;
1027410119
pub extern "c" fn endpwent() void;
1027510120
pub extern "c" fn setpwent() void;
@@ -10343,8 +10188,6 @@ pub extern "c" fn inotify_init1(flags: c_uint) c_int;
1034310188
pub extern "c" fn inotify_add_watch(fd: fd_t, pathname: [*:0]const u8, mask: u32) c_int;
1034410189
pub extern "c" fn inotify_rm_watch(fd: fd_t, wd: c_int) c_int;
1034510190

10346-
pub extern "c" fn fstat64(fd: fd_t, buf: *Stat) c_int;
10347-
pub extern "c" fn fstatat64(dirfd: fd_t, noalias path: [*:0]const u8, noalias stat_buf: *Stat, flags: u32) c_int;
1034810191
pub extern "c" fn fallocate64(fd: fd_t, mode: c_int, offset: off_t, len: off_t) c_int;
1034910192
pub extern "c" fn fopen64(noalias filename: [*:0]const u8, noalias modes: [*:0]const u8) ?*FILE;
1035010193
pub extern "c" fn ftruncate64(fd: c_int, length: off_t) c_int;
@@ -10517,14 +10360,6 @@ pub const socketpair = switch (native_os) {
1051710360
else => private.socketpair,
1051810361
};
1051910362

10520-
pub const stat = switch (native_os) {
10521-
.macos => switch (native_arch) {
10522-
.x86_64 => private.@"stat$INODE64",
10523-
else => private.stat,
10524-
},
10525-
else => private.stat,
10526-
};
10527-
1052810363
pub const _msize = switch (native_os) {
1052910364
.windows => private._msize,
1053010365
else => {},
@@ -11363,7 +11198,6 @@ const private = struct {
1136311198
extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;
1136411199
extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int;
1136511200
extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint, sv: *[2]fd_t) c_int;
11366-
extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: *Stat) c_int;
1136711201
extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
1136811202
extern "c" fn sysconf(sc: c_int) c_long;
1136911203
extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int;

lib/std/fs/Dir.zig

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2805,32 +2805,26 @@ pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
28052805
const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });
28062806
return Stat.fromWasi(st);
28072807
}
2808-
if (native_os == .linux) {
2809-
const sub_path_c = try posix.toPosixPath(sub_path);
2810-
var stx = std.mem.zeroes(linux.Statx);
2811-
2812-
const rc = linux.statx(
2813-
self.fd,
2814-
&sub_path_c,
2815-
linux.AT.NO_AUTOMOUNT,
2816-
linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
2817-
&stx,
2818-
);
28192808

2820-
return switch (linux.E.init(rc)) {
2821-
.SUCCESS => Stat.fromLinux(stx),
2822-
.ACCES => error.AccessDenied,
2823-
.BADF => unreachable,
2824-
.FAULT => unreachable,
2825-
.INVAL => unreachable,
2826-
.LOOP => error.SymLinkLoop,
2827-
.NAMETOOLONG => unreachable, // Handled by posix.toPosixPath() above.
2828-
.NOENT, .NOTDIR => error.FileNotFound,
2829-
.NOMEM => error.SystemResources,
2830-
else => |err| posix.unexpectedErrno(err),
2831-
};
2832-
}
2833-
const st = try posix.fstatat(self.fd, sub_path, 0);
2809+
const sub_path_z = try posix.toPosixPath(sub_path);
2810+
if (native_os == .linux) return if (linux.wrapped.statx(
2811+
self.fd,
2812+
&sub_path_z,
2813+
linux.AT.NO_AUTOMOUNT,
2814+
.{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true },
2815+
)) |stx| blk: {
2816+
assert(stx.mask.TYPE);
2817+
assert(stx.mask.MODE);
2818+
assert(stx.mask.ATIME);
2819+
assert(stx.mask.MTIME);
2820+
assert(stx.mask.CTIME);
2821+
break :blk Stat.fromLinux(stx);
2822+
} else |err| switch (err) {
2823+
error.NameTooLong => unreachable, // Handled by toPosixPath above.
2824+
else => |e| e,
2825+
};
2826+
2827+
const st = try posix.fstatatZ(self.fd, &sub_path_z, 0);
28342828
return Stat.fromPosix(st);
28352829
}
28362830

lib/std/fs/File.zig

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -552,37 +552,29 @@ pub fn stat(self: File) StatError!Stat {
552552
.ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
553553
};
554554
}
555-
556555
if (builtin.os.tag == .wasi and !builtin.link_libc) {
557556
const st = try std.os.fstat_wasi(self.handle);
558557
return Stat.fromWasi(st);
559558
}
560-
561-
if (builtin.os.tag == .linux) {
562-
var stx = std.mem.zeroes(linux.Statx);
563-
564-
const rc = linux.statx(
565-
self.handle,
566-
"",
567-
linux.AT.EMPTY_PATH,
568-
linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
569-
&stx,
570-
);
571-
572-
return switch (linux.E.init(rc)) {
573-
.SUCCESS => Stat.fromLinux(stx),
574-
.ACCES => unreachable,
575-
.BADF => unreachable,
576-
.FAULT => unreachable,
577-
.INVAL => unreachable,
578-
.LOOP => unreachable,
579-
.NAMETOOLONG => unreachable,
580-
.NOENT => unreachable,
581-
.NOMEM => error.SystemResources,
582-
.NOTDIR => unreachable,
583-
else => |err| posix.unexpectedErrno(err),
584-
};
585-
}
559+
if (builtin.os.tag == .linux) return if (linux.wrapped.statx(
560+
self.handle,
561+
"",
562+
linux.AT.EMPTY_PATH,
563+
.{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true },
564+
)) |stx| blk: {
565+
assert(stx.mask.TYPE);
566+
assert(stx.mask.MODE);
567+
assert(stx.mask.ATIME);
568+
assert(stx.mask.MTIME);
569+
assert(stx.mask.CTIME);
570+
break :blk Stat.fromLinux(stx);
571+
} else |err| switch (err) {
572+
error.AccessDenied => unreachable,
573+
error.SymLinkLoop => unreachable,
574+
error.FileNotFound => unreachable,
575+
error.NameTooLong => unreachable,
576+
else => |e| e,
577+
};
586578

587579
const st = try posix.fstat(self.handle);
588580
return Stat.fromPosix(st);
@@ -1166,7 +1158,7 @@ pub const Reader = struct {
11661158
return err;
11671159
}
11681160
}
1169-
if (posix.Stat == void) {
1161+
if (posix.Stat == void and native_os != .linux) {
11701162
r.size_err = error.Streaming;
11711163
return error.Streaming;
11721164
}

0 commit comments

Comments
 (0)