diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 8bd7f815fe..83c798c342 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -983,7 +983,7 @@ fn detectAbiAndDynamicLinker( // Best case scenario: the executable is dynamically linked, and we can iterate // over our own shared objects and find a dynamic linker. - const elf_file = blk: { + const elf_file = elf_file: { // 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. @@ -995,9 +995,22 @@ fn detectAbiAndDynamicLinker( // Haiku does not have a /usr root directory. .haiku => "/bin/env", }; - // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) - var buffer: [258]u8 = undefined; + + // According to `man 2 execve`: + // + // The kernel imposes a maximum length on the text + // that follows the "#!" characters at the start of a script; + // characters beyond the limit are ignored. + // Before Linux 5.1, the limit is 127 characters. + // Since Linux 5.1, the limit is 255 characters. + // + // Tests show that bash and zsh consider 255 as total limit, + // *including* "#!" characters and ignoring newline. + // For safety, we set max length as 255 + \n (1). + var buffer: [255 + 1]u8 = undefined; while (true) { + // Interpreter path can be relative on Linux, but + // for simplicity we are asserting it is an absolute path. const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, error.NameTooLong => unreachable, @@ -1027,27 +1040,55 @@ fn detectAbiAndDynamicLinker( else => |e| return e, }; - errdefer file.close(); + var is_elf_file = false; + defer if (is_elf_file == false) file.close(); - const len = preadAtLeast(file, &buffer, 0, buffer.len) catch |err| switch (err) { + // Shortest working interpreter path is "#!/i" (4) + // (interpreter is "/i", assuming all pathes are absolute, like in above comment). + // ELF magic number length is also 4. + // + // If file is shorter than that, it is definitely not ELF file + // nor file with "shebang" line. + const min_len: usize = 4; + + const len = preadAtLeast(file, &buffer, 0, min_len) catch |err| switch (err) { error.UnexpectedEndOfFile, error.UnableToReadElfFile, - => break :blk file, + => return defaultAbiAndDynamicLinker(cpu, os, query), else => |e| return e, }; - const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file; - const line = buffer[0..newline]; - if (!mem.startsWith(u8, line, "#!")) break :blk file; - var it = mem.tokenizeScalar(u8, line[2..], ' '); - file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query); - file.close(); + const content = buffer[0..len]; + + if (mem.eql(u8, content[0..4], std.elf.MAGIC)) { + // It is very likely ELF file! + is_elf_file = true; + break :elf_file file; + } else if (mem.eql(u8, content[0..2], "#!")) { + // We detected shebang, now parse entire line. + + // Trim leading "#!", spaces and tabs. + const trimmed_line = mem.trimLeft(u8, content[2..], &.{ ' ', '\t' }); + + // This line can have: + // * Interpreter path only, + // * Interpreter path and arguments, all separated by space, tab or NUL character. + // And optionally newline at the end. + const path_maybe_args = mem.trimRight(u8, trimmed_line, "\n"); + + // Separate path and args. + const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len; + + file_name = path_maybe_args[0..path_end]; + continue; + } else { + // Not a ELF file, not a shell script with "shebang line", invalid duck. + return defaultAbiAndDynamicLinker(cpu, os, query); + } } }; defer elf_file.close(); - // If Zig is statically linked, such as via distributed binary static builds, the above - // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file. // TODO: inline this function and combine the buffer we already read above to find // the possible shebang line with the buffer we use for the ELF header. return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) { @@ -1075,7 +1116,7 @@ fn detectAbiAndDynamicLinker( }; } -fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target { +fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) Target { const abi = query.abi orelse Target.Abi.default(cpu.arch, os); return .{ .cpu = cpu,