@@ -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.
11141011fn 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