uncurses/terminal/stdio.rs
1//! Direct, unbuffered handles for inherited standard streams.
2//!
3//! [`Stdout`], [`Stderr`], and [`Stdin`] mirror [`std::io::stdout`],
4//! [`std::io::stderr`], and [`std::io::stdin`] for terminal I/O, but avoid the
5//! standard library's line writer and input buffering. They are cheap `Copy`
6//! handles over process-wide stream state and are suitable for renderers that
7//! want explicit control over flushing and buffering.
8//!
9//! ## Behavior relative to `std::io`
10//!
11//! * **Process-wide singletons with a shared lock.** Every call to
12//! [`stdout`] / [`stderr`] / [`stdin`] returns a fresh handle that
13//! references the same underlying [`Mutex`]-guarded raw stream.
14//! Cloning a handle is free; only the [`Mutex`] is shared, so
15//! concurrent writes from multiple threads are serialised and do
16//! not interleave at the byte level.
17//! * **No line buffering on writes.** A call to [`Write::write`] is
18//! forwarded to a single OS write call, regardless of any `\n`
19//! present in the buffer.
20//! * **Unbuffered.** No reads or writes are batched. Wrap in
21//! [`std::io::BufWriter`] / [`std::io::BufReader`] to coalesce
22//! syscalls.
23//! * **Same Windows console semantics.** When the inherited handle
24//! refers to a console, output is transcoded UTF-8 → UTF-16 and
25//! delivered through `WriteConsoleW`, and input is read with
26//! `ReadConsoleW` and transcoded UTF-16 → UTF-8 — matching what
27//! `std::io::Stdout` / `std::io::Stdin` do. UTF-8 sequences split
28//! across calls are carried forward via a 4-byte per-stream buffer.
29//! * **Independent of `std`'s lock.** The shared [`Mutex`] used here
30//! is separate from the one inside [`std::io::Stdout`] /
31//! [`std::io::Stdin`], so concurrent `println!` / panic output may
32//! still interleave with ours. TUI applications that own the
33//! screen typically route diagnostic output away from stdout to
34//! avoid this.
35//! * **Non-reentrant lock.** Writing to the same stream from inside
36//! a write call (for example, from a panic hook that prints to
37//! stdout while a write is in progress on the same thread) will
38//! deadlock. The std counterparts use a reentrant lock to avoid
39//! this; we accept the limitation in exchange for keeping the
40//! surface area small.
41//!
42//! ## Usage
43//!
44//! Use the free functions when constructing [`Terminal`](super::Terminal) or
45//! when writing directly to the inherited streams. Wrap a handle in
46//! [`std::io::BufWriter`] if many small writes should be coalesced.
47//!
48//! Each [`Stdout`] / [`Stderr`] / [`Stdin`] is a borrowed view of the
49//! inherited descriptor; dropping it does not close that descriptor.
50
51use std::io::{self, IoSlice, Read, Write};
52use std::sync::{Mutex, MutexGuard, OnceLock};
53
54#[cfg(unix)]
55use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
56
57#[cfg(windows)]
58use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle};
59
60// ---------------------------------------------------------------------------
61// Process-wide singletons
62// ---------------------------------------------------------------------------
63
64static STDOUT: OnceLock<Mutex<StdoutRaw>> = OnceLock::new();
65static STDERR: OnceLock<Mutex<StderrRaw>> = OnceLock::new();
66static STDIN: OnceLock<Mutex<StdinRaw>> = OnceLock::new();
67
68fn stdout_lock() -> &'static Mutex<StdoutRaw> {
69 STDOUT.get_or_init(|| Mutex::new(StdoutRaw(imp::out())))
70}
71fn stderr_lock() -> &'static Mutex<StderrRaw> {
72 STDERR.get_or_init(|| Mutex::new(StderrRaw(imp::err())))
73}
74fn stdin_lock() -> &'static Mutex<StdinRaw> {
75 STDIN.get_or_init(|| Mutex::new(StdinRaw(imp::input())))
76}
77
78/// Return a handle to inherited standard output.
79///
80/// Every call returns a cheap `Copy` handle referencing the same process-wide
81/// stdout state. Writes through this handle are unbuffered except for the
82/// platform console transcoding described in the module-level documentation.
83///
84/// # Returns
85///
86/// A [`Stdout`] handle.
87///
88/// # Errors and panics
89///
90/// This function does not fail or intentionally panic. I/O errors are reported
91/// by the handle's [`Write`] implementation.
92pub fn stdout() -> Stdout {
93 Stdout {
94 inner: stdout_lock(),
95 }
96}
97
98/// Return a handle to inherited standard error.
99///
100/// Every call returns a cheap `Copy` handle referencing the same process-wide
101/// stderr state. Writes are unbuffered and serialized through this module's
102/// stderr lock.
103///
104/// # Returns
105///
106/// A [`Stderr`] handle.
107///
108/// # Errors and panics
109///
110/// This function does not fail or intentionally panic. I/O errors are reported
111/// by the handle's [`Write`] implementation.
112pub fn stderr() -> Stderr {
113 Stderr {
114 inner: stderr_lock(),
115 }
116}
117
118/// Return a handle to inherited standard input.
119///
120/// Every call returns a cheap `Copy` handle referencing the same process-wide
121/// stdin state. Reads are unbuffered and serialized through this module's
122/// stdin lock.
123///
124/// # Returns
125///
126/// A [`Stdin`] handle.
127///
128/// # Errors and panics
129///
130/// This function does not fail or intentionally panic. I/O errors are reported
131/// by the handle's [`Read`] implementation.
132pub fn stdin() -> Stdin {
133 Stdin {
134 inner: stdin_lock(),
135 }
136}
137
138// ---------------------------------------------------------------------------
139// Raw, unbuffered, unlocked streams (private)
140// ---------------------------------------------------------------------------
141
142struct StdoutRaw(imp::Output);
143struct StderrRaw(imp::Output);
144struct StdinRaw(imp::Input);
145
146impl Write for StdoutRaw {
147 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
148 self.0.write(buf)
149 }
150 fn flush(&mut self) -> io::Result<()> {
151 self.0.flush()
152 }
153 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
154 self.0.write_vectored(bufs)
155 }
156}
157
158impl Write for StderrRaw {
159 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
160 self.0.write(buf)
161 }
162 fn flush(&mut self) -> io::Result<()> {
163 self.0.flush()
164 }
165 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
166 self.0.write_vectored(bufs)
167 }
168}
169
170impl Read for StdinRaw {
171 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
172 self.0.read(buf)
173 }
174}
175
176// ---------------------------------------------------------------------------
177// Public handle types
178// ---------------------------------------------------------------------------
179
180/// Handle to the inherited standard output stream.
181///
182/// `Stdout` is `Copy`; copying it creates another view of the same
183/// process-wide stream and lock. Dropping it does not close file descriptor 1
184/// or the Windows standard output handle.
185///
186/// Use [`lock`](Stdout::lock) to hold the stdout lock across multiple writes,
187/// or write directly to the handle for per-call locking. See the
188/// module-level documentation for buffering and Windows console behavior.
189#[derive(Clone, Copy)]
190pub struct Stdout {
191 inner: &'static Mutex<StdoutRaw>,
192}
193
194/// Handle to the inherited standard error stream.
195///
196/// `Stderr` is `Copy`; copying it creates another view of the same
197/// process-wide stream and lock. Dropping it does not close file descriptor 2
198/// or the Windows standard error handle.
199///
200/// Use [`lock`](Stderr::lock) to hold the stderr lock across multiple writes,
201/// or write directly to the handle for per-call locking.
202#[derive(Clone, Copy)]
203pub struct Stderr {
204 inner: &'static Mutex<StderrRaw>,
205}
206
207/// Handle to the inherited standard input stream.
208///
209/// `Stdin` is `Copy`; copying it creates another view of the same process-wide
210/// stream and lock. Dropping it does not close file descriptor 0 or the
211/// Windows standard input handle.
212///
213/// Use [`lock`](Stdin::lock) to hold the stdin lock across multiple reads, or
214/// read directly from the handle for per-call locking.
215#[derive(Clone, Copy)]
216pub struct Stdin {
217 inner: &'static Mutex<StdinRaw>,
218}
219
220impl Stdout {
221 /// Acquire stdout's shared write lock.
222 ///
223 /// While the returned guard is held, no other [`Stdout`] handle can write
224 /// through this module. The lock is not reentrant; attempting to write to
225 /// stdout through this module again on the same thread while holding the
226 /// guard will deadlock.
227 ///
228 /// # Returns
229 ///
230 /// A [`StdoutLock`] that implements [`Write`].
231 ///
232 /// # Errors and panics
233 ///
234 /// This method does not return errors. If another thread panicked while
235 /// holding the mutex, the poisoned lock is recovered and the guard is still
236 /// returned.
237 pub fn lock(&self) -> StdoutLock<'static> {
238 StdoutLock {
239 guard: self.inner.lock().unwrap_or_else(|e| e.into_inner()),
240 }
241 }
242}
243
244impl Stderr {
245 /// Acquire stderr's shared write lock.
246 ///
247 /// While the returned guard is held, no other [`Stderr`] handle can write
248 /// through this module. The lock is not reentrant; attempting to write to
249 /// stderr through this module again on the same thread while holding the
250 /// guard will deadlock.
251 ///
252 /// # Returns
253 ///
254 /// A [`StderrLock`] that implements [`Write`].
255 ///
256 /// # Errors and panics
257 ///
258 /// This method does not return errors. Poisoned locks are recovered.
259 pub fn lock(&self) -> StderrLock<'static> {
260 StderrLock {
261 guard: self.inner.lock().unwrap_or_else(|e| e.into_inner()),
262 }
263 }
264}
265
266impl Stdin {
267 /// Acquire stdin's shared read lock.
268 ///
269 /// While the returned guard is held, no other [`Stdin`] handle can read
270 /// through this module. The lock is not reentrant; attempting to read from
271 /// stdin through this module again on the same thread while holding the
272 /// guard will deadlock.
273 ///
274 /// # Returns
275 ///
276 /// A [`StdinLock`] that implements [`Read`].
277 ///
278 /// # Errors and panics
279 ///
280 /// This method does not return errors. Poisoned locks are recovered.
281 pub fn lock(&self) -> StdinLock<'static> {
282 StdinLock {
283 guard: self.inner.lock().unwrap_or_else(|e| e.into_inner()),
284 }
285 }
286}
287
288// ---------------------------------------------------------------------------
289// Lock guards
290// ---------------------------------------------------------------------------
291
292/// Locked, exclusive access to [`Stdout`].
293///
294/// The guard implements [`Write`] and releases the stdout lock when dropped.
295/// Holding it across several writes prevents interleaving with other writes
296/// performed through this module.
297pub struct StdoutLock<'a> {
298 guard: MutexGuard<'a, StdoutRaw>,
299}
300
301/// Locked, exclusive access to [`Stderr`].
302///
303/// The guard implements [`Write`] and releases the stderr lock when dropped.
304pub struct StderrLock<'a> {
305 guard: MutexGuard<'a, StderrRaw>,
306}
307
308/// Locked, exclusive access to [`Stdin`].
309///
310/// The guard implements [`Read`] and releases the stdin lock when dropped.
311pub struct StdinLock<'a> {
312 guard: MutexGuard<'a, StdinRaw>,
313}
314
315impl Write for StdoutLock<'_> {
316 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
317 self.guard.write(buf)
318 }
319 fn flush(&mut self) -> io::Result<()> {
320 self.guard.flush()
321 }
322 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
323 self.guard.write_vectored(bufs)
324 }
325}
326
327impl Write for StderrLock<'_> {
328 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
329 self.guard.write(buf)
330 }
331 fn flush(&mut self) -> io::Result<()> {
332 self.guard.flush()
333 }
334 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
335 self.guard.write_vectored(bufs)
336 }
337}
338
339impl Read for StdinLock<'_> {
340 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
341 self.guard.read(buf)
342 }
343}
344
345// ---------------------------------------------------------------------------
346// Write/Read impls on the handle types
347//
348// Each call locks the shared mutex briefly, performs the I/O, and
349// releases. Both `&mut self` and `&self` impls are provided so the
350// handles can be used with `write!`/`writeln!` macros via either an
351// owned binding or a shared reference.
352// ---------------------------------------------------------------------------
353
354impl Write for Stdout {
355 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
356 self.lock().write(buf)
357 }
358 fn flush(&mut self) -> io::Result<()> {
359 self.lock().flush()
360 }
361 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
362 self.lock().write_vectored(bufs)
363 }
364}
365
366impl Write for &Stdout {
367 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
368 self.lock().write(buf)
369 }
370 fn flush(&mut self) -> io::Result<()> {
371 self.lock().flush()
372 }
373 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
374 self.lock().write_vectored(bufs)
375 }
376}
377
378impl Write for Stderr {
379 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
380 self.lock().write(buf)
381 }
382 fn flush(&mut self) -> io::Result<()> {
383 self.lock().flush()
384 }
385 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
386 self.lock().write_vectored(bufs)
387 }
388}
389
390impl Write for &Stderr {
391 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
392 self.lock().write(buf)
393 }
394 fn flush(&mut self) -> io::Result<()> {
395 self.lock().flush()
396 }
397 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
398 self.lock().write_vectored(bufs)
399 }
400}
401
402impl Read for Stdin {
403 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
404 self.lock().read(buf)
405 }
406}
407
408impl Read for &Stdin {
409 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
410 self.lock().read(buf)
411 }
412}
413
414// ---------------------------------------------------------------------------
415// AsFd / AsRawFd / AsHandle / AsRawHandle
416//
417// Implemented on every handle and guard type. The underlying
418// descriptor / handle is process-stable, so these are lock-free and
419// always reference the inherited stream.
420// ---------------------------------------------------------------------------
421
422#[cfg(unix)]
423const STDOUT_FD: RawFd = libc::STDOUT_FILENO;
424#[cfg(unix)]
425const STDERR_FD: RawFd = libc::STDERR_FILENO;
426#[cfg(unix)]
427const STDIN_FD: RawFd = libc::STDIN_FILENO;
428
429#[cfg(unix)]
430macro_rules! impl_as_fd {
431 ($ty:ty, $fd:expr) => {
432 impl AsFd for $ty {
433 fn as_fd(&self) -> BorrowedFd<'_> {
434 // SAFETY: the standard descriptors are inherited from
435 // the parent and remain valid for the lifetime of the
436 // process; the returned borrow is bounded by `&self`.
437 unsafe { BorrowedFd::borrow_raw($fd) }
438 }
439 }
440 impl AsRawFd for $ty {
441 fn as_raw_fd(&self) -> RawFd {
442 $fd
443 }
444 }
445 };
446}
447
448#[cfg(unix)]
449impl_as_fd!(Stdout, STDOUT_FD);
450#[cfg(unix)]
451impl_as_fd!(Stderr, STDERR_FD);
452#[cfg(unix)]
453impl_as_fd!(Stdin, STDIN_FD);
454#[cfg(unix)]
455impl_as_fd!(StdoutLock<'_>, STDOUT_FD);
456#[cfg(unix)]
457impl_as_fd!(StderrLock<'_>, STDERR_FD);
458#[cfg(unix)]
459impl_as_fd!(StdinLock<'_>, STDIN_FD);
460
461#[cfg(windows)]
462macro_rules! impl_as_handle {
463 ($ty:ty, $which:expr) => {
464 impl AsHandle for $ty {
465 fn as_handle(&self) -> BorrowedHandle<'_> {
466 // SAFETY: the standard handles are inherited from the
467 // parent and remain valid for the lifetime of the
468 // process; the returned borrow is bounded by `&self`.
469 let h = unsafe { ::windows_sys::Win32::System::Console::GetStdHandle($which) };
470 unsafe { BorrowedHandle::borrow_raw(h as _) }
471 }
472 }
473 impl AsRawHandle for $ty {
474 fn as_raw_handle(&self) -> RawHandle {
475 let h = unsafe { ::windows_sys::Win32::System::Console::GetStdHandle($which) };
476 h as RawHandle
477 }
478 }
479 };
480}
481
482#[cfg(windows)]
483impl_as_handle!(
484 Stdout,
485 ::windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE
486);
487#[cfg(windows)]
488impl_as_handle!(
489 Stderr,
490 ::windows_sys::Win32::System::Console::STD_ERROR_HANDLE
491);
492#[cfg(windows)]
493impl_as_handle!(
494 Stdin,
495 ::windows_sys::Win32::System::Console::STD_INPUT_HANDLE
496);
497#[cfg(windows)]
498impl_as_handle!(
499 StdoutLock<'_>,
500 ::windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE
501);
502#[cfg(windows)]
503impl_as_handle!(
504 StderrLock<'_>,
505 ::windows_sys::Win32::System::Console::STD_ERROR_HANDLE
506);
507#[cfg(windows)]
508impl_as_handle!(
509 StdinLock<'_>,
510 ::windows_sys::Win32::System::Console::STD_INPUT_HANDLE
511);
512
513// ---------------------------------------------------------------------------
514// Unix implementation
515// ---------------------------------------------------------------------------
516
517#[cfg(unix)]
518mod imp {
519 use std::fs::File;
520 use std::io::{self, Read, Write};
521 use std::mem::ManuallyDrop;
522 use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd};
523
524 pub(super) fn out() -> Output {
525 Output::new(libc::STDOUT_FILENO)
526 }
527 pub(super) fn err() -> Output {
528 Output::new(libc::STDERR_FILENO)
529 }
530 pub(super) fn input() -> Input {
531 Input::new(libc::STDIN_FILENO)
532 }
533
534 pub(super) struct Output {
535 // Borrowed view of an inherited fd; `ManuallyDrop` keeps `File`
536 // from closing the descriptor when the wrapper is dropped.
537 file: ManuallyDrop<File>,
538 }
539
540 pub(super) struct Input {
541 file: ManuallyDrop<File>,
542 }
543
544 impl Output {
545 fn new(fd: RawFd) -> Self {
546 // SAFETY: fd 1 / 2 are inherited from the parent process
547 // and remain valid for its lifetime; `ManuallyDrop`
548 // prevents `File::drop` from calling `close(2)`.
549 Self {
550 file: ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }),
551 }
552 }
553 }
554
555 impl Input {
556 fn new(fd: RawFd) -> Self {
557 // SAFETY: see `Output::new`.
558 Self {
559 file: ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }),
560 }
561 }
562 }
563
564 impl Write for Output {
565 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
566 (&*self.file).write(buf)
567 }
568 fn flush(&mut self) -> io::Result<()> {
569 (&*self.file).flush()
570 }
571 fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
572 (&*self.file).write_vectored(bufs)
573 }
574 }
575
576 impl Read for Input {
577 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
578 (&*self.file).read(buf)
579 }
580 }
581
582 impl AsFd for Output {
583 fn as_fd(&self) -> BorrowedFd<'_> {
584 self.file.as_fd()
585 }
586 }
587 impl AsRawFd for Output {
588 fn as_raw_fd(&self) -> RawFd {
589 self.file.as_raw_fd()
590 }
591 }
592 impl AsFd for Input {
593 fn as_fd(&self) -> BorrowedFd<'_> {
594 self.file.as_fd()
595 }
596 }
597 impl AsRawFd for Input {
598 fn as_raw_fd(&self) -> RawFd {
599 self.file.as_raw_fd()
600 }
601 }
602}
603
604// ---------------------------------------------------------------------------
605// Windows implementation
606// ---------------------------------------------------------------------------
607
608#[cfg(windows)]
609mod imp {
610 use std::io::{self, IoSlice, Read, Write};
611 use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle};
612 use std::ptr;
613 use std::sync::{Mutex, OnceLock};
614
615 use windows_sys::Win32::Foundation::HANDLE;
616 use windows_sys::Win32::Storage::FileSystem::{
617 FILE_TYPE_CHAR, GetFileType, ReadFile, WriteFile,
618 };
619 use windows_sys::Win32::System::Console::{
620 GetConsoleMode, GetStdHandle, ReadConsoleW, STD_ERROR_HANDLE, STD_INPUT_HANDLE,
621 STD_OUTPUT_HANDLE, WriteConsoleW,
622 };
623
624 // ---- Output ----------------------------------------------------------
625
626 pub(super) fn out() -> Output {
627 Output::new(STD_OUTPUT_HANDLE, &OUT_STATE)
628 }
629 pub(super) fn err() -> Output {
630 Output::new(STD_ERROR_HANDLE, &ERR_STATE)
631 }
632
633 static OUT_STATE: OnceLock<Mutex<PartialUtf8>> = OnceLock::new();
634 static ERR_STATE: OnceLock<Mutex<PartialUtf8>> = OnceLock::new();
635 static IN_STATE: OnceLock<Mutex<PartialUtf8>> = OnceLock::new();
636
637 pub(super) struct Output {
638 which: u32,
639 state: &'static OnceLock<Mutex<PartialUtf8>>,
640 }
641
642 impl Output {
643 fn new(which: u32, state: &'static OnceLock<Mutex<PartialUtf8>>) -> Self {
644 Self { which, state }
645 }
646 fn raw_handle(&self) -> HANDLE {
647 // SAFETY: GetStdHandle returns the inherited process handle
648 // or a null/invalid sentinel; callers downstream check for
649 // failure via WriteFile/WriteConsoleW returning 0.
650 unsafe { GetStdHandle(self.which) }
651 }
652 fn state(&self) -> &'static Mutex<PartialUtf8> {
653 self.state.get_or_init(|| Mutex::new(PartialUtf8::new()))
654 }
655 }
656
657 impl Write for Output {
658 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
659 let h = self.raw_handle();
660 if is_console(h) {
661 write_console(h, &mut self.state().lock().unwrap(), buf)
662 } else {
663 write_file(h, buf)
664 }
665 }
666 fn flush(&mut self) -> io::Result<()> {
667 Ok(())
668 }
669 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
670 // No scatter syscall on Windows console writes; do the
671 // simple thing: write the first non-empty slice.
672 for b in bufs {
673 if !b.is_empty() {
674 return self.write(b);
675 }
676 }
677 Ok(0)
678 }
679 }
680
681 impl AsHandle for Output {
682 fn as_handle(&self) -> BorrowedHandle<'_> {
683 // SAFETY: the returned BorrowedHandle is tied to `&self`
684 // and never outlives the process-inherited handle.
685 unsafe { BorrowedHandle::borrow_raw(self.raw_handle() as _) }
686 }
687 }
688 impl AsRawHandle for Output {
689 fn as_raw_handle(&self) -> RawHandle {
690 self.raw_handle() as RawHandle
691 }
692 }
693
694 // ---- Input -----------------------------------------------------------
695
696 pub(super) fn input() -> Input {
697 Input
698 }
699
700 pub(super) struct Input;
701
702 impl Input {
703 fn raw_handle(&self) -> HANDLE {
704 unsafe { GetStdHandle(STD_INPUT_HANDLE) }
705 }
706 fn state(&self) -> &'static Mutex<PartialUtf8> {
707 IN_STATE.get_or_init(|| Mutex::new(PartialUtf8::new()))
708 }
709 }
710
711 impl Read for Input {
712 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
713 let h = self.raw_handle();
714 if is_console(h) {
715 read_console(h, &mut self.state().lock().unwrap(), buf)
716 } else {
717 read_file(h, buf)
718 }
719 }
720 }
721
722 impl AsHandle for Input {
723 fn as_handle(&self) -> BorrowedHandle<'_> {
724 unsafe { BorrowedHandle::borrow_raw(self.raw_handle() as _) }
725 }
726 }
727 impl AsRawHandle for Input {
728 fn as_raw_handle(&self) -> RawHandle {
729 self.raw_handle() as RawHandle
730 }
731 }
732
733 // ---- shared helpers --------------------------------------------------
734
735 /// Carry-over for UTF-8 sequences split across calls.
736 ///
737 /// On the write side: holds the trailing bytes of a partial UTF-8
738 /// codepoint from the previous call so the next call can prepend
739 /// them.
740 ///
741 /// On the read side: holds the trailing bytes of a UTF-8 codepoint
742 /// that did not fit in the caller's buffer so the next call can
743 /// return them first. Sized at 8 bytes so up to two 4-byte
744 /// codepoints worth of overflow can be queued (see `read_console`
745 /// for the sizing argument).
746 struct PartialUtf8 {
747 buf: [u8; 8],
748 len: u8,
749 }
750
751 impl PartialUtf8 {
752 fn new() -> Self {
753 Self {
754 buf: [0; 8],
755 len: 0,
756 }
757 }
758 }
759
760 fn is_console(h: HANDLE) -> bool {
761 if h.is_null() || (h as isize) == -1 {
762 return false;
763 }
764 if unsafe { GetFileType(h) } != FILE_TYPE_CHAR {
765 return false;
766 }
767 let mut mode: u32 = 0;
768 unsafe { GetConsoleMode(h, &mut mode) != 0 }
769 }
770
771 fn write_file(h: HANDLE, buf: &[u8]) -> io::Result<usize> {
772 if buf.is_empty() {
773 return Ok(0);
774 }
775 // WriteFile takes a u32 length; cap to a safe chunk.
776 let len = buf.len().min(u32::MAX as usize) as u32;
777 let mut written: u32 = 0;
778 let ok = unsafe { WriteFile(h, buf.as_ptr(), len, &mut written, ptr::null_mut()) };
779 if ok == 0 {
780 return Err(io::Error::last_os_error());
781 }
782 Ok(written as usize)
783 }
784
785 fn read_file(h: HANDLE, buf: &mut [u8]) -> io::Result<usize> {
786 if buf.is_empty() {
787 return Ok(0);
788 }
789 let len = buf.len().min(u32::MAX as usize) as u32;
790 let mut read: u32 = 0;
791 let ok = unsafe { ReadFile(h, buf.as_mut_ptr(), len, &mut read, ptr::null_mut()) };
792 if ok == 0 {
793 return Err(io::Error::last_os_error());
794 }
795 Ok(read as usize)
796 }
797
798 /// Console write path.
799 ///
800 /// Validates `buf` (concatenated with any carried-over partial
801 /// UTF-8 prefix) as UTF-8, transcodes the longest valid prefix to
802 /// UTF-16, and writes it with `WriteConsoleW`. Saves any trailing
803 /// 1..=3 bytes of an incomplete final codepoint into `state` so a
804 /// future call can complete it.
805 ///
806 /// Returns the number of bytes from the *caller's* `buf` that were
807 /// consumed.
808 fn write_console(h: HANDLE, state: &mut PartialUtf8, buf: &[u8]) -> io::Result<usize> {
809 if buf.is_empty() {
810 return Ok(0);
811 }
812
813 // 1. Try to complete any saved partial sequence with the
814 // leading bytes of `buf`. A UTF-8 codepoint is at most 4
815 // bytes, so we need to append at most 3 leading bytes.
816 let mut leading_consumed: usize = 0;
817 if state.len > 0 {
818 while leading_consumed < buf.len() && (state.len as usize) < 4 {
819 state.buf[state.len as usize] = buf[leading_consumed];
820 state.len += 1;
821 leading_consumed += 1;
822
823 // After each append: if the saved bytes form a valid
824 // UTF-8 codepoint, emit it and clear the buffer.
825 if let Ok(s) = std::str::from_utf8(&state.buf[..state.len as usize]) {
826 let c = s.chars().next().unwrap();
827 let mut units = [0u16; 2];
828 let encoded = c.encode_utf16(&mut units);
829 write_utf16_console_all(h, encoded)?;
830 state.len = 0;
831 break;
832 }
833
834 // 4 bytes that still aren't valid UTF-8: bail out with
835 // a replacement character.
836 if state.len == 4 {
837 write_utf16_console_all(h, &['\u{FFFD}' as u16])?;
838 state.len = 0;
839 break;
840 }
841 }
842 if state.len > 0 {
843 // Still incomplete; we've consumed everything we were
844 // given but produced no caller-visible codepoint yet.
845 return Ok(leading_consumed);
846 }
847 }
848
849 let rest = &buf[leading_consumed..];
850 if rest.is_empty() {
851 return Ok(leading_consumed);
852 }
853
854 // 2. Find the longest valid UTF-8 prefix of `rest`.
855 match std::str::from_utf8(rest) {
856 Ok(s) => {
857 write_utf8_to_console(h, s)?;
858 Ok(leading_consumed + rest.len())
859 }
860 Err(e) => {
861 let v = e.valid_up_to();
862 // SAFETY: `v` is a valid UTF-8 boundary as reported by
863 // `Utf8Error::valid_up_to`.
864 let valid = unsafe { std::str::from_utf8_unchecked(&rest[..v]) };
865 if !valid.is_empty() {
866 write_utf8_to_console(h, valid)?;
867 }
868 match e.error_len() {
869 Some(err_len) => {
870 // Truly invalid bytes; emit a single
871 // replacement and report progress past them.
872 write_utf16_console_all(h, &['\u{FFFD}' as u16])?;
873 Ok(leading_consumed + v + err_len)
874 }
875 None => {
876 // Trailing partial codepoint; stash 1..=3
877 // bytes for the next call to complete.
878 let trailing = &rest[v..];
879 state.buf[..trailing.len()].copy_from_slice(trailing);
880 state.len = trailing.len() as u8;
881 Ok(leading_consumed + rest.len())
882 }
883 }
884 }
885 }
886 }
887
888 /// Transcode `s` to UTF-16 and write it with `WriteConsoleW`,
889 /// looping until every code unit is delivered.
890 fn write_utf8_to_console(h: HANDLE, s: &str) -> io::Result<()> {
891 // Use a small stack buffer and refill it in chunks to avoid an
892 // unbounded heap allocation for very large writes.
893 const CHUNK: usize = 1024;
894 let mut buf = [0u16; CHUNK];
895 let mut idx = 0;
896 for c in s.chars() {
897 let need = c.len_utf16();
898 if idx + need > CHUNK {
899 write_utf16_console_all(h, &buf[..idx])?;
900 idx = 0;
901 }
902 // Encode directly into the buffer.
903 let written = c.encode_utf16(&mut buf[idx..idx + need]).len();
904 idx += written;
905 }
906 if idx > 0 {
907 write_utf16_console_all(h, &buf[..idx])?;
908 }
909 Ok(())
910 }
911
912 /// Single `WriteConsoleW` call; returns the number of UTF-16 code
913 /// units written, or an error.
914 fn write_utf16_console(h: HANDLE, data: &[u16]) -> io::Result<usize> {
915 if data.is_empty() {
916 return Ok(0);
917 }
918 let mut written: u32 = 0;
919 let ok = unsafe {
920 WriteConsoleW(
921 h,
922 data.as_ptr(),
923 data.len() as u32,
924 &mut written,
925 ptr::null(),
926 )
927 };
928 if ok == 0 {
929 return Err(io::Error::last_os_error());
930 }
931 Ok(written as usize)
932 }
933
934 /// `WriteConsoleW` looped until the entire slice is consumed.
935 fn write_utf16_console_all(h: HANDLE, mut data: &[u16]) -> io::Result<()> {
936 while !data.is_empty() {
937 let n = write_utf16_console(h, data)?;
938 if n == 0 {
939 return Err(io::Error::new(
940 io::ErrorKind::WriteZero,
941 "WriteConsoleW returned 0 with non-empty buffer",
942 ));
943 }
944 data = &data[n..];
945 }
946 Ok(())
947 }
948
949 /// Console read path.
950 ///
951 /// Drains any UTF-8 bytes previously stashed (overflow from the
952 /// last read), then calls `ReadConsoleW` for a fresh batch and
953 /// transcodes UTF-16 → UTF-8 into the caller's buffer. If the
954 /// caller's buffer is too small for a decoded codepoint, the
955 /// remaining bytes are saved in `state` for the next call.
956 ///
957 /// Sizing: each UTF-16 code unit decodes to at most 3 UTF-8 bytes
958 /// (BMP), and a surrogate pair (2 units) decodes to 4 UTF-8 bytes
959 /// total. We cap the request at `(buf.len() + 4) / 3` units, with
960 /// a floor of 2 so a surrogate pair can be paired in a single
961 /// read. That bounds the produced UTF-8 byte count at
962 /// `request * 3`, which never exceeds `buf.len() + stash_cap`
963 /// (`stash_cap == 8`) for `buf.len() >= 1`.
964 fn read_console(h: HANDLE, state: &mut PartialUtf8, buf: &mut [u8]) -> io::Result<usize> {
965 if buf.is_empty() {
966 return Ok(0);
967 }
968
969 // 1. Drain anything we stashed from a previous call.
970 if state.len > 0 {
971 let n = (state.len as usize).min(buf.len());
972 buf[..n].copy_from_slice(&state.buf[..n]);
973 let rem = state.len as usize - n;
974 if rem > 0 {
975 state.buf.copy_within(n..n + rem, 0);
976 }
977 state.len = rem as u8;
978 return Ok(n);
979 }
980
981 // 2. Read a bounded number of UTF-16 code units so that the
982 // decoded UTF-8 byte count fits in `buf` plus the stash.
983 const MAX_UNITS: usize = 256;
984 let want = ((buf.len() + 4) / 3).clamp(2, MAX_UNITS);
985 let mut units = [0u16; MAX_UNITS];
986 let mut read_units: u32 = 0;
987 let ok = unsafe {
988 ReadConsoleW(
989 h,
990 units.as_mut_ptr().cast(),
991 want as u32,
992 &mut read_units,
993 ptr::null(),
994 )
995 };
996 if ok == 0 {
997 return Err(io::Error::last_os_error());
998 }
999 if read_units == 0 {
1000 return Ok(0);
1001 }
1002 let read_units = read_units as usize;
1003
1004 // 3. Decode all received UTF-16 units to UTF-8 in one pass into
1005 // a stack scratch buffer; then split between caller `buf`
1006 // and `state.buf`.
1007 //
1008 // Worst case decoded size: `read_units * 3` for an all-BMP
1009 // 3-byte-UTF-8 stream. `read_units <= MAX_UNITS == 256`, so
1010 // 768 bytes suffices.
1011 let mut scratch = [0u8; MAX_UNITS * 3];
1012 let mut s_len = 0usize;
1013 for r in char::decode_utf16(units[..read_units].iter().copied()) {
1014 let c = r.unwrap_or('\u{FFFD}');
1015 let n = c.len_utf8();
1016 c.encode_utf8(&mut scratch[s_len..s_len + n]);
1017 s_len += n;
1018 }
1019
1020 let n = s_len.min(buf.len());
1021 buf[..n].copy_from_slice(&scratch[..n]);
1022 let overflow = s_len - n;
1023 if overflow > 0 {
1024 debug_assert!(overflow <= state.buf.len());
1025 let cap = state.buf.len();
1026 let take = overflow.min(cap);
1027 state.buf[..take].copy_from_slice(&scratch[n..n + take]);
1028 state.len = take as u8;
1029 }
1030 Ok(n)
1031 }
1032}
1033
1034#[cfg(test)]
1035mod tests {
1036 use super::*;
1037 use std::io::Write;
1038
1039 #[test]
1040 fn stdout_handle_constructible() {
1041 let _ = stdout();
1042 let _ = stderr();
1043 let _ = stdin();
1044 }
1045
1046 #[test]
1047 fn stdout_write_empty_ok() {
1048 // Writing zero bytes must be a no-op regardless of attachment.
1049 let mut out = stdout();
1050 assert!(matches!(out.write(b""), Ok(0)));
1051 }
1052
1053 #[cfg(unix)]
1054 #[test]
1055 fn handles_expose_inherited_fds() {
1056 use std::os::fd::AsRawFd;
1057 assert_eq!(stdin().as_raw_fd(), libc::STDIN_FILENO);
1058 assert_eq!(stdout().as_raw_fd(), libc::STDOUT_FILENO);
1059 assert_eq!(stderr().as_raw_fd(), libc::STDERR_FILENO);
1060 }
1061
1062 #[cfg(windows)]
1063 #[test]
1064 fn handles_match_getstdhandle() {
1065 use std::os::windows::io::AsRawHandle;
1066 use windows_sys::Win32::System::Console::{
1067 GetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
1068 };
1069 unsafe {
1070 assert_eq!(
1071 stdin().as_raw_handle() as isize,
1072 GetStdHandle(STD_INPUT_HANDLE) as isize
1073 );
1074 assert_eq!(
1075 stdout().as_raw_handle() as isize,
1076 GetStdHandle(STD_OUTPUT_HANDLE) as isize
1077 );
1078 assert_eq!(
1079 stderr().as_raw_handle() as isize,
1080 GetStdHandle(STD_ERROR_HANDLE) as isize
1081 );
1082 }
1083 }
1084}