Skip to content

Commit cff37af

Browse files
authored
Fix locale.getencoding, overlapped.getresult, chdir for windows (RustPython#6629)
* locale getencoding * overlapped.getresult * Fix windows chdir * mark flaky
1 parent 5ae75a6 commit cff37af

File tree

4 files changed

+253
-3
lines changed

4 files changed

+253
-3
lines changed

Lib/test/_test_multiprocessing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,7 @@ def _acquire_release(lock, timeout, l=None, n=1):
14591459
for _ in range(n):
14601460
lock.release()
14611461

1462+
@unittest.skip("TODO: RUSTPYTHON; flaky test")
14621463
def test_repr_rlock(self):
14631464
if self.TYPE != 'processes':
14641465
self.skipTest('test not appropriate for {}'.format(self.TYPE))

crates/stdlib/src/locale.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ mod _locale {
4949
convert::ToPyException,
5050
function::OptionalArg,
5151
};
52+
#[cfg(windows)]
53+
use windows_sys::Win32::Globalization::GetACP;
5254

5355
#[cfg(all(
5456
unix,
@@ -260,4 +262,39 @@ mod _locale {
260262
pystr_from_raw_cstr(vm, result)
261263
}
262264
}
265+
266+
/// Get the current locale encoding.
267+
#[pyfunction]
268+
fn getencoding() -> String {
269+
#[cfg(windows)]
270+
{
271+
// On Windows, use GetACP() to get the ANSI code page
272+
let acp = unsafe { GetACP() };
273+
format!("cp{}", acp)
274+
}
275+
#[cfg(not(windows))]
276+
{
277+
// On Unix, use nl_langinfo(CODESET) or fallback to UTF-8
278+
#[cfg(all(
279+
unix,
280+
not(any(target_os = "ios", target_os = "android", target_os = "redox"))
281+
))]
282+
{
283+
unsafe {
284+
let codeset = libc::nl_langinfo(libc::CODESET);
285+
if !codeset.is_null()
286+
&& let Ok(s) = CStr::from_ptr(codeset).to_str()
287+
&& !s.is_empty()
288+
{
289+
return s.to_string();
290+
}
291+
}
292+
"UTF-8".to_string()
293+
}
294+
#[cfg(any(target_os = "ios", target_os = "android", target_os = "redox"))]
295+
{
296+
"UTF-8".to_string()
297+
}
298+
}
299+
}
263300
}

crates/stdlib/src/overlapped.rs

