Commit f65cdef7c8

Philippe Pittoli <karchnu@karchnu.fr>
2022-05-06 01:03:03
std.fs.Dir.statFile: use fstatat
This avoids extra syscalls.
1 parent 4af305b
Changed files (3)
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;
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;
+
+    /// 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();
+        }
 
-    // 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();
+        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 file.stat();
+        return Stat.fromSystemStat(try os.fstatat(self.fd, sub_path, flags));
     }
 
     const Permissions = File.Permissions;
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);