Commit 8d3eaab871

Andrew Kelley <superjoe30@gmail.com>
2017-10-13 15:31:03
implement std.os.ChildProcess for windows
1 parent 7f9dc4e
std/os/windows/index.zig
@@ -14,6 +14,14 @@ pub extern "kernel32" stdcallcc fn CreateFileA(lpFileName: LPCSTR, dwDesiredAcce
     dwShareMode: DWORD, lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, dwCreationDisposition: DWORD,
         dwFlagsAndAttributes: DWORD, hTemplateFile: ?HANDLE) -> HANDLE;
 
+pub extern "kernel32" stdcallcc fn CreatePipe(hReadPipe: &HANDLE, hWritePipe: &HANDLE,
+    lpPipeAttributes: &SECURITY_ATTRIBUTES, nSize: DWORD) -> BOOL;
+
+pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lpCommandLine: LPSTR,
+    lpProcessAttributes: ?&SECURITY_ATTRIBUTES, lpThreadAttributes: ?&SECURITY_ATTRIBUTES, bInheritHandles: BOOL,
+    dwCreationFlags: DWORD, lpEnvironment: ?LPVOID, lpCurrentDirectory: ?LPCSTR, lpStartupInfo: &STARTUPINFOA,
+    lpProcessInformation: &PROCESS_INFORMATION) -> BOOL;
+
 pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) -> bool;
 
 pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) -> noreturn;
@@ -24,11 +32,10 @@ pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out
 
 pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) -> DWORD;
 
-/// Retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis.
-/// Multiple threads do not overwrite each other's last-error code.
+pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCode: &DWORD) -> BOOL;
+
 pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD;
 
-/// Retrieves file information for the specified file.
 pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE,
     in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, out_lpFileInformation: &c_void,
     in_dwBufferSize: DWORD) -> bool;
@@ -36,26 +43,29 @@ pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE
 pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(hFile: HANDLE, lpszFilePath: LPSTR,
   cchFilePath: DWORD, dwFlags: DWORD) -> DWORD;
 
-/// Retrieves a handle to the specified standard device (standard input, standard output, or standard error).
+pub extern "kernel32" stdcallcc fn GetProcessHeap() -> HANDLE;
+
 pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) -> ?HANDLE;
 
+pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) -> LPVOID;
+
+pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL;
+
 pub extern "kernel32" stdcallcc fn ReadFile(in_hFile: HANDLE, out_lpBuffer: LPVOID,
     in_nNumberOfBytesToRead: DWORD, out_lpNumberOfBytesRead: &DWORD,
     in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
 
-/// Writes data to the specified file or input/output (I/O) device.
-/// This function is designed for both synchronous and asynchronous operation. For a similar function designed solely for asynchronous operation, see WriteFileEx.
-pub extern "kernel32" stdcallcc fn WriteFile(in_hFile: HANDLE, in_lpBuffer: &const c_void,
-    in_nNumberOfBytesToWrite: DWORD, out_lpNumberOfBytesWritten: ?&DWORD,
-    in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
+pub extern "kernel32" stdcallcc fn SetHandleInformation(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) -> BOOL;
 
 pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD);
 
-pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) -> LPVOID;
+pub extern "kernel32" stdcallcc fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) -> BOOL;
 
-pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL;
+pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD;
 
-pub extern "kernel32" stdcallcc fn GetProcessHeap() -> HANDLE;
+pub extern "kernel32" stdcallcc fn WriteFile(in_hFile: HANDLE, in_lpBuffer: &const c_void,
+    in_nNumberOfBytesToWrite: DWORD, out_lpNumberOfBytesWritten: ?&DWORD,
+    in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
 
 pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) -> c_int;
 
@@ -88,7 +98,7 @@ pub const INT = c_int;
 pub const ULONG_PTR = usize;
 pub const WCHAR = u16;
 pub const LPCVOID = &const c_void;
-
+pub const LPBYTE = &BYTE;
 
 /// The standard input device. Initially, this is the console input buffer, CONIN$.
 pub const STD_INPUT_HANDLE = @maxValue(DWORD) - 10 + 1;
@@ -158,7 +168,7 @@ pub const VOLUME_NAME_NT = 0x2;
 
 pub const SECURITY_ATTRIBUTES = extern struct {
     nLength: DWORD,
-    lpSecurityDescriptor: LPVOID,
+    lpSecurityDescriptor: ?LPVOID,
     bInheritHandle: BOOL,
 };
 pub const PSECURITY_ATTRIBUTES = &SECURITY_ATTRIBUTES;
