diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 374bfa6ed3..523ef98824 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -38,6 +38,10 @@ pub fn main() !void { const args = try process.argsAlloc(arena); + var threaded: std.Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + // skip my own exe name var arg_idx: usize = 1; @@ -68,6 +72,7 @@ pub fn main() !void { }; var graph: std.Build.Graph = .{ + .io = io, .arena = arena, .cache = .{ .gpa = arena, diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 9fd906e333..d3df0c0d39 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1,5 +1,7 @@ -const std = @import("std.zig"); const builtin = @import("builtin"); + +const std = @import("std.zig"); +const Io = std.Io; const fs = std.fs; const mem = std.mem; const debug = std.debug; @@ -110,6 +112,7 @@ pub const ReleaseMode = enum { /// Shared state among all Build instances. /// Settings that are here rather than in Build are not configurable per-package. pub const Graph = struct { + io: Io, arena: Allocator, system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty, system_package_mode: bool = false, @@ -2666,9 +2669,10 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget { // Hot path. This is faster than querying the native CPU and OS again. return b.graph.host; } + const io = b.graph.io; return .{ .query = query, - .result = std.zig.system.resolveTargetQuery(query) catch + .result = std.zig.system.resolveTargetQuery(io, query) catch @panic("unable to resolve target query"), }; } diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index fd6194f7ff..d738b8edaa 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -532,6 +532,8 @@ const Arg = struct { test Options { if (builtin.os.tag == .wasi) return error.SkipZigTest; + const io = std.testing.io; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); @@ -546,7 +548,7 @@ test Options { .global_cache_root = .{ .path = "test", .handle = std.fs.cwd() }, .host = .{ .query = .{}, - .result = try std.zig.system.resolveTargetQuery(.{}), + .result = try std.zig.system.resolveTargetQuery(io, .{}), }, .zig_lib_directory = std.Build.Cache.Directory.cwd(), .time_report = false, diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index a67b96da03..135fbe7f0a 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -516,6 +516,7 @@ pub fn serveTarFile( } fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path { + const io = ws.graph.io; const root_name = "build-web"; const arch_os_abi = "wasm32-freestanding"; const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; @@ -659,7 +660,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim }; const bin_name = try std.zig.binNameAlloc(arena, .{ .root_name = root_name, - .target = &(std.zig.system.resolveTargetQuery(std.Build.parseTargetQuery(.{ + .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{ .arch_os_abi = arch_os_abi, .cpu_features = cpu_features, }) catch unreachable) catch unreachable), diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 5f3c9811b8..5cdb9b0f01 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -738,9 +738,9 @@ pub const Timestamp = struct { /// * On Linux, corresponds `CLOCK_MONOTONIC`. /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`. awake, - /// Identical to `awake` except it expresses intent to include time - /// that the system is suspended, however, it may be implemented - /// identically to `awake`. + /// Identical to `awake` except it expresses intent to **include time + /// that the system is suspended**, however, due to limitations it may + /// behave identically to `awake`. /// /// * On Linux, corresponds `CLOCK_BOOTTIME`. /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 67f3f553ec..0727730b93 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1054,7 +1054,7 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Error! fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const pool: *Pool = @ptrCast(@alignCast(userdata)); const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { - .none => .monotonic, + .none => .awake, .duration => |d| d.clock, .deadline => |d| d.clock, }); @@ -1087,7 +1087,6 @@ fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const ms = ms: { const duration_and_clock = (try timeout.toDurationFromNow(pool.io())) orelse break :ms std.math.maxInt(windows.DWORD); - if (duration_and_clock.clock != .monotonic) return error.UnsupportedClock; break :ms std.math.lossyCast(windows.DWORD, duration_and_clock.duration.toMilliseconds()); }; windows.kernel32.Sleep(ms); @@ -1132,8 +1131,6 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { .sec = std.math.maxInt(sec_type), .nsec = std.math.maxInt(nsec_type), }; - // TODO check which clock nanosleep uses on this host - // and return error.UnsupportedClock if it does not match const ns = d.duration.nanoseconds; break :t .{ .sec = @intCast(@divFloor(ns, std.time.ns_per_s)), @@ -2046,9 +2043,9 @@ fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t { fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t { return switch (clock) { .realtime => .REALTIME, - .monotonic => .MONOTONIC, - .uptime => .MONOTONIC, - .process_cputime_id => .PROCESS_CPUTIME_ID, - .thread_cputime_id => .THREAD_CPUTIME_ID, + .awake => .MONOTONIC, + .boot => .MONOTONIC, + .cpu_process => .PROCESS_CPUTIME_ID, + .cpu_thread => .THREAD_CPUTIME_ID, }; } diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 8fdc64987c..e305145575 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -228,7 +228,7 @@ pub const IpAddress = union(enum) { /// /// One bound `Socket` can be used to receive messages from multiple /// different addresses. - pub fn bind(address: IpAddress, io: Io, options: BindOptions) BindError!Socket { + pub fn bind(address: *const IpAddress, io: Io, options: BindOptions) BindError!Socket { return io.vtable.ipBind(io.userdata, address, options); } diff --git a/lib/std/Target/Query.zig b/lib/std/Target/Query.zig index 90c127d800..f3f5155b06 100644 --- a/lib/std/Target/Query.zig +++ b/lib/std/Target/Query.zig @@ -612,6 +612,8 @@ fn versionEqualOpt(a: ?SemanticVersion, b: ?SemanticVersion) bool { } test parse { + const io = std.testing.io; + if (builtin.target.isGnuLibC()) { var query = try Query.parse(.{}); query.setGnuLibCVersion(2, 1, 1); @@ -654,7 +656,7 @@ test parse { .arch_os_abi = "x86_64-linux-gnu", .cpu_features = "x86_64-sse-sse2-avx-cx8", }); - const target = try std.zig.system.resolveTargetQuery(query); + const target = try std.zig.system.resolveTargetQuery(io, query); try std.testing.expect(target.os.tag == .linux); try std.testing.expect(target.abi == .gnu); @@ -679,7 +681,7 @@ test parse { .arch_os_abi = "arm-linux-musleabihf", .cpu_features = "generic+v8a", }); - const target = try std.zig.system.resolveTargetQuery(query); + const target = try std.zig.system.resolveTargetQuery(io, query); try std.testing.expect(target.os.tag == .linux); try std.testing.expect(target.abi == .musleabihf); @@ -696,7 +698,7 @@ test parse { .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27", .cpu_features = "generic+v8a", }); - const target = try std.zig.system.resolveTargetQuery(query); + const target = try std.zig.system.resolveTargetQuery(io, query); try std.testing.expect(target.cpu.arch == .aarch64); try std.testing.expect(target.os.tag == .linux); @@ -719,7 +721,7 @@ test parse { const query = try Query.parse(.{ .arch_os_abi = "aarch64-linux.3.10...4.4.1-android.30", }); - const target = try std.zig.system.resolveTargetQuery(query); + const target = try std.zig.system.resolveTargetQuery(io, query); try std.testing.expect(target.cpu.arch == .aarch64); try std.testing.expect(target.os.tag == .linux); @@ -740,7 +742,7 @@ test parse { const query = try Query.parse(.{ .arch_os_abi = "x86-windows.xp...win8-msvc", }); - const target = try std.zig.system.resolveTargetQuery(query); + const target = try std.zig.system.resolveTargetQuery(io, query); try std.testing.expect(target.cpu.arch == .x86); try std.testing.expect(target.os.tag == .windows); diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 3b0c085003..746d24f61a 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -1,9 +1,11 @@ //! Executable and Linkable Format. const std = @import("std.zig"); +const Io = std.Io; const math = std.math; const mem = std.mem; const assert = std.debug.assert; +const Endian = std.builtin.Endian; const native_endian = @import("builtin").target.cpu.arch.endian(); pub const AT_NULL = 0; @@ -568,7 +570,7 @@ pub const ET = enum(u16) { /// All integers are native endian. pub const Header = struct { is_64: bool, - endian: std.builtin.Endian, + endian: Endian, os_abi: OSABI, /// The meaning of this value depends on `os_abi`. abi_version: u8, @@ -583,48 +585,76 @@ pub const Header = struct { shnum: u16, shstrndx: u16, - pub fn iterateProgramHeaders(h: Header, file_reader: *std.fs.File.Reader) ProgramHeaderIterator { + pub fn iterateProgramHeaders(h: *const Header, file_reader: *Io.File.Reader) ProgramHeaderIterator { return .{ - .elf_header = h, + .is_64 = h.is_64, + .endian = h.endian, + .phnum = h.phnum, + .phoff = h.phoff, .file_reader = file_reader, }; } - pub fn iterateProgramHeadersBuffer(h: Header, buf: []const u8) ProgramHeaderBufferIterator { + pub fn iterateProgramHeadersBuffer(h: *const Header, buf: []const u8) ProgramHeaderBufferIterator { return .{ - .elf_header = h, + .is_64 = h.is_64, + .endian = h.endian, + .phnum = h.phnum, + .phoff = h.phoff, .buf = buf, }; } - pub fn iterateSectionHeaders(h: Header, file_reader: *std.fs.File.Reader) SectionHeaderIterator { + pub fn iterateSectionHeaders(h: *const Header, file_reader: *Io.File.Reader) SectionHeaderIterator { return .{ - .elf_header = h, + .is_64 = h.is_64, + .endian = h.endian, + .shnum = h.shnum, + .shoff = h.shoff, .file_reader = file_reader, }; } - pub fn iterateSectionHeadersBuffer(h: Header, buf: []const u8) SectionHeaderBufferIterator { + pub fn iterateSectionHeadersBuffer(h: *const Header, buf: []const u8) SectionHeaderBufferIterator { return .{ - .elf_header = h, + .is_64 = h.is_64, + .endian = h.endian, + .shnum = h.shnum, + .shoff = h.shoff, .buf = buf, }; } - pub const ReadError = std.Io.Reader.Error || error{ + pub fn iterateDynamicSection( + h: *const Header, + file_reader: *Io.File.Reader, + offset: u64, + size: u64, + ) DynamicSectionIterator { + return .{ + .is_64 = h.is_64, + .endian = h.endian, + .offset = offset, + .end_offset = offset + size, + .file_reader = file_reader, + }; + } + + pub const ReadError = Io.Reader.Error || error{ InvalidElfMagic, InvalidElfVersion, InvalidElfClass, InvalidElfEndian, }; - pub fn read(r: *std.Io.Reader) ReadError!Header { + /// If this function fails, seek position of `r` is unchanged. + pub fn read(r: *Io.Reader) ReadError!Header { const buf = try r.peek(@sizeOf(Elf64_Ehdr)); if (!mem.eql(u8, buf[0..4], MAGIC)) return error.InvalidElfMagic; if (buf[EI.VERSION] != 1) return error.InvalidElfVersion; - const endian: std.builtin.Endian = switch (buf[EI.DATA]) { + const endian: Endian = switch (buf[EI.DATA]) { ELFDATA2LSB => .little, ELFDATA2MSB => .big, else => return error.InvalidElfEndian, @@ -637,7 +667,7 @@ pub const Header = struct { }; } - pub fn init(hdr: anytype, endian: std.builtin.Endian) Header { + pub fn init(hdr: anytype, endian: Endian) Header { // Converting integers to exhaustive enums using `@enumFromInt` could cause a panic. comptime assert(!@typeInfo(OSABI).@"enum".is_exhaustive); return .{ @@ -664,46 +694,54 @@ pub const Header = struct { }; pub const ProgramHeaderIterator = struct { - elf_header: Header, - file_reader: *std.fs.File.Reader, + is_64: bool, + endian: Endian, + phnum: u16, + phoff: u64, + + file_reader: *Io.File.Reader, index: usize = 0, pub fn next(it: *ProgramHeaderIterator) !?Elf64_Phdr { - if (it.index >= it.elf_header.phnum) return null; + if (it.index >= it.phnum) return null; defer it.index += 1; - const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr); - const offset = it.elf_header.phoff + size * it.index; + const size: u64 = if (it.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr); + const offset = it.phoff + size * it.index; try it.file_reader.seekTo(offset); - return takePhdr(&it.file_reader.interface, it.elf_header); + return takeProgramHeader(&it.file_reader.interface, it.is_64, it.endian); } }; pub const ProgramHeaderBufferIterator = struct { - elf_header: Header, + is_64: bool, + endian: Endian, + phnum: u16, + phoff: u64, + buf: []const u8, index: usize = 0, pub fn next(it: *ProgramHeaderBufferIterator) !?Elf64_Phdr { - if (it.index >= it.elf_header.phnum) return null; + if (it.index >= it.phnum) return null; defer it.index += 1; - const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr); - const offset = it.elf_header.phoff + size * it.index; - var reader = std.Io.Reader.fixed(it.buf[offset..]); + const size: u64 = if (it.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr); + const offset = it.phoff + size * it.index; + var reader = Io.Reader.fixed(it.buf[offset..]); - return takePhdr(&reader, it.elf_header); + return takeProgramHeader(&reader, it.is_64, it.endian); } }; -fn takePhdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Phdr { - if (elf_header.is_64) { - const phdr = try reader.takeStruct(Elf64_Phdr, elf_header.endian); +pub fn takeProgramHeader(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Phdr { + if (is_64) { + const phdr = try reader.takeStruct(Elf64_Phdr, endian); return phdr; } - const phdr = try reader.takeStruct(Elf32_Phdr, elf_header.endian); + const phdr = try reader.takeStruct(Elf32_Phdr, endian); return .{ .p_type = phdr.p_type, .p_offset = phdr.p_offset, @@ -717,47 +755,55 @@ fn takePhdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Phdr { } pub const SectionHeaderIterator = struct { - elf_header: Header, - file_reader: *std.fs.File.Reader, + is_64: bool, + endian: Endian, + shnum: u16, + shoff: u64, + + file_reader: *Io.File.Reader, index: usize = 0, pub fn next(it: *SectionHeaderIterator) !?Elf64_Shdr { - if (it.index >= it.elf_header.shnum) return null; + if (it.index >= it.shnum) return null; defer it.index += 1; - const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr); - const offset = it.elf_header.shoff + size * it.index; + const size: u64 = if (it.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr); + const offset = it.shoff + size * it.index; try it.file_reader.seekTo(offset); - return takeShdr(&it.file_reader.interface, it.elf_header); + return takeSectionHeader(&it.file_reader.interface, it.is_64, it.endian); } }; pub const SectionHeaderBufferIterator = struct { - elf_header: Header, + is_64: bool, + endian: Endian, + shnum: u16, + shoff: u64, + buf: []const u8, index: usize = 0, pub fn next(it: *SectionHeaderBufferIterator) !?Elf64_Shdr { - if (it.index >= it.elf_header.shnum) return null; + if (it.index >= it.shnum) return null; defer it.index += 1; - const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr); - const offset = it.elf_header.shoff + size * it.index; + const size: u64 = if (it.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr); + const offset = it.shoff + size * it.index; if (offset > it.buf.len) return error.EndOfStream; - var reader = std.Io.Reader.fixed(it.buf[@intCast(offset)..]); + var reader = Io.Reader.fixed(it.buf[@intCast(offset)..]); - return takeShdr(&reader, it.elf_header); + return takeSectionHeader(&reader, it.is_64, it.endian); } }; -fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr { - if (elf_header.is_64) { - const shdr = try reader.takeStruct(Elf64_Shdr, elf_header.endian); +pub fn takeSectionHeader(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Shdr { + if (is_64) { + const shdr = try reader.takeStruct(Elf64_Shdr, endian); return shdr; } - const shdr = try reader.takeStruct(Elf32_Shdr, elf_header.endian); + const shdr = try reader.takeStruct(Elf32_Shdr, endian); return .{ .sh_name = shdr.sh_name, .sh_type = shdr.sh_type, @@ -772,6 +818,36 @@ fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr { }; } +pub const DynamicSectionIterator = struct { + is_64: bool, + endian: Endian, + offset: u64, + end_offset: u64, + + file_reader: *Io.File.Reader, + + pub fn next(it: *SectionHeaderIterator) !?Elf64_Dyn { + if (it.offset >= it.end_offset) return null; + const size: u64 = if (it.is_64) @sizeOf(Elf64_Dyn) else @sizeOf(Elf32_Dyn); + defer it.offset += size; + try it.file_reader.seekTo(it.offset); + return takeDynamicSection(&it.file_reader.interface, it.is_64, it.endian); + } +}; + +pub fn takeDynamicSection(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Dyn { + if (is_64) { + const dyn = try reader.takeStruct(Elf64_Dyn, endian); + return dyn; + } + + const dyn = try reader.takeStruct(Elf32_Dyn, endian); + return .{ + .d_tag = dyn.d_tag, + .d_val = dyn.d_val, + }; +} + pub const EI = struct { pub const CLASS = 4; pub const DATA = 5; diff --git a/lib/std/zig.zig b/lib/std/zig.zig index dfae03dea7..04e5c2b221 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -6,6 +6,7 @@ const std = @import("std.zig"); const tokenizer = @import("zig/tokenizer.zig"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const Io = std.Io; const Writer = std.Io.Writer; pub const ErrorBundle = @import("zig/ErrorBundle.zig"); @@ -52,9 +53,9 @@ pub const Color = enum { /// Assume stderr is a terminal. on, - pub fn get_tty_conf(color: Color) std.Io.tty.Config { + pub fn get_tty_conf(color: Color) Io.tty.Config { return switch (color) { - .auto => std.Io.tty.detectConfig(std.fs.File.stderr()), + .auto => Io.tty.detectConfig(std.fs.File.stderr()), .on => .escape_codes, .off => .no_color, }; @@ -323,7 +324,7 @@ pub const BuildId = union(enum) { try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx")); } - pub fn format(id: BuildId, writer: *std.Io.Writer) std.Io.Writer.Error!void { + pub fn format(id: BuildId, writer: *Writer) Writer.Error!void { switch (id) { .none, .fast, .uuid, .sha1, .md5 => { try writer.writeAll(@tagName(id)); @@ -620,8 +621,8 @@ pub fn putAstErrorsIntoBundle( try wip_errors.addZirErrorMessages(zir, tree, tree.source, path); } -pub fn resolveTargetQueryOrFatal(target_query: std.Target.Query) std.Target { - return std.zig.system.resolveTargetQuery(target_query) catch |err| +pub fn resolveTargetQueryOrFatal(io: Io, target_query: std.Target.Query) std.Target { + return std.zig.system.resolveTargetQuery(io, target_query) catch |err| std.process.fatal("unable to resolve target: {s}", .{@errorName(err)}); } diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 47ff3d05fa..43d64205a7 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -1,3 +1,14 @@ +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const mem = std.mem; +const elf = std.elf; +const fs = std.fs; +const assert = std.debug.assert; +const Target = std.Target; +const native_endian = builtin.cpu.arch.endian(); +const posix = std.posix; +const Io = std.Io; + pub const NativePaths = @import("system/NativePaths.zig"); pub const windows = @import("system/windows.zig"); @@ -199,14 +210,14 @@ pub const DetectError = error{ OSVersionDetectionFail, Unexpected, ProcessNotFound, -}; +} || Io.Cancelable; /// Given a `Target.Query`, which specifies in detail which parts of the /// target should be detected natively, which should be standard or default, /// and which are provided explicitly, this function resolves the native /// components by detecting the native system, and then resolves /// standard/default parts relative to that. -pub fn resolveTargetQuery(query: Target.Query) DetectError!Target { +pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the // native CPU architecture as being different than the current target), we use this: const query_cpu_arch = query.cpu_arch orelse builtin.cpu.arch; @@ -411,7 +422,33 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target { query.cpu_features_sub, ); - var result = try detectAbiAndDynamicLinker(cpu, os, query); + var result = detectAbiAndDynamicLinker(io, cpu, os, query) catch |err| switch (err) { + error.Canceled => |e| return e, + error.Unexpected => |e| return e, + error.WouldBlock => return error.Unexpected, + error.BrokenPipe => return error.Unexpected, + error.ConnectionResetByPeer => return error.Unexpected, + error.ConnectionTimedOut => return error.Unexpected, + error.NotOpenForReading => return error.Unexpected, + error.SocketUnconnected => return error.Unexpected, + + error.AccessDenied, + error.ProcessNotFound, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + error.SystemResources, + error.IsDir, + error.DeviceBusy, + error.InputOutput, + error.LockViolation, + + error.UnableToOpenElfFile, + error.UnhelpfulFile, + error.InvalidElfFile, + error.RelativeShebang, + => return defaultAbiAndDynamicLinker(cpu, os, query), + }; // These CPU feature hacks have to come after ABI detection. { @@ -505,54 +542,16 @@ fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: T return null; } -pub const AbiAndDynamicLinkerFromFileError = error{ - FileSystem, - SystemResources, - SymLinkLoop, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - UnableToReadElfFile, - InvalidElfClass, - InvalidElfVersion, - InvalidElfEndian, - InvalidElfFile, - InvalidElfMagic, - Unexpected, - UnexpectedEndOfFile, - NameTooLong, - ProcessNotFound, - StaticElfFile, -}; +pub const AbiAndDynamicLinkerFromFileError = error{}; pub fn abiAndDynamicLinkerFromFile( - file: fs.File, + file_reader: *Io.File.Reader, + header: *const elf.Header, cpu: Target.Cpu, os: Target.Os, ld_info_list: []const LdInfo, query: Target.Query, ) AbiAndDynamicLinkerFromFileError!Target { - var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; - _ = try preadAtLeast(file, &hdr_buf, 0, hdr_buf.len); - const hdr32: *elf.Elf32_Ehdr = @ptrCast(&hdr_buf); - const hdr64: *elf.Elf64_Ehdr = @ptrCast(&hdr_buf); - if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; - const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI.DATA]) { - elf.ELFDATA2LSB => .little, - elf.ELFDATA2MSB => .big, - else => return error.InvalidElfEndian, - }; - const need_bswap = elf_endian != native_endian; - if (hdr32.e_ident[elf.EI.VERSION] != 1) return error.InvalidElfVersion; - - const is_64 = switch (hdr32.e_ident[elf.EI.CLASS]) { - elf.ELFCLASS32 => false, - elf.ELFCLASS64 => true, - else => return error.InvalidElfClass, - }; - var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff); - const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize); - const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum); - var result: Target = .{ .cpu = cpu, .os = os, @@ -563,167 +562,87 @@ pub fn abiAndDynamicLinkerFromFile( var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC const look_for_ld = query.dynamic_linker.get() == null; - var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; - if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; - - var ph_i: u16 = 0; var got_dyn_section: bool = false; + { + var it = header.iterateProgramHeaders(file_reader); + while (try it.next()) |phdr| switch (phdr.p_type) { + elf.PT_INTERP => { + got_dyn_section = true; - while (ph_i < phnum) { - // Reserve some bytes so that we can deref the 64-bit struct fields - // even when the ELF file is 32-bits. - const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr); - const ph_read_byte_len = try preadAtLeast(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize); - var ph_buf_i: usize = 0; - while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({ - ph_i += 1; - phoff += phentsize; - ph_buf_i += phentsize; - }) { - const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); - const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i])); - const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); - switch (p_type) { - elf.PT_INTERP => { - got_dyn_section = true; + if (look_for_ld) { + const p_filesz = phdr.p_filesz; + if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; + const filesz: usize = @intCast(p_filesz); + try file_reader.seekTo(phdr.p_offset); + try file_reader.interface.readSliceAll(result.dynamic_linker.buffer[0..filesz]); + // PT_INTERP includes a null byte in filesz. + const len = filesz - 1; + // dynamic_linker.max_byte is "max", not "len". + // We know it will fit in u8 because we check against dynamic_linker.buffer.len above. + result.dynamic_linker.len = @intCast(len); - if (look_for_ld) { - const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); - const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); - if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; - const filesz: usize = @intCast(p_filesz); - _ = try preadAtLeast(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz); - // PT_INTERP includes a null byte in filesz. - const len = filesz - 1; - // dynamic_linker.max_byte is "max", not "len". - // We know it will fit in u8 because we check against dynamic_linker.buffer.len above. - result.dynamic_linker.len = @intCast(len); - - // Use it to determine ABI. - const full_ld_path = result.dynamic_linker.buffer[0..len]; - for (ld_info_list) |ld_info| { - const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); - if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) { - result.abi = ld_info.abi; - break; - } + // Use it to determine ABI. + const full_ld_path = result.dynamic_linker.buffer[0..len]; + for (ld_info_list) |ld_info| { + const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); + if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) { + result.abi = ld_info.abi; + break; } } - }, - // We only need this for detecting glibc version. - elf.PT_DYNAMIC => { - got_dyn_section = true; + } + }, + // We only need this for detecting glibc version. + elf.PT_DYNAMIC => { + got_dyn_section = true; - if (builtin.target.os.tag == .linux and result.isGnuLibC() and - query.glibc_version == null) - { - var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); - const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); - const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); - const dyn_num = p_filesz / dyn_size; - var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined; - var dyn_i: usize = 0; - dyn: while (dyn_i < dyn_num) { - // Reserve some bytes so that we can deref the 64-bit struct fields - // even when the ELF file is 32-bits. - const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn); - const dyn_read_byte_len = try preadAtLeast( - file, - dyn_buf[0 .. dyn_buf.len - dyn_reserve], - dyn_off, - dyn_size, - ); - var dyn_buf_i: usize = 0; - while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({ - dyn_i += 1; - dyn_off += dyn_size; - dyn_buf_i += dyn_size; - }) { - const dyn32: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); - const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i])); - const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag); - const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val); - if (tag == elf.DT_RUNPATH) { - rpath_offset = val; - break :dyn; - } - } + if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) { + var dyn_it = header.iterateDynamicSection(file_reader, phdr.p_offset, phdr.p_filesz); + while (try dyn_it.next()) |dyn| { + if (dyn.d_tag == elf.DT_RUNPATH) { + rpath_offset = dyn.d_val; + break; } } - }, - else => continue, - } - } + } + }, + else => continue, + }; } if (!got_dyn_section) { return error.StaticElfFile; } - if (builtin.target.os.tag == .linux and result.isGnuLibC() and - query.glibc_version == null) - { - const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); - - var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); - const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); - const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); - - var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; - if (sh_buf.len < shentsize) return error.InvalidElfFile; - - _ = try preadAtLeast(file, &sh_buf, str_section_off, shentsize); - const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); - const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); - const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); - const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); - var strtab_buf: [4096:0]u8 = undefined; - const shstrtab_len = @min(shstrtab_size, strtab_buf.len); - const shstrtab_read_len = try preadAtLeast(file, &strtab_buf, shstrtab_off, shstrtab_len); - const shstrtab = strtab_buf[0..shstrtab_read_len]; - - const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); - var sh_i: u16 = 0; - const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { - // Reserve some bytes so that we can deref the 64-bit struct fields - // even when the ELF file is 32-bits. - const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); - const sh_read_byte_len = try preadAtLeast( - file, - sh_buf[0 .. sh_buf.len - sh_reserve], - shoff, - shentsize, - ); - var sh_buf_i: usize = 0; - while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ - sh_i += 1; - shoff += shentsize; - sh_buf_i += shentsize; - }) { - const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); - const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); - const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); - const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); - if (mem.eql(u8, sh_name, ".dynstr")) { - break :find_dyn_str .{ - .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), - .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), - }; - } - } - } else null; - + if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) { + const str_section_off = header.shoff + @as(u64, header.shentsize) * @as(u64, header.shstrndx); + try file_reader.seekTo(str_section_off); + const shstr = try elf.takeSectionHeader(&file_reader.interface, header.is_64, header.endian); + var strtab_buf: [4096]u8 = undefined; + const shstrtab = strtab_buf[0..@min(shstr.sh_size, strtab_buf.len)]; + try file_reader.seekTo(shstr.sh_offset); + try file_reader.interface.readSliceAll(shstrtab); + const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: { + var it = header.iterateSectionHeaders(&file_reader.interface); + while (it.next()) |shdr| { + const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue; + const sh_name = shstrtab[shdr.sh_name..end :0]; + if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{ + .offset = shdr.sh_offset, + .size = shdr.sh_size, + }; + } else break :find_dyn_str null; + }; if (dynstr) |ds| { if (rpath_offset) |rpoff| { if (rpoff > ds.size) return error.InvalidElfFile; const rpoff_file = ds.offset + rpoff; const rp_max_size = ds.size - rpoff; - const strtab_len = @min(rp_max_size, strtab_buf.len); - const strtab_read_len = try preadAtLeast(file, &strtab_buf, rpoff_file, strtab_len); - const strtab = strtab_buf[0..strtab_read_len]; + try file_reader.seekTo(rpoff_file); + const rpath_list = try file_reader.interface.takeSentinel(0); + if (rpath_list.len > rp_max_size) return error.StreamTooLong; - const rpath_list = mem.sliceTo(strtab, 0); var it = mem.tokenizeScalar(u8, rpath_list, ':'); while (it.next()) |rpath| { if (glibcVerFromRPath(rpath)) |ver| { @@ -845,7 +764,7 @@ test glibcVerFromLinkName { try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-")); } -fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { +fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { error.NameTooLong => unreachable, error.InvalidUtf8 => unreachable, // WASI only @@ -879,7 +798,7 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { // .dynstr section, and finding the max version number of symbols // that start with "GLIBC_2.". const glibc_so_basename = "libc.so.6"; - var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { + var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { error.NameTooLong => unreachable, error.InvalidUtf8 => unreachable, // WASI only error.InvalidWtf8 => unreachable, // Windows only @@ -913,16 +832,20 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { error.Unexpected, => |e| return e, }; - defer f.close(); + defer file.close(); - return glibcVerFromSoFile(f) catch |err| switch (err) { + // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. + var buffer: [8000]u8 = undefined; + var file_reader: Io.File.Reader = .initAdapted(file, io, &buffer); + + return glibcVerFromSoFile(&file_reader) catch |err| switch (err) { error.InvalidElfMagic, error.InvalidElfEndian, error.InvalidElfClass, error.InvalidElfFile, error.InvalidElfVersion, error.InvalidGnuLibCVersion, - error.UnexpectedEndOfFile, + error.EndOfStream, => return error.GLibCNotFound, error.SystemResources, @@ -934,88 +857,34 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { }; } -fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion { - var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; - _ = try preadAtLeast(file, &hdr_buf, 0, hdr_buf.len); - const hdr32: *elf.Elf32_Ehdr = @ptrCast(&hdr_buf); - const hdr64: *elf.Elf64_Ehdr = @ptrCast(&hdr_buf); - if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; - const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI.DATA]) { - elf.ELFDATA2LSB => .little, - elf.ELFDATA2MSB => .big, - else => return error.InvalidElfEndian, +fn glibcVerFromSoFile(file_reader: *Io.File.Reader) !std.SemanticVersion { + const header = try elf.Header.read(&file_reader.interface); + const str_section_off = header.shoff + @as(u64, header.shentsize) * @as(u64, header.shstrndx); + try file_reader.seekTo(str_section_off); + const shstr = try elf.takeSectionHeader(&file_reader.interface, header.is_64, header.endian); + var strtab_buf: [4096]u8 = undefined; + const shstrtab = strtab_buf[0..@min(shstr.sh_size, strtab_buf.len)]; + try file_reader.seekTo(shstr.sh_offset); + try file_reader.interface.readSliceAll(shstrtab); + const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: { + var it = header.iterateSectionHeaders(&file_reader.interface); + while (it.next()) |shdr| { + const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue; + const sh_name = shstrtab[shdr.sh_name..end :0]; + if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{ + .offset = shdr.sh_offset, + .size = shdr.sh_size, + }; + } else return error.InvalidGnuLibCVersion; }; - const need_bswap = elf_endian != native_endian; - if (hdr32.e_ident[elf.EI.VERSION] != 1) return error.InvalidElfVersion; - - const is_64 = switch (hdr32.e_ident[elf.EI.CLASS]) { - elf.ELFCLASS32 => false, - elf.ELFCLASS64 => true, - else => return error.InvalidElfClass, - }; - const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); - var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); - const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); - const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); - var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; - if (sh_buf.len < shentsize) return error.InvalidElfFile; - - _ = try preadAtLeast(file, &sh_buf, str_section_off, shentsize); - const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf)); - const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf)); - const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); - const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); - var strtab_buf: [4096:0]u8 = undefined; - const shstrtab_len = @min(shstrtab_size, strtab_buf.len); - const shstrtab_read_len = try preadAtLeast(file, &strtab_buf, shstrtab_off, shstrtab_len); - const shstrtab = strtab_buf[0..shstrtab_read_len]; - const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); - var sh_i: u16 = 0; - const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { - // Reserve some bytes so that we can deref the 64-bit struct fields - // even when the ELF file is 32-bits. - const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); - const sh_read_byte_len = try preadAtLeast( - file, - sh_buf[0 .. sh_buf.len - sh_reserve], - shoff, - shentsize, - ); - var sh_buf_i: usize = 0; - while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ - sh_i += 1; - shoff += shentsize; - sh_buf_i += shentsize; - }) { - const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); - const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i])); - const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); - const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0); - if (mem.eql(u8, sh_name, ".dynstr")) { - break :find_dyn_str .{ - .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), - .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), - }; - } - } - } else return error.InvalidGnuLibCVersion; // Here we loop over all the strings in the dynstr string table, assuming that any // strings that start with "GLIBC_2." indicate the existence of such a glibc version, // and furthermore, that the system-installed glibc is at minimum that version. - - // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. - // Here I use double this value plus some headroom. This makes it only need - // a single read syscall here. - var buf: [80000]u8 = undefined; - if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion; - - const dynstr_size: usize = @intCast(dynstr.size); - const dynstr_bytes = buf[0..dynstr_size]; - _ = try preadAtLeast(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len); - var it = mem.splitScalar(u8, dynstr_bytes, 0); var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 }; - while (it.next()) |s| { + + try file_reader.seekTo(dynstr.offset); + while (file_reader.interface.takeSentinel(0)) |s| { if (mem.startsWith(u8, s, "GLIBC_2.")) { const chopped = s["GLIBC_".len..]; const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) { @@ -1028,6 +897,7 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion { } } } + return max_ver; } @@ -1044,11 +914,7 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion { /// answer to these questions, or if there is a shebang line, then it chases the referenced /// file recursively. If that does not provide the answer, then the function falls back to /// defaults. -fn detectAbiAndDynamicLinker( - cpu: Target.Cpu, - os: Target.Os, - query: Target.Query, -) DetectError!Target { +fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target { const native_target_has_ld = comptime Target.DynamicLinker.kind(builtin.os.tag) != .none; const is_linux = builtin.target.os.tag == .linux; const is_illumos = builtin.target.os.tag == .illumos; @@ -1111,49 +977,52 @@ fn detectAbiAndDynamicLinker( const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; + var file_reader: Io.File.Reader = undefined; + // According to `man 2 execve`: + // + // The kernel imposes a maximum length on the text + // that follows the "#!" characters at the start of a script; + // characters beyond the limit are ignored. + // Before Linux 5.1, the limit is 127 characters. + // Since Linux 5.1, the limit is 255 characters. + // + // Tests show that bash and zsh consider 255 as total limit, + // *including* "#!" characters and ignoring newline. + // For safety, we set max length as 255 + \n (1). + const max_shebang_line_size = 256; + var file_reader_buffer: [4096]u8 = undefined; + comptime assert(file_reader_buffer.len >= max_shebang_line_size); + // Best case scenario: the executable is dynamically linked, and we can iterate // over our own shared objects and find a dynamic linker. - const elf_file = elf_file: { - // This block looks for a shebang line in /usr/bin/env, - // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead, - // doing the same logic recursively in case it finds another shebang line. + const header = elf_file: { + // This block looks for a shebang line in "/usr/bin/env". If it finds + // one, then instead of using "/usr/bin/env" as the ELF file to examine, + // it uses the file it references instead, doing the same logic + // recursively in case it finds another shebang line. var file_name: []const u8 = switch (os.tag) { - // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a - // reasonably reliable path to start with. + // Since /usr/bin/env is hard-coded into the shebang line of many + // portable scripts, it's a reasonably reliable path to start with. else => "/usr/bin/env", // Haiku does not have a /usr root directory. .haiku => "/bin/env", }; - // According to `man 2 execve`: - // - // The kernel imposes a maximum length on the text - // that follows the "#!" characters at the start of a script; - // characters beyond the limit are ignored. - // Before Linux 5.1, the limit is 127 characters. - // Since Linux 5.1, the limit is 255 characters. - // - // Tests show that bash and zsh consider 255 as total limit, - // *including* "#!" characters and ignoring newline. - // For safety, we set max length as 255 + \n (1). - var buffer: [255 + 1]u8 = undefined; while (true) { - // Interpreter path can be relative on Linux, but - // for simplicity we are asserting it is an absolute path. const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { - error.NoSpaceLeft => unreachable, - error.NameTooLong => unreachable, - error.PathAlreadyExists => unreachable, - error.SharingViolation => unreachable, - error.InvalidUtf8 => unreachable, // WASI only - error.InvalidWtf8 => unreachable, // Windows only - error.BadPathName => unreachable, - error.PipeBusy => unreachable, - error.FileLocksNotSupported => unreachable, - error.WouldBlock => unreachable, - error.FileBusy => unreachable, // opened without write permissions - error.AntivirusInterference => unreachable, // Windows-only error + error.NoSpaceLeft => return error.Unexpected, + error.NameTooLong => return error.Unexpected, + error.PathAlreadyExists => return error.Unexpected, + error.SharingViolation => return error.Unexpected, + error.InvalidUtf8 => return error.Unexpected, // WASI only + error.InvalidWtf8 => return error.Unexpected, // Windows only + error.BadPathName => return error.Unexpected, + error.PipeBusy => return error.Unexpected, + error.FileLocksNotSupported => return error.Unexpected, + error.WouldBlock => return error.Unexpected, + error.FileBusy => return error.Unexpected, // opened without write permissions + error.AntivirusInterference => return error.Unexpected, // Windows-only error error.IsDir, error.NotDir, @@ -1164,66 +1033,58 @@ fn detectAbiAndDynamicLinker( error.NetworkNotFound, error.FileTooBig, error.Unexpected, - => |e| { - std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)}); - return defaultAbiAndDynamicLinker(cpu, os, query); - }, + => return error.UnableToOpenElfFile, else => |e| return e, }; var is_elf_file = false; - defer if (is_elf_file == false) file.close(); + defer if (!is_elf_file) file.close(); - // Shortest working interpreter path is "#!/i" (4) - // (interpreter is "/i", assuming all paths are absolute, like in above comment). - // ELF magic number length is also 4. - // - // If file is shorter than that, it is definitely not ELF file - // nor file with "shebang" line. - const min_len: usize = 4; + file_reader = .initAdapted(file, io, &file_reader_buffer); + file_name = undefined; // it aliases file_reader_buffer - const len = preadAtLeast(file, &buffer, 0, min_len) catch |err| switch (err) { - error.UnexpectedEndOfFile, - error.UnableToReadElfFile, - error.ProcessNotFound, - => return defaultAbiAndDynamicLinker(cpu, os, query), + const header = elf.Header.read(&file_reader.interface) catch |hdr_err| switch (hdr_err) { + error.EndOfStream, + error.InvalidElfMagic, + => { + const shebang_line = file_reader.interface.takeSentinel('\n') catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + // It's neither an ELF file nor file with shebang line. + error.EndOfStream, error.StreamTooLong => return error.UnhelpfulFile, + }; + if (!mem.startsWith(u8, shebang_line, "#!")) return error.UnhelpfulFile; + // We detected shebang, now parse entire line. - else => |e| return e, + // Trim leading "#!", spaces and tabs. + const trimmed_line = mem.trimStart(u8, shebang_line[2..], &.{ ' ', '\t' }); + + // This line can have: + // * Interpreter path only, + // * Interpreter path and arguments, all separated by space, tab or NUL character. + // And optionally newline at the end. + const path_maybe_args = mem.trimEnd(u8, trimmed_line, "\n"); + + // Separate path and args. + const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len; + const unvalidated_path = path_maybe_args[0..path_end]; + file_name = if (fs.path.isAbsolute(unvalidated_path)) unvalidated_path else return error.RelativeShebang; + continue; + }, + + error.InvalidElfVersion, + error.InvalidElfClass, + error.InvalidElfEndian, + => return error.InvalidElfFile, + + error.ReadFailed => return file_reader.err.?, }; - const content = buffer[0..len]; - - if (mem.eql(u8, content[0..4], std.elf.MAGIC)) { - // It is very likely ELF file! - is_elf_file = true; - break :elf_file file; - } else if (mem.eql(u8, content[0..2], "#!")) { - // We detected shebang, now parse entire line. - - // Trim leading "#!", spaces and tabs. - const trimmed_line = mem.trimStart(u8, content[2..], &.{ ' ', '\t' }); - - // This line can have: - // * Interpreter path only, - // * Interpreter path and arguments, all separated by space, tab or NUL character. - // And optionally newline at the end. - const path_maybe_args = mem.trimEnd(u8, trimmed_line, "\n"); - - // Separate path and args. - const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len; - - file_name = path_maybe_args[0..path_end]; - continue; - } else { - // Not a ELF file, not a shell script with "shebang line", invalid duck. - return defaultAbiAndDynamicLinker(cpu, os, query); - } + is_elf_file = true; + break :elf_file header; } }; - defer elf_file.close(); + defer file_reader.file.close(io); - // TODO: inline this function and combine the buffer we already read above to find - // the possible shebang line with the buffer we use for the ELF header. - return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) { + return abiAndDynamicLinkerFromFile(&file_reader, &header, cpu, os, ld_info_list, query) catch |err| switch (err) { error.FileSystem, error.SystemResources, error.SymLinkLoop, @@ -1232,6 +1093,8 @@ fn detectAbiAndDynamicLinker( error.ProcessNotFound, => |e| return e, + error.ReadFailed => return file_reader.err.?, + error.UnableToReadElfFile, error.InvalidElfClass, error.InvalidElfVersion, @@ -1239,12 +1102,12 @@ fn detectAbiAndDynamicLinker( error.InvalidElfFile, error.InvalidElfMagic, error.Unexpected, - error.UnexpectedEndOfFile, + error.EndOfStream, error.NameTooLong, error.StaticElfFile, // Finally, we fall back on the standard path. => |e| { - std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)}); + std.log.warn("encountered {t}; falling back to default ABI and dynamic linker", .{e}); return defaultAbiAndDynamicLinker(cpu, os, query); }, }; @@ -1269,59 +1132,6 @@ const LdInfo = struct { abi: Target.Abi, }; -fn preadAtLeast(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize { - var i: usize = 0; - while (i < min_read_len) { - const len = file.pread(buf[i..], offset + i) catch |err| switch (err) { - error.OperationAborted => unreachable, // Windows-only - error.WouldBlock => unreachable, // Did not request blocking mode - error.Canceled => unreachable, // timerfd is unseekable - error.NotOpenForReading => unreachable, - error.SystemResources => return error.SystemResources, - error.IsDir => return error.UnableToReadElfFile, - error.BrokenPipe => return error.UnableToReadElfFile, - error.Unseekable => return error.UnableToReadElfFile, - error.ConnectionResetByPeer => return error.UnableToReadElfFile, - error.ConnectionTimedOut => return error.UnableToReadElfFile, - error.SocketUnconnected => return error.UnableToReadElfFile, - error.Unexpected => return error.Unexpected, - error.InputOutput => return error.FileSystem, - error.AccessDenied => return error.Unexpected, - error.ProcessNotFound => return error.ProcessNotFound, - error.LockViolation => return error.UnableToReadElfFile, - }; - if (len == 0) return error.UnexpectedEndOfFile; - i += len; - } - return i; -} - -fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { - if (is_64) { - if (need_bswap) { - return @byteSwap(int_64); - } else { - return int_64; - } - } else { - if (need_bswap) { - return @byteSwap(int_32); - } else { - return int_32; - } - } -} - -const builtin = @import("builtin"); -const std = @import("../std.zig"); -const mem = std.mem; -const elf = std.elf; -const fs = std.fs; -const assert = std.debug.assert; -const Target = std.Target; -const native_endian = builtin.cpu.arch.endian(); -const posix = std.posix; - test { _ = NativePaths; diff --git a/src/main.zig b/src/main.zig index f84fc36d80..76c77a7b83 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,8 @@ -const std = @import("std"); const builtin = @import("builtin"); +const native_os = builtin.os.tag; + +const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const fs = std.fs; const mem = std.mem; @@ -10,7 +13,6 @@ const Color = std.zig.Color; const warn = std.log.warn; const ThreadPool = std.Thread.Pool; const cleanExit = std.process.cleanExit; -const native_os = builtin.os.tag; const Cache = std.Build.Cache; const Path = std.Build.Cache.Path; const Directory = std.Build.Cache.Directory; @@ -245,26 +247,30 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } } + var threaded: Io.Threaded = .init(gpa); + defer threaded.deinit(); + const io = threaded.io(); + const cmd = args[1]; const cmd_args = args[2..]; if (mem.eql(u8, cmd, "build-exe")) { dev.check(.build_exe_command); - return buildOutputType(gpa, arena, args, .{ .build = .Exe }); + return buildOutputType(gpa, arena, io, args, .{ .build = .Exe }); } else if (mem.eql(u8, cmd, "build-lib")) { dev.check(.build_lib_command); - return buildOutputType(gpa, arena, args, .{ .build = .Lib }); + return buildOutputType(gpa, arena, io, args, .{ .build = .Lib }); } else if (mem.eql(u8, cmd, "build-obj")) { dev.check(.build_obj_command); - return buildOutputType(gpa, arena, args, .{ .build = .Obj }); + return buildOutputType(gpa, arena, io, args, .{ .build = .Obj }); } else if (mem.eql(u8, cmd, "test")) { dev.check(.test_command); - return buildOutputType(gpa, arena, args, .zig_test); + return buildOutputType(gpa, arena, io, args, .zig_test); } else if (mem.eql(u8, cmd, "test-obj")) { dev.check(.test_command); - return buildOutputType(gpa, arena, args, .zig_test_obj); + return buildOutputType(gpa, arena, io, args, .zig_test_obj); } else if (mem.eql(u8, cmd, "run")) { dev.check(.run_command); - return buildOutputType(gpa, arena, args, .run); + return buildOutputType(gpa, arena, io, args, .run); } else if (mem.eql(u8, cmd, "dlltool") or mem.eql(u8, cmd, "ranlib") or mem.eql(u8, cmd, "lib") or @@ -274,7 +280,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { return process.exit(try llvmArMain(arena, args)); } else if (mem.eql(u8, cmd, "build")) { dev.check(.build_command); - return cmdBuild(gpa, arena, cmd_args); + return cmdBuild(gpa, arena, io, cmd_args); } else if (mem.eql(u8, cmd, "clang") or mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as")) { @@ -288,16 +294,16 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { return process.exit(try lldMain(arena, args, true)); } else if (mem.eql(u8, cmd, "cc")) { dev.check(.cc_command); - return buildOutputType(gpa, arena, args, .cc); + return buildOutputType(gpa, arena, io, args, .cc); } else if (mem.eql(u8, cmd, "c++")) { dev.check(.cc_command); - return buildOutputType(gpa, arena, args, .cpp); + return buildOutputType(gpa, arena, io, args, .cpp); } else if (mem.eql(u8, cmd, "translate-c")) { dev.check(.translate_c_command); - return buildOutputType(gpa, arena, args, .translate_c); + return buildOutputType(gpa, arena, io, args, .translate_c); } else if (mem.eql(u8, cmd, "rc")) { const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration"); - return jitCmd(gpa, arena, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, .{ .cmd_name = "resinator", .root_src_path = "resinator/main.zig", .depend_on_aro = true, @@ -308,20 +314,20 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { dev.check(.fmt_command); return @import("fmt.zig").run(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "objcopy")) { - return jitCmd(gpa, arena, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, .{ .cmd_name = "objcopy", .root_src_path = "objcopy.zig", }); } else if (mem.eql(u8, cmd, "fetch")) { return cmdFetch(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "libc")) { - return jitCmd(gpa, arena, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, .{ .cmd_name = "libc", .root_src_path = "libc.zig", .prepend_zig_lib_dir_path = true, }); } else if (mem.eql(u8, cmd, "std")) { - return jitCmd(gpa, arena, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, .{ .cmd_name = "std", .root_src_path = "std-docs.zig", .prepend_zig_lib_dir_path = true, @@ -332,7 +338,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { return cmdInit(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "targets")) { dev.check(.targets_command); - const host = std.zig.resolveTargetQueryOrFatal(.{}); + const host = std.zig.resolveTargetQueryOrFatal(io, .{}); var stdout_writer = fs.File.stdout().writer(&stdout_buffer); try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host); return stdout_writer.interface.flush(); @@ -351,7 +357,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { ); return stdout_writer.interface.flush(); } else if (mem.eql(u8, cmd, "reduce")) { - return jitCmd(gpa, arena, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, .{ .cmd_name = "reduce", .root_src_path = "reduce.zig", }); @@ -364,7 +370,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } else if (mem.eql(u8, cmd, "ast-check")) { return cmdAstCheck(arena, cmd_args); } else if (mem.eql(u8, cmd, "detect-cpu")) { - return cmdDetectCpu(cmd_args); + return cmdDetectCpu(io, cmd_args); } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "changelist")) { return cmdChangelist(arena, cmd_args); } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) { @@ -792,6 +798,7 @@ const CliModule = struct { fn buildOutputType( gpa: Allocator, arena: Allocator, + io: Io, all_args: []const []const u8, arg_mode: ArgMode, ) !void { @@ -3017,7 +3024,7 @@ fn buildOutputType( create_module.opts.emit_bin = emit_bin != .no; create_module.opts.any_c_source_files = create_module.c_source_files.items.len != 0; - const main_mod = try createModule(gpa, arena, &create_module, 0, null, color); + const main_mod = try createModule(gpa, arena, io, &create_module, 0, null, color); for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| { if (cli_mod.resolved == null) fatal("module '{s}' declared but not used", .{key}); @@ -3545,6 +3552,7 @@ fn buildOutputType( var stdin_reader = fs.File.stdin().reader(&stdin_buffer); var stdout_writer = fs.File.stdout().writer(&stdout_buffer); try serve( + io, comp, &stdin_reader.interface, &stdout_writer.interface, @@ -3571,6 +3579,7 @@ fn buildOutputType( var output = conn.stream.writer(&stdout_buffer); try serve( + io, comp, input.interface(), &output.interface, @@ -3646,6 +3655,7 @@ fn buildOutputType( comp, gpa, arena, + io, test_exec_args.items, self_exe_path, arg_mode, @@ -3704,6 +3714,7 @@ const CreateModule = struct { fn createModule( gpa: Allocator, arena: Allocator, + io: Io, create_module: *CreateModule, index: usize, parent: ?*Package.Module, @@ -3777,7 +3788,7 @@ fn createModule( } const target_query = std.zig.parseTargetQueryOrReportFatalError(arena, target_parse_options); - const target = std.zig.resolveTargetQueryOrFatal(target_query); + const target = std.zig.resolveTargetQueryOrFatal(io, target_query); break :t .{ .result = target, .is_native_os = target_query.isNativeOs(), @@ -4022,7 +4033,7 @@ fn createModule( for (cli_mod.deps) |dep| { const dep_index = create_module.modules.getIndex(dep.value) orelse fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key }); - const dep_mod = try createModule(gpa, arena, create_module, dep_index, mod, color); + const dep_mod = try createModule(gpa, arena, io, create_module, dep_index, mod, color); try mod.deps.put(arena, dep.key, dep_mod); } @@ -4038,9 +4049,10 @@ fn saveState(comp: *Compilation, incremental: bool) void { } fn serve( + io: Io, comp: *Compilation, - in: *std.Io.Reader, - out: *std.Io.Writer, + in: *Io.Reader, + out: *Io.Writer, test_exec_args: []const ?[]const u8, self_exe_path: ?[]const u8, arg_mode: ArgMode, @@ -4090,7 +4102,7 @@ fn serve( defer arena_instance.deinit(); const arena = arena_instance.allocator(); var output: Compilation.CImportResult = undefined; - try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node); + try cmdTranslateC(io, comp, arena, &output, file_system_inputs, main_progress_node); defer output.deinit(gpa); if (file_system_inputs.items.len != 0) { @@ -4126,6 +4138,7 @@ fn serve( // comp, // gpa, // arena, + // io, // test_exec_args, // self_exe_path.?, // arg_mode, @@ -4280,6 +4293,7 @@ fn runOrTest( comp: *Compilation, gpa: Allocator, arena: Allocator, + io: Io, test_exec_args: []const ?[]const u8, self_exe_path: []const u8, arg_mode: ArgMode, @@ -4334,7 +4348,7 @@ fn runOrTest( std.debug.lockStdErr(); const err = process.execve(gpa, argv.items, &env_map); std.debug.unlockStdErr(); - try warnAboutForeignBinaries(arena, arg_mode, target, link_libc); + try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd }); } else if (process.can_spawn) { @@ -4355,7 +4369,7 @@ fn runOrTest( break :t child.spawnAndWait(); }; const term = term_result catch |err| { - try warnAboutForeignBinaries(arena, arg_mode, target, link_libc); + try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd }); }; @@ -4594,11 +4608,12 @@ fn cmdTranslateC( pub fn translateC( gpa: Allocator, arena: Allocator, + io: Io, argv: []const []const u8, prog_node: std.Progress.Node, capture: ?*[]u8, ) !void { - try jitCmd(gpa, arena, argv, .{ + try jitCmd(gpa, arena, io, argv, .{ .cmd_name = "translate-c", .root_src_path = "translate-c/main.zig", .depend_on_aro = true, @@ -4755,7 +4770,7 @@ test sanitizeExampleName { try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project")); } -fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { +fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void { dev.check(.build_command); var build_file: ?[]const u8 = null; @@ -4983,7 +4998,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { .arch_os_abi = triple, }); break :t .{ - .result = std.zig.resolveTargetQueryOrFatal(target_query), + .result = std.zig.resolveTargetQueryOrFatal(io, target_query), .is_native_os = false, .is_native_abi = false, .is_explicit_dynamic_linker = false, @@ -4991,7 +5006,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } } break :t .{ - .result = std.zig.resolveTargetQueryOrFatal(.{}), + .result = std.zig.resolveTargetQueryOrFatal(io, .{}), .is_native_os = true, .is_native_abi = true, .is_explicit_dynamic_linker = false, @@ -5400,6 +5415,7 @@ const JitCmdOptions = struct { fn jitCmd( gpa: Allocator, arena: Allocator, + io: Io, args: []const []const u8, options: JitCmdOptions, ) !void { @@ -5412,7 +5428,7 @@ fn jitCmd( const target_query: std.Target.Query = .{}; const resolved_target: Package.Module.ResolvedTarget = .{ - .result = std.zig.resolveTargetQueryOrFatal(target_query), + .result = std.zig.resolveTargetQueryOrFatal(io, target_query), .is_native_os = true, .is_native_abi = true, .is_explicit_dynamic_linker = false, @@ -6209,7 +6225,7 @@ fn cmdAstCheck( } } -fn cmdDetectCpu(args: []const []const u8) !void { +fn cmdDetectCpu(io: Io, args: []const []const u8) !void { dev.check(.detect_cpu_command); const detect_cpu_usage = @@ -6254,7 +6270,7 @@ fn cmdDetectCpu(args: []const []const u8) !void { const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features); try printCpu(cpu); } else { - const host_target = std.zig.resolveTargetQueryOrFatal(.{}); + const host_target = std.zig.resolveTargetQueryOrFatal(io, .{}); try printCpu(host_target.cpu); } } @@ -6521,13 +6537,14 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 { } fn warnAboutForeignBinaries( + io: Io, arena: Allocator, arg_mode: ArgMode, target: *const std.Target, link_libc: bool, ) !void { const host_query: std.Target.Query = .{}; - const host_target = std.zig.resolveTargetQueryOrFatal(host_query); + const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query); switch (std.zig.system.getExternalExecutor(&host_target, target, .{ .link_libc = link_libc })) { .native => return, @@ -7080,7 +7097,7 @@ fn cmdFetch( try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text); } - var aw: std.Io.Writer.Allocating = .init(gpa); + var aw: Io.Writer.Allocating = .init(gpa); defer aw.deinit(); try ast.render(gpa, &aw.writer, fixups); const rendered = aw.written();