| ... | ... | @@ -186,14 +186,22 @@ pub const ChildProcess = struct { |
| 186 | 186 | |
| 187 | 187 | pub const exec2 = @compileError("deprecated: exec2 is renamed to exec"); |
| 188 | 188 | |
| 189 | | fn collectOutputPosix(child: *const ChildProcess, stdout: *std.ArrayList(u8), stderr: *std.ArrayList(u8), max_output_bytes: usize) !void { |
| 189 | fn collectOutputPosix( |
| 190 | child: *const ChildProcess, |
| 191 | stdout: *std.ArrayList(u8), |
| 192 | stderr: *std.ArrayList(u8), |
| 193 | max_output_bytes: usize, |
| 194 | ) !void { |
| 190 | 195 | var poll_fds = [_]os.pollfd{ |
| 191 | 196 | .{ .fd = child.stdout.?.handle, .events = os.POLLIN, .revents = undefined }, |
| 192 | 197 | .{ .fd = child.stderr.?.handle, .events = os.POLLIN, .revents = undefined }, |
| 193 | 198 | }; |
| 194 | 199 | |
| 195 | 200 | var dead_fds: usize = 0; |
| 196 | | var loop_buf: [4096]u8 = undefined; |
| 201 | // We ask for ensureCapacity with this much extra space. This has more of an |
| 202 | // effect on small reads because once the reads start to get larger the amount |
| 203 | // of space an ArrayList will allocate grows exponentially. |
| 204 | const bump_amt = 512; |
| 197 | 205 | |
| 198 | 206 | while (dead_fds < poll_fds.len) { |
| 199 | 207 | const events = try os.poll(&poll_fds, std.math.maxInt(i32)); |
| ... | ... | @@ -203,13 +211,17 @@ pub const ChildProcess = struct { |
| 203 | 211 | // conditions. |
| 204 | 212 | if (poll_fds[0].revents & os.POLLIN != 0) { |
| 205 | 213 | // stdout is ready. |
| 206 | | const n = try os.read(poll_fds[0].fd, &loop_buf); |
| 207 | | try stdout.appendSlice(loop_buf[0..n]); |
| 214 | const new_capacity = std.math.min(stdout.items.len + bump_amt, max_output_bytes); |
| 215 | if (new_capacity == stdout.capacity) return error.StdoutStreamTooLong; |
| 216 | try stdout.ensureCapacity(new_capacity); |
| 217 | stdout.items.len += try os.read(poll_fds[0].fd, stdout.unusedCapacitySlice()); |
| 208 | 218 | } |
| 209 | 219 | if (poll_fds[1].revents & os.POLLIN != 0) { |
| 210 | 220 | // stderr is ready. |
| 211 | | const n = try os.read(poll_fds[1].fd, &loop_buf); |
| 212 | | try stderr.appendSlice(loop_buf[0..n]); |
| 221 | const new_capacity = std.math.min(stderr.items.len + bump_amt, max_output_bytes); |
| 222 | if (new_capacity == stderr.capacity) return error.StderrStreamTooLong; |
| 223 | try stderr.ensureCapacity(new_capacity); |
| 224 | stderr.items.len += try os.read(poll_fds[1].fd, stderr.unusedCapacitySlice()); |
| 213 | 225 | } |
| 214 | 226 | |
| 215 | 227 | // Exclude the fds that signaled an error. |
| ... | ... | @@ -224,61 +236,6 @@ pub const ChildProcess = struct { |
| 224 | 236 | } |
| 225 | 237 | } |
| 226 | 238 | |
| 227 | | fn collectOutputWindows(child: *const ChildProcess, stdout: *std.ArrayList(u8), stderr: *std.ArrayList(u8), max_output_bytes: usize) !void { |
| 228 | | var wait_objects = [_]windows.kernel32.HANDLE{ |
| 229 | | child.stdout.?.handle, child.stderr.?.handle, |
| 230 | | }; |
| 231 | | var waiting_objects: u32 = wait_objects.len; |
| 232 | | |
| 233 | | // XXX: Calling zeroes([2]windows.OVERLAPPED) causes the stage1 compiler |
| 234 | | // to crash and burn. |
| 235 | | var overlapped = [_]windows.OVERLAPPED{ |
| 236 | | mem.zeroes(windows.OVERLAPPED), |
| 237 | | mem.zeroes(windows.OVERLAPPED), |
| 238 | | }; |
| 239 | | var temp_buf: [2][4096]u8 = undefined; |
| 240 | | |
| 241 | | // Kickstart the loop by issuing two async reads. |
| 242 | | // ReadFile returns false and GetLastError returns ERROR_IO_PENDING if |
| 243 | | // everything is ok. |
| 244 | | _ = windows.kernel32.ReadFile(wait_objects[0], &temp_buf[0], temp_buf[0].len, null, &overlapped[0]); |
| 245 | | _ = windows.kernel32.ReadFile(wait_objects[1], &temp_buf[1], temp_buf[1].len, null, &overlapped[1]); |
| 246 | | |
| 247 | | poll: while (waiting_objects > 0) { |
| 248 | | const status = windows.kernel32.WaitForMultipleObjects(waiting_objects, &wait_objects, 0, windows.INFINITE); |
| 249 | | switch (status) { |
| 250 | | windows.WAIT_OBJECT_0 + 0...windows.WAIT_OBJECT_0 + 1 => { |
| 251 | | // stdout (or stderr) is ready. |
| 252 | | const object = status - windows.WAIT_OBJECT_0; |
| 253 | | |
| 254 | | var read_bytes: u32 = undefined; |
| 255 | | if (windows.kernel32.GetOverlappedResult(wait_objects[object], &overlapped[object], &read_bytes, 0) == 0) { |
| 256 | | switch (windows.kernel32.GetLastError()) { |
| 257 | | .BROKEN_PIPE => { |
| 258 | | // Move it to the end to remove it. |
| 259 | | if (object != waiting_objects - 1) |
| 260 | | mem.swap(windows.kernel32.HANDLE, &wait_objects[object], &wait_objects[waiting_objects - 1]); |
| 261 | | waiting_objects -= 1; |
| 262 | | continue :poll; |
| 263 | | }, |
| 264 | | else => |err| return windows.unexpectedError(err), |
| 265 | | } |
| 266 | | } |
| 267 | | try stdout.appendSlice(temp_buf[object][0..read_bytes]); |
| 268 | | _ = windows.kernel32.ReadFile(wait_objects[object], &temp_buf[object], temp_buf[object].len, null, &overlapped[object]); |
| 269 | | }, |
| 270 | | windows.WAIT_FAILED => { |
| 271 | | switch (windows.kernel32.GetLastError()) { |
| 272 | | else => |err| return windows.unexpectedError(err), |
| 273 | | } |
| 274 | | }, |
| 275 | | // We're waiting with an infinite timeout |
| 276 | | windows.WAIT_TIMEOUT => unreachable, |
| 277 | | else => unreachable, |
| 278 | | } |
| 279 | | } |
| 280 | | } |
| 281 | | |
| 282 | 239 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 283 | 240 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 284 | 241 | pub fn exec(args: struct { |
| ... | ... | @@ -303,16 +260,28 @@ pub const ChildProcess = struct { |
| 303 | 260 | |
| 304 | 261 | try child.spawn(); |
| 305 | 262 | |
| 263 | // TODO collect output in a deadlock-avoiding way on Windows. |
| 264 | // https://github.com/ziglang/zig/issues/6343 |
| 265 | if (builtin.os.tag == .windows) { |
| 266 | const stdout_in = child.stdout.?.reader(); |
| 267 | const stderr_in = child.stderr.?.reader(); |
| 268 | |
| 269 | const stdout = try stdout_in.readAllAlloc(args.allocator, args.max_output_bytes); |
| 270 | errdefer args.allocator.free(stdout); |
| 271 | const stderr = try stderr_in.readAllAlloc(args.allocator, args.max_output_bytes); |
| 272 | errdefer args.allocator.free(stderr); |
| 273 | |
| 274 | return ExecResult{ |
| 275 | .term = try child.wait(), |
| 276 | .stdout = stdout, |
| 277 | .stderr = stderr, |
| 278 | }; |
| 279 | } |
| 280 | |
| 306 | 281 | var stdout = std.ArrayList(u8).init(args.allocator); |
| 307 | 282 | var stderr = std.ArrayList(u8).init(args.allocator); |
| 308 | 283 | |
| 309 | | // XXX: Respect max_output_bytes |
| 310 | | // XXX: Smarter reading logic, read directly into the ArrayList |
| 311 | | if (builtin.os.tag == .windows) { |
| 312 | | try collectOutputWindows(child, &stdout, &stderr, args.max_output_bytes); |
| 313 | | } else { |
| 314 | | try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); |
| 315 | | } |
| 284 | try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); |
| 316 | 285 | |
| 317 | 286 | return ExecResult{ |
| 318 | 287 | .term = try child.wait(), |
| ... | ... | @@ -650,7 +619,7 @@ pub const ChildProcess = struct { |
| 650 | 619 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; |
| 651 | 620 | switch (self.stdout_behavior) { |
| 652 | 621 | StdIo.Pipe => { |
| 653 | | try windowsMakePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); |
| 622 | try windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); |
| 654 | 623 | }, |
| 655 | 624 | StdIo.Ignore => { |
| 656 | 625 | g_hChildStd_OUT_Wr = nul_handle; |
| ... | ... | @@ -670,7 +639,7 @@ pub const ChildProcess = struct { |
| 670 | 639 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; |
| 671 | 640 | switch (self.stderr_behavior) { |
| 672 | 641 | StdIo.Pipe => { |
| 673 | | try windowsMakePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); |
| 642 | try windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); |
| 674 | 643 | }, |
| 675 | 644 | StdIo.Ignore => { |
| 676 | 645 | g_hChildStd_ERR_Wr = nul_handle; |
| ... | ... | @@ -903,55 +872,6 @@ fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { |
| 903 | 872 | if (wr) |h| os.close(h); |
| 904 | 873 | } |
| 905 | 874 | |
| 906 | | var pipe_name_counter = std.atomic.Int(u32).init(1); |
| 907 | | |
| 908 | | fn windowsMakePipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 909 | | var tmp_buf: [128]u8 = undefined; |
| 910 | | // Forge a random path for the pipe. |
| 911 | | const pipe_path = std.fmt.bufPrintZ( |
| 912 | | &tmp_buf, |
| 913 | | "\\\\.\\pipe\\zig-childprocess-{d}-{d}", |
| 914 | | .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1) }, |
| 915 | | ) catch unreachable; |
| 916 | | |
| 917 | | // Create the read handle that can be used with overlapped IO ops. |
| 918 | | const read_handle = windows.kernel32.CreateNamedPipeA( |
| 919 | | pipe_path, |
| 920 | | windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, |
| 921 | | windows.PIPE_TYPE_BYTE, |
| 922 | | 1, |
| 923 | | 0x1000, |
| 924 | | 0x1000, |
| 925 | | 0, |
| 926 | | sattr, |
| 927 | | ); |
| 928 | | if (read_handle == windows.INVALID_HANDLE_VALUE) { |
| 929 | | switch (windows.kernel32.GetLastError()) { |
| 930 | | else => |err| return windows.unexpectedError(err), |
| 931 | | } |
| 932 | | } |
| 933 | | |
| 934 | | const write_handle = windows.kernel32.CreateFileA( |
| 935 | | pipe_path, |
| 936 | | windows.GENERIC_WRITE, |
| 937 | | 0, |
| 938 | | sattr, |
| 939 | | windows.OPEN_EXISTING, |
| 940 | | windows.FILE_ATTRIBUTE_NORMAL, |
| 941 | | null, |
| 942 | | ); |
| 943 | | if (write_handle == windows.INVALID_HANDLE_VALUE) { |
| 944 | | switch (windows.kernel32.GetLastError()) { |
| 945 | | else => |err| return windows.unexpectedError(err), |
| 946 | | } |
| 947 | | } |
| 948 | | |
| 949 | | try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); |
| 950 | | |
| 951 | | rd.* = read_handle; |
| 952 | | wr.* = write_handle; |
| 953 | | } |
| 954 | | |
| 955 | 875 | fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 956 | 876 | var rd_h: windows.HANDLE = undefined; |
| 957 | 877 | var wr_h: windows.HANDLE = undefined; |