authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-01-21 11:21:36-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-01-21 11:22:28-08:00
log0bacb79c090aabb367987ed0cff3982b84b8a4f7
treeacc7fdcb04d06b0b8d97f76adebda083e849022c
parent18fcb3b5e82d84e50b951373203f48325b2feada

Revert "Merge pull request #21540 from BratishkaErik/search-env-in-path"

It caused an assertion failure when building Zig from source. This reverts commit 0595feb34128db22fbebea843af929de3ede8190, reversing changes made to 744771d3303e122474a72c8a94b14fe1e9fb480c. closes #22566 closes #22568

2 files changed, 114 insertions(+), 141 deletions(-)

lib/std/fs/path.zig+6-2
......@@ -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
......@@ -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.
1006fn 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.
11141011fn detectAbiAndDynamicLinker(
11151012 cpu: Target.Cpu,
11161013 os: Target.Os,
......@@ -1178,33 +1075,14 @@ 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",
......@@ -1212,10 +1090,98 @@ fn detectAbiAndDynamicLinker(
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