From f65cdef7c8bfe7a47e1c30cfa3903ca2d53495cf Mon Sep 17 00:00:00 2001 From: Philippe Pittoli Date: Fri, 6 May 2022 01:03:03 +0200 Subject: [PATCH] std.fs.Dir.statFile: use fstatat This avoids extra syscalls. --- lib/std/fs.zig | 24 +++++++++++++++------ lib/std/fs/file.zig | 51 +++++++++++++++++++++++++++++++++++++++++++++ src/Module.zig | 17 +++++++++------ 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index e253aaff9e..f47f06a2d9 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -2598,14 +2598,26 @@ pub const Dir = struct { return file.stat(); } - pub const StatFileError = File.OpenError || StatError; + pub const StatFileError = File.OpenError || File.StatError || os.FStatAtError; - // TODO: improve this to use the fstatat syscall instead of making 2 syscalls here. - pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!File.Stat { - var file = try self.openFile(sub_path, .{}); - defer file.close(); + /// Provides info on a file (File.Stat) for any file in the opened directory, + /// with a single syscall (fstatat), except on Windows. + /// Currently on Windows, files are opened then closed (implying several syscalls, unfortunately). + /// Symlinks are not followed on linux, haiku, solaris and *BSDs. + /// Other OSs have a default behavior (they currently lack an os.AT.SYMLINK_NOFOLLOW flag). + pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat { + if (builtin.os.tag == .windows) { + var file = try self.openFile(sub_path, .{}); + defer file.close(); + return file.stat(); + } - return file.stat(); + const flags = switch (builtin.os.tag) { + .linux, .haiku, .solaris, .freebsd, .netbsd, .dragonfly, .openbsd => os.AT.SYMLINK_NOFOLLOW, + else => 0, // TODO: correct flags not yet implemented + }; + + return Stat.fromSystemStat(try os.fstatat(self.fd, sub_path, flags)); } const Permissions = File.Permissions; diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index a1e81c9b94..4dd40fe0a2 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -313,6 +313,57 @@ pub const File = struct { mtime: i128, /// Creation time in nanoseconds, relative to UTC 1970-01-01. ctime: i128, + + pub fn systemStatKindToFsKind(st: os.system.Stat) Kind { + const kind: File.Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) + switch (st.filetype) { + .BLOCK_DEVICE => Kind.BlockDevice, + .CHARACTER_DEVICE => Kind.CharacterDevice, + .DIRECTORY => Kind.Directory, + .SYMBOLIC_LINK => Kind.SymLink, + .REGULAR_FILE => Kind.File, + .SOCKET_STREAM, .SOCKET_DGRAM => Kind.UnixDomainSocket, + else => Kind.Unknown, + } + else blk: { + const m = st.mode & os.S.IFMT; + switch (m) { + os.S.IFBLK => break :blk Kind.BlockDevice, + os.S.IFCHR => break :blk Kind.CharacterDevice, + os.S.IFDIR => break :blk Kind.Directory, + os.S.IFIFO => break :blk Kind.NamedPipe, + os.S.IFLNK => break :blk Kind.SymLink, + os.S.IFREG => break :blk Kind.File, + os.S.IFSOCK => break :blk Kind.UnixDomainSocket, + else => {}, + } + if (builtin.os.tag == .solaris) switch (m) { + os.S.IFDOOR => break :blk Kind.Door, + os.S.IFPORT => break :blk Kind.EventPort, + else => {}, + }; + + break :blk .Unknown; + }; + return kind; + } + + pub fn fromSystemStat(st: os.system.Stat) File.StatError!Stat { + const atime = st.atime(); + const mtime = st.mtime(); + const ctime = st.ctime(); + const kind = systemStatKindToFsKind(st); + + return Stat{ + .inode = st.ino, + .size = @bitCast(u64, st.size), + .mode = st.mode, + .kind = kind, + .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, + .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, + .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, + }; + } }; pub const StatError = os.FStatError; diff --git a/src/Module.zig b/src/Module.zig index 83294f3068..d4af06f896 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4137,14 +4137,19 @@ pub fn populateBuiltinFile(mod: *Module) !void { }; } } else |err| switch (err) { - error.BadPathName => unreachable, // it's always "builtin.zig" error.NameTooLong => unreachable, // it's always "builtin.zig" - error.PipeBusy => unreachable, // it's not a pipe - error.WouldBlock => unreachable, // not asking for non-blocking I/O - error.FileNotFound => try writeBuiltinFile(file, builtin_pkg), - - else => |e| return e, + else => |e| { + if (builtin.os.tag == .windows) { + switch (e) { + error.BadPathName => unreachable, // it's always "builtin.zig" + error.PipeBusy => unreachable, // it's not a pipe + error.WouldBlock => unreachable, // not asking for non-blocking I/O + else => return e, + } + } + return e; + }, } file.tree = try std.zig.parse(gpa, file.source);