Skip to content

Commit 3648727

Browse files
BratishkaErikLinuxUserGD
authored andcommitted
std.zig.system.NativeTargetInfo: improve examined file search
* Append Termux-specific path to `zig_examined_elf_files` to workaround different path for `env` binary. See also ziglang#14146 * Use same buffer for searching shebang line to search ELF magic constant, if any, and remove corresponding (now duplicate) check from `abiAndDynamicLinkerFromFile` * Fix not finding interpret path when it it separater from shebang by one or more blank spaces * Fix too big minimal limit of bytes from file (minimal shebang length instead of `buffer.len`) Signed-off-by: Eric Joldasov <[email protected]>
1 parent 64d03fa commit 3648727

File tree

1 file changed

+51
-58
lines changed

1 file changed

+51
-58
lines changed

lib/std/zig/system/NativeTargetInfo.zig

Lines changed: 51 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ pub fn detect(cross_target: CrossTarget) DetectError!NativeTargetInfo {
246246
/// mismatching will fail to run.
247247
///
248248
/// Therefore, this function works the same regardless of whether the compiler binary is
249-
/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the
249+
/// dynamically or statically linked. It inspects files from default list of paths (`/usr/bin/env` and equivalent on Termux) as an ELF file to find the
250250
/// answer to these questions, or if there is a shebang line, then it chases the referenced
251251
/// file recursively. If that does not provide the answer, then the function falls back to
252252
/// defaults.
@@ -307,68 +307,61 @@ fn detectAbiAndDynamicLinker(
307307
}
308308
const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
309309

310-
// Best case scenario: the executable is dynamically linked, and we can iterate
311-
// over our own shared objects and find a dynamic linker.
312-
const elf_file = blk: {
313-
// This block looks for a shebang line in /usr/bin/env,
314-
// if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
315-
// doing the same logic recursively in case it finds another shebang line.
310+
// Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
311+
// reasonably reliable path to start with.
312+
const zig_examined_elf_files = std.fmt.comptimePrint("/usr/bin/env{[sep]}/data/data/com.termux/files/usr/bin/env", .{ .sep = fs.path.delimiter });
313+
var examined_elf_file_names_iterator = mem.tokenizeScalar(u8, zig_examined_elf_files, fs.path.delimiter);
316314

317-
// Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
318-
// reasonably reliable path to start with.
319-
var file_name: []const u8 = "/usr/bin/env";
320-
// #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)
321-
var buffer: [258]u8 = undefined;
322-
while (true) {
323-
const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
324-
error.NoSpaceLeft => unreachable,
325-
error.NameTooLong => unreachable,
326-
error.PathAlreadyExists => unreachable,
327-
error.SharingViolation => unreachable,
328-
error.InvalidUtf8 => unreachable,
329-
error.BadPathName => unreachable,
330-
error.PipeBusy => unreachable,
331-
error.FileLocksNotSupported => unreachable,
332-
error.WouldBlock => unreachable,
333-
error.FileBusy => unreachable, // opened without write permissions
334-
335-
error.IsDir,
336-
error.NotDir,
337-
error.InvalidHandle,
338-
error.AccessDenied,
339-
error.NoDevice,
340-
error.FileNotFound,
341-
error.NetworkNotFound,
342-
error.FileTooBig,
343-
error.Unexpected,
344-
=> |e| {
345-
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
346-
return defaultAbiAndDynamicLinker(cpu, os, cross_target);
347-
},
315+
// #! (2) + 255 (max length of remaining "shebang line" since Linux 5.1, see `man execve(2)`)
316+
var buffer: [257]u8 = undefined;
317+
const cwd = fs.cwd();
348318

349-
else => |e| return e,
350-
};
351-
errdefer file.close();
319+
const elf_file: fs.File = iterate: while (examined_elf_file_names_iterator.next()) |starting_point| {
320+
var examined_file_path = starting_point;
321+
// This block looks for a shebang line in `examined_file_path`,
322+
// if it finds one, then instead of using `examined_file_path` as the ELF file to examine, it uses the file it references instead,
323+
// doing the same logic recursively in case it finds another shebang line.
324+
const chased_elf_file: fs.File = chase: while (true) {
325+
const examined_file = cwd.openFile(examined_file_path, .{ .mode = .read_only }) catch |err| switch (err) {
326+
error.InvalidUtf8, error.BadPathName, error.NetworkNotFound => unreachable, // Windows-only
327+
error.PathAlreadyExists, error.NoSpaceLeft => unreachable, // We do not create a file
328+
error.FileBusy => unreachable, // We do not request write access or use O_TRUNC flag
352329

353-
const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) {
354-
error.UnexpectedEndOfFile,
355-
error.UnableToReadElfFile,
356-
=> break :blk file,
330+
else => continue :iterate,
331+
};
332+
var is_elf_file = false;
333+
defer if (!is_elf_file) examined_file.close();
357334

358-
else => |e| return e,
335+
const len = examined_file.preadAll(buffer[0..], 0) catch |err| switch (err) {
336+
error.NetNameDeleted => unreachable, // Windows-only
337+
else => continue :iterate,
359338
};
360-
const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file;
361-
const line = buffer[0..newline];
362-
if (!mem.startsWith(u8, line, "#!")) break :blk file;
363-
var it = mem.tokenizeScalar(u8, line[2..], ' ');
364-
file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, cross_target);
365-
file.close();
366-
}
367-
};
339+
// Shortest working "shebang line" is "#!e" (3)
340+
// (assuming relative path to cwd in shebang is allowed on this system)
341+
// std.elf.MAGIC.len is 4
342+
// If it is smaller than 3, it is definitely not ELF file and
343+
// not shell script with shebang line.
344+
if (len < 3) continue :iterate; // Too short for a ELF file or shell script with "shebang line"
345+
if (mem.startsWith(u8, buffer[0..], elf.MAGIC)) // It is likely ELF file!
346+
{
347+
is_elf_file = true;
348+
break :chase examined_file;
349+
}
350+
if (!mem.startsWith(u8, buffer[0..], "#!")) continue :iterate; // Not a ELF file, not a shell script with "shebang line", invalid duck.
351+
352+
// We detected shebang, now parse entire line.
353+
const first_line_end = mem.indexOfScalar(u8, buffer[0..], '\n') orelse buffer.len;
354+
const first_line = buffer[0..first_line_end];
355+
356+
const interpreter_and_arguments = mem.trim(u8, first_line[2..], std.ascii.whitespace[0..]); // Skip "#!" and trim whitespace at both ends of line (it is possible to have blank space right after "#!")
357+
const interpreter_end = mem.indexOfScalar(u8, interpreter_and_arguments, ' ') orelse interpreter_and_arguments.len; // blank space possibly used for separation of interpreter and arguments
358+
const interpreter_path = interpreter_and_arguments[0..interpreter_end]; // another file which **might** be ELF file or shell script with "shebang line"
359+
examined_file_path = interpreter_path; // move to examining this interpreter instead of starting point
360+
} else continue :iterate; // This path was not suitable as a starting point, move to the next path
361+
break :iterate chased_elf_file; // TODO should it be only first suitable ELF file or a list of suitable files?
362+
} else return defaultAbiAndDynamicLinker(cpu, os, cross_target);
368363
defer elf_file.close();
369364

370-
// If Zig is statically linked, such as via distributed binary static builds, the above
371-
// trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file.
372365
// TODO: inline this function and combine the buffer we already read above to find
373366
// the possible shebang line with the buffer we use for the ELF header.
374367
return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) {
@@ -620,7 +613,7 @@ pub fn abiAndDynamicLinkerFromFile(
620613
_ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
621614
const hdr32 = @as(*elf.Elf32_Ehdr, @ptrCast(&hdr_buf));
622615
const hdr64 = @as(*elf.Elf64_Ehdr, @ptrCast(&hdr_buf));
623-
if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
616+
624617
const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) {
625618
elf.ELFDATA2LSB => .Little,
626619
elf.ELFDATA2MSB => .Big,
@@ -915,7 +908,7 @@ fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
915908
return i;
916909
}
917910

918-
fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo {
911+
fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) NativeTargetInfo {
919912
const target: Target = .{
920913
.cpu = cpu,
921914
.os = os,

0 commit comments

Comments
 (0)