My upstream PRs https://github.com/ziglang/zig/pull/12151 and https://github.com/ziglang/zig/pull/12567, adapted for 0.9.1 Bug https://bugs.gentoo.org/866374 Bug https://bugs.gentoo.org/780612 From: Eric Joldasov diff --git a/lib/std/zig/system/NativeTargetInfo.zig b/lib/std/zig/system/NativeTargetInfo.zig index af41fc7..2fdacce 100644 --- a/lib/std/zig/system/NativeTargetInfo.zig +++ b/lib/std/zig/system/NativeTargetInfo.zig @@ -237,7 +237,7 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ /// First we attempt to use the executable's own binary. If it is dynamically /// linked, then it should answer both the C ABI question and the dynamic linker question. -/// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then +/// If it is statically linked, then we try /usr/bin/env (or the file it references in shebang). If that does not provide the answer, then /// we fall back to the defaults. /// TODO Remove the Allocator requirement from this function. fn detectAbiAndDynamicLinker( @@ -328,16 +328,17 @@ fn detectAbiAndDynamicLinker( if (builtin.target.os.tag == .linux and found_ld_info.abi.isGnu() and cross_target.glibc_version == null) { - for (lib_paths) |lib_path| { - if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) { - os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) { - error.UnrecognizedGnuLibCFileName => continue, - error.InvalidGnuLibCVersion => continue, - error.GnuLibCVersionUnavailable => continue, - else => |e| return e, - }; - break; - } + const getconf = std.ChildProcess.exec(.{ .allocator = allocator, .argv = &.{ "getconf", "GNU_LIBC_VERSION" } }) catch unreachable; + defer { + allocator.free(getconf.stderr); + allocator.free(getconf.stdout); + } + const glibc_ver_from_stdout = getconf.stdout["glibc ".len..]; + const glibc_version = std.builtin.Version.parse(glibc_ver_from_stdout) catch unreachable; + if (glibc_version.minor >= 35) { + os_adjusted.version_range.linux.glibc = std.builtin.Version{ .major = 2, .minor = 34 }; // Zig 0.9.1 does not provide glibc newer than 2.34 + } else { + os_adjusted.version_range.linux.glibc = glibc_version; } } @@ -355,41 +356,78 @@ fn detectAbiAndDynamicLinker( return result; } - const env_file = std.fs.openFileAbsoluteZ("/usr/bin/env", .{}) catch |err| switch (err) { - error.NoSpaceLeft => unreachable, - error.NameTooLong => unreachable, - error.PathAlreadyExists => unreachable, - error.SharingViolation => unreachable, - error.InvalidUtf8 => unreachable, - error.BadPathName => unreachable, - error.PipeBusy => unreachable, - error.FileLocksNotSupported => unreachable, - error.WouldBlock => unreachable, - error.FileBusy => unreachable, // opened without write permissions - - error.IsDir, - error.NotDir, - error.AccessDenied, - error.NoDevice, - error.FileNotFound, - error.FileTooBig, - error.Unexpected, - => return defaultAbiAndDynamicLinker(cpu, os, cross_target), + const elf_file = blk: { + // 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. + + // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a + // reasonably reliable path to start with. + var file_name: []const u8 = "/usr/bin/env"; + // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) + var buffer: [258]u8 = undefined; + while (true) { + const file = std.fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + error.NameTooLong => unreachable, + error.PathAlreadyExists => unreachable, + error.SharingViolation => unreachable, + error.InvalidUtf8 => unreachable, + error.BadPathName => unreachable, + error.PipeBusy => unreachable, + error.FileLocksNotSupported => unreachable, + error.WouldBlock => unreachable, + error.FileBusy => unreachable, // opened without write permissions + + error.IsDir, + error.NotDir, + error.AccessDenied, + error.NoDevice, + error.FileNotFound, + error.FileTooBig, + error.Unexpected, + => |e| { + std.log.warn("Encoutered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); + return defaultAbiAndDynamicLinker(cpu, os, cross_target); + }, - else => |e| return e, + else => |e| return e, + }; + + const line = file.reader().readUntilDelimiter(&buffer, '\n') catch |err| switch (err) { + error.IsDir => unreachable, // Handled before + error.AccessDenied => unreachable, + error.WouldBlock => unreachable, // Did not request blocking mode + error.OperationAborted => unreachable, // Windows-only + error.BrokenPipe => unreachable, + error.ConnectionResetByPeer => unreachable, + error.ConnectionTimedOut => unreachable, + error.InputOutput => unreachable, + error.Unexpected => unreachable, + + error.StreamTooLong, + error.EndOfStream, + error.NotOpenForReading, + => break :blk file, + + else => |e| { + file.close(); + return e; + }, + }; + if (!mem.startsWith(u8, line, "#!")) break :blk file; + var it = std.mem.tokenize(u8, line[2..], " "); + file.close(); + file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, cross_target); + } }; - defer env_file.close(); + defer elf_file.close(); // If Zig is statically linked, such as via distributed binary static builds, the above - // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env. - // Since that path is hard-coded into the shebang line of many portable scripts, it's a - // reasonably reliable path to check for. - return abiAndDynamicLinkerFromFile(env_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) { + // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file. + return abiAndDynamicLinkerFromFile(allocator, elf_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) { error.FileSystem, error.SystemResources, - error.SymLinkLoop, - error.ProcessFdQuotaExceeded, - error.SystemFdQuotaExceeded, => |e| return e, error.UnableToReadElfFile, @@ -402,54 +440,16 @@ fn detectAbiAndDynamicLinker( error.UnexpectedEndOfFile, error.NameTooLong, // Finally, we fall back on the standard path. - => defaultAbiAndDynamicLinker(cpu, os, cross_target), - }; -} - -const glibc_so_basename = "libc.so.6"; - -fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version { - var link_buf: [std.os.PATH_MAX]u8 = undefined; - const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) { - error.AccessDenied => return error.GnuLibCVersionUnavailable, - error.FileSystem => return error.FileSystem, - error.SymLinkLoop => return error.SymLinkLoop, - error.NameTooLong => unreachable, - error.NotLink => return error.GnuLibCVersionUnavailable, - error.FileNotFound => return error.GnuLibCVersionUnavailable, - error.SystemResources => return error.SystemResources, - error.NotDir => return error.GnuLibCVersionUnavailable, - error.Unexpected => return error.GnuLibCVersionUnavailable, - error.InvalidUtf8 => unreachable, // Windows only - error.BadPathName => unreachable, // Windows only - error.UnsupportedReparsePointType => unreachable, // Windows only - }; - return glibcVerFromLinkName(link_name, "libc-"); -} - -fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version { - // example: "libc-2.3.4.so" - // example: "libc-2.27.so" - // example: "ld-2.33.so" - const suffix = ".so"; - if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) { - return error.UnrecognizedGnuLibCFileName; - } - // chop off "libc-" and ".so" - const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len]; - return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) { - error.Overflow => return error.InvalidGnuLibCVersion, - error.InvalidCharacter => return error.InvalidGnuLibCVersion, - error.InvalidVersion => return error.InvalidGnuLibCVersion, + => |e| { + std.log.warn("Encoutered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)}); + return defaultAbiAndDynamicLinker(cpu, os, cross_target); + }, }; } pub const AbiAndDynamicLinkerFromFileError = error{ FileSystem, SystemResources, - SymLinkLoop, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, UnableToReadElfFile, InvalidElfClass, InvalidElfVersion, @@ -462,6 +462,7 @@ pub const AbiAndDynamicLinkerFromFileError = error{ }; pub fn abiAndDynamicLinkerFromFile( + allocator: Allocator, file: fs.File, cpu: Target.Cpu, os: Target.Os, @@ -498,7 +499,6 @@ pub fn abiAndDynamicLinkerFromFile( }, .dynamic_linker = cross_target.dynamic_linker, }; - var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC const look_for_ld = cross_target.dynamic_linker.get() == null; var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; @@ -542,49 +542,6 @@ pub fn abiAndDynamicLinkerFromFile( } } }, - // We only need this for detecting glibc version. - elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and - cross_target.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 preadMin( - 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 = @ptrCast( - *elf.Elf32_Dyn, - @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]), - ); - const dyn64 = @ptrCast( - *elf.Elf64_Dyn, - @alignCast(@alignOf(elf.Elf64_Dyn), &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; - } - } - } - }, else => continue, } } @@ -593,163 +550,19 @@ pub fn abiAndDynamicLinkerFromFile( if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and cross_target.glibc_version == null) { - if (rpath_offset) |rpoff| { - 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 preadMin(file, &sh_buf, str_section_off, shentsize); - const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf)); - const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &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 = std.math.min(shstrtab_size, strtab_buf.len); - const shstrtab_read_len = try preadMin(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 preadMin( - 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 = @ptrCast( - *elf.Elf32_Shdr, - @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]), - ); - const sh64 = @ptrCast( - *elf.Elf64_Shdr, - @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]), - ); - const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); - // TODO this pointer cast should not be necessary - const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 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 (dynstr) |ds| { - const strtab_len = std.math.min(ds.size, strtab_buf.len); - const strtab_read_len = try preadMin(file, &strtab_buf, ds.offset, strtab_len); - const strtab = strtab_buf[0..strtab_read_len]; - // TODO this pointer cast should not be necessary - const rpoff_usize = std.math.cast(usize, rpoff) catch |err| switch (err) { - error.Overflow => return error.InvalidElfFile, - }; - const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab[rpoff_usize..].ptr, 0), 0); - var it = mem.tokenize(u8, rpath_list, ":"); - while (it.next()) |rpath| { - var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { - error.NameTooLong => unreachable, - error.InvalidUtf8 => unreachable, - error.BadPathName => unreachable, - error.DeviceBusy => unreachable, - - error.FileNotFound, - error.NotDir, - error.AccessDenied, - error.NoDevice, - => continue, - - error.ProcessFdQuotaExceeded, - error.SystemFdQuotaExceeded, - error.SystemResources, - error.SymLinkLoop, - error.Unexpected, - => |e| return e, - }; - defer dir.close(); - - var link_buf: [std.os.PATH_MAX]u8 = undefined; - const link_name = std.os.readlinkatZ( - dir.fd, - glibc_so_basename, - &link_buf, - ) catch |err| switch (err) { - error.NameTooLong => unreachable, - error.InvalidUtf8 => unreachable, // Windows only - error.BadPathName => unreachable, // Windows only - error.UnsupportedReparsePointType => unreachable, // Windows only - - error.AccessDenied, - error.FileNotFound, - error.NotLink, - error.NotDir, - => continue, - - error.SystemResources, - error.FileSystem, - error.SymLinkLoop, - error.Unexpected, - => |e| return e, - }; - result.target.os.version_range.linux.glibc = glibcVerFromLinkName( - link_name, - "libc-", - ) catch |err| switch (err) { - error.UnrecognizedGnuLibCFileName, - error.InvalidGnuLibCVersion, - => continue, - }; - break; - } - } - } else if (result.dynamic_linker.get()) |dl_path| glibc_ver: { - // There is no DT_RUNPATH but we can try to see if the information is - // present in the symlink data for the dynamic linker path. - var link_buf: [std.os.PATH_MAX]u8 = undefined; - const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { - error.NameTooLong => unreachable, - error.InvalidUtf8 => unreachable, // Windows only - error.BadPathName => unreachable, // Windows only - error.UnsupportedReparsePointType => unreachable, // Windows only - - error.AccessDenied, - error.FileNotFound, - error.NotLink, - error.NotDir, - => break :glibc_ver, - - error.SystemResources, - error.FileSystem, - error.SymLinkLoop, - error.Unexpected, - => |e| return e, - }; - result.target.os.version_range.linux.glibc = glibcVerFromLinkName( - fs.path.basename(link_name), - "ld-", - ) catch |err| switch (err) { - error.UnrecognizedGnuLibCFileName, - error.InvalidGnuLibCVersion, - => break :glibc_ver, - }; + const getconf = std.ChildProcess.exec(.{ .allocator = allocator, .argv = &.{ "getconf", "GNU_LIBC_VERSION" } }) catch unreachable; + defer { + allocator.free(getconf.stderr); + allocator.free(getconf.stdout); + } + const glibc_ver_from_stdout = getconf.stdout["glibc ".len..]; + const glibc_version = std.builtin.Version.parse(glibc_ver_from_stdout) catch unreachable; + if (glibc_version.minor >= 35) { + result.target.os.version_range.linux.glibc = std.builtin.Version{ .major = 2, .minor = 34 }; // Zig 0.9.1 does not provide glibc newer than 2.34 + } else { + result.target.os.version_range.linux.glibc = glibc_version; } } - return result; }