diff --git a/.cspell.json b/.cspell.json index 89cde1ce7..6c481b691 100644 --- a/.cspell.json +++ b/.cspell.json @@ -139,7 +139,11 @@ "birthtime", "IFEXEC", // "stat" - "FIRMLINK" + "FIRMLINK", + // CPython internal names + "sysdict", + "settraceallthreads", + "setprofileallthreads" ], // flagWords - list of words to be always considered incorrect "flagWords": [ diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 4755aef08..f88659764 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -2773,7 +2773,6 @@ class Win32KillTests(unittest.TestCase): self._kill_with_event(signal.CTRL_C_EVENT, "CTRL_C_EVENT") - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_CTRL_BREAK_EVENT(self): self._kill_with_event(signal.CTRL_BREAK_EVENT, "CTRL_BREAK_EVENT") diff --git a/crates/common/src/os.rs b/crates/common/src/os.rs index 1ce25988d..a09352397 100644 --- a/crates/common/src/os.rs +++ b/crates/common/src/os.rs @@ -131,7 +131,10 @@ pub fn winerror_to_errno(winerror: i32) -> i32 { use libc::*; use windows_sys::Win32::{ Foundation::*, - Networking::WinSock::{WSAEACCES, WSAEBADF, WSAEFAULT, WSAEINTR, WSAEINVAL, WSAEMFILE}, + Networking::WinSock::{ + WSAEACCES, WSAEBADF, WSAECONNABORTED, WSAECONNREFUSED, WSAECONNRESET, WSAEFAULT, + WSAEINTR, WSAEINVAL, WSAEMFILE, + }, }; // Unwrap FACILITY_WIN32 HRESULT errors. // if ((winerror & 0xFFFF0000) == 0x80070000) { @@ -218,6 +221,11 @@ pub fn winerror_to_errno(winerror: i32) -> i32 { ERROR_BROKEN_PIPE | ERROR_NO_DATA => EPIPE, ERROR_DIR_NOT_EMPTY => ENOTEMPTY, ERROR_NO_UNICODE_TRANSLATION => EILSEQ, + // Connection-related Windows error codes - map to Winsock error codes + // which Python uses on Windows (errno.ECONNREFUSED = 10061, etc.) + ERROR_CONNECTION_REFUSED => WSAECONNREFUSED, + ERROR_CONNECTION_ABORTED => WSAECONNABORTED, + ERROR_NETNAME_DELETED => WSAECONNRESET, ERROR_INVALID_FUNCTION | ERROR_INVALID_ACCESS | ERROR_INVALID_DATA diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index c265f1149..231fae753 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -101,6 +101,14 @@ pub(crate) mod _signal { #[pyattr] pub use libc::{SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM}; + // Windows-specific control events for GenerateConsoleCtrlEvent + #[cfg(windows)] + #[pyattr] + const CTRL_C_EVENT: u32 = 0; + #[cfg(windows)] + #[pyattr] + const CTRL_BREAK_EVENT: u32 = 1; + #[cfg(unix)] #[pyattr] use libc::{ diff --git a/crates/vm/src/stdlib/winapi.rs b/crates/vm/src/stdlib/winapi.rs index 25df0964c..1ebb28317 100644 --- a/crates/vm/src/stdlib/winapi.rs +++ b/crates/vm/src/stdlib/winapi.rs @@ -6,15 +6,16 @@ pub(crate) use _winapi::module_def; #[pymodule] mod _winapi { use crate::{ - PyObjectRef, PyResult, TryFromObject, VirtualMachine, + Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, builtins::PyStrRef, - common::windows::ToWideString, + common::{lock::PyMutex, windows::ToWideString}, convert::{ToPyException, ToPyResult}, function::{ArgMapping, ArgSequence, OptionalArg}, + types::Constructor, windows::{WinHandle, WindowsSysResult}, }; use std::ptr::{null, null_mut}; - use windows_sys::Win32::Foundation::{INVALID_HANDLE_VALUE, MAX_PATH}; + use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE, MAX_PATH}; #[pyattr] use windows_sys::Win32::{ @@ -557,6 +558,48 @@ mod _winapi { } } + #[pyfunction] + fn WaitForMultipleObjects( + handle_seq: ArgSequence, + wait_all: bool, + milliseconds: u32, + vm: &VirtualMachine, + ) -> PyResult { + use windows_sys::Win32::Foundation::WAIT_FAILED; + use windows_sys::Win32::System::Threading::WaitForMultipleObjects as WinWaitForMultipleObjects; + + let handles: Vec = handle_seq + .into_vec() + .into_iter() + .map(|h| h as HANDLE) + .collect(); + + if handles.is_empty() { + return Err(vm.new_value_error("handle_seq must not be empty".to_owned())); + } + + if handles.len() > 64 { + return Err( + vm.new_value_error("WaitForMultipleObjects supports at most 64 handles".to_owned()) + ); + } + + let ret = unsafe { + WinWaitForMultipleObjects( + handles.len() as u32, + handles.as_ptr(), + if wait_all { 1 } else { 0 }, + milliseconds, + ) + }; + + if ret == WAIT_FAILED { + Err(vm.new_last_os_error()) + } else { + Ok(ret) + } + } + #[pyfunction] fn GetExitCodeProcess(h: WinHandle, vm: &VirtualMachine) -> PyResult { unsafe { @@ -761,6 +804,229 @@ mod _winapi { Ok(WinHandle(handle)) } + // ==================== Overlapped class ==================== + // Used for asynchronous I/O operations (ConnectNamedPipe, ReadFile, WriteFile) + + #[pyattr] + #[pyclass(name = "Overlapped", module = "_winapi")] + #[derive(Debug, PyPayload)] + struct Overlapped { + inner: PyMutex, + } + + struct OverlappedInner { + overlapped: windows_sys::Win32::System::IO::OVERLAPPED, + handle: HANDLE, + pending: bool, + completed: bool, + read_buffer: Option>, + } + + impl std::fmt::Debug for OverlappedInner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OverlappedInner") + .field("handle", &self.handle) + .field("pending", &self.pending) + .field("completed", &self.completed) + .finish() + } + } + + unsafe impl Sync for OverlappedInner {} + unsafe impl Send for OverlappedInner {} + + #[pyclass(with(Constructor))] + impl Overlapped { + fn new_with_handle(handle: HANDLE) -> Self { + use windows_sys::Win32::System::Threading::CreateEventW; + + let event = unsafe { CreateEventW(null(), 1, 0, null()) }; + let mut overlapped: windows_sys::Win32::System::IO::OVERLAPPED = + unsafe { std::mem::zeroed() }; + overlapped.hEvent = event; + + Overlapped { + inner: PyMutex::new(OverlappedInner { + overlapped, + handle, + pending: false, + completed: false, + read_buffer: None, + }), + } + } + + #[pymethod] + fn GetOverlappedResult(&self, wait: bool, vm: &VirtualMachine) -> PyResult { + use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, GetLastError}; + use windows_sys::Win32::System::IO::GetOverlappedResult; + + let mut inner = self.inner.lock(); + + // If operation was already completed synchronously (e.g., ERROR_PIPE_CONNECTED), + // return immediately without calling GetOverlappedResult + if inner.completed && !inner.pending { + return Ok(0); + } + + let mut transferred: u32 = 0; + + let ret = unsafe { + GetOverlappedResult( + inner.handle, + &inner.overlapped, + &mut transferred, + if wait { 1 } else { 0 }, + ) + }; + + if ret == 0 { + let err = unsafe { GetLastError() }; + if err == ERROR_IO_PENDING { + return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); + } + return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); + } + + inner.completed = true; + inner.pending = false; + Ok(transferred) + } + + #[pymethod] + fn getbuffer(&self, vm: &VirtualMachine) -> PyResult> { + let inner = self.inner.lock(); + if !inner.completed { + return Err(vm.new_value_error("operation not completed".to_owned())); + } + Ok(inner + .read_buffer + .as_ref() + .map(|buf| vm.ctx.new_bytes(buf.clone()).into())) + } + + #[pymethod] + fn cancel(&self, vm: &VirtualMachine) -> PyResult<()> { + use windows_sys::Win32::System::IO::CancelIoEx; + + let inner = self.inner.lock(); + if !inner.pending { + return Ok(()); + } + + let ret = unsafe { CancelIoEx(inner.handle, &inner.overlapped) }; + if ret == 0 { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + // ERROR_NOT_FOUND means operation already completed + if err != windows_sys::Win32::Foundation::ERROR_NOT_FOUND { + return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); + } + } + Ok(()) + } + + #[pygetset] + fn event(&self) -> isize { + let inner = self.inner.lock(); + inner.overlapped.hEvent as isize + } + } + + impl Constructor for Overlapped { + type Args = (); + + fn py_new( + _cls: &Py, + _args: Self::Args, + _vm: &VirtualMachine, + ) -> PyResult { + Ok(Overlapped::new_with_handle(null_mut())) + } + } + + impl Drop for OverlappedInner { + fn drop(&mut self) { + use windows_sys::Win32::Foundation::CloseHandle; + if !self.overlapped.hEvent.is_null() { + unsafe { CloseHandle(self.overlapped.hEvent) }; + } + } + } + + /// ConnectNamedPipe - Wait for a client to connect to a named pipe + #[derive(FromArgs)] + struct ConnectNamedPipeArgs { + #[pyarg(positional)] + handle: WinHandle, + #[pyarg(named, optional)] + overlapped: OptionalArg, + } + + #[pyfunction] + fn ConnectNamedPipe(args: ConnectNamedPipeArgs, vm: &VirtualMachine) -> PyResult { + use windows_sys::Win32::Foundation::{ + ERROR_IO_PENDING, ERROR_PIPE_CONNECTED, GetLastError, + }; + + let handle = args.handle; + let use_overlapped = args.overlapped.unwrap_or(false); + + if use_overlapped { + // Overlapped (async) mode + let ov = Overlapped::new_with_handle(handle.0); + + let ret = { + let mut inner = ov.inner.lock(); + unsafe { + windows_sys::Win32::System::Pipes::ConnectNamedPipe( + handle.0, + &mut inner.overlapped, + ) + } + }; + + if ret != 0 { + // Connected immediately + let mut inner = ov.inner.lock(); + inner.completed = true; + } else { + let err = unsafe { GetLastError() }; + match err { + ERROR_IO_PENDING => { + let mut inner = ov.inner.lock(); + inner.pending = true; + } + ERROR_PIPE_CONNECTED => { + // Client was already connected + let mut inner = ov.inner.lock(); + inner.completed = true; + } + _ => { + return Err( + std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm) + ); + } + } + } + + Ok(ov.into_pyobject(vm)) + } else { + // Synchronous mode + let ret = unsafe { + windows_sys::Win32::System::Pipes::ConnectNamedPipe(handle.0, null_mut()) + }; + + if ret == 0 { + let err = unsafe { GetLastError() }; + if err != ERROR_PIPE_CONNECTED { + return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); + } + } + + Ok(vm.ctx.none()) + } + } + /// Helper for GetShortPathName and GetLongPathName fn get_path_name_impl( path: &PyStrRef,