rustpython_vm/stdlib/
io.rs

1/*
2 * I/O core tools.
3 */
4cfg_if::cfg_if! {
5    if #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] {
6        use crate::common::crt_fd::Offset;
7    } else {
8        type Offset = i64;
9    }
10}
11
12use crate::{
13    builtins::{PyBaseExceptionRef, PyModule},
14    common::os::ErrorExt,
15    convert::{IntoPyException, ToPyException},
16    PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
17};
18pub use _io::io_open as open;
19
20impl ToPyException for std::io::Error {
21    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
22        let errno = self.posix_errno();
23        let msg = self.to_string();
24        #[allow(clippy::let_and_return)]
25        let exc = vm.new_errno_error(errno, msg);
26
27        #[cfg(windows)]
28        {
29            use crate::object::AsObject;
30            let winerror = if let Some(winerror) = self.raw_os_error() {
31                vm.new_pyobj(winerror)
32            } else {
33                vm.ctx.none()
34            };
35
36            // FIXME: manual setup winerror due to lack of OSError.__init__ support
37            exc.as_object()
38                .set_attr("winerror", vm.new_pyobj(winerror), vm)
39                .unwrap();
40        }
41        exc
42    }
43}
44
45impl IntoPyException for std::io::Error {
46    fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
47        self.to_pyexception(vm)
48    }
49}
50
51pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
52    let ctx = &vm.ctx;
53
54    let module = _io::make_module(vm);
55
56    #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
57    fileio::extend_module(vm, &module).unwrap();
58
59    let unsupported_operation = _io::UNSUPPORTED_OPERATION
60        .get_or_init(|| _io::make_unsupportedop(ctx))
61        .clone();
62    extend_module!(vm, &module, {
63        "UnsupportedOperation" => unsupported_operation,
64        "BlockingIOError" => ctx.exceptions.blocking_io_error.to_owned(),
65    });
66
67    module
68}
69
70// not used on all platforms
71#[allow(unused)]
72#[derive(Copy, Clone)]
73#[repr(transparent)]
74pub struct Fildes(pub i32);
75
76impl TryFromObject for Fildes {
77    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
78        use crate::builtins::int;
79        let int = match obj.downcast::<int::PyInt>() {
80            Ok(i) => i,
81            Err(obj) => {
82                let fileno_meth = vm.get_attribute_opt(obj, "fileno")?.ok_or_else(|| {
83                    vm.new_type_error(
84                        "argument must be an int, or have a fileno() method.".to_owned(),
85                    )
86                })?;
87                fileno_meth
88                    .call((), vm)?
89                    .downcast()
90                    .map_err(|_| vm.new_type_error("fileno() returned a non-integer".to_owned()))?
91            }
92        };
93        let fd = int.try_to_primitive(vm)?;
94        if fd < 0 {
95            return Err(vm.new_value_error(format!(
96                "file descriptor cannot be a negative integer ({fd})"
97            )));
98        }
99        Ok(Fildes(fd))
100    }
101}
102
103#[pymodule]
104mod _io {
105    use super::*;
106    use crate::{
107        builtins::{
108            PyBaseExceptionRef, PyByteArray, PyBytes, PyBytesRef, PyIntRef, PyMemoryView, PyStr,
109            PyStrRef, PyType, PyTypeRef,
110        },
111        class::StaticType,
112        common::lock::{
113            PyMappedThreadMutexGuard, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard,
114            PyThreadMutex, PyThreadMutexGuard,
115        },
116        convert::ToPyObject,
117        function::{
118            ArgBytesLike, ArgIterable, ArgMemoryBuffer, ArgSize, Either, FuncArgs, IntoFuncArgs,
119            OptionalArg, OptionalOption, PySetterValue,
120        },
121        protocol::{
122            BufferDescriptor, BufferMethods, BufferResizeGuard, PyBuffer, PyIterReturn, VecBuffer,
123        },
124        recursion::ReprGuard,
125        types::{
126            Callable, Constructor, DefaultConstructor, Destructor, Initializer, IterNext, Iterable,
127        },
128        vm::VirtualMachine,
129        AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
130        TryFromBorrowedObject, TryFromObject,
131    };
132    use bstr::ByteSlice;
133    use crossbeam_utils::atomic::AtomicCell;
134    use malachite_bigint::{BigInt, BigUint};
135    use num_traits::ToPrimitive;
136    use std::{
137        io::{self, prelude::*, Cursor, SeekFrom},
138        ops::Range,
139    };
140
141    #[allow(clippy::let_and_return)]
142    fn validate_whence(whence: i32) -> bool {
143        let x = (0..=2).contains(&whence);
144        cfg_if::cfg_if! {
145            if #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))] {
146                x || matches!(whence, libc::SEEK_DATA | libc::SEEK_HOLE)
147            } else {
148                x
149            }
150        }
151    }
152
153    fn ensure_unclosed(file: &PyObject, msg: &str, vm: &VirtualMachine) -> PyResult<()> {
154        if file.get_attr("closed", vm)?.try_to_bool(vm)? {
155            Err(vm.new_value_error(msg.to_owned()))
156        } else {
157            Ok(())
158        }
159    }
160
161    pub fn new_unsupported_operation(vm: &VirtualMachine, msg: String) -> PyBaseExceptionRef {
162        vm.new_exception_msg(UNSUPPORTED_OPERATION.get().unwrap().clone(), msg)
163    }
164
165    fn _unsupported<T>(vm: &VirtualMachine, zelf: &PyObject, operation: &str) -> PyResult<T> {
166        Err(new_unsupported_operation(
167            vm,
168            format!("{}.{}() not supported", zelf.class().name(), operation),
169        ))
170    }
171
172    #[derive(FromArgs)]
173    pub(super) struct OptionalSize {
174        // In a few functions, the default value is -1 rather than None.
175        // Make sure the default value doesn't affect compatibility.
176        #[pyarg(positional, default)]
177        size: Option<ArgSize>,
178    }
179
180    impl OptionalSize {
181        #[allow(clippy::wrong_self_convention)]
182        pub fn to_usize(self) -> Option<usize> {
183            self.size?.to_usize()
184        }
185
186        pub fn try_usize(self, vm: &VirtualMachine) -> PyResult<Option<usize>> {
187            self.size
188                .map(|v| {
189                    let v = *v;
190                    if v >= 0 {
191                        Ok(v as usize)
192                    } else {
193                        Err(vm.new_value_error(format!("Negative size value {v}")))
194                    }
195                })
196                .transpose()
197        }
198    }
199
200    fn os_err(vm: &VirtualMachine, err: io::Error) -> PyBaseExceptionRef {
201        #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
202        {
203            use crate::convert::ToPyException;
204            err.to_pyexception(vm)
205        }
206        #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
207        {
208            vm.new_os_error(err.to_string())
209        }
210    }
211
212    pub(super) fn io_closed_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
213        vm.new_value_error("I/O operation on closed file".to_owned())
214    }
215
216    #[pyattr]
217    const DEFAULT_BUFFER_SIZE: usize = 8 * 1024;
218
219    pub(super) fn seekfrom(
220        vm: &VirtualMachine,
221        offset: PyObjectRef,
222        how: OptionalArg<i32>,
223    ) -> PyResult<SeekFrom> {
224        let seek = match how {
225            OptionalArg::Present(0) | OptionalArg::Missing => {
226                SeekFrom::Start(offset.try_into_value(vm)?)
227            }
228            OptionalArg::Present(1) => SeekFrom::Current(offset.try_into_value(vm)?),
229            OptionalArg::Present(2) => SeekFrom::End(offset.try_into_value(vm)?),
230            _ => return Err(vm.new_value_error("invalid value for how".to_owned())),
231        };
232        Ok(seek)
233    }
234
235    #[derive(Debug)]
236    struct BufferedIO {
237        cursor: Cursor<Vec<u8>>,
238    }
239
240    impl BufferedIO {
241        fn new(cursor: Cursor<Vec<u8>>) -> BufferedIO {
242            BufferedIO { cursor }
243        }
244
245        fn write(&mut self, data: &[u8]) -> Option<u64> {
246            let length = data.len();
247            self.cursor.write_all(data).ok()?;
248            Some(length as u64)
249        }
250
251        //return the entire contents of the underlying
252        fn getvalue(&self) -> Vec<u8> {
253            self.cursor.clone().into_inner()
254        }
255
256        //skip to the jth position
257        fn seek(&mut self, seek: SeekFrom) -> io::Result<u64> {
258            self.cursor.seek(seek)
259        }
260
261        //Read k bytes from the object and return.
262        fn read(&mut self, bytes: Option<usize>) -> Option<Vec<u8>> {
263            let pos = self.cursor.position().to_usize()?;
264            let avail_slice = self.cursor.get_ref().get(pos..)?;
265            // if we don't specify the number of bytes, or it's too big, give the whole rest of the slice
266            let n = bytes.map_or_else(
267                || avail_slice.len(),
268                |n| std::cmp::min(n, avail_slice.len()),
269            );
270            let b = avail_slice[..n].to_vec();
271            self.cursor.set_position((pos + n) as u64);
272            Some(b)
273        }
274
275        fn tell(&self) -> u64 {
276            self.cursor.position()
277        }
278
279        fn readline(&mut self, size: Option<usize>, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
280            self.read_until(size, b'\n', vm)
281        }
282
283        fn read_until(
284            &mut self,
285            size: Option<usize>,
286            byte: u8,
287            vm: &VirtualMachine,
288        ) -> PyResult<Vec<u8>> {
289            let size = match size {
290                None => {
291                    let mut buf: Vec<u8> = Vec::new();
292                    self.cursor
293                        .read_until(byte, &mut buf)
294                        .map_err(|err| os_err(vm, err))?;
295                    return Ok(buf);
296                }
297                Some(0) => {
298                    return Ok(Vec::new());
299                }
300                Some(size) => size,
301            };
302
303            let available = {
304                // For Cursor, fill_buf returns all of the remaining data unlike other BufReads which have outer reading source.
305                // Unless we add other data by write, there will be no more data.
306                let buf = self.cursor.fill_buf().map_err(|err| os_err(vm, err))?;
307                if size < buf.len() {
308                    &buf[..size]
309                } else {
310                    buf
311                }
312            };
313            let buf = match available.find_byte(byte) {
314                Some(i) => available[..=i].to_vec(),
315                _ => available.to_vec(),
316            };
317            self.cursor.consume(buf.len());
318            Ok(buf)
319        }
320
321        fn truncate(&mut self, pos: Option<usize>) -> usize {
322            let pos = pos.unwrap_or_else(|| self.tell() as usize);
323            self.cursor.get_mut().truncate(pos);
324            pos
325        }
326    }
327
328    fn file_closed(file: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
329        file.get_attr("closed", vm)?.try_to_bool(vm)
330    }
331    fn check_closed(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
332        if file_closed(file, vm)? {
333            Err(io_closed_error(vm))
334        } else {
335            Ok(())
336        }
337    }
338
339    fn check_readable(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
340        if vm.call_method(file, "readable", ())?.try_to_bool(vm)? {
341            Ok(())
342        } else {
343            Err(new_unsupported_operation(
344                vm,
345                "File or stream is not readable".to_owned(),
346            ))
347        }
348    }
349
350    fn check_writable(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
351        if vm.call_method(file, "writable", ())?.try_to_bool(vm)? {
352            Ok(())
353        } else {
354            Err(new_unsupported_operation(
355                vm,
356                "File or stream is not writable.".to_owned(),
357            ))
358        }
359    }
360
361    fn check_seekable(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
362        if vm.call_method(file, "seekable", ())?.try_to_bool(vm)? {
363            Ok(())
364        } else {
365            Err(new_unsupported_operation(
366                vm,
367                "File or stream is not seekable".to_owned(),
368            ))
369        }
370    }
371
372    fn check_decoded(decoded: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
373        decoded.downcast().map_err(|obj| {
374            vm.new_type_error(format!(
375                "decoder should return a string result, not '{}'",
376                obj.class().name()
377            ))
378        })
379    }
380
381    #[pyattr]
382    #[pyclass(name = "_IOBase")]
383    #[derive(Debug, PyPayload)]
384    pub struct _IOBase;
385
386    #[pyclass(with(IterNext, Iterable, Destructor), flags(BASETYPE, HAS_DICT))]
387    impl _IOBase {
388        #[pymethod]
389        fn seek(
390            zelf: PyObjectRef,
391            _pos: PyObjectRef,
392            _whence: OptionalArg,
393            vm: &VirtualMachine,
394        ) -> PyResult {
395            _unsupported(vm, &zelf, "seek")
396        }
397        #[pymethod]
398        fn tell(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
399            vm.call_method(&zelf, "seek", (0, 1))
400        }
401        #[pymethod]
402        fn truncate(zelf: PyObjectRef, _pos: OptionalArg, vm: &VirtualMachine) -> PyResult {
403            _unsupported(vm, &zelf, "truncate")
404        }
405        #[pymethod]
406        fn fileno(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
407            _unsupported(vm, &zelf, "truncate")
408        }
409
410        #[pyattr]
411        fn __closed(ctx: &Context) -> PyIntRef {
412            ctx.new_bool(false)
413        }
414
415        #[pymethod(magic)]
416        fn enter(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult {
417            check_closed(&instance, vm)?;
418            Ok(instance)
419        }
420
421        #[pymethod(magic)]
422        fn exit(instance: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
423            vm.call_method(&instance, "close", ())?;
424            Ok(())
425        }
426
427        #[pymethod]
428        fn flush(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
429            // just check if this is closed; if it isn't, do nothing
430            check_closed(&instance, vm)
431        }
432
433        #[pymethod]
434        fn seekable(_self: PyObjectRef) -> bool {
435            false
436        }
437        #[pymethod]
438        fn readable(_self: PyObjectRef) -> bool {
439            false
440        }
441        #[pymethod]
442        fn writable(_self: PyObjectRef) -> bool {
443            false
444        }
445
446        #[pymethod]
447        fn isatty(_self: PyObjectRef) -> bool {
448            false
449        }
450
451        #[pygetset]
452        fn closed(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult {
453            instance.get_attr("__closed", vm)
454        }
455
456        #[pymethod]
457        fn close(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
458            iobase_close(&instance, vm)
459        }
460
461        #[pymethod]
462        fn readline(
463            instance: PyObjectRef,
464            size: OptionalSize,
465            vm: &VirtualMachine,
466        ) -> PyResult<Vec<u8>> {
467            let size = size.to_usize();
468            let read = instance.get_attr("read", vm)?;
469            let mut res = Vec::new();
470            while size.map_or(true, |s| res.len() < s) {
471                let read_res = ArgBytesLike::try_from_object(vm, read.call((1,), vm)?)?;
472                if read_res.with_ref(|b| b.is_empty()) {
473                    break;
474                }
475                read_res.with_ref(|b| res.extend_from_slice(b));
476                if res.ends_with(b"\n") {
477                    break;
478                }
479            }
480            Ok(res)
481        }
482
483        #[pymethod]
484        fn readlines(
485            instance: PyObjectRef,
486            hint: OptionalOption<isize>,
487            vm: &VirtualMachine,
488        ) -> PyResult<Vec<PyObjectRef>> {
489            let hint = hint.flatten().unwrap_or(-1);
490            if hint <= 0 {
491                return instance.try_to_value(vm);
492            }
493            let hint = hint as usize;
494            let mut ret = Vec::new();
495            let it = ArgIterable::<PyObjectRef>::try_from_object(vm, instance)?;
496            let mut full_len = 0;
497            for line in it.iter(vm)? {
498                let line = line?;
499                let line_len = line.length(vm)?;
500                ret.push(line.clone());
501                full_len += line_len;
502                if full_len > hint {
503                    break;
504                }
505            }
506            Ok(ret)
507        }
508
509        #[pymethod]
510        fn writelines(
511            instance: PyObjectRef,
512            lines: ArgIterable,
513            vm: &VirtualMachine,
514        ) -> PyResult<()> {
515            check_closed(&instance, vm)?;
516            for line in lines.iter(vm)? {
517                vm.call_method(&instance, "write", (line?,))?;
518            }
519            Ok(())
520        }
521
522        #[pymethod(name = "_checkClosed")]
523        fn check_closed(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
524            check_closed(&instance, vm)
525        }
526
527        #[pymethod(name = "_checkReadable")]
528        fn check_readable(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
529            check_readable(&instance, vm)
530        }
531
532        #[pymethod(name = "_checkWritable")]
533        fn check_writable(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
534            check_writable(&instance, vm)
535        }
536
537        #[pymethod(name = "_checkSeekable")]
538        fn check_seekable(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
539            check_seekable(&instance, vm)
540        }
541    }
542
543    impl Destructor for _IOBase {
544        fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
545            let _ = vm.call_method(zelf, "close", ());
546            Ok(())
547        }
548
549        #[cold]
550        fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> {
551            unreachable!("slot_del is implemented")
552        }
553    }
554
555    impl Iterable for _IOBase {
556        fn slot_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
557            check_closed(&zelf, vm)?;
558            Ok(zelf)
559        }
560
561        fn iter(_zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult {
562            unreachable!("slot_iter is implemented")
563        }
564    }
565
566    impl IterNext for _IOBase {
567        fn slot_iternext(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
568            let line = vm.call_method(zelf, "readline", ())?;
569            Ok(if !line.clone().try_to_bool(vm)? {
570                PyIterReturn::StopIteration(None)
571            } else {
572                PyIterReturn::Return(line)
573            })
574        }
575
576        fn next(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyIterReturn> {
577            unreachable!("slot_iternext is implemented")
578        }
579    }
580
581    pub(super) fn iobase_close(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
582        if !file_closed(file, vm)? {
583            let res = vm.call_method(file, "flush", ());
584            file.set_attr("__closed", vm.new_pyobj(true), vm)?;
585            res?;
586        }
587        Ok(())
588    }
589
590    #[pyattr]
591    #[pyclass(name = "_RawIOBase", base = "_IOBase")]
592    pub(super) struct _RawIOBase;
593
594    #[pyclass(flags(BASETYPE, HAS_DICT))]
595    impl _RawIOBase {
596        #[pymethod]
597        fn read(instance: PyObjectRef, size: OptionalSize, vm: &VirtualMachine) -> PyResult {
598            if let Some(size) = size.to_usize() {
599                // FIXME: unnecessary zero-init
600                let b = PyByteArray::from(vec![0; size]).into_ref(&vm.ctx);
601                let n = <Option<usize>>::try_from_object(
602                    vm,
603                    vm.call_method(&instance, "readinto", (b.clone(),))?,
604                )?;
605                Ok(n.map(|n| {
606                    let mut bytes = b.borrow_buf_mut();
607                    bytes.truncate(n);
608                    // FIXME: try to use Arc::unwrap on the bytearray to get at the inner buffer
609                    bytes.clone()
610                })
611                .to_pyobject(vm))
612            } else {
613                vm.call_method(&instance, "readall", ())
614            }
615        }
616
617        #[pymethod]
618        fn readall(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<Option<Vec<u8>>> {
619            let mut chunks = Vec::new();
620            let mut total_len = 0;
621            loop {
622                let data = vm.call_method(&instance, "read", (DEFAULT_BUFFER_SIZE,))?;
623                let data = <Option<PyBytesRef>>::try_from_object(vm, data)?;
624                match data {
625                    None => {
626                        if chunks.is_empty() {
627                            return Ok(None);
628                        }
629                        break;
630                    }
631                    Some(b) => {
632                        if b.as_bytes().is_empty() {
633                            break;
634                        }
635                        total_len += b.as_bytes().len();
636                        chunks.push(b)
637                    }
638                }
639            }
640            let mut ret = Vec::with_capacity(total_len);
641            for b in chunks {
642                ret.extend_from_slice(b.as_bytes())
643            }
644            Ok(Some(ret))
645        }
646    }
647
648    #[pyattr]
649    #[pyclass(name = "_BufferedIOBase", base = "_IOBase")]
650    struct _BufferedIOBase;
651
652    #[pyclass(flags(BASETYPE))]
653    impl _BufferedIOBase {
654        #[pymethod]
655        fn read(zelf: PyObjectRef, _size: OptionalArg, vm: &VirtualMachine) -> PyResult {
656            _unsupported(vm, &zelf, "read")
657        }
658        #[pymethod]
659        fn read1(zelf: PyObjectRef, _size: OptionalArg, vm: &VirtualMachine) -> PyResult {
660            _unsupported(vm, &zelf, "read1")
661        }
662        fn _readinto(
663            zelf: PyObjectRef,
664            bufobj: PyObjectRef,
665            method: &str,
666            vm: &VirtualMachine,
667        ) -> PyResult<usize> {
668            let b = ArgMemoryBuffer::try_from_borrowed_object(vm, &bufobj)?;
669            let l = b.len();
670            let data = vm.call_method(&zelf, method, (l,))?;
671            if data.is(&bufobj) {
672                return Ok(l);
673            }
674            let mut buf = b.borrow_buf_mut();
675            let data = ArgBytesLike::try_from_object(vm, data)?;
676            let data = data.borrow_buf();
677            match buf.get_mut(..data.len()) {
678                Some(slice) => {
679                    slice.copy_from_slice(&data);
680                    Ok(data.len())
681                }
682                None => Err(vm.new_value_error(
683                    "readinto: buffer and read data have different lengths".to_owned(),
684                )),
685            }
686        }
687        #[pymethod]
688        fn readinto(zelf: PyObjectRef, b: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
689            Self::_readinto(zelf, b, "read", vm)
690        }
691        #[pymethod]
692        fn readinto1(zelf: PyObjectRef, b: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
693            Self::_readinto(zelf, b, "read1", vm)
694        }
695        #[pymethod]
696        fn write(zelf: PyObjectRef, _b: PyObjectRef, vm: &VirtualMachine) -> PyResult {
697            _unsupported(vm, &zelf, "write")
698        }
699        #[pymethod]
700        fn detach(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
701            _unsupported(vm, &zelf, "detach")
702        }
703    }
704
705    // TextIO Base has no public constructor
706    #[pyattr]
707    #[pyclass(name = "_TextIOBase", base = "_IOBase")]
708    #[derive(Debug, PyPayload)]
709    struct _TextIOBase;
710
711    #[pyclass(flags(BASETYPE))]
712    impl _TextIOBase {
713        #[pygetset]
714        fn encoding(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
715            vm.ctx.none()
716        }
717    }
718
719    #[derive(FromArgs, Clone)]
720    struct BufferSize {
721        #[pyarg(any, optional)]
722        buffer_size: OptionalArg<isize>,
723    }
724
725    bitflags::bitflags! {
726        #[derive(Copy, Clone, Debug, PartialEq, Default)]
727        struct BufferedFlags: u8 {
728            const DETACHED = 1 << 0;
729            const WRITABLE = 1 << 1;
730            const READABLE = 1 << 2;
731        }
732    }
733
734    #[derive(Debug, Default)]
735    struct BufferedData {
736        raw: Option<PyObjectRef>,
737        flags: BufferedFlags,
738        abs_pos: Offset,
739        buffer: Vec<u8>,
740        pos: Offset,
741        raw_pos: Offset,
742        read_end: Offset,
743        write_pos: Offset,
744        write_end: Offset,
745    }
746
747    impl BufferedData {
748        fn check_init(&self, vm: &VirtualMachine) -> PyResult<&PyObject> {
749            if let Some(raw) = &self.raw {
750                Ok(raw)
751            } else {
752                let msg = if self.flags.contains(BufferedFlags::DETACHED) {
753                    "raw stream has been detached"
754                } else {
755                    "I/O operation on uninitialized object"
756                };
757                Err(vm.new_value_error(msg.to_owned()))
758            }
759        }
760
761        #[inline]
762        fn writable(&self) -> bool {
763            self.flags.contains(BufferedFlags::WRITABLE)
764        }
765        #[inline]
766        fn readable(&self) -> bool {
767            self.flags.contains(BufferedFlags::READABLE)
768        }
769
770        #[inline]
771        fn valid_read(&self) -> bool {
772            self.readable() && self.read_end != -1
773        }
774        #[inline]
775        fn valid_write(&self) -> bool {
776            self.writable() && self.write_end != -1
777        }
778
779        #[inline]
780        fn raw_offset(&self) -> Offset {
781            if (self.valid_read() || self.valid_write()) && self.raw_pos >= 0 {
782                self.raw_pos - self.pos
783            } else {
784                0
785            }
786        }
787        #[inline]
788        fn readahead(&self) -> Offset {
789            if self.valid_read() {
790                self.read_end - self.pos
791            } else {
792                0
793            }
794        }
795
796        fn reset_read(&mut self) {
797            self.read_end = -1;
798        }
799        fn reset_write(&mut self) {
800            self.write_pos = 0;
801            self.write_end = -1;
802        }
803
804        fn flush(&mut self, vm: &VirtualMachine) -> PyResult<()> {
805            if !self.valid_write() || self.write_pos == self.write_end {
806                self.reset_write();
807                return Ok(());
808            }
809
810            let rewind = self.raw_offset() + (self.pos - self.write_pos);
811            if rewind != 0 {
812                self.raw_seek(-rewind, 1, vm)?;
813                self.raw_pos = -rewind;
814            }
815
816            while self.write_pos < self.write_end {
817                let n =
818                    self.raw_write(None, self.write_pos as usize..self.write_end as usize, vm)?;
819                let n = n.ok_or_else(|| {
820                    vm.new_exception_msg(
821                        vm.ctx.exceptions.blocking_io_error.to_owned(),
822                        "write could not complete without blocking".to_owned(),
823                    )
824                })?;
825                self.write_pos += n as Offset;
826                self.raw_pos = self.write_pos;
827                vm.check_signals()?;
828            }
829
830            self.reset_write();
831
832            Ok(())
833        }
834
835        fn flush_rewind(&mut self, vm: &VirtualMachine) -> PyResult<()> {
836            self.flush(vm)?;
837            if self.readable() {
838                let res = self.raw_seek(-self.raw_offset(), 1, vm);
839                self.reset_read();
840                res?;
841            }
842            Ok(())
843        }
844
845        fn raw_seek(&mut self, pos: Offset, whence: i32, vm: &VirtualMachine) -> PyResult<Offset> {
846            let ret = vm.call_method(self.check_init(vm)?, "seek", (pos, whence))?;
847            let offset = get_offset(ret, vm)?;
848            if offset < 0 {
849                return Err(
850                    vm.new_os_error(format!("Raw stream returned invalid position {offset}"))
851                );
852            }
853            self.abs_pos = offset;
854            Ok(offset)
855        }
856
857        fn seek(&mut self, target: Offset, whence: i32, vm: &VirtualMachine) -> PyResult<Offset> {
858            if matches!(whence, 0 | 1) && self.readable() {
859                let current = self.raw_tell_cache(vm)?;
860                let available = self.readahead();
861                if available > 0 {
862                    let offset = if whence == 0 {
863                        target - (current - self.raw_offset())
864                    } else {
865                        target
866                    };
867                    if offset >= -self.pos && offset <= available {
868                        self.pos += offset;
869                        return Ok(current - available + offset);
870                    }
871                }
872            }
873            // raw.get_attr("seek", vm)?.call(args, vm)
874            if self.writable() {
875                self.flush(vm)?;
876            }
877            let target = if whence == 1 {
878                target - self.raw_offset()
879            } else {
880                target
881            };
882            let res = self.raw_seek(target, whence, vm);
883            self.raw_pos = -1;
884            if res.is_ok() && self.readable() {
885                self.reset_read();
886            }
887            res
888        }
889
890        fn raw_tell(&mut self, vm: &VirtualMachine) -> PyResult<Offset> {
891            let raw = self.check_init(vm)?;
892            let ret = vm.call_method(raw, "tell", ())?;
893            let offset = get_offset(ret, vm)?;
894            if offset < 0 {
895                return Err(
896                    vm.new_os_error(format!("Raw stream returned invalid position {offset}"))
897                );
898            }
899            self.abs_pos = offset;
900            Ok(offset)
901        }
902
903        fn raw_tell_cache(&mut self, vm: &VirtualMachine) -> PyResult<Offset> {
904            if self.abs_pos == -1 {
905                self.raw_tell(vm)
906            } else {
907                Ok(self.abs_pos)
908            }
909        }
910
911        /// None means non-blocking failed
912        fn raw_write(
913            &mut self,
914            buf: Option<PyBuffer>,
915            buf_range: Range<usize>,
916            vm: &VirtualMachine,
917        ) -> PyResult<Option<usize>> {
918            let len = buf_range.len();
919            let res = if let Some(buf) = buf {
920                let memobj = PyMemoryView::from_buffer_range(buf, buf_range, vm)?.to_pyobject(vm);
921
922                // TODO: loop if write() raises an interrupt
923                vm.call_method(self.raw.as_ref().unwrap(), "write", (memobj,))?
924            } else {
925                let v = std::mem::take(&mut self.buffer);
926                let writebuf = VecBuffer::from(v).into_ref(&vm.ctx);
927                let memobj = PyMemoryView::from_buffer_range(
928                    writebuf.clone().into_pybuffer(true),
929                    buf_range,
930                    vm,
931                )?
932                .into_ref(&vm.ctx);
933
934                // TODO: loop if write() raises an interrupt
935                let res = vm.call_method(self.raw.as_ref().unwrap(), "write", (memobj.clone(),));
936
937                memobj.release();
938                self.buffer = writebuf.take();
939
940                res?
941            };
942
943            if vm.is_none(&res) {
944                return Ok(None);
945            }
946            let n = isize::try_from_object(vm, res)?;
947            if n < 0 || n as usize > len {
948                return Err(vm.new_os_error(format!(
949                    "raw write() returned invalid length {n} (should have been between 0 and {len})"
950                )));
951            }
952            if self.abs_pos != -1 {
953                self.abs_pos += n as Offset
954            }
955            Ok(Some(n as usize))
956        }
957
958        fn write(&mut self, obj: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> {
959            if !self.valid_read() && !self.valid_write() {
960                self.pos = 0;
961                self.raw_pos = 0;
962            }
963            let avail = self.buffer.len() - self.pos as usize;
964            let buf_len;
965            {
966                let buf = obj.borrow_buf();
967                buf_len = buf.len();
968                if buf.len() <= avail {
969                    self.buffer[self.pos as usize..][..buf.len()].copy_from_slice(&buf);
970                    if !self.valid_write() || self.write_pos > self.pos {
971                        self.write_pos = self.pos
972                    }
973                    self.adjust_position(self.pos + buf.len() as Offset);
974                    if self.pos > self.write_end {
975                        self.write_end = self.pos
976                    }
977                    return Ok(buf.len());
978                }
979            }
980
981            // TODO: something something check if error is BlockingIOError?
982            let _ = self.flush(vm);
983
984            let offset = self.raw_offset();
985            if offset != 0 {
986                self.raw_seek(-offset, 1, vm)?;
987                self.raw_pos -= offset;
988            }
989
990            let mut remaining = buf_len;
991            let mut written = 0;
992            let buffer: PyBuffer = obj.into();
993            while remaining > self.buffer.len() {
994                let res = self.raw_write(Some(buffer.clone()), written..buf_len, vm)?;
995                match res {
996                    Some(n) => {
997                        written += n;
998                        if let Some(r) = remaining.checked_sub(n) {
999                            remaining = r
1000                        } else {
1001                            break;
1002                        }
1003                        vm.check_signals()?;
1004                    }
1005                    None => {
1006                        // raw file is non-blocking
1007                        if remaining > self.buffer.len() {
1008                            // can't buffer everything, buffer what we can and error
1009                            let buf = buffer.as_contiguous().unwrap();
1010                            let buffer_len = self.buffer.len();
1011                            self.buffer.copy_from_slice(&buf[written..][..buffer_len]);
1012                            self.raw_pos = 0;
1013                            let buffer_size = self.buffer.len() as _;
1014                            self.adjust_position(buffer_size);
1015                            self.write_end = buffer_size;
1016                            // TODO: BlockingIOError(errno, msg, written)
1017                            // written += self.buffer.len();
1018                            return Err(vm.new_exception_msg(
1019                                vm.ctx.exceptions.blocking_io_error.to_owned(),
1020                                "write could not complete without blocking".to_owned(),
1021                            ));
1022                        } else {
1023                            break;
1024                        }
1025                    }
1026                }
1027            }
1028            if self.readable() {
1029                self.reset_read();
1030            }
1031            if remaining > 0 {
1032                let buf = buffer.as_contiguous().unwrap();
1033                self.buffer[..remaining].copy_from_slice(&buf[written..][..remaining]);
1034                written += remaining;
1035            }
1036            self.write_pos = 0;
1037            self.write_end = remaining as _;
1038            self.adjust_position(remaining as _);
1039            self.raw_pos = 0;
1040
1041            Ok(written)
1042        }
1043
1044        fn active_read_slice(&self) -> &[u8] {
1045            &self.buffer[self.pos as usize..][..self.readahead() as usize]
1046        }
1047
1048        fn read_fast(&mut self, n: usize) -> Option<Vec<u8>> {
1049            let ret = self.active_read_slice().get(..n)?.to_vec();
1050            self.pos += n as Offset;
1051            Some(ret)
1052        }
1053
1054        fn read_generic(&mut self, n: usize, vm: &VirtualMachine) -> PyResult<Option<Vec<u8>>> {
1055            if let Some(fast) = self.read_fast(n) {
1056                return Ok(Some(fast));
1057            }
1058
1059            let current_size = self.readahead() as usize;
1060
1061            let mut out = vec![0u8; n];
1062            let mut remaining = n;
1063            let mut written = 0;
1064            if current_size > 0 {
1065                let slice = self.active_read_slice();
1066                out[..slice.len()].copy_from_slice(slice);
1067                remaining -= current_size;
1068                written += current_size;
1069                self.pos += current_size as Offset;
1070            }
1071            if self.writable() {
1072                self.flush_rewind(vm)?;
1073            }
1074            self.reset_read();
1075            macro_rules! handle_opt_read {
1076                ($x:expr) => {
1077                    match ($x, written > 0) {
1078                        (Some(0), _) | (None, true) => {
1079                            out.truncate(written);
1080                            return Ok(Some(out));
1081                        }
1082                        (Some(r), _) => r,
1083                        (None, _) => return Ok(None),
1084                    }
1085                };
1086            }
1087            while remaining > 0 {
1088                // MINUS_LAST_BLOCK() in CPython
1089                let r = self.buffer.len() * (remaining / self.buffer.len());
1090                if r == 0 {
1091                    break;
1092                }
1093                let r = self.raw_read(Either::A(Some(&mut out)), written..written + r, vm)?;
1094                let r = handle_opt_read!(r);
1095                remaining -= r;
1096                written += r;
1097            }
1098            self.pos = 0;
1099            self.raw_pos = 0;
1100            self.read_end = 0;
1101
1102            while remaining > 0 && (self.read_end as usize) < self.buffer.len() {
1103                let r = handle_opt_read!(self.fill_buffer(vm)?);
1104                if remaining > r {
1105                    out[written..][..r].copy_from_slice(&self.buffer[self.pos as usize..][..r]);
1106                    written += r;
1107                    self.pos += r as Offset;
1108                    remaining -= r;
1109                } else if remaining > 0 {
1110                    out[written..][..remaining]
1111                        .copy_from_slice(&self.buffer[self.pos as usize..][..remaining]);
1112                    written += remaining;
1113                    self.pos += remaining as Offset;
1114                    remaining = 0;
1115                }
1116                if remaining == 0 {
1117                    break;
1118                }
1119            }
1120
1121            Ok(Some(out))
1122        }
1123
1124        fn fill_buffer(&mut self, vm: &VirtualMachine) -> PyResult<Option<usize>> {
1125            let start = if self.valid_read() {
1126                self.read_end as usize
1127            } else {
1128                0
1129            };
1130            let buf_end = self.buffer.len();
1131            let res = self.raw_read(Either::A(None), start..buf_end, vm)?;
1132            if let Some(n) = res.filter(|n| *n > 0) {
1133                let new_start = (start + n) as Offset;
1134                self.read_end = new_start;
1135                self.raw_pos = new_start;
1136            }
1137            Ok(res)
1138        }
1139
1140        fn raw_read(
1141            &mut self,
1142            v: Either<Option<&mut Vec<u8>>, PyBuffer>,
1143            buf_range: Range<usize>,
1144            vm: &VirtualMachine,
1145        ) -> PyResult<Option<usize>> {
1146            let len = buf_range.len();
1147            let res = match v {
1148                Either::A(v) => {
1149                    let v = v.unwrap_or(&mut self.buffer);
1150                    let readbuf = VecBuffer::from(std::mem::take(v)).into_ref(&vm.ctx);
1151                    let memobj = PyMemoryView::from_buffer_range(
1152                        readbuf.clone().into_pybuffer(false),
1153                        buf_range,
1154                        vm,
1155                    )?
1156                    .into_ref(&vm.ctx);
1157
1158                    // TODO: loop if readinto() raises an interrupt
1159                    let res =
1160                        vm.call_method(self.raw.as_ref().unwrap(), "readinto", (memobj.clone(),));
1161
1162                    memobj.release();
1163                    std::mem::swap(v, &mut readbuf.take());
1164
1165                    res?
1166                }
1167                Either::B(buf) => {
1168                    let memobj = PyMemoryView::from_buffer_range(buf, buf_range, vm)?;
1169                    // TODO: loop if readinto() raises an interrupt
1170                    vm.call_method(self.raw.as_ref().unwrap(), "readinto", (memobj,))?
1171                }
1172            };
1173
1174            if vm.is_none(&res) {
1175                return Ok(None);
1176            }
1177            let n = isize::try_from_object(vm, res)?;
1178            if n < 0 || n as usize > len {
1179                return Err(vm.new_os_error(format!(
1180                    "raw readinto() returned invalid length {n} (should have been between 0 and {len})"
1181                )));
1182            }
1183            if n > 0 && self.abs_pos != -1 {
1184                self.abs_pos += n as Offset
1185            }
1186            Ok(Some(n as usize))
1187        }
1188
1189        fn read_all(&mut self, vm: &VirtualMachine) -> PyResult<Option<PyBytesRef>> {
1190            let buf = self.active_read_slice();
1191            let data = if buf.is_empty() {
1192                None
1193            } else {
1194                let b = buf.to_vec();
1195                self.pos += buf.len() as Offset;
1196                Some(b)
1197            };
1198
1199            if self.writable() {
1200                self.flush_rewind(vm)?;
1201            }
1202
1203            let readall = vm
1204                .get_str_method(self.raw.clone().unwrap(), "readall")
1205                .transpose()?;
1206            if let Some(readall) = readall {
1207                let res = readall.call((), vm)?;
1208                let res = <Option<PyBytesRef>>::try_from_object(vm, res)?;
1209                let ret = if let Some(mut data) = data {
1210                    if let Some(bytes) = res {
1211                        data.extend_from_slice(bytes.as_bytes());
1212                    }
1213                    Some(PyBytes::from(data).into_ref(&vm.ctx))
1214                } else {
1215                    res
1216                };
1217                return Ok(ret);
1218            }
1219
1220            let mut chunks = Vec::new();
1221
1222            let mut read_size = 0;
1223            loop {
1224                let read_data = vm.call_method(self.raw.as_ref().unwrap(), "read", ())?;
1225                let read_data = <Option<PyBytesRef>>::try_from_object(vm, read_data)?;
1226
1227                match read_data {
1228                    Some(b) if !b.as_bytes().is_empty() => {
1229                        let l = b.as_bytes().len();
1230                        read_size += l;
1231                        if self.abs_pos != -1 {
1232                            self.abs_pos += l as Offset;
1233                        }
1234                        chunks.push(b);
1235                    }
1236                    read_data => {
1237                        let ret = if data.is_none() && read_size == 0 {
1238                            read_data
1239                        } else {
1240                            let mut data = data.unwrap_or_default();
1241                            data.reserve(read_size);
1242                            for bytes in &chunks {
1243                                data.extend_from_slice(bytes.as_bytes())
1244                            }
1245                            Some(PyBytes::from(data).into_ref(&vm.ctx))
1246                        };
1247                        break Ok(ret);
1248                    }
1249                }
1250            }
1251        }
1252
1253        fn adjust_position(&mut self, new_pos: Offset) {
1254            self.pos = new_pos;
1255            if self.valid_read() && self.read_end < self.pos {
1256                self.read_end = self.pos
1257            }
1258        }
1259
1260        fn peek(&mut self, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
1261            let have = self.readahead();
1262            let slice = if have > 0 {
1263                &self.buffer[self.pos as usize..][..have as usize]
1264            } else {
1265                self.reset_read();
1266                let r = self.fill_buffer(vm)?.unwrap_or(0);
1267                self.pos = 0;
1268                &self.buffer[..r]
1269            };
1270            Ok(slice.to_vec())
1271        }
1272
1273        fn readinto_generic(
1274            &mut self,
1275            buf: PyBuffer,
1276            readinto1: bool,
1277            vm: &VirtualMachine,
1278        ) -> PyResult<Option<usize>> {
1279            let mut written = 0;
1280            let n = self.readahead();
1281            let buf_len;
1282            {
1283                let mut b = buf.as_contiguous_mut().unwrap();
1284                buf_len = b.len();
1285                if n > 0 {
1286                    if n as usize >= b.len() {
1287                        b.copy_from_slice(&self.buffer[self.pos as usize..][..buf_len]);
1288                        self.pos += buf_len as Offset;
1289                        return Ok(Some(buf_len));
1290                    }
1291                    b[..n as usize]
1292                        .copy_from_slice(&self.buffer[self.pos as usize..][..n as usize]);
1293                    self.pos += n;
1294                    written = n as usize;
1295                }
1296            }
1297            if self.writable() {
1298                self.flush_rewind(vm)?;
1299            }
1300            self.reset_read();
1301            self.pos = 0;
1302
1303            let mut remaining = buf_len - written;
1304            while remaining > 0 {
1305                let n = if remaining > self.buffer.len() {
1306                    self.raw_read(Either::B(buf.clone()), written..written + remaining, vm)?
1307                } else if !(readinto1 && written != 0) {
1308                    let n = self.fill_buffer(vm)?;
1309                    if let Some(n) = n.filter(|&n| n > 0) {
1310                        let n = std::cmp::min(n, remaining);
1311                        buf.as_contiguous_mut().unwrap()[written..][..n]
1312                            .copy_from_slice(&self.buffer[self.pos as usize..][..n]);
1313                        self.pos += n as Offset;
1314                        written += n;
1315                        remaining -= n;
1316                        continue;
1317                    }
1318                    n
1319                } else {
1320                    break;
1321                };
1322                let n = match n {
1323                    Some(0) => break,
1324                    None if written > 0 => break,
1325                    None => return Ok(None),
1326                    Some(n) => n,
1327                };
1328
1329                if readinto1 {
1330                    written += n;
1331                    break;
1332                }
1333                written += n;
1334                remaining -= n;
1335            }
1336
1337            Ok(Some(written))
1338        }
1339    }
1340
1341    pub fn get_offset(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Offset> {
1342        let int = obj.try_index(vm)?;
1343        int.as_bigint().try_into().map_err(|_| {
1344            vm.new_value_error(format!(
1345                "cannot fit '{}' into an offset-sized integer",
1346                obj.class().name()
1347            ))
1348        })
1349    }
1350
1351    pub fn repr_fileobj_name(obj: &PyObject, vm: &VirtualMachine) -> PyResult<Option<PyStrRef>> {
1352        let name = match obj.get_attr("name", vm) {
1353            Ok(name) => Some(name),
1354            Err(e)
1355                if e.fast_isinstance(vm.ctx.exceptions.attribute_error)
1356                    || e.fast_isinstance(vm.ctx.exceptions.value_error) =>
1357            {
1358                None
1359            }
1360            Err(e) => return Err(e),
1361        };
1362        match name {
1363            Some(name) => {
1364                if let Some(_guard) = ReprGuard::enter(vm, obj) {
1365                    name.repr(vm).map(Some)
1366                } else {
1367                    Err(vm.new_runtime_error(format!(
1368                        "reentrant call inside {}.__repr__",
1369                        obj.class().slot_name()
1370                    )))
1371                }
1372            }
1373            None => Ok(None),
1374        }
1375    }
1376
1377    #[pyclass]
1378    trait BufferedMixin: PyPayload {
1379        const CLASS_NAME: &'static str;
1380        const READABLE: bool;
1381        const WRITABLE: bool;
1382        const SEEKABLE: bool = false;
1383        fn data(&self) -> &PyThreadMutex<BufferedData>;
1384        fn lock(&self, vm: &VirtualMachine) -> PyResult<PyThreadMutexGuard<BufferedData>> {
1385            self.data()
1386                .lock()
1387                .ok_or_else(|| vm.new_runtime_error("reentrant call inside buffered io".to_owned()))
1388        }
1389
1390        #[pyslot]
1391        fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
1392            let zelf: PyRef<Self> = zelf.try_into_value(vm)?;
1393            zelf.__init__(args, vm)
1394        }
1395
1396        #[pymethod]
1397        fn __init__(&self, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
1398            let (raw, BufferSize { buffer_size }): (PyObjectRef, _) =
1399                args.bind(vm).map_err(|e| {
1400                    let msg = format!("{}() {}", Self::CLASS_NAME, *e.str(vm));
1401                    vm.new_exception_msg(e.class().to_owned(), msg)
1402                })?;
1403            self.init(raw, BufferSize { buffer_size }, vm)
1404        }
1405
1406        fn init(
1407            &self,
1408            raw: PyObjectRef,
1409            BufferSize { buffer_size }: BufferSize,
1410            vm: &VirtualMachine,
1411        ) -> PyResult<()> {
1412            let mut data = self.lock(vm)?;
1413            data.raw = None;
1414            data.flags.remove(BufferedFlags::DETACHED);
1415
1416            let buffer_size = match buffer_size {
1417                OptionalArg::Present(i) if i <= 0 => {
1418                    return Err(
1419                        vm.new_value_error("buffer size must be strictly positive".to_owned())
1420                    );
1421                }
1422                OptionalArg::Present(i) => i as usize,
1423                OptionalArg::Missing => DEFAULT_BUFFER_SIZE,
1424            };
1425
1426            if Self::SEEKABLE {
1427                check_seekable(&raw, vm)?;
1428            }
1429            if Self::READABLE {
1430                data.flags.insert(BufferedFlags::READABLE);
1431                check_readable(&raw, vm)?;
1432            }
1433            if Self::WRITABLE {
1434                data.flags.insert(BufferedFlags::WRITABLE);
1435                check_writable(&raw, vm)?;
1436            }
1437
1438            data.buffer = vec![0; buffer_size];
1439
1440            if Self::READABLE {
1441                data.reset_read();
1442            }
1443            if Self::WRITABLE {
1444                data.reset_write();
1445            }
1446            if Self::SEEKABLE {
1447                data.pos = 0;
1448            }
1449
1450            data.raw = Some(raw);
1451
1452            Ok(())
1453        }
1454        #[pymethod]
1455        fn seek(
1456            &self,
1457            target: PyObjectRef,
1458            whence: OptionalArg<i32>,
1459            vm: &VirtualMachine,
1460        ) -> PyResult<Offset> {
1461            let whence = whence.unwrap_or(0);
1462            if !validate_whence(whence) {
1463                return Err(vm.new_value_error(format!("whence value {whence} unsupported")));
1464            }
1465            let mut data = self.lock(vm)?;
1466            let raw = data.check_init(vm)?;
1467            ensure_unclosed(raw, "seek of closed file", vm)?;
1468            check_seekable(raw, vm)?;
1469            let target = get_offset(target, vm)?;
1470            data.seek(target, whence, vm)
1471        }
1472        #[pymethod]
1473        fn tell(&self, vm: &VirtualMachine) -> PyResult<Offset> {
1474            let mut data = self.lock(vm)?;
1475            let raw_tell = data.raw_tell(vm)?;
1476            let raw_offset = data.raw_offset();
1477            let mut pos = raw_tell - raw_offset;
1478            // GH-95782
1479            if pos < 0 {
1480                pos = 0;
1481            }
1482            Ok(pos)
1483        }
1484        #[pymethod]
1485        fn truncate(
1486            zelf: PyRef<Self>,
1487            pos: OptionalOption<PyObjectRef>,
1488            vm: &VirtualMachine,
1489        ) -> PyResult {
1490            let pos = pos.flatten().to_pyobject(vm);
1491            let mut data = zelf.lock(vm)?;
1492            data.check_init(vm)?;
1493            if data.writable() {
1494                data.flush_rewind(vm)?;
1495            }
1496            let res = vm.call_method(data.raw.as_ref().unwrap(), "truncate", (pos,))?;
1497            let _ = data.raw_tell(vm);
1498            Ok(res)
1499        }
1500        #[pymethod]
1501        fn detach(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
1502            vm.call_method(zelf.as_object(), "flush", ())?;
1503            let mut data = zelf.lock(vm)?;
1504            data.flags.insert(BufferedFlags::DETACHED);
1505            data.raw
1506                .take()
1507                .ok_or_else(|| vm.new_value_error("raw stream has been detached".to_owned()))
1508        }
1509        #[pymethod]
1510        fn seekable(&self, vm: &VirtualMachine) -> PyResult {
1511            vm.call_method(self.lock(vm)?.check_init(vm)?, "seekable", ())
1512        }
1513        #[pygetset]
1514        fn raw(&self, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
1515            Ok(self.lock(vm)?.raw.clone())
1516        }
1517        #[pygetset]
1518        fn closed(&self, vm: &VirtualMachine) -> PyResult {
1519            self.lock(vm)?.check_init(vm)?.get_attr("closed", vm)
1520        }
1521        #[pygetset]
1522        fn name(&self, vm: &VirtualMachine) -> PyResult {
1523            self.lock(vm)?.check_init(vm)?.get_attr("name", vm)
1524        }
1525        #[pygetset]
1526        fn mode(&self, vm: &VirtualMachine) -> PyResult {
1527            self.lock(vm)?.check_init(vm)?.get_attr("mode", vm)
1528        }
1529        #[pymethod]
1530        fn fileno(&self, vm: &VirtualMachine) -> PyResult {
1531            vm.call_method(self.lock(vm)?.check_init(vm)?, "fileno", ())
1532        }
1533        #[pymethod]
1534        fn isatty(&self, vm: &VirtualMachine) -> PyResult {
1535            vm.call_method(self.lock(vm)?.check_init(vm)?, "isatty", ())
1536        }
1537
1538        #[pyslot]
1539        fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
1540            let name_repr = repr_fileobj_name(zelf, vm)?;
1541            let cls = zelf.class();
1542            let slot_name = cls.slot_name();
1543            let repr = if let Some(name_repr) = name_repr {
1544                format!("<{slot_name} name={name_repr}>")
1545            } else {
1546                format!("<{slot_name}>")
1547            };
1548            Ok(vm.ctx.new_str(repr))
1549        }
1550
1551        #[pymethod(magic)]
1552        fn repr(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
1553            Self::slot_repr(&zelf, vm)
1554        }
1555
1556        fn close_strict(&self, vm: &VirtualMachine) -> PyResult {
1557            let mut data = self.lock(vm)?;
1558            let raw = data.check_init(vm)?;
1559            if file_closed(raw, vm)? {
1560                return Ok(vm.ctx.none());
1561            }
1562            let flush_res = data.flush(vm);
1563            let close_res = vm.call_method(data.raw.as_ref().unwrap(), "close", ());
1564            exception_chain(flush_res, close_res)
1565        }
1566
1567        #[pymethod]
1568        fn close(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
1569            {
1570                let data = zelf.lock(vm)?;
1571                let raw = data.check_init(vm)?;
1572                if file_closed(raw, vm)? {
1573                    return Ok(vm.ctx.none());
1574                }
1575            }
1576            let flush_res = vm.call_method(zelf.as_object(), "flush", ()).map(drop);
1577            let data = zelf.lock(vm)?;
1578            let raw = data.raw.as_ref().unwrap();
1579            let close_res = vm.call_method(raw, "close", ());
1580            exception_chain(flush_res, close_res)
1581        }
1582
1583        #[pymethod]
1584        fn readable(&self) -> bool {
1585            Self::READABLE
1586        }
1587        #[pymethod]
1588        fn writable(&self) -> bool {
1589            Self::WRITABLE
1590        }
1591
1592        // TODO: this should be the default for an equivalent of _PyObject_GetState
1593        #[pymethod(magic)]
1594        fn reduce(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
1595            Err(vm.new_type_error(format!("cannot pickle '{}' object", zelf.class().name())))
1596        }
1597    }
1598
1599    #[pyclass]
1600    trait BufferedReadable: PyPayload {
1601        type Reader: BufferedMixin;
1602        fn reader(&self) -> &Self::Reader;
1603        #[pymethod]
1604        fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Option<PyBytesRef>> {
1605            let mut data = self.reader().lock(vm)?;
1606            let raw = data.check_init(vm)?;
1607            let n = size.size.map(|s| *s).unwrap_or(-1);
1608            if n < -1 {
1609                return Err(vm.new_value_error("read length must be non-negative or -1".to_owned()));
1610            }
1611            ensure_unclosed(raw, "read of closed file", vm)?;
1612            match n.to_usize() {
1613                Some(n) => data
1614                    .read_generic(n, vm)
1615                    .map(|x| x.map(|b| PyBytes::from(b).into_ref(&vm.ctx))),
1616                None => data.read_all(vm),
1617            }
1618        }
1619        #[pymethod]
1620        fn peek(&self, _size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
1621            let mut data = self.reader().lock(vm)?;
1622            let raw = data.check_init(vm)?;
1623            ensure_unclosed(raw, "peek of closed file", vm)?;
1624
1625            if data.writable() {
1626                let _ = data.flush_rewind(vm);
1627            }
1628            data.peek(vm)
1629        }
1630        #[pymethod]
1631        fn read1(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
1632            let mut data = self.reader().lock(vm)?;
1633            let raw = data.check_init(vm)?;
1634            ensure_unclosed(raw, "read of closed file", vm)?;
1635            let n = size.to_usize().unwrap_or(data.buffer.len());
1636            if n == 0 {
1637                return Ok(Vec::new());
1638            }
1639            let have = data.readahead();
1640            if have > 0 {
1641                let n = std::cmp::min(have as usize, n);
1642                return Ok(data.read_fast(n).unwrap());
1643            }
1644            let mut v = vec![0; n];
1645            data.reset_read();
1646            let r = data
1647                .raw_read(Either::A(Some(&mut v)), 0..n, vm)?
1648                .unwrap_or(0);
1649            v.truncate(r);
1650            v.shrink_to_fit();
1651            Ok(v)
1652        }
1653        #[pymethod]
1654        fn readinto(&self, buf: ArgMemoryBuffer, vm: &VirtualMachine) -> PyResult<Option<usize>> {
1655            let mut data = self.reader().lock(vm)?;
1656            let raw = data.check_init(vm)?;
1657            ensure_unclosed(raw, "readinto of closed file", vm)?;
1658            data.readinto_generic(buf.into(), false, vm)
1659        }
1660        #[pymethod]
1661        fn readinto1(&self, buf: ArgMemoryBuffer, vm: &VirtualMachine) -> PyResult<Option<usize>> {
1662            let mut data = self.reader().lock(vm)?;
1663            let raw = data.check_init(vm)?;
1664            ensure_unclosed(raw, "readinto of closed file", vm)?;
1665            data.readinto_generic(buf.into(), true, vm)
1666        }
1667    }
1668
1669    fn exception_chain<T>(e1: PyResult<()>, e2: PyResult<T>) -> PyResult<T> {
1670        match (e1, e2) {
1671            (Err(e1), Err(e)) => {
1672                e.set_context(Some(e1));
1673                Err(e)
1674            }
1675            (Err(e), Ok(_)) | (Ok(()), Err(e)) => Err(e),
1676            (Ok(()), Ok(close_res)) => Ok(close_res),
1677        }
1678    }
1679
1680    #[pyattr]
1681    #[pyclass(name = "BufferedReader", base = "_BufferedIOBase")]
1682    #[derive(Debug, Default, PyPayload)]
1683    struct BufferedReader {
1684        data: PyThreadMutex<BufferedData>,
1685    }
1686    impl BufferedMixin for BufferedReader {
1687        const CLASS_NAME: &'static str = "BufferedReader";
1688        const READABLE: bool = true;
1689        const WRITABLE: bool = false;
1690        fn data(&self) -> &PyThreadMutex<BufferedData> {
1691            &self.data
1692        }
1693    }
1694    impl BufferedReadable for BufferedReader {
1695        type Reader = Self;
1696        fn reader(&self) -> &Self::Reader {
1697            self
1698        }
1699    }
1700
1701    #[pyclass(
1702        with(Constructor, BufferedMixin, BufferedReadable),
1703        flags(BASETYPE, HAS_DICT)
1704    )]
1705    impl BufferedReader {}
1706
1707    impl DefaultConstructor for BufferedReader {}
1708
1709    #[pyclass]
1710    trait BufferedWritable: PyPayload {
1711        type Writer: BufferedMixin;
1712        fn writer(&self) -> &Self::Writer;
1713        #[pymethod]
1714        fn write(&self, obj: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> {
1715            let mut data = self.writer().lock(vm)?;
1716            let raw = data.check_init(vm)?;
1717            ensure_unclosed(raw, "write to closed file", vm)?;
1718
1719            data.write(obj, vm)
1720        }
1721        #[pymethod]
1722        fn flush(&self, vm: &VirtualMachine) -> PyResult<()> {
1723            let mut data = self.writer().lock(vm)?;
1724            let raw = data.check_init(vm)?;
1725            ensure_unclosed(raw, "flush of closed file", vm)?;
1726            data.flush_rewind(vm)
1727        }
1728    }
1729
1730    #[pyattr]
1731    #[pyclass(name = "BufferedWriter", base = "_BufferedIOBase")]
1732    #[derive(Debug, Default, PyPayload)]
1733    struct BufferedWriter {
1734        data: PyThreadMutex<BufferedData>,
1735    }
1736    impl BufferedMixin for BufferedWriter {
1737        const CLASS_NAME: &'static str = "BufferedWriter";
1738        const READABLE: bool = false;
1739        const WRITABLE: bool = true;
1740        fn data(&self) -> &PyThreadMutex<BufferedData> {
1741            &self.data
1742        }
1743    }
1744    impl BufferedWritable for BufferedWriter {
1745        type Writer = Self;
1746        fn writer(&self) -> &Self::Writer {
1747            self
1748        }
1749    }
1750
1751    #[pyclass(
1752        with(Constructor, BufferedMixin, BufferedWritable),
1753        flags(BASETYPE, HAS_DICT)
1754    )]
1755    impl BufferedWriter {}
1756
1757    impl DefaultConstructor for BufferedWriter {}
1758
1759    #[pyattr]
1760    #[pyclass(name = "BufferedRandom", base = "_BufferedIOBase")]
1761    #[derive(Debug, Default, PyPayload)]
1762    struct BufferedRandom {
1763        data: PyThreadMutex<BufferedData>,
1764    }
1765    impl BufferedMixin for BufferedRandom {
1766        const CLASS_NAME: &'static str = "BufferedRandom";
1767        const READABLE: bool = true;
1768        const WRITABLE: bool = true;
1769        const SEEKABLE: bool = true;
1770        fn data(&self) -> &PyThreadMutex<BufferedData> {
1771            &self.data
1772        }
1773    }
1774    impl BufferedReadable for BufferedRandom {
1775        type Reader = Self;
1776        fn reader(&self) -> &Self::Reader {
1777            self
1778        }
1779    }
1780    impl BufferedWritable for BufferedRandom {
1781        type Writer = Self;
1782        fn writer(&self) -> &Self::Writer {
1783            self
1784        }
1785    }
1786
1787    #[pyclass(
1788        with(Constructor, BufferedMixin, BufferedReadable, BufferedWritable),
1789        flags(BASETYPE, HAS_DICT)
1790    )]
1791    impl BufferedRandom {}
1792
1793    impl DefaultConstructor for BufferedRandom {}
1794
1795    #[pyattr]
1796    #[pyclass(name = "BufferedRWPair", base = "_BufferedIOBase")]
1797    #[derive(Debug, Default, PyPayload)]
1798    struct BufferedRWPair {
1799        read: BufferedReader,
1800        write: BufferedWriter,
1801    }
1802    impl BufferedReadable for BufferedRWPair {
1803        type Reader = BufferedReader;
1804        fn reader(&self) -> &Self::Reader {
1805            &self.read
1806        }
1807    }
1808    impl BufferedWritable for BufferedRWPair {
1809        type Writer = BufferedWriter;
1810        fn writer(&self) -> &Self::Writer {
1811            &self.write
1812        }
1813    }
1814
1815    impl DefaultConstructor for BufferedRWPair {}
1816
1817    impl Initializer for BufferedRWPair {
1818        type Args = (PyObjectRef, PyObjectRef, BufferSize);
1819
1820        fn init(
1821            zelf: PyRef<Self>,
1822            (reader, writer, buffer_size): Self::Args,
1823            vm: &VirtualMachine,
1824        ) -> PyResult<()> {
1825            zelf.read.init(reader, buffer_size.clone(), vm)?;
1826            zelf.write.init(writer, buffer_size, vm)?;
1827            Ok(())
1828        }
1829    }
1830
1831    #[pyclass(
1832        with(Constructor, Initializer, BufferedReadable, BufferedWritable),
1833        flags(BASETYPE, HAS_DICT)
1834    )]
1835    impl BufferedRWPair {
1836        #[pymethod]
1837        fn flush(&self, vm: &VirtualMachine) -> PyResult<()> {
1838            self.write.flush(vm)
1839        }
1840
1841        #[pymethod]
1842        fn readable(&self) -> bool {
1843            true
1844        }
1845        #[pymethod]
1846        fn writable(&self) -> bool {
1847            true
1848        }
1849
1850        #[pygetset]
1851        fn closed(&self, vm: &VirtualMachine) -> PyResult {
1852            self.write.closed(vm)
1853        }
1854
1855        #[pymethod]
1856        fn isatty(&self, vm: &VirtualMachine) -> PyResult {
1857            // read.isatty() or write.isatty()
1858            let res = self.read.isatty(vm)?;
1859            if res.clone().try_to_bool(vm)? {
1860                Ok(res)
1861            } else {
1862                self.write.isatty(vm)
1863            }
1864        }
1865
1866        #[pymethod]
1867        fn close(&self, vm: &VirtualMachine) -> PyResult {
1868            let write_res = self.write.close_strict(vm).map(drop);
1869            let read_res = self.read.close_strict(vm);
1870            exception_chain(write_res, read_res)
1871        }
1872    }
1873
1874    #[derive(FromArgs)]
1875    struct TextIOWrapperArgs {
1876        #[pyarg(any, default)]
1877        encoding: Option<PyStrRef>,
1878        #[pyarg(any, default)]
1879        errors: Option<PyStrRef>,
1880        #[pyarg(any, default)]
1881        newline: Option<Newlines>,
1882        #[pyarg(any, default)]
1883        line_buffering: Option<bool>,
1884        #[pyarg(any, default)]
1885        write_through: Option<bool>,
1886    }
1887
1888    #[derive(Debug, Copy, Clone, Default)]
1889    enum Newlines {
1890        #[default]
1891        Universal,
1892        Passthrough,
1893        Lf,
1894        Cr,
1895        Crlf,
1896    }
1897
1898    impl Newlines {
1899        /// returns position where the new line starts if found, otherwise position at which to
1900        /// continue the search after more is read into the buffer
1901        fn find_newline(&self, s: &str) -> Result<usize, usize> {
1902            let len = s.len();
1903            match self {
1904                Newlines::Universal | Newlines::Lf => s.find('\n').map(|p| p + 1).ok_or(len),
1905                Newlines::Passthrough => {
1906                    let bytes = s.as_bytes();
1907                    memchr::memchr2(b'\n', b'\r', bytes)
1908                        .map(|p| {
1909                            let nl_len =
1910                                if bytes[p] == b'\r' && bytes.get(p + 1).copied() == Some(b'\n') {
1911                                    2
1912                                } else {
1913                                    1
1914                                };
1915                            p + nl_len
1916                        })
1917                        .ok_or(len)
1918                }
1919                Newlines::Cr => s.find('\n').map(|p| p + 1).ok_or(len),
1920                Newlines::Crlf => {
1921                    // s[searched..] == remaining
1922                    let mut searched = 0;
1923                    let mut remaining = s.as_bytes();
1924                    loop {
1925                        match memchr::memchr(b'\r', remaining) {
1926                            Some(p) => match remaining.get(p + 1) {
1927                                Some(&ch_after_cr) => {
1928                                    let pos_after = p + 2;
1929                                    if ch_after_cr == b'\n' {
1930                                        break Ok(searched + pos_after);
1931                                    } else {
1932                                        searched += pos_after;
1933                                        remaining = &remaining[pos_after..];
1934                                        continue;
1935                                    }
1936                                }
1937                                None => break Err(searched + p),
1938                            },
1939                            None => break Err(len),
1940                        }
1941                    }
1942                }
1943            }
1944        }
1945    }
1946
1947    impl TryFromObject for Newlines {
1948        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
1949            let nl = if vm.is_none(&obj) {
1950                Self::Universal
1951            } else {
1952                let s = obj.downcast::<PyStr>().map_err(|obj| {
1953                    vm.new_type_error(format!(
1954                        "newline argument must be str or None, not {}",
1955                        obj.class().name()
1956                    ))
1957                })?;
1958                match s.as_str() {
1959                    "" => Self::Passthrough,
1960                    "\n" => Self::Lf,
1961                    "\r" => Self::Cr,
1962                    "\r\n" => Self::Crlf,
1963                    _ => return Err(vm.new_value_error(format!("illegal newline value: {s}"))),
1964                }
1965            };
1966            Ok(nl)
1967        }
1968    }
1969
1970    /// A length of or index into a UTF-8 string, measured in both chars and bytes
1971    #[derive(Debug, Default, Copy, Clone)]
1972    struct Utf8size {
1973        bytes: usize,
1974        chars: usize,
1975    }
1976    impl Utf8size {
1977        fn len_pystr(s: &PyStr) -> Self {
1978            Utf8size {
1979                bytes: s.byte_len(),
1980                chars: s.char_len(),
1981            }
1982        }
1983
1984        fn len_str(s: &str) -> Self {
1985            Utf8size {
1986                bytes: s.len(),
1987                chars: s.chars().count(),
1988            }
1989        }
1990    }
1991    impl std::ops::Add for Utf8size {
1992        type Output = Self;
1993        #[inline]
1994        fn add(mut self, rhs: Self) -> Self {
1995            self += rhs;
1996            self
1997        }
1998    }
1999    impl std::ops::AddAssign for Utf8size {
2000        #[inline]
2001        fn add_assign(&mut self, rhs: Self) {
2002            self.bytes += rhs.bytes;
2003            self.chars += rhs.chars;
2004        }
2005    }
2006    impl std::ops::Sub for Utf8size {
2007        type Output = Self;
2008        #[inline]
2009        fn sub(mut self, rhs: Self) -> Self {
2010            self -= rhs;
2011            self
2012        }
2013    }
2014    impl std::ops::SubAssign for Utf8size {
2015        #[inline]
2016        fn sub_assign(&mut self, rhs: Self) {
2017            self.bytes -= rhs.bytes;
2018            self.chars -= rhs.chars;
2019        }
2020    }
2021
2022    // TODO: implement legit fast-paths for other encodings
2023    type EncodeFunc = fn(PyStrRef) -> PendingWrite;
2024    fn textio_encode_utf8(s: PyStrRef) -> PendingWrite {
2025        PendingWrite::Utf8(s)
2026    }
2027
2028    #[derive(Debug)]
2029    struct TextIOData {
2030        buffer: PyObjectRef,
2031        encoder: Option<(PyObjectRef, Option<EncodeFunc>)>,
2032        decoder: Option<PyObjectRef>,
2033        encoding: PyStrRef,
2034        errors: PyStrRef,
2035        newline: Newlines,
2036        line_buffering: bool,
2037        write_through: bool,
2038        chunk_size: usize,
2039        seekable: bool,
2040        has_read1: bool,
2041        // these are more state than configuration
2042        pending: PendingWrites,
2043        telling: bool,
2044        snapshot: Option<(i32, PyBytesRef)>,
2045        decoded_chars: Option<PyStrRef>,
2046        // number of characters we've consumed from decoded_chars
2047        decoded_chars_used: Utf8size,
2048        b2cratio: f64,
2049    }
2050
2051    #[derive(Debug, Default)]
2052    struct PendingWrites {
2053        num_bytes: usize,
2054        data: PendingWritesData,
2055    }
2056
2057    #[derive(Debug, Default)]
2058    enum PendingWritesData {
2059        #[default]
2060        None,
2061        One(PendingWrite),
2062        Many(Vec<PendingWrite>),
2063    }
2064
2065    #[derive(Debug)]
2066    enum PendingWrite {
2067        Utf8(PyStrRef),
2068        Bytes(PyBytesRef),
2069    }
2070
2071    impl PendingWrite {
2072        fn as_bytes(&self) -> &[u8] {
2073            match self {
2074                Self::Utf8(s) => s.as_str().as_bytes(),
2075                Self::Bytes(b) => b.as_bytes(),
2076            }
2077        }
2078    }
2079
2080    impl PendingWrites {
2081        fn push(&mut self, write: PendingWrite) {
2082            self.num_bytes += write.as_bytes().len();
2083            self.data = match std::mem::take(&mut self.data) {
2084                PendingWritesData::None => PendingWritesData::One(write),
2085                PendingWritesData::One(write1) => PendingWritesData::Many(vec![write1, write]),
2086                PendingWritesData::Many(mut v) => {
2087                    v.push(write);
2088                    PendingWritesData::Many(v)
2089                }
2090            }
2091        }
2092        fn take(&mut self, vm: &VirtualMachine) -> PyBytesRef {
2093            let PendingWrites { num_bytes, data } = std::mem::take(self);
2094            if let PendingWritesData::One(PendingWrite::Bytes(b)) = data {
2095                return b;
2096            }
2097            let writes_iter = match data {
2098                PendingWritesData::None => itertools::Either::Left(vec![].into_iter()),
2099                PendingWritesData::One(write) => itertools::Either::Right(std::iter::once(write)),
2100                PendingWritesData::Many(writes) => itertools::Either::Left(writes.into_iter()),
2101            };
2102            let mut buf = Vec::with_capacity(num_bytes);
2103            writes_iter.for_each(|chunk| buf.extend_from_slice(chunk.as_bytes()));
2104            PyBytes::from(buf).into_ref(&vm.ctx)
2105        }
2106    }
2107
2108    #[derive(Default, Debug)]
2109    struct TextIOCookie {
2110        start_pos: Offset,
2111        dec_flags: i32,
2112        bytes_to_feed: i32,
2113        chars_to_skip: i32,
2114        need_eof: bool,
2115        // chars_to_skip but utf8 bytes
2116        bytes_to_skip: i32,
2117    }
2118
2119    impl TextIOCookie {
2120        const START_POS_OFF: usize = 0;
2121        const DEC_FLAGS_OFF: usize = Self::START_POS_OFF + std::mem::size_of::<Offset>();
2122        const BYTES_TO_FEED_OFF: usize = Self::DEC_FLAGS_OFF + 4;
2123        const CHARS_TO_SKIP_OFF: usize = Self::BYTES_TO_FEED_OFF + 4;
2124        const NEED_EOF_OFF: usize = Self::CHARS_TO_SKIP_OFF + 4;
2125        const BYTES_TO_SKIP_OFF: usize = Self::NEED_EOF_OFF + 1;
2126        const BYTE_LEN: usize = Self::BYTES_TO_SKIP_OFF + 4;
2127        fn parse(cookie: &BigInt) -> Option<Self> {
2128            let (_, mut buf) = cookie.to_bytes_le();
2129            if buf.len() > Self::BYTE_LEN {
2130                return None;
2131            }
2132            buf.resize(Self::BYTE_LEN, 0);
2133            let buf: &[u8; Self::BYTE_LEN] = buf.as_slice().try_into().unwrap();
2134            macro_rules! get_field {
2135                ($t:ty, $off:ident) => {{
2136                    <$t>::from_ne_bytes(
2137                        buf[Self::$off..][..std::mem::size_of::<$t>()]
2138                            .try_into()
2139                            .unwrap(),
2140                    )
2141                }};
2142            }
2143            Some(TextIOCookie {
2144                start_pos: get_field!(Offset, START_POS_OFF),
2145                dec_flags: get_field!(i32, DEC_FLAGS_OFF),
2146                bytes_to_feed: get_field!(i32, BYTES_TO_FEED_OFF),
2147                chars_to_skip: get_field!(i32, CHARS_TO_SKIP_OFF),
2148                need_eof: get_field!(u8, NEED_EOF_OFF) != 0,
2149                bytes_to_skip: get_field!(i32, BYTES_TO_SKIP_OFF),
2150            })
2151        }
2152        fn build(&self) -> BigInt {
2153            let mut buf = [0; Self::BYTE_LEN];
2154            macro_rules! set_field {
2155                ($field:expr, $off:ident) => {{
2156                    let field = $field;
2157                    buf[Self::$off..][..std::mem::size_of_val(&field)]
2158                        .copy_from_slice(&field.to_ne_bytes())
2159                }};
2160            }
2161            set_field!(self.start_pos, START_POS_OFF);
2162            set_field!(self.dec_flags, DEC_FLAGS_OFF);
2163            set_field!(self.bytes_to_feed, BYTES_TO_FEED_OFF);
2164            set_field!(self.chars_to_skip, CHARS_TO_SKIP_OFF);
2165            set_field!(self.need_eof as u8, NEED_EOF_OFF);
2166            set_field!(self.bytes_to_skip, BYTES_TO_SKIP_OFF);
2167            BigUint::from_bytes_le(&buf).into()
2168        }
2169        fn set_decoder_state(&self, decoder: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
2170            if self.start_pos == 0 && self.dec_flags == 0 {
2171                vm.call_method(decoder, "reset", ())?;
2172            } else {
2173                vm.call_method(
2174                    decoder,
2175                    "setstate",
2176                    ((vm.ctx.new_bytes(vec![]), self.dec_flags),),
2177                )?;
2178            }
2179            Ok(())
2180        }
2181        fn num_to_skip(&self) -> Utf8size {
2182            Utf8size {
2183                bytes: self.bytes_to_skip as usize,
2184                chars: self.chars_to_skip as usize,
2185            }
2186        }
2187        fn set_num_to_skip(&mut self, num: Utf8size) {
2188            self.bytes_to_skip = num.bytes as i32;
2189            self.chars_to_skip = num.chars as i32;
2190        }
2191    }
2192
2193    #[pyattr]
2194    #[pyclass(name = "TextIOWrapper", base = "_TextIOBase")]
2195    #[derive(Debug, Default, PyPayload)]
2196    struct TextIOWrapper {
2197        data: PyThreadMutex<Option<TextIOData>>,
2198    }
2199
2200    impl DefaultConstructor for TextIOWrapper {}
2201
2202    impl Initializer for TextIOWrapper {
2203        type Args = (PyObjectRef, TextIOWrapperArgs);
2204
2205        fn init(
2206            zelf: PyRef<Self>,
2207            (buffer, args): Self::Args,
2208            vm: &VirtualMachine,
2209        ) -> PyResult<()> {
2210            let mut data = zelf.lock_opt(vm)?;
2211            *data = None;
2212
2213            let encoding = match args.encoding {
2214                None if vm.state.settings.utf8_mode > 0 => PyStr::from("utf-8").into_ref(&vm.ctx),
2215                Some(enc) if enc.as_str() != "locale" => enc,
2216                _ => {
2217                    // None without utf8_mode or "locale" encoding
2218                    vm.import("locale", 0)?
2219                        .get_attr("getencoding", vm)?
2220                        .call((), vm)?
2221                        .try_into_value(vm)?
2222                }
2223            };
2224
2225            let errors = args
2226                .errors
2227                .unwrap_or_else(|| PyStr::from("strict").into_ref(&vm.ctx));
2228
2229            let has_read1 = vm.get_attribute_opt(buffer.clone(), "read1")?.is_some();
2230            let seekable = vm.call_method(&buffer, "seekable", ())?.try_to_bool(vm)?;
2231
2232            let (encoder, decoder) = Self::find_coder(&buffer, encoding.as_str(), &errors, vm)?;
2233
2234            *data = Some(TextIOData {
2235                buffer,
2236                encoder,
2237                decoder,
2238                encoding,
2239                errors,
2240                newline: args.newline.unwrap_or_default(),
2241                line_buffering: args.line_buffering.unwrap_or_default(),
2242                write_through: args.write_through.unwrap_or_default(),
2243                chunk_size: 8192,
2244                seekable,
2245                has_read1,
2246
2247                pending: PendingWrites::default(),
2248                telling: seekable,
2249                snapshot: None,
2250                decoded_chars: None,
2251                decoded_chars_used: Utf8size::default(),
2252                b2cratio: 0.0,
2253            });
2254
2255            Ok(())
2256        }
2257    }
2258
2259    impl TextIOWrapper {
2260        fn lock_opt(
2261            &self,
2262            vm: &VirtualMachine,
2263        ) -> PyResult<PyThreadMutexGuard<Option<TextIOData>>> {
2264            self.data
2265                .lock()
2266                .ok_or_else(|| vm.new_runtime_error("reentrant call inside textio".to_owned()))
2267        }
2268
2269        fn lock(&self, vm: &VirtualMachine) -> PyResult<PyMappedThreadMutexGuard<TextIOData>> {
2270            let lock = self.lock_opt(vm)?;
2271            PyThreadMutexGuard::try_map(lock, |x| x.as_mut())
2272                .map_err(|_| vm.new_value_error("I/O operation on uninitialized object".to_owned()))
2273        }
2274
2275        #[allow(clippy::type_complexity)]
2276        fn find_coder(
2277            buffer: &PyObject,
2278            encoding: &str,
2279            errors: &Py<PyStr>,
2280            vm: &VirtualMachine,
2281        ) -> PyResult<(
2282            Option<(PyObjectRef, Option<EncodeFunc>)>,
2283            Option<PyObjectRef>,
2284        )> {
2285            let codec = vm.state.codec_registry.lookup(encoding, vm)?;
2286
2287            let encoder = if vm.call_method(buffer, "writable", ())?.try_to_bool(vm)? {
2288                let incremental_encoder =
2289                    codec.get_incremental_encoder(Some(errors.to_owned()), vm)?;
2290                let encoding_name = vm.get_attribute_opt(incremental_encoder.clone(), "name")?;
2291                let encodefunc = encoding_name.and_then(|name| {
2292                    let name = name.payload::<PyStr>()?;
2293                    match name.as_str() {
2294                        "utf-8" => Some(textio_encode_utf8 as EncodeFunc),
2295                        _ => None,
2296                    }
2297                });
2298                Some((incremental_encoder, encodefunc))
2299            } else {
2300                None
2301            };
2302
2303            let decoder = if vm.call_method(buffer, "readable", ())?.try_to_bool(vm)? {
2304                let incremental_decoder =
2305                    codec.get_incremental_decoder(Some(errors.to_owned()), vm)?;
2306                // TODO: wrap in IncrementalNewlineDecoder if newlines == Universal | Passthrough
2307                Some(incremental_decoder)
2308            } else {
2309                None
2310            };
2311            Ok((encoder, decoder))
2312        }
2313    }
2314
2315    #[pyclass(with(Constructor, Initializer), flags(BASETYPE))]
2316    impl TextIOWrapper {
2317        #[pymethod]
2318        fn reconfigure(&self, args: TextIOWrapperArgs, vm: &VirtualMachine) -> PyResult<()> {
2319            let mut data = self.data.lock().unwrap();
2320            if let Some(data) = data.as_mut() {
2321                if let Some(encoding) = args.encoding {
2322                    let (encoder, decoder) =
2323                        Self::find_coder(&data.buffer, encoding.as_str(), &data.errors, vm)?;
2324                    data.encoding = encoding;
2325                    data.encoder = encoder;
2326                    data.decoder = decoder;
2327                }
2328                if let Some(errors) = args.errors {
2329                    data.errors = errors;
2330                }
2331                if let Some(newline) = args.newline {
2332                    data.newline = newline;
2333                }
2334                if let Some(line_buffering) = args.line_buffering {
2335                    data.line_buffering = line_buffering;
2336                }
2337                if let Some(write_through) = args.write_through {
2338                    data.write_through = write_through;
2339                }
2340            }
2341            Ok(())
2342        }
2343        #[pymethod]
2344        fn seekable(&self, vm: &VirtualMachine) -> PyResult {
2345            let textio = self.lock(vm)?;
2346            vm.call_method(&textio.buffer, "seekable", ())
2347        }
2348        #[pymethod]
2349        fn readable(&self, vm: &VirtualMachine) -> PyResult {
2350            let textio = self.lock(vm)?;
2351            vm.call_method(&textio.buffer, "readable", ())
2352        }
2353        #[pymethod]
2354        fn writable(&self, vm: &VirtualMachine) -> PyResult {
2355            let textio = self.lock(vm)?;
2356            vm.call_method(&textio.buffer, "writable", ())
2357        }
2358
2359        #[pygetset(name = "_CHUNK_SIZE")]
2360        fn chunksize(&self, vm: &VirtualMachine) -> PyResult<usize> {
2361            Ok(self.lock(vm)?.chunk_size)
2362        }
2363
2364        #[pygetset(setter, name = "_CHUNK_SIZE")]
2365        fn set_chunksize(
2366            &self,
2367            chunk_size: PySetterValue<usize>,
2368            vm: &VirtualMachine,
2369        ) -> PyResult<()> {
2370            let mut textio = self.lock(vm)?;
2371            match chunk_size {
2372                PySetterValue::Assign(chunk_size) => textio.chunk_size = chunk_size,
2373                PySetterValue::Delete => {
2374                    Err(vm.new_attribute_error("cannot delete attribute".to_owned()))?
2375                }
2376            };
2377            // TODO: RUSTPYTHON
2378            // Change chunk_size type, validate it manually and throws ValueError if invalid.
2379            // https://github.com/python/cpython/blob/2e9da8e3522764d09f1d6054a2be567e91a30812/Modules/_io/textio.c#L3124-L3143
2380            Ok(())
2381        }
2382
2383        #[pymethod]
2384        fn seek(
2385            zelf: PyRef<Self>,
2386            cookie: PyObjectRef,
2387            how: OptionalArg<i32>,
2388            vm: &VirtualMachine,
2389        ) -> PyResult {
2390            let how = how.unwrap_or(0);
2391
2392            let reset_encoder = |encoder, start_of_stream| {
2393                if start_of_stream {
2394                    vm.call_method(encoder, "reset", ())
2395                } else {
2396                    vm.call_method(encoder, "setstate", (0,))
2397                }
2398            };
2399
2400            let textio = zelf.lock(vm)?;
2401
2402            if !textio.seekable {
2403                return Err(new_unsupported_operation(
2404                    vm,
2405                    "underlying stream is not seekable".to_owned(),
2406                ));
2407            }
2408
2409            let cookie = match how {
2410                // SEEK_SET
2411                0 => cookie,
2412                // SEEK_CUR
2413                1 => {
2414                    if vm.bool_eq(&cookie, vm.ctx.new_int(0).as_ref())? {
2415                        vm.call_method(&textio.buffer, "tell", ())?
2416                    } else {
2417                        return Err(new_unsupported_operation(
2418                            vm,
2419                            "can't do nonzero cur-relative seeks".to_owned(),
2420                        ));
2421                    }
2422                }
2423                // SEEK_END
2424                2 => {
2425                    if vm.bool_eq(&cookie, vm.ctx.new_int(0).as_ref())? {
2426                        drop(textio);
2427                        vm.call_method(zelf.as_object(), "flush", ())?;
2428                        let mut textio = zelf.lock(vm)?;
2429                        textio.set_decoded_chars(None);
2430                        textio.snapshot = None;
2431                        if let Some(decoder) = &textio.decoder {
2432                            vm.call_method(decoder, "reset", ())?;
2433                        }
2434                        let res = vm.call_method(&textio.buffer, "seek", (0, 2))?;
2435                        if let Some((encoder, _)) = &textio.encoder {
2436                            let start_of_stream = vm.bool_eq(&res, vm.ctx.new_int(0).as_ref())?;
2437                            reset_encoder(encoder, start_of_stream)?;
2438                        }
2439                        return Ok(res);
2440                    } else {
2441                        return Err(new_unsupported_operation(
2442                            vm,
2443                            "can't do nonzero end-relative seeks".to_owned(),
2444                        ));
2445                    }
2446                }
2447                _ => {
2448                    return Err(
2449                        vm.new_value_error(format!("invalid whence ({how}, should be 0, 1 or 2)"))
2450                    )
2451                }
2452            };
2453            use crate::types::PyComparisonOp;
2454            if cookie.rich_compare_bool(vm.ctx.new_int(0).as_ref(), PyComparisonOp::Lt, vm)? {
2455                return Err(
2456                    vm.new_value_error(format!("negative seek position {}", &cookie.repr(vm)?))
2457                );
2458            }
2459            drop(textio);
2460            vm.call_method(zelf.as_object(), "flush", ())?;
2461            let cookie_obj = crate::builtins::PyIntRef::try_from_object(vm, cookie)?;
2462            let cookie = TextIOCookie::parse(cookie_obj.as_bigint())
2463                .ok_or_else(|| vm.new_value_error("invalid cookie".to_owned()))?;
2464            let mut textio = zelf.lock(vm)?;
2465            vm.call_method(&textio.buffer, "seek", (cookie.start_pos,))?;
2466            textio.set_decoded_chars(None);
2467            textio.snapshot = None;
2468            if let Some(decoder) = &textio.decoder {
2469                cookie.set_decoder_state(decoder, vm)?;
2470            }
2471            if cookie.chars_to_skip != 0 {
2472                let TextIOData {
2473                    ref decoder,
2474                    ref buffer,
2475                    ref mut snapshot,
2476                    ..
2477                } = *textio;
2478                let decoder = decoder
2479                    .as_ref()
2480                    .ok_or_else(|| vm.new_value_error("invalid cookie".to_owned()))?;
2481                let input_chunk = vm.call_method(buffer, "read", (cookie.bytes_to_feed,))?;
2482                let input_chunk: PyBytesRef = input_chunk.downcast().map_err(|obj| {
2483                    vm.new_type_error(format!(
2484                        "underlying read() should have returned a bytes object, not '{}'",
2485                        obj.class().name()
2486                    ))
2487                })?;
2488                *snapshot = Some((cookie.dec_flags, input_chunk.clone()));
2489                let decoded = vm.call_method(decoder, "decode", (input_chunk, cookie.need_eof))?;
2490                let decoded = check_decoded(decoded, vm)?;
2491                let pos_is_valid = decoded
2492                    .as_str()
2493                    .is_char_boundary(cookie.bytes_to_skip as usize);
2494                textio.set_decoded_chars(Some(decoded));
2495                if !pos_is_valid {
2496                    return Err(vm.new_os_error("can't restore logical file position".to_owned()));
2497                }
2498                textio.decoded_chars_used = cookie.num_to_skip();
2499            } else {
2500                textio.snapshot = Some((cookie.dec_flags, PyBytes::from(vec![]).into_ref(&vm.ctx)))
2501            }
2502            if let Some((encoder, _)) = &textio.encoder {
2503                let start_of_stream = cookie.start_pos == 0 && cookie.dec_flags == 0;
2504                reset_encoder(encoder, start_of_stream)?;
2505            }
2506            Ok(cookie_obj.into())
2507        }
2508
2509        #[pymethod]
2510        fn tell(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
2511            let mut textio = zelf.lock(vm)?;
2512            if !textio.seekable {
2513                return Err(new_unsupported_operation(
2514                    vm,
2515                    "underlying stream is not seekable".to_owned(),
2516                ));
2517            }
2518            if !textio.telling {
2519                return Err(vm.new_os_error("telling position disabled by next() call".to_owned()));
2520            }
2521            textio.write_pending(vm)?;
2522            drop(textio);
2523            vm.call_method(zelf.as_object(), "flush", ())?;
2524            let textio = zelf.lock(vm)?;
2525            let pos = vm.call_method(&textio.buffer, "tell", ())?;
2526            let (decoder, (dec_flags, next_input)) = match (&textio.decoder, &textio.snapshot) {
2527                (Some(d), Some(s)) => (d, s),
2528                _ => return Ok(pos),
2529            };
2530            let pos = Offset::try_from_object(vm, pos)?;
2531            let mut cookie = TextIOCookie {
2532                start_pos: pos - next_input.len() as Offset,
2533                dec_flags: *dec_flags,
2534                ..Default::default()
2535            };
2536            if textio.decoded_chars_used.bytes == 0 {
2537                return Ok(cookie.build().to_pyobject(vm));
2538            }
2539            let decoder_getstate = || {
2540                let state = vm.call_method(decoder, "getstate", ())?;
2541                parse_decoder_state(state, vm)
2542            };
2543            let decoder_decode = |b: &[u8]| {
2544                let decoded = vm.call_method(decoder, "decode", (vm.ctx.new_bytes(b.to_vec()),))?;
2545                let decoded = check_decoded(decoded, vm)?;
2546                Ok(Utf8size::len_pystr(&decoded))
2547            };
2548            let saved_state = vm.call_method(decoder, "getstate", ())?;
2549            let mut num_to_skip = textio.decoded_chars_used;
2550            let mut skip_bytes = (textio.b2cratio * num_to_skip.chars as f64) as isize;
2551            let mut skip_back = 1;
2552            while skip_bytes > 0 {
2553                cookie.set_decoder_state(decoder, vm)?;
2554                let input = &next_input.as_bytes()[..skip_bytes as usize];
2555                let ndecoded = decoder_decode(input)?;
2556                if ndecoded.chars <= num_to_skip.chars {
2557                    let (dec_buffer, dec_flags) = decoder_getstate()?;
2558                    if dec_buffer.is_empty() {
2559                        cookie.dec_flags = dec_flags;
2560                        num_to_skip -= ndecoded;
2561                        break;
2562                    }
2563                    skip_bytes -= dec_buffer.len() as isize;
2564                    skip_back = 1;
2565                } else {
2566                    skip_bytes -= skip_back;
2567                    skip_back *= 2;
2568                }
2569            }
2570            if skip_bytes <= 0 {
2571                skip_bytes = 0;
2572                cookie.set_decoder_state(decoder, vm)?;
2573            }
2574            let skip_bytes = skip_bytes as usize;
2575
2576            cookie.start_pos += skip_bytes as Offset;
2577            cookie.set_num_to_skip(num_to_skip);
2578
2579            if num_to_skip.chars != 0 {
2580                let mut ndecoded = Utf8size::default();
2581                let mut input = next_input.as_bytes();
2582                input = &input[skip_bytes..];
2583                while !input.is_empty() {
2584                    let (byte1, rest) = input.split_at(1);
2585                    let n = decoder_decode(byte1)?;
2586                    ndecoded += n;
2587                    cookie.bytes_to_feed += 1;
2588                    let (dec_buffer, dec_flags) = decoder_getstate()?;
2589                    if dec_buffer.is_empty() && ndecoded.chars < num_to_skip.chars {
2590                        cookie.start_pos += cookie.bytes_to_feed as Offset;
2591                        num_to_skip -= ndecoded;
2592                        cookie.dec_flags = dec_flags;
2593                        cookie.bytes_to_feed = 0;
2594                        ndecoded = Utf8size::default();
2595                    }
2596                    if ndecoded.chars >= num_to_skip.chars {
2597                        break;
2598                    }
2599                    input = rest;
2600                }
2601                if input.is_empty() {
2602                    let decoded =
2603                        vm.call_method(decoder, "decode", (vm.ctx.new_bytes(vec![]), true))?;
2604                    let decoded = check_decoded(decoded, vm)?;
2605                    let final_decoded_chars = ndecoded.chars + decoded.char_len();
2606                    cookie.need_eof = true;
2607                    if final_decoded_chars < num_to_skip.chars {
2608                        return Err(
2609                            vm.new_os_error("can't reconstruct logical file position".to_owned())
2610                        );
2611                    }
2612                }
2613            }
2614            vm.call_method(decoder, "setstate", (saved_state,))?;
2615            cookie.set_num_to_skip(num_to_skip);
2616            Ok(cookie.build().to_pyobject(vm))
2617        }
2618
2619        #[pygetset]
2620        fn name(&self, vm: &VirtualMachine) -> PyResult {
2621            let buffer = self.lock(vm)?.buffer.clone();
2622            buffer.get_attr("name", vm)
2623        }
2624        #[pygetset]
2625        fn encoding(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> {
2626            Ok(self.lock(vm)?.encoding.clone())
2627        }
2628        #[pygetset]
2629        fn errors(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> {
2630            Ok(self.lock(vm)?.errors.clone())
2631        }
2632
2633        #[pymethod]
2634        fn fileno(&self, vm: &VirtualMachine) -> PyResult {
2635            let buffer = self.lock(vm)?.buffer.clone();
2636            vm.call_method(&buffer, "fileno", ())
2637        }
2638
2639        #[pymethod]
2640        fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<PyStrRef> {
2641            let mut textio = self.lock(vm)?;
2642            textio.check_closed(vm)?;
2643            let decoder = textio
2644                .decoder
2645                .clone()
2646                .ok_or_else(|| new_unsupported_operation(vm, "not readable".to_owned()))?;
2647
2648            textio.write_pending(vm)?;
2649
2650            let s = if let Some(mut remaining) = size.to_usize() {
2651                let mut chunks = Vec::new();
2652                let mut chunks_bytes = 0;
2653                loop {
2654                    if let Some((s, char_len)) = textio.get_decoded_chars(remaining, vm) {
2655                        chunks_bytes += s.byte_len();
2656                        chunks.push(s);
2657                        remaining = remaining.saturating_sub(char_len);
2658                    }
2659                    if remaining == 0 {
2660                        break;
2661                    }
2662                    let eof = textio.read_chunk(remaining, vm)?;
2663                    if eof {
2664                        break;
2665                    }
2666                }
2667                if chunks.is_empty() {
2668                    vm.ctx.empty_str.to_owned()
2669                } else if chunks.len() == 1 {
2670                    chunks.pop().unwrap()
2671                } else {
2672                    let mut ret = String::with_capacity(chunks_bytes);
2673                    for chunk in chunks {
2674                        ret.push_str(chunk.as_str())
2675                    }
2676                    PyStr::from(ret).into_ref(&vm.ctx)
2677                }
2678            } else {
2679                let bytes = vm.call_method(&textio.buffer, "read", ())?;
2680                let decoded = vm.call_method(&decoder, "decode", (bytes, true))?;
2681                let decoded = check_decoded(decoded, vm)?;
2682                let ret = textio.take_decoded_chars(Some(decoded), vm);
2683                textio.snapshot = None;
2684                ret
2685            };
2686            Ok(s)
2687        }
2688
2689        #[pymethod]
2690        fn write(&self, obj: PyStrRef, vm: &VirtualMachine) -> PyResult<usize> {
2691            let mut textio = self.lock(vm)?;
2692            textio.check_closed(vm)?;
2693
2694            let (encoder, encodefunc) = textio
2695                .encoder
2696                .as_ref()
2697                .ok_or_else(|| new_unsupported_operation(vm, "not writable".to_owned()))?;
2698
2699            let char_len = obj.char_len();
2700
2701            let data = obj.as_str();
2702
2703            let replace_nl = match textio.newline {
2704                Newlines::Cr => Some("\r"),
2705                Newlines::Crlf => Some("\r\n"),
2706                _ => None,
2707            };
2708            let has_lf = if replace_nl.is_some() || textio.line_buffering {
2709                data.contains('\n')
2710            } else {
2711                false
2712            };
2713            let flush = textio.line_buffering && (has_lf || data.contains('\r'));
2714            let chunk = if let Some(replace_nl) = replace_nl {
2715                if has_lf {
2716                    PyStr::from(data.replace('\n', replace_nl)).into_ref(&vm.ctx)
2717                } else {
2718                    obj
2719                }
2720            } else {
2721                obj
2722            };
2723            let chunk = if let Some(encodefunc) = *encodefunc {
2724                encodefunc(chunk)
2725            } else {
2726                let b = vm.call_method(encoder, "encode", (chunk.clone(),))?;
2727                b.downcast::<PyBytes>()
2728                    .map(PendingWrite::Bytes)
2729                    .or_else(|obj| {
2730                        // TODO: not sure if encode() returning the str it was passed is officially
2731                        // supported or just a quirk of how the CPython code is written
2732                        if obj.is(&chunk) {
2733                            Ok(PendingWrite::Utf8(chunk))
2734                        } else {
2735                            Err(vm.new_type_error(format!(
2736                                "encoder should return a bytes object, not '{}'",
2737                                obj.class().name()
2738                            )))
2739                        }
2740                    })?
2741            };
2742            if textio.pending.num_bytes + chunk.as_bytes().len() > textio.chunk_size {
2743                textio.write_pending(vm)?;
2744            }
2745            textio.pending.push(chunk);
2746            if flush || textio.write_through || textio.pending.num_bytes >= textio.chunk_size {
2747                textio.write_pending(vm)?;
2748            }
2749            if flush {
2750                let _ = vm.call_method(&textio.buffer, "flush", ());
2751            }
2752
2753            Ok(char_len)
2754        }
2755
2756        #[pymethod]
2757        fn flush(&self, vm: &VirtualMachine) -> PyResult {
2758            let mut textio = self.lock(vm)?;
2759            textio.check_closed(vm)?;
2760            textio.telling = textio.seekable;
2761            textio.write_pending(vm)?;
2762            vm.call_method(&textio.buffer, "flush", ())
2763        }
2764
2765        #[pymethod]
2766        fn isatty(&self, vm: &VirtualMachine) -> PyResult {
2767            let textio = self.lock(vm)?;
2768            textio.check_closed(vm)?;
2769            vm.call_method(&textio.buffer, "isatty", ())
2770        }
2771
2772        #[pymethod]
2773        fn readline(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<PyStrRef> {
2774            let limit = size.to_usize();
2775
2776            let mut textio = self.lock(vm)?;
2777            check_closed(&textio.buffer, vm)?;
2778
2779            textio.write_pending(vm)?;
2780
2781            #[derive(Clone)]
2782            struct SlicedStr(PyStrRef, Range<usize>);
2783            impl SlicedStr {
2784                #[inline]
2785                fn byte_len(&self) -> usize {
2786                    self.1.len()
2787                }
2788                #[inline]
2789                fn char_len(&self) -> usize {
2790                    if self.is_full_slice() {
2791                        self.0.char_len()
2792                    } else {
2793                        self.slice().chars().count()
2794                    }
2795                }
2796                #[inline]
2797                fn is_full_slice(&self) -> bool {
2798                    self.1.len() >= self.0.byte_len()
2799                }
2800                #[inline]
2801                fn slice(&self) -> &str {
2802                    &self.0.as_str()[self.1.clone()]
2803                }
2804                #[inline]
2805                fn slice_pystr(self, vm: &VirtualMachine) -> PyStrRef {
2806                    if self.is_full_slice() {
2807                        self.0
2808                    } else {
2809                        // TODO: try to use Arc::get_mut() on the str?
2810                        PyStr::from(self.slice()).into_ref(&vm.ctx)
2811                    }
2812                }
2813                fn utf8_len(&self) -> Utf8size {
2814                    Utf8size {
2815                        bytes: self.byte_len(),
2816                        chars: self.char_len(),
2817                    }
2818                }
2819            }
2820
2821            let mut start;
2822            let mut endpos;
2823            let mut offset_to_buffer;
2824            let mut chunked = Utf8size::default();
2825            let mut remaining: Option<SlicedStr> = None;
2826            let mut chunks = Vec::new();
2827
2828            let cur_line = 'outer: loop {
2829                let decoded_chars = loop {
2830                    match textio.decoded_chars.as_ref() {
2831                        Some(s) if !s.is_empty() => break s,
2832                        _ => {}
2833                    }
2834                    let eof = textio.read_chunk(0, vm)?;
2835                    if eof {
2836                        textio.set_decoded_chars(None);
2837                        textio.snapshot = None;
2838                        start = Utf8size::default();
2839                        endpos = Utf8size::default();
2840                        offset_to_buffer = Utf8size::default();
2841                        break 'outer None;
2842                    }
2843                };
2844                let line = match remaining.take() {
2845                    None => {
2846                        start = textio.decoded_chars_used;
2847                        offset_to_buffer = Utf8size::default();
2848                        decoded_chars.clone()
2849                    }
2850                    Some(remaining) => {
2851                        assert_eq!(textio.decoded_chars_used.bytes, 0);
2852                        offset_to_buffer = remaining.utf8_len();
2853                        let decoded_chars = decoded_chars.as_str();
2854                        let line = if remaining.is_full_slice() {
2855                            let mut line = remaining.0;
2856                            line.concat_in_place(decoded_chars, vm);
2857                            line
2858                        } else {
2859                            let remaining = remaining.slice();
2860                            let mut s =
2861                                String::with_capacity(remaining.len() + decoded_chars.len());
2862                            s.push_str(remaining);
2863                            s.push_str(decoded_chars);
2864                            PyStr::from(s).into_ref(&vm.ctx)
2865                        };
2866                        start = Utf8size::default();
2867                        line
2868                    }
2869                };
2870                let line_from_start = &line.as_str()[start.bytes..];
2871                let nl_res = textio.newline.find_newline(line_from_start);
2872                match nl_res {
2873                    Ok(p) | Err(p) => {
2874                        endpos = start + Utf8size::len_str(&line_from_start[..p]);
2875                        if let Some(limit) = limit {
2876                            // original CPython logic: endpos = start + limit - chunked
2877                            if chunked.chars + endpos.chars >= limit {
2878                                endpos = start
2879                                    + Utf8size {
2880                                        chars: limit - chunked.chars,
2881                                        bytes: crate::common::str::char_range_end(
2882                                            line_from_start,
2883                                            limit - chunked.chars,
2884                                        )
2885                                        .unwrap(),
2886                                    };
2887                                break Some(line);
2888                            }
2889                        }
2890                    }
2891                }
2892                if nl_res.is_ok() {
2893                    break Some(line);
2894                }
2895                if endpos.bytes > start.bytes {
2896                    let chunk = SlicedStr(line.clone(), start.bytes..endpos.bytes);
2897                    chunked += chunk.utf8_len();
2898                    chunks.push(chunk);
2899                }
2900                let line_len = line.byte_len();
2901                if endpos.bytes < line_len {
2902                    remaining = Some(SlicedStr(line, endpos.bytes..line_len));
2903                }
2904                textio.set_decoded_chars(None);
2905            };
2906
2907            let cur_line = cur_line.map(|line| {
2908                textio.decoded_chars_used = endpos - offset_to_buffer;
2909                SlicedStr(line, start.bytes..endpos.bytes)
2910            });
2911            // don't need to care about chunked.chars anymore
2912            let mut chunked = chunked.bytes;
2913            if let Some(remaining) = remaining {
2914                chunked += remaining.byte_len();
2915                chunks.push(remaining);
2916            }
2917            let line = if !chunks.is_empty() {
2918                if let Some(cur_line) = cur_line {
2919                    chunked += cur_line.byte_len();
2920                    chunks.push(cur_line);
2921                }
2922                let mut s = String::with_capacity(chunked);
2923                for chunk in chunks {
2924                    s.push_str(chunk.slice())
2925                }
2926                PyStr::from(s).into_ref(&vm.ctx)
2927            } else if let Some(cur_line) = cur_line {
2928                cur_line.slice_pystr(vm)
2929            } else {
2930                vm.ctx.empty_str.to_owned()
2931            };
2932            Ok(line)
2933        }
2934
2935        #[pymethod]
2936        fn close(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<()> {
2937            let buffer = zelf.lock(vm)?.buffer.clone();
2938            if file_closed(&buffer, vm)? {
2939                return Ok(());
2940            }
2941            let flush_res = vm.call_method(zelf.as_object(), "flush", ()).map(drop);
2942            let close_res = vm.call_method(&buffer, "close", ()).map(drop);
2943            exception_chain(flush_res, close_res)
2944        }
2945        #[pygetset]
2946        fn closed(&self, vm: &VirtualMachine) -> PyResult {
2947            let buffer = self.lock(vm)?.buffer.clone();
2948            buffer.get_attr("closed", vm)
2949        }
2950        #[pygetset]
2951        fn buffer(&self, vm: &VirtualMachine) -> PyResult {
2952            Ok(self.lock(vm)?.buffer.clone())
2953        }
2954
2955        #[pymethod(magic)]
2956        fn reduce(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
2957            Err(vm.new_type_error(format!("cannot pickle '{}' object", zelf.class().name())))
2958        }
2959    }
2960
2961    fn parse_decoder_state(state: PyObjectRef, vm: &VirtualMachine) -> PyResult<(PyBytesRef, i32)> {
2962        use crate::builtins::{int, PyTuple};
2963        let state_err = || vm.new_type_error("illegal decoder state".to_owned());
2964        let state = state.downcast::<PyTuple>().map_err(|_| state_err())?;
2965        match state.as_slice() {
2966            [buf, flags] => {
2967                let buf = buf.clone().downcast::<PyBytes>().map_err(|obj| {
2968                    vm.new_type_error(format!(
2969                        "illegal decoder state: the first item should be a bytes object, not '{}'",
2970                        obj.class().name()
2971                    ))
2972                })?;
2973                let flags = flags.payload::<int::PyInt>().ok_or_else(state_err)?;
2974                let flags = flags.try_to_primitive(vm)?;
2975                Ok((buf, flags))
2976            }
2977            _ => Err(state_err()),
2978        }
2979    }
2980
2981    impl TextIOData {
2982        fn write_pending(&mut self, vm: &VirtualMachine) -> PyResult<()> {
2983            if self.pending.num_bytes == 0 {
2984                return Ok(());
2985            }
2986            let data = self.pending.take(vm);
2987            vm.call_method(&self.buffer, "write", (data,))?;
2988            Ok(())
2989        }
2990        /// returns true on EOF
2991        fn read_chunk(&mut self, size_hint: usize, vm: &VirtualMachine) -> PyResult<bool> {
2992            let decoder = self
2993                .decoder
2994                .as_ref()
2995                .ok_or_else(|| new_unsupported_operation(vm, "not readable".to_owned()))?;
2996
2997            let dec_state = if self.telling {
2998                let state = vm.call_method(decoder, "getstate", ())?;
2999                Some(parse_decoder_state(state, vm)?)
3000            } else {
3001                None
3002            };
3003
3004            let method = if self.has_read1 { "read1" } else { "read" };
3005            let size_hint = if size_hint > 0 {
3006                (self.b2cratio.max(1.0) * size_hint as f64) as usize
3007            } else {
3008                size_hint
3009            };
3010            let chunk_size = std::cmp::max(self.chunk_size, size_hint);
3011            let input_chunk = vm.call_method(&self.buffer, method, (chunk_size,))?;
3012
3013            let buf = ArgBytesLike::try_from_borrowed_object(vm, &input_chunk).map_err(|_| {
3014                vm.new_type_error(format!(
3015                    "underlying {}() should have returned a bytes-like object, not '{}'",
3016                    method,
3017                    input_chunk.class().name()
3018                ))
3019            })?;
3020            let nbytes = buf.borrow_buf().len();
3021            let eof = nbytes == 0;
3022            let decoded = vm.call_method(decoder, "decode", (input_chunk, eof))?;
3023            let decoded = check_decoded(decoded, vm)?;
3024
3025            let char_len = decoded.char_len();
3026            self.b2cratio = if char_len > 0 {
3027                nbytes as f64 / char_len as f64
3028            } else {
3029                0.0
3030            };
3031            let eof = if char_len > 0 { false } else { eof };
3032            self.set_decoded_chars(Some(decoded));
3033
3034            if let Some((dec_buffer, dec_flags)) = dec_state {
3035                // TODO: inplace append to bytes when refcount == 1
3036                let mut next_input = dec_buffer.as_bytes().to_vec();
3037                next_input.extend_from_slice(&buf.borrow_buf());
3038                self.snapshot = Some((dec_flags, PyBytes::from(next_input).into_ref(&vm.ctx)));
3039            }
3040
3041            Ok(eof)
3042        }
3043
3044        fn check_closed(&self, vm: &VirtualMachine) -> PyResult<()> {
3045            check_closed(&self.buffer, vm)
3046        }
3047
3048        /// returns str, str.char_len() (it might not be cached in the str yet but we calculate it
3049        /// anyway in this method)
3050        fn get_decoded_chars(
3051            &mut self,
3052            n: usize,
3053            vm: &VirtualMachine,
3054        ) -> Option<(PyStrRef, usize)> {
3055            if n == 0 {
3056                return None;
3057            }
3058            let decoded_chars = self.decoded_chars.as_ref()?;
3059            let avail = &decoded_chars.as_str()[self.decoded_chars_used.bytes..];
3060            if avail.is_empty() {
3061                return None;
3062            }
3063            let avail_chars = decoded_chars.char_len() - self.decoded_chars_used.chars;
3064            let (chars, chars_used) = if n >= avail_chars {
3065                if self.decoded_chars_used.bytes == 0 {
3066                    (decoded_chars.clone(), avail_chars)
3067                } else {
3068                    (PyStr::from(avail).into_ref(&vm.ctx), avail_chars)
3069                }
3070            } else {
3071                let s = crate::common::str::get_chars(avail, 0..n);
3072                (PyStr::from(s).into_ref(&vm.ctx), n)
3073            };
3074            self.decoded_chars_used += Utf8size {
3075                bytes: chars.byte_len(),
3076                chars: chars_used,
3077            };
3078            Some((chars, chars_used))
3079        }
3080        fn set_decoded_chars(&mut self, s: Option<PyStrRef>) {
3081            self.decoded_chars = s;
3082            self.decoded_chars_used = Utf8size::default();
3083        }
3084        fn take_decoded_chars(
3085            &mut self,
3086            append: Option<PyStrRef>,
3087            vm: &VirtualMachine,
3088        ) -> PyStrRef {
3089            let empty_str = || vm.ctx.empty_str.to_owned();
3090            let chars_pos = std::mem::take(&mut self.decoded_chars_used).bytes;
3091            let decoded_chars = match std::mem::take(&mut self.decoded_chars) {
3092                None => return append.unwrap_or_else(empty_str),
3093                Some(s) if s.is_empty() => return append.unwrap_or_else(empty_str),
3094                Some(s) => s,
3095            };
3096            let append_len = append.as_ref().map_or(0, |s| s.byte_len());
3097            if append_len == 0 && chars_pos == 0 {
3098                return decoded_chars;
3099            }
3100            // TODO: in-place editing of `str` when refcount == 1
3101            let decoded_chars_unused = &decoded_chars.as_str()[chars_pos..];
3102            let mut s = String::with_capacity(decoded_chars_unused.len() + append_len);
3103            s.push_str(decoded_chars_unused);
3104            if let Some(append) = append {
3105                s.push_str(append.as_str())
3106            }
3107            PyStr::from(s).into_ref(&vm.ctx)
3108        }
3109    }
3110
3111    #[pyattr]
3112    #[pyclass(name = "StringIO", base = "_TextIOBase")]
3113    #[derive(Debug, PyPayload)]
3114    struct StringIO {
3115        buffer: PyRwLock<BufferedIO>,
3116        closed: AtomicCell<bool>,
3117    }
3118
3119    #[derive(FromArgs)]
3120    struct StringIONewArgs {
3121        #[pyarg(positional, optional)]
3122        object: OptionalOption<PyStrRef>,
3123
3124        // TODO: use this
3125        #[pyarg(any, default)]
3126        #[allow(dead_code)]
3127        newline: Newlines,
3128    }
3129
3130    impl Constructor for StringIO {
3131        type Args = StringIONewArgs;
3132
3133        #[allow(unused_variables)]
3134        fn py_new(
3135            cls: PyTypeRef,
3136            Self::Args { object, newline }: Self::Args,
3137            vm: &VirtualMachine,
3138        ) -> PyResult {
3139            let raw_bytes = object
3140                .flatten()
3141                .map_or_else(Vec::new, |v| v.as_str().as_bytes().to_vec());
3142
3143            StringIO {
3144                buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))),
3145                closed: AtomicCell::new(false),
3146            }
3147            .into_ref_with_type(vm, cls)
3148            .map(Into::into)
3149        }
3150    }
3151
3152    impl StringIO {
3153        fn buffer(&self, vm: &VirtualMachine) -> PyResult<PyRwLockWriteGuard<'_, BufferedIO>> {
3154            if !self.closed.load() {
3155                Ok(self.buffer.write())
3156            } else {
3157                Err(io_closed_error(vm))
3158            }
3159        }
3160    }
3161
3162    #[pyclass(flags(BASETYPE, HAS_DICT), with(Constructor))]
3163    impl StringIO {
3164        #[pymethod]
3165        fn readable(&self) -> bool {
3166            true
3167        }
3168        #[pymethod]
3169        fn writable(&self) -> bool {
3170            true
3171        }
3172        #[pymethod]
3173        fn seekable(&self) -> bool {
3174            true
3175        }
3176
3177        #[pygetset]
3178        fn closed(&self) -> bool {
3179            self.closed.load()
3180        }
3181
3182        #[pymethod]
3183        fn close(&self) {
3184            self.closed.store(true);
3185        }
3186
3187        // write string to underlying vector
3188        #[pymethod]
3189        fn write(&self, data: PyStrRef, vm: &VirtualMachine) -> PyResult<u64> {
3190            let bytes = data.as_str().as_bytes();
3191            self.buffer(vm)?
3192                .write(bytes)
3193                .ok_or_else(|| vm.new_type_error("Error Writing String".to_owned()))
3194        }
3195
3196        // return the entire contents of the underlying
3197        #[pymethod]
3198        fn getvalue(&self, vm: &VirtualMachine) -> PyResult<String> {
3199            let bytes = self.buffer(vm)?.getvalue();
3200            String::from_utf8(bytes)
3201                .map_err(|_| vm.new_value_error("Error Retrieving Value".to_owned()))
3202        }
3203
3204        // skip to the jth position
3205        #[pymethod]
3206        fn seek(
3207            &self,
3208            offset: PyObjectRef,
3209            how: OptionalArg<i32>,
3210            vm: &VirtualMachine,
3211        ) -> PyResult<u64> {
3212            self.buffer(vm)?
3213                .seek(seekfrom(vm, offset, how)?)
3214                .map_err(|err| os_err(vm, err))
3215        }
3216
3217        // Read k bytes from the object and return.
3218        // If k is undefined || k == -1, then we read all bytes until the end of the file.
3219        // This also increments the stream position by the value of k
3220        #[pymethod]
3221        fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<String> {
3222            let data = self.buffer(vm)?.read(size.to_usize()).unwrap_or_default();
3223
3224            let value = String::from_utf8(data)
3225                .map_err(|_| vm.new_value_error("Error Retrieving Value".to_owned()))?;
3226            Ok(value)
3227        }
3228
3229        #[pymethod]
3230        fn tell(&self, vm: &VirtualMachine) -> PyResult<u64> {
3231            Ok(self.buffer(vm)?.tell())
3232        }
3233
3234        #[pymethod]
3235        fn readline(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<String> {
3236            // TODO size should correspond to the number of characters, at the moments its the number of
3237            // bytes.
3238            let input = self.buffer(vm)?.readline(size.to_usize(), vm)?;
3239            String::from_utf8(input)
3240                .map_err(|_| vm.new_value_error("Error Retrieving Value".to_owned()))
3241        }
3242
3243        #[pymethod]
3244        fn truncate(&self, pos: OptionalSize, vm: &VirtualMachine) -> PyResult<usize> {
3245            let mut buffer = self.buffer(vm)?;
3246            let pos = pos.try_usize(vm)?;
3247            Ok(buffer.truncate(pos))
3248        }
3249    }
3250
3251    #[pyattr]
3252    #[pyclass(name = "BytesIO", base = "_BufferedIOBase")]
3253    #[derive(Debug, PyPayload)]
3254    struct BytesIO {
3255        buffer: PyRwLock<BufferedIO>,
3256        closed: AtomicCell<bool>,
3257        exports: AtomicCell<usize>,
3258    }
3259
3260    impl Constructor for BytesIO {
3261        type Args = OptionalArg<Option<PyBytesRef>>;
3262
3263        fn py_new(cls: PyTypeRef, object: Self::Args, vm: &VirtualMachine) -> PyResult {
3264            let raw_bytes = object
3265                .flatten()
3266                .map_or_else(Vec::new, |input| input.as_bytes().to_vec());
3267
3268            BytesIO {
3269                buffer: PyRwLock::new(BufferedIO::new(Cursor::new(raw_bytes))),
3270                closed: AtomicCell::new(false),
3271                exports: AtomicCell::new(0),
3272            }
3273            .into_ref_with_type(vm, cls)
3274            .map(Into::into)
3275        }
3276    }
3277
3278    impl BytesIO {
3279        fn buffer(&self, vm: &VirtualMachine) -> PyResult<PyRwLockWriteGuard<'_, BufferedIO>> {
3280            if !self.closed.load() {
3281                Ok(self.buffer.write())
3282            } else {
3283                Err(io_closed_error(vm))
3284            }
3285        }
3286    }
3287
3288    #[pyclass(flags(BASETYPE, HAS_DICT), with(PyRef, Constructor))]
3289    impl BytesIO {
3290        #[pymethod]
3291        fn readable(&self) -> bool {
3292            true
3293        }
3294        #[pymethod]
3295        fn writable(&self) -> bool {
3296            true
3297        }
3298        #[pymethod]
3299        fn seekable(&self) -> bool {
3300            true
3301        }
3302
3303        #[pymethod]
3304        fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<u64> {
3305            let mut buffer = self.try_resizable(vm)?;
3306            data.with_ref(|b| buffer.write(b))
3307                .ok_or_else(|| vm.new_type_error("Error Writing Bytes".to_owned()))
3308        }
3309
3310        // Retrieves the entire bytes object value from the underlying buffer
3311        #[pymethod]
3312        fn getvalue(&self, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
3313            let bytes = self.buffer(vm)?.getvalue();
3314            Ok(vm.ctx.new_bytes(bytes))
3315        }
3316
3317        // Takes an integer k (bytes) and returns them from the underlying buffer
3318        // If k is undefined || k == -1, then we read all bytes until the end of the file.
3319        // This also increments the stream position by the value of k
3320        #[pymethod]
3321        #[pymethod(name = "read1")]
3322        fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
3323            let buf = self.buffer(vm)?.read(size.to_usize()).unwrap_or_default();
3324            Ok(buf)
3325        }
3326
3327        #[pymethod]
3328        fn readinto(&self, obj: ArgMemoryBuffer, vm: &VirtualMachine) -> PyResult<usize> {
3329            let mut buf = self.buffer(vm)?;
3330            let ret = buf
3331                .cursor
3332                .read(&mut obj.borrow_buf_mut())
3333                .map_err(|_| vm.new_value_error("Error readinto from Take".to_owned()))?;
3334
3335            Ok(ret)
3336        }
3337
3338        //skip to the jth position
3339        #[pymethod]
3340        fn seek(
3341            &self,
3342            offset: PyObjectRef,
3343            how: OptionalArg<i32>,
3344            vm: &VirtualMachine,
3345        ) -> PyResult<u64> {
3346            self.buffer(vm)?
3347                .seek(seekfrom(vm, offset, how)?)
3348                .map_err(|err| os_err(vm, err))
3349        }
3350
3351        #[pymethod]
3352        fn tell(&self, vm: &VirtualMachine) -> PyResult<u64> {
3353            Ok(self.buffer(vm)?.tell())
3354        }
3355
3356        #[pymethod]
3357        fn readline(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
3358            self.buffer(vm)?.readline(size.to_usize(), vm)
3359        }
3360
3361        #[pymethod]
3362        fn truncate(&self, pos: OptionalSize, vm: &VirtualMachine) -> PyResult<usize> {
3363            if self.closed.load() {
3364                return Err(io_closed_error(vm));
3365            }
3366            let mut buffer = self.try_resizable(vm)?;
3367            let pos = pos.try_usize(vm)?;
3368            Ok(buffer.truncate(pos))
3369        }
3370
3371        #[pygetset]
3372        fn closed(&self) -> bool {
3373            self.closed.load()
3374        }
3375
3376        #[pymethod]
3377        fn close(&self, vm: &VirtualMachine) -> PyResult<()> {
3378            drop(self.try_resizable(vm)?);
3379            self.closed.store(true);
3380            Ok(())
3381        }
3382    }
3383
3384    #[pyclass]
3385    impl PyRef<BytesIO> {
3386        #[pymethod]
3387        fn getbuffer(self, vm: &VirtualMachine) -> PyResult<PyMemoryView> {
3388            let len = self.buffer.read().cursor.get_ref().len();
3389            let buffer = PyBuffer::new(
3390                self.into(),
3391                BufferDescriptor::simple(len, false),
3392                &BYTES_IO_BUFFER_METHODS,
3393            );
3394            let view = PyMemoryView::from_buffer(buffer, vm)?;
3395            Ok(view)
3396        }
3397    }
3398
3399    static BYTES_IO_BUFFER_METHODS: BufferMethods = BufferMethods {
3400        obj_bytes: |buffer| {
3401            let zelf = buffer.obj_as::<BytesIO>();
3402            PyRwLockReadGuard::map(zelf.buffer.read(), |x| x.cursor.get_ref().as_slice()).into()
3403        },
3404        obj_bytes_mut: |buffer| {
3405            let zelf = buffer.obj_as::<BytesIO>();
3406            PyRwLockWriteGuard::map(zelf.buffer.write(), |x| x.cursor.get_mut().as_mut_slice())
3407                .into()
3408        },
3409
3410        release: |buffer| {
3411            buffer.obj_as::<BytesIO>().exports.fetch_sub(1);
3412        },
3413
3414        retain: |buffer| {
3415            buffer.obj_as::<BytesIO>().exports.fetch_add(1);
3416        },
3417    };
3418
3419    impl BufferResizeGuard for BytesIO {
3420        type Resizable<'a> = PyRwLockWriteGuard<'a, BufferedIO>;
3421
3422        fn try_resizable_opt(&self) -> Option<Self::Resizable<'_>> {
3423            let w = self.buffer.write();
3424            (self.exports.load() == 0).then_some(w)
3425        }
3426    }
3427
3428    #[repr(u8)]
3429    #[derive(Debug)]
3430    enum FileMode {
3431        Read = b'r',
3432        Write = b'w',
3433        Exclusive = b'x',
3434        Append = b'a',
3435    }
3436    #[repr(u8)]
3437    #[derive(Debug)]
3438    enum EncodeMode {
3439        Text = b't',
3440        Bytes = b'b',
3441    }
3442    #[derive(Debug)]
3443    struct Mode {
3444        file: FileMode,
3445        encode: EncodeMode,
3446        plus: bool,
3447    }
3448    impl std::str::FromStr for Mode {
3449        type Err = ParseModeError;
3450        fn from_str(s: &str) -> Result<Self, Self::Err> {
3451            let mut file = None;
3452            let mut encode = None;
3453            let mut plus = false;
3454            macro_rules! set_mode {
3455                ($var:ident, $mode:path, $err:ident) => {{
3456                    match $var {
3457                        Some($mode) => return Err(ParseModeError::InvalidMode),
3458                        Some(_) => return Err(ParseModeError::$err),
3459                        None => $var = Some($mode),
3460                    }
3461                }};
3462            }
3463
3464            for ch in s.chars() {
3465                match ch {
3466                    '+' => {
3467                        if plus {
3468                            return Err(ParseModeError::InvalidMode);
3469                        }
3470                        plus = true
3471                    }
3472                    't' => set_mode!(encode, EncodeMode::Text, MultipleEncode),
3473                    'b' => set_mode!(encode, EncodeMode::Bytes, MultipleEncode),
3474                    'r' => set_mode!(file, FileMode::Read, MultipleFile),
3475                    'a' => set_mode!(file, FileMode::Append, MultipleFile),
3476                    'w' => set_mode!(file, FileMode::Write, MultipleFile),
3477                    'x' => set_mode!(file, FileMode::Exclusive, MultipleFile),
3478                    _ => return Err(ParseModeError::InvalidMode),
3479                }
3480            }
3481
3482            let file = file.ok_or(ParseModeError::NoFile)?;
3483            let encode = encode.unwrap_or(EncodeMode::Text);
3484
3485            Ok(Mode { file, encode, plus })
3486        }
3487    }
3488    impl Mode {
3489        fn rawmode(&self) -> &'static str {
3490            match (&self.file, self.plus) {
3491                (FileMode::Read, true) => "rb+",
3492                (FileMode::Read, false) => "rb",
3493                (FileMode::Write, true) => "wb+",
3494                (FileMode::Write, false) => "wb",
3495                (FileMode::Exclusive, true) => "xb+",
3496                (FileMode::Exclusive, false) => "xb",
3497                (FileMode::Append, true) => "ab+",
3498                (FileMode::Append, false) => "ab",
3499            }
3500        }
3501    }
3502    enum ParseModeError {
3503        InvalidMode,
3504        MultipleFile,
3505        MultipleEncode,
3506        NoFile,
3507    }
3508    impl ParseModeError {
3509        fn error_msg(&self, mode_string: &str) -> String {
3510            match self {
3511                ParseModeError::InvalidMode => format!("invalid mode: '{mode_string}'"),
3512                ParseModeError::MultipleFile => {
3513                    "must have exactly one of create/read/write/append mode".to_owned()
3514                }
3515                ParseModeError::MultipleEncode => {
3516                    "can't have text and binary mode at once".to_owned()
3517                }
3518                ParseModeError::NoFile => {
3519                    "Must have exactly one of create/read/write/append mode and at most one plus"
3520                        .to_owned()
3521                }
3522            }
3523        }
3524    }
3525
3526    #[derive(FromArgs)]
3527    struct IoOpenArgs {
3528        file: PyObjectRef,
3529        #[pyarg(any, optional)]
3530        mode: OptionalArg<PyStrRef>,
3531        #[pyarg(flatten)]
3532        opts: OpenArgs,
3533    }
3534    #[pyfunction]
3535    fn open(args: IoOpenArgs, vm: &VirtualMachine) -> PyResult {
3536        io_open(
3537            args.file,
3538            args.mode.as_ref().into_option().map(|s| s.as_str()),
3539            args.opts,
3540            vm,
3541        )
3542    }
3543
3544    #[pyfunction]
3545    fn open_code(file: PyObjectRef, vm: &VirtualMachine) -> PyResult {
3546        // TODO: lifecycle hooks or something?
3547        io_open(file, Some("rb"), OpenArgs::default(), vm)
3548    }
3549
3550    #[derive(FromArgs)]
3551    pub struct OpenArgs {
3552        #[pyarg(any, default = "-1")]
3553        buffering: isize,
3554        #[pyarg(any, default)]
3555        encoding: Option<PyStrRef>,
3556        #[pyarg(any, default)]
3557        errors: Option<PyStrRef>,
3558        #[pyarg(any, default)]
3559        newline: Option<PyStrRef>,
3560        #[pyarg(any, default = "true")]
3561        closefd: bool,
3562        #[pyarg(any, default)]
3563        opener: Option<PyObjectRef>,
3564    }
3565    impl Default for OpenArgs {
3566        fn default() -> Self {
3567            OpenArgs {
3568                buffering: -1,
3569                encoding: None,
3570                errors: None,
3571                newline: None,
3572                closefd: true,
3573                opener: None,
3574            }
3575        }
3576    }
3577
3578    pub fn io_open(
3579        file: PyObjectRef,
3580        mode: Option<&str>,
3581        opts: OpenArgs,
3582        vm: &VirtualMachine,
3583    ) -> PyResult {
3584        // mode is optional: 'rt' is the default mode (open from reading text)
3585        let mode_string = mode.unwrap_or("r");
3586        let mode = mode_string
3587            .parse::<Mode>()
3588            .map_err(|e| vm.new_value_error(e.error_msg(mode_string)))?;
3589
3590        if let EncodeMode::Bytes = mode.encode {
3591            let msg = if opts.encoding.is_some() {
3592                Some("binary mode doesn't take an encoding argument")
3593            } else if opts.errors.is_some() {
3594                Some("binary mode doesn't take an errors argument")
3595            } else if opts.newline.is_some() {
3596                Some("binary mode doesn't take a newline argument")
3597            } else {
3598                None
3599            };
3600            if let Some(msg) = msg {
3601                return Err(vm.new_value_error(msg.to_owned()));
3602            }
3603        }
3604
3605        // check file descriptor validity
3606        #[cfg(unix)]
3607        if let Ok(crate::ospath::OsPathOrFd::Fd(fd)) = file.clone().try_into_value(vm) {
3608            nix::fcntl::fcntl(fd, nix::fcntl::F_GETFD)
3609                .map_err(|_| crate::stdlib::os::errno_err(vm))?;
3610        }
3611
3612        // Construct a FileIO (subclass of RawIOBase)
3613        // This is subsequently consumed by a Buffered Class.
3614        let file_io_class: &Py<PyType> = {
3615            cfg_if::cfg_if! {
3616                if #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] {
3617                    Some(super::fileio::FileIO::static_type())
3618                } else {
3619                    None
3620                }
3621            }
3622        }
3623        .ok_or_else(|| {
3624            new_unsupported_operation(
3625                vm,
3626                "Couldn't get FileIO, io.open likely isn't supported on your platform".to_owned(),
3627            )
3628        })?;
3629        let raw = PyType::call(
3630            file_io_class,
3631            (file, mode.rawmode(), opts.closefd, opts.opener).into_args(vm),
3632            vm,
3633        )?;
3634
3635        let isatty = opts.buffering < 0 && {
3636            let atty = vm.call_method(&raw, "isatty", ())?;
3637            bool::try_from_object(vm, atty)?
3638        };
3639
3640        let line_buffering = opts.buffering == 1 || isatty;
3641
3642        let buffering = if opts.buffering < 0 || opts.buffering == 1 {
3643            DEFAULT_BUFFER_SIZE
3644        } else {
3645            opts.buffering as usize
3646        };
3647
3648        if buffering == 0 {
3649            let ret = match mode.encode {
3650                EncodeMode::Text => {
3651                    Err(vm.new_value_error("can't have unbuffered text I/O".to_owned()))
3652                }
3653                EncodeMode::Bytes => Ok(raw),
3654            };
3655            return ret;
3656        }
3657
3658        let cls = if mode.plus {
3659            BufferedRandom::static_type()
3660        } else if let FileMode::Read = mode.file {
3661            BufferedReader::static_type()
3662        } else {
3663            BufferedWriter::static_type()
3664        };
3665        let buffered = PyType::call(cls, (raw, buffering).into_args(vm), vm)?;
3666
3667        match mode.encode {
3668            EncodeMode::Text => {
3669                let tio = TextIOWrapper::static_type();
3670                let wrapper = PyType::call(
3671                    tio,
3672                    (
3673                        buffered,
3674                        opts.encoding,
3675                        opts.errors,
3676                        opts.newline,
3677                        line_buffering,
3678                    )
3679                        .into_args(vm),
3680                    vm,
3681                )?;
3682                wrapper.set_attr("mode", vm.new_pyobj(mode_string), vm)?;
3683                Ok(wrapper)
3684            }
3685            EncodeMode::Bytes => Ok(buffered),
3686        }
3687    }
3688
3689    rustpython_common::static_cell! {
3690        pub(super) static UNSUPPORTED_OPERATION: PyTypeRef;
3691    }
3692
3693    pub(super) fn make_unsupportedop(ctx: &Context) -> PyTypeRef {
3694        use crate::types::PyTypeSlots;
3695        PyType::new_heap(
3696            "UnsupportedOperation",
3697            vec![
3698                ctx.exceptions.os_error.to_owned(),
3699                ctx.exceptions.value_error.to_owned(),
3700            ],
3701            Default::default(),
3702            PyTypeSlots::heap_default(),
3703            ctx.types.type_type.to_owned(),
3704            ctx,
3705        )
3706        .unwrap()
3707    }
3708
3709    #[pyfunction]
3710    fn text_encoding(
3711        encoding: PyObjectRef,
3712        _stacklevel: OptionalArg<i32>,
3713        vm: &VirtualMachine,
3714    ) -> PyResult<PyStrRef> {
3715        if vm.is_none(&encoding) {
3716            // TODO: This is `locale` encoding - but we don't have locale encoding yet
3717            return Ok(vm.ctx.new_str("utf-8"));
3718        }
3719        encoding.try_into_value(vm)
3720    }
3721
3722    #[cfg(test)]
3723    mod tests {
3724        use super::*;
3725
3726        #[test]
3727        fn test_buffered_read() {
3728            let data = vec![1, 2, 3, 4];
3729            let bytes = None;
3730            let mut buffered = BufferedIO {
3731                cursor: Cursor::new(data.clone()),
3732            };
3733
3734            assert_eq!(buffered.read(bytes).unwrap(), data);
3735        }
3736
3737        #[test]
3738        fn test_buffered_seek() {
3739            let data = vec![1, 2, 3, 4];
3740            let count: u64 = 2;
3741            let mut buffered = BufferedIO {
3742                cursor: Cursor::new(data),
3743            };
3744
3745            assert_eq!(buffered.seek(SeekFrom::Start(count)).unwrap(), count);
3746            assert_eq!(buffered.read(Some(count as usize)).unwrap(), vec![3, 4]);
3747        }
3748
3749        #[test]
3750        fn test_buffered_value() {
3751            let data = vec![1, 2, 3, 4];
3752            let buffered = BufferedIO {
3753                cursor: Cursor::new(data.clone()),
3754            };
3755
3756            assert_eq!(buffered.getvalue(), data);
3757        }
3758    }
3759}
3760
3761// disable FileIO on WASM
3762#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
3763#[pymodule]
3764mod fileio {
3765    use super::{Offset, _io::*};
3766    use crate::{
3767        builtins::{PyBaseExceptionRef, PyStr, PyStrRef},
3768        common::crt_fd::Fd,
3769        convert::ToPyException,
3770        function::{ArgBytesLike, ArgMemoryBuffer, OptionalArg, OptionalOption},
3771        ospath::{IOErrorBuilder, OsPath, OsPathOrFd},
3772        stdlib::os,
3773        types::{Constructor, DefaultConstructor, Initializer, Representable},
3774        AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
3775    };
3776    use crossbeam_utils::atomic::AtomicCell;
3777    use std::io::{Read, Write};
3778
3779    bitflags::bitflags! {
3780        #[derive(Copy, Clone, Debug, PartialEq)]
3781        struct Mode: u8 {
3782            const CREATED   = 0b0001;
3783            const READABLE  = 0b0010;
3784            const WRITABLE  = 0b0100;
3785            const APPENDING = 0b1000;
3786        }
3787    }
3788
3789    enum ModeError {
3790        Invalid,
3791        BadRwa,
3792    }
3793    impl ModeError {
3794        fn error_msg(&self, mode_str: &str) -> String {
3795            match self {
3796                ModeError::Invalid => format!("invalid mode: {mode_str}"),
3797                ModeError::BadRwa => {
3798                    "Must have exactly one of create/read/write/append mode and at most one plus"
3799                        .to_owned()
3800                }
3801            }
3802        }
3803    }
3804
3805    fn compute_mode(mode_str: &str) -> Result<(Mode, i32), ModeError> {
3806        let mut flags = 0;
3807        let mut plus = false;
3808        let mut rwa = false;
3809        let mut mode = Mode::empty();
3810        for c in mode_str.bytes() {
3811            match c {
3812                b'x' => {
3813                    if rwa {
3814                        return Err(ModeError::BadRwa);
3815                    }
3816                    rwa = true;
3817                    mode.insert(Mode::WRITABLE | Mode::CREATED);
3818                    flags |= libc::O_EXCL | libc::O_CREAT;
3819                }
3820                b'r' => {
3821                    if rwa {
3822                        return Err(ModeError::BadRwa);
3823                    }
3824                    rwa = true;
3825                    mode.insert(Mode::READABLE);
3826                }
3827                b'w' => {
3828                    if rwa {
3829                        return Err(ModeError::BadRwa);
3830                    }
3831                    rwa = true;
3832                    mode.insert(Mode::WRITABLE);
3833                    flags |= libc::O_CREAT | libc::O_TRUNC;
3834                }
3835                b'a' => {
3836                    if rwa {
3837                        return Err(ModeError::BadRwa);
3838                    }
3839                    rwa = true;
3840                    mode.insert(Mode::WRITABLE | Mode::APPENDING);
3841                    flags |= libc::O_APPEND | libc::O_CREAT;
3842                }
3843                b'+' => {
3844                    if plus {
3845                        return Err(ModeError::BadRwa);
3846                    }
3847                    plus = true;
3848                    mode.insert(Mode::READABLE | Mode::WRITABLE);
3849                }
3850                b'b' => {}
3851                _ => return Err(ModeError::Invalid),
3852            }
3853        }
3854
3855        if !rwa {
3856            return Err(ModeError::BadRwa);
3857        }
3858
3859        if mode.contains(Mode::READABLE | Mode::WRITABLE) {
3860            flags |= libc::O_RDWR
3861        } else if mode.contains(Mode::READABLE) {
3862            flags |= libc::O_RDONLY
3863        } else {
3864            flags |= libc::O_WRONLY
3865        }
3866
3867        #[cfg(windows)]
3868        {
3869            flags |= libc::O_BINARY | libc::O_NOINHERIT;
3870        }
3871        #[cfg(unix)]
3872        {
3873            flags |= libc::O_CLOEXEC
3874        }
3875
3876        Ok((mode, flags as _))
3877    }
3878
3879    #[pyattr]
3880    #[pyclass(module = "io", name, base = "_RawIOBase")]
3881    #[derive(Debug, PyPayload)]
3882    pub(super) struct FileIO {
3883        fd: AtomicCell<i32>,
3884        closefd: AtomicCell<bool>,
3885        mode: AtomicCell<Mode>,
3886        seekable: AtomicCell<Option<bool>>,
3887    }
3888
3889    #[derive(FromArgs)]
3890    pub struct FileIOArgs {
3891        #[pyarg(positional)]
3892        name: PyObjectRef,
3893        #[pyarg(any, default)]
3894        mode: Option<PyStrRef>,
3895        #[pyarg(any, default = "true")]
3896        closefd: bool,
3897        #[pyarg(any, default)]
3898        opener: Option<PyObjectRef>,
3899    }
3900
3901    impl Default for FileIO {
3902        fn default() -> Self {
3903            Self {
3904                fd: AtomicCell::new(-1),
3905                closefd: AtomicCell::new(true),
3906                mode: AtomicCell::new(Mode::empty()),
3907                seekable: AtomicCell::new(None),
3908            }
3909        }
3910    }
3911
3912    impl DefaultConstructor for FileIO {}
3913
3914    impl Initializer for FileIO {
3915        type Args = FileIOArgs;
3916
3917        fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
3918            // TODO: let atomic_flag_works
3919            let name = args.name;
3920            let arg_fd = if let Some(i) = name.payload::<crate::builtins::PyInt>() {
3921                let fd = i.try_to_primitive(vm)?;
3922                if fd < 0 {
3923                    return Err(vm.new_value_error("negative file descriptor".to_owned()));
3924                }
3925                Some(fd)
3926            } else {
3927                None
3928            };
3929
3930            let mode_obj = args
3931                .mode
3932                .unwrap_or_else(|| PyStr::from("rb").into_ref(&vm.ctx));
3933            let mode_str = mode_obj.as_str();
3934            let (mode, flags) =
3935                compute_mode(mode_str).map_err(|e| vm.new_value_error(e.error_msg(mode_str)))?;
3936            zelf.mode.store(mode);
3937
3938            let (fd, filename) = if let Some(fd) = arg_fd {
3939                zelf.closefd.store(args.closefd);
3940                (fd, OsPathOrFd::Fd(fd))
3941            } else {
3942                zelf.closefd.store(true);
3943                if !args.closefd {
3944                    return Err(
3945                        vm.new_value_error("Cannot use closefd=False with file name".to_owned())
3946                    );
3947                }
3948
3949                if let Some(opener) = args.opener {
3950                    let fd = opener.call((name.clone(), flags), vm)?;
3951                    if !fd.fast_isinstance(vm.ctx.types.int_type) {
3952                        return Err(vm.new_type_error("expected integer from opener".to_owned()));
3953                    }
3954                    let fd = i32::try_from_object(vm, fd)?;
3955                    if fd < 0 {
3956                        return Err(vm.new_value_error(format!("opener returned {fd}")));
3957                    }
3958                    (
3959                        fd,
3960                        OsPathOrFd::try_from_object(vm, name.clone()).unwrap_or(OsPathOrFd::Fd(fd)),
3961                    )
3962                } else {
3963                    let path = OsPath::try_from_object(vm, name.clone())?;
3964                    #[cfg(any(unix, target_os = "wasi"))]
3965                    let fd = Fd::open(&path.clone().into_cstring(vm)?, flags, 0o666);
3966                    #[cfg(windows)]
3967                    let fd = Fd::wopen(&path.to_widecstring(vm)?, flags, 0o666);
3968                    let filename = OsPathOrFd::Path(path);
3969                    match fd {
3970                        Ok(fd) => (fd.0, filename),
3971                        Err(e) => return Err(IOErrorBuilder::with_filename(&e, filename, vm)),
3972                    }
3973                }
3974            };
3975            zelf.fd.store(fd);
3976
3977            // TODO: _Py_set_inheritable
3978
3979            let fd_fstat = crate::common::fileutils::fstat(fd);
3980
3981            #[cfg(windows)]
3982            {
3983                if let Err(err) = fd_fstat {
3984                    return Err(IOErrorBuilder::with_filename(&err, filename, vm));
3985                }
3986            }
3987            #[cfg(any(unix, target_os = "wasi"))]
3988            {
3989                match fd_fstat {
3990                    Ok(status) => {
3991                        if (status.st_mode & libc::S_IFMT) == libc::S_IFDIR {
3992                            let err = std::io::Error::from_raw_os_error(libc::EISDIR);
3993                            return Err(IOErrorBuilder::with_filename(&err, filename, vm));
3994                        }
3995                    }
3996                    Err(err) => {
3997                        if err.raw_os_error() == Some(libc::EBADF) {
3998                            return Err(IOErrorBuilder::with_filename(&err, filename, vm));
3999                        }
4000                    }
4001                }
4002            }
4003
4004            #[cfg(windows)]
4005            crate::stdlib::msvcrt::setmode_binary(fd);
4006            zelf.as_object().set_attr("name", name, vm)?;
4007
4008            if mode.contains(Mode::APPENDING) {
4009                let _ = os::lseek(fd as _, 0, libc::SEEK_END, vm);
4010            }
4011
4012            Ok(())
4013        }
4014    }
4015
4016    impl Representable for FileIO {
4017        #[inline]
4018        fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
4019            let fd = zelf.fd.load();
4020            if fd < 0 {
4021                return Ok("<_io.FileIO [closed]>".to_owned());
4022            }
4023            let name_repr = repr_fileobj_name(zelf.as_object(), vm)?;
4024            let mode = zelf.mode();
4025            let closefd = if zelf.closefd.load() { "True" } else { "False" };
4026            let repr = if let Some(name_repr) = name_repr {
4027                format!("<_io.FileIO name={name_repr} mode='{mode}' closefd={closefd}>")
4028            } else {
4029                format!("<_io.FileIO fd={fd} mode='{mode}' closefd={closefd}>")
4030            };
4031            Ok(repr)
4032        }
4033    }
4034
4035    #[pyclass(
4036        with(Constructor, Initializer, Representable),
4037        flags(BASETYPE, HAS_DICT)
4038    )]
4039    impl FileIO {
4040        fn io_error(
4041            zelf: &Py<Self>,
4042            error: std::io::Error,
4043            vm: &VirtualMachine,
4044        ) -> PyBaseExceptionRef {
4045            let exc = error.to_pyexception(vm);
4046            if let Ok(name) = zelf.as_object().get_attr("name", vm) {
4047                exc.as_object()
4048                    .set_attr("filename", name, vm)
4049                    .expect("OSError.filename set must success");
4050            }
4051            exc
4052        }
4053
4054        #[pygetset]
4055        fn closed(&self) -> bool {
4056            self.fd.load() < 0
4057        }
4058
4059        #[pygetset]
4060        fn closefd(&self) -> bool {
4061            self.closefd.load()
4062        }
4063
4064        #[pymethod]
4065        fn fileno(&self, vm: &VirtualMachine) -> PyResult<i32> {
4066            let fd = self.fd.load();
4067            if fd >= 0 {
4068                Ok(fd)
4069            } else {
4070                Err(io_closed_error(vm))
4071            }
4072        }
4073
4074        fn get_fd(&self, vm: &VirtualMachine) -> PyResult<Fd> {
4075            self.fileno(vm).map(Fd)
4076        }
4077
4078        #[pymethod]
4079        fn readable(&self) -> bool {
4080            self.mode.load().contains(Mode::READABLE)
4081        }
4082        #[pymethod]
4083        fn writable(&self) -> bool {
4084            self.mode.load().contains(Mode::WRITABLE)
4085        }
4086        #[pygetset]
4087        fn mode(&self) -> &'static str {
4088            let mode = self.mode.load();
4089            if mode.contains(Mode::CREATED) {
4090                if mode.contains(Mode::READABLE) {
4091                    "xb+"
4092                } else {
4093                    "xb"
4094                }
4095            } else if mode.contains(Mode::APPENDING) {
4096                if mode.contains(Mode::READABLE) {
4097                    "ab+"
4098                } else {
4099                    "ab"
4100                }
4101            } else if mode.contains(Mode::READABLE) {
4102                if mode.contains(Mode::WRITABLE) {
4103                    "rb+"
4104                } else {
4105                    "rb"
4106                }
4107            } else {
4108                "wb"
4109            }
4110        }
4111
4112        #[pymethod]
4113        fn read(
4114            zelf: &Py<Self>,
4115            read_byte: OptionalSize,
4116            vm: &VirtualMachine,
4117        ) -> PyResult<Vec<u8>> {
4118            if !zelf.mode.load().contains(Mode::READABLE) {
4119                return Err(new_unsupported_operation(
4120                    vm,
4121                    "File or stream is not readable".to_owned(),
4122                ));
4123            }
4124            let mut handle = zelf.get_fd(vm)?;
4125            let bytes = if let Some(read_byte) = read_byte.to_usize() {
4126                let mut bytes = vec![0; read_byte];
4127                let n = handle
4128                    .read(&mut bytes)
4129                    .map_err(|err| Self::io_error(zelf, err, vm))?;
4130                bytes.truncate(n);
4131                bytes
4132            } else {
4133                let mut bytes = vec![];
4134                handle
4135                    .read_to_end(&mut bytes)
4136                    .map_err(|err| Self::io_error(zelf, err, vm))?;
4137                bytes
4138            };
4139
4140            Ok(bytes)
4141        }
4142
4143        #[pymethod]
4144        fn readinto(zelf: &Py<Self>, obj: ArgMemoryBuffer, vm: &VirtualMachine) -> PyResult<usize> {
4145            if !zelf.mode.load().contains(Mode::READABLE) {
4146                return Err(new_unsupported_operation(
4147                    vm,
4148                    "File or stream is not readable".to_owned(),
4149                ));
4150            }
4151
4152            let handle = zelf.get_fd(vm)?;
4153
4154            let mut buf = obj.borrow_buf_mut();
4155            let mut f = handle.take(buf.len() as _);
4156            let ret = f
4157                .read(&mut buf)
4158                .map_err(|err| Self::io_error(zelf, err, vm))?;
4159
4160            Ok(ret)
4161        }
4162
4163        #[pymethod]
4164        fn write(zelf: &Py<Self>, obj: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> {
4165            if !zelf.mode.load().contains(Mode::WRITABLE) {
4166                return Err(new_unsupported_operation(
4167                    vm,
4168                    "File or stream is not writable".to_owned(),
4169                ));
4170            }
4171
4172            let mut handle = zelf.get_fd(vm)?;
4173
4174            let len = obj
4175                .with_ref(|b| handle.write(b))
4176                .map_err(|err| Self::io_error(zelf, err, vm))?;
4177
4178            //return number of bytes written
4179            Ok(len)
4180        }
4181
4182        #[pymethod]
4183        fn close(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> {
4184            let res = iobase_close(zelf.as_object(), vm);
4185            if !zelf.closefd.load() {
4186                zelf.fd.store(-1);
4187                return res;
4188            }
4189            let fd = zelf.fd.swap(-1);
4190            if fd >= 0 {
4191                Fd(fd)
4192                    .close()
4193                    .map_err(|err| Self::io_error(zelf, err, vm))?;
4194            }
4195            res
4196        }
4197
4198        #[pymethod]
4199        fn seekable(&self, vm: &VirtualMachine) -> PyResult<bool> {
4200            let fd = self.fileno(vm)?;
4201            Ok(self.seekable.load().unwrap_or_else(|| {
4202                let seekable = os::lseek(fd, 0, libc::SEEK_CUR, vm).is_ok();
4203                self.seekable.store(Some(seekable));
4204                seekable
4205            }))
4206        }
4207
4208        #[pymethod]
4209        fn seek(
4210            &self,
4211            offset: PyObjectRef,
4212            how: OptionalArg<i32>,
4213            vm: &VirtualMachine,
4214        ) -> PyResult<Offset> {
4215            let how = how.unwrap_or(0);
4216            let fd = self.fileno(vm)?;
4217            let offset = get_offset(offset, vm)?;
4218
4219            os::lseek(fd, offset, how, vm)
4220        }
4221
4222        #[pymethod]
4223        fn tell(&self, vm: &VirtualMachine) -> PyResult<Offset> {
4224            let fd = self.fileno(vm)?;
4225            os::lseek(fd, 0, libc::SEEK_CUR, vm)
4226        }
4227
4228        #[pymethod]
4229        fn truncate(&self, len: OptionalOption, vm: &VirtualMachine) -> PyResult<Offset> {
4230            let fd = self.fileno(vm)?;
4231            let len = match len.flatten() {
4232                Some(l) => get_offset(l, vm)?,
4233                None => os::lseek(fd, 0, libc::SEEK_CUR, vm)?,
4234            };
4235            os::ftruncate(fd, len, vm)?;
4236            Ok(len)
4237        }
4238
4239        #[pymethod]
4240        fn isatty(&self, vm: &VirtualMachine) -> PyResult<bool> {
4241            let fd = self.fileno(vm)?;
4242            Ok(os::isatty(fd))
4243        }
4244
4245        #[pymethod(magic)]
4246        fn reduce(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
4247            Err(vm.new_type_error(format!("cannot pickle '{}' object", zelf.class().name())))
4248        }
4249    }
4250}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.