std.zig.system: upgrade to std.Io.Reader

This commit is contained in:
Andrew Kelley 2025-10-06 18:34:51 -07:00
parent b428612a20
commit 066864a0bf
12 changed files with 428 additions and 513 deletions

View file

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

View file

@ -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"),
};
}

View file

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

View file

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

View file

@ -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`.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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