1pub(crate) use _io::module_def;
5
6#[cfg(all(unix, feature = "threading", feature = "host_env"))]
7pub(crate) use _io::reinit_std_streams_after_fork;
8
9cfg_select! {
10 any(not(target_arch = "wasm32"), target_os = "wasi") => {
11 use rustpython_host_env::crt_fd::Offset;
12 const EAGAIN: i32 = libc::EAGAIN;
14 }
15 _ => {
16 type Offset = i64;
17 const EAGAIN: i32 = rustpython_host_env::errno::errors::EAGAIN;
19 }
20}
21
22use crate::{
23 AsObject, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
24 builtins::{PyModule, PyOSError},
25};
26pub use _io::{OpenArgs, io_open as open};
27use rustpython_host_env::io as host_io;
28
29fn file_closed(file: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
30 file.get_attr("closed", vm)?.try_to_bool(vm)
31}
32
33const DEFAULT_BUFFER_SIZE: usize = 128 * 1024;
34
35fn iobase_finalize(zelf: &PyObject, vm: &VirtualMachine) {
37 let closed = match vm.get_attribute_opt(zelf, "closed") {
40 Ok(Some(val)) => match val.try_to_bool(vm) {
41 Ok(b) => b,
42 Err(_) => return,
43 },
44 _ => return,
45 };
46 if !closed {
47 let _ = zelf.set_attr("_finalizing", vm.ctx.true_value.clone(), vm);
50 if let Err(e) = vm.call_method(zelf, "close", ()) {
51 if !e.fast_isinstance(vm.ctx.exceptions.broken_pipe_error) {
57 vm.run_unraisable(e, None, zelf.to_owned());
58 }
59 }
60 }
61}
62
63#[derive(Copy, Clone)]
65#[repr(transparent)]
66pub struct Fildes(pub i32);
67
68impl TryFromObject for Fildes {
69 fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
70 use crate::builtins::int;
71 let int = match obj.downcast::<int::PyInt>() {
72 Ok(i) => i,
73 Err(obj) => {
74 let fileno_meth = vm.get_attribute_opt(&obj, "fileno")?.ok_or_else(|| {
75 vm.new_type_error("argument must be an int, or have a fileno() method.")
76 })?;
77 fileno_meth
78 .call((), vm)?
79 .downcast()
80 .map_err(|_| vm.new_type_error("fileno() returned a non-integer"))?
81 }
82 };
83 let fd = int.try_to_primitive(vm)?;
84 if fd < 0 {
85 return Err(vm.new_value_error(format!(
86 "file descriptor cannot be a negative integer ({fd})"
87 )));
88 }
89 Ok(Self(fd))
90 }
91}
92
93impl From<Fildes> for i32 {
94 fn from(fildes: Fildes) -> Self {
95 fildes.0
96 }
97}
98
99#[cfg(unix)]
100impl std::os::fd::AsFd for Fildes {
101 fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
102 unsafe { std::os::fd::BorrowedFd::borrow_raw(self.0) }
105 }
106}
107
108#[cfg(unix)]
109impl std::os::fd::AsRawFd for Fildes {
110 fn as_raw_fd(&self) -> std::os::fd::RawFd {
111 self.0
112 }
113}
114
115#[pymodule]
116mod _io {
117 use super::*;
118 use crate::{
119 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
120 TryFromBorrowedObject, TryFromObject,
121 builtins::{
122 PyBaseExceptionRef, PyBool, PyByteArray, PyBytes, PyBytesRef, PyDict, PyMemoryView,
123 PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, PyUtf8Str, PyUtf8StrRef,
124 },
125 class::{PyClassDef, StaticType},
126 common::lock::{
127 PyMappedThreadMutexGuard, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard,
128 PyThreadMutex, PyThreadMutexGuard,
129 },
130 common::wtf8::{Wtf8, Wtf8Buf},
131 convert::ToPyObject,
132 exceptions::nul_char_error,
133 function::{
134 ArgBytesLike, ArgContiguousBytesLike, ArgIterable, ArgMemoryBuffer, Either, FsPath,
135 FuncArgs, IntoFuncArgs, OptionalArg, OptionalOption, PySetterValue, PySsize,
136 },
137 protocol::{
138 BufferDescriptor, BufferMethods, BufferResizeGuard, PyBuffer, PyIterReturn, VecBuffer,
139 },
140 recursion::ReprGuard,
141 types::{
142 Callable, Constructor, DefaultConstructor, Destructor, Initializer, IterNext, Iterable,
143 Representable,
144 },
145 vm::VirtualMachine,
146 };
147 use alloc::borrow::Cow;
148 use bstr::ByteSlice;
149 use core::{
150 hint::cold_path,
151 ops::Range,
152 sync::atomic::{AtomicBool, Ordering},
153 };
154 use crossbeam_utils::atomic::AtomicCell;
155 use malachite_bigint::BigInt;
156 use num_traits::ToPrimitive;
157 use std::io::{self, Cursor, SeekFrom, prelude::*};
158
159 fn validate_whence(whence: i32) -> bool {
160 host_io::validate_whence(whence)
161 }
162
163 fn ensure_unclosed(file: &PyObject, msg: &str, vm: &VirtualMachine) -> PyResult<()> {
164 if file.get_attr("closed", vm)?.try_to_bool(vm)? {
165 Err(vm.new_value_error(msg))
166 } else {
167 Ok(())
168 }
169 }
170
171 #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
176 fn trap_eintr<T>(result: PyResult<T>, vm: &VirtualMachine) -> PyResult<Option<T>> {
177 match result {
178 Ok(val) => Ok(Some(val)),
179 Err(exc) => {
180 if exc.fast_isinstance(vm.ctx.exceptions.os_error)
182 && let Ok(errno_attr) = exc.as_object().get_attr("errno", vm)
183 && let Ok(errno_val) = i32::try_from_object(vm, errno_attr)
184 && host_io::is_interrupted_errno(errno_val)
185 {
186 vm.check_signals()?;
187 return Ok(None);
188 }
189 Err(exc)
190 }
191 }
192 }
193
194 #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
196 fn trap_eintr<T>(result: PyResult<T>, _vm: &VirtualMachine) -> PyResult<Option<T>> {
197 result.map(Some)
198 }
199
200 pub(super) fn new_unsupported_operation<T: Into<String>>(
201 msg: T,
202 vm: &VirtualMachine,
203 ) -> PyBaseExceptionRef {
204 vm.new_os_subtype_error(unsupported_operation().to_owned(), None, msg.into())
205 .upcast()
206 }
207
208 fn _unsupported<T>(vm: &VirtualMachine, zelf: &PyObject, operation: &str) -> PyResult<T> {
209 Err(new_unsupported_operation(
210 format!("{}.{}() not supported", zelf.class().name(), operation),
211 vm,
212 ))
213 }
214
215 #[derive(Clone, Copy, FromArgs)]
216 pub(super) struct OptionalSize {
217 #[pyarg(positional, default, py_default = "-1")]
219 size: Option<PySsize>,
220 }
221
222 #[derive(Clone, Copy, FromArgs)]
224 struct OptionalPos {
225 #[pyarg(positional, optional)]
226 size: Option<PySsize>,
227 }
228
229 #[derive(Clone, Copy, FromArgs)]
230 struct StringIOPos {
231 #[pyarg(positional, optional)]
232 pos: Option<PySsize>,
233 }
234
235 impl StringIOPos {
236 fn as_optional(self) -> OptionalPos {
237 OptionalPos { size: self.pos }
238 }
239 }
240
241 impl OptionalPos {
242 fn try_usize(self, vm: &VirtualMachine) -> PyResult<Option<usize>> {
243 OptionalSize { size: self.size }.try_usize(vm)
244 }
245 }
246
247 #[derive(Clone, Copy, FromArgs)]
249 #[allow(dead_code)]
250 struct PeekSize {
251 #[pyarg(positional, default)]
252 size: isize,
253 }
254
255 #[derive(FromArgs)]
256 struct WhenceArg {
257 #[pyarg(positional, default)]
258 whence: i32,
259 }
260
261 #[derive(FromArgs)]
262 pub(super) struct HowArg {
263 #[pyarg(positional, default)]
264 pub whence: i32,
265 }
266
267 #[derive(FromArgs)]
268 #[allow(dead_code)]
269 struct IgnoredWhence {
270 #[pyarg(positional, default)]
271 whence: i32,
272 }
273
274 #[derive(FromArgs)]
275 #[allow(dead_code)]
276 struct IgnoredSize {
277 #[pyarg(positional, default = -1)]
278 size: isize,
279 }
280
281 #[derive(FromArgs)]
282 #[allow(dead_code)]
283 struct IgnoredPos {
284 #[pyarg(positional, optional)]
285 size: Option<PyObjectRef>,
286 }
287
288 #[derive(FromArgs)]
289 struct ReadlinesHint {
290 #[pyarg(positional, optional, py_default = "-1")]
292 hint: Option<isize>,
293 }
294
295 #[derive(FromArgs)]
296 struct ObjPos {
297 #[pyarg(positional, optional)]
298 pos: Option<PyObjectRef>,
299 }
300
301 #[cfg(feature = "host_env")]
302 #[derive(FromArgs)]
303 pub(super) struct ObjLen {
304 #[pyarg(positional, optional)]
305 pub size: Option<PyObjectRef>,
306 }
307
308 #[derive(FromArgs)]
310 struct KeepNonePos {
311 #[pyarg(positional, optional, py_default = "None")]
312 pos: OptionalArg<PyObjectRef>,
313 }
314
315 #[derive(FromArgs)]
316 struct StackLevelArg {
317 #[pyarg(positional, default = 2)]
318 stacklevel: i32,
319 }
320
321 #[cfg(all(feature = "host_env", windows))]
322 #[derive(FromArgs)]
323 pub(super) struct SignedSize {
324 #[pyarg(positional, default = -1)]
325 pub size: isize,
326 }
327
328 impl OptionalSize {
329 pub(super) fn to_usize(self) -> Option<usize> {
330 self.size?.to_usize()
331 }
332
333 pub(super) fn try_usize(self, vm: &VirtualMachine) -> PyResult<Option<usize>> {
334 self.size
335 .map(|v| {
336 if v >= 0 {
337 Ok(v as usize)
338 } else {
339 Err(vm.new_value_error(format!("Negative size value {v}")))
340 }
341 })
342 .transpose()
343 }
344 }
345
346 fn os_err(vm: &VirtualMachine, err: io::Error) -> PyBaseExceptionRef {
347 use crate::convert::ToPyException;
348 err.to_pyexception(vm)
349 }
350
351 pub(super) fn io_closed_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
352 vm.new_value_error("I/O operation on closed file")
353 }
354
355 #[pyattr]
356 const DEFAULT_BUFFER_SIZE: usize = super::DEFAULT_BUFFER_SIZE;
357
358 pub(super) fn seekfrom(
359 vm: &VirtualMachine,
360 offset: PyObjectRef,
361 how: i32,
362 ) -> PyResult<SeekFrom> {
363 let seek = match how {
364 0 => SeekFrom::Start(offset.try_into_value(vm)?),
365 1 => SeekFrom::Current(offset.try_into_value(vm)?),
366 2 => SeekFrom::End(offset.try_into_value(vm)?),
367 _ => return Err(vm.new_value_error("invalid value for how")),
368 };
369 Ok(seek)
370 }
371
372 #[derive(Debug)]
373 struct BufferedIO {
374 cursor: Cursor<Vec<u8>>,
375 }
376
377 impl BufferedIO {
378 const fn new(cursor: Cursor<Vec<u8>>) -> Self {
379 Self { cursor }
380 }
381
382 fn write(&mut self, data: &[u8]) -> Option<u64> {
383 if data.is_empty() {
384 return Some(0);
385 }
386 let length = data.len();
387 self.cursor.write_all(data).ok()?;
388 Some(length as u64)
389 }
390
391 fn getvalue(&self) -> Vec<u8> {
393 self.cursor.clone().into_inner()
394 }
395
396 fn seek(&mut self, seek: SeekFrom) -> io::Result<u64> {
398 self.cursor.seek(seek)
399 }
400
401 fn read(&mut self, bytes: Option<usize>) -> Option<Vec<u8>> {
403 let pos = self.cursor.position().to_usize()?;
404 let avail_slice = self.cursor.get_ref().get(pos..)?;
405 let n = bytes.map_or_else(
407 || avail_slice.len(),
408 |n| core::cmp::min(n, avail_slice.len()),
409 );
410 let b = avail_slice[..n].to_vec();
411 self.cursor.set_position((pos + n) as u64);
412 Some(b)
413 }
414
415 const fn tell(&self) -> u64 {
416 self.cursor.position()
417 }
418
419 fn readline(&mut self, size: Option<usize>, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
420 self.read_until(size, b'\n', vm)
421 }
422
423 fn read_until(
424 &mut self,
425 size: Option<usize>,
426 byte: u8,
427 vm: &VirtualMachine,
428 ) -> PyResult<Vec<u8>> {
429 let size = match size {
430 None => {
431 let mut buf: Vec<u8> = Vec::new();
432 self.cursor
433 .read_until(byte, &mut buf)
434 .map_err(|err| os_err(vm, err))?;
435 return Ok(buf);
436 }
437 Some(0) => {
438 return Ok(Vec::new());
439 }
440 Some(size) => size,
441 };
442
443 let available = {
444 let buf = self.cursor.fill_buf().map_err(|err| os_err(vm, err))?;
447 if size < buf.len() { &buf[..size] } else { buf }
448 };
449 let buf = match available.find_byte(byte) {
450 Some(i) => available[..=i].to_vec(),
451 _ => available.to_vec(),
452 };
453 self.cursor.consume(buf.len());
454 Ok(buf)
455 }
456
457 fn truncate(&mut self, pos: Option<usize>) -> usize {
458 let pos = pos.unwrap_or_else(|| self.tell() as usize);
459 self.cursor.get_mut().truncate(pos);
460 pos
461 }
462 }
463
464 fn check_closed(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
465 if file_closed(file, vm)? {
466 Err(io_closed_error(vm))
467 } else {
468 Ok(())
469 }
470 }
471
472 fn check_readable(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
473 if vm.call_method(file, "readable", ())?.try_to_bool(vm)? {
474 Ok(())
475 } else {
476 Err(new_unsupported_operation(
477 "File or stream is not readable",
478 vm,
479 ))
480 }
481 }
482
483 fn check_writable(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
484 if vm.call_method(file, "writable", ())?.try_to_bool(vm)? {
485 Ok(())
486 } else {
487 Err(new_unsupported_operation(
488 "File or stream is not writable.",
489 vm,
490 ))
491 }
492 }
493
494 fn check_seekable(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
495 if vm.call_method(file, "seekable", ())?.try_to_bool(vm)? {
496 Ok(())
497 } else {
498 Err(new_unsupported_operation(
499 "File or stream is not seekable",
500 vm,
501 ))
502 }
503 }
504
505 fn check_decoded(decoded: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
506 decoded.downcast().map_err(|obj| {
507 vm.new_type_error(format!(
508 "decoder should return a string result, not '{}'",
509 obj.class().name()
510 ))
511 })
512 }
513
514 #[pyattr]
515 #[pyclass(name = "_IOBase")]
516 #[derive(Debug, Default, PyPayload)]
517 #[repr(align(8))]
519 pub(super) struct _IOBase;
520
521 #[pyclass(
522 with(IterNext, Iterable, Destructor),
523 flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
524 )]
525 impl _IOBase {
526 #[pymethod]
527 fn seek(
528 zelf: PyObjectRef,
529 _offset: PyObjectRef,
530 _whence: IgnoredWhence,
531 vm: &VirtualMachine,
532 ) -> PyResult {
533 _unsupported(vm, &zelf, "seek")
534 }
535
536 #[pymethod]
537 fn tell(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
538 vm.call_method(&zelf, "seek", (0, 1))
539 }
540
541 #[pymethod]
542 fn truncate(zelf: PyObjectRef, _pos: IgnoredPos, vm: &VirtualMachine) -> PyResult {
543 _unsupported(vm, &zelf, "truncate")
544 }
545
546 #[pymethod]
547 fn fileno(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
548 _unsupported(vm, &zelf, "fileno")
549 }
550
551 #[pyattr]
552 fn __closed(ctx: &Context) -> PyRef<PyBool> {
553 ctx.new_bool(false)
554 }
555
556 #[pymethod]
557 fn __enter__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
558 check_closed(&zelf, vm)?;
559 Ok(zelf)
560 }
561
562 #[pymethod]
563 fn __exit__(zelf: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
564 vm.call_method(&zelf, "close", ())?;
565 Ok(())
566 }
567
568 #[pymethod]
569 fn flush(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
570 check_closed(&zelf, vm)
572 }
573
574 #[pymethod]
575 fn seekable(_zelf: PyObjectRef) -> bool {
576 false
577 }
578
579 #[pymethod]
580 fn readable(_zelf: PyObjectRef) -> bool {
581 false
582 }
583
584 #[pymethod]
585 fn writable(_zelf: PyObjectRef) -> bool {
586 false
587 }
588
589 #[pymethod]
590 fn isatty(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
591 check_closed(&zelf, vm)?;
592 Ok(false)
593 }
594
595 #[pygetset]
596 fn closed(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult {
597 instance.get_attr("__closed", vm)
598 }
599
600 #[pymethod]
601 fn close(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
602 iobase_close(&zelf, vm)
603 }
604
605 #[pymethod]
606 fn readline(
607 instance: PyObjectRef,
608 size: OptionalSize,
609 vm: &VirtualMachine,
610 ) -> PyResult<Vec<u8>> {
611 let size = size.to_usize();
612 let read = instance.get_attr("read", vm)?;
613 let mut res = Vec::new();
614 while size.is_none_or(|s| res.len() < s) {
615 let read_res = ArgBytesLike::try_from_object(vm, read.call((1,), vm)?)?;
616 if read_res.with_ref(|b| b.is_empty()) {
617 break;
618 }
619 read_res.with_ref(|b| res.extend_from_slice(b));
620 if res.ends_with(b"\n") {
621 break;
622 }
623 }
624 Ok(res)
625 }
626
627 #[pymethod]
628 fn readlines(
629 instance: PyObjectRef,
630 hint: ReadlinesHint,
631 vm: &VirtualMachine,
632 ) -> PyResult<Vec<PyObjectRef>> {
633 let hint = hint.hint.unwrap_or(-1);
634 if hint <= 0 {
635 return instance.try_to_value(vm);
636 }
637 let hint = hint as usize;
638 let mut ret = Vec::new();
639 let it = ArgIterable::<PyObjectRef>::try_from_object(vm, instance)?;
640 let mut full_len = 0;
641 for line in it.iter(vm)? {
642 let line = line?;
643 let line_len = line.length(vm)?;
644 ret.push(line);
645 full_len += line_len;
646 if full_len > hint {
647 break;
648 }
649 }
650 Ok(ret)
651 }
652
653 #[pymethod]
654 fn writelines(
655 instance: PyObjectRef,
656 lines: ArgIterable,
657 vm: &VirtualMachine,
658 ) -> PyResult<()> {
659 check_closed(&instance, vm)?;
660 for line in lines.iter(vm)? {
661 vm.call_method(&instance, "write", (line?,))?;
662 }
663 Ok(())
664 }
665
666 #[pymethod(name = "_checkClosed")]
667 fn check_closed(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
668 check_closed(&zelf, vm)
669 }
670
671 #[pymethod(name = "_checkReadable")]
672 fn check_readable(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
673 check_readable(&zelf, vm)
674 }
675
676 #[pymethod(name = "_checkWritable")]
677 fn check_writable(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
678 check_writable(&zelf, vm)
679 }
680
681 #[pymethod(name = "_checkSeekable")]
682 fn check_seekable(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
683 check_seekable(&zelf, vm)
684 }
685 }
686
687 impl Destructor for _IOBase {
688 fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
689 let _ = vm.call_method(zelf, "close", ());
695 Ok(())
696 }
697
698 #[cold]
699 fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> {
700 unreachable!("slot_del is implemented")
701 }
702 }
703
704 impl Iterable for _IOBase {
705 fn slot_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
706 check_closed(&zelf, vm)?;
707 Ok(zelf)
708 }
709
710 fn iter(_zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult {
711 unreachable!("slot_iter is implemented")
712 }
713 }
714
715 impl IterNext for _IOBase {
716 fn slot_iternext(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
717 let line = vm.call_method(zelf, "readline", ())?;
718 Ok(if !line.try_to_bool(vm)? {
719 PyIterReturn::StopIteration(None)
720 } else {
721 PyIterReturn::Return(line)
722 })
723 }
724
725 fn next(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyIterReturn> {
726 unreachable!("slot_iternext is implemented")
727 }
728 }
729
730 pub(super) fn iobase_close(file: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
731 if !file_closed(file, vm)? {
732 let res = vm.call_method(file, "flush", ());
733 file.set_attr("__closed", vm.new_pyobj(true), vm)?;
734 res?;
735 }
736 Ok(())
737 }
738
739 #[pyattr]
740 #[pyclass(name = "_RawIOBase", base = _IOBase)]
741 #[derive(Debug, Default)]
742 #[repr(transparent)]
743 pub(super) struct _RawIOBase(_IOBase);
744
745 #[pyclass(flags(BASETYPE, HAS_DICT, HAS_WEAKREF))]
746 impl _RawIOBase {
747 #[pymethod]
748 fn read(zelf: PyObjectRef, size: OptionalSize, vm: &VirtualMachine) -> PyResult {
749 if let Some(size) = size.to_usize() {
750 let b = PyByteArray::from(vm.new_zeroed_bytes(size)?).into_ref(&vm.ctx);
751 let n = <Option<isize>>::try_from_object(
752 vm,
753 vm.call_method(&zelf, "readinto", (b.clone(),))?,
754 )?;
755 Ok(match n {
756 None => vm.ctx.none(),
757 Some(n) => {
758 if n < 0 || (n as usize) > size {
760 return Err(vm.new_value_error(format!(
761 "readinto returned {n} outside buffer size {size}"
762 )));
763 }
764 let n = n as usize;
765 let mut bytes = b.borrow_buf_mut();
766 bytes.truncate(n);
767 bytes.clone().to_pyobject(vm)
769 }
770 })
771 } else {
772 vm.call_method(&zelf, "readall", ())
773 }
774 }
775
776 #[pymethod]
777 fn readall(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<Option<Vec<u8>>> {
778 let mut chunks = Vec::new();
779 let mut total_len = 0;
780 loop {
781 let data = loop {
783 let res = vm.call_method(&zelf, "read", (DEFAULT_BUFFER_SIZE,));
784 match trap_eintr(res, vm)? {
785 Some(val) => break val,
786 None => continue,
787 }
788 };
789 let data = <Option<PyBytesRef>>::try_from_object(vm, data)?;
790 match data {
791 None => {
792 if chunks.is_empty() {
793 return Ok(None);
794 }
795 break;
796 }
797 Some(b) => {
798 if b.as_bytes().is_empty() {
799 break;
800 }
801 total_len += b.as_bytes().len();
802 chunks.push(b)
803 }
804 }
805 }
806 let mut ret = Vec::with_capacity(total_len);
807 for b in chunks {
808 ret.extend_from_slice(b.as_bytes())
809 }
810 Ok(Some(ret))
811 }
812
813 #[pymethod]
814 fn readinto(_zelf: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
815 Err(vm.new_not_implemented_error(String::new()))
816 }
817
818 #[pymethod]
819 fn write(_zelf: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
820 Err(vm.new_not_implemented_error(String::new()))
821 }
822 }
823
824 #[pyattr]
825 #[pyclass(name = "_BufferedIOBase", base = _IOBase)]
826 #[derive(Debug, Default)]
827 #[repr(transparent)]
828 struct _BufferedIOBase(_IOBase);
829
830 #[pyclass(flags(BASETYPE, HAS_WEAKREF))]
831 impl _BufferedIOBase {
832 #[pymethod]
833 fn read(zelf: PyObjectRef, _size: IgnoredSize, vm: &VirtualMachine) -> PyResult {
834 _unsupported(vm, &zelf, "read")
835 }
836
837 #[pymethod]
838 fn read1(zelf: PyObjectRef, _size: IgnoredSize, vm: &VirtualMachine) -> PyResult {
839 _unsupported(vm, &zelf, "read1")
840 }
841
842 fn _readinto(
843 zelf: &PyObject,
844 buf_obj: &PyObject,
845 method: &str,
846 vm: &VirtualMachine,
847 ) -> PyResult<usize> {
848 let b = ArgMemoryBuffer::try_from_borrowed_object(vm, buf_obj)?;
849 let l = b.len();
850 let data = vm.call_method(zelf, method, (l,))?;
851 if data.is(buf_obj) {
852 return Ok(l);
853 }
854 let mut buf = b.borrow_buf_mut();
855 let data = ArgBytesLike::try_from_object(vm, data)?;
856 let data = data.borrow_buf();
857 match buf.get_mut(..data.len()) {
858 Some(slice) => {
859 slice.copy_from_slice(&data);
860 Ok(data.len())
861 }
862 None => {
863 Err(vm.new_value_error("readinto: buffer and read data have different lengths"))
864 }
865 }
866 }
867 #[pymethod]
868 fn readinto(
869 zelf: PyObjectRef,
870 buffer: PyObjectRef,
871 vm: &VirtualMachine,
872 ) -> PyResult<usize> {
873 Self::_readinto(&zelf, &buffer, "read", vm)
874 }
875
876 #[pymethod]
877 fn readinto1(
878 zelf: PyObjectRef,
879 buffer: PyObjectRef,
880 vm: &VirtualMachine,
881 ) -> PyResult<usize> {
882 Self::_readinto(&zelf, &buffer, "read1", vm)
883 }
884
885 #[pymethod]
886 fn write(zelf: PyObjectRef, _b: PyObjectRef, vm: &VirtualMachine) -> PyResult {
887 _unsupported(vm, &zelf, "write")
888 }
889
890 #[pymethod]
891 fn detach(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
892 _unsupported(vm, &zelf, "detach")
893 }
894 }
895
896 #[pyattr]
898 #[pyclass(name = "_TextIOBase", base = _IOBase)]
899 #[derive(Debug, Default)]
900 #[repr(transparent)]
901 struct _TextIOBase(_IOBase);
902
903 #[pyclass(flags(BASETYPE, HAS_WEAKREF))]
904 impl _TextIOBase {
905 #[pymethod]
906 fn read(zelf: PyObjectRef, _size: IgnoredSize, vm: &VirtualMachine) -> PyResult {
907 _unsupported(vm, &zelf, "read")
908 }
909
910 #[pymethod]
911 fn write(zelf: PyObjectRef, _s: PyObjectRef, vm: &VirtualMachine) -> PyResult {
912 _unsupported(vm, &zelf, "write")
913 }
914
915 #[pymethod]
916 fn truncate(zelf: PyObjectRef, _pos: IgnoredPos, vm: &VirtualMachine) -> PyResult {
917 _unsupported(vm, &zelf, "truncate")
918 }
919
920 #[pymethod]
921 fn readline(zelf: PyObjectRef, _size: IgnoredSize, vm: &VirtualMachine) -> PyResult {
922 _unsupported(vm, &zelf, "readline")
923 }
924
925 #[pymethod]
926 fn detach(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
927 _unsupported(vm, &zelf, "detach")
928 }
929
930 #[pygetset]
931 fn encoding(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
932 vm.ctx.none()
933 }
934
935 #[pygetset]
936 fn newlines(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
937 vm.ctx.none()
938 }
939
940 #[pygetset]
941 fn errors(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
942 vm.ctx.none()
943 }
944 }
945
946 #[derive(FromArgs, Clone)]
947 struct BufferSize {
948 #[pyarg(any, default = 131072)]
950 buffer_size: isize,
951 }
952
953 #[derive(FromArgs)]
954 struct BufferedInitArgs {
955 #[pyarg(any)]
956 raw: PyObjectRef,
957 #[pyarg(any, default = 131072)]
958 buffer_size: isize,
959 }
960
961 #[derive(FromArgs)]
962 struct BufferedRWPairArgs {
963 #[pyarg(positional)]
964 reader: PyObjectRef,
965 #[pyarg(positional)]
966 writer: PyObjectRef,
967 #[pyarg(positional, default = 131072)]
968 buffer_size: isize,
969 }
970
971 bitflags::bitflags! {
972 #[derive(Copy, Clone, Debug, PartialEq, Default)]
973 struct BufferedFlags: u8 {
974 const DETACHED = 1 << 0;
975 const WRITABLE = 1 << 1;
976 const READABLE = 1 << 2;
977 }
978 }
979
980 type RawCell = crate::object::PyAtomicRef<Option<PyObject>>;
981
982 #[derive(Debug, Default)]
983 struct BufferedData {
984 flags: BufferedFlags,
985 abs_pos: Offset,
986 buffer: Vec<u8>,
987 pos: Offset,
988 raw_pos: Offset,
989 read_end: Offset,
990 write_pos: Offset,
991 write_end: Offset,
992 }
993
994 impl BufferedData {
995 fn missing_raw_msg(&self) -> &'static str {
996 if self.flags.contains(BufferedFlags::DETACHED) {
997 "raw stream has been detached"
998 } else {
999 "I/O operation on uninitialized object"
1000 }
1001 }
1002
1003 fn check_init<'a>(&self, raw: &'a RawCell, vm: &VirtualMachine) -> PyResult<&'a PyObject> {
1004 raw.deref()
1005 .ok_or_else(|| vm.new_value_error(self.missing_raw_msg()))
1006 }
1007
1008 fn raw_owned(&self, raw: &RawCell, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1009 raw.load_owned()
1010 .ok_or_else(|| vm.new_value_error(self.missing_raw_msg()))
1011 }
1012
1013 #[inline]
1014 const fn writable(&self) -> bool {
1015 self.flags.contains(BufferedFlags::WRITABLE)
1016 }
1017
1018 #[inline]
1019 const fn readable(&self) -> bool {
1020 self.flags.contains(BufferedFlags::READABLE)
1021 }
1022
1023 #[inline]
1024 const fn valid_read(&self) -> bool {
1025 self.readable() && self.read_end != -1
1026 }
1027
1028 #[inline]
1029 const fn valid_write(&self) -> bool {
1030 self.writable() && self.write_end != -1
1031 }
1032
1033 #[inline]
1034 const fn raw_offset(&self) -> Offset {
1035 if (self.valid_read() || self.valid_write()) && self.raw_pos >= 0 {
1036 self.raw_pos - self.pos
1037 } else {
1038 0
1039 }
1040 }
1041
1042 #[inline]
1043 const fn readahead(&self) -> Offset {
1044 if self.valid_read() {
1045 self.read_end - self.pos
1046 } else {
1047 0
1048 }
1049 }
1050
1051 const fn reset_read(&mut self) {
1052 self.read_end = -1;
1053 }
1054
1055 const fn reset_write(&mut self) {
1056 self.write_pos = 0;
1057 self.write_end = -1;
1058 }
1059
1060 fn flush(&mut self, raw: &RawCell, vm: &VirtualMachine) -> PyResult<()> {
1061 if !self.valid_write() || self.write_pos == self.write_end {
1062 self.reset_write();
1063 return Ok(());
1064 }
1065
1066 let rewind = self.raw_offset() + (self.pos - self.write_pos);
1067 if rewind != 0 {
1068 self.raw_seek(raw, -rewind, 1, vm)?;
1069 self.raw_pos -= rewind;
1070 }
1071
1072 while self.write_pos < self.write_end {
1073 let n = self.raw_write(
1074 raw,
1075 None,
1076 self.write_pos as usize..self.write_end as usize,
1077 vm,
1078 )?;
1079 let n = match n {
1080 Some(n) => n,
1081 None => {
1082 return Err(vm
1084 .new_payload_exception::<PyOSError>(
1085 vm.ctx.exceptions.blocking_io_error.to_owned(),
1086 vec![
1087 vm.new_pyobj(EAGAIN),
1088 vm.new_pyobj("write could not complete without blocking"),
1089 vm.new_pyobj(0),
1090 ]
1091 .into(),
1092 )?
1093 .upcast());
1094 }
1095 };
1096 self.write_pos += n as Offset;
1097 self.raw_pos = self.write_pos;
1098 vm.check_signals()?;
1099 }
1100
1101 self.reset_write();
1102
1103 Ok(())
1104 }
1105
1106 fn flush_rewind(&mut self, raw: &RawCell, vm: &VirtualMachine) -> PyResult<()> {
1107 self.flush(raw, vm)?;
1108 if self.readable() {
1109 let res = self.raw_seek(raw, -self.raw_offset(), 1, vm);
1110 self.reset_read();
1111 res?;
1112 }
1113 Ok(())
1114 }
1115
1116 fn raw_seek(
1117 &mut self,
1118 raw: &RawCell,
1119 pos: Offset,
1120 whence: i32,
1121 vm: &VirtualMachine,
1122 ) -> PyResult<Offset> {
1123 let ret = vm.call_method(self.check_init(raw, vm)?, "seek", (pos, whence))?;
1124 let offset = get_offset(&ret, vm)?;
1125 if offset < 0 {
1126 return Err(
1127 vm.new_os_error(format!("Raw stream returned invalid position {offset}"))
1128 );
1129 }
1130 self.abs_pos = offset;
1131 Ok(offset)
1132 }
1133
1134 fn seek(
1135 &mut self,
1136 raw: &RawCell,
1137 target: Offset,
1138 whence: i32,
1139 vm: &VirtualMachine,
1140 ) -> PyResult<Offset> {
1141 if matches!(whence, 0 | 1) && self.readable() {
1142 let current = self.raw_tell_cache(raw, vm)?;
1143 let available = self.readahead();
1144 if available > 0 {
1145 let offset = if whence == 0 {
1146 target - (current - self.raw_offset())
1147 } else {
1148 target
1149 };
1150 if offset >= -self.pos && offset <= available {
1151 self.pos += offset;
1152 let result = current - available + offset;
1155 return Ok(if result < 0 { 0 } else { result });
1156 }
1157 }
1158 }
1159 if self.writable() {
1161 self.flush(raw, vm)?;
1162 }
1163 let target = if whence == 1 {
1164 target - self.raw_offset()
1165 } else {
1166 target
1167 };
1168 let res = self.raw_seek(raw, target, whence, vm);
1169 self.raw_pos = -1;
1170 if res.is_ok() && self.readable() {
1171 self.reset_read();
1172 }
1173 res
1174 }
1175
1176 fn raw_tell(&mut self, raw: &RawCell, vm: &VirtualMachine) -> PyResult<Offset> {
1177 let raw_obj = self.check_init(raw, vm)?;
1178 let ret = vm.call_method(raw_obj, "tell", ())?;
1179 let offset = get_offset(&ret, vm)?;
1180 if offset < 0 {
1181 return Err(
1182 vm.new_os_error(format!("Raw stream returned invalid position {offset}"))
1183 );
1184 }
1185 self.abs_pos = offset;
1186 Ok(offset)
1187 }
1188
1189 fn raw_tell_cache(&mut self, raw: &RawCell, vm: &VirtualMachine) -> PyResult<Offset> {
1190 if self.abs_pos == -1 {
1191 self.raw_tell(raw, vm)
1192 } else {
1193 Ok(self.abs_pos)
1194 }
1195 }
1196
1197 fn raw_write(
1199 &mut self,
1200 raw: &RawCell,
1201 buf: Option<PyBuffer>,
1202 buf_range: Range<usize>,
1203 vm: &VirtualMachine,
1204 ) -> PyResult<Option<usize>> {
1205 let len = buf_range.len();
1206
1207 let (mem_obj, write_buf) = if let Some(buf) = buf {
1210 let mem_obj =
1211 PyMemoryView::from_buffer_range(buf, buf_range, vm)?.into_ref(&vm.ctx);
1212 (mem_obj, None)
1213 } else {
1214 let v = core::mem::take(&mut self.buffer);
1215 let wb = VecBuffer::from(v).into_ref(&vm.ctx);
1216 let mem_obj =
1217 PyMemoryView::from_buffer_range(wb.clone().into_pybuffer(true), buf_range, vm)?
1218 .into_ref(&vm.ctx);
1219 (mem_obj, Some(wb))
1220 };
1221
1222 let res = loop {
1224 let res = vm.call_method(self.check_init(raw, vm)?, "write", (mem_obj.clone(),));
1225 match trap_eintr(res, vm) {
1226 Ok(Some(val)) => break Ok(val),
1227 Ok(None) => continue,
1228 Err(e) => break Err(e),
1229 }
1230 };
1231
1232 if let Some(wb) = write_buf {
1234 mem_obj.release();
1235 self.buffer = wb.take();
1236 }
1237
1238 let res = res?;
1239
1240 if vm.is_none(&res) {
1241 return Ok(None);
1242 }
1243 let n = isize::try_from_object(vm, res)?;
1244 if n < 0 || n as usize > len {
1245 return Err(vm.new_os_error(format!(
1246 "raw write() returned invalid length {n} (should have been between 0 and {len})"
1247 )));
1248 }
1249 if self.abs_pos != -1 {
1250 self.abs_pos += n as Offset
1251 }
1252 Ok(Some(n as usize))
1253 }
1254
1255 fn write(
1256 &mut self,
1257 raw: &RawCell,
1258 obj: ArgBytesLike,
1259 vm: &VirtualMachine,
1260 ) -> PyResult<usize> {
1261 if !self.valid_read() && !self.valid_write() {
1262 self.pos = 0;
1263 self.raw_pos = 0;
1264 }
1265 let avail = self.buffer.len() - self.pos as usize;
1266 let buf_len;
1267 {
1268 let buf = obj.borrow_buf();
1269 buf_len = buf.len();
1270 if buf.len() <= avail {
1271 self.buffer[self.pos as usize..][..buf.len()].copy_from_slice(&buf);
1272 if !self.valid_write() || self.write_pos > self.pos {
1273 self.write_pos = self.pos
1274 }
1275 self.adjust_position(self.pos + buf.len() as Offset);
1276 if self.pos > self.write_end {
1277 self.write_end = self.pos
1278 }
1279 return Ok(buf.len());
1280 }
1281 }
1282
1283 match self.flush(raw, vm) {
1286 Ok(()) => {}
1287 Err(e) if e.fast_isinstance(vm.ctx.exceptions.blocking_io_error) => {
1288 if self.readable() {
1289 self.reset_read();
1290 }
1291 let shift = self.write_pos;
1293 if shift > 0 {
1294 self.buffer
1295 .copy_within(shift as usize..self.write_end as usize, 0);
1296 self.write_end -= shift;
1297 self.raw_pos -= shift;
1298 self.pos -= shift;
1299 self.write_pos = 0;
1300 }
1301 let avail = self.buffer.len() - self.write_end as usize;
1302 if buf_len <= avail {
1303 let buf = obj.borrow_buf();
1305 self.buffer[self.write_end as usize..][..buf_len].copy_from_slice(&buf);
1306 self.write_end += buf_len as Offset;
1307 self.pos += buf_len as Offset;
1308 return Ok(buf_len);
1309 }
1310 let buf = obj.borrow_buf();
1312 self.buffer[self.write_end as usize..][..avail].copy_from_slice(&buf[..avail]);
1313 self.write_end += avail as Offset;
1314 self.pos += avail as Offset;
1315 return Err(vm
1316 .new_payload_exception::<PyOSError>(
1317 vm.ctx.exceptions.blocking_io_error.to_owned(),
1318 vec![
1319 vm.new_pyobj(EAGAIN),
1320 vm.new_pyobj("write could not complete without blocking"),
1321 vm.new_pyobj(avail),
1322 ]
1323 .into(),
1324 )?
1325 .upcast());
1326 }
1327 Err(e) => return Err(e),
1328 }
1329
1330 let offset = self.raw_offset();
1332 if offset != 0 {
1333 self.raw_seek(raw, -offset, 1, vm)?;
1334 self.raw_pos -= offset;
1335 }
1336
1337 let mut remaining = buf_len;
1338 let mut written = 0;
1339 let buffer: PyBuffer = obj.into();
1340 while remaining > self.buffer.len() {
1341 let res = self.raw_write(raw, Some(buffer.clone()), written..buf_len, vm)?;
1342 match res {
1343 Some(n) => {
1344 written += n;
1345 if let Some(r) = remaining.checked_sub(n) {
1346 remaining = r
1347 } else {
1348 break;
1349 }
1350 vm.check_signals()?;
1351 }
1352 None if remaining > self.buffer.len() => {
1353 let buf = buffer.as_contiguous().unwrap();
1356 let buffer_len = self.buffer.len();
1357 self.buffer.copy_from_slice(&buf[written..][..buffer_len]);
1358 self.raw_pos = 0;
1359 let buffer_size = self.buffer.len() as _;
1360 self.adjust_position(buffer_size);
1361 self.write_end = buffer_size;
1362 let chars_written = written + buffer_len;
1364 return Err(vm
1365 .new_payload_exception::<PyOSError>(
1366 vm.ctx.exceptions.blocking_io_error.to_owned(),
1367 vec![
1368 vm.new_pyobj(EAGAIN),
1369 vm.new_pyobj("write could not complete without blocking"),
1370 vm.new_pyobj(chars_written),
1371 ]
1372 .into(),
1373 )?
1374 .upcast());
1375 }
1376 None => break,
1377 }
1378 }
1379
1380 if self.readable() {
1381 self.reset_read();
1382 }
1383
1384 if remaining > 0 {
1385 let buf = buffer.as_contiguous().unwrap();
1386 self.buffer[..remaining].copy_from_slice(&buf[written..][..remaining]);
1387 written += remaining;
1388 }
1389
1390 self.write_pos = 0;
1391 self.write_end = remaining as _;
1392 self.adjust_position(remaining as _);
1393 self.raw_pos = 0;
1394
1395 Ok(written)
1396 }
1397
1398 fn active_read_slice(&self) -> &[u8] {
1399 &self.buffer[self.pos as usize..][..self.readahead() as usize]
1400 }
1401
1402 fn read_fast(&mut self, n: usize) -> Option<Vec<u8>> {
1403 let ret = self.active_read_slice().get(..n)?.to_vec();
1404 self.pos += n as Offset;
1405 Some(ret)
1406 }
1407
1408 fn read_generic(
1409 &mut self,
1410 raw: &RawCell,
1411 n: usize,
1412 vm: &VirtualMachine,
1413 ) -> PyResult<Option<Vec<u8>>> {
1414 if let Some(fast) = self.read_fast(n) {
1415 return Ok(Some(fast));
1416 }
1417
1418 let current_size = self.readahead() as usize;
1419
1420 let mut out = vm.new_zeroed_bytes(n)?;
1421 let mut remaining = n;
1422 let mut written = 0;
1423 if current_size > 0 {
1424 let slice = self.active_read_slice();
1425 out[..slice.len()].copy_from_slice(slice);
1426 remaining -= current_size;
1427 written += current_size;
1428 self.pos += current_size as Offset;
1429 }
1430 if self.writable() {
1431 self.flush_rewind(raw, vm)?;
1432 }
1433 self.reset_read();
1434 macro_rules! handle_opt_read {
1435 ($x:expr) => {
1436 match ($x, written > 0) {
1437 (Some(0), _) | (None, true) => {
1438 out.truncate(written);
1439 return Ok(Some(out));
1440 }
1441 (Some(r), _) => r,
1442 (None, _) => return Ok(None),
1443 }
1444 };
1445 }
1446 while remaining > 0 && !self.buffer.is_empty() {
1447 let r = self.buffer.len() * (remaining / self.buffer.len());
1449 if r == 0 {
1450 break;
1451 }
1452 let r = self.raw_read(raw, Either::A(Some(&mut out)), written..written + r, vm)?;
1453 let r = handle_opt_read!(r);
1454 remaining -= r;
1455 written += r;
1456 }
1457 self.pos = 0;
1458 self.raw_pos = 0;
1459 self.read_end = 0;
1460
1461 while remaining > 0 && (self.read_end as usize) < self.buffer.len() {
1462 let r = handle_opt_read!(self.fill_buffer(raw, vm)?);
1463 if remaining > r {
1464 out[written..][..r].copy_from_slice(&self.buffer[self.pos as usize..][..r]);
1465 written += r;
1466 self.pos += r as Offset;
1467 remaining -= r;
1468 } else if remaining > 0 {
1469 out[written..][..remaining]
1470 .copy_from_slice(&self.buffer[self.pos as usize..][..remaining]);
1471 written += remaining;
1472 self.pos += remaining as Offset;
1473 remaining = 0;
1474 }
1475 if remaining == 0 {
1476 break;
1477 }
1478 }
1479
1480 Ok(Some(out))
1481 }
1482
1483 fn fill_buffer(&mut self, raw: &RawCell, vm: &VirtualMachine) -> PyResult<Option<usize>> {
1484 let start = if self.valid_read() {
1485 self.read_end as usize
1486 } else {
1487 0
1488 };
1489 let buf_end = self.buffer.len();
1490 let res = self.raw_read(raw, Either::A(None), start..buf_end, vm)?;
1491 if let Some(n) = res.filter(|n| *n > 0) {
1492 let new_start = (start + n) as Offset;
1493 self.read_end = new_start;
1494 self.raw_pos = new_start;
1495 }
1496 Ok(res)
1497 }
1498
1499 fn raw_read(
1500 &mut self,
1501 raw: &RawCell,
1502 v: Either<Option<&mut Vec<u8>>, PyBuffer>,
1503 buf_range: Range<usize>,
1504 vm: &VirtualMachine,
1505 ) -> PyResult<Option<usize>> {
1506 let len = buf_range.len();
1507 let raw_obj = self.check_init(raw, vm)?;
1508 let res = match v {
1509 Either::A(v) => {
1510 let v = v.unwrap_or(&mut self.buffer);
1511 let read_buf = VecBuffer::from(core::mem::take(v)).into_ref(&vm.ctx);
1512 let mem_obj = PyMemoryView::from_buffer_range(
1513 read_buf.clone().into_pybuffer(false),
1514 buf_range,
1515 vm,
1516 )?
1517 .into_ref(&vm.ctx);
1518
1519 let res = loop {
1521 let res = vm.call_method(raw_obj, "readinto", (mem_obj.clone(),));
1522 match trap_eintr(res, vm) {
1523 Ok(Some(val)) => break Ok(val),
1524 Ok(None) => continue, Err(e) => break Err(e),
1526 }
1527 };
1528
1529 mem_obj.release();
1530 *v = read_buf.take();
1532
1533 res?
1534 }
1535 Either::B(buf) => {
1536 let mem_obj =
1537 PyMemoryView::from_buffer_range(buf, buf_range, vm)?.into_ref(&vm.ctx);
1538 loop {
1540 let res = vm.call_method(raw_obj, "readinto", (mem_obj.clone(),));
1541 match trap_eintr(res, vm)? {
1542 Some(val) => break val,
1543 None => continue,
1544 }
1545 }
1546 }
1547 };
1548
1549 if vm.is_none(&res) {
1550 return Ok(None);
1551 }
1552 let (n, type_error) = match isize::try_from_object(vm, res) {
1554 Ok(n) => (n, None),
1555 Err(e) => (-1, Some(e)),
1556 };
1557 if n < 0 || n as usize > len {
1558 let os_error = vm.new_os_error(format!(
1559 "raw readinto() returned invalid length {n} (should have been between 0 and {len})"
1560 ));
1561 if let Some(cause) = type_error {
1562 os_error.set_cause(Some(cause));
1563 }
1564 return Err(os_error);
1565 }
1566 if n > 0 && self.abs_pos != -1 {
1567 self.abs_pos += n as Offset
1568 }
1569 Ok(Some(n as usize))
1570 }
1571
1572 fn read_all(&mut self, raw: &RawCell, vm: &VirtualMachine) -> PyResult<Option<PyBytesRef>> {
1573 let buf = self.active_read_slice();
1574 let data = if buf.is_empty() {
1575 None
1576 } else {
1577 let b = buf.to_vec();
1578 self.pos += buf.len() as Offset;
1579 Some(b)
1580 };
1581
1582 if self.writable() {
1583 self.flush_rewind(raw, vm)?;
1584 }
1585
1586 let readall = vm
1587 .get_str_method(self.raw_owned(raw, vm)?, "readall")
1588 .transpose()?;
1589 if let Some(readall) = readall {
1590 let res = readall.call((), vm)?;
1591 let res = <Option<PyBytesRef>>::try_from_object(vm, res)?;
1592 let ret = if let Some(mut data) = data {
1593 if let Some(bytes) = res {
1594 data.extend_from_slice(bytes.as_bytes());
1595 }
1596 Some(PyBytes::from(data).into_ref(&vm.ctx))
1597 } else {
1598 res
1599 };
1600 return Ok(ret);
1601 }
1602
1603 let mut chunks = Vec::new();
1604
1605 let mut read_size = 0;
1606 loop {
1607 let read_data = loop {
1609 let res = vm.call_method(self.check_init(raw, vm)?, "read", ());
1610 match trap_eintr(res, vm)? {
1611 Some(val) => break val,
1612 None => continue,
1613 }
1614 };
1615 let read_data = <Option<PyBytesRef>>::try_from_object(vm, read_data)?;
1616
1617 match read_data {
1618 Some(b) if !b.as_bytes().is_empty() => {
1619 let l = b.as_bytes().len();
1620 read_size += l;
1621 if self.abs_pos != -1 {
1622 self.abs_pos += l as Offset;
1623 }
1624 chunks.push(b);
1625 }
1626 read_data => {
1627 let ret = if data.is_none() && read_size == 0 {
1628 read_data
1629 } else {
1630 let mut data = data.unwrap_or_default();
1631 data.reserve(read_size);
1632 for bytes in &chunks {
1633 data.extend_from_slice(bytes.as_bytes())
1634 }
1635 Some(PyBytes::from(data).into_ref(&vm.ctx))
1636 };
1637 break Ok(ret);
1638 }
1639 }
1640 }
1641 }
1642
1643 const fn adjust_position(&mut self, new_pos: Offset) {
1644 self.pos = new_pos;
1645 if self.valid_read() && self.read_end < self.pos {
1646 self.read_end = self.pos
1647 }
1648 }
1649
1650 fn peek(&mut self, raw: &RawCell, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
1651 let have = self.readahead();
1652 let slice = if have > 0 {
1653 &self.buffer[self.pos as usize..][..have as usize]
1654 } else {
1655 self.reset_read();
1656 let r = self.fill_buffer(raw, vm)?.unwrap_or(0);
1657 self.pos = 0;
1658 &self.buffer[..r]
1659 };
1660 Ok(slice.to_vec())
1661 }
1662
1663 fn readinto_generic(
1664 &mut self,
1665 raw: &RawCell,
1666 buf: PyBuffer,
1667 readinto1: bool,
1668 vm: &VirtualMachine,
1669 ) -> PyResult<Option<usize>> {
1670 let mut written = 0;
1671 let n = self.readahead();
1672 let buf_len;
1673 {
1674 let mut b = buf.as_contiguous_mut().unwrap();
1675 buf_len = b.len();
1676 if n > 0 {
1677 if n as usize >= b.len() {
1678 b.copy_from_slice(&self.buffer[self.pos as usize..][..buf_len]);
1679 self.pos += buf_len as Offset;
1680 return Ok(Some(buf_len));
1681 }
1682 b[..n as usize]
1683 .copy_from_slice(&self.buffer[self.pos as usize..][..n as usize]);
1684 self.pos += n;
1685 written = n as usize;
1686 }
1687 }
1688 if self.writable() {
1689 self.flush_rewind(raw, vm)?;
1690 }
1691 self.reset_read();
1692 self.pos = 0;
1693
1694 let mut remaining = buf_len - written;
1695 while remaining > 0 {
1696 let n = if remaining > self.buffer.len() {
1697 self.raw_read(
1698 raw,
1699 Either::B(buf.clone()),
1700 written..written + remaining,
1701 vm,
1702 )?
1703 } else if !(readinto1 && written != 0) {
1704 let n = self.fill_buffer(raw, vm)?;
1705 if let Some(n) = n.filter(|&n| n > 0) {
1706 let n = core::cmp::min(n, remaining);
1707 buf.as_contiguous_mut().unwrap()[written..][..n]
1708 .copy_from_slice(&self.buffer[self.pos as usize..][..n]);
1709 self.pos += n as Offset;
1710 written += n;
1711 remaining -= n;
1712 continue;
1713 }
1714 n
1715 } else {
1716 break;
1717 };
1718 let n = match n {
1719 Some(0) => break,
1720 None if written > 0 => break,
1721 None => return Ok(None),
1722 Some(n) => n,
1723 };
1724
1725 if readinto1 {
1726 written += n;
1727 break;
1728 }
1729 written += n;
1730 remaining -= n;
1731 }
1732
1733 Ok(Some(written))
1734 }
1735 }
1736
1737 pub(super) fn get_offset(obj: &PyObject, vm: &VirtualMachine) -> PyResult<Offset> {
1738 let int = obj.try_index(vm)?;
1739 int.as_bigint().try_into().map_err(|_| {
1740 vm.new_value_error(format!(
1741 "cannot fit '{}' into an offset-sized integer",
1742 obj.class().name()
1743 ))
1744 })
1745 }
1746
1747 pub(super) fn repr_file_obj_name(
1748 obj: &PyObject,
1749 vm: &VirtualMachine,
1750 ) -> PyResult<Option<PyStrRef>> {
1751 let name = match obj.get_attr("name", vm) {
1752 Ok(name) => Some(name),
1753 Err(e)
1754 if e.fast_isinstance(vm.ctx.exceptions.attribute_error)
1755 || e.fast_isinstance(vm.ctx.exceptions.value_error) =>
1756 {
1757 None
1758 }
1759 Err(e) => return Err(e),
1760 };
1761 match name {
1762 Some(name) => {
1763 if let Some(_guard) = ReprGuard::enter(vm, obj) {
1764 name.repr(vm).map(Some)
1765 } else {
1766 Err(vm.new_runtime_error(format!(
1767 "reentrant call inside {}.__repr__",
1768 obj.class().slot_name()
1769 )))
1770 }
1771 }
1772 None => Ok(None),
1773 }
1774 }
1775
1776 #[pyclass]
1777 trait BufferedMixin: PyPayload + StaticType {
1778 const CLASS_NAME: &'static str;
1779 const READABLE: bool;
1780 const WRITABLE: bool;
1781 const SEEKABLE: bool = false;
1782
1783 fn data(&self) -> &PyThreadMutex<BufferedData>;
1784 fn raw_cell(&self) -> &RawCell;
1785 fn closing(&self) -> &AtomicBool;
1786 fn finalizing(&self) -> &AtomicBool;
1787
1788 fn lock(&self, vm: &VirtualMachine) -> PyResult<PyThreadMutexGuard<'_, BufferedData>> {
1789 self.data()
1790 .lock_wrapped(|do_lock| vm.allow_threads(do_lock))
1791 .ok_or_else(|| vm.new_runtime_error("reentrant call inside buffered io"))
1792 }
1793
1794 #[pyslot]
1795 fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
1796 let zelf: &Py<Self> = zelf.try_to_ref(vm)?;
1797 let (raw, BufferSize { buffer_size }): (PyObjectRef, _) =
1798 args.bind(vm).map_err(|e| {
1799 let str_repr = e.__str__(vm).as_wtf8().to_owned();
1800 let msg = format!("{}() {}", Self::CLASS_NAME, str_repr);
1801 vm.new_exception_msg(e.class().to_owned(), msg.into())
1802 })?;
1803 zelf.init(raw, BufferSize { buffer_size }, vm)
1804 }
1805
1806 fn init(
1807 &self,
1808 raw: PyObjectRef,
1809 BufferSize { buffer_size }: BufferSize,
1810 vm: &VirtualMachine,
1811 ) -> PyResult<()> {
1812 let mut data = self.lock(vm)?;
1813 self.raw_cell().store(None);
1814 data.flags.remove(BufferedFlags::DETACHED);
1815
1816 if buffer_size <= 0 {
1817 return Err(vm.new_value_error("buffer size must be strictly positive"));
1818 }
1819 let buffer_size = buffer_size as usize;
1820
1821 if Self::SEEKABLE {
1822 check_seekable(&raw, vm)?;
1823 }
1824 if Self::READABLE {
1825 data.flags.insert(BufferedFlags::READABLE);
1826 check_readable(&raw, vm)?;
1827 }
1828 if Self::WRITABLE {
1829 data.flags.insert(BufferedFlags::WRITABLE);
1830 check_writable(&raw, vm)?;
1831 }
1832
1833 data.buffer = vm.new_zeroed_bytes(buffer_size)?;
1834
1835 if Self::READABLE {
1836 data.reset_read();
1837 }
1838 if Self::WRITABLE {
1839 data.reset_write();
1840 }
1841 if Self::SEEKABLE {
1842 data.pos = 0;
1843 }
1844
1845 self.raw_cell().store(Some(raw));
1846
1847 Ok(())
1848 }
1849
1850 #[pymethod]
1851 fn seek(
1852 zelf: &Py<Self>,
1853 target: PyObjectRef,
1854 whence: WhenceArg,
1855 vm: &VirtualMachine,
1856 ) -> PyResult<Offset> {
1857 let whence = whence.whence;
1858 if !validate_whence(whence) {
1859 return Err(vm.new_value_error(format!("whence value {whence} unsupported")));
1860 }
1861 let mut data = zelf.lock(vm)?;
1862 let raw_obj = data.check_init(zelf.raw_cell(), vm)?;
1863 ensure_unclosed(raw_obj, "seek of closed file", vm)?;
1864 check_seekable(raw_obj, vm)?;
1865 let target = get_offset(&target, vm)?;
1866 data.seek(zelf.raw_cell(), target, whence, vm)
1867 }
1868
1869 #[pymethod]
1870 fn tell(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Offset> {
1871 let mut data = zelf.lock(vm)?;
1872 let raw_tell = data.raw_tell(zelf.raw_cell(), vm)?;
1873 let raw_offset = data.raw_offset();
1874 let mut pos = raw_tell - raw_offset;
1875 if pos < 0 {
1877 pos = 0;
1878 }
1879 Ok(pos)
1880 }
1881
1882 #[pymethod]
1883 fn truncate(zelf: PyRef<Self>, pos: ObjPos, vm: &VirtualMachine) -> PyResult {
1884 let pos = pos.pos.unwrap_or_else(|| vm.ctx.none());
1885 let mut data = zelf.lock(vm)?;
1886 let raw_obj = data.check_init(zelf.raw_cell(), vm)?;
1887 if !data.writable() {
1888 return Err(new_unsupported_operation("truncate", vm));
1889 }
1890 data.flush_rewind(zelf.raw_cell(), vm)?;
1891 let res = vm.call_method(raw_obj, "truncate", (pos,))?;
1892 let _ = data.raw_tell(zelf.raw_cell(), vm);
1893 Ok(res)
1894 }
1895 #[pymethod]
1896 fn detach(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
1897 vm.call_method(zelf.as_object(), "flush", ())?;
1898 let mut data = zelf.lock(vm)?;
1899 data.flags.insert(BufferedFlags::DETACHED);
1900 zelf.raw_cell()
1901 .store(None)
1902 .ok_or_else(|| vm.new_value_error("raw stream has been detached"))
1903 }
1904
1905 #[pymethod]
1906 fn seekable(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
1907 let data = zelf.lock(vm)?;
1908 vm.call_method(data.check_init(zelf.raw_cell(), vm)?, "seekable", ())
1909 }
1910
1911 fn get_raw_unlocked(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1913 let data = self.lock(vm)?;
1914 data.raw_owned(self.raw_cell(), vm)
1915 }
1916
1917 fn closed(&self, vm: &VirtualMachine) -> PyResult {
1919 self.get_raw_unlocked(vm)?.get_attr("closed", vm)
1920 }
1921
1922 #[pygetset(name = "closed")]
1923 fn py_closed(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
1924 zelf.closed(vm)
1925 }
1926
1927 #[pygetset]
1928 fn name(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
1929 zelf.get_raw_unlocked(vm)?.get_attr("name", vm)
1930 }
1931
1932 #[pygetset]
1933 fn mode(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
1934 zelf.get_raw_unlocked(vm)?.get_attr("mode", vm)
1935 }
1936
1937 #[pymethod]
1938 fn fileno(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
1939 let data = zelf.lock(vm)?;
1940 vm.call_method(data.check_init(zelf.raw_cell(), vm)?, "fileno", ())
1941 }
1942
1943 fn isatty(&self, vm: &VirtualMachine) -> PyResult {
1945 let data = self.lock(vm)?;
1946 vm.call_method(data.check_init(self.raw_cell(), vm)?, "isatty", ())
1947 }
1948
1949 #[pymethod(name = "isatty")]
1950 fn py_isatty(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
1951 zelf.isatty(vm)
1952 }
1953
1954 #[pyslot]
1955 fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
1956 let name_repr = repr_file_obj_name(zelf, vm)?;
1957 let cls = zelf.class();
1958 let slot_name = cls.slot_name();
1959 let repr = if let Some(name_repr) = name_repr {
1960 format!("<{slot_name} name={name_repr}>")
1961 } else {
1962 format!("<{slot_name}>")
1963 };
1964 Ok(vm.ctx.new_str(repr))
1965 }
1966
1967 #[pymethod]
1968 fn __repr__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
1969 Self::slot_repr(&zelf, vm)
1970 }
1971
1972 fn close_strict(&self, vm: &VirtualMachine) -> PyResult {
1973 let mut data = self.lock(vm)?;
1974 let raw = data.check_init(self.raw_cell(), vm)?;
1975 if file_closed(raw, vm)? {
1976 return Ok(vm.ctx.none());
1977 }
1978 let flush_res = data.flush(self.raw_cell(), vm);
1979 let close_res = vm.call_method(raw, "close", ());
1980 exception_chain(flush_res, close_res)
1981 }
1982
1983 #[pymethod]
1984 fn close(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
1985 let raw = {
1987 let data = zelf.lock(vm)?;
1988 let raw = data.raw_owned(zelf.raw_cell(), vm)?;
1989 if file_closed(&raw, vm)? {
1990 return Ok(vm.ctx.none());
1991 }
1992 raw
1993 };
1994 if zelf.finalizing().load(Ordering::Relaxed) {
1995 let _ = vm.call_method(&raw, "_dealloc_warn", (zelf.as_object().to_owned(),));
1997 }
1998 zelf.closing().store(true, Ordering::Release);
2000 let flush_res = vm.call_method(zelf.as_object(), "flush", ()).map(drop);
2001 let close_res = vm.call_method(&raw, "close", ());
2002 exception_chain(flush_res, close_res)
2003 }
2004
2005 #[pymethod]
2006 fn readable(_zelf: &Py<Self>) -> bool {
2007 Self::READABLE
2008 }
2009
2010 #[pymethod]
2011 fn writable(_zelf: &Py<Self>) -> bool {
2012 Self::WRITABLE
2013 }
2014
2015 #[pymethod]
2016 fn __getstate__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
2017 Err(vm.new_type_error(format!("cannot pickle '{}' instances", zelf.class().name())))
2018 }
2019
2020 #[pymethod]
2021 fn __reduce_ex__(zelf: PyObjectRef, proto: usize, vm: &VirtualMachine) -> PyResult {
2022 if zelf.class().is(Self::static_type()) {
2023 return Err(
2024 vm.new_type_error(format!("cannot pickle '{}' object", zelf.class().name()))
2025 );
2026 }
2027 let _ = proto;
2028 reduce_ex_for_subclass(&zelf, vm)
2029 }
2030
2031 #[pymethod]
2032 fn _dealloc_warn(
2033 zelf: PyRef<Self>,
2034 source: PyObjectRef,
2035 vm: &VirtualMachine,
2036 ) -> PyResult<()> {
2037 let raw = {
2039 let _data = zelf.lock(vm)?;
2040 zelf.raw_cell().load_owned()
2041 };
2042 if let Some(raw) = raw {
2043 let _ = vm.call_method(&raw, "_dealloc_warn", (source,));
2044 }
2045 Ok(())
2046 }
2047 }
2048
2049 #[pyclass]
2050 trait BufferedReadable: PyPayload {
2051 type Reader: BufferedMixin;
2052
2053 fn reader(&self) -> &Self::Reader;
2054
2055 #[pymethod]
2056 fn read(
2057 zelf: &Py<Self>,
2058 size: OptionalSize,
2059 vm: &VirtualMachine,
2060 ) -> PyResult<Option<PyBytesRef>> {
2061 let mut data = zelf.reader().lock(vm)?;
2062 let raw_cell = zelf.reader().raw_cell();
2063 let raw = data.check_init(raw_cell, vm)?;
2064 let n = size.size.unwrap_or(-1);
2065 if n < -1 {
2066 return Err(vm.new_value_error("read length must be non-negative or -1"));
2067 }
2068 ensure_unclosed(raw, "read of closed file", vm)?;
2069 match n.to_usize() {
2070 Some(n) => data
2071 .read_generic(raw_cell, n, vm)
2072 .map(|x| x.map(|b| PyBytes::from(b).into_ref(&vm.ctx))),
2073 None => data.read_all(raw_cell, vm),
2074 }
2075 }
2076
2077 #[pymethod]
2078 fn peek(zelf: &Py<Self>, _size: PeekSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
2079 let mut data = zelf.reader().lock(vm)?;
2080 let raw_cell = zelf.reader().raw_cell();
2081 let raw = data.check_init(raw_cell, vm)?;
2082 ensure_unclosed(raw, "peek of closed file", vm)?;
2083
2084 if data.writable() {
2085 let _ = data.flush_rewind(raw_cell, vm);
2086 }
2087 data.peek(raw_cell, vm)
2088 }
2089
2090 #[pymethod]
2091 fn read1(zelf: &Py<Self>, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
2092 let mut data = zelf.reader().lock(vm)?;
2093 let raw_cell = zelf.reader().raw_cell();
2094 let raw = data.check_init(raw_cell, vm)?;
2095 ensure_unclosed(raw, "read of closed file", vm)?;
2096 let n = size.to_usize().unwrap_or(data.buffer.len());
2097 if n == 0 {
2098 return Ok(Vec::new());
2099 }
2100 let have = data.readahead();
2101 if have > 0 {
2102 let n = core::cmp::min(have as usize, n);
2103 return Ok(data.read_fast(n).unwrap());
2104 }
2105 if data.writable() {
2107 data.flush_rewind(raw_cell, vm)?;
2108 }
2109 let mut v = vm.new_zeroed_bytes(n)?;
2110 data.reset_read();
2111 let r = data
2112 .raw_read(raw_cell, Either::A(Some(&mut v)), 0..n, vm)?
2113 .unwrap_or(0);
2114 v.truncate(r);
2115 v.shrink_to_fit();
2116 Ok(v)
2117 }
2118
2119 #[pymethod]
2120 fn readinto(
2121 zelf: &Py<Self>,
2122 buffer: ArgMemoryBuffer,
2123 vm: &VirtualMachine,
2124 ) -> PyResult<Option<usize>> {
2125 let mut data = zelf.reader().lock(vm)?;
2126 let raw_cell = zelf.reader().raw_cell();
2127 let raw = data.check_init(raw_cell, vm)?;
2128 ensure_unclosed(raw, "readinto of closed file", vm)?;
2129 data.readinto_generic(raw_cell, buffer.into(), false, vm)
2130 }
2131
2132 #[pymethod]
2133 fn readinto1(
2134 zelf: &Py<Self>,
2135 buffer: ArgMemoryBuffer,
2136 vm: &VirtualMachine,
2137 ) -> PyResult<Option<usize>> {
2138 let mut data = zelf.reader().lock(vm)?;
2139 let raw_cell = zelf.reader().raw_cell();
2140 let raw = data.check_init(raw_cell, vm)?;
2141 ensure_unclosed(raw, "readinto of closed file", vm)?;
2142 data.readinto_generic(raw_cell, buffer.into(), true, vm)
2143 }
2144
2145 #[pymethod]
2146 fn flush(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> {
2147 let raw = {
2150 let data = zelf.reader().lock(vm)?;
2151 data.raw_owned(zelf.reader().raw_cell(), vm)?
2152 };
2153 ensure_unclosed(&raw, "flush of closed file", vm)?;
2154 vm.call_method(&raw, "flush", ())?;
2155 Ok(())
2156 }
2157 }
2158
2159 fn exception_chain<T>(e1: PyResult<()>, e2: PyResult<T>) -> PyResult<T> {
2160 match (e1, e2) {
2161 (Err(e1), Err(e)) => {
2162 e.set_context(Some(e1));
2163 Err(e)
2164 }
2165 (Err(e), Ok(_)) | (Ok(()), Err(e)) => Err(e),
2166 (Ok(()), Ok(close_res)) => Ok(close_res),
2167 }
2168 }
2169
2170 #[pyattr]
2171 #[pyclass(name = "BufferedReader", base = _BufferedIOBase)]
2172 #[derive(Debug)]
2173 struct BufferedReader {
2174 _base: _BufferedIOBase,
2175 #[pymember]
2176 raw: RawCell,
2177 data: PyThreadMutex<BufferedData>,
2178 closing: AtomicBool,
2179 finalizing: AtomicBool,
2180 }
2181
2182 impl Default for BufferedReader {
2183 fn default() -> Self {
2184 Self {
2185 _base: _BufferedIOBase::default(),
2186 raw: RawCell::new_empty(),
2187 data: PyThreadMutex::default(),
2188 closing: AtomicBool::new(false),
2189 finalizing: AtomicBool::new(false),
2190 }
2191 }
2192 }
2193
2194 impl BufferedMixin for BufferedReader {
2195 const CLASS_NAME: &'static str = "BufferedReader";
2196 const READABLE: bool = true;
2197 const WRITABLE: bool = false;
2198
2199 fn data(&self) -> &PyThreadMutex<BufferedData> {
2200 &self.data
2201 }
2202
2203 fn raw_cell(&self) -> &RawCell {
2204 &self.raw
2205 }
2206
2207 fn closing(&self) -> &AtomicBool {
2208 &self.closing
2209 }
2210
2211 fn finalizing(&self) -> &AtomicBool {
2212 &self.finalizing
2213 }
2214 }
2215
2216 impl BufferedReadable for BufferedReader {
2217 type Reader = Self;
2218
2219 fn reader(&self) -> &Self::Reader {
2220 self
2221 }
2222 }
2223
2224 #[pyclass(
2225 with(Constructor, Initializer, BufferedMixin, BufferedReadable, Destructor),
2226 flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
2227 )]
2228 impl BufferedReader {}
2229
2230 impl Destructor for BufferedReader {
2231 fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
2232 if let Some(buf) = zelf.downcast_ref::<Self>() {
2233 buf.finalizing.store(true, Ordering::Relaxed);
2234 }
2235 iobase_finalize(zelf, vm);
2236 Ok(())
2237 }
2238
2239 #[cold]
2240 fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> {
2241 unreachable!("slot_del is implemented")
2242 }
2243 }
2244
2245 impl DefaultConstructor for BufferedReader {}
2246
2247 impl Initializer for BufferedReader {
2248 type Args = BufferedInitArgs;
2249
2250 fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
2251 zelf.init(
2252 args.raw,
2253 BufferSize {
2254 buffer_size: args.buffer_size,
2255 },
2256 vm,
2257 )
2258 }
2259 }
2260
2261 #[pyclass]
2262 trait BufferedWritable: PyPayload {
2263 type Writer: BufferedMixin;
2264
2265 fn writer(&self) -> &Self::Writer;
2266
2267 #[pymethod]
2268 fn write(zelf: &Py<Self>, buffer: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> {
2269 if zelf.writer().closing().load(Ordering::Acquire) {
2274 loop {
2275 let raw = {
2276 let _data = zelf.writer().lock(vm)?;
2277 match zelf.writer().raw_cell().load_owned() {
2278 Some(raw) => raw,
2279 None => break, }
2281 };
2282 if file_closed(&raw, vm)? {
2283 break;
2284 }
2285 std::thread::yield_now();
2287 }
2288 return Err(vm.new_value_error("write to closed file"));
2289 }
2290 let mut data = zelf.writer().lock(vm)?;
2291 let raw_cell = zelf.writer().raw_cell();
2292 let raw = data.check_init(raw_cell, vm)?;
2293 ensure_unclosed(raw, "write to closed file", vm)?;
2294
2295 data.write(raw_cell, buffer, vm)
2296 }
2297
2298 fn flush(&self, vm: &VirtualMachine) -> PyResult<()> {
2300 let mut data = self.writer().lock(vm)?;
2301 let raw_cell = self.writer().raw_cell();
2302 let raw = data.check_init(raw_cell, vm)?;
2303 ensure_unclosed(raw, "flush of closed file", vm)?;
2304 data.flush_rewind(raw_cell, vm)
2305 }
2306
2307 #[pymethod(name = "flush")]
2308 fn py_flush(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> {
2309 zelf.flush(vm)
2310 }
2311 }
2312
2313 #[pyattr]
2314 #[pyclass(name = "BufferedWriter", base = _BufferedIOBase)]
2315 #[derive(Debug)]
2316 struct BufferedWriter {
2317 _base: _BufferedIOBase,
2318 #[pymember]
2319 raw: RawCell,
2320 data: PyThreadMutex<BufferedData>,
2321 closing: AtomicBool,
2322 finalizing: AtomicBool,
2323 }
2324
2325 impl Default for BufferedWriter {
2326 fn default() -> Self {
2327 Self {
2328 _base: _BufferedIOBase::default(),
2329 raw: RawCell::new_empty(),
2330 data: PyThreadMutex::default(),
2331 closing: AtomicBool::new(false),
2332 finalizing: AtomicBool::new(false),
2333 }
2334 }
2335 }
2336
2337 impl BufferedMixin for BufferedWriter {
2338 const CLASS_NAME: &'static str = "BufferedWriter";
2339 const READABLE: bool = false;
2340 const WRITABLE: bool = true;
2341
2342 fn data(&self) -> &PyThreadMutex<BufferedData> {
2343 &self.data
2344 }
2345
2346 fn raw_cell(&self) -> &RawCell {
2347 &self.raw
2348 }
2349
2350 fn closing(&self) -> &AtomicBool {
2351 &self.closing
2352 }
2353
2354 fn finalizing(&self) -> &AtomicBool {
2355 &self.finalizing
2356 }
2357 }
2358
2359 impl BufferedWritable for BufferedWriter {
2360 type Writer = Self;
2361
2362 fn writer(&self) -> &Self::Writer {
2363 self
2364 }
2365 }
2366
2367 #[pyclass(
2368 with(Constructor, Initializer, BufferedMixin, BufferedWritable, Destructor),
2369 flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
2370 )]
2371 impl BufferedWriter {}
2372
2373 impl Destructor for BufferedWriter {
2374 fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
2375 if let Some(buf) = zelf.downcast_ref::<Self>() {
2376 buf.finalizing.store(true, Ordering::Relaxed);
2377 }
2378 iobase_finalize(zelf, vm);
2379 Ok(())
2380 }
2381
2382 #[cold]
2383 fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> {
2384 unreachable!("slot_del is implemented")
2385 }
2386 }
2387
2388 impl DefaultConstructor for BufferedWriter {}
2389
2390 impl Initializer for BufferedWriter {
2391 type Args = BufferedInitArgs;
2392
2393 fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
2394 zelf.init(
2395 args.raw,
2396 BufferSize {
2397 buffer_size: args.buffer_size,
2398 },
2399 vm,
2400 )
2401 }
2402 }
2403
2404 #[pyattr]
2405 #[pyclass(name = "BufferedRandom", base = _BufferedIOBase)]
2406 #[derive(Debug)]
2407 struct BufferedRandom {
2408 _base: _BufferedIOBase,
2409 #[pymember]
2410 raw: RawCell,
2411 data: PyThreadMutex<BufferedData>,
2412 closing: AtomicBool,
2413 finalizing: AtomicBool,
2414 }
2415
2416 impl Default for BufferedRandom {
2417 fn default() -> Self {
2418 Self {
2419 _base: _BufferedIOBase::default(),
2420 raw: RawCell::new_empty(),
2421 data: PyThreadMutex::default(),
2422 closing: AtomicBool::new(false),
2423 finalizing: AtomicBool::new(false),
2424 }
2425 }
2426 }
2427
2428 impl BufferedMixin for BufferedRandom {
2429 const CLASS_NAME: &'static str = "BufferedRandom";
2430 const READABLE: bool = true;
2431 const WRITABLE: bool = true;
2432 const SEEKABLE: bool = true;
2433
2434 fn data(&self) -> &PyThreadMutex<BufferedData> {
2435 &self.data
2436 }
2437
2438 fn raw_cell(&self) -> &RawCell {
2439 &self.raw
2440 }
2441
2442 fn closing(&self) -> &AtomicBool {
2443 &self.closing
2444 }
2445
2446 fn finalizing(&self) -> &AtomicBool {
2447 &self.finalizing
2448 }
2449 }
2450
2451 impl BufferedReadable for BufferedRandom {
2452 type Reader = Self;
2453
2454 fn reader(&self) -> &Self::Reader {
2455 self
2456 }
2457 }
2458
2459 impl BufferedWritable for BufferedRandom {
2460 type Writer = Self;
2461
2462 fn writer(&self) -> &Self::Writer {
2463 self
2464 }
2465 }
2466
2467 #[pyclass(
2468 with(
2469 Constructor,
2470 Initializer,
2471 BufferedMixin,
2472 BufferedReadable,
2473 BufferedWritable,
2474 Destructor
2475 ),
2476 flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
2477 )]
2478 impl BufferedRandom {}
2479
2480 impl Destructor for BufferedRandom {
2481 fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
2482 if let Some(buf) = zelf.downcast_ref::<Self>() {
2483 buf.finalizing.store(true, Ordering::Relaxed);
2484 }
2485 iobase_finalize(zelf, vm);
2486 Ok(())
2487 }
2488
2489 #[cold]
2490 fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> {
2491 unreachable!("slot_del is implemented")
2492 }
2493 }
2494
2495 impl DefaultConstructor for BufferedRandom {}
2496
2497 impl Initializer for BufferedRandom {
2498 type Args = BufferedInitArgs;
2499
2500 fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
2501 zelf.init(
2502 args.raw,
2503 BufferSize {
2504 buffer_size: args.buffer_size,
2505 },
2506 vm,
2507 )
2508 }
2509 }
2510
2511 #[pyattr]
2512 #[pyclass(name = "BufferedRWPair", base = _BufferedIOBase)]
2513 #[derive(Debug, Default)]
2514 struct BufferedRWPair {
2515 _base: _BufferedIOBase,
2516 read: BufferedReader,
2517 write: BufferedWriter,
2518 }
2519
2520 impl BufferedReadable for BufferedRWPair {
2521 type Reader = BufferedReader;
2522
2523 fn reader(&self) -> &Self::Reader {
2524 &self.read
2525 }
2526 }
2527
2528 impl BufferedWritable for BufferedRWPair {
2529 type Writer = BufferedWriter;
2530
2531 fn writer(&self) -> &Self::Writer {
2532 &self.write
2533 }
2534 }
2535
2536 impl DefaultConstructor for BufferedRWPair {}
2537
2538 impl Initializer for BufferedRWPair {
2539 type Args = BufferedRWPairArgs;
2540
2541 fn init(
2542 zelf: &Py<Self>,
2543 BufferedRWPairArgs {
2544 reader,
2545 writer,
2546 buffer_size,
2547 }: Self::Args,
2548 vm: &VirtualMachine,
2549 ) -> PyResult<()> {
2550 let size = BufferSize { buffer_size };
2551 zelf.read.init(reader, size.clone(), vm)?;
2552 zelf.write.init(writer, size, vm)?;
2553 Ok(())
2554 }
2555 }
2556
2557 #[pyclass(
2558 with(
2559 Constructor,
2560 Initializer,
2561 BufferedReadable,
2562 BufferedWritable,
2563 Destructor
2564 ),
2565 flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
2566 )]
2567 impl Py<BufferedRWPair> {
2568 #[pymethod]
2569 fn flush(&self, vm: &VirtualMachine) -> PyResult<()> {
2570 self.write.flush(vm)
2571 }
2572
2573 #[pymethod]
2574 const fn readable(&self) -> bool {
2575 true
2576 }
2577 #[pymethod]
2578 const fn writable(&self) -> bool {
2579 true
2580 }
2581
2582 #[pygetset]
2583 fn closed(&self, vm: &VirtualMachine) -> PyResult {
2584 self.write.closed(vm)
2585 }
2586
2587 #[pymethod]
2588 fn isatty(&self, vm: &VirtualMachine) -> PyResult {
2589 let res = self.read.isatty(vm)?;
2591 if res.try_to_bool(vm)? {
2592 Ok(res)
2593 } else {
2594 self.write.isatty(vm)
2595 }
2596 }
2597
2598 #[pymethod]
2599 fn close(&self, vm: &VirtualMachine) -> PyResult {
2600 let write_res = self.write.close_strict(vm).map(drop);
2601 let read_res = self.read.close_strict(vm);
2602 exception_chain(write_res, read_res)
2603 }
2604 }
2605
2606 impl Destructor for BufferedRWPair {
2607 fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
2608 iobase_finalize(zelf, vm);
2609 Ok(())
2610 }
2611
2612 #[cold]
2613 fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> {
2614 unreachable!("slot_del is implemented")
2615 }
2616 }
2617
2618 #[derive(FromArgs)]
2619 struct TextIOWrapperArgs {
2620 #[pyarg(any, optional)]
2621 encoding: Option<PyUtf8StrRef>,
2622 #[pyarg(any, optional)]
2623 errors: Option<PyUtf8StrRef>,
2624 #[pyarg(any, optional)]
2626 newline: Option<Newlines>,
2627 #[pyarg(any, optional, py_default = "False")]
2629 line_buffering: Option<PyObjectRef>,
2630 #[pyarg(any, optional, py_default = "False")]
2632 write_through: Option<PyObjectRef>,
2633 }
2634
2635 #[derive(FromArgs)]
2636 struct TextIOWrapperReconfigureArgs {
2637 #[pyarg(named, optional)]
2638 encoding: Option<PyUtf8StrRef>,
2639 #[pyarg(named, optional)]
2640 errors: Option<PyUtf8StrRef>,
2641 #[pyarg(named, optional, py_default = "None")]
2643 newline: OptionalOption<Newlines>,
2644 #[pyarg(named, optional)]
2645 line_buffering: Option<PyObjectRef>,
2646 #[pyarg(named, optional)]
2647 write_through: Option<PyObjectRef>,
2648 }
2649
2650 #[derive(Debug, Copy, Clone, Default, PartialEq)]
2651 enum Newlines {
2652 #[default]
2653 Universal,
2654 Passthrough,
2655 Lf,
2656 Cr,
2657 Crlf,
2658 }
2659
2660 impl Newlines {
2661 fn find_newline(self, s: &Wtf8) -> Result<usize, usize> {
2664 let len = s.len();
2665 match self {
2666 Self::Universal | Self::Lf => s.find("\n".as_ref()).map(|p| p + 1).ok_or(len),
2667 Self::Passthrough => {
2668 let bytes = s.as_bytes();
2669 memchr::memchr2(b'\n', b'\r', bytes)
2670 .map(|p| {
2671 let nl_len =
2672 if bytes[p] == b'\r' && bytes.get(p + 1).copied() == Some(b'\n') {
2673 2
2674 } else {
2675 1
2676 };
2677 p + nl_len
2678 })
2679 .ok_or(len)
2680 }
2681 Self::Cr => s.find("\r".as_ref()).map(|p| p + 1).ok_or(len),
2682 Self::Crlf => {
2683 let mut searched = 0;
2685 let mut remaining = s.as_bytes();
2686 loop {
2687 match memchr::memchr(b'\r', remaining) {
2688 Some(p) => match remaining.get(p + 1) {
2689 Some(&ch_after_cr) => {
2690 if ch_after_cr == b'\n' {
2691 break Ok(searched + p + 2);
2692 }
2693 let pos_after = p + 1;
2694 searched += pos_after;
2695 remaining = &remaining[pos_after..];
2696 continue;
2697 }
2698 None => break Err(searched + p),
2699 },
2700 None => break Err(len),
2701 }
2702 }
2703 }
2704 }
2705 }
2706 }
2707
2708 impl TryFromObject for Newlines {
2709 fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
2710 let nl = if vm.is_none(&obj) {
2711 Self::Universal
2712 } else {
2713 let s = obj.downcast::<PyStr>().map_err(|obj| {
2714 vm.new_type_error(format!(
2715 "newline argument must be str or None, not {}",
2716 obj.class().name()
2717 ))
2718 })?;
2719 let wtf8 = s.as_wtf8();
2720 if !wtf8.is_utf8() {
2721 let repr = s.repr(vm)?.as_str().to_owned();
2722 return Err(vm.new_value_error(format!("illegal newline value: {repr}")));
2723 }
2724 let s_str = wtf8.as_str().expect("checked utf8");
2725 match s_str {
2726 "" => Self::Passthrough,
2727 "\n" => Self::Lf,
2728 "\r" => Self::Cr,
2729 "\r\n" => Self::Crlf,
2730 _ => return Err(vm.new_value_error(format!("illegal newline value: {s}"))),
2731 }
2732 };
2733 Ok(nl)
2734 }
2735 }
2736
2737 fn reduce_ex_for_subclass(zelf: &PyObject, vm: &VirtualMachine) -> PyResult {
2738 let cls = zelf.class();
2739 let new = vm
2740 .get_attribute_opt(cls.as_object(), "__new__")?
2741 .ok_or_else(|| vm.new_attribute_error("type has no attribute '__new__'"))?;
2742 let args = vm.ctx.new_tuple(vec![cls.to_owned().into()]);
2743 let state = if let Some(getstate) = vm.get_attribute_opt(zelf, "__getstate__")? {
2744 getstate.call((), vm)?
2745 } else if let Ok(dict) = zelf.get_attr("__dict__", vm) {
2746 dict
2747 } else {
2748 vm.ctx.none()
2749 };
2750 Ok(vm.ctx.new_tuple(vec![new, args.into(), state]).into())
2751 }
2752
2753 #[derive(Debug, Default, Copy, Clone)]
2755 struct Utf8size {
2756 bytes: usize,
2757 chars: usize,
2758 }
2759
2760 impl Utf8size {
2761 fn len_pystr(s: &Py<PyStr>) -> Self {
2762 Self {
2763 bytes: s.byte_len(),
2764 chars: s.char_len(),
2765 }
2766 }
2767
2768 fn len_str(s: &Wtf8) -> Self {
2769 Self {
2770 bytes: s.len(),
2771 chars: s.code_points().count(),
2772 }
2773 }
2774 }
2775
2776 impl core::ops::Add for Utf8size {
2777 type Output = Self;
2778
2779 #[inline]
2780 fn add(mut self, rhs: Self) -> Self {
2781 self += rhs;
2782 self
2783 }
2784 }
2785
2786 impl core::ops::AddAssign for Utf8size {
2787 #[inline]
2788 fn add_assign(&mut self, rhs: Self) {
2789 self.bytes += rhs.bytes;
2790 self.chars += rhs.chars;
2791 }
2792 }
2793
2794 impl core::ops::Sub for Utf8size {
2795 type Output = Self;
2796
2797 #[inline]
2798 fn sub(mut self, rhs: Self) -> Self {
2799 self -= rhs;
2800 self
2801 }
2802 }
2803
2804 impl core::ops::SubAssign for Utf8size {
2805 #[inline]
2806 fn sub_assign(&mut self, rhs: Self) {
2807 self.bytes -= rhs.bytes;
2808 self.chars -= rhs.chars;
2809 }
2810 }
2811
2812 type EncodeFunc = fn(PyStrRef) -> PendingWrite;
2814 const fn textio_encode_utf8(s: PyStrRef) -> PendingWrite {
2815 PendingWrite::Utf8(s)
2816 }
2817
2818 #[derive(Debug)]
2819 struct TextIOData {
2820 encoder: Option<(PyObjectRef, Option<EncodeFunc>)>,
2821 decoder: Option<PyObjectRef>,
2822 errors: PyUtf8StrRef,
2823 newline: Newlines,
2824 chunk_size: usize,
2825 seekable: bool,
2826 has_read1: bool,
2827 pending: PendingWrites,
2829 telling: bool,
2830 snapshot: Option<(i32, PyBytesRef)>,
2831 decoded_chars: Option<PyStrRef>,
2832 decoded_chars_used: Utf8size,
2834 b2cratio: f64,
2835 }
2836
2837 #[derive(Debug, Default)]
2838 struct PendingWrites {
2839 num_bytes: usize,
2840 data: PendingWritesData,
2841 }
2842
2843 #[derive(Debug, Default)]
2844 enum PendingWritesData {
2845 #[default]
2846 None,
2847 One(PendingWrite),
2848 Many(Vec<PendingWrite>),
2849 }
2850
2851 #[derive(Debug)]
2852 enum PendingWrite {
2853 Utf8(PyStrRef),
2854 Bytes(PyBytesRef),
2855 }
2856
2857 impl PendingWrite {
2858 fn as_bytes(&self) -> &[u8] {
2859 match self {
2860 Self::Utf8(s) => s.as_bytes(),
2861 Self::Bytes(b) => b.as_bytes(),
2862 }
2863 }
2864 }
2865
2866 impl PendingWrites {
2867 fn push(&mut self, write: PendingWrite) {
2868 self.num_bytes += write.as_bytes().len();
2869 self.data = match core::mem::take(&mut self.data) {
2870 PendingWritesData::None => PendingWritesData::One(write),
2871 PendingWritesData::One(write1) => PendingWritesData::Many(vec![write1, write]),
2872 PendingWritesData::Many(mut v) => {
2873 v.push(write);
2874 PendingWritesData::Many(v)
2875 }
2876 }
2877 }
2878 fn take(&mut self, vm: &VirtualMachine) -> PyBytesRef {
2879 let Self { num_bytes, data } = core::mem::take(self);
2880 if let PendingWritesData::One(PendingWrite::Bytes(b)) = data {
2881 return b;
2882 }
2883 let writes_iter = match data {
2884 PendingWritesData::None => itertools::Either::Left(vec![].into_iter()),
2885 PendingWritesData::One(write) => itertools::Either::Right(core::iter::once(write)),
2886 PendingWritesData::Many(writes) => itertools::Either::Left(writes.into_iter()),
2887 };
2888 let mut buf = Vec::with_capacity(num_bytes);
2889 writes_iter.for_each(|chunk| buf.extend_from_slice(chunk.as_bytes()));
2890 PyBytes::from(buf).into_ref(&vm.ctx)
2891 }
2892 }
2893
2894 #[derive(Default, Debug)]
2895 struct TextIOCookie {
2896 start_pos: Offset,
2897 dec_flags: i32,
2898 bytes_to_feed: i32,
2899 chars_to_skip: i32,
2900 need_eof: bool,
2901 bytes_to_skip: i32,
2903 }
2904
2905 impl TextIOCookie {
2906 const START_POS_OFF: usize = 0;
2907 const DEC_FLAGS_OFF: usize = Self::START_POS_OFF + core::mem::size_of::<Offset>();
2908 const BYTES_TO_FEED_OFF: usize = Self::DEC_FLAGS_OFF + 4;
2909 const CHARS_TO_SKIP_OFF: usize = Self::BYTES_TO_FEED_OFF + 4;
2910 const NEED_EOF_OFF: usize = Self::CHARS_TO_SKIP_OFF + 4;
2911 const BYTES_TO_SKIP_OFF: usize = Self::NEED_EOF_OFF + 1;
2912 const BYTE_LEN: usize = Self::BYTES_TO_SKIP_OFF + 4;
2913
2914 fn parse(cookie: &BigInt) -> Option<Self> {
2915 let (_, mut buf) = cookie.to_bytes_le();
2916 if buf.len() > Self::BYTE_LEN {
2917 return None;
2918 }
2919 buf.resize(Self::BYTE_LEN, 0);
2920 let buf: &[u8; Self::BYTE_LEN] = buf.as_array()?;
2921 macro_rules! get_field {
2922 ($t:ty, $off:ident) => {
2923 <$t>::from_ne_bytes(*buf[Self::$off..].first_chunk().unwrap())
2924 };
2925 }
2926 Some(Self {
2927 start_pos: get_field!(Offset, START_POS_OFF),
2928 dec_flags: get_field!(i32, DEC_FLAGS_OFF),
2929 bytes_to_feed: get_field!(i32, BYTES_TO_FEED_OFF),
2930 chars_to_skip: get_field!(i32, CHARS_TO_SKIP_OFF),
2931 need_eof: get_field!(u8, NEED_EOF_OFF) != 0,
2932 bytes_to_skip: get_field!(i32, BYTES_TO_SKIP_OFF),
2933 })
2934 }
2935
2936 fn build(&self) -> BigInt {
2937 let mut buf = [0; Self::BYTE_LEN];
2938 macro_rules! set_field {
2939 ($field:expr, $off:ident) => {{
2940 let field = $field;
2941 buf[Self::$off..][..core::mem::size_of_val(&field)]
2942 .copy_from_slice(&field.to_ne_bytes())
2943 }};
2944 }
2945 set_field!(self.start_pos, START_POS_OFF);
2946 set_field!(self.dec_flags, DEC_FLAGS_OFF);
2947 set_field!(self.bytes_to_feed, BYTES_TO_FEED_OFF);
2948 set_field!(self.chars_to_skip, CHARS_TO_SKIP_OFF);
2949 set_field!(self.need_eof as u8, NEED_EOF_OFF);
2950 set_field!(self.bytes_to_skip, BYTES_TO_SKIP_OFF);
2951 BigInt::from_signed_bytes_le(&buf)
2952 }
2953
2954 fn set_decoder_state(&self, decoder: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
2955 if self.start_pos == 0 && self.dec_flags == 0 {
2956 vm.call_method(decoder, "reset", ())?;
2957 } else {
2958 vm.call_method(
2959 decoder,
2960 "setstate",
2961 ((vm.ctx.new_bytes(vec![]), self.dec_flags),),
2962 )?;
2963 }
2964 Ok(())
2965 }
2966
2967 const fn num_to_skip(&self) -> Utf8size {
2968 Utf8size {
2969 bytes: self.bytes_to_skip as usize,
2970 chars: self.chars_to_skip as usize,
2971 }
2972 }
2973
2974 const fn set_num_to_skip(&mut self, num: Utf8size) {
2975 self.bytes_to_skip = num.bytes as i32;
2976 self.chars_to_skip = num.chars as i32;
2977 }
2978 }
2979
2980 #[pyclass(module = "_io", name, no_attr)]
2981 #[derive(Debug, PyPayload)]
2982 struct StatelessIncrementalEncoder {
2983 encode: PyObjectRef,
2984 errors: Option<PyStrRef>,
2985 name: Option<PyStrRef>,
2986 }
2987
2988 #[pyclass(with(Py))]
2989 impl StatelessIncrementalEncoder {}
2990
2991 #[pyclass]
2992 impl Py<StatelessIncrementalEncoder> {
2993 #[pymethod]
2994 fn encode(
2995 &self,
2996 input: PyObjectRef,
2997 _final: OptionalArg<bool>,
2998 vm: &VirtualMachine,
2999 ) -> PyResult {
3000 let mut args: Vec<PyObjectRef> = vec![input];
3001 if let Some(errors) = &self.errors {
3002 args.push(errors.to_owned().into());
3003 }
3004 let res = self.encode.call(args, vm)?;
3005 let tuple: PyTupleRef = res.try_into_value(vm)?;
3006 if tuple.as_slice().len() != 2 {
3007 return Err(vm.new_type_error("encoder must return a tuple (object, integer)"));
3008 }
3009 Ok(tuple.as_slice()[0].clone())
3010 }
3011
3012 #[pymethod]
3013 fn reset(&self) {}
3014
3015 #[pymethod]
3016 fn setstate(&self, _state: PyObjectRef) {}
3017
3018 #[pymethod]
3019 fn getstate(&self, vm: &VirtualMachine) -> PyObjectRef {
3020 vm.ctx.new_int(0).into()
3021 }
3022
3023 #[pygetset]
3024 fn name(&self) -> Option<PyStrRef> {
3025 self.name.clone()
3026 }
3027 }
3028
3029 #[pyclass(module = "_io", name, no_attr)]
3030 #[derive(Debug, PyPayload)]
3031 struct StatelessIncrementalDecoder {
3032 decode: PyObjectRef,
3033 errors: Option<PyStrRef>,
3034 }
3035
3036 #[pyclass(with(Py))]
3037 impl StatelessIncrementalDecoder {}
3038
3039 #[pyclass]
3040 impl Py<StatelessIncrementalDecoder> {
3041 #[pymethod]
3042 fn decode(
3043 &self,
3044 input: PyObjectRef,
3045 _final: OptionalArg<bool>,
3046 vm: &VirtualMachine,
3047 ) -> PyResult {
3048 let mut args: Vec<PyObjectRef> = vec![input];
3049 if let Some(errors) = &self.errors {
3050 args.push(errors.to_owned().into());
3051 }
3052 let res = self.decode.call(args, vm)?;
3053 let tuple: PyTupleRef = res.try_into_value(vm)?;
3054 if tuple.as_slice().len() != 2 {
3055 return Err(vm.new_type_error("decoder must return a tuple (object, integer)"));
3056 }
3057 Ok(tuple.as_slice()[0].clone())
3058 }
3059
3060 #[pymethod]
3061 fn getstate(&self, vm: &VirtualMachine) -> (PyBytesRef, u64) {
3062 (vm.ctx.empty_bytes.to_owned(), 0)
3063 }
3064
3065 #[pymethod]
3066 fn setstate(&self, _state: PyTupleRef, _vm: &VirtualMachine) {}
3067
3068 #[pymethod]
3069 fn reset(&self) {}
3070 }
3071
3072 #[pyattr]
3073 #[pyclass(name = "TextIOWrapper", base = _TextIOBase)]
3074 #[derive(Debug)]
3075 struct TextIOWrapper {
3076 _base: _TextIOBase,
3077 #[pymember]
3078 buffer: RawCell,
3079 #[pymember]
3080 encoding: RawCell,
3081 #[pymember]
3082 line_buffering: AtomicBool,
3083 #[pymember]
3084 write_through: AtomicBool,
3085 data: PyThreadMutex<Option<TextIOData>>,
3086 finalizing: AtomicBool,
3087 }
3088
3089 impl Default for TextIOWrapper {
3090 fn default() -> Self {
3091 Self {
3092 _base: _TextIOBase::default(),
3093 buffer: RawCell::new_empty(),
3094 encoding: RawCell::new_empty(),
3095 line_buffering: AtomicBool::new(false),
3096 write_through: AtomicBool::new(false),
3097 data: PyThreadMutex::default(),
3098 finalizing: AtomicBool::new(false),
3099 }
3100 }
3101 }
3102
3103 impl DefaultConstructor for TextIOWrapper {}
3104
3105 #[derive(FromArgs)]
3106 struct TextIOWrapperInitArgs {
3107 #[pyarg(any)]
3108 buffer: PyObjectRef,
3109 #[pyarg(flatten)]
3110 args: TextIOWrapperArgs,
3111 }
3112
3113 impl Initializer for TextIOWrapper {
3114 type Args = TextIOWrapperInitArgs;
3115
3116 fn init(
3117 zelf: &Py<Self>,
3118 TextIOWrapperInitArgs { buffer, args }: Self::Args,
3119 vm: &VirtualMachine,
3120 ) -> PyResult<()> {
3121 zelf.buffer.store(None);
3122 zelf.encoding.store(None);
3123 zelf.line_buffering.store(false, Ordering::Relaxed);
3124 zelf.write_through.store(false, Ordering::Relaxed);
3125 let mut data = zelf.lock_opt(vm)?;
3126 *data = None;
3127
3128 let encoding = Self::resolve_encoding(args.encoding, vm)?;
3129
3130 let errors = args.errors.unwrap_or_else(|| vm.ctx.new_utf8_str("strict"));
3131 Self::validate_errors(&errors, vm)?;
3132
3133 let has_read1 = vm.get_attribute_opt(&buffer, "read1")?.is_some();
3134 let seekable = vm.call_method(&buffer, "seekable", ())?.try_to_bool(vm)?;
3135
3136 let newline = args.newline.unwrap_or_default();
3137 let (encoder, decoder) =
3138 Self::find_coder(&buffer, encoding.as_str(), &errors, newline, vm)?;
3139 if let Some((encoder, _)) = &encoder {
3140 Self::adjust_encoder_state_for_bom(encoder, encoding.as_str(), &buffer, vm)?;
3141 }
3142
3143 let line_buffering = match args.line_buffering {
3144 Some(value) => value.try_to_bool(vm)?,
3145 None => false,
3146 };
3147 let write_through = match args.write_through {
3148 Some(value) => value.try_to_bool(vm)?,
3149 None => false,
3150 };
3151
3152 zelf.buffer.store(Some(buffer));
3153 zelf.encoding.store(Some(encoding.into()));
3154 zelf.line_buffering.store(line_buffering, Ordering::Relaxed);
3155 zelf.write_through.store(write_through, Ordering::Relaxed);
3156
3157 *data = Some(TextIOData {
3158 encoder,
3159 decoder,
3160 errors,
3161 newline,
3162 chunk_size: 8192,
3163 seekable,
3164 has_read1,
3165
3166 pending: PendingWrites::default(),
3167 telling: seekable,
3168 snapshot: None,
3169 decoded_chars: None,
3170 decoded_chars_used: Utf8size::default(),
3171 b2cratio: 0.0,
3172 });
3173
3174 Ok(())
3175 }
3176
3177 fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
3178 let zelf_ref: &Py<Self> = zelf.try_to_ref(vm)?;
3179 {
3180 zelf_ref.buffer.store(None);
3181 zelf_ref.encoding.store(None);
3182 zelf_ref.line_buffering.store(false, Ordering::Relaxed);
3183 zelf_ref.write_through.store(false, Ordering::Relaxed);
3184 let mut data = zelf_ref.lock_opt(vm)?;
3185 *data = None;
3186 }
3187 let parsed = args.bind_for(vm, Self::NAME)?;
3188 Self::init(zelf_ref, parsed, vm)
3189 }
3190 }
3191
3192 impl TextIOWrapper {
3193 fn lock_opt(
3194 &self,
3195 vm: &VirtualMachine,
3196 ) -> PyResult<PyThreadMutexGuard<'_, Option<TextIOData>>> {
3197 self.data
3198 .lock_wrapped(|do_lock| vm.allow_threads(do_lock))
3199 .ok_or_else(|| vm.new_runtime_error("reentrant call inside textio"))
3200 }
3201
3202 fn lock(&self, vm: &VirtualMachine) -> PyResult<PyMappedThreadMutexGuard<'_, TextIOData>> {
3203 let lock = self.lock_opt(vm)?;
3204 PyThreadMutexGuard::try_map(lock, |x| x.as_mut())
3205 .map_err(|_| vm.new_value_error("I/O operation on uninitialized object"))
3206 }
3207
3208 fn validate_errors(errors: &Py<PyUtf8Str>, vm: &VirtualMachine) -> PyResult<()> {
3209 if errors.as_pystr().contains_nuls() {
3210 cold_path();
3211 return Err(nul_char_error(vm));
3212 }
3213 vm.state
3214 .codec_registry
3215 .lookup_error(errors.as_str(), vm)
3216 .map(drop)
3217 }
3218
3219 fn bool_from_index(value: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
3220 let int = value.try_index(vm)?;
3221 let value: i32 = int.try_to_primitive(vm)?;
3222 Ok(value != 0)
3223 }
3224
3225 fn resolve_encoding(
3226 encoding: Option<PyUtf8StrRef>,
3227 vm: &VirtualMachine,
3228 ) -> PyResult<PyUtf8StrRef> {
3229 let encoding = match encoding {
3234 None if vm.state.config.settings.utf8_mode > 0 => {
3235 identifier_utf8!(vm, utf_8).to_owned()
3236 }
3237 Some(enc) if enc.as_str() == "locale" => match vm.import("locale", 0) {
3238 Ok(locale) => locale
3239 .get_attr("getencoding", vm)?
3240 .call((), vm)?
3241 .try_into_value(vm)?,
3242 Err(err)
3243 if err.fast_isinstance(vm.ctx.exceptions.import_error)
3244 || err.fast_isinstance(vm.ctx.exceptions.module_not_found_error) =>
3245 {
3246 identifier_utf8!(vm, utf_8).to_owned()
3247 }
3248 Err(err) => return Err(err),
3249 },
3250 Some(enc) => enc,
3251 _ => match vm.import("locale", 0) {
3252 Ok(locale) => locale
3253 .get_attr("getencoding", vm)?
3254 .call((), vm)?
3255 .try_into_value(vm)?,
3256 Err(err)
3257 if err.fast_isinstance(vm.ctx.exceptions.import_error)
3258 || err.fast_isinstance(vm.ctx.exceptions.module_not_found_error) =>
3259 {
3260 identifier_utf8!(vm, utf_8).to_owned()
3261 }
3262 Err(err) => return Err(err),
3263 },
3264 };
3265 if encoding.as_pystr().contains_nuls() {
3266 cold_path();
3267 return Err(nul_char_error(vm));
3268 }
3269 Ok(encoding)
3270 }
3271
3272 fn adjust_encoder_state_for_bom(
3273 encoder: &PyObject,
3274 encoding: &str,
3275 buffer: &PyObject,
3276 vm: &VirtualMachine,
3277 ) -> PyResult<()> {
3278 let needs_bom = matches!(encoding, "utf-8-sig" | "utf-16" | "utf-32");
3279 if !needs_bom {
3280 return Ok(());
3281 }
3282 let seekable = vm.call_method(buffer, "seekable", ())?.try_to_bool(vm)?;
3283 if !seekable {
3284 return Ok(());
3285 }
3286 let pos = vm.call_method(buffer, "tell", ())?;
3287 if vm.bool_eq(&pos, vm.ctx.new_int(0).as_ref())? {
3288 return Ok(());
3289 }
3290 if let Err(err) = vm.call_method(encoder, "setstate", (0,))
3291 && !err.fast_isinstance(vm.ctx.exceptions.attribute_error)
3292 {
3293 return Err(err);
3294 }
3295 Ok(())
3296 }
3297
3298 #[expect(clippy::type_complexity, reason = "ignore warning for now")]
3299 fn find_coder(
3300 buffer: &PyObject,
3301 encoding: &str,
3302 errors: &Py<PyUtf8Str>,
3303 newline: Newlines,
3304 vm: &VirtualMachine,
3305 ) -> PyResult<(
3306 Option<(PyObjectRef, Option<EncodeFunc>)>,
3307 Option<PyObjectRef>,
3308 )> {
3309 let codec = vm.state.codec_registry.lookup(encoding, vm)?;
3310 if !codec.is_text_codec(vm)? {
3311 return Err(vm.new_lookup_error(format!(
3312 "'{encoding}' is not a text encoding; use codecs.open() to handle arbitrary codecs"
3313 )));
3314 }
3315 let errors = errors.to_owned().into_wtf8();
3316
3317 let encoder = if vm.call_method(buffer, "writable", ())?.try_to_bool(vm)? {
3318 let incremental_encoder =
3319 match codec.get_incremental_encoder(Some(errors.clone()), vm) {
3320 Ok(encoder) => encoder,
3321 Err(err)
3322 if err.fast_isinstance(vm.ctx.exceptions.type_error)
3323 || err.fast_isinstance(vm.ctx.exceptions.attribute_error) =>
3324 {
3325 let name = vm
3326 .get_attribute_opt(codec.as_tuple().as_object(), "name")?
3327 .and_then(|obj| obj.downcast::<PyStr>().ok());
3328 StatelessIncrementalEncoder {
3329 encode: codec.get_encode_func().to_owned(),
3330 errors: Some(errors.clone()),
3331 name,
3332 }
3333 .into_ref(&vm.ctx)
3334 .into()
3335 }
3336 Err(err) => return Err(err),
3337 };
3338 let encoding_name = vm.get_attribute_opt(&incremental_encoder, "name")?;
3339 let encode_func = encoding_name.and_then(|name| {
3340 let name = name.downcast_ref::<PyStr>()?;
3341 match name.to_str()? {
3342 "utf-8" => Some(textio_encode_utf8 as EncodeFunc),
3343 _ => None,
3344 }
3345 });
3346 Some((incremental_encoder, encode_func))
3347 } else {
3348 None
3349 };
3350
3351 let decoder = if vm.call_method(buffer, "readable", ())?.try_to_bool(vm)? {
3352 let decoder = match codec.get_incremental_decoder(Some(errors.clone()), vm) {
3353 Ok(decoder) => decoder,
3354 Err(err)
3355 if err.fast_isinstance(vm.ctx.exceptions.type_error)
3356 || err.fast_isinstance(vm.ctx.exceptions.attribute_error) =>
3357 {
3358 StatelessIncrementalDecoder {
3359 decode: codec.get_decode_func().to_owned(),
3360 errors: Some(errors),
3361 }
3362 .into_ref(&vm.ctx)
3363 .into()
3364 }
3365 Err(err) => return Err(err),
3366 };
3367 if let Newlines::Universal | Newlines::Passthrough = newline {
3368 let args = IncrementalNewlineDecoderArgs {
3369 decoder,
3370 translate: matches!(newline, Newlines::Universal),
3371 errors: None,
3372 };
3373 Some(IncrementalNewlineDecoder::construct_and_init(args, vm)?.into())
3374 } else {
3375 Some(decoder)
3376 }
3377 } else {
3378 None
3379 };
3380 Ok((encoder, decoder))
3381 }
3382 }
3383
3384 #[inline]
3385 fn flush_inner(textio: &mut TextIOData, buffer: &PyObject, vm: &VirtualMachine) -> PyResult {
3386 textio.check_closed(buffer, vm)?;
3387 textio.telling = textio.seekable;
3388 textio.write_pending(buffer, vm)?;
3389 vm.call_method(buffer, "flush", ())
3390 }
3391
3392 fn text_buffer(cell: &RawCell, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
3393 cell.load_owned()
3394 .ok_or_else(|| vm.new_value_error("underlying buffer has been detached"))
3395 }
3396
3397 fn textio_encoding(cell: &RawCell, vm: &VirtualMachine) -> PyResult<PyUtf8StrRef> {
3398 let obj = cell
3399 .load_owned()
3400 .ok_or_else(|| vm.new_value_error("I/O operation on uninitialized object"))?;
3401 obj.downcast::<PyUtf8Str>().map_err(|obj| {
3402 vm.new_type_error(format!(
3403 "encoding must be a str, not '{}'",
3404 obj.class().name()
3405 ))
3406 })
3407 }
3408
3409 #[pyclass(
3410 with(
3411 Constructor,
3412 Initializer,
3413 Destructor,
3414 Iterable,
3415 IterNext,
3416 Representable
3417 ),
3418 flags(BASETYPE, HAS_WEAKREF)
3419 )]
3420 impl Py<TextIOWrapper> {
3421 #[pymethod]
3422 fn reconfigure(
3423 &self,
3424 args: TextIOWrapperReconfigureArgs,
3425 vm: &VirtualMachine,
3426 ) -> PyResult<()> {
3427 let mut data = self.lock(vm)?;
3428 let buffer = text_buffer(&self.buffer, vm)?;
3429 data.check_closed(&buffer, vm)?;
3430
3431 let mut encoding = textio_encoding(&self.encoding, vm)?;
3432 let mut errors = data.errors.clone();
3433 let mut newline = data.newline;
3434 let mut encoding_changed = false;
3435 let mut errors_changed = false;
3436 let mut newline_changed = false;
3437 let mut line_buffering = None;
3438 let mut write_through = None;
3439
3440 if let Some(enc) = args.encoding {
3441 if enc.as_pystr().contains_nuls() && enc.as_str().starts_with("locale") {
3442 cold_path();
3443 return Err(vm.new_lookup_error(format!("unknown encoding: {enc}")));
3444 }
3445 let resolved = TextIOWrapper::resolve_encoding(Some(enc), vm)?;
3446 encoding_changed = resolved.as_str() != encoding.as_str();
3447 encoding = resolved;
3448 }
3449
3450 if let Some(errs) = args.errors {
3451 TextIOWrapper::validate_errors(&errs, vm)?;
3452 errors_changed = errs.as_str() != errors.as_str();
3453 errors = errs;
3454 } else if encoding_changed {
3455 errors = identifier_utf8!(vm, strict).to_owned();
3456 errors_changed = true;
3457 }
3458
3459 if let OptionalArg::Present(nl) = args.newline {
3460 let nl = nl.unwrap_or_default();
3461 newline_changed = nl != newline;
3462 newline = nl;
3463 }
3464
3465 if let Some(value) = args.line_buffering {
3466 line_buffering = Some(TextIOWrapper::bool_from_index(&value, vm)?);
3467 }
3468 if let Some(value) = args.write_through {
3469 write_through = Some(TextIOWrapper::bool_from_index(&value, vm)?);
3470 }
3471
3472 if (encoding_changed || newline_changed)
3473 && data.decoder.is_some()
3474 && (data.decoded_chars.is_some()
3475 || data.snapshot.is_some()
3476 || data.decoded_chars_used.chars != 0)
3477 {
3478 return Err(new_unsupported_operation(
3479 "cannot reconfigure encoding or newline after reading from the stream",
3480 vm,
3481 ));
3482 }
3483
3484 if data.pending.num_bytes > 0 {
3485 data.write_pending(&buffer, vm)?;
3486 }
3487 vm.call_method(&buffer, "flush", ())?;
3488
3489 if encoding_changed || errors_changed || newline_changed {
3490 if data.pending.num_bytes > 0 {
3491 data.write_pending(&buffer, vm)?;
3492 }
3493 let (encoder, decoder) =
3494 TextIOWrapper::find_coder(&buffer, encoding.as_str(), &errors, newline, vm)?;
3495 self.encoding.store(Some(encoding.clone().into()));
3496 data.errors = errors;
3497 data.newline = newline;
3498 data.encoder = encoder;
3499 data.decoder = decoder;
3500 data.set_decoded_chars(None);
3501 data.snapshot = None;
3502 data.decoded_chars_used = Utf8size::default();
3503 if let Some((encoder, _)) = &data.encoder {
3504 TextIOWrapper::adjust_encoder_state_for_bom(
3505 encoder,
3506 encoding.as_str(),
3507 &buffer,
3508 vm,
3509 )?;
3510 }
3511 }
3512
3513 if let Some(line_buffering) = line_buffering {
3514 self.line_buffering.store(line_buffering, Ordering::Relaxed);
3515 }
3516 if let Some(write_through) = write_through {
3517 self.write_through.store(write_through, Ordering::Relaxed);
3518 }
3519 Ok(())
3520 }
3521
3522 #[pymethod]
3523 fn detach(zelf: PyRef<TextIOWrapper>, vm: &VirtualMachine) -> PyResult {
3524 let mut textio = zelf.lock(vm)?;
3525 let buffer = text_buffer(&zelf.buffer, vm)?;
3526 flush_inner(&mut textio, &buffer, vm)?;
3527 zelf.buffer.store(None);
3528 Ok(buffer)
3529 }
3530
3531 #[pymethod]
3532 fn seekable(&self, vm: &VirtualMachine) -> PyResult {
3533 let textio = self.lock(vm)?;
3534 let buffer = text_buffer(&self.buffer, vm)?;
3535 drop(textio);
3536 vm.call_method(&buffer, "seekable", ())
3537 }
3538
3539 #[pymethod]
3540 fn readable(&self, vm: &VirtualMachine) -> PyResult {
3541 let textio = self.lock(vm)?;
3542 let buffer = text_buffer(&self.buffer, vm)?;
3543 drop(textio);
3544 vm.call_method(&buffer, "readable", ())
3545 }
3546
3547 #[pymethod]
3548 fn writable(&self, vm: &VirtualMachine) -> PyResult {
3549 let textio = self.lock(vm)?;
3550 let buffer = text_buffer(&self.buffer, vm)?;
3551 drop(textio);
3552 vm.call_method(&buffer, "writable", ())
3553 }
3554
3555 #[pygetset]
3556 fn newlines(&self, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
3557 let data = self.lock(vm)?;
3558 let Some(decoder) = &data.decoder else {
3559 return Ok(None);
3560 };
3561 vm.get_attribute_opt(decoder, "newlines")
3562 }
3563
3564 #[pygetset(name = "_CHUNK_SIZE")]
3565 fn chunksize(&self, vm: &VirtualMachine) -> PyResult<usize> {
3566 Ok(self.lock(vm)?.chunk_size)
3567 }
3568
3569 #[pygetset(setter, name = "_CHUNK_SIZE")]
3570 fn set_chunksize(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
3571 {
3572 let textio = self.lock(vm)?;
3573 if self.buffer.deref().is_none() {
3574 return Err(vm.new_value_error("underlying buffer has been detached"));
3575 }
3576 drop(textio);
3577 }
3578
3579 let chunk_size: isize = match value {
3580 PySetterValue::Assign(object_value) => {
3581 let integer = object_value.try_index(vm)?;
3582
3583 integer.try_to_primitive::<isize>(vm).map_err(|_| {
3584 let class = object_value.class();
3585 let type_name = class.name();
3586 let mut end = type_name.len().min(200);
3587 while !type_name.is_char_boundary(end) {
3588 end -= 1;
3589 }
3590 vm.new_value_error(format!(
3591 "cannot fit '{}' into an index-sized integer",
3592 &type_name[..end]
3593 ))
3594 })?
3595 }
3596 PySetterValue::Delete => {
3597 return Err(vm.new_attribute_error("cannot delete attribute"));
3598 }
3599 };
3600
3601 if chunk_size <= 0 {
3602 return Err(vm.new_value_error("a strictly positive integer is required"));
3603 }
3604
3605 let chunk_size = usize::try_from(chunk_size)
3606 .map_err(|_| vm.new_value_error("a strictly positive integer is required"))?;
3607
3608 let mut textio = self.lock(vm)?;
3609 textio.chunk_size = chunk_size;
3610 Ok(())
3611 }
3612
3613 #[pymethod]
3614 fn seek(
3615 zelf: PyRef<TextIOWrapper>,
3616 cookie: PyObjectRef,
3617 whence: HowArg,
3618 vm: &VirtualMachine,
3619 ) -> PyResult {
3620 let how = whence.whence;
3621
3622 let reset_encoder = |encoder, start_of_stream| {
3623 if start_of_stream {
3624 vm.call_method(encoder, "reset", ())
3625 } else {
3626 vm.call_method(encoder, "setstate", (0,))
3627 }
3628 };
3629
3630 let textio = zelf.lock(vm)?;
3631
3632 if !textio.seekable {
3633 return Err(new_unsupported_operation(
3634 "underlying stream is not seekable",
3635 vm,
3636 ));
3637 }
3638
3639 let cookie = match how {
3640 0 => cookie,
3642 1 => {
3644 if vm.bool_eq(&cookie, vm.ctx.new_int(0).as_ref())? {
3645 let buffer = text_buffer(&zelf.buffer, vm)?;
3646 vm.call_method(&buffer, "tell", ())?
3647 } else {
3648 return Err(new_unsupported_operation(
3649 "can't do nonzero cur-relative seeks",
3650 vm,
3651 ));
3652 }
3653 }
3654 2 => {
3656 if vm.bool_eq(&cookie, vm.ctx.new_int(0).as_ref())? {
3657 drop(textio);
3658 vm.call_method(zelf.as_object(), "flush", ())?;
3659 let mut textio = zelf.lock(vm)?;
3660 textio.set_decoded_chars(None);
3661 textio.snapshot = None;
3662 if let Some(decoder) = &textio.decoder {
3663 vm.call_method(decoder, "reset", ())?;
3664 }
3665 let buffer = text_buffer(&zelf.buffer, vm)?;
3666 let res = vm.call_method(&buffer, "seek", (0, 2))?;
3667 if let Some((encoder, _)) = &textio.encoder {
3668 let start_of_stream = vm.bool_eq(&res, vm.ctx.new_int(0).as_ref())?;
3669 reset_encoder(encoder, start_of_stream)?;
3670 }
3671 return Ok(res);
3672 }
3673 return Err(new_unsupported_operation(
3674 "can't do nonzero end-relative seeks",
3675 vm,
3676 ));
3677 }
3678 _ => {
3679 return Err(
3680 vm.new_value_error(format!("invalid whence ({how}, should be 0, 1 or 2)"))
3681 );
3682 }
3683 };
3684 use crate::types::PyComparisonOp;
3685 if cookie.rich_compare_bool(vm.ctx.new_int(0).as_ref(), PyComparisonOp::Lt, vm)? {
3686 return Err(
3687 vm.new_value_error(format!("negative seek position {}", cookie.repr(vm)?))
3688 );
3689 }
3690 drop(textio);
3691 vm.call_method(zelf.as_object(), "flush", ())?;
3692 let cookie_obj = crate::builtins::PyIntRef::try_from_object(vm, cookie)?;
3693 let cookie = TextIOCookie::parse(cookie_obj.as_bigint())
3694 .ok_or_else(|| vm.new_value_error("invalid cookie"))?;
3695 let mut textio = zelf.lock(vm)?;
3696 let buffer = text_buffer(&zelf.buffer, vm)?;
3697 vm.call_method(&buffer, "seek", (cookie.start_pos,))?;
3698 textio.set_decoded_chars(None);
3699 textio.snapshot = None;
3700 if let Some(decoder) = &textio.decoder {
3701 cookie.set_decoder_state(decoder, vm)?;
3702 }
3703 if cookie.chars_to_skip != 0 {
3704 let decoder = textio
3705 .decoder
3706 .clone()
3707 .ok_or_else(|| vm.new_value_error("invalid cookie"))?;
3708 let input_chunk = vm.call_method(&buffer, "read", (cookie.bytes_to_feed,))?;
3709 let input_chunk: PyBytesRef = input_chunk.downcast().map_err(|obj| {
3710 vm.new_type_error(format!(
3711 "underlying read() should have returned a bytes object, not '{}'",
3712 obj.class().name()
3713 ))
3714 })?;
3715 textio.snapshot = Some((cookie.dec_flags, input_chunk.clone()));
3716 let decoded = vm.call_method(&decoder, "decode", (input_chunk, cookie.need_eof))?;
3717 let decoded = check_decoded(decoded, vm)?;
3718 let num_to_skip = cookie.num_to_skip();
3722 let pos_is_valid = num_to_skip.chars <= decoded.char_len()
3723 && decoded.as_wtf8().is_code_point_boundary(num_to_skip.bytes);
3724 textio.set_decoded_chars(Some(decoded));
3725 if !pos_is_valid {
3726 return Err(vm.new_os_error("can't restore logical file position"));
3727 }
3728 textio.decoded_chars_used = num_to_skip;
3729 } else {
3730 textio.snapshot = Some((cookie.dec_flags, PyBytes::from(vec![]).into_ref(&vm.ctx)))
3731 }
3732 if let Some((encoder, _)) = &textio.encoder {
3733 let start_of_stream = cookie.start_pos == 0 && cookie.dec_flags == 0;
3734 reset_encoder(encoder, start_of_stream)?;
3735 }
3736 Ok(cookie_obj.into())
3737 }
3738
3739 #[pymethod]
3740 fn tell(zelf: PyRef<TextIOWrapper>, vm: &VirtualMachine) -> PyResult {
3741 let mut textio = zelf.lock(vm)?;
3742 if !textio.seekable {
3743 return Err(new_unsupported_operation(
3744 "underlying stream is not seekable",
3745 vm,
3746 ));
3747 }
3748 if !textio.telling {
3749 return Err(vm.new_os_error("telling position disabled by next() call"));
3750 }
3751 let buffer = text_buffer(&zelf.buffer, vm)?;
3752 textio.write_pending(&buffer, vm)?;
3753 drop(textio);
3754 vm.call_method(zelf.as_object(), "flush", ())?;
3755 let textio = zelf.lock(vm)?;
3756 let buffer = text_buffer(&zelf.buffer, vm)?;
3757 let pos = vm.call_method(&buffer, "tell", ())?;
3758 let (decoder, (dec_flags, next_input)) = match (&textio.decoder, &textio.snapshot) {
3759 (Some(d), Some(s)) => (d, s),
3760 _ => return Ok(pos),
3761 };
3762 let pos = Offset::try_from_object(vm, pos)?;
3763 let mut cookie = TextIOCookie {
3764 start_pos: pos - next_input.as_bytes().len() as Offset,
3765 dec_flags: *dec_flags,
3766 ..Default::default()
3767 };
3768 if textio.decoded_chars_used.bytes == 0 {
3769 return Ok(cookie.build().to_pyobject(vm));
3770 }
3771 let decoder_getstate = || {
3772 let state = vm.call_method(decoder, "getstate", ())?;
3773 parse_decoder_state(state, vm)
3774 };
3775 let decoder_decode = |b: &[u8]| {
3776 let decoded = vm.call_method(decoder, "decode", (vm.ctx.new_bytes(b.to_vec()),))?;
3777 let decoded = check_decoded(decoded, vm)?;
3778 Ok(Utf8size::len_pystr(&decoded))
3779 };
3780 let saved_state = vm.call_method(decoder, "getstate", ())?;
3781 let mut num_to_skip = textio.decoded_chars_used;
3782 let mut skip_bytes = (textio.b2cratio * num_to_skip.chars as f64) as isize;
3783 let mut skip_back = 1;
3784 while skip_bytes > 0 {
3785 cookie.set_decoder_state(decoder, vm)?;
3786 let input = &next_input.as_bytes()[..skip_bytes as usize];
3787 let n_decoded = decoder_decode(input)?;
3788 if n_decoded.chars <= num_to_skip.chars {
3789 let (dec_buffer, dec_flags) = decoder_getstate()?;
3790 if dec_buffer.as_bytes().is_empty() {
3791 cookie.dec_flags = dec_flags;
3792 num_to_skip -= n_decoded;
3793 break;
3794 }
3795 skip_bytes -= dec_buffer.as_bytes().len() as isize;
3796 skip_back = 1;
3797 } else {
3798 skip_bytes -= skip_back;
3799 skip_back *= 2;
3800 }
3801 }
3802 if skip_bytes <= 0 {
3803 skip_bytes = 0;
3804 cookie.set_decoder_state(decoder, vm)?;
3805 }
3806 let skip_bytes = skip_bytes as usize;
3807
3808 cookie.start_pos += skip_bytes as Offset;
3809 cookie.set_num_to_skip(num_to_skip);
3810
3811 if num_to_skip.chars != 0 {
3812 let mut n_decoded = Utf8size::default();
3813 let mut input = next_input.as_bytes();
3814 input = &input[skip_bytes..];
3815 while !input.is_empty() {
3816 let (byte1, rest) = input.split_at(1);
3817 let n = decoder_decode(byte1)?;
3818 n_decoded += n;
3819 cookie.bytes_to_feed += 1;
3820 let (dec_buffer, dec_flags) = decoder_getstate()?;
3821 if dec_buffer.as_bytes().is_empty() && n_decoded.chars <= num_to_skip.chars {
3822 cookie.start_pos += cookie.bytes_to_feed as Offset;
3823 num_to_skip -= n_decoded;
3824 cookie.dec_flags = dec_flags;
3825 cookie.bytes_to_feed = 0;
3826 n_decoded = Utf8size::default();
3827 }
3828 if n_decoded.chars >= num_to_skip.chars {
3829 break;
3830 }
3831 input = rest;
3832 }
3833 if input.is_empty() {
3834 let decoded =
3835 vm.call_method(decoder, "decode", (vm.ctx.new_bytes(vec![]), true))?;
3836 let decoded = check_decoded(decoded, vm)?;
3837 let final_decoded_chars = n_decoded.chars + decoded.char_len();
3838 cookie.need_eof = true;
3839 if final_decoded_chars < num_to_skip.chars {
3840 return Err(vm.new_os_error("can't reconstruct logical file position"));
3841 }
3842 }
3843 }
3844 vm.call_method(decoder, "setstate", (saved_state,))?;
3845 cookie.set_num_to_skip(num_to_skip);
3846 Ok(cookie.build().to_pyobject(vm))
3847 }
3848
3849 #[pygetset]
3850 fn name(&self, vm: &VirtualMachine) -> PyResult {
3851 let _textio = self.lock(vm)?;
3852 text_buffer(&self.buffer, vm)?.get_attr("name", vm)
3853 }
3854
3855 #[pygetset]
3856 fn errors(&self, vm: &VirtualMachine) -> PyResult<PyUtf8StrRef> {
3857 Ok(self.lock(vm)?.errors.clone())
3858 }
3859
3860 #[pymethod]
3861 fn fileno(&self, vm: &VirtualMachine) -> PyResult {
3862 let _textio = self.lock(vm)?;
3863 let buffer = text_buffer(&self.buffer, vm)?;
3864 vm.call_method(&buffer, "fileno", ())
3865 }
3866
3867 #[pymethod]
3868 fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<PyStrRef> {
3869 let mut textio = self.lock(vm)?;
3870 let buffer = text_buffer(&self.buffer, vm)?;
3871 textio.check_closed(&buffer, vm)?;
3872 let decoder = textio
3873 .decoder
3874 .clone()
3875 .ok_or_else(|| new_unsupported_operation("not readable", vm))?;
3876
3877 textio.write_pending(&buffer, vm)?;
3878
3879 let s = if let Some(mut remaining) = size.to_usize() {
3880 let mut chunks = Vec::new();
3881 let mut chunks_bytes = 0;
3882 loop {
3883 if let Some((s, char_len)) = textio.get_decoded_chars(remaining, vm) {
3884 chunks_bytes += s.byte_len();
3885 chunks.push(s);
3886 remaining = remaining.saturating_sub(char_len);
3887 }
3888 if remaining == 0 {
3889 break;
3890 }
3891 let eof = textio.read_chunk(&buffer, remaining, vm)?;
3892 if eof {
3893 break;
3894 }
3895 }
3896 if chunks.is_empty() {
3897 vm.ctx.empty_str.to_owned()
3898 } else if chunks.len() == 1 {
3899 chunks.pop().unwrap()
3900 } else {
3901 let mut ret = Wtf8Buf::with_capacity(chunks_bytes);
3902 for chunk in chunks {
3903 ret.push_wtf8(chunk.as_wtf8())
3904 }
3905 PyStr::from(ret).into_ref(&vm.ctx)
3906 }
3907 } else {
3908 let bytes = vm.call_method(&buffer, "read", ())?;
3909 let decoded = vm.call_method(&decoder, "decode", (bytes, true))?;
3910 let decoded = check_decoded(decoded, vm)?;
3911 let ret = textio.take_decoded_chars(Some(decoded), vm);
3912 textio.snapshot = None;
3913 ret
3914 };
3915 Ok(s)
3916 }
3917
3918 #[pymethod]
3919 fn write(&self, text: PyStrRef, vm: &VirtualMachine) -> PyResult<usize> {
3920 let mut textio = self.lock(vm)?;
3921 let buffer = text_buffer(&self.buffer, vm)?;
3922 textio.check_closed(&buffer, vm)?;
3923
3924 let (encoder, encode_func) = textio
3925 .encoder
3926 .as_ref()
3927 .ok_or_else(|| new_unsupported_operation("not writable", vm))?;
3928
3929 let char_len = text.char_len();
3930
3931 let data = text.as_wtf8();
3932
3933 let replace_nl = match textio.newline {
3934 Newlines::Lf => Some("\n"),
3935 Newlines::Cr => Some("\r"),
3936 Newlines::Crlf => Some("\r\n"),
3937 Newlines::Universal if cfg!(windows) => Some("\r\n"),
3938 _ => None,
3939 };
3940 let line_buffering = self.line_buffering.load(Ordering::Relaxed);
3941 let has_lf =
3942 (replace_nl.is_some() || line_buffering) && data.contains_code_point('\n'.into());
3943 let flush = line_buffering && (has_lf || data.contains_code_point('\r'.into()));
3944 let chunk = if let Some(replace_nl) = replace_nl {
3945 if has_lf {
3946 PyStr::from(data.replace("\n".as_ref(), replace_nl.as_ref())).into_ref(&vm.ctx)
3947 } else {
3948 text
3949 }
3950 } else {
3951 text
3952 };
3953 let chunk = if let Some(encode_func) = *encode_func {
3954 encode_func(chunk)
3955 } else {
3956 let b = vm.call_method(encoder, "encode", (chunk.clone(),))?;
3957 b.downcast::<PyBytes>()
3958 .map(PendingWrite::Bytes)
3959 .or_else(|obj| {
3960 if obj.is(&chunk) {
3963 Ok(PendingWrite::Utf8(chunk))
3964 } else {
3965 Err(vm.new_type_error(format!(
3966 "encoder should return a bytes object, not '{}'",
3967 obj.class().name()
3968 )))
3969 }
3970 })?
3971 };
3972 if textio.pending.num_bytes > 0
3973 && textio.pending.num_bytes + chunk.as_bytes().len() > textio.chunk_size
3974 {
3975 let pending = textio.pending.take(vm);
3976 drop(textio);
3977 vm.call_method(&buffer, "write", (pending,))?;
3978 textio = self.lock(vm)?;
3979 let buffer = text_buffer(&self.buffer, vm)?;
3980 textio.check_closed(&buffer, vm)?;
3981 if textio.pending.num_bytes > 0 {
3982 let pending = textio.pending.take(vm);
3983 drop(textio);
3984 vm.call_method(&buffer, "write", (pending,))?;
3985 textio = self.lock(vm)?;
3986 let buffer = text_buffer(&self.buffer, vm)?;
3987 textio.check_closed(&buffer, vm)?;
3988 }
3989 }
3990 textio.pending.push(chunk);
3991 if textio.pending.num_bytes > 0
3992 && (flush
3993 || self.write_through.load(Ordering::Relaxed)
3994 || textio.pending.num_bytes >= textio.chunk_size)
3995 {
3996 let buffer = text_buffer(&self.buffer, vm)?;
3997 let pending = textio.pending.take(vm);
3998 drop(textio);
3999 vm.call_method(&buffer, "write", (pending,))?;
4000 textio = self.lock(vm)?;
4001 let buffer = text_buffer(&self.buffer, vm)?;
4002 textio.check_closed(&buffer, vm)?;
4003 }
4004 if flush {
4005 let buffer = text_buffer(&self.buffer, vm)?;
4006 let _ = vm.call_method(&buffer, "flush", ());
4007 }
4008
4009 Ok(char_len)
4010 }
4011
4012 #[pymethod]
4013 fn flush(&self, vm: &VirtualMachine) -> PyResult {
4014 let mut textio = self.lock(vm)?;
4015 let buffer = text_buffer(&self.buffer, vm)?;
4016 flush_inner(&mut textio, &buffer, vm)
4017 }
4018
4019 #[pymethod]
4020 fn truncate(zelf: PyRef<TextIOWrapper>, pos: KeepNonePos, vm: &VirtualMachine) -> PyResult {
4021 let mut textio = zelf.lock(vm)?;
4023 let buffer = text_buffer(&zelf.buffer, vm)?;
4024 flush_inner(&mut textio, &buffer, vm)?;
4025 drop(textio);
4026
4027 let pos = match pos.pos.into_option() {
4028 Some(p) => p,
4029 None => vm.call_method(zelf.as_object(), "tell", ())?,
4030 };
4031 vm.call_method(&buffer, "truncate", (pos,))
4032 }
4033
4034 #[pymethod]
4035 fn isatty(&self, vm: &VirtualMachine) -> PyResult {
4036 let textio = self.lock(vm)?;
4037 let buffer = text_buffer(&self.buffer, vm)?;
4038 textio.check_closed(&buffer, vm)?;
4039 vm.call_method(&buffer, "isatty", ())
4040 }
4041
4042 #[pymethod]
4043 fn readline(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<PyStrRef> {
4044 let limit = size.to_usize();
4045
4046 let mut textio = self.lock(vm)?;
4047 let buffer = text_buffer(&self.buffer, vm)?;
4048 check_closed(&buffer, vm)?;
4049
4050 textio.write_pending(&buffer, vm)?;
4051
4052 #[derive(Clone)]
4053 struct SlicedStr(PyStrRef, Range<usize>);
4054
4055 impl SlicedStr {
4056 #[inline]
4057 fn byte_len(&self) -> usize {
4058 self.1.len()
4059 }
4060
4061 #[inline]
4062 fn char_len(&self) -> usize {
4063 if self.is_full_slice() {
4064 self.0.char_len()
4065 } else {
4066 self.slice().code_points().count()
4067 }
4068 }
4069
4070 #[inline]
4071 fn is_full_slice(&self) -> bool {
4072 self.1.len() >= self.0.byte_len()
4073 }
4074
4075 #[inline]
4076 fn slice(&self) -> &Wtf8 {
4077 &self.0.as_wtf8()[self.1.clone()]
4078 }
4079
4080 #[inline]
4081 fn slice_pystr(self, vm: &VirtualMachine) -> PyStrRef {
4082 if self.is_full_slice() {
4083 self.0
4084 } else {
4085 PyStr::from(self.slice()).into_ref(&vm.ctx)
4087 }
4088 }
4089
4090 fn utf8_len(&self) -> Utf8size {
4091 Utf8size {
4092 bytes: self.byte_len(),
4093 chars: self.char_len(),
4094 }
4095 }
4096 }
4097
4098 let mut start;
4099 let mut end_pos;
4100 let mut offset_to_buffer;
4101 let mut chunked = Utf8size::default();
4102 let mut remaining: Option<SlicedStr> = None;
4103 let mut chunks = Vec::new();
4104
4105 let cur_line = 'outer: loop {
4106 let decoded_chars = loop {
4107 match textio.decoded_chars.as_ref() {
4108 Some(s) if !s.is_empty() => break s,
4109 _ => {}
4110 }
4111 let eof = textio.read_chunk(&buffer, 0, vm)?;
4112 if eof {
4113 textio.set_decoded_chars(None);
4114 textio.snapshot = None;
4115 start = Utf8size::default();
4116 end_pos = Utf8size::default();
4117 offset_to_buffer = Utf8size::default();
4118 break 'outer None;
4119 }
4120 };
4121 let line = match remaining.take() {
4122 None => {
4123 start = textio.decoded_chars_used;
4124 offset_to_buffer = Utf8size::default();
4125 decoded_chars.clone()
4126 }
4127 Some(remaining) => {
4128 assert_eq!(textio.decoded_chars_used.bytes, 0);
4129 offset_to_buffer = remaining.utf8_len();
4130 let decoded_chars = decoded_chars.as_wtf8();
4131 let line = if remaining.is_full_slice() {
4132 let mut line = remaining.0;
4133 line.concat_in_place(decoded_chars, vm);
4134 line
4135 } else {
4136 let remaining = remaining.slice();
4137 let mut s =
4138 Wtf8Buf::with_capacity(remaining.len() + decoded_chars.len());
4139 s.push_wtf8(remaining);
4140 s.push_wtf8(decoded_chars);
4141 PyStr::from(s).into_ref(&vm.ctx)
4142 };
4143 start = Utf8size::default();
4144 line
4145 }
4146 };
4147 let line_from_start = &line.as_wtf8()[start.bytes..];
4148 let nl_res = textio.newline.find_newline(line_from_start);
4149 match nl_res {
4150 Ok(p) | Err(p) => {
4151 end_pos = start + Utf8size::len_str(&line_from_start[..p]);
4152 if let Some(limit) = limit {
4153 if chunked.chars + end_pos.chars >= limit {
4155 end_pos = start
4156 + Utf8size {
4157 chars: limit - chunked.chars,
4158 bytes: crate::common::str::codepoint_range_end(
4159 line_from_start,
4160 limit - chunked.chars,
4161 )
4162 .unwrap(),
4163 };
4164 break Some(line);
4165 }
4166 }
4167 }
4168 }
4169 if nl_res.is_ok() {
4170 break Some(line);
4171 }
4172 if end_pos.bytes > start.bytes {
4173 let chunk = SlicedStr(line.clone(), start.bytes..end_pos.bytes);
4174 chunked += chunk.utf8_len();
4175 chunks.push(chunk);
4176 }
4177 let line_len = line.byte_len();
4178 if end_pos.bytes < line_len {
4179 remaining = Some(SlicedStr(line, end_pos.bytes..line_len));
4180 }
4181 textio.set_decoded_chars(None);
4182 };
4183
4184 let cur_line = cur_line.map(|line| {
4185 textio.decoded_chars_used = end_pos - offset_to_buffer;
4186 SlicedStr(line, start.bytes..end_pos.bytes)
4187 });
4188 let mut chunked = chunked.bytes;
4190 if let Some(remaining) = remaining {
4191 chunked += remaining.byte_len();
4192 chunks.push(remaining);
4193 }
4194 let line = if !chunks.is_empty() {
4195 if let Some(cur_line) = cur_line {
4196 chunked += cur_line.byte_len();
4197 chunks.push(cur_line);
4198 }
4199 let mut s = Wtf8Buf::with_capacity(chunked);
4200 for chunk in chunks {
4201 s.push_wtf8(chunk.slice())
4202 }
4203 PyStr::from(s).into_ref(&vm.ctx)
4204 } else if let Some(cur_line) = cur_line {
4205 cur_line.slice_pystr(vm)
4206 } else {
4207 vm.ctx.empty_str.to_owned()
4208 };
4209 Ok(line)
4210 }
4211
4212 #[pymethod]
4213 fn close(zelf: PyRef<TextIOWrapper>, vm: &VirtualMachine) -> PyResult<()> {
4214 let _textio = zelf.lock(vm)?;
4215 let Some(buffer) = zelf.buffer.load_owned() else {
4216 return Err(vm.new_value_error("underlying buffer has been detached"));
4217 };
4218 drop(_textio);
4219 if file_closed(&buffer, vm)? {
4220 return Ok(());
4221 }
4222 if zelf.buffer.deref().is_none() {
4227 return Ok(());
4228 }
4229 if zelf.finalizing.load(Ordering::Relaxed) {
4230 let _ = vm.call_method(&buffer, "_dealloc_warn", (zelf.as_object().to_owned(),));
4232 }
4233 let flush_res = vm.call_method(zelf.as_object(), "flush", ()).map(drop);
4234 let close_res = vm.call_method(&buffer, "close", ()).map(drop);
4235 exception_chain(flush_res, close_res)
4236 }
4237
4238 #[pygetset]
4239 fn closed(&self, vm: &VirtualMachine) -> PyResult {
4240 let _textio = self.lock(vm)?;
4241 text_buffer(&self.buffer, vm)?.get_attr("closed", vm)
4242 }
4243
4244 #[pymethod]
4245 fn __getstate__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
4246 Err(vm.new_type_error(format!("cannot pickle '{}' instances", zelf.class().name())))
4247 }
4248
4249 #[pymethod]
4250 fn __reduce_ex__(zelf: PyObjectRef, proto: usize, vm: &VirtualMachine) -> PyResult {
4251 if zelf.class().is(TextIOWrapper::static_type()) {
4252 return Err(
4253 vm.new_type_error(format!("cannot pickle '{}' object", zelf.class().name()))
4254 );
4255 }
4256 let _ = proto;
4257 reduce_ex_for_subclass(&zelf, vm)
4258 }
4259 }
4260
4261 fn parse_decoder_state(state: PyObjectRef, vm: &VirtualMachine) -> PyResult<(PyBytesRef, i32)> {
4262 use crate::builtins::{PyTuple, int};
4263 let state_err = || vm.new_type_error("illegal decoder state");
4264 let state = state.downcast::<PyTuple>().map_err(|_| state_err())?;
4265 match state.as_slice() {
4266 [buf, flags] => {
4267 let buf = buf.clone().downcast::<PyBytes>().map_err(|obj| {
4268 vm.new_type_error(format!(
4269 "illegal decoder state: the first item should be a bytes object, not '{}'",
4270 obj.class().name()
4271 ))
4272 })?;
4273 let flags = flags.downcast_ref::<int::PyInt>().ok_or_else(state_err)?;
4274 let flags = flags.try_to_primitive(vm)?;
4275 Ok((buf, flags))
4276 }
4277 _ => Err(state_err()),
4278 }
4279 }
4280
4281 impl TextIOData {
4282 fn write_pending(&mut self, buffer: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
4283 if self.pending.num_bytes == 0 {
4284 return Ok(());
4285 }
4286 let data = self.pending.take(vm);
4287 vm.call_method(buffer, "write", (data,))?;
4288 Ok(())
4289 }
4290
4291 fn read_chunk(
4293 &mut self,
4294 buffer: &PyObject,
4295 size_hint: usize,
4296 vm: &VirtualMachine,
4297 ) -> PyResult<bool> {
4298 let decoder = self
4299 .decoder
4300 .as_ref()
4301 .ok_or_else(|| new_unsupported_operation("not readable", vm))?;
4302
4303 let dec_state = if self.telling {
4304 let state = vm.call_method(decoder, "getstate", ())?;
4305 Some(parse_decoder_state(state, vm)?)
4306 } else {
4307 None
4308 };
4309
4310 let method = if self.has_read1 { "read1" } else { "read" };
4311 let size_hint = if size_hint > 0 {
4312 (self.b2cratio.max(1.0) * size_hint as f64) as usize
4313 } else {
4314 size_hint
4315 };
4316 let chunk_size = core::cmp::max(self.chunk_size, size_hint);
4317 let input_chunk = vm.call_method(buffer, method, (chunk_size,))?;
4318
4319 let buf = ArgBytesLike::try_from_borrowed_object(vm, &input_chunk).map_err(|_| {
4320 vm.new_type_error(format!(
4321 "underlying {}() should have returned a bytes-like object, not '{}'",
4322 method,
4323 input_chunk.class().name()
4324 ))
4325 })?;
4326 let nbytes = buf.borrow_buf().len();
4327 let eof = nbytes == 0;
4328 let decoded = vm.call_method(decoder, "decode", (input_chunk, eof))?;
4329 let decoded = check_decoded(decoded, vm)?;
4330
4331 let char_len = decoded.char_len();
4332 self.b2cratio = if char_len > 0 {
4333 nbytes as f64 / char_len as f64
4334 } else {
4335 0.0
4336 };
4337 let eof = if char_len > 0 { false } else { eof };
4338 self.set_decoded_chars(Some(decoded));
4339
4340 if let Some((dec_buffer, dec_flags)) = dec_state {
4341 let mut next_input = dec_buffer.as_bytes().to_vec();
4343 next_input.extend_from_slice(&buf.borrow_buf());
4344 self.snapshot = Some((dec_flags, PyBytes::from(next_input).into_ref(&vm.ctx)));
4345 }
4346
4347 Ok(eof)
4348 }
4349
4350 fn check_closed(&self, buffer: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
4351 check_closed(buffer, vm)
4352 }
4353
4354 fn get_decoded_chars(
4357 &mut self,
4358 n: usize,
4359 vm: &VirtualMachine,
4360 ) -> Option<(PyStrRef, usize)> {
4361 if n == 0 {
4362 return None;
4363 }
4364 let decoded_chars = self.decoded_chars.as_ref()?;
4365 let avail = &decoded_chars.as_wtf8()[self.decoded_chars_used.bytes..];
4366 if avail.is_empty() {
4367 return None;
4368 }
4369 let avail_chars = decoded_chars.char_len() - self.decoded_chars_used.chars;
4370 let (chars, chars_used) = if n >= avail_chars {
4371 if self.decoded_chars_used.bytes == 0 {
4372 (decoded_chars.clone(), avail_chars)
4373 } else {
4374 (PyStr::from(avail).into_ref(&vm.ctx), avail_chars)
4375 }
4376 } else {
4377 let s = crate::common::str::get_codepoints(avail, 0..n);
4378 (PyStr::from(s).into_ref(&vm.ctx), n)
4379 };
4380 self.decoded_chars_used += Utf8size {
4381 bytes: chars.byte_len(),
4382 chars: chars_used,
4383 };
4384 Some((chars, chars_used))
4385 }
4386
4387 fn set_decoded_chars(&mut self, s: Option<PyStrRef>) {
4388 self.decoded_chars = s;
4389 self.decoded_chars_used = Utf8size::default();
4390 }
4391
4392 fn take_decoded_chars(
4393 &mut self,
4394 append: Option<PyStrRef>,
4395 vm: &VirtualMachine,
4396 ) -> PyStrRef {
4397 let empty_str = || vm.ctx.empty_str.to_owned();
4398 let chars_pos = core::mem::take(&mut self.decoded_chars_used).bytes;
4399 let decoded_chars = match core::mem::take(&mut self.decoded_chars) {
4400 None => return append.unwrap_or_else(empty_str),
4401 Some(s) if s.is_empty() => return append.unwrap_or_else(empty_str),
4402 Some(s) => s,
4403 };
4404 let append_len = append.as_ref().map_or(0, |s| s.byte_len());
4405 if append_len == 0 && chars_pos == 0 {
4406 return decoded_chars;
4407 }
4408 let decoded_chars_unused = &decoded_chars.as_wtf8()[chars_pos..];
4410 let mut s = Wtf8Buf::with_capacity(decoded_chars_unused.len() + append_len);
4411 s.push_wtf8(decoded_chars_unused);
4412 if let Some(append) = append {
4413 s.push_wtf8(append.as_wtf8())
4414 }
4415 PyStr::from(s).into_ref(&vm.ctx)
4416 }
4417 }
4418
4419 impl Destructor for TextIOWrapper {
4420 fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
4421 if let Some(wrapper) = zelf.downcast_ref::<Self>() {
4422 wrapper.finalizing.store(true, Ordering::Relaxed);
4423 }
4424 iobase_finalize(zelf, vm);
4425 Ok(())
4426 }
4427
4428 #[cold]
4429 fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> {
4430 unreachable!("slot_del is implemented")
4431 }
4432 }
4433
4434 impl Representable for TextIOWrapper {
4435 #[inline]
4436 fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
4437 let type_name = zelf.class().slot_name();
4438 let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) else {
4439 return Err(
4440 vm.new_runtime_error(format!("reentrant call inside {type_name}.__repr__"))
4441 );
4442 };
4443 let Some(data) = zelf.data.lock_wrapped(|do_lock| vm.allow_threads(do_lock)) else {
4447 return Ok(vm.ctx.new_str(Wtf8Buf::from(format!("<{type_name}>"))));
4449 };
4450 if data.is_none() {
4451 return Err(vm.new_value_error("I/O operation on uninitialized object"));
4452 }
4453
4454 let mut result = Wtf8Buf::from(format!("<{type_name}"));
4455 let buffer = zelf.buffer.load_owned();
4456
4457 if let Some(buffer) = &buffer
4459 && let Ok(Some(name)) = vm.get_attribute_opt(buffer, "name")
4460 {
4461 let name_repr = name.repr(vm)?;
4462 result.push_wtf8(" name=".as_ref());
4463 result.push_wtf8(name_repr.as_wtf8());
4464 }
4465
4466 let mode_obj = match vm.get_attribute_opt(zelf.as_object(), "mode") {
4468 Ok(Some(mode)) => Some(mode),
4469 Ok(None) | Err(_) => match buffer.as_ref() {
4470 Some(buffer) => match vm.get_attribute_opt(buffer, "mode") {
4471 Ok(Some(mode)) => Some(mode),
4472 _ => None,
4473 },
4474 None => None,
4475 },
4476 };
4477 if let Some(mode) = mode_obj {
4478 let mode_repr = mode.repr(vm)?;
4479 result.push_wtf8(" mode=".as_ref());
4480 result.push_wtf8(mode_repr.as_wtf8());
4481 }
4482
4483 let encoding = textio_encoding(&zelf.encoding, vm)?;
4485 result.push_wtf8(" encoding='".as_ref());
4486 result.push_wtf8(encoding.as_str().as_ref());
4487 result.push_wtf8("'>".as_ref());
4488
4489 Ok(vm.ctx.new_str(result))
4490 }
4491
4492 fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
4493 unreachable!("repr() is overridden directly")
4494 }
4495 }
4496
4497 impl Iterable for TextIOWrapper {
4498 fn slot_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
4499 check_closed(&zelf, vm)?;
4500 Ok(zelf)
4501 }
4502
4503 fn iter(_zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult {
4504 unreachable!("slot_iter is implemented")
4505 }
4506 }
4507
4508 impl IterNext for TextIOWrapper {
4509 fn slot_iternext(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
4510 let textio_ref: PyRef<Self> = zelf.downcast_ref::<Self>().unwrap().to_owned();
4512 {
4513 let mut textio = textio_ref.lock(vm)?;
4514 textio.telling = false;
4515 }
4516
4517 let line = vm.call_method(zelf, "readline", ())?;
4518
4519 if !line.try_to_bool(vm)? {
4520 let mut textio = textio_ref.lock(vm)?;
4522 textio.snapshot = None;
4523 textio.telling = textio.seekable;
4524 Ok(PyIterReturn::StopIteration(None))
4525 } else {
4526 Ok(PyIterReturn::Return(line))
4527 }
4528 }
4529
4530 fn next(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyIterReturn> {
4531 unreachable!("slot_iternext is implemented")
4532 }
4533 }
4534
4535 #[pyattr]
4536 #[pyclass(name)]
4537 #[derive(Debug, PyPayload, Default)]
4538 struct IncrementalNewlineDecoder {
4539 data: PyThreadMutex<Option<IncrementalNewlineDecoderData>>,
4541 }
4542
4543 #[derive(Debug)]
4544 struct IncrementalNewlineDecoderData {
4545 decoder: PyObjectRef,
4546 pendingcr: bool,
4549 translate: bool,
4550 seennl: SeenNewline,
4551 }
4552
4553 bitflags! {
4554 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
4555 struct SeenNewline: u8 {
4556 const LF = 1;
4557 const CR = 2;
4558 const CRLF = 4;
4559 }
4560 }
4561
4562 impl SeenNewline {
4563 fn observe(&mut self, text: &Wtf8) {
4564 let bytes = text.as_bytes();
4565 let mut matches = memchr::memchr2_iter(b'\r', b'\n', bytes);
4566 while !self.is_all() {
4567 let Some(i) = matches.next() else { break };
4568 match bytes[i] {
4569 b'\n' => self.insert(Self::LF),
4570 _ if bytes.get(i + 1) == Some(&b'\n') => {
4571 matches.next();
4572 self.insert(Self::CRLF);
4573 }
4574 _ => self.insert(Self::CR),
4575 }
4576 }
4577 }
4578
4579 fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
4580 match self.bits() {
4581 1 => "\n".to_pyobject(vm),
4582 2 => "\r".to_pyobject(vm),
4583 3 => ("\r", "\n").to_pyobject(vm),
4584 4 => "\r\n".to_pyobject(vm),
4585 5 => ("\n", "\r\n").to_pyobject(vm),
4586 6 => ("\r", "\r\n").to_pyobject(vm),
4587 7 => ("\r", "\n", "\r\n").to_pyobject(vm),
4588 _ => vm.ctx.none(),
4589 }
4590 }
4591 }
4592
4593 impl DefaultConstructor for IncrementalNewlineDecoder {}
4594
4595 #[derive(FromArgs)]
4596 struct IncrementalNewlineDecoderArgs {
4597 #[pyarg(any)]
4598 decoder: PyObjectRef,
4599 #[pyarg(any)]
4600 translate: bool,
4601 #[pyarg(any, default, py_default = "'strict'")]
4603 errors: Option<PyObjectRef>,
4604 }
4605
4606 impl Initializer for IncrementalNewlineDecoder {
4607 type Args = IncrementalNewlineDecoderArgs;
4608 fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
4609 let _ = args.errors;
4610 let mut data = zelf.lock_opt(vm)?;
4611 *data = Some(IncrementalNewlineDecoderData {
4612 decoder: args.decoder,
4613 translate: args.translate,
4614 pendingcr: false,
4615 seennl: SeenNewline::empty(),
4616 });
4617 Ok(())
4618 }
4619 }
4620
4621 impl IncrementalNewlineDecoder {
4622 fn lock_opt(
4623 &self,
4624 vm: &VirtualMachine,
4625 ) -> PyResult<PyThreadMutexGuard<'_, Option<IncrementalNewlineDecoderData>>> {
4626 self.data
4627 .lock_wrapped(|do_lock| vm.allow_threads(do_lock))
4628 .ok_or_else(|| vm.new_runtime_error("reentrant call inside nldecoder"))
4629 }
4630
4631 fn lock(
4632 &self,
4633 vm: &VirtualMachine,
4634 ) -> PyResult<PyMappedThreadMutexGuard<'_, IncrementalNewlineDecoderData>> {
4635 let lock = self.lock_opt(vm)?;
4636 PyThreadMutexGuard::try_map(lock, |x| x.as_mut())
4637 .map_err(|_| vm.new_value_error("I/O operation on uninitialized nldecoder"))
4638 }
4639 }
4640
4641 #[pyclass(with(Constructor, Initializer))]
4642 impl Py<IncrementalNewlineDecoder> {
4643 #[pymethod]
4644 fn decode(&self, args: NewlineDecodeArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
4645 self.lock(vm)?.decode(args.input, args.r#final, vm)
4646 }
4647
4648 #[pymethod]
4649 fn getstate(&self, vm: &VirtualMachine) -> PyResult<(PyObjectRef, u64)> {
4650 let data = self.lock(vm)?;
4651 let (buffer, flag) = if vm.is_none(&data.decoder) {
4652 (vm.ctx.new_bytes(vec![]).into(), 0)
4653 } else {
4654 vm.call_method(&data.decoder, "getstate", ())?
4655 .try_to_ref::<PyTuple>(vm)?
4656 .extract_tuple::<(PyObjectRef, u64)>(vm)?
4657 };
4658 let flag = (flag << 1) | (data.pendingcr as u64);
4659 Ok((buffer, flag))
4660 }
4661
4662 #[pymethod]
4663 fn setstate(&self, state: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> {
4664 let mut data = self.lock(vm)?;
4665 let (buffer, flag) = state.extract_tuple::<(PyObjectRef, u64)>(vm)?;
4666 data.pendingcr = flag & 1 != 0;
4667 if !vm.is_none(&data.decoder) {
4668 vm.call_method(&data.decoder, "setstate", ((buffer, flag >> 1),))?;
4669 }
4670 Ok(())
4671 }
4672
4673 #[pymethod]
4674 fn reset(&self, vm: &VirtualMachine) -> PyResult<()> {
4675 let mut data = self.lock(vm)?;
4676 data.seennl = SeenNewline::empty();
4677 data.pendingcr = false;
4678 if !vm.is_none(&data.decoder) {
4679 vm.call_method(&data.decoder, "reset", ())?;
4680 }
4681 Ok(())
4682 }
4683
4684 #[pygetset]
4685 fn newlines(&self, vm: &VirtualMachine) -> PyResult {
4686 let data = self.lock(vm)?;
4687 Ok(data.seennl.to_pyobject(vm))
4688 }
4689 }
4690
4691 #[derive(FromArgs)]
4692 struct NewlineDecodeArgs {
4693 #[pyarg(any)]
4694 input: PyObjectRef,
4695 #[pyarg(any, default)]
4696 r#final: bool,
4697 }
4698
4699 impl IncrementalNewlineDecoderData {
4700 fn decode(
4701 &mut self,
4702 input: PyObjectRef,
4703 final_: bool,
4704 vm: &VirtualMachine,
4705 ) -> PyResult<PyStrRef> {
4706 let output = if vm.is_none(&self.decoder) {
4707 input
4708 } else {
4709 vm.call_method(&self.decoder, "decode", (input, final_))?
4710 };
4711 let orig_output: PyStrRef = output.try_into_value(vm)?;
4712 let mut output = Cow::Borrowed(orig_output.as_wtf8());
4714 if self.pendingcr && (final_ || !output.is_empty()) {
4715 output.to_mut().insert(0, '\r'.into());
4716 self.pendingcr = false;
4717 }
4718 if !final_ && let Some(s) = output.strip_suffix("\r") {
4719 output = Cow::Owned(s.to_owned());
4720 self.pendingcr = true;
4721 }
4722
4723 if output.is_empty() {
4724 return Ok(vm.ctx.empty_str.to_owned());
4725 }
4726
4727 if (self.seennl == SeenNewline::LF || self.seennl.is_empty())
4728 && !output.contains_code_point('\r'.into())
4729 {
4730 if self.seennl.is_empty() && output.contains_code_point('\n'.into()) {
4731 self.seennl.insert(SeenNewline::LF);
4732 }
4733 } else if !self.translate {
4734 self.seennl.observe(&output);
4735 } else {
4736 let bytes = output.as_bytes();
4737 let mut matches = memchr::memchr2_iter(b'\r', b'\n', bytes);
4738 let mut new_string = Wtf8Buf::with_capacity(output.len());
4739 let mut last_modification_index = 0;
4740 while let Some(cr_index) = matches.next() {
4741 if bytes[cr_index] == b'\r' {
4742 let mut next_chunk_index = cr_index + 1;
4744 if bytes.get(cr_index + 1) == Some(&b'\n') {
4745 matches.next();
4746 self.seennl.insert(SeenNewline::CRLF);
4747 next_chunk_index += 1;
4749 } else {
4750 self.seennl.insert(SeenNewline::CR);
4751 }
4752 new_string.push_wtf8(&output[last_modification_index..cr_index]);
4753 new_string.push_char('\n');
4754 last_modification_index = next_chunk_index;
4755 } else {
4756 self.seennl.insert(SeenNewline::LF);
4757 }
4758 }
4759 new_string.push_wtf8(&output[last_modification_index..]);
4760 output = Cow::Owned(new_string);
4761 }
4762
4763 Ok(match output {
4764 Cow::Borrowed(_) => orig_output,
4765 Cow::Owned(s) => vm.ctx.new_str(s),
4766 })
4767 }
4768 }
4769
4770 #[pyattr]
4771 #[pyclass(name = "StringIO", base = _TextIOBase)]
4772 #[derive(Debug)]
4773 struct StringIO {
4774 _base: _TextIOBase,
4775 buffer: PyRwLock<BufferedIO>,
4776 newline: AtomicCell<Newlines>,
4777 seennl: AtomicCell<SeenNewline>,
4778 closed: AtomicCell<bool>,
4779 }
4780
4781 #[derive(FromArgs)]
4782 struct StringIONewArgs {
4783 #[pyarg(any, optional, py_default = "''")]
4785 initial_value: Option<PyStrRef>,
4786
4787 #[pyarg(any, default, py_default = "'\\n'")]
4789 newline: OptionalOption<Newlines>,
4790 }
4791
4792 impl Constructor for StringIO {
4793 type Args = FuncArgs;
4794
4795 fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
4796 Ok(Self {
4797 _base: Default::default(),
4798 buffer: PyRwLock::new(BufferedIO::new(Cursor::new(Vec::new()))),
4799 newline: AtomicCell::new(Newlines::Lf),
4800 seennl: AtomicCell::new(SeenNewline::empty()),
4801 closed: AtomicCell::new(false),
4802 })
4803 }
4804 }
4805
4806 impl Initializer for StringIO {
4807 type Args = StringIONewArgs;
4808
4809 fn init(
4810 zelf: &Py<Self>,
4811 Self::Args {
4812 initial_value,
4813 newline,
4814 }: Self::Args,
4815 _vm: &VirtualMachine,
4816 ) -> PyResult<()> {
4817 let newline = match newline {
4818 OptionalArg::Missing => Newlines::Lf,
4819 OptionalArg::Present(None) => Newlines::Universal,
4820 OptionalArg::Present(Some(newline)) => newline,
4821 };
4822 let raw_bytes = initial_value.as_ref().map_or_else(Vec::new, |v| {
4823 Self::translate_newlines(v.as_wtf8(), newline).into_bytes()
4824 });
4825 *zelf.buffer.write() = BufferedIO::new(Cursor::new(raw_bytes));
4826 zelf.newline.store(newline);
4827 zelf.seennl.store(SeenNewline::empty());
4828 if let Some(initial_value) = initial_value {
4829 zelf.observe_newlines(initial_value.as_wtf8(), newline);
4830 }
4831 Ok(())
4832 }
4833 }
4834
4835 impl StringIO {
4836 fn buffer(&self, vm: &VirtualMachine) -> PyResult<PyRwLockWriteGuard<'_, BufferedIO>> {
4837 if !self.closed.load() {
4838 Ok(self.buffer.write())
4839 } else {
4840 Err(io_closed_error(vm))
4841 }
4842 }
4843
4844 fn translate_newlines(data: &Wtf8, newline: Newlines) -> Wtf8Buf {
4845 match newline {
4846 Newlines::Universal => data
4847 .replace("\r\n".as_ref(), "\n".as_ref())
4848 .replace("\r".as_ref(), "\n".as_ref()),
4849 Newlines::Cr => data.replace("\n".as_ref(), "\r".as_ref()),
4850 Newlines::Crlf => data.replace("\n".as_ref(), "\r\n".as_ref()),
4851 Newlines::Passthrough | Newlines::Lf => data.to_owned(),
4852 }
4853 }
4854
4855 fn observe_newlines(&self, data: &Wtf8, newline: Newlines) {
4856 if matches!(newline, Newlines::Universal | Newlines::Passthrough) {
4857 let mut seennl = self.seennl.load();
4858 seennl.observe(data);
4859 self.seennl.store(seennl);
4860 }
4861 }
4862
4863 fn text(bytes: &[u8]) -> &Wtf8 {
4864 unsafe { Wtf8::from_bytes_unchecked(bytes) }
4866 }
4867
4868 fn char_offset_to_byte(bytes: &[u8], char_offset: usize) -> usize {
4869 let text = Self::text(bytes);
4870 crate::common::str::codepoint_range_end(text, char_offset)
4871 .unwrap_or_else(|| bytes.len() + (char_offset - text.code_points().count()))
4872 }
4873
4874 fn byte_offset_to_char(bytes: &[u8], byte_offset: usize) -> usize {
4875 let content_len = bytes.len();
4876 let in_content = byte_offset.min(content_len);
4877 Self::text(&bytes[..in_content]).code_points().count()
4878 + byte_offset.saturating_sub(content_len)
4879 }
4880
4881 fn read_size(buffer: &BufferedIO, size: Option<usize>, newline: Option<Newlines>) -> usize {
4882 let position = buffer.tell() as usize;
4883 let bytes = buffer.cursor.get_ref().get(position..).unwrap_or_default();
4884 let size_end = size
4885 .and_then(|size| crate::common::str::codepoint_range_end(Self::text(bytes), size))
4886 .unwrap_or(bytes.len());
4887 newline
4888 .and_then(|newline| newline.find_newline(Self::text(&bytes[..size_end])).ok())
4889 .unwrap_or(size_end)
4890 }
4891 }
4892
4893 #[pyclass(flags(BASETYPE, HAS_DICT, HAS_WEAKREF), with(Constructor, Initializer))]
4894 impl Py<StringIO> {
4895 #[pymethod]
4896 const fn readable(&self) -> bool {
4897 true
4898 }
4899
4900 #[pymethod]
4901 const fn writable(&self) -> bool {
4902 true
4903 }
4904
4905 #[pymethod]
4906 const fn seekable(&self) -> bool {
4907 true
4908 }
4909
4910 #[pygetset]
4911 fn closed(&self) -> bool {
4912 self.closed.load()
4913 }
4914
4915 #[pygetset]
4916 fn newlines(&self, vm: &VirtualMachine) -> PyResult {
4917 if self.closed.load() {
4918 Err(io_closed_error(vm))
4919 } else {
4920 Ok(self.seennl.load().to_pyobject(vm))
4921 }
4922 }
4923
4924 #[pymethod]
4925 fn close(&self) {
4926 self.closed.store(true);
4927 }
4928
4929 #[pymethod]
4931 fn write(&self, s: PyStrRef, vm: &VirtualMachine) -> PyResult<u64> {
4932 let newline = self.newline.load();
4933 let bytes = StringIO::translate_newlines(s.as_wtf8(), newline).into_bytes();
4934 let mut buffer = self.buffer(vm)?;
4935 self.observe_newlines(s.as_wtf8(), newline);
4936 buffer
4937 .write(&bytes)
4938 .ok_or_else(|| vm.new_type_error("Error Writing String"))?;
4939 Ok(s.char_len() as u64)
4940 }
4941
4942 #[pymethod]
4944 fn getvalue(&self, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
4945 let bytes = self.buffer(vm)?.getvalue();
4946 Wtf8Buf::from_bytes(bytes).map_err(|_| vm.new_value_error("Error Retrieving Value"))
4947 }
4948
4949 #[pymethod]
4951 fn seek(&self, pos: PyObjectRef, whence: HowArg, vm: &VirtualMachine) -> PyResult<u64> {
4952 let offset: isize = isize::try_from_object(vm, pos)?;
4953 let how = whence.whence;
4954 let mut buffer = self.buffer(vm)?;
4955 let char_offset = match how {
4956 0 if offset >= 0 => offset as usize,
4957 0 => return Err(vm.new_value_error(format!("negative seek position {offset}"))),
4958 1 | 2 if offset != 0 => {
4959 let kind = if how == 1 { "cur" } else { "end" };
4960 return Err(vm.new_os_error(format!("can't do nonzero {kind}-relative seeks")));
4961 }
4962 1 | 2 => {
4963 let byte_offset = if how == 1 {
4964 buffer.tell() as usize
4965 } else {
4966 buffer.cursor.get_ref().len()
4967 };
4968 StringIO::byte_offset_to_char(buffer.cursor.get_ref(), byte_offset)
4969 }
4970 _ => {
4971 return Err(
4972 vm.new_value_error(format!("invalid whence ({how}, should be 0, 1 or 2)"))
4973 );
4974 }
4975 };
4976
4977 let byte_offset = StringIO::char_offset_to_byte(buffer.cursor.get_ref(), char_offset);
4978 buffer
4979 .seek(SeekFrom::Start(byte_offset as u64))
4980 .map_err(|err| os_err(vm, err))?;
4981 Ok(char_offset as u64)
4982 }
4983
4984 #[pymethod]
4988 fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
4989 let mut buffer = self.buffer(vm)?;
4990 let size = StringIO::read_size(&buffer, size.to_usize(), None);
4991 let data = buffer.read(Some(size)).unwrap_or_default();
4992
4993 let value = Wtf8Buf::from_bytes(data)
4994 .map_err(|_| vm.new_value_error("Error Retrieving Value"))?;
4995 Ok(value)
4996 }
4997
4998 #[pymethod]
4999 fn tell(&self, vm: &VirtualMachine) -> PyResult<u64> {
5000 let buffer = self.buffer(vm)?;
5001 Ok(
5002 StringIO::byte_offset_to_char(buffer.cursor.get_ref(), buffer.tell() as usize)
5003 as u64,
5004 )
5005 }
5006
5007 #[pymethod]
5008 fn readline(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
5009 let mut buffer = self.buffer(vm)?;
5010 let size = StringIO::read_size(&buffer, size.to_usize(), Some(self.newline.load()));
5011 let input = buffer.read(Some(size)).unwrap_or_default();
5012 Wtf8Buf::from_bytes(input).map_err(|_| vm.new_value_error("Error Retrieving Value"))
5013 }
5014
5015 #[pymethod]
5016 fn truncate(&self, pos: StringIOPos, vm: &VirtualMachine) -> PyResult<usize> {
5017 let mut buffer = self.buffer(vm)?;
5018 let pos = match pos.as_optional().try_usize(vm)? {
5019 Some(pos) => pos,
5020 None => {
5021 StringIO::byte_offset_to_char(buffer.cursor.get_ref(), buffer.tell() as usize)
5022 }
5023 };
5024 let byte_pos = StringIO::char_offset_to_byte(buffer.cursor.get_ref(), pos);
5025 buffer.truncate(Some(byte_pos));
5026 Ok(pos)
5027 }
5028
5029 #[pygetset]
5030 const fn line_buffering(&self) -> bool {
5031 false
5032 }
5033
5034 #[pymethod]
5035 fn __getstate__(zelf: PyRef<StringIO>, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
5036 let buffer = zelf.buffer(vm)?;
5037 let content = Wtf8Buf::from_bytes(buffer.getvalue())
5038 .map_err(|_| vm.new_value_error("Error Retrieving Value"))?;
5039 let pos =
5040 StringIO::byte_offset_to_char(buffer.cursor.get_ref(), buffer.tell() as usize);
5041 drop(buffer);
5042
5043 let dict_obj: PyObjectRef = match zelf.as_object().dict() {
5045 Some(d) if !d.is_empty() => d.into(),
5046 _ => vm.ctx.none(),
5047 };
5048
5049 let newline = match zelf.newline.load() {
5050 Newlines::Universal => vm.ctx.none(),
5051 Newlines::Passthrough => vm.ctx.new_str("").into(),
5052 Newlines::Lf => vm.ctx.new_str("\n").into(),
5053 Newlines::Cr => vm.ctx.new_str("\r").into(),
5054 Newlines::Crlf => vm.ctx.new_str("\r\n").into(),
5055 };
5056
5057 Ok(vm.ctx.new_tuple(vec![
5059 vm.ctx.new_str(content).into(),
5060 newline,
5061 vm.ctx.new_int(pos).into(),
5062 dict_obj,
5063 ]))
5064 }
5065
5066 #[pymethod]
5067 fn __setstate__(
5068 zelf: PyRef<StringIO>,
5069 state: PyTupleRef,
5070 vm: &VirtualMachine,
5071 ) -> PyResult<()> {
5072 if zelf.closed.load() {
5074 return Err(vm.new_value_error("__setstate__ on closed file"));
5075 }
5076 if state.as_slice().len() != 4 {
5077 return Err(vm.new_type_error(format!(
5078 "__setstate__ argument should be 4-tuple, got {}",
5079 state.as_slice().len()
5080 )));
5081 }
5082
5083 let content: PyStrRef = state.as_slice()[0].clone().try_into_value(vm)?;
5084 let newline = Newlines::try_from_object(vm, state.as_slice()[1].clone())?;
5085 let pos: isize = isize::try_from_object(vm, state.as_slice()[2].clone())?;
5086 if pos < 0 {
5087 return Err(vm.new_value_error("negative seek position"));
5088 }
5089 let dict = &state.as_slice()[3];
5090
5091 let raw_bytes = content.as_bytes().to_vec();
5093 let mut buffer = zelf.buffer.write();
5094 *buffer = BufferedIO::new(Cursor::new(raw_bytes));
5095 let byte_pos = StringIO::char_offset_to_byte(buffer.cursor.get_ref(), pos as usize);
5096 buffer
5097 .seek(SeekFrom::Start(byte_pos as u64))
5098 .map_err(|err| os_err(vm, err))?;
5099 drop(buffer);
5100 zelf.newline.store(newline);
5101 let mut seennl = SeenNewline::empty();
5102 if matches!(newline, Newlines::Universal | Newlines::Passthrough) {
5103 seennl.observe(content.as_wtf8());
5104 }
5105 zelf.seennl.store(seennl);
5106
5107 if !vm.is_none(dict) {
5109 let dict_ref: PyRef<PyDict> = dict.clone().try_into_value(vm)?;
5110 if let Some(obj_dict) = zelf.as_object().dict() {
5111 obj_dict.clear();
5112 for (key, value) in dict_ref {
5113 obj_dict.set_item(&*key, value, vm)?;
5114 }
5115 }
5116 }
5117
5118 Ok(())
5119 }
5120 }
5121
5122 #[derive(FromArgs)]
5123 struct BytesIOArgs {
5124 #[pyarg(any, optional, py_default = "b''")]
5126 initial_bytes: Option<ArgBytesLike>,
5127 }
5128
5129 #[pyattr]
5130 #[pyclass(name = "BytesIO", base = _BufferedIOBase)]
5131 #[derive(Debug)]
5132 struct BytesIO {
5133 _base: _BufferedIOBase,
5134 buffer: PyRwLock<BufferedIO>,
5135 closed: AtomicCell<bool>,
5136 exports: AtomicCell<usize>,
5137 }
5138
5139 impl Constructor for BytesIO {
5140 type Args = FuncArgs;
5141
5142 fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
5143 Ok(Self {
5144 _base: Default::default(),
5145 buffer: PyRwLock::new(BufferedIO::new(Cursor::new(Vec::new()))),
5146 closed: AtomicCell::new(false),
5147 exports: AtomicCell::new(0),
5148 })
5149 }
5150 }
5151
5152 impl Initializer for BytesIO {
5153 type Args = BytesIOArgs;
5154
5155 fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
5156 if zelf.exports.load() > 0 {
5157 return Err(
5158 vm.new_buffer_error("Existing exports of data: object cannot be re-sized")
5159 );
5160 }
5161
5162 let raw_bytes = args
5163 .initial_bytes
5164 .map_or_else(Vec::new, |input| input.borrow_buf().to_vec());
5165 *zelf.buffer.write() = BufferedIO::new(Cursor::new(raw_bytes));
5166 Ok(())
5167 }
5168 }
5169
5170 impl BytesIO {
5171 fn buffer(&self, vm: &VirtualMachine) -> PyResult<PyRwLockWriteGuard<'_, BufferedIO>> {
5172 if !self.closed.load() {
5173 Ok(self.buffer.write())
5174 } else {
5175 Err(io_closed_error(vm))
5176 }
5177 }
5178 }
5179
5180 #[pyclass(
5181 flags(BASETYPE, HAS_DICT, HAS_WEAKREF),
5182 with(PyRef, Constructor, Initializer)
5183 )]
5184 impl Py<BytesIO> {
5185 #[pymethod]
5186 const fn readable(&self) -> bool {
5187 true
5188 }
5189
5190 #[pymethod]
5191 const fn writable(&self) -> bool {
5192 true
5193 }
5194
5195 #[pymethod]
5196 const fn seekable(&self) -> bool {
5197 true
5198 }
5199
5200 #[pymethod]
5201 fn flush(&self, vm: &VirtualMachine) -> PyResult<()> {
5202 if self.closed.load() {
5203 Err(io_closed_error(vm))
5204 } else {
5205 Ok(())
5206 }
5207 }
5208
5209 #[pymethod]
5210 fn write(&self, b: ArgContiguousBytesLike, vm: &VirtualMachine) -> PyResult<u64> {
5211 let mut buffer = self.try_resizable(vm)?;
5212 if self.closed.load() {
5214 return Err(io_closed_error(vm));
5215 }
5216 b.with_ref(|bytes| buffer.write(bytes))
5217 .ok_or_else(|| vm.new_type_error("Error Writing Bytes"))
5218 }
5219
5220 #[pymethod]
5222 fn getvalue(&self, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
5223 let bytes = self.buffer(vm)?.getvalue();
5224 Ok(vm.ctx.new_bytes(bytes))
5225 }
5226
5227 #[pymethod]
5231 #[pymethod(name = "read1")]
5232 fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
5233 let buf = self.buffer(vm)?.read(size.to_usize()).unwrap_or_default();
5234 Ok(buf)
5235 }
5236
5237 #[pymethod]
5238 fn readinto(zelf: &Self, buffer: ArgMemoryBuffer, vm: &VirtualMachine) -> PyResult<usize> {
5239 if buffer.source_object().is(zelf.as_object()) {
5242 let mut data = vm.new_zeroed_bytes(buffer.len())?;
5243 let ret = zelf
5244 .buffer(vm)?
5245 .cursor
5246 .read(&mut data)
5247 .map_err(|_| vm.new_value_error("Error readinto from Take"))?;
5248 buffer.borrow_buf_mut()[..ret].copy_from_slice(&data[..ret]);
5249 return Ok(ret);
5250 }
5251 let mut buf = zelf.buffer(vm)?;
5252 let ret = buf
5253 .cursor
5254 .read(&mut buffer.borrow_buf_mut())
5255 .map_err(|_| vm.new_value_error("Error readinto from Take"))?;
5256
5257 Ok(ret)
5258 }
5259
5260 #[pymethod]
5262 fn seek(&self, pos: PyObjectRef, whence: HowArg, vm: &VirtualMachine) -> PyResult<u64> {
5263 let seek_from = seekfrom(vm, pos, whence.whence)?;
5264 let mut buffer = self.buffer(vm)?;
5265
5266 match seek_from {
5268 SeekFrom::Current(offset) if offset < 0 => {
5269 let current = buffer.tell();
5270 let new_pos = current.saturating_add_signed(offset);
5271 buffer
5272 .seek(SeekFrom::Start(new_pos))
5273 .map_err(|err| os_err(vm, err))
5274 }
5275 _ => buffer.seek(seek_from).map_err(|err| os_err(vm, err)),
5276 }
5277 }
5278
5279 #[pymethod]
5280 fn tell(&self, vm: &VirtualMachine) -> PyResult<u64> {
5281 Ok(self.buffer(vm)?.tell())
5282 }
5283
5284 #[pymethod]
5285 fn readline(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
5286 self.buffer(vm)?.readline(size.to_usize(), vm)
5287 }
5288
5289 #[pymethod]
5290 fn truncate(&self, pos: OptionalPos, vm: &VirtualMachine) -> PyResult<usize> {
5291 if self.closed.load() {
5292 return Err(io_closed_error(vm));
5293 }
5294 let mut buffer = self.try_resizable(vm)?;
5295 let pos = pos.try_usize(vm)?;
5296 Ok(buffer.truncate(pos))
5297 }
5298
5299 #[pygetset]
5300 fn closed(&self) -> bool {
5301 self.closed.load()
5302 }
5303
5304 #[pymethod]
5305 fn close(&self, vm: &VirtualMachine) -> PyResult<()> {
5306 if self.exports.load() > 0 {
5307 return Err(
5308 vm.new_buffer_error("Existing exports of data: object cannot be closed")
5309 );
5310 }
5311 self.closed.store(true);
5312 Ok(())
5313 }
5314
5315 #[pymethod]
5316 fn __getstate__(zelf: PyRef<BytesIO>, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
5317 let buffer = zelf.buffer(vm)?;
5318 let content = buffer.getvalue();
5319 let pos = buffer.tell();
5320 drop(buffer);
5321
5322 let dict_obj: PyObjectRef = match zelf.as_object().dict() {
5324 Some(d) if !d.is_empty() => d.into(),
5325 _ => vm.ctx.none(),
5326 };
5327
5328 Ok(vm.ctx.new_tuple(vec![
5330 vm.ctx.new_bytes(content).into(),
5331 vm.ctx.new_int(pos).into(),
5332 dict_obj,
5333 ]))
5334 }
5335
5336 #[pymethod]
5337 fn __setstate__(
5338 zelf: PyRef<BytesIO>,
5339 object: PyTupleRef,
5340 vm: &VirtualMachine,
5341 ) -> PyResult<()> {
5342 if zelf.closed.load() {
5343 return Err(vm.new_value_error("__setstate__ on closed file"));
5344 }
5345 if object.as_slice().len() != 3 {
5346 return Err(vm.new_type_error(format!(
5347 "__setstate__ argument should be 3-tuple, got {}",
5348 object.as_slice().len()
5349 )));
5350 }
5351
5352 let content: PyBytesRef = object.as_slice()[0].clone().try_into_value(vm)?;
5353 let pos: u64 = object.as_slice()[1].clone().try_into_value(vm)?;
5354 let dict = &object.as_slice()[2];
5355
5356 let mut buffer = zelf.try_resizable(vm)?;
5358 *buffer = BufferedIO::new(Cursor::new(content.as_bytes().to_vec()));
5359 buffer
5360 .seek(SeekFrom::Start(pos))
5361 .map_err(|err| os_err(vm, err))?;
5362 drop(buffer);
5363
5364 if !vm.is_none(dict) {
5366 let dict_ref: PyRef<PyDict> = dict.clone().try_into_value(vm)?;
5367 if let Some(obj_dict) = zelf.as_object().dict() {
5368 obj_dict.clear();
5369 for (key, value) in dict_ref {
5370 obj_dict.set_item(&*key, value, vm)?;
5371 }
5372 }
5373 }
5374
5375 Ok(())
5376 }
5377
5378 #[pymethod]
5379 fn isatty(&self, vm: &VirtualMachine) -> PyResult<bool> {
5380 if self.closed() {
5381 return Err(io_closed_error(vm));
5382 }
5383
5384 Ok(false)
5385 }
5386 }
5387
5388 #[pyclass]
5389 impl PyRef<BytesIO> {
5390 #[pymethod]
5391 fn getbuffer(self, vm: &VirtualMachine) -> PyResult<PyMemoryView> {
5392 if self.closed.load() {
5393 return Err(vm.new_value_error("I/O operation on closed file."));
5394 }
5395 let len = self.buffer.read().cursor.get_ref().len();
5396 let buffer = PyBuffer::new(
5397 self.into(),
5398 BufferDescriptor::simple(len, false),
5399 &BYTES_IO_BUFFER_METHODS,
5400 );
5401 let view = PyMemoryView::from_buffer(buffer, vm)?;
5402 Ok(view)
5403 }
5404 }
5405
5406 static BYTES_IO_BUFFER_METHODS: BufferMethods = BufferMethods {
5407 obj_bytes: |buffer| {
5408 let zelf = buffer.obj_as::<BytesIO>();
5409 PyRwLockReadGuard::map(zelf.buffer.read(), |x| x.cursor.get_ref().as_slice()).into()
5410 },
5411 obj_bytes_mut: |buffer| {
5412 let zelf = buffer.obj_as::<BytesIO>();
5413 PyRwLockWriteGuard::map(zelf.buffer.write(), |x| x.cursor.get_mut().as_mut_slice())
5414 .into()
5415 },
5416
5417 release: |buffer| {
5418 buffer.obj_as::<BytesIO>().exports.fetch_sub(1);
5419 },
5420
5421 retain: |buffer| {
5422 buffer.obj_as::<BytesIO>().exports.fetch_add(1);
5423 },
5424 };
5425
5426 impl BufferResizeGuard for BytesIO {
5427 type Resizable<'a> = PyRwLockWriteGuard<'a, BufferedIO>;
5428
5429 fn try_resizable_opt(&self) -> Option<Self::Resizable<'_>> {
5430 let w = self.buffer.write();
5431 (self.exports.load() == 0).then_some(w)
5432 }
5433 }
5434
5435 #[repr(u8)]
5436 #[derive(Debug)]
5437 enum FileMode {
5438 Read = b'r',
5439 Write = b'w',
5440 Exclusive = b'x',
5441 Append = b'a',
5442 }
5443
5444 #[repr(u8)]
5445 #[derive(Debug)]
5446 enum EncodeMode {
5447 Text = b't',
5448 Bytes = b'b',
5449 }
5450
5451 #[derive(Debug)]
5452 struct Mode {
5453 file: FileMode,
5454 encode: EncodeMode,
5455 plus: bool,
5456 }
5457
5458 impl core::str::FromStr for Mode {
5459 type Err = ParseModeError;
5460
5461 fn from_str(s: &str) -> Result<Self, Self::Err> {
5462 let mut file = None;
5463 let mut encode = None;
5464 let mut plus = false;
5465 macro_rules! set_mode {
5466 ($var:ident, $mode:path, $err:ident) => {{
5467 match $var {
5468 Some($mode) => return Err(ParseModeError::InvalidMode),
5469 Some(_) => return Err(ParseModeError::$err),
5470 None => $var = Some($mode),
5471 }
5472 }};
5473 }
5474
5475 for ch in s.chars() {
5476 match ch {
5477 '+' => {
5478 if plus {
5479 return Err(ParseModeError::InvalidMode);
5480 }
5481 plus = true
5482 }
5483 't' => set_mode!(encode, EncodeMode::Text, MultipleEncode),
5484 'b' => set_mode!(encode, EncodeMode::Bytes, MultipleEncode),
5485 'r' => set_mode!(file, FileMode::Read, MultipleFile),
5486 'a' => set_mode!(file, FileMode::Append, MultipleFile),
5487 'w' => set_mode!(file, FileMode::Write, MultipleFile),
5488 'x' => set_mode!(file, FileMode::Exclusive, MultipleFile),
5489 _ => return Err(ParseModeError::InvalidMode),
5490 }
5491 }
5492
5493 let file = file.ok_or(ParseModeError::NoFile)?;
5494 let encode = encode.unwrap_or(EncodeMode::Text);
5495
5496 Ok(Self { file, encode, plus })
5497 }
5498 }
5499
5500 impl Mode {
5501 const fn rawmode(&self) -> &'static str {
5502 match (&self.file, self.plus) {
5503 (FileMode::Read, true) => "rb+",
5504 (FileMode::Read, false) => "rb",
5505 (FileMode::Write, true) => "wb+",
5506 (FileMode::Write, false) => "wb",
5507 (FileMode::Exclusive, true) => "xb+",
5508 (FileMode::Exclusive, false) => "xb",
5509 (FileMode::Append, true) => "ab+",
5510 (FileMode::Append, false) => "ab",
5511 }
5512 }
5513 }
5514
5515 enum ParseModeError {
5516 InvalidMode,
5517 MultipleFile,
5518 MultipleEncode,
5519 NoFile,
5520 }
5521
5522 impl ParseModeError {
5523 fn error_msg(&self, mode_string: &str) -> String {
5524 match self {
5525 Self::InvalidMode => format!("invalid mode: '{mode_string}'"),
5526 Self::MultipleFile => {
5527 "must have exactly one of create/read/write/append mode".to_owned()
5528 }
5529 Self::MultipleEncode => "can't have text and binary mode at once".to_owned(),
5530 Self::NoFile => {
5531 "Must have exactly one of create/read/write/append mode and at most one plus"
5532 .to_owned()
5533 }
5534 }
5535 }
5536 }
5537
5538 #[derive(FromArgs)]
5539 struct IoOpenArgs {
5540 file: PyObjectRef,
5541 #[pyarg(any, default = "r")]
5542 mode: PyUtf8StrRef,
5543 #[pyarg(flatten)]
5544 opts: OpenArgs,
5545 }
5546
5547 #[pyfunction]
5548 fn open(args: IoOpenArgs, vm: &VirtualMachine) -> PyResult {
5549 io_open(args.file, Some(args.mode.as_str()), args.opts, vm)
5550 }
5551
5552 #[derive(FromArgs)]
5553 struct OpenCodeArgs {
5554 #[pyarg(any)]
5555 path: PyObjectRef,
5556 }
5557
5558 #[pyfunction]
5559 fn open_code(args: OpenCodeArgs, vm: &VirtualMachine) -> PyResult {
5560 io_open(args.path, Some("rb"), OpenArgs::default(), vm)
5562 }
5563
5564 #[derive(FromArgs)]
5565 pub struct OpenArgs {
5566 #[pyarg(any, default = -1)]
5567 pub buffering: isize,
5568 #[pyarg(any, optional)]
5569 pub encoding: Option<PyUtf8StrRef>,
5570 #[pyarg(any, optional)]
5571 pub errors: Option<PyUtf8StrRef>,
5572 #[pyarg(any, optional)]
5573 pub newline: Option<PyUtf8StrRef>,
5574 #[pyarg(any, default = true)]
5575 pub closefd: bool,
5576 #[pyarg(any, optional)]
5577 pub opener: Option<PyObjectRef>,
5578 }
5579
5580 impl Default for OpenArgs {
5581 fn default() -> Self {
5582 Self {
5583 buffering: -1,
5584 encoding: None,
5585 errors: None,
5586 newline: None,
5587 closefd: true,
5588 opener: None,
5589 }
5590 }
5591 }
5592
5593 #[cfg(all(unix, feature = "threading", feature = "host_env"))]
5600 pub(crate) unsafe fn reinit_std_streams_after_fork(vm: &VirtualMachine) {
5601 for name in ["stdin", "stdout", "stderr"] {
5602 let Ok(stream) = vm.sys_module.get_attr(name, vm) else {
5603 continue;
5604 };
5605 reinit_io_locks(&stream);
5606 }
5607 }
5608
5609 #[cfg(all(unix, feature = "threading", feature = "host_env"))]
5610 fn reinit_io_locks(obj: &PyObject) {
5611 use crate::common::lock::reinit_thread_mutex_after_fork;
5612
5613 if let Some(tio) = obj.downcast_ref::<TextIOWrapper>() {
5614 unsafe { reinit_thread_mutex_after_fork(&tio.data) };
5615 if let Some(guard) = tio.data.lock()
5616 && let Some(ref data) = *guard
5617 {
5618 if let Some(ref decoder) = data.decoder {
5619 reinit_io_locks(decoder);
5620 }
5621 if let Some(buffer) = tio.buffer.load_owned() {
5622 reinit_io_locks(&buffer);
5623 }
5624 }
5625 return;
5626 }
5627 if let Some(nl) = obj.downcast_ref::<IncrementalNewlineDecoder>() {
5628 unsafe { reinit_thread_mutex_after_fork(&nl.data) };
5629 return;
5630 }
5631 if let Some(br) = obj.downcast_ref::<BufferedReader>() {
5632 unsafe { reinit_thread_mutex_after_fork(&br.data) };
5633 return;
5634 }
5635 if let Some(bw) = obj.downcast_ref::<BufferedWriter>() {
5636 unsafe { reinit_thread_mutex_after_fork(&bw.data) };
5637 return;
5638 }
5639 if let Some(brw) = obj.downcast_ref::<BufferedRandom>() {
5640 unsafe { reinit_thread_mutex_after_fork(&brw.data) };
5641 return;
5642 }
5643 if let Some(brw) = obj.downcast_ref::<BufferedRWPair>() {
5644 unsafe { reinit_thread_mutex_after_fork(&brw.read.data) };
5645 unsafe { reinit_thread_mutex_after_fork(&brw.write.data) };
5646 }
5647 }
5648
5649 pub fn io_open(
5650 file: PyObjectRef,
5651 mode: Option<&str>,
5652 opts: OpenArgs,
5653 vm: &VirtualMachine,
5654 ) -> PyResult {
5655 let mode_string = mode.unwrap_or("r");
5657 let mode = mode_string
5658 .parse::<Mode>()
5659 .map_err(|e| vm.new_value_error(e.error_msg(mode_string)))?;
5660
5661 if let EncodeMode::Bytes = mode.encode {
5662 let msg = if opts.encoding.is_some() {
5663 Some("binary mode doesn't take an encoding argument")
5664 } else if opts.errors.is_some() {
5665 Some("binary mode doesn't take an errors argument")
5666 } else if opts.newline.is_some() {
5667 Some("binary mode doesn't take a newline argument")
5668 } else {
5669 None
5670 };
5671 if let Some(msg) = msg {
5672 return Err(vm.new_value_error(msg));
5673 }
5674 }
5675
5676 let file = if file.fast_isinstance(vm.ctx.types.int_type) {
5680 file
5681 } else {
5682 FsPath::try_from_path_like(file, true, vm)?.to_pyobject(vm)
5683 };
5684
5685 #[cfg(all(unix, feature = "host_env"))]
5687 if let Ok(crate::ospath::OsPathOrFd::Fd(fd)) = file.clone().try_into_value(vm) {
5688 rustpython_host_env::fcntl::validate_fd(fd.as_raw())
5689 .map_err(|_| vm.new_last_errno_error())?;
5690 }
5691
5692 let is_console = cfg_select! {
5696 all(feature = "host_env", windows) => {
5697 super::winconsoleio::pyio_get_console_type(&file, vm) != '\0'
5698 }
5699 _ => false,
5700 };
5701
5702 let file_io_class: &Py<PyType> = cfg_select! {
5703 all(feature = "host_env", windows) => {
5704 if is_console {
5705 Some(super::winconsoleio::WindowsConsoleIO::static_type())
5706 } else {
5707 Some(super::fileio::FileIO::static_type())
5708 }
5709 }
5710 feature = "host_env" => Some(super::fileio::FileIO::static_type()),
5711 _ => None,
5712 }
5713 .ok_or_else(|| {
5714 new_unsupported_operation(
5715 "Couldn't get FileIO, io.open likely isn't supported on your platform",
5716 vm,
5717 )
5718 })?;
5719 let raw = PyType::call(
5720 file_io_class,
5721 (file, mode.rawmode(), opts.closefd, opts.opener).into_args(vm),
5722 vm,
5723 )?;
5724
5725 let isatty = opts.buffering < 0 && {
5726 let atty = vm.call_method(&raw, "isatty", ())?;
5727 bool::try_from_object(vm, atty)?
5728 };
5729
5730 if opts.buffering == 1 && matches!(mode.encode, EncodeMode::Bytes) {
5732 crate::stdlib::_warnings::warn(
5733 vm.ctx.exceptions.runtime_warning,
5734 "line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used".to_owned(),
5735 1,
5736 vm,
5737 )?;
5738 }
5739
5740 let line_buffering = opts.buffering == 1 || isatty;
5741
5742 let buffering = if opts.buffering < 0 || opts.buffering == 1 {
5743 DEFAULT_BUFFER_SIZE
5744 } else {
5745 opts.buffering as usize
5746 };
5747
5748 if buffering == 0 {
5749 let ret = match mode.encode {
5750 EncodeMode::Text => {
5751 let _ = vm.call_method(&raw, "close", ());
5752 Err(vm.new_value_error("can't have unbuffered text I/O"))
5753 }
5754 EncodeMode::Bytes => Ok(raw),
5755 };
5756 return ret;
5757 }
5758
5759 let cls = if mode.plus {
5760 BufferedRandom::static_type()
5761 } else if let FileMode::Read = mode.file {
5762 BufferedReader::static_type()
5763 } else {
5764 BufferedWriter::static_type()
5765 };
5766 let buffered = PyType::call(cls, (raw, buffering).into_args(vm), vm)?;
5767
5768 match mode.encode {
5769 EncodeMode::Text => {
5770 let encoding = if is_console && opts.encoding.is_none() {
5771 Some(PyUtf8Str::from("utf-8").into_ref(&vm.ctx))
5773 } else {
5774 match opts.encoding {
5775 Some(enc) => Some(enc),
5776 None => {
5777 let encoding =
5778 text_encoding(vm.ctx.none(), StackLevelArg { stacklevel: 2 }, vm)?;
5779 Some(PyUtf8StrRef::try_from_object(vm, encoding.into())?)
5780 }
5781 }
5782 };
5783 let tio = TextIOWrapper::static_type();
5784 let wrapper = PyType::call(
5785 tio,
5786 (
5787 buffered.clone(),
5788 encoding,
5789 opts.errors,
5790 opts.newline,
5791 line_buffering,
5792 )
5793 .into_args(vm),
5794 vm,
5795 )
5796 .inspect_err(|_err| {
5797 let _ = vm.call_method(&buffered, "close", ());
5798 })?;
5799 wrapper.set_attr("mode", vm.new_pyobj(mode_string), vm)?;
5800 Ok(wrapper)
5801 }
5802 EncodeMode::Bytes => Ok(buffered),
5803 }
5804 }
5805
5806 fn create_unsupported_operation(ctx: &Context) -> PyTypeRef {
5807 use crate::builtins::type_::PyAttributes;
5808 use crate::types::PyTypeSlots;
5809
5810 let mut attrs = PyAttributes::default();
5811 attrs.insert(identifier!(ctx, __module__), ctx.new_str("io").into());
5812
5813 PyType::new_heap(
5814 "UnsupportedOperation",
5815 vec![
5816 ctx.exceptions.os_error.to_owned(),
5817 ctx.exceptions.value_error.to_owned(),
5818 ],
5819 attrs,
5820 PyTypeSlots::heap_default(),
5821 ctx.types.type_type.to_owned(),
5822 ctx,
5823 )
5824 .unwrap()
5825 }
5826
5827 pub(super) fn unsupported_operation() -> &'static Py<PyType> {
5828 rustpython_common::static_cell! {
5829 static CELL: PyTypeRef;
5830 }
5831 CELL.get_or_init(|| create_unsupported_operation(Context::genesis()))
5832 }
5833
5834 #[pyfunction]
5835 fn text_encoding(
5836 encoding: PyObjectRef,
5837 stacklevel: StackLevelArg,
5838 vm: &VirtualMachine,
5839 ) -> PyResult<PyStrRef> {
5840 if vm.is_none(&encoding) {
5841 let encoding = if vm.state.config.settings.utf8_mode > 0 {
5842 "utf-8"
5843 } else {
5844 "locale"
5845 };
5846 if vm.state.config.settings.warn_default_encoding {
5847 let mut stacklevel = stacklevel.stacklevel;
5848 if stacklevel > 1
5849 && let Some(code) = crate::frame::current_code()
5850 && let Some(stdlib_dir) = vm.state.config.paths.stdlib_dir.as_deref()
5851 {
5852 let path = code.source_path().as_str();
5853 if !path.starts_with(stdlib_dir) {
5854 stacklevel = stacklevel.saturating_sub(1);
5855 }
5856 }
5857 let stacklevel = usize::try_from(stacklevel).unwrap_or(0);
5858 crate::stdlib::_warnings::warn(
5859 vm.ctx.exceptions.encoding_warning,
5860 "'encoding' argument not specified".to_owned(),
5861 stacklevel,
5862 vm,
5863 )?;
5864 }
5865 return Ok(vm.ctx.new_str(encoding));
5866 }
5867 encoding.try_into_value(vm)
5868 }
5869
5870 #[cfg(test)]
5871 mod tests {
5872 use super::*;
5873
5874 #[test]
5875 fn buffered_read() {
5876 let data = vec![1, 2, 3, 4];
5877 let bytes = None;
5878 let mut buffered = BufferedIO {
5879 cursor: Cursor::new(data.clone()),
5880 };
5881
5882 assert_eq!(buffered.read(bytes).unwrap(), data);
5883 }
5884
5885 #[test]
5886 fn buffered_seek() {
5887 let data = vec![1, 2, 3, 4];
5888 let count: u64 = 2;
5889 let mut buffered = BufferedIO {
5890 cursor: Cursor::new(data),
5891 };
5892
5893 assert_eq!(buffered.seek(SeekFrom::Start(count)).unwrap(), count);
5894 assert_eq!(buffered.read(Some(count as usize)).unwrap(), vec![3, 4]);
5895 }
5896
5897 #[test]
5898 fn buffered_value() {
5899 let data = vec![1, 2, 3, 4];
5900 let buffered = BufferedIO {
5901 cursor: Cursor::new(data.clone()),
5902 };
5903
5904 assert_eq!(buffered.getvalue(), data);
5905 }
5906 }
5907
5908 #[cfg_attr(
5909 not(feature = "host_env"),
5910 expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")
5911 )]
5912 pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
5913 __module_exec(vm, module);
5915
5916 #[cfg(feature = "host_env")]
5918 super::fileio::module_exec(vm, module)?;
5919
5920 #[cfg(all(feature = "host_env", windows))]
5922 super::winconsoleio::module_exec(vm, module)?;
5923
5924 let unsupported_operation = unsupported_operation().to_owned();
5925 extend_module!(vm, module, {
5926 "UnsupportedOperation" => unsupported_operation,
5927 "BlockingIOError" => vm.ctx.exceptions.blocking_io_error.to_owned(),
5928 });
5929 Ok(())
5930 }
5931}
5932#[cfg(feature = "host_env")]
5934#[pymodule]
5935mod fileio {
5936 use super::{_io::*, Offset, iobase_finalize};
5937 use crate::host_env::crt_fd;
5938 use crate::{
5939 AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine,
5940 builtins::{PyBaseExceptionRef, PyUtf8Str, PyUtf8StrRef},
5941 common::wtf8::Wtf8Buf,
5942 convert::{IntoPyException, ToPyException},
5943 exceptions::OSErrorBuilder,
5944 function::{ArgBytesLike, ArgMemoryBuffer},
5945 ospath::{OsPath, OsPathOrFd},
5946 stdlib::os,
5947 types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable},
5948 };
5949 use crossbeam_utils::atomic::AtomicCell;
5950 use rustpython_host_env::io as host_io;
5951
5952 #[pyattr]
5953 #[pyclass(module = "_io", name, base = _RawIOBase)]
5954 #[derive(Debug)]
5955 pub(super) struct FileIO {
5956 _base: _RawIOBase,
5957 fd: AtomicCell<i32>,
5958 closefd: AtomicCell<bool>,
5959 mode: AtomicCell<host_io::FileMode>,
5960 seekable: AtomicCell<Option<bool>>,
5961 blksize: AtomicCell<i64>,
5962 finalizing: AtomicCell<bool>,
5963 }
5964
5965 #[derive(FromArgs)]
5966 pub(super) struct FileIOArgs {
5967 #[pyarg(any)]
5968 file: PyObjectRef,
5969 #[pyarg(any, default, py_default = "'r'")]
5971 mode: Option<PyUtf8StrRef>,
5972 #[pyarg(any, default = true)]
5973 closefd: bool,
5974 #[pyarg(any, optional)]
5975 opener: Option<PyObjectRef>,
5976 }
5977
5978 impl Default for FileIO {
5979 fn default() -> Self {
5980 Self {
5981 _base: Default::default(),
5982 fd: AtomicCell::new(-1),
5983 closefd: AtomicCell::new(true),
5984 mode: AtomicCell::new(host_io::FileMode::empty()),
5985 seekable: AtomicCell::new(None),
5986 blksize: AtomicCell::new(super::DEFAULT_BUFFER_SIZE as _),
5987 finalizing: AtomicCell::new(false),
5988 }
5989 }
5990 }
5991
5992 impl DefaultConstructor for FileIO {}
5993
5994 impl Initializer for FileIO {
5995 type Args = FileIOArgs;
5996
5997 fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
5998 let name = args.file;
6000 if name.class().is(vm.ctx.types.bool_type) {
6002 crate::stdlib::_warnings::warn(
6003 vm.ctx.exceptions.runtime_warning,
6004 "bool is used as a file descriptor".to_owned(),
6005 1,
6006 vm,
6007 )?;
6008 }
6009 let arg_fd = if let Some(i) = name.downcast_ref::<crate::builtins::PyInt>() {
6010 let fd = i.try_to_primitive(vm)?;
6011 if fd < 0 {
6012 return Err(vm.new_value_error("negative file descriptor"));
6013 }
6014 Some(fd)
6015 } else {
6016 None
6017 };
6018
6019 let mode_obj = args
6020 .mode
6021 .unwrap_or_else(|| PyUtf8Str::from("rb").into_ref(&vm.ctx));
6022 let mode_str = mode_obj.as_str();
6023 let parsed = host_io::parse_fileio_mode(mode_str)
6024 .map_err(|e| vm.new_value_error(e.error_msg(mode_str)))?;
6025 let mode = parsed.mode;
6026 let flags = parsed.flags;
6027 zelf.mode.store(mode);
6028
6029 let (fd, filename) = if let Some(fd) = arg_fd {
6030 zelf.closefd.store(args.closefd);
6031 (fd, None)
6032 } else {
6033 zelf.closefd.store(true);
6034 if !args.closefd {
6035 return Err(vm.new_value_error("Cannot use closefd=False with file name"));
6036 }
6037
6038 if let Some(opener) = args.opener {
6039 let fd = opener.call((name.clone(), flags), vm)?;
6040 if !fd.fast_isinstance(vm.ctx.types.int_type) {
6041 return Err(vm.new_type_error("expected integer from opener"));
6042 }
6043 let fd = i32::try_from_object(vm, fd)?;
6044 if fd < 0 {
6045 return Err(vm.new_value_error(format!("opener returned {fd}")));
6046 }
6047 (fd, None)
6048 } else {
6049 let path = OsPath::try_from_fspath(name.clone(), vm)?;
6050 #[cfg(any(unix, target_os = "wasi"))]
6051 let fd = host_io::open_path(&path.clone().into_cstring(vm)?, flags, 0o666);
6052 #[cfg(windows)]
6053 let fd = host_io::open_path(&path.to_wide_cstring(vm)?, flags, 0o666);
6054 let filename = OsPathOrFd::Path(path);
6055 match fd {
6056 Ok(fd) => (fd.into_raw(), Some(filename)),
6057 Err(e) => {
6058 return Err(OSErrorBuilder::with_filename_from_errno(&e, filename, vm));
6059 }
6060 }
6061 }
6062 };
6063 let fd_is_own = arg_fd.is_none();
6064 zelf.fd.store(fd);
6065 let fd = unsafe { crt_fd::Borrowed::borrow_raw(fd) };
6066 let filename = filename.unwrap_or(OsPathOrFd::Fd(fd));
6067
6068 match host_io::inspect_file_target(fd) {
6071 Ok(info) => {
6072 if let Some(blksize) = info.blksize {
6073 zelf.blksize.store(blksize);
6074 }
6075 }
6076 Err(err) => {
6077 if host_io::should_forget_fd_after_inspect_error(&err, fd_is_own) {
6078 zelf.fd.store(-1);
6079 }
6080 return Err(OSErrorBuilder::with_filename(&err, filename, vm));
6081 }
6082 }
6083
6084 #[cfg(windows)]
6085 crate::stdlib::msvcrt::setmode_binary(fd);
6086 if let Err(e) = zelf.as_object().set_attr("name", name, vm) {
6087 if !fd_is_own {
6089 zelf.fd.store(-1);
6090 }
6091 return Err(e);
6092 }
6093
6094 if mode.is_superset(&host_io::FileMode::APPENDING) {
6095 let _ = host_io::seek_to_end(fd);
6096 }
6097
6098 Ok(())
6099 }
6100 }
6101
6102 impl Representable for FileIO {
6103 #[inline]
6104 fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
6105 let type_name = zelf.class().slot_name();
6106 let fd = zelf.fd.load();
6107 if fd < 0 {
6108 return Ok(format!("<{type_name} [closed]>"));
6109 }
6110 let name_repr = repr_file_obj_name(zelf.as_object(), vm)?;
6111 let mode = zelf.mode();
6112 let closefd = if zelf.closefd.load() { "True" } else { "False" };
6113 let repr = if let Some(name_repr) = name_repr {
6114 format!("<{type_name} name={name_repr} mode='{mode}' closefd={closefd}>")
6115 } else {
6116 format!("<{type_name} fd={fd} mode='{mode}' closefd={closefd}>")
6117 };
6118 Ok(repr)
6119 }
6120 }
6121
6122 impl FileIO {
6123 fn io_error(
6124 zelf: &Py<Self>,
6125 error: std::io::Error,
6126 vm: &VirtualMachine,
6127 ) -> PyBaseExceptionRef {
6128 let exc = error.to_pyexception(vm);
6129 if let Ok(name) = zelf.as_object().get_attr("name", vm) {
6130 exc.as_object()
6131 .set_attr("filename", name, vm)
6132 .expect("OSError.filename set must success");
6133 }
6134 exc
6135 }
6136
6137 fn fileno(&self, vm: &VirtualMachine) -> PyResult<i32> {
6138 let fd = self.fd.load();
6139 if fd >= 0 {
6140 Ok(fd)
6141 } else {
6142 Err(io_closed_error(vm))
6143 }
6144 }
6145
6146 fn get_fd(&self, vm: &VirtualMachine) -> PyResult<crt_fd::Borrowed<'_>> {
6147 self.fileno(vm)
6148 .map(|fd| unsafe { crt_fd::Borrowed::borrow_raw(fd) })
6149 }
6150
6151 fn read_once_into(
6153 zelf: &Py<Self>,
6154 handle: crt_fd::Borrowed<'_>,
6155 buf: &mut [u8],
6156 vm: &VirtualMachine,
6157 ) -> PyResult<Option<usize>> {
6158 loop {
6159 match vm.allow_threads(|| host_io::read_once(handle, buf)) {
6160 Ok(n) => return Ok(Some(n)),
6161 Err(e) if host_io::is_interrupted_error(&e) => {
6162 vm.check_signals()?;
6163 }
6164 Err(e) if host_io::is_would_block_error(&e) => return Ok(None),
6166 Err(e) => return Err(Self::io_error(zelf, e, vm)),
6167 }
6168 }
6169 }
6170 }
6171
6172 #[pyclass(
6173 with(Constructor, Initializer, Representable, Destructor),
6174 flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
6175 )]
6176 impl Py<FileIO> {
6177 #[pygetset]
6178 fn closed(&self) -> bool {
6179 self.fd.load() < 0
6180 }
6181
6182 #[pygetset]
6183 fn closefd(&self) -> bool {
6184 self.closefd.load()
6185 }
6186
6187 #[pygetset(name = "_blksize")]
6188 fn blksize(&self) -> i64 {
6189 self.blksize.load()
6190 }
6191
6192 #[pymethod]
6193 fn fileno(&self, vm: &VirtualMachine) -> PyResult<i32> {
6194 self.payload.fileno(vm)
6195 }
6196
6197 #[pymethod]
6198 fn readable(&self, vm: &VirtualMachine) -> PyResult<bool> {
6199 if self.fd.load() < 0 {
6200 return Err(io_closed_error(vm));
6201 }
6202 Ok(self.mode.load().is_superset(&host_io::FileMode::READABLE))
6203 }
6204
6205 #[pymethod]
6206 fn writable(&self, vm: &VirtualMachine) -> PyResult<bool> {
6207 if self.fd.load() < 0 {
6208 return Err(io_closed_error(vm));
6209 }
6210 Ok(self.mode.load().is_superset(&host_io::FileMode::WRITABLE))
6211 }
6212
6213 #[pygetset]
6214 fn mode(&self) -> &'static str {
6215 self.mode.load().raw_mode()
6216 }
6217
6218 #[pymethod]
6219 fn read(
6220 zelf: &Self,
6221 read_byte: OptionalSize,
6222 vm: &VirtualMachine,
6223 ) -> PyResult<Option<Vec<u8>>> {
6224 if !zelf.mode.load().is_superset(&host_io::FileMode::READABLE) {
6225 return Err(new_unsupported_operation(
6226 "File or stream is not readable",
6227 vm,
6228 ));
6229 }
6230 let handle = zelf.get_fd(vm)?;
6231 let bytes = if let Some(read_byte) = read_byte.to_usize() {
6232 let mut bytes = vm.new_zeroed_bytes(read_byte)?;
6233 let n = loop {
6235 match vm.allow_threads(|| host_io::read_once(handle, &mut bytes)) {
6236 Ok(n) => break n,
6237 Err(e) if host_io::is_interrupted_error(&e) => {
6238 vm.check_signals()?;
6239 continue;
6240 }
6241 Err(e) if host_io::is_would_block_error(&e) => {
6243 return Ok(None);
6244 }
6245 Err(e) => return Err(FileIO::io_error(zelf, e, vm)),
6246 }
6247 };
6248 bytes.truncate(n);
6249 bytes
6250 } else {
6251 let mut bytes = vec![];
6252 loop {
6254 match vm.allow_threads(|| host_io::read_all(handle, &mut bytes)) {
6255 Ok(()) => break,
6256 Err(e) if host_io::is_interrupted_error(&e) => {
6257 vm.check_signals()?;
6258 continue;
6259 }
6260 Err(e) if host_io::is_would_block_error(&e) => {
6262 if bytes.is_empty() {
6263 return Ok(None);
6264 }
6265 break;
6266 }
6267 Err(e) => return Err(FileIO::io_error(zelf, e, vm)),
6268 }
6269 }
6270 bytes
6271 };
6272
6273 Ok(Some(bytes))
6274 }
6275
6276 #[pymethod]
6277 fn readinto(
6278 zelf: &Self,
6279 buffer: ArgMemoryBuffer,
6280 vm: &VirtualMachine,
6281 ) -> PyResult<Option<usize>> {
6282 if !zelf.mode.load().is_superset(&host_io::FileMode::READABLE) {
6283 return Err(new_unsupported_operation(
6284 "File or stream is not readable",
6285 vm,
6286 ));
6287 }
6288
6289 let handle = zelf.get_fd(vm)?;
6290
6291 if host_io::reads_without_waiting(handle) {
6292 let mut buf = buffer.borrow_buf_mut();
6296 return FileIO::read_once_into(zelf, handle, &mut buf, vm);
6297 }
6298
6299 let mut scratch = vm.new_zeroed_bytes(buffer.len())?;
6308 let ret = FileIO::read_once_into(zelf, handle, &mut scratch, vm)?;
6309 if let Some(n) = ret {
6310 buffer.borrow_buf_mut()[..n].copy_from_slice(&scratch[..n]);
6311 }
6312 Ok(ret)
6313 }
6314
6315 #[pymethod]
6316 fn write(zelf: &Self, b: ArgBytesLike, vm: &VirtualMachine) -> PyResult<Option<usize>> {
6317 if !zelf.mode.load().is_superset(&host_io::FileMode::WRITABLE) {
6318 return Err(new_unsupported_operation(
6319 "File or stream is not writable",
6320 vm,
6321 ));
6322 }
6323
6324 let handle = zelf.get_fd(vm)?;
6325
6326 let buf = b.borrow_buf_unlocked(vm)?;
6330
6331 let len = loop {
6333 match vm.allow_threads(|| host_io::write_once(handle, &buf)) {
6334 Ok(n) => break n,
6335 Err(e) if host_io::is_interrupted_error(&e) => {
6336 vm.check_signals()?;
6337 continue;
6338 }
6339 Err(e) if host_io::is_would_block_error(&e) => return Ok(None),
6341 Err(e) => return Err(FileIO::io_error(zelf, e, vm)),
6342 }
6343 };
6344
6345 Ok(Some(len))
6347 }
6348
6349 #[pymethod]
6350 fn close(zelf: &Self, vm: &VirtualMachine) -> PyResult<()> {
6351 let res = iobase_close(zelf.as_object(), vm);
6352 if !zelf.closefd.load() {
6353 zelf.fd.store(-1);
6354 return res;
6355 }
6356 let flush_exc = res.err();
6357 if zelf.finalizing.load() {
6358 FileIO::dealloc_warn(zelf, zelf.as_object(), vm);
6359 }
6360 let fd = zelf.fd.swap(-1);
6361 let close_err = if fd >= 0 {
6362 host_io::close_owned_fd(unsafe { crt_fd::Owned::from_raw(fd) })
6363 .map_err(|err| FileIO::io_error(zelf, err, vm))
6364 .err()
6365 } else {
6366 None
6367 };
6368 match (flush_exc, close_err) {
6369 (Some(fe), Some(ce)) => {
6370 ce.set_context(Some(fe));
6371 Err(ce)
6372 }
6373 (Some(e), None) | (None, Some(e)) => Err(e),
6374 (None, None) => Ok(()),
6375 }
6376 }
6377
6378 #[pymethod]
6379 fn seekable(&self, vm: &VirtualMachine) -> PyResult<bool> {
6380 let fd = self.get_fd(vm)?;
6381 Ok(self.seekable.load().unwrap_or_else(|| {
6382 let seekable = host_io::is_seekable(fd);
6383 self.seekable.store(Some(seekable));
6384 seekable
6385 }))
6386 }
6387
6388 #[pymethod]
6389 fn seek(&self, pos: PyObjectRef, whence: HowArg, vm: &VirtualMachine) -> PyResult<Offset> {
6390 let how = whence.whence;
6391 let fd = self.get_fd(vm)?;
6392 let offset = get_offset(&pos, vm)?;
6393
6394 host_io::seek(fd, offset, how).map_err(|e| e.into_pyexception(vm))
6395 }
6396
6397 #[pymethod]
6398 fn tell(&self, vm: &VirtualMachine) -> PyResult<Offset> {
6399 let fd = self.get_fd(vm)?;
6400 host_io::tell(fd).map_err(|e| e.into_pyexception(vm))
6401 }
6402
6403 #[pymethod]
6404 fn truncate(&self, len: ObjLen, vm: &VirtualMachine) -> PyResult<Offset> {
6405 let fd = self.get_fd(vm)?;
6406 let len = match len.size {
6407 Some(l) => get_offset(&l, vm)?,
6408 None => host_io::tell(fd).map_err(|e| e.into_pyexception(vm))?,
6409 };
6410 os::ftruncate(fd, len).map_err(|e| e.into_pyexception(vm))?;
6411 Ok(len)
6412 }
6413
6414 #[pymethod]
6415 fn isatty(&self, vm: &VirtualMachine) -> PyResult<bool> {
6416 let fd = self.fileno(vm)?;
6417 Ok(host_io::isatty(fd))
6418 }
6419
6420 #[pymethod]
6421 fn __getstate__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
6422 Err(vm.new_type_error(format!("cannot pickle '{}' instances", zelf.class().name())))
6423 }
6424
6425 #[pymethod(name = "_dealloc_warn")]
6427 fn _dealloc_warn_method(zelf: &Self, object: PyObjectRef, vm: &VirtualMachine) {
6428 FileIO::dealloc_warn(zelf, &object, vm);
6429 }
6430 }
6431
6432 impl FileIO {
6433 fn dealloc_warn(zelf: &Py<Self>, source: &PyObject, vm: &VirtualMachine) {
6435 if zelf.fd.load() >= 0 && zelf.closefd.load() {
6436 let repr = source
6437 .repr(vm)
6438 .map_or_else(|_| Wtf8Buf::from("<file>"), |s| s.as_wtf8().to_owned());
6439 if let Err(e) = crate::stdlib::_warnings::warn(
6440 vm.ctx.exceptions.resource_warning,
6441 format!("unclosed file {repr}"),
6442 1,
6443 vm,
6444 ) {
6445 vm.run_unraisable(e, None, zelf.as_object().to_owned());
6446 }
6447 }
6448 }
6449 }
6450
6451 impl Destructor for FileIO {
6452 fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
6453 if let Some(fileio) = zelf.downcast_ref::<Self>() {
6454 fileio.finalizing.store(true);
6455 }
6456 iobase_finalize(zelf, vm);
6457 Ok(())
6458 }
6459
6460 #[cold]
6461 fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> {
6462 unreachable!("slot_del is implemented")
6463 }
6464 }
6465}
6466
6467#[cfg(all(feature = "host_env", windows))]
6469#[pymodule]
6470mod winconsoleio {
6471 use super::{_io::*, iobase_finalize};
6472 use crate::{
6473 AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
6474 builtins::{PyBaseExceptionRef, PyUtf8StrRef},
6475 common::{lock::PyMutex, wtf8::Wtf8Buf},
6476 convert::{IntoPyException, ToPyException},
6477 function::{ArgBytesLike, ArgMemoryBuffer},
6478 types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable},
6479 };
6480 use crossbeam_utils::atomic::AtomicCell;
6481 use rustpython_host_env::io as host_io;
6482 use rustpython_host_env::nt as host_nt;
6483 use rustpython_host_env::windows::ToWideString;
6484 type HANDLE = host_nt::Handle;
6485
6486 const SMALLBUF: usize = 4;
6487 const BUFMAX: usize = 32 * 1024 * 1024;
6488
6489 fn handle_from_fd(fd: i32) -> HANDLE {
6490 host_nt::handle_from_fd(fd)
6491 }
6492
6493 fn is_invalid_handle(handle: HANDLE) -> bool {
6494 host_nt::is_invalid_handle(handle)
6495 }
6496
6497 pub(super) fn pyio_get_console_type(path_or_fd: &PyObject, vm: &VirtualMachine) -> char {
6500 if let Ok(fd) = i32::try_from_object(vm, path_or_fd.to_owned()) {
6502 return host_nt::console_type_from_fd(fd);
6503 }
6504
6505 let Ok(name) = path_or_fd.str(vm) else {
6507 return '\0';
6508 };
6509 let Some(name_str) = name.to_str() else {
6510 return '\0';
6512 };
6513 host_nt::console_type_from_name(name_str)
6514 }
6515
6516 #[pyattr]
6517 #[pyclass(module = "_io", name = "_WindowsConsoleIO", base = _RawIOBase)]
6518 #[derive(Debug)]
6519 pub(super) struct WindowsConsoleIO {
6520 _base: _RawIOBase,
6521 fd: AtomicCell<i32>,
6522 readable: AtomicCell<bool>,
6523 writable: AtomicCell<bool>,
6524 closefd: AtomicCell<bool>,
6525 finalizing: AtomicCell<bool>,
6526 blksize: AtomicCell<i64>,
6527 buf: PyMutex<[u8; SMALLBUF]>,
6528 }
6529
6530 impl Default for WindowsConsoleIO {
6531 fn default() -> Self {
6532 Self {
6533 _base: Default::default(),
6534 fd: AtomicCell::new(-1),
6535 readable: AtomicCell::new(false),
6536 writable: AtomicCell::new(false),
6537 closefd: AtomicCell::new(false),
6538 finalizing: AtomicCell::new(false),
6539 blksize: AtomicCell::new(super::DEFAULT_BUFFER_SIZE as _),
6540 buf: PyMutex::new([0u8; SMALLBUF]),
6541 }
6542 }
6543 }
6544
6545 impl DefaultConstructor for WindowsConsoleIO {}
6546
6547 #[derive(FromArgs)]
6548 pub(super) struct WindowsConsoleIOArgs {
6549 #[pyarg(positional)]
6550 name: PyObjectRef,
6551 #[pyarg(any, default)]
6552 mode: Option<PyUtf8StrRef>,
6553 #[pyarg(any, default = true)]
6554 closefd: bool,
6555 #[allow(dead_code)]
6556 #[pyarg(any, default)]
6557 opener: Option<PyObjectRef>,
6558 }
6559
6560 impl Initializer for WindowsConsoleIO {
6561 type Args = WindowsConsoleIOArgs;
6562
6563 fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
6564 let nameobj = args.name;
6565
6566 if zelf.fd.load() >= 0 {
6567 if zelf.closefd.load() {
6568 internal_close(zelf);
6569 } else {
6570 zelf.fd.store(-1);
6571 }
6572 }
6573
6574 if nameobj.class().is(vm.ctx.types.bool_type) {
6576 crate::stdlib::_warnings::warn(
6577 vm.ctx.exceptions.runtime_warning,
6578 "bool is used as a file descriptor".to_owned(),
6579 1,
6580 vm,
6581 )?;
6582 }
6583
6584 let mut fd: i32 = -1;
6586 if let Some(i) = nameobj.downcast_ref::<crate::builtins::PyInt>() {
6587 fd = i.try_to_primitive::<i32>(vm).unwrap_or(-1);
6588 if fd < 0 {
6589 return Err(vm.new_value_error("negative file descriptor"));
6590 }
6591 }
6592
6593 let mode_str: &str = args.mode.as_deref().map_or("r", |s| s.as_str());
6595
6596 let mut rwa = false;
6597 let mut readable = false;
6598 let mut writable = false;
6599 let mut console_type = '\0';
6600 for c in mode_str.bytes() {
6601 match c {
6602 b'+' | b'a' | b'b' | b'x' => {}
6603 b'r' => {
6604 if rwa {
6605 return Err(
6606 vm.new_value_error("Must have exactly one of read or write mode")
6607 );
6608 }
6609 rwa = true;
6610 readable = true;
6611 }
6612 b'w' => {
6613 if rwa {
6614 return Err(
6615 vm.new_value_error("Must have exactly one of read or write mode")
6616 );
6617 }
6618 rwa = true;
6619 writable = true;
6620 }
6621 _ => {
6622 return Err(vm.new_value_error(format!("invalid mode: {mode_str}")));
6623 }
6624 }
6625 }
6626 if !rwa {
6627 return Err(vm.new_value_error("Must have exactly one of read or write mode"));
6628 }
6629
6630 zelf.readable.store(readable);
6631 zelf.writable.store(writable);
6632
6633 if fd < 0 {
6634 console_type = pyio_get_console_type(&nameobj, vm);
6636 if console_type == 'x' {
6637 if writable {
6638 console_type = 'w';
6639 } else {
6640 console_type = 'r';
6641 }
6642 }
6643
6644 zelf.closefd.store(true);
6646 if !args.closefd {
6647 return Err(vm.new_value_error("Cannot use closefd=False with file name"));
6648 }
6649
6650 let name_str = nameobj.str(vm)?;
6651 let wide = name_str
6652 .as_wtf8()
6653 .to_wide_cstring()
6654 .map_err(|e| e.to_pyexception(vm))?;
6655
6656 fd = host_nt::open_console_path_fd(&wide, writable)
6657 .map_err(|err| err.to_pyexception(vm))?;
6658 } else {
6659 zelf.closefd.store(false);
6661 }
6662
6663 zelf.fd.store(fd);
6664
6665 if console_type == '\0' {
6667 let handle = handle_from_fd(fd);
6668 console_type = host_nt::console_type(handle);
6669 }
6670
6671 if console_type == '\0' {
6672 internal_close(zelf);
6674 return Err(vm.new_value_error("Cannot open non-console file"));
6675 }
6676
6677 if writable && console_type != 'w' {
6678 internal_close(zelf);
6679 return Err(vm.new_value_error("Cannot open console input buffer for writing"));
6680 }
6681 if readable && console_type != 'r' {
6682 internal_close(zelf);
6683 return Err(vm.new_value_error("Cannot open console output buffer for reading"));
6684 }
6685
6686 zelf.blksize.store(super::DEFAULT_BUFFER_SIZE as _);
6687 *zelf.buf.lock() = [0u8; SMALLBUF];
6688
6689 zelf.as_object().set_attr("name", nameobj, vm)?;
6690
6691 Ok(())
6692 }
6693 }
6694
6695 fn internal_close(zelf: &WindowsConsoleIO) {
6696 let fd = zelf.fd.swap(-1);
6697 if fd >= 0 && zelf.closefd.load() {
6698 let _ =
6699 host_io::close_owned_fd(unsafe { crate::host_env::crt_fd::Owned::from_raw(fd) });
6700 }
6701 }
6702
6703 impl Representable for WindowsConsoleIO {
6704 #[inline]
6705 fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
6706 let type_name = zelf.class().slot_name();
6707 let fd = zelf.fd.load();
6708 if fd < 0 {
6709 return Ok(format!("<{type_name} [closed]>"));
6710 }
6711 let mode = if zelf.readable.load() { "rb" } else { "wb" };
6712 let closefd = if zelf.closefd.load() { "True" } else { "False" };
6713 Ok(format!("<{type_name} mode='{mode}' closefd={closefd}>"))
6714 }
6715 }
6716
6717 impl WindowsConsoleIO {
6718 #[allow(dead_code)]
6719 fn io_error(
6720 zelf: &Py<Self>,
6721 error: std::io::Error,
6722 vm: &VirtualMachine,
6723 ) -> PyBaseExceptionRef {
6724 let exc = error.to_pyexception(vm);
6725 if let Ok(name) = zelf.as_object().get_attr("name", vm) {
6726 exc.as_object()
6727 .set_attr("filename", name, vm)
6728 .expect("OSError.filename set must succeed");
6729 }
6730 exc
6731 }
6732
6733 fn fileno(&self, vm: &VirtualMachine) -> PyResult<i32> {
6734 let fd = self.fd.load();
6735 if fd >= 0 {
6736 Ok(fd)
6737 } else {
6738 Err(io_closed_error(vm))
6739 }
6740 }
6741
6742 fn get_fd(&self, vm: &VirtualMachine) -> PyResult<i32> {
6743 self.fileno(vm)
6744 }
6745
6746 fn dealloc_warn(zelf: &Py<Self>, source: PyObjectRef, vm: &VirtualMachine) {
6747 if zelf.fd.load() >= 0 && zelf.closefd.load() {
6748 let repr = source
6749 .repr(vm)
6750 .map_or_else(|_| Wtf8Buf::from("<file>"), |s| s.as_wtf8().to_owned());
6751
6752 if let Err(e) = crate::stdlib::_warnings::warn(
6753 vm.ctx.exceptions.resource_warning,
6754 format!("unclosed file {repr}"),
6755 1,
6756 vm,
6757 ) {
6758 vm.run_unraisable(e, None, zelf.as_object().to_owned());
6759 }
6760 }
6761 }
6762
6763 fn copy_from_buf(buf: &mut [u8; SMALLBUF], dest: &mut [u8]) -> usize {
6764 let mut n = 0;
6765 while buf[0] != 0 && n < dest.len() {
6766 dest[n] = buf[0];
6767 n += 1;
6768 for i in 1..SMALLBUF {
6769 buf[i - 1] = buf[i];
6770 }
6771 buf[SMALLBUF - 1] = 0;
6772 }
6773 n
6774 }
6775 }
6776
6777 #[pyclass(
6778 with(Constructor, Initializer, Representable, Destructor),
6779 flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
6780 )]
6781 impl Py<WindowsConsoleIO> {
6782 #[pygetset]
6783 fn closed(&self) -> bool {
6784 self.fd.load() < 0
6785 }
6786
6787 #[pygetset]
6788 fn closefd(&self) -> bool {
6789 self.closefd.load()
6790 }
6791
6792 #[pygetset(name = "_blksize")]
6793 fn blksize(&self) -> i64 {
6794 self.blksize.load()
6795 }
6796
6797 #[pygetset]
6798 fn mode(&self) -> &'static str {
6799 if self.readable.load() { "rb" } else { "wb" }
6800 }
6801
6802 #[pymethod]
6803 fn fileno(&self, vm: &VirtualMachine) -> PyResult<i32> {
6804 self.payload.fileno(vm)
6805 }
6806
6807 #[pymethod]
6808 fn readable(&self, vm: &VirtualMachine) -> PyResult<bool> {
6809 if self.fd.load() < 0 {
6810 return Err(io_closed_error(vm));
6811 }
6812 Ok(self.readable.load())
6813 }
6814
6815 #[pymethod]
6816 fn writable(&self, vm: &VirtualMachine) -> PyResult<bool> {
6817 if self.fd.load() < 0 {
6818 return Err(io_closed_error(vm));
6819 }
6820 Ok(self.writable.load())
6821 }
6822
6823 #[pymethod]
6824 fn isatty(&self, vm: &VirtualMachine) -> PyResult<bool> {
6825 if self.fd.load() < 0 {
6826 return Err(io_closed_error(vm));
6827 }
6828 Ok(true)
6829 }
6830
6831 #[pymethod]
6832 fn close(zelf: &Self, vm: &VirtualMachine) -> PyResult<()> {
6833 let res = iobase_close(zelf.as_object(), vm);
6834 if !zelf.closefd.load() {
6835 zelf.fd.store(-1);
6836 return res;
6837 }
6838 let flush_exc = res.err();
6839 if zelf.finalizing.load() {
6840 WindowsConsoleIO::dealloc_warn(zelf, zelf.as_object().to_owned(), vm);
6841 }
6842 let fd = zelf.fd.swap(-1);
6843 let close_err: Option<PyBaseExceptionRef> = if fd >= 0 {
6844 host_io::close_owned_fd(unsafe { crate::host_env::crt_fd::Owned::from_raw(fd) })
6845 .err()
6846 .map(|e| e.into_pyexception(vm))
6847 } else {
6848 None
6849 };
6850 match (flush_exc, close_err) {
6851 (Some(fe), Some(ce)) => {
6852 ce.set_context(Some(fe));
6853 Err(ce)
6854 }
6855 (Some(e), None) | (None, Some(e)) => Err(e),
6856 (None, None) => Ok(()),
6857 }
6858 }
6859
6860 #[pymethod]
6861 fn readinto(&self, buffer: ArgMemoryBuffer, vm: &VirtualMachine) -> PyResult<usize> {
6862 let fd = self.get_fd(vm)?;
6863 if !self.readable.load() {
6864 return Err(new_unsupported_operation(
6865 "Console buffer does not support reading",
6866 vm,
6867 ));
6868 }
6869 let mut buf_ref = buffer.borrow_buf_mut();
6870 let len = buf_ref.len();
6871 if len == 0 {
6872 return Ok(0);
6873 }
6874 if len > BUFMAX {
6875 return Err(vm.new_value_error(format!("cannot read more than {BUFMAX} bytes")));
6876 }
6877
6878 let handle = handle_from_fd(fd);
6879 if is_invalid_handle(handle) {
6880 return Err(std::io::Error::last_os_error().to_pyexception(vm));
6881 }
6882
6883 let dest = &mut *buf_ref;
6884 let mut smallbuf = self.buf.lock();
6885 host_nt::read_console_into(handle, dest, &mut smallbuf)
6886 .map_err(|err| err.to_pyexception(vm))
6887 }
6888
6889 #[pymethod]
6890 fn readall(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
6891 if self.fd.load() < 0 {
6892 return Err(io_closed_error(vm));
6893 }
6894
6895 let handle = handle_from_fd(self.fd.load());
6896 if is_invalid_handle(handle) {
6897 return Err(std::io::Error::last_os_error().to_pyexception(vm));
6898 }
6899
6900 let mut smallbuf = self.buf.lock();
6901 let result = host_nt::read_console_all(handle, &mut smallbuf)
6902 .map_err(|err| err.into_pyexception(vm))?;
6903 Ok(vm.ctx.new_bytes(result).into())
6904 }
6905
6906 #[pymethod]
6907 fn read(&self, size: SignedSize, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
6908 if self.fd.load() < 0 {
6909 return Err(io_closed_error(vm));
6910 }
6911 if !self.readable.load() {
6912 return Err(new_unsupported_operation(
6913 "Console buffer does not support reading",
6914 vm,
6915 ));
6916 }
6917 let size = size.size;
6918 if size < 0 {
6919 return self.readall(vm);
6920 }
6921 if size as usize > BUFMAX {
6922 return Err(vm.new_value_error(format!("cannot read more than {BUFMAX} bytes")));
6923 }
6924 let mut buf = vec![0u8; size as usize];
6925 let handle = handle_from_fd(self.fd.load());
6926 if is_invalid_handle(handle) {
6927 return Err(std::io::Error::last_os_error().to_pyexception(vm));
6928 }
6929
6930 let mut read_len = {
6931 let mut ibuf = self.buf.lock();
6932 WindowsConsoleIO::copy_from_buf(&mut ibuf, &mut buf)
6933 };
6934 if read_len >= size as usize {
6935 buf.truncate(read_len);
6936 return Ok(vm.ctx.new_bytes(buf).into());
6937 }
6938 {
6939 let mut ibuf = self.buf.lock();
6940 let n = host_nt::read_console_into(handle, &mut buf[read_len..], &mut ibuf)
6941 .map_err(|err| err.to_pyexception(vm))?;
6942 read_len += n;
6943 }
6944
6945 buf.truncate(read_len);
6946 Ok(vm.ctx.new_bytes(buf).into())
6947 }
6948
6949 #[pymethod]
6950 fn write(&self, b: ArgBytesLike, vm: &VirtualMachine) -> PyResult<usize> {
6951 if self.fd.load() < 0 {
6952 return Err(io_closed_error(vm));
6953 }
6954 if !self.writable.load() {
6955 return Err(new_unsupported_operation(
6956 "Console buffer does not support writing",
6957 vm,
6958 ));
6959 }
6960
6961 let handle = handle_from_fd(self.fd.load());
6962 if is_invalid_handle(handle) {
6963 return Err(std::io::Error::last_os_error().to_pyexception(vm));
6964 }
6965
6966 let data = b.borrow_buf();
6967 let data = &*data;
6968 if data.is_empty() {
6969 return Ok(0);
6970 }
6971
6972 host_nt::write_console_utf8(handle, data, BUFMAX)
6973 .map_err(|err| err.into_pyexception(vm))
6974 }
6975
6976 #[pymethod(name = "__reduce__")]
6977 fn reduce(_zelf: &Self, vm: &VirtualMachine) -> PyResult {
6978 Err(vm.new_type_error("cannot pickle '_WindowsConsoleIO' instances"))
6979 }
6980 }
6981
6982 impl Destructor for WindowsConsoleIO {
6983 fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
6984 if let Some(cio) = zelf.downcast_ref::<Self>() {
6985 cio.finalizing.store(true);
6986 }
6987 iobase_finalize(zelf, vm);
6988 Ok(())
6989 }
6990
6991 #[cold]
6992 fn del(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<()> {
6993 unreachable!("slot_del is implemented")
6994 }
6995 }
6996}