From 82dffc5ecde10395f03435df8a71dfbbf07c2f7d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 3 Jan 2026 08:52:59 +0900 Subject: [PATCH 1/4] locale getencoding --- crates/stdlib/src/locale.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/stdlib/src/locale.rs b/crates/stdlib/src/locale.rs index c65f861d208..51cf352590b 100644 --- a/crates/stdlib/src/locale.rs +++ b/crates/stdlib/src/locale.rs @@ -49,6 +49,8 @@ mod _locale { convert::ToPyException, function::OptionalArg, }; + #[cfg(windows)] + use windows_sys::Win32::Globalization::GetACP; #[cfg(all( unix, @@ -260,4 +262,39 @@ mod _locale { pystr_from_raw_cstr(vm, result) } } + + /// Get the current locale encoding. + #[pyfunction] + fn getencoding() -> String { + #[cfg(windows)] + { + // On Windows, use GetACP() to get the ANSI code page + let acp = unsafe { GetACP() }; + format!("cp{}", acp) + } + #[cfg(not(windows))] + { + // On Unix, use nl_langinfo(CODESET) or fallback to UTF-8 + #[cfg(all( + unix, + not(any(target_os = "ios", target_os = "android", target_os = "redox")) + ))] + { + unsafe { + let codeset = libc::nl_langinfo(libc::CODESET); + if !codeset.is_null() + && let Ok(s) = CStr::from_ptr(codeset).to_str() + && !s.is_empty() + { + return s.to_string(); + } + } + "UTF-8".to_string() + } + #[cfg(any(target_os = "ios", target_os = "android", target_os = "redox"))] + { + "UTF-8".to_string() + } + } + } } From 35af908e46da046d12b4ec1b68993793ff864d21 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 3 Jan 2026 08:53:35 +0900 Subject: [PATCH 2/4] overlapped.getresult --- crates/stdlib/src/overlapped.rs | 183 +++++++++++++++++++++++++++++++- 1 file changed, 181 insertions(+), 2 deletions(-) diff --git a/crates/stdlib/src/overlapped.rs b/crates/stdlib/src/overlapped.rs index 1c74ee271b9..664aa3f392c 100644 --- a/crates/stdlib/src/overlapped.rs +++ b/crates/stdlib/src/overlapped.rs @@ -8,16 +8,17 @@ mod _overlapped { // straight-forward port of Modules/overlapped.c use crate::vm::{ - Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyBaseExceptionRef, PyBytesRef, PyType}, common::lock::PyMutex, convert::{ToPyException, ToPyObject}, + function::OptionalArg, protocol::PyBuffer, types::Constructor, }; use windows_sys::Win32::{ Foundation::{self, GetLastError, HANDLE}, - Networking::WinSock::SOCKADDR_IN6, + Networking::WinSock::{AF_INET, AF_INET6, SOCKADDR_IN, SOCKADDR_IN6}, System::IO::OVERLAPPED, }; @@ -153,6 +154,51 @@ mod _overlapped { overlapped.Internal != (Foundation::STATUS_PENDING as usize) } + /// Parse a SOCKADDR_IN6 (which can also hold IPv4 addresses) to a Python address tuple + fn unparse_address( + addr: &SOCKADDR_IN6, + _addr_len: libc::c_int, + vm: &VirtualMachine, + ) -> PyObjectRef { + use crate::vm::convert::ToPyObject; + + unsafe { + let family = addr.sin6_family; + if family == AF_INET { + // IPv4 address stored in SOCKADDR_IN6 structure + let addr_in = &*(addr as *const SOCKADDR_IN6 as *const SOCKADDR_IN); + let ip_bytes = addr_in.sin_addr.S_un.S_un_b; + let ip_str = format!( + "{}.{}.{}.{}", + ip_bytes.s_b1, ip_bytes.s_b2, ip_bytes.s_b3, ip_bytes.s_b4 + ); + let port = u16::from_be(addr_in.sin_port); + (ip_str, port).to_pyobject(vm) + } else if family == AF_INET6 { + // IPv6 address + let ip_bytes = addr.sin6_addr.u.Byte; + let ip_str = format!( + "{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", + u16::from_be_bytes([ip_bytes[0], ip_bytes[1]]), + u16::from_be_bytes([ip_bytes[2], ip_bytes[3]]), + u16::from_be_bytes([ip_bytes[4], ip_bytes[5]]), + u16::from_be_bytes([ip_bytes[6], ip_bytes[7]]), + u16::from_be_bytes([ip_bytes[8], ip_bytes[9]]), + u16::from_be_bytes([ip_bytes[10], ip_bytes[11]]), + u16::from_be_bytes([ip_bytes[12], ip_bytes[13]]), + u16::from_be_bytes([ip_bytes[14], ip_bytes[15]]), + ); + let port = u16::from_be(addr.sin6_port); + let flowinfo = addr.sin6_flowinfo; + let scope_id = addr.Anonymous.sin6_scope_id; + (ip_str, port, flowinfo, scope_id).to_pyobject(vm) + } else { + // Unknown address family, return None + vm.ctx.none() + } + } + } + #[pyclass(with(Constructor))] impl Overlapped { #[pygetset] @@ -259,6 +305,139 @@ mod _overlapped { } Ok(()) } + + #[pymethod] + fn getresult(zelf: &Py, wait: OptionalArg, vm: &VirtualMachine) -> PyResult { + use windows_sys::Win32::Foundation::{ + ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, + }; + use windows_sys::Win32::System::IO::GetOverlappedResult; + + let mut inner = zelf.inner.lock(); + let wait = wait.unwrap_or(false); + + // Check operation state + if matches!(inner.data, OverlappedData::None) { + return Err(vm.new_value_error("operation not yet attempted".to_owned())); + } + if matches!(inner.data, OverlappedData::NotStarted) { + return Err(vm.new_value_error("operation failed to start".to_owned())); + } + + // Get the result + let mut transferred: u32 = 0; + let ret = unsafe { + GetOverlappedResult( + inner.handle, + &inner.overlapped, + &mut transferred, + if wait { 1 } else { 0 }, + ) + }; + + let err = if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { GetLastError() } + }; + inner.error = err; + + // Handle errors + match err { + ERROR_SUCCESS | ERROR_MORE_DATA => {} + ERROR_BROKEN_PIPE => { + // For read operations, broken pipe is acceptable + match &inner.data { + OverlappedData::Read(_) | OverlappedData::ReadInto(_) => {} + OverlappedData::ReadFrom(rf) + if rf.result.is(&vm.ctx.none()) + || rf.allocated_buffer.is(&vm.ctx.none()) => + { + return Err(from_windows_err(err, vm)); + } + OverlappedData::ReadFrom(_) => {} + OverlappedData::ReadFromInto(rfi) if rfi.result.is(&vm.ctx.none()) => { + return Err(from_windows_err(err, vm)); + } + OverlappedData::ReadFromInto(_) => {} + _ => return Err(from_windows_err(err, vm)), + } + } + ERROR_IO_PENDING => { + return Err(from_windows_err(err, vm)); + } + _ => return Err(from_windows_err(err, vm)), + } + + // Return result based on operation type + match &inner.data { + OverlappedData::Read(buf) => { + // Resize buffer to actual bytes read + let bytes = buf.as_bytes(); + let result = if transferred as usize != bytes.len() { + vm.ctx.new_bytes(bytes[..transferred as usize].to_vec()) + } else { + buf.clone() + }; + Ok(result.into()) + } + OverlappedData::ReadInto(_) => { + // Return number of bytes read + Ok(vm.ctx.new_int(transferred).into()) + } + OverlappedData::Write(_) => { + // Return number of bytes written + Ok(vm.ctx.new_int(transferred).into()) + } + OverlappedData::Accept(_) => { + // Return None for accept + Ok(vm.ctx.none()) + } + OverlappedData::Connect => { + // Return None for connect + Ok(vm.ctx.none()) + } + OverlappedData::Disconnect => { + // Return None for disconnect + Ok(vm.ctx.none()) + } + OverlappedData::ConnectNamedPipe => { + // Return None for connect named pipe + Ok(vm.ctx.none()) + } + OverlappedData::WaitNamedPipeAndConnect => { + // Return None + Ok(vm.ctx.none()) + } + OverlappedData::ReadFrom(rf) => { + // Return (resized_buffer, (host, port)) tuple + let buf = rf + .allocated_buffer + .downcast_ref::() + .unwrap(); + let bytes = buf.as_bytes(); + let resized_buf = if transferred as usize != bytes.len() { + vm.ctx.new_bytes(bytes[..transferred as usize].to_vec()) + } else { + buf.to_owned() + }; + let addr_tuple = unparse_address(&rf.address, rf.address_length, vm); + Ok(vm + .ctx + .new_tuple(vec![resized_buf.into(), addr_tuple]) + .into()) + } + OverlappedData::ReadFromInto(rfi) => { + // Return (transferred, (host, port)) tuple + let addr_tuple = unparse_address(&rfi.address, rfi.address_length, vm); + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_int(transferred).into(), addr_tuple]) + .into()) + } + _ => Ok(vm.ctx.none()), + } + } } impl Constructor for Overlapped { From 4c86f7fefaf47c65d790f125c27f62284a50c186 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 3 Jan 2026 08:55:45 +0900 Subject: [PATCH 3/4] Fix windows chdir --- crates/vm/src/stdlib/os.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index fa1495fcdbf..416f26018cb 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1124,7 +1124,40 @@ pub(super) mod _os { #[pyfunction] fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> { env::set_current_dir(&path.path) - .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?; + + #[cfg(windows)] + { + // win32_wchdir() + + // On Windows, set the per-drive CWD environment variable (=X:) + // This is required for GetFullPathNameW to work correctly with drive-relative paths + + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::System::Environment::SetEnvironmentVariableW; + + if let Ok(cwd) = env::current_dir() { + let cwd_str = cwd.as_os_str(); + let mut cwd_wide: Vec = cwd_str.encode_wide().collect(); + + // Check for UNC-like paths (\\server\share or //server/share) + // wcsncmp(new_path, L"\\\\", 2) == 0 || wcsncmp(new_path, L"//", 2) == 0 + let is_unc_like_path = cwd_wide.len() >= 2 + && ((cwd_wide[0] == b'\\' as u16 && cwd_wide[1] == b'\\' as u16) + || (cwd_wide[0] == b'/' as u16 && cwd_wide[1] == b'/' as u16)); + + if !is_unc_like_path { + // Create env var name "=X:" where X is the drive letter + let env_name: [u16; 4] = [b'=' as u16, cwd_wide[0], b':' as u16, 0]; + cwd_wide.push(0); // null-terminate the path + unsafe { + SetEnvironmentVariableW(env_name.as_ptr(), cwd_wide.as_ptr()); + } + } + } + } + + Ok(()) } #[pyfunction] From e19ba5f7b4c7bd953daeb857b58a9c7a5fdf4746 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 3 Jan 2026 10:05:59 +0900 Subject: [PATCH 4/4] mark flaky --- Lib/test/_test_multiprocessing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 351887819e6..093139e45e5 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -1459,6 +1459,7 @@ def _acquire_release(lock, timeout, l=None, n=1): for _ in range(n): lock.release() + @unittest.skip("TODO: RUSTPYTHON; flaky test") def test_repr_rlock(self): if self.TYPE != 'processes': self.skipTest('test not appropriate for {}'.format(self.TYPE))