@@ -189,3 +199,56 @@ pub const FILE_ATTRIBUTE_OFFLINE = 0x1000;
 pub const FILE_ATTRIBUTE_READONLY = 0x1;
 pub const FILE_ATTRIBUTE_SYSTEM = 0x4;
 pub const FILE_ATTRIBUTE_TEMPORARY = 0x100;
+
+pub const PROCESS_INFORMATION = extern struct {
+    hProcess: HANDLE,
+    hThread: HANDLE,
+    dwProcessId: DWORD,
+    dwThreadId: DWORD,
+};
+
+pub const STARTUPINFOA = extern struct {
+    cb: DWORD,
+    lpReserved: ?LPSTR,
+    lpDesktop: ?LPSTR,
+    lpTitle: ?LPSTR,
+    dwX: DWORD,
+    dwY: DWORD,
+    dwXSize: DWORD,
+    dwYSize: DWORD,
+    dwXCountChars: DWORD,
+    dwYCountChars: DWORD,
+    dwFillAttribute: DWORD,
+    dwFlags: DWORD,
+    wShowWindow: WORD,
+    cbReserved2: WORD,
+    lpReserved2: ?LPBYTE,
+    hStdInput: ?HANDLE,
+    hStdOutput: ?HANDLE,
+    hStdError: ?HANDLE,
+};
+
+pub const STARTF_FORCEONFEEDBACK = 0x00000040;
+pub const STARTF_FORCEOFFFEEDBACK = 0x00000080;
+pub const STARTF_PREVENTPINNING = 0x00002000;
+pub const STARTF_RUNFULLSCREEN = 0x00000020;
+pub const STARTF_TITLEISAPPID = 0x00001000;
+pub const STARTF_TITLEISLINKNAME = 0x00000800;
+pub const STARTF_UNTRUSTEDSOURCE = 0x00008000;
+pub const STARTF_USECOUNTCHARS = 0x00000008;
+pub const STARTF_USEFILLATTRIBUTE = 0x00000010;
+pub const STARTF_USEHOTKEY = 0x00000200;
+pub const STARTF_USEPOSITION = 0x00000004;
+pub const STARTF_USESHOWWINDOW = 0x00000001;
+pub const STARTF_USESIZE = 0x00000002;
+pub const STARTF_USESTDHANDLES = 0x00000100;
+
+pub const INFINITE = 4294967295;
+
+pub const WAIT_ABANDONED = 0x00000080;
+pub const WAIT_OBJECT_0 = 0x00000000;
+pub const WAIT_TIMEOUT = 0x00000102;
+pub const WAIT_FAILED = 0xFFFFFFFF;
+
+pub const HANDLE_FLAG_INHERIT = 0x00000001;
+pub const HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002;
std/os/child_process.zig
@@ -1,22 +1,30 @@
-const io = @import("../io.zig");
-const os = @import("index.zig");
+const std = @import("../index.zig");
+const cstr = std.cstr;
+const io = std.io;
+const os = std.os;
 const posix = os.posix;
-const mem = @import("../mem.zig");
+const windows = os.windows;
+const mem = std.mem;
 const Allocator = mem.Allocator;
-const debug = @import("../debug.zig");
+const debug = std.debug;
 const assert = debug.assert;
-const BufMap = @import("../buf_map.zig").BufMap;
+const BufMap = std.BufMap;
+const Buffer = std.Buffer;
 const builtin = @import("builtin");
 const Os = builtin.Os;
-const LinkedList = @import("../linked_list.zig").LinkedList;
+const LinkedList = std.LinkedList;
 
 error PermissionDenied;
 error ProcessNotFound;
 
 var children_nodes = LinkedList(&ChildProcess).init();
 
