1818use os_pipe:: { PipeReader , PipeWriter } ;
1919use std:: fs:: OpenOptions ;
2020use std:: io:: { BufRead , BufReader , Write } ;
21+ #[ cfg( unix) ]
2122use std:: os:: unix:: io:: IntoRawFd ;
23+ #[ cfg( windows) ]
24+ use std:: os:: windows:: io:: IntoRawHandle ;
2225use std:: sync:: { Arc , Mutex } ;
2326use std:: thread;
2427use tokio:: sync:: mpsc:: UnboundedSender ;
2528
2629use crate :: repl:: tui:: event:: { OutputKind , UiEvent } ;
2730
31+ /// Standard-output and standard-error file descriptors. POSIX fixes these
32+ /// at 1 and 2; the Microsoft CRT matches. `libc` exposes `STDOUT_FILENO` /
33+ /// `STDERR_FILENO` on Unix but not on Windows, so we keep our own aliases
34+ /// to stay cfg-free at the call sites.
35+ const STDOUT_FD : libc:: c_int = 1 ;
36+ const STDERR_FD : libc:: c_int = 2 ;
37+
2838/// Env var that, when set to a writable file path, makes every byte
2939/// read from the stdout/stderr capture pipes also get appended to
3040/// that file in a hex-escaped format. Intended purely for debugging
@@ -111,8 +121,8 @@ impl OutputCapture {
111121 /// Redirect fds 1 and 2 to pipes and spawn reader threads that forward
112122 /// complete lines to the UI event channel.
113123 pub fn install ( tx : UnboundedSender < UiEvent > ) -> std:: io:: Result < Self > {
114- let saved_stdout = dup_fd ( libc :: STDOUT_FILENO ) ?;
115- let saved_stderr = match dup_fd ( libc :: STDERR_FILENO ) {
124+ let saved_stdout = dup_fd ( STDOUT_FD ) ?;
125+ let saved_stderr = match dup_fd ( STDERR_FD ) {
116126 Ok ( fd) => fd,
117127 Err ( e) => {
118128 unsafe { libc:: close ( saved_stdout) } ;
@@ -141,7 +151,7 @@ impl OutputCapture {
141151 }
142152 } ;
143153
144- let pipe_stdout = match redirect ( stdout_writer, libc :: STDOUT_FILENO ) {
154+ let pipe_stdout = match redirect ( stdout_writer, STDOUT_FD ) {
145155 Ok ( fd) => fd,
146156 Err ( e) => {
147157 unsafe {
@@ -151,11 +161,11 @@ impl OutputCapture {
151161 return Err ( e) ;
152162 }
153163 } ;
154- let pipe_stderr = match redirect ( stderr_writer, libc :: STDERR_FILENO ) {
164+ let pipe_stderr = match redirect ( stderr_writer, STDERR_FD ) {
155165 Ok ( fd) => fd,
156166 Err ( e) => {
157167 unsafe {
158- libc:: dup2 ( saved_stdout, libc :: STDOUT_FILENO ) ;
168+ libc:: dup2 ( saved_stdout, STDOUT_FD ) ;
159169 libc:: close ( saved_stdout) ;
160170 libc:: close ( saved_stderr) ;
161171 libc:: close ( pipe_stdout) ;
@@ -188,8 +198,8 @@ impl OutputCapture {
188198impl Drop for OutputCapture {
189199 fn drop ( & mut self ) {
190200 unsafe {
191- libc:: dup2 ( self . saved_stdout , libc :: STDOUT_FILENO ) ;
192- libc:: dup2 ( self . saved_stderr , libc :: STDERR_FILENO ) ;
201+ libc:: dup2 ( self . saved_stdout , STDOUT_FD ) ;
202+ libc:: dup2 ( self . saved_stderr , STDERR_FD ) ;
193203 libc:: close ( self . saved_stdout ) ;
194204 libc:: close ( self . saved_stderr ) ;
195205 libc:: close ( self . pipe_stdout ) ;
@@ -212,7 +222,7 @@ fn dup_fd(fd: libc::c_int) -> std::io::Result<libc::c_int> {
212222/// the caller can dup2 it back over `target_fd` to resume redirection after
213223/// a `pause`.
214224fn redirect ( writer : PipeWriter , target_fd : libc:: c_int ) -> std:: io:: Result < libc:: c_int > {
215- let write_fd = writer . into_raw_fd ( ) ;
225+ let write_fd = pipe_writer_into_fd ( writer ) ? ;
216226 let rc = unsafe { libc:: dup2 ( write_fd, target_fd) } ;
217227 if rc < 0 {
218228 let err = std:: io:: Error :: last_os_error ( ) ;
@@ -222,6 +232,28 @@ fn redirect(writer: PipeWriter, target_fd: libc::c_int) -> std::io::Result<libc:
222232 Ok ( write_fd)
223233}
224234
235+ /// Take ownership of a `PipeWriter` and return a C-style file descriptor
236+ /// suitable for `dup2`. On Unix the pipe is already an fd; on Windows
237+ /// `os_pipe` hands us a `HANDLE`, which we register with the MSVCRT via
238+ /// `_open_osfhandle` so the CRT's fd table knows about it. The fd then
239+ /// owns the underlying handle — closing the fd closes the handle.
240+ #[ cfg( unix) ]
241+ fn pipe_writer_into_fd ( writer : PipeWriter ) -> std:: io:: Result < libc:: c_int > {
242+ Ok ( writer. into_raw_fd ( ) )
243+ }
244+
245+ #[ cfg( windows) ]
246+ fn pipe_writer_into_fd ( writer : PipeWriter ) -> std:: io:: Result < libc:: c_int > {
247+ let handle = writer. into_raw_handle ( ) ;
248+ let fd =
249+ unsafe { libc:: open_osfhandle ( handle as libc:: intptr_t , libc:: O_BINARY | libc:: O_WRONLY ) } ;
250+ if fd < 0 {
251+ Err ( std:: io:: Error :: last_os_error ( ) )
252+ } else {
253+ Ok ( fd)
254+ }
255+ }
256+
225257fn spawn_reader (
226258 reader : PipeReader ,
227259 kind : OutputKind ,
0 commit comments