diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index f3626f5249..171a330bac 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -49,6 +49,10 @@ pub const ChildProcess = struct { err_pipe: if (builtin.os == .windows) void else [2]os.fd_t, + expand_arg0: Arg0Expand, + + pub const Arg0Expand = os.Arg0Expand; + pub const SpawnError = error{ OutOfMemory, @@ -100,6 +104,7 @@ pub const ChildProcess = struct { .stdin_behavior = StdIo.Inherit, .stdout_behavior = StdIo.Inherit, .stderr_behavior = StdIo.Inherit, + .expand_arg0 = .no_expand, }; errdefer allocator.destroy(child); return child; @@ -172,34 +177,56 @@ pub const ChildProcess = struct { /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. /// If it succeeds, the caller owns result.stdout and result.stderr memory. + /// TODO deprecate in favor of exec2 pub fn exec( allocator: *mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, env_map: ?*const BufMap, - max_output_size: usize, + max_output_bytes: usize, ) !ExecResult { - const child = try ChildProcess.init(argv, allocator); + return exec2(.{ + .allocator = allocator, + .argv = argv, + .cwd = cwd, + .env_map = env_map, + .max_output_bytes = max_output_bytes, + }); + } + + /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. + /// If it succeeds, the caller owns result.stdout and result.stderr memory. + /// TODO rename to exec + pub fn exec2(args: struct { + allocator: *mem.Allocator, + argv: []const []const u8, + cwd: ?[]const u8 = null, + env_map: ?*const BufMap = null, + max_output_bytes: usize = 50 * 1024, + expand_arg0: Arg0Expand = .no_expand, + }) !ExecResult { + const child = try ChildProcess.init(args.argv, args.allocator); defer child.deinit(); - child.stdin_behavior = ChildProcess.StdIo.Ignore; - child.stdout_behavior = ChildProcess.StdIo.Pipe; - child.stderr_behavior = ChildProcess.StdIo.Pipe; - child.cwd = cwd; - child.env_map = env_map; + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + child.cwd = args.cwd; + child.env_map = args.env_map; + child.expand_arg0 = args.expand_arg0; try child.spawn(); - var stdout = Buffer.initNull(allocator); - var stderr = Buffer.initNull(allocator); + var stdout = Buffer.initNull(args.allocator); + var stderr = Buffer.initNull(args.allocator); defer Buffer.deinit(&stdout); defer Buffer.deinit(&stderr); var stdout_file_in_stream = child.stdout.?.inStream(); var stderr_file_in_stream = child.stderr.?.inStream(); - try stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size); - try stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size); + try stdout_file_in_stream.stream.readAllBuffer(&stdout, args.max_output_bytes); + try stderr_file_in_stream.stream.readAllBuffer(&stderr, args.max_output_bytes); return ExecResult{ .term = try child.wait(), @@ -418,7 +445,7 @@ pub const ChildProcess = struct { os.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err); } - const err = os.execvpe(self.allocator, self.argv, env_map); + const err = os.execvpe_expandArg0(self.allocator, self.expand_arg0, self.argv, env_map); forkChildErrReport(err_pipe[1], err); } diff --git a/lib/std/os.zig b/lib/std/os.zig index ff2296d928..c5fdc41d83 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -916,10 +916,13 @@ pub const ExecveError = error{ NameTooLong, } || UnexpectedError; +/// Deprecated in favor of `execveZ`. +pub const execveC = execveZ; + /// Like `execve` except the parameters are null-terminated, /// matching the syscall API on all targets. This removes the need for an allocator. -/// This function ignores PATH environment variable. See `execvpeC` for that. -pub fn execveC(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError { +/// This function ignores PATH environment variable. See `execvpeZ` for that. +pub fn execveZ(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError { switch (errno(system.execve(path, child_argv, envp))) { 0 => unreachable, EFAULT => unreachable, @@ -942,11 +945,25 @@ pub fn execveC(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, en } } -/// Like `execvpe` except the parameters are null-terminated, -/// matching the syscall API on all targets. This removes the need for an allocator. -/// This function also uses the PATH environment variable to get the full path to the executable. -/// If `file` is an absolute path, this is the same as `execveC`. -pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError { +/// Deprecated in favor of `execvpeZ`. +pub const execvpeC = execvpeZ; + +pub const Arg0Expand = enum { + expand, + no_expand, +}; + +/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, +/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. +pub fn execvpeZ_expandArg0( + comptime arg0_expand: Arg0Expand, + file: [*:0]const u8, + child_argv: switch (arg0_expand) { + .expand => [*:null]?[*:0]const u8, + .no_expand => [*:null]const ?[*:0]const u8, + }, + envp: [*:null]const ?[*:0]const u8, +) ExecveError { const file_slice = mem.toSliceConst(u8, file); if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveC(file, child_argv, envp); @@ -962,7 +979,12 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice); const path_len = search_path.len + file_slice.len + 1; path_buf[path_len] = 0; - err = execveC(path_buf[0..path_len :0].ptr, child_argv, envp); + const full_path = path_buf[0..path_len :0].ptr; + switch (arg0_expand) { + .expand => child_argv[0] = full_path, + .no_expand => {}, + } + err = execveC(full_path, child_argv, envp); switch (err) { error.AccessDenied => seen_eacces = true, error.FileNotFound, error.NotDir => {}, @@ -973,13 +995,24 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e return err; } -/// This function must allocate memory to add a null terminating bytes on path and each arg. -/// It must also convert to KEY=VALUE\0 format for environment variables, and include null -/// pointers after the args and after the environment variables. -/// `argv_slice[0]` is the executable path. +/// Like `execvpe` except the parameters are null-terminated, +/// matching the syscall API on all targets. This removes the need for an allocator. /// This function also uses the PATH environment variable to get the full path to the executable. -pub fn execvpe( +/// If `file` is an absolute path, this is the same as `execveC`. +pub fn execvpeZ( + file: [*:0]const u8, + argv: [*:null]const ?[*:0]const u8, + envp: [*:null]const ?[*:0]const u8, +) ExecveError { + return execvpeZ_expandArg0(.no_expand, file, argv, envp); +} + +/// This is the same as `execvpe` except if the `arg0_expand` parameter is set to `.expand`, +/// then argv[0] will be replaced with the expanded version of it, after resolving in accordance +/// with the PATH environment variable. +pub fn execvpe_expandArg0( allocator: *mem.Allocator, + arg0_expand: Arg0Expand, argv_slice: []const []const u8, env_map: *const std.BufMap, ) (ExecveError || error{OutOfMemory}) { @@ -1004,7 +1037,23 @@ pub fn execvpe( const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); defer freeNullDelimitedEnvMap(allocator, envp_buf); - return execvpeC(argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr); + switch (arg0_expand) { + .expand => return execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr), + .no_expand => return execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr), + } +} + +/// This function must allocate memory to add a null terminating bytes on path and each arg. +/// It must also convert to KEY=VALUE\0 format for environment variables, and include null +/// pointers after the args and after the environment variables. +/// `argv_slice[0]` is the executable path. +/// This function also uses the PATH environment variable to get the full path to the executable. +pub fn execvpe( + allocator: *mem.Allocator, + argv_slice: []const []const u8, + env_map: *const std.BufMap, +) (ExecveError || error{OutOfMemory}) { + return execvpe_expandArg0(allocator, .no_expand, argv_slice, env_map); } pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![:null]?[*:0]u8 { diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig index 85d6fc61ed..08d7571cec 100644 --- a/src-self-hosted/libc_installation.zig +++ b/src-self-hosted/libc_installation.zig @@ -241,8 +241,16 @@ pub const LibCInstallation = struct { "-xc", dev_null, }; - const max_bytes = 1024 * 1024; - const exec_res = std.ChildProcess.exec(allocator, &argv, null, null, max_bytes) catch |err| switch (err) { + const exec_res = std.ChildProcess.exec2(.{ + .allocator = allocator, + .argv = &argv, + .max_output_bytes = 1024 * 1024, + // 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 use the expandArg0 variant of ChildProcess to give them a helping hand. + .expand_arg0 = .expand, + }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.UnableToSpawnCCompiler, }; @@ -494,8 +502,16 @@ pub fn ccPrintFileName( defer allocator.free(arg1); const argv = [_][]const u8{ cc_exe, arg1 }; - const max_bytes = 1024 * 1024; - const exec_res = std.ChildProcess.exec(allocator, &argv, null, null, max_bytes) catch |err| switch (err) { + const exec_res = std.ChildProcess.exec2(.{ + .allocator = allocator, + .argv = &argv, + .max_output_bytes = 1024 * 1024, + // 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 use the expandArg0 variant of ChildProcess to give them a helping hand. + .expand_arg0 = .expand, + }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.UnableToSpawnCCompiler, };