Skip to content

Commit f86cf22

Browse files
committed
Add fallback for IO internals (pipe, handle):
- 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 ce661d8 commit f86cf22

File tree

6 files changed

+181
-5
lines changed

6 files changed

+181
-5
lines changed

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

+27-4
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ compat_fn_with_fallback! {
245245
}
246246

247247
// These functions are available on UWP when lazily loaded. They will fail WACK if loaded statically.
248-
#[cfg(target_vendor = "uwp")]
248+
#[cfg(any(target_vendor = "uwp", target_vendor = "rust9x"))]
249249
pub fn NtCreateFile(
250250
filehandle: *mut HANDLE,
251251
desiredaccess: FILE_ACCESS_RIGHTS,
@@ -261,7 +261,7 @@ compat_fn_with_fallback! {
261261
) -> NTSTATUS {
262262
STATUS_NOT_IMPLEMENTED
263263
}
264-
#[cfg(target_vendor = "uwp")]
264+
#[cfg(any(target_vendor = "uwp", target_vendor = "rust9x"))]
265265
pub fn NtReadFile(
266266
filehandle: HANDLE,
267267
event: HANDLE,
@@ -275,7 +275,7 @@ compat_fn_with_fallback! {
275275
) -> NTSTATUS {
276276
STATUS_NOT_IMPLEMENTED
277277
}
278-
#[cfg(target_vendor = "uwp")]
278+
#[cfg(any(target_vendor = "uwp", target_vendor = "rust9x"))]
279279
pub fn NtWriteFile(
280280
filehandle: HANDLE,
281281
event: HANDLE,
@@ -289,10 +289,22 @@ compat_fn_with_fallback! {
289289
) -> NTSTATUS {
290290
STATUS_NOT_IMPLEMENTED
291291
}
292-
#[cfg(target_vendor = "uwp")]
292+
#[cfg(any(target_vendor = "uwp", target_vendor = "rust9x"))]
293293
pub fn RtlNtStatusToDosError(Status: NTSTATUS) -> u32 {
294294
Status as u32
295295
}
296+
297+
#[cfg(target_vendor = "rust9x")]
298+
pub fn NtOpenFile(
299+
filehandle: *mut HANDLE,
300+
desiredaccess: u32,
301+
objectattributes: *const OBJECT_ATTRIBUTES,
302+
iostatusblock: *mut IO_STATUS_BLOCK,
303+
shareaccess: u32,
304+
openoptions: u32
305+
) -> NTSTATUS {
306+
STATUS_NOT_IMPLEMENTED
307+
}
296308
}
297309

298310
#[cfg(target_vendor = "rust9x")]
@@ -665,3 +677,14 @@ compat_fn_with_fallback! {
665677
unimplemented!()
666678
}
667679
}
680+
681+
#[cfg(target_vendor = "rust9x")]
682+
compat_fn_with_fallback! {
683+
pub static KERNEL32: &CStr = c"kernel32" => { load: false, unicows: false };
684+
// >= 98+, NT4.0
685+
// https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringordinal
686+
pub fn CancelIo(hfile: HANDLE) -> BOOL {
687+
unsafe { SetLastError(ERROR_CALL_NOT_IMPLEMENTED as u32); };
688+
FALSE
689+
}
690+
}

library/std/src/sys/pal/windows/c/bindings.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2421,6 +2421,7 @@ Windows.Win32.Storage.FileSystem.VOLUME_NAME_NONE
24212421
Windows.Win32.Storage.FileSystem.WIN32_FIND_DATAW
24222422
Windows.Win32.Storage.FileSystem.WRITE_DAC
24232423
Windows.Win32.Storage.FileSystem.WRITE_OWNER
2424+
Windows.Win32.Storage.FileSystem.WriteFile
24242425
Windows.Win32.Storage.FileSystem.WriteFileEx
24252426
Windows.Win32.System.Console.CONSOLE_MODE
24262427
Windows.Win32.System.Console.CONSOLE_READCONSOLE_CONTROL

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

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ windows_targets::link!("kernel32.dll" "system" fn WakeAllConditionVariable(condi
126126
windows_targets::link!("kernel32.dll" "system" fn WakeConditionVariable(conditionvariable : *mut CONDITION_VARIABLE));
127127
windows_targets::link!("kernel32.dll" "system" fn WideCharToMultiByte(codepage : u32, dwflags : u32, lpwidecharstr : PCWSTR, cchwidechar : i32, lpmultibytestr : PSTR, cbmultibyte : i32, lpdefaultchar : PCSTR, lpuseddefaultchar : *mut BOOL) -> i32);
128128
windows_targets::link!("kernel32.dll" "system" fn WriteConsoleW(hconsoleoutput : HANDLE, lpbuffer : PCWSTR, nnumberofcharstowrite : u32, lpnumberofcharswritten : *mut u32, lpreserved : *const core::ffi::c_void) -> BOOL);
129+
windows_targets::link!("kernel32.dll" "system" fn WriteFile(hfile : HANDLE, lpbuffer : *const u8, nnumberofbytestowrite : u32, lpnumberofbyteswritten : *mut u32, lpoverlapped : *mut OVERLAPPED) -> BOOL);
129130
windows_targets::link!("kernel32.dll" "system" fn WriteFileEx(hfile : HANDLE, lpbuffer : *const u8, nnumberofbytestowrite : u32, lpoverlapped : *mut OVERLAPPED, lpcompletionroutine : LPOVERLAPPED_COMPLETION_ROUTINE) -> BOOL);
130131
windows_targets::link!("ntdll.dll" "system" fn NtCreateFile(filehandle : *mut HANDLE, desiredaccess : FILE_ACCESS_RIGHTS, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, allocationsize : *const i64, fileattributes : FILE_FLAGS_AND_ATTRIBUTES, shareaccess : FILE_SHARE_MODE, createdisposition : NTCREATEFILE_CREATE_DISPOSITION, createoptions : NTCREATEFILE_CREATE_OPTIONS, eabuffer : *const core::ffi::c_void, ealength : u32) -> NTSTATUS);
131132
windows_targets::link!("ntdll.dll" "system" fn NtOpenFile(filehandle : *mut HANDLE, desiredaccess : u32, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, shareaccess : u32, openoptions : u32) -> NTSTATUS);

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ pub fn is_windows_nt() -> bool {
88
unsafe { IS_NT }
99
}
1010

11+
#[inline(always)]
12+
pub fn supports_async_io() -> bool {
13+
unsafe { SUPPORTS_ASYNC_IO }
14+
}
15+
1116
pub fn init_rust9x_checks() {
1217
// DO NOT do anything interesting or complicated in this function! DO NOT call
1318
// any Rust functions or CRT functions if those functions touch any global state,
@@ -19,10 +24,14 @@ pub fn init_rust9x_checks() {
1924
}
2025

2126
static mut IS_NT: bool = true;
27+
static mut SUPPORTS_ASYNC_IO: bool = true;
2228

2329
fn init_windows_version_check() {
2430
// according to old MSDN info, the high-order bit is set only on 95/98/ME.
25-
unsafe { IS_NT = c::GetVersion() < 0x8000_0000 };
31+
unsafe {
32+
IS_NT = c::GetVersion() < 0x8000_0000;
33+
SUPPORTS_ASYNC_IO = IS_NT && c::CancelIo::available().is_some();
34+
};
2635
}
2736

2837
#[derive(Clone, Copy, PartialEq)]

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

+52
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,32 @@ impl Handle {
245245

246246
// The length is clamped at u32::MAX.
247247
let len = cmp::min(len, u32::MAX as usize) as u32;
248+
249+
#[cfg(target_vendor = "rust9x")]
250+
if !crate::sys::compat::checks::supports_async_io() {
251+
unsafe {
252+
if let Some(offset) = offset {
253+
cvt(c::SetFilePointerEx(
254+
self.as_raw_handle(),
255+
offset as i64,
256+
ptr::null_mut(),
257+
c::FILE_BEGIN,
258+
))?;
259+
}
260+
261+
let mut bytes_read = 0;
262+
cvt(c::ReadFile(
263+
self.as_raw_handle(),
264+
buf.cast(),
265+
len,
266+
&mut bytes_read,
267+
ptr::null_mut(),
268+
))?;
269+
270+
return Ok(bytes_read as usize);
271+
}
272+
}
273+
248274
// SAFETY: It's up to the caller to ensure `buf` is writeable up to
249275
// the provided `len`.
250276
let status = unsafe {
@@ -297,6 +323,32 @@ impl Handle {
297323

298324
// The length is clamped at u32::MAX.
299325
let len = cmp::min(buf.len(), u32::MAX as usize) as u32;
326+
327+
#[cfg(target_vendor = "rust9x")]
328+
if !crate::sys::compat::checks::supports_async_io() {
329+
unsafe {
330+
if let Some(offset) = offset {
331+
cvt(c::SetFilePointerEx(
332+
self.as_raw_handle(),
333+
offset as i64,
334+
ptr::null_mut(),
335+
c::FILE_BEGIN,
336+
))?;
337+
}
338+
339+
let mut bytes_written = 0;
340+
cvt(c::WriteFile(
341+
self.as_raw_handle(),
342+
buf.as_ptr(),
343+
len,
344+
&mut bytes_written,
345+
ptr::null_mut(),
346+
))?;
347+
348+
return Ok(bytes_written as usize);
349+
}
350+
}
351+
300352
let status = unsafe {
301353
c::NtWriteFile(
302354
self.as_raw_handle(),

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

+90
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,63 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res
6060
// A 64kb pipe capacity is the same as a typical Linux default.
6161
const PIPE_BUFFER_CAPACITY: u32 = 64 * 1024;
6262

63+
#[cfg(target_vendor = "rust9x")]
64+
{
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::checks::supports_async_io() {
78+
let size = mem::size_of::<c::SECURITY_ATTRIBUTES>();
79+
let mut sa = c::SECURITY_ATTRIBUTES {
80+
nLength: size as u32,
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+
}
119+
63120
// Note that we specifically do *not* use `CreatePipe` here because
64121
// unfortunately the anonymous pipes returned do not support overlapped
65122
// operations. Instead, we create a "hopefully unique" name and create a
@@ -232,6 +289,11 @@ impl AnonPipe {
232289
}
233290

234291
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
292+
#[cfg(target_vendor = "rust9x")]
293+
if !crate::sys::compat::checks::supports_async_io() {
294+
return self.inner.read(buf);
295+
}
296+
235297
let result = unsafe {
236298
let len = crate::cmp::min(buf.len(), u32::MAX as usize) as u32;
237299
let ptr = buf.as_mut_ptr();
@@ -251,6 +313,11 @@ impl AnonPipe {
251313
}
252314

253315
pub fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> {
316+
#[cfg(target_vendor = "rust9x")]
317+
if !crate::sys::compat::checks::supports_async_io() {
318+
return self.inner.read_buf(buf);
319+
}
320+
254321
let result = unsafe {
255322
let len = crate::cmp::min(buf.capacity(), u32::MAX as usize) as u32;
256323
let ptr = buf.as_mut().as_mut_ptr().cast::<u8>();
@@ -289,6 +356,11 @@ impl AnonPipe {
289356
}
290357

291358
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
359+
#[cfg(target_vendor = "rust9x")]
360+
if !crate::sys::compat::checks::supports_async_io() {
361+
return self.inner.write(buf);
362+
}
363+
292364
unsafe {
293365
let len = crate::cmp::min(buf.len(), u32::MAX as usize) as u32;
294366
self.alertable_io_internal(|overlapped, callback| {
@@ -408,6 +480,24 @@ pub fn read2(p1: AnonPipe, v1: &mut Vec<u8>, p2: AnonPipe, v2: &mut Vec<u8>) ->
408480
let p1 = p1.into_handle();
409481
let p2 = p2.into_handle();
410482

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

0 commit comments

Comments
 (0)