Skip to content

Commit

Permalink
Add fallback for IO internals (pipe, handle):
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
seritools committed Dec 1, 2024
1 parent ce661d8 commit f86cf22
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 5 deletions.
31 changes: 27 additions & 4 deletions library/std/src/sys/pal/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ compat_fn_with_fallback! {
}

// These functions are available on UWP when lazily loaded. They will fail WACK if loaded statically.
#[cfg(target_vendor = "uwp")]
#[cfg(any(target_vendor = "uwp", target_vendor = "rust9x"))]
pub fn NtCreateFile(
filehandle: *mut HANDLE,
desiredaccess: FILE_ACCESS_RIGHTS,
Expand All @@ -261,7 +261,7 @@ compat_fn_with_fallback! {
) -> NTSTATUS {
STATUS_NOT_IMPLEMENTED
}
#[cfg(target_vendor = "uwp")]
#[cfg(any(target_vendor = "uwp", target_vendor = "rust9x"))]
pub fn NtReadFile(
filehandle: HANDLE,
event: HANDLE,
Expand All @@ -275,7 +275,7 @@ compat_fn_with_fallback! {
) -> NTSTATUS {
STATUS_NOT_IMPLEMENTED
}
#[cfg(target_vendor = "uwp")]
#[cfg(any(target_vendor = "uwp", target_vendor = "rust9x"))]
pub fn NtWriteFile(
filehandle: HANDLE,
event: HANDLE,
Expand All @@ -289,10 +289,22 @@ compat_fn_with_fallback! {
) -> NTSTATUS {
STATUS_NOT_IMPLEMENTED
}
#[cfg(target_vendor = "uwp")]
#[cfg(any(target_vendor = "uwp", target_vendor = "rust9x"))]
pub fn RtlNtStatusToDosError(Status: NTSTATUS) -> u32 {
Status as u32
}

#[cfg(target_vendor = "rust9x")]
pub fn NtOpenFile(
filehandle: *mut HANDLE,
desiredaccess: u32,
objectattributes: *const OBJECT_ATTRIBUTES,
iostatusblock: *mut IO_STATUS_BLOCK,
shareaccess: u32,
openoptions: u32
) -> NTSTATUS {
STATUS_NOT_IMPLEMENTED
}
}

#[cfg(target_vendor = "rust9x")]
Expand Down Expand Up @@ -665,3 +677,14 @@ compat_fn_with_fallback! {
unimplemented!()
}
}

#[cfg(target_vendor = "rust9x")]
compat_fn_with_fallback! {
pub static KERNEL32: &CStr = c"kernel32" => { load: false, unicows: false };
// >= 98+, NT4.0
// https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringordinal
pub fn CancelIo(hfile: HANDLE) -> BOOL {
unsafe { SetLastError(ERROR_CALL_NOT_IMPLEMENTED as u32); };
FALSE
}
}
1 change: 1 addition & 0 deletions library/std/src/sys/pal/windows/c/bindings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2421,6 +2421,7 @@ Windows.Win32.Storage.FileSystem.VOLUME_NAME_NONE
Windows.Win32.Storage.FileSystem.WIN32_FIND_DATAW
Windows.Win32.Storage.FileSystem.WRITE_DAC
Windows.Win32.Storage.FileSystem.WRITE_OWNER
Windows.Win32.Storage.FileSystem.WriteFile
Windows.Win32.Storage.FileSystem.WriteFileEx
Windows.Win32.System.Console.CONSOLE_MODE
Windows.Win32.System.Console.CONSOLE_READCONSOLE_CONTROL
Expand Down
1 change: 1 addition & 0 deletions library/std/src/sys/pal/windows/c/windows_sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ windows_targets::link!("kernel32.dll" "system" fn WakeAllConditionVariable(condi
windows_targets::link!("kernel32.dll" "system" fn WakeConditionVariable(conditionvariable : *mut CONDITION_VARIABLE));
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);
windows_targets::link!("kernel32.dll" "system" fn WriteConsoleW(hconsoleoutput : HANDLE, lpbuffer : PCWSTR, nnumberofcharstowrite : u32, lpnumberofcharswritten : *mut u32, lpreserved : *const core::ffi::c_void) -> BOOL);
windows_targets::link!("kernel32.dll" "system" fn WriteFile(hfile : HANDLE, lpbuffer : *const u8, nnumberofbytestowrite : u32, lpnumberofbyteswritten : *mut u32, lpoverlapped : *mut OVERLAPPED) -> BOOL);
windows_targets::link!("kernel32.dll" "system" fn WriteFileEx(hfile : HANDLE, lpbuffer : *const u8, nnumberofbytestowrite : u32, lpoverlapped : *mut OVERLAPPED, lpcompletionroutine : LPOVERLAPPED_COMPLETION_ROUTINE) -> BOOL);
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);
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);
Expand Down
11 changes: 10 additions & 1 deletion library/std/src/sys/pal/windows/compat/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ pub fn is_windows_nt() -> bool {
unsafe { IS_NT }
}

