Skip to content

Commit fe55eee

Browse files
authored
Rollup merge of rust-lang#93263 - sunfishcode:sunfishcode/detatched-console-handle, r=dtolnay
Consistently present absent stdio handles on Windows as NULL handles. This addresses rust-lang#90964 by making the std API consistent about presenting absent stdio handles on Windows as NULL handles. Stdio handles may be absent due to `#![windows_subsystem = "windows"]`, due to the console being detached, or due to a child process having been launched from a parent where stdio handles are absent. Specifically, this fixes the case of child processes of parents with absent stdio, which previously ended up with `stdin().as_raw_handle()` returning `INVALID_HANDLE_VALUE`, which was surprising, and which overlapped with an unrelated valid handle value. With this patch, `stdin().as_raw_handle()` now returns null in these situation, which is consistent with what it does in the parent process. And, document this in the "Windows Portability Considerations" sections of the relevant documentation.
2 parents e9f63fd + 7ddf41c commit fe55eee

File tree

3 files changed

+100
-16
lines changed

3 files changed

+100
-16
lines changed

library/std/src/io/stdio.rs

+70-9
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,18 @@ fn handle_ebadf<T>(r: io::Result<T>, default: T) -> io::Result<T> {
202202
///
203203
/// [`io::stdin`]: stdin
204204
///
205-
/// ### Note: Windows Portability Consideration
205+
/// ### Note: Windows Portability Considerations
206206
///
207207
/// When operating in a console, the Windows implementation of this stream does not support
208208
/// non-UTF-8 byte sequences. Attempting to read bytes that are not valid UTF-8 will return
209209
/// an error.
210210
///
211+
/// In a process with a detached console, such as one using
212+
/// `#![windows_subsystem = "windows"]`, or in a child process spawned from such a process,
213+
/// the contained handle will be null. In such cases, the standard library's `Read` and
214+
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
215+
/// standard library or via raw Windows API calls, will fail.
216+
///
211217
/// # Examples
212218
///
213219
/// ```no_run
@@ -230,12 +236,18 @@ pub struct Stdin {
230236
/// This handle implements both the [`Read`] and [`BufRead`] traits, and
231237
/// is constructed via the [`Stdin::lock`] method.
232238
///
233-
/// ### Note: Windows Portability Consideration
239+
/// ### Note: Windows Portability Considerations
234240
///
235241
/// When operating in a console, the Windows implementation of this stream does not support
236242
/// non-UTF-8 byte sequences. Attempting to read bytes that are not valid UTF-8 will return
237243
/// an error.
238244
///
245+
/// In a process with a detached console, such as one using
246+
/// `#![windows_subsystem = "windows"]`, or in a child process spawned from such a process,
247+
/// the contained handle will be null. In such cases, the standard library's `Read` and
248+
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
249+
/// standard library or via raw Windows API calls, will fail.
250+
///
239251
/// # Examples
240252
///
241253
/// ```no_run
@@ -263,11 +275,18 @@ pub struct StdinLock<'a> {
263275
/// is synchronized via a mutex. If you need more explicit control over
264276
/// locking, see the [`Stdin::lock`] method.
265277
///
266-
/// ### Note: Windows Portability Consideration
278+
/// ### Note: Windows Portability Considerations
279+
///
267280
/// When operating in a console, the Windows implementation of this stream does not support
268281
/// non-UTF-8 byte sequences. Attempting to read bytes that are not valid UTF-8 will return
269282
/// an error.
270283
///
284+
/// In a process with a detached console, such as one using
285+
/// `#![windows_subsystem = "windows"]`, or in a child process spawned from such a process,
286+
/// the contained handle will be null. In such cases, the standard library's `Read` and
287+
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
288+
/// standard library or via raw Windows API calls, will fail.
289+
///
271290
/// # Examples
272291
///
273292
/// Using implicit synchronization:
@@ -490,11 +509,18 @@ impl fmt::Debug for StdinLock<'_> {
490509
///
491510
/// Created by the [`io::stdout`] method.
492511
///
493-
/// ### Note: Windows Portability Consideration
512+
/// ### Note: Windows Portability Considerations
513+
///
494514
/// When operating in a console, the Windows implementation of this stream does not support
495515
/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return
496516
/// an error.
497517
///
518+
/// In a process with a detached console, such as one using
519+
/// `#![windows_subsystem = "windows"]`, or in a child process spawned from such a process,
520+
/// the contained handle will be null. In such cases, the standard library's `Read` and
521+
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
522+
/// standard library or via raw Windows API calls, will fail.
523+
///
498524
/// [`lock`]: Stdout::lock
499525
/// [`io::stdout`]: stdout
500526
#[stable(feature = "rust1", since = "1.0.0")]
@@ -510,10 +536,17 @@ pub struct Stdout {
510536
/// This handle implements the [`Write`] trait, and is constructed via
511537
/// the [`Stdout::lock`] method. See its documentation for more.
512538
///
513-
/// ### Note: Windows Portability Consideration
539+
/// ### Note: Windows Portability Considerations
540+
///
514541
/// When operating in a console, the Windows implementation of this stream does not support
515542
/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return
516543
/// an error.
544+
///
545+
/// In a process with a detached console, such as one using
546+
/// `#![windows_subsystem = "windows"]`, or in a child process spawned from such a process,
547+
/// the contained handle will be null. In such cases, the standard library's `Read` and
548+
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
549+
/// standard library or via raw Windows API calls, will fail.
517550
#[must_use = "if unused stdout will immediately unlock"]
518551
#[stable(feature = "rust1", since = "1.0.0")]
519552
pub struct StdoutLock<'a> {
@@ -528,11 +561,18 @@ static STDOUT: SyncOnceCell<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = Sy
528561
/// is synchronized via a mutex. If you need more explicit control over
529562
/// locking, see the [`Stdout::lock`] method.
530563
///
531-
/// ### Note: Windows Portability Consideration
564+
/// ### Note: Windows Portability Considerations
565+
///
532566
/// When operating in a console, the Windows implementation of this stream does not support
533567
/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return
534568
/// an error.
535569
///
570+
/// In a process with a detached console, such as one using
571+
/// `#![windows_subsystem = "windows"]`, or in a child process spawned from such a process,
572+
/// the contained handle will be null. In such cases, the standard library's `Read` and
573+
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
574+
/// standard library or via raw Windows API calls, will fail.
575+
///
536576
/// # Examples
537577
///
538578
/// Using implicit synchronization:
@@ -710,10 +750,17 @@ impl fmt::Debug for StdoutLock<'_> {
710750
///
711751
/// [`io::stderr`]: stderr
712752
///
713-
/// ### Note: Windows Portability Consideration
753+
/// ### Note: Windows Portability Considerations
754+
///
714755
/// When operating in a console, the Windows implementation of this stream does not support
715756
/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return
716757
/// an error.
758+
///
759+
/// In a process with a detached console, such as one using
760+
/// `#![windows_subsystem = "windows"]`, or in a child process spawned from such a process,
761+
/// the contained handle will be null. In such cases, the standard library's `Read` and
762+
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
763+
/// standard library or via raw Windows API calls, will fail.
717764
#[stable(feature = "rust1", since = "1.0.0")]
718765
pub struct Stderr {
719766
inner: Pin<&'static ReentrantMutex<RefCell<StderrRaw>>>,
@@ -724,10 +771,17 @@ pub struct Stderr {
724771
/// This handle implements the [`Write`] trait and is constructed via
725772
/// the [`Stderr::lock`] method. See its documentation for more.
726773
///
727-
/// ### Note: Windows Portability Consideration
774+
/// ### Note: Windows Portability Considerations
775+
///
728776
/// When operating in a console, the Windows implementation of this stream does not support
729777
/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return
730778
/// an error.
779+
///
780+
/// In a process with a detached console, such as one using
781+
/// `#![windows_subsystem = "windows"]`, or in a child process spawned from such a process,
782+
/// the contained handle will be null. In such cases, the standard library's `Read` and
783+
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
784+
/// standard library or via raw Windows API calls, will fail.
731785
#[must_use = "if unused stderr will immediately unlock"]
732786
#[stable(feature = "rust1", since = "1.0.0")]
733787
pub struct StderrLock<'a> {
@@ -738,11 +792,18 @@ pub struct StderrLock<'a> {
738792
///
739793
/// This handle is not buffered.
740794
///
741-
/// ### Note: Windows Portability Consideration
795+
/// ### Note: Windows Portability Considerations
796+
///
742797
/// When operating in a console, the Windows implementation of this stream does not support
743798
/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return
744799
/// an error.
745800
///
801+
/// In a process with a detached console, such as one using
802+
/// `#![windows_subsystem = "windows"]`, or in a child process spawned from such a process,
803+
/// the contained handle will be null. In such cases, the standard library's `Read` and
804+
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
805+
/// standard library or via raw Windows API calls, will fail.
806+
///
746807
/// # Examples
747808
///
748809
/// Using implicit synchronization:

library/std/src/os/windows/io/handle.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,22 @@ impl OwnedHandle {
164164
inherit: bool,
165165
options: c::DWORD,
166166
) -> io::Result<Self> {
167+
let handle = self.as_raw_handle();
168+
169+
// `Stdin`, `Stdout`, and `Stderr` can all hold null handles, such as
170+
// in a process with a detached console. `DuplicateHandle` would fail
171+
// if we passed it a null handle, but we can treat null as a valid
172+
// handle which doesn't do any I/O, and allow it to be duplicated.
173+
if handle.is_null() {
174+
return unsafe { Ok(Self::from_raw_handle(handle)) };
175+
}
176+
167177
let mut ret = 0 as c::HANDLE;
168178
cvt(unsafe {
169179
let cur_proc = c::GetCurrentProcess();
170180
c::DuplicateHandle(
171181
cur_proc,
172-
self.as_raw_handle(),
182+
handle,
173183
cur_proc,
174184
&mut ret,
175185
access,

library/std/src/os/windows/io/raw.rs

+19-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::net;
99
use crate::os::windows::io::{AsHandle, AsSocket};
1010
use crate::os::windows::io::{OwnedHandle, OwnedSocket};
1111
use crate::os::windows::raw;
12+
use crate::ptr;
1213
use crate::sys;
1314
use crate::sys::c;
1415
use crate::sys_common::{self, AsInner, FromInner, IntoInner};
@@ -96,45 +97,57 @@ impl AsRawHandle for fs::File {
9697
#[stable(feature = "asraw_stdio", since = "1.21.0")]
9798
impl AsRawHandle for io::Stdin {
9899
fn as_raw_handle(&self) -> RawHandle {
99-
unsafe { c::GetStdHandle(c::STD_INPUT_HANDLE) as RawHandle }
100+
stdio_handle(unsafe { c::GetStdHandle(c::STD_INPUT_HANDLE) as RawHandle })
100101
}
101102
}
102103

103104
#[stable(feature = "asraw_stdio", since = "1.21.0")]
104105
impl AsRawHandle for io::Stdout {
105106
fn as_raw_handle(&self) -> RawHandle {
106-
unsafe { c::GetStdHandle(c::STD_OUTPUT_HANDLE) as RawHandle }
107+
stdio_handle(unsafe { c::GetStdHandle(c::STD_OUTPUT_HANDLE) as RawHandle })
107108
}
108109
}
109110

110111
#[stable(feature = "asraw_stdio", since = "1.21.0")]
111112
impl AsRawHandle for io::Stderr {
112113
fn as_raw_handle(&self) -> RawHandle {
113-
unsafe { c::GetStdHandle(c::STD_ERROR_HANDLE) as RawHandle }
114+
stdio_handle(unsafe { c::GetStdHandle(c::STD_ERROR_HANDLE) as RawHandle })
114115
}
115116
}
116117

117118
#[stable(feature = "asraw_stdio_locks", since = "1.35.0")]
118119
impl<'a> AsRawHandle for io::StdinLock<'a> {
119120
fn as_raw_handle(&self) -> RawHandle {
120-
unsafe { c::GetStdHandle(c::STD_INPUT_HANDLE) as RawHandle }
121+
stdio_handle(unsafe { c::GetStdHandle(c::STD_INPUT_HANDLE) as RawHandle })
121122
}
122123
}
123124

124125
#[stable(feature = "asraw_stdio_locks", since = "1.35.0")]
125126
impl<'a> AsRawHandle for io::StdoutLock<'a> {
126127
fn as_raw_handle(&self) -> RawHandle {
127-
unsafe { c::GetStdHandle(c::STD_OUTPUT_HANDLE) as RawHandle }
128+
stdio_handle(unsafe { c::GetStdHandle(c::STD_OUTPUT_HANDLE) as RawHandle })
128129
}
129130
}
130131

131132
#[stable(feature = "asraw_stdio_locks", since = "1.35.0")]
132133
impl<'a> AsRawHandle for io::StderrLock<'a> {
133134
fn as_raw_handle(&self) -> RawHandle {
134-
unsafe { c::GetStdHandle(c::STD_ERROR_HANDLE) as RawHandle }
135+
stdio_handle(unsafe { c::GetStdHandle(c::STD_ERROR_HANDLE) as RawHandle })
135136
}
136137
}
137138

139+
// Translate a handle returned from `GetStdHandle` into a handle to return to
140+
// the user.
141+
fn stdio_handle(raw: RawHandle) -> RawHandle {
142+
// `GetStdHandle` isn't expected to actually fail, so when it returns
143+
// `INVALID_HANDLE_VALUE`, it means we were launched from a parent which
144+
// didn't provide us with stdio handles, such as a parent with a detached
145+
// console. In that case, return null to the user, which is consistent
146+
// with what they'd get in the parent, and which avoids the problem that
147+
// `INVALID_HANDLE_VALUE` aliases the current process handle.
148+
if raw == c::INVALID_HANDLE_VALUE { ptr::null_mut() } else { raw }
149+
}
150+
138151
#[stable(feature = "from_raw_os", since = "1.1.0")]
139152
impl FromRawHandle for fs::File {
140153
#[inline]

0 commit comments

Comments
 (0)