From ce470e3928bbb5cc3e7848074049e0b6589f9aeb Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Fri, 18 Sep 2026 11:02:55 +0900 Subject: [PATCH] Implement os.pread and os.pwrite (#8730) Assisted-by: Codex:gpt-6-astra --- crates/host_env/src/posix.rs | 18 +++++++++++ crates/vm/src/stdlib/posix.rs | 57 +++++++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/crates/host_env/src/posix.rs b/crates/host_env/src/posix.rs index c196c6cb6..07c653777 100644 --- a/crates/host_env/src/posix.rs +++ b/crates/host_env/src/posix.rs @@ -341,6 +341,24 @@ pub fn write_fd(fd: BorrowedFd<'_>, buf: &[u8]) -> std::io::Result { nix::unistd::write(fd, buf).map_err(std::io::Error::from) } +pub fn pread(fd: i32, buf: &mut [u8], offset: libc::off_t) -> std::io::Result { + let ret = unsafe { libc::pread(fd, buf.as_mut_ptr().cast(), buf.len(), offset) }; + if ret == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(ret as usize) + } +} + +pub fn pwrite(fd: i32, buf: &[u8], offset: libc::off_t) -> std::io::Result { + let ret = unsafe { libc::pwrite(fd, buf.as_ptr().cast(), buf.len(), offset) }; + if ret == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(ret as usize) + } +} + pub fn fchownat( dir_fd: BorrowedFd<'_>, path: &OsStr, diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 9460d92eb..adbbc8bc4 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -18,10 +18,12 @@ pub use rustpython_host_env::posix::set_inheritable; pub mod module { use crate::{ AsObject, Py, PyObjectRef, PyResult, VirtualMachine, - builtins::{PyDictRef, PyInt, PyListRef, PyTuple, PyTupleRef, PyUtf8Str}, + builtins::{PyBytesRef, PyDictRef, PyInt, PyListRef, PyTuple, PyTupleRef, PyUtf8Str}, convert::{IntoPyException, ToPyException, ToPyObject, TryFromObject}, exceptions::OSErrorBuilder, - function::{ArgMapping, Either, KwArgs, OptionalArg}, + function::{ + ArgBytesLike, ArgMapping, ArgPrimitiveIndex, ArgSize, Either, KwArgs, OptionalArg, + }, ospath::{OsPath, OsPathOrFd}, stdlib::os::{ _os, DirFd, FollowSymlinks, SupportFunc, SymlinkArgs, fs_metadata, warn_if_bool_fd, @@ -962,6 +964,57 @@ pub mod module { .map_err(|err| err.into_pyexception(vm)) } + #[pyfunction] + fn pread( + fd: ArgPrimitiveIndex, + n: ArgSize, + offset: ArgPrimitiveIndex, + vm: &VirtualMachine, + ) -> PyResult { + let (fd, n, offset) = (fd.value, n.value, offset.value); + if n < 0 { + return Err(io::Error::from_raw_os_error(libc::EINVAL).into_pyexception(vm)); + } + let mut buffer = vm.new_zeroed_bytes(n as usize)?; + loop { + match vm.allow_threads(|| rustpython_host_env::posix::pread(fd, &mut buffer, offset)) { + Ok(n) => { + buffer.truncate(n); + return Ok(vm.ctx.new_bytes(buffer)); + } + Err(err) if err.raw_os_error() == Some(libc::EINTR) => { + vm.check_signals()?; + continue; + } + Err(err) => return Err(err.into_pyexception(vm)), + } + } + } + + #[pyfunction] + fn pwrite( + fd: ArgPrimitiveIndex, + buffer: ArgBytesLike, + offset: ArgPrimitiveIndex, + vm: &VirtualMachine, + ) -> PyResult { + // Avoid holding a buffer lock across blocking I/O, + // which can prevent other threads from reaching GC safepoints. + let data = buffer.borrow_buf_unlocked(vm)?; + loop { + match vm + .allow_threads(|| rustpython_host_env::posix::pwrite(fd.value, &data, offset.value)) + { + Ok(n) => return Ok(n), + Err(err) if err.raw_os_error() == Some(libc::EINTR) => { + vm.check_signals()?; + continue; + } + Err(err) => return Err(err.into_pyexception(vm)), + } + } + } + #[pyfunction] fn pipe(vm: &VirtualMachine) -> PyResult<(OwnedFd, OwnedFd)> { rustpython_host_env::posix::pipe().map_err(|err| err.into_pyexception(vm))