authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-06-25 21:42:18-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-03 15:25:22-07:00
logb0be0c77d8fb11e1abed578f37a859a2084a934e
tree3502a52d80387023d4908ff550560573e3e37b26
parent440da6bfad48fe6ede6f097863f0d815de79fd27

Merge pull request #9148 from marler8997/windowsChildOutput

finish ChildProcess collectOutputWindows

3 files changed, 166 insertions(+), 11 deletions(-)

lib/std/child_process.zig+139-11
...@@ -13,6 +13,7 @@ const process = std.process;...@@ -13,6 +13,7 @@ const process = std.process;
13const File = std.fs.File;13const File = std.fs.File;
14const windows = os.windows;14const windows = os.windows;
15const mem = std.mem;15const mem = std.mem;
16const math = std.math;
16const debug = std.debug;17const debug = std.debug;
17const BufMap = std.BufMap;18const BufMap = std.BufMap;
18const builtin = std.builtin;19const builtin = std.builtin;
...@@ -257,6 +258,79 @@ pub const ChildProcess = struct {...@@ -257,6 +258,79 @@ pub const ChildProcess = struct {
257 }258 }
258 }259 }
259260
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 }
308382
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 }
310388
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}
909987
910fn windowsMakePipeOut(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {988var pipe_name_counter = std.atomic.Atomic(u32).init(1);
911 var rd_h: windows.HANDLE = undefined;989
912 var wr_h: windows.HANDLE = undefined;990fn 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}
9191047
920fn destroyPipe(pipe: [2]os.fd_t) void {1048fn destroyPipe(pipe: [2]os.fd_t) void {
lib/std/os/windows/bits.zig+13
...@@ -452,6 +452,19 @@ pub const SECURITY_ATTRIBUTES = extern struct {...@@ -452,6 +452,19 @@ pub const SECURITY_ATTRIBUTES = extern struct {
452pub const PSECURITY_ATTRIBUTES = *SECURITY_ATTRIBUTES;452pub const PSECURITY_ATTRIBUTES = *SECURITY_ATTRIBUTES;
453pub const LPSECURITY_ATTRIBUTES = *SECURITY_ATTRIBUTES;453pub const LPSECURITY_ATTRIBUTES = *SECURITY_ATTRIBUTES;
454454
455pub const PIPE_ACCESS_INBOUND = 0x00000001;
456pub const PIPE_ACCESS_OUTBOUND = 0x00000002;
457pub const PIPE_ACCESS_DUPLEX = 0x00000003;
458
459pub const PIPE_TYPE_BYTE = 0x00000000;
460pub const PIPE_TYPE_MESSAGE = 0x00000004;
461
462pub const PIPE_READMODE_BYTE = 0x00000000;
463pub const PIPE_READMODE_MESSAGE = 0x00000002;
464
465pub const PIPE_WAIT = 0x00000000;
466pub const PIPE_NOWAIT = 0x00000001;
467
455pub const GENERIC_READ = 0x80000000;468pub const GENERIC_READ = 0x80000000;
456pub const GENERIC_WRITE = 0x40000000;469pub const GENERIC_WRITE = 0x40000000;
457pub const GENERIC_EXECUTE = 0x20000000;470pub const GENERIC_EXECUTE = 0x20000000;
lib/std/os/windows/kernel32.zig+14
...@@ -8,6 +8,7 @@ usingnamespace @import("bits.zig");...@@ -8,6 +8,7 @@ usingnamespace @import("bits.zig");
8pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*c_void;8pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*c_void;
9pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;9pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;
1010
11pub extern "kernel32" fn CancelIo(hFile: HANDLE) callconv(WINAPI) BOOL;
11pub extern "kernel32" fn CancelIoEx(hFile: HANDLE, lpOverlapped: ?LPOVERLAPPED) callconv(WINAPI) BOOL;12pub extern "kernel32" fn CancelIoEx(hFile: HANDLE, lpOverlapped: ?LPOVERLAPPED) callconv(WINAPI) BOOL;
1213
13pub extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL;14pub extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL;
...@@ -39,6 +40,17 @@ pub extern "kernel32" fn CreatePipe(...@@ -39,6 +40,17 @@ pub extern "kernel32" fn CreatePipe(
39 nSize: DWORD,40 nSize: DWORD,
40) callconv(WINAPI) BOOL;41) callconv(WINAPI) BOOL;
4142
43pub extern "kernel32" fn CreateNamedPipeW(
44 lpName: LPCWSTR,
45 dwOpenMode: DWORD,
46 dwPipeMode: DWORD,
47 nMaxInstances: DWORD,
48 nOutBufferSize: DWORD,
49 nInBufferSize: DWORD,
50 nDefaultTimeOut: DWORD,
51 lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES,
52) callconv(WINAPI) HANDLE;
53
42pub extern "kernel32" fn CreateProcessW(54pub extern "kernel32" fn CreateProcessW(
43 lpApplicationName: ?LPWSTR,55 lpApplicationName: ?LPWSTR,
44 lpCommandLine: LPWSTR,56 lpCommandLine: LPWSTR,
...@@ -99,6 +111,8 @@ pub extern "kernel32" fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[...@@ -99,6 +111,8 @@ pub extern "kernel32" fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[
99pub extern "kernel32" fn GetCurrentThread() callconv(WINAPI) HANDLE;111pub extern "kernel32" fn GetCurrentThread() callconv(WINAPI) HANDLE;
100pub extern "kernel32" fn GetCurrentThreadId() callconv(WINAPI) DWORD;112pub extern "kernel32" fn GetCurrentThreadId() callconv(WINAPI) DWORD;
101113
114pub extern "kernel32" fn GetCurrentProcessId() callconv(WINAPI) DWORD;
115
102pub extern "kernel32" fn GetCurrentProcess() callconv(WINAPI) HANDLE;116pub extern "kernel32" fn GetCurrentProcess() callconv(WINAPI) HANDLE;
103117
104pub extern "kernel32" fn GetEnvironmentStringsW() callconv(WINAPI) ?[*:0]u16;118pub extern "kernel32" fn GetEnvironmentStringsW() callconv(WINAPI) ?[*:0]u16;