From 13945548fcd4fe3aab0879b670db1fe8131d8b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 21 Jul 2024 12:45:47 +0200 Subject: [PATCH] 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. --- lib/std/fs/Dir.zig | 29 ++++++++++++++-- lib/std/fs/File.zig | 83 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 101 insertions(+), 11 deletions(-) diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 2743f3ed7d..34967301e1 100644 --- a/lib/std/fs/Dir.zig +++ b/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; diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 05a3b4028e..36e7999bf7 100644 --- a/lib/std/fs/File.zig +++ b/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); + var stx = std.mem.zeroes(linux.Statx); - switch (posix.errno(rcx)) { + // 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;