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