| ... | ... | @@ -257,6 +257,68 @@ pub const ChildProcess = struct { |
| 257 | 257 | } |
| 258 | 258 | } |
| 259 | 259 | |
| 260 | fn collectOutputWindows(child: *const ChildProcess, stdout: *std.ArrayList(u8), stderr: *std.ArrayList(u8), max_output_bytes: usize) !void { |
| 261 | // The order of the objects here is important, WaitForMultipleObjects |
| 262 | // uses the same order when scanning the events. |
| 263 | var wait_objects = [_]windows.kernel32.HANDLE{ |
| 264 | child.handle, child.stdout.?.handle, child.stderr.?.handle, |
| 265 | }; |
| 266 | // XXX: Calling zeroes([2]windows.OVERLAPPED) causes the stage1 compiler |
| 267 | // to crash and burn. |
| 268 | var overlapped = [_]windows.OVERLAPPED{ |
| 269 | mem.zeroes(windows.OVERLAPPED), |
| 270 | mem.zeroes(windows.OVERLAPPED), |
| 271 | }; |
| 272 | var temp_buf: [2][4096]u8 = undefined; |
| 273 | |
| 274 | // Kickstart the loop by issuing two async reads. |
| 275 | // ReadFile returns false and GetLastError returns ERROR_IO_PENDING if |
| 276 | // everything is ok. |
| 277 | _ = windows.kernel32.ReadFile(wait_objects[1], &temp_buf[0], temp_buf[0].len, null, &overlapped[0]); |
| 278 | _ = windows.kernel32.ReadFile(wait_objects[2], &temp_buf[1], temp_buf[1].len, null, &overlapped[1]); |
| 279 | |
| 280 | while (true) { |
| 281 | const status = windows.kernel32.WaitForMultipleObjects(wait_objects.len, &wait_objects, 0, windows.INFINITE); |
| 282 | std.debug.print("status {x}\n", .{status}); |
| 283 | switch (status) { |
| 284 | windows.WAIT_OBJECT_0 + 0 => { |
| 285 | // The child process was terminated. |
| 286 | break; |
| 287 | }, |
| 288 | windows.WAIT_OBJECT_0 + 1 => { |
| 289 | // stdout is ready. |
| 290 | var read_bytes: u32 = undefined; |
| 291 | if (windows.kernel32.GetOverlappedResult(wait_objects[1], &overlapped[0], &read_bytes, 0) == 0) { |
| 292 | switch (windows.kernel32.GetLastError()) { |
| 293 | else => |err| return windows.unexpectedError(err), |
| 294 | } |
| 295 | } |
| 296 | try stdout.appendSlice(temp_buf[0][0..read_bytes]); |
| 297 | _ = windows.kernel32.ReadFile(wait_objects[1], &temp_buf[0], temp_buf[0].len, null, &overlapped[0]); |
| 298 | }, |
| 299 | windows.WAIT_OBJECT_0 + 2 => { |
| 300 | // stderr is ready. |
| 301 | var read_bytes: u32 = undefined; |
| 302 | if (windows.kernel32.GetOverlappedResult(wait_objects[2], &overlapped[1], &read_bytes, 0) == 0) { |
| 303 | switch (windows.kernel32.GetLastError()) { |
| 304 | else => |err| return windows.unexpectedError(err), |
| 305 | } |
| 306 | } |
| 307 | try stdout.appendSlice(temp_buf[1][0..read_bytes]); |
| 308 | _ = windows.kernel32.ReadFile(wait_objects[2], &temp_buf[1], temp_buf[1].len, null, &overlapped[1]); |
| 309 | }, |
| 310 | windows.WAIT_FAILED => { |
| 311 | switch (windows.kernel32.GetLastError()) { |
| 312 | else => |err| return windows.unexpectedError(err), |
| 313 | } |
| 314 | }, |
| 315 | // We're waiting with an infinite timeout |
| 316 | windows.WAIT_TIMEOUT => unreachable, |
| 317 | else => unreachable, |
| 318 | } |
| 319 | } |
| 320 | } |
| 321 | |
| 260 | 322 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 261 | 323 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 262 | 324 | pub fn exec(args: struct { |
| ... | ... | @@ -308,6 +370,14 @@ pub const ChildProcess = struct { |
| 308 | 370 | |
| 309 | 371 | try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); |
| 310 | 372 | |
| 373 | // XXX: Respect max_output_bytes |
| 374 | // XXX: Smarter reading logic, read directly into the ArrayList |
| 375 | if (builtin.os.tag == .windows) { |
| 376 | try collectOutputWindows(child, &stdout, &stderr, args.max_output_bytes); |
| 377 | } else { |
| 378 | try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); |
| 379 | } |
| 380 | |
| 311 | 381 | return ExecResult{ |
| 312 | 382 | .term = try child.wait(), |
| 313 | 383 | .stdout = stdout.toOwnedSlice(), |
| ... | ... | @@ -644,7 +714,7 @@ pub const ChildProcess = struct { |
| 644 | 714 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; |
| 645 | 715 | switch (self.stdout_behavior) { |
| 646 | 716 | StdIo.Pipe => { |
| 647 | | try windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); |
| 717 | try windowsMakePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); |
| 648 | 718 | }, |
| 649 | 719 | StdIo.Ignore => { |
| 650 | 720 | g_hChildStd_OUT_Wr = nul_handle; |
| ... | ... | @@ -664,7 +734,7 @@ pub const ChildProcess = struct { |
| 664 | 734 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; |
| 665 | 735 | switch (self.stderr_behavior) { |
| 666 | 736 | StdIo.Pipe => { |
| 667 | | try windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); |
| 737 | try windowsMakePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); |
| 668 | 738 | }, |
| 669 | 739 | StdIo.Ignore => { |
| 670 | 740 | g_hChildStd_ERR_Wr = nul_handle; |
| ... | ... | @@ -897,6 +967,55 @@ fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { |
| 897 | 967 | if (wr) |h| os.close(h); |
| 898 | 968 | } |
| 899 | 969 | |
| 970 | var pipe_name_counter = std.atomic.Int(u32).init(1); |
| 971 | |
| 972 | fn windowsMakePipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 973 | var tmp_buf: [128]u8 = undefined; |
| 974 | // Forge a random path for the pipe. |
| 975 | const pipe_path = std.fmt.bufPrintZ( |
| 976 | &tmp_buf, |
| 977 | "\\\\.\\pipe\\zig-childprocess-{d}-{d}", |
| 978 | .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1) }, |
| 979 | ) catch unreachable; |
| 980 | |
| 981 | // Create the read handle that can be used with overlapped IO ops. |
| 982 | const read_handle = windows.kernel32.CreateNamedPipeA( |
| 983 | pipe_path, |
| 984 | windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, |
| 985 | windows.PIPE_TYPE_BYTE, |
| 986 | 1, |
| 987 | 0x1000, |
| 988 | 0x1000, |
| 989 | 0, |
| 990 | sattr, |
| 991 | ); |
| 992 | if (read_handle == windows.INVALID_HANDLE_VALUE) { |
| 993 | switch (windows.kernel32.GetLastError()) { |
| 994 | else => |err| return windows.unexpectedError(err), |
| 995 | } |
| 996 | } |
| 997 | |
| 998 | const write_handle = windows.kernel32.CreateFileA( |
| 999 | pipe_path, |
| 1000 | windows.GENERIC_WRITE, |
| 1001 | 0, |
| 1002 | sattr, |
| 1003 | windows.OPEN_EXISTING, |
| 1004 | windows.FILE_ATTRIBUTE_NORMAL, |
| 1005 | null, |
| 1006 | ); |
| 1007 | if (write_handle == windows.INVALID_HANDLE_VALUE) { |
| 1008 | switch (windows.kernel32.GetLastError()) { |
| 1009 | else => |err| return windows.unexpectedError(err), |
| 1010 | } |
| 1011 | } |
| 1012 | |
| 1013 | try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); |
| 1014 | |
| 1015 | rd.* = read_handle; |
| 1016 | wr.* = write_handle; |
| 1017 | } |
| 1018 | |
| 900 | 1019 | fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 901 | 1020 | var rd_h: windows.HANDLE = undefined; |
| 902 | 1021 | var wr_h: windows.HANDLE = undefined; |