+const is_windows = builtin.os == Os.windows;
+
 pub const ChildProcess = struct {
-    pub pid: i32,
+    pub pid: if (is_windows) void else i32,
+    pub handle: if (is_windows) windows.HANDLE else void,
+
     pub allocator: &mem.Allocator,
 
     pub stdin: ?&io.OutStream,
@@ -38,16 +46,16 @@ pub const ChildProcess = struct {
     pub stderr_behavior: StdIo,
 
     /// Set to change the user id when spawning the child process.
-    pub uid: ?u32,
+    pub uid: if (is_windows) void else ?u32,
 
     /// Set to change the group id when spawning the child process.
-    pub gid: ?u32,
+    pub gid: if (is_windows) void else ?u32,
 
     /// Set to change the current working directory when spawning the child process.
     pub cwd: ?[]const u8,
 
-    err_pipe: [2]i32,
-    llnode: LinkedList(&ChildProcess).Node,
+    err_pipe: if (is_windows) void else [2]i32,
+    llnode: if (is_windows) void else LinkedList(&ChildProcess).Node,
 
     pub const Term = enum {
         Exited: i32,
@@ -73,14 +81,15 @@ pub const ChildProcess = struct {
             .allocator = allocator,
             .argv = argv,
             .pid = undefined,
+            .handle = undefined,
             .err_pipe = undefined,
             .llnode = undefined,
             .term = null,
             .onTerm = null,
             .env_map = null,
             .cwd = null,
-            .uid = null,
-            .gid = null,
+            .uid = if (is_windows) {} else null,
+            .gid = if (is_windows) {} else null,
             .stdin = null,
             .stdout = null,
             .stderr = null,
@@ -101,9 +110,10 @@ pub const ChildProcess = struct {
     /// onTerm can be called before `spawn` returns.
     /// 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"),
+        if (is_windows) {
+            return self.spawnWindows();
+        } else {
+            return self.spawnPosix();
         };
     }
 
@@ -114,6 +124,30 @@ pub const ChildProcess = struct {
 
     /// Forcibly terminates child process and then cleans up all resources.
     pub fn kill(self: &ChildProcess) -> %Term {
+        if (is_windows) {
+            return self.killWindows(1);
+        } else {
+            return self.killPosix();
+        }
+    }
+
+    pub fn killWindows(self: &ChildProcess, exit_code: windows.UINT) -> %Term {
+        if (self.term) |term| {
+            self.cleanupStreams();
+            return term;
+        }
+
+        if (!windows.TerminateProcess(self.handle, exit_code)) {
+            const err = windows.GetLastError();
+            return switch (err) {
+                else => error.Unexpected,
+            };
+        }
+        self.waitUnwrappedWindows();
+        return ??self.term;
+    }
+
+    pub fn killPosix(self: &ChildProcess) -> %Term {
         block_SIGCHLD();
         defer restore_SIGCHLD();
 
@@ -137,6 +171,24 @@ pub const ChildProcess = struct {
 
     /// Blocks until child process terminates and then cleans up all resources.
     pub fn wait(self: &ChildProcess) -> %Term {
+        if (is_windows) {
+            return self.waitWindows();
+        } else {
+            return self.waitPosix();
+        }
+    }
+
+    fn waitWindows(self: &ChildProcess) -> %Term {
+        if (self.term) |term| {
+            self.cleanupStreams();
+            return term;
+        }
+
+        %return self.waitUnwrappedWindows();
+        return ??self.term;
+    }
+
+    fn waitPosix(self: &ChildProcess) -> %Term {
         block_SIGCHLD();
         defer restore_SIGCHLD();
 
@@ -153,6 +205,23 @@ pub const ChildProcess = struct {
         self.allocator.destroy(self);
     }
 
+    fn waitUnwrappedWindows(self: &ChildProcess) -> %void {
+        const result = os.windowsWaitSingle(self.handle, windows.INFINITE);
+
+        self.term = (%Term)({
+            var exit_code: windows.DWORD = undefined;
+            if (!windows.GetExitCodeProcess(self.handle, &exit_code)) {
+                Term.Unknown{0}
+            } else {
+                Term.Exited {@bitCast(i32, exit_code)}
+            }
+        });
+
+        os.windowsClose(self.handle);
+        self.cleanupStreams();
+        return result;
+    }
+
     fn waitUnwrapped(self: &ChildProcess) {
         var status: i32 = undefined;
         while (true) {
@@ -262,16 +331,21 @@ pub const ChildProcess = struct {
         } else {
             null
         };
+        %defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr);
+
         const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {
             %return self.allocator.create(io.InStream)
         } else {
             null
         };
+        %defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr);
+
         const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {
             %return self.allocator.create(io.InStream)
         } else {
             null
         };
+        %defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr);
 
         block_SIGCHLD();
         const pid_result = posix.fork();
@@ -355,6 +429,195 @@ pub const ChildProcess = struct {
         if (self.stderr_behavior == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); }
     }
 
+    fn spawnWindows(self: &ChildProcess) -> %void {
+        var saAttr: windows.SECURITY_ATTRIBUTES = undefined;
+        saAttr.nLength = @sizeOf(windows.SECURITY_ATTRIBUTES);
+        saAttr.bInheritHandle = true;
+        saAttr.lpSecurityDescriptor = null;
+
+        const any_ignore = (self.stdin_behavior == StdIo.Ignore or
+            self.stdout_behavior == StdIo.Ignore or
+            self.stderr_behavior == StdIo.Ignore);
+
+        const nul_handle = if (any_ignore) {
+            %return os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ,
+                windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null)
+        } else {
+            undefined
+        };
+        defer { if (any_ignore) os.windowsClose(nul_handle); };
+        if (any_ignore) {
+            %return windowsSetHandleInfo(nul_handle, windows.HANDLE_FLAG_INHERIT, 0);
+        }
+
+
+        var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
+        var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
+        switch (self.stdin_behavior) {
+            StdIo.Pipe => {
+                %return windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
+            },
+            StdIo.Ignore => {
+                g_hChildStd_IN_Rd = nul_handle;
+            },
+            StdIo.Inherit => {
+                g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE);
+            },
+            StdIo.Close => {
+                g_hChildStd_IN_Rd = null;
+            },
+        }
+        %defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); };
+
+        var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
+        var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
+        switch (self.stdout_behavior) {
+            StdIo.Pipe => {
+                %return windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
+            },
+            StdIo.Ignore => {
+                g_hChildStd_OUT_Wr = nul_handle;
+            },
+            StdIo.Inherit => {
+                g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE);
+            },
+            StdIo.Close => {
+                g_hChildStd_OUT_Wr = null;
+            },
+        }
+        %defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr); };
+
+        var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
+        var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
+        switch (self.stderr_behavior) {
+            StdIo.Pipe => {
+                %return windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
+            },
+            StdIo.Ignore => {
+                g_hChildStd_ERR_Wr = nul_handle;
+            },
+            StdIo.Inherit => {
+                g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE);
+            },
+            StdIo.Close => {
+                g_hChildStd_ERR_Wr = null;
+            },
+        }
+        %defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); };
+
+        const stdin_ptr = if (self.stdin_behavior == StdIo.Pipe) {
+            %return self.allocator.create(io.OutStream)
+        } else {
+            null
+        };
+        %defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr);
+
+        const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {
+            %return self.allocator.create(io.InStream)
+        } else {
+            null
+        };
+        %defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr);
+
+        const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {
+            %return self.allocator.create(io.InStream)
+        } else {
+            null
+        };
+        %defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr);
+
+        const cmd_line = %return windowsCreateCommandLine(self.allocator, self.argv);
+        defer self.allocator.free(cmd_line);
+
+        var siStartInfo = windows.STARTUPINFOA {
+            .cb = @sizeOf(windows.STARTUPINFOA),
+            .hStdError = g_hChildStd_ERR_Wr,
+            .hStdOutput = g_hChildStd_OUT_Wr,
+            .hStdInput = g_hChildStd_IN_Rd,
+            .dwFlags = windows.STARTF_USESTDHANDLES,
+
+            .lpReserved = null,
+            .lpDesktop = null,
+            .lpTitle = null,
+            .dwX = 0,
+            .dwY = 0,
+            .dwXSize = 0,
+            .dwYSize = 0,
+            .dwXCountChars = 0,
+            .dwYCountChars = 0,
+            .dwFillAttribute = 0,
+            .wShowWindow = 0,
+            .cbReserved2 = 0,
+            .lpReserved2 = null,
+        };
+        var piProcInfo: windows.PROCESS_INFORMATION = undefined;
+
+        const app_name = %return cstr.addNullByte(self.allocator, self.argv[0]);
+        defer self.allocator.free(app_name);
+
+        const cwd_slice = if (self.cwd) |cwd| {
+            %return cstr.addNullByte(self.allocator, cwd)
+        } else {
+            null
+        };
+        defer if (cwd_slice) |cwd| self.allocator.free(cwd);
+        const cwd_ptr = if (cwd_slice) |cwd| cwd.ptr else null;
+
+        const maybe_envp_buf = if (self.env_map) |env_map| {
+            %return os.createNullDelimitedEnvMap(self.allocator, env_map)
+        } else {
+            null
+        };
+        defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
+        const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
+
+        if (!windows.CreateProcessA(app_name.ptr, cmd_line.ptr, null, null, true, 0,
+            @ptrCast(?&c_void, envp_ptr),
+            cwd_ptr, &siStartInfo, &piProcInfo))
+        {
+            const err = windows.GetLastError();
+            return switch (err) {
+                windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
+                else => error.Unexpected,
+            };
+        }
+        os.windowsClose(piProcInfo.hThread);
+
+        if (stdin_ptr) |outstream| {
+            *outstream = io.OutStream {
+                .fd = {},
+                .handle = g_hChildStd_IN_Wr,
+                .handle_id = undefined,
+                .buffer = undefined,
+                .index = 0,
+            };
+        }
+        if (stdout_ptr) |instream| {
+            *instream = io.InStream {
+                .fd = {},
+                .handle = g_hChildStd_OUT_Rd,
+                .handle_id = undefined,
+            };
+        }
+        if (stderr_ptr) |instream| {
+            *instream = io.InStream {
+                .fd = {},
+                .handle = g_hChildStd_ERR_Rd,
+                .handle_id = undefined,
+            };
+        }
+
+        self.handle = piProcInfo.hProcess;
+        self.term = null;
+        self.stdin = stdin_ptr;
+        self.stdout = stdout_ptr;
+        self.stderr = stderr_ptr;
+
+        if (self.stdin_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_IN_Rd); }
+        if (self.stderr_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_ERR_Wr); }
+        if (self.stdout_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_OUT_Wr); }
+    }
+
     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),
