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