| ... | @@ -186,6 +186,106 @@ pub const ChildProcess = struct { | ... | @@ -186,6 +186,106 @@ pub const ChildProcess = struct { |
| 186 | | 186 | |
| 187 | pub const exec2 = @compileError("deprecated: exec2 is renamed to exec"); | 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 { |
| | 190 | var poll_fds = [_]os.pollfd{ |
| | 191 | .{ .fd = child.stdout.?.handle, .events = os.POLLIN, .revents = undefined }, |
| | 192 | .{ .fd = child.stderr.?.handle, .events = os.POLLIN, .revents = undefined }, |
| | 193 | }; |
| | 194 | |
| | 195 | var dead_fds: usize = 0; |
| | 196 | var loop_buf: [4096]u8 = undefined; |
| | 197 | |
| | 198 | while (dead_fds < poll_fds.len) { |
| | 199 | const events = try os.poll(&poll_fds, std.math.maxInt(i32)); |
| | 200 | if (events == 0) continue; |
| | 201 | |
| | 202 | // Try reading whatever is available before checking the error |
| | 203 | // conditions. |
| | 204 | if (poll_fds[0].revents & os.POLLIN != 0) { |
| | 205 | // stdout is ready. |
| | 206 | const n = try os.read(poll_fds[0].fd, &loop_buf); |
| | 207 | try stdout.appendSlice(loop_buf[0..n]); |
| | 208 | } |
| | 209 | if (poll_fds[1].revents & os.POLLIN != 0) { |
| | 210 | // stderr is ready. |
| | 211 | const n = try os.read(poll_fds[1].fd, &loop_buf); |
| | 212 | try stderr.appendSlice(loop_buf[0..n]); |
| | 213 | } |
| | 214 | |
| | 215 | // Exclude the fds that signaled an error. |
| | 216 | if (poll_fds[0].revents & (os.POLLERR | os.POLLNVAL | os.POLLHUP) != 0) { |
| | 217 | poll_fds[0].fd = -1; |
| | 218 | dead_fds += 1; |
| | 219 | } |
| | 220 | if (poll_fds[1].revents & (os.POLLERR | os.POLLNVAL | os.POLLHUP) != 0) { |
| | 221 | poll_fds[1].fd = -1; |
| | 222 | dead_fds += 1; |
| | 223 | } |
| | 224 | } |
| | 225 | } |
| | 226 | |
| | 227 | fn collectOutputWindows(child: *const ChildProcess, stdout: *std.ArrayList(u8), stderr: *std.ArrayList(u8), max_output_bytes: usize) !void { |
| | 228 | // The order of the objects here is important, WaitForMultipleObjects |
| | 229 | // uses the same order when scanning the events. |
| | 230 | var wait_objects = [_]windows.kernel32.HANDLE{ |
| | 231 | child.handle, child.stdout.?.handle, child.stderr.?.handle, |
| | 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[1], &temp_buf[0], temp_buf[0].len, null, &overlapped[0]); |
| | 245 | _ = windows.kernel32.ReadFile(wait_objects[2], &temp_buf[1], temp_buf[1].len, null, &overlapped[1]); |
| | 246 | |
| | 247 | while (true) { |
| | 248 | const status = windows.kernel32.WaitForMultipleObjects(wait_objects.len, &wait_objects, 0, windows.INFINITE); |
| | 249 | std.debug.print("status {x}\n", .{status}); |
| | 250 | switch (status) { |
| | 251 | windows.WAIT_OBJECT_0 + 0 => { |
| | 252 | // The child process was terminated. |
| | 253 | break; |
| | 254 | }, |
| | 255 | windows.WAIT_OBJECT_0 + 1 => { |
| | 256 | // stdout is ready. |
| | 257 | var read_bytes: u32 = undefined; |
| | 258 | if (windows.kernel32.GetOverlappedResult(wait_objects[1], &overlapped[0], &read_bytes, 0) == 0) { |
| | 259 | switch (windows.kernel32.GetLastError()) { |
| | 260 | else => |err| return windows.unexpectedError(err), |
| | 261 | } |
| | 262 | } |
| | 263 | try stdout.appendSlice(temp_buf[0][0..read_bytes]); |
| | 264 | _ = windows.kernel32.ReadFile(wait_objects[1], &temp_buf[0], temp_buf[0].len, null, &overlapped[0]); |
| | 265 | }, |
| | 266 | windows.WAIT_OBJECT_0 + 2 => { |
| | 267 | // stderr is ready. |
| | 268 | var read_bytes: u32 = undefined; |
| | 269 | if (windows.kernel32.GetOverlappedResult(wait_objects[2], &overlapped[1], &read_bytes, 0) == 0) { |
| | 270 | switch (windows.kernel32.GetLastError()) { |
| | 271 | else => |err| return windows.unexpectedError(err), |
| | 272 | } |
| | 273 | } |
| | 274 | try stdout.appendSlice(temp_buf[1][0..read_bytes]); |
| | 275 | _ = windows.kernel32.ReadFile(wait_objects[2], &temp_buf[1], temp_buf[1].len, null, &overlapped[1]); |
| | 276 | }, |
| | 277 | windows.WAIT_FAILED => { |
| | 278 | switch (windows.kernel32.GetLastError()) { |
| | 279 | else => |err| return windows.unexpectedError(err), |
| | 280 | } |
| | 281 | }, |
| | 282 | // We're waiting with an infinite timeout |
| | 283 | windows.WAIT_TIMEOUT => unreachable, |
| | 284 | else => unreachable, |
| | 285 | } |
| | 286 | } |
| | 287 | } |
| | 288 | |
| 189 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. | 289 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 190 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. | 290 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 191 | pub fn exec(args: struct { | 291 | pub fn exec(args: struct { |
| ... | @@ -210,19 +310,21 @@ pub const ChildProcess = struct { | ... | @@ -210,19 +310,21 @@ pub const ChildProcess = struct { |
| 210 | | 310 | |
| 211 | try child.spawn(); | 311 | try child.spawn(); |
| 212 | | 312 | |
| 213 | const stdout_in = child.stdout.?.reader(); | 313 | var stdout = std.ArrayList(u8).init(args.allocator); |
| 214 | const stderr_in = child.stderr.?.reader(); | 314 | var stderr = std.ArrayList(u8).init(args.allocator); |
| 215 | | 315 | |
| 216 | // TODO https://github.com/ziglang/zig/issues/6343 | 316 | // XXX: Respect max_output_bytes |
| 217 | const stdout = try stdout_in.readAllAlloc(args.allocator, args.max_output_bytes); | 317 | // XXX: Smarter reading logic, read directly into the ArrayList |
| 218 | errdefer args.allocator.free(stdout); | 318 | if (builtin.os.tag == .windows) { |
| 219 | const stderr = try stderr_in.readAllAlloc(args.allocator, args.max_output_bytes); | 319 | try collectOutputWindows(child, &stdout, &stderr, args.max_output_bytes); |
| 220 | errdefer args.allocator.free(stderr); | 320 | } else { |
| | 321 | try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); |
| | 322 | } |
| 221 | | 323 | |
| 222 | return ExecResult{ | 324 | return ExecResult{ |
| 223 | .term = try child.wait(), | 325 | .term = try child.wait(), |
| 224 | .stdout = stdout, | 326 | .stdout = stdout.toOwnedSlice(), |
| 225 | .stderr = stderr, | 327 | .stderr = stderr.toOwnedSlice(), |
| 226 | }; | 328 | }; |
| 227 | } | 329 | } |
| 228 | | 330 | |
| ... | @@ -555,7 +657,7 @@ pub const ChildProcess = struct { | ... | @@ -555,7 +657,7 @@ pub const ChildProcess = struct { |
| 555 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; | 657 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; |
| 556 | switch (self.stdout_behavior) { | 658 | switch (self.stdout_behavior) { |
| 557 | StdIo.Pipe => { | 659 | StdIo.Pipe => { |
| 558 | try windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); | 660 | try windowsMakePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); |
| 559 | }, | 661 | }, |
| 560 | StdIo.Ignore => { | 662 | StdIo.Ignore => { |
| 561 | g_hChildStd_OUT_Wr = nul_handle; | 663 | g_hChildStd_OUT_Wr = nul_handle; |
| ... | @@ -575,7 +677,7 @@ pub const ChildProcess = struct { | ... | @@ -575,7 +677,7 @@ pub const ChildProcess = struct { |
| 575 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; | 677 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; |
| 576 | switch (self.stderr_behavior) { | 678 | switch (self.stderr_behavior) { |
| 577 | StdIo.Pipe => { | 679 | StdIo.Pipe => { |
| 578 | try windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); | 680 | try windowsMakePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); |
| 579 | }, | 681 | }, |
| 580 | StdIo.Ignore => { | 682 | StdIo.Ignore => { |
| 581 | g_hChildStd_ERR_Wr = nul_handle; | 683 | g_hChildStd_ERR_Wr = nul_handle; |
| ... | @@ -808,6 +910,55 @@ fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { | ... | @@ -808,6 +910,55 @@ fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { |
| 808 | if (wr) |h| os.close(h); | 910 | if (wr) |h| os.close(h); |
| 809 | } | 911 | } |
| 810 | | 912 | |
| | 913 | var pipe_name_counter = std.atomic.Int(u32).init(1); |
| | 914 | |
| | 915 | fn windowsMakePipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| | 916 | var tmp_buf: [128]u8 = undefined; |
| | 917 | // Forge a random path for the pipe. |
| | 918 | const pipe_path = std.fmt.bufPrintZ( |
| | 919 | &tmp_buf, |
| | 920 | "\\\\.\\pipe\\zig-childprocess-{d}-{d}", |
| | 921 | .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1) }, |
| | 922 | ) catch unreachable; |
| | 923 | |
| | 924 | // Create the read handle that can be used with overlapped IO ops. |
| | 925 | const read_handle = windows.kernel32.CreateNamedPipeA( |
| | 926 | pipe_path, |
| | 927 | windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, |
| | 928 | windows.PIPE_TYPE_BYTE, |
| | 929 | 1, |
| | 930 | 0x1000, |
| | 931 | 0x1000, |
| | 932 | 0, |
| | 933 | sattr, |
| | 934 | ); |
| | 935 | if (read_handle == windows.INVALID_HANDLE_VALUE) { |
| | 936 | switch (windows.kernel32.GetLastError()) { |
| | 937 | else => |err| return windows.unexpectedError(err), |
| | 938 | } |
| | 939 | } |
| | 940 | |
| | 941 | const write_handle = windows.kernel32.CreateFileA( |
| | 942 | pipe_path, |
| | 943 | windows.GENERIC_WRITE, |
| | 944 | 0, |
| | 945 | sattr, |
| | 946 | windows.OPEN_EXISTING, |
| | 947 | windows.FILE_ATTRIBUTE_NORMAL, |
| | 948 | null, |
| | 949 | ); |
| | 950 | if (write_handle == windows.INVALID_HANDLE_VALUE) { |
| | 951 | switch (windows.kernel32.GetLastError()) { |
| | 952 | else => |err| return windows.unexpectedError(err), |
| | 953 | } |
| | 954 | } |
| | 955 | |
| | 956 | try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); |
| | 957 | |
| | 958 | rd.* = read_handle; |
| | 959 | wr.* = write_handle; |
| | 960 | } |
| | 961 | |
| 811 | fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { | 962 | fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { |
| 812 | var rd_h: windows.HANDLE = undefined; | 963 | var rd_h: windows.HANDLE = undefined; |
| 813 | var wr_h: windows.HANDLE = undefined; | 964 | var wr_h: windows.HANDLE = undefined; |