mirror of
https://github.com/ziglang/zig.git
synced 2024-11-14 16:13:24 +00:00
std.zig.system.NativeTargetInfo: improve glibc version detection
Previously, this code would fail to detect glibc version because it relied on libc.so.6 being a symlink which revealed the answer. On modern distros, this is no longer the case. This new strategy finds the path to libc.so.6 from /usr/bin/env, then inspects the .dynstr section of libc.so.6, looking for symbols that start with "GLIBC_2.". It then parses those as semantic versions and takes the maximum value as the system-native glibc version. closes #6469 see #11137 closes #12567
This commit is contained in:
parent
a833bdcd7e
commit
fa940bafa2
@ -28,6 +28,7 @@ pub const DetectError = error{
|
||||
SystemFdQuotaExceeded,
|
||||
DeviceBusy,
|
||||
OSVersionDetectionFail,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
/// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected
|
||||
@ -332,9 +333,7 @@ fn detectAbiAndDynamicLinker(
|
||||
{
|
||||
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,
|
||||
os_adjusted.version_range.linux.glibc = glibcVerFromSo(lib_path) catch |err| switch (err) {
|
||||
error.GnuLibCVersionUnavailable => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
@ -369,7 +368,7 @@ fn detectAbiAndDynamicLinker(
|
||||
// #! (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) {
|
||||
const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
|
||||
error.NoSpaceLeft => unreachable,
|
||||
error.NameTooLong => unreachable,
|
||||
error.PathAlreadyExists => unreachable,
|
||||
@ -396,6 +395,7 @@ fn detectAbiAndDynamicLinker(
|
||||
|
||||
else => |e| return e,
|
||||
};
|
||||
errdefer file.close();
|
||||
|
||||
const line = file.reader().readUntilDelimiter(&buffer, '\n') catch |err| switch (err) {
|
||||
error.IsDir => unreachable, // Handled before
|
||||
@ -413,15 +413,12 @@ fn detectAbiAndDynamicLinker(
|
||||
error.NotOpenForReading,
|
||||
=> break :blk file,
|
||||
|
||||
else => |e| {
|
||||
file.close();
|
||||
return e;
|
||||
},
|
||||
else => |e| 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);
|
||||
file.close();
|
||||
}
|
||||
};
|
||||
defer elf_file.close();
|
||||
@ -455,23 +452,158 @@ fn detectAbiAndDynamicLinker(
|
||||
|
||||
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,
|
||||
fn glibcVerFromSo(so_path: [:0]const u8) !std.builtin.Version {
|
||||
const file = fs.openFileAbsolute(so_path, .{}) catch |err| switch (err) {
|
||||
// Contextually impossible errors.
|
||||
error.NoSpaceLeft => unreachable,
|
||||
error.NameTooLong => unreachable,
|
||||
error.NotLink => return error.GnuLibCVersionUnavailable,
|
||||
error.FileNotFound => return error.GnuLibCVersionUnavailable,
|
||||
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.NoDevice => unreachable, // not accessing special device
|
||||
error.InvalidHandle => unreachable, // should not be in the error set
|
||||
error.DeviceBusy => unreachable, // read-only
|
||||
|
||||
// Errors that indicate a false negative may occur if we treat this as
|
||||
// not a libc shared object.
|
||||
error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded,
|
||||
error.SystemFdQuotaExceeded => return error.SystemFdQuotaExceeded,
|
||||
error.SystemResources => return error.SystemResources,
|
||||
error.Unexpected => return error.Unexpected,
|
||||
|
||||
// Errors that indicate this file is not a libc shared object.
|
||||
error.SymLinkLoop => return error.GnuLibCVersionUnavailable,
|
||||
error.IsDir => return error.GnuLibCVersionUnavailable,
|
||||
error.AccessDenied => return error.GnuLibCVersionUnavailable,
|
||||
error.FileNotFound => return error.GnuLibCVersionUnavailable,
|
||||
error.FileTooBig => return error.GnuLibCVersionUnavailable,
|
||||
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-");
|
||||
defer file.close();
|
||||
|
||||
return glibcVerFromSoFile(file) catch |err| switch (err) {
|
||||
error.InvalidElfMagic => return error.GnuLibCVersionUnavailable,
|
||||
error.InvalidElfEndian => return error.GnuLibCVersionUnavailable,
|
||||
error.InvalidElfClass => return error.GnuLibCVersionUnavailable,
|
||||
error.InvalidElfFile => return error.GnuLibCVersionUnavailable,
|
||||
error.InvalidElfVersion => return error.GnuLibCVersionUnavailable,
|
||||
error.InvalidGnuLibCVersion => return error.GnuLibCVersionUnavailable,
|
||||
error.UnexpectedEndOfFile => return error.GnuLibCVersionUnavailable,
|
||||
error.UnableToReadElfFile => return error.GnuLibCVersionUnavailable,
|
||||
|
||||
error.SystemResources => return error.SystemResources,
|
||||
error.FileSystem => return error.FileSystem,
|
||||
error.Unexpected => return error.Unexpected,
|
||||
};
|
||||
}
|
||||
|
||||
fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {
|
||||
var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
|
||||
_ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
|
||||
const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
|
||||
const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &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,
|
||||
};
|
||||
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 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 this value plus some headroom. This makes it only need
|
||||
// a single read syscall here.
|
||||
var buf: [40000]u8 = undefined;
|
||||
if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion;
|
||||
|
||||
const dynstr_bytes = buf[0..dynstr.size];
|
||||
_ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr.size);
|
||||
var it = mem.split(u8, dynstr_bytes, &.{0});
|
||||
var max_ver: std.builtin.Version = .{ .major = 2, .minor = 2, .patch = 5 };
|
||||
while (it.next()) |s| {
|
||||
if (mem.startsWith(u8, s, "GLIBC_2.")) {
|
||||
const chopped = s["GLIBC_".len..];
|
||||
const ver = std.builtin.Version.parse(chopped) catch |err| switch (err) {
|
||||
error.Overflow => return error.InvalidGnuLibCVersion,
|
||||
error.InvalidCharacter => return error.InvalidGnuLibCVersion,
|
||||
error.InvalidVersion => return error.InvalidGnuLibCVersion,
|
||||
};
|
||||
switch (ver.order(max_ver)) {
|
||||
.gt => max_ver = ver,
|
||||
.lt, .eq => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
return max_ver;
|
||||
}
|
||||
|
||||
fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version {
|
||||
@ -735,36 +867,60 @@ pub fn abiAndDynamicLinkerFromFile(
|
||||
};
|
||||
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) {
|
||||
// Now we have a candidate for the path to libc shared object. In
|
||||
// the past, we used readlink() here because the link name would
|
||||
// reveal the glibc version. However, in more recent GNU/Linux
|
||||
// installations, there is no symlink. Thus we instead use a more
|
||||
// robust check of opening the libc shared object and looking at the
|
||||
// .dynstr section, and finding the max version number of symbols
|
||||
// that start with "GLIBC_2.".
|
||||
var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
error.InvalidUtf8 => unreachable, // Windows only
|
||||
error.BadPathName => unreachable, // Windows only
|
||||
error.UnsupportedReparsePointType => unreachable, // Windows only
|
||||
error.PipeBusy => unreachable, // Windows-only
|
||||
error.SharingViolation => unreachable, // Windows-only
|
||||
error.FileLocksNotSupported => unreachable, // No lock requested.
|
||||
error.NoSpaceLeft => unreachable, // read-only
|
||||
error.PathAlreadyExists => unreachable, // read-only
|
||||
error.DeviceBusy => unreachable, // read-only
|
||||
error.FileBusy => unreachable, // read-only
|
||||
error.InvalidHandle => unreachable, // should not be in the error set
|
||||
error.WouldBlock => unreachable, // not using O_NONBLOCK
|
||||
error.NoDevice => unreachable, // not asking for a special device
|
||||
|
||||
error.AccessDenied,
|
||||
error.FileNotFound,
|
||||
error.NotLink,
|
||||
error.NotDir,
|
||||
=> continue,
|
||||
|
||||
error.IsDir => return error.InvalidElfFile,
|
||||
error.FileTooBig => return error.Unexpected,
|
||||
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.SystemFdQuotaExceeded,
|
||||
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,
|
||||
defer f.close();
|
||||
|
||||
result.target.os.version_range.linux.glibc = glibcVerFromSoFile(f) catch |err| switch (err) {
|
||||
error.InvalidElfMagic,
|
||||
error.InvalidElfEndian,
|
||||
error.InvalidElfClass,
|
||||
error.InvalidElfFile,
|
||||
error.InvalidElfVersion,
|
||||
error.InvalidGnuLibCVersion,
|
||||
error.UnexpectedEndOfFile,
|
||||
=> continue,
|
||||
|
||||
error.SystemResources,
|
||||
error.UnableToReadElfFile,
|
||||
error.Unexpected,
|
||||
error.FileSystem,
|
||||
=> |e| return e,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user