| ... | ... | @@ -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,58 +258,76 @@ pub const ChildProcess = struct { |
| 257 | 258 | } |
| 258 | 259 | } |
| 259 | 260 | |
| 260 | | fn collectOutputWindows(child: *const ChildProcess, stdout: *std.ArrayList(u8), stderr: *std.ArrayList(u8), max_output_bytes: usize) !void { |
| 261 | | var wait_objects = [_]windows.kernel32.HANDLE{ |
| 262 | | child.stdout.?.handle, child.stderr.?.handle, |
| 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, |
| 263 | 266 | }; |
| 264 | | var waiting_objects: u32 = wait_objects.len; |
| 265 | 267 | |
| 266 | | // XXX: Calling zeroes([2]windows.OVERLAPPED) causes the stage1 compiler |
| 267 | | // to crash and burn. |
| 268 | 268 | var overlapped = [_]windows.OVERLAPPED{ |
| 269 | 269 | mem.zeroes(windows.OVERLAPPED), |
| 270 | 270 | mem.zeroes(windows.OVERLAPPED), |
| 271 | 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[0], &temp_buf[0], temp_buf[0].len, null, &overlapped[0]); |
| 278 | | _ = windows.kernel32.ReadFile(wait_objects[1], &temp_buf[1], temp_buf[1].len, null, &overlapped[1]); |
| 279 | | |
| 280 | | poll: while (waiting_objects > 0) { |
| 281 | | const status = windows.kernel32.WaitForMultipleObjects(waiting_objects, &wait_objects, 0, windows.INFINITE); |
| 282 | | switch (status) { |
| 283 | | windows.WAIT_OBJECT_0 + 0...windows.WAIT_OBJECT_0 + 1 => { |
| 284 | | // stdout (or stderr) is ready. |
| 285 | | const object = status - windows.WAIT_OBJECT_0; |
| 286 | | |
| 287 | | var read_bytes: u32 = undefined; |
| 288 | | if (windows.kernel32.GetOverlappedResult(wait_objects[object], &overlapped[object], &read_bytes, 0) == 0) { |
| 289 | | switch (windows.kernel32.GetLastError()) { |
| 290 | | .BROKEN_PIPE => { |
| 291 | | // Move it to the end to remove it. |
| 292 | | if (object != waiting_objects - 1) |
| 293 | | mem.swap(windows.kernel32.HANDLE, &wait_objects[object], &wait_objects[waiting_objects - 1]); |
| 294 | | waiting_objects -= 1; |
| 295 | | continue :poll; |
| 296 | | }, |
| 297 | | else => |err| return windows.unexpectedError(err), |
| 298 | | } |
| 299 | | } |
| 300 | | try stdout.appendSlice(temp_buf[object][0..read_bytes]); |
| 301 | | _ = windows.kernel32.ReadFile(wait_objects[object], &temp_buf[object], temp_buf[object].len, null, &overlapped[object]); |
| 302 | | }, |
| 303 | | windows.WAIT_FAILED => { |
| 304 | | switch (windows.kernel32.GetLastError()) { |
| 305 | | else => |err| return windows.unexpectedError(err), |
| 306 | | } |
| 307 | | }, |
| 308 | | // We're waiting with an infinite timeout |
| 309 | | windows.WAIT_TIMEOUT => unreachable, |
| 310 | | else => unreachable, |
| 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 | } |
| 311 | 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; |
| 312 | 331 | } |
| 313 | 332 | } |
| 314 | 333 | |
| ... | ... | @@ -361,12 +380,8 @@ pub const ChildProcess = struct { |
| 361 | 380 | stderr.deinit(); |
| 362 | 381 | } |
| 363 | 382 | |
| 364 | | try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); |
| 365 | | |
| 366 | | // XXX: Respect max_output_bytes |
| 367 | | // XXX: Smarter reading logic, read directly into the ArrayList |
| 368 | 383 | if (builtin.os.tag == .windows) { |
| 369 | | try collectOutputWindows(child, &stdout, &stderr, args.max_output_bytes); |
| 384 | try collectOutputWindows(child, [_]*std.ArrayList(u8){ &stdout, &stderr }, args.max_output_bytes); |
| 370 | 385 | } else { |
| 371 | 386 | try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); |
| 372 | 387 | } |
| ... | ... | @@ -707,7 +722,7 @@ pub const ChildProcess = struct { |
| 707 | 722 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; |
| 708 | 723 | switch (self.stdout_behavior) { |
| 709 | 724 | StdIo.Pipe => { |
| 710 | | try windowsMakePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); |
| 725 | try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); |
| 711 | 726 | }, |
| 712 | 727 | StdIo.Ignore => { |
| 713 | 728 | g_hChildStd_OUT_Wr = nul_handle; |
| ... | ... | @@ -727,7 +742,7 @@ pub const ChildProcess = struct { |
| 727 | 742 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; |
| 728 | 743 | switch (self.stderr_behavior) { |
| 729 | 744 | StdIo.Pipe => { |
| 730 | | try windowsMakePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); |
| 745 | try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); |
| 731 | 746 | }, |
| 732 | 747 | StdIo.Ignore => { |
| 733 | 748 | g_hChildStd_ERR_Wr = nul_handle; |
| ... | ... | @@ -960,25 +975,43 @@ fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { |
| 960 | 975 | if (wr) |h| os.close(h); |
| 961 | 976 | } |
| 962 | 977 | |
| 963 | | var pipe_name_counter = std.atomic.Int(u32).init(1); |
| 978 | fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 979 | var rd_h: windows.HANDLE = undefined; |
| 980 | var wr_h: windows.HANDLE = undefined; |
| 981 | try windows.CreatePipe(&rd_h, &wr_h, sattr); |
| 982 | errdefer windowsDestroyPipe(rd_h, wr_h); |
| 983 | try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0); |
| 984 | rd.* = rd_h; |
| 985 | wr.* = wr_h; |
| 986 | } |
| 964 | 987 | |
| 965 | | fn windowsMakePipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 966 | | var tmp_buf: [128]u8 = undefined; |
| 967 | | // Forge a random path for the pipe. |
| 968 | | const pipe_path = std.fmt.bufPrintZ( |
| 969 | | &tmp_buf, |
| 970 | | "\\\\.\\pipe\\zig-childprocess-{d}-{d}", |
| 971 | | .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1) }, |
| 972 | | ) catch unreachable; |
| 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 | }; |
| 973 | 1006 | |
| 974 | 1007 | // Create the read handle that can be used with overlapped IO ops. |
| 975 | | const read_handle = windows.kernel32.CreateNamedPipeA( |
| 976 | | pipe_path, |
| 1008 | const read_handle = windows.kernel32.CreateNamedPipeW( |
| 1009 | pipe_path.ptr, |
| 977 | 1010 | windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, |
| 978 | 1011 | windows.PIPE_TYPE_BYTE, |
| 979 | 1012 | 1, |
| 980 | | 0x1000, |
| 981 | | 0x1000, |
| 1013 | 4096, |
| 1014 | 4096, |
| 982 | 1015 | 0, |
| 983 | 1016 | sattr, |
| 984 | 1017 | ); |
| ... | ... | @@ -987,12 +1020,14 @@ fn windowsMakePipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const win |
| 987 | 1020 | else => |err| return windows.unexpectedError(err), |
| 988 | 1021 | } |
| 989 | 1022 | } |
| 1023 | errdefer os.close(read_handle); |
| 990 | 1024 | |
| 991 | | const write_handle = windows.kernel32.CreateFileA( |
| 992 | | pipe_path, |
| 1025 | var sattr_copy = sattr.*; |
| 1026 | const write_handle = windows.kernel32.CreateFileW( |
| 1027 | pipe_path.ptr, |
| 993 | 1028 | windows.GENERIC_WRITE, |
| 994 | 1029 | 0, |
| 995 | | sattr, |
| 1030 | &sattr_copy, |
| 996 | 1031 | windows.OPEN_EXISTING, |
| 997 | 1032 | windows.FILE_ATTRIBUTE_NORMAL, |
| 998 | 1033 | null, |
| ... | ... | @@ -1002,6 +1037,7 @@ fn windowsMakePipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const win |
| 1002 | 1037 | else => |err| return windows.unexpectedError(err), |
| 1003 | 1038 | } |
| 1004 | 1039 | } |
| 1040 | errdefer os.close(write_handle); |
| 1005 | 1041 | |
| 1006 | 1042 | try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); |
| 1007 | 1043 | |
| ... | ... | @@ -1009,26 +1045,6 @@ fn windowsMakePipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const win |
| 1009 | 1045 | wr.* = write_handle; |
| 1010 | 1046 | } |
| 1011 | 1047 | |
| 1012 | | fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 1013 | | var rd_h: windows.HANDLE = undefined; |
| 1014 | | var wr_h: windows.HANDLE = undefined; |
| 1015 | | try windows.CreatePipe(&rd_h, &wr_h, sattr); |
| 1016 | | errdefer windowsDestroyPipe(rd_h, wr_h); |
| 1017 | | try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0); |
| 1018 | | rd.* = rd_h; |
| 1019 | | wr.* = wr_h; |
| 1020 | | } |
| 1021 | | |
| 1022 | | fn windowsMakePipeOut(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 1023 | | var rd_h: windows.HANDLE = undefined; |
| 1024 | | var wr_h: windows.HANDLE = undefined; |
| 1025 | | try windows.CreatePipe(&rd_h, &wr_h, sattr); |
| 1026 | | errdefer windowsDestroyPipe(rd_h, wr_h); |
| 1027 | | try windows.SetHandleInformation(rd_h, windows.HANDLE_FLAG_INHERIT, 0); |
| 1028 | | rd.* = rd_h; |
| 1029 | | wr.* = wr_h; |
| 1030 | | } |
| 1031 | | |
| 1032 | 1048 | fn destroyPipe(pipe: [2]os.fd_t) void { |
| 1033 | 1049 | os.close(pipe[0]); |
| 1034 | 1050 | if (pipe[0] != pipe[1]) os.close(pipe[1]); |