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