Skip to content

Commit

Permalink
std.fs: Rework to always use statx() instead of fstat()/fstatat() on …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
alexrp committed Jul 25, 2024
1 parent 38a54cd commit 31e03d3
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 12 deletions.
29 changes: 27 additions & 2 deletions lib/std/fs/Dir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -2642,8 +2641,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;
Expand Down Expand Up @@ -2700,6 +2724,7 @@ const mem = std.mem;
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;
85 changes: 75 additions & 10 deletions lib/std/fs/File.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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),
}

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 31e03d3

Please sign in to comment.