| ... | ... | @@ -28,6 +28,7 @@ pub const DetectError = error{ |
| 28 | 28 | SystemFdQuotaExceeded, |
| 29 | 29 | DeviceBusy, |
| 30 | 30 | OSVersionDetectionFail, |
| 31 | Unexpected, |
| 31 | 32 | }; |
| 32 | 33 | |
| 33 | 34 | /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected |
| ... | ... | @@ -332,9 +333,7 @@ fn detectAbiAndDynamicLinker( |
| 332 | 333 | { |
| 333 | 334 | for (lib_paths) |lib_path| { |
| 334 | 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 | | error.UnrecognizedGnuLibCFileName => continue, |
| 337 | | error.InvalidGnuLibCVersion => continue, |
| 336 | os_adjusted.version_range.linux.glibc = glibcVerFromSo(lib_path) catch |err| switch (err) { |
| 338 | 337 | error.GnuLibCVersionUnavailable => continue, |
| 339 | 338 | else => |e| return e, |
| 340 | 339 | }; |
| ... | ... | @@ -369,7 +368,7 @@ fn detectAbiAndDynamicLinker( |
| 369 | 368 | // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) |
| 370 | 369 | var buffer: [258]u8 = undefined; |
| 371 | 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 | 372 | error.NoSpaceLeft => unreachable, |
| 374 | 373 | error.NameTooLong => unreachable, |
| 375 | 374 | error.PathAlreadyExists => unreachable, |
| ... | ... | @@ -396,6 +395,7 @@ fn detectAbiAndDynamicLinker( |
| 396 | 395 | |
| 397 | 396 | else => |e| return e, |
| 398 | 397 | }; |
| 398 | errdefer file.close(); |
| 399 | 399 | |
| 400 | 400 | const line = file.reader().readUntilDelimiter(&buffer, '\n') catch |err| switch (err) { |
| 401 | 401 | error.IsDir => unreachable, // Handled before |
| ... | ... | @@ -413,15 +413,12 @@ fn detectAbiAndDynamicLinker( |
| 413 | 413 | error.NotOpenForReading, |
| 414 | 414 | => break :blk file, |
| 415 | 415 | |
| 416 | | else => |e| { |
| 417 | | file.close(); |
| 418 | | return e; |
| 419 | | }, |
| 416 | else => |e| return e, |
| 420 | 417 | }; |
| 421 | 418 | if (!mem.startsWith(u8, line, "#!")) break :blk file; |
| 422 | 419 | var it = std.mem.tokenize(u8, line[2..], " "); |
| 423 | | file.close(); |
| 424 | 420 | file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, cross_target); |
| 421 | file.close(); |
| 425 | 422 | } |
| 426 | 423 | }; |
| 427 | 424 | defer elf_file.close(); |
| ... | ... | @@ -455,23 +452,158 @@ fn detectAbiAndDynamicLinker( |
| 455 | 452 | |
| 456 | 453 | const glibc_so_basename = "libc.so.6"; |
| 457 | 454 | |
| 458 | | fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version { |
| 459 | | var link_buf: [std.os.PATH_MAX]u8 = undefined; |
| 460 | | const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) { |
| 461 | | error.AccessDenied => return error.GnuLibCVersionUnavailable, |
| 462 | | error.FileSystem => return error.FileSystem, |
| 463 | | error.SymLinkLoop => return error.SymLinkLoop, |
| 455 | fn glibcVerFromSo(so_path: [:0]const u8) !std.builtin.Version { |
| 456 | const file = fs.openFileAbsolute(so_path, .{}) catch |err| switch (err) { |
| 457 | // Contextually impossible errors. |
| 458 | error.NoSpaceLeft => unreachable, |
| 464 | 459 | error.NameTooLong => unreachable, |
| 465 | | error.NotLink => return error.GnuLibCVersionUnavailable, |
| 466 | | error.FileNotFound => return error.GnuLibCVersionUnavailable, |
| 460 | error.PathAlreadyExists => unreachable, |
| 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 | 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 | 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 | |
| 505 | fn 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 | } |
| 476 | 608 | |
| 477 | 609 | fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version { |
| ... | ... | @@ -735,36 +867,60 @@ pub fn abiAndDynamicLinkerFromFile( |
| 735 | 867 | }; |
| 736 | 868 | defer dir.close(); |
| 737 | 869 | |
| 738 | | var link_buf: [std.os.PATH_MAX]u8 = undefined; |
| 739 | | const link_name = std.os.readlinkatZ( |
| 740 | | dir.fd, |
| 741 | | glibc_so_basename, |
| 742 | | &link_buf, |
| 743 | | ) catch |err| switch (err) { |
| 870 | // Now we have a candidate for the path to libc shared object. In |
| 871 | // the past, we used readlink() here because the link name would |
| 872 | // reveal the glibc version. However, in more recent GNU/Linux |
| 873 | // installations, there is no symlink. Thus we instead use a more |
| 874 | // robust check of opening the libc shared object and looking at the |
| 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 | 878 | error.NameTooLong => unreachable, |
| 745 | 879 | error.InvalidUtf8 => unreachable, // Windows only |
| 746 | 880 | error.BadPathName => unreachable, // Windows only |
| 747 | | error.UnsupportedReparsePointType => unreachable, // Windows only |
| 881 | 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 |
| 748 | 891 | |
| 749 | 892 | error.AccessDenied, |
| 750 | 893 | error.FileNotFound, |
| 751 | | error.NotLink, |
| 752 | 894 | error.NotDir, |
| 753 | 895 | => continue, |
| 754 | 896 | |
| 897 | error.IsDir => return error.InvalidElfFile, |
| 898 | error.FileTooBig => return error.Unexpected, |
| 899 | |
| 900 | error.ProcessFdQuotaExceeded, |
| 901 | error.SystemFdQuotaExceeded, |
| 755 | 902 | error.SystemResources, |
| 756 | | error.FileSystem, |
| 757 | 903 | error.SymLinkLoop, |
| 758 | 904 | error.Unexpected, |
| 759 | 905 | => |e| return e, |
| 760 | 906 | }; |
| 761 | | result.target.os.version_range.linux.glibc = glibcVerFromLinkName( |
| 762 | | link_name, |
| 763 | | "libc-", |
| 764 | | ) catch |err| switch (err) { |
| 765 | | error.UnrecognizedGnuLibCFileName, |
| 907 | defer f.close(); |
| 908 | |
| 909 | result.target.os.version_range.linux.glibc = glibcVerFromSoFile(f) catch |err| switch (err) { |
| 910 | error.InvalidElfMagic, |
| 911 | error.InvalidElfEndian, |
| 912 | error.InvalidElfClass, |
| 913 | error.InvalidElfFile, |
| 914 | error.InvalidElfVersion, |
| 766 | 915 | error.InvalidGnuLibCVersion, |
| 916 | error.UnexpectedEndOfFile, |
| 767 | 917 | => continue, |
| 918 | |
| 919 | error.SystemResources, |
| 920 | error.UnableToReadElfFile, |
| 921 | error.Unexpected, |
| 922 | error.FileSystem, |
| 923 | => |e| return e, |
| 768 | 924 | }; |
| 769 | 925 | break; |
| 770 | 926 | } |