Lines changed: 181 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@ mod _overlapped {
88
// straight-forward port of Modules/overlapped.c
99

1010
use crate::vm::{
11-
Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
11+
AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
1212
builtins::{PyBaseExceptionRef, PyBytesRef, PyType},
1313
common::lock::PyMutex,
1414
convert::{ToPyException, ToPyObject},
15+
function::OptionalArg,
1516
protocol::PyBuffer,
1617
types::Constructor,
1718
};
1819
use windows_sys::Win32::{
1920
Foundation::{self, GetLastError, HANDLE},
20-
Networking::WinSock::SOCKADDR_IN6,
21+
Networking::WinSock::{AF_INET, AF_INET6, SOCKADDR_IN, SOCKADDR_IN6},
2122
System::IO::OVERLAPPED,
2223
};
2324

@@ -153,6 +154,51 @@ mod _overlapped {
153154
overlapped.Internal != (Foundation::STATUS_PENDING as usize)
154155
}
155156

157+
/// Parse a SOCKADDR_IN6 (which can also hold IPv4 addresses) to a Python address tuple
158+
fn unparse_address(
159+
addr: &SOCKADDR_IN6,
160+
_addr_len: libc::c_int,
161+
vm: &VirtualMachine,
162+
) -> PyObjectRef {
163+
use crate::vm::convert::ToPyObject;
164+
165+
unsafe {
166+
let family = addr.sin6_family;
167+
if family == AF_INET {
168+
// IPv4 address stored in SOCKADDR_IN6 structure
169+
let addr_in = &*(addr as *const SOCKADDR_IN6 as *const SOCKADDR_IN);
170+
let ip_bytes = addr_in.sin_addr.S_un.S_un_b;
171+
let ip_str = format!(
172+
"{}.{}.{}.{}",
173+
ip_bytes.s_b1, ip_bytes.s_b2, ip_bytes.s_b3, ip_bytes.s_b4
174+
);
175+
let port = u16::from_be(addr_in.sin_port);
176+
(ip_str, port).to_pyobject(vm)
177+
} else if family == AF_INET6 {
178+
// IPv6 address
179+
let ip_bytes = addr.sin6_addr.u.Byte;
180+
let ip_str = format!(
181+
"{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}",
182+
u16::from_be_bytes([ip_bytes[0], ip_bytes[1]]),
183+
u16::from_be_bytes([ip_bytes[2], ip_bytes[3]]),
184+
u16::from_be_bytes([ip_bytes[4], ip_bytes[5]]),
185+
u16::from_be_bytes([ip_bytes[6], ip_bytes[7]]),
186+
u16::from_be_bytes([ip_bytes[8], ip_bytes[9]]),
187+
u16::from_be_bytes([ip_bytes[10], ip_bytes[11]]),
188+
u16::from_be_bytes([ip_bytes[12], ip_bytes[13]]),
189+
u16::from_be_bytes([ip_bytes[14], ip_bytes[15]]),
190+
);
191+
let port = u16::from_be(addr.sin6_port);
192+
let flowinfo = addr.sin6_flowinfo;
193+
let scope_id = addr.Anonymous.sin6_scope_id;
194+
(ip_str, port, flowinfo, scope_id).to_pyobject(vm)
195+
} else {
196+
// Unknown address family, return None
197+
vm.ctx.none()
198+
}
199+
}
200+
}
201+
156202
#[pyclass(with(Constructor))]
157203
impl Overlapped {
158204
#[pygetset]
@@ -259,6 +305,139 @@ mod _overlapped {
259305
}
260306
Ok(())
261307
}
308+
309+
#[pymethod]
310+
fn getresult(zelf: &Py<Self>, wait: OptionalArg<bool>, vm: &VirtualMachine) -> PyResult {
311+
use windows_sys::Win32::Foundation::{
312+
ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS,
313+
};
314+
use windows_sys::Win32::System::IO::GetOverlappedResult;
315+
316+
let mut inner = zelf.inner.lock();
317+
let wait = wait.unwrap_or(false);
318+
319+
// Check operation state
320+
if matches!(inner.data, OverlappedData::None) {
321+
return Err(vm.new_value_error("operation not yet attempted".to_owned()));
322+
}
323+
if matches!(inner.data, OverlappedData::NotStarted) {
324+
return Err(vm.new_value_error("operation failed to start".to_owned()));
325+
}
326+
327+
// Get the result
328+
let mut transferred: u32 = 0;
329+
let ret = unsafe {
330+
GetOverlappedResult(
331+
inner.handle,
332+
&inner.overlapped,
333+
&mut transferred,
334+
if wait { 1 } else { 0 },
335+
)
336+
};
337+
338+
let err = if ret != 0 {
339+
ERROR_SUCCESS
340+
} else {
341+
unsafe { GetLastError() }
342+
};
343+
inner.error = err;
344+
345+
// Handle errors
346+
match err {
347+
ERROR_SUCCESS | ERROR_MORE_DATA => {}
348+
ERROR_BROKEN_PIPE => {
349+
// For read operations, broken pipe is acceptable
350+
match &inner.data {
351+
OverlappedData::Read(_) | OverlappedData::ReadInto(_) => {}
352+
OverlappedData::ReadFrom(rf)
353+
if rf.result.is(&vm.ctx.none())
354+
|| rf.allocated_buffer.is(&vm.ctx.none()) =>
355+
{
356+
return Err(from_windows_err(err, vm));
357+
}
358+
OverlappedData::ReadFrom(_) => {}
359+
OverlappedData::ReadFromInto(rfi) if rfi.result.is(&vm.ctx.none()) => {
360+
return Err(from_windows_err(err, vm));
361+
}
362+
OverlappedData::ReadFromInto(_) => {}
363+
_ => return Err(from_windows_err(err, vm)),
364+
}
365+
}
366+
ERROR_IO_PENDING => {
367+
return Err(from_windows_err(err, vm));
368+
}
369+
_ => return Err(from_windows_err(err, vm)),
370+
}
371+
372+
// Return result based on operation type
373+
match &inner.data {
374+
OverlappedData::Read(buf) => {
375+
// Resize buffer to actual bytes read
376+
let bytes = buf.as_bytes();
377+
let result = if transferred as usize != bytes.len() {
378+
vm.ctx.new_bytes(bytes[..transferred as usize].to_vec())
379+
} else {
380+
buf.clone()
381+
};
382+
Ok(result.into())
383+
}
384+
OverlappedData::ReadInto(_) => {
385+
// Return number of bytes read
386+
Ok(vm.ctx.new_int(transferred).into())
387+
}
388+
OverlappedData::Write(_) => {
389+
// Return number of bytes written
390+
Ok(vm.ctx.new_int(transferred).into())
391+
}
392+
OverlappedData::Accept(_) => {
393+
// Return None for accept
394+
Ok(vm.ctx.none())
395+
}
396+
OverlappedData::Connect => {
397+
// Return None for connect
398+
Ok(vm.ctx.none())
399+
}
400+
OverlappedData::Disconnect => {
401+
// Return None for disconnect
402+
Ok(vm.ctx.none())
403+
}
404+
OverlappedData::ConnectNamedPipe => {
405+
// Return None for connect named pipe
406+
Ok(vm.ctx.none())
407+
}
408+
OverlappedData::WaitNamedPipeAndConnect => {
409+
// Return None
410+
Ok(vm.ctx.none())
411+
}
412+
OverlappedData::ReadFrom(rf) => {
413+
// Return (resized_buffer, (host, port)) tuple
414+
let buf = rf
415+
.allocated_buffer
416+
.downcast_ref::<crate::vm::builtins::PyBytes>()
417+
.unwrap();
418+
let bytes = buf.as_bytes();
419+
let resized_buf = if transferred as usize != bytes.len() {
420+
vm.ctx.new_bytes(bytes[..transferred as usize].to_vec())
421+
} else {
422+
buf.to_owned()
423+
};
424+
let addr_tuple = unparse_address(&rf.address, rf.address_length, vm);
425+
Ok(vm
426+
.ctx
427+
.new_tuple(vec![resized_buf.into(), addr_tuple])
428+
.into())
429+
}
430+
OverlappedData::ReadFromInto(rfi) => {
431+
// Return (transferred, (host, port)) tuple
432+
let addr_tuple = unparse_address(&rfi.address, rfi.address_length, vm);
433+
Ok(vm
434+
.ctx
435+
.new_tuple(vec![vm.ctx.new_int(transferred).into(), addr_tuple])
436+
.into())
437+
}
438+
_ => Ok(vm.ctx.none()),
439+
}
440+
}
262441
}
263442