@@ -365,6 +628,84 @@ pub const ChildProcess = struct {
     }
 };
 
+/// Caller must dealloc.
+/// Guarantees a null byte at result[result.len].
+fn windowsCreateCommandLine(allocator: &Allocator, argv: []const []const u8) -> %[]u8 {
+    var buf = %return Buffer.initSize(allocator, 0);
+    defer buf.deinit();
+
+    for (argv) |arg, arg_i| {
+        if (arg_i != 0)
+            %return buf.appendByte(' ');
+        if (mem.indexOfAny(u8, arg, " \t\n\"") == null) {
+            %return buf.append(arg);
+            continue;
+        }
+        %return buf.appendByte('"');
+        var backslash_count: usize = 0;
+        for (arg) |byte| {
+            switch (byte) {
+                '\\' => backslash_count += 1,
+                '"' => {
+                    %return buf.appendByteNTimes('\\', backslash_count * 2 + 1);
+                    %return buf.appendByte('"');
+                    backslash_count = 0;
+                },
+                else => {
+                    %return buf.appendByteNTimes('\\', backslash_count);
+                    %return buf.appendByte(byte);
+                    backslash_count = 0;
+                },
+            }
+        }
+        %return buf.appendByteNTimes('\\', backslash_count * 2);
+        %return buf.appendByte('"');
+    }
+
+    return buf.toOwnedSlice();
+}
+
+fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) {
+    if (rd) |h| os.windowsClose(h);
+    if (wr) |h| os.windowsClose(h);
+}
+
+fn windowsMakePipe(rd: &windows.HANDLE, wr: &windows.HANDLE, sattr: &windows.SECURITY_ATTRIBUTES) -> %void {
+    if (!windows.CreatePipe(rd, wr, sattr, 0)) {
+        const err = windows.GetLastError();
+        return switch (err) {
+            else => error.Unexpected,
+        };
+    }
+}
+
+fn windowsSetHandleInfo(h: windows.HANDLE, mask: windows.DWORD, flags: windows.DWORD) -> %void {
+    if (!windows.SetHandleInformation(h, mask, flags)) {
+        const err = windows.GetLastError();
+        return switch (err) {
+            else => error.Unexpected,
+        };
+    }
+}
+
+fn windowsMakePipeIn(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &windows.SECURITY_ATTRIBUTES) -> %void {
+    var rd_h: windows.HANDLE = undefined;
+    var wr_h: windows.HANDLE = undefined;
+    %return windowsMakePipe(&rd_h, &wr_h, sattr);
+    %return windowsSetHandleInfo(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
+    *rd = rd_h;
+    *wr = wr_h;
+}
+
+fn windowsMakePipeOut(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &windows.SECURITY_ATTRIBUTES) -> %void {
+    var rd_h: windows.HANDLE = undefined;
+    var wr_h: windows.HANDLE = undefined;
+    %return windowsMakePipe(&rd_h, &wr_h, sattr);
+    %return windowsSetHandleInfo(rd_h, windows.HANDLE_FLAG_INHERIT, 0);
+    *rd = rd_h;
+    *wr = wr_h;
+}
+
 fn makePipe() -> %[2]i32 {
     var fds: [2]i32 = undefined;
     const err = posix.getErrno(posix.pipe(&fds));
std/os/index.zig
@@ -382,6 +382,37 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void {
     }
 }
 
+pub fn createNullDelimitedEnvMap(allocator: &Allocator, env_map: &const BufMap) -> %[]?&u8 {
+    const envp_count = env_map.count();
+    const envp_buf = %return allocator.alloc(?&u8, envp_count + 1);
+    mem.set(?&u8, envp_buf, null);
+    %defer freeNullDelimitedEnvMap(allocator, envp_buf);
+    {
+        var it = env_map.iterator();
+        var i: usize = 0;
+        while (it.next()) |pair| : (i += 1) {
+            const env_buf = %return allocator.alloc(u8, pair.key.len + pair.value.len + 2);
+            @memcpy(&env_buf[0], pair.key.ptr, pair.key.len);
+            env_buf[pair.key.len] = '=';
+            @memcpy(&env_buf[pair.key.len + 1], pair.value.ptr, pair.value.len);
+            env_buf[env_buf.len - 1] = 0;
+
+            envp_buf[i] = env_buf.ptr;
+        }
+        assert(i == envp_count);
+    }
+    assert(envp_buf[envp_count] == null);
+    return envp_buf;
+}
+
+pub fn freeNullDelimitedEnvMap(allocator: &Allocator, envp_buf: []?&u8) {
+    for (envp_buf) |env| {
+        const env_buf = if (env) |ptr| cstr.toSlice(ptr) else break;
+        allocator.free(env_buf);
+    }
+    allocator.free(envp_buf);
+}
+
 /// 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.
@@ -408,31 +439,8 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
     }
     argv_buf[argv.len] = null;
 
