Commit d87cd06296

Andrew Kelley <andrew@ziglang.org>
2020-06-21 00:27:37
rework zig fmt to use less syscalls and open fds
* `std.fs.Dir.Entry.Kind` is moved to `std.fs.File.Kind` * `std.fs.File.Stat` gains the `kind` field, so performing a stat() on a File now tells what kind of file it is. On Windows this only will distinguish between directories and files. * rework zig fmt logic so that in the case of opening a file and discovering it to be a directory, it closes the file descriptor before re-opening it with O_DIRECTORY, using fewer simultaneous open file descriptors when walking a directory tree. * rework zig fmt logic so that it pays attention to the kind of directory entries, and when it sees a sub-directory it attempts to open it as a directory rather than a file, reducing the number of open() syscalls when walking a directory tree.
1 parent bc0ca73
Changed files (3)
lib
src-self-hosted
lib/std/fs/file.zig
@@ -29,6 +29,18 @@ pub const File = struct {
     pub const Mode = os.mode_t;
     pub const INode = os.ino_t;
 
+    pub const Kind = enum {
+        BlockDevice,
+        CharacterDevice,
+        Directory,
+        NamedPipe,
+        SymLink,
+        File,
+        UnixDomainSocket,
+        Whiteout,
+        Unknown,
+    };
+
     pub const default_mode = switch (builtin.os.tag) {
         .windows => 0,
         .wasi => 0,
@@ -219,13 +231,14 @@ pub const File = struct {
         /// unique across time, as some file systems may reuse an inode after its file has been deleted.
         /// Some systems may change the inode of a file over time.
         ///
-        /// On Linux, the inode _is_ structure that stores the metadata, and the inode _number_ is what
+        /// On Linux, the inode is a structure that stores the metadata, and the inode _number_ is what
         /// you see here: the index number of the inode.
         ///
         /// The FileIndex on Windows is similar. It is a number for a file that is unique to each filesystem.
         inode: INode,
         size: u64,
         mode: Mode,
+        kind: Kind,
 
         /// Access time in nanoseconds, relative to UTC 1970-01-01.
         atime: i128,
@@ -254,6 +267,7 @@ pub const File = struct {
                 .inode = info.InternalInformation.IndexNumber,
                 .size = @bitCast(u64, info.StandardInformation.EndOfFile),
                 .mode = 0,
+                .kind = if (info.StandardInformation.Directory == 0) .File else .Directory,
                 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
                 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
                 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
@@ -268,6 +282,16 @@ pub const File = struct {
             .inode = st.ino,
             .size = @bitCast(u64, st.size),
             .mode = st.mode,
+            .kind = switch (st.mode & os.S_IFMT) {
+                os.S_IFBLK => .BlockDevice,
+                os.S_IFCHR => .CharacterDevice,
+                os.S_IFDIR => .Directory,
+                os.S_IFIFO => .NamedPipe,
+                os.S_IFLNK => .SymLink,
+                os.S_IFREG => .File,
+                os.S_IFSOCK => .UnixDomainSocket,
+                else => .Unknown,
+            },
             .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,
lib/std/fs.zig
@@ -261,17 +261,7 @@ pub const Dir = struct {
         name: []const u8,
         kind: Kind,
 
-        pub const Kind = enum {
-            BlockDevice,
-            CharacterDevice,
-            Directory,
-            NamedPipe,
-            SymLink,
-            File,
-            UnixDomainSocket,
-            Whiteout,
-            Unknown,
-        };
+        pub const Kind = File.Kind;
     };
 
     const IteratorError = error{AccessDenied} || os.UnexpectedError;
src-self-hosted/main.zig
@@ -670,11 +670,12 @@ const FmtError = error{
     ReadOnlyFileSystem,
     LinkQuotaExceeded,
     FileBusy,
+    EndOfStream,
 } || fs.File.OpenError;
 
 fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void {
     // get the real path here to avoid Windows failing on relative file paths with . or .. in them
-    var real_path = fs.realpathAlloc(fmt.gpa, file_path) catch |err| {
+    const real_path = fs.realpathAlloc(fmt.gpa, file_path) catch |err| {
         std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });
         fmt.any_error = true;
         return;
@@ -684,47 +685,65 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void {
     if (fmt.seen.exists(real_path)) return;
     try fmt.seen.put(real_path);
 
-    const source_file = fs.cwd().openFile(real_path, .{}) catch |err| {
-        std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });
-        fmt.any_error = true;
-        return;
+    fmtPathFile(fmt, file_path, check_mode, real_path) catch |err| switch (err) {
+        error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, real_path),
+        else => {
+            std.debug.warn("unable to format '{}': {}\n", .{ file_path, err });
+            fmt.any_error = true;
+            return;
+        },
     };
-    defer source_file.close();
+}
 
-    const stat = source_file.stat() catch |err| {
-        std.debug.warn("unable to stat '{}': {}\n", .{ file_path, err });
-        fmt.any_error = true;
-        return;
-    };
+fn fmtPathDir(fmt: *Fmt, file_path: []const u8, check_mode: bool, parent_real_path: []const u8) FmtError!void {
+    var dir = try fs.cwd().openDir(parent_real_path, .{ .iterate = true });
+    defer dir.close();
+
+    var dir_it = dir.iterate();
+    while (try dir_it.next()) |entry| {
+        const is_dir = entry.kind == .Directory;
+        if (is_dir or mem.endsWith(u8, entry.name, ".zig")) {
+            const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name });
+            const sub_real_path = fs.realpathAlloc(fmt.gpa, full_path) catch |err| {
+                std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });
+                fmt.any_error = true;
+                return;
+            };
+            defer fmt.gpa.free(sub_real_path);
+
+            if (fmt.seen.exists(sub_real_path)) return;
+            try fmt.seen.put(sub_real_path);
+
+            if (is_dir) {
+                try fmtPathDir(fmt, full_path, check_mode, sub_real_path);
+            } else {
+                fmtPathFile(fmt, full_path, check_mode, sub_real_path) catch |err| {
+                    std.debug.warn("unable to format '{}': {}\n", .{ full_path, err });
+                    fmt.any_error = true;
+                    return;
+                };
+            }
+        }
+    }
+}
 