#[inline(always)]
pub fn supports_async_io() -> bool {
unsafe { SUPPORTS_ASYNC_IO }
}

pub fn init_rust9x_checks() {
// DO NOT do anything interesting or complicated in this function! DO NOT call
// any Rust functions or CRT functions if those functions touch any global state,
Expand All @@ -19,10 +24,14 @@ pub fn init_rust9x_checks() {
}

static mut IS_NT: bool = true;
static mut SUPPORTS_ASYNC_IO: bool = true;

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

#[derive(Clone, Copy, PartialEq)]
Expand Down
52 changes: 52 additions & 0 deletions library/std/src/sys/pal/windows/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,32 @@ impl Handle {

// The length is clamped at u32::MAX.
let len = cmp::min(len, u32::MAX as usize) as u32;

#[cfg(target_vendor = "rust9x")]
if !crate::sys::compat::checks::supports_async_io() {
unsafe {
if let Some(offset) = offset {
cvt(c::SetFilePointerEx(
self.as_raw_handle(),
offset as i64,
ptr::null_mut(),
c::FILE_BEGIN,
))?;
}

let mut bytes_read = 0;
cvt(c::ReadFile(
self.as_raw_handle(),
buf.cast(),
len,
&mut bytes_read,
ptr::null_mut(),
))?;

return Ok(bytes_read as usize);
}
}

// SAFETY: It's up to the caller to ensure `buf` is writeable up to
// the provided `len`.
let status = unsafe {
Expand Down Expand Up @@ -297,6 +323,32 @@ impl Handle {

// The length is clamped at u32::MAX.
let len = cmp::min(buf.len(), u32::MAX as usize) as u32;

#[cfg(target_vendor = "rust9x")]
if !crate::sys::compat::checks::supports_async_io() {
unsafe {
if let Some(offset) = offset {
cvt(c::SetFilePointerEx(
self.as_raw_handle(),
offset as i64,
ptr::null_mut(),
c::FILE_BEGIN,
))?;
}

let mut bytes_written = 0;
cvt(c::WriteFile(
self.as_raw_handle(),
buf.as_ptr(),
len,
&mut bytes_written,
ptr::null_mut(),
))?;

return Ok(bytes_written as usize);
}
}

let status = unsafe {
c::NtWriteFile(
self.as_raw_handle(),
Expand Down
90 changes: 90 additions & 0 deletions library/std/src/sys/pal/windows/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,63 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res
// A 64kb pipe capacity is the same as a typical Linux default.
const PIPE_BUFFER_CAPACITY: u32 = 64 * 1024;

#[cfg(target_vendor = "rust9x")]
{
// 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 do reading from two threads in the case where both
// stdout and stderr being piped (see `read2`).

// 9X/ME *does* have a kernel32 export entry for `CreateNamedPipe`, so an availability check
// would not work. We're just gonna check the bit that's only set on non-unicode Windows
// versions instead...

// The `AnonPipe` impl used in `read2` below needs to be able to cancel the overlapped i/o
// operation, so we also have to check for `CancelIo` being available. This means that the
// "modern" path is taken only for NT4+.
if !crate::sys::compat::checks::supports_async_io() {
let size = mem::size_of::<c::SECURITY_ATTRIBUTES>();
let mut sa = c::SECURITY_ATTRIBUTES {
nLength: size as u32,
lpSecurityDescriptor: ptr::null_mut(),
// We follow the old "Creating a Child Process with Redirected Input and Output" MSDN
// entry (pre-`SetHandleInformation`) here, duplicating the handle that is not being
// sent to the child process as non-inheritable and then closing the inheritable one.
// Usually, this would be racy, but this function is only called in `Stdio::to_handle`,
// which is in turn only called form `process::spawn`, which acquires a lock on process
// spawning because of this.
bInheritHandle: c::TRUE,
};

unsafe {
let mut read_pipe = mem::zeroed();
let mut write_pipe = mem::zeroed();
crate::sys::cvt(c::CreatePipe(
&mut read_pipe,
&mut write_pipe,
&mut sa,
PIPE_BUFFER_CAPACITY,
))?;
let read_pipe = Handle::from_raw_handle(read_pipe);
let write_pipe = Handle::from_raw_handle(write_pipe);

let (ours_inheritable, theirs) =
if ours_readable { (read_pipe, write_pipe) } else { (write_pipe, read_pipe) };

// Make `ours` non-inheritable by duplicating it with the approriate setting
let ours = ours_inheritable.duplicate(0, false, c::DUPLICATE_SAME_ACCESS)?;

// close the old, inheritable handle to the pipe end that is ours
drop(ours_inheritable);

return Ok(Pipes {
ours: AnonPipe { inner: ours },
theirs: AnonPipe { inner: theirs },
});
}
}
}

// Note that we specifically do *not* use `CreatePipe` here because
// unfortunately the anonymous pipes returned do not support overlapped
// operations. Instead, we create a "hopefully unique" name and create a
Expand Down Expand Up @@ -232,6 +289,11 @@ impl AnonPipe {
}

pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
#[cfg(target_vendor = "rust9x")]
if !crate::sys::compat::checks::supports_async_io() {
return self.inner.read(buf);
}

let result = unsafe {
let len = crate::cmp::min(buf.len(), u32::MAX as usize) as u32;
let ptr = buf.as_mut_ptr();
Expand All @@ -251,6 +313,11 @@ impl AnonPipe {
}

pub fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> {
#[cfg(target_vendor = "rust9x")]
if !crate::sys::compat::checks::supports_async_io() {
return self.inner.read_buf(buf);
}

let result = unsafe {
let len = crate::cmp::min(buf.capacity(), u32::MAX as usize) as u32;
let ptr = buf.as_mut().as_mut_ptr().cast::<u8>();
Expand Down Expand Up @@ -289,6 +356,11 @@ impl AnonPipe {
}

pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
#[cfg(target_vendor = "rust9x")]
if !crate::sys::compat::checks::supports_async_io() {
return self.inner.write(buf);
}

unsafe {
let len = crate::cmp::min(buf.len(), u32::MAX as usize) as u32;
self.alertable_io_internal(|overlapped, callback| {
Expand Down Expand Up @@ -408,6 +480,24 @@ pub fn read2(p1: AnonPipe, v1: &mut Vec<u8>, p2: AnonPipe, v2: &mut Vec<u8>) ->
let p1 = p1.into_handle();
let p2 = p2.into_handle();

#[cfg(target_vendor = "rust9x")]
if !crate::sys::compat::checks::supports_async_io() {
// Since we are using anonymous pipes (= without overlapped I/O support) here, we can't do
// async waiting on both stdout and stderr at the same time on one thread, so we have to
// spawn an additional thread to do the waiting for the second pipe.

// See https://github.com/rust-lang/rust/pull/31618, where this was removed initially.
let second_pipe = crate::thread::spawn(move || {
let mut ret = Vec::new();
(&p2).read_to_end(&mut ret).map(|_| ret)
});

(&p1).read_to_end(v1)?;
*v2 = second_pipe.join().unwrap()?;

return Ok(());
}

let mut p1 = AsyncPipe::new(p1, v1)?;
let mut p2 = AsyncPipe::new(p2, v2)?;
let objs = [p1.event.as_raw_handle(), p2.event.as_raw_handle()];
Expand Down

0 comments on commit f86cf22

Please sign in to comment.