Skip to content

Commit 78a347e

Browse files
committed
Make TUI output capture compile on Windows via open_osfhandle
1 parent 987bde3 commit 78a347e

1 file changed

Lines changed: 40 additions & 8 deletions

File tree

src/repl/tui/output.rs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,23 @@
1818
use os_pipe::{PipeReader, PipeWriter};
1919
use std::fs::OpenOptions;
2020
use std::io::{BufRead, BufReader, Write};
21+
#[cfg(unix)]
2122
use std::os::unix::io::IntoRawFd;
23+
#[cfg(windows)]
24+
use std::os::windows::io::IntoRawHandle;
2225
use std::sync::{Arc, Mutex};
2326
use std::thread;
2427
use tokio::sync::mpsc::UnboundedSender;
2528

2629
use 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 {
188198
impl 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`.
214224
fn 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+
225257
fn spawn_reader(
226258
reader: PipeReader,
227259
kind: OutputKind,

0 commit comments

Comments
 (0)