| ... | ... | @@ -13,6 +13,7 @@ const process = std.process; |
| 13 | 13 | const File = std.fs.File; |
| 14 | 14 | const windows = os.windows; |
| 15 | 15 | const mem = std.mem; |
| 16 | const math = std.math; |
| 16 | 17 | const debug = std.debug; |
| 17 | 18 | const BufMap = std.BufMap; |
| 18 | 19 | const builtin = std.builtin; |
| ... | ... | @@ -257,6 +258,79 @@ pub const ChildProcess = struct { |
| 257 | 258 | } |
| 258 | 259 | } |
| 259 | 260 | |
| 261 | fn collectOutputWindows(child: *const ChildProcess, outs: [2]*std.ArrayList(u8), max_output_bytes: usize) !void { |
| 262 | const bump_amt = 512; |
| 263 | const handles = [_]windows.HANDLE{ |
| 264 | child.stdout.?.handle, |
| 265 | child.stderr.?.handle, |
| 266 | }; |
| 267 | |
| 268 | var overlapped = [_]windows.OVERLAPPED{ |
| 269 | mem.zeroes(windows.OVERLAPPED), |
| 270 | mem.zeroes(windows.OVERLAPPED), |
| 271 | }; |
| 272 | |
| 273 | var wait_objects: [2]windows.HANDLE = undefined; |
| 274 | var wait_object_count: u2 = 0; |
| 275 | |
| 276 | // we need to cancel all pending IO before returning so our OVERLAPPED values don't go out of scope |
| 277 | defer for (wait_objects[0..wait_object_count]) |o| { |
| 278 | _ = windows.kernel32.CancelIo(o); |
| 279 | }; |
| 280 | |
| 281 | // Windows Async IO requires an initial call to ReadFile before waiting on the handle |
| 282 | for ([_]u1{ 0, 1 }) |i| { |
| 283 | try outs[i].ensureCapacity(bump_amt); |
| 284 | const buf = outs[i].unusedCapacitySlice(); |
| 285 | _ = windows.kernel32.ReadFile(handles[i], buf.ptr, math.cast(u32, buf.len) catch maxInt(u32), null, &overlapped[i]); |
| 286 | wait_objects[wait_object_count] = handles[i]; |
| 287 | wait_object_count += 1; |
| 288 | } |
| 289 | |
| 290 | while (true) { |
| 291 | const status = windows.kernel32.WaitForMultipleObjects(wait_object_count, &wait_objects, 0, windows.INFINITE); |
| 292 | if (status == windows.WAIT_FAILED) { |
| 293 | switch (windows.kernel32.GetLastError()) { |
| 294 | else => |err| return windows.unexpectedError(err), |
| 295 | } |
| 296 | } |
| 297 | if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + wait_object_count - 1) |
| 298 | unreachable; |
| 299 | |
| 300 | const wait_idx = status - windows.WAIT_OBJECT_0; |
| 301 | |
| 302 | // this extra `i` index is needed to map the wait handle back to the stdout or stderr |
| 303 | // values since the wait_idx can change which handle it corresponds with |
| 304 | const i: u1 = if (wait_objects[wait_idx] == handles[0]) 0 else 1; |
| 305 | |
| 306 | // remove completed event from the wait list |
| 307 | wait_object_count -= 1; |
| 308 | if (wait_idx == 0) |
| 309 | wait_objects[0] = wait_objects[1]; |
| 310 | |
| 311 | var read_bytes: u32 = undefined; |
| 312 | if (windows.kernel32.GetOverlappedResult(handles[i], &overlapped[i], &read_bytes, 0) == 0) { |
| 313 | switch (windows.kernel32.GetLastError()) { |
| 314 | .BROKEN_PIPE => { |
| 315 | if (wait_object_count == 0) |
| 316 | break; |
| 317 | continue; |
| 318 | }, |
| 319 | else => |err| return windows.unexpectedError(err), |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | outs[i].items.len += read_bytes; |
| 324 | const new_capacity = std.math.min(outs[i].items.len + bump_amt, max_output_bytes); |
| 325 | try outs[i].ensureCapacity(new_capacity); |
| 326 | const buf = outs[i].unusedCapacitySlice(); |
| 327 | if (buf.len == 0) return if (i == 0) error.StdoutStreamTooLong else error.StderrStreamTooLong; |
| 328 | _ = windows.kernel32.ReadFile(handles[i], buf.ptr, math.cast(u32, buf.len) catch maxInt(u32), null, &overlapped[i]); |
| 329 | wait_objects[wait_object_count] = handles[i]; |
| 330 | wait_object_count += 1; |
| 331 | } |
| 332 | } |
| 333 | |
| 260 | 334 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 261 | 335 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 262 | 336 | pub fn exec(args: struct { |
| ... | ... | @@ -306,7 +380,11 @@ pub const ChildProcess = struct { |
| 306 | 380 | stderr.deinit(); |
| 307 | 381 | } |
| 308 | 382 | |
| 309 | | try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); |
| 383 | if (builtin.os.tag == .windows) { |
| 384 | try collectOutputWindows(child, [_]*std.ArrayList(u8){ &stdout, &stderr }, args.max_output_bytes); |
| 385 | } else { |
| 386 | try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); |
| 387 | } |
| 310 | 388 | |
| 311 | 389 | return ExecResult{ |
| 312 | 390 | .term = try child.wait(), |
| ... | ... | @@ -644,7 +722,7 @@ pub const ChildProcess = struct { |
| 644 | 722 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; |
| 645 | 723 | switch (self.stdout_behavior) { |
| 646 | 724 | StdIo.Pipe => { |
| 647 | | try windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); |
| 725 | try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); |
| 648 | 726 | }, |
| 649 | 727 | StdIo.Ignore => { |
| 650 | 728 | g_hChildStd_OUT_Wr = nul_handle; |
| ... | ... | @@ -664,7 +742,7 @@ pub const ChildProcess = struct { |
| 664 | 742 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; |
| 665 | 743 | switch (self.stderr_behavior) { |
| 666 | 744 | StdIo.Pipe => { |
| 667 | | try windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); |
| 745 | try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); |
| 668 | 746 | }, |
| 669 | 747 | StdIo.Ignore => { |
| 670 | 748 | g_hChildStd_ERR_Wr = nul_handle; |
| ... | ... | @@ -907,14 +985,64 @@ fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const w |
| 907 | 985 | wr.* = wr_h; |
| 908 | 986 | } |
| 909 | 987 | |
| 910 | | fn windowsMakePipeOut(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 911 | | var rd_h: windows.HANDLE = undefined; |
| 912 | | var wr_h: windows.HANDLE = undefined; |
| 913 | | try windows.CreatePipe(&rd_h, &wr_h, sattr); |
| 914 | | errdefer windowsDestroyPipe(rd_h, wr_h); |
| 915 | | try windows.SetHandleInformation(rd_h, windows.HANDLE_FLAG_INHERIT, 0); |
| 916 | | rd.* = rd_h; |
| 917 | | wr.* = wr_h; |
| 988 | var pipe_name_counter = std.atomic.Atomic(u32).init(1); |
| 989 | |
| 990 | fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 991 | var tmp_bufw: [128]u16 = undefined; |
| 992 | |
| 993 | // We must make a named pipe on windows because anonymous pipes do not support async IO |
| 994 | const pipe_path = blk: { |
| 995 | var tmp_buf: [128]u8 = undefined; |
| 996 | // Forge a random path for the pipe. |
| 997 | const pipe_path = std.fmt.bufPrintZ( |
| 998 | &tmp_buf, |
| 999 | "\\\\.\\pipe\\zig-childprocess-{d}-{d}", |
| 1000 | .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .Monotonic) }, |
| 1001 | ) catch unreachable; |
| 1002 | const len = std.unicode.utf8ToUtf16Le(&tmp_bufw, pipe_path) catch unreachable; |
| 1003 | tmp_bufw[len] = 0; |
| 1004 | break :blk tmp_bufw[0..len :0]; |
| 1005 | }; |
| 1006 | |
| 1007 | // Create the read handle that can be used with overlapped IO ops. |
| 1008 | const read_handle = windows.kernel32.CreateNamedPipeW( |
| 1009 | pipe_path.ptr, |
| 1010 | windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, |
| 1011 | windows.PIPE_TYPE_BYTE, |
| 1012 | 1, |
| 1013 | 4096, |
| 1014 | 4096, |
| 1015 | 0, |
| 1016 | sattr, |
| 1017 | ); |
| 1018 | if (read_handle == windows.INVALID_HANDLE_VALUE) { |
| 1019 | switch (windows.kernel32.GetLastError()) { |
| 1020 | else => |err| return windows.unexpectedError(err), |
| 1021 | } |
| 1022 | } |
| 1023 | errdefer os.close(read_handle); |
| 1024 | |
| 1025 | var sattr_copy = sattr.*; |
| 1026 | const write_handle = windows.kernel32.CreateFileW( |
| 1027 | pipe_path.ptr, |
| 1028 | windows.GENERIC_WRITE, |
| 1029 | 0, |
| 1030 | &sattr_copy, |
| 1031 | windows.OPEN_EXISTING, |
| 1032 | windows.FILE_ATTRIBUTE_NORMAL, |
| 1033 | null, |
| 1034 | ); |
| 1035 | if (write_handle == windows.INVALID_HANDLE_VALUE) { |
| 1036 | switch (windows.kernel32.GetLastError()) { |
| 1037 | else => |err| return windows.unexpectedError(err), |
| 1038 | } |
| 1039 | } |
| 1040 | errdefer os.close(write_handle); |
| 1041 | |
| 1042 | try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); |
| 1043 | |
| 1044 | rd.* = read_handle; |
| 1045 | wr.* = write_handle; |
| 918 | 1046 | } |
| 919 | 1047 | |
| 920 | 1048 | fn destroyPipe(pipe: [2]os.fd_t) void { |