264443
impl Constructor for Overlapped {

crates/vm/src/stdlib/os.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1124,7 +1124,40 @@ pub(super) mod _os {
11241124
#[pyfunction]
11251125
fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> {
11261126
env::set_current_dir(&path.path)
1127-
.map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
1127+
.map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?;
1128+
1129+
#[cfg(windows)]
1130+
{
1131+
// win32_wchdir()
1132+
1133+
// On Windows, set the per-drive CWD environment variable (=X:)
1134+
// This is required for GetFullPathNameW to work correctly with drive-relative paths
1135+
1136+
use std::os::windows::ffi::OsStrExt;
1137+
use windows_sys::Win32::System::Environment::SetEnvironmentVariableW;
1138+
1139+
if let Ok(cwd) = env::current_dir() {
1140+
let cwd_str = cwd.as_os_str();
1141+
let mut cwd_wide: Vec<u16> = cwd_str.encode_wide().collect();
1142+
1143+
// Check for UNC-like paths (\\server\share or //server/share)
1144+
// wcsncmp(new_path, L"\\\\", 2) == 0 || wcsncmp(new_path, L"//", 2) == 0
1145+
let is_unc_like_path = cwd_wide.len() >= 2
1146+
&& ((cwd_wide[0] == b'\\' as u16 && cwd_wide[1] == b'\\' as u16)
1147+
|| (cwd_wide[0] == b'/' as u16 && cwd_wide[1] == b'/' as u16));
1148+
1149+
if !is_unc_like_path {
1150+
// Create env var name "=X:" where X is the drive letter
1151+
let env_name: [u16; 4] = [b'=' as u16, cwd_wide[0], b':' as u16, 0];
1152+
cwd_wide.push(0); // null-terminate the path
1153+
unsafe {
1154+
SetEnvironmentVariableW(env_name.as_ptr(), cwd_wide.as_ptr());
1155+
}
1156+
}
1157+
}
1158+
}
1159+
1160+
Ok(())
11281161
}
11291162

11301163
#[pyfunction]

0 commit comments

Comments
 (0)