Commit cba4a9ad4a

Andrew Kelley <superjoe30@gmail.com>
2017-09-26 07:01:49
update std.os.ChildProcess API
* add std.os.ChildProcess.setUserName * add std.os.getUserId
1 parent 79400bf
Changed files (10)
example/mix_o_files/build.zig
@@ -12,7 +12,7 @@ pub fn build(b: &Builder) {
 
     b.default_step.dependOn(&exe.step);
 
-    const run_cmd = b.addCommand(".", b.env_map, exe.getOutputPath(), [][]const u8{});
+    const run_cmd = b.addCommand(".", b.env_map, [][]const u8{exe.getOutputPath()});
     run_cmd.step.dependOn(&exe.step);
 
     const test_step = b.step("test", "Test the program");
example/shared_library/build.zig
@@ -12,7 +12,7 @@ pub fn build(b: &Builder) {
 
     b.default_step.dependOn(&exe.step);
 
-    const run_cmd = b.addCommand(".", b.env_map, exe.getOutputPath(), [][]const u8{});
+    const run_cmd = b.addCommand(".", b.env_map, [][]const u8{exe.getOutputPath()});
     run_cmd.step.dependOn(&exe.step);
 
     const test_step = b.step("test", "Test the program");
std/os/child_process.zig
@@ -16,20 +16,35 @@ error ProcessNotFound;
 var children_nodes = LinkedList(&ChildProcess).init();
 
 pub const ChildProcess = struct {
-    pid: i32,
+    pub pid: i32,
+    pub allocator: &mem.Allocator,
 
-    err_pipe: [2]i32,
-    llnode: LinkedList(&ChildProcess).Node,
-    allocator: &mem.Allocator,
+    pub stdin: ?&io.OutStream,
+    pub stdout: ?&io.InStream,
+    pub stderr: ?&io.InStream,
+
+    pub term: ?%Term,
+
+    pub argv: []const []const u8,
+
+    /// Possibly called from a signal handler. Must set this before calling `spawn`.
+    pub onTerm: ?fn(&ChildProcess),
+
+    /// Leave as null to use the current env map using the supplied allocator.
+    pub env_map: ?&const BufMap,
 
-    stdin: ?&io.OutStream,
-    stdout: ?&io.InStream,
-    stderr: ?&io.InStream,
+    pub stdin_behavior: StdIo,
+    pub stdout_behavior: StdIo,
+    pub stderr_behavior: StdIo,
 
-    term: ?%Term,
+    /// Set to change the user id when spawning the child process.
+    pub uid: ?u32,
 
-    /// Possibly called from a signal handler.
-    onTerm: ?fn(&ChildProcess),
+    /// Set to change the current working directory when spawning the child process.
+    pub cwd: ?[]const u8,
+
+    err_pipe: [2]i32,
+    llnode: LinkedList(&ChildProcess).Node,
 
     pub const Term = enum {
         Exited: i32,
@@ -45,18 +60,50 @@ pub const ChildProcess = struct {
         Close,
     };
 
+    /// First argument in argv is the executable.
+    /// On success must call deinit.
+    pub fn init(argv: []const []const u8, allocator: &Allocator) -> %&ChildProcess {
+        const child = %return allocator.create(ChildProcess);
+        %defer allocator.destroy(child);
+
+        *child = ChildProcess {
+            .allocator = allocator,
+            .argv = argv,
+            .pid = undefined,
+            .err_pipe = undefined,
+            .llnode = undefined,
+            .term = null,
+            .onTerm = null,
+            .env_map = null,
+            .cwd = null,
+            .uid = null,
+            .stdin = null,
+            .stdout = null,
+            .stderr = null,
+            .stdin_behavior = StdIo.Inherit,
+            .stdout_behavior = StdIo.Inherit,
+            .stderr_behavior = StdIo.Inherit,
+        };
+
+        return child;
+    }
+
+    pub fn setUserName(self: &ChildProcess, name: []const u8) -> %void {
+        self.uid = %return os.getUserId(name);
+    }
+
     /// onTerm can be called before `spawn` returns.
-    pub fn spawn(exe_path: []const u8, args: []const []const u8,
-        cwd: ?[]const u8, env_map: &const BufMap,
-        stdin: StdIo, stdout: StdIo, stderr: StdIo,
-        onTerm: ?fn(&ChildProcess), allocator: &Allocator) -> %&ChildProcess
-    {
-        switch (builtin.os) {
-            Os.linux, Os.macosx, Os.ios, Os.darwin => {
-                return spawnPosix(exe_path, args, cwd, env_map, stdin, stdout, stderr, onTerm, allocator);
-            },
+    /// On success must call `kill` or `wait`.
+    pub fn spawn(self: &ChildProcess) -> %void {
+        return switch (builtin.os) {
+            Os.linux, Os.macosx, Os.ios, Os.darwin => self.spawnPosix(),
             else => @compileError("Unsupported OS"),
-        }
+        };
+    }
+
+    pub fn spawnAndWait(self: &ChildProcess) -> %Term {
+        %return self.spawn();
+        return self.wait();
     }
 
     /// Forcibly terminates child process and then cleans up all resources.
@@ -96,6 +143,10 @@ pub const ChildProcess = struct {
         return ??self.term;
     }
 
+    pub fn deinit(self: &ChildProcess) {
+        self.allocator.destroy(self);
+    }
+
     fn waitUnwrapped(self: &ChildProcess) {
         var status: i32 = undefined;
         while (true) {
@@ -162,50 +213,56 @@ pub const ChildProcess = struct {
         };
     }
 
-    fn spawnPosix(exe_path: []const u8, args: []const []const u8,
-        maybe_cwd: ?[]const u8, env_map: &const BufMap,
-        stdin: StdIo, stdout: StdIo, stderr: StdIo,
-        onTerm: ?fn(&ChildProcess), allocator: &Allocator) -> %&ChildProcess
-    {
+    fn spawnPosix(self: &ChildProcess) -> %void {
         // TODO atomically set a flag saying that we already did this
         install_SIGCHLD_handler();
 
-        const stdin_pipe = if (stdin == StdIo.Pipe) %return makePipe() else undefined;
-        %defer if (stdin == StdIo.Pipe) { destroyPipe(stdin_pipe); };
+        const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) %return makePipe() else undefined;
+        %defer if (self.stdin_behavior == StdIo.Pipe) { destroyPipe(stdin_pipe); };
 
-        const stdout_pipe = if (stdout == StdIo.Pipe) %return makePipe() else undefined;
-        %defer if (stdout == StdIo.Pipe) { destroyPipe(stdout_pipe); };
+        const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) %return makePipe() else undefined;
+        %defer if (self.stdout_behavior == StdIo.Pipe) { destroyPipe(stdout_pipe); };
 
-        const stderr_pipe = if (stderr == StdIo.Pipe) %return makePipe() else undefined;
-        %defer if (stderr == StdIo.Pipe) { destroyPipe(stderr_pipe); };
+        const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) %return makePipe() else undefined;
+        %defer if (self.stderr_behavior == StdIo.Pipe) { destroyPipe(stderr_pipe); };
 
-        const any_ignore = (stdin == StdIo.Ignore or stdout == StdIo.Ignore or stderr == StdIo.Ignore);
+        const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
         const dev_null_fd = if (any_ignore) {
             %return os.posixOpen("/dev/null", posix.O_RDWR, 0, null)
         } else {
             undefined
         };
+        defer { if (any_ignore) os.posixClose(dev_null_fd); };
+
+        var env_map_owned: BufMap = undefined;
+        var we_own_env_map: bool = undefined;
+        const env_map = if (self.env_map) |env_map| {
+            we_own_env_map = false;
+            env_map
+        } else {
+            we_own_env_map = true;
+            env_map_owned = %return os.getEnvMap(self.allocator);
+            &env_map_owned
+        };
+        defer { if (we_own_env_map) env_map_owned.deinit(); }
 
         // This pipe is used to communicate errors between the time of fork
         // and execve from the child process to the parent process.
         const err_pipe = %return makePipe();
         %defer destroyPipe(err_pipe);
 
-        const child = %return allocator.create(ChildProcess);
-        %defer allocator.destroy(child);
-
-        const stdin_ptr = if (stdin == StdIo.Pipe) {
-            %return allocator.create(io.OutStream)
+        const stdin_ptr = if (self.stdin_behavior == StdIo.Pipe) {
+            %return self.allocator.create(io.OutStream)
         } else {
             null
         };
-        const stdout_ptr = if (stdout == StdIo.Pipe) {
-            %return allocator.create(io.InStream)
+        const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {
+            %return self.allocator.create(io.InStream)
         } else {
             null
         };
-        const stderr_ptr = if (stderr == StdIo.Pipe) {
-            %return allocator.create(io.InStream)
+        const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {
+            %return self.allocator.create(io.InStream)
         } else {
             null
         };
@@ -224,19 +281,23 @@ pub const ChildProcess = struct {
             // we are the child
             restore_SIGCHLD();
 
-            setUpChildIo(stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) %%
+            setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) %%
                 |err| forkChildErrReport(err_pipe[1], err);
-            setUpChildIo(stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) %%
+            setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) %%
                 |err| forkChildErrReport(err_pipe[1], err);
-            setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %%
+            setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %%
                 |err| forkChildErrReport(err_pipe[1], err);
 
-            if (maybe_cwd) |cwd| {
-                os.changeCurDir(allocator, cwd) %%
+            if (self.cwd) |cwd| {
+                os.changeCurDir(self.allocator, cwd) %%
                     |err| forkChildErrReport(err_pipe[1], err);
             }
 
-            os.posixExecve(exe_path, args, env_map, allocator) %%
+            if (self.uid) |uid| {
+                os.posix_setuid(uid) %% |err| forkChildErrReport(err_pipe[1], err);
+            }
+
+            os.posixExecve(self.argv, env_map, self.allocator) %%
                 |err| forkChildErrReport(err_pipe[1], err);
         }
 
@@ -266,28 +327,22 @@ pub const ChildProcess = struct {
             };
         }
 
-        *child = ChildProcess {
-            .allocator = allocator,
-            .pid = pid,
-            .err_pipe = err_pipe,
-            .llnode = LinkedList(&ChildProcess).Node.init(child),
-            .term = null,
-            .onTerm = onTerm,
-            .stdin = stdin_ptr,
-            .stdout = stdout_ptr,
-            .stderr = stderr_ptr,
-        };
+        self.pid = pid;
+        self.err_pipe = err_pipe;
+        self.llnode = LinkedList(&ChildProcess).Node.init(self);
+        self.term = null;
+        self.stdin = stdin_ptr;
+        self.stdout = stdout_ptr;
+        self.stderr = stderr_ptr;
 
-        children_nodes.prepend(&child.llnode);
+        // TODO make this atomic so it works even with threads
+        children_nodes.prepend(&self.llnode);
 
         restore_SIGCHLD();
 
-        if (stdin == StdIo.Pipe) { os.posixClose(stdin_pipe[0]); }
-        if (stdout == StdIo.Pipe) { os.posixClose(stdout_pipe[1]); }
-        if (stderr == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); }
-        if (any_ignore) { os.posixClose(dev_null_fd); }
-
-        return child;
+        if (self.stdin_behavior == StdIo.Pipe) { os.posixClose(stdin_pipe[0]); }
+        if (self.stdout_behavior == StdIo.Pipe) { os.posixClose(stdout_pipe[1]); }
+        if (self.stderr_behavior == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); }
     }
 
     fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void {
std/os/get_user_id.zig
@@ -0,0 +1,78 @@
+const builtin = @import("builtin");
+const Os = builtin.Os;
+const os = @import("index.zig");
+const io = @import("../io.zig");
+
+/// POSIX function which gets a uid from username.
+pub fn getUserId(name: []const u8) -> %u32 {
+    return switch (builtin.os) {
+        Os.linux, Os.darwin, Os.macosx, Os.ios => posixGetUserId(name),
+        else => @compileError("Unsupported OS"),
+    };
+}
+
+const State = enum {
+    Start,
+    WaitForNextLine,
+    SkipPassword,
+    ReadId,
+};
+
+error UserNotFound;
+error CorruptPasswordFile;
+
+pub fn posixGetUserId(name: []const u8) -> %u32 {
+    var in_stream = %return io.InStream.open("/etc/passwd", null);
+    defer in_stream.close();
+
+    var buf: [os.page_size]u8 = undefined;
+    var name_index: usize = 0;
+    var state = State.Start;
+    var uid: u32 = 0;
+
+    while (true) {
+        const amt_read = %return in_stream.read(buf[0..]);
+        for (buf[0..amt_read]) |byte| {
+            switch (state) {
+                State.Start => switch (byte) {
+                    ':' => {
+                        state = if (name_index == name.len) State.SkipPassword else State.WaitForNextLine;
+                    },
+                    '\n' => return error.CorruptPasswordFile,
+                    else => {
+                        if (name_index == name.len or name[name_index] != byte) {
+                            state = State.WaitForNextLine;
+                        }
+                        name_index += 1;
+                    },
+                },
+                State.WaitForNextLine => switch (byte) {
+                    '\n' => {
+                        name_index = 0;
+                        state = State.Start;
+                    },
+                    else => continue,
+                },
+                State.SkipPassword => switch (byte) {
+                    '\n' => return error.CorruptPasswordFile,
+                    ':' => {
+                        state = State.ReadId;
+                    },
+                    else => continue,
+                },
+                State.ReadId => switch (byte) {
+                    '\n', ':' => return uid,
+                    else => {
+                        const digit = switch (byte) {
+                            '0' ... '9' => byte - '0',
+                            else => return error.CorruptPasswordFile,
+                        };
+                        if (@mulWithOverflow(u32, uid, 10, &uid)) return error.CorruptPasswordFile;
+                        if (@addWithOverflow(u32, uid, digit, &uid)) return error.CorruptPasswordFile;
+                    },
+                },
+            }
+        }
+        if (amt_read < buf.len) return error.UserNotFound;
+    }
+}
std/os/index.zig
@@ -20,6 +20,8 @@ pub const line_sep = switch (builtin.os) {
 
 pub const page_size = 4 * 1024;
 
+pub const getUserId = @import("get_user_id.zig").getUserId;
+
 const debug = @import("../debug.zig");
 const assert = debug.assert;
 
@@ -321,12 +323,12 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void {
 /// 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.
-/// Also make the first arg equal to exe_path.
+/// `argv[0]` is the executable path.
 /// This function also uses the PATH environment variable to get the full path to the executable.
-pub fn posixExecve(exe_path: []const u8, argv: []const []const u8, env_map: &const BufMap,
+pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
     allocator: &Allocator) -> %void
 {
-    const argv_buf = %return allocator.alloc(?&u8, argv.len + 2);
+    const argv_buf = %return allocator.alloc(?&u8, argv.len + 1);
     mem.set(?&u8, argv_buf, null);
     defer {
         for (argv_buf) |arg| {
@@ -335,22 +337,14 @@ pub fn posixExecve(exe_path: []const u8, argv: []const []const u8, env_map: &con
         }
         allocator.free(argv_buf);
     }
-    {
-        // Add exe_path to the first argument.
-        const arg_buf = %return allocator.alloc(u8, exe_path.len + 1);
-        @memcpy(&arg_buf[0], exe_path.ptr, exe_path.len);
-        arg_buf[exe_path.len] = 0;
-
-        argv_buf[0] = arg_buf.ptr;
-    }
     for (argv) |arg, i| {
         const arg_buf = %return allocator.alloc(u8, arg.len + 1);
         @memcpy(&arg_buf[0], arg.ptr, arg.len);
         arg_buf[arg.len] = 0;
 
-        argv_buf[i + 1] = arg_buf.ptr;
+        argv_buf[i] = arg_buf.ptr;
     }
-    argv_buf[argv.len + 1] = null;
+    argv_buf[argv.len] = null;
 
     const envp_count = env_map.count();
     const envp_buf = %return allocator.alloc(?&u8, envp_count + 1);
@@ -378,14 +372,9 @@ pub fn posixExecve(exe_path: []const u8, argv: []const []const u8, env_map: &con
     }
     envp_buf[envp_count] = null;
 
-
+    const exe_path = argv[0];
     if (mem.indexOfScalar(u8, exe_path, '/') != null) {
-        // +1 for the null terminating byte
-        const path_buf = %return allocator.alloc(u8, exe_path.len + 1);
-        defer allocator.free(path_buf);
-        @memcpy(&path_buf[0], &exe_path[0], exe_path.len);
-        path_buf[exe_path.len] = 0;
-        return posixExecveErrnoToErr(posix.getErrno(posix.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr)));
+        return posixExecveErrnoToErr(posix.getErrno(posix.execve(??argv_buf[0], argv_buf.ptr, envp_buf.ptr)));
     }
 
     const PATH = getEnv("PATH") ?? "/usr/local/bin:/bin/:/usr/bin";
@@ -434,6 +423,7 @@ fn posixExecveErrnoToErr(err: usize) -> error {
 
 pub var environ_raw: []&u8 = undefined;
 
+/// Caller must free result when done.
 pub fn getEnvMap(allocator: &Allocator) -> %BufMap {
     var result = BufMap.init(allocator);
     %defer result.deinit();
@@ -840,7 +830,7 @@ pub const Dir = struct {
     start_over:
         if (self.index >= self.end_index) {
             if (self.buf.len == 0) {
-                self.buf = %return self.allocator.alloc(u8, 2); //page_size);
+                self.buf = %return self.allocator.alloc(u8, page_size);
             }
 
             while (true) {
@@ -992,3 +982,20 @@ pub fn posixSleep(seconds: u63, nanoseconds: u63) {
 test "os.sleep" {
     sleep(0, 1);
 }
+
+
+error ResourceLimitReached;
+error InvalidUserId;
+error PermissionDenied;
+error Unexpected;
+
+pub fn posix_setuid(uid: u32) -> %void {
+    const err = posix.getErrno(posix.setuid(uid));
+    if (err == 0) return;
+    return switch (err) {
+        posix.EAGAIN => error.ResourceLimitReached,
+        posix.EINVAL => error.InvalidUserId,
+        posix.EPERM => error.PermissionDenied,
+        else => error.Unexpected,
+    };
+}
std/os/linux.zig
@@ -480,6 +480,10 @@ pub fn nanosleep(req: &const timespec, rem: ?&timespec) -> usize {
     arch.syscall2(arch.SYS_nanosleep, @ptrToInt(req), @ptrToInt(rem))
 }
 
+pub fn setuid(uid: u32) -> usize {
+    arch.syscall1(arch.SYS_setuid, uid)
+}
+
 pub fn sigprocmask(flags: u32, noalias set: &const sigset_t, noalias oldset: ?&sigset_t) -> usize {
     arch.syscall4(arch.SYS_rt_sigprocmask, flags, @ptrToInt(set), @ptrToInt(oldset), NSIG/8)
 }
std/build.zig
@@ -180,11 +180,11 @@ pub const Builder = struct {
         return LibExeObjStep.createCObject(self, name, src);
     }
 
-    /// ::args are copied.
+    /// ::argv is copied.
     pub fn addCommand(self: &Builder, cwd: ?[]const u8, env_map: &const BufMap,
-        path: []const u8, args: []const []const u8) -> &CommandStep
+        argv: []const []const u8) -> &CommandStep
     {
-        return CommandStep.create(self, cwd, env_map, path, args);
+        return CommandStep.create(self, cwd, env_map, argv);
     }
 
     pub fn addWriteFile(self: &Builder, file_path: []const u8, data: []const u8) -> &WriteFileStep {
@@ -528,42 +528,41 @@ pub const Builder = struct {
         return self.invalid_user_input;
     }
 
-    fn spawnChild(self: &Builder, exe_path: []const u8, args: []const []const u8) -> %void {
-        return self.spawnChildEnvMap(null, &self.env_map, exe_path, args);
+    fn spawnChild(self: &Builder, argv: []const []const u8) -> %void {
+        return self.spawnChildEnvMap(null, &self.env_map, argv);
     }
 
     fn spawnChildEnvMap(self: &Builder, cwd: ?[]const u8, env_map: &const BufMap,
-        exe_path: []const u8, args: []const []const u8) -> %void
+        argv: []const []const u8) -> %void
     {
         if (self.verbose) {
             if (cwd) |yes_cwd| %%io.stderr.print("cd {}; ", yes_cwd);
-            %%io.stderr.print("{}", exe_path);
-            for (args) |arg| {
-                %%io.stderr.print(" {}", arg);
+            for (argv) |arg| {
+                %%io.stderr.print("{} ", arg);
             }
             %%io.stderr.printf("\n");
         }
 
-        var child = os.ChildProcess.spawn(exe_path, args, cwd, env_map,
-            StdIo.Inherit, StdIo.Inherit, StdIo.Inherit, null, self.allocator) %% |err|
-        {
-            %%io.stderr.printf("Unable to spawn {}: {}\n", exe_path, @errorName(err));
-            return err;
-        };
+        const child = %%os.ChildProcess.init(argv, self.allocator);
+        defer child.deinit();
 
-        const term = child.wait() %% |err| {
-            %%io.stderr.printf("Unable to spawn {}: {}\n", exe_path, @errorName(err));
+        child.cwd = cwd;
+        child.env_map = env_map;
+
+        const term = child.spawnAndWait() %% |err| {
+            %%io.stderr.printf("Unable to spawn {}: {}\n", argv[0], @errorName(err));
             return err;
         };
+
         switch (term) {
             Term.Exited => |code| {
                 if (code != 0) {
-                    %%io.stderr.printf("Process {} exited with error code {}\n", exe_path, code);
+                    %%io.stderr.printf("Process {} exited with error code {}\n", argv[0], code);
                     return error.UncleanExit;
                 }
             },
             else => {
-                %%io.stderr.printf("Process {} terminated unexpectedly\n", exe_path);
+                %%io.stderr.printf("Process {} terminated unexpectedly\n", argv[0]);
                 return error.UncleanExit;
             },
         };
@@ -1063,6 +1062,8 @@ pub const LibExeObjStep = struct {
         var zig_args = ArrayList([]const u8).init(builder.allocator);
         defer zig_args.deinit();
 
+        %%zig_args.append(builder.zig_exe);
+
         const cmd = switch (self.kind) {
             Kind.Lib => "build-lib",
             Kind.Exe => "build-exe",
@@ -1193,7 +1194,7 @@ pub const LibExeObjStep = struct {
             }
         }
 
-        %return builder.spawnChild(builder.zig_exe, zig_args.toSliceConst());
+        %return builder.spawnChild(zig_args.toSliceConst());
 
         if (self.kind == Kind.Lib and !self.static) {
             %return doAtomicSymLinks(builder.allocator, output_path, self.major_only_filename,
@@ -1255,6 +1256,8 @@ pub const LibExeObjStep = struct {
         var cc_args = ArrayList([]const u8).init(builder.allocator);
         defer cc_args.deinit();
 
+        %%cc_args.append(cc);
+
         const is_darwin = self.target.isDarwin();
 
         switch (self.kind) {
@@ -1268,11 +1271,12 @@ pub const LibExeObjStep = struct {
 
                 self.appendCompileFlags(&cc_args);
 
-                %return builder.spawnChild(cc, cc_args.toSliceConst());
+                %return builder.spawnChild(cc_args.toSliceConst());
             },
             Kind.Lib => {
                 for (self.source_files.toSliceConst()) |source_file| {
                     %%cc_args.resize(0);
+                    %%cc_args.append(cc);
 
                     if (!self.static) {
                         %%cc_args.append("-fPIC");
@@ -1291,7 +1295,7 @@ pub const LibExeObjStep = struct {
 
                     self.appendCompileFlags(&cc_args);
 
-                    %return builder.spawnChild(cc, cc_args.toSliceConst());
+                    %return builder.spawnChild(cc_args.toSliceConst());
 
                     %%self.object_files.append(cache_o_file);
                 }
@@ -1299,6 +1303,8 @@ pub const LibExeObjStep = struct {
                 if (self.static) {
                     // ar
                     %%cc_args.resize(0);
+                    %%cc_args.append("ar");
+
                     %%cc_args.append("qc");
 
                     const output_path = builder.pathFromRoot(self.getOutputPath());
@@ -1308,15 +1314,17 @@ pub const LibExeObjStep = struct {
                         %%cc_args.append(builder.pathFromRoot(object_file));
                     }
 
-                    %return builder.spawnChild("ar", cc_args.toSliceConst());
+                    %return builder.spawnChild(cc_args.toSliceConst());
 
                     // ranlib
                     %%cc_args.resize(0);
+                    %%cc_args.append("ranlib");
                     %%cc_args.append(output_path);
 
-                    %return builder.spawnChild("ranlib", cc_args.toSliceConst());
+                    %return builder.spawnChild(cc_args.toSliceConst());
                 } else {
                     %%cc_args.resize(0);
+                    %%cc_args.append(cc);
 
                     if (is_darwin) {
                         %%cc_args.append("-dynamiclib");
@@ -1370,7 +1378,7 @@ pub const LibExeObjStep = struct {
                         }
                     }
 
-                    %return builder.spawnChild(cc, cc_args.toSliceConst());
+                    %return builder.spawnChild(cc_args.toSliceConst());
 
                     %return doAtomicSymLinks(builder.allocator, output_path, self.major_only_filename,
                         self.name_only_filename);
@@ -1379,6 +1387,7 @@ pub const LibExeObjStep = struct {
             Kind.Exe => {
                 for (self.source_files.toSliceConst()) |source_file| {
                     %%cc_args.resize(0);
+                    %%cc_args.append(cc);
 
                     const abs_source_file = builder.pathFromRoot(source_file);
                     %%cc_args.append("-c");
@@ -1400,12 +1409,13 @@ pub const LibExeObjStep = struct {
                         %%cc_args.append(builder.pathFromRoot(dir));
                     }
 
-                    %return builder.spawnChild(cc, cc_args.toSliceConst());
+                    %return builder.spawnChild(cc_args.toSliceConst());
 
                     %%self.object_files.append(cache_o_file);
                 }
 
                 %%cc_args.resize(0);
+                %%cc_args.append(cc);
 
                 for (self.object_files.toSliceConst()) |object_file| {
                     %%cc_args.append(builder.pathFromRoot(object_file));
@@ -1441,7 +1451,7 @@ pub const LibExeObjStep = struct {
                     }
                 }
 
-                %return builder.spawnChild(cc, cc_args.toSliceConst());
+                %return builder.spawnChild(cc_args.toSliceConst());
             },
         }
     }
@@ -1518,6 +1528,8 @@ pub const TestStep = struct {
         var zig_args = ArrayList([]const u8).init(builder.allocator);
         defer zig_args.deinit();
 
+        %%zig_args.append(builder.zig_exe);
+
         %%zig_args.append("test");
         %%zig_args.append(builder.pathFromRoot(self.root_src));
 
@@ -1590,32 +1602,31 @@ pub const TestStep = struct {
             %%zig_args.append(lib_path);
         }
 
-        %return builder.spawnChild(builder.zig_exe, zig_args.toSliceConst());
+        %return builder.spawnChild(zig_args.toSliceConst());
     }
 };
 
 pub const CommandStep = struct {
     step: Step,
     builder: &Builder,
-    exe_path: []const u8,
-    args: [][]const u8,
+    argv: [][]const u8,
     cwd: ?[]const u8,
     env_map: &const BufMap,
 
-    /// ::args are copied.
+    /// ::argv is copied.
     pub fn create(builder: &Builder, cwd: ?[]const u8, env_map: &const BufMap,
-        exe_path: []const u8, args: []const []const u8) -> &CommandStep
+        argv: []const []const u8) -> &CommandStep
     {
         const self = %%builder.allocator.create(CommandStep);
         *self = CommandStep {
             .builder = builder,
-            .step = Step.init(exe_path, builder.allocator, make),
-            .exe_path = exe_path,
-            .args = %%builder.allocator.alloc([]u8, args.len),
+            .step = Step.init(argv[0], builder.allocator, make),
+            .argv = %%builder.allocator.alloc([]u8, argv.len),
             .cwd = cwd,
             .env_map = env_map,
         };
-        mem.copy([]const u8, self.args, args);
+        mem.copy([]const u8, self.argv, argv);
+        self.step.name = self.argv[0];
         return self;
     }
 
@@ -1623,7 +1634,7 @@ pub const CommandStep = struct {
         const self = @fieldParentPtr(CommandStep, "step", step);
 
         const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else null;
-        return self.builder.spawnChildEnvMap(cwd, self.env_map, self.exe_path, self.args);
+        return self.builder.spawnChildEnvMap(cwd, self.env_map, self.argv);
     }
 };
 
test/standalone/pkg_import/build.zig
@@ -9,7 +9,7 @@ pub fn build(b: &Builder) {
     exe.setBuildMode(b.standardReleaseOptions());
     exe.setBuildMode(b.standardReleaseOptions());
 
-    const run = b.addCommand(".", b.env_map, exe.getOutputPath(), [][]const u8{});
+    const run = b.addCommand(".", b.env_map, [][]const u8{exe.getOutputPath()});
     run.step.dependOn(&exe.step);
 
     const test_step = b.step("test", "Test it");
test/tests.zig
@@ -241,11 +241,15 @@ pub const CompareOutputContext = struct {
 
             %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
 
-            var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, null, &b.env_map,
-                StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
-            {
-                debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
-            };
+            const child = %%os.ChildProcess.init([][]u8{full_exe_path}, b.allocator);
+            defer child.deinit();
+
+            child.stdin_behavior = StdIo.Ignore;
+            child.stdout_behavior = StdIo.Pipe;
+            child.stderr_behavior = StdIo.Pipe;
+            child.env_map = &b.env_map;
+
+            child.spawn() %% |err| debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
 
             var stdout = Buffer.initNull(b.allocator);
             var stderr = Buffer.initNull(b.allocator);
@@ -316,13 +320,15 @@ pub const CompareOutputContext = struct {
 
             %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
 
-            var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, null, &b.env_map,
-                StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
-            {
-                debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
-            };
+            const child = %%os.ChildProcess.init([][]u8{full_exe_path}, b.allocator);
+            defer child.deinit();
 
-            const term = child.wait() %% |err| {
+            child.env_map = &b.env_map;
+            child.stdin_behavior = StdIo.Ignore;
+            child.stdout_behavior = StdIo.Ignore;
+            child.stderr_behavior = StdIo.Ignore;
+
+            const term = child.spawnAndWait() %% |err| {
                 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
             };
 
@@ -539,6 +545,8 @@ pub const CompileErrorContext = struct {
             const obj_path = %%os.path.join(b.allocator, b.cache_root, "test.o");
 
             var zig_args = ArrayList([]const u8).init(b.allocator);
+            %%zig_args.append(b.zig_exe);
+
             %%zig_args.append(if (self.case.is_exe) "build-exe" else "build-obj");
             %%zig_args.append(b.pathFromRoot(root_src));
 
@@ -560,11 +568,15 @@ pub const CompileErrorContext = struct {
                 printInvocation(b.zig_exe, zig_args.toSliceConst());
             }
 
-            var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), null, &b.env_map,
-                StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
-            {
-                debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
-            };
+            const child = %%os.ChildProcess.init(zig_args.toSliceConst(), b.allocator);
+            defer child.deinit();
+
+            child.env_map = &b.env_map;
+            child.stdin_behavior = StdIo.Ignore;
+            child.stdout_behavior = StdIo.Pipe;
+            child.stderr_behavior = StdIo.Pipe;
+
+            child.spawn() %% |err| debug.panic("Unable to spawn {}: {}\n", zig_args.items[0], @errorName(err));
 
             var stdout_buf = Buffer.initNull(b.allocator);
             var stderr_buf = Buffer.initNull(b.allocator);
@@ -573,7 +585,7 @@ pub const CompileErrorContext = struct {
             %%(??child.stderr).readAll(&stderr_buf);
 
             const term = child.wait() %% |err| {
-                debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
+                debug.panic("Unable to spawn {}: {}\n", zig_args.items[0], @errorName(err));
             };
             switch (term) {
                 Term.Exited => |code| {
@@ -712,6 +724,7 @@ pub const BuildExamplesContext = struct {
         }
 
         var zig_args = ArrayList([]const u8).init(b.allocator);
+        %%zig_args.append(b.zig_exe);
         %%zig_args.append("build");
 
         %%zig_args.append("--build-file");
@@ -723,7 +736,7 @@ pub const BuildExamplesContext = struct {
             %%zig_args.append("--verbose");
         }
 
-        const run_cmd = b.addCommand(null, b.env_map, b.zig_exe, zig_args.toSliceConst());
+        const run_cmd = b.addCommand(null, b.env_map, zig_args.toSliceConst());
 
         const log_step = b.addLog("PASS {}\n", annotated_case_name);
         log_step.step.dependOn(&run_cmd.step);
@@ -813,6 +826,8 @@ pub const ParseCContext = struct {
             const root_src = %%os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename);
 
             var zig_args = ArrayList([]const u8).init(b.allocator);
+            %%zig_args.append(b.zig_exe);
+
             %%zig_args.append("parsec");
             %%zig_args.append(b.pathFromRoot(root_src));
 
@@ -822,11 +837,15 @@ pub const ParseCContext = struct {
                 printInvocation(b.zig_exe, zig_args.toSliceConst());
             }
 
-            var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), null, &b.env_map,
-                StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, null, b.allocator) %% |err|
-            {
-                debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
-            };
+            const child = %%os.ChildProcess.init(zig_args.toSliceConst(), b.allocator);
+            defer child.deinit();
+
+            child.env_map = &b.env_map;
+            child.stdin_behavior = StdIo.Ignore;
+            child.stdout_behavior = StdIo.Pipe;
+            child.stderr_behavior = StdIo.Pipe;
+
+            child.spawn() %% |err| debug.panic("Unable to spawn {}: {}\n", zig_args.toSliceConst()[0], @errorName(err));
 
             var stdout_buf = Buffer.initNull(b.allocator);
             var stderr_buf = Buffer.initNull(b.allocator);
@@ -835,7 +854,7 @@ pub const ParseCContext = struct {
             %%(??child.stderr).readAll(&stderr_buf);
 
             const term = child.wait() %% |err| {
-                debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
+                debug.panic("Unable to spawn {}: {}\n", zig_args.toSliceConst()[0], @errorName(err));
             };
             switch (term) {
                 Term.Exited => |code| {
CMakeLists.txt
@@ -548,6 +548,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/net.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/child_process.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/darwin.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/darwin_errno.zig" DESTINATION "${ZIG_STD_DEST}/os")
+install(FILES "${CMAKE_SOURCE_DIR}/std/os/get_user_id.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/index.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/linux.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/linux_errno.zig" DESTINATION "${ZIG_STD_DEST}/os")