authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-17 00:58:30-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-17 00:58:30-05:00
log44c14749a1063e5525b3249cddca6435d0654df4
tree3c53668e301d9e8359660f90e62e990ebfe47177
parenta5d47be5adc7c493ff7e87fc47241a84e268a3f0
signature Commit is signed but in an unrecognized format.

expand argv[0] when spawning system C compiler

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 {
4949
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,
5151
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,
5458
...@@ -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 {
172177
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();
184210
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;
190217
191 try child.spawn();218 try child.spawn();
192219
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);
197224
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();
200227
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);
203230
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 }
420447
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 }
424451
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;
918918
919/// Deprecated in favor of `execveZ`.
920pub 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.
922pub fn execveC(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError {925pub 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}
944947
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.949pub 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`.951pub const Arg0Expand = enum {
949pub 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.
958pub 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);
952969
...@@ -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}
975997
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 null999/// 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.
981pub fn execvpe(1001/// If `file` is an absolute path, this is the same as `execveC`.
1002pub 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.
1013pub 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);
10061039
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.
1051pub 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}
10091058
1010pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![:null]?[*:0]u8 {1059pub 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 };
496504
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 };