rustpython_vm/stdlib/
posix.rs

1use crate::{builtins::PyModule, PyRef, VirtualMachine};
2use std::os::unix::io::RawFd;
3
4pub fn raw_set_inheritable(fd: RawFd, inheritable: bool) -> nix::Result<()> {
5    use nix::fcntl;
6    let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?);
7    let mut new_flags = flags;
8    new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable);
9    if flags != new_flags {
10        fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags))?;
11    }
12    Ok(())
13}
14
15pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
16    let module = module::make_module(vm);
17    super::os::extend_module(vm, &module);
18    module
19}
20
21#[pymodule(name = "posix", with(super::os::_os))]
22pub mod module {
23    use crate::{
24        builtins::{PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyTypeRef},
25        convert::{IntoPyException, ToPyObject, TryFromObject},
26        function::{Either, KwArgs, OptionalArg},
27        ospath::{IOErrorBuilder, OsPath, OsPathOrFd},
28        stdlib::os::{
29            errno_err, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, _os, fs_metadata,
30        },
31        types::{Constructor, Representable},
32        utils::ToCString,
33        AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
34    };
35    use bitflags::bitflags;
36    use nix::{
37        fcntl,
38        unistd::{self, Gid, Pid, Uid},
39    };
40    use std::{
41        env,
42        ffi::{CStr, CString},
43        fs, io,
44        os::fd::{AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd},
45    };
46    use strum_macros::{EnumIter, EnumString};
47
48    #[pyattr]
49    use libc::{PRIO_PGRP, PRIO_PROCESS, PRIO_USER};
50
51    #[cfg(any(
52        target_os = "dragonfly",
53        target_os = "freebsd",
54        target_os = "linux",
55        target_os = "macos"
56    ))]
57    #[pyattr]
58    use libc::{SEEK_DATA, SEEK_HOLE};
59
60    #[cfg(not(any(target_os = "redox", target_os = "freebsd")))]
61    #[pyattr]
62    use libc::O_DSYNC;
63    #[pyattr]
64    use libc::{O_CLOEXEC, O_NONBLOCK, WNOHANG};
65    #[cfg(target_os = "macos")]
66    #[pyattr]
67    use libc::{O_EVTONLY, O_FSYNC, O_NOFOLLOW_ANY, O_SYMLINK};
68    #[cfg(not(target_os = "redox"))]
69    #[pyattr]
70    use libc::{O_NDELAY, O_NOCTTY};
71
72    #[pyattr]
73    use libc::{RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, RTLD_NOW};
74
75    #[cfg(target_os = "linux")]
76    #[pyattr]
77    use libc::{GRND_NONBLOCK, GRND_RANDOM};
78
79    #[pyattr]
80    const EX_OK: i8 = exitcode::OK as i8;
81    #[pyattr]
82    const EX_USAGE: i8 = exitcode::USAGE as i8;
83    #[pyattr]
84    const EX_DATAERR: i8 = exitcode::DATAERR as i8;
85    #[pyattr]
86    const EX_NOINPUT: i8 = exitcode::NOINPUT as i8;
87    #[pyattr]
88    const EX_NOUSER: i8 = exitcode::NOUSER as i8;
89    #[pyattr]
90    const EX_NOHOST: i8 = exitcode::NOHOST as i8;
91    #[pyattr]
92    const EX_UNAVAILABLE: i8 = exitcode::UNAVAILABLE as i8;
93    #[pyattr]
94    const EX_SOFTWARE: i8 = exitcode::SOFTWARE as i8;
95    #[pyattr]
96    const EX_OSERR: i8 = exitcode::OSERR as i8;
97    #[pyattr]
98    const EX_OSFILE: i8 = exitcode::OSFILE as i8;
99    #[pyattr]
100    const EX_CANTCREAT: i8 = exitcode::CANTCREAT as i8;
101    #[pyattr]
102    const EX_IOERR: i8 = exitcode::IOERR as i8;
103    #[pyattr]
104    const EX_TEMPFAIL: i8 = exitcode::TEMPFAIL as i8;
105    #[pyattr]
106    const EX_PROTOCOL: i8 = exitcode::PROTOCOL as i8;
107    #[pyattr]
108    const EX_NOPERM: i8 = exitcode::NOPERM as i8;
109    #[pyattr]
110    const EX_CONFIG: i8 = exitcode::CONFIG as i8;
111
112    #[cfg(any(
113        target_os = "macos",
114        target_os = "linux",
115        target_os = "android",
116        target_os = "freebsd",
117        target_os = "dragonfly",
118        target_os = "netbsd",
119        target_os = "macos"
120    ))]
121    #[pyattr]
122    const SCHED_RR: i32 = libc::SCHED_RR;
123    #[cfg(any(
124        target_os = "macos",
125        target_os = "linux",
126        target_os = "android",
127        target_os = "freebsd",
128        target_os = "dragonfly",
129        target_os = "netbsd",
130        target_os = "macos"
131    ))]
132    #[pyattr]
133    const SCHED_FIFO: i32 = libc::SCHED_FIFO;
134    #[cfg(any(
135        target_os = "macos",
136        target_os = "linux",
137        target_os = "freebsd",
138        target_os = "dragonfly",
139        target_os = "netbsd",
140        target_os = "macos"
141    ))]
142    #[pyattr]
143    const SCHED_OTHER: i32 = libc::SCHED_OTHER;
144    #[cfg(any(target_os = "linux", target_os = "android"))]
145    #[pyattr]
146    const SCHED_IDLE: i32 = libc::SCHED_IDLE;
147    #[cfg(any(target_os = "linux", target_os = "android"))]
148    #[pyattr]
149    const SCHED_BATCH: i32 = libc::SCHED_BATCH;
150
151    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
152    #[pyattr]
153    const POSIX_SPAWN_OPEN: i32 = PosixSpawnFileActionIdentifier::Open as i32;
154    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
155    #[pyattr]
156    const POSIX_SPAWN_CLOSE: i32 = PosixSpawnFileActionIdentifier::Close as i32;
157    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
158    #[pyattr]
159    const POSIX_SPAWN_DUP2: i32 = PosixSpawnFileActionIdentifier::Dup2 as i32;
160
161    #[cfg(target_os = "macos")]
162    #[pyattr]
163    const _COPYFILE_DATA: u32 = 1 << 3;
164
165    impl TryFromObject for BorrowedFd<'_> {
166        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
167            let fd = i32::try_from_object(vm, obj)?;
168            if fd == -1 {
169                return Err(io::Error::from_raw_os_error(libc::EBADF).into_pyexception(vm));
170            }
171            // SAFETY: none, really. but, python's os api of passing around file descriptors
172            //         everywhere isn't really io-safe anyway, so, this is passed to the user.
173            Ok(unsafe { BorrowedFd::borrow_raw(fd) })
174        }
175    }
176
177    impl ToPyObject for OwnedFd {
178        fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
179            self.into_raw_fd().to_pyobject(vm)
180        }
181    }
182
183    // Flags for os_access
184    bitflags! {
185        #[derive(Copy, Clone, Debug, PartialEq)]
186        pub struct AccessFlags: u8 {
187            const F_OK = _os::F_OK;
188            const R_OK = _os::R_OK;
189            const W_OK = _os::W_OK;
190            const X_OK = _os::X_OK;
191        }
192    }
193
194    struct Permissions {
195        is_readable: bool,
196        is_writable: bool,
197        is_executable: bool,
198    }
199
200    fn get_permissions(mode: u32) -> Permissions {
201        Permissions {
202            is_readable: mode & 4 != 0,
203            is_writable: mode & 2 != 0,
204            is_executable: mode & 1 != 0,
205        }
206    }
207
208    fn get_right_permission(
209        mode: u32,
210        file_owner: Uid,
211        file_group: Gid,
212    ) -> nix::Result<Permissions> {
213        let owner_mode = (mode & 0o700) >> 6;
214        let owner_permissions = get_permissions(owner_mode);
215
216        let group_mode = (mode & 0o070) >> 3;
217        let group_permissions = get_permissions(group_mode);
218
219        let others_mode = mode & 0o007;
220        let others_permissions = get_permissions(others_mode);
221
222        let user_id = nix::unistd::getuid();
223        let groups_ids = getgroups_impl()?;
224
225        if file_owner == user_id {
226            Ok(owner_permissions)
227        } else if groups_ids.contains(&file_group) {
228            Ok(group_permissions)
229        } else {
230            Ok(others_permissions)
231        }
232    }
233
234    #[cfg(any(target_os = "macos", target_os = "ios"))]
235    fn getgroups_impl() -> nix::Result<Vec<Gid>> {
236        use libc::{c_int, gid_t};
237        use nix::errno::Errno;
238        use std::ptr;
239        let ret = unsafe { libc::getgroups(0, ptr::null_mut()) };
240        let mut groups = Vec::<Gid>::with_capacity(Errno::result(ret)? as usize);
241        let ret = unsafe {
242            libc::getgroups(
243                groups.capacity() as c_int,
244                groups.as_mut_ptr() as *mut gid_t,
245            )
246        };
247
248        Errno::result(ret).map(|s| {
249            unsafe { groups.set_len(s as usize) };
250            groups
251        })
252    }
253
254    #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "redox")))]
255    use nix::unistd::getgroups as getgroups_impl;
256
257    #[cfg(target_os = "redox")]
258    fn getgroups_impl() -> nix::Result<Vec<Gid>> {
259        Err(nix::Error::EOPNOTSUPP)
260    }
261
262    #[pyfunction]
263    fn getgroups(vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
264        let group_ids = getgroups_impl().map_err(|e| e.into_pyexception(vm))?;
265        Ok(group_ids
266            .into_iter()
267            .map(|gid| vm.ctx.new_int(gid.as_raw()).into())
268            .collect())
269    }
270
271    #[pyfunction]
272    pub(super) fn access(path: OsPath, mode: u8, vm: &VirtualMachine) -> PyResult<bool> {
273        use std::os::unix::fs::MetadataExt;
274
275        let flags = AccessFlags::from_bits(mode).ok_or_else(|| {
276            vm.new_value_error(
277            "One of the flags is wrong, there are only 4 possibilities F_OK, R_OK, W_OK and X_OK"
278                .to_owned(),
279        )
280        })?;
281
282        let metadata = fs::metadata(&path.path);
283
284        // if it's only checking for F_OK
285        if flags == AccessFlags::F_OK {
286            return Ok(metadata.is_ok());
287        }
288
289        let metadata =
290            metadata.map_err(|err| IOErrorBuilder::with_filename(&err, path.clone(), vm))?;
291
292        let user_id = metadata.uid();
293        let group_id = metadata.gid();
294        let mode = metadata.mode();
295
296        let perm = get_right_permission(mode, Uid::from_raw(user_id), Gid::from_raw(group_id))
297            .map_err(|err| err.into_pyexception(vm))?;
298
299        let r_ok = !flags.contains(AccessFlags::R_OK) || perm.is_readable;
300        let w_ok = !flags.contains(AccessFlags::W_OK) || perm.is_writable;
301        let x_ok = !flags.contains(AccessFlags::X_OK) || perm.is_executable;
302
303        Ok(r_ok && w_ok && x_ok)
304    }
305
306    #[pyattr]
307    fn environ(vm: &VirtualMachine) -> PyDictRef {
308        use rustpython_common::os::ffi::OsStringExt;
309
310        let environ = vm.ctx.new_dict();
311        for (key, value) in env::vars_os() {
312            let key: PyObjectRef = vm.ctx.new_bytes(key.into_vec()).into();
313            let value: PyObjectRef = vm.ctx.new_bytes(value.into_vec()).into();
314            environ.set_item(&*key, value, vm).unwrap();
315        }
316
317        environ
318    }
319
320    #[derive(FromArgs)]
321    pub(super) struct SymlinkArgs {
322        src: OsPath,
323        dst: OsPath,
324        #[pyarg(flatten)]
325        _target_is_directory: TargetIsDirectory,
326        #[pyarg(flatten)]
327        dir_fd: DirFd<{ _os::SYMLINK_DIR_FD as usize }>,
328    }
329
330    #[pyfunction]
331    pub(super) fn symlink(args: SymlinkArgs, vm: &VirtualMachine) -> PyResult<()> {
332        let src = args.src.into_cstring(vm)?;
333        let dst = args.dst.into_cstring(vm)?;
334        #[cfg(not(target_os = "redox"))]
335        {
336            nix::unistd::symlinkat(&*src, args.dir_fd.get_opt(), &*dst)
337                .map_err(|err| err.into_pyexception(vm))
338        }
339        #[cfg(target_os = "redox")]
340        {
341            let [] = args.dir_fd.0;
342            let res = unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) };
343            if res < 0 {
344                Err(errno_err(vm))
345            } else {
346                Ok(())
347            }
348        }
349    }
350
351    #[cfg(not(target_os = "redox"))]
352    #[pyfunction]
353    fn fchdir(fd: RawFd, vm: &VirtualMachine) -> PyResult<()> {
354        nix::unistd::fchdir(fd).map_err(|err| err.into_pyexception(vm))
355    }
356
357    #[cfg(not(target_os = "redox"))]
358    #[pyfunction]
359    fn chroot(path: OsPath, vm: &VirtualMachine) -> PyResult<()> {
360        use crate::ospath::IOErrorBuilder;
361
362        nix::unistd::chroot(&*path.path).map_err(|err| {
363            // Use `From<nix::Error> for io::Error` when it is available
364            let err = io::Error::from_raw_os_error(err as i32);
365            IOErrorBuilder::with_filename(&err, path, vm)
366        })
367    }
368
369    // As of now, redox does not seems to support chown command (cf. https://gitlab.redox-os.org/redox-os/coreutils , last checked on 05/07/2020)
370    #[cfg(not(target_os = "redox"))]
371    #[pyfunction]
372    fn chown(
373        path: OsPathOrFd,
374        uid: isize,
375        gid: isize,
376        dir_fd: DirFd<1>,
377        follow_symlinks: FollowSymlinks,
378        vm: &VirtualMachine,
379    ) -> PyResult<()> {
380        let uid = if uid >= 0 {
381            Some(nix::unistd::Uid::from_raw(uid as u32))
382        } else if uid == -1 {
383            None
384        } else {
385            return Err(vm.new_os_error(String::from("Specified uid is not valid.")));
386        };
387
388        let gid = if gid >= 0 {
389            Some(nix::unistd::Gid::from_raw(gid as u32))
390        } else if gid == -1 {
391            None
392        } else {
393            return Err(vm.new_os_error(String::from("Specified gid is not valid.")));
394        };
395
396        let flag = if follow_symlinks.0 {
397            nix::unistd::FchownatFlags::FollowSymlink
398        } else {
399            nix::unistd::FchownatFlags::NoFollowSymlink
400        };
401
402        let dir_fd = dir_fd.get_opt();
403        match path {
404            OsPathOrFd::Path(ref p) => {
405                nix::unistd::fchownat(dir_fd, p.path.as_os_str(), uid, gid, flag)
406            }
407            OsPathOrFd::Fd(fd) => nix::unistd::fchown(fd, uid, gid),
408        }
409        .map_err(|err| {
410            // Use `From<nix::Error> for io::Error` when it is available
411            let err = io::Error::from_raw_os_error(err as i32);
412            IOErrorBuilder::with_filename(&err, path, vm)
413        })
414    }
415
416    #[cfg(not(target_os = "redox"))]
417    #[pyfunction]
418    fn lchown(path: OsPath, uid: isize, gid: isize, vm: &VirtualMachine) -> PyResult<()> {
419        chown(
420            OsPathOrFd::Path(path),
421            uid,
422            gid,
423            DirFd::default(),
424            FollowSymlinks(false),
425            vm,
426        )
427    }
428
429    #[cfg(not(target_os = "redox"))]
430    #[pyfunction]
431    fn fchown(fd: i32, uid: isize, gid: isize, vm: &VirtualMachine) -> PyResult<()> {
432        chown(
433            OsPathOrFd::Fd(fd),
434            uid,
435            gid,
436            DirFd::default(),
437            FollowSymlinks(true),
438            vm,
439        )
440    }
441
442    #[derive(FromArgs)]
443    struct RegisterAtForkArgs {
444        #[pyarg(named, optional)]
445        before: OptionalArg<PyObjectRef>,
446        #[pyarg(named, optional)]
447        after_in_parent: OptionalArg<PyObjectRef>,
448        #[pyarg(named, optional)]
449        after_in_child: OptionalArg<PyObjectRef>,
450    }
451
452    impl RegisterAtForkArgs {
453        fn into_validated(
454            self,
455            vm: &VirtualMachine,
456        ) -> PyResult<(
457            Option<PyObjectRef>,
458            Option<PyObjectRef>,
459            Option<PyObjectRef>,
460        )> {
461            fn into_option(
462                arg: OptionalArg<PyObjectRef>,
463                vm: &VirtualMachine,
464            ) -> PyResult<Option<PyObjectRef>> {
465                match arg {
466                    OptionalArg::Present(obj) => {
467                        if !obj.is_callable() {
468                            return Err(vm.new_type_error("Args must be callable".to_owned()));
469                        }
470                        Ok(Some(obj))
471                    }
472                    OptionalArg::Missing => Ok(None),
473                }
474            }
475            let before = into_option(self.before, vm)?;
476            let after_in_parent = into_option(self.after_in_parent, vm)?;
477            let after_in_child = into_option(self.after_in_child, vm)?;
478            if before.is_none() && after_in_parent.is_none() && after_in_child.is_none() {
479                return Err(vm.new_type_error("At least one arg must be present".to_owned()));
480            }
481            Ok((before, after_in_parent, after_in_child))
482        }
483    }
484
485    #[pyfunction]
486    fn register_at_fork(
487        args: RegisterAtForkArgs,
488        _ignored: KwArgs,
489        vm: &VirtualMachine,
490    ) -> PyResult<()> {
491        let (before, after_in_parent, after_in_child) = args.into_validated(vm)?;
492
493        if let Some(before) = before {
494            vm.state.before_forkers.lock().push(before);
495        }
496        if let Some(after_in_parent) = after_in_parent {
497            vm.state.after_forkers_parent.lock().push(after_in_parent);
498        }
499        if let Some(after_in_child) = after_in_child {
500            vm.state.after_forkers_child.lock().push(after_in_child);
501        }
502        Ok(())
503    }
504
505    fn run_at_forkers(mut funcs: Vec<PyObjectRef>, reversed: bool, vm: &VirtualMachine) {
506        if !funcs.is_empty() {
507            if reversed {
508                funcs.reverse();
509            }
510            for func in funcs.into_iter() {
511                if let Err(e) = func.call((), vm) {
512                    let exit = e.fast_isinstance(vm.ctx.exceptions.system_exit);
513                    vm.run_unraisable(e, Some("Exception ignored in".to_owned()), func);
514                    if exit {
515                        // Do nothing!
516                    }
517                }
518            }
519        }
520    }
521
522    fn py_os_before_fork(vm: &VirtualMachine) {
523        let before_forkers: Vec<PyObjectRef> = vm.state.before_forkers.lock().clone();
524        // functions must be executed in reversed order as they are registered
525        // only for before_forkers, refer: test_register_at_fork in test_posix
526
527        run_at_forkers(before_forkers, true, vm);
528    }
529
530    fn py_os_after_fork_child(vm: &VirtualMachine) {
531        let after_forkers_child: Vec<PyObjectRef> = vm.state.after_forkers_child.lock().clone();
532        run_at_forkers(after_forkers_child, false, vm);
533    }
534
535    fn py_os_after_fork_parent(vm: &VirtualMachine) {
536        let after_forkers_parent: Vec<PyObjectRef> = vm.state.after_forkers_parent.lock().clone();
537        run_at_forkers(after_forkers_parent, false, vm);
538    }
539
540    #[pyfunction]
541    fn fork(vm: &VirtualMachine) -> i32 {
542        let pid: i32;
543        py_os_before_fork(vm);
544        unsafe {
545            pid = libc::fork();
546        }
547        if pid == 0 {
548            py_os_after_fork_child(vm);
549        } else {
550            py_os_after_fork_parent(vm);
551        }
552        pid
553    }
554
555    #[cfg(not(target_os = "redox"))]
556    const MKNOD_DIR_FD: bool = cfg!(not(target_vendor = "apple"));
557
558    #[cfg(not(target_os = "redox"))]
559    #[derive(FromArgs)]
560    struct MknodArgs {
561        #[pyarg(any)]
562        path: OsPath,
563        #[pyarg(any)]
564        mode: libc::mode_t,
565        #[pyarg(any)]
566        device: libc::dev_t,
567        #[pyarg(flatten)]
568        dir_fd: DirFd<{ MKNOD_DIR_FD as usize }>,
569    }
570
571    #[cfg(not(target_os = "redox"))]
572    impl MknodArgs {
573        fn _mknod(self, vm: &VirtualMachine) -> PyResult<i32> {
574            Ok(unsafe {
575                libc::mknod(
576                    self.path.clone().into_cstring(vm)?.as_ptr(),
577                    self.mode,
578                    self.device,
579                )
580            })
581        }
582        #[cfg(not(target_vendor = "apple"))]
583        fn mknod(self, vm: &VirtualMachine) -> PyResult<()> {
584            let ret = match self.dir_fd.get_opt() {
585                None => self._mknod(vm)?,
586                Some(non_default_fd) => unsafe {
587                    libc::mknodat(
588                        non_default_fd,
589                        self.path.clone().into_cstring(vm)?.as_ptr(),
590                        self.mode,
591                        self.device,
592                    )
593                },
594            };
595            if ret != 0 {
596                Err(errno_err(vm))
597            } else {
598                Ok(())
599            }
600        }
601        #[cfg(target_vendor = "apple")]
602        fn mknod(self, vm: &VirtualMachine) -> PyResult<()> {
603            let [] = self.dir_fd.0;
604            let ret = self._mknod(vm)?;
605            if ret != 0 {
606                Err(errno_err(vm))
607            } else {
608                Ok(())
609            }
610        }
611    }
612
613    #[cfg(not(target_os = "redox"))]
614    #[pyfunction]
615    fn mknod(args: MknodArgs, vm: &VirtualMachine) -> PyResult<()> {
616        args.mknod(vm)
617    }
618
619    #[cfg(not(target_os = "redox"))]
620    #[pyfunction]
621    fn nice(increment: i32, vm: &VirtualMachine) -> PyResult<i32> {
622        use nix::errno::{errno, Errno};
623        Errno::clear();
624        let res = unsafe { libc::nice(increment) };
625        if res == -1 && errno() != 0 {
626            Err(errno_err(vm))
627        } else {
628            Ok(res)
629        }
630    }
631
632    #[cfg(not(target_os = "redox"))]
633    #[pyfunction]
634    fn sched_get_priority_max(policy: i32, vm: &VirtualMachine) -> PyResult<i32> {
635        let max = unsafe { libc::sched_get_priority_max(policy) };
636        if max == -1 {
637            Err(errno_err(vm))
638        } else {
639            Ok(max)
640        }
641    }
642
643    #[cfg(not(target_os = "redox"))]
644    #[pyfunction]
645    fn sched_get_priority_min(policy: i32, vm: &VirtualMachine) -> PyResult<i32> {
646        let min = unsafe { libc::sched_get_priority_min(policy) };
647        if min == -1 {
648            Err(errno_err(vm))
649        } else {
650            Ok(min)
651        }
652    }
653
654    #[pyfunction]
655    fn sched_yield(vm: &VirtualMachine) -> PyResult<()> {
656        nix::sched::sched_yield().map_err(|e| e.into_pyexception(vm))
657    }
658
659    #[pyattr]
660    #[pyclass(name = "sched_param")]
661    #[derive(Debug, PyPayload)]
662    struct SchedParam {
663        sched_priority: PyObjectRef,
664    }
665
666    impl TryFromObject for SchedParam {
667        fn try_from_object(_vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
668            Ok(SchedParam {
669                sched_priority: obj,
670            })
671        }
672    }
673
674    #[pyclass(with(Constructor, Representable))]
675    impl SchedParam {
676        #[pygetset]
677        fn sched_priority(&self, vm: &VirtualMachine) -> PyObjectRef {
678            self.sched_priority.clone().to_pyobject(vm)
679        }
680
681        #[cfg(any(
682            target_os = "linux",
683            target_os = "netbsd",
684            target_os = "freebsd",
685            target_os = "android"
686        ))]
687        #[cfg(not(target_env = "musl"))]
688        fn try_to_libc(&self, vm: &VirtualMachine) -> PyResult<libc::sched_param> {
689            use crate::AsObject;
690            let priority_class = self.sched_priority.class();
691            let priority_type = priority_class.name();
692            let priority = self.sched_priority.clone();
693            let value = priority.downcast::<PyInt>().map_err(|_| {
694                vm.new_type_error(format!("an integer is required (got type {priority_type})"))
695            })?;
696            let sched_priority = value.try_to_primitive(vm)?;
697            Ok(libc::sched_param { sched_priority })
698        }
699    }
700
701    #[derive(FromArgs)]
702    pub struct SchedParamArg {
703        sched_priority: PyObjectRef,
704    }
705    impl Constructor for SchedParam {
706        type Args = SchedParamArg;
707        fn py_new(cls: PyTypeRef, arg: Self::Args, vm: &VirtualMachine) -> PyResult {
708            SchedParam {
709                sched_priority: arg.sched_priority,
710            }
711            .into_ref_with_type(vm, cls)
712            .map(Into::into)
713        }
714    }
715
716    impl Representable for SchedParam {
717        #[inline]
718        fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
719            let sched_priority_repr = zelf.sched_priority.repr(vm)?;
720            Ok(format!(
721                "posix.sched_param(sched_priority = {})",
722                sched_priority_repr.as_str()
723            ))
724        }
725    }
726
727    #[cfg(any(
728        target_os = "linux",
729        target_os = "netbsd",
730        target_os = "freebsd",
731        target_os = "android"
732    ))]
733    #[pyfunction]
734    fn sched_getscheduler(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<i32> {
735        let policy = unsafe { libc::sched_getscheduler(pid) };
736        if policy == -1 {
737            Err(errno_err(vm))
738        } else {
739            Ok(policy)
740        }
741    }
742
743    #[cfg(any(
744        target_os = "linux",
745        target_os = "netbsd",
746        target_os = "freebsd",
747        target_os = "android"
748    ))]
749    #[derive(FromArgs)]
750    struct SchedSetschedulerArgs {
751        #[pyarg(positional)]
752        pid: i32,
753        #[pyarg(positional)]
754        policy: i32,
755        #[pyarg(positional)]
756        sched_param_obj: crate::PyRef<SchedParam>,
757    }
758
759    #[cfg(any(
760        target_os = "linux",
761        target_os = "netbsd",
762        target_os = "freebsd",
763        target_os = "android"
764    ))]
765    #[cfg(not(target_env = "musl"))]
766    #[pyfunction]
767    fn sched_setscheduler(args: SchedSetschedulerArgs, vm: &VirtualMachine) -> PyResult<i32> {
768        let libc_sched_param = args.sched_param_obj.try_to_libc(vm)?;
769        let policy = unsafe { libc::sched_setscheduler(args.pid, args.policy, &libc_sched_param) };
770        if policy == -1 {
771            Err(errno_err(vm))
772        } else {
773            Ok(policy)
774        }
775    }
776    #[cfg(any(
777        target_os = "linux",
778        target_os = "netbsd",
779        target_os = "freebsd",
780        target_os = "android"
781    ))]
782    #[pyfunction]
783    fn sched_getparam(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<SchedParam> {
784        let param = unsafe {
785            let mut param = std::mem::MaybeUninit::uninit();
786            if -1 == libc::sched_getparam(pid, param.as_mut_ptr()) {
787                return Err(errno_err(vm));
788            }
789            param.assume_init()
790        };
791        Ok(SchedParam {
792            sched_priority: param.sched_priority.to_pyobject(vm),
793        })
794    }
795
796    #[cfg(any(
797        target_os = "linux",
798        target_os = "netbsd",
799        target_os = "freebsd",
800        target_os = "android"
801    ))]
802    #[derive(FromArgs)]
803    struct SchedSetParamArgs {
804        #[pyarg(positional)]
805        pid: i32,
806        #[pyarg(positional)]
807        sched_param_obj: crate::PyRef<SchedParam>,
808    }
809
810    #[cfg(any(
811        target_os = "linux",
812        target_os = "netbsd",
813        target_os = "freebsd",
814        target_os = "android"
815    ))]
816    #[cfg(not(target_env = "musl"))]
817    #[pyfunction]
818    fn sched_setparam(args: SchedSetParamArgs, vm: &VirtualMachine) -> PyResult<i32> {
819        let libc_sched_param = args.sched_param_obj.try_to_libc(vm)?;
820        let ret = unsafe { libc::sched_setparam(args.pid, &libc_sched_param) };
821        if ret == -1 {
822            Err(errno_err(vm))
823        } else {
824            Ok(ret)
825        }
826    }
827
828    #[pyfunction]
829    fn get_inheritable(fd: RawFd, vm: &VirtualMachine) -> PyResult<bool> {
830        let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD);
831        match flags {
832            Ok(ret) => Ok((ret & libc::FD_CLOEXEC) == 0),
833            Err(err) => Err(err.into_pyexception(vm)),
834        }
835    }
836
837    #[pyfunction]
838    fn set_inheritable(fd: i32, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> {
839        super::raw_set_inheritable(fd, inheritable).map_err(|err| err.into_pyexception(vm))
840    }
841
842    #[pyfunction]
843    fn get_blocking(fd: RawFd, vm: &VirtualMachine) -> PyResult<bool> {
844        let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL);
845        match flags {
846            Ok(ret) => Ok((ret & libc::O_NONBLOCK) == 0),
847            Err(err) => Err(err.into_pyexception(vm)),
848        }
849    }
850
851    #[pyfunction]
852    fn set_blocking(fd: RawFd, blocking: bool, vm: &VirtualMachine) -> PyResult<()> {
853        let _set_flag = || {
854            use nix::fcntl::{fcntl, FcntlArg, OFlag};
855
856            let flags = OFlag::from_bits_truncate(fcntl(fd, FcntlArg::F_GETFL)?);
857            let mut new_flags = flags;
858            new_flags.set(OFlag::from_bits_truncate(libc::O_NONBLOCK), !blocking);
859            if flags != new_flags {
860                fcntl(fd, FcntlArg::F_SETFL(new_flags))?;
861            }
862            Ok(())
863        };
864        _set_flag().map_err(|err: nix::Error| err.into_pyexception(vm))
865    }
866
867    #[pyfunction]
868    fn pipe(vm: &VirtualMachine) -> PyResult<(RawFd, RawFd)> {
869        use nix::unistd::close;
870        use nix::unistd::pipe;
871        let (rfd, wfd) = pipe().map_err(|err| err.into_pyexception(vm))?;
872        set_inheritable(rfd, false, vm)
873            .and_then(|_| set_inheritable(wfd, false, vm))
874            .inspect_err(|_| {
875                let _ = close(rfd);
876                let _ = close(wfd);
877            })?;
878        Ok((rfd, wfd))
879    }
880
881    // cfg from nix
882    #[cfg(any(
883        target_os = "android",
884        target_os = "dragonfly",
885        target_os = "emscripten",
886        target_os = "freebsd",
887        target_os = "linux",
888        target_os = "netbsd",
889        target_os = "openbsd"
890    ))]
891    #[pyfunction]
892    fn pipe2(flags: libc::c_int, vm: &VirtualMachine) -> PyResult<(RawFd, RawFd)> {
893        let oflags = fcntl::OFlag::from_bits_truncate(flags);
894        nix::unistd::pipe2(oflags).map_err(|err| err.into_pyexception(vm))
895    }
896
897    #[pyfunction]
898    fn system(command: PyStrRef, vm: &VirtualMachine) -> PyResult<i32> {
899        let cstr = command.to_cstring(vm)?;
900        let x = unsafe { libc::system(cstr.as_ptr()) };
901        Ok(x)
902    }
903
904    fn _chmod(
905        path: OsPath,
906        dir_fd: DirFd<0>,
907        mode: u32,
908        follow_symlinks: FollowSymlinks,
909        vm: &VirtualMachine,
910    ) -> PyResult<()> {
911        let [] = dir_fd.0;
912        let err_path = path.clone();
913        let body = move || {
914            use std::os::unix::fs::PermissionsExt;
915            let meta = fs_metadata(&path, follow_symlinks.0)?;
916            let mut permissions = meta.permissions();
917            permissions.set_mode(mode);
918            fs::set_permissions(&path, permissions)
919        };
920        body().map_err(|err| IOErrorBuilder::with_filename(&err, err_path, vm))
921    }
922
923    #[cfg(not(target_os = "redox"))]
924    fn _fchmod(fd: RawFd, mode: u32, vm: &VirtualMachine) -> PyResult<()> {
925        nix::sys::stat::fchmod(
926            fd,
927            nix::sys::stat::Mode::from_bits(mode as libc::mode_t).unwrap(),
928        )
929        .map_err(|err| err.into_pyexception(vm))
930    }
931
932    #[cfg(not(target_os = "redox"))]
933    #[pyfunction]
934    fn chmod(
935        path: OsPathOrFd,
936        dir_fd: DirFd<0>,
937        mode: u32,
938        follow_symlinks: FollowSymlinks,
939        vm: &VirtualMachine,
940    ) -> PyResult<()> {
941        match path {
942            OsPathOrFd::Path(path) => {
943                #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd",))]
944                if !follow_symlinks.0 && dir_fd == Default::default() {
945                    return lchmod(path, mode, vm);
946                }
947                _chmod(path, dir_fd, mode, follow_symlinks, vm)
948            }
949            OsPathOrFd::Fd(fd) => _fchmod(fd, mode, vm),
950        }
951    }
952
953    #[cfg(target_os = "redox")]
954    #[pyfunction]
955    fn chmod(
956        path: OsPath,
957        dir_fd: DirFd<0>,
958        mode: u32,
959        follow_symlinks: FollowSymlinks,
960        vm: &VirtualMachine,
961    ) -> PyResult<()> {
962        _chmod(path, dir_fd, mode, follow_symlinks, vm)
963    }
964
965    #[cfg(not(target_os = "redox"))]
966    #[pyfunction]
967    fn fchmod(fd: RawFd, mode: u32, vm: &VirtualMachine) -> PyResult<()> {
968        _fchmod(fd, mode, vm)
969    }
970
971    #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd",))]
972    #[pyfunction]
973    fn lchmod(path: OsPath, mode: u32, vm: &VirtualMachine) -> PyResult<()> {
974        extern "C" {
975            fn lchmod(path: *const libc::c_char, mode: libc::mode_t) -> libc::c_int;
976        }
977        let c_path = path.clone().into_cstring(vm)?;
978        if unsafe { lchmod(c_path.as_ptr(), mode as libc::mode_t) } == 0 {
979            Ok(())
980        } else {
981            let err = std::io::Error::last_os_error();
982            Err(IOErrorBuilder::with_filename(&err, path, vm))
983        }
984    }
985
986    #[pyfunction]
987    fn execv(
988        path: OsPath,
989        argv: Either<PyListRef, PyTupleRef>,
990        vm: &VirtualMachine,
991    ) -> PyResult<()> {
992        let path = path.into_cstring(vm)?;
993
994        let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
995            PyStrRef::try_from_object(vm, obj)?.to_cstring(vm)
996        })?;
997        let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect();
998
999        let first = argv
1000            .first()
1001            .ok_or_else(|| vm.new_value_error("execv() arg 2 must not be empty".to_owned()))?;
1002        if first.to_bytes().is_empty() {
1003            return Err(
1004                vm.new_value_error("execv() arg 2 first element cannot be empty".to_owned())
1005            );
1006        }
1007
1008        unistd::execv(&path, &argv)
1009            .map(|_ok| ())
1010            .map_err(|err| err.into_pyexception(vm))
1011    }
1012
1013    #[pyfunction]
1014    fn execve(
1015        path: OsPath,
1016        argv: Either<PyListRef, PyTupleRef>,
1017        env: PyDictRef,
1018        vm: &VirtualMachine,
1019    ) -> PyResult<()> {
1020        let path = path.into_cstring(vm)?;
1021
1022        let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
1023            PyStrRef::try_from_object(vm, obj)?.to_cstring(vm)
1024        })?;
1025        let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect();
1026
1027        let first = argv
1028            .first()
1029            .ok_or_else(|| vm.new_value_error("execve() arg 2 must not be empty".to_owned()))?;
1030
1031        if first.to_bytes().is_empty() {
1032            return Err(
1033                vm.new_value_error("execve() arg 2 first element cannot be empty".to_owned())
1034            );
1035        }
1036
1037        let env = env
1038            .into_iter()
1039            .map(|(k, v)| -> PyResult<_> {
1040                let (key, value) = (
1041                    OsPath::try_from_object(vm, k)?.into_bytes(),
1042                    OsPath::try_from_object(vm, v)?.into_bytes(),
1043                );
1044
1045                if memchr::memchr(b'=', &key).is_some() {
1046                    return Err(vm.new_value_error("illegal environment variable name".to_owned()));
1047                }
1048
1049                let mut entry = key;
1050                entry.push(b'=');
1051                entry.extend_from_slice(&value);
1052
1053                CString::new(entry).map_err(|err| err.into_pyexception(vm))
1054            })
1055            .collect::<Result<Vec<_>, _>>()?;
1056
1057        let env: Vec<&CStr> = env.iter().map(|entry| entry.as_c_str()).collect();
1058
1059        unistd::execve(&path, &argv, &env).map_err(|err| err.into_pyexception(vm))?;
1060        Ok(())
1061    }
1062
1063    #[pyfunction]
1064    fn getppid(vm: &VirtualMachine) -> PyObjectRef {
1065        let ppid = unistd::getppid().as_raw();
1066        vm.ctx.new_int(ppid).into()
1067    }
1068
1069    #[pyfunction]
1070    fn getgid(vm: &VirtualMachine) -> PyObjectRef {
1071        let gid = unistd::getgid().as_raw();
1072        vm.ctx.new_int(gid).into()
1073    }
1074
1075    #[pyfunction]
1076    fn getegid(vm: &VirtualMachine) -> PyObjectRef {
1077        let egid = unistd::getegid().as_raw();
1078        vm.ctx.new_int(egid).into()
1079    }
1080
1081    #[pyfunction]
1082    fn getpgid(pid: u32, vm: &VirtualMachine) -> PyResult {
1083        let pgid =
1084            unistd::getpgid(Some(Pid::from_raw(pid as i32))).map_err(|e| e.into_pyexception(vm))?;
1085        Ok(vm.new_pyobj(pgid.as_raw()))
1086    }
1087
1088    #[pyfunction]
1089    fn getpgrp(vm: &VirtualMachine) -> PyObjectRef {
1090        vm.ctx.new_int(unistd::getpgrp().as_raw()).into()
1091    }
1092
1093    #[cfg(not(target_os = "redox"))]
1094    #[pyfunction]
1095    fn getsid(pid: u32, vm: &VirtualMachine) -> PyResult {
1096        let sid =
1097            unistd::getsid(Some(Pid::from_raw(pid as i32))).map_err(|e| e.into_pyexception(vm))?;
1098        Ok(vm.new_pyobj(sid.as_raw()))
1099    }
1100
1101    #[pyfunction]
1102    fn getuid(vm: &VirtualMachine) -> PyObjectRef {
1103        let uid = unistd::getuid().as_raw();
1104        vm.ctx.new_int(uid).into()
1105    }
1106
1107    #[pyfunction]
1108    fn geteuid(vm: &VirtualMachine) -> PyObjectRef {
1109        let euid = unistd::geteuid().as_raw();
1110        vm.ctx.new_int(euid).into()
1111    }
1112
1113    #[pyfunction]
1114    fn setgid(gid: Option<Gid>, vm: &VirtualMachine) -> PyResult<()> {
1115        let gid =
1116            gid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?;
1117        unistd::setgid(gid).map_err(|err| err.into_pyexception(vm))
1118    }
1119
1120    #[cfg(not(target_os = "redox"))]
1121    #[pyfunction]
1122    fn setegid(egid: Option<Gid>, vm: &VirtualMachine) -> PyResult<()> {
1123        let egid =
1124            egid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?;
1125        unistd::setegid(egid).map_err(|err| err.into_pyexception(vm))
1126    }
1127
1128    #[pyfunction]
1129    fn setpgid(pid: u32, pgid: u32, vm: &VirtualMachine) -> PyResult<()> {
1130        unistd::setpgid(Pid::from_raw(pid as i32), Pid::from_raw(pgid as i32))
1131            .map_err(|err| err.into_pyexception(vm))
1132    }
1133
1134    #[cfg(not(target_os = "redox"))]
1135    #[pyfunction]
1136    fn setsid(vm: &VirtualMachine) -> PyResult<()> {
1137        unistd::setsid()
1138            .map(|_ok| ())
1139            .map_err(|err| err.into_pyexception(vm))
1140    }
1141
1142    fn try_from_id(vm: &VirtualMachine, obj: PyObjectRef, typ_name: &str) -> PyResult<Option<u32>> {
1143        use std::cmp::Ordering;
1144        let i = obj
1145            .try_to_ref::<PyInt>(vm)
1146            .map_err(|_| {
1147                vm.new_type_error(format!(
1148                    "an integer is required (got type {})",
1149                    obj.class().name()
1150                ))
1151            })?
1152            .try_to_primitive::<i64>(vm)?;
1153
1154        match i.cmp(&-1) {
1155            Ordering::Greater => Ok(Some(i.try_into().map_err(|_| {
1156                vm.new_overflow_error(format!("{typ_name} is larger than maximum"))
1157            })?)),
1158            Ordering::Less => {
1159                Err(vm.new_overflow_error(format!("{typ_name} is less than minimum")))
1160            }
1161            Ordering::Equal => Ok(None), // -1 means does not change the value
1162        }
1163    }
1164
1165    impl TryFromObject for Option<Uid> {
1166        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
1167            Ok(try_from_id(vm, obj, "uid")?.map(Uid::from_raw))
1168        }
1169    }
1170
1171    impl TryFromObject for Option<Gid> {
1172        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
1173            Ok(try_from_id(vm, obj, "gid")?.map(Gid::from_raw))
1174        }
1175    }
1176
1177    #[pyfunction]
1178    fn setuid(uid: Option<Uid>, vm: &VirtualMachine) -> PyResult<()> {
1179        let uid =
1180            uid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?;
1181        unistd::setuid(uid).map_err(|err| err.into_pyexception(vm))
1182    }
1183
1184    #[cfg(not(target_os = "redox"))]
1185    #[pyfunction]
1186    fn seteuid(euid: Option<Uid>, vm: &VirtualMachine) -> PyResult<()> {
1187        let euid =
1188            euid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?;
1189        unistd::seteuid(euid).map_err(|err| err.into_pyexception(vm))
1190    }
1191
1192    #[cfg(not(target_os = "redox"))]
1193    #[pyfunction]
1194    fn setreuid(ruid: Option<Uid>, euid: Option<Uid>, vm: &VirtualMachine) -> PyResult<()> {
1195        if let Some(ruid) = ruid {
1196            unistd::setuid(ruid).map_err(|err| err.into_pyexception(vm))?;
1197        }
1198        if let Some(euid) = euid {
1199            unistd::seteuid(euid).map_err(|err| err.into_pyexception(vm))?;
1200        }
1201        Ok(())
1202    }
1203
1204    // cfg from nix
1205    #[cfg(any(
1206        target_os = "android",
1207        target_os = "freebsd",
1208        target_os = "linux",
1209        target_os = "openbsd"
1210    ))]
1211    #[pyfunction]
1212    fn setresuid(
1213        ruid: Option<Uid>,
1214        euid: Option<Uid>,
1215        suid: Option<Uid>,
1216        vm: &VirtualMachine,
1217    ) -> PyResult<()> {
1218        let unwrap_or_unchanged =
1219            |u: Option<Uid>| u.unwrap_or_else(|| Uid::from_raw(libc::uid_t::MAX));
1220        unistd::setresuid(
1221            unwrap_or_unchanged(ruid),
1222            unwrap_or_unchanged(euid),
1223            unwrap_or_unchanged(suid),
1224        )
1225        .map_err(|err| err.into_pyexception(vm))
1226    }
1227
1228    #[cfg(not(target_os = "redox"))]
1229    #[pyfunction]
1230    fn openpty(vm: &VirtualMachine) -> PyResult<(OwnedFd, OwnedFd)> {
1231        let r = nix::pty::openpty(None, None).map_err(|err| err.into_pyexception(vm))?;
1232        for fd in [&r.master, &r.slave] {
1233            super::raw_set_inheritable(fd.as_raw_fd(), false)
1234                .map_err(|e| e.into_pyexception(vm))?;
1235        }
1236        Ok((r.master, r.slave))
1237    }
1238
1239    #[pyfunction]
1240    fn ttyname(fd: i32, vm: &VirtualMachine) -> PyResult {
1241        let name = unistd::ttyname(fd).map_err(|e| e.into_pyexception(vm))?;
1242        let name = name.into_os_string().into_string().unwrap();
1243        Ok(vm.ctx.new_str(name).into())
1244    }
1245
1246    #[pyfunction]
1247    fn umask(mask: libc::mode_t) -> libc::mode_t {
1248        unsafe { libc::umask(mask) }
1249    }
1250
1251    #[pyfunction]
1252    fn uname(vm: &VirtualMachine) -> PyResult<_os::UnameResult> {
1253        let info = uname::uname().map_err(|err| err.into_pyexception(vm))?;
1254        Ok(_os::UnameResult {
1255            sysname: info.sysname,
1256            nodename: info.nodename,
1257            release: info.release,
1258            version: info.version,
1259            machine: info.machine,
1260        })
1261    }
1262
1263    #[pyfunction]
1264    fn sync() {
1265        #[cfg(not(any(target_os = "redox", target_os = "android")))]
1266        unsafe {
1267            libc::sync();
1268        }
1269    }
1270
1271    // cfg from nix
1272    #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))]
1273    #[pyfunction]
1274    fn getresuid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> {
1275        let ret = unistd::getresuid().map_err(|e| e.into_pyexception(vm))?;
1276        Ok((
1277            ret.real.as_raw(),
1278            ret.effective.as_raw(),
1279            ret.saved.as_raw(),
1280        ))
1281    }
1282
1283    // cfg from nix
1284    #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))]
1285    #[pyfunction]
1286    fn getresgid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> {
1287        let ret = unistd::getresgid().map_err(|e| e.into_pyexception(vm))?;
1288        Ok((
1289            ret.real.as_raw(),
1290            ret.effective.as_raw(),
1291            ret.saved.as_raw(),
1292        ))
1293    }
1294
1295    // cfg from nix
1296    #[cfg(any(
1297        target_os = "android",
1298        target_os = "freebsd",
1299        target_os = "linux",
1300        target_os = "openbsd"
1301    ))]
1302    #[pyfunction]
1303    fn setresgid(
1304        rgid: Option<Gid>,
1305        egid: Option<Gid>,
1306        sgid: Option<Gid>,
1307        vm: &VirtualMachine,
1308    ) -> PyResult<()> {
1309        let unwrap_or_unchanged =
1310            |u: Option<Gid>| u.unwrap_or_else(|| Gid::from_raw(libc::gid_t::MAX));
1311        unistd::setresgid(
1312            unwrap_or_unchanged(rgid),
1313            unwrap_or_unchanged(egid),
1314            unwrap_or_unchanged(sgid),
1315        )
1316        .map_err(|err| err.into_pyexception(vm))
1317    }
1318
1319    #[cfg(not(target_os = "redox"))]
1320    #[pyfunction]
1321    fn setregid(rgid: Option<Gid>, egid: Option<Gid>, vm: &VirtualMachine) -> PyResult<()> {
1322        if let Some(rgid) = rgid {
1323            unistd::setgid(rgid).map_err(|err| err.into_pyexception(vm))?;
1324        }
1325        if let Some(egid) = egid {
1326            unistd::setegid(egid).map_err(|err| err.into_pyexception(vm))?;
1327        }
1328        Ok(())
1329    }
1330
1331    // cfg from nix
1332    #[cfg(any(
1333        target_os = "android",
1334        target_os = "freebsd",
1335        target_os = "linux",
1336        target_os = "openbsd"
1337    ))]
1338    #[pyfunction]
1339    fn initgroups(user_name: PyStrRef, gid: Option<Gid>, vm: &VirtualMachine) -> PyResult<()> {
1340        let user = user_name.to_cstring(vm)?;
1341        let gid =
1342            gid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?;
1343        unistd::initgroups(&user, gid).map_err(|err| err.into_pyexception(vm))
1344    }
1345
1346    // cfg from nix
1347    #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
1348    #[pyfunction]
1349    fn setgroups(
1350        group_ids: crate::function::ArgIterable<Option<Gid>>,
1351        vm: &VirtualMachine,
1352    ) -> PyResult<()> {
1353        let gids = group_ids
1354            .iter(vm)?
1355            .collect::<Result<Option<Vec<_>>, _>>()?
1356            .ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?;
1357        let ret = unistd::setgroups(&gids);
1358        ret.map_err(|err| err.into_pyexception(vm))
1359    }
1360
1361    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1362    fn envp_from_dict(
1363        env: crate::function::ArgMapping,
1364        vm: &VirtualMachine,
1365    ) -> PyResult<Vec<CString>> {
1366        let keys = env.mapping().keys(vm)?;
1367        let values = env.mapping().values(vm)?;
1368
1369        let keys = PyListRef::try_from_object(vm, keys)
1370            .map_err(|_| vm.new_type_error("env.keys() is not a list".to_owned()))?
1371            .borrow_vec()
1372            .to_vec();
1373        let values = PyListRef::try_from_object(vm, values)
1374            .map_err(|_| vm.new_type_error("env.values() is not a list".to_owned()))?
1375            .borrow_vec()
1376            .to_vec();
1377
1378        keys.into_iter()
1379            .zip(values)
1380            .map(|(k, v)| {
1381                let k = OsPath::try_from_object(vm, k)?.into_bytes();
1382                let v = OsPath::try_from_object(vm, v)?.into_bytes();
1383                if k.contains(&0) {
1384                    return Err(
1385                        vm.new_value_error("envp dict key cannot contain a nul byte".to_owned())
1386                    );
1387                }
1388                if k.contains(&b'=') {
1389                    return Err(vm.new_value_error(
1390                        "envp dict key cannot contain a '=' character".to_owned(),
1391                    ));
1392                }
1393                if v.contains(&0) {
1394                    return Err(
1395                        vm.new_value_error("envp dict value cannot contain a nul byte".to_owned())
1396                    );
1397                }
1398                let mut env = k;
1399                env.push(b'=');
1400                env.extend(v);
1401                Ok(unsafe { CString::from_vec_unchecked(env) })
1402            })
1403            .collect()
1404    }
1405
1406    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1407    #[derive(FromArgs)]
1408    pub(super) struct PosixSpawnArgs {
1409        #[pyarg(positional)]
1410        path: OsPath,
1411        #[pyarg(positional)]
1412        args: crate::function::ArgIterable<OsPath>,
1413        #[pyarg(positional)]
1414        env: crate::function::ArgMapping,
1415        #[pyarg(named, default)]
1416        file_actions: Option<crate::function::ArgIterable<PyTupleRef>>,
1417        #[pyarg(named, default)]
1418        setsigdef: Option<crate::function::ArgIterable<i32>>,
1419    }
1420
1421    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1422    #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
1423    #[repr(i32)]
1424    enum PosixSpawnFileActionIdentifier {
1425        Open,
1426        Close,
1427        Dup2,
1428    }
1429
1430    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1431    impl PosixSpawnArgs {
1432        fn spawn(self, spawnp: bool, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
1433            use crate::TryFromBorrowedObject;
1434
1435            let path = self
1436                .path
1437                .clone()
1438                .into_cstring(vm)
1439                .map_err(|_| vm.new_value_error("path should not have nul bytes".to_owned()))?;
1440
1441            let mut file_actions = unsafe {
1442                let mut fa = std::mem::MaybeUninit::uninit();
1443                assert!(libc::posix_spawn_file_actions_init(fa.as_mut_ptr()) == 0);
1444                fa.assume_init()
1445            };
1446            if let Some(it) = self.file_actions {
1447                for action in it.iter(vm)? {
1448                    let action = action?;
1449                    let (id, args) = action.split_first().ok_or_else(|| {
1450                        vm.new_type_error(
1451                            "Each file_actions element must be a non-empty tuple".to_owned(),
1452                        )
1453                    })?;
1454                    let id = i32::try_from_borrowed_object(vm, id)?;
1455                    let id = PosixSpawnFileActionIdentifier::try_from(id).map_err(|_| {
1456                        vm.new_type_error("Unknown file_actions identifier".to_owned())
1457                    })?;
1458                    let args: crate::function::FuncArgs = args.to_vec().into();
1459                    let ret = match id {
1460                        PosixSpawnFileActionIdentifier::Open => {
1461                            let (fd, path, oflag, mode): (_, OsPath, _, _) = args.bind(vm)?;
1462                            let path = CString::new(path.into_bytes()).map_err(|_| {
1463                                vm.new_value_error(
1464                                    "POSIX_SPAWN_OPEN path should not have nul bytes".to_owned(),
1465                                )
1466                            })?;
1467                            unsafe {
1468                                libc::posix_spawn_file_actions_addopen(
1469                                    &mut file_actions,
1470                                    fd,
1471                                    path.as_ptr(),
1472                                    oflag,
1473                                    mode,
1474                                )
1475                            }
1476                        }
1477                        PosixSpawnFileActionIdentifier::Close => {
1478                            let (fd,) = args.bind(vm)?;
1479                            unsafe {
1480                                libc::posix_spawn_file_actions_addclose(&mut file_actions, fd)
1481                            }
1482                        }
1483                        PosixSpawnFileActionIdentifier::Dup2 => {
1484                            let (fd, newfd) = args.bind(vm)?;
1485                            unsafe {
1486                                libc::posix_spawn_file_actions_adddup2(&mut file_actions, fd, newfd)
1487                            }
1488                        }
1489                    };
1490                    if ret != 0 {
1491                        let err = std::io::Error::from_raw_os_error(ret);
1492                        return Err(IOErrorBuilder::with_filename(&err, self.path, vm));
1493                    }
1494                }
1495            }
1496
1497            let mut attrp = unsafe {
1498                let mut sa = std::mem::MaybeUninit::uninit();
1499                assert!(libc::posix_spawnattr_init(sa.as_mut_ptr()) == 0);
1500                sa.assume_init()
1501            };
1502            if let Some(sigs) = self.setsigdef {
1503                use nix::sys::signal;
1504                let mut set = signal::SigSet::empty();
1505                for sig in sigs.iter(vm)? {
1506                    let sig = sig?;
1507                    let sig = signal::Signal::try_from(sig).map_err(|_| {
1508                        vm.new_value_error(format!("signal number {sig} out of range"))
1509                    })?;
1510                    set.add(sig);
1511                }
1512                assert!(
1513                    unsafe { libc::posix_spawnattr_setsigdefault(&mut attrp, set.as_ref()) } == 0
1514                );
1515            }
1516
1517            let mut args: Vec<CString> = self
1518                .args
1519                .iter(vm)?
1520                .map(|res| {
1521                    CString::new(res?.into_bytes()).map_err(|_| {
1522                        vm.new_value_error("path should not have nul bytes".to_owned())
1523                    })
1524                })
1525                .collect::<Result<_, _>>()?;
1526            let argv: Vec<*mut libc::c_char> = args
1527                .iter_mut()
1528                .map(|s| s.as_ptr() as _)
1529                .chain(std::iter::once(std::ptr::null_mut()))
1530                .collect();
1531            let mut env = envp_from_dict(self.env, vm)?;
1532            let envp: Vec<*mut libc::c_char> = env
1533                .iter_mut()
1534                .map(|s| s.as_ptr() as _)
1535                .chain(std::iter::once(std::ptr::null_mut()))
1536                .collect();
1537
1538            let mut pid = 0;
1539            let ret = unsafe {
1540                if spawnp {
1541                    libc::posix_spawnp(
1542                        &mut pid,
1543                        path.as_ptr(),
1544                        &file_actions,
1545                        &attrp,
1546                        argv.as_ptr(),
1547                        envp.as_ptr(),
1548                    )
1549                } else {
1550                    libc::posix_spawn(
1551                        &mut pid,
1552                        path.as_ptr(),
1553                        &file_actions,
1554                        &attrp,
1555                        argv.as_ptr(),
1556                        envp.as_ptr(),
1557                    )
1558                }
1559            };
1560
1561            if ret == 0 {
1562                Ok(pid)
1563            } else {
1564                let err = std::io::Error::from_raw_os_error(ret);
1565                Err(IOErrorBuilder::with_filename(&err, self.path, vm))
1566            }
1567        }
1568    }
1569
1570    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1571    #[pyfunction]
1572    fn posix_spawn(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
1573        args.spawn(false, vm)
1574    }
1575    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1576    #[pyfunction]
1577    fn posix_spawnp(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
1578        args.spawn(true, vm)
1579    }
1580
1581    #[pyfunction(name = "WIFSIGNALED")]
1582    fn wifsignaled(status: i32) -> bool {
1583        libc::WIFSIGNALED(status)
1584    }
1585    #[pyfunction(name = "WIFSTOPPED")]
1586    fn wifstopped(status: i32) -> bool {
1587        libc::WIFSTOPPED(status)
1588    }
1589    #[pyfunction(name = "WIFEXITED")]
1590    fn wifexited(status: i32) -> bool {
1591        libc::WIFEXITED(status)
1592    }
1593    #[pyfunction(name = "WTERMSIG")]
1594    fn wtermsig(status: i32) -> i32 {
1595        libc::WTERMSIG(status)
1596    }
1597    #[pyfunction(name = "WSTOPSIG")]
1598    fn wstopsig(status: i32) -> i32 {
1599        libc::WSTOPSIG(status)
1600    }
1601    #[pyfunction(name = "WEXITSTATUS")]
1602    fn wexitstatus(status: i32) -> i32 {
1603        libc::WEXITSTATUS(status)
1604    }
1605
1606    #[pyfunction]
1607    fn waitpid(pid: libc::pid_t, opt: i32, vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> {
1608        let mut status = 0;
1609        let pid = unsafe { libc::waitpid(pid, &mut status, opt) };
1610        let pid = nix::Error::result(pid).map_err(|err| err.into_pyexception(vm))?;
1611        Ok((pid, status))
1612    }
1613    #[pyfunction]
1614    fn wait(vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> {
1615        waitpid(-1, 0, vm)
1616    }
1617
1618    #[pyfunction]
1619    fn kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> {
1620        {
1621            let ret = unsafe { libc::kill(pid, sig as i32) };
1622            if ret == -1 {
1623                Err(errno_err(vm))
1624            } else {
1625                Ok(())
1626            }
1627        }
1628    }
1629
1630    #[pyfunction]
1631    fn get_terminal_size(
1632        fd: OptionalArg<i32>,
1633        vm: &VirtualMachine,
1634    ) -> PyResult<_os::PyTerminalSize> {
1635        let (columns, lines) = {
1636            nix::ioctl_read_bad!(winsz, libc::TIOCGWINSZ, libc::winsize);
1637            let mut w = libc::winsize {
1638                ws_row: 0,
1639                ws_col: 0,
1640                ws_xpixel: 0,
1641                ws_ypixel: 0,
1642            };
1643            unsafe { winsz(fd.unwrap_or(libc::STDOUT_FILENO), &mut w) }
1644                .map_err(|err| err.into_pyexception(vm))?;
1645            (w.ws_col.into(), w.ws_row.into())
1646        };
1647        Ok(_os::PyTerminalSize { columns, lines })
1648    }
1649
1650    // from libstd:
1651    // https://github.com/rust-lang/rust/blob/daecab3a784f28082df90cebb204998051f3557d/src/libstd/sys/unix/fs.rs#L1251
1652    #[cfg(target_os = "macos")]
1653    extern "C" {
1654        fn fcopyfile(
1655            in_fd: libc::c_int,
1656            out_fd: libc::c_int,
1657            state: *mut libc::c_void, // copyfile_state_t (unused)
1658            flags: u32,               // copyfile_flags_t
1659        ) -> libc::c_int;
1660    }
1661
1662    #[cfg(target_os = "macos")]
1663    #[pyfunction]
1664    fn _fcopyfile(in_fd: i32, out_fd: i32, flags: i32, vm: &VirtualMachine) -> PyResult<()> {
1665        let ret = unsafe { fcopyfile(in_fd, out_fd, std::ptr::null_mut(), flags as u32) };
1666        if ret < 0 {
1667            Err(errno_err(vm))
1668        } else {
1669            Ok(())
1670        }
1671    }
1672
1673    #[pyfunction]
1674    fn dup(fd: i32, vm: &VirtualMachine) -> PyResult<i32> {
1675        let fd = nix::unistd::dup(fd).map_err(|e| e.into_pyexception(vm))?;
1676        super::raw_set_inheritable(fd, false)
1677            .map(|()| fd)
1678            .map_err(|e| {
1679                let _ = nix::unistd::close(fd);
1680                e.into_pyexception(vm)
1681            })
1682    }
1683
1684    #[derive(FromArgs)]
1685    struct Dup2Args {
1686        #[pyarg(positional)]
1687        fd: i32,
1688        #[pyarg(positional)]
1689        fd2: i32,
1690        #[pyarg(any, default = "true")]
1691        inheritable: bool,
1692    }
1693
1694    #[pyfunction]
1695    fn dup2(args: Dup2Args, vm: &VirtualMachine) -> PyResult<i32> {
1696        let fd = nix::unistd::dup2(args.fd, args.fd2).map_err(|e| e.into_pyexception(vm))?;
1697        if !args.inheritable {
1698            super::raw_set_inheritable(fd, false).map_err(|e| {
1699                let _ = nix::unistd::close(fd);
1700                e.into_pyexception(vm)
1701            })?
1702        }
1703        Ok(fd)
1704    }
1705
1706    pub(crate) fn support_funcs() -> Vec<SupportFunc> {
1707        vec![
1708            SupportFunc::new(
1709                "chmod",
1710                Some(false),
1711                Some(false),
1712                Some(cfg!(any(
1713                    target_os = "macos",
1714                    target_os = "freebsd",
1715                    target_os = "netbsd"
1716                ))),
1717            ),
1718            #[cfg(not(target_os = "redox"))]
1719            SupportFunc::new("chroot", Some(false), None, None),
1720            #[cfg(not(target_os = "redox"))]
1721            SupportFunc::new("chown", Some(true), Some(true), Some(true)),
1722            #[cfg(not(target_os = "redox"))]
1723            SupportFunc::new("lchown", None, None, None),
1724            #[cfg(not(target_os = "redox"))]
1725            SupportFunc::new("fchown", Some(true), None, Some(true)),
1726            #[cfg(not(target_os = "redox"))]
1727            SupportFunc::new("mknod", Some(true), Some(MKNOD_DIR_FD), Some(false)),
1728            SupportFunc::new("umask", Some(false), Some(false), Some(false)),
1729            SupportFunc::new("execv", None, None, None),
1730            SupportFunc::new("pathconf", Some(true), None, None),
1731        ]
1732    }
1733
1734    #[pyfunction]
1735    fn getlogin(vm: &VirtualMachine) -> PyResult<String> {
1736        // Get a pointer to the login name string. The string is statically
1737        // allocated and might be overwritten on subsequent calls to this
1738        // function or to `cuserid()`. See man getlogin(3) for more information.
1739        let ptr = unsafe { libc::getlogin() };
1740        if ptr.is_null() {
1741            return Err(vm.new_os_error("unable to determine login name".to_owned()));
1742        }
1743        let slice = unsafe { CStr::from_ptr(ptr) };
1744        slice
1745            .to_str()
1746            .map(|s| s.to_owned())
1747            .map_err(|e| vm.new_unicode_decode_error(format!("unable to decode login name: {e}")))
1748    }
1749
1750    // cfg from nix
1751    #[cfg(any(
1752        target_os = "android",
1753        target_os = "freebsd",
1754        target_os = "linux",
1755        target_os = "openbsd"
1756    ))]
1757    #[pyfunction]
1758    fn getgrouplist(user: PyStrRef, group: u32, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
1759        let user = CString::new(user.as_str()).unwrap();
1760        let gid = Gid::from_raw(group);
1761        let group_ids = unistd::getgrouplist(&user, gid).map_err(|err| err.into_pyexception(vm))?;
1762        Ok(group_ids
1763            .into_iter()
1764            .map(|gid| vm.new_pyobj(gid.as_raw()))
1765            .collect())
1766    }
1767
1768    #[cfg(not(target_os = "redox"))]
1769    cfg_if::cfg_if! {
1770        if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
1771            type PriorityWhichType = libc::__priority_which_t;
1772        } else {
1773            type PriorityWhichType = libc::c_int;
1774        }
1775    }
1776    #[cfg(not(target_os = "redox"))]
1777    cfg_if::cfg_if! {
1778        if #[cfg(target_os = "freebsd")] {
1779            type PriorityWhoType = i32;
1780        } else {
1781            type PriorityWhoType = u32;
1782        }
1783    }
1784
1785    #[cfg(not(target_os = "redox"))]
1786    #[pyfunction]
1787    fn getpriority(
1788        which: PriorityWhichType,
1789        who: PriorityWhoType,
1790        vm: &VirtualMachine,
1791    ) -> PyResult {
1792        use nix::errno::{errno, Errno};
1793        Errno::clear();
1794        let retval = unsafe { libc::getpriority(which, who) };
1795        if errno() != 0 {
1796            Err(errno_err(vm))
1797        } else {
1798            Ok(vm.ctx.new_int(retval).into())
1799        }
1800    }
1801
1802    #[cfg(not(target_os = "redox"))]
1803    #[pyfunction]
1804    fn setpriority(
1805        which: PriorityWhichType,
1806        who: PriorityWhoType,
1807        priority: i32,
1808        vm: &VirtualMachine,
1809    ) -> PyResult<()> {
1810        let retval = unsafe { libc::setpriority(which, who, priority) };
1811        if retval == -1 {
1812            Err(errno_err(vm))
1813        } else {
1814            Ok(())
1815        }
1816    }
1817
1818    struct PathconfName(i32);
1819
1820    impl TryFromObject for PathconfName {
1821        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
1822            let i = match obj.downcast::<PyInt>() {
1823                Ok(int) => int.try_to_primitive(vm)?,
1824                Err(obj) => {
1825                    let s = PyStrRef::try_from_object(vm, obj)?;
1826                    s.as_str().parse::<PathconfVar>().map_err(|_| {
1827                        vm.new_value_error("unrecognized configuration name".to_string())
1828                    })? as i32
1829                }
1830            };
1831            Ok(Self(i))
1832        }
1833    }
1834
1835    // Copy from [nix::unistd::PathconfVar](https://docs.rs/nix/0.21.0/nix/unistd/enum.PathconfVar.html)
1836    // Change enum name to fit python doc
1837    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumIter, EnumString)]
1838    #[repr(i32)]
1839    #[allow(non_camel_case_types)]
1840    pub enum PathconfVar {
1841        #[cfg(any(
1842            target_os = "dragonfly",
1843            target_os = "freebsd",
1844            target_os = "linux",
1845            target_os = "netbsd",
1846            target_os = "openbsd",
1847            target_os = "redox"
1848        ))]
1849        /// Minimum number of bits needed to represent, as a signed integer value,
1850        /// the maximum size of a regular file allowed in the specified directory.
1851        PC_FILESIZEBITS = libc::_PC_FILESIZEBITS,
1852        /// Maximum number of links to a single file.
1853        PC_LINK_MAX = libc::_PC_LINK_MAX,
1854        /// Maximum number of bytes in a terminal canonical input line.
1855        PC_MAX_CANON = libc::_PC_MAX_CANON,
1856        /// Minimum number of bytes for which space is available in a terminal input
1857        /// queue; therefore, the maximum number of bytes a conforming application
1858        /// may require to be typed as input before reading them.
1859        PC_MAX_INPUT = libc::_PC_MAX_INPUT,
1860        /// Maximum number of bytes in a filename (not including the terminating
1861        /// null of a filename string).
1862        PC_NAME_MAX = libc::_PC_NAME_MAX,
1863        /// Maximum number of bytes the implementation will store as a pathname in a
1864        /// user-supplied buffer of unspecified size, including the terminating null
1865        /// character. Minimum number the implementation will accept as the maximum
1866        /// number of bytes in a pathname.
1867        PC_PATH_MAX = libc::_PC_PATH_MAX,
1868        /// Maximum number of bytes that is guaranteed to be atomic when writing to
1869        /// a pipe.
1870        PC_PIPE_BUF = libc::_PC_PIPE_BUF,
1871        #[cfg(any(
1872            target_os = "android",
1873            target_os = "dragonfly",
1874            target_os = "illumos",
1875            target_os = "linux",
1876            target_os = "netbsd",
1877            target_os = "openbsd",
1878            target_os = "redox",
1879            target_os = "solaris"
1880        ))]
1881        /// Symbolic links can be created.
1882        PC_2_SYMLINKS = libc::_PC_2_SYMLINKS,
1883        #[cfg(any(
1884            target_os = "android",
1885            target_os = "dragonfly",
1886            target_os = "freebsd",
1887            target_os = "linux",
1888            target_os = "openbsd",
1889            target_os = "redox"
1890        ))]
1891        /// Minimum number of bytes of storage actually allocated for any portion of
1892        /// a file.
1893        PC_ALLOC_SIZE_MIN = libc::_PC_ALLOC_SIZE_MIN,
1894        #[cfg(any(
1895            target_os = "android",
1896            target_os = "dragonfly",
1897            target_os = "freebsd",
1898            target_os = "linux",
1899            target_os = "openbsd"
1900        ))]
1901        /// Recommended increment for file transfer sizes between the
1902        /// `POSIX_REC_MIN_XFER_SIZE` and `POSIX_REC_MAX_XFER_SIZE` values.
1903        PC_REC_INCR_XFER_SIZE = libc::_PC_REC_INCR_XFER_SIZE,
1904        #[cfg(any(
1905            target_os = "android",
1906            target_os = "dragonfly",
1907            target_os = "freebsd",
1908            target_os = "linux",
1909            target_os = "openbsd",
1910            target_os = "redox"
1911        ))]
1912        /// Maximum recommended file transfer size.
1913        PC_REC_MAX_XFER_SIZE = libc::_PC_REC_MAX_XFER_SIZE,
1914        #[cfg(any(
1915            target_os = "android",
1916            target_os = "dragonfly",
1917            target_os = "freebsd",
1918            target_os = "linux",
1919            target_os = "openbsd",
1920            target_os = "redox"
1921        ))]
1922        /// Minimum recommended file transfer size.
1923        PC_REC_MIN_XFER_SIZE = libc::_PC_REC_MIN_XFER_SIZE,
1924        #[cfg(any(
1925            target_os = "android",
1926            target_os = "dragonfly",
1927            target_os = "freebsd",
1928            target_os = "linux",
1929            target_os = "openbsd",
1930            target_os = "redox"
1931        ))]
1932        ///  Recommended file transfer buffer alignment.
1933        PC_REC_XFER_ALIGN = libc::_PC_REC_XFER_ALIGN,
1934        #[cfg(any(
1935            target_os = "android",
1936            target_os = "dragonfly",
1937            target_os = "freebsd",
1938            target_os = "illumos",
1939            target_os = "linux",
1940            target_os = "netbsd",
1941            target_os = "openbsd",
1942            target_os = "redox",
1943            target_os = "solaris"
1944        ))]
1945        /// Maximum number of bytes in a symbolic link.
1946        PC_SYMLINK_MAX = libc::_PC_SYMLINK_MAX,
1947        /// The use of `chown` and `fchown` is restricted to a process with
1948        /// appropriate privileges, and to changing the group ID of a file only to
1949        /// the effective group ID of the process or to one of its supplementary
1950        /// group IDs.
1951        PC_CHOWN_RESTRICTED = libc::_PC_CHOWN_RESTRICTED,
1952        /// Pathname components longer than {NAME_MAX} generate an error.
1953        PC_NO_TRUNC = libc::_PC_NO_TRUNC,
1954        /// This symbol shall be defined to be the value of a character that shall
1955        /// disable terminal special character handling.
1956        PC_VDISABLE = libc::_PC_VDISABLE,
1957        #[cfg(any(
1958            target_os = "android",
1959            target_os = "dragonfly",
1960            target_os = "freebsd",
1961            target_os = "illumos",
1962            target_os = "linux",
1963            target_os = "openbsd",
1964            target_os = "redox",
1965            target_os = "solaris"
1966        ))]
1967        /// Asynchronous input or output operations may be performed for the
1968        /// associated file.
1969        PC_ASYNC_IO = libc::_PC_ASYNC_IO,
1970        #[cfg(any(
1971            target_os = "android",
1972            target_os = "dragonfly",
1973            target_os = "freebsd",
1974            target_os = "illumos",
1975            target_os = "linux",
1976            target_os = "openbsd",
1977            target_os = "redox",
1978            target_os = "solaris"
1979        ))]
1980        /// Prioritized input or output operations may be performed for the
1981        /// associated file.
1982        PC_PRIO_IO = libc::_PC_PRIO_IO,
1983        #[cfg(any(
1984            target_os = "android",
1985            target_os = "dragonfly",
1986            target_os = "freebsd",
1987            target_os = "illumos",
1988            target_os = "linux",
1989            target_os = "netbsd",
1990            target_os = "openbsd",
1991            target_os = "redox",
1992            target_os = "solaris"
1993        ))]
1994        /// Synchronized input or output operations may be performed for the
1995        /// associated file.
1996        PC_SYNC_IO = libc::_PC_SYNC_IO,
1997        #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))]
1998        /// The resolution in nanoseconds for all file timestamps.
1999        PC_TIMESTAMP_RESOLUTION = libc::_PC_TIMESTAMP_RESOLUTION,
2000    }
2001
2002    #[cfg(unix)]
2003    #[pyfunction]
2004    fn pathconf(
2005        path: OsPathOrFd,
2006        PathconfName(name): PathconfName,
2007        vm: &VirtualMachine,
2008    ) -> PyResult<Option<libc::c_long>> {
2009        use nix::errno::{self, Errno};
2010
2011        Errno::clear();
2012        debug_assert_eq!(errno::errno(), 0);
2013        let raw = match &path {
2014            OsPathOrFd::Path(path) => {
2015                let path = path.clone().into_cstring(vm)?;
2016                unsafe { libc::pathconf(path.as_ptr(), name) }
2017            }
2018            OsPathOrFd::Fd(fd) => unsafe { libc::fpathconf(*fd, name) },
2019        };
2020
2021        if raw == -1 {
2022            if errno::errno() == 0 {
2023                Ok(None)
2024            } else {
2025                Err(IOErrorBuilder::with_filename(
2026                    &io::Error::from(Errno::last()),
2027                    path,
2028                    vm,
2029                ))
2030            }
2031        } else {
2032            Ok(Some(raw))
2033        }
2034    }
2035
2036    #[pyfunction]
2037    fn fpathconf(
2038        fd: i32,
2039        name: PathconfName,
2040        vm: &VirtualMachine,
2041    ) -> PyResult<Option<libc::c_long>> {
2042        pathconf(OsPathOrFd::Fd(fd), name, vm)
2043    }
2044
2045    #[pyattr]
2046    fn pathconf_names(vm: &VirtualMachine) -> PyDictRef {
2047        use strum::IntoEnumIterator;
2048        let pathname = vm.ctx.new_dict();
2049        for variant in PathconfVar::iter() {
2050            // get the name of variant as a string to use as the dictionary key
2051            let key = vm.ctx.new_str(format!("{:?}", variant));
2052            // get the enum from the string and convert it to an integer for the dictionary value
2053            let value = vm.ctx.new_int(variant as u8);
2054            pathname
2055                .set_item(&*key, value.into(), vm)
2056                .expect("dict set_item unexpectedly failed");
2057        }
2058        pathname
2059    }
2060
2061    #[cfg(not(target_os = "redox"))]
2062    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumIter, EnumString)]
2063    #[repr(i32)]
2064    #[allow(non_camel_case_types)]
2065    pub enum SysconfVar {
2066        SC_2_CHAR_TERM = libc::_SC_2_CHAR_TERM,
2067        SC_2_C_BIND = libc::_SC_2_C_BIND,
2068        SC_2_C_DEV = libc::_SC_2_C_DEV,
2069        SC_2_FORT_DEV = libc::_SC_2_FORT_DEV,
2070        SC_2_FORT_RUN = libc::_SC_2_FORT_RUN,
2071        SC_2_LOCALEDEF = libc::_SC_2_LOCALEDEF,
2072        SC_2_SW_DEV = libc::_SC_2_SW_DEV,
2073        SC_2_UPE = libc::_SC_2_UPE,
2074        SC_2_VERSION = libc::_SC_2_VERSION,
2075        SC_AIO_LISTIO_MAX = libc::_SC_AIO_LISTIO_MAX,
2076        SC_AIO_MAX = libc::_SC_AIO_MAX,
2077        SC_AIO_PRIO_DELTA_MAX = libc::_SC_AIO_PRIO_DELTA_MAX,
2078        SC_ARG_MAX = libc::_SC_ARG_MAX,
2079        SC_ASYNCHRONOUS_IO = libc::_SC_ASYNCHRONOUS_IO,
2080        SC_ATEXIT_MAX = libc::_SC_ATEXIT_MAX,
2081        SC_BC_BASE_MAX = libc::_SC_BC_BASE_MAX,
2082        SC_BC_DIM_MAX = libc::_SC_BC_DIM_MAX,
2083        SC_BC_SCALE_MAX = libc::_SC_BC_SCALE_MAX,
2084        SC_BC_STRING_MAX = libc::_SC_BC_STRING_MAX,
2085        SC_CHILD_MAX = libc::_SC_CHILD_MAX,
2086        SC_CLK_TCK = libc::_SC_CLK_TCK,
2087        SC_COLL_WEIGHTS_MAX = libc::_SC_COLL_WEIGHTS_MAX,
2088        SC_DELAYTIMER_MAX = libc::_SC_DELAYTIMER_MAX,
2089        SC_EXPR_NEST_MAX = libc::_SC_EXPR_NEST_MAX,
2090        SC_FSYNC = libc::_SC_FSYNC,
2091        SC_GETGR_R_SIZE_MAX = libc::_SC_GETGR_R_SIZE_MAX,
2092        SC_GETPW_R_SIZE_MAX = libc::_SC_GETPW_R_SIZE_MAX,
2093        SC_IOV_MAX = libc::_SC_IOV_MAX,
2094        SC_JOB_CONTROL = libc::_SC_JOB_CONTROL,
2095        SC_LINE_MAX = libc::_SC_LINE_MAX,
2096        SC_LOGIN_NAME_MAX = libc::_SC_LOGIN_NAME_MAX,
2097        SC_MAPPED_FILES = libc::_SC_MAPPED_FILES,
2098        SC_MEMLOCK = libc::_SC_MEMLOCK,
2099        SC_MEMLOCK_RANGE = libc::_SC_MEMLOCK_RANGE,
2100        SC_MEMORY_PROTECTION = libc::_SC_MEMORY_PROTECTION,
2101        SC_MESSAGE_PASSING = libc::_SC_MESSAGE_PASSING,
2102        SC_MQ_OPEN_MAX = libc::_SC_MQ_OPEN_MAX,
2103        SC_MQ_PRIO_MAX = libc::_SC_MQ_PRIO_MAX,
2104        SC_NGROUPS_MAX = libc::_SC_NGROUPS_MAX,
2105        SC_NPROCESSORS_CONF = libc::_SC_NPROCESSORS_CONF,
2106        SC_NPROCESSORS_ONLN = libc::_SC_NPROCESSORS_ONLN,
2107        SC_OPEN_MAX = libc::_SC_OPEN_MAX,
2108        SC_PAGE_SIZE = libc::_SC_PAGE_SIZE,
2109        #[cfg(any(
2110            target_os = "linux",
2111            target_vendor = "apple",
2112            target_os = "netbsd",
2113            target_os = "fuchsia"
2114        ))]
2115        SC_PASS_MAX = libc::_SC_PASS_MAX,
2116        SC_PHYS_PAGES = libc::_SC_PHYS_PAGES,
2117        SC_PRIORITIZED_IO = libc::_SC_PRIORITIZED_IO,
2118        SC_PRIORITY_SCHEDULING = libc::_SC_PRIORITY_SCHEDULING,
2119        SC_REALTIME_SIGNALS = libc::_SC_REALTIME_SIGNALS,
2120        SC_RE_DUP_MAX = libc::_SC_RE_DUP_MAX,
2121        SC_RTSIG_MAX = libc::_SC_RTSIG_MAX,
2122        SC_SAVED_IDS = libc::_SC_SAVED_IDS,
2123        SC_SEMAPHORES = libc::_SC_SEMAPHORES,
2124        SC_SEM_NSEMS_MAX = libc::_SC_SEM_NSEMS_MAX,
2125        SC_SEM_VALUE_MAX = libc::_SC_SEM_VALUE_MAX,
2126        SC_SHARED_MEMORY_OBJECTS = libc::_SC_SHARED_MEMORY_OBJECTS,
2127        SC_SIGQUEUE_MAX = libc::_SC_SIGQUEUE_MAX,
2128        SC_STREAM_MAX = libc::_SC_STREAM_MAX,
2129        SC_SYNCHRONIZED_IO = libc::_SC_SYNCHRONIZED_IO,
2130        SC_THREADS = libc::_SC_THREADS,
2131        SC_THREAD_ATTR_STACKADDR = libc::_SC_THREAD_ATTR_STACKADDR,
2132        SC_THREAD_ATTR_STACKSIZE = libc::_SC_THREAD_ATTR_STACKSIZE,
2133        SC_THREAD_DESTRUCTOR_ITERATIONS = libc::_SC_THREAD_DESTRUCTOR_ITERATIONS,
2134        SC_THREAD_KEYS_MAX = libc::_SC_THREAD_KEYS_MAX,
2135        SC_THREAD_PRIORITY_SCHEDULING = libc::_SC_THREAD_PRIORITY_SCHEDULING,
2136        SC_THREAD_PRIO_INHERIT = libc::_SC_THREAD_PRIO_INHERIT,
2137        SC_THREAD_PRIO_PROTECT = libc::_SC_THREAD_PRIO_PROTECT,
2138        SC_THREAD_PROCESS_SHARED = libc::_SC_THREAD_PROCESS_SHARED,
2139        SC_THREAD_SAFE_FUNCTIONS = libc::_SC_THREAD_SAFE_FUNCTIONS,
2140        SC_THREAD_STACK_MIN = libc::_SC_THREAD_STACK_MIN,
2141        SC_THREAD_THREADS_MAX = libc::_SC_THREAD_THREADS_MAX,
2142        SC_TIMERS = libc::_SC_TIMERS,
2143        SC_TIMER_MAX = libc::_SC_TIMER_MAX,
2144        SC_TTY_NAME_MAX = libc::_SC_TTY_NAME_MAX,
2145        SC_TZNAME_MAX = libc::_SC_TZNAME_MAX,
2146        SC_VERSION = libc::_SC_VERSION,
2147        SC_XOPEN_CRYPT = libc::_SC_XOPEN_CRYPT,
2148        SC_XOPEN_ENH_I18N = libc::_SC_XOPEN_ENH_I18N,
2149        SC_XOPEN_LEGACY = libc::_SC_XOPEN_LEGACY,
2150        SC_XOPEN_REALTIME = libc::_SC_XOPEN_REALTIME,
2151        SC_XOPEN_REALTIME_THREADS = libc::_SC_XOPEN_REALTIME_THREADS,
2152        SC_XOPEN_SHM = libc::_SC_XOPEN_SHM,
2153        SC_XOPEN_UNIX = libc::_SC_XOPEN_UNIX,
2154        SC_XOPEN_VERSION = libc::_SC_XOPEN_VERSION,
2155        SC_XOPEN_XCU_VERSION = libc::_SC_XOPEN_XCU_VERSION,
2156        #[cfg(any(
2157            target_os = "linux",
2158            target_vendor = "apple",
2159            target_os = "netbsd",
2160            target_os = "fuchsia"
2161        ))]
2162        SC_XBS5_ILP32_OFF32 = libc::_SC_XBS5_ILP32_OFF32,
2163        #[cfg(any(
2164            target_os = "linux",
2165            target_vendor = "apple",
2166            target_os = "netbsd",
2167            target_os = "fuchsia"
2168        ))]
2169        SC_XBS5_ILP32_OFFBIG = libc::_SC_XBS5_ILP32_OFFBIG,
2170        #[cfg(any(
2171            target_os = "linux",
2172            target_vendor = "apple",
2173            target_os = "netbsd",
2174            target_os = "fuchsia"
2175        ))]
2176        SC_XBS5_LP64_OFF64 = libc::_SC_XBS5_LP64_OFF64,
2177        #[cfg(any(
2178            target_os = "linux",
2179            target_vendor = "apple",
2180            target_os = "netbsd",
2181            target_os = "fuchsia"
2182        ))]
2183        SC_XBS5_LPBIG_OFFBIG = libc::_SC_XBS5_LPBIG_OFFBIG,
2184    }
2185
2186    #[cfg(target_os = "redox")]
2187    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumIter, EnumString)]
2188    #[repr(i32)]
2189    #[allow(non_camel_case_types)]
2190    pub enum SysconfVar {
2191        SC_ARG_MAX = libc::_SC_ARG_MAX,
2192        SC_CHILD_MAX = libc::_SC_CHILD_MAX,
2193        SC_CLK_TCK = libc::_SC_CLK_TCK,
2194        SC_NGROUPS_MAX = libc::_SC_NGROUPS_MAX,
2195        SC_OPEN_MAX = libc::_SC_OPEN_MAX,
2196        SC_STREAM_MAX = libc::_SC_STREAM_MAX,
2197        SC_TZNAME_MAX = libc::_SC_TZNAME_MAX,
2198        SC_VERSION = libc::_SC_VERSION,
2199        SC_PAGE_SIZE = libc::_SC_PAGE_SIZE,
2200        SC_RE_DUP_MAX = libc::_SC_RE_DUP_MAX,
2201        SC_LOGIN_NAME_MAX = libc::_SC_LOGIN_NAME_MAX,
2202        SC_TTY_NAME_MAX = libc::_SC_TTY_NAME_MAX,
2203        SC_SYMLOOP_MAX = libc::_SC_SYMLOOP_MAX,
2204        SC_HOST_NAME_MAX = libc::_SC_HOST_NAME_MAX,
2205    }
2206
2207    impl SysconfVar {
2208        pub const SC_PAGESIZE: SysconfVar = Self::SC_PAGE_SIZE;
2209    }
2210
2211    struct SysconfName(i32);
2212
2213    impl TryFromObject for SysconfName {
2214        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
2215            let i = match obj.downcast::<PyInt>() {
2216                Ok(int) => int.try_to_primitive(vm)?,
2217                Err(obj) => {
2218                    let s = PyStrRef::try_from_object(vm, obj)?;
2219                    s.as_str().parse::<SysconfVar>().or_else(|_| {
2220                        if s.as_str() == "SC_PAGESIZE" {
2221                            Ok(SysconfVar::SC_PAGESIZE)
2222                        } else {
2223                            Err(vm.new_value_error("unrecognized configuration name".to_string()))
2224                        }
2225                    })? as i32
2226                }
2227            };
2228            Ok(Self(i))
2229        }
2230    }
2231
2232    #[pyfunction]
2233    fn sysconf(name: SysconfName, vm: &VirtualMachine) -> PyResult<libc::c_long> {
2234        let r = unsafe { libc::sysconf(name.0) };
2235        if r == -1 {
2236            return Err(errno_err(vm));
2237        }
2238        Ok(r)
2239    }
2240
2241    #[pyattr]
2242    fn sysconf_names(vm: &VirtualMachine) -> PyDictRef {
2243        use strum::IntoEnumIterator;
2244        let names = vm.ctx.new_dict();
2245        for variant in SysconfVar::iter() {
2246            // get the name of variant as a string to use as the dictionary key
2247            let key = vm.ctx.new_str(format!("{:?}", variant));
2248            // get the enum from the string and convert it to an integer for the dictionary value
2249            let value = vm.ctx.new_int(variant as u8);
2250            names
2251                .set_item(&*key, value.into(), vm)
2252                .expect("dict set_item unexpectedly failed");
2253        }
2254        names
2255    }
2256
2257    #[cfg(any(target_os = "linux", target_os = "macos"))]
2258    #[derive(FromArgs)]
2259    struct SendFileArgs<'fd> {
2260        out_fd: BorrowedFd<'fd>,
2261        in_fd: BorrowedFd<'fd>,
2262        offset: crate::common::crt_fd::Offset,
2263        count: i64,
2264        #[cfg(target_os = "macos")]
2265        #[pyarg(any, optional)]
2266        headers: OptionalArg<PyObjectRef>,
2267        #[cfg(target_os = "macos")]
2268        #[pyarg(any, optional)]
2269        trailers: OptionalArg<PyObjectRef>,
2270        #[cfg(target_os = "macos")]
2271        #[allow(dead_code)]
2272        #[pyarg(any, default)]
2273        // TODO: not implemented
2274        flags: OptionalArg<i32>,
2275    }
2276
2277    #[cfg(target_os = "linux")]
2278    #[pyfunction]
2279    fn sendfile(args: SendFileArgs<'_>, vm: &VirtualMachine) -> PyResult {
2280        let mut file_offset = args.offset;
2281
2282        let res = nix::sys::sendfile::sendfile(
2283            args.out_fd,
2284            args.in_fd,
2285            Some(&mut file_offset),
2286            args.count as usize,
2287        )
2288        .map_err(|err| err.into_pyexception(vm))?;
2289        Ok(vm.ctx.new_int(res as u64).into())
2290    }
2291
2292    #[cfg(target_os = "macos")]
2293    fn _extract_vec_bytes(
2294        x: OptionalArg,
2295        vm: &VirtualMachine,
2296    ) -> PyResult<Option<Vec<crate::function::ArgBytesLike>>> {
2297        x.into_option()
2298            .map(|x| {
2299                let v: Vec<crate::function::ArgBytesLike> = x.try_to_value(vm)?;
2300                Ok(if v.is_empty() { None } else { Some(v) })
2301            })
2302            .transpose()
2303            .map(Option::flatten)
2304    }
2305
2306    #[cfg(target_os = "macos")]
2307    #[pyfunction]
2308    fn sendfile(args: SendFileArgs<'_>, vm: &VirtualMachine) -> PyResult {
2309        let headers = _extract_vec_bytes(args.headers, vm)?;
2310        let count = headers
2311            .as_ref()
2312            .map(|v| v.iter().map(|s| s.len()).sum())
2313            .unwrap_or(0) as i64
2314            + args.count;
2315
2316        let headers = headers
2317            .as_ref()
2318            .map(|v| v.iter().map(|b| b.borrow_buf()).collect::<Vec<_>>());
2319        let headers = headers
2320            .as_ref()
2321            .map(|v| v.iter().map(|borrowed| &**borrowed).collect::<Vec<_>>());
2322        let headers = headers.as_deref();
2323
2324        let trailers = _extract_vec_bytes(args.trailers, vm)?;
2325        let trailers = trailers
2326            .as_ref()
2327            .map(|v| v.iter().map(|b| b.borrow_buf()).collect::<Vec<_>>());
2328        let trailers = trailers
2329            .as_ref()
2330            .map(|v| v.iter().map(|borrowed| &**borrowed).collect::<Vec<_>>());
2331        let trailers = trailers.as_deref();
2332
2333        let (res, written) = nix::sys::sendfile::sendfile(
2334            args.in_fd,
2335            args.out_fd,
2336            args.offset,
2337            Some(count),
2338            headers,
2339            trailers,
2340        );
2341        res.map_err(|err| err.into_pyexception(vm))?;
2342        Ok(vm.ctx.new_int(written as u64).into())
2343    }
2344
2345    #[cfg(target_os = "linux")]
2346    unsafe fn sys_getrandom(buf: *mut libc::c_void, buflen: usize, flags: u32) -> isize {
2347        libc::syscall(libc::SYS_getrandom, buf, buflen, flags as usize) as _
2348    }
2349
2350    #[cfg(target_os = "linux")]
2351    #[pyfunction]
2352    fn getrandom(size: isize, flags: OptionalArg<u32>, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
2353        let size = usize::try_from(size)
2354            .map_err(|_| vm.new_os_error(format!("Invalid argument for size: {size}")))?;
2355        let mut buf = Vec::with_capacity(size);
2356        unsafe {
2357            let len = sys_getrandom(
2358                buf.as_mut_ptr() as *mut libc::c_void,
2359                size,
2360                flags.unwrap_or(0),
2361            )
2362            .try_into()
2363            .map_err(|_| errno_err(vm))?;
2364            buf.set_len(len);
2365        }
2366        Ok(buf)
2367    }
2368}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.