1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3const mem = std.mem;
4const assert = std.debug.assert;
5const fs = std.fs;
6const elf = std.elf;
7const native_endian = builtin.cpu.arch.endian();
8
9const NativeTargetInfo = @This();
10const Target = std.Target;
11const Allocator = std.mem.Allocator;
12const CrossTarget = std.zig.CrossTarget;
13const windows = std.zig.system.windows;
14const darwin = std.zig.system.darwin;
15const linux = std.zig.system.linux;
16
17target: Target,
18dynamic_linker: DynamicLinker = DynamicLinker{},
19
20pub const DynamicLinker = Target.DynamicLinker;
21
22pub const DetectError = error{
23    OutOfMemory,
24    FileSystem,
25    SystemResources,
26    SymLinkLoop,
27    ProcessFdQuotaExceeded,
28    SystemFdQuotaExceeded,
29    DeviceBusy,
30    OSVersionDetectionFail,
31};
32
33/// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected
34/// natively, which should be standard or default, and which are provided explicitly, this function
35/// resolves the native components by detecting the native system, and then resolves standard/default parts
36/// relative to that.
37/// Any resources this function allocates are released before returning, and so there is no
38/// deinitialization method.
39/// TODO Remove the Allocator requirement from this function.
40pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo {
41    var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch());
42    if (cross_target.os_tag == null) {
43        switch (builtin.target.os.tag) {
44            .linux => {
45                const uts = std.os.uname();
46                const release = mem.sliceTo(&uts.release, 0);
47                // The release field sometimes has a weird format,
48                // `Version.parse` will attempt to find some meaningful interpretation.
49                if (std.builtin.Version.parse(release)) |ver| {
50                    os.version_range.linux.range.min = ver;
51                    os.version_range.linux.range.max = ver;
52                } else |err| switch (err) {
53                    error.Overflow => {},
54                    error.InvalidCharacter => {},
55                    error.InvalidVersion => {},
56                }
57            },
58            .solaris => {
59                const uts = std.os.uname();
60                const release = mem.sliceTo(&uts.release, 0);
61                if (std.builtin.Version.parse(release)) |ver| {
62                    os.version_range.semver.min = ver;
63                    os.version_range.semver.max = ver;
64                } else |err| switch (err) {
65                    error.Overflow => {},
66                    error.InvalidCharacter => {},
67                    error.InvalidVersion => {},
68                }
69            },
70            .windows => {
71                const detected_version = windows.detectRuntimeVersion();
72                os.version_range.windows.min = detected_version;
73                os.version_range.windows.max = detected_version;
74            },
75            .macos => try darwin.macos.detect(&os),
76            .freebsd, .netbsd, .dragonfly => {
77                const key = switch (builtin.target.os.tag) {
78                    .freebsd => "kern.osreldate",
79                    .netbsd, .dragonfly => "kern.osrevision",
80                    else => unreachable,
81                };
82                var value: u32 = undefined;
83                var len: usize = @sizeOf(@TypeOf(value));
84
85                std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) {
86                    error.NameTooLong => unreachable, // constant, known good value
87                    error.PermissionDenied => unreachable, // only when setting values,
88                    error.SystemResources => unreachable, // memory already on the stack
89                    error.UnknownName => unreachable, // constant, known good value
90                    error.Unexpected => return error.OSVersionDetectionFail,
91                };
92
93                switch (builtin.target.os.tag) {
94                    .freebsd => {
95                        // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html
96                        // Major * 100,000 has been convention since FreeBSD 2.2 (1997)
97                        // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997)
98                        // e.g. 492101 = 4.11-STABLE = 4.(9+2)
99                        const major = value / 100_000;
100                        const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1
101                        const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since
102                        const patch = value % 1_000;
103                        os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch };
104                        os.version_range.semver.max = os.version_range.semver.min;
105                    },
106                    .netbsd => {
107                        // #define __NetBSD_Version__ MMmmrrpp00
108                        //
109                        // M = major version
110                        // m = minor version; a minor number of 99 indicates current.
111                        // r = 0 (*)
112                        // p = patchlevel
113                        const major = value / 100_000_000;
114                        const minor = value % 100_000_000 / 1_000_000;
115                        const patch = value % 10_000 / 100;
116                        os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
117                        os.version_range.semver.max = os.version_range.semver.min;
118                    },
119                    .dragonfly => {
120                        // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17
121                        // flat base10 format: Mmmmpp
122                        //   M = major
123                        //   m = minor; odd-numbers indicate current dev branch
124                        //   p = patch
125                        const major = value / 100_000;
126                        const minor = value % 100_000 / 100;
127                        const patch = value % 100;
128                        os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch };
129                        os.version_range.semver.max = os.version_range.semver.min;
130                    },
131                    else => unreachable,
132                }
133            },
134            .openbsd => {
135                const mib: [2]c_int = [_]c_int{
136                    std.os.CTL.KERN,
137                    std.os.KERN.OSRELEASE,
138                };
139                var buf: [64]u8 = undefined;
140                var len: usize = buf.len;
141
142                std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) {
143                    error.NameTooLong => unreachable, // constant, known good value
144                    error.PermissionDenied => unreachable, // only when setting values,
145                    error.SystemResources => unreachable, // memory already on the stack
146                    error.UnknownName => unreachable, // constant, known good value
147                    error.Unexpected => return error.OSVersionDetectionFail,
148                };
149
150                if (std.builtin.Version.parse(buf[0 .. len - 1])) |ver| {
151                    os.version_range.semver.min = ver;
152                    os.version_range.semver.max = ver;
153                } else |_| {
154                    return error.OSVersionDetectionFail;
155                }
156            },
157            else => {
158                // Unimplemented, fall back to default version range.
159            },
160        }
161    }
162
163    if (cross_target.os_version_min) |min| switch (min) {
164        .none => {},
165        .semver => |semver| switch (cross_target.getOsTag()) {
166            .linux => os.version_range.linux.range.min = semver,
167            else => os.version_range.semver.min = semver,
168        },
169        .windows => |win_ver| os.version_range.windows.min = win_ver,
170    };
171
172    if (cross_target.os_version_max) |max| switch (max) {
173        .none => {},
174        .semver => |semver| switch (cross_target.getOsTag()) {
175            .linux => os.version_range.linux.range.max = semver,
176            else => os.version_range.semver.max = semver,
177        },
178        .windows => |win_ver| os.version_range.windows.max = win_ver,
179    };
180
181    if (cross_target.glibc_version) |glibc| {
182        assert(cross_target.isGnuLibC());
183        os.version_range.linux.glibc = glibc;
184    }
185
186    // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
187    // native CPU architecture as being different than the current target), we use this:
188    const cpu_arch = cross_target.getCpuArch();
189
190    var cpu = switch (cross_target.cpu_model) {
191        .native => detectNativeCpuAndFeatures(cpu_arch, os, cross_target),
192        .baseline => Target.Cpu.baseline(cpu_arch),
193        .determined_by_cpu_arch => if (cross_target.cpu_arch == null)
194            detectNativeCpuAndFeatures(cpu_arch, os, cross_target)
195        else
196            Target.Cpu.baseline(cpu_arch),
197        .explicit => |model| model.toCpu(cpu_arch),
198    } orelse backup_cpu_detection: {
199        break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
200    };
201    var result = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target);
202    // For x86, we need to populate some CPU feature flags depending on architecture
203    // and mode:
204    //  * 16bit_mode => if the abi is code16
205    //  * 32bit_mode => if the arch is i386
206    // However, the "mode" flags can be used as overrides, so if the user explicitly
207    // sets one of them, that takes precedence.
208    switch (cpu_arch) {
209        .i386 => {
210            if (!std.Target.x86.featureSetHasAny(cross_target.cpu_features_add, .{
211                .@"16bit_mode", .@"32bit_mode",
212            })) {
213                switch (result.target.abi) {
214                    .code16 => result.target.cpu.features.addFeature(
215                        @enumToInt(std.Target.x86.Feature.@"16bit_mode"),
216                    ),
217                    else => result.target.cpu.features.addFeature(
218                        @enumToInt(std.Target.x86.Feature.@"32bit_mode"),
219                    ),
220                }
221            }
222        },
223        .arm, .armeb => {
224            // XXX What do we do if the target has the noarm feature?
225            //     What do we do if the user specifies +thumb_mode?
226        },
227        .thumb, .thumbeb => {
228            result.target.cpu.features.addFeature(
229                @enumToInt(std.Target.arm.Feature.thumb_mode),
230            );
231        },
232        else => {},
233    }
234    cross_target.updateCpuFeatures(&result.target.cpu.features);
235    return result;
236}
237
238/// First we attempt to use the executable's own binary. If it is dynamically
239/// linked, then it should answer both the C ABI question and the dynamic linker question.
240/// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then
241/// we fall back to the defaults.
242/// TODO Remove the Allocator requirement from this function.
243fn detectAbiAndDynamicLinker(
244    allocator: Allocator,
245    cpu: Target.Cpu,
246    os: Target.Os,
247    cross_target: CrossTarget,
248) DetectError!NativeTargetInfo {
249    const native_target_has_ld = comptime builtin.target.hasDynamicLinker();
250    const is_linux = builtin.target.os.tag == .linux;
251    const have_all_info = cross_target.dynamic_linker.get() != null and
252        cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu());
253    const os_is_non_native = cross_target.os_tag != null;
254    if (!native_target_has_ld or have_all_info or os_is_non_native) {
255        return defaultAbiAndDynamicLinker(cpu, os, cross_target);
256    }
257    if (cross_target.abi) |abi| {
258        if (abi.isMusl()) {
259            // musl implies static linking.
260            return defaultAbiAndDynamicLinker(cpu, os, cross_target);
261        }
262    }
263    // The current target's ABI cannot be relied on for this. For example, we may build the zig
264    // compiler for target riscv64-linux-musl and provide a tarball for users to download.
265    // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
266    // and supported by Zig. But that means that we must detect the system ABI here rather than
267    // relying on `builtin.target`.
268    const all_abis = comptime blk: {
269        assert(@enumToInt(Target.Abi.none) == 0);
270        const fields = std.meta.fields(Target.Abi)[1..];
271        var array: [fields.len]Target.Abi = undefined;
272        inline for (fields) |field, i| {
273            array[i] = @field(Target.Abi, field.name);
274        }
275        break :blk array;
276    };
277    var ld_info_list_buffer: [all_abis.len]LdInfo = undefined;
278    var ld_info_list_len: usize = 0;
279
280    for (all_abis) |abi| {
281        // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and
282        // skip adding it to `ld_info_list`.
283        const target: Target = .{
284            .cpu = cpu,
285            .os = os,
286            .abi = abi,
287        };
288        const ld = target.standardDynamicLinkerPath();
289        if (ld.get() == null) continue;
290
291        ld_info_list_buffer[ld_info_list_len] = .{
292            .ld = ld,
293            .abi = abi,
294        };
295        ld_info_list_len += 1;
296    }
297    const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
298
299    // Best case scenario: the executable is dynamically linked, and we can iterate
300    // over our own shared objects and find a dynamic linker.
301    self_exe: {
302        const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
303        defer {
304            for (lib_paths) |lib_path| {
305                allocator.free(lib_path);
306            }
307            allocator.free(lib_paths);
308        }
309
310        var found_ld_info: LdInfo = undefined;
311        var found_ld_path: [:0]const u8 = undefined;
312
313        // Look for dynamic linker.
314        // This is O(N^M) but typical case here is N=2 and M=10.
315        find_ld: for (lib_paths) |lib_path| {
316            for (ld_info_list) |ld_info| {
317                const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
318                if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
319                    found_ld_info = ld_info;
320                    found_ld_path = lib_path;
321                    break :find_ld;
322                }
323            }
324        } else break :self_exe;
325
326        // Look for glibc version.
327        var os_adjusted = os;
328        if (builtin.target.os.tag == .linux and found_ld_info.abi.isGnu() and
329            cross_target.glibc_version == null)
330        {
331            for (lib_paths) |lib_path| {
332                if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
333                    os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) {
334                        error.UnrecognizedGnuLibCFileName => continue,
335                        error.InvalidGnuLibCVersion => continue,
336                        error.GnuLibCVersionUnavailable => continue,
337                        else => |e| return e,
338                    };
339                    break;
340                }
341            }
342        }
343
344        var result: NativeTargetInfo = .{
345            .target = .{
346                .cpu = cpu,
347                .os = os_adjusted,
348                .abi = cross_target.abi orelse found_ld_info.abi,
349            },
350            .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
351                DynamicLinker.init(found_ld_path)
352            else
353                cross_target.dynamic_linker,
354        };
355        return result;
356    }
357
358    const env_file = std.fs.openFileAbsoluteZ("/usr/bin/env", .{}) catch |err| switch (err) {
359        error.NoSpaceLeft => unreachable,
360        error.NameTooLong => unreachable,
361        error.PathAlreadyExists => unreachable,
362        error.SharingViolation => unreachable,
363        error.InvalidUtf8 => unreachable,
364        error.BadPathName => unreachable,
365        error.PipeBusy => unreachable,
366        error.FileLocksNotSupported => unreachable,
367        error.WouldBlock => unreachable,
368        error.FileBusy => unreachable, // opened without write permissions
369
370        error.IsDir,
371        error.NotDir,
372        error.AccessDenied,
373        error.NoDevice,
374        error.FileNotFound,
375        error.FileTooBig,
376        error.Unexpected,
377        => return defaultAbiAndDynamicLinker(cpu, os, cross_target),
378
379        else => |e| return e,
380    };
381    defer env_file.close();
382
383    // If Zig is statically linked, such as via distributed binary static builds, the above
384    // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env.
385    // Since that path is hard-coded into the shebang line of many portable scripts, it's a
386    // reasonably reliable path to check for.
387    return abiAndDynamicLinkerFromFile(env_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) {
388        error.FileSystem,
389        error.SystemResources,
390        error.SymLinkLoop,
391        error.ProcessFdQuotaExceeded,
392        error.SystemFdQuotaExceeded,
393        => |e| return e,
394
395        error.UnableToReadElfFile,
396        error.InvalidElfClass,
397        error.InvalidElfVersion,
398        error.InvalidElfEndian,
399        error.InvalidElfFile,
400        error.InvalidElfMagic,
401        error.Unexpected,
402        error.UnexpectedEndOfFile,
403        error.NameTooLong,
404        // Finally, we fall back on the standard path.
405        => defaultAbiAndDynamicLinker(cpu, os, cross_target),
406    };
407}
408
409const glibc_so_basename = "libc.so.6";
410
411fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version {
412    var link_buf: [std.os.PATH_MAX]u8 = undefined;
413    const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) {
414        error.AccessDenied => return error.GnuLibCVersionUnavailable,
415        error.FileSystem => return error.FileSystem,
416        error.SymLinkLoop => return error.SymLinkLoop,
417        error.NameTooLong => unreachable,
418        error.NotLink => return error.GnuLibCVersionUnavailable,
419        error.FileNotFound => return error.GnuLibCVersionUnavailable,
420        error.SystemResources => return error.SystemResources,
421        error.NotDir => return error.GnuLibCVersionUnavailable,
422        error.Unexpected => return error.GnuLibCVersionUnavailable,
423        error.InvalidUtf8 => unreachable, // Windows only
424        error.BadPathName => unreachable, // Windows only
425        error.UnsupportedReparsePointType => unreachable, // Windows only
426    };
427    return glibcVerFromLinkName(link_name, "libc-");
428}
429
430fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version {
431    // example: "libc-2.3.4.so"
432    // example: "libc-2.27.so"
433    // example: "ld-2.33.so"
434    const suffix = ".so";
435    if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
436        return error.UnrecognizedGnuLibCFileName;
437    }
438    // chop off "libc-" and ".so"
439    const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
440    return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) {
441        error.Overflow => return error.InvalidGnuLibCVersion,
442        error.InvalidCharacter => return error.InvalidGnuLibCVersion,
443        error.InvalidVersion => return error.InvalidGnuLibCVersion,
444    };
445}
446
447pub const AbiAndDynamicLinkerFromFileError = error{
448    FileSystem,
449    SystemResources,
450    SymLinkLoop,
451    ProcessFdQuotaExceeded,
452    SystemFdQuotaExceeded,
453    UnableToReadElfFile,
454    InvalidElfClass,
455    InvalidElfVersion,
456    InvalidElfEndian,
457    InvalidElfFile,
458    InvalidElfMagic,
459    Unexpected,
460    UnexpectedEndOfFile,
461    NameTooLong,
462};
463
464pub fn abiAndDynamicLinkerFromFile(
465    file: fs.File,
466    cpu: Target.Cpu,
467    os: Target.Os,
468    ld_info_list: []const LdInfo,
469    cross_target: CrossTarget,
470) AbiAndDynamicLinkerFromFileError!NativeTargetInfo {
471    var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
472    _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
473    const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
474    const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
475    if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic;
476    const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
477        elf.ELFDATA2LSB => .Little,
478        elf.ELFDATA2MSB => .Big,
479        else => return error.InvalidElfEndian,
480    };
481    const need_bswap = elf_endian != native_endian;
482    if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
483
484    const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
485        elf.ELFCLASS32 => false,
486        elf.ELFCLASS64 => true,
487        else => return error.InvalidElfClass,
488    };
489    var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
490    const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
491    const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
492
493    var result: NativeTargetInfo = .{
494        .target = .{
495            .cpu = cpu,
496            .os = os,
497            .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
498        },
499        .dynamic_linker = cross_target.dynamic_linker,
500    };
501    var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
502    const look_for_ld = cross_target.dynamic_linker.get() == null;
503
504    var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
505    if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
506
507    var ph_i: u16 = 0;
508    while (ph_i < phnum) {
509        // Reserve some bytes so that we can deref the 64-bit struct fields
510        // even when the ELF file is 32-bits.
511        const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
512        const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
513        var ph_buf_i: usize = 0;
514        while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
515            ph_i += 1;
516            phoff += phentsize;
517            ph_buf_i += phentsize;
518        }) {
519            const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i]));
520            const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i]));
521            const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
522            switch (p_type) {
523                elf.PT_INTERP => if (look_for_ld) {
524                    const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
525                    const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
526                    if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
527                    const filesz = @intCast(usize, p_filesz);
528                    _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
529                    // PT_INTERP includes a null byte in filesz.
530                    const len = filesz - 1;
531                    // dynamic_linker.max_byte is "max", not "len".
532                    // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
533                    result.dynamic_linker.max_byte = @intCast(u8, len - 1);
534
535                    // Use it to determine ABI.
536                    const full_ld_path = result.dynamic_linker.buffer[0..len];
537                    for (ld_info_list) |ld_info| {
538                        const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
539                        if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
540                            result.target.abi = ld_info.abi;
541                            break;
542                        }
543                    }
544                },
545                // We only need this for detecting glibc version.
546                elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
547                    cross_target.glibc_version == null)
548                {
549                    var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
550                    const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
551                    const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
552                    const dyn_num = p_filesz / dyn_size;
553                    var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
554                    var dyn_i: usize = 0;
555                    dyn: while (dyn_i < dyn_num) {
556                        // Reserve some bytes so that we can deref the 64-bit struct fields
557                        // even when the ELF file is 32-bits.
558                        const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
559                        const dyn_read_byte_len = try preadMin(
560                            file,
561                            dyn_buf[0 .. dyn_buf.len - dyn_reserve],
562                            dyn_off,
563                            dyn_size,
564                        );
565                        var dyn_buf_i: usize = 0;
566                        while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
567                            dyn_i += 1;
568                            dyn_off += dyn_size;
569                            dyn_buf_i += dyn_size;
570                        }) {
571                            const dyn32 = @ptrCast(
572                                *elf.Elf32_Dyn,
573                                @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]),
574                            );
575                            const dyn64 = @ptrCast(
576                                *elf.Elf64_Dyn,
577                                @alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]),
578                            );
579                            const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
580                            const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
581                            if (tag == elf.DT_RUNPATH) {
582                                rpath_offset = val;
583                                break :dyn;
584                            }
585                        }
586                    }
587                },
588                else => continue,
589            }
590        }
591    }
592
593    if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
594        cross_target.glibc_version == null)
595    {
596        if (rpath_offset) |rpoff| {
597            const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
598
599            var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
600            const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
601            const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
602
603            var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
604            if (sh_buf.len < shentsize) return error.InvalidElfFile;
605
606            _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
607            const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
608            const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
609            const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
610            const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
611            var strtab_buf: [4096:0]u8 = undefined;
612            const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
613            const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
614            const shstrtab = strtab_buf[0..shstrtab_read_len];
615
616            const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
617            var sh_i: u16 = 0;
618            const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
619                // Reserve some bytes so that we can deref the 64-bit struct fields
620                // even when the ELF file is 32-bits.
621                const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
622                const sh_read_byte_len = try preadMin(
623                    file,
624                    sh_buf[0 .. sh_buf.len - sh_reserve],
625                    shoff,
626                    shentsize,
627                );
628                var sh_buf_i: usize = 0;
629                while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
630                    sh_i += 1;
631                    shoff += shentsize;
632                    sh_buf_i += shentsize;
633                }) {
634                    const sh32 = @ptrCast(
635                        *elf.Elf32_Shdr,
636                        @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
637                    );
638                    const sh64 = @ptrCast(
639                        *elf.Elf64_Shdr,
640                        @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
641                    );
642                    const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
643                    // TODO this pointer cast should not be necessary
644                    const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
645                    if (mem.eql(u8, sh_name, ".dynstr")) {
646                        break :find_dyn_str .{
647                            .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
648                            .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
649                        };
650                    }
651                }
652            } else null;
653
654            if (dynstr) |ds| {
655                const strtab_len = std.math.min(ds.size, strtab_buf.len);
656                const strtab_read_len = try preadMin(file, &strtab_buf, ds.offset, strtab_len);
657                const strtab = strtab_buf[0..strtab_read_len];
658                // TODO this pointer cast should not be necessary
659                const rpoff_usize = std.math.cast(usize, rpoff) catch |err| switch (err) {
660                    error.Overflow => return error.InvalidElfFile,
661                };
662                const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab[rpoff_usize..].ptr, 0), 0);
663                var it = mem.tokenize(u8, rpath_list, ":");
664                while (it.next()) |rpath| {
665                    var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
666                        error.NameTooLong => unreachable,
667                        error.InvalidUtf8 => unreachable,
668                        error.BadPathName => unreachable,
669                        error.DeviceBusy => unreachable,
670
671                        error.FileNotFound,
672                        error.NotDir,
673                        error.AccessDenied,
674                        error.NoDevice,
675                        => continue,
676
677                        error.ProcessFdQuotaExceeded,
678                        error.SystemFdQuotaExceeded,
679                        error.SystemResources,
680                        error.SymLinkLoop,
681                        error.Unexpected,
682                        => |e| return e,
683                    };
684                    defer dir.close();
685
686                    var link_buf: [std.os.PATH_MAX]u8 = undefined;
687                    const link_name = std.os.readlinkatZ(
688                        dir.fd,
689                        glibc_so_basename,
690                        &link_buf,
691                    ) catch |err| switch (err) {
692                        error.NameTooLong => unreachable,
693                        error.InvalidUtf8 => unreachable, // Windows only
694                        error.BadPathName => unreachable, // Windows only
695                        error.UnsupportedReparsePointType => unreachable, // Windows only
696
697                        error.AccessDenied,
698                        error.FileNotFound,
699                        error.NotLink,
700                        error.NotDir,
701                        => continue,
702
703                        error.SystemResources,
704                        error.FileSystem,
705                        error.SymLinkLoop,
706                        error.Unexpected,
707                        => |e| return e,
708                    };
709                    result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
710                        link_name,
711                        "libc-",
712                    ) catch |err| switch (err) {
713                        error.UnrecognizedGnuLibCFileName,
714                        error.InvalidGnuLibCVersion,
715                        => continue,
716                    };
717                    break;
718                }
719            }
720        } else if (result.dynamic_linker.get()) |dl_path| glibc_ver: {
721            // There is no DT_RUNPATH but we can try to see if the information is
722            // present in the symlink data for the dynamic linker path.
723            var link_buf: [std.os.PATH_MAX]u8 = undefined;
724            const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
725                error.NameTooLong => unreachable,
726                error.InvalidUtf8 => unreachable, // Windows only
727                error.BadPathName => unreachable, // Windows only
728                error.UnsupportedReparsePointType => unreachable, // Windows only
729
730                error.AccessDenied,
731                error.FileNotFound,
732                error.NotLink,
733                error.NotDir,
734                => break :glibc_ver,
735
736                error.SystemResources,
737                error.FileSystem,
738                error.SymLinkLoop,
739                error.Unexpected,
740                => |e| return e,
741            };
742            result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
743                fs.path.basename(link_name),
744                "ld-",
745            ) catch |err| switch (err) {
746                error.UnrecognizedGnuLibCFileName,
747                error.InvalidGnuLibCVersion,
748                => break :glibc_ver,
749            };
750        }
751    }
752
753    return result;
754}
755
756fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
757    var i: usize = 0;
758    while (i < min_read_len) {
759        const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
760            error.OperationAborted => unreachable, // Windows-only
761            error.WouldBlock => unreachable, // Did not request blocking mode
762            error.NotOpenForReading => unreachable,
763            error.SystemResources => return error.SystemResources,
764            error.IsDir => return error.UnableToReadElfFile,
765            error.BrokenPipe => return error.UnableToReadElfFile,
766            error.Unseekable => return error.UnableToReadElfFile,
767            error.ConnectionResetByPeer => return error.UnableToReadElfFile,
768            error.ConnectionTimedOut => return error.UnableToReadElfFile,
769            error.Unexpected => return error.Unexpected,
770            error.InputOutput => return error.FileSystem,
771            error.AccessDenied => return error.Unexpected,
772        };
773        if (len == 0) return error.UnexpectedEndOfFile;
774        i += len;
775    }
776    return i;
777}
778
779fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo {
780    const target: Target = .{
781        .cpu = cpu,
782        .os = os,
783        .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os),
784    };
785    return NativeTargetInfo{
786        .target = target,
787        .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
788            target.standardDynamicLinkerPath()
789        else
790            cross_target.dynamic_linker,
791    };
792}
793
794pub const LdInfo = struct {
795    ld: DynamicLinker,
796    abi: Target.Abi,
797};
798
799pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
800    if (is_64) {
801        if (need_bswap) {
802            return @byteSwap(@TypeOf(int_64), int_64);
803        } else {
804            return int_64;
805        }
806    } else {
807        if (need_bswap) {
808            return @byteSwap(@TypeOf(int_32), int_32);
809        } else {
810            return int_32;
811        }
812    }
813}
814
815fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, cross_target: CrossTarget) ?Target.Cpu {
816    // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
817    // although it is a runtime value, is guaranteed to be one of the architectures in the set
818    // of the respective switch prong.
819    switch (builtin.cpu.arch) {
820        .x86_64, .i386 => {
821            return @import("x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target);
822        },
823        else => {},
824    }
825
826    switch (builtin.os.tag) {
827        .linux => return linux.detectNativeCpuAndFeatures(),
828        .macos => return darwin.macos.detectNativeCpuAndFeatures(),
829        else => {},
830    }
831
832    // This architecture does not have CPU model & feature detection yet.
833    // See https://github.com/ziglang/zig/issues/4591
834    return null;
835}
836
837pub const Executor = union(enum) {
838    native,
839    rosetta,
840    qemu: []const u8,
841    wine: []const u8,
842    wasmtime: []const u8,
843    darling: []const u8,
844    bad_dl: []const u8,
845    bad_os_or_cpu,
846};
847
848pub const GetExternalExecutorOptions = struct {
849    allow_darling: bool = true,
850    allow_qemu: bool = true,
851    allow_rosetta: bool = true,
852    allow_wasmtime: bool = true,
853    allow_wine: bool = true,
854    qemu_fixes_dl: bool = false,
855    link_libc: bool = false,
856};
857
858/// Return whether or not the given host target is capable of executing natively executables
859/// of the other target.
860pub fn getExternalExecutor(
861    host: NativeTargetInfo,
862    candidate: NativeTargetInfo,
863    options: GetExternalExecutorOptions,
864) Executor {
865    const os_match = host.target.os.tag == candidate.target.os.tag;
866    const cpu_ok = cpu_ok: {
867        if (host.target.cpu.arch == candidate.target.cpu.arch)
868            break :cpu_ok true;
869
870        if (host.target.cpu.arch == .x86_64 and candidate.target.cpu.arch == .i386)
871            break :cpu_ok true;
872
873        if (host.target.cpu.arch == .aarch64 and candidate.target.cpu.arch == .arm)
874            break :cpu_ok true;
875
876        if (host.target.cpu.arch == .aarch64_be and candidate.target.cpu.arch == .armeb)
877            break :cpu_ok true;
878
879        // TODO additionally detect incompatible CPU features.
880        // Note that in some cases the OS kernel will emulate missing CPU features
881        // when an illegal instruction is encountered.
882
883        break :cpu_ok false;
884    };
885
886    var bad_result: Executor = .bad_os_or_cpu;
887
888    if (os_match and cpu_ok) native: {
889        if (options.link_libc) {
890            if (candidate.dynamic_linker.get()) |candidate_dl| {
891                fs.cwd().access(candidate_dl, .{}) catch {
892                    bad_result = .{ .bad_dl = candidate_dl };
893                    break :native;
894                };
895            }
896        }
897        return .native;
898    }
899
900    // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
901    // to emulate the foreign architecture.
902    if (options.allow_rosetta and os_match and
903        host.target.os.tag == .macos and host.target.cpu.arch == .aarch64)
904    {
905        switch (candidate.target.cpu.arch) {
906            .x86_64 => return .rosetta,
907            else => return bad_result,
908        }
909    }
910
911    // If the OS matches, we can use QEMU to emulate a foreign architecture.
912    if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) {
913        return switch (candidate.target.cpu.arch) {
914            .aarch64 => Executor{ .qemu = "qemu-aarch64" },
915            .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" },
916            .arm => Executor{ .qemu = "qemu-arm" },
917            .armeb => Executor{ .qemu = "qemu-armeb" },
918            .hexagon => Executor{ .qemu = "qemu-hexagon" },
919            .i386 => Executor{ .qemu = "qemu-i386" },
920            .m68k => Executor{ .qemu = "qemu-m68k" },
921            .mips => Executor{ .qemu = "qemu-mips" },
922            .mipsel => Executor{ .qemu = "qemu-mipsel" },
923            .mips64 => Executor{ .qemu = "qemu-mips64" },
924            .mips64el => Executor{ .qemu = "qemu-mips64el" },
925            .powerpc => Executor{ .qemu = "qemu-ppc" },
926            .powerpc64 => Executor{ .qemu = "qemu-ppc64" },
927            .powerpc64le => Executor{ .qemu = "qemu-ppc64le" },
928            .riscv32 => Executor{ .qemu = "qemu-riscv32" },
929            .riscv64 => Executor{ .qemu = "qemu-riscv64" },
930            .s390x => Executor{ .qemu = "qemu-s390x" },
931            .sparc => Executor{ .qemu = "qemu-sparc" },
932            .x86_64 => Executor{ .qemu = "qemu-x86_64" },
933            else => return bad_result,
934        };
935    }
936
937    switch (candidate.target.os.tag) {
938        .windows => {
939            if (options.allow_wine) {
940                switch (candidate.target.cpu.arch.ptrBitWidth()) {
941                    32 => return Executor{ .wine = "wine" },
942                    64 => return Executor{ .wine = "wine64" },
943                    else => return bad_result,
944                }
945            }
946            return bad_result;
947        },
948        .wasi => {
949            if (options.allow_wasmtime) {
950                switch (candidate.target.cpu.arch.ptrBitWidth()) {
951                    32 => return Executor{ .wasmtime = "wasmtime" },
952                    else => return bad_result,
953                }
954            }
955            return bad_result;
956        },
957        .macos => {
958            if (options.allow_darling) {
959                // This check can be loosened once darling adds a QEMU-based emulation
960                // layer for non-host architectures:
961                // https://github.com/darlinghq/darling/issues/863
962                if (candidate.target.cpu.arch != builtin.cpu.arch) {
963                    return bad_result;
964                }
965                return Executor{ .darling = "darling" };
966            }
967            return bad_result;
968        },
969        else => return bad_result,
970    }
971}
972