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.
This commit is contained in:
Andrew Kelley 2020-06-20 18:27:37 -04:00
parent bc0ca73887
commit d87cd06296
3 changed files with 80 additions and 47 deletions

View File

@ -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;

View File

@ -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,

View File

@ -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;
};
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;
};
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();
var dir_it = dir.iterate();
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;
},
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 read '{}': {}\n", .{ file_path, err });
std.debug.warn("unable to format '{}': {}\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;
};
}
}
}
}
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();
const stat = try source_file.stat();
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| {