Skip to main content

rustpython_vm/stdlib/
posix.rs

1// spell-checker:disable
2
3pub(crate) use module::module_def;
4
5pub use rustpython_host_env::posix::set_inheritable;
6
7#[pymodule(name = "posix", with(
8    super::os::_os,
9    super::posix_unix_like::_posix_unix_like,
10    #[cfg(any(
11        target_os = "linux",
12        target_os = "netbsd",
13        target_os = "freebsd",
14        target_os = "android"
15    ))]
16    posix_sched
17))]
18pub mod module {
19    use crate::{
20        AsObject, Py, PyObject, PyObjectRef, PyResult, VirtualMachine,
21        builtins::{PyBytesRef, PyDictRef, PyInt, PyListRef, PyTuple, PyTupleRef, PyUtf8Str},
22        convert::{IntoPyException, ToPyException, ToPyObject, TryFromObject},
23        exceptions::OSErrorBuilder,
24        function::{ArgBytesLike, ArgMapping, Either, OptionalArg, PySsize},
25        ospath::{OsPath, OsPathOrFd},
26        stdlib::os::{
27            _os, DirFd, FollowSymlinks, SupportFunc, SymlinkArgs, fs_metadata, warn_if_bool_fd,
28        },
29    };
30    #[cfg(any(
31        target_os = "android",
32        target_os = "freebsd",
33        target_os = "linux",
34        target_os = "openbsd"
35    ))]
36    use crate::{builtins::PyUtf8StrRef, utils::ToCString};
37    use alloc::ffi::CString;
38    use core::ffi::CStr;
39    use rustpython_host_env::os::ffi::OsStringExt;
40    use rustpython_host_env::posix as host_posix;
41    use std::{
42        fs, io,
43        os::fd::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd},
44    };
45    use strum::IntoEnumIterator;
46    use strum_macros::{EnumIter, EnumString};
47
48    #[cfg(target_os = "linux")]
49    #[pyattr]
50    use rustpython_host_env::posix::PIDFD_NONBLOCK;
51
52    #[cfg(target_os = "macos")]
53    #[pyattr]
54    use rustpython_host_env::posix::{
55        COPYFILE_DATA as _COPYFILE_DATA, PRIO_DARWIN_BG, PRIO_DARWIN_NONUI, PRIO_DARWIN_PROCESS,
56        PRIO_DARWIN_THREAD,
57    };
58
59    #[cfg(target_os = "macos")]
60    #[pyattr]
61    use rustpython_host_env::os::{O_EVTONLY, O_NOFOLLOW_ANY};
62
63    #[cfg(target_os = "freebsd")]
64    #[pyattr]
65    use rustpython_host_env::posix::{SF_MNOWAIT, SF_NOCACHE, SF_NODISKIO, SF_SYNC};
66
67    #[cfg(any(target_os = "android", target_os = "linux"))]
68    #[pyattr]
69    use rustpython_host_env::posix::{
70        CLONE_FILES, CLONE_FS, CLONE_NEWCGROUP, CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS,
71        CLONE_NEWPID, CLONE_NEWUSER, CLONE_NEWUTS, CLONE_SIGHAND, CLONE_SYSVSEM, CLONE_THREAD,
72        CLONE_VM, MFD_HUGE_SHIFT, P_PIDFD, SCHED_BATCH, SCHED_DEADLINE, SCHED_IDLE, SCHED_NORMAL,
73        SCHED_RESET_ON_FORK, SPLICE_F_MORE, SPLICE_F_MOVE, SPLICE_F_NONBLOCK,
74    };
75
76    #[cfg(any(target_os = "android", target_os = "linux"))]
77    #[pyattr]
78    use rustpython_host_env::os::{O_NOATIME, O_TMPFILE};
79
80    #[cfg(any(target_os = "macos", target_os = "redox"))]
81    #[pyattr]
82    use rustpython_host_env::os::O_SYMLINK;
83
84    #[cfg(any(target_os = "android", target_os = "redox", unix))]
85    #[pyattr]
86    use rustpython_host_env::posix::{PRIO_PGRP, PRIO_PROCESS, PRIO_USER};
87
88    #[cfg(any(target_os = "android", target_os = "redox", unix))]
89    #[pyattr]
90    use rustpython_host_env::os::O_NOFOLLOW;
91
92    #[cfg(any(target_os = "linux", target_os = "macos", target_os = "netbsd"))]
93    #[pyattr]
94    use rustpython_host_env::posix::{XATTR_CREATE, XATTR_REPLACE};
95
96    #[cfg(any(target_os = "android", target_os = "linux", target_os = "netbsd"))]
97    #[pyattr]
98    use rustpython_host_env::os::O_RSYNC;
99
100    #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
101    #[pyattr]
102    use rustpython_host_env::posix::{
103        MFD_ALLOW_SEALING, MFD_CLOEXEC, MFD_HUGE_MASK, MFD_HUGETLB, POSIX_FADV_DONTNEED,
104        POSIX_FADV_NOREUSE, POSIX_FADV_NORMAL, POSIX_FADV_RANDOM, POSIX_FADV_SEQUENTIAL,
105        POSIX_FADV_WILLNEED,
106    };
107
108    #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox", unix))]
109    #[pyattr]
110    use rustpython_host_env::posix::{RTLD_LAZY, RTLD_NOW, WNOHANG};
111
112    #[cfg(any(target_os = "android", target_os = "macos", target_os = "redox", unix))]
113    #[pyattr]
114    use rustpython_host_env::posix::RTLD_GLOBAL;
115
116    #[cfg(any(
117        target_os = "android",
118        target_os = "freebsd",
119        target_os = "linux",
120        target_os = "redox"
121    ))]
122    #[pyattr]
123    use rustpython_host_env::os::O_PATH;
124
125    #[cfg(any(
126        target_os = "android",
127        target_os = "freebsd",
128        target_os = "linux",
129        target_os = "netbsd"
130    ))]
131    #[pyattr]
132    use rustpython_host_env::posix::{
133        EFD_CLOEXEC, EFD_NONBLOCK, EFD_SEMAPHORE, TFD_CLOEXEC, TFD_NONBLOCK, TFD_TIMER_ABSTIME,
134        TFD_TIMER_CANCEL_ON_SET,
135    };
136
137    #[cfg(any(
138        target_os = "android",
139        target_os = "dragonfly",
140        target_os = "linux",
141        target_os = "netbsd"
142    ))]
143    #[pyattr]
144    use rustpython_host_env::posix::{GRND_NONBLOCK, GRND_RANDOM};
145
146    #[cfg(any(
147        target_os = "android",
148        target_os = "linux",
149        target_os = "macos",
150        target_os = "redox",
151        unix
152    ))]
153    #[pyattr]
154    use rustpython_host_env::os::{F_OK, R_OK, W_OK, X_OK};
155
156    #[cfg(any(
157        target_os = "android",
158        target_os = "freebsd",
159        target_os = "netbsd",
160        target_os = "redox",
161        unix
162    ))]
163    #[pyattr]
164    use rustpython_host_env::os::O_NONBLOCK;
165
166    #[cfg(any(
167        target_os = "android",
168        target_os = "freebsd",
169        target_os = "linux",
170        target_os = "macos",
171        target_os = "netbsd"
172    ))]
173    #[pyattr]
174    use rustpython_host_env::os::O_DSYNC;
175
176    #[cfg(any(
177        target_os = "dragonfly",
178        target_os = "freebsd",
179        target_os = "linux",
180        target_os = "macos",
181        target_os = "netbsd"
182    ))]
183    #[pyattr]
184    use rustpython_host_env::posix::SCHED_OTHER;
185
186    #[cfg(any(
187        target_os = "android",
188        target_os = "dragonfly",
189        target_os = "freebsd",
190        target_os = "linux",
191        target_os = "macos"
192    ))]
193    #[pyattr]
194    use rustpython_host_env::posix::{RTLD_NODELETE, SEEK_DATA, SEEK_HOLE};
195
196    #[cfg(any(
197        target_os = "android",
198        target_os = "dragonfly",
199        target_os = "freebsd",
200        target_os = "linux",
201        target_os = "netbsd"
202    ))]
203    #[pyattr]
204    use rustpython_host_env::os::O_DIRECT;
205
206    #[cfg(any(
207        target_os = "dragonfly",
208        target_os = "freebsd",
209        target_os = "macos",
210        target_os = "netbsd",
211        target_os = "redox"
212    ))]
213    #[pyattr]
214    use rustpython_host_env::os::{O_EXLOCK, O_FSYNC, O_SHLOCK};
215
216    #[cfg(any(
217        target_os = "android",
218        target_os = "linux",
219        target_os = "macos",
220        target_os = "netbsd",
221        target_os = "redox",
222        unix
223    ))]
224    #[pyattr]
225    use rustpython_host_env::posix::RTLD_LOCAL;
226
227    #[cfg(any(
228        target_os = "android",
229        target_os = "freebsd",
230        target_os = "linux",
231        target_os = "netbsd",
232        target_os = "redox",
233        unix
234    ))]
235    #[pyattr]
236    use rustpython_host_env::posix::WUNTRACED;
237
238    #[cfg(any(
239        target_os = "android",
240        target_os = "dragonfly",
241        target_os = "freebsd",
242        target_os = "linux",
243        target_os = "macos",
244        target_os = "netbsd"
245    ))]
246    #[pyattr]
247    use rustpython_host_env::posix::{
248        CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, CLD_TRAPPED, P_ALL, P_PGID,
249        P_PID, SCHED_FIFO, SCHED_RR,
250    };
251
252    #[cfg(any(
253        target_os = "android",
254        target_os = "dragonfly",
255        target_os = "freebsd",
256        target_os = "linux",
257        target_os = "macos",
258        target_os = "netbsd"
259    ))]
260    #[pyattr]
261    use rustpython_host_env::os::O_SYNC;
262
263    #[cfg(any(
264        target_os = "android",
265        target_os = "dragonfly",
266        target_os = "freebsd",
267        target_os = "macos",
268        target_os = "netbsd",
269        target_os = "redox",
270        unix
271    ))]
272    #[pyattr]
273    use rustpython_host_env::os::O_DIRECTORY;
274
275    #[cfg(any(
276        target_os = "android",
277        target_os = "dragonfly",
278        target_os = "freebsd",
279        target_os = "linux",
280        target_os = "macos",
281        target_os = "netbsd",
282        target_os = "redox"
283    ))]
284    #[pyattr]
285    use rustpython_host_env::fcntl::{F_LOCK, F_TEST, F_TLOCK, F_ULOCK};
286    #[cfg(any(
287        target_os = "android",
288        target_os = "dragonfly",
289        target_os = "freebsd",
290        target_os = "linux",
291        target_os = "macos",
292        target_os = "netbsd",
293        target_os = "redox"
294    ))]
295    #[pyattr]
296    use rustpython_host_env::posix::{RTLD_NOLOAD, WEXITED, WNOWAIT, WSTOPPED};
297
298    #[cfg(any(
299        target_os = "android",
300        target_os = "dragonfly",
301        target_os = "freebsd",
302        target_os = "linux",
303        target_os = "macos",
304        target_os = "netbsd",
305        target_os = "redox"
306    ))]
307    #[pyattr]
308    use rustpython_host_env::os::{O_ASYNC, O_NDELAY, O_NOCTTY};
309
310    #[cfg(any(
311        target_os = "android",
312        target_os = "dragonfly",
313        target_os = "freebsd",
314        target_os = "linux",
315        target_os = "macos",
316        target_os = "netbsd",
317        target_os = "redox",
318        unix
319    ))]
320    #[pyattr]
321    use rustpython_host_env::posix::WCONTINUED;
322
323    #[cfg(any(
324        target_os = "android",
325        target_os = "dragonfly",
326        target_os = "freebsd",
327        target_os = "linux",
328        target_os = "macos",
329        target_os = "netbsd",
330        target_os = "redox",
331        unix
332    ))]
333    #[pyattr]
334    use rustpython_host_env::os::O_CLOEXEC;
335
336    #[pyattr]
337    use rustpython_host_env::posix::{
338        EX_CANTCREAT, EX_CONFIG, EX_DATAERR, EX_IOERR, EX_NOHOST, EX_NOINPUT, EX_NOPERM, EX_NOUSER,
339        EX_OK, EX_OSERR, EX_OSFILE, EX_PROTOCOL, EX_SOFTWARE, EX_TEMPFAIL, EX_UNAVAILABLE,
340        EX_USAGE,
341    };
342
343    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
344    #[pyattr]
345    const POSIX_SPAWN_OPEN: i32 = PosixSpawnFileActionIdentifier::Open as i32;
346
347    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
348    #[pyattr]
349    const POSIX_SPAWN_CLOSE: i32 = PosixSpawnFileActionIdentifier::Close as i32;
350
351    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
352    #[pyattr]
353    const POSIX_SPAWN_DUP2: i32 = PosixSpawnFileActionIdentifier::Dup2 as i32;
354
355    impl TryFromObject for BorrowedFd<'_> {
356        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
357            crate::stdlib::os::warn_if_bool_fd(&obj, vm)?;
358            let fd = i32::try_from_object(vm, obj)?;
359            if fd == -1 {
360                return Err(io::Error::from_raw_os_error(libc::EBADF).into_pyexception(vm));
361            }
362            // SAFETY: none, really. but, python's os api of passing around file descriptors
363            //         everywhere isn't really io-safe anyway, so, this is passed to the user.
364            Ok(unsafe { BorrowedFd::borrow_raw(fd) })
365        }
366    }
367
368    impl TryFromObject for OwnedFd {
369        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
370            let fd = i32::try_from_object(vm, obj)?;
371            if fd == -1 {
372                return Err(io::Error::from_raw_os_error(libc::EBADF).into_pyexception(vm));
373            }
374            // SAFETY: none, really. but, python's os api of passing around file descriptors
375            //         everywhere isn't really io-safe anyway, so, this is passed to the user.
376            Ok(unsafe { Self::from_raw_fd(fd) })
377        }
378    }
379
380    impl ToPyObject for OwnedFd {
381        fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
382            self.into_raw_fd().to_pyobject(vm)
383        }
384    }
385
386    #[pyfunction]
387    fn getgroups(vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
388        let group_ids =
389            rustpython_host_env::posix::getgroups().map_err(|e| e.into_pyexception(vm))?;
390        Ok(group_ids
391            .into_iter()
392            .map(|gid| vm.ctx.new_int(gid).into())
393            .collect())
394    }
395
396    #[derive(FromArgs)]
397    pub(super) struct AccessArgs<'a> {
398        #[pyarg(any)]
399        path: OsPath,
400        #[pyarg(any)]
401        mode: u8,
402        #[pyarg(flatten)]
403        dir_fd: DirFd<'a, 0>,
404        #[pyarg(named, default)]
405        effective_ids: bool,
406        #[pyarg(flatten)]
407        follow_symlinks: FollowSymlinks,
408    }
409
410    #[pyfunction]
411    pub(super) fn access(args: AccessArgs<'_>, vm: &VirtualMachine) -> PyResult<bool> {
412        let [] = args.dir_fd.0;
413        if args.effective_ids {
414            return Err(
415                vm.new_not_implemented_error("access: effective_ids unavailable on this platform")
416            );
417        }
418        if !args.follow_symlinks.0 {
419            return Err(vm.new_not_implemented_error(
420                "access: follow_symlinks unavailable on this platform",
421            ));
422        }
423        rustpython_host_env::posix::check_access(args.path.as_ref(), args.mode)
424            .map_err(|err| err.to_pyexception(vm))
425    }
426
427    #[pyfunction]
428    fn _create_environ(vm: &VirtualMachine) -> PyDictRef {
429        let environ = vm.ctx.new_dict();
430        for (key, value) in crate::host_env::os::vars_os() {
431            let key: PyObjectRef = vm.ctx.new_bytes(key.into_vec()).into();
432            let value: PyObjectRef = vm.ctx.new_bytes(value.into_vec()).into();
433            environ.set_item(&*key, value, vm).unwrap();
434        }
435        environ
436    }
437
438    #[pyfunction]
439    pub(super) fn symlink(args: SymlinkArgs<'_>, vm: &VirtualMachine) -> PyResult<()> {
440        let src = args.src.into_cstring(vm)?;
441        let dst = args.dst.into_cstring(vm)?;
442        #[cfg(not(target_os = "redox"))]
443        {
444            rustpython_host_env::posix::symlinkat(&src, args.dir_fd.get().into(), &dst)
445                .map_err(|err| err.into_pyexception(vm))
446        }
447        #[cfg(target_os = "redox")]
448        {
449            let [] = args.dir_fd.0;
450            rustpython_host_env::posix::symlink(&src, &dst).map_err(|err| err.into_pyexception(vm))
451        }
452    }
453
454    #[cfg(not(target_os = "redox"))]
455    #[derive(FromArgs)]
456    struct FchdirArgs {
457        #[pyarg(any)]
458        fd: PyObjectRef,
459    }
460
461    #[cfg(not(target_os = "redox"))]
462    #[pyfunction]
463    fn fchdir(fd: FchdirArgs, vm: &VirtualMachine) -> PyResult<()> {
464        let fd = fd.fd;
465        warn_if_bool_fd(&fd, vm)?;
466        let fd = i32::try_from_object(vm, fd)?;
467        rustpython_host_env::posix::fchdir(fd).map_err(|err| err.into_pyexception(vm))
468    }
469
470    #[cfg(not(target_os = "redox"))]
471    #[derive(FromArgs)]
472    struct ChrootArgs {
473        #[pyarg(any)]
474        path: OsPath,
475    }
476
477    #[cfg(not(target_os = "redox"))]
478    #[pyfunction]
479    fn chroot(path: ChrootArgs, vm: &VirtualMachine) -> PyResult<()> {
480        use crate::exceptions::OSErrorBuilder;
481
482        let path = path.path;
483        rustpython_host_env::posix::chroot(std::path::Path::new(&path.path))
484            .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
485    }
486
487    // 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)
488    #[cfg(not(target_os = "redox"))]
489    fn chown_inner(
490        path: OsPathOrFd<'_>,
491        uid: isize,
492        gid: isize,
493        dir_fd: DirFd<'_, 1>,
494        follow_symlinks: FollowSymlinks,
495        vm: &VirtualMachine,
496    ) -> PyResult<()> {
497        let uid = if uid >= 0 {
498            Some(uid as u32)
499        } else if uid == -1 {
500            None
501        } else {
502            return Err(vm.new_os_error("Specified uid is not valid."));
503        };
504
505        let gid = if gid >= 0 {
506            Some(gid as u32)
507        } else if gid == -1 {
508            None
509        } else {
510            return Err(vm.new_os_error("Specified gid is not valid."));
511        };
512
513        match path {
514            OsPathOrFd::Path(ref p) => rustpython_host_env::posix::fchownat(
515                dir_fd.get().into(),
516                p.path.as_os_str(),
517                uid,
518                gid,
519                follow_symlinks.0,
520            ),
521            OsPathOrFd::Fd(fd) => rustpython_host_env::posix::fchown(fd.into(), uid, gid),
522        }
523        .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
524    }
525
526    #[cfg(not(target_os = "redox"))]
527    #[derive(FromArgs)]
528    struct ChownArgs<'a> {
529        #[pyarg(any)]
530        path: OsPathOrFd<'a>,
531        #[pyarg(any)]
532        uid: isize,
533        #[pyarg(any)]
534        gid: isize,
535        #[pyarg(flatten)]
536        dir_fd: DirFd<'a, 1>,
537        #[pyarg(flatten)]
538        follow_symlinks: FollowSymlinks,
539    }
540
541    #[cfg(not(target_os = "redox"))]
542    #[pyfunction]
543    fn chown(args: ChownArgs<'_>, vm: &VirtualMachine) -> PyResult<()> {
544        chown_inner(
545            args.path,
546            args.uid,
547            args.gid,
548            args.dir_fd,
549            args.follow_symlinks,
550            vm,
551        )
552    }
553
554    #[cfg(not(target_os = "redox"))]
555    #[derive(FromArgs)]
556    struct LchownArgs {
557        #[pyarg(any)]
558        path: OsPath,
559        #[pyarg(any)]
560        uid: isize,
561        #[pyarg(any)]
562        gid: isize,
563    }
564
565    #[cfg(not(target_os = "redox"))]
566    #[pyfunction]
567    fn lchown(args: LchownArgs, vm: &VirtualMachine) -> PyResult<()> {
568        let LchownArgs { path, uid, gid } = args;
569        chown_inner(
570            OsPathOrFd::Path(path),
571            uid,
572            gid,
573            DirFd::default(),
574            FollowSymlinks(false),
575            vm,
576        )
577    }
578
579    #[cfg(not(target_os = "redox"))]
580    #[derive(FromArgs)]
581    struct FchownArgs<'a> {
582        #[pyarg(any)]
583        fd: BorrowedFd<'a>,
584        #[pyarg(any)]
585        uid: isize,
586        #[pyarg(any)]
587        gid: isize,
588    }
589
590    #[cfg(not(target_os = "redox"))]
591    #[pyfunction]
592    fn fchown(args: FchownArgs<'_>, vm: &VirtualMachine) -> PyResult<()> {
593        let FchownArgs { fd, uid, gid } = args;
594        chown_inner(
595            OsPathOrFd::Fd(fd.into()),
596            uid,
597            gid,
598            DirFd::default(),
599            FollowSymlinks(true),
600            vm,
601        )
602    }
603
604    #[derive(FromArgs)]
605    struct RegisterAtForkArgs {
606        #[pyarg(named, optional)]
607        before: OptionalArg<PyObjectRef>,
608        #[pyarg(named, optional)]
609        after_in_child: OptionalArg<PyObjectRef>,
610        #[pyarg(named, optional)]
611        after_in_parent: OptionalArg<PyObjectRef>,
612    }
613
614    impl RegisterAtForkArgs {
615        fn into_validated(
616            self,
617            vm: &VirtualMachine,
618        ) -> PyResult<(
619            Option<PyObjectRef>,
620            Option<PyObjectRef>,
621            Option<PyObjectRef>,
622        )> {
623            fn into_option(
624                arg: OptionalArg<PyObjectRef>,
625                vm: &VirtualMachine,
626            ) -> PyResult<Option<PyObjectRef>> {
627                match arg {
628                    OptionalArg::Present(obj) => {
629                        if !obj.is_callable() {
630                            return Err(vm.new_type_error("Args must be callable"));
631                        }
632                        Ok(Some(obj))
633                    }
634                    OptionalArg::Missing => Ok(None),
635                }
636            }
637            let before = into_option(self.before, vm)?;
638            let after_in_parent = into_option(self.after_in_parent, vm)?;
639            let after_in_child = into_option(self.after_in_child, vm)?;
640            if before.is_none() && after_in_parent.is_none() && after_in_child.is_none() {
641                return Err(vm.new_type_error("At least one arg must be present"));
642            }
643            Ok((before, after_in_parent, after_in_child))
644        }
645    }
646
647    #[pyfunction]
648    fn register_at_fork(args: RegisterAtForkArgs, vm: &VirtualMachine) -> PyResult<()> {
649        let (before, after_in_parent, after_in_child) = args.into_validated(vm)?;
650
651        if let Some(before) = before {
652            vm.state.before_forkers.lock().push(before);
653        }
654        if let Some(after_in_parent) = after_in_parent {
655            vm.state.after_forkers_parent.lock().push(after_in_parent);
656        }
657        if let Some(after_in_child) = after_in_child {
658            vm.state.after_forkers_child.lock().push(after_in_child);
659        }
660        Ok(())
661    }
662
663    fn run_at_forkers(mut funcs: Vec<PyObjectRef>, reversed: bool, vm: &VirtualMachine) {
664        if !funcs.is_empty() {
665            if reversed {
666                funcs.reverse();
667            }
668            for func in funcs {
669                if let Err(e) = func.call((), vm) {
670                    let exit = e.fast_isinstance(vm.ctx.exceptions.system_exit);
671                    vm.run_unraisable(e, Some("Exception ignored in".to_owned()), func);
672                    if exit {
673                        // Do nothing!
674                    }
675                }
676            }
677        }
678    }
679
680    fn py_os_before_fork(vm: &VirtualMachine) {
681        let before_forkers: Vec<PyObjectRef> = vm.state.before_forkers.lock().clone();
682        // functions must be executed in reversed order as they are registered
683        // only for before_forkers, refer: test_register_at_fork in test_posix
684
685        run_at_forkers(before_forkers, true, vm);
686
687        #[cfg(feature = "threading")]
688        crate::stdlib::_imp::acquire_imp_lock_for_fork(vm);
689
690        #[cfg(feature = "threading")]
691        vm.state.stop_the_world.stop_the_world(&vm.state);
692    }
693
694    fn py_os_after_fork_child(vm: &VirtualMachine) {
695        // The interpreter registry is reachable from every thread, so repair it
696        // before anything enumerates interpreters.
697        #[cfg(all(unix, feature = "threading"))]
698        unsafe {
699            crate::vm::runtime::reinit_after_fork()
700        };
701
702        #[cfg(feature = "threading")]
703        vm.state.stop_the_world.reset_after_fork();
704
705        // Phase 1: Reset all internal locks FIRST.
706        // After fork(), locks held by dead parent threads would deadlock
707        // if we try to acquire them. This must happen before anything else.
708        #[cfg(feature = "threading")]
709        reinit_locks_after_fork(vm);
710
711        // The collector stops every interpreter, so interpreters other than the
712        // forking one must be repaired too; otherwise the child's first
713        // collection waits for threads that did not survive the fork.
714        #[cfg(all(unix, feature = "threading"))]
715        reinit_other_interpreters_after_fork(vm);
716
717        // Reinit per-object IO buffer locks on std streams.
718        // BufferedReader/Writer/TextIOWrapper use PyThreadMutex which can be
719        // held by dead parent threads, causing deadlocks on any IO in the child.
720        #[cfg(feature = "threading")]
721        unsafe {
722            crate::stdlib::_io::reinit_std_streams_after_fork(vm)
723        };
724
725        // Phase 2: Reset low-level atomic state (no locks needed).
726        crate::signal::clear_after_fork();
727        crate::stdlib::_signal::_signal::clear_wakeup_fd_after_fork();
728
729        #[cfg(any(
730            target_os = "macos",
731            target_os = "ios",
732            target_os = "freebsd",
733            target_os = "netbsd",
734            target_os = "openbsd",
735            target_os = "dragonfly",
736        ))]
737        rustpython_host_env::select::kqueue::mark_closed_after_fork();
738
739        // Reset weakref stripe locks that may have been held during fork.
740        #[cfg(feature = "threading")]
741        crate::object::reset_weakref_locks_after_fork();
742
743        // Repair any type-cache entries left mid-update at fork time.
744        unsafe { crate::builtins::type_::type_cache_after_fork() };
745
746        // Reset QSBR: dead parent threads' slots would stall reclamation
747        // forever, and retired memory can be freed immediately in the
748        // single-threaded child.
749        #[cfg(feature = "threading")]
750        unsafe {
751            crate::object::qsbr::QSBR.reset_after_fork()
752        };
753
754        // Phase 3: Clean up thread state. Locks are now reinit'd so we can
755        // acquire them normally instead of using try_lock().
756        #[cfg(feature = "threading")]
757        crate::stdlib::_thread::after_fork_child(vm);
758
759        // Reinit import lock ownership metadata in child and release the lock
760        // acquired before fork.
761        #[cfg(feature = "threading")]
762        unsafe {
763            crate::stdlib::_imp::after_fork_child_imp_lock_release()
764        };
765
766        // Initialize signal handlers for the child's main thread.
767        // When forked from a worker thread, the OnceCell is empty.
768        vm.signal_handlers
769            .get_or_init(crate::signal::SignalHandlers::default);
770
771        // Phase 4: Run Python-level at-fork callbacks.
772        let after_forkers_child: Vec<PyObjectRef> = vm.state.after_forkers_child.lock().clone();
773        run_at_forkers(after_forkers_child, false, vm);
774    }
775
776    /// Reset all parking_lot-based locks in the interpreter state after fork().
777    ///
778    /// After fork(), only the calling thread survives. Any locks held by other
779    /// (now-dead) threads would cause deadlocks. We unconditionally reset them
780    /// to unlocked by zeroing the raw lock bytes.
781    #[cfg(all(unix, feature = "threading"))]
782    fn reinit_locks_after_fork(vm: &VirtualMachine) {
783        use rustpython_common::lock::reinit_mutex_after_fork;
784
785        unsafe {
786            // PyGlobalState PyMutex locks
787            reinit_mutex_after_fork(&vm.state.before_forkers);
788            reinit_mutex_after_fork(&vm.state.after_forkers_child);
789            reinit_mutex_after_fork(&vm.state.after_forkers_parent);
790            reinit_mutex_after_fork(&vm.state.atexit_funcs);
791            reinit_mutex_after_fork(&vm.state.global_trace_func);
792            reinit_mutex_after_fork(&vm.state.global_profile_func);
793            reinit_mutex_after_fork(&vm.state.type_mutex);
794            reinit_mutex_after_fork(&vm.state.monitoring);
795
796            // PyGlobalState parking_lot::Mutex locks
797            reinit_mutex_after_fork(&vm.state.thread_frames);
798            reinit_mutex_after_fork(&vm.state.thread_handles);
799            reinit_mutex_after_fork(&vm.state.shutdown_handles);
800
801            // Context-level RwLock
802            vm.ctx.string_pool.reinit_after_fork();
803
804            // Codec registry RwLock
805            vm.state.codec_registry.reinit_after_fork();
806
807            // GC state (multiple Mutex + RwLock), shared lists and this
808            // interpreter's own policy state.
809            crate::gc_state::gc_state().reinit_after_fork();
810            vm.state.gc.reinit_after_fork();
811
812            // Import lock (RawReentrantMutex<RawMutex, RawThreadId>)
813            crate::stdlib::_imp::reinit_imp_lock_after_fork();
814        }
815    }
816
817    /// Repair every live interpreter other than the forking one after `fork()`.
818    ///
819    /// Only the forking thread survives, so each other interpreter is left with
820    /// slots for threads that no longer exist (still ATTACHED if they were
821    /// running bytecode) and possibly locks or stop-the-world flags held by
822    /// them. Since a collection stops all interpreters, that state would hang
823    /// the child's first collection.
824    ///
825    /// # Safety
826    /// Must only be called after `fork()` in the child, when no other threads exist.
827    #[cfg(all(unix, feature = "threading"))]
828    fn reinit_other_interpreters_after_fork(vm: &VirtualMachine) {
829        use rustpython_common::lock::reinit_mutex_after_fork;
830
831        for state in crate::vm::runtime::live_interpreter_states() {
832            if state.interpreter_id == vm.state.interpreter_id {
833                continue;
834            }
835
836            unsafe {
837                reinit_mutex_after_fork(&state.before_forkers);
838                reinit_mutex_after_fork(&state.after_forkers_child);
839                reinit_mutex_after_fork(&state.after_forkers_parent);
840                reinit_mutex_after_fork(&state.atexit_funcs);
841                reinit_mutex_after_fork(&state.global_trace_func);
842                reinit_mutex_after_fork(&state.global_profile_func);
843                reinit_mutex_after_fork(&state.type_mutex);
844                reinit_mutex_after_fork(&state.monitoring);
845                reinit_mutex_after_fork(&state.thread_frames);
846                reinit_mutex_after_fork(&state.thread_handles);
847                reinit_mutex_after_fork(&state.shutdown_handles);
848
849                state.codec_registry.reinit_after_fork();
850                state.gc.reinit_after_fork();
851            }
852
853            state.stop_the_world.reset_after_fork();
854
855            // Every thread registered here belongs to the parent, including any
856            // slot the forking thread itself registered before the fork.
857            state.thread_frames.lock().clear();
858            state.thread_handles.lock().clear();
859            state.shutdown_handles.lock().clear();
860        }
861
862        crate::vm::thread::purge_other_interpreter_slots_after_fork(vm.state.interpreter_id);
863    }
864
865    fn py_os_after_fork_parent(vm: &VirtualMachine) {
866        #[cfg(feature = "threading")]
867        vm.state.stop_the_world.start_the_world(&vm.state);
868
869        #[cfg(feature = "threading")]
870        crate::stdlib::_imp::release_imp_lock_after_fork_parent();
871
872        let after_forkers_parent: Vec<PyObjectRef> = vm.state.after_forkers_parent.lock().clone();
873        run_at_forkers(after_forkers_parent, false, vm);
874    }
875
876    /// Best-effort number of OS threads in this process.
877    /// Returns <= 0 when unavailable.
878    fn get_number_of_os_threads() -> isize {
879        rustpython_host_env::posix::get_number_of_os_threads()
880    }
881
882    /// Warn if forking from a multi-threaded process.
883    /// `num_os_threads` should be captured before parent after-fork hooks run.
884    fn warn_if_multi_threaded(name: &str, num_os_threads: isize, vm: &VirtualMachine) {
885        let num_threads = if num_os_threads > 0 {
886            num_os_threads as usize
887        } else {
888            // CPython fallback: if OS-level count isn't available, use the
889            // threading module's active+limbo view.
890            // Only check threading if it was already imported. Avoid vm.import()
891            // which can execute arbitrary Python code in the fork path.
892            let threading = match vm
893                .sys_module
894                .get_attr("modules", vm)
895                .and_then(|m| m.get_item("threading", vm))
896            {
897                Ok(m) => m,
898                Err(_) => return,
899            };
900            let active = threading.get_attr("_active", vm).ok();
901            let limbo = threading.get_attr("_limbo", vm).ok();
902
903            // Match threading module internals and avoid sequence overcounting:
904            // count only dict-backed _active/_limbo containers.
905            let count_dict = |obj: Option<crate::PyObjectRef>| -> usize {
906                obj.and_then(|o| {
907                    o.downcast_ref::<crate::builtins::PyDict>()
908                        .map(|d| d.__len__())
909                })
910                .unwrap_or(0)
911            };
912
913            count_dict(active) + count_dict(limbo)
914        };
915
916        if num_threads > 1 {
917            let pid = rustpython_host_env::posix::getpid();
918            let msg = format!(
919                "This process (pid={pid}) is multi-threaded, use of {name}() may lead to deadlocks in the child."
920            );
921
922            // Match PyErr_WarnFormat(..., stacklevel=1) in CPython.
923            // Best effort: ignore failures like CPython does in this path.
924            let _ =
925                crate::stdlib::_warnings::warn(vm.ctx.exceptions.deprecation_warning, msg, 1, vm);
926        }
927    }
928
929    #[pyfunction]
930    fn fork(vm: &VirtualMachine) -> PyResult<i32> {
931        if vm
932            .state
933            .finalizing
934            .load(core::sync::atomic::Ordering::Acquire)
935        {
936            return Err(vm.new_exception_msg(
937                vm.ctx.exceptions.python_finalization_error.to_owned(),
938                "can't fork at interpreter shutdown".into(),
939            ));
940        }
941        if !vm.state.allow_fork() {
942            return Err(
943                vm.new_runtime_error("fork not supported for isolated subinterpreters".to_owned())
944            );
945        }
946
947        vm.audit("os.fork", || ())?;
948
949        py_os_before_fork(vm);
950        let pid = rustpython_host_env::posix::fork();
951
952        match pid {
953            Ok(0) => {
954                py_os_after_fork_child(vm);
955                Ok(0)
956            }
957            Ok(pid) => {
958                // Capture this before parent after-fork hooks in case those
959                // hooks start threads.
960                let num_os_threads = get_number_of_os_threads();
961                py_os_after_fork_parent(vm);
962                // Warn only after parent callback path resumes the world.
963                warn_if_multi_threaded("fork", num_os_threads, vm);
964                Ok(pid)
965            }
966            Err(err) => {
967                py_os_after_fork_parent(vm);
968                Err(err.into_pyexception(vm))
969            }
970        }
971    }
972
973    #[cfg(not(any(target_os = "redox", target_os = "wasi")))]
974    #[pyfunction]
975    fn forkpty(vm: &VirtualMachine) -> PyResult<(i32, i32)> {
976        if vm
977            .state
978            .finalizing
979            .load(core::sync::atomic::Ordering::Acquire)
980        {
981            return Err(vm.new_exception_msg(
982                vm.ctx.exceptions.python_finalization_error.to_owned(),
983                "can't fork at interpreter shutdown".into(),
984            ));
985        }
986        if !vm.state.allow_fork() {
987            return Err(
988                vm.new_runtime_error("fork not supported for isolated subinterpreters".to_owned())
989            );
990        }
991
992        vm.audit("os.forkpty", || ())?;
993
994        py_os_before_fork(vm);
995        let result = rustpython_host_env::posix::forkpty();
996
997        match result {
998            Ok((0, master)) => {
999                py_os_after_fork_child(vm);
1000                Ok((0, master))
1001            }
1002            Ok((pid, master)) => {
1003                let num_os_threads = get_number_of_os_threads();
1004                py_os_after_fork_parent(vm);
1005                warn_if_multi_threaded("forkpty", num_os_threads, vm);
1006                Ok((pid, master))
1007            }
1008            Err(err) => {
1009                py_os_after_fork_parent(vm);
1010                Err(err.into_pyexception(vm))
1011            }
1012        }
1013    }
1014
1015    #[cfg(not(target_os = "redox"))]
1016    const MKNOD_DIR_FD: bool = cfg!(not(target_vendor = "apple"));
1017
1018    #[cfg(not(target_os = "redox"))]
1019    #[derive(FromArgs)]
1020    struct MknodArgs<'fd> {
1021        #[pyarg(any)]
1022        path: OsPath,
1023        #[pyarg(any, default = 0o600)]
1024        mode: libc::mode_t,
1025        #[pyarg(any, default = 0)]
1026        device: libc::dev_t,
1027        #[pyarg(flatten)]
1028        dir_fd: DirFd<'fd, { MKNOD_DIR_FD as usize }>,
1029    }
1030
1031    #[cfg(not(target_os = "redox"))]
1032    impl MknodArgs<'_> {
1033        #[cfg(not(target_vendor = "apple"))]
1034        fn mknod(self, vm: &VirtualMachine) -> PyResult<()> {
1035            let c_path = self.path.clone().into_cstring(vm)?;
1036            match self.dir_fd.raw_opt() {
1037                None => rustpython_host_env::posix::mknod(&c_path, self.mode, self.device),
1038                Some(non_default_fd) => rustpython_host_env::posix::mknodat(
1039                    non_default_fd,
1040                    &c_path,
1041                    self.mode,
1042                    self.device,
1043                ),
1044            }
1045            .map_err(|err| err.into_pyexception(vm))
1046        }
1047
1048        #[cfg(target_vendor = "apple")]
1049        fn mknod(self, vm: &VirtualMachine) -> PyResult<()> {
1050            let [] = self.dir_fd.0;
1051            let c_path = self.path.clone().into_cstring(vm)?;
1052            rustpython_host_env::posix::mknod(&c_path, self.mode, self.device)
1053                .map_err(|err| err.into_pyexception(vm))
1054        }
1055    }
1056
1057    #[cfg(not(target_os = "redox"))]
1058    #[pyfunction]
1059    fn mknod(args: MknodArgs<'_>, vm: &VirtualMachine) -> PyResult<()> {
1060        args.mknod(vm)
1061    }
1062
1063    #[cfg(not(target_os = "redox"))]
1064    #[pyfunction]
1065    fn nice(increment: i32, vm: &VirtualMachine) -> PyResult<i32> {
1066        rustpython_host_env::posix::nice(increment).map_err(|err| err.into_pyexception(vm))
1067    }
1068
1069    #[cfg(not(target_os = "redox"))]
1070    #[derive(FromArgs)]
1071    struct SchedPolicyArgs {
1072        #[pyarg(any)]
1073        policy: i32,
1074    }
1075
1076    #[cfg(not(target_os = "redox"))]
1077    #[pyfunction]
1078    fn sched_get_priority_max(policy: SchedPolicyArgs, vm: &VirtualMachine) -> PyResult<i32> {
1079        rustpython_host_env::posix::sched_get_priority_max(policy.policy)
1080            .map_err(|err| err.into_pyexception(vm))
1081    }
1082
1083    #[cfg(not(target_os = "redox"))]
1084    #[pyfunction]
1085    fn sched_get_priority_min(policy: SchedPolicyArgs, vm: &VirtualMachine) -> PyResult<i32> {
1086        rustpython_host_env::posix::sched_get_priority_min(policy.policy)
1087            .map_err(|err| err.into_pyexception(vm))
1088    }
1089
1090    #[pyfunction]
1091    fn sched_yield(vm: &VirtualMachine) -> PyResult<()> {
1092        rustpython_host_env::posix::sched_yield().map_err(|e| e.into_pyexception(vm))
1093    }
1094
1095    #[pyfunction]
1096    fn get_inheritable(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult<bool> {
1097        rustpython_host_env::fcntl::get_inheritable(fd).map_err(|err| err.into_pyexception(vm))
1098    }
1099
1100    #[pyfunction]
1101    fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> {
1102        super::set_inheritable(fd, inheritable).map_err(|err| err.into_pyexception(vm))
1103    }
1104
1105    #[pyfunction]
1106    fn get_blocking(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult<bool> {
1107        rustpython_host_env::fcntl::get_blocking(fd).map_err(|err| err.into_pyexception(vm))
1108    }
1109
1110    #[pyfunction]
1111    fn set_blocking(fd: BorrowedFd<'_>, blocking: bool, vm: &VirtualMachine) -> PyResult<()> {
1112        rustpython_host_env::fcntl::set_blocking(fd, blocking)
1113            .map_err(|err| err.into_pyexception(vm))
1114    }
1115
1116    #[pyfunction]
1117    fn pread(
1118        fd: i32,
1119        length: PySsize,
1120        offset: libc::off_t,
1121        vm: &VirtualMachine,
1122    ) -> PyResult<PyBytesRef> {
1123        let (fd, n, offset) = (fd, length, offset);
1124        if n < 0 {
1125            return Err(io::Error::from_raw_os_error(libc::EINVAL).into_pyexception(vm));
1126        }
1127        let mut buffer = vm.new_zeroed_bytes(n as usize)?;
1128        loop {
1129            match vm.allow_threads(|| rustpython_host_env::posix::pread(fd, &mut buffer, offset)) {
1130                Ok(n) => {
1131                    buffer.truncate(n);
1132                    return Ok(vm.ctx.new_bytes(buffer));
1133                }
1134                Err(err) if err.raw_os_error() == Some(libc::EINTR) => {
1135                    vm.check_signals()?;
1136                    continue;
1137                }
1138                Err(err) => return Err(err.into_pyexception(vm)),
1139            }
1140        }
1141    }
1142
1143    #[pyfunction]
1144    fn pwrite(
1145        fd: i32,
1146        buffer: ArgBytesLike,
1147        offset: libc::off_t,
1148        vm: &VirtualMachine,
1149    ) -> PyResult<usize> {
1150        // Avoid holding a buffer lock across blocking I/O,
1151        // which can prevent other threads from reaching GC safepoints.
1152        let data = buffer.borrow_buf_unlocked(vm)?;
1153        loop {
1154            match vm.allow_threads(|| rustpython_host_env::posix::pwrite(fd, &data, offset)) {
1155                Ok(n) => return Ok(n),
1156                Err(err) if err.raw_os_error() == Some(libc::EINTR) => {
1157                    vm.check_signals()?;
1158                    continue;
1159                }
1160                Err(err) => return Err(err.into_pyexception(vm)),
1161            }
1162        }
1163    }
1164
1165    #[pyfunction]
1166    fn pipe(vm: &VirtualMachine) -> PyResult<(OwnedFd, OwnedFd)> {
1167        rustpython_host_env::posix::pipe().map_err(|err| err.into_pyexception(vm))
1168    }
1169
1170    // cfg from nix
1171    #[cfg(any(
1172        target_os = "android",
1173        target_os = "dragonfly",
1174        target_os = "emscripten",
1175        target_os = "freebsd",
1176        target_os = "linux",
1177        target_os = "netbsd",
1178        target_os = "openbsd"
1179    ))]
1180    #[pyfunction]
1181    fn pipe2(flags: libc::c_int, vm: &VirtualMachine) -> PyResult<(OwnedFd, OwnedFd)> {
1182        rustpython_host_env::posix::pipe2(flags).map_err(|err| err.into_pyexception(vm))
1183    }
1184
1185    fn _chmod(
1186        path: OsPath,
1187        dir_fd: DirFd<'_, 0>,
1188        mode: u32,
1189        follow_symlinks: FollowSymlinks,
1190        vm: &VirtualMachine,
1191    ) -> PyResult<()> {
1192        let [] = dir_fd.0;
1193        #[cfg(all(
1194            unix,
1195            not(target_os = "redox"),
1196            not(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd"))
1197        ))]
1198        if !follow_symlinks.0 {
1199            let err_path = path.clone();
1200            let c_path = path.into_cstring(vm)?;
1201            return rustpython_host_env::posix::fchmodat(
1202                libc::AT_FDCWD,
1203                &c_path,
1204                mode as libc::mode_t,
1205                libc::AT_SYMLINK_NOFOLLOW,
1206            )
1207            .map_err(|err| {
1208                let enotsup = err.raw_os_error() == Some(libc::EOPNOTSUPP)
1209                    || err.raw_os_error() == Some(libc::ENOTSUP);
1210                if enotsup {
1211                    vm.new_not_implemented_error(
1212                        "chmod: follow_symlinks unavailable on this platform".to_owned(),
1213                    )
1214                } else {
1215                    OSErrorBuilder::with_filename(&err, err_path, vm)
1216                }
1217            });
1218        }
1219        let err_path = path.clone();
1220        let body = move || {
1221            use std::os::unix::fs::PermissionsExt;
1222            let meta = fs_metadata(&path, follow_symlinks.0)?;
1223            let mut permissions = meta.permissions();
1224            permissions.set_mode(mode);
1225            fs::set_permissions(&path, permissions)
1226        };
1227        body().map_err(|err| OSErrorBuilder::with_filename(&err, err_path, vm))
1228    }
1229
1230    #[cfg(not(target_os = "redox"))]
1231    fn _fchmod(fd: BorrowedFd<'_>, mode: u32, vm: &VirtualMachine) -> PyResult<()> {
1232        rustpython_host_env::posix::fchmod(fd, mode).map_err(|err| err.into_pyexception(vm))
1233    }
1234
1235    #[cfg(not(target_os = "redox"))]
1236    #[pyfunction]
1237    fn chmod(
1238        path: OsPathOrFd<'_>,
1239        dir_fd: DirFd<'_, 0>,
1240        mode: u32,
1241        follow_symlinks: FollowSymlinks,
1242        vm: &VirtualMachine,
1243    ) -> PyResult<()> {
1244        match path {
1245            OsPathOrFd::Path(path) => {
1246                #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd",))]
1247                if !follow_symlinks.0 && dir_fd == Default::default() {
1248                    return lchmod(LchmodArgs { path, mode }, vm);
1249                }
1250                _chmod(path, dir_fd, mode, follow_symlinks, vm)
1251            }
1252            OsPathOrFd::Fd(fd) => _fchmod(fd.into(), mode, vm),
1253        }
1254    }
1255
1256    #[cfg(target_os = "redox")]
1257    #[pyfunction]
1258    fn chmod(
1259        path: OsPath,
1260        dir_fd: DirFd<0>,
1261        mode: u32,
1262        follow_symlinks: FollowSymlinks,
1263        vm: &VirtualMachine,
1264    ) -> PyResult<()> {
1265        _chmod(path, dir_fd, mode, follow_symlinks, vm)
1266    }
1267
1268    #[cfg(not(target_os = "redox"))]
1269    #[derive(FromArgs)]
1270    struct FchmodArgs<'a> {
1271        #[pyarg(any)]
1272        fd: BorrowedFd<'a>,
1273        #[pyarg(any)]
1274        mode: u32,
1275    }
1276
1277    #[cfg(not(target_os = "redox"))]
1278    #[pyfunction]
1279    fn fchmod(args: FchmodArgs<'_>, vm: &VirtualMachine) -> PyResult<()> {
1280        _fchmod(args.fd, args.mode, vm)
1281    }
1282
1283    #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd",))]
1284    #[derive(FromArgs)]
1285    struct LchmodArgs {
1286        #[pyarg(any)]
1287        path: OsPath,
1288        #[pyarg(any)]
1289        mode: u32,
1290    }
1291
1292    #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd",))]
1293    #[pyfunction]
1294    fn lchmod(args: LchmodArgs, vm: &VirtualMachine) -> PyResult<()> {
1295        let LchmodArgs { path, mode } = args;
1296        let c_path = path.clone().into_cstring(vm)?;
1297        rustpython_host_env::posix::lchmod(&c_path, mode as libc::mode_t)
1298            .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
1299    }
1300
1301    #[pyfunction]
1302    fn execv(
1303        path: OsPath,
1304        argv: Either<PyListRef, PyTupleRef>,
1305        vm: &VirtualMachine,
1306    ) -> PyResult<()> {
1307        if !vm.state.allow_exec() {
1308            return Err(
1309                vm.new_runtime_error("exec not supported for isolated subinterpreters".to_owned())
1310            );
1311        }
1312        let path = path.into_cstring(vm)?;
1313
1314        let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
1315            OsPath::try_from_object(vm, obj)?.into_cstring(vm)
1316        })?;
1317        let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect();
1318
1319        let first = argv
1320            .first()
1321            .ok_or_else(|| vm.new_value_error("execv() arg 2 must not be empty"))?;
1322        if first.to_bytes().is_empty() {
1323            return Err(vm.new_value_error("execv() arg 2 first element cannot be empty"));
1324        }
1325
1326        rustpython_host_env::posix::execv(&path, &argv).map_err(|err| err.into_pyexception(vm))
1327    }
1328
1329    #[derive(FromArgs)]
1330    struct ExecveArgs {
1331        #[pyarg(any)]
1332        path: OsPath,
1333        #[pyarg(any)]
1334        argv: Either<PyListRef, PyTupleRef>,
1335        #[pyarg(any)]
1336        env: ArgMapping,
1337    }
1338
1339    #[pyfunction]
1340    fn execve(args: ExecveArgs, vm: &VirtualMachine) -> PyResult<()> {
1341        let ExecveArgs { path, argv, env } = args;
1342        if !vm.state.allow_exec() {
1343            return Err(
1344                vm.new_runtime_error("exec not supported for isolated subinterpreters".to_owned())
1345            );
1346        }
1347        let path = path.into_cstring(vm)?;
1348
1349        let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
1350            OsPath::try_from_object(vm, obj)?.into_cstring(vm)
1351        })?;
1352        let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect();
1353
1354        let first = argv
1355            .first()
1356            .ok_or_else(|| vm.new_value_error("execve() arg 2 must not be empty"))?;
1357
1358        if first.to_bytes().is_empty() {
1359            return Err(vm.new_value_error("execve() arg 2 first element cannot be empty"));
1360        }
1361
1362        let env = crate::stdlib::os::envobj_to_dict(env, vm)?;
1363        let env = env
1364            .into_iter()
1365            .map(|(k, v)| -> PyResult<_> {
1366                let (key, value) = (
1367                    OsPath::try_from_object(vm, k)?.into_bytes(),
1368                    OsPath::try_from_object(vm, v)?.into_bytes(),
1369                );
1370
1371                if key.is_empty() || memchr::memchr(b'=', &key).is_some() {
1372                    return Err(vm.new_value_error("illegal environment variable name"));
1373                }
1374
1375                let mut entry = key;
1376                entry.push(b'=');
1377                entry.extend_from_slice(&value);
1378
1379                CString::new(entry).map_err(|err| err.into_pyexception(vm))
1380            })
1381            .collect::<Result<Vec<_>, _>>()?;
1382
1383        let env: Vec<&CStr> = env.iter().map(|entry| entry.as_c_str()).collect();
1384
1385        rustpython_host_env::posix::execve(&path, &argv, &env)
1386            .map_err(|err| err.into_pyexception(vm))?;
1387        Ok(())
1388    }
1389
1390    #[pyfunction]
1391    fn getppid(vm: &VirtualMachine) -> PyObjectRef {
1392        let ppid = rustpython_host_env::posix::getppid();
1393        vm.ctx.new_int(ppid).into()
1394    }
1395
1396    #[pyfunction]
1397    fn getgid(vm: &VirtualMachine) -> PyObjectRef {
1398        let gid = rustpython_host_env::posix::getgid();
1399        vm.ctx.new_int(gid).into()
1400    }
1401
1402    #[pyfunction]
1403    fn getegid(vm: &VirtualMachine) -> PyObjectRef {
1404        let egid = rustpython_host_env::posix::getegid();
1405        vm.ctx.new_int(egid).into()
1406    }
1407
1408    #[derive(FromArgs)]
1409    struct GetPgidArgs {
1410        #[pyarg(any)]
1411        pid: u32,
1412    }
1413
1414    #[pyfunction]
1415    fn getpgid(pid: GetPgidArgs, vm: &VirtualMachine) -> PyResult {
1416        let pid = pid.pid;
1417        let pgid = rustpython_host_env::posix::getpgid(pid).map_err(|e| e.into_pyexception(vm))?;
1418        Ok(vm.new_pyobj(pgid))
1419    }
1420
1421    #[pyfunction]
1422    fn getpgrp(vm: &VirtualMachine) -> PyObjectRef {
1423        vm.ctx.new_int(rustpython_host_env::posix::getpgrp()).into()
1424    }
1425
1426    #[cfg(not(target_os = "redox"))]
1427    #[pyfunction]
1428    fn getsid(pid: u32, vm: &VirtualMachine) -> PyResult {
1429        let sid = rustpython_host_env::posix::getsid(pid).map_err(|e| e.into_pyexception(vm))?;
1430        Ok(vm.new_pyobj(sid))
1431    }
1432
1433    #[pyfunction]
1434    fn getuid(vm: &VirtualMachine) -> PyObjectRef {
1435        let uid = rustpython_host_env::posix::getuid();
1436        vm.ctx.new_int(uid).into()
1437    }
1438
1439    #[pyfunction]
1440    fn geteuid(vm: &VirtualMachine) -> PyObjectRef {
1441        let euid = rustpython_host_env::posix::geteuid();
1442        vm.ctx.new_int(euid).into()
1443    }
1444
1445    #[cfg(not(any(target_os = "wasi", target_os = "android")))]
1446    #[pyfunction]
1447    fn setgid(gid: RawGid, vm: &VirtualMachine) -> PyResult<()> {
1448        rustpython_host_env::posix::setgid(gid.0).map_err(|err| err.into_pyexception(vm))
1449    }
1450
1451    #[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))]
1452    #[pyfunction]
1453    fn setegid(egid: RawGid, vm: &VirtualMachine) -> PyResult<()> {
1454        rustpython_host_env::posix::setegid(egid.0).map_err(|err| err.into_pyexception(vm))
1455    }
1456
1457    #[pyfunction]
1458    fn setpgid(pid: u32, pgrp: u32, vm: &VirtualMachine) -> PyResult<()> {
1459        rustpython_host_env::posix::setpgid(pid, pgrp).map_err(|err| err.into_pyexception(vm))
1460    }
1461
1462    #[pyfunction]
1463    fn setpgrp(vm: &VirtualMachine) -> PyResult<()> {
1464        // setpgrp() is equivalent to setpgid(0, 0)
1465        rustpython_host_env::posix::setpgrp().map_err(|err| err.into_pyexception(vm))
1466    }
1467
1468    #[cfg(not(any(target_os = "wasi", target_os = "redox")))]
1469    #[pyfunction]
1470    fn setsid(vm: &VirtualMachine) -> PyResult<()> {
1471        rustpython_host_env::posix::setsid().map_err(|err| err.into_pyexception(vm))
1472    }
1473
1474    #[cfg(not(any(target_os = "wasi", target_os = "redox")))]
1475    #[pyfunction]
1476    fn tcgetpgrp(fd: i32, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
1477        use std::os::fd::BorrowedFd;
1478        let fd = unsafe { BorrowedFd::borrow_raw(fd) };
1479        rustpython_host_env::posix::tcgetpgrp(fd).map_err(|err| err.into_pyexception(vm))
1480    }
1481
1482    #[cfg(not(any(target_os = "wasi", target_os = "redox")))]
1483    #[pyfunction]
1484    fn tcsetpgrp(fd: i32, pgid: libc::pid_t, vm: &VirtualMachine) -> PyResult<()> {
1485        use std::os::fd::BorrowedFd;
1486        let fd = unsafe { BorrowedFd::borrow_raw(fd) };
1487        rustpython_host_env::posix::tcsetpgrp(fd, pgid).map_err(|err| err.into_pyexception(vm))
1488    }
1489
1490    fn try_from_id(vm: &VirtualMachine, obj: &PyObject, typ_name: &str) -> PyResult<u32> {
1491        use core::cmp::Ordering;
1492        let i = obj
1493            .try_to_ref::<PyInt>(vm)
1494            .map_err(|_| {
1495                vm.new_type_error(format!(
1496                    "an integer is required (got type {})",
1497                    obj.class().name()
1498                ))
1499            })?
1500            .try_to_primitive::<i64>(vm)?;
1501
1502        match i.cmp(&-1) {
1503            Ordering::Greater => Ok(i.try_into().map_err(|_| {
1504                vm.new_overflow_error(format!("{typ_name} is larger than maximum"))
1505            })?),
1506            Ordering::Less => {
1507                Err(vm.new_overflow_error(format!("{typ_name} is less than minimum")))
1508            }
1509            // -1 means does not change the value
1510            // In CPython, this is `(uid_t) -1`, rustc gets mad when we try to declare
1511            // a negative unsigned integer :).
1512            Ordering::Equal => Ok(-1i32 as u32),
1513        }
1514    }
1515
1516    #[derive(Clone, Copy)]
1517    struct RawUid(u32);
1518
1519    #[derive(Clone, Copy)]
1520    struct RawGid(u32);
1521
1522    impl TryFromObject for RawUid {
1523        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
1524            try_from_id(vm, &obj, "uid").map(Self)
1525        }
1526    }
1527
1528    impl TryFromObject for RawGid {
1529        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
1530            try_from_id(vm, &obj, "gid").map(Self)
1531        }
1532    }
1533
1534    #[cfg(not(any(target_os = "wasi", target_os = "android")))]
1535    #[pyfunction]
1536    fn setuid(uid: RawUid, vm: &VirtualMachine) -> PyResult<()> {
1537        rustpython_host_env::posix::setuid(uid.0).map_err(|err| err.into_pyexception(vm))
1538    }
1539
1540    #[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))]
1541    #[pyfunction]
1542    fn seteuid(euid: RawUid, vm: &VirtualMachine) -> PyResult<()> {
1543        rustpython_host_env::posix::seteuid(euid.0).map_err(|err| err.into_pyexception(vm))
1544    }
1545
1546    #[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))]
1547    #[pyfunction]
1548    fn setreuid(ruid: RawUid, euid: RawUid, vm: &VirtualMachine) -> PyResult<()> {
1549        rustpython_host_env::posix::setreuid(ruid.0, euid.0).map_err(|err| err.into_pyexception(vm))
1550    }
1551
1552    // cfg from nix
1553    #[cfg(any(
1554        target_os = "android",
1555        target_os = "freebsd",
1556        target_os = "linux",
1557        target_os = "openbsd"
1558    ))]
1559    #[pyfunction]
1560    fn setresuid(ruid: RawUid, euid: RawUid, suid: RawUid, vm: &VirtualMachine) -> PyResult<()> {
1561        rustpython_host_env::posix::setresuid(ruid.0, euid.0, suid.0)
1562            .map_err(|err| err.into_pyexception(vm))
1563    }
1564
1565    #[cfg(not(target_os = "wasi"))]
1566    #[pyfunction]
1567    fn login_tty(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult<()> {
1568        rustpython_host_env::posix::login_tty(fd.as_raw_fd())
1569            .map_err(|err| err.into_pyexception(vm))
1570    }
1571
1572    #[cfg(not(target_os = "redox"))]
1573    #[pyfunction]
1574    fn openpty(vm: &VirtualMachine) -> PyResult<(OwnedFd, OwnedFd)> {
1575        rustpython_host_env::posix::openpty().map_err(|err| err.into_pyexception(vm))
1576    }
1577
1578    #[pyfunction]
1579    fn ttyname(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult {
1580        let name = rustpython_host_env::posix::ttyname(fd).map_err(|e| e.into_pyexception(vm))?;
1581        let name = name.into_string().unwrap();
1582        Ok(vm.ctx.new_str(name).into())
1583    }
1584
1585    #[pyfunction]
1586    fn umask(mask: libc::mode_t) -> libc::mode_t {
1587        rustpython_host_env::posix::umask(mask)
1588    }
1589
1590    #[pyfunction]
1591    fn uname(vm: &VirtualMachine) -> PyResult<_os::UnameResultData> {
1592        let info = rustpython_host_env::posix::uname_info().map_err(|err| {
1593            let start = err.error.valid_up_to();
1594            let end = err
1595                .error
1596                .error_len()
1597                .map_or(err.bytes.len(), |len| start + len);
1598            vm.new_unicode_decode_error(
1599                vm.ctx.new_str("utf-8"),
1600                vm.ctx.new_bytes(err.bytes),
1601                start,
1602                end,
1603                vm.ctx.new_str(err.error.to_string()),
1604            )
1605        })?;
1606        Ok(_os::UnameResultData {
1607            sysname: info.sysname,
1608            nodename: info.nodename,
1609            release: info.release,
1610            version: info.version,
1611            machine: info.machine,
1612        })
1613    }
1614
1615    #[pyfunction]
1616    fn sync() {
1617        #[cfg(not(any(target_os = "redox", target_os = "android")))]
1618        rustpython_host_env::posix::sync();
1619    }
1620
1621    // cfg from nix
1622    #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))]
1623    #[pyfunction]
1624    fn getresuid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> {
1625        rustpython_host_env::posix::getresuid().map_err(|err| err.into_pyexception(vm))
1626    }
1627
1628    // cfg from nix
1629    #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))]
1630    #[pyfunction]
1631    fn getresgid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> {
1632        rustpython_host_env::posix::getresgid().map_err(|err| err.into_pyexception(vm))
1633    }
1634
1635    // cfg from nix
1636    #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
1637    #[pyfunction]
1638    fn setresgid(rgid: RawGid, egid: RawGid, sgid: RawGid, vm: &VirtualMachine) -> PyResult<()> {
1639        rustpython_host_env::posix::setresgid(rgid.0, egid.0, sgid.0)
1640            .map_err(|err| err.into_pyexception(vm))
1641    }
1642
1643    #[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))]
1644    #[pyfunction]
1645    fn setregid(rgid: RawGid, egid: RawGid, vm: &VirtualMachine) -> PyResult<()> {
1646        rustpython_host_env::posix::setregid(rgid.0, egid.0).map_err(|err| err.into_pyexception(vm))
1647    }
1648
1649    // cfg from nix
1650    #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
1651    #[pyfunction]
1652    fn initgroups(username: PyUtf8StrRef, gid: RawGid, vm: &VirtualMachine) -> PyResult<()> {
1653        let user = username.to_cstring(vm)?;
1654        rustpython_host_env::posix::initgroups(&user, gid.0).map_err(|err| err.into_pyexception(vm))
1655    }
1656
1657    // cfg from nix
1658    #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
1659    #[pyfunction]
1660    fn setgroups(groups: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
1661        groups
1662            .try_sequence(vm)
1663            .map_err(|_| vm.new_type_error("setgroups argument must be a sequence"))?;
1664        let gids = vm.extract_elements_with(&groups, |gid| {
1665            RawGid::try_from_object(vm, gid).map(|gid| gid.0)
1666        })?;
1667        rustpython_host_env::posix::setgroups_raw(&gids).map_err(|err| err.into_pyexception(vm))
1668    }
1669
1670    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1671    fn envp_from_dict(
1672        env: crate::function::ArgMapping,
1673        vm: &VirtualMachine,
1674    ) -> PyResult<Vec<CString>> {
1675        let items = env.mapping().items(vm)?;
1676
1677        // Convert items to list if it isn't already
1678        let items = vm.ctx.new_list(
1679            items
1680                .get_iter(vm)?
1681                .iter(vm)?
1682                .collect::<PyResult<Vec<_>>>()?,
1683        );
1684
1685        items
1686            .borrow_vec()
1687            .iter()
1688            .map(|item| {
1689                let tuple = item
1690                    .downcast_ref::<crate::builtins::PyTuple>()
1691                    .ok_or_else(|| vm.new_type_error("items() should return tuples"))?;
1692                let tuple_items = tuple.as_slice();
1693                if tuple_items.len() != 2 {
1694                    return Err(vm.new_value_error("items() tuples should have exactly 2 elements"));
1695                }
1696                Ok((tuple_items[0].clone(), tuple_items[1].clone()))
1697            })
1698            .collect::<PyResult<Vec<_>>>()?
1699            .into_iter()
1700            .map(|(k, v)| {
1701                let k = OsPath::try_from_object(vm, k)?.into_bytes();
1702                let v = OsPath::try_from_object(vm, v)?.into_bytes();
1703                if k.contains(&0) {
1704                    return Err(vm.new_value_error("envp dict key cannot contain a nul byte"));
1705                }
1706                if k.contains(&b'=') {
1707                    return Err(vm.new_value_error("envp dict key cannot contain a '=' character"));
1708                }
1709                if v.contains(&0) {
1710                    return Err(vm.new_value_error("envp dict value cannot contain a nul byte"));
1711                }
1712                let mut env = k;
1713                env.push(b'=');
1714                env.extend(v);
1715                Ok(unsafe { CString::from_vec_unchecked(env) })
1716            })
1717            .collect()
1718    }
1719
1720    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1721    #[derive(FromArgs)]
1722    pub(super) struct PosixSpawnArgs {
1723        #[pyarg(positional)]
1724        path: OsPath,
1725        #[pyarg(positional)]
1726        argv: PyObjectRef,
1727        #[pyarg(positional)]
1728        env: Option<crate::function::ArgMapping>,
1729        #[pyarg(named, default, py_default = "()")]
1730        file_actions: Option<crate::function::ArgIterable<PyTupleRef>>,
1731        #[pyarg(named, optional)]
1732        setpgroup: Option<libc::pid_t>,
1733        #[pyarg(named, default)]
1734        resetids: bool,
1735        #[pyarg(named, default)]
1736        setsid: bool,
1737        #[pyarg(named, default, py_default = "()")]
1738        setsigmask: Option<crate::function::ArgIterable<i32>>,
1739        #[pyarg(named, default, py_default = "()")]
1740        setsigdef: Option<crate::function::ArgIterable<i32>>,
1741        #[pyarg(named, optional)]
1742        scheduler: Option<PyObjectRef>,
1743    }
1744
1745    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1746    #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
1747    #[repr(i32)]
1748    enum PosixSpawnFileActionIdentifier {
1749        Open,
1750        Close,
1751        Dup2,
1752    }
1753
1754    #[cfg(all(
1755        any(target_os = "linux", target_os = "freebsd", target_os = "android"),
1756        not(target_env = "musl")
1757    ))]
1758    fn parse_posix_spawn_scheduler(
1759        scheduler: Option<PyObjectRef>,
1760        vm: &VirtualMachine,
1761    ) -> PyResult<Option<rustpython_host_env::posix::PosixSpawnScheduler>> {
1762        let Some(obj) = scheduler else {
1763            return Ok(None);
1764        };
1765        if obj.is(&vm.ctx.none()) {
1766            return Ok(None);
1767        }
1768        let Some(tuple) = obj.downcast_ref::<PyTuple>() else {
1769            return Err(vm.new_type_error("scheduler must be a tuple or None"));
1770        };
1771        if tuple.as_slice().len() != 2 {
1772            return Err(vm.new_type_error("A scheduler tuple must have two elements"));
1773        }
1774        let policy = if tuple.as_slice()[0].is(&vm.ctx.none()) {
1775            None
1776        } else {
1777            Some(i32::try_from_object(vm, tuple.as_slice()[0].clone())?)
1778        };
1779        let param = super::posix_sched::convert_sched_param(&tuple.as_slice()[1], vm)?;
1780        Ok(Some(rustpython_host_env::posix::PosixSpawnScheduler {
1781            policy,
1782            param,
1783        }))
1784    }
1785
1786    #[cfg(any(target_os = "macos", target_env = "musl"))]
1787    fn parse_posix_spawn_scheduler(
1788        scheduler: Option<PyObjectRef>,
1789        vm: &VirtualMachine,
1790    ) -> PyResult<()> {
1791        let Some(obj) = scheduler else {
1792            return Ok(());
1793        };
1794        if obj.is(&vm.ctx.none()) {
1795            return Ok(());
1796        }
1797        let Some(tuple) = obj.downcast_ref::<PyTuple>() else {
1798            return Err(vm.new_type_error("scheduler must be a tuple or None"));
1799        };
1800        if tuple.as_slice().len() != 2 {
1801            return Err(vm.new_type_error("A scheduler tuple must have two elements"));
1802        }
1803        Err(vm.new_not_implemented_error("The scheduler option is not supported in this system."))
1804    }
1805
1806    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1807    impl PosixSpawnArgs {
1808        fn spawn(self, spawnp: bool, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
1809            use crate::TryFromBorrowedObject;
1810
1811            let path = self
1812                .path
1813                .clone()
1814                .into_cstring(vm)
1815                .map_err(|_| vm.new_value_error("path should not have nul bytes"))?;
1816
1817            let function_name = if spawnp {
1818                "posix_spawnp"
1819            } else {
1820                "posix_spawn"
1821            };
1822            if !self.argv.fast_isinstance(vm.ctx.types.list_type)
1823                && !self.argv.fast_isinstance(vm.ctx.types.tuple_type)
1824            {
1825                return Err(
1826                    vm.new_type_error(format!("{function_name}: argv must be a tuple or list"))
1827                );
1828            }
1829
1830            let mut file_actions = Vec::new();
1831            if let Some(it) = self.file_actions {
1832                for action in it.iter(vm)? {
1833                    let action = action?;
1834                    let (id, args) = action.as_slice().split_first().ok_or_else(|| {
1835                        vm.new_type_error("Each file_actions element must be a non-empty tuple")
1836                    })?;
1837                    let id = i32::try_from_borrowed_object(vm, id)?;
1838                    let id = PosixSpawnFileActionIdentifier::try_from(id)
1839                        .map_err(|_| vm.new_type_error("Unknown file_actions identifier"))?;
1840                    let args: crate::function::FuncArgs = args.to_vec().into();
1841                    let parsed = match id {
1842                        PosixSpawnFileActionIdentifier::Open => {
1843                            let (fd, path, oflag, mode): (_, OsPath, _, _) = args.bind(vm)?;
1844                            let path = CString::new(path.into_bytes()).map_err(|_| {
1845                                vm.new_value_error(
1846                                    "POSIX_SPAWN_OPEN path should not have nul bytes",
1847                                )
1848                            })?;
1849                            rustpython_host_env::posix::PosixSpawnFileAction::Open {
1850                                fd,
1851                                path,
1852                                oflag,
1853                                mode,
1854                            }
1855                        }
1856                        PosixSpawnFileActionIdentifier::Close => {
1857                            let (fd,) = args.bind(vm)?;
1858                            rustpython_host_env::posix::PosixSpawnFileAction::Close { fd }
1859                        }
1860                        PosixSpawnFileActionIdentifier::Dup2 => {
1861                            let (fd, newfd) = args.bind(vm)?;
1862                            rustpython_host_env::posix::PosixSpawnFileAction::Dup2 { fd, newfd }
1863                        }
1864                    };
1865                    file_actions.push(parsed);
1866                }
1867            }
1868
1869            let collect_signals = |sigs: crate::function::ArgIterable<i32>| {
1870                let mut collected = Vec::new();
1871                for sig in sigs.iter(vm)? {
1872                    let sig = sig?;
1873                    if !rustpython_host_env::posix::validate_posix_spawn_signal(sig) {
1874                        return Err(vm.new_value_error(format!("signal number {sig} out of range")));
1875                    }
1876                    if !collected.contains(&sig) {
1877                        collected.push(sig);
1878                    }
1879                }
1880                Ok(collected)
1881            };
1882
1883            let setsigdef = self.setsigdef.map(collect_signals).transpose()?;
1884
1885            #[cfg(all(
1886                any(target_os = "linux", target_os = "freebsd", target_os = "android"),
1887                not(target_env = "musl")
1888            ))]
1889            let scheduler = parse_posix_spawn_scheduler(self.scheduler, vm)?;
1890            #[cfg(any(target_os = "macos", target_env = "musl"))]
1891            parse_posix_spawn_scheduler(self.scheduler, vm)?;
1892
1893            if self.setsid && !rustpython_host_env::posix::supports_posix_spawn_setsid() {
1894                return Err(vm.new_not_implemented_error(
1895                    "setsid parameter is not supported on this platform",
1896                ));
1897            }
1898
1899            let setsigmask = self.setsigmask.map(collect_signals).transpose()?;
1900
1901            let args = vm.extract_elements_with(&self.argv, |arg| {
1902                CString::new(OsPath::try_from_object(vm, arg)?.into_bytes())
1903                    .map_err(|_| vm.new_value_error("path should not have nul bytes"))
1904            })?;
1905            let env = if let Some(env_dict) = self.env {
1906                envp_from_dict(env_dict, vm)?
1907            } else {
1908                // env=None means use the current environment
1909
1910                crate::host_env::os::vars_os()
1911                    .map(|(k, v)| {
1912                        let mut entry = k.into_vec();
1913                        entry.push(b'=');
1914                        entry.extend(v.into_vec());
1915                        CString::new(entry).map_err(|_| {
1916                            vm.new_value_error("environment string contains null byte")
1917                        })
1918                    })
1919                    .collect::<PyResult<Vec<_>>>()?
1920            };
1921
1922            rustpython_host_env::posix::posix_spawn(rustpython_host_env::posix::PosixSpawnConfig {
1923                path: &path,
1924                args: &args,
1925                env: &env,
1926                file_actions: &file_actions,
1927                setsigdef: setsigdef.as_deref(),
1928                setpgroup: self.setpgroup,
1929                resetids: self.resetids,
1930                setsid: self.setsid,
1931                setsigmask: setsigmask.as_deref(),
1932                spawnp,
1933                #[cfg(all(
1934                    any(target_os = "linux", target_os = "freebsd", target_os = "android"),
1935                    not(target_env = "musl")
1936                ))]
1937                scheduler,
1938            })
1939            .map_err(|err| OSErrorBuilder::with_filename(&err, self.path, vm))
1940        }
1941    }
1942
1943    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1944    #[pyfunction]
1945    fn posix_spawn(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
1946        args.spawn(false, vm)
1947    }
1948
1949    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1950    #[pyfunction]
1951    fn posix_spawnp(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
1952        args.spawn(true, vm)
1953    }
1954
1955    #[derive(FromArgs)]
1956    struct StatusArg {
1957        #[pyarg(any)]
1958        status: i32,
1959    }
1960
1961    #[pyfunction(name = "WCOREDUMP")]
1962    fn wcoredump(status: i32) -> bool {
1963        rustpython_host_env::posix::wcoredump(status)
1964    }
1965
1966    #[pyfunction(name = "WIFCONTINUED")]
1967    fn wifcontinued(status: StatusArg) -> bool {
1968        rustpython_host_env::posix::wifcontinued(status.status)
1969    }
1970
1971    #[pyfunction(name = "WIFSTOPPED")]
1972    fn wifstopped(status: StatusArg) -> bool {
1973        rustpython_host_env::posix::wifstopped(status.status)
1974    }
1975
1976    #[pyfunction(name = "WIFSIGNALED")]
1977    fn wifsignaled(status: StatusArg) -> bool {
1978        rustpython_host_env::posix::wifsignaled(status.status)
1979    }
1980
1981    #[pyfunction(name = "WIFEXITED")]
1982    fn wifexited(status: StatusArg) -> bool {
1983        rustpython_host_env::posix::wifexited(status.status)
1984    }
1985
1986    #[pyfunction(name = "WEXITSTATUS")]
1987    fn wexitstatus(status: StatusArg) -> i32 {
1988        rustpython_host_env::posix::wexitstatus(status.status)
1989    }
1990
1991    #[pyfunction(name = "WSTOPSIG")]
1992    fn wstopsig(status: StatusArg) -> i32 {
1993        rustpython_host_env::posix::wstopsig(status.status)
1994    }
1995
1996    #[pyfunction(name = "WTERMSIG")]
1997    fn wtermsig(status: StatusArg) -> i32 {
1998        rustpython_host_env::posix::wtermsig(status.status)
1999    }
2000
2001    #[cfg(target_os = "linux")]
2002    #[pyfunction]
2003    fn pidfd_open(
2004        pid: libc::pid_t,
2005        flags: OptionalArg<u32>,
2006        vm: &VirtualMachine,
2007    ) -> PyResult<OwnedFd> {
2008        let flags = flags.unwrap_or(0);
2009        rustpython_host_env::posix::pidfd_open(pid, flags).map_err(|err| err.into_pyexception(vm))
2010    }
2011
2012    #[pyfunction]
2013    fn waitpid(
2014        pid: libc::pid_t,
2015        options: i32,
2016        vm: &VirtualMachine,
2017    ) -> PyResult<(libc::pid_t, i32)> {
2018        let mut status = 0;
2019        loop {
2020            let res =
2021                vm.allow_threads(|| rustpython_host_env::posix::waitpid(pid, &mut status, options));
2022            match res {
2023                Err(err) if err.raw_os_error() == Some(libc::EINTR) => {
2024                    vm.check_signals()?;
2025                    continue;
2026                }
2027                Err(err) => return Err(err.into_pyexception(vm)),
2028                Ok(res) => return Ok((res, status)),
2029            }
2030        }
2031    }
2032
2033    #[pyfunction]
2034    fn wait(vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> {
2035        waitpid(-1, 0, vm)
2036    }
2037
2038    fn rusage_to_py(
2039        ru: rustpython_host_env::resource::RUsage,
2040        vm: &VirtualMachine,
2041    ) -> PyResult<PyObjectRef> {
2042        let resource = vm.import("resource", 0)?;
2043        let struct_rusage = resource.get_attr("struct_rusage", vm)?;
2044        let tv = |t: rustpython_host_env::resource::timeval| {
2045            t.tv_sec as f64 + (t.tv_usec as f64 / 1_000_000.0)
2046        };
2047        let fields = vm.ctx.new_tuple(vec![
2048            vm.ctx.new_float(tv(ru.ru_utime)).into(),
2049            vm.ctx.new_float(tv(ru.ru_stime)).into(),
2050            vm.ctx.new_int(ru.ru_maxrss).into(),
2051            vm.ctx.new_int(ru.ru_ixrss).into(),
2052            vm.ctx.new_int(ru.ru_idrss).into(),
2053            vm.ctx.new_int(ru.ru_isrss).into(),
2054            vm.ctx.new_int(ru.ru_minflt).into(),
2055            vm.ctx.new_int(ru.ru_majflt).into(),
2056            vm.ctx.new_int(ru.ru_nswap).into(),
2057            vm.ctx.new_int(ru.ru_inblock).into(),
2058            vm.ctx.new_int(ru.ru_oublock).into(),
2059            vm.ctx.new_int(ru.ru_msgsnd).into(),
2060            vm.ctx.new_int(ru.ru_msgrcv).into(),
2061            vm.ctx.new_int(ru.ru_nsignals).into(),
2062            vm.ctx.new_int(ru.ru_nvcsw).into(),
2063            vm.ctx.new_int(ru.ru_nivcsw).into(),
2064        ]);
2065        struct_rusage.call((fields,), vm)
2066    }
2067
2068    fn wait_with_rusage<F>(vm: &VirtualMachine, wait: F) -> PyResult<PyTupleRef>
2069    where
2070        F: Fn() -> std::io::Result<(libc::pid_t, i32, rustpython_host_env::resource::RUsage)>,
2071    {
2072        loop {
2073            match vm.allow_threads(&wait) {
2074                Err(err) if err.raw_os_error() == Some(libc::EINTR) => {
2075                    vm.check_signals()?;
2076                    continue;
2077                }
2078                Err(err) => return Err(err.into_pyexception(vm)),
2079                Ok((pid, status, ru)) => {
2080                    let rusage = rusage_to_py(ru, vm)?;
2081                    return Ok(vm.new_tuple((pid, status, rusage)));
2082                }
2083            }
2084        }
2085    }
2086
2087    #[derive(FromArgs)]
2088    struct Wait3Args {
2089        #[pyarg(any)]
2090        options: i32,
2091    }
2092
2093    #[pyfunction]
2094    fn wait3(options: Wait3Args, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
2095        let options = options.options;
2096        wait_with_rusage(vm, || rustpython_host_env::posix::wait3(options))
2097    }
2098
2099    #[derive(FromArgs)]
2100    struct Wait4Args {
2101        #[pyarg(any)]
2102        pid: libc::pid_t,
2103        #[pyarg(any)]
2104        options: i32,
2105    }
2106
2107    #[pyfunction]
2108    fn wait4(args: Wait4Args, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
2109        wait_with_rusage(vm, || {
2110            rustpython_host_env::posix::wait4(args.pid, args.options)
2111        })
2112    }
2113
2114    #[pyfunction]
2115    fn kill(pid: i32, signal: isize, vm: &VirtualMachine) -> PyResult<()> {
2116        rustpython_host_env::posix::kill(pid, signal as i32).map_err(|err| err.into_pyexception(vm))
2117    }
2118
2119    #[pyfunction]
2120    fn killpg(pgid: i32, signal: isize, vm: &VirtualMachine) -> PyResult<()> {
2121        rustpython_host_env::posix::killpg(pgid, signal as i32)
2122            .map_err(|err| err.into_pyexception(vm))
2123    }
2124
2125    #[pyfunction]
2126    fn get_terminal_size(
2127        fd: OptionalArg<i32>,
2128        vm: &VirtualMachine,
2129    ) -> PyResult<_os::TerminalSizeData> {
2130        let (columns, lines) =
2131            rustpython_host_env::posix::get_terminal_size(fd.unwrap_or(libc::STDOUT_FILENO))
2132                .map(|(columns, lines)| (columns.into(), lines.into()))
2133                .map_err(|err| err.into_pyexception(vm))?;
2134        Ok(_os::TerminalSizeData { columns, lines })
2135    }
2136
2137    #[cfg(target_os = "macos")]
2138    #[pyfunction]
2139    fn _fcopyfile(in_fd: i32, out_fd: i32, flags: i32, vm: &VirtualMachine) -> PyResult<()> {
2140        rustpython_host_env::posix::fcopyfile(in_fd, out_fd, flags as u32)
2141            .map_err(|err| err.into_pyexception(vm))
2142    }
2143
2144    #[pyfunction]
2145    fn dup(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult<OwnedFd> {
2146        rustpython_host_env::posix::dup_noninheritable(fd).map_err(|e| e.into_pyexception(vm))
2147    }
2148
2149    #[derive(FromArgs)]
2150    struct Dup2Args<'fd> {
2151        #[pyarg(any)]
2152        fd: BorrowedFd<'fd>,
2153        #[pyarg(any)]
2154        fd2: OwnedFd,
2155        #[pyarg(any, default = true)]
2156        inheritable: bool,
2157    }
2158
2159    #[pyfunction]
2160    fn dup2(args: Dup2Args<'_>, vm: &VirtualMachine) -> PyResult<OwnedFd> {
2161        rustpython_host_env::posix::dup2(args.fd, args.fd2, args.inheritable)
2162            .map_err(|e| e.into_pyexception(vm))
2163    }
2164
2165    pub(crate) fn support_funcs() -> Vec<SupportFunc> {
2166        vec![
2167            SupportFunc::new(
2168                "chmod",
2169                Some(false),
2170                Some(false),
2171                Some(cfg!(any(
2172                    target_os = "macos",
2173                    target_os = "freebsd",
2174                    target_os = "netbsd"
2175                ))),
2176            ),
2177            #[cfg(not(target_os = "redox"))]
2178            SupportFunc::new("chroot", Some(false), None, None),
2179            #[cfg(not(target_os = "redox"))]
2180            SupportFunc::new("chown", Some(true), Some(true), Some(true)),
2181            #[cfg(not(target_os = "redox"))]
2182            SupportFunc::new("lchown", None, None, None),
2183            #[cfg(not(target_os = "redox"))]
2184            SupportFunc::new("fchown", Some(true), None, Some(true)),
2185            #[cfg(not(target_os = "redox"))]
2186            SupportFunc::new("mknod", Some(true), Some(MKNOD_DIR_FD), Some(false)),
2187            SupportFunc::new("umask", Some(false), Some(false), Some(false)),
2188            SupportFunc::new("execv", None, None, None),
2189            SupportFunc::new("pathconf", Some(true), None, None),
2190            SupportFunc::new("fpathconf", Some(true), None, None),
2191            SupportFunc::new("fchdir", Some(true), None, None),
2192        ]
2193    }
2194
2195    #[pyfunction]
2196    fn getlogin(vm: &VirtualMachine) -> PyResult<String> {
2197        // Get a pointer to the login name string. The string is statically
2198        // allocated and might be overwritten on subsequent calls to this
2199        // function or to `cuserid()`. See man getlogin(3) for more information.
2200        let Some(login) = rustpython_host_env::posix::getlogin() else {
2201            return Err(vm.new_os_error("unable to determine login name"));
2202        };
2203        login.to_str().map(|s| s.to_owned()).map_err(|e| {
2204            vm.new_unicode_decode_error(
2205                vm.ctx.new_str("utf-8"),
2206                vm.ctx.new_bytes(login.as_bytes().to_vec()),
2207                e.valid_up_to(),
2208                e.error_len()
2209                    .map_or(login.as_bytes().len(), |n| e.valid_up_to() + n),
2210                vm.ctx.new_str("unable to decode login name"),
2211            )
2212        })
2213    }
2214
2215    // cfg from nix
2216    #[cfg(any(
2217        target_os = "android",
2218        target_os = "freebsd",
2219        target_os = "linux",
2220        target_os = "openbsd"
2221    ))]
2222    #[pyfunction]
2223    fn getgrouplist(
2224        user: PyUtf8StrRef,
2225        group: u32,
2226        vm: &VirtualMachine,
2227    ) -> PyResult<Vec<PyObjectRef>> {
2228        let user = user.to_cstring(vm)?;
2229        let group_ids = rustpython_host_env::posix::getgrouplist(&user, group)
2230            .map_err(|err| err.into_pyexception(vm))?;
2231        Ok(group_ids.into_iter().map(|gid| vm.new_pyobj(gid)).collect())
2232    }
2233
2234    #[cfg(not(target_os = "redox"))]
2235    #[derive(FromArgs)]
2236    struct GetPriorityArgs {
2237        #[pyarg(any)]
2238        which: rustpython_host_env::posix::PriorityWhichType,
2239        #[pyarg(any)]
2240        who: rustpython_host_env::posix::PriorityWhoType,
2241    }
2242
2243    #[cfg(not(target_os = "redox"))]
2244    #[pyfunction]
2245    fn getpriority(args: GetPriorityArgs, vm: &VirtualMachine) -> PyResult {
2246        let GetPriorityArgs { which, who } = args;
2247        rustpython_host_env::posix::getpriority(which, who)
2248            .map(|retval| vm.ctx.new_int(retval).into())
2249            .map_err(|err| err.into_pyexception(vm))
2250    }
2251
2252    #[cfg(not(target_os = "redox"))]
2253    #[derive(FromArgs)]
2254    struct SetPriorityArgs {
2255        #[pyarg(any)]
2256        which: rustpython_host_env::posix::PriorityWhichType,
2257        #[pyarg(any)]
2258        who: rustpython_host_env::posix::PriorityWhoType,
2259        #[pyarg(any)]
2260        priority: i32,
2261    }
2262
2263    #[cfg(not(target_os = "redox"))]
2264    #[pyfunction]
2265    fn setpriority(args: SetPriorityArgs, vm: &VirtualMachine) -> PyResult<()> {
2266        let SetPriorityArgs {
2267            which,
2268            who,
2269            priority,
2270        } = args;
2271        rustpython_host_env::posix::setpriority(which, who, priority)
2272            .map_err(|err| err.into_pyexception(vm))
2273    }
2274
2275    struct PathconfName(i32);
2276
2277    impl TryFromObject for PathconfName {
2278        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
2279            let i = match obj.downcast::<PyInt>() {
2280                Ok(int) => int.try_to_primitive(vm)?,
2281                Err(obj) => {
2282                    let s = obj.downcast::<PyUtf8Str>().map_err(|_| {
2283                        vm.new_type_error("configuration names must be strings or integers")
2284                    })?;
2285                    s.as_str()
2286                        .parse::<PathconfVar>()
2287                        .map_err(|_| vm.new_value_error("unrecognized configuration name"))?
2288                        as i32
2289                }
2290            };
2291            Ok(Self(i))
2292        }
2293    }
2294
2295    // Mirror the libc pathconf constants as Python-facing names.
2296    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumIter, EnumString)]
2297    #[repr(i32)]
2298    #[allow(non_camel_case_types)]
2299    pub enum PathconfVar {
2300        #[cfg(any(
2301            target_os = "dragonfly",
2302            target_os = "freebsd",
2303            target_os = "linux",
2304            target_os = "netbsd",
2305            target_os = "openbsd",
2306            target_os = "redox"
2307        ))]
2308        /// Minimum number of bits needed to represent, as a signed integer value,
2309        /// the maximum size of a regular file allowed in the specified directory.
2310        PC_FILESIZEBITS = host_posix::_PC_FILESIZEBITS,
2311        /// Maximum number of links to a single file.
2312        PC_LINK_MAX = host_posix::_PC_LINK_MAX,
2313        /// Maximum number of bytes in a terminal canonical input line.
2314        PC_MAX_CANON = host_posix::_PC_MAX_CANON,
2315        /// Minimum number of bytes for which space is available in a terminal input
2316        /// queue; therefore, the maximum number of bytes a conforming application
2317        /// may require to be typed as input before reading them.
2318        PC_MAX_INPUT = host_posix::_PC_MAX_INPUT,
2319        /// Maximum number of bytes in a filename (not including the terminating
2320        /// null of a filename string).
2321        PC_NAME_MAX = host_posix::_PC_NAME_MAX,
2322        /// Maximum number of bytes the implementation will store as a pathname in a
2323        /// user-supplied buffer of unspecified size, including the terminating null
2324        /// character. Minimum number the implementation will accept as the maximum
2325        /// number of bytes in a pathname.
2326        PC_PATH_MAX = host_posix::_PC_PATH_MAX,
2327        /// Maximum number of bytes that is guaranteed to be atomic when writing to
2328        /// a pipe.
2329        PC_PIPE_BUF = host_posix::_PC_PIPE_BUF,
2330        #[cfg(any(
2331            target_os = "android",
2332            target_os = "dragonfly",
2333            target_os = "illumos",
2334            target_os = "linux",
2335            target_os = "netbsd",
2336            target_os = "openbsd",
2337            target_os = "redox",
2338            target_os = "solaris"
2339        ))]
2340        /// Symbolic links can be created.
2341        PC_2_SYMLINKS = host_posix::_PC_2_SYMLINKS,
2342        #[cfg(any(
2343            target_os = "android",
2344            target_os = "dragonfly",
2345            target_os = "freebsd",
2346            target_os = "linux",
2347            target_os = "openbsd",
2348            target_os = "redox"
2349        ))]
2350        /// Minimum number of bytes of storage actually allocated for any portion of
2351        /// a file.
2352        PC_ALLOC_SIZE_MIN = host_posix::_PC_ALLOC_SIZE_MIN,
2353        #[cfg(any(
2354            target_os = "android",
2355            target_os = "dragonfly",
2356            target_os = "freebsd",
2357            target_os = "linux",
2358            target_os = "openbsd"
2359        ))]
2360        /// Recommended increment for file transfer sizes between the
2361        /// `POSIX_REC_MIN_XFER_SIZE` and `POSIX_REC_MAX_XFER_SIZE` values.
2362        PC_REC_INCR_XFER_SIZE = host_posix::_PC_REC_INCR_XFER_SIZE,
2363        #[cfg(any(
2364            target_os = "android",
2365            target_os = "dragonfly",
2366            target_os = "freebsd",
2367            target_os = "linux",
2368            target_os = "openbsd",
2369            target_os = "redox"
2370        ))]
2371        /// Maximum recommended file transfer size.
2372        PC_REC_MAX_XFER_SIZE = host_posix::_PC_REC_MAX_XFER_SIZE,
2373        #[cfg(any(
2374            target_os = "android",
2375            target_os = "dragonfly",
2376            target_os = "freebsd",
2377            target_os = "linux",
2378            target_os = "openbsd",
2379            target_os = "redox"
2380        ))]
2381        /// Minimum recommended file transfer size.
2382        PC_REC_MIN_XFER_SIZE = host_posix::_PC_REC_MIN_XFER_SIZE,
2383        #[cfg(any(
2384            target_os = "android",
2385            target_os = "dragonfly",
2386            target_os = "freebsd",
2387            target_os = "linux",
2388            target_os = "openbsd",
2389            target_os = "redox"
2390        ))]
2391        ///  Recommended file transfer buffer alignment.
2392        PC_REC_XFER_ALIGN = host_posix::_PC_REC_XFER_ALIGN,
2393        #[cfg(any(
2394            target_os = "android",
2395            target_os = "dragonfly",
2396            target_os = "freebsd",
2397            target_os = "illumos",
2398            target_os = "linux",
2399            target_os = "netbsd",
2400            target_os = "openbsd",
2401            target_os = "redox",
2402            target_os = "solaris"
2403        ))]
2404        /// Maximum number of bytes in a symbolic link.
2405        PC_SYMLINK_MAX = host_posix::_PC_SYMLINK_MAX,
2406        /// The use of `chown` and `fchown` is restricted to a process with
2407        /// appropriate privileges, and to changing the group ID of a file only to
2408        /// the effective group ID of the process or to one of its supplementary
2409        /// group IDs.
2410        PC_CHOWN_RESTRICTED = host_posix::_PC_CHOWN_RESTRICTED,
2411        /// Pathname components longer than {NAME_MAX} generate an error.
2412        PC_NO_TRUNC = host_posix::_PC_NO_TRUNC,
2413        /// This symbol shall be defined to be the value of a character that shall
2414        /// disable terminal special character handling.
2415        PC_VDISABLE = host_posix::_PC_VDISABLE,
2416        #[cfg(any(
2417            target_os = "android",
2418            target_os = "dragonfly",
2419            target_os = "freebsd",
2420            target_os = "illumos",
2421            target_os = "linux",
2422            target_os = "openbsd",
2423            target_os = "redox",
2424            target_os = "solaris"
2425        ))]
2426        /// Asynchronous input or output operations may be performed for the
2427        /// associated file.
2428        PC_ASYNC_IO = host_posix::_PC_ASYNC_IO,
2429        #[cfg(any(
2430            target_os = "android",
2431            target_os = "dragonfly",
2432            target_os = "freebsd",
2433            target_os = "illumos",
2434            target_os = "linux",
2435            target_os = "openbsd",
2436            target_os = "redox",
2437            target_os = "solaris"
2438        ))]
2439        /// Prioritized input or output operations may be performed for the
2440        /// associated file.
2441        PC_PRIO_IO = host_posix::_PC_PRIO_IO,
2442        #[cfg(any(
2443            target_os = "android",
2444            target_os = "dragonfly",
2445            target_os = "freebsd",
2446            target_os = "illumos",
2447            target_os = "linux",
2448            target_os = "netbsd",
2449            target_os = "openbsd",
2450            target_os = "redox",
2451            target_os = "solaris"
2452        ))]
2453        /// Synchronized input or output operations may be performed for the
2454        /// associated file.
2455        PC_SYNC_IO = host_posix::_PC_SYNC_IO,
2456        #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))]
2457        /// The resolution in nanoseconds for all file timestamps.
2458        PC_TIMESTAMP_RESOLUTION = host_posix::_PC_TIMESTAMP_RESOLUTION,
2459    }
2460
2461    #[cfg(unix)]
2462    #[derive(FromArgs)]
2463    struct PathconfArgs {
2464        #[pyarg(any)]
2465        path: OsPathOrFd<'static>,
2466        #[pyarg(any)]
2467        name: PathconfName,
2468    }
2469
2470    #[cfg(unix)]
2471    #[pyfunction]
2472    fn pathconf(
2473        PathconfArgs {
2474            path,
2475            name: PathconfName(name),
2476        }: PathconfArgs,
2477        vm: &VirtualMachine,
2478    ) -> PyResult<Option<libc::c_long>> {
2479        match &path {
2480            OsPathOrFd::Path(path) => {
2481                let c_path = path.clone().into_cstring(vm)?;
2482                rustpython_host_env::posix::pathconf(&c_path, name)
2483                    .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))
2484            }
2485            OsPathOrFd::Fd(fd) => rustpython_host_env::posix::fpathconf(fd.as_raw(), name)
2486                .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)),
2487        }
2488    }
2489
2490    #[pyfunction]
2491    fn fpathconf(
2492        fd: BorrowedFd<'_>,
2493        name: PathconfName,
2494        vm: &VirtualMachine,
2495    ) -> PyResult<Option<libc::c_long>> {
2496        let path = OsPathOrFd::Fd(fd.into());
2497        let OsPathOrFd::Fd(fd) = &path else {
2498            unreachable!()
2499        };
2500        rustpython_host_env::posix::fpathconf(fd.as_raw(), name.0)
2501            .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
2502    }
2503
2504    #[pyattr]
2505    fn pathconf_names(vm: &VirtualMachine) -> PyDictRef {
2506        let pathname = vm.ctx.new_dict();
2507        for variant in PathconfVar::iter() {
2508            // get the name of variant as a string to use as the dictionary key
2509            let key = vm.ctx.new_str(format!("{variant:?}"));
2510            // get the enum from the string and convert it to an integer for the dictionary value
2511            let value = vm.ctx.new_int(variant as u8);
2512            pathname
2513                .set_item(&*key, value.into(), vm)
2514                .expect("dict set_item unexpectedly failed");
2515        }
2516        pathname
2517    }
2518
2519    #[cfg(not(target_os = "redox"))]
2520    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumIter, EnumString)]
2521    #[repr(i32)]
2522    #[allow(non_camel_case_types)]
2523    pub enum SysconfVar {
2524        SC_2_CHAR_TERM = host_posix::_SC_2_CHAR_TERM,
2525        SC_2_C_BIND = host_posix::_SC_2_C_BIND,
2526        SC_2_C_DEV = host_posix::_SC_2_C_DEV,
2527        SC_2_FORT_DEV = host_posix::_SC_2_FORT_DEV,
2528        SC_2_FORT_RUN = host_posix::_SC_2_FORT_RUN,
2529        SC_2_LOCALEDEF = host_posix::_SC_2_LOCALEDEF,
2530        SC_2_SW_DEV = host_posix::_SC_2_SW_DEV,
2531        SC_2_UPE = host_posix::_SC_2_UPE,
2532        SC_2_VERSION = host_posix::_SC_2_VERSION,
2533        SC_AIO_LISTIO_MAX = host_posix::_SC_AIO_LISTIO_MAX,
2534        SC_AIO_MAX = host_posix::_SC_AIO_MAX,
2535        SC_AIO_PRIO_DELTA_MAX = host_posix::_SC_AIO_PRIO_DELTA_MAX,
2536        SC_ARG_MAX = host_posix::_SC_ARG_MAX,
2537        SC_ASYNCHRONOUS_IO = host_posix::_SC_ASYNCHRONOUS_IO,
2538        SC_ATEXIT_MAX = host_posix::_SC_ATEXIT_MAX,
2539        SC_BC_BASE_MAX = host_posix::_SC_BC_BASE_MAX,
2540        SC_BC_DIM_MAX = host_posix::_SC_BC_DIM_MAX,
2541        SC_BC_SCALE_MAX = host_posix::_SC_BC_SCALE_MAX,
2542        SC_BC_STRING_MAX = host_posix::_SC_BC_STRING_MAX,
2543        SC_CHILD_MAX = host_posix::_SC_CHILD_MAX,
2544        SC_CLK_TCK = host_posix::_SC_CLK_TCK,
2545        SC_COLL_WEIGHTS_MAX = host_posix::_SC_COLL_WEIGHTS_MAX,
2546        SC_DELAYTIMER_MAX = host_posix::_SC_DELAYTIMER_MAX,
2547        SC_EXPR_NEST_MAX = host_posix::_SC_EXPR_NEST_MAX,
2548        SC_FSYNC = host_posix::_SC_FSYNC,
2549        SC_GETGR_R_SIZE_MAX = host_posix::_SC_GETGR_R_SIZE_MAX,
2550        SC_GETPW_R_SIZE_MAX = host_posix::_SC_GETPW_R_SIZE_MAX,
2551        SC_IOV_MAX = host_posix::_SC_IOV_MAX,
2552        SC_JOB_CONTROL = host_posix::_SC_JOB_CONTROL,
2553        SC_LINE_MAX = host_posix::_SC_LINE_MAX,
2554        SC_LOGIN_NAME_MAX = host_posix::_SC_LOGIN_NAME_MAX,
2555        SC_MAPPED_FILES = host_posix::_SC_MAPPED_FILES,
2556        SC_MEMLOCK = host_posix::_SC_MEMLOCK,
2557        SC_MEMLOCK_RANGE = host_posix::_SC_MEMLOCK_RANGE,
2558        SC_MEMORY_PROTECTION = host_posix::_SC_MEMORY_PROTECTION,
2559        SC_MESSAGE_PASSING = host_posix::_SC_MESSAGE_PASSING,
2560        SC_MQ_OPEN_MAX = host_posix::_SC_MQ_OPEN_MAX,
2561        SC_MQ_PRIO_MAX = host_posix::_SC_MQ_PRIO_MAX,
2562        SC_NGROUPS_MAX = host_posix::_SC_NGROUPS_MAX,
2563        SC_NPROCESSORS_CONF = host_posix::_SC_NPROCESSORS_CONF,
2564        SC_NPROCESSORS_ONLN = host_posix::_SC_NPROCESSORS_ONLN,
2565        SC_OPEN_MAX = host_posix::_SC_OPEN_MAX,
2566        SC_PAGE_SIZE = host_posix::_SC_PAGE_SIZE,
2567        #[cfg(any(
2568            target_os = "linux",
2569            target_vendor = "apple",
2570            target_os = "netbsd",
2571            target_os = "fuchsia"
2572        ))]
2573        SC_PASS_MAX = host_posix::_SC_PASS_MAX,
2574        SC_PHYS_PAGES = host_posix::_SC_PHYS_PAGES,
2575        SC_PRIORITIZED_IO = host_posix::_SC_PRIORITIZED_IO,
2576        SC_PRIORITY_SCHEDULING = host_posix::_SC_PRIORITY_SCHEDULING,
2577        SC_REALTIME_SIGNALS = host_posix::_SC_REALTIME_SIGNALS,
2578        SC_RE_DUP_MAX = host_posix::_SC_RE_DUP_MAX,
2579        SC_RTSIG_MAX = host_posix::_SC_RTSIG_MAX,
2580        SC_SAVED_IDS = host_posix::_SC_SAVED_IDS,
2581        SC_SEMAPHORES = host_posix::_SC_SEMAPHORES,
2582        SC_SEM_NSEMS_MAX = host_posix::_SC_SEM_NSEMS_MAX,
2583        SC_SEM_VALUE_MAX = host_posix::_SC_SEM_VALUE_MAX,
2584        SC_SHARED_MEMORY_OBJECTS = host_posix::_SC_SHARED_MEMORY_OBJECTS,
2585        SC_SIGQUEUE_MAX = host_posix::_SC_SIGQUEUE_MAX,
2586        SC_STREAM_MAX = host_posix::_SC_STREAM_MAX,
2587        SC_SYNCHRONIZED_IO = host_posix::_SC_SYNCHRONIZED_IO,
2588        SC_THREADS = host_posix::_SC_THREADS,
2589        SC_THREAD_ATTR_STACKADDR = host_posix::_SC_THREAD_ATTR_STACKADDR,
2590        SC_THREAD_ATTR_STACKSIZE = host_posix::_SC_THREAD_ATTR_STACKSIZE,
2591        SC_THREAD_DESTRUCTOR_ITERATIONS = host_posix::_SC_THREAD_DESTRUCTOR_ITERATIONS,
2592        SC_THREAD_KEYS_MAX = host_posix::_SC_THREAD_KEYS_MAX,
2593        SC_THREAD_PRIORITY_SCHEDULING = host_posix::_SC_THREAD_PRIORITY_SCHEDULING,
2594        SC_THREAD_PRIO_INHERIT = host_posix::_SC_THREAD_PRIO_INHERIT,
2595        SC_THREAD_PRIO_PROTECT = host_posix::_SC_THREAD_PRIO_PROTECT,
2596        SC_THREAD_PROCESS_SHARED = host_posix::_SC_THREAD_PROCESS_SHARED,
2597        SC_THREAD_SAFE_FUNCTIONS = host_posix::_SC_THREAD_SAFE_FUNCTIONS,
2598        SC_THREAD_STACK_MIN = host_posix::_SC_THREAD_STACK_MIN,
2599        SC_THREAD_THREADS_MAX = host_posix::_SC_THREAD_THREADS_MAX,
2600        SC_TIMERS = host_posix::_SC_TIMERS,
2601        SC_TIMER_MAX = host_posix::_SC_TIMER_MAX,
2602        SC_TTY_NAME_MAX = host_posix::_SC_TTY_NAME_MAX,
2603        SC_TZNAME_MAX = host_posix::_SC_TZNAME_MAX,
2604        SC_VERSION = host_posix::_SC_VERSION,
2605        SC_XOPEN_CRYPT = host_posix::_SC_XOPEN_CRYPT,
2606        SC_XOPEN_ENH_I18N = host_posix::_SC_XOPEN_ENH_I18N,
2607        SC_XOPEN_LEGACY = host_posix::_SC_XOPEN_LEGACY,
2608        SC_XOPEN_REALTIME = host_posix::_SC_XOPEN_REALTIME,
2609        SC_XOPEN_REALTIME_THREADS = host_posix::_SC_XOPEN_REALTIME_THREADS,
2610        SC_XOPEN_SHM = host_posix::_SC_XOPEN_SHM,
2611        SC_XOPEN_UNIX = host_posix::_SC_XOPEN_UNIX,
2612        SC_XOPEN_VERSION = host_posix::_SC_XOPEN_VERSION,
2613        SC_XOPEN_XCU_VERSION = host_posix::_SC_XOPEN_XCU_VERSION,
2614        #[cfg(any(
2615            target_os = "linux",
2616            target_vendor = "apple",
2617            target_os = "netbsd",
2618            target_os = "fuchsia"
2619        ))]
2620        SC_XBS5_ILP32_OFF32 = host_posix::_SC_XBS5_ILP32_OFF32,
2621        #[cfg(any(
2622            target_os = "linux",
2623            target_vendor = "apple",
2624            target_os = "netbsd",
2625            target_os = "fuchsia"
2626        ))]
2627        SC_XBS5_ILP32_OFFBIG = host_posix::_SC_XBS5_ILP32_OFFBIG,
2628        #[cfg(any(
2629            target_os = "linux",
2630            target_vendor = "apple",
2631            target_os = "netbsd",
2632            target_os = "fuchsia"
2633        ))]
2634        SC_XBS5_LP64_OFF64 = host_posix::_SC_XBS5_LP64_OFF64,
2635        #[cfg(any(
2636            target_os = "linux",
2637            target_vendor = "apple",
2638            target_os = "netbsd",
2639            target_os = "fuchsia"
2640        ))]
2641        SC_XBS5_LPBIG_OFFBIG = host_posix::_SC_XBS5_LPBIG_OFFBIG,
2642    }
2643
2644    #[cfg(target_os = "redox")]
2645    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumIter, EnumString)]
2646    #[repr(i32)]
2647    #[allow(non_camel_case_types)]
2648    pub enum SysconfVar {
2649        SC_ARG_MAX = host_posix::_SC_ARG_MAX,
2650        SC_CHILD_MAX = host_posix::_SC_CHILD_MAX,
2651        SC_CLK_TCK = host_posix::_SC_CLK_TCK,
2652        SC_NGROUPS_MAX = host_posix::_SC_NGROUPS_MAX,
2653        SC_OPEN_MAX = host_posix::_SC_OPEN_MAX,
2654        SC_STREAM_MAX = host_posix::_SC_STREAM_MAX,
2655        SC_TZNAME_MAX = host_posix::_SC_TZNAME_MAX,
2656        SC_VERSION = host_posix::_SC_VERSION,
2657        SC_PAGE_SIZE = host_posix::_SC_PAGE_SIZE,
2658        SC_RE_DUP_MAX = host_posix::_SC_RE_DUP_MAX,
2659        SC_LOGIN_NAME_MAX = host_posix::_SC_LOGIN_NAME_MAX,
2660        SC_TTY_NAME_MAX = host_posix::_SC_TTY_NAME_MAX,
2661        SC_SYMLOOP_MAX = host_posix::_SC_SYMLOOP_MAX,
2662        SC_HOST_NAME_MAX = host_posix::_SC_HOST_NAME_MAX,
2663    }
2664
2665    impl SysconfVar {
2666        pub const SC_PAGESIZE: Self = Self::SC_PAGE_SIZE;
2667    }
2668
2669    struct SysconfName(i32);
2670
2671    impl TryFromObject for SysconfName {
2672        fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
2673            let i = match obj.downcast::<PyInt>() {
2674                Ok(int) => int.try_to_primitive(vm)?,
2675                Err(obj) => {
2676                    let s = obj.downcast::<PyUtf8Str>().map_err(|_| {
2677                        vm.new_type_error("configuration names must be strings or integers")
2678                    })?;
2679                    {
2680                        let name = s.as_str();
2681                        name.parse::<SysconfVar>().or_else(|_| {
2682                            if name == "SC_PAGESIZE" {
2683                                Ok(SysconfVar::SC_PAGESIZE)
2684                            } else {
2685                                Err(vm.new_value_error("unrecognized configuration name"))
2686                            }
2687                        })? as i32
2688                    }
2689                }
2690            };
2691            Ok(Self(i))
2692        }
2693    }
2694
2695    #[pyfunction]
2696    fn sysconf(name: SysconfName, vm: &VirtualMachine) -> PyResult<libc::c_long> {
2697        rustpython_host_env::posix::sysconf(name.0).map_err(|err| err.into_pyexception(vm))
2698    }
2699
2700    #[pyattr]
2701    fn sysconf_names(vm: &VirtualMachine) -> PyDictRef {
2702        let names = vm.ctx.new_dict();
2703        for variant in SysconfVar::iter() {
2704            // get the name of variant as a string to use as the dictionary key
2705            let key = vm.ctx.new_str(format!("{variant:?}"));
2706            // get the enum from the string and convert it to an integer for the dictionary value
2707            let value = vm.ctx.new_int(variant as u8);
2708            names
2709                .set_item(&*key, value.into(), vm)
2710                .expect("dict set_item unexpectedly failed");
2711        }
2712        names
2713    }
2714
2715    #[cfg(any(target_os = "linux", target_os = "macos"))]
2716    #[derive(FromArgs)]
2717    struct SendFileArgs<'fd> {
2718        out_fd: BorrowedFd<'fd>,
2719        in_fd: BorrowedFd<'fd>,
2720        offset: rustpython_host_env::crt_fd::Offset,
2721        count: i64,
2722        #[cfg(target_os = "macos")]
2723        // Missing means an empty header list.
2724        #[pyarg(any, optional, py_default = "()")]
2725        headers: OptionalArg<PyObjectRef>,
2726        #[cfg(target_os = "macos")]
2727        // Missing means an empty trailer list.
2728        #[pyarg(any, optional, py_default = "()")]
2729        trailers: OptionalArg<PyObjectRef>,
2730        #[cfg(target_os = "macos")]
2731        #[allow(dead_code)]
2732        #[pyarg(any, default)]
2733        // TODO: not implemented
2734        flags: i32,
2735    }
2736
2737    #[cfg(target_os = "linux")]
2738    #[pyfunction]
2739    fn sendfile(args: SendFileArgs<'_>, vm: &VirtualMachine) -> PyResult {
2740        let mut file_offset = args.offset;
2741
2742        let res = rustpython_host_env::posix::sendfile(
2743            args.out_fd,
2744            args.in_fd,
2745            &mut file_offset,
2746            args.count as usize,
2747        )
2748        .map_err(|err| err.into_pyexception(vm))?;
2749        Ok(vm.ctx.new_int(res as u64).into())
2750    }
2751
2752    #[cfg(target_os = "macos")]
2753    fn _extract_vec_bytes(
2754        x: OptionalArg,
2755        vm: &VirtualMachine,
2756    ) -> PyResult<Option<Vec<crate::function::ArgBytesLike>>> {
2757        x.into_option()
2758            .map(|x| {
2759                let v: Vec<crate::function::ArgBytesLike> = x.try_to_value(vm)?;
2760                Ok(if v.is_empty() { None } else { Some(v) })
2761            })
2762            .transpose()
2763            .map(Option::flatten)
2764    }
2765
2766    #[cfg(target_os = "macos")]
2767    #[pyfunction]
2768    fn sendfile(args: SendFileArgs<'_>, vm: &VirtualMachine) -> PyResult {
2769        let headers = _extract_vec_bytes(args.headers, vm)?;
2770        let count = headers
2771            .as_ref()
2772            .map_or(0, |v| v.iter().map(|s| s.len()).sum()) as i64
2773            + args.count;
2774
2775        let headers = headers
2776            .as_ref()
2777            .map(|v| v.iter().map(|b| b.borrow_buf()).collect::<Vec<_>>());
2778        let headers = headers
2779            .as_ref()
2780            .map(|v| v.iter().map(|borrowed| &**borrowed).collect::<Vec<_>>());
2781        let headers = headers.as_deref();
2782
2783        let trailers = _extract_vec_bytes(args.trailers, vm)?;
2784        let trailers = trailers
2785            .as_ref()
2786            .map(|v| v.iter().map(|b| b.borrow_buf()).collect::<Vec<_>>());
2787        let trailers = trailers
2788            .as_ref()
2789            .map(|v| v.iter().map(|borrowed| &**borrowed).collect::<Vec<_>>());
2790        let trailers = trailers.as_deref();
2791
2792        let (res, written) = rustpython_host_env::posix::sendfile(
2793            args.in_fd,
2794            args.out_fd,
2795            args.offset,
2796            count,
2797            headers,
2798            trailers,
2799        );
2800        // On macOS, sendfile can return EAGAIN even when some bytes were written.
2801        // In that case, we should return the number of bytes written rather than
2802        // raising an exception. Only raise an error if no bytes were written.
2803        if let Err(err) = res
2804            && written == 0
2805        {
2806            return Err(err.into_pyexception(vm));
2807        }
2808        Ok(vm.ctx.new_int(written as u64).into())
2809    }
2810
2811    #[cfg(target_os = "linux")]
2812    #[pyfunction]
2813    fn getrandom(size: isize, flags: OptionalArg<u32>, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
2814        let size = usize::try_from(size)
2815            .map_err(|_| vm.new_os_error(format!("Invalid argument for size: {size}")))?;
2816        let mut buf = Vec::with_capacity(size);
2817        unsafe {
2818            let len = rustpython_host_env::posix::getrandom(
2819                buf.as_mut_ptr() as *mut libc::c_void,
2820                size,
2821                flags.unwrap_or(0),
2822            )
2823            .map_err(|_| vm.new_last_os_error())?;
2824            buf.set_len(len);
2825        }
2826        Ok(buf)
2827    }
2828
2829    pub(crate) fn module_exec(
2830        vm: &VirtualMachine,
2831        module: &Py<crate::builtins::PyModule>,
2832    ) -> PyResult<()> {
2833        __module_exec(vm, module);
2834        super::super::os::module_exec(vm, module)?;
2835        Ok(())
2836    }
2837
2838    #[pyfunction]
2839    fn _is_inputhook_installed() -> bool {
2840        // TODO: Implement the actual logic here
2841        false
2842    }
2843}
2844
2845#[cfg(any(
2846    target_os = "linux",
2847    target_os = "netbsd",
2848    target_os = "freebsd",
2849    target_os = "android"
2850))]
2851#[pymodule(sub, name = "posix")]
2852mod posix_sched {
2853    use crate::{
2854        AsObject, Py, PyObject, PyObjectRef, PyResult, VirtualMachine,
2855        builtins::PyTupleRef,
2856        class::PyClassDef,
2857        convert::{IntoPyException, ToPyObject},
2858        function::FuncArgs,
2859        types::PyStructSequence,
2860    };
2861
2862    #[derive(FromArgs)]
2863    struct SchedParamArgs {
2864        #[pyarg(any)]
2865        sched_priority: PyObjectRef,
2866    }
2867
2868    #[pystruct_sequence_data]
2869    struct SchedParamData {
2870        pub sched_priority: PyObjectRef,
2871    }
2872
2873    #[pyattr]
2874    #[pystruct_sequence(name = "sched_param", module = "posix", data = "SchedParamData")]
2875    struct PySchedParam;
2876
2877    #[pyclass(with(PyStructSequence))]
2878    impl PySchedParam {
2879        #[pyslot]
2880        fn slot_new(
2881            cls: crate::builtins::PyTypeRef,
2882            args: FuncArgs,
2883            vm: &VirtualMachine,
2884        ) -> PyResult {
2885            use crate::PyPayload;
2886            let SchedParamArgs { sched_priority } = args.bind_for(vm, Self::NAME)?;
2887            let items = vec![sched_priority];
2888            crate::builtins::PyTuple::new_unchecked(items.into_boxed_slice())
2889                .into_ref_with_type(vm, cls)
2890                .map(Into::into)
2891        }
2892
2893        #[extend_class]
2894        fn extend_pyclass(ctx: &crate::vm::Context, class: &'static Py<crate::builtins::PyType>) {
2895            // Override __reduce__ to return (type, (sched_priority,))
2896            // instead of the generic structseq (type, ((sched_priority,),)).
2897            // The trait's extend_class checks contains_key before setting default.
2898            const SCHED_PARAM_REDUCE: crate::function::PyMethodDef =
2899                crate::function::PyMethodDef::new_const(
2900                    "__reduce__",
2901                    |zelf: crate::PyRef<crate::builtins::PyTuple>,
2902                     vm: &VirtualMachine|
2903                     -> PyTupleRef {
2904                        vm.new_tuple((zelf.class().to_owned(), (zelf.as_slice()[0].clone(),)))
2905                    },
2906                    crate::function::PyMethodFlags::METHOD,
2907                    crate::function::ItemDoc::NONE,
2908                );
2909            class.set_attr(
2910                ctx.intern_str("__reduce__"),
2911                SCHED_PARAM_REDUCE.to_proper_method(class, ctx),
2912            );
2913        }
2914    }
2915
2916    #[cfg(not(target_env = "musl"))]
2917    pub(super) fn convert_sched_param(
2918        obj: &PyObject,
2919        vm: &VirtualMachine,
2920    ) -> PyResult<libc::sched_param> {
2921        use crate::{
2922            builtins::{PyInt, PyTuple},
2923            class::StaticType,
2924        };
2925        if !obj.fast_isinstance(PySchedParam::static_type()) {
2926            return Err(vm.new_type_error("must have a sched_param object"));
2927        }
2928        let tuple = obj.downcast_ref::<PyTuple>().unwrap();
2929        let priority = tuple.as_slice()[0].clone();
2930        let priority_type = priority.class().name().to_string();
2931        let value = priority.downcast::<PyInt>().map_err(|_| {
2932            vm.new_type_error(format!("an integer is required (got type {priority_type})"))
2933        })?;
2934        let sched_priority = value.try_to_primitive(vm)?;
2935        Ok(libc::sched_param { sched_priority })
2936    }
2937
2938    #[pyfunction]
2939    fn sched_getscheduler(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<i32> {
2940        rustpython_host_env::posix::sched_getscheduler(pid).map_err(|err| err.into_pyexception(vm))
2941    }
2942
2943    #[cfg(not(target_env = "musl"))]
2944    #[derive(FromArgs)]
2945    struct SchedSetschedulerArgs {
2946        #[pyarg(positional)]
2947        pid: i32,
2948        #[pyarg(positional)]
2949        policy: i32,
2950        #[pyarg(positional)]
2951        sched_param: PyObjectRef,
2952    }
2953
2954    #[cfg(not(target_env = "musl"))]
2955    #[pyfunction]
2956    fn sched_setscheduler(args: SchedSetschedulerArgs, vm: &VirtualMachine) -> PyResult<i32> {
2957        let libc_sched_param = convert_sched_param(&args.sched_param, vm)?;
2958        rustpython_host_env::posix::sched_setscheduler(args.pid, args.policy, &libc_sched_param)
2959            .map_err(|err| err.into_pyexception(vm))
2960    }
2961
2962    #[pyfunction]
2963    fn sched_getparam(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
2964        let param = rustpython_host_env::posix::sched_getparam(pid)
2965            .map_err(|err| err.into_pyexception(vm))?;
2966        Ok(PySchedParam::from_data(
2967            SchedParamData {
2968                sched_priority: param.sched_priority.to_pyobject(vm),
2969            },
2970            vm,
2971        ))
2972    }
2973
2974    #[cfg(not(target_env = "musl"))]
2975    #[derive(FromArgs)]
2976    struct SchedSetParamArgs {
2977        #[pyarg(positional)]
2978        pid: i32,
2979        #[pyarg(positional)]
2980        sched_param: PyObjectRef,
2981    }
2982
2983    #[cfg(not(target_env = "musl"))]
2984    #[pyfunction]
2985    fn sched_setparam(args: SchedSetParamArgs, vm: &VirtualMachine) -> PyResult<i32> {
2986        let libc_sched_param = convert_sched_param(&args.sched_param, vm)?;
2987        rustpython_host_env::posix::sched_setparam(args.pid, &libc_sched_param)
2988            .map_err(|err| err.into_pyexception(vm))
2989    }
2990}