| author | |
| committer | |
| log | 44c14749a1063e5525b3249cddca6435d0654df4 |
| tree | 3c53668e301d9e8359660f90e62e990ebfe47177 |
| parent | a5d47be5adc7c493ff7e87fc47241a84e268a3f0 |
| signature | Commit is signed but in an unrecognized format. |
Some C compilers, such as Clang, are known to rely on
argv[0] to find the path to their own executable,
without even bothering to resolve PATH. This results
in the message:
error: unable to execute command: Executable "" doesn't exist!
So we tell ChildProcess to expand argv[0] to the absolute path
to give them a helping hand.3 files changed, 122 insertions(+), 30 deletions(-)
lib/std/child_process.zig+39-12| ... | @@ -49,6 +49,10 @@ pub const ChildProcess = struct { | ... | @@ -49,6 +49,10 @@ pub const ChildProcess = struct { |
| 49 | 49 | ||
| 50 | err_pipe: if (builtin.os == .windows) void else [2]os.fd_t, | 50 | err_pipe: if (builtin.os == .windows) void else [2]os.fd_t, |
| 51 | 51 | ||
| 52 | expand_arg0: Arg0Expand, | ||
| 53 | |||
| 54 | pub const Arg0Expand = os.Arg0Expand; | ||
| 55 | |||
| 52 | pub const SpawnError = error{ | 56 | pub const SpawnError = error{ |
| 53 | OutOfMemory, | 57 | OutOfMemory, |
| 54 | 58 | ||
| ... | @@ -100,6 +104,7 @@ pub const ChildProcess = struct { | ... | @@ -100,6 +104,7 @@ pub const ChildProcess = struct { |
| 100 | .stdin_behavior = StdIo.Inherit, | 104 | .stdin_behavior = StdIo.Inherit, |
| 101 | .stdout_behavior = StdIo.Inherit, | 105 | .stdout_behavior = StdIo.Inherit, |
| 102 | .stderr_behavior = StdIo.Inherit, | 106 | .stderr_behavior = StdIo.Inherit, |
| 107 | .expand_arg0 = .no_expand, | ||
| 103 | }; | 108 | }; |
| 104 | errdefer allocator.destroy(child); | 109 | errdefer allocator.destroy(child); |
| 105 | return child; | 110 | return child; |
| ... | @@ -172,34 +177,56 @@ pub const ChildProcess = struct { | ... | @@ -172,34 +177,56 @@ pub const ChildProcess = struct { |
| 172 | 177 | ||
| 173 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. | 178 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 174 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. | 179 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 180 | /// TODO deprecate in favor of exec2 | ||
| 175 | pub fn exec( | 181 | pub fn exec( |
| 176 | allocator: *mem.Allocator, | 182 | allocator: *mem.Allocator, |
| 177 | argv: []const []const u8, | 183 | argv: []const []const u8, |
| 178 | cwd: ?[]const u8, | 184 | cwd: ?[]const u8, |
| 179 | env_map: ?*const BufMap, | 185 | env_map: ?*const BufMap, |
| 180 | max_output_size: usize, | 186 | max_output_bytes: usize, |
| 181 | ) !ExecResult { | 187 | ) !ExecResult { |
| 182 | const child = try ChildProcess.init(argv, allocator); | 188 | return exec2(.{ |
| 189 | .allocator = allocator, | ||
| 190 | .argv = argv, | ||
| 191 | .cwd = cwd, | ||
| 192 | .env_map = env_map, | ||
| 193 | .max_output_bytes = max_output_bytes, | ||
| 194 | }); | ||
| 195 | } | ||
| 196 | |||
| 197 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. | ||
| 198 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. | ||
| 199 | /// TODO rename to exec | ||
| 200 | pub fn exec2(args: struct { | ||
| 201 | allocator: *mem.Allocator, | ||
| 202 | argv: []const []const u8, | ||
| 203 | cwd: ?[]const u8 = null, | ||
| 204 | env_map: ?*const BufMap = null, | ||
| 205 | max_output_bytes: usize = 50 * 1024, | ||
| 206 | expand_arg0: Arg0Expand = .no_expand, | ||
| 207 | }) !ExecResult { | ||
| 208 | const child = try ChildProcess.init(args.argv, args.allocator); | ||
| 183 | defer child.deinit(); | 209 | defer child.deinit(); |
| 184 | 210 | ||
| 185 | child.stdin_behavior = ChildProcess.StdIo.Ignore; | 211 | child.stdin_behavior = .Ignore; |
| 186 | child.stdout_behavior = ChildProcess.StdIo.Pipe; | 212 | child.stdout_behavior = .Pipe; |
| 187 | child.stderr_behavior = ChildProcess.StdIo.Pipe; | 213 | child.stderr_behavior = .Pipe; |
| 188 | child.cwd = cwd; | 214 | child.cwd = args.cwd; |
| 189 | child.env_map = env_map; | 215 | child.env_map = args.env_map; |
| 216 | child.expand_arg0 = args.expand_arg0; | ||
| 190 | 217 | ||
| 191 | try child.spawn(); | 218 | try child.spawn(); |
| 192 | 219 | ||
| 193 | var stdout = Buffer.initNull(allocator); | 220 | var stdout = Buffer.initNull(args.allocator); |
| 194 | var stderr = Buffer.initNull(allocator); | 221 | var stderr = Buffer.initNull(args.allocator); |
| 195 | defer Buffer.deinit(&stdout); | 222 | defer Buffer.deinit(&stdout); |
| 196 | defer Buffer.deinit(&stderr); | 223 | defer Buffer.deinit(&stderr); |
| 197 | 224 | ||
| 198 | var stdout_file_in_stream = child.stdout.?.inStream(); | 225 | var stdout_file_in_stream = child.stdout.?.inStream(); |
| 199 | var stderr_file_in_stream = child.stderr.?.inStream(); | 226 | var stderr_file_in_stream = child.stderr.?.inStream(); |
| 200 | 227 | ||
| 201 | try stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size); | 228 | try stdout_file_in_stream.stream.readAllBuffer(&stdout, args.max_output_bytes); |
| 202 | try stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size); | 229 | try stderr_file_in_stream.stream.readAllBuffer(&stderr, args.max_output_bytes); |
| 203 | 230 | ||
| 204 | return ExecResult{ | 231 | return ExecResult{ |
| 205 | .term = try child.wait(), | 232 | .term = try child.wait(), |
| ... | @@ -418,7 +445,7 @@ pub const ChildProcess = struct { | ... | @@ -418,7 +445,7 @@ pub const ChildProcess = struct { |
| 418 | os.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err); | 445 | os.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err); |
| 419 | } | 446 | } |
| 420 | 447 | ||
| 421 | const err = os.execvpe(self.allocator, self.argv, env_map); | 448 | const err = os.execvpe_expandArg0(self.allocator, self.expand_arg0, self.argv, env_map); |
| 422 | forkChildErrReport(err_pipe[1], err); | 449 | forkChildErrReport(err_pipe[1], err); |
| 423 | } | 450 | } |
| 424 | 451 |
lib/std/os.zig+63-14| ... | @@ -916,10 +916,13 @@ pub const ExecveError = error{ | ... | @@ -916,10 +916,13 @@ pub const ExecveError = error{ |
| 916 | NameTooLong, | 916 | NameTooLong, |
| 917 | } || UnexpectedError; | 917 | } || UnexpectedError; |
| 918 | 918 | ||
| 919 | /// Deprecated in favor of `execveZ`. | ||
| 920 | pub const execveC = execveZ; | ||
| 921 | |||
| 919 | /// Like `execve` except the parameters are null-terminated, | 922 | /// Like `execve` except the parameters are null-terminated, |
| 920 | /// matching the syscall API on all targets. This removes the need for an allocator. | 923 | /// matching the syscall API on all targets. This removes the need for an allocator. |
| 921 | /// This function ignores PATH environment variable. See `execvpeC` for that. | 924 | /// This function ignores PATH environment variable. See `execvpeZ` for that. |
| 922 | pub fn execveC(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError { | 925 | pub fn execveZ(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError { |
| 923 | switch (errno(system.execve(path, child_argv, envp))) { | 926 | switch (errno(system.execve(path, child_argv, envp))) { |
| 924 | 0 => unreachable, | 927 | 0 => unreachable, |
| 925 | EFAULT => unreachable, | 928 | EFAULT => unreachable, |
| ... | @@ -942,11 +945,25 @@ pub fn execveC(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, en | ... | @@ -942,11 +945,25 @@ pub fn execveC(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, en |
| 942 | } | 945 | } |
| 943 | } | 946 | } |
| 944 | 947 | ||
| 945 | /// Like `execvpe` except the parameters are null-terminated, | 948 | /// Deprecated in favor of `execvpeZ`. |
| 946 | /// matching the syscall API on all targets. This removes the need for an allocator. | 949 | pub const execvpeC = execvpeZ; |
| 947 | /// This function also uses the PATH environment variable to get the full path to the executable. | 950 | |
| 948 | /// If `file` is an absolute path, this is the same as `execveC`. | 951 | pub const Arg0Expand = enum { |
| 949 | pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError { | 952 | expand, |
| 953 | no_expand, | ||
| 954 | }; | ||
| 955 | |||
| 956 | /// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, | ||
| 957 | /// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. | ||
| 958 | pub fn execvpeZ_expandArg0( | ||
| 959 | comptime arg0_expand: Arg0Expand, | ||
| 960 | file: [*:0]const u8, | ||
| 961 | child_argv: switch (arg0_expand) { | ||
| 962 | .expand => [*:null]?[*:0]const u8, | ||
| 963 | .no_expand => [*:null]const ?[*:0]const u8, | ||
| 964 | }, | ||
| 965 | envp: [*:null]const ?[*:0]const u8, | ||
| 966 | ) ExecveError { | ||
| 950 | const file_slice = mem.toSliceConst(u8, file); | 967 | const file_slice = mem.toSliceConst(u8, file); |
| 951 | if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveC(file, child_argv, envp); | 968 | if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveC(file, child_argv, envp); |
| 952 | 969 | ||
| ... | @@ -962,7 +979,12 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e | ... | @@ -962,7 +979,12 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e |
| 962 | mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice); | 979 | mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice); |
| 963 | const path_len = search_path.len + file_slice.len + 1; | 980 | const path_len = search_path.len + file_slice.len + 1; |
| 964 | path_buf[path_len] = 0; | 981 | path_buf[path_len] = 0; |
| 965 | err = execveC(path_buf[0..path_len :0].ptr, child_argv, envp); | 982 | const full_path = path_buf[0..path_len :0].ptr; |
| 983 | switch (arg0_expand) { | ||
| 984 | .expand => child_argv[0] = full_path, | ||
| 985 | .no_expand => {}, | ||
| 986 | } | ||
| 987 | err = execveC(full_path, child_argv, envp); | ||
| 966 | switch (err) { | 988 | switch (err) { |
| 967 | error.AccessDenied => seen_eacces = true, | 989 | error.AccessDenied => seen_eacces = true, |
| 968 | error.FileNotFound, error.NotDir => {}, | 990 | error.FileNotFound, error.NotDir => {}, |
| ... | @@ -973,13 +995,24 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e | ... | @@ -973,13 +995,24 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e |
| 973 | return err; | 995 | return err; |
| 974 | } | 996 | } |
| 975 | 997 | ||
| 976 | /// This function must allocate memory to add a null terminating bytes on path and each arg. | 998 | /// Like `execvpe` except the parameters are null-terminated, |
| 977 | /// It must also convert to KEY=VALUE\0 format for environment variables, and include null | 999 | /// matching the syscall API on all targets. This removes the need for an allocator. |
| 978 | /// pointers after the args and after the environment variables. | ||
| 979 | /// `argv_slice[0]` is the executable path. | ||
| 980 | /// This function also uses the PATH environment variable to get the full path to the executable. | 1000 | /// This function also uses the PATH environment variable to get the full path to the executable. |
| 981 | pub fn execvpe( | 1001 | /// If `file` is an absolute path, this is the same as `execveC`. |
| 1002 | pub fn execvpeZ( | ||
| 1003 | file: [*:0]const u8, | ||
| 1004 | argv: [*:null]const ?[*:0]const u8, | ||
| 1005 | envp: [*:null]const ?[*:0]const u8, | ||
| 1006 | ) ExecveError { | ||
| 1007 | return execvpeZ_expandArg0(.no_expand, file, argv, envp); | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | /// This is the same as `execvpe` except if the `arg0_expand` parameter is set to `.expand`, | ||
| 1011 | /// then argv[0] will be replaced with the expanded version of it, after resolving in accordance | ||
| 1012 | /// with the PATH environment variable. | ||
| 1013 | pub fn execvpe_expandArg0( | ||
| 982 | allocator: *mem.Allocator, | 1014 | allocator: *mem.Allocator, |
| 1015 | arg0_expand: Arg0Expand, | ||
| 983 | argv_slice: []const []const u8, | 1016 | argv_slice: []const []const u8, |
| 984 | env_map: *const std.BufMap, | 1017 | env_map: *const std.BufMap, |
| 985 | ) (ExecveError || error{OutOfMemory}) { | 1018 | ) (ExecveError || error{OutOfMemory}) { |
| ... | @@ -1004,7 +1037,23 @@ pub fn execvpe( | ... | @@ -1004,7 +1037,23 @@ pub fn execvpe( |
| 1004 | const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); | 1037 | const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); |
| 1005 | defer freeNullDelimitedEnvMap(allocator, envp_buf); | 1038 | defer freeNullDelimitedEnvMap(allocator, envp_buf); |
| 1006 | 1039 | ||
| 1007 | return execvpeC(argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr); | 1040 | switch (arg0_expand) { |
| 1041 | .expand => return execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr), | ||
| 1042 | .no_expand => return execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr), | ||
| 1043 | } | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | /// This function must allocate memory to add a null terminating bytes on path and each arg. | ||
| 1047 | /// It must also convert to KEY=VALUE\0 format for environment variables, and include null | ||
| 1048 | /// pointers after the args and after the environment variables. | ||
| 1049 | /// `argv_slice[0]` is the executable path. | ||
| 1050 | /// This function also uses the PATH environment variable to get the full path to the executable. | ||
| 1051 | pub fn execvpe( | ||
| 1052 | allocator: *mem.Allocator, | ||
| 1053 | argv_slice: []const []const u8, | ||
| 1054 | env_map: *const std.BufMap, | ||
| 1055 | ) (ExecveError || error{OutOfMemory}) { | ||
| 1056 | return execvpe_expandArg0(allocator, .no_expand, argv_slice, env_map); | ||
| 1008 | } | 1057 | } |
| 1009 | 1058 | ||
| 1010 | pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![:null]?[*:0]u8 { | 1059 | pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![:null]?[*:0]u8 { |
src-self-hosted/libc_installation.zig+20-4| ... | @@ -241,8 +241,16 @@ pub const LibCInstallation = struct { | ... | @@ -241,8 +241,16 @@ pub const LibCInstallation = struct { |
| 241 | "-xc", | 241 | "-xc", |
| 242 | dev_null, | 242 | dev_null, |
| 243 | }; | 243 | }; |
| 244 | const max_bytes = 1024 * 1024; | 244 | const exec_res = std.ChildProcess.exec2(.{ |
| 245 | const exec_res = std.ChildProcess.exec(allocator, &argv, null, null, max_bytes) catch |err| switch (err) { | 245 | .allocator = allocator, |
| 246 | .argv = &argv, | ||
| 247 | .max_output_bytes = 1024 * 1024, | ||
| 248 | // Some C compilers, such as Clang, are known to rely on argv[0] to find the path | ||
| 249 | // to their own executable, without even bothering to resolve PATH. This results in the message: | ||
| 250 | // error: unable to execute command: Executable "" doesn't exist! | ||
| 251 | // So we use the expandArg0 variant of ChildProcess to give them a helping hand. | ||
| 252 | .expand_arg0 = .expand, | ||
| 253 | }) catch |err| switch (err) { | ||
| 246 | error.OutOfMemory => return error.OutOfMemory, | 254 | error.OutOfMemory => return error.OutOfMemory, |
| 247 | else => return error.UnableToSpawnCCompiler, | 255 | else => return error.UnableToSpawnCCompiler, |
| 248 | }; | 256 | }; |
| ... | @@ -494,8 +502,16 @@ pub fn ccPrintFileName( | ... | @@ -494,8 +502,16 @@ pub fn ccPrintFileName( |
| 494 | defer allocator.free(arg1); | 502 | defer allocator.free(arg1); |
| 495 | const argv = [_][]const u8{ cc_exe, arg1 }; | 503 | const argv = [_][]const u8{ cc_exe, arg1 }; |
| 496 | 504 | ||
| 497 | const max_bytes = 1024 * 1024; | 505 | const exec_res = std.ChildProcess.exec2(.{ |
| 498 | const exec_res = std.ChildProcess.exec(allocator, &argv, null, null, max_bytes) catch |err| switch (err) { | 506 | .allocator = allocator, |
| 507 | .argv = &argv, | ||
| 508 | .max_output_bytes = 1024 * 1024, | ||
| 509 | // Some C compilers, such as Clang, are known to rely on argv[0] to find the path | ||
| 510 | // to their own executable, without even bothering to resolve PATH. This results in the message: | ||
| 511 | // error: unable to execute command: Executable "" doesn't exist! | ||
| 512 | // So we use the expandArg0 variant of ChildProcess to give them a helping hand. | ||
| 513 | .expand_arg0 = .expand, | ||
| 514 | }) catch |err| switch (err) { | ||
| 499 | error.OutOfMemory => return error.OutOfMemory, | 515 | error.OutOfMemory => return error.OutOfMemory, |
| 500 | else => return error.UnableToSpawnCCompiler, | 516 | else => return error.UnableToSpawnCCompiler, |
| 501 | }; | 517 | }; |