diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 1cdb3ef34c..936609d309 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -257,6 +257,68 @@ pub const ChildProcess = struct { } } + fn collectOutputWindows(child: *const ChildProcess, stdout: *std.ArrayList(u8), stderr: *std.ArrayList(u8), max_output_bytes: usize) !void { + // The order of the objects here is important, WaitForMultipleObjects + // uses the same order when scanning the events. + var wait_objects = [_]windows.kernel32.HANDLE{ + child.handle, child.stdout.?.handle, child.stderr.?.handle, + }; + // XXX: Calling zeroes([2]windows.OVERLAPPED) causes the stage1 compiler + // to crash and burn. + var overlapped = [_]windows.OVERLAPPED{ + mem.zeroes(windows.OVERLAPPED), + mem.zeroes(windows.OVERLAPPED), + }; + var temp_buf: [2][4096]u8 = undefined; + + // Kickstart the loop by issuing two async reads. + // ReadFile returns false and GetLastError returns ERROR_IO_PENDING if + // everything is ok. + _ = windows.kernel32.ReadFile(wait_objects[1], &temp_buf[0], temp_buf[0].len, null, &overlapped[0]); + _ = windows.kernel32.ReadFile(wait_objects[2], &temp_buf[1], temp_buf[1].len, null, &overlapped[1]); + + while (true) { + const status = windows.kernel32.WaitForMultipleObjects(wait_objects.len, &wait_objects, 0, windows.INFINITE); + std.debug.print("status {x}\n", .{status}); + switch (status) { + windows.WAIT_OBJECT_0 + 0 => { + // The child process was terminated. + break; + }, + windows.WAIT_OBJECT_0 + 1 => { + // stdout is ready. + var read_bytes: u32 = undefined; + if (windows.kernel32.GetOverlappedResult(wait_objects[1], &overlapped[0], &read_bytes, 0) == 0) { + switch (windows.kernel32.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + } + try stdout.appendSlice(temp_buf[0][0..read_bytes]); + _ = windows.kernel32.ReadFile(wait_objects[1], &temp_buf[0], temp_buf[0].len, null, &overlapped[0]); + }, + windows.WAIT_OBJECT_0 + 2 => { + // stderr is ready. + var read_bytes: u32 = undefined; + if (windows.kernel32.GetOverlappedResult(wait_objects[2], &overlapped[1], &read_bytes, 0) == 0) { + switch (windows.kernel32.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + } + try stdout.appendSlice(temp_buf[1][0..read_bytes]); + _ = windows.kernel32.ReadFile(wait_objects[2], &temp_buf[1], temp_buf[1].len, null, &overlapped[1]); + }, + windows.WAIT_FAILED => { + switch (windows.kernel32.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + }, + // We're waiting with an infinite timeout + windows.WAIT_TIMEOUT => unreachable, + else => unreachable, + } + } + } + /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. /// If it succeeds, the caller owns result.stdout and result.stderr memory. pub fn exec(args: struct { @@ -308,6 +370,14 @@ pub const ChildProcess = struct { try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); + // XXX: Respect max_output_bytes + // XXX: Smarter reading logic, read directly into the ArrayList + if (builtin.os.tag == .windows) { + try collectOutputWindows(child, &stdout, &stderr, args.max_output_bytes); + } else { + try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); + } + return ExecResult{ .term = try child.wait(), .stdout = stdout.toOwnedSlice(), @@ -644,7 +714,7 @@ pub const ChildProcess = struct { var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; switch (self.stdout_behavior) { StdIo.Pipe => { - try windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); + try windowsMakePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); }, StdIo.Ignore => { g_hChildStd_OUT_Wr = nul_handle; @@ -664,7 +734,7 @@ pub const ChildProcess = struct { var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; switch (self.stderr_behavior) { StdIo.Pipe => { - try windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); + try windowsMakePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); }, StdIo.Ignore => { g_hChildStd_ERR_Wr = nul_handle; @@ -897,6 +967,55 @@ fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { if (wr) |h| os.close(h); } +var pipe_name_counter = std.atomic.Int(u32).init(1); + +fn windowsMakePipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { + var tmp_buf: [128]u8 = undefined; + // Forge a random path for the pipe. + const pipe_path = std.fmt.bufPrintZ( + &tmp_buf, + "\\\\.\\pipe\\zig-childprocess-{d}-{d}", + .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1) }, + ) catch unreachable; + + // Create the read handle that can be used with overlapped IO ops. + const read_handle = windows.kernel32.CreateNamedPipeA( + pipe_path, + windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, + windows.PIPE_TYPE_BYTE, + 1, + 0x1000, + 0x1000, + 0, + sattr, + ); + if (read_handle == windows.INVALID_HANDLE_VALUE) { + switch (windows.kernel32.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + } + + const write_handle = windows.kernel32.CreateFileA( + pipe_path, + windows.GENERIC_WRITE, + 0, + sattr, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL, + null, + ); + if (write_handle == windows.INVALID_HANDLE_VALUE) { + switch (windows.kernel32.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + } + + try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); + + rd.* = read_handle; + wr.* = write_handle; +} + fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { var rd_h: windows.HANDLE = undefined; var wr_h: windows.HANDLE = undefined; diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 977c63cc5b..59a2a87415 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -448,6 +448,19 @@ pub const SECURITY_ATTRIBUTES = extern struct { pub const PSECURITY_ATTRIBUTES = *SECURITY_ATTRIBUTES; pub const LPSECURITY_ATTRIBUTES = *SECURITY_ATTRIBUTES; +pub const PIPE_ACCESS_INBOUND = 0x00000001; +pub const PIPE_ACCESS_OUTBOUND = 0x00000002; +pub const PIPE_ACCESS_DUPLEX = 0x00000003; + +pub const PIPE_TYPE_BYTE = 0x00000000; +pub const PIPE_TYPE_MESSAGE = 0x00000004; + +pub const PIPE_READMODE_BYTE = 0x00000000; +pub const PIPE_READMODE_MESSAGE = 0x00000002; + +pub const PIPE_WAIT = 0x00000000; +pub const PIPE_NOWAIT = 0x00000001; + pub const GENERIC_READ = 0x80000000; pub const GENERIC_WRITE = 0x40000000; pub const GENERIC_EXECUTE = 0x20000000; diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index 8f100a9511..1ce3bd004c 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -15,6 +15,29 @@ pub extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL; pub extern "kernel32" fn CreateDirectoryW(lpPathName: [*:0]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) callconv(WINAPI) BOOL; pub extern "kernel32" fn SetEndOfFile(hFile: HANDLE) callconv(WINAPI) BOOL; +pub extern "kernel32" fn GetCurrentProcessId() callconv(.Stdcall) DWORD; + +pub extern "kernel32" fn CreateNamedPipeA( + lpName: [*:0]const u8, + dwOpenMode: DWORD, + dwPipeMode: DWORD, + nMaxInstances: DWORD, + nOutBufferSize: DWORD, + nInBufferSize: DWORD, + nDefaultTimeOut: DWORD, + lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES, +) callconv(.Stdcall) HANDLE; +pub extern "kernel32" fn CreateNamedPipeW( + lpName: LPCWSTR, + dwOpenMode: DWORD, + dwPipeMode: DWORD, + nMaxInstances: DWORD, + nOutBufferSize: DWORD, + nInBufferSize: DWORD, + nDefaultTimeOut: DWORD, + lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES, +) callconv(.Stdcall) HANDLE; + pub extern "kernel32" fn CreateEventExW( lpEventAttributes: ?*SECURITY_ATTRIBUTES, lpName: [*:0]const u16, @@ -32,6 +55,16 @@ pub extern "kernel32" fn CreateFileW( hTemplateFile: ?HANDLE, ) callconv(WINAPI) HANDLE; +pub extern "kernel32" fn CreateFileA( + lpFileName: [*:0]const u8, + dwDesiredAccess: DWORD, + dwShareMode: DWORD, + lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES, + dwCreationDisposition: DWORD, + dwFlagsAndAttributes: DWORD, + hTemplateFile: ?HANDLE, +) callconv(.Stdcall) HANDLE; + pub extern "kernel32" fn CreatePipe( hReadPipe: *HANDLE, hWritePipe: *HANDLE,