| ... | @@ -13,6 +13,7 @@ const process = std.process; | ... | @@ -13,6 +13,7 @@ const process = std.process; |
| 13 | const File = std.fs.File; | 13 | const File = std.fs.File; |
| 14 | const windows = os.windows; | 14 | const windows = os.windows; |
| 15 | const mem = std.mem; | 15 | const mem = std.mem; |
| | 16 | const math = std.math; |
| 16 | const debug = std.debug; | 17 | const debug = std.debug; |
| 17 | const BufMap = std.BufMap; | 18 | const BufMap = std.BufMap; |
| 18 | const builtin = std.builtin; | 19 | const builtin = std.builtin; |
| ... | @@ -257,6 +258,79 @@ pub const ChildProcess = struct { | ... | @@ -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 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. | 334 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 261 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. | 335 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 262 | pub fn exec(args: struct { | 336 | pub fn exec(args: struct { |
| ... | @@ -306,7 +380,11 @@ pub const ChildProcess = struct { | ... | @@ -306,7 +380,11 @@ pub const ChildProcess = struct { |
| 306 | stderr.deinit(); | 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 | return ExecResult{ | 389 | return ExecResult{ |
| 312 | .term = try child.wait(), | 390 | .term = try child.wait(), |
| ... | @@ -644,7 +722,7 @@ pub const ChildProcess = struct { | ... | @@ -644,7 +722,7 @@ pub const ChildProcess = struct { |
| 644 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; | 722 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; |
| 645 | switch (self.stdout_behavior) { | 723 | switch (self.stdout_behavior) { |
| 646 | StdIo.Pipe => { | 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 | StdIo.Ignore => { | 727 | StdIo.Ignore => { |
| 650 | g_hChildStd_OUT_Wr = nul_handle; | 728 | g_hChildStd_OUT_Wr = nul_handle; |
| ... | @@ -664,7 +742,7 @@ pub const ChildProcess = struct { | ... | @@ -664,7 +742,7 @@ pub const ChildProcess = struct { |
| 664 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; | 742 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; |
| 665 | switch (self.stderr_behavior) { | 743 | switch (self.stderr_behavior) { |
| 666 | StdIo.Pipe => { | 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 | StdIo.Ignore => { | 747 | StdIo.Ignore => { |
| 670 | g_hChildStd_ERR_Wr = nul_handle; | 748 | g_hChildStd_ERR_Wr = nul_handle; |
| ... | @@ -907,14 +985,64 @@ fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const w | ... | @@ -907,14 +985,64 @@ fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const w |
| 907 | wr.* = wr_h; | 985 | wr.* = wr_h; |
| 908 | } | 986 | } |
| 909 | | 987 | |
| 910 | fn windowsMakePipeOut(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { | 988 | var pipe_name_counter = std.atomic.Atomic(u32).init(1); |
| 911 | var rd_h: windows.HANDLE = undefined; | 989 | |
| 912 | var wr_h: windows.HANDLE = undefined; | 990 | fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 913 | try windows.CreatePipe(&rd_h, &wr_h, sattr); | 991 | var tmp_bufw: [128]u16 = undefined; |
| 914 | errdefer windowsDestroyPipe(rd_h, wr_h); | 992 | |
| 915 | try windows.SetHandleInformation(rd_h, windows.HANDLE_FLAG_INHERIT, 0); | 993 | // We must make a named pipe on windows because anonymous pipes do not support async IO |
| 916 | rd.* = rd_h; | 994 | const pipe_path = blk: { |
| 917 | wr.* = wr_h; | 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 | fn destroyPipe(pipe: [2]os.fd_t) void { | 1048 | fn destroyPipe(pipe: [2]os.fd_t) void { |