Skip to content

Commit 773eb1c

Browse files
committed
Iterate PATH when no /usr/bin/env (ziglang#14146)
1 parent 5c25ad0 commit 773eb1c

File tree

1 file changed

+59
-59
lines changed

1 file changed

+59
-59
lines changed

lib/std/zig/system.zig

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,44 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
908908
return max_ver;
909909
}
910910

911+
/// Recursively chases the shebang line until it finds what is likely the desired ELF.
912+
fn followElfShebangs(origin_file_name: []const u8) ?fs.File {
913+
var file_name = origin_file_name;
914+
915+
// #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)
916+
var buffer: [258]u8 = undefined;
917+
while (true) {
918+
const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
919+
error.NoSpaceLeft => unreachable,
920+
error.NameTooLong => unreachable,
921+
error.PathAlreadyExists => unreachable,
922+
error.SharingViolation => unreachable,
923+
error.InvalidUtf8 => unreachable,
924+
error.BadPathName => unreachable,
925+
error.PipeBusy => unreachable,
926+
error.FileLocksNotSupported => unreachable,
927+
error.WouldBlock => unreachable,
928+
error.FileBusy => unreachable, // opened without write permissions
929+
error.AntivirusInterference => unreachable, // Windows-only error
930+
else => return null,
931+
};
932+
errdefer file.close();
933+
934+
const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) {
935+
error.UnexpectedEndOfFile,
936+
error.UnableToReadElfFile,
937+
=> return file,
938+
else => return null,
939+
};
940+
const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse return file;
941+
const line = buffer[0..newline];
942+
if (!mem.startsWith(u8, line, "#!")) return file;
943+
var it = mem.tokenizeScalar(u8, line[2..], ' ');
944+
file_name = it.next() orelse return null;
945+
file.close();
946+
}
947+
}
948+
911949
/// In the past, this function attempted to use the executable's own binary if it was dynamically
912950
/// linked to answer both the C ABI question and the dynamic linker question. However, this
913951
/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking
@@ -917,10 +955,9 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
917955
/// mismatching will fail to run.
918956
///
919957
/// Therefore, this function works the same regardless of whether the compiler binary is
920-
/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the
921-
/// answer to these questions, or if there is a shebang line, then it chases the referenced
922-
/// file recursively. If that does not provide the answer, then the function falls back to
923-
/// defaults.
958+
/// dynamically or statically linked. It first checks `/usr/bin/env` as an ELF file to find
959+
/// the answer to these questions, or iterates PATH for another `env` if it doesn't exist at
960+
/// the usual path.
924961
fn detectAbiAndDynamicLinker(
925962
cpu: Target.Cpu,
926963
os: Target.Os,
@@ -983,61 +1020,24 @@ fn detectAbiAndDynamicLinker(
9831020
// Best case scenario: the executable is dynamically linked, and we can iterate
9841021
// over our own shared objects and find a dynamic linker.
9851022
const elf_file = blk: {
986-
// This block looks for a shebang line in /usr/bin/env,
987-
// if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
988-
// doing the same logic recursively in case it finds another shebang line.
989-
990-
// Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
991-
// reasonably reliable path to start with.
992-
var file_name: []const u8 = "/usr/bin/env";
993-
// #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)
994-
var buffer: [258]u8 = undefined;
995-
while (true) {
996-
const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
997-
error.NoSpaceLeft => unreachable,
998-
error.NameTooLong => unreachable,
999-
error.PathAlreadyExists => unreachable,
1000-
error.SharingViolation => unreachable,
1001-
error.InvalidUtf8 => unreachable,
1002-
error.BadPathName => unreachable,
1003-
error.PipeBusy => unreachable,
1004-
error.FileLocksNotSupported => unreachable,
1005-
error.WouldBlock => unreachable,
1006-
error.FileBusy => unreachable, // opened without write permissions
1007-
error.AntivirusInterference => unreachable, // Windows-only error
1008-
1009-
error.IsDir,
1010-
error.NotDir,
1011-
error.InvalidHandle,
1012-
error.AccessDenied,
1013-
error.NoDevice,
1014-
error.FileNotFound,
1015-
error.NetworkNotFound,
1016-
error.FileTooBig,
1017-
error.Unexpected,
1018-
=> |e| {
1019-
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
1020-
return defaultAbiAndDynamicLinker(cpu, os, query);
1021-
},
1022-
1023-
else => |e| return e,
1024-
};
1025-
errdefer file.close();
1026-
1027-
const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) {
1028-
error.UnexpectedEndOfFile,
1029-
error.UnableToReadElfFile,
1030-
=> break :blk file,
1031-
1032-
else => |e| return e,
1033-
};
1034-
const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file;
1035-
const line = buffer[0..newline];
1036-
if (!mem.startsWith(u8, line, "#!")) break :blk file;
1037-
var it = mem.tokenizeScalar(u8, line[2..], ' ');
1038-
file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query);
1039-
file.close();
1023+
if (followElfShebangs("/usr/bin/env")) |file| {
1024+
break :blk file;
10401025
}
1026+
// If /usr/bin/env is missing, iterate PATH to find another "env" binary.
1027+
const PATH = std.os.getenv("PATH") orelse {
1028+
std.log.warn("Could not find /usr/bin/env and PATH environment variable is missing, falling back to default ABI and dynamic linker.\n", .{});
1029+
return defaultAbiAndDynamicLinker(cpu, os, query);
1030+
};
1031+
var path_it = mem.tokenizeScalar(u8, PATH, ':');
1032+
var path_buf: [std.os.PATH_MAX]u8 = undefined;
1033+
while (path_it.next()) |path| {
1034+
var path_alloc = std.heap.FixedBufferAllocator.init(&path_buf);
1035+
const path_env = std.fs.path.join(path_alloc.allocator(), &.{ path, "env" }) catch continue;
1036+
const env_file = followElfShebangs(path_env) orelse continue;
1037+
break :blk env_file;
1038+
}
1039+
std.log.warn("Could not find env in PATH, falling back to default ABI and dynamic linker.\n", .{});
1040+
return defaultAbiAndDynamicLinker(cpu, os, query);
10411041
};
10421042
defer elf_file.close();
10431043

@@ -1064,7 +1064,7 @@ fn detectAbiAndDynamicLinker(
10641064
error.NameTooLong,
10651065
// Finally, we fall back on the standard path.
10661066
=> |e| {
1067-
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
1067+
std.log.warn("Encountered error: {s} whilst detecting dynamic linker in env binary, falling back to default ABI and dynamic linker.\n", .{@errorName(e)});
10681068
return defaultAbiAndDynamicLinker(cpu, os, query);
10691069
},
10701070
};

0 commit comments

Comments
 (0)