Skip to content

Commit c1d0e7e

Browse files
committed
Add fallback for io internals
Add fallback implementation in stdio redirection for when named pipes are not available Since Windows 9X/ME does not support creating named pipes (only connecting to remote pipes created on NT), we'll have to make do with anonymous pipes, without overlapped I/O. In particular, this means that we'll have to spawn another thread in the case where both stdout and stderr are being piped and read from (`read2`). We also use the fallback implementation on NT before 4.0, as the `Drop` impl of `AsyncPipe` needs to be able to cancel I/O via `CancelIo`. Add fallbacks for `NtReadFile` and `NtWriteFile` in `synchronous_{read, write}` These might be unsound for handles that _can_ be asynchronous on 9x/ME. See rust-lang#95469 for more info
1 parent 698f731 commit c1d0e7e

File tree

7 files changed

+173
-6
lines changed

7 files changed

+173
-6
lines changed

library/std/src/sys/windows/c.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,8 @@ compat_fn_lazy! {
474474
cchcount2: i32,
475475
bignorecase: BOOL,
476476
) -> COMPARESTRING_RESULT;
477+
478+
pub fn CancelIo(hfile: HANDLE) -> BOOL;
477479
}
478480

479481
compat_fn_optional! {

library/std/src/sys/windows/c/windows_sys.lst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2631,3 +2631,8 @@ Windows.Win32.System.SystemInformation.GetVersion
26312631
Windows.Win32.Storage.FileSystem.CopyFileW
26322632
Windows.Win32.Storage.FileSystem.GetFileSize
26332633
Windows.Win32.Storage.FileSystem.INVALID_FILE_SIZE
2634+
2635+
// async io fallbacks
2636+
Windows.Win32.System.IO.CancelIo
2637+
Windows.Win32.System.Pipes.CreatePipe
2638+
Windows.Win32.Storage.FileSystem.WriteFile

library/std/src/sys/windows/c/windows_sys.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ extern "system" {
142142
) -> HANDLE;
143143
}
144144
#[link(name = "kernel32")]
145+
extern "system" {
146+
pub fn CreatePipe(
147+
hreadpipe: *mut HANDLE,
148+
hwritepipe: *mut HANDLE,
149+
lppipeattributes: *const SECURITY_ATTRIBUTES,
150+
nsize: u32,
151+
) -> BOOL;
152+
}
153+
#[link(name = "kernel32")]
145154
extern "system" {
146155
pub fn CreateProcessW(
147156
lpapplicationname: PCWSTR,
@@ -724,6 +733,16 @@ extern "system" {
724733
) -> BOOL;
725734
}
726735
#[link(name = "kernel32")]
736+
extern "system" {
737+
pub fn WriteFile(
738+
hfile: HANDLE,
739+
lpbuffer: *const u8,
740+
nnumberofbytestowrite: u32,
741+
lpnumberofbyteswritten: *mut u32,
742+
lpoverlapped: *mut OVERLAPPED,
743+
) -> BOOL;
744+
}
745+
#[link(name = "kernel32")]
727746
extern "system" {
728747
pub fn WriteFileEx(
729748
hfile: HANDLE,

library/std/src/sys/windows/compat.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::sync::atomic::Ordering;
2525
use crate::sys::c;
2626

2727
mod version;
28-
pub use version::is_windows_nt;
28+
pub use version::{is_windows_nt, supports_async_io};
2929

3030
// This uses a static initializer to preload some imported functions.
3131
// The CRT (C runtime) executes static initializers before `main`
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
use crate::sys::c;
22

33
static mut IS_NT: bool = true;
4+
static mut SUPPORTS_ASYNC_IO: bool = true;
45

56
pub fn init_windows_version_check() {
67
// according to old MSDN info, the high-order bit is set only on 95/98/ME.
7-
unsafe { IS_NT = c::GetVersion() < 0x8000_0000 };
8+
unsafe {
9+
IS_NT = c::GetVersion() < 0x8000_0000;
10+
SUPPORTS_ASYNC_IO = IS_NT && c::CancelIo::option().is_some();
11+
};
812
}
913

1014
/// Returns true if we are running on a Windows NT-based system. Only use this for APIs where the
@@ -13,3 +17,8 @@ pub fn init_windows_version_check() {
1317
pub fn is_windows_nt() -> bool {
1418
unsafe { IS_NT }
1519
}
20+
21+
#[inline(always)]
22+
pub fn supports_async_io() -> bool {
23+
unsafe { SUPPORTS_ASYNC_IO }
24+
}

library/std/src/sys/windows/handle.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#[cfg(test)]
44
mod tests;
55

6+
use super::compat;
67
use crate::cmp;
78
use crate::io::{self, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut, Read};
89
use crate::mem;
@@ -230,10 +231,32 @@ impl Handle {
230231
len: usize,
231232
offset: Option<u64>,
232233
) -> io::Result<usize> {
233-
let mut io_status = c::IO_STATUS_BLOCK::PENDING;
234-
235234
// The length is clamped at u32::MAX.
236235
let len = cmp::min(len, c::DWORD::MAX as usize) as c::DWORD;
236+
237+
if !compat::supports_async_io() {
238+
if let Some(offset) = offset {
239+
cvt(c::SetFilePointerEx(
240+
self.as_raw_handle(),
241+
offset as i64,
242+
ptr::null_mut(),
243+
c::FILE_BEGIN,
244+
))?;
245+
}
246+
247+
let mut bytes_read = 0;
248+
cvt(c::ReadFile(
249+
self.as_raw_handle(),
250+
buf.cast(),
251+
len,
252+
&mut bytes_read,
253+
ptr::null_mut(),
254+
))?;
255+
256+
return Ok(bytes_read as usize);
257+
}
258+
259+
let mut io_status = c::IO_STATUS_BLOCK::PENDING;
237260
let status = c::NtReadFile(
238261
self.as_handle(),
239262
ptr::null_mut(),
@@ -278,10 +301,34 @@ impl Handle {
278301
///
279302
/// If `offset` is `None` then the current file position is used.
280303
fn synchronous_write(&self, buf: &[u8], offset: Option<u64>) -> io::Result<usize> {
281-
let mut io_status = c::IO_STATUS_BLOCK::PENDING;
282-
283304
// The length is clamped at u32::MAX.
284305
let len = cmp::min(buf.len(), c::DWORD::MAX as usize) as c::DWORD;
306+
307+
if !compat::supports_async_io() {
308+
unsafe {
309+
if let Some(offset) = offset {
310+
cvt(c::SetFilePointerEx(
311+
self.as_raw_handle(),
312+
offset as i64,
313+
ptr::null_mut(),
314+
c::FILE_BEGIN,
315+
))?;
316+
}
317+
318+
let mut bytes_written = 0;
319+
cvt(c::WriteFile(
320+
self.as_raw_handle(),
321+
buf.as_ptr(),
322+
len,
323+
&mut bytes_written,
324+
ptr::null_mut(),
325+
))?;
326+
327+
return Ok(bytes_written as usize);
328+
}
329+
}
330+
331+
let mut io_status = c::IO_STATUS_BLOCK::PENDING;
285332
let status = unsafe {
286333
c::NtWriteFile(
287334
self.as_handle(),

library/std/src/sys/windows/pipe.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,60 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res
6262
// A 64kb pipe capacity is the same as a typical Linux default.
6363
const PIPE_BUFFER_CAPACITY: u32 = 64 * 1024;
6464

65+
// Since Windows 9X/ME does not support creating named pipes (only connecting to remote pipes
66+
// created on NT), we'll have to make do with anonymous pipes, without overlapped I/O. In
67+
// particular, this means that we'll have to do reading from two threads in the case where both
68+
// stdout and stderr being piped (see `read2`).
69+
70+
// 9X/ME *does* have a kernel32 export entry for `CreateNamedPipe`, so an availability check
71+
// would not work. We're just gonna check the bit that's only set on non-unicode Windows
72+
// versions instead...
73+
74+
// The `AnonPipe` impl used in `read2` below needs to be able to cancel the overlapped i/o
75+
// operation, so we also have to check for `CancelIo` being available. This means that the
76+
// "modern" path is taken only for NT4+.
77+
if !crate::sys::compat::supports_async_io() {
78+
let size = mem::size_of::<c::SECURITY_ATTRIBUTES>();
79+
let mut sa = c::SECURITY_ATTRIBUTES {
80+
nLength: size as c::DWORD,
81+
lpSecurityDescriptor: ptr::null_mut(),
82+
// We follow the old "Creating a Child Process with Redirected Input and Output" MSDN
83+
// entry (pre-`SetHandleInformation`) here, duplicating the handle that is not being
84+
// sent to the child process as non-inheritable and then closing the inheritable one.
85+
// Usually, this would be racy, but this function is only called in `Stdio::to_handle`,
86+
// which is in turn only called form `process::spawn`, which acquires a lock on process
87+
// spawning because of this.
88+
bInheritHandle: c::TRUE,
89+
};
90+
91+
unsafe {
92+
let mut read_pipe = mem::zeroed();
93+
let mut write_pipe = mem::zeroed();
94+
crate::sys::cvt(c::CreatePipe(
95+
&mut read_pipe,
96+
&mut write_pipe,
97+
&mut sa,
98+
PIPE_BUFFER_CAPACITY,
99+
))?;
100+
let read_pipe = Handle::from_raw_handle(read_pipe);
101+
let write_pipe = Handle::from_raw_handle(write_pipe);
102+
103+
let (ours_inheritable, theirs) =
104+
if ours_readable { (read_pipe, write_pipe) } else { (write_pipe, read_pipe) };
105+
106+
// Make `ours` non-inheritable by duplicating it with the approriate setting
107+
let ours = ours_inheritable.duplicate(0, false, c::DUPLICATE_SAME_ACCESS)?;
108+
109+
// close the old, inheritable handle to the pipe end that is ours
110+
drop(ours_inheritable);
111+
112+
return Ok(Pipes {
113+
ours: AnonPipe { inner: ours },
114+
theirs: AnonPipe { inner: theirs },
115+
});
116+
}
117+
}
118+
65119
// Note that we specifically do *not* use `CreatePipe` here because
66120
// unfortunately the anonymous pipes returned do not support overlapped
67121
// operations. Instead, we create a "hopefully unique" name and create a
@@ -243,6 +297,10 @@ impl AnonPipe {
243297
}
244298

245299
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
300+
if !crate::sys::compat::supports_async_io() {
301+
return self.inner.read(buf);
302+
}
303+
246304
let result = unsafe {
247305
let len = crate::cmp::min(buf.len(), c::DWORD::MAX as usize) as c::DWORD;
248306
self.alertable_io_internal(c::ReadFileEx, buf.as_mut_ptr() as _, len)
@@ -259,6 +317,10 @@ impl AnonPipe {
259317
}
260318

261319
pub fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> {
320+
if !crate::sys::compat::supports_async_io() {
321+
return self.inner.read_buf(buf);
322+
}
323+
262324
let result = unsafe {
263325
let len = crate::cmp::min(buf.capacity(), c::DWORD::MAX as usize) as c::DWORD;
264326
self.alertable_io_internal(c::ReadFileEx, buf.as_mut().as_mut_ptr() as _, len)
@@ -294,6 +356,10 @@ impl AnonPipe {
294356
}
295357

296358
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
359+
if !crate::sys::compat::supports_async_io() {
360+
return self.inner.write(buf);
361+
}
362+
297363
unsafe {
298364
let len = crate::cmp::min(buf.len(), c::DWORD::MAX as usize) as c::DWORD;
299365
self.alertable_io_internal(c::WriteFileEx, buf.as_ptr() as _, len)
@@ -408,6 +474,25 @@ pub fn read2(p1: AnonPipe, v1: &mut Vec<u8>, p2: AnonPipe, v2: &mut Vec<u8>) ->
408474
let p1 = p1.into_handle();
409475
let p2 = p2.into_handle();
410476

477+
if !crate::sys::compat::supports_async_io() {
478+
use crate::io::Read;
479+
480+
// Since we are using anonymous pipes (= without overlapped I/O support) here, we can't do
481+
// async waiting on both stdout and stderr at the same time on one thread, so we have to
482+
// spawn an additional thread to do the waiting for the second pipe.
483+
484+
// See https://github.com/rust-lang/rust/pull/31618, where this was removed initially.
485+
let second_pipe = crate::thread::spawn(move || {
486+
let mut ret = Vec::new();
487+
(&p2).read_to_end(&mut ret).map(|_| ret)
488+
});
489+
490+
(&p1).read_to_end(v1)?;
491+
*v2 = second_pipe.join().unwrap()?;
492+
493+
return Ok(());
494+
}
495+
411496
let mut p1 = AsyncPipe::new(p1, v1)?;
412497
let mut p2 = AsyncPipe::new(p2, v2)?;
413498
let objs = [p1.event.as_raw_handle(), p2.event.as_raw_handle()];

0 commit comments

Comments
 (0)