From 07457254d6a9623ad55fca4b9db048fc17707b03 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Mon, 12 Oct 2020 01:50:47 -0500 Subject: [PATCH] Implement buffered io sans buffering --- vm/src/stdlib/io.rs | 605 +++++++++++++++++++++++++++++++------------- vm/src/stdlib/os.rs | 14 +- 2 files changed, 436 insertions(+), 183 deletions(-) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index e45621028..ca9546980 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -1,12 +1,19 @@ /* * I/O core tools. */ +use super::os::Offset; use crate::pyobject::PyObjectRef; use crate::VirtualMachine; pub(crate) use _io::io_open as open; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let module = _io::make_module(vm); + let unsupported_operation = _io::UNSUPPORTED_OPERATION + .get_or_init(|| _io::make_unsupportedop(&vm.ctx)) + .clone(); + extend_module!(vm, module, { + "UnsupportedOperation" => unsupported_operation, + }); #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] fileio::extend_module(vm, &module); module @@ -14,9 +21,11 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { #[pymodule] mod _io { + use super::*; + use bstr::ByteSlice; use crossbeam_utils::atomic::AtomicCell; - use num_traits::ToPrimitive; + use num_traits::{NumCast, ToPrimitive}; use std::io::{self, prelude::*, Cursor, SeekFrom}; use crate::builtins::bytearray::PyByteArray; @@ -25,11 +34,12 @@ mod _io { use crate::builtins::memory::{Buffer, BufferOptions, BufferRef, PyMemoryView, ResizeGuard}; use crate::builtins::pybool; use crate::builtins::pystr::{self, PyStr, PyStrRef}; - use crate::builtins::pytype::PyTypeRef; + use crate::builtins::pytype::{self, PyTypeRef}; use crate::byteslike::{PyBytesLike, PyRwBytesLike}; use crate::common::borrow::{BorrowedValue, BorrowedValueMut}; use crate::common::lock::{ PyRwLock, PyRwLockReadGuard, PyRwLockUpgradableReadGuard, PyRwLockWriteGuard, + PyThreadMutex, PyThreadMutexGuard, }; use crate::exceptions::{IntoPyException, PyBaseExceptionRef}; use crate::function::{FuncArgs, OptionalArg, OptionalOption}; @@ -39,6 +49,25 @@ mod _io { }; use crate::vm::VirtualMachine; + fn ensure_unclosed(file: PyObjectRef, msg: &str, vm: &VirtualMachine) -> PyResult<()> { + if pybool::boolval(vm, vm.get_attribute(file, "closed")?)? { + Err(vm.new_value_error(msg.to_owned())) + } else { + Ok(()) + } + } + + fn new_unsupported_operation(vm: &VirtualMachine, msg: String) -> PyBaseExceptionRef { + vm.new_exception_msg(UNSUPPORTED_OPERATION.get().unwrap().clone(), msg) + } + + fn _unsupported(vm: &VirtualMachine, zelf: &PyObjectRef, operation: &str) -> PyResult { + Err(new_unsupported_operation( + vm, + format!("{}.{}() not supported", zelf.class().name, operation), + )) + } + #[derive(FromArgs)] pub(super) struct OptionalSize { // In a few functions, the default value is -1 rather than None. @@ -210,13 +239,63 @@ mod _io { } } + fn check_closed(file: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if pybool::boolval(vm, vm.get_attribute(file.clone(), "closed")?)? { + Err(vm.new_value_error("I/O operation on closed file".to_owned())) + } else { + Ok(()) + } + } + + fn check_readable(file: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if pybool::boolval(vm, vm.call_method(file, "readable", ())?)? { + Ok(()) + } else { + _unsupported(vm, file, "File or stream is not readable") + } + } + + fn check_writable(file: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if pybool::boolval(vm, vm.call_method(file, "writable", ())?)? { + Ok(()) + } else { + _unsupported(vm, file, "File or stream is not writable.") + } + } + + fn check_seekable(file: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if pybool::boolval(vm, vm.call_method(file, "seekable", ())?)? { + Ok(()) + } else { + _unsupported(vm, file, "File or stream is not seekable") + } + } + #[pyattr] #[pyclass(name = "_IOBase")] struct _IOBase; #[pyimpl(flags(BASETYPE))] impl _IOBase { + #[pymethod] + fn seek( + zelf: PyObjectRef, + _pos: PyObjectRef, + _whence: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + _unsupported(vm, &zelf, "seek") + } + #[pymethod] + fn tell(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + vm.call_method(&zelf, "seek", vec![vm.ctx.new_int(0), vm.ctx.new_int(1)]) + } + #[pymethod] + fn truncate(zelf: PyObjectRef, _pos: OptionalArg, vm: &VirtualMachine) -> PyResult { + _unsupported(vm, &zelf, "truncate") + } #[pyattr] + fn __closed(ctx: &PyContext) -> PyObjectRef { ctx.new_bool(false) } @@ -304,67 +383,23 @@ mod _io { } #[pymethod(name = "_checkClosed")] - fn check_closed( - instance: PyObjectRef, - msg: OptionalOption, - vm: &VirtualMachine, - ) -> PyResult<()> { - if pybool::boolval(vm, vm.get_attribute(instance, "closed")?)? { - let msg = msg - .flatten() - .unwrap_or_else(|| vm.ctx.new_str("I/O operation on closed file")); - Err(vm.new_exception(vm.ctx.exceptions.value_error.clone(), vec![msg])) - } else { - Ok(()) - } + fn check_closed(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + check_closed(&instance, vm) } #[pymethod(name = "_checkReadable")] - fn check_readable( - instance: PyObjectRef, - msg: OptionalOption, - vm: &VirtualMachine, - ) -> PyResult<()> { - if !pybool::boolval(vm, vm.call_method(&instance, "readable", ())?)? { - let msg = msg - .flatten() - .unwrap_or_else(|| vm.ctx.new_str("File or stream is not readable.")); - Err(vm.new_exception(vm.ctx.exceptions.value_error.clone(), vec![msg])) - } else { - Ok(()) - } + fn check_readable(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + check_readable(&instance, vm) } #[pymethod(name = "_checkWritable")] - fn check_writable( - instance: PyObjectRef, - msg: OptionalOption, - vm: &VirtualMachine, - ) -> PyResult<()> { - if !pybool::boolval(vm, vm.call_method(&instance, "writable", ())?)? { - let msg = msg - .flatten() - .unwrap_or_else(|| vm.ctx.new_str("File or stream is not writable.")); - Err(vm.new_exception(vm.ctx.exceptions.value_error.clone(), vec![msg])) - } else { - Ok(()) - } + fn check_writable(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + check_writable(&instance, vm) } #[pymethod(name = "_checkSeekable")] - fn check_seekable( - instance: PyObjectRef, - msg: OptionalOption, - vm: &VirtualMachine, - ) -> PyResult<()> { - if !pybool::boolval(vm, vm.call_method(&instance, "seekable", ())?)? { - let msg = msg - .flatten() - .unwrap_or_else(|| vm.ctx.new_str("File or stream is not seekable.")); - Err(vm.new_exception(vm.ctx.exceptions.value_error.clone(), vec![msg])) - } else { - Ok(()) - } + fn check_seekable(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + check_seekable(&instance, vm) } #[pymethod(magic)] @@ -415,114 +450,318 @@ mod _io { #[pyimpl(flags(BASETYPE))] impl _BufferedIOBase { - // #[pymethod(magic)] - fn init( - instance: PyObjectRef, - raw: PyObjectRef, - buffer_size: OptionalArg, + #[pymethod] + fn read(zelf: PyObjectRef, _size: OptionalArg, vm: &VirtualMachine) -> PyResult { + _unsupported(vm, &zelf, "read") + } + #[pymethod] + fn read1(zelf: PyObjectRef, _size: OptionalArg, vm: &VirtualMachine) -> PyResult { + _unsupported(vm, &zelf, "read1") + } + fn _readinto( + zelf: PyObjectRef, + b: PyObjectRef, + method: &str, vm: &VirtualMachine, - ) -> PyResult<()> { - vm.set_attr(&instance, "raw", raw)?; - vm.set_attr( - &instance, - "buffer_size", - vm.ctx.new_int(buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE)), - )?; - Ok(()) + ) -> PyResult { + let b = PyRwBytesLike::try_from_object(vm, b)?; + let mut buf = b.borrow_value(); + let data = vm.call_method(&zelf, method, vec![vm.ctx.new_int(buf.len())])?; + let data = PyBytesLike::try_from_object(vm, data)?; + let data = data.borrow_value(); + match buf.get_mut(..data.len()) { + Some(slice) => { + slice.copy_from_slice(&data); + Ok(b.len()) + } + None => Err(vm.new_value_error( + "readinto: buffer and read data have different lengths".to_owned(), + )), + } } - - // #[pymethod] - fn fileno(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let raw = vm.get_attribute(instance, "raw")?; - vm.call_method(&raw, "fileno", ()) + #[pymethod] + fn readinto(zelf: PyObjectRef, b: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Self::_readinto(zelf, b, "read", vm) } - - // #[pyproperty] - fn mode(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let raw = vm.get_attribute(instance, "raw")?; - vm.get_attribute(raw, "mode") + #[pymethod] + fn readinto1(zelf: PyObjectRef, b: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Self::_readinto(zelf, b, "read1", vm) } - - // #[pyproperty] - fn name(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let raw = vm.get_attribute(instance, "raw")?; - vm.get_attribute(raw, "name") + #[pymethod] + fn write(zelf: PyObjectRef, _b: PyObjectRef, vm: &VirtualMachine) -> PyResult { + _unsupported(vm, &zelf, "write") } - - // #[pymethod] - fn tell(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let raw = vm.get_attribute(instance, "raw")?; - vm.invoke(&vm.get_attribute(raw, "tell")?, ()) - } - - // #[pymethod] - fn close(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - let raw = vm.get_attribute(instance, "raw")?; - vm.invoke(&vm.get_attribute(raw, "close")?, ())?; - Ok(()) + #[pymethod] + fn detach(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + _unsupported(vm, &zelf, "detach") } } // TextIO Base has no public constructor - #[pyattr] - #[pyclass(name = "_TextIOBase", base = "_IOBase")] + #[pyclass(name = "_TextIOBase", noattr)] struct _TextIOBase; #[pyimpl(flags(BASETYPE))] impl _TextIOBase {} - #[pyattr] - #[pyclass(name = "BufferedReader", base = "_BufferedIOBase")] - struct BufferedReader; + bitflags::bitflags! { + #[derive(Default)] + struct BufferedFlags: u8 { + const DETACHED = 1 << 0; + const WRITABLE = 1 << 1; + const READABLE = 1 << 2; + } + } - #[pyimpl(flags(BASETYPE))] - impl BufferedReader { - //workaround till the buffered classes can be fixed up to be more - //consistent with the python model - //For more info see: https://github.com/RustPython/RustPython/issues/547 + #[derive(Debug, Default)] + struct BufferedData { + raw: Option, + flags: BufferedFlags, + buffer: Vec, + pos: Offset, + abs_pos: Offset, + read_end: Offset, + write_pos: Offset, + write_end: Offset, + } + + impl BufferedData { + fn check_init(&self, vm: &VirtualMachine) -> PyResult<&PyObjectRef> { + if let Some(raw) = &self.raw { + Ok(raw) + } else { + let msg = if self.flags.contains(BufferedFlags::DETACHED) { + "raw stream has been detached" + } else { + "I/O operation on uninitialized object" + }; + Err(vm.new_value_error(msg.to_owned())) + } + } + + fn writable(&self) -> bool { + self.flags.contains(BufferedFlags::WRITABLE) + } + fn readable(&self) -> bool { + self.flags.contains(BufferedFlags::READABLE) + } + + fn flush(&mut self, vm: &VirtualMachine) -> PyResult<()> { + let _ = vm; + + Ok(()) + } + + fn flush_rewind(&mut self, vm: &VirtualMachine) -> PyResult<()> { + self.flush(vm)?; + if self.readable() { + // TODO: rewind + } + Ok(()) + } + + fn raw_seek(&mut self, pos: Offset, whence: i32, vm: &VirtualMachine) -> PyResult { + let ret = vm.call_method(self.check_init(vm)?, "seek", (pos, whence))?; + let offset = get_offset(ret, vm)?; + if offset < 0 { + return Err( + vm.new_os_error(format!("Raw stream returned invalid position {}", offset)) + ); + } + self.abs_pos = offset; + Ok(offset) + } + + fn raw_tell(&mut self, vm: &VirtualMachine) -> PyResult { + let ret = vm.call_method(self.check_init(vm)?, "seek", ())?; + let offset = get_offset(ret, vm)?; + if offset < 0 { + return Err( + vm.new_os_error(format!("Raw stream returned invalid position {}", offset)) + ); + } + self.abs_pos = offset; + Ok(offset) + } + } + + fn get_offset(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let int = vm.to_index(&obj)?; + // TODO: patch num-traits to impl ToPrimitive for &T + NumCast::from(int.borrow_value().clone()).ok_or_else(|| { + vm.new_value_error(format!( + "cannot fit '{}' into an offset-sized integer", + obj.class().name + )) + }) + } + + #[pyimpl] + trait BufferedMixin: PyValue { + const READABLE: bool; + const WRITABLE: bool; + const SEEKABLE: bool = false; + fn data(&self) -> &PyThreadMutex; + fn lock(&self, vm: &VirtualMachine) -> PyResult> { + self.data() + .lock() + .ok_or_else(|| vm.new_runtime_error("reentrant call inside buffered io".to_owned())) + } #[pymethod(magic)] fn init( - instance: PyObjectRef, + &self, raw: PyObjectRef, - buffer_size: OptionalArg, + buffer_size: OptionalArg, vm: &VirtualMachine, ) -> PyResult<()> { - _BufferedIOBase::init(instance, raw, buffer_size, vm) + let buffer_size = match buffer_size { + OptionalArg::Present(i) => i.to_usize().ok_or_else(|| { + vm.new_value_error("buffer size must be strictly positive".to_owned()) + })?, + OptionalArg::Missing => DEFAULT_BUFFER_SIZE, + }; + + let mut data = self.lock(vm)?; + data.raw = None; + data.flags.remove(BufferedFlags::DETACHED); + + if Self::SEEKABLE { + check_seekable(&raw, vm)?; + } + if Self::READABLE { + data.flags.insert(BufferedFlags::READABLE); + check_readable(&raw, vm)?; + } + if Self::WRITABLE { + data.flags.insert(BufferedFlags::WRITABLE); + check_writable(&raw, vm)?; + } + + data.buffer = vec![0; buffer_size]; + + if Self::READABLE { + data.read_end = -1; + } + if Self::WRITABLE { + data.write_pos = 0; + data.write_end = -1; + } + if Self::SEEKABLE { + data.pos = 0; + } + + data.raw = Some(raw); + + Ok(()) } #[pymethod] - fn fileno(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - _BufferedIOBase::fileno(instance, vm) + fn seek( + &self, + pos: PyObjectRef, + whence: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let pos = get_offset(pos, vm)?; + let whence = whence.unwrap_or(0); + self.lock(vm)?.raw_seek(pos, whence, vm) + } + #[pymethod] + fn tell(&self, vm: &VirtualMachine) -> PyResult { + self.lock(vm)?.raw_tell(vm) + } + #[pymethod] + fn truncate( + zelf: PyRef, + pos: OptionalOption, + vm: &VirtualMachine, + ) -> PyResult { + let pos = pos.flatten().into_pyobject(vm); + let mut data = zelf.lock(vm)?; + data.check_init(vm)?; + if data.writable() { + data.flush_rewind(vm)?; + } + let res = vm.call_method(data.raw.as_ref().unwrap(), "truncate", vec![pos])?; + let _ = data.raw_tell(vm); + Ok(res) + } + #[pymethod] + fn flush(zelf: PyRef, vm: &VirtualMachine) -> PyResult<()> { + let mut data = zelf.lock(vm)?; + let raw = data.check_init(vm)?; + ensure_unclosed(raw.clone(), "flush of closed file", vm)?; + data.flush_rewind(vm) + } + #[pymethod] + fn detach(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + vm.call_method(zelf.as_object(), "flush", vec![])?; + let mut data = zelf.lock(vm)?; + data.flags.insert(BufferedFlags::DETACHED); + data.raw + .take() + .ok_or_else(|| vm.new_value_error("raw stream has been detached".to_owned())) + } + #[pymethod] + fn seekable(&self, vm: &VirtualMachine) -> PyResult { + vm.call_method(self.lock(vm)?.check_init(vm)?, "seekable", vec![]) } - #[pyproperty] - fn mode(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - _BufferedIOBase::mode(instance, vm) + fn raw(&self, vm: &VirtualMachine) -> PyResult> { + Ok(self.lock(vm)?.raw.clone()) + } + fn closed(&self, vm: &VirtualMachine) -> PyResult { + vm.get_attribute(self.lock(vm)?.check_init(vm)?.clone(), "closed") } - #[pyproperty] - fn name(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - _BufferedIOBase::name(instance, vm) + fn name(&self, vm: &VirtualMachine) -> PyResult { + vm.get_attribute(self.lock(vm)?.check_init(vm)?.clone(), "name") + } + #[pymethod] + fn fileno(&self, vm: &VirtualMachine) -> PyResult { + vm.call_method(self.lock(vm)?.check_init(vm)?, "fileno", vec![]) + } + #[pymethod] + fn isatty(&self, vm: &VirtualMachine) -> PyResult { + vm.call_method(self.lock(vm)?.check_init(vm)?, "isatty", vec![]) + } + } + + #[pyclass(name = "BufferedReader", noattr)] + #[derive(Debug)] + struct BufferedReader { + data: PyThreadMutex, + } + impl PyValue for BufferedReader { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } + } + impl BufferedMixin for BufferedReader { + const READABLE: bool = true; + const WRITABLE: bool = false; + fn data(&self) -> &PyThreadMutex { + &self.data + } + } + + #[pyimpl(with(BufferedMixin), flags(BASETYPE, HAS_DICT))] + impl BufferedReader { + #[pyslot] + fn tp_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult> { + Self { + data: Default::default(), + } + .into_ref_with_type(vm, cls) } #[pymethod] - fn tell(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - _BufferedIOBase::tell(instance, vm) - } - - #[pymethod] - fn close(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - _BufferedIOBase::close(instance, vm) - } - - #[pymethod] - fn read(instance: PyObjectRef, size: OptionalSize, vm: &VirtualMachine) -> PyResult { - vm.call_method( - &vm.get_attribute(instance, "raw")?, - "read", - (size.to_usize(),), - ) + fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult { + let data = self.lock(vm)?; + let raw = data.check_init(vm)?; + ensure_unclosed(raw.clone(), "read of closed file", vm)?; + vm.call_method(raw, "read", (size.to_usize(),)) } #[pymethod] @@ -548,54 +787,39 @@ mod _io { } } - #[pyattr] - #[pyclass(name = "BufferedWriter", base = "_BufferedIOBase")] - struct BufferedWriter; + #[pyclass(name = "BufferedWriter", noattr)] + #[derive(Debug)] + struct BufferedWriter { + data: PyThreadMutex, + } + impl PyValue for BufferedWriter { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } + } + impl BufferedMixin for BufferedWriter { + const READABLE: bool = false; + const WRITABLE: bool = true; + fn data(&self) -> &PyThreadMutex { + &self.data + } + } - #[pyimpl(flags(BASETYPE))] + #[pyimpl(with(BufferedMixin), flags(BASETYPE, HAS_DICT))] impl BufferedWriter { - //workaround till the buffered classes can be fixed up to be more - //consistent with the python model - //For more info see: https://github.com/RustPython/RustPython/issues/547 - - #[pymethod(magic)] - fn init( - instance: PyObjectRef, - raw: PyObjectRef, - buffer_size: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult<()> { - _BufferedIOBase::init(instance, raw, buffer_size, vm) + #[pyslot] + fn tp_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult> { + Self { + data: Default::default(), + } + .into_ref_with_type(vm, cls) } #[pymethod] - fn fileno(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - _BufferedIOBase::fileno(instance, vm) - } - - #[pyproperty] - fn mode(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - _BufferedIOBase::mode(instance, vm) - } - - #[pyproperty] - fn name(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - _BufferedIOBase::name(instance, vm) - } - - #[pymethod] - fn tell(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - _BufferedIOBase::tell(instance, vm) - } - - #[pymethod] - fn close(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - _BufferedIOBase::close(instance, vm) - } - - #[pymethod] - fn write(instance: PyObjectRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let raw = vm.get_attribute(instance, "raw").unwrap(); + fn write(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let data = self.lock(vm)?; + let raw = data.check_init(vm)?; + ensure_unclosed(raw.clone(), "write to closed file", vm)?; //This should be replaced with a more appropriate chunking implementation vm.call_method(&raw, "write", (obj,)) @@ -1265,8 +1489,8 @@ mod _io { // Construct a FileIO (subclass of RawIOBase) // This is subsequently consumed by a Buffered Class. let file_io_class = vm.get_attribute(io_module.clone(), "FileIO").map_err(|_| { - // TODO: UnsupportedOperation here - vm.new_os_error( + new_unsupported_operation( + vm, "Couldn't get FileIO, io.open likely isn't supported on your platform".to_owned(), ) })?; @@ -1319,6 +1543,25 @@ mod _io { } } + rustpython_common::static_cell! { + pub(super) static UNSUPPORTED_OPERATION: PyTypeRef; + } + + pub(super) fn make_unsupportedop(ctx: &PyContext) -> PyTypeRef { + pytype::new( + ctx.types.type_type.clone(), + "UnsupportedOperation", + ctx.exceptions.os_error.clone(), + vec![ + ctx.exceptions.os_error.clone(), + ctx.exceptions.value_error.clone(), + ], + Default::default(), + Default::default(), + ) + .unwrap() + } + #[cfg(test)] mod tests { use super::*; @@ -1588,6 +1831,11 @@ mod fileio { Ok(()) } + #[pymethod] + fn readable(&self) -> bool { + true + } + #[pymethod] fn read(&self, read_byte: OptionalSize, vm: &VirtualMachine) -> PyResult> { let mut handle = self.get_file(vm)?; @@ -1626,6 +1874,11 @@ mod fileio { Ok(ret) } + #[pymethod] + fn writable(&self) -> bool { + true + } + #[pymethod] fn write(&self, obj: PyBytesLike, vm: &VirtualMachine) -> PyResult { let mut handle = self.get_file(vm)?; diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 7a6dae6b0..fc19aa5ac 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -27,6 +27,13 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; +// this is basically what CPython has for Py_off_t; windows uses long long +// for offsets, other platforms just use off_t +#[cfg(not(windows))] +pub type Offset = libc::off_t; +#[cfg(windows)] +pub type Offset = libc::c_longlong; + #[derive(Debug, Copy, Clone)] enum OutputMode { String, @@ -782,13 +789,6 @@ mod _os { Ok(buf) } - // this is basically what CPython has for Py_off_t; windows uses long long - // for offsets, other platforms just use off_t - #[cfg(not(windows))] - pub type Offset = libc::off_t; - #[cfg(windows)] - pub type Offset = libc::c_longlong; - #[pyfunction] fn isatty(fd: i32) -> bool { unsafe { suppress_iph!(libc::isatty(fd)) != 0 }