| ... | ... | @@ -995,106 +995,6 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion { |
| 995 | 995 | return max_ver; |
| 996 | 996 | } |
| 997 | 997 | |
| 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 | | |
| 1098 | 998 | /// In the past, this function attempted to use the executable's own binary if it was dynamically |
| 1099 | 999 | /// linked to answer both the C ABI question and the dynamic linker question. However, this |
| 1100 | 1000 | /// 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 |
| 1103 | 1003 | /// the dynamic linker will match that of the compiler binary. Executables with these versions |
| 1104 | 1004 | /// mismatching will fail to run. |
| 1105 | 1005 | /// |
| 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. |
| 1114 | 1011 | fn detectAbiAndDynamicLinker( |
| 1115 | 1012 | cpu: Target.Cpu, |
| 1116 | 1013 | os: Target.Os, |
| ... | ... | @@ -1178,33 +1075,14 @@ fn detectAbiAndDynamicLinker( |
| 1178 | 1075 | |
| 1179 | 1076 | const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; |
| 1180 | 1077 | |
| 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. |
| 1201 | 1084 | |
| 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) { |
| 1208 | 1086 | // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a |
| 1209 | 1087 | // reasonably reliable path to start with. |
| 1210 | 1088 | else => "/usr/bin/env", |
| ... | ... | @@ -1212,10 +1090,98 @@ fn detectAbiAndDynamicLinker( |
| 1212 | 1090 | .haiku => "/bin/env", |
| 1213 | 1091 | }; |
| 1214 | 1092 | |
| 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 | }; |
| 1219 | 1185 | defer elf_file.close(); |
| 1220 | 1186 | |
| 1221 | 1187 | // TODO: inline this function and combine the buffer we already read above to find |
| ... | ... | @@ -1239,7 +1205,10 @@ fn detectAbiAndDynamicLinker( |
| 1239 | 1205 | error.UnexpectedEndOfFile, |
| 1240 | 1206 | error.NameTooLong, |
| 1241 | 1207 | // 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 | }, |
| 1243 | 1212 | }; |
| 1244 | 1213 | } |
| 1245 | 1214 | |