fix: use safe version of posix.read for tree walking

std.posix.read will panic when walking trees with unreadable files
such as `/proc` on linux.
This commit is contained in:
CJ van den Berg 2026-02-11 14:21:38 +01:00
parent 0503357509
commit a581bef152
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9

View file

@ -642,7 +642,7 @@ pub fn guess_file_type(file_path: []const u8) struct { []const u8, []const u8, u
const content: []const u8 = blk: { const content: []const u8 = blk: {
const file = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch break :blk &.{}; const file = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch break :blk &.{};
defer file.close(); defer file.close();
const size = file.read(&buf) catch break :blk &.{}; const size = safe_file_read(file, &buf) catch break :blk &.{};
break :blk buf[0..size]; break :blk buf[0..size];
}; };
return if (file_type_config.guess_file_type(file_path, content)) |ft| .{ return if (file_type_config.guess_file_type(file_path, content)) |ft| .{
@ -652,6 +652,81 @@ pub fn guess_file_type(file_path: []const u8) struct { []const u8, []const u8, u
} else default_ft(); } else default_ft();
} }
fn safe_file_read(self: std.fs.File, buffer: []u8) (error{FileHandleInvalidForReading} || std.fs.File.ReadError)!usize {
if (builtin.os.tag == .windows) {
return std.os.windows.ReadFile(self.handle, buffer, null);
}
return safe_posix_read(self.handle, buffer);
}
fn safe_posix_read(fd: std.posix.fd_t, buf: []u8) (error{FileHandleInvalidForReading} || std.fs.File.ReadError)!usize {
const native_os = builtin.os.tag;
const unexpectedErrno = std.posix.unexpectedErrno;
const maxInt = std.math.maxInt;
const system = std.posix.system;
const errno = std.posix.errno;
if (buf.len == 0) return 0;
if (native_os == .windows) {
return std.os.windows.ReadFile(fd, buf, null);
}
if (native_os == .wasi and !builtin.link_libc) {
const iovec = std.os.posix.iovec;
const wasi = std.os.wasi;
const iovs = [1]iovec{iovec{
.base = buf.ptr,
.len = buf.len,
}};
var nread: usize = undefined;
switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) {
.SUCCESS => return nread,
.INTR => unreachable,
.INVAL => return error.FileHandleInvalidForReading,
.FAULT => unreachable,
.AGAIN => unreachable,
.BADF => return error.NotOpenForReading, // Can be a race condition.
.IO => return error.InputOutput,
.ISDIR => return error.IsDir,
.NOBUFS => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOTCONN => return error.SocketNotConnected,
.CONNRESET => return error.ConnectionResetByPeer,
.TIMEDOUT => return error.ConnectionTimedOut,
.NOTCAPABLE => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
// Prevents EINVAL.
const max_count = switch (native_os) {
.linux => 0x7ffff000,
.macos, .ios, .watchos, .tvos, .visionos => maxInt(i32),
else => maxInt(isize),
};
while (true) {
const rc = system.read(fd, buf.ptr, @min(buf.len, max_count));
switch (errno(rc)) {
.SUCCESS => return @intCast(rc),
.INTR => continue,
.INVAL => return error.FileHandleInvalidForReading,
.FAULT => unreachable,
.SRCH => return error.ProcessNotFound,
.AGAIN => return error.WouldBlock,
.CANCELED => return error.Canceled,
.BADF => return error.NotOpenForReading, // Can be a race condition.
.IO => return error.InputOutput,
.ISDIR => return error.IsDir,
.NOBUFS => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOTCONN => return error.SocketNotConnected,
.CONNRESET => return error.ConnectionResetByPeer,
.TIMEDOUT => return error.ConnectionTimedOut,
else => |err| return unexpectedErrno(err),
}
}
}
fn merge_pending_files(self: *Self) OutOfMemoryError!void { fn merge_pending_files(self: *Self) OutOfMemoryError!void {
defer self.sort_files_by_mtime(); defer self.sort_files_by_mtime();
const existing = try self.files.toOwnedSlice(self.allocator); const existing = try self.files.toOwnedSlice(self.allocator);