Skip to content

Commit 0bacb79

Browse files
committed
Revert "Merge pull request #21540 from BratishkaErik/search-env-in-path"
It caused an assertion failure when building Zig from source. This reverts commit 0595feb, reversing changes made to 744771d. closes #22566 closes #22568
1 parent 18fcb3b commit 0bacb79

File tree

2 files changed

+114
-141
lines changed

2 files changed

+114
-141
lines changed

lib/std/fs/path.zig

+6-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ fn joinSepMaybeZ(allocator: Allocator, separator: u8, comptime sepPredicate: fn
9090
var sum: usize = paths[first_path_index].len;
9191
var prev_path = paths[first_path_index];
9292
assert(prev_path.len > 0);
93-
for (paths[first_path_index + 1 ..]) |this_path| {
93+
var i: usize = first_path_index + 1;
94+
while (i < paths.len) : (i += 1) {
95+
const this_path = paths[i];
9496
if (this_path.len == 0) continue;
9597
const prev_sep = sepPredicate(prev_path[prev_path.len - 1]);
9698
const this_sep = sepPredicate(this_path[0]);
@@ -110,7 +112,9 @@ fn joinSepMaybeZ(allocator: Allocator, separator: u8, comptime sepPredicate: fn
110112
var buf_index: usize = paths[first_path_index].len;
111113
var prev_path = paths[first_path_index];
112114
assert(prev_path.len > 0);
113-
for (paths[first_path_index + 1 ..]) |this_path| {
115+
var i: usize = first_path_index + 1;
116+
while (i < paths.len) : (i += 1) {
117+
const this_path = paths[i];
114118
if (this_path.len == 0) continue;
115119
const prev_sep = sepPredicate(prev_path[prev_path.len - 1]);
116120
const this_sep = sepPredicate(this_path[0]);

lib/std/zig/system.zig

+108-139
Original file line numberDiff line numberDiff line change
@@ -995,106 +995,6 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
995995
return max_ver;
996996
}
997997

998-
/// This functions tries to open file located at `start_path`, and then guesses
999-
/// whether it is a script or an ELF file.
1000-
///
1001-
/// If it finds "shebang line", file is considered a script, and logic is re-run
1002-
/// using interpreter referenced after "#!" symbols. If interpreter is itself also a script,
1003-
/// logic becomes recursive until non-script file is found.
1004-
///
1005-
/// If it finds ELF magic sequence, file is considered an ELF file and function returns.
1006-
fn resolveElfFileRecursively(cwd: fs.Dir, start_path: []const u8) error{UnableToFindElfFile}!fs.File {
1007-
var current_path = start_path;
1008-
1009-
// According to `man 2 execve`:
1010-
//
1011-
// The kernel imposes a maximum length on the text
1012-
// that follows the "#!" characters at the start of a script;
1013-
// characters beyond the limit are ignored.
1014-
// Before Linux 5.1, the limit is 127 characters.
1015-
// Since Linux 5.1, the limit is 255 characters.
1016-
//
1017-
// Tests show that bash and zsh consider 255 as total limit,
1018-
// *including* "#!" characters and ignoring newline.
1019-
// For safety, we set max length as 255 + \n (1).
1020-
var buffer: [255 + 1]u8 = undefined;
1021-
while (true) {
1022-
// Interpreter path can be relative on Linux, but
1023-
// for simplicity we are asserting it is an absolute path.
1024-
assert(std.fs.path.isAbsolute(current_path));
1025-
const file = cwd.openFile(current_path, .{}) catch |err| switch (err) {
1026-
error.NoSpaceLeft => unreachable,
1027-
error.NameTooLong => unreachable,
1028-
error.PathAlreadyExists => unreachable,
1029-
error.SharingViolation => unreachable,
1030-
error.InvalidUtf8 => unreachable, // WASI only
1031-
error.InvalidWtf8 => unreachable, // Windows only
1032-
error.BadPathName => unreachable,
1033-
error.PipeBusy => unreachable,
1034-
error.FileLocksNotSupported => unreachable,
1035-
error.WouldBlock => unreachable,
1036-
error.FileBusy => unreachable, // opened without write permissions
1037-
error.AntivirusInterference => unreachable, // Windows-only error
1038-
1039-
error.IsDir,
1040-
error.NotDir,
1041-
1042-
error.AccessDenied,
1043-
error.DeviceBusy,
1044-
error.FileTooBig,
1045-
error.SymLinkLoop,
1046-
error.ProcessFdQuotaExceeded,
1047-
error.SystemFdQuotaExceeded,
1048-
error.SystemResources,
1049-
1050-
error.FileNotFound,
1051-
error.NetworkNotFound,
1052-
error.NoDevice,
1053-
error.Unexpected,
1054-
=> return error.UnableToFindElfFile,
1055-
};
1056-
var is_elf_file = false;
1057-
defer if (is_elf_file == false) file.close();
1058-
1059-
// Shortest working interpreter path is "#!/i" (4)
1060-
// (interpreter is "/i", assuming all paths are absolute, like in above comment).
1061-
// ELF magic number length is also 4.
1062-
//
1063-
// If file is shorter than that, it is definitely not ELF file
1064-
// nor file with "shebang" line.
1065-
const min_len = 4;
1066-
1067-
const len = preadAtLeast(file, &buffer, 0, min_len) catch return error.UnableToFindElfFile;
1068-
const content = buffer[0..len];
1069-
1070-
if (mem.eql(u8, content[0..4], std.elf.MAGIC)) {
1071-
// It is very likely ELF file!
1072-
is_elf_file = true;
1073-
return file;
1074-
} else if (mem.eql(u8, content[0..2], "#!")) {
1075-
// We detected shebang, now parse entire line.
1076-
1077-
// Trim leading "#!", spaces and tabs.
1078-
const trimmed_line = mem.trimLeft(u8, content[2..], &.{ ' ', '\t' });
1079-
1080-
// This line can have:
1081-
// * Interpreter path only,
1082-
// * Interpreter path and arguments, all separated by space, tab or NUL character.
1083-
// And optionally newline at the end.
1084-
const path_maybe_args = mem.trimRight(u8, trimmed_line, "\n");
1085-
1086-
// Separate path and args.
1087-
const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len;
1088-
1089-
current_path = path_maybe_args[0..path_end];
1090-
continue;
1091-
} else {
1092-
// Not a ELF file, not a shell script with "shebang line", invalid duck.
1093-
return error.UnableToFindElfFile;
1094-
}
1095-
}
1096-
}
1097-
1098998
/// In the past, this function attempted to use the executable's own binary if it was dynamically
1099999
/// linked to answer both the C ABI question and the dynamic linker question. However, this
11001000
/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking
@@ -1103,14 +1003,11 @@ fn resolveElfFileRecursively(cwd: fs.Dir, start_path: []const u8) error{UnableTo
11031003
/// the dynamic linker will match that of the compiler binary. Executables with these versions
11041004
/// mismatching will fail to run.
11051005
///
1106-
/// Therefore, this function now does not inspect the executable's own binary.
1107-
/// Instead, it tries to find `env` program in PATH or in hardcoded location, and uses it
1108-
/// to find suitable ELF file. If `env` program is an executable, work is done and function starts to
1109-
/// inspect inner structure of a file. But if `env` is a script or other non-ELF file, it uses
1110-
/// interpreter path instead and tries to search ELF file again, going recursively in case interpreter
1111-
/// is also a script/non-ELF file.
1112-
///
1113-
/// If nothing was found, then the function falls back to defaults.
1006+
/// Therefore, this function works the same regardless of whether the compiler binary is
1007+
/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the
1008+
/// answer to these questions, or if there is a shebang line, then it chases the referenced
1009+
/// file recursively. If that does not provide the answer, then the function falls back to
1010+
/// defaults.
11141011
fn detectAbiAndDynamicLinker(
11151012
cpu: Target.Cpu,
11161013
os: Target.Os,
@@ -1178,44 +1075,113 @@ fn detectAbiAndDynamicLinker(
11781075

11791076
const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
11801077

1181-
const cwd = std.fs.cwd();
1182-
1183-
// Algorithm is:
1184-
// 1a) try_path: If PATH is non-empty and `env` file was found in one of the directories, use that.
1185-
// 1b) try_path: If `env` was not found or PATH is empty, try hardcoded path below.
1186-
// 2a) try_hardcoded: If `env` was found in hardcoded location, use that.
1187-
// 2b) try_hardcoded: If `env` was not found, fall back to default ABI and dynamic linker.
1188-
// Source: https://github.com/ziglang/zig/issues/14146#issuecomment-2308984936
1189-
const elf_file = (try_path: {
1190-
const PATH = std.posix.getenv("PATH") orelse break :try_path null;
1191-
var it = mem.tokenizeScalar(u8, PATH, fs.path.delimiter);
1192-
1193-
var buf: [std.fs.max_path_bytes + 1]u8 = undefined;
1194-
var fbs: std.heap.FixedBufferAllocator = .init(&buf);
1195-
const allocator = fbs.allocator();
1196-
1197-
while (it.next()) |path| : (fbs.reset()) {
1198-
const start_path = std.fs.path.joinZ(allocator, &.{ path, "env" }) catch |err| switch (err) {
1199-
error.OutOfMemory => continue,
1200-
};
1078+
// Best case scenario: the executable is dynamically linked, and we can iterate
1079+
// over our own shared objects and find a dynamic linker.
1080+
const elf_file = elf_file: {
1081+
// This block looks for a shebang line in /usr/bin/env,
1082+
// if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
1083+
// doing the same logic recursively in case it finds another shebang line.
12011084

1202-
break :try_path resolveElfFileRecursively(cwd, start_path) catch |err| switch (err) {
1203-
error.UnableToFindElfFile => continue,
1204-
};
1205-
} else break :try_path null;
1206-
} orelse try_hardcoded: {
1207-
const hardcoded_file_name = switch (os.tag) {
1085+
var file_name: []const u8 = switch (os.tag) {
12081086
// Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
12091087
// reasonably reliable path to start with.
12101088
else => "/usr/bin/env",
12111089
// Haiku does not have a /usr root directory.
12121090
.haiku => "/bin/env",
12131091
};
12141092

1215-
break :try_hardcoded resolveElfFileRecursively(cwd, hardcoded_file_name) catch |err| switch (err) {
1216-
error.UnableToFindElfFile => null,
1217-
};
1218-
}) orelse return defaultAbiAndDynamicLinker(cpu, os, query);
1093+
// According to `man 2 execve`:
1094+
//
1095+
// The kernel imposes a maximum length on the text
1096+
// that follows the "#!" characters at the start of a script;
1097+
// characters beyond the limit are ignored.
1098+
// Before Linux 5.1, the limit is 127 characters.
1099+
// Since Linux 5.1, the limit is 255 characters.
1100+
//
1101+
// Tests show that bash and zsh consider 255 as total limit,
1102+
// *including* "#!" characters and ignoring newline.
1103+
// For safety, we set max length as 255 + \n (1).
1104+
var buffer: [255 + 1]u8 = undefined;
1105+
while (true) {
1106+
// Interpreter path can be relative on Linux, but
1107+
// for simplicity we are asserting it is an absolute path.
1108+
const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
1109+
error.NoSpaceLeft => unreachable,
1110+
error.NameTooLong => unreachable,
1111+
error.PathAlreadyExists => unreachable,
1112+
error.SharingViolation => unreachable,
1113+
error.InvalidUtf8 => unreachable, // WASI only
1114+
error.InvalidWtf8 => unreachable, // Windows only
1115+
error.BadPathName => unreachable,
1116+
error.PipeBusy => unreachable,
1117+
error.FileLocksNotSupported => unreachable,
1118+
error.WouldBlock => unreachable,
1119+
error.FileBusy => unreachable, // opened without write permissions
1120+
error.AntivirusInterference => unreachable, // Windows-only error
1121+
1122+
error.IsDir,
1123+
error.NotDir,
1124+
error.AccessDenied,
1125+
error.NoDevice,
1126+
error.FileNotFound,
1127+
error.NetworkNotFound,
1128+
error.FileTooBig,
1129+
error.Unexpected,
1130+
=> |e| {
1131+
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)});
1132+
return defaultAbiAndDynamicLinker(cpu, os, query);
1133+
},
1134+
1135+
else => |e| return e,
1136+
};
1137+
var is_elf_file = false;
1138+
defer if (is_elf_file == false) file.close();
1139+
1140+
// Shortest working interpreter path is "#!/i" (4)
1141+
// (interpreter is "/i", assuming all paths are absolute, like in above comment).
1142+
// ELF magic number length is also 4.
1143+
//
1144+
// If file is shorter than that, it is definitely not ELF file
1145+
// nor file with "shebang" line.
1146+
const min_len: usize = 4;
1147+
1148+
const len = preadAtLeast(file, &buffer, 0, min_len) catch |err| switch (err) {
1149+
error.UnexpectedEndOfFile,
1150+
error.UnableToReadElfFile,
1151+
error.ProcessNotFound,
1152+
=> return defaultAbiAndDynamicLinker(cpu, os, query),
1153+
1154+
else => |e| return e,
1155+
};
1156+
const content = buffer[0..len];
1157+
1158+
if (mem.eql(u8, content[0..4], std.elf.MAGIC)) {
1159+
// It is very likely ELF file!
1160+
is_elf_file = true;
1161+
break :elf_file file;
1162+
} else if (mem.eql(u8, content[0..2], "#!")) {
1163+
// We detected shebang, now parse entire line.
1164+
1165+
// Trim leading "#!", spaces and tabs.
1166+
const trimmed_line = mem.trimLeft(u8, content[2..], &.{ ' ', '\t' });
1167+
1168+
// This line can have:
1169+
// * Interpreter path only,
1170+
// * Interpreter path and arguments, all separated by space, tab or NUL character.
1171+
// And optionally newline at the end.
1172+
const path_maybe_args = mem.trimRight(u8, trimmed_line, "\n");
1173+
1174+
// Separate path and args.
1175+
const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len;
1176+
1177+
file_name = path_maybe_args[0..path_end];
1178+
continue;
1179+
} else {
1180+
// Not a ELF file, not a shell script with "shebang line", invalid duck.
1181+
return defaultAbiAndDynamicLinker(cpu, os, query);
1182+
}
1183+
}
1184+
};
12191185
defer elf_file.close();
12201186

12211187
// TODO: inline this function and combine the buffer we already read above to find
@@ -1239,7 +1205,10 @@ fn detectAbiAndDynamicLinker(
12391205
error.UnexpectedEndOfFile,
12401206
error.NameTooLong,
12411207
// Finally, we fall back on the standard path.
1242-
=> defaultAbiAndDynamicLinker(cpu, os, query),
1208+
=> |e| {
1209+
std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)});
1210+
return defaultAbiAndDynamicLinker(cpu, os, query);
1211+
},
12431212
};
12441213
}
12451214

0 commit comments

Comments
 (0)