Commit 72fb2443e0

Andrew Kelley <superjoe30@gmail.com>
2017-04-04 06:17:24
API for command line args
closes #300
1 parent b46344f
example/cat/main.zig
@@ -1,23 +1,25 @@
 const std = @import("std");
 const io = std.io;
 const mem = std.mem;
+const os = std.os;
 
-pub fn main(args: [][]u8) -> %void {
-    const exe = args[0];
+pub fn main() -> %void {
+    const exe = os.args.at(0);
     var catted_anything = false;
-    for (args[1...]) |arg| {
+    var arg_i: usize = 1;
+    while (arg_i < os.args.count(); arg_i += 1) {
+        const arg = os.args.at(arg_i);
         if (mem.eql(u8, arg, "-")) {
             catted_anything = true;
             %return cat_stream(&io.stdin);
         } else if (arg[0] == '-') {
             return usage(exe);
         } else {
-            var is: io.InStream = undefined;
-            is.open(arg) %% |err| {
+            var is = io.InStream.open(arg, null) %% |err| {
                 %%io.stderr.printf("Unable to open file: {}\n", @errorName(err));
                 return err;
             };
-            defer %%is.close();
+            defer is.close();
 
             catted_anything = true;
             %return cat_stream(&is);
@@ -29,7 +31,7 @@ pub fn main(args: [][]u8) -> %void {
     %return io.stdout.flush();
 }
 
-fn usage(exe: []u8) -> %void {
+fn usage(exe: []const u8) -> %void {
     %%io.stderr.printf("Usage: {} [FILE]...\n", exe);
     return error.Invalid;
 }
example/guess_number/main.zig
@@ -4,7 +4,7 @@ const fmt = std.fmt;
 const Rand = std.rand.Rand;
 const os = std.os;
 
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     %%io.stdout.printf("Welcome to the Guess Number Game in Zig.\n");
 
     var seed_bytes: [@sizeOf(usize)]u8 = undefined;
example/hello_world/hello.zig
@@ -1,5 +1,5 @@
 const io = @import("std").io;
 
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     %%io.stdout.printf("Hello, world!\n");
 }
std/os/child_process.zig
@@ -0,0 +1,280 @@
+const io = @import("../io.zig");
+const os = @import("index.zig");
+const posix = os.posix;
+const mem = @import("../mem.zig");
+const Allocator = mem.Allocator;
+const errno = @import("errno.zig");
+const debug = @import("../debug.zig");
+const assert = debug.assert;
+
+pub const ChildProcess = struct {
+    pid: i32,
+    err_pipe: [2]i32,
+
+    stdin: ?io.OutStream,
+    stdout: ?io.InStream,
+    stderr: ?io.InStream,
+
+    pub const Term = enum {
+        Clean: i32,
+        Signal: i32,
+        Stopped: i32,
+        Unknown: i32,
+    };
+
+    pub const StdIo = enum {
+        Inherit,
+        Ignore,
+        Pipe,
+        Close,
+    };
+
+    pub fn spawn(exe_path: []const u8, args: []const []const u8, env_map: &const os.EnvMap,
+        stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess
+    {
+        switch (@compileVar("os")) {
+            Os.linux, Os.macosx, Os.ios, Os.darwin => {
+                return spawnPosix(exe_path, args, env_map, stdin, stdout, stderr, allocator);
+            },
+            else => @compileError("Unsupported OS"),
+        }
+    }
+
+    pub fn wait(self: &ChildProcess) -> %Term {
+        defer {
+            os.posixClose(self.err_pipe[0]);
+            os.posixClose(self.err_pipe[1]);
+        };
+
+        var status: i32 = undefined;
+        while (true) {
+            const err = posix.getErrno(posix.waitpid(self.pid, &status, 0));
+            if (err > 0) {
+                switch (err) {
+                    errno.EINVAL, errno.ECHILD => unreachable,
+                    errno.EINTR => continue,
+                    else => {
+                        if (const *stdin ?= self.stdin) { stdin.close(); }
+                        if (const *stdout ?= self.stdin) { stdout.close(); }
+                        if (const *stderr ?= self.stdin) { stderr.close(); }
+                        return error.Unexpected;
+                    },
+                }
+            }
+            break;
+        }
+
+        if (const *stdin ?= self.stdin) { stdin.close(); }
+        if (const *stdout ?= self.stdin) { stdout.close(); }
+        if (const *stderr ?= self.stdin) { stderr.close(); }
+
+        // Write @maxValue(ErrInt) to the write end of the err_pipe. This is after
+        // waitpid, so this write is guaranteed to be after the child
+        // pid potentially wrote an error. This way we can do a blocking
+        // read on the error pipe and either get @maxValue(ErrInt) (no error) or
+        // an error code.
+        %return writeIntFd(self.err_pipe[1], @maxValue(ErrInt));
+        const err_int = %return readIntFd(self.err_pipe[0]);
+        // Here we potentially return the fork child's error
+        // from the parent pid.
+        if (err_int != @maxValue(ErrInt)) {
+            return error(err_int);
+        }
+
+        return statusToTerm(status);
+    }
+
+    fn statusToTerm(status: i32) -> Term {
+        return if (posix.WIFEXITED(status)) {
+            Term.Clean { posix.WEXITSTATUS(status) }
+        } else if (posix.WIFSIGNALED(status)) {
+            Term.Signal { posix.WTERMSIG(status) }
+        } else if (posix.WIFSTOPPED(status)) {
+            Term.Stopped { posix.WSTOPSIG(status) }
+        } else {
+            Term.Unknown { status }
+        };
+    }
+
+    fn spawnPosix(exe_path: []const u8, args: []const []const u8, env_map: &const os.EnvMap,
+        stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess
+    {
+        // TODO issue #295
+        //const stdin_pipe = if (stdin == StdIo.Pipe) %return makePipe() else undefined;
+        var stdin_pipe: [2]i32 = undefined;
+        if (stdin == StdIo.Pipe)
+            stdin_pipe = %return makePipe();
+        %defer if (stdin == StdIo.Pipe) { destroyPipe(stdin_pipe); };
+
+        // TODO issue #295
+        //const stdout_pipe = if (stdout == StdIo.Pipe) %return makePipe() else undefined;
+        var stdout_pipe: [2]i32 = undefined;
+        if (stdout == StdIo.Pipe) 
+            stdout_pipe = %return makePipe();
+        %defer if (stdout == StdIo.Pipe) { destroyPipe(stdout_pipe); };
+
+        // TODO issue #295
+        //const stderr_pipe = if (stderr == StdIo.Pipe) %return makePipe() else undefined;
+        var stderr_pipe: [2]i32 = undefined;
+        if (stderr == StdIo.Pipe) 
+            stderr_pipe = %return makePipe();
+        %defer if (stderr == StdIo.Pipe) { destroyPipe(stderr_pipe); };
+
+        const any_ignore = (stdin == StdIo.Ignore or stdout == StdIo.Ignore or stderr == StdIo.Ignore);
+        // TODO issue #295
+        //const dev_null_fd = if (any_ignore) {
+        //    %return os.posixOpen("/dev/null", posix.O_RDWR, 0, null)
+        //} else {
+        //    undefined
+        //};
+        var dev_null_fd: i32 = undefined;
+        if (any_ignore)
+            dev_null_fd = %return os.posixOpen("/dev/null", posix.O_RDWR, 0, null);
+
+        // 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 pid = posix.fork();
+        const pid_err = posix.getErrno(pid);
+        if (pid_err > 0) {
+            return switch (pid_err) {
+                errno.EAGAIN, errno.ENOMEM, errno.ENOSYS => error.SysResources,
+                else => error.Unexpected,
+            };
+        }
+        if (pid == 0) {
+            // we are the child
+            setUpChildIo(stdin, 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) %%
+                |err| forkChildErrReport(err_pipe[1], err);
+            setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %%
+                |err| forkChildErrReport(err_pipe[1], err);
+
+            const err = posix.getErrno(%return os.posixExecve(exe_path, args, env_map, allocator));
+            assert(err > 0);
+            forkChildErrReport(err_pipe[1], switch (err) {
+                errno.EFAULT => unreachable,
+                errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SysResources,
+                errno.EACCES, errno.EPERM => error.AccessDenied,
+                errno.EINVAL, errno.ENOEXEC => error.InvalidExe,
+                errno.EIO, errno.ELOOP => error.FileSystem,
+                errno.EISDIR => error.IsDir,
+                errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
+                errno.ETXTBSY => error.FileBusy,
+                else => error.Unexpected,
+            });
+        }
+
+        // we are the parent
+        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 ChildProcess {
+            .pid = i32(pid),
+            .err_pipe = err_pipe,
+
+            .stdin = if (stdin == StdIo.Pipe) {
+                io.OutStream {
+                    .fd = stdin_pipe[1],
+                    .buffer = undefined,
+                    .index = 0,
+                }
+            } else {
+                null
+            },
+            .stdout = if (stdout == StdIo.Pipe) {
+                io.InStream {
+                    .fd = stdout_pipe[0],
+                }
+            } else {
+                null
+            },
+            .stderr = if (stderr == StdIo.Pipe) {
+                io.InStream {
+                    .fd = stderr_pipe[0],
+                }
+            } else {
+                null
+            },
+        };
+    }
+
+    fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void {
+        switch (stdio) {
+            StdIo.Pipe => %return os.posixDup2(pipe_fd, std_fileno),
+            StdIo.Close => os.posixClose(std_fileno),
+            StdIo.Inherit => {},
+            StdIo.Ignore => %return os.posixDup2(dev_null_fd, std_fileno),
+        }
+    }
+};
+
+fn makePipe() -> %[2]i32 {
+    var fds: [2]i32 = undefined;
+    const err = posix.getErrno(posix.pipe(&fds));
+    if (err > 0) {
+        return switch (err) {
+            errno.EMFILE, errno.ENFILE => error.SysResources,
+            else => error.Unexpected,
+        }
+    }
+    return fds;
+}
+
+fn destroyPipe(pipe: &const [2]i32) {
+    os.posixClose((*pipe)[0]);
+    os.posixClose((*pipe)[1]);
+}
+
+// Child of fork calls this to report an error to the fork parent.
+// Then the child exits.
+fn forkChildErrReport(fd: i32, err: error) -> noreturn {
+    _ = writeIntFd(fd, ErrInt(err));
+    posix.exit(1);
+}
+
+const ErrInt = @intType(false, @sizeOf(error) * 8);
+fn writeIntFd(fd: i32, value: ErrInt) -> %void {
+    var bytes: [@sizeOf(ErrInt)]u8 = undefined;
+    mem.writeInt(bytes[0...], value, true);
+
+    var index: usize = 0;
+    while (index < bytes.len) {
+        const amt_written = posix.write(fd, &bytes[index], bytes.len - index);
+        const err = posix.getErrno(amt_written);
+        if (err > 0) {
+            switch (err) {
+                errno.EINTR => continue,
+                errno.EINVAL => unreachable,
+                else => return error.SysResources,
+            }
+        }
+        index += amt_written;
+    }
+}
+
+fn readIntFd(fd: i32) -> %ErrInt {
+    var bytes: [@sizeOf(ErrInt)]u8 = undefined;
+
+    var index: usize = 0;
+    while (index < bytes.len) {
+        const amt_written = posix.read(fd, &bytes[index], bytes.len - index);
+        const err = posix.getErrno(amt_written);
+        if (err > 0) {
+            switch (err) {
+                errno.EINTR => continue,
+                errno.EINVAL => unreachable,
+                else => return error.SysResources,
+            }
+        }
+        index += amt_written;
+    }
+
+    return mem.readInt(bytes[0...], ErrInt, true);
+}
+
std/os/index.zig
@@ -9,6 +9,7 @@ pub const posix = switch(@compileVar("os")) {
 };
 
 pub const max_noalloc_path_len = 1024;
+pub const ChildProcess = @import("child_process.zig").ChildProcess;
 
 const debug = @import("../debug.zig");
 const assert = debug.assert;
@@ -20,7 +21,6 @@ const c = @import("../c/index.zig");
 const mem = @import("../mem.zig");
 const Allocator = mem.Allocator;
 
-const io = @import("../io.zig");
 const HashMap = @import("../hash_map.zig").HashMap;
 const cstr = @import("../cstr.zig");
 
@@ -96,23 +96,6 @@ pub coldcc fn abort() -> noreturn {
     }
 }
 
-fn makePipe() -> %[2]i32 {
-    var fds: [2]i32 = undefined;
-    const err = posix.getErrno(posix.pipe(&fds));
-    if (err > 0) {
-        return switch (err) {
-            errno.EMFILE, errno.ENFILE => error.SysResources,
-            else => error.Unexpected,
-        }
-    }
-    return fds;
-}
-
-fn destroyPipe(pipe: &const [2]i32) {
-    posixClose((*pipe)[0]);
-    posixClose((*pipe)[1]);
-}
-
 /// Calls POSIX close, and keeps trying if it gets interrupted.
 pub fn posixClose(fd: i32) {
     while (true) {
@@ -202,54 +185,7 @@ pub fn posixOpen(path: []const u8, flags: usize, perm: usize, allocator: ?&Alloc
     }
 }
 
-const ErrInt = @intType(false, @sizeOf(error) * 8);
-fn writeIntFd(fd: i32, value: ErrInt) -> %void {
-    var bytes: [@sizeOf(ErrInt)]u8 = undefined;
-    mem.writeInt(bytes[0...], value, true);
-
-    var index: usize = 0;
-    while (index < bytes.len) {
-        const amt_written = posix.write(fd, &bytes[index], bytes.len - index);
-        const err = posix.getErrno(amt_written);
-        if (err > 0) {
-            switch (err) {
-                errno.EINTR => continue,
-                errno.EINVAL => unreachable,
-                else => return error.SysResources,
-            }
-        }
-        index += amt_written;
-    }
-}
-
-fn readIntFd(fd: i32) -> %ErrInt {
-    var bytes: [@sizeOf(ErrInt)]u8 = undefined;
-
-    var index: usize = 0;
-    while (index < bytes.len) {
-        const amt_written = posix.read(fd, &bytes[index], bytes.len - index);
-        const err = posix.getErrno(amt_written);
-        if (err > 0) {
-            switch (err) {
-                errno.EINTR => continue,
-                errno.EINVAL => unreachable,
-                else => return error.SysResources,
-            }
-        }
-        index += amt_written;
-    }
-
-    return mem.readInt(bytes[0...], ErrInt, true);
-}
-
-// Child of fork calls this to report an error to the fork parent.
-// Then the child exits.
-fn forkChildErrReport(fd: i32, err: error) -> noreturn {
-    _ = writeIntFd(fd, ErrInt(err));
-    posix.exit(1);
-}
-
-fn dup2NoIntr(old_fd: i32, new_fd: i32) -> %void {
+pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void {
     while (true) {
         const err = posix.getErrno(posix.dup2(old_fd, new_fd));
         if (err > 0) {
@@ -264,218 +200,13 @@ fn dup2NoIntr(old_fd: i32, new_fd: i32) -> %void {
     }
 }
 
-pub const ChildProcess = struct {
-    pid: i32,
-    err_pipe: [2]i32,
-
-    stdin: ?io.OutStream,
-    stdout: ?io.InStream,
-    stderr: ?io.InStream,
-
-    pub const Term = enum {
-        Clean: i32,
-        Signal: i32,
-        Stopped: i32,
-        Unknown: i32,
-    };
-
-    pub const StdIo = enum {
-        Inherit,
-        Ignore,
-        Pipe,
-        Close,
-    };
-
-    pub fn spawn(exe_path: []const u8, args: []const []const u8, env_map: &const EnvMap,
-        stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess
-    {
-        switch (@compileVar("os")) {
-            Os.linux, Os.macosx, Os.ios, Os.darwin => {
-                return spawnPosix(exe_path, args, env_map, stdin, stdout, stderr, allocator);
-            },
-            else => @compileError("Unsupported OS"),
-        }
-    }
-
-    pub fn wait(self: &ChildProcess) -> %Term {
-        defer {
-            posixClose(self.err_pipe[0]);
-            posixClose(self.err_pipe[1]);
-        };
-
-        var status: i32 = undefined;
-        while (true) {
-            const err = posix.getErrno(posix.waitpid(self.pid, &status, 0));
-            if (err > 0) {
-                switch (err) {
-                    errno.EINVAL, errno.ECHILD => unreachable,
-                    errno.EINTR => continue,
-                    else => {
-                        if (const *stdin ?= self.stdin) { stdin.close(); }
-                        if (const *stdout ?= self.stdin) { stdout.close(); }
-                        if (const *stderr ?= self.stdin) { stderr.close(); }
-                        return error.Unexpected;
-                    },
-                }
-            }
-            break;
-        }
-
-        if (const *stdin ?= self.stdin) { stdin.close(); }
-        if (const *stdout ?= self.stdin) { stdout.close(); }
-        if (const *stderr ?= self.stdin) { stderr.close(); }
-
-        // Write @maxValue(ErrInt) to the write end of the err_pipe. This is after
-        // waitpid, so this write is guaranteed to be after the child
-        // pid potentially wrote an error. This way we can do a blocking
-        // read on the error pipe and either get @maxValue(ErrInt) (no error) or
-        // an error code.
-        %return writeIntFd(self.err_pipe[1], @maxValue(ErrInt));
-        const err_int = %return readIntFd(self.err_pipe[0]);
-        // Here we potentially return the fork child's error
-        // from the parent pid.
-        if (err_int != @maxValue(ErrInt)) {
-            return error(err_int);
-        }
-
-        return statusToTerm(status);
-    }
-
-    fn statusToTerm(status: i32) -> Term {
-        return if (posix.WIFEXITED(status)) {
-            Term.Clean { posix.WEXITSTATUS(status) }
-        } else if (posix.WIFSIGNALED(status)) {
-            Term.Signal { posix.WTERMSIG(status) }
-        } else if (posix.WIFSTOPPED(status)) {
-            Term.Stopped { posix.WSTOPSIG(status) }
-        } else {
-            Term.Unknown { status }
-        };
-    }
-
-    fn spawnPosix(exe_path: []const u8, args: []const []const u8, env_map: &const EnvMap,
-        stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess
-    {
-        // TODO issue #295
-        //const stdin_pipe = if (stdin == StdIo.Pipe) %return makePipe() else undefined;
-        var stdin_pipe: [2]i32 = undefined;
-        if (stdin == StdIo.Pipe)
-            stdin_pipe = %return makePipe();
-        %defer if (stdin == StdIo.Pipe) { destroyPipe(stdin_pipe); };
-
-        // TODO issue #295
-        //const stdout_pipe = if (stdout == StdIo.Pipe) %return makePipe() else undefined;
-        var stdout_pipe: [2]i32 = undefined;
-        if (stdout == StdIo.Pipe) 
-            stdout_pipe = %return makePipe();
-        %defer if (stdout == StdIo.Pipe) { destroyPipe(stdout_pipe); };
-
-        // TODO issue #295
-        //const stderr_pipe = if (stderr == StdIo.Pipe) %return makePipe() else undefined;
-        var stderr_pipe: [2]i32 = undefined;
-        if (stderr == StdIo.Pipe) 
-            stderr_pipe = %return makePipe();
-        %defer if (stderr == StdIo.Pipe) { destroyPipe(stderr_pipe); };
-
-        const any_ignore = (stdin == StdIo.Ignore or stdout == StdIo.Ignore or stderr == StdIo.Ignore);
-        // TODO issue #295
-        //const dev_null_fd = if (any_ignore) {
-        //    %return posixOpen("/dev/null", posix.O_RDWR, 0, null)
-        //} else {
-        //    undefined
-        //};
-        var dev_null_fd: i32 = undefined;
-        if (any_ignore)
-            dev_null_fd = %return posixOpen("/dev/null", posix.O_RDWR, 0, null);
-
-        // 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 pid = posix.fork();
-        const pid_err = linux.getErrno(pid);
-        if (pid_err > 0) {
-            return switch (pid_err) {
-                errno.EAGAIN, errno.ENOMEM, errno.ENOSYS => error.SysResources,
-                else => error.Unexpected,
-            };
-        }
-        if (pid == 0) {
-            // we are the child
-            setUpChildIo(stdin, 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) %%
-                |err| forkChildErrReport(err_pipe[1], err);
-            setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %%
-                |err| forkChildErrReport(err_pipe[1], err);
-
-            const err = posix.getErrno(%return execve(exe_path, args, env_map, allocator));
-            assert(err > 0);
-            forkChildErrReport(err_pipe[1], switch (err) {
-                errno.EFAULT => unreachable,
-                errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SysResources,
-                errno.EACCES, errno.EPERM => error.AccessDenied,
-                errno.EINVAL, errno.ENOEXEC => error.InvalidExe,
-                errno.EIO, errno.ELOOP => error.FileSystem,
-                errno.EISDIR => error.IsDir,
-                errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
-                errno.ETXTBSY => error.FileBusy,
-                else => error.Unexpected,
-            });
-        }
-
-        // we are the parent
-        if (stdin == StdIo.Pipe) { posixClose(stdin_pipe[0]); }
-        if (stdout == StdIo.Pipe) { posixClose(stdout_pipe[1]); }
-        if (stderr == StdIo.Pipe) { posixClose(stderr_pipe[1]); }
-        if (any_ignore) { posixClose(dev_null_fd); }
-
-        return ChildProcess {
-            .pid = i32(pid),
-            .err_pipe = err_pipe,
-
-            .stdin = if (stdin == StdIo.Pipe) {
-                io.OutStream {
-                    .fd = stdin_pipe[1],
-                    .buffer = undefined,
-                    .index = 0,
-                }
-            } else {
-                null
-            },
-            .stdout = if (stdout == StdIo.Pipe) {
-                io.InStream {
-                    .fd = stdout_pipe[0],
-                }
-            } else {
-                null
-            },
-            .stderr = if (stderr == StdIo.Pipe) {
-                io.InStream {
-                    .fd = stderr_pipe[0],
-                }
-            } else {
-                null
-            },
-        };
-    }
-
-    fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void {
-        switch (stdio) {
-            StdIo.Pipe => %return dup2NoIntr(pipe_fd, std_fileno),
-            StdIo.Close => posixClose(std_fileno),
-            StdIo.Inherit => {},
-            StdIo.Ignore => %return dup2NoIntr(dev_null_fd, std_fileno),
-        }
-    }
-};
-
 /// 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 path.
-fn execve(path: []const u8, argv: []const []const u8, env_map: &const EnvMap, allocator: &Allocator) -> %usize {
+pub fn posixExecve(path: []const u8, argv: []const []const u8, env_map: &const EnvMap,
+    allocator: &Allocator) -> %usize
+{
     const path_buf = %return allocator.alloc(u8, path.len + 1);
     defer allocator.free(path_buf);
     @memcpy(&path_buf[0], &path[0], path.len);
@@ -604,6 +335,19 @@ pub const EnvMap = struct {
         mem.copy(u8, result, value);
         return result;
     }
+
+    fn hash_slice_u8(k: []const u8) -> u32 {
+        // FNV 32-bit hash
+        var h: u32 = 2166136261;
+        for (k) |b| {
+            h = (h ^ b) *% 16777619;
+        }
+        return h;
+    }
+
+    fn eql_slice_u8(a: []const u8, b: []const u8) -> bool {
+        return mem.eql(u8, a, b);
+    }
 };
 
 pub fn getEnvMap(allocator: &Allocator) -> %EnvMap {
@@ -641,15 +385,14 @@ pub fn getEnv(key: []const u8) -> ?[]const u8 {
     return null;
 }
 
-fn hash_slice_u8(k: []const u8) -> u32 {
-    // FNV 32-bit hash
-    var h: u32 = 2166136261;
-    for (k) |b| {
-        h = (h ^ b) *% 16777619;
-    }
-    return h;
-}
+pub const args = struct {
+    pub var raw: []&u8 = undefined;
 
-fn eql_slice_u8(a: []const u8, b: []const u8) -> bool {
-    return mem.eql(u8, a, b);
-}
+    pub fn count() -> usize {
+        return raw.len;
+    }
+    pub fn at(i: usize) -> []const u8 {
+        const s = raw[i];
+        return s[0...cstr.len(s)];
+    }
+};
std/special/bootstrap.zig
@@ -37,20 +37,14 @@ fn callMainAndExit() -> noreturn {
     exit(0);
 }
 
-var args_data: [32][]u8 = undefined;
 fn callMain(argc: usize, argv: &&u8, envp: &?&u8) -> %void {
-    // TODO create args API to make it work with > 32 args
-    const args = args_data[0...argc];
-    for (args) |_, i| {
-        const ptr = argv[i];
-        args[i] = ptr[0...std.cstr.len(ptr)];
-    }
+    std.os.args.raw = argv[0...argc];
 
     var env_count: usize = 0;
     while (envp[env_count] != null; env_count += 1) {}
     std.os.environ_raw = @ptrcast(&&u8, envp)[0...env_count];
 
-    return root.main(args);
+    return root.main();
 }
 
 export fn main(c_argc: i32, c_argv: &&u8, c_envp: &?&u8) -> i32 {
std/special/build_runner.zig
@@ -1,18 +1,19 @@
 const root = @import("@build");
 const std = @import("std");
 const io = std.io;
+const os = std.os;
 const Builder = std.build.Builder;
 const mem = std.mem;
 
 error InvalidArgs;
 
-pub fn main(args: [][]u8) -> %void {
-    if (args.len < 2) {
+pub fn main() -> %void {
+    if (os.args.count() < 2) {
         %%io.stderr.printf("Expected first argument to be path to zig compiler\n");
         return error.InvalidArgs;
     }
-    const zig_exe = args[1];
-    const leftover_args = args[2...];
+    const zig_exe = os.args.at(1);
+    const leftover_arg_index = 2;
 
     // TODO use a more general purpose allocator here
     var inc_allocator = %%mem.IncrementingAllocator.init(10 * 1024 * 1024);
@@ -20,5 +21,5 @@ pub fn main(args: [][]u8) -> %void {
 
     var builder = Builder.init(zig_exe, &inc_allocator.allocator);
     root.build(&builder);
-    %return builder.make(leftover_args);
+    %return builder.make(leftover_arg_index);
 }
std/special/test_runner.zig
@@ -7,7 +7,7 @@ const TestFn = struct {
 
 extern var zig_test_fn_list: []TestFn;
 
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     for (zig_test_fn_list) |testFn, i| {
         %%io.stderr.printf("Test {}/{} {}...", i + 1, zig_test_fn_list.len, testFn.name);
 
std/build.zig
@@ -39,11 +39,13 @@ pub const Builder = struct {
         return exe;
     }
 
-    pub fn make(self: &Builder, cli_args: []const []const u8) -> %void {
+    pub fn make(self: &Builder, leftover_arg_index: usize) -> %void {
         var env_map = %return os.getEnvMap(self.allocator);
 
         var verbose = false;
-        for (cli_args) |arg| {
+        var arg_i: usize = leftover_arg_index;
+        while (arg_i < os.args.count(); arg_i += 1) {
+            const arg = os.args.at(arg_i);
             if (mem.eql(u8, arg, "--verbose")) {
                 verbose = true;
             } else {
@@ -95,7 +97,8 @@ pub const Builder = struct {
             }
 
             printInvocation(self.zig_exe, zig_args);
-            var child = %return os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), env_map,
+            // TODO issue #301
+            var child = %return os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), &env_map,
                 StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator);
             const term = %return child.wait();
             switch (term) {
test/run_tests.cpp
@@ -215,7 +215,7 @@ export fn main(argc: c_int, argv: &&u8) -> c_int {
 use @import("std").io;
 use @import("foo.zig");
 
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     privateFunction();
     %%stdout.printf("OK 2\n");
 }
@@ -245,7 +245,7 @@ pub fn printText() {
 use @import("foo.zig");
 use @import("bar.zig");
 
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     foo_function();
     bar_function();
 }
@@ -281,7 +281,7 @@ pub fn foo_function() -> bool {
         TestCase *tc = add_simple_case("two files use import each other", R"SOURCE(
 use @import("a.zig");
 
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     ok();
 }
         )SOURCE", "OK\n");
@@ -309,7 +309,7 @@ pub const b_text = a_text;
     add_simple_case("hello world without libc", R"SOURCE(
 const io = @import("std").io;
 
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     %%io.stdout.printf("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a'));
 }
     )SOURCE", "Hello, world!\n0012 012 a\n");
@@ -446,7 +446,7 @@ const io = @import("std").io;
 const z = io.stdin_fileno;
 const x : @typeOf(y) = 1234;
 const y : u16 = 5678;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     var x_local : i32 = print_ok(x);
 }
 fn print_ok(val: @typeOf(x)) -> @typeOf(foo) {
@@ -471,7 +471,7 @@ export fn compare_fn(a: ?&const c_void, b: ?&const c_void) -> c_int {
     }
 }
 
-export fn main(args: c_int, argv: &&u8) -> c_int {
+export fn main() -> c_int {
     var array = []u32 { 1, 7, 3, 2, 0, 9, 4, 8, 6, 5 };
 
     c.qsort(@ptrcast(&c_void, &array[0]), c_ulong(array.len), @sizeOf(i32), compare_fn);
@@ -516,7 +516,7 @@ const Bar = struct {
     fn method(b: &const Bar) -> bool { true }
 };
 
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const bar = Bar {.field2 = 13,};
     const foo = Foo {.field1 = bar,};
     if (!foo.method()) {
@@ -532,7 +532,7 @@ pub fn main(args: [][]u8) -> %void {
 
     add_simple_case("defer with only fallthrough", R"SOURCE(
 const io = @import("std").io;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     %%io.stdout.printf("before\n");
     defer %%io.stdout.printf("defer1\n");
     defer %%io.stdout.printf("defer2\n");
@@ -544,11 +544,12 @@ pub fn main(args: [][]u8) -> %void {
 
     add_simple_case("defer with return", R"SOURCE(
 const io = @import("std").io;
-pub fn main(args: [][]u8) -> %void {
+const os = @import("std").os;
+pub fn main() -> %void {
     %%io.stdout.printf("before\n");
     defer %%io.stdout.printf("defer1\n");
     defer %%io.stdout.printf("defer2\n");
-    if (args.len == 1) return;
+    if (os.args.count() == 1) return;
     defer %%io.stdout.printf("defer3\n");
     %%io.stdout.printf("after\n");
 }
@@ -557,7 +558,7 @@ pub fn main(args: [][]u8) -> %void {
 
     add_simple_case("%defer and it fails", R"SOURCE(
 const io = @import("std").io;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     do_test() %% return;
 }
 fn do_test() -> %void {
@@ -577,7 +578,7 @@ fn its_gonna_fail() -> %void {
 
     add_simple_case("%defer and it passes", R"SOURCE(
 const io = @import("std").io;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     do_test() %% return;
 }
 fn do_test() -> %void {
@@ -597,7 +598,7 @@ fn its_gonna_pass() -> %void { }
 const foo_txt = @embedFile("foo.txt");
 const io = @import("std").io;
 
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     %%io.stdout.printf(foo_txt);
 }
         )SOURCE", "1234\nabcd\n");
@@ -1388,9 +1389,13 @@ fn something() -> %void { }
             ".tmp_source.zig:3:5: error: expected type 'void', found 'error'");
 
     add_compile_fail_case("wrong return type for main", R"SOURCE(
-pub fn main(args: [][]u8) { }
-    )SOURCE", 1, ".tmp_source.zig:2:27: error: expected return type of main to be '%void', instead is 'void'");
+pub fn main() { }
+    )SOURCE", 1, ".tmp_source.zig:2:15: error: expected return type of main to be '%void', instead is 'void'");
 
+    add_compile_fail_case("double ?? on main return value", R"SOURCE(
+pub fn main() -> ??void {
+}
+    )SOURCE", 1, ".tmp_source.zig:2:18: error: expected return type of main to be '%void', instead is '??void'");
 
     add_compile_fail_case("invalid pointer for var type", R"SOURCE(
 extern fn ext() -> usize;
@@ -1689,11 +1694,6 @@ fn bar(a: i32, b: []const u8) {
         ".tmp_source.zig:8:5: error: found compile log statement",
         ".tmp_source.zig:3:17: note: called from here");
 
-    add_compile_fail_case("double ?? on main return value", R"SOURCE(
-pub fn main(args: [][]u8) -> ??void {
-}
-    )SOURCE", 1, ".tmp_source.zig:2:30: error: expected return type of main to be '%void', instead is '??void'");
-
     add_compile_fail_case("casting bit offset pointer to regular pointer", R"SOURCE(
 const u2 = @intType(false, 2);
 const u3 = @intType(false, 3);
@@ -1844,7 +1844,7 @@ pub fn panic(message: []const u8) -> noreturn {
     @breakpoint();
     while (true) {}
 }
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     if (!@compileVar("is_release")) {
         @panic("oh no");
     }
@@ -1856,7 +1856,7 @@ pub fn panic(message: []const u8) -> noreturn {
     @breakpoint();
     while (true) {}
 }
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const a = []i32{1, 2, 3, 4};
     baz(bar(a));
 }
@@ -1872,7 +1872,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = add(65530, 10);
     if (x == 0) return error.Whatever;
 }
@@ -1887,7 +1887,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = sub(10, 20);
     if (x == 0) return error.Whatever;
 }
@@ -1902,7 +1902,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = mul(300, 6000);
     if (x == 0) return error.Whatever;
 }
@@ -1917,7 +1917,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = neg(-32768);
     if (x == 32767) return error.Whatever;
 }
@@ -1932,7 +1932,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = div(-32768, -1);
     if (x == 32767) return error.Whatever;
 }
@@ -1947,7 +1947,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = shl(-16385, 1);
     if (x == 0) return error.Whatever;
 }
@@ -1962,7 +1962,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = shl(0b0010111111111111, 3);
     if (x == 0) return error.Whatever;
 }
@@ -1977,7 +1977,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = div0(999, 0);
 }
 fn div0(a: i32, b: i32) -> i32 {
@@ -1991,7 +1991,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = divExact(10, 3);
     if (x == 0) return error.Whatever;
 }
@@ -2006,7 +2006,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = widenSlice([]u8{1, 2, 3, 4, 5});
     if (x.len == 0) return error.Whatever;
 }
@@ -2021,7 +2021,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = shorten_cast(200);
     if (x == 0) return error.Whatever;
 }
@@ -2036,7 +2036,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     const x = unsigned_cast(-10);
     if (x == 0) return error.Whatever;
 }
@@ -2051,7 +2051,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 error Whatever;
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     %%bar();
 }
 fn bar() -> %void {
@@ -2064,7 +2064,7 @@ pub fn panic(message: []const u8) -> noreturn {
     @breakpoint();
     while (true) {}
 }
-pub fn main(args: [][]u8) -> %void {
+pub fn main() -> %void {
     _ = bar(9999);
 }
 fn bar(x: u32) -> error {
@@ -2500,25 +2500,13 @@ static void run_test(TestCase *test_case) {
     }
 }
 
-static void run_all_tests(bool reverse) {
-    if (reverse) {
-        for (size_t i = test_cases.length;;) {
-            TestCase *test_case = test_cases.at(i);
-            printf("Test %zu/%zu %s...", i + 1, test_cases.length, test_case->case_name);
-            fflush(stdout);
-            run_test(test_case);
-            printf("OK\n");
-            if (i == 0) break;
-            i -= 1;
-        }
-    } else {
-        for (size_t i = 0; i < test_cases.length; i += 1) {
-            TestCase *test_case = test_cases.at(i);
-            printf("Test %zu/%zu %s...", i + 1, test_cases.length, test_case->case_name);
-            fflush(stdout);
-            run_test(test_case);
-            printf("OK\n");
-        }
+static void run_all_tests(void) {
+    for (size_t i = 0; i < test_cases.length; i += 1) {
+        TestCase *test_case = test_cases.at(i);
+        printf("Test %zu/%zu %s...", i + 1, test_cases.length, test_case->case_name);
+        fflush(stdout);
+        run_test(test_case);
+        printf("OK\n");
     }
     printf("%zu tests passed.\n", test_cases.length);
 }
@@ -2530,18 +2518,13 @@ static void cleanup(void) {
 }
 
 static int usage(const char *arg0) {
-    fprintf(stderr, "Usage: %s [--reverse]\n", arg0);
+    fprintf(stderr, "Usage: %s\n", arg0);
     return 1;
 }
 
 int main(int argc, char **argv) {
-    bool reverse = false;
     for (int i = 1; i < argc; i += 1) {
-        if (strcmp(argv[i], "--reverse") == 0) {
-            reverse = true;
-        } else {
-            return usage(argv[0]);
-        }
+        return usage(argv[0]);
     }
     add_compiling_test_cases();
     add_debug_safety_test_cases();
@@ -2549,6 +2532,6 @@ int main(int argc, char **argv) {
     add_parseh_test_cases();
     add_self_hosted_tests();
     add_std_lib_tests();
-    run_all_tests(reverse);
+    run_all_tests();
     cleanup();
 }
CMakeLists.txt
@@ -223,6 +223,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/list.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/math.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/mem.zig" DESTINATION "${ZIG_STD_DEST}")
 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_x86_64.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/errno.zig" DESTINATION "${ZIG_STD_DEST}/os")