Commit 13945548fc

Alex Rønne Petersen <alex@alexrp.com>
2024-07-21 12:45:47
std.fs: Rework to always use statx() instead of fstat()/fstatat() on Linux.
statx() is strictly superior to stat() and friends. We can do this because the standard library declares Linux 4.19 to be the minimum version supported in std.Target. This is also necessary on riscv32 where there is only statx(). While here, I improved std.fs.File.metadata() to gather as much information as possible when calling statx() since that is the expectation from this particular API.
1 parent 4e5068c
Changed files (2)
lib
lib/std/fs/Dir.zig
@@ -334,7 +334,6 @@ pub const Iterator = switch (native_os) {
         first_iter: bool,
 
         const Self = @This();
-        const linux = std.os.linux;
 
         pub const Error = IteratorError;
 
@@ -2690,8 +2689,33 @@ pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
         const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });
         return Stat.fromWasi(st);
     }
+    if (native_os == .linux) {
+        const sub_path_c = try posix.toPosixPath(sub_path);
+        var stx = std.mem.zeroes(linux.Statx);
+
+        const rc = linux.statx(
+            self.fd,
+            &sub_path_c,
+            linux.AT.NO_AUTOMOUNT,
+            linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
+            &stx,
+        );
+
+        return switch (linux.E.init(rc)) {
+            .SUCCESS => Stat.fromLinux(stx),
+            .ACCES => error.AccessDenied,
+            .BADF => unreachable,
+            .FAULT => unreachable,
+            .INVAL => unreachable,
+            .LOOP => error.SymLinkLoop,
+            .NAMETOOLONG => unreachable, // Handled by posix.toPosixPath() above.
+            .NOENT, .NOTDIR => error.FileNotFound,
+            .NOMEM => error.SystemResources,
+            else => |err| posix.unexpectedErrno(err),
+        };
+    }
     const st = try posix.fstatat(self.fd, sub_path, 0);
-    return Stat.fromSystem(st);
+    return Stat.fromPosix(st);
 }
 
 pub const ChmodError = File.ChmodError;
@@ -2751,6 +2775,7 @@ const path = fs.path;
 const fs = std.fs;
 const Allocator = std.mem.Allocator;
 const assert = std.debug.assert;
+const linux = std.os.linux;
 const windows = std.os.windows;
 const native_os = builtin.os.tag;
 const have_flock = @TypeOf(posix.system.flock) != void;
lib/std/fs/File.zig
@@ -342,7 +342,7 @@ pub fn seekTo(self: File, offset: u64) SeekError!void {
     return posix.lseek_SET(self.handle, offset);
 }
 
-pub const GetSeekPosError = posix.SeekError || posix.FStatError;
+pub const GetSeekPosError = posix.SeekError || StatError;
 
 /// TODO: integrate with async I/O
 pub fn getPos(self: File) GetSeekPosError!u64 {
@@ -357,7 +357,7 @@ pub fn getEndPos(self: File) GetSeekPosError!u64 {
     return (try self.stat()).size;
 }
 
-pub const ModeError = posix.FStatError;
+pub const ModeError = StatError;
 
 /// TODO: integrate with async I/O
 pub fn mode(self: File) ModeError!Mode {
@@ -392,7 +392,7 @@ pub const Stat = struct {
     /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
     ctime: i128,
 
-    pub fn fromSystem(st: posix.Stat) Stat {
+    pub fn fromPosix(st: posix.Stat) Stat {
         const atime = st.atime();
         const mtime = st.mtime();
         const ctime = st.ctime();
@@ -426,6 +426,31 @@ pub const Stat = struct {
         };
     }
 
+    pub fn fromLinux(stx: linux.Statx) Stat {
+        const atime = stx.atime;
+        const mtime = stx.mtime;
+        const ctime = stx.ctime;
+
+        return .{
+            .inode = stx.ino,
+            .size = stx.size,
+            .mode = stx.mode,
+            .kind = switch (stx.mode & linux.S.IFMT) {
+                linux.S.IFDIR => .directory,
+                linux.S.IFCHR => .character_device,
+                linux.S.IFBLK => .block_device,
+                linux.S.IFREG => .file,
+                linux.S.IFIFO => .named_pipe,
+                linux.S.IFLNK => .sym_link,
+                linux.S.IFSOCK => .unix_domain_socket,
+                else => .unknown,
+            },
+            .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
+            .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
+            .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
+        };
+    }
+
     pub fn fromWasi(st: std.os.wasi.filestat_t) Stat {
         return .{
             .inode = st.ino,
@@ -502,8 +527,34 @@ pub fn stat(self: File) StatError!Stat {
         return Stat.fromWasi(st);
     }
 
+    if (builtin.os.tag == .linux) {
+        var stx = std.mem.zeroes(linux.Statx);
+
+        const rc = linux.statx(
+            self.handle,
+            "",
+            linux.AT.EMPTY_PATH,
+            linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
+            &stx,
+        );
+
+        return switch (linux.E.init(rc)) {
+            .SUCCESS => Stat.fromLinux(stx),
+            .ACCES => unreachable,
+            .BADF => unreachable,
+            .FAULT => unreachable,
+            .INVAL => unreachable,
+            .LOOP => unreachable,
+            .NAMETOOLONG => unreachable,
+            .NOENT => unreachable,
+            .NOMEM => error.SystemResources,
+            .NOTDIR => unreachable,
+            else => |err| posix.unexpectedErrno(err),
+        };
+    }
+
     const st = try posix.fstat(self.handle);
-    return Stat.fromSystem(st);
+    return Stat.fromPosix(st);
 }
 
 pub const ChmodError = posix.FChmodError;
@@ -1009,16 +1060,29 @@ pub fn metadata(self: File) MetadataError!Metadata {
                 };
             },
             .linux => blk: {
-                const l = std.os.linux;
-                var stx = std.mem.zeroes(l.Statx);
-                const rcx = l.statx(self.handle, "\x00", l.AT.EMPTY_PATH, l.STATX_TYPE |
-                    l.STATX_MODE | l.STATX_ATIME | l.STATX_MTIME | l.STATX_BTIME, &stx);
-
-                switch (posix.errno(rcx)) {
+                var stx = std.mem.zeroes(linux.Statx);
+
+                // We are gathering information for Metadata, which is meant to contain all the
+                // native OS information about the file, so use all known flags.
+                const rc = linux.statx(
+                    self.handle,
+                    "",
+                    linux.AT.EMPTY_PATH,
+                    linux.STATX_BASIC_STATS | linux.STATX_BTIME,
+                    &stx,
+                );
+
+                switch (posix.errno(rc)) {
                     .SUCCESS => {},
+                    .ACCES => unreachable,
                     .BADF => unreachable,
                     .FAULT => unreachable,
+                    .INVAL => unreachable,
+                    .LOOP => unreachable,
+                    .NAMETOOLONG => unreachable,
+                    .NOENT => unreachable,
                     .NOMEM => return error.SystemResources,
+                    .NOTDIR => unreachable,
                     else => |err| return posix.unexpectedErrno(err),
                 }
 
@@ -1712,6 +1776,7 @@ const posix = std.posix;
 const io = std.io;
 const math = std.math;
 const assert = std.debug.assert;
+const linux = std.os.linux;
 const windows = std.os.windows;
 const Os = std.builtin.Os;
 const maxInt = std.math.maxInt;