| ... | @@ -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 | }; |
| 32 | | 33 | |
| 33 | /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected | 34 | /// 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( |
| 396 | | 395 | |
| 397 | else => |e| return e, | 396 | else => |e| return e, |
| 398 | }; | 397 | }; |
| | 398 | errdefer file.close(); |
| 399 | | 399 | |
| 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 before | 401 | 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, |
| 415 | | 415 | |
| 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( |
| 455 | | 452 | |
| 456 | const glibc_so_basename = "libc.so.6"; | 453 | const glibc_so_basename = "libc.so.6"; |
| 457 | | 454 | |
| 458 | fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version { | 455 | fn 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 | |
| | 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 | fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version { | 609 | fn 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(); |
| 737 | | 869 | |
| 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 only | 879 | error.InvalidUtf8 => unreachable, // Windows only |
| 746 | error.BadPathName => unreachable, // Windows only | 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 | error.AccessDenied, | 892 | error.AccessDenied, |
| 750 | error.FileNotFound, | 893 | error.FileNotFound, |
| 751 | error.NotLink, | | |
| 752 | error.NotDir, | 894 | error.NotDir, |
| 753 | => continue, | 895 | => continue, |
| 754 | | 896 | |
| | 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 | } |