-    const source_code = source_file.readAllAlloc(fmt.gpa, stat.size, max_src_size) catch |err| switch (err) {
-        error.IsDir => {
-            var dir = try fs.cwd().openDir(file_path, .{ .iterate = true });
-            defer dir.close();
+fn fmtPathFile(fmt: *Fmt, file_path: []const u8, check_mode: bool, real_path: []const u8) FmtError!void {
+    const source_file = try fs.cwd().openFile(real_path, .{});
+    defer source_file.close();
 
-            var dir_it = dir.iterate();
+    const stat = try source_file.stat();
 
-            while (try dir_it.next()) |entry| {
-                if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) {
-                    const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name });
-                    try fmtPath(fmt, full_path, check_mode);
-                }
-            }
-            return;
-        },
-        else => {
-            std.debug.warn("unable to read '{}': {}\n", .{ file_path, err });
-            fmt.any_error = true;
-            return;
-        },
+    if (stat.kind == .Directory)
+        return error.IsDir;
+
+    const source_code = source_file.readAllAlloc(fmt.gpa, stat.size, max_src_size) catch |err| switch (err) {
+        error.ConnectionResetByPeer => unreachable,
+        error.ConnectionTimedOut => unreachable,
+        else => |e| return e,
     };
     defer fmt.gpa.free(source_code);
 
-    const tree = std.zig.parse(fmt.gpa, source_code) catch |err| {
-        std.debug.warn("error parsing file '{}': {}\n", .{ file_path, err });
-        fmt.any_error = true;
-        return;
-    };
+    const tree = try std.zig.parse(fmt.gpa, source_code);
     defer tree.deinit();
 
     for (tree.errors) |parse_error| {