@@ -949,6 +949,106 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
949
949
return max_ver ;
950
950
}
951
951
952
+ /// This functions tries to open file located at `start_path`, and then guesses
953
+ /// whether it is a script or an ELF file.
954
+ ///
955
+ /// If it finds "shebang line", file is considered a script, and logic is re-run
956
+ /// using interpreter referenced after "#!" symbols. If interpreter is itself also a script,
957
+ /// logic becomes recursive until non-script file is found.
958
+ ///
959
+ /// If it finds ELF magic sequence, file is considered an ELF file and function returns.
960
+ fn resolveElfFileRecursively (cwd : fs.Dir , start_path : []const u8 ) error {UnableToFindElfFile }! fs.File {
961
+ var current_path = start_path ;
962
+
963
+ // According to `man 2 execve`:
964
+ //
965
+ // The kernel imposes a maximum length on the text
966
+ // that follows the "#!" characters at the start of a script;
967
+ // characters beyond the limit are ignored.
968
+ // Before Linux 5.1, the limit is 127 characters.
969
+ // Since Linux 5.1, the limit is 255 characters.
970
+ //
971
+ // Tests show that bash and zsh consider 255 as total limit,
972
+ // *including* "#!" characters and ignoring newline.
973
+ // For safety, we set max length as 255 + \n (1).
974
+ var buffer : [255 + 1 ]u8 = undefined ;
975
+ while (true ) {
976
+ // Interpreter path can be relative on Linux, but
977
+ // for simplicity we are asserting it is an absolute path.
978
+ assert (std .fs .path .isAbsolute (current_path ));
979
+ const file = cwd .openFile (current_path , .{}) catch | err | switch (err ) {
980
+ error .NoSpaceLeft = > unreachable ,
981
+ error .NameTooLong = > unreachable ,
982
+ error .PathAlreadyExists = > unreachable ,
983
+ error .SharingViolation = > unreachable ,
984
+ error .InvalidUtf8 = > unreachable , // WASI only
985
+ error .InvalidWtf8 = > unreachable , // Windows only
986
+ error .BadPathName = > unreachable ,
987
+ error .PipeBusy = > unreachable ,
988
+ error .FileLocksNotSupported = > unreachable ,
989
+ error .WouldBlock = > unreachable ,
990
+ error .FileBusy = > unreachable , // opened without write permissions
991
+ error .AntivirusInterference = > unreachable , // Windows-only error
992
+
993
+ error .IsDir ,
994
+ error .NotDir ,
995
+
996
+ error .AccessDenied ,
997
+ error .DeviceBusy ,
998
+ error .FileTooBig ,
999
+ error .SymLinkLoop ,
1000
+ error .ProcessFdQuotaExceeded ,
1001
+ error .SystemFdQuotaExceeded ,
1002
+ error .SystemResources ,
1003
+
1004
+ error .FileNotFound ,
1005
+ error .NetworkNotFound ,
1006
+ error .NoDevice ,
1007
+ error .Unexpected ,
1008
+ = > return error .UnableToFindElfFile ,
1009
+ };
1010
+ var is_elf_file = false ;
1011
+ defer if (is_elf_file == false ) file .close ();
1012
+
1013
+ // Shortest working interpreter path is "#!/i" (4)
1014
+ // (interpreter is "/i", assuming all paths are absolute, like in above comment).
1015
+ // ELF magic number length is also 4.
1016
+ //
1017
+ // If file is shorter than that, it is definitely not ELF file
1018
+ // nor file with "shebang" line.
1019
+ const min_len = 4 ;
1020
+
1021
+ const len = preadAtLeast (file , & buffer , 0 , min_len ) catch return error .UnableToFindElfFile ;
1022
+ const content = buffer [0.. len ];
1023
+
1024
+ if (mem .eql (u8 , content [0.. 4], std .elf .MAGIC )) {
1025
+ // It is very likely ELF file!
1026
+ is_elf_file = true ;
1027
+ return file ;
1028
+ } else if (mem .eql (u8 , content [0.. 2], "#!" )) {
1029
+ // We detected shebang, now parse entire line.
1030
+
1031
+ // Trim leading "#!", spaces and tabs.
1032
+ const trimmed_line = mem .trimLeft (u8 , content [2.. ], &.{ ' ' , '\t ' });
1033
+
1034
+ // This line can have:
1035
+ // * Interpreter path only,
1036
+ // * Interpreter path and arguments, all separated by space, tab or NUL character.
1037
+ // And optionally newline at the end.
1038
+ const path_maybe_args = mem .trimRight (u8 , trimmed_line , "\n " );
1039
+
1040
+ // Separate path and args.
1041
+ const path_end = mem .indexOfAny (u8 , path_maybe_args , &.{ ' ' , '\t ' , 0 }) orelse path_maybe_args .len ;
1042
+
1043
+ current_path = path_maybe_args [0.. path_end ];
1044
+ continue ;
1045
+ } else {
1046
+ // Not a ELF file, not a shell script with "shebang line", invalid duck.
1047
+ return error .UnableToFindElfFile ;
1048
+ }
1049
+ }
1050
+ }
1051
+
952
1052
/// In the past, this function attempted to use the executable's own binary if it was dynamically
953
1053
/// linked to answer both the C ABI question and the dynamic linker question. However, this
954
1054
/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking
@@ -957,11 +1057,14 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
957
1057
/// the dynamic linker will match that of the compiler binary. Executables with these versions
958
1058
/// mismatching will fail to run.
959
1059
///
960
- /// Therefore, this function works the same regardless of whether the compiler binary is
961
- /// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the
962
- /// answer to these questions, or if there is a shebang line, then it chases the referenced
963
- /// file recursively. If that does not provide the answer, then the function falls back to
964
- /// defaults.
1060
+ /// Therefore, this function now does not inspect the executable's own binary.
1061
+ /// Instead, it tries to find `env` program in PATH or in hardcoded location, and uses it
1062
+ /// to find suitable ELF file. If `env` program is an executable, work is done and function starts to
1063
+ /// inspect inner structure of a file. But if `env` is a script or other non-ELF file, it uses
1064
+ /// interpreter path instead and tries to search ELF file again, going recursively in case interpreter
1065
+ /// is also a script/non-ELF file.
1066
+ ///
1067
+ /// If nothing was found, then the function falls back to defaults.
965
1068
fn detectAbiAndDynamicLinker (
966
1069
cpu : Target.Cpu ,
967
1070
os : Target.Os ,
@@ -1029,113 +1132,44 @@ fn detectAbiAndDynamicLinker(
1029
1132
1030
1133
const ld_info_list = ld_info_list_buffer [0.. ld_info_list_len ];
1031
1134
1032
- // Best case scenario: the executable is dynamically linked, and we can iterate
1033
- // over our own shared objects and find a dynamic linker.
1034
- const elf_file = elf_file : {
1035
- // This block looks for a shebang line in /usr/bin/env,
1036
- // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
1037
- // doing the same logic recursively in case it finds another shebang line.
1135
+ const cwd = std .fs .cwd ();
1136
+
1137
+ // Algorithm is:
1138
+ // 1a) try_path: If PATH is non-empty and `env` file was found in one of the directories, use that.
1139
+ // 1b) try_path: If `env` was not found or PATH is empty, try hardcoded path below.
1140
+ // 2a) try_hardcoded: If `env` was found in hardcoded location, use that.
1141
+ // 2b) try_hardcoded: If `env` was not found, fall back to default ABI and dynamic linker.
1142
+ // Source: https://github.com/ziglang/zig/issues/14146#issuecomment-2308984936
1143
+ const elf_file = (try_path : {
1144
+ const PATH = std .posix .getenv ("PATH" ) orelse break :try_path null ;
1145
+ var it = mem .tokenizeScalar (u8 , PATH , fs .path .delimiter );
1146
+
1147
+ var buf : [std .fs .max_path_bytes + 1 ]u8 = undefined ;
1148
+ var fbs : std.heap.FixedBufferAllocator = .init (& buf );
1149
+ const allocator = fbs .allocator ();
1150
+
1151
+ while (it .next ()) | path | : (fbs .reset ()) {
1152
+ const start_path = std .fs .path .joinZ (allocator , &.{ path , "env" }) catch | err | switch (err ) {
1153
+ error .OutOfMemory = > continue ,
1154
+ };
1038
1155
1039
- var file_name : []const u8 = switch (os .tag ) {
1156
+ break :try_path resolveElfFileRecursively (cwd , start_path ) catch | err | switch (err ) {
1157
+ error .UnableToFindElfFile = > continue ,
1158
+ };
1159
+ } else break :try_path null ;
1160
+ } orelse try_hardcoded : {
1161
+ const hardcoded_file_name = switch (os .tag ) {
1040
1162
// Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
1041
1163
// reasonably reliable path to start with.
1042
1164
else = > "/usr/bin/env" ,
1043
1165
// Haiku does not have a /usr root directory.
1044
1166
.haiku = > "/bin/env" ,
1045
1167
};
1046
1168
1047
- // According to `man 2 execve`:
1048
- //
1049
- // The kernel imposes a maximum length on the text
1050
- // that follows the "#!" characters at the start of a script;
1051
- // characters beyond the limit are ignored.
1052
- // Before Linux 5.1, the limit is 127 characters.
1053
- // Since Linux 5.1, the limit is 255 characters.
1054
- //
1055
- // Tests show that bash and zsh consider 255 as total limit,
1056
- // *including* "#!" characters and ignoring newline.
1057
- // For safety, we set max length as 255 + \n (1).
1058
- var buffer : [255 + 1 ]u8 = undefined ;
1059
- while (true ) {
1060
- // Interpreter path can be relative on Linux, but
1061
- // for simplicity we are asserting it is an absolute path.
1062
- const file = fs .openFileAbsolute (file_name , .{}) catch | err | switch (err ) {
1063
- error .NoSpaceLeft = > unreachable ,
1064
- error .NameTooLong = > unreachable ,
1065
- error .PathAlreadyExists = > unreachable ,
1066
- error .SharingViolation = > unreachable ,
1067
- error .InvalidUtf8 = > unreachable , // WASI only
1068
- error .InvalidWtf8 = > unreachable , // Windows only
1069
- error .BadPathName = > unreachable ,
1070
- error .PipeBusy = > unreachable ,
1071
- error .FileLocksNotSupported = > unreachable ,
1072
- error .WouldBlock = > unreachable ,
1073
- error .FileBusy = > unreachable , // opened without write permissions
1074
- error .AntivirusInterference = > unreachable , // Windows-only error
1075
-
1076
- error .IsDir ,
1077
- error .NotDir ,
1078
- error .AccessDenied ,
1079
- error .NoDevice ,
1080
- error .FileNotFound ,
1081
- error .NetworkNotFound ,
1082
- error .FileTooBig ,
1083
- error .Unexpected ,
1084
- = > | e | {
1085
- std .log .warn ("Encountered error: {s}, falling back to default ABI and dynamic linker." , .{@errorName (e )});
1086
- return defaultAbiAndDynamicLinker (cpu , os , query );
1087
- },
1088
-
1089
- else = > | e | return e ,
1090
- };
1091
- var is_elf_file = false ;
1092
- defer if (is_elf_file == false ) file .close ();
1093
-
1094
- // Shortest working interpreter path is "#!/i" (4)
1095
- // (interpreter is "/i", assuming all paths are absolute, like in above comment).
1096
- // ELF magic number length is also 4.
1097
- //
1098
- // If file is shorter than that, it is definitely not ELF file
1099
- // nor file with "shebang" line.
1100
- const min_len : usize = 4 ;
1101
-
1102
- const len = preadAtLeast (file , & buffer , 0 , min_len ) catch | err | switch (err ) {
1103
- error .UnexpectedEndOfFile ,
1104
- error .UnableToReadElfFile ,
1105
- error .ProcessNotFound ,
1106
- = > return defaultAbiAndDynamicLinker (cpu , os , query ),
1107
-
1108
- else = > | e | return e ,
1109
- };
1110
- const content = buffer [0.. len ];
1111
-
1112
- if (mem .eql (u8 , content [0.. 4], std .elf .MAGIC )) {
1113
- // It is very likely ELF file!
1114
- is_elf_file = true ;
1115
- break :elf_file file ;
1116
- } else if (mem .eql (u8 , content [0.. 2], "#!" )) {
1117
- // We detected shebang, now parse entire line.
1118
-
1119
- // Trim leading "#!", spaces and tabs.
1120
- const trimmed_line = mem .trimLeft (u8 , content [2.. ], &.{ ' ' , '\t ' });
1121
-
1122
- // This line can have:
1123
- // * Interpreter path only,
1124
- // * Interpreter path and arguments, all separated by space, tab or NUL character.
1125
- // And optionally newline at the end.
1126
- const path_maybe_args = mem .trimRight (u8 , trimmed_line , "\n " );
1127
-
1128
- // Separate path and args.
1129
- const path_end = mem .indexOfAny (u8 , path_maybe_args , &.{ ' ' , '\t ' , 0 }) orelse path_maybe_args .len ;
1130
-
1131
- file_name = path_maybe_args [0.. path_end ];
1132
- continue ;
1133
- } else {
1134
- // Not a ELF file, not a shell script with "shebang line", invalid duck.
1135
- return defaultAbiAndDynamicLinker (cpu , os , query );
1136
- }
1137
- }
1138
- };
1169
+ break :try_hardcoded resolveElfFileRecursively (cwd , hardcoded_file_name ) catch | err | switch (err ) {
1170
+ error .UnableToFindElfFile = > null ,
1171
+ };
1172
+ }) orelse return defaultAbiAndDynamicLinker (cpu , os , query );
1139
1173
defer elf_file .close ();
1140
1174
1141
1175
// TODO: inline this function and combine the buffer we already read above to find
@@ -1159,10 +1193,7 @@ fn detectAbiAndDynamicLinker(
1159
1193
error .UnexpectedEndOfFile ,
1160
1194
error .NameTooLong ,
1161
1195
// Finally, we fall back on the standard path.
1162
- = > | e | {
1163
- std .log .warn ("Encountered error: {s}, falling back to default ABI and dynamic linker." , .{@errorName (e )});
1164
- return defaultAbiAndDynamicLinker (cpu , os , query );
1165
- },
1196
+ = > defaultAbiAndDynamicLinker (cpu , os , query ),
1166
1197
};
1167
1198
}
1168
1199
0 commit comments