-    const envp_count = env_map.count();
-    const envp_buf = %return allocator.alloc(?&u8, envp_count + 1);
-    mem.set(?&u8, envp_buf, null);
-    defer {
-        for (envp_buf) |env| {
-            const env_buf = if (env) |ptr| cstr.toSlice(ptr) else break;
-            allocator.free(env_buf);
-        }
-        allocator.free(envp_buf);
-    }
-    {
-        var it = env_map.iterator();
-        var i: usize = 0;
-        while (it.next()) |pair| : (i += 1) {
-            const env_buf = %return allocator.alloc(u8, pair.key.len + pair.value.len + 2);
-            @memcpy(&env_buf[0], pair.key.ptr, pair.key.len);
-            env_buf[pair.key.len] = '=';
-            @memcpy(&env_buf[pair.key.len + 1], pair.value.ptr, pair.value.len);
-            env_buf[env_buf.len - 1] = 0;
-
-            envp_buf[i] = env_buf.ptr;
-        }
-        assert(i == envp_count);
-    }
-    envp_buf[envp_count] = null;
+    const envp_buf = %return createNullDelimitedEnvMap(allocator, env_map);
+    defer freeNullDelimitedEnvMap(allocator, envp_buf);
 
     const exe_path = argv[0];
     if (mem.indexOfScalar(u8, exe_path, '/') != null) {
@@ -1367,6 +1375,22 @@ fn testWindowsCmdLine(input_cmd_line: &const u8, expected_args: []const []const
     assert(it.next(&debug.global_allocator) == null);
 }
 
+error WaitAbandoned;
+error WaitTimeOut;
+
+pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) -> %void {
+    const result = windows.WaitForSingleObject(handle, milliseconds);
+    return switch (result) {
+        windows.WAIT_ABANDONED => error.WaitAbandoned,
+        windows.WAIT_OBJECT_0 => {},
+        windows.WAIT_TIMEOUT => error.WaitTimeOut,
+        windows.WAIT_FAILED => switch (windows.GetLastError()) {
+            else => error.Unexpected,
+        },
+        else => error.Unexpected,
+    };
+}
+
 test "std.os" {
     _ = @import("child_process.zig");
     _ = @import("darwin_errno.zig");
std/buf_set.zig
@@ -7,9 +7,9 @@ pub const BufSet = struct {
 
     const BufSetHashMap = HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);
 
-    pub fn init(allocator: &Allocator) -> BufSet {
+    pub fn init(a: &Allocator) -> BufSet {
         var self = BufSet {
-            .hash_map = BufSetHashMap.init(allocator),
+            .hash_map = BufSetHashMap.init(a),
         };
         return self;
     }
@@ -45,6 +45,10 @@ pub const BufSet = struct {
         return self.hash_map.iterator();
     }
 
+    pub fn allocator(self: &const BufSet) -> &Allocator {
+        return self.hash_map.allocator;
+    }
+
     fn free(self: &BufSet, value: []const u8) {
         // remove the const
         const mut_value = @ptrCast(&u8, value.ptr)[0..value.len];
std/buffer.zig
@@ -91,8 +91,17 @@ pub const Buffer = struct {
     }
 
     pub fn appendByte(self: &Buffer, byte: u8) -> %void {
-        %return self.resize(self.len() + 1);
-        self.list.items[self.len() - 1] = byte;
+        return self.appendByteNTimes(byte, 1);
+    }
+
+    pub fn appendByteNTimes(self: &Buffer, byte: u8, count: usize) -> %void {
+        var prev_size: usize = self.len();
+        %return self.resize(prev_size + count);
+
+        var i: usize = 0;
+        while (i < count) : (i += 1) {
+            self.list.items[prev_size + i] = byte;
+        }
     }
 
     pub fn eql(self: &const Buffer, m: []const u8) -> bool {
std/cstr.zig
@@ -1,4 +1,5 @@
 const debug = @import("debug.zig");
+const mem = @import("mem.zig");
 const assert = debug.assert;
 
 pub fn len(ptr: &const u8) -> usize {
@@ -36,3 +37,13 @@ fn testCStrFnsImpl() {
     assert(cmp(c"aoeu", c"aoez") == -1);
     assert(len(c"123456789") == 9);
 }
+
+/// Returns a mutable slice with exactly the same size which is guaranteed to
+/// have a null byte after it.
+/// Caller owns the returned memory.
+pub fn addNullByte(allocator: &mem.Allocator, slice: []const u8) -> %[]u8 {
+    const result = %return allocator.alloc(u8, slice.len + 1);
+    mem.copy(u8, result, slice);
+    result[slice.len] = 0;
+    return result[0..slice.len];
+}
std/mem.zig
@@ -254,6 +254,21 @@ pub fn indexOfScalarPos(comptime T: type, slice: []const T, start_index: usize,
     return null;
 }
 
+pub fn indexOfAny(comptime T: type, slice: []const T, values: []const T) -> ?usize {
+    return indexOfAnyPos(T, slice, 0, values);
+}
+
+pub fn indexOfAnyPos(comptime T: type, slice: []const T, start_index: usize, values: []const T) -> ?usize {
+    var i: usize = start_index;
+    while (i < slice.len) : (i += 1) {
+        for (values) |value| {
+            if (slice[i] == value)
+                return i;
+        }
+    }
+    return null;
+}
+
 pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize {
     return indexOfPos(T, haystack, 0, needle);
 }
README.md
@@ -18,7 +18,7 @@ clarity.
    writing buggy code.
  * Debug mode optimizes for fast compilation time and crashing with a stack trace
    when undefined behavior *would* happen.
- * Release mode produces heavily optimized code. What other projects call
+ * ReleaseFast mode produces heavily optimized code. What other projects call
    "Link Time Optimization" Zig does automatically.
  * Compatible with C libraries with no wrapper necessary. Directly include
    C .h files and get access to the functions and symbols therein.
@@ -36,16 +36,13 @@ clarity.
    a preprocessor or macros.
  * The binaries produced by Zig have complete debugging information so you can,
    for example, use GDB to debug your software.
- * Mark functions as tests and automatically run them with `zig test`.
+ * Built-in unit tests with `zig test`.
  * Friendly toward package maintainers. Reproducible build, bootstrapping
    process carefully documented. Issues filed by package maintainers are
    considered especially important.
  * Cross-compiling is a primary use case.
  * In addition to creating executables, creating a C library is a primary use
    case. You can export an auto-generated .h file.
- * For OS development, Zig supports all architectures that LLVM does. All the
-   standard library that does not depend on an OS is available to you in
-   freestanding mode.
 
 ### Support Table