authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-09-08 18:02:38-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-09-08 20:52:49-07:00
logfa940bafa2720f49ee249eda1ee4cf26a247172a
treeab82a24917a27755897fb863bb0773ff723d75ca
parenta833bdcd7e6fcfee6e9cc33a3f7de78b16a36941

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

1 files changed, 192 insertions(+), 36 deletions(-)

lib/std/zig/system/NativeTargetInfo.zig+192-36
...@@ -28,6 +28,7 @@ pub const DetectError = error{...@@ -28,6 +28,7 @@ pub const DetectError = error{
28 SystemFdQuotaExceeded,28 SystemFdQuotaExceeded,
29 DeviceBusy,29 DeviceBusy,
30 OSVersionDetectionFail,30 OSVersionDetectionFail,
31 Unexpected,
31};32};
3233
33/// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected34/// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected
...@@ -332,9 +333,7 @@ fn detectAbiAndDynamicLinker(...@@ -332,9 +333,7 @@ fn detectAbiAndDynamicLinker(
332 {333 {
333 for (lib_paths) |lib_path| {334 for (lib_paths) |lib_path| {
334 if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {335 if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
335 os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) {336 os_adjusted.version_range.linux.glibc = glibcVerFromSo(lib_path) catch |err| switch (err) {
336 error.UnrecognizedGnuLibCFileName => continue,
337 error.InvalidGnuLibCVersion => continue,
338 error.GnuLibCVersionUnavailable => continue,337 error.GnuLibCVersionUnavailable => continue,
339 else => |e| return e,338 else => |e| return e,
340 };339 };
...@@ -369,7 +368,7 @@ fn detectAbiAndDynamicLinker(...@@ -369,7 +368,7 @@ fn detectAbiAndDynamicLinker(
369 // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)368 // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)
370 var buffer: [258]u8 = undefined;369 var buffer: [258]u8 = undefined;
371 while (true) {370 while (true) {
372 const file = std.fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {371 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
373 error.NoSpaceLeft => unreachable,372 error.NoSpaceLeft => unreachable,
374 error.NameTooLong => unreachable,373 error.NameTooLong => unreachable,
375 error.PathAlreadyExists => unreachable,374 error.PathAlreadyExists => unreachable,
...@@ -396,6 +395,7 @@ fn detectAbiAndDynamicLinker(...@@ -396,6 +395,7 @@ fn detectAbiAndDynamicLinker(
396395
397 else => |e| return e,396 else => |e| return e,
398 };397 };
398 errdefer file.close();
399399
400 const line = file.reader().readUntilDelimiter(&buffer, '\n') catch |err| switch (err) {400 const line = file.reader().readUntilDelimiter(&buffer, '\n') catch |err| switch (err) {
401 error.IsDir => unreachable, // Handled before401 error.IsDir => unreachable, // Handled before
...@@ -413,15 +413,12 @@ fn detectAbiAndDynamicLinker(...@@ -413,15 +413,12 @@ fn detectAbiAndDynamicLinker(
413 error.NotOpenForReading,413 error.NotOpenForReading,
414 => break :blk file,414 => break :blk file,
415415
416 else => |e| {416 else => |e| return e,
417 file.close();
418 return e;
419 },
420 };417 };
421 if (!mem.startsWith(u8, line, "#!")) break :blk file;418 if (!mem.startsWith(u8, line, "#!")) break :blk file;
422 var it = std.mem.tokenize(u8, line[2..], " ");419 var it = std.mem.tokenize(u8, line[2..], " ");
423 file.close();
424 file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, cross_target);420 file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, cross_target);
421 file.close();
425 }422 }
426 };423 };
427 defer elf_file.close();424 defer elf_file.close();
...@@ -455,23 +452,158 @@ fn detectAbiAndDynamicLinker(...@@ -455,23 +452,158 @@ fn detectAbiAndDynamicLinker(
455452
456const glibc_so_basename = "libc.so.6";453const glibc_so_basename = "libc.so.6";
457454
458fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version {455fn glibcVerFromSo(so_path: [:0]const u8) !std.builtin.Version {
459 var link_buf: [std.os.PATH_MAX]u8 = undefined;456 const file = fs.openFileAbsolute(so_path, .{}) catch |err| switch (err) {
460 const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) {457 // Contextually impossible errors.
461 error.AccessDenied => return error.GnuLibCVersionUnavailable,458 error.NoSpaceLeft => unreachable,
462 error.FileSystem => return error.FileSystem,
463 error.SymLinkLoop => return error.SymLinkLoop,
464 error.NameTooLong => unreachable,459 error.NameTooLong => unreachable,
465 error.NotLink => return error.GnuLibCVersionUnavailable,460 error.PathAlreadyExists => unreachable,
466 error.FileNotFound => return error.GnuLibCVersionUnavailable,461 error.SharingViolation => unreachable,
462 error.InvalidUtf8 => unreachable,
463 error.BadPathName => unreachable,
464 error.PipeBusy => unreachable,
465 error.FileLocksNotSupported => unreachable,
466 error.WouldBlock => unreachable,
467 error.FileBusy => unreachable, // opened without write permissions
468 error.NoDevice => unreachable, // not accessing special device
469 error.InvalidHandle => unreachable, // should not be in the error set
470 error.DeviceBusy => unreachable, // read-only
471
472 // Errors that indicate a false negative may occur if we treat this as
473 // not a libc shared object.
474 error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded,
475 error.SystemFdQuotaExceeded => return error.SystemFdQuotaExceeded,
467 error.SystemResources => return error.SystemResources,476 error.SystemResources => return error.SystemResources,
477 error.Unexpected => return error.Unexpected,
478
479 // Errors that indicate this file is not a libc shared object.
480 error.SymLinkLoop => return error.GnuLibCVersionUnavailable,
481 error.IsDir => return error.GnuLibCVersionUnavailable,
482 error.AccessDenied => return error.GnuLibCVersionUnavailable,
483 error.FileNotFound => return error.GnuLibCVersionUnavailable,
484 error.FileTooBig => return error.GnuLibCVersionUnavailable,
468 error.NotDir => return error.GnuLibCVersionUnavailable,485 error.NotDir => return error.GnuLibCVersionUnavailable,
469 error.Unexpected => return error.GnuLibCVersionUnavailable,
470 error.InvalidUtf8 => unreachable, // Windows only
471 error.BadPathName => unreachable, // Windows only
472 error.UnsupportedReparsePointType => unreachable, // Windows only
473 };486 };
474 return glibcVerFromLinkName(link_name, "libc-");487 defer file.close();
488
489 return glibcVerFromSoFile(file) catch |err| switch (err) {
490 error.InvalidElfMagic => return error.GnuLibCVersionUnavailable,
491 error.InvalidElfEndian => return error.GnuLibCVersionUnavailable,
492 error.InvalidElfClass => return error.GnuLibCVersionUnavailable,
493 error.InvalidElfFile => return error.GnuLibCVersionUnavailable,
494 error.InvalidElfVersion => return error.GnuLibCVersionUnavailable,
495 error.InvalidGnuLibCVersion => return error.GnuLibCVersionUnavailable,
496 error.UnexpectedEndOfFile => return error.GnuLibCVersionUnavailable,
497 error.UnableToReadElfFile => return error.GnuLibCVersionUnavailable,
498
499 error.SystemResources => return error.SystemResources,
500 error.FileSystem => return error.FileSystem,
501 error.Unexpected => return error.Unexpected,
502 };
503}
504
505fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {
506 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
507 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
508 const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
509 const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
510 if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
511 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
512 elf.ELFDATA2LSB => .Little,
513 elf.ELFDATA2MSB => .Big,
514 else => return error.InvalidElfEndian,
515 };
516 const need_bswap = elf_endian != native_endian;
517 if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
518
519 const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) {
520 elf.ELFCLASS32 => false,
521 elf.ELFCLASS64 => true,
522 else => return error.InvalidElfClass,
523 };
524 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
525 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
526 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
527 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
528 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
529 if (sh_buf.len < shentsize) return error.InvalidElfFile;
530
531 _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
532 const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
533 const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
534 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
535 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
536 var strtab_buf: [4096:0]u8 = undefined;
537 const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
538 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
539 const shstrtab = strtab_buf[0..shstrtab_read_len];
540 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
541 var sh_i: u16 = 0;
542 const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
543 // Reserve some bytes so that we can deref the 64-bit struct fields
544 // even when the ELF file is 32-bits.
545 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
546 const sh_read_byte_len = try preadMin(
547 file,
548 sh_buf[0 .. sh_buf.len - sh_reserve],
549 shoff,
550 shentsize,
551 );
552 var sh_buf_i: usize = 0;
553 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
554 sh_i += 1;
555 shoff += shentsize;
556 sh_buf_i += shentsize;
557 }) {
558 const sh32 = @ptrCast(
559 *elf.Elf32_Shdr,
560 @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
561 );
562 const sh64 = @ptrCast(
563 *elf.Elf64_Shdr,
564 @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
565 );
566 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
567 // TODO this pointer cast should not be necessary
568 const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
569 if (mem.eql(u8, sh_name, ".dynstr")) {
570 break :find_dyn_str .{
571 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
572 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
573 };
574 }
575 }
576 } else return error.InvalidGnuLibCVersion;
577
578 // Here we loop over all the strings in the dynstr string table, assuming that any
579 // strings that start with "GLIBC_2." indicate the existence of such a glibc version,
580 // and furthermore, that the system-installed glibc is at minimum that version.
581
582 // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
583 // Here I use this value plus some headroom. This makes it only need
584 // a single read syscall here.
585 var buf: [40000]u8 = undefined;
586 if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion;
587
588 const dynstr_bytes = buf[0..dynstr.size];
589 _ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr.size);
590 var it = mem.split(u8, dynstr_bytes, &.{0});
591 var max_ver: std.builtin.Version = .{ .major = 2, .minor = 2, .patch = 5 };
592 while (it.next()) |s| {
593 if (mem.startsWith(u8, s, "GLIBC_2.")) {
594 const chopped = s["GLIBC_".len..];
595 const ver = std.builtin.Version.parse(chopped) catch |err| switch (err) {
596 error.Overflow => return error.InvalidGnuLibCVersion,
597 error.InvalidCharacter => return error.InvalidGnuLibCVersion,
598 error.InvalidVersion => return error.InvalidGnuLibCVersion,
599 };
600 switch (ver.order(max_ver)) {
601 .gt => max_ver = ver,
602 .lt, .eq => continue,
603 }
604 }
605 }
606 return max_ver;
475}607}
476608
477fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version {609fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version {
...@@ -735,36 +867,60 @@ pub fn abiAndDynamicLinkerFromFile(...@@ -735,36 +867,60 @@ pub fn abiAndDynamicLinkerFromFile(
735 };867 };
736 defer dir.close();868 defer dir.close();
737869
738 var link_buf: [std.os.PATH_MAX]u8 = undefined;870 // Now we have a candidate for the path to libc shared object. In
739 const link_name = std.os.readlinkatZ(871 // the past, we used readlink() here because the link name would
740 dir.fd,872 // reveal the glibc version. However, in more recent GNU/Linux
741 glibc_so_basename,873 // installations, there is no symlink. Thus we instead use a more
742 &link_buf,874 // robust check of opening the libc shared object and looking at the
743 ) catch |err| switch (err) {875 // .dynstr section, and finding the max version number of symbols
876 // that start with "GLIBC_2.".
877 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
744 error.NameTooLong => unreachable,878 error.NameTooLong => unreachable,
745 error.InvalidUtf8 => unreachable, // Windows only879 error.InvalidUtf8 => unreachable, // Windows only
746 error.BadPathName => unreachable, // Windows only880 error.BadPathName => unreachable, // Windows only
747 error.UnsupportedReparsePointType => unreachable, // Windows only881 error.PipeBusy => unreachable, // Windows-only
882 error.SharingViolation => unreachable, // Windows-only
883 error.FileLocksNotSupported => unreachable, // No lock requested.
884 error.NoSpaceLeft => unreachable, // read-only
885 error.PathAlreadyExists => unreachable, // read-only
886 error.DeviceBusy => unreachable, // read-only
887 error.FileBusy => unreachable, // read-only
888 error.InvalidHandle => unreachable, // should not be in the error set
889 error.WouldBlock => unreachable, // not using O_NONBLOCK
890 error.NoDevice => unreachable, // not asking for a special device
748891
749 error.AccessDenied,892 error.AccessDenied,
750 error.FileNotFound,893 error.FileNotFound,
751 error.NotLink,
752 error.NotDir,894 error.NotDir,
753 => continue,895 => continue,
754896
897 error.IsDir => return error.InvalidElfFile,
898 error.FileTooBig => return error.Unexpected,
899
900 error.ProcessFdQuotaExceeded,
901 error.SystemFdQuotaExceeded,
755 error.SystemResources,902 error.SystemResources,
756 error.FileSystem,
757 error.SymLinkLoop,903 error.SymLinkLoop,
758 error.Unexpected,904 error.Unexpected,
759 => |e| return e,905 => |e| return e,
760 };906 };
761 result.target.os.version_range.linux.glibc = glibcVerFromLinkName(907 defer f.close();
762 link_name,908
763 "libc-",909 result.target.os.version_range.linux.glibc = glibcVerFromSoFile(f) catch |err| switch (err) {
764 ) catch |err| switch (err) {910 error.InvalidElfMagic,
765 error.UnrecognizedGnuLibCFileName,911 error.InvalidElfEndian,
912 error.InvalidElfClass,
913 error.InvalidElfFile,
914 error.InvalidElfVersion,
766 error.InvalidGnuLibCVersion,915 error.InvalidGnuLibCVersion,
916 error.UnexpectedEndOfFile,
767 => continue,917 => continue,
918
919 error.SystemResources,
920 error.UnableToReadElfFile,
921 error.Unexpected,
922 error.FileSystem,
923 => |e| return e,
768 };924 };
769 break;925 break;
770 }926 }