@@ -33,6 +33,9 @@ public func CreatePipe(
3333 nSize : DWORD
3434) : BOOL;
3535
36+ @ dllimport @ extern @ stdcall
37+ public func SetHandleInformation (hObject : HANDLE, dwMask : DWORD, dwFlags : DWORD) : BOOL;
38+
3639// ---------------------------------------------------------------------------
3740// Constants
3841// ---------------------------------------------------------------------------
@@ -42,6 +45,7 @@ comptime const CREATE_NO_WINDOW : DWORD = 0x08000000 as DWORD;
4245comptime const WAIT_OBJECT_0 : DWORD = 0 as DWORD;
4346comptime const WAIT_TIMEOUT : DWORD = 258 as DWORD;
4447comptime const INFINITE : DWORD = 0xFFFFFFFF as DWORD;
48+ comptime const HANDLE_FLAG_INHERIT : DWORD = 0x00000001 as DWORD;
4549
4650// ---------------------------------------------------------------------------
4751// Internal helpers
@@ -64,11 +68,9 @@ func win_read_all(h : HANDLE, data : *mut vector<u8>) : bool {
6468 return true
6569}
6670
67- /// Build a command line string from args: "cmd /c arg1 arg2 arg3"
68- /// The result is a null-terminated mutable buffer for CreateProcessA .
71+ /// Build a command line string from args with "cmd /c" wrapper.
72+ /// Used by win_execute where stdin piping is not needed .
6973func win_build_cmdline (args : *vector<string>) : string {
70- // Always use cmd /c to ensure cmd.exe builtins and PATH resolution work.
71- // Quote args after -c flags so sh receives the full command string.
7274 var cmd = string ("cmd /c " )
7375 var i : size_t = 0
7476 var prev_was_flag = false
@@ -114,6 +116,7 @@ public func win_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool {
114116 if (CreatePipe (&raw mut stdout_read, &raw mut stdout_write, &raw mut sa, 0 ) == 0 ) {
115117 return false
116118 }
119+ SetHandleInformation (stdout_read, HANDLE_FLAG_INHERIT, 0 )
117120 }
118121
119122 // Create stderr pipe (not needed when merging into stdout)
@@ -125,6 +128,7 @@ public func win_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool {
125128 }
126129 return false
127130 }
131+ SetHandleInformation (stderr_read, HANDLE_FLAG_INHERIT, 0 )
128132 }
129133
130134 // Build command line
@@ -242,6 +246,11 @@ public func win_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool {
242246 if (CreatePipe (&raw mut stdout_read, &raw mut stdout_write, &raw mut sa, 0 ) == 0 ) {
243247 return false
244248 }
249+ // Prevent the parent-side read handle from being inherited by the child.
250+ // Without this, CreateProcessA duplicates ALL inheritable handles, so the
251+ // parent's copy of stdout_read would keep the pipe alive even after the
252+ // real read end is closed — blocking win_read_all forever.
253+ SetHandleInformation (stdout_read, HANDLE_FLAG_INHERIT, 0 )
245254 }
246255
247256 // Create stderr pipe
@@ -253,6 +262,7 @@ public func win_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool {
253262 }
254263 return false
255264 }
265+ SetHandleInformation (stderr_read, HANDLE_FLAG_INHERIT, 0 )
256266 }
257267
258268 // Create stdin pipe
@@ -261,6 +271,10 @@ public func win_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool {
261271 if (cfg.capture_stderr) { CloseHandle (stderr_read); CloseHandle (stderr_write) }
262272 return false
263273 }
274+ // Prevent the parent-side write handle from being inherited by the child.
275+ // Without this, closing stdin_write in the parent doesn't send EOF because
276+ // the child still has its own inherited copy of the write handle.
277+ SetHandleInformation (stdin_write, HANDLE_FLAG_INHERIT, 0 )
264278
265279 // Build command line
266280 var cmd = win_build_cmdline (&raw mut cfg.args)
@@ -331,6 +345,14 @@ public func win_wait(child : *mut ChildProcess, out : *mut ProcessResult) : bool
331345 var stdout_data = vector< u8> ()
332346 var stderr_data = vector< u8> ()
333347
348+ // Close stdin first — the child may be blocking on stdin (e.g. cat,
349+ // shell). Closing the write end sends EOF so the child can exit and
350+ // close its stdout/stderr, preventing a deadlock in win_read_all.
351+ if (child.win.h_stdin_write != null) {
352+ CloseHandle (child.win.h_stdin_write)
353+ child.win.h_stdin_write = null
354+ }
355+
334356 // Read stdout
335357 if (child.win.h_stdout_read != null) {
336358 win_read_all (child.win.h_stdout_read, &raw mut stdout_data)
@@ -345,12 +367,6 @@ public func win_wait(child : *mut ChildProcess, out : *mut ProcessResult) : bool
345367 child.win.h_stderr_read = null
346368 }
347369
348- // Close stdin
349- if (child.win.h_stdin_write != null) {
350- CloseHandle (child.win.h_stdin_write)
351- child.win.h_stdin_write = null
352- }
353-
354370 // Wait for process
355371 WaitForSingleObject (child.win.h_process, INFINITE)
356372
0 commit comments