1use crate::{
2 builtins::{PyBaseExceptionRef, PyModule, PySet},
3 common::crt_fd::Fd,
4 convert::ToPyException,
5 function::{ArgumentError, FromArgs, FuncArgs},
6 AsObject, Py, PyPayload, PyResult, VirtualMachine,
7};
8use std::{ffi, fs, io, path::Path};
9
10pub(crate) fn fs_metadata<P: AsRef<Path>>(
11 path: P,
12 follow_symlink: bool,
13) -> io::Result<fs::Metadata> {
14 if follow_symlink {
15 fs::metadata(path.as_ref())
16 } else {
17 fs::symlink_metadata(path.as_ref())
18 }
19}
20
21#[cfg(unix)]
22impl crate::convert::IntoPyException for nix::Error {
23 fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
24 io::Error::from(self).into_pyexception(vm)
25 }
26}
27
28#[inline]
30pub fn errno_err(vm: &VirtualMachine) -> PyBaseExceptionRef {
31 crate::common::os::last_os_error().to_pyexception(vm)
32}
33
34#[allow(dead_code)]
35#[derive(FromArgs, Default)]
36pub struct TargetIsDirectory {
37 #[pyarg(any, default = "false")]
38 pub(crate) target_is_directory: bool,
39}
40
41cfg_if::cfg_if! {
42 if #[cfg(all(any(unix, target_os = "wasi"), not(target_os = "redox")))] {
43 use libc::AT_FDCWD;
44 } else {
45 const AT_FDCWD: i32 = -100;
46 }
47}
48const DEFAULT_DIR_FD: Fd = Fd(AT_FDCWD);
49
50#[derive(Copy, Clone, PartialEq, Eq)]
52pub struct DirFd<const AVAILABLE: usize>(pub(crate) [Fd; AVAILABLE]);
53
54impl<const AVAILABLE: usize> Default for DirFd<AVAILABLE> {
55 fn default() -> Self {
56 Self([DEFAULT_DIR_FD; AVAILABLE])
57 }
58}
59
60#[allow(unused)]
62impl DirFd<1> {
63 #[inline(always)]
64 pub(crate) fn fd_opt(&self) -> Option<Fd> {
65 self.get_opt().map(Fd)
66 }
67
68 #[inline]
69 pub(crate) fn get_opt(&self) -> Option<i32> {
70 let fd = self.fd();
71 if fd == DEFAULT_DIR_FD {
72 None
73 } else {
74 Some(fd.0)
75 }
76 }
77
78 #[inline(always)]
79 pub(crate) fn fd(&self) -> Fd {
80 self.0[0]
81 }
82}
83
84impl<const AVAILABLE: usize> FromArgs for DirFd<AVAILABLE> {
85 fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
86 let fd = match args.take_keyword("dir_fd") {
87 Some(o) if vm.is_none(&o) => DEFAULT_DIR_FD,
88 None => DEFAULT_DIR_FD,
89 Some(o) => {
90 let fd = o.try_index_opt(vm).unwrap_or_else(|| {
91 Err(vm.new_type_error(format!(
92 "argument should be integer or None, not {}",
93 o.class().name()
94 )))
95 })?;
96 let fd = fd.try_to_primitive(vm)?;
97 Fd(fd)
98 }
99 };
100 if AVAILABLE == 0 && fd != DEFAULT_DIR_FD {
101 return Err(vm
102 .new_not_implemented_error("dir_fd unavailable on this platform".to_owned())
103 .into());
104 }
105 Ok(Self([fd; AVAILABLE]))
106 }
107}
108
109#[derive(FromArgs)]
110pub(super) struct FollowSymlinks(
111 #[pyarg(named, name = "follow_symlinks", default = "true")] pub bool,
112);
113
114fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a ffi::OsStr> {
115 rustpython_common::os::bytes_as_osstr(b)
116 .map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8".to_owned()))
117}
118
119#[pymodule(sub)]
120pub(super) mod _os {
121 use super::{errno_err, DirFd, FollowSymlinks, SupportFunc};
122 use crate::{
123 builtins::{
124 PyBytesRef, PyGenericAlias, PyIntRef, PyStrRef, PyTuple, PyTupleRef, PyTypeRef,
125 },
126 common::{
127 crt_fd::{Fd, Offset},
128 fileutils::StatStruct,
129 lock::{OnceCell, PyRwLock},
130 suppress_iph,
131 },
132 convert::{IntoPyException, ToPyObject},
133 function::{ArgBytesLike, Either, FsPath, FuncArgs, OptionalArg},
134 ospath::{IOErrorBuilder, OsPath, OsPathOrFd, OutputMode},
135 protocol::PyIterReturn,
136 recursion::ReprGuard,
137 types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter},
138 vm::VirtualMachine,
139 AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
140 };
141 use crossbeam_utils::atomic::AtomicCell;
142 use itertools::Itertools;
143 use std::{
144 env, ffi, fs,
145 fs::OpenOptions,
146 io::{self, Read, Write},
147 path::PathBuf,
148 time::{Duration, SystemTime},
149 };
150
151 const OPEN_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox")));
152 pub(crate) const MKDIR_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox")));
153 const STAT_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox")));
154 const UTIME_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox")));
155 pub(crate) const SYMLINK_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox")));
156
157 #[pyattr]
158 use libc::{
159 O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, SEEK_CUR, SEEK_END,
160 SEEK_SET,
161 };
162
163 #[pyattr]
164 pub(crate) const F_OK: u8 = 0;
165 #[pyattr]
166 pub(crate) const R_OK: u8 = 1 << 2;
167 #[pyattr]
168 pub(crate) const W_OK: u8 = 1 << 1;
169 #[pyattr]
170 pub(crate) const X_OK: u8 = 1 << 0;
171
172 #[pyfunction]
173 fn close(fileno: i32, vm: &VirtualMachine) -> PyResult<()> {
174 Fd(fileno).close().map_err(|e| e.into_pyexception(vm))
175 }
176
177 #[pyfunction]
178 fn closerange(fd_low: i32, fd_high: i32) {
179 for fileno in fd_low..fd_high {
180 let _ = Fd(fileno).close();
181 }
182 }
183
184 #[cfg(any(unix, windows, target_os = "wasi"))]
185 #[derive(FromArgs)]
186 struct OpenArgs {
187 path: OsPath,
188 flags: i32,
189 #[pyarg(any, default)]
190 mode: Option<i32>,
191 #[pyarg(flatten)]
192 dir_fd: DirFd<{ OPEN_DIR_FD as usize }>,
193 }
194
195 #[pyfunction]
196 fn open(args: OpenArgs, vm: &VirtualMachine) -> PyResult<i32> {
197 os_open(args.path, args.flags, args.mode, args.dir_fd, vm)
198 }
199
200 #[cfg(any(unix, windows, target_os = "wasi"))]
201 pub(crate) fn os_open(
202 name: OsPath,
203 flags: i32,
204 mode: Option<i32>,
205 dir_fd: DirFd<{ OPEN_DIR_FD as usize }>,
206 vm: &VirtualMachine,
207 ) -> PyResult<i32> {
208 let mode = mode.unwrap_or(0o777);
209 #[cfg(windows)]
210 let fd = {
211 let [] = dir_fd.0;
212 let name = name.to_widecstring(vm)?;
213 let flags = flags | libc::O_NOINHERIT;
214 Fd::wopen(&name, flags, mode)
215 };
216 #[cfg(not(windows))]
217 let fd = {
218 let name = name.clone().into_cstring(vm)?;
219 #[cfg(not(target_os = "wasi"))]
220 let flags = flags | libc::O_CLOEXEC;
221 #[cfg(not(target_os = "redox"))]
222 if let Some(dir_fd) = dir_fd.fd_opt() {
223 dir_fd.openat(&name, flags, mode)
224 } else {
225 Fd::open(&name, flags, mode)
226 }
227 #[cfg(target_os = "redox")]
228 {
229 let [] = dir_fd.0;
230 Fd::open(&name, flags, mode)
231 }
232 };
233 fd.map(|fd| fd.0)
234 .map_err(|err| IOErrorBuilder::with_filename(&err, name, vm))
235 }
236
237 #[pyfunction]
238 fn fsync(fd: i32, vm: &VirtualMachine) -> PyResult<()> {
239 Fd(fd).fsync().map_err(|err| err.into_pyexception(vm))
240 }
241
242 #[pyfunction]
243 fn read(fd: i32, n: usize, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
244 let mut buffer = vec![0u8; n];
245 let mut file = Fd(fd);
246 let n = file
247 .read(&mut buffer)
248 .map_err(|err| err.into_pyexception(vm))?;
249 buffer.truncate(n);
250
251 Ok(vm.ctx.new_bytes(buffer))
252 }
253
254 #[pyfunction]
255 fn write(fd: i32, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult {
256 let mut file = Fd(fd);
257 let written = data
258 .with_ref(|b| file.write(b))
259 .map_err(|err| err.into_pyexception(vm))?;
260
261 Ok(vm.ctx.new_int(written).into())
262 }
263
264 #[pyfunction]
265 #[pyfunction(name = "unlink")]
266 fn remove(path: OsPath, dir_fd: DirFd<0>, vm: &VirtualMachine) -> PyResult<()> {
267 let [] = dir_fd.0;
268 let is_junction = cfg!(windows)
269 && fs::metadata(&path).map_or(false, |meta| meta.file_type().is_dir())
270 && fs::symlink_metadata(&path).map_or(false, |meta| meta.file_type().is_symlink());
271 let res = if is_junction {
272 fs::remove_dir(&path)
273 } else {
274 fs::remove_file(&path)
275 };
276 res.map_err(|err| IOErrorBuilder::with_filename(&err, path, vm))
277 }
278
279 #[cfg(not(windows))]
280 #[pyfunction]
281 fn mkdir(
282 path: OsPath,
283 mode: OptionalArg<i32>,
284 dir_fd: DirFd<{ MKDIR_DIR_FD as usize }>,
285 vm: &VirtualMachine,
286 ) -> PyResult<()> {
287 let mode = mode.unwrap_or(0o777);
288 let path = path.into_cstring(vm)?;
289 #[cfg(not(target_os = "redox"))]
290 if let Some(fd) = dir_fd.get_opt() {
291 let res = unsafe { libc::mkdirat(fd, path.as_ptr(), mode as _) };
292 let res = if res < 0 { Err(errno_err(vm)) } else { Ok(()) };
293 return res;
294 }
295 #[cfg(target_os = "redox")]
296 let [] = dir_fd.0;
297 let res = unsafe { libc::mkdir(path.as_ptr(), mode as _) };
298 if res < 0 {
299 Err(errno_err(vm))
300 } else {
301 Ok(())
302 }
303 }
304
305 #[pyfunction]
306 fn mkdirs(path: PyStrRef, vm: &VirtualMachine) -> PyResult<()> {
307 fs::create_dir_all(path.as_str()).map_err(|err| err.into_pyexception(vm))
308 }
309
310 #[pyfunction]
311 fn rmdir(path: OsPath, dir_fd: DirFd<0>, vm: &VirtualMachine) -> PyResult<()> {
312 let [] = dir_fd.0;
313 fs::remove_dir(&path).map_err(|err| IOErrorBuilder::with_filename(&err, path, vm))
314 }
315
316 const LISTDIR_FD: bool = cfg!(all(unix, not(target_os = "redox")));
317
318 #[pyfunction]
319 fn listdir(path: OptionalArg<OsPathOrFd>, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
320 let path = path.unwrap_or_else(|| OsPathOrFd::Path(OsPath::new_str(".")));
321 let list = match path {
322 OsPathOrFd::Path(path) => {
323 let dir_iter = match fs::read_dir(&path) {
324 Ok(iter) => iter,
325 Err(err) => {
326 return Err(IOErrorBuilder::with_filename(&err, path, vm));
327 }
328 };
329 dir_iter
330 .map(|entry| match entry {
331 Ok(entry_path) => path.mode.process_path(entry_path.file_name(), vm),
332 Err(err) => Err(IOErrorBuilder::with_filename(&err, path.clone(), vm)),
333 })
334 .collect::<PyResult<_>>()?
335 }
336 OsPathOrFd::Fd(fno) => {
337 #[cfg(not(all(unix, not(target_os = "redox"))))]
338 {
339 let _ = fno;
340 return Err(vm.new_not_implemented_error(
341 "can't pass fd to listdir on this platform".to_owned(),
342 ));
343 }
344 #[cfg(all(unix, not(target_os = "redox")))]
345 {
346 use rustpython_common::os::ffi::OsStrExt;
347 let new_fd = nix::unistd::dup(fno).map_err(|e| e.into_pyexception(vm))?;
348 let mut dir =
349 nix::dir::Dir::from_fd(new_fd).map_err(|e| e.into_pyexception(vm))?;
350 dir.iter()
351 .filter_map(|entry| {
352 entry
353 .map_err(|e| e.into_pyexception(vm))
354 .and_then(|entry| {
355 let fname = entry.file_name().to_bytes();
356 Ok(match fname {
357 b"." | b".." => None,
358 _ => Some(
359 OutputMode::String
360 .process_path(ffi::OsStr::from_bytes(fname), vm)?,
361 ),
362 })
363 })
364 .transpose()
365 })
366 .collect::<PyResult<_>>()?
367 }
368 }
369 };
370 Ok(list)
371 }
372
373 fn env_bytes_as_bytes(obj: &Either<PyStrRef, PyBytesRef>) -> &[u8] {
374 match obj {
375 Either::A(ref s) => s.as_str().as_bytes(),
376 Either::B(ref b) => b.as_bytes(),
377 }
378 }
379
380 #[pyfunction]
381 fn putenv(
382 key: Either<PyStrRef, PyBytesRef>,
383 value: Either<PyStrRef, PyBytesRef>,
384 vm: &VirtualMachine,
385 ) -> PyResult<()> {
386 let key = env_bytes_as_bytes(&key);
387 let value = env_bytes_as_bytes(&value);
388 if key.contains(&b'\0') || value.contains(&b'\0') {
389 return Err(vm.new_value_error("embedded null byte".to_string()));
390 }
391 if key.is_empty() || key.contains(&b'=') {
392 return Err(vm.new_value_error("illegal environment variable name".to_string()));
393 }
394 let key = super::bytes_as_osstr(key, vm)?;
395 let value = super::bytes_as_osstr(value, vm)?;
396 env::set_var(key, value);
397 Ok(())
398 }
399
400 #[pyfunction]
401 fn unsetenv(key: Either<PyStrRef, PyBytesRef>, vm: &VirtualMachine) -> PyResult<()> {
402 let key = env_bytes_as_bytes(&key);
403 if key.contains(&b'\0') {
404 return Err(vm.new_value_error("embedded null byte".to_string()));
405 }
406 if key.is_empty() || key.contains(&b'=') {
407 return Err(vm.new_errno_error(
408 22,
409 format!(
410 "Invalid argument: {}",
411 std::str::from_utf8(key).unwrap_or("<bytes encoding failure>")
412 ),
413 ));
414 }
415 let key = super::bytes_as_osstr(key, vm)?;
416 env::remove_var(key);
417 Ok(())
418 }
419
420 #[pyfunction]
421 fn readlink(path: OsPath, dir_fd: DirFd<0>, vm: &VirtualMachine) -> PyResult {
422 let mode = path.mode;
423 let [] = dir_fd.0;
424 let path =
425 fs::read_link(&path).map_err(|err| IOErrorBuilder::with_filename(&err, path, vm))?;
426 mode.process_path(path, vm)
427 }
428
429 #[pyattr]
430 #[pyclass(name)]
431 #[derive(Debug, PyPayload)]
432 struct DirEntry {
433 file_name: std::ffi::OsString,
434 pathval: PathBuf,
435 file_type: io::Result<fs::FileType>,
436 mode: OutputMode,
437 stat: OnceCell<PyObjectRef>,
438 lstat: OnceCell<PyObjectRef>,
439 #[cfg(unix)]
440 ino: AtomicCell<u64>,
441 #[cfg(not(unix))]
442 ino: AtomicCell<Option<u64>>,
443 }
444
445 #[pyclass(with(Representable))]
446 impl DirEntry {
447 #[pygetset]
448 fn name(&self, vm: &VirtualMachine) -> PyResult {
449 self.mode.process_path(&self.file_name, vm)
450 }
451
452 #[pygetset]
453 fn path(&self, vm: &VirtualMachine) -> PyResult {
454 self.mode.process_path(&self.pathval, vm)
455 }
456
457 fn perform_on_metadata(
458 &self,
459 follow_symlinks: FollowSymlinks,
460 action: fn(fs::Metadata) -> bool,
461 vm: &VirtualMachine,
462 ) -> PyResult<bool> {
463 match super::fs_metadata(&self.pathval, follow_symlinks.0) {
464 Ok(meta) => Ok(action(meta)),
465 Err(e) => {
466 if e.kind() == io::ErrorKind::NotFound {
468 Ok(false)
469 } else {
470 Err(e.into_pyexception(vm))
471 }
472 }
473 }
474 }
475
476 #[pymethod]
477 fn is_dir(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> {
478 self.perform_on_metadata(
479 follow_symlinks,
480 |meta: fs::Metadata| -> bool { meta.is_dir() },
481 vm,
482 )
483 }
484
485 #[pymethod]
486 fn is_file(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> {
487 self.perform_on_metadata(
488 follow_symlinks,
489 |meta: fs::Metadata| -> bool { meta.is_file() },
490 vm,
491 )
492 }
493
494 #[pymethod]
495 fn is_symlink(&self, vm: &VirtualMachine) -> PyResult<bool> {
496 Ok(self
497 .file_type
498 .as_ref()
499 .map_err(|err| err.into_pyexception(vm))?
500 .is_symlink())
501 }
502
503 #[pymethod]
504 fn stat(
505 &self,
506 dir_fd: DirFd<{ STAT_DIR_FD as usize }>,
507 follow_symlinks: FollowSymlinks,
508 vm: &VirtualMachine,
509 ) -> PyResult {
510 let do_stat = |follow_symlinks| {
511 stat(
512 OsPath {
513 path: self.pathval.as_os_str().to_owned(),
514 mode: OutputMode::String,
515 }
516 .into(),
517 dir_fd,
518 FollowSymlinks(follow_symlinks),
519 vm,
520 )
521 };
522 let lstat = || self.lstat.get_or_try_init(|| do_stat(false));
523 let stat = if follow_symlinks.0 {
524 self.stat.get_or_try_init(|| {
526 if self.is_symlink(vm)? {
527 do_stat(true)
528 } else {
529 lstat().cloned()
530 }
531 })?
532 } else {
533 lstat()?
534 };
535 Ok(stat.clone())
536 }
537
538 #[cfg(not(unix))]
539 #[pymethod]
540 fn inode(&self, vm: &VirtualMachine) -> PyResult<u64> {
541 match self.ino.load() {
542 Some(ino) => Ok(ino),
543 None => {
544 let stat = stat_inner(
545 OsPath {
546 path: self.pathval.as_os_str().to_owned(),
547 mode: OutputMode::String,
548 }
549 .into(),
550 DirFd::default(),
551 FollowSymlinks(false),
552 )
553 .map_err(|e| e.into_pyexception(vm))?
554 .ok_or_else(|| crate::exceptions::cstring_error(vm))?;
555 let _ = self.ino.compare_exchange(None, Some(stat.st_ino));
557 Ok(stat.st_ino)
558 }
559 }
560 }
561
562 #[cfg(unix)]
563 #[pymethod]
564 fn inode(&self, _vm: &VirtualMachine) -> PyResult<u64> {
565 Ok(self.ino.load())
566 }
567
568 #[cfg(not(windows))]
569 #[pymethod]
570 fn is_junction(&self, _vm: &VirtualMachine) -> PyResult<bool> {
571 Ok(false)
572 }
573
574 #[cfg(windows)]
575 #[pymethod]
576 fn is_junction(&self, _vm: &VirtualMachine) -> PyResult<bool> {
577 Ok(junction::exists(self.pathval.clone()).unwrap_or(false))
578 }
579
580 #[pymethod(magic)]
581 fn fspath(&self, vm: &VirtualMachine) -> PyResult {
582 self.path(vm)
583 }
584
585 #[pyclassmethod(magic)]
586 fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
587 PyGenericAlias::new(cls, args, vm)
588 }
589 }
590
591 impl Representable for DirEntry {
592 #[inline]
593 fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
594 let name = match zelf.as_object().get_attr("name", vm) {
595 Ok(name) => Some(name),
596 Err(e)
597 if e.fast_isinstance(vm.ctx.exceptions.attribute_error)
598 || e.fast_isinstance(vm.ctx.exceptions.value_error) =>
599 {
600 None
601 }
602 Err(e) => return Err(e),
603 };
604 if let Some(name) = name {
605 if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
606 let repr = name.repr(vm)?;
607 Ok(format!("<{} {}>", zelf.class(), repr))
608 } else {
609 Err(vm.new_runtime_error(format!(
610 "reentrant call inside {}.__repr__",
611 zelf.class()
612 )))
613 }
614 } else {
615 Ok(format!("<{}>", zelf.class()))
616 }
617 }
618 }
619
620 #[pyattr]
621 #[pyclass(name = "ScandirIter")]
622 #[derive(Debug, PyPayload)]
623 struct ScandirIterator {
624 entries: PyRwLock<Option<fs::ReadDir>>,
625 mode: OutputMode,
626 }
627
628 #[pyclass(with(IterNext, Iterable))]
629 impl ScandirIterator {
630 #[pymethod]
631 fn close(&self) {
632 let entryref: &mut Option<fs::ReadDir> = &mut self.entries.write();
633 let _dropped = entryref.take();
634 }
635
636 #[pymethod(magic)]
637 fn enter(zelf: PyRef<Self>) -> PyRef<Self> {
638 zelf
639 }
640
641 #[pymethod(magic)]
642 fn exit(zelf: PyRef<Self>, _args: FuncArgs) {
643 zelf.close()
644 }
645 }
646 impl SelfIter for ScandirIterator {}
647 impl IterNext for ScandirIterator {
648 fn next(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
649 let entryref: &mut Option<fs::ReadDir> = &mut zelf.entries.write();
650
651 match entryref {
652 None => Ok(PyIterReturn::StopIteration(None)),
653 Some(inner) => match inner.next() {
654 Some(entry) => match entry {
655 Ok(entry) => {
656 #[cfg(unix)]
657 let ino = {
658 use std::os::unix::fs::DirEntryExt;
659 entry.ino()
660 };
661 #[cfg(not(unix))]
662 let ino = None;
663
664 Ok(PyIterReturn::Return(
665 DirEntry {
666 file_name: entry.file_name(),
667 pathval: entry.path(),
668 file_type: entry.file_type(),
669 mode: zelf.mode,
670 lstat: OnceCell::new(),
671 stat: OnceCell::new(),
672 ino: AtomicCell::new(ino),
673 }
674 .into_ref(&vm.ctx)
675 .into(),
676 ))
677 }
678 Err(err) => Err(err.into_pyexception(vm)),
679 },
680 None => {
681 let _dropped = entryref.take();
682 Ok(PyIterReturn::StopIteration(None))
683 }
684 },
685 }
686 }
687 }
688
689 #[pyfunction]
690 fn scandir(path: OptionalArg<OsPath>, vm: &VirtualMachine) -> PyResult {
691 let path = path.unwrap_or_else(|| OsPath::new_str("."));
692 let entries = fs::read_dir(&path.path)
693 .map_err(|err| IOErrorBuilder::with_filename(&err, path.clone(), vm))?;
694 Ok(ScandirIterator {
695 entries: PyRwLock::new(Some(entries)),
696 mode: path.mode,
697 }
698 .into_ref(&vm.ctx)
699 .into())
700 }
701
702 #[pyattr]
703 #[pyclass(module = "os", name = "stat_result")]
704 #[derive(Debug, PyStructSequence, FromArgs)]
705 struct StatResult {
706 pub st_mode: PyIntRef,
707 pub st_ino: PyIntRef,
708 pub st_dev: PyIntRef,
709 pub st_nlink: PyIntRef,
710 pub st_uid: PyIntRef,
711 pub st_gid: PyIntRef,
712 pub st_size: PyIntRef,
713 #[pyarg(positional, default)]
715 pub __st_atime_int: libc::time_t,
716 #[pyarg(positional, default)]
717 pub __st_mtime_int: libc::time_t,
718 #[pyarg(positional, default)]
719 pub __st_ctime_int: libc::time_t,
720 #[pyarg(any, default)]
721 pub st_atime: f64,
722 #[pyarg(any, default)]
723 pub st_mtime: f64,
724 #[pyarg(any, default)]
725 pub st_ctime: f64,
726 #[pyarg(any, default)]
727 pub st_atime_ns: i128,
728 #[pyarg(any, default)]
729 pub st_mtime_ns: i128,
730 #[pyarg(any, default)]
731 pub st_ctime_ns: i128,
732 #[pyarg(any, default)]
733 pub st_reparse_tag: u32,
734 }
735
736 #[pyclass(with(PyStructSequence))]
737 impl StatResult {
738 fn from_stat(stat: &StatStruct, vm: &VirtualMachine) -> Self {
739 let (atime, mtime, ctime);
740 #[cfg(any(unix, windows))]
741 #[cfg(not(target_os = "netbsd"))]
742 {
743 atime = (stat.st_atime, stat.st_atime_nsec);
744 mtime = (stat.st_mtime, stat.st_mtime_nsec);
745 ctime = (stat.st_ctime, stat.st_ctime_nsec);
746 }
747 #[cfg(target_os = "netbsd")]
748 {
749 atime = (stat.st_atime, stat.st_atimensec);
750 mtime = (stat.st_mtime, stat.st_mtimensec);
751 ctime = (stat.st_ctime, stat.st_ctimensec);
752 }
753 #[cfg(target_os = "wasi")]
754 {
755 atime = (stat.st_atim.tv_sec, stat.st_atim.tv_nsec);
756 mtime = (stat.st_mtim.tv_sec, stat.st_mtim.tv_nsec);
757 ctime = (stat.st_ctim.tv_sec, stat.st_ctim.tv_nsec);
758 }
759
760 const NANOS_PER_SEC: u32 = 1_000_000_000;
761 let to_f64 = |(s, ns)| (s as f64) + (ns as f64) / (NANOS_PER_SEC as f64);
762 let to_ns = |(s, ns)| s as i128 * NANOS_PER_SEC as i128 + ns as i128;
763
764 #[cfg(windows)]
765 let st_reparse_tag = stat.st_reparse_tag;
766 #[cfg(not(windows))]
767 let st_reparse_tag = 0;
768
769 StatResult {
770 st_mode: vm.ctx.new_pyref(stat.st_mode),
771 st_ino: vm.ctx.new_pyref(stat.st_ino),
772 st_dev: vm.ctx.new_pyref(stat.st_dev),
773 st_nlink: vm.ctx.new_pyref(stat.st_nlink),
774 st_uid: vm.ctx.new_pyref(stat.st_uid),
775 st_gid: vm.ctx.new_pyref(stat.st_gid),
776 st_size: vm.ctx.new_pyref(stat.st_size),
777 __st_atime_int: atime.0,
778 __st_mtime_int: mtime.0,
779 __st_ctime_int: ctime.0,
780 st_atime: to_f64(atime),
781 st_mtime: to_f64(mtime),
782 st_ctime: to_f64(ctime),
783 st_atime_ns: to_ns(atime),
784 st_mtime_ns: to_ns(mtime),
785 st_ctime_ns: to_ns(ctime),
786 st_reparse_tag,
787 }
788 }
789
790 #[pyslot]
791 fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
792 let flatten_args = |r: &[PyObjectRef]| {
793 let mut vec_args = Vec::from(r);
794 loop {
795 if let Ok(obj) = vec_args.iter().exactly_one() {
796 match obj.payload::<PyTuple>() {
797 Some(t) => {
798 vec_args = Vec::from(t.as_slice());
799 }
800 None => {
801 return vec_args;
802 }
803 }
804 } else {
805 return vec_args;
806 }
807 }
808 };
809
810 let args: FuncArgs = flatten_args(&args.args).into();
811
812 let stat: StatResult = args.bind(vm)?;
813 Ok(stat.to_pyobject(vm))
814 }
815 }
816
817 #[cfg(windows)]
818 fn stat_inner(
819 file: OsPathOrFd,
820 dir_fd: DirFd<{ STAT_DIR_FD as usize }>,
821 follow_symlinks: FollowSymlinks,
822 ) -> io::Result<Option<StatStruct>> {
823 let [] = dir_fd.0;
825 match file {
826 OsPathOrFd::Path(path) => crate::windows::win32_xstat(&path.path, follow_symlinks.0),
827 OsPathOrFd::Fd(fd) => crate::common::fileutils::fstat(fd),
828 }
829 .map(Some)
830 }
831
832 #[cfg(not(windows))]
833 fn stat_inner(
834 file: OsPathOrFd,
835 dir_fd: DirFd<{ STAT_DIR_FD as usize }>,
836 follow_symlinks: FollowSymlinks,
837 ) -> io::Result<Option<StatStruct>> {
838 let mut stat = std::mem::MaybeUninit::uninit();
839 let ret = match file {
840 OsPathOrFd::Path(path) => {
841 use rustpython_common::os::ffi::OsStrExt;
842 let path = path.as_ref().as_os_str().as_bytes();
843 let path = match ffi::CString::new(path) {
844 Ok(x) => x,
845 Err(_) => return Ok(None),
846 };
847
848 #[cfg(not(target_os = "redox"))]
849 let fstatat_ret = dir_fd.get_opt().map(|dir_fd| {
850 let flags = if follow_symlinks.0 {
851 0
852 } else {
853 libc::AT_SYMLINK_NOFOLLOW
854 };
855 unsafe { libc::fstatat(dir_fd, path.as_ptr(), stat.as_mut_ptr(), flags) }
856 });
857 #[cfg(target_os = "redox")]
858 let ([], fstatat_ret) = (dir_fd.0, None);
859
860 fstatat_ret.unwrap_or_else(|| {
861 if follow_symlinks.0 {
862 unsafe { libc::stat(path.as_ptr(), stat.as_mut_ptr()) }
863 } else {
864 unsafe { libc::lstat(path.as_ptr(), stat.as_mut_ptr()) }
865 }
866 })
867 }
868 OsPathOrFd::Fd(fd) => unsafe { libc::fstat(fd, stat.as_mut_ptr()) },
869 };
870 if ret < 0 {
871 return Err(io::Error::last_os_error());
872 }
873 Ok(Some(unsafe { stat.assume_init() }))
874 }
875
876 #[pyfunction]
877 #[pyfunction(name = "fstat")]
878 fn stat(
879 file: OsPathOrFd,
880 dir_fd: DirFd<{ STAT_DIR_FD as usize }>,
881 follow_symlinks: FollowSymlinks,
882 vm: &VirtualMachine,
883 ) -> PyResult {
884 let stat = stat_inner(file.clone(), dir_fd, follow_symlinks)
885 .map_err(|err| IOErrorBuilder::with_filename(&err, file, vm))?
886 .ok_or_else(|| crate::exceptions::cstring_error(vm))?;
887 Ok(StatResult::from_stat(&stat, vm).to_pyobject(vm))
888 }
889
890 #[pyfunction]
891 fn lstat(
892 file: OsPathOrFd,
893 dir_fd: DirFd<{ STAT_DIR_FD as usize }>,
894 vm: &VirtualMachine,
895 ) -> PyResult {
896 stat(file, dir_fd, FollowSymlinks(false), vm)
897 }
898
899 fn curdir_inner(vm: &VirtualMachine) -> PyResult<PathBuf> {
900 env::current_dir().map_err(|err| err.into_pyexception(vm))
901 }
902
903 #[pyfunction]
904 fn getcwd(vm: &VirtualMachine) -> PyResult {
905 OutputMode::String.process_path(curdir_inner(vm)?, vm)
906 }
907
908 #[pyfunction]
909 fn getcwdb(vm: &VirtualMachine) -> PyResult {
910 OutputMode::Bytes.process_path(curdir_inner(vm)?, vm)
911 }
912
913 #[pyfunction]
914 fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> {
915 env::set_current_dir(&path.path)
916 .map_err(|err| IOErrorBuilder::with_filename(&err, path, vm))
917 }
918
919 #[pyfunction]
920 fn fspath(path: PyObjectRef, vm: &VirtualMachine) -> PyResult<FsPath> {
921 FsPath::try_from(path, false, vm)
922 }
923
924 #[pyfunction]
925 #[pyfunction(name = "replace")]
926 fn rename(src: OsPath, dst: OsPath, vm: &VirtualMachine) -> PyResult<()> {
927 fs::rename(&src.path, &dst.path).map_err(|err| {
928 IOErrorBuilder::new(&err)
929 .filename(src)
930 .filename2(dst)
931 .into_pyexception(vm)
932 })
933 }
934
935 #[pyfunction]
936 fn getpid(vm: &VirtualMachine) -> PyObjectRef {
937 let pid = if cfg!(target_arch = "wasm32") {
938 42
942 } else {
943 std::process::id()
944 };
945 vm.ctx.new_int(pid).into()
946 }
947
948 #[pyfunction]
949 fn cpu_count(vm: &VirtualMachine) -> PyObjectRef {
950 let cpu_count = num_cpus::get();
951 vm.ctx.new_int(cpu_count).into()
952 }
953
954 #[pyfunction]
955 fn _exit(code: i32) {
956 std::process::exit(code)
957 }
958
959 #[pyfunction]
960 fn abort() {
961 extern "C" {
962 fn abort();
963 }
964 unsafe { abort() }
965 }
966
967 #[pyfunction]
968 fn urandom(size: isize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
969 if size < 0 {
970 return Err(vm.new_value_error("negative argument not allowed".to_owned()));
971 }
972 let mut buf = vec![0u8; size as usize];
973 getrandom::getrandom(&mut buf).map_err(|e| match e.raw_os_error() {
974 Some(errno) => io::Error::from_raw_os_error(errno).into_pyexception(vm),
975 None => vm.new_os_error("Getting random failed".to_owned()),
976 })?;
977 Ok(buf)
978 }
979
980 #[pyfunction]
981 pub fn isatty(fd: i32) -> bool {
982 unsafe { suppress_iph!(libc::isatty(fd)) != 0 }
983 }
984
985 #[pyfunction]
986 pub fn lseek(fd: i32, position: Offset, how: i32, vm: &VirtualMachine) -> PyResult<Offset> {
987 #[cfg(not(windows))]
988 let res = unsafe { suppress_iph!(libc::lseek(fd, position, how)) };
989 #[cfg(windows)]
990 let res = unsafe {
991 use windows_sys::Win32::Storage::FileSystem;
992 let handle = Fd(fd).to_raw_handle().map_err(|e| e.into_pyexception(vm))?;
993 let mut distance_to_move: [i32; 2] = std::mem::transmute(position);
994 let ret = FileSystem::SetFilePointer(
995 handle as _,
996 distance_to_move[0],
997 &mut distance_to_move[1],
998 how as _,
999 );
1000 if ret == FileSystem::INVALID_SET_FILE_POINTER {
1001 -1
1002 } else {
1003 distance_to_move[0] = ret as _;
1004 std::mem::transmute::<[i32; 2], i64>(distance_to_move)
1005 }
1006 };
1007 if res < 0 {
1008 Err(errno_err(vm))
1009 } else {
1010 Ok(res)
1011 }
1012 }
1013
1014 #[pyfunction]
1015 fn link(src: OsPath, dst: OsPath, vm: &VirtualMachine) -> PyResult<()> {
1016 fs::hard_link(&src.path, &dst.path).map_err(|err| {
1017 IOErrorBuilder::new(&err)
1018 .filename(src)
1019 .filename2(dst)
1020 .into_pyexception(vm)
1021 })
1022 }
1023
1024 #[derive(FromArgs)]
1025 struct UtimeArgs {
1026 path: OsPath,
1027 #[pyarg(any, default)]
1028 times: Option<PyTupleRef>,
1029 #[pyarg(named, default)]
1030 ns: Option<PyTupleRef>,
1031 #[pyarg(flatten)]
1032 dir_fd: DirFd<{ UTIME_DIR_FD as usize }>,
1033 #[pyarg(flatten)]
1034 follow_symlinks: FollowSymlinks,
1035 }
1036
1037 #[pyfunction]
1038 fn utime(args: UtimeArgs, vm: &VirtualMachine) -> PyResult<()> {
1039 let parse_tup = |tup: &PyTuple| -> Option<(PyObjectRef, PyObjectRef)> {
1040 if tup.len() != 2 {
1041 None
1042 } else {
1043 Some((tup[0].clone(), tup[1].clone()))
1044 }
1045 };
1046 let (acc, modif) = match (args.times, args.ns) {
1047 (Some(t), None) => {
1048 let (a, m) = parse_tup(&t).ok_or_else(|| {
1049 vm.new_type_error(
1050 "utime: 'times' must be either a tuple of two ints or None".to_owned(),
1051 )
1052 })?;
1053 (a.try_into_value(vm)?, m.try_into_value(vm)?)
1054 }
1055 (None, Some(ns)) => {
1056 let (a, m) = parse_tup(&ns).ok_or_else(|| {
1057 vm.new_type_error("utime: 'ns' must be a tuple of two ints".to_owned())
1058 })?;
1059 let ns_in_sec: PyObjectRef = vm.ctx.new_int(1_000_000_000).into();
1060 let ns_to_dur = |obj: PyObjectRef| {
1061 let divmod = vm._divmod(&obj, &ns_in_sec)?;
1062 let (div, rem) =
1063 divmod
1064 .payload::<PyTuple>()
1065 .and_then(parse_tup)
1066 .ok_or_else(|| {
1067 vm.new_type_error(format!(
1068 "{}.__divmod__() must return a 2-tuple, not {}",
1069 obj.class().name(),
1070 divmod.class().name()
1071 ))
1072 })?;
1073 let secs = div.try_index(vm)?.try_to_primitive(vm)?;
1074 let ns = rem.try_index(vm)?.try_to_primitive(vm)?;
1075 Ok(Duration::new(secs, ns))
1076 };
1077 (ns_to_dur(a)?, ns_to_dur(m)?)
1079 }
1080 (None, None) => {
1081 let now = SystemTime::now();
1082 let now = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
1083 (now, now)
1084 }
1085 (Some(_), Some(_)) => {
1086 return Err(vm.new_value_error(
1087 "utime: you may specify either 'times' or 'ns' but not both".to_owned(),
1088 ))
1089 }
1090 };
1091 utime_impl(args.path, acc, modif, args.dir_fd, args.follow_symlinks, vm)
1092 }
1093
1094 fn utime_impl(
1095 path: OsPath,
1096 acc: Duration,
1097 modif: Duration,
1098 dir_fd: DirFd<{ UTIME_DIR_FD as usize }>,
1099 _follow_symlinks: FollowSymlinks,
1100 vm: &VirtualMachine,
1101 ) -> PyResult<()> {
1102 #[cfg(any(target_os = "wasi", unix))]
1103 {
1104 #[cfg(not(target_os = "redox"))]
1105 {
1106 let path = path.into_cstring(vm)?;
1107
1108 let ts = |d: Duration| libc::timespec {
1109 tv_sec: d.as_secs() as _,
1110 tv_nsec: d.subsec_nanos() as _,
1111 };
1112 let times = [ts(acc), ts(modif)];
1113
1114 let ret = unsafe {
1115 libc::utimensat(
1116 dir_fd.fd().0,
1117 path.as_ptr(),
1118 times.as_ptr(),
1119 if _follow_symlinks.0 {
1120 0
1121 } else {
1122 libc::AT_SYMLINK_NOFOLLOW
1123 },
1124 )
1125 };
1126 if ret < 0 {
1127 Err(errno_err(vm))
1128 } else {
1129 Ok(())
1130 }
1131 }
1132 #[cfg(target_os = "redox")]
1133 {
1134 let [] = dir_fd.0;
1135
1136 let tv = |d: Duration| libc::timeval {
1137 tv_sec: d.as_secs() as _,
1138 tv_usec: d.as_micros() as _,
1139 };
1140 nix::sys::stat::utimes(path.as_ref(), &tv(acc).into(), &tv(modif).into())
1141 .map_err(|err| err.into_pyexception(vm))
1142 }
1143 }
1144 #[cfg(windows)]
1145 {
1146 use std::{fs::OpenOptions, os::windows::prelude::*};
1147 type DWORD = u32;
1148 use windows_sys::Win32::{Foundation::FILETIME, Storage::FileSystem};
1149
1150 let [] = dir_fd.0;
1151
1152 let ft = |d: Duration| {
1153 let intervals =
1154 ((d.as_secs() as i64 + 11644473600) * 10_000_000) + (d.as_nanos() as i64 / 100);
1155 FILETIME {
1156 dwLowDateTime: intervals as DWORD,
1157 dwHighDateTime: (intervals >> 32) as DWORD,
1158 }
1159 };
1160
1161 let acc = ft(acc);
1162 let modif = ft(modif);
1163
1164 let f = OpenOptions::new()
1165 .write(true)
1166 .custom_flags(windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS)
1167 .open(path)
1168 .map_err(|err| err.into_pyexception(vm))?;
1169
1170 let ret = unsafe {
1171 FileSystem::SetFileTime(f.as_raw_handle() as _, std::ptr::null(), &acc, &modif)
1172 };
1173
1174 if ret == 0 {
1175 Err(io::Error::last_os_error().into_pyexception(vm))
1176 } else {
1177 Ok(())
1178 }
1179 }
1180 }
1181
1182 #[cfg(all(any(unix, windows), not(target_os = "redox")))]
1183 #[pyattr]
1184 #[pyclass(module = "os", name = "times_result")]
1185 #[derive(Debug, PyStructSequence)]
1186 struct TimesResult {
1187 pub user: f64,
1188 pub system: f64,
1189 pub children_user: f64,
1190 pub children_system: f64,
1191 pub elapsed: f64,
1192 }
1193
1194 #[cfg(all(any(unix, windows), not(target_os = "redox")))]
1195 #[pyclass(with(PyStructSequence))]
1196 impl TimesResult {}
1197
1198 #[cfg(all(any(unix, windows), not(target_os = "redox")))]
1199 #[pyfunction]
1200 fn times(vm: &VirtualMachine) -> PyResult {
1201 #[cfg(windows)]
1202 {
1203 use std::mem::MaybeUninit;
1204 use windows_sys::Win32::{Foundation::FILETIME, System::Threading};
1205
1206 let mut _create = MaybeUninit::<FILETIME>::uninit();
1207 let mut _exit = MaybeUninit::<FILETIME>::uninit();
1208 let mut kernel = MaybeUninit::<FILETIME>::uninit();
1209 let mut user = MaybeUninit::<FILETIME>::uninit();
1210
1211 unsafe {
1212 let h_proc = Threading::GetCurrentProcess();
1213 Threading::GetProcessTimes(
1214 h_proc,
1215 _create.as_mut_ptr(),
1216 _exit.as_mut_ptr(),
1217 kernel.as_mut_ptr(),
1218 user.as_mut_ptr(),
1219 );
1220 }
1221
1222 let kernel = unsafe { kernel.assume_init() };
1223 let user = unsafe { user.assume_init() };
1224
1225 let times_result = TimesResult {
1226 user: user.dwHighDateTime as f64 * 429.4967296 + user.dwLowDateTime as f64 * 1e-7,
1227 system: kernel.dwHighDateTime as f64 * 429.4967296
1228 + kernel.dwLowDateTime as f64 * 1e-7,
1229 children_user: 0.0,
1230 children_system: 0.0,
1231 elapsed: 0.0,
1232 };
1233
1234 Ok(times_result.to_pyobject(vm))
1235 }
1236 #[cfg(unix)]
1237 {
1238 let mut t = libc::tms {
1239 tms_utime: 0,
1240 tms_stime: 0,
1241 tms_cutime: 0,
1242 tms_cstime: 0,
1243 };
1244
1245 let tick_for_second = unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as f64;
1246 let c = unsafe { libc::times(&mut t as *mut _) };
1247
1248 if c == (-1i8) as libc::clock_t {
1250 return Err(vm.new_os_error("Fail to get times".to_string()));
1251 }
1252
1253 let times_result = TimesResult {
1254 user: t.tms_utime as f64 / tick_for_second,
1255 system: t.tms_stime as f64 / tick_for_second,
1256 children_user: t.tms_cutime as f64 / tick_for_second,
1257 children_system: t.tms_cstime as f64 / tick_for_second,
1258 elapsed: c as f64 / tick_for_second,
1259 };
1260
1261 Ok(times_result.to_pyobject(vm))
1262 }
1263 }
1264
1265 #[cfg(target_os = "linux")]
1266 #[derive(FromArgs)]
1267 struct CopyFileRangeArgs {
1268 #[pyarg(positional)]
1269 src: i32,
1270 #[pyarg(positional)]
1271 dst: i32,
1272 #[pyarg(positional)]
1273 count: i64,
1274 #[pyarg(any, default)]
1275 offset_src: Option<Offset>,
1276 #[pyarg(any, default)]
1277 offset_dst: Option<Offset>,
1278 }
1279
1280 #[cfg(target_os = "linux")]
1281 #[pyfunction]
1282 fn copy_file_range(args: CopyFileRangeArgs, vm: &VirtualMachine) -> PyResult<usize> {
1283 let p_offset_src = args.offset_src.as_ref().map_or_else(std::ptr::null, |x| x);
1284 let p_offset_dst = args.offset_dst.as_ref().map_or_else(std::ptr::null, |x| x);
1285 let count: usize = args
1286 .count
1287 .try_into()
1288 .map_err(|_| vm.new_value_error("count should >= 0".to_string()))?;
1289
1290 let flags = 0u32;
1293
1294 let ret = unsafe {
1300 libc::syscall(
1301 libc::SYS_copy_file_range,
1302 args.src,
1303 p_offset_src as *mut i64,
1304 args.dst,
1305 p_offset_dst as *mut i64,
1306 count,
1307 flags,
1308 )
1309 };
1310
1311 usize::try_from(ret).map_err(|_| errno_err(vm))
1312 }
1313
1314 #[pyfunction]
1315 fn strerror(e: i32) -> String {
1316 unsafe { ffi::CStr::from_ptr(libc::strerror(e)) }
1317 .to_string_lossy()
1318 .into_owned()
1319 }
1320
1321 #[pyfunction]
1322 pub fn ftruncate(fd: i32, length: Offset, vm: &VirtualMachine) -> PyResult<()> {
1323 Fd(fd).ftruncate(length).map_err(|e| e.into_pyexception(vm))
1324 }
1325
1326 #[pyfunction]
1327 fn truncate(path: PyObjectRef, length: Offset, vm: &VirtualMachine) -> PyResult<()> {
1328 if let Ok(fd) = path.try_to_value(vm) {
1329 return ftruncate(fd, length, vm);
1330 }
1331
1332 #[cold]
1333 fn error(
1334 vm: &VirtualMachine,
1335 error: std::io::Error,
1336 path: OsPath,
1337 ) -> crate::builtins::PyBaseExceptionRef {
1338 IOErrorBuilder::with_filename(&error, path, vm)
1339 }
1340
1341 let path = OsPath::try_from_object(vm, path)?;
1342 let f = match OpenOptions::new().write(true).open(&path) {
1344 Ok(f) => f,
1345 Err(e) => return Err(error(vm, e, path)),
1346 };
1347 f.set_len(length as u64).map_err(|e| error(vm, e, path))?;
1348 drop(f);
1349 Ok(())
1350 }
1351
1352 #[cfg(all(unix, not(any(target_os = "redox", target_os = "android"))))]
1353 #[pyfunction]
1354 fn getloadavg(vm: &VirtualMachine) -> PyResult<(f64, f64, f64)> {
1355 let mut loadavg = [0f64; 3];
1356
1357 unsafe {
1360 if libc::getloadavg(&mut loadavg[0] as *mut f64, 3) != 3 {
1361 return Err(vm.new_os_error("Load averages are unobtainable".to_string()));
1362 }
1363 }
1364
1365 Ok((loadavg[0], loadavg[1], loadavg[2]))
1366 }
1367
1368 #[cfg(any(unix, windows))]
1369 #[pyfunction]
1370 fn waitstatus_to_exitcode(status: i32, vm: &VirtualMachine) -> PyResult<i32> {
1371 let status = u32::try_from(status)
1372 .map_err(|_| vm.new_value_error(format!("invalid WEXITSTATUS: {status}")))?;
1373
1374 cfg_if::cfg_if! {
1375 if #[cfg(not(windows))] {
1376 let status = status as libc::c_int;
1377 if libc::WIFEXITED(status) {
1378 return Ok(libc::WEXITSTATUS(status));
1379 }
1380
1381 if libc::WIFSIGNALED(status) {
1382 return Ok(-libc::WTERMSIG(status));
1383 }
1384
1385 Err(vm.new_value_error(format!("Invalid wait status: {status}")))
1386 } else {
1387 i32::try_from(status.rotate_right(8))
1388 .map_err(|_| vm.new_value_error(format!("invalid wait status: {status}")))
1389 }
1390 }
1391 }
1392
1393 #[pyfunction]
1394 fn device_encoding(fd: i32, _vm: &VirtualMachine) -> PyResult<Option<String>> {
1395 if !isatty(fd) {
1396 return Ok(None);
1397 }
1398
1399 cfg_if::cfg_if! {
1400 if #[cfg(any(target_os = "android", target_os = "redox"))] {
1401 Ok(Some("UTF-8".to_owned()))
1402 } else if #[cfg(windows)] {
1403 use windows_sys::Win32::System::Console;
1404 let cp = match fd {
1405 0 => unsafe { Console::GetConsoleCP() },
1406 1 | 2 => unsafe { Console::GetConsoleOutputCP() },
1407 _ => 0,
1408 };
1409
1410 Ok(Some(format!("cp{cp}")))
1411 } else {
1412 let encoding = unsafe {
1413 let encoding = libc::nl_langinfo(libc::CODESET);
1414 if encoding.is_null() || encoding.read() == '\0' as libc::c_char {
1415 "UTF-8".to_owned()
1416 } else {
1417 ffi::CStr::from_ptr(encoding).to_string_lossy().into_owned()
1418 }
1419 };
1420
1421 Ok(Some(encoding))
1422 }
1423 }
1424 }
1425
1426 #[pyattr]
1427 #[pyclass(module = "os", name = "terminal_size")]
1428 #[derive(PyStructSequence)]
1429 #[allow(dead_code)]
1430 pub(crate) struct PyTerminalSize {
1431 pub columns: usize,
1432 pub lines: usize,
1433 }
1434 #[pyclass(with(PyStructSequence))]
1435 impl PyTerminalSize {}
1436
1437 #[pyattr]
1438 #[pyclass(module = "os", name = "uname_result")]
1439 #[derive(Debug, PyStructSequence)]
1440 pub(crate) struct UnameResult {
1441 pub sysname: String,
1442 pub nodename: String,
1443 pub release: String,
1444 pub version: String,
1445 pub machine: String,
1446 }
1447
1448 #[pyclass(with(PyStructSequence))]
1449 impl UnameResult {}
1450
1451 pub(super) fn support_funcs() -> Vec<SupportFunc> {
1452 let mut supports = super::platform::module::support_funcs();
1453 supports.extend(vec![
1454 SupportFunc::new("open", Some(false), Some(OPEN_DIR_FD), Some(false)),
1455 SupportFunc::new("access", Some(false), Some(false), None),
1456 SupportFunc::new("chdir", None, Some(false), Some(false)),
1457 SupportFunc::new("listdir", Some(LISTDIR_FD), Some(false), Some(false)),
1459 SupportFunc::new("mkdir", Some(false), Some(MKDIR_DIR_FD), Some(false)),
1460 SupportFunc::new("readlink", Some(false), None, Some(false)),
1463 SupportFunc::new("remove", Some(false), None, Some(false)),
1464 SupportFunc::new("unlink", Some(false), None, Some(false)),
1465 SupportFunc::new("rename", Some(false), None, Some(false)),
1466 SupportFunc::new("replace", Some(false), None, Some(false)), SupportFunc::new("rmdir", Some(false), None, Some(false)),
1468 SupportFunc::new("scandir", None, Some(false), Some(false)),
1469 SupportFunc::new("stat", Some(true), Some(STAT_DIR_FD), Some(true)),
1470 SupportFunc::new("fstat", Some(true), Some(STAT_DIR_FD), Some(true)),
1471 SupportFunc::new("symlink", Some(false), Some(SYMLINK_DIR_FD), Some(false)),
1472 SupportFunc::new("truncate", Some(true), Some(false), Some(false)),
1473 SupportFunc::new(
1474 "utime",
1475 Some(false),
1476 Some(UTIME_DIR_FD),
1477 Some(cfg!(all(unix, not(target_os = "redox")))),
1478 ),
1479 ]);
1480 supports
1481 }
1482}
1483pub(crate) use _os::{ftruncate, isatty, lseek};
1484
1485pub(crate) struct SupportFunc {
1486 name: &'static str,
1487 fd: Option<bool>,
1491 dir_fd: Option<bool>,
1492 follow_symlinks: Option<bool>,
1493}
1494
1495impl SupportFunc {
1496 pub(crate) fn new(
1497 name: &'static str,
1498 fd: Option<bool>,
1499 dir_fd: Option<bool>,
1500 follow_symlinks: Option<bool>,
1501 ) -> Self {
1502 Self {
1503 name,
1504 fd,
1505 dir_fd,
1506 follow_symlinks,
1507 }
1508 }
1509}
1510
1511pub fn extend_module(vm: &VirtualMachine, module: &Py<PyModule>) {
1512 let support_funcs = _os::support_funcs();
1513 let supports_fd = PySet::default().into_ref(&vm.ctx);
1514 let supports_dir_fd = PySet::default().into_ref(&vm.ctx);
1515 let supports_follow_symlinks = PySet::default().into_ref(&vm.ctx);
1516 for support in support_funcs {
1517 let func_obj = module.get_attr(support.name, vm).unwrap();
1518 if support.fd.unwrap_or(false) {
1519 supports_fd.clone().add(func_obj.clone(), vm).unwrap();
1520 }
1521 if support.dir_fd.unwrap_or(false) {
1522 supports_dir_fd.clone().add(func_obj.clone(), vm).unwrap();
1523 }
1524 if support.follow_symlinks.unwrap_or(false) {
1525 supports_follow_symlinks.clone().add(func_obj, vm).unwrap();
1526 }
1527 }
1528
1529 extend_module!(vm, module, {
1530 "supports_fd" => supports_fd,
1531 "supports_dir_fd" => supports_dir_fd,
1532 "supports_follow_symlinks" => supports_follow_symlinks,
1533 "error" => vm.ctx.exceptions.os_error.to_owned(),
1534 });
1535}
1536
1537#[cfg(not(windows))]
1538use super::posix as platform;
1539
1540#[cfg(windows)]
1541use super::nt as platform;
1542
1543pub(crate) use platform::module::MODULE_NAME;