Skip to main content

rustpython_host_env/
posix.rs

1// spell-checker:ignore CANTCREAT NOHOST NOPERM TEMPFAIL DATAERR NOINPUT NOUSER
2
3use alloc::ffi::CString;
4use alloc::vec::Vec;
5use core::ffi::CStr;
6#[cfg(all(unix, not(target_os = "redox")))]
7use core::ptr::NonNull;
8use std::ffi::{OsStr, OsString};
9#[cfg(target_os = "linux")]
10use std::os::fd::FromRawFd;
11use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd};
12use std::path::Path;
13
14pub use super::posix_unix_like::*;
15
16pub use libc::{c_char, pid_t};
17
18/// `<sysexits.h>`. The `libc` crate does not bind these.
19pub const EX_OK: i32 = 0;
20pub const EX_USAGE: i32 = 64;
21pub const EX_DATAERR: i32 = 65;
22pub const EX_NOINPUT: i32 = 66;
23pub const EX_NOUSER: i32 = 67;
24pub const EX_NOHOST: i32 = 68;
25pub const EX_UNAVAILABLE: i32 = 69;
26pub const EX_SOFTWARE: i32 = 70;
27pub const EX_OSERR: i32 = 71;
28pub const EX_OSFILE: i32 = 72;
29pub const EX_CANTCREAT: i32 = 73;
30pub const EX_IOERR: i32 = 74;
31pub const EX_TEMPFAIL: i32 = 75;
32pub const EX_PROTOCOL: i32 = 76;
33pub const EX_NOPERM: i32 = 77;
34pub const EX_CONFIG: i32 = 78;
35
36/// Remaining `posix` integer names the VM still took from `libc`.
37#[cfg(any(target_os = "android", target_os = "redox", unix))]
38pub use libc::{PRIO_PGRP, PRIO_PROCESS, PRIO_USER};
39
40#[cfg(target_os = "macos")]
41pub use libc::{
42    COPYFILE_ACL, COPYFILE_DATA, COPYFILE_STAT, COPYFILE_XATTR, PRIO_DARWIN_BG, PRIO_DARWIN_NONUI,
43    PRIO_DARWIN_PROCESS, PRIO_DARWIN_THREAD, TMP_MAX,
44};
45
46#[cfg(target_os = "linux")]
47pub use libc::PIDFD_NONBLOCK;
48
49#[cfg(any(target_os = "android", target_os = "linux"))]
50pub use libc::{
51    CLONE_FILES, CLONE_FS, CLONE_NEWCGROUP, CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWPID,
52    CLONE_NEWUSER, CLONE_NEWUTS, CLONE_SIGHAND, CLONE_SYSVSEM, CLONE_THREAD, CLONE_VM,
53    MFD_HUGE_SHIFT, P_PIDFD, SCHED_BATCH, SCHED_DEADLINE, SCHED_IDLE, SCHED_NORMAL,
54    SCHED_RESET_ON_FORK, SPLICE_F_MORE, SPLICE_F_MOVE, SPLICE_F_NONBLOCK,
55};
56
57#[cfg(any(target_os = "linux", target_os = "macos", target_os = "netbsd"))]
58pub use libc::{XATTR_CREATE, XATTR_REPLACE};
59
60#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
61pub use libc::{
62    MFD_ALLOW_SEALING, MFD_CLOEXEC, MFD_HUGE_MASK, MFD_HUGETLB, POSIX_FADV_DONTNEED,
63    POSIX_FADV_NOREUSE, POSIX_FADV_NORMAL, POSIX_FADV_RANDOM, POSIX_FADV_SEQUENTIAL,
64    POSIX_FADV_WILLNEED,
65};
66
67#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox", unix))]
68pub use libc::{RTLD_LAZY, RTLD_NOW, WNOHANG};
69
70#[cfg(any(target_os = "android", target_os = "macos", target_os = "redox", unix))]
71pub use libc::RTLD_GLOBAL;
72
73#[cfg(any(
74    target_os = "android",
75    target_os = "freebsd",
76    target_os = "linux",
77    target_os = "netbsd"
78))]
79pub use libc::{
80    EFD_CLOEXEC, EFD_NONBLOCK, EFD_SEMAPHORE, TFD_CLOEXEC, TFD_NONBLOCK, TFD_TIMER_ABSTIME,
81    TFD_TIMER_CANCEL_ON_SET,
82};
83
84#[cfg(any(
85    target_os = "android",
86    target_os = "dragonfly",
87    target_os = "linux",
88    target_os = "netbsd"
89))]
90pub use libc::{GRND_NONBLOCK, GRND_RANDOM};
91
92#[cfg(any(
93    target_os = "dragonfly",
94    target_os = "freebsd",
95    target_os = "linux",
96    target_os = "macos",
97    target_os = "netbsd"
98))]
99pub use libc::SCHED_OTHER;
100
101#[cfg(any(
102    target_os = "android",
103    target_os = "dragonfly",
104    target_os = "freebsd",
105    target_os = "linux",
106    target_os = "macos"
107))]
108pub use libc::{RTLD_NODELETE, SEEK_DATA, SEEK_HOLE};
109
110#[cfg(any(
111    target_os = "android",
112    target_os = "linux",
113    target_os = "macos",
114    target_os = "netbsd",
115    target_os = "redox",
116    unix
117))]
118pub use libc::RTLD_LOCAL;
119
120#[cfg(any(
121    target_os = "android",
122    target_os = "freebsd",
123    target_os = "linux",
124    target_os = "netbsd",
125    target_os = "redox",
126    unix
127))]
128pub use libc::WUNTRACED;
129
130#[cfg(any(
131    target_os = "android",
132    target_os = "dragonfly",
133    target_os = "freebsd",
134    target_os = "linux",
135    target_os = "macos",
136    target_os = "netbsd"
137))]
138pub use libc::{
139    CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, CLD_TRAPPED, P_ALL, P_PGID,
140    P_PID, SCHED_FIFO, SCHED_RR,
141};
142
143#[cfg(any(
144    target_os = "android",
145    target_os = "dragonfly",
146    target_os = "freebsd",
147    target_os = "linux",
148    target_os = "macos",
149    target_os = "netbsd",
150    target_os = "redox"
151))]
152pub use libc::{RTLD_NOLOAD, WEXITED, WNOWAIT, WSTOPPED};
153
154#[cfg(any(
155    target_os = "android",
156    target_os = "dragonfly",
157    target_os = "freebsd",
158    target_os = "linux",
159    target_os = "macos",
160    target_os = "netbsd",
161    target_os = "redox",
162    unix
163))]
164pub use libc::WCONTINUED;
165
166#[cfg(all(target_os = "linux", target_env = "gnu"))]
167pub use libc::RTLD_DEEPBIND;
168
169#[cfg(unix)]
170pub use libc::{UTIME_NOW, UTIME_OMIT};
171
172#[cfg(target_os = "freebsd")]
173pub use libc::{SF_MNOWAIT, SF_NOCACHE, SF_NODISKIO, SF_SYNC};
174
175/// `pathconf` / `sysconf` names. The VM enum discriminants still took these
176/// from `libc`.
177#[cfg(unix)]
178pub use libc::{
179    _PC_CHOWN_RESTRICTED, _PC_LINK_MAX, _PC_MAX_CANON, _PC_MAX_INPUT, _PC_NAME_MAX, _PC_NO_TRUNC,
180    _PC_PATH_MAX, _PC_PIPE_BUF, _PC_VDISABLE, _SC_ARG_MAX, _SC_CHILD_MAX, _SC_CLK_TCK,
181    _SC_LOGIN_NAME_MAX, _SC_NGROUPS_MAX, _SC_OPEN_MAX, _SC_PAGE_SIZE, _SC_RE_DUP_MAX,
182    _SC_STREAM_MAX, _SC_TTY_NAME_MAX, _SC_TZNAME_MAX, _SC_VERSION,
183};
184
185#[cfg(any(
186    target_os = "dragonfly",
187    target_os = "freebsd",
188    target_os = "linux",
189    target_os = "netbsd",
190    target_os = "openbsd",
191    target_os = "redox"
192))]
193pub use libc::_PC_FILESIZEBITS;
194
195#[cfg(any(
196    target_os = "android",
197    target_os = "dragonfly",
198    target_os = "illumos",
199    target_os = "linux",
200    target_os = "netbsd",
201    target_os = "openbsd",
202    target_os = "redox",
203    target_os = "solaris"
204))]
205pub use libc::_PC_2_SYMLINKS;
206
207#[cfg(any(
208    target_os = "android",
209    target_os = "dragonfly",
210    target_os = "freebsd",
211    target_os = "linux",
212    target_os = "openbsd",
213    target_os = "redox"
214))]
215pub use libc::_PC_ALLOC_SIZE_MIN;
216
217#[cfg(any(
218    target_os = "android",
219    target_os = "dragonfly",
220    target_os = "freebsd",
221    target_os = "linux",
222    target_os = "openbsd"
223))]
224pub use libc::_PC_REC_INCR_XFER_SIZE;
225
226#[cfg(any(
227    target_os = "android",
228    target_os = "dragonfly",
229    target_os = "freebsd",
230    target_os = "linux",
231    target_os = "openbsd",
232    target_os = "redox"
233))]
234pub use libc::{_PC_REC_MAX_XFER_SIZE, _PC_REC_MIN_XFER_SIZE, _PC_REC_XFER_ALIGN};
235
236#[cfg(any(
237    target_os = "android",
238    target_os = "dragonfly",
239    target_os = "freebsd",
240    target_os = "illumos",
241    target_os = "linux",
242    target_os = "netbsd",
243    target_os = "openbsd",
244    target_os = "redox",
245    target_os = "solaris"
246))]
247pub use libc::_PC_SYMLINK_MAX;
248
249#[cfg(any(
250    target_os = "android",
251    target_os = "dragonfly",
252    target_os = "freebsd",
253    target_os = "illumos",
254    target_os = "linux",
255    target_os = "openbsd",
256    target_os = "redox",
257    target_os = "solaris"
258))]
259pub use libc::{_PC_ASYNC_IO, _PC_PRIO_IO};
260
261#[cfg(any(
262    target_os = "android",
263    target_os = "dragonfly",
264    target_os = "freebsd",
265    target_os = "illumos",
266    target_os = "linux",
267    target_os = "netbsd",
268    target_os = "openbsd",
269    target_os = "redox",
270    target_os = "solaris"
271))]
272pub use libc::_PC_SYNC_IO;
273
274#[cfg(any(target_os = "dragonfly", target_os = "openbsd"))]
275pub use libc::_PC_TIMESTAMP_RESOLUTION;
276
277#[cfg(all(unix, not(target_os = "redox")))]
278pub use libc::{
279    _SC_2_C_BIND, _SC_2_C_DEV, _SC_2_CHAR_TERM, _SC_2_FORT_DEV, _SC_2_FORT_RUN, _SC_2_LOCALEDEF,
280    _SC_2_SW_DEV, _SC_2_UPE, _SC_2_VERSION, _SC_AIO_LISTIO_MAX, _SC_AIO_MAX,
281    _SC_AIO_PRIO_DELTA_MAX, _SC_ASYNCHRONOUS_IO, _SC_ATEXIT_MAX, _SC_BC_BASE_MAX, _SC_BC_DIM_MAX,
282    _SC_BC_SCALE_MAX, _SC_BC_STRING_MAX, _SC_COLL_WEIGHTS_MAX, _SC_DELAYTIMER_MAX,
283    _SC_EXPR_NEST_MAX, _SC_FSYNC, _SC_GETGR_R_SIZE_MAX, _SC_GETPW_R_SIZE_MAX, _SC_IOV_MAX,
284    _SC_JOB_CONTROL, _SC_LINE_MAX, _SC_MAPPED_FILES, _SC_MEMLOCK, _SC_MEMLOCK_RANGE,
285    _SC_MEMORY_PROTECTION, _SC_MESSAGE_PASSING, _SC_MQ_OPEN_MAX, _SC_MQ_PRIO_MAX,
286    _SC_NPROCESSORS_CONF, _SC_NPROCESSORS_ONLN, _SC_PHYS_PAGES, _SC_PRIORITIZED_IO,
287    _SC_PRIORITY_SCHEDULING, _SC_REALTIME_SIGNALS, _SC_RTSIG_MAX, _SC_SAVED_IDS, _SC_SEM_NSEMS_MAX,
288    _SC_SEM_VALUE_MAX, _SC_SEMAPHORES, _SC_SHARED_MEMORY_OBJECTS, _SC_SIGQUEUE_MAX,
289    _SC_SYNCHRONIZED_IO, _SC_THREAD_ATTR_STACKADDR, _SC_THREAD_ATTR_STACKSIZE,
290    _SC_THREAD_DESTRUCTOR_ITERATIONS, _SC_THREAD_KEYS_MAX, _SC_THREAD_PRIO_INHERIT,
291    _SC_THREAD_PRIO_PROTECT, _SC_THREAD_PRIORITY_SCHEDULING, _SC_THREAD_PROCESS_SHARED,
292    _SC_THREAD_SAFE_FUNCTIONS, _SC_THREAD_STACK_MIN, _SC_THREAD_THREADS_MAX, _SC_THREADS,
293    _SC_TIMER_MAX, _SC_TIMERS, _SC_XOPEN_CRYPT, _SC_XOPEN_ENH_I18N, _SC_XOPEN_LEGACY,
294    _SC_XOPEN_REALTIME, _SC_XOPEN_REALTIME_THREADS, _SC_XOPEN_SHM, _SC_XOPEN_UNIX,
295    _SC_XOPEN_VERSION, _SC_XOPEN_XCU_VERSION,
296};
297
298#[cfg(any(
299    target_os = "linux",
300    target_vendor = "apple",
301    target_os = "netbsd",
302    target_os = "fuchsia"
303))]
304pub use libc::{
305    _SC_PASS_MAX, _SC_XBS5_ILP32_OFF32, _SC_XBS5_ILP32_OFFBIG, _SC_XBS5_LP64_OFF64,
306    _SC_XBS5_LPBIG_OFFBIG,
307};
308
309#[cfg(target_os = "redox")]
310pub use libc::{_SC_HOST_NAME_MAX, _SC_SYMLOOP_MAX};
311
312pub struct UnameInfo {
313    pub sysname: String,
314    pub nodename: String,
315    pub release: String,
316    pub version: String,
317    pub machine: String,
318}
319
320#[derive(Debug)]
321pub struct UnameDecodeError {
322    pub bytes: Vec<u8>,
323    pub error: core::str::Utf8Error,
324}
325
326#[cfg(all(unix, not(target_os = "redox")))]
327#[derive(Clone, Copy, Debug)]
328pub struct StatVfsInfo {
329    pub f_bsize: libc::c_ulong,
330    pub f_frsize: libc::c_ulong,
331    pub f_blocks: libc::fsblkcnt_t,
332    pub f_bfree: libc::fsblkcnt_t,
333    pub f_bavail: libc::fsblkcnt_t,
334    pub f_files: libc::fsfilcnt_t,
335    pub f_ffree: libc::fsfilcnt_t,
336    pub f_favail: libc::fsfilcnt_t,
337    pub f_flag: libc::c_ulong,
338    pub f_namemax: libc::c_ulong,
339    pub f_fsid: libc::c_ulong,
340}
341
342#[cfg(all(unix, not(target_os = "redox")))]
343#[derive(Clone, Debug)]
344pub struct RawDirEntry {
345    pub name: Vec<u8>,
346    pub d_type: Option<u8>,
347    pub ino: u64,
348}
349
350#[cfg(all(unix, not(target_os = "redox")))]
351pub struct FdDirStream(NonNull<libc::DIR>);
352
353#[cfg(all(target_os = "linux", target_env = "gnu"))]
354pub type PriorityWhichType = libc::__priority_which_t;
355#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
356pub type PriorityWhichType = libc::c_int;
357
358#[cfg(target_os = "freebsd")]
359pub type PriorityWhoType = i32;
360#[cfg(not(target_os = "freebsd"))]
361pub type PriorityWhoType = u32;
362
363#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
364#[derive(Clone, Debug)]
365pub enum PosixSpawnFileAction {
366    Open {
367        fd: i32,
368        path: CString,
369        oflag: i32,
370        mode: u32,
371    },
372    Close {
373        fd: i32,
374    },
375    Dup2 {
376        fd: i32,
377        newfd: i32,
378    },
379}
380
381#[cfg(all(
382    any(target_os = "linux", target_os = "freebsd", target_os = "android"),
383    not(target_env = "musl")
384))]
385pub struct PosixSpawnScheduler {
386    pub policy: Option<i32>,
387    pub param: libc::sched_param,
388}
389
390#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
391pub struct PosixSpawnConfig<'a> {
392    pub path: &'a CStr,
393    pub args: &'a [CString],
394    pub env: &'a [CString],
395    pub file_actions: &'a [PosixSpawnFileAction],
396    pub setsigdef: Option<&'a [i32]>,
397    pub setpgroup: Option<libc::pid_t>,
398    pub resetids: bool,
399    pub setsid: bool,
400    pub setsigmask: Option<&'a [i32]>,
401    pub spawnp: bool,
402    #[cfg(all(
403        any(target_os = "linux", target_os = "freebsd", target_os = "android"),
404        not(target_env = "musl")
405    ))]
406    pub scheduler: Option<PosixSpawnScheduler>,
407}
408
409pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> std::io::Result<()> {
410    use nix::fcntl;
411
412    let flags = fcntl::FdFlag::from_bits_truncate(
413        fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD).map_err(std::io::Error::from)?,
414    );
415    let mut new_flags = flags;
416    new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable);
417    if flags != new_flags {
418        fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags)).map_err(std::io::Error::from)?;
419    }
420    Ok(())
421}
422
423pub fn is_session_leader() -> bool {
424    unsafe { libc::getsid(0) == libc::getpid() }
425}
426
427pub fn getpid() -> libc::pid_t {
428    unsafe { libc::getpid() }
429}
430
431#[cfg(all(unix, not(target_os = "redox")))]
432pub fn dup_fd(fd: BorrowedFd<'_>) -> std::io::Result<std::os::fd::OwnedFd> {
433    nix::unistd::dup(fd).map_err(std::io::Error::from)
434}
435
436#[cfg(not(target_os = "redox"))]
437pub fn symlinkat(src: &CStr, dir_fd: BorrowedFd<'_>, dst: &CStr) -> std::io::Result<()> {
438    nix::unistd::symlinkat(src, dir_fd, dst).map_err(std::io::Error::from)
439}
440
441#[cfg(target_os = "redox")]
442pub fn symlink(src: &CStr, dst: &CStr) -> std::io::Result<()> {
443    let ret = unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) };
444    if ret < 0 {
445        Err(std::io::Error::last_os_error())
446    } else {
447        Ok(())
448    }
449}
450
451#[cfg(not(target_os = "redox"))]
452pub fn chroot(path: &Path) -> std::io::Result<()> {
453    nix::unistd::chroot(path).map_err(std::io::Error::from)
454}
455
456#[cfg(all(unix, not(target_os = "redox")))]
457pub fn fchmodat(dirfd: i32, path: &CStr, mode: libc::mode_t, flags: i32) -> std::io::Result<()> {
458    let ret = unsafe { libc::fchmodat(dirfd, path.as_ptr(), mode, flags) };
459    if ret == 0 {
460        Ok(())
461    } else {
462        Err(std::io::Error::last_os_error())
463    }
464}
465
466#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd"))]
467pub fn lchmod(path: &CStr, mode: libc::mode_t) -> std::io::Result<()> {
468    unsafe extern "C" {
469        fn lchmod(path: *const libc::c_char, mode: libc::mode_t) -> libc::c_int;
470    }
471    if unsafe { lchmod(path.as_ptr(), mode) } == 0 {
472        Ok(())
473    } else {
474        Err(std::io::Error::last_os_error())
475    }
476}
477
478#[cfg(target_os = "macos")]
479pub fn fcopyfile(in_fd: i32, out_fd: i32, flags: u32) -> std::io::Result<()> {
480    unsafe extern "C" {
481        fn fcopyfile(
482            in_fd: libc::c_int,
483            out_fd: libc::c_int,
484            state: *mut libc::c_void,
485            flags: u32,
486        ) -> libc::c_int;
487    }
488    let ret = unsafe { fcopyfile(in_fd, out_fd, core::ptr::null_mut(), flags) };
489    if ret < 0 {
490        Err(std::io::Error::last_os_error())
491    } else {
492        Ok(())
493    }
494}
495
496#[cfg(unix)]
497pub fn link_paths(
498    src_dir_fd: i32,
499    src: &CStr,
500    dst_dir_fd: i32,
501    dst: &CStr,
502    follow_symlinks: bool,
503) -> std::io::Result<()> {
504    let flags = if follow_symlinks {
505        libc::AT_SYMLINK_FOLLOW
506    } else {
507        0
508    };
509    let ret = unsafe { libc::linkat(src_dir_fd, src.as_ptr(), dst_dir_fd, dst.as_ptr(), flags) };
510    if ret != 0 {
511        Err(std::io::Error::last_os_error())
512    } else {
513        Ok(())
514    }
515}
516
517#[cfg(all(unix, not(target_os = "redox")))]
518fn statvfs_info_from_raw(st: libc::statvfs) -> StatVfsInfo {
519    let f_fsid = {
520        let ptr = core::ptr::addr_of!(st.f_fsid) as *const u8;
521        let size = core::mem::size_of_val(&st.f_fsid);
522        if size >= 8 {
523            let bytes = unsafe { core::slice::from_raw_parts(ptr, 8) };
524            u64::from_ne_bytes([
525                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
526            ]) as libc::c_ulong
527        } else if size >= 4 {
528            let bytes = unsafe { core::slice::from_raw_parts(ptr, 4) };
529            u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as libc::c_ulong
530        } else {
531            0
532        }
533    };
534
535    StatVfsInfo {
536        f_bsize: st.f_bsize,
537        f_frsize: st.f_frsize,
538        f_blocks: st.f_blocks,
539        f_bfree: st.f_bfree,
540        f_bavail: st.f_bavail,
541        f_files: st.f_files,
542        f_ffree: st.f_ffree,
543        f_favail: st.f_favail,
544        f_flag: st.f_flag,
545        f_namemax: st.f_namemax,
546        f_fsid,
547    }
548}
549
550#[cfg(all(unix, not(target_os = "redox")))]
551pub fn statvfs_path(path: &CStr) -> std::io::Result<StatVfsInfo> {
552    let mut st: libc::statvfs = unsafe { core::mem::zeroed() };
553    let ret = unsafe { libc::statvfs(path.as_ptr(), &mut st) };
554    if ret != 0 {
555        Err(std::io::Error::last_os_error())
556    } else {
557        Ok(statvfs_info_from_raw(st))
558    }
559}
560
561#[cfg(all(unix, not(target_os = "redox")))]
562pub fn statvfs_fd(fd: i32) -> std::io::Result<StatVfsInfo> {
563    let mut st: libc::statvfs = unsafe { core::mem::zeroed() };
564    let ret = unsafe { libc::fstatvfs(fd, &mut st) };
565    if ret != 0 {
566        Err(std::io::Error::last_os_error())
567    } else {
568        Ok(statvfs_info_from_raw(st))
569    }
570}
571
572#[cfg(not(target_os = "redox"))]
573pub fn mknod(path: &CStr, mode: libc::mode_t, device: libc::dev_t) -> std::io::Result<()> {
574    let ret = unsafe { libc::mknod(path.as_ptr(), mode, device) };
575    if ret == 0 {
576        Ok(())
577    } else {
578        Err(std::io::Error::last_os_error())
579    }
580}
581
582#[cfg(all(not(target_os = "redox"), not(target_vendor = "apple")))]
583pub fn mknodat(
584    dir_fd: i32,
585    path: &CStr,
586    mode: libc::mode_t,
587    device: libc::dev_t,
588) -> std::io::Result<()> {
589    let ret = unsafe { libc::mknodat(dir_fd, path.as_ptr(), mode, device) };
590    if ret == 0 {
591        Ok(())
592    } else {
593        Err(std::io::Error::last_os_error())
594    }
595}
596
597fn uid_from_raw(uid: u32) -> nix::unistd::Uid {
598    nix::unistd::Uid::from_raw(uid)
599}
600
601fn gid_from_raw(gid: u32) -> nix::unistd::Gid {
602    nix::unistd::Gid::from_raw(gid)
603}
604
605pub fn fchown(fd: BorrowedFd<'_>, uid: Option<u32>, gid: Option<u32>) -> std::io::Result<()> {
606    nix::unistd::fchown(fd, uid.map(uid_from_raw), gid.map(gid_from_raw))
607        .map_err(std::io::Error::from)
608}
609
610#[cfg(not(windows))]
611pub fn stat_fd(fd: crate::crt_fd::Borrowed<'_>) -> std::io::Result<crate::fileutils::StatStruct> {
612    crate::fileutils::fstat(fd)
613}
614
615#[cfg(not(target_os = "redox"))]
616pub fn fchdir(fd: i32) -> std::io::Result<()> {
617    let ret = unsafe { libc::fchdir(fd) };
618    if ret == 0 {
619        Ok(())
620    } else {
621        Err(std::io::Error::last_os_error())
622    }
623}
624
625pub fn fork() -> std::io::Result<libc::pid_t> {
626    let pid = unsafe { libc::fork() };
627    if pid == -1 {
628        Err(std::io::Error::last_os_error())
629    } else {
630        Ok(pid)
631    }
632}
633
634pub fn write_fd(fd: BorrowedFd<'_>, buf: &[u8]) -> std::io::Result<usize> {
635    nix::unistd::write(fd, buf).map_err(std::io::Error::from)
636}
637
638pub fn pread(fd: i32, buf: &mut [u8], offset: libc::off_t) -> std::io::Result<usize> {
639    let ret = unsafe { libc::pread(fd, buf.as_mut_ptr().cast(), buf.len(), offset) };
640    if ret == -1 {
641        Err(std::io::Error::last_os_error())
642    } else {
643        Ok(ret as usize)
644    }
645}
646
647pub fn pwrite(fd: i32, buf: &[u8], offset: libc::off_t) -> std::io::Result<usize> {
648    let ret = unsafe { libc::pwrite(fd, buf.as_ptr().cast(), buf.len(), offset) };
649    if ret == -1 {
650        Err(std::io::Error::last_os_error())
651    } else {
652        Ok(ret as usize)
653    }
654}
655
656pub fn fchownat(
657    dir_fd: BorrowedFd<'_>,
658    path: &OsStr,
659    uid: Option<u32>,
660    gid: Option<u32>,
661    follow_symlinks: bool,
662) -> std::io::Result<()> {
663    let flag = if follow_symlinks {
664        nix::fcntl::AtFlags::empty()
665    } else {
666        nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW
667    };
668    nix::unistd::fchownat(
669        dir_fd,
670        path,
671        uid.map(uid_from_raw),
672        gid.map(gid_from_raw),
673        flag,
674    )
675    .map_err(std::io::Error::from)
676}
677
678pub fn uname_info() -> Result<UnameInfo, UnameDecodeError> {
679    fn decode(value: &CStr) -> Result<String, UnameDecodeError> {
680        core::str::from_utf8(value.to_bytes())
681            .map(str::to_owned)
682            .map_err(|error| UnameDecodeError {
683                bytes: value.to_bytes().to_vec(),
684                error,
685            })
686    }
687
688    let info = rustix::system::uname();
689    Ok(UnameInfo {
690        sysname: decode(info.sysname())?,
691        nodename: decode(info.nodename())?,
692        release: decode(info.release())?,
693        version: decode(info.version())?,
694        machine: decode(info.machine())?,
695    })
696}
697
698#[cfg(any(
699    target_os = "dragonfly",
700    target_os = "freebsd",
701    target_os = "linux",
702    target_os = "android",
703    target_os = "netbsd",
704    target_os = "openbsd"
705))]
706pub fn pipe2(flags: libc::c_int) -> std::io::Result<(std::os::fd::OwnedFd, std::os::fd::OwnedFd)> {
707    nix::unistd::pipe2(nix::fcntl::OFlag::from_bits_truncate(flags)).map_err(std::io::Error::from)
708}
709
710#[cfg(not(target_os = "redox"))]
711pub fn pipe() -> std::io::Result<(OwnedFd, OwnedFd)> {
712    let (rfd, wfd) = nix::unistd::pipe().map_err(std::io::Error::from)?;
713    set_inheritable(rfd.as_fd(), false)?;
714    set_inheritable(wfd.as_fd(), false)?;
715    Ok((rfd, wfd))
716}
717
718pub fn sched_yield() -> std::io::Result<()> {
719    nix::sched::sched_yield().map_err(std::io::Error::from)
720}
721
722#[cfg(not(target_os = "redox"))]
723pub fn nice(increment: i32) -> std::io::Result<i32> {
724    crate::os::clear_errno();
725    let res = unsafe { libc::nice(increment) };
726    if res == -1 && crate::os::get_errno() != 0 {
727        Err(std::io::Error::last_os_error())
728    } else {
729        Ok(res)
730    }
731}
732
733#[cfg(not(target_os = "redox"))]
734pub fn sched_get_priority_max(policy: i32) -> std::io::Result<i32> {
735    let max = unsafe { libc::sched_get_priority_max(policy) };
736    if max == -1 {
737        Err(std::io::Error::last_os_error())
738    } else {
739        Ok(max)
740    }
741}
742
743#[cfg(not(target_os = "redox"))]
744pub fn sched_get_priority_min(policy: i32) -> std::io::Result<i32> {
745    let min = unsafe { libc::sched_get_priority_min(policy) };
746    if min == -1 {
747        Err(std::io::Error::last_os_error())
748    } else {
749        Ok(min)
750    }
751}
752
753#[cfg(not(target_os = "redox"))]
754pub fn fchmod(fd: BorrowedFd<'_>, mode: u32) -> std::io::Result<()> {
755    nix::sys::stat::fchmod(
756        fd,
757        nix::sys::stat::Mode::from_bits_truncate(mode as libc::mode_t),
758    )
759    .map_err(std::io::Error::from)
760}
761
762#[cfg(target_os = "redox")]
763pub fn utimes(
764    path: &Path,
765    acc: core::time::Duration,
766    modif: core::time::Duration,
767) -> std::io::Result<()> {
768    let tv = |d: core::time::Duration| libc::timeval {
769        tv_sec: d.as_secs() as _,
770        tv_usec: d.subsec_micros() as _,
771    };
772    nix::sys::stat::utimes(path, &tv(acc).into(), &tv(modif).into()).map_err(std::io::Error::from)
773}
774
775#[cfg(all(any(target_os = "wasi", unix), not(target_os = "redox")))]
776pub fn set_file_times_at(
777    dir_fd: i32,
778    path: &CStr,
779    access: core::time::Duration,
780    modified: core::time::Duration,
781    follow_symlinks: bool,
782) -> std::io::Result<()> {
783    let ts = |d: core::time::Duration| libc::timespec {
784        tv_sec: d.as_secs() as _,
785        tv_nsec: d.subsec_nanos() as _,
786    };
787    let times = [ts(access), ts(modified)];
788    let ret = unsafe {
789        libc::utimensat(
790            dir_fd,
791            path.as_ptr(),
792            times.as_ptr(),
793            if follow_symlinks {
794                0
795            } else {
796                libc::AT_SYMLINK_NOFOLLOW
797            },
798        )
799    };
800    if ret < 0 {
801        Err(std::io::Error::last_os_error())
802    } else {
803        Ok(())
804    }
805}
806
807#[cfg(target_os = "macos")]
808#[must_use]
809pub fn get_number_of_os_threads() -> isize {
810    type MachPortT = libc::c_uint;
811    type KernReturnT = libc::c_int;
812    type MachMsgTypeNumberT = libc::c_uint;
813    type ThreadActArrayT = *mut MachPortT;
814    const KERN_SUCCESS: KernReturnT = 0;
815    unsafe extern "C" {
816        fn mach_task_self() -> MachPortT;
817        fn task_for_pid(
818            task: MachPortT,
819            pid: libc::c_int,
820            target_task: *mut MachPortT,
821        ) -> KernReturnT;
822        fn task_threads(
823            target_task: MachPortT,
824            act_list: *mut ThreadActArrayT,
825            act_list_cnt: *mut MachMsgTypeNumberT,
826        ) -> KernReturnT;
827        fn vm_deallocate(
828            target_task: MachPortT,
829            address: libc::uintptr_t,
830            size: libc::uintptr_t,
831        ) -> KernReturnT;
832    }
833
834    let self_task = unsafe { mach_task_self() };
835    let mut proc_task: MachPortT = 0;
836    if unsafe { task_for_pid(self_task, libc::getpid(), &mut proc_task) } == KERN_SUCCESS {
837        let mut threads: ThreadActArrayT = core::ptr::null_mut();
838        let mut n_threads: MachMsgTypeNumberT = 0;
839        if unsafe { task_threads(proc_task, &mut threads, &mut n_threads) } == KERN_SUCCESS {
840            if !threads.is_null() {
841                let _ = unsafe {
842                    vm_deallocate(
843                        self_task,
844                        threads as libc::uintptr_t,
845                        (n_threads as usize * core::mem::size_of::<MachPortT>()) as libc::uintptr_t,
846                    )
847                };
848            }
849            return n_threads as isize;
850        }
851    }
852    0
853}
854
855#[cfg(target_os = "linux")]
856#[must_use]
857pub fn get_number_of_os_threads() -> isize {
858    use std::io::Read as _;
859
860    let mut file = match crate::fs::open("/proc/self/stat") {
861        Ok(f) => f,
862        Err(_) => return 0,
863    };
864    let mut buf = [0u8; 160];
865    let n = match file.read(&mut buf) {
866        Ok(n) => n,
867        Err(_) => return 0,
868    };
869    let line = match core::str::from_utf8(&buf[..n]) {
870        Ok(s) => s,
871        Err(_) => return 0,
872    };
873    if let Some(field) = line.split_whitespace().nth(19) {
874        return field.parse::<isize>().unwrap_or(0);
875    }
876    0
877}
878
879#[cfg(not(any(target_os = "macos", target_os = "linux")))]
880#[must_use]
881pub fn get_number_of_os_threads() -> isize {
882    0
883}
884
885#[derive(Clone, Copy, Debug, Eq, PartialEq)]
886pub struct Permissions {
887    pub is_readable: bool,
888    pub is_writable: bool,
889    pub is_executable: bool,
890}
891
892#[derive(Clone, Copy, Debug, Eq, PartialEq)]
893pub enum AccessError {
894    InvalidMode,
895    Os(i32),
896}
897
898impl From<std::io::Error> for AccessError {
899    fn from(value: std::io::Error) -> Self {
900        Self::Os(value.raw_os_error().unwrap_or(0))
901    }
902}
903
904pub use crate::os::{AccessMode, F_OK, R_OK, W_OK, X_OK};
905
906fn get_permissions(mode: u32) -> Permissions {
907    Permissions {
908        is_readable: mode & 4 != 0,
909        is_writable: mode & 2 != 0,
910        is_executable: mode & 1 != 0,
911    }
912}
913
914pub fn get_right_permission(
915    mode: u32,
916    file_owner: u32,
917    file_group: u32,
918) -> std::io::Result<Permissions> {
919    let owner_mode = (mode & 0o700) >> 6;
920    let owner_permissions = get_permissions(owner_mode);
921
922    let group_mode = (mode & 0o070) >> 3;
923    let group_permissions = get_permissions(group_mode);
924
925    let others_mode = mode & 0o007;
926    let others_permissions = get_permissions(others_mode);
927
928    let user_id = nix::unistd::getuid().as_raw();
929    let groups_ids = getgroups()?;
930
931    if file_owner == user_id {
932        Ok(owner_permissions)
933    } else if groups_ids.contains(&file_group) {
934        Ok(group_permissions)
935    } else {
936        Ok(others_permissions)
937    }
938}
939
940#[cfg(any(target_os = "macos", target_os = "ios"))]
941pub fn getgroups() -> std::io::Result<Vec<u32>> {
942    use core::ptr;
943    use libc::{c_int, gid_t};
944    use nix::errno::Errno;
945
946    let ret = unsafe { libc::getgroups(0, ptr::null_mut()) };
947    let mut groups =
948        Vec::<gid_t>::with_capacity(Errno::result(ret).map_err(std::io::Error::from)? as usize);
949    let ret = unsafe { libc::getgroups(groups.capacity() as c_int, groups.as_mut_ptr()) };
950
951    Errno::result(ret).map_err(std::io::Error::from).map(|s| {
952        unsafe { groups.set_len(s as usize) };
953        groups.into_iter().collect()
954    })
955}
956
957#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "redox")))]
958pub fn getgroups() -> std::io::Result<Vec<u32>> {
959    nix::unistd::getgroups()
960        .map(|groups| groups.into_iter().map(|gid| gid.as_raw()).collect())
961        .map_err(std::io::Error::from)
962}
963
964#[cfg(target_os = "redox")]
965pub fn getgroups() -> std::io::Result<Vec<u32>> {
966    Err(std::io::Error::from_raw_os_error(libc::EOPNOTSUPP))
967}
968
969pub fn check_access(path: &Path, mode: u8) -> Result<bool, AccessError> {
970    use std::os::unix::fs::MetadataExt;
971
972    let Some(mode) = AccessMode::from_bits(mode) else {
973        return Err(AccessError::InvalidMode);
974    };
975
976    let metadata = match crate::fs::metadata(path) {
977        Ok(m) => m,
978        Err(_) => return Ok(false),
979    };
980
981    if mode.is_empty() {
982        return Ok(true);
983    }
984
985    let perm = get_right_permission(metadata.mode(), metadata.uid(), metadata.gid())?;
986
987    let r_ok = !mode.contains(&crate::os::AccessFlag::R) || perm.is_readable;
988    let w_ok = !mode.contains(&crate::os::AccessFlag::W) || perm.is_writable;
989    let x_ok = !mode.contains(&crate::os::AccessFlag::X) || perm.is_executable;
990
991    Ok(r_ok && w_ok && x_ok)
992}
993
994pub fn close_fds(above: i32, keep: &[BorrowedFd<'_>]) {
995    #[cfg(not(target_os = "redox"))]
996    if close_dir_fds(above, keep).is_ok() {
997        return;
998    }
999    #[cfg(target_os = "redox")]
1000    if close_filetable_fds(above, keep).is_ok() {
1001        return;
1002    }
1003    close_fds_brute_force(above, keep)
1004}
1005
1006#[allow(clippy::too_many_arguments)]
1007pub fn setup_child_fds(
1008    fds_to_keep: &[BorrowedFd<'_>],
1009    errpipe_write: BorrowedFd<'_>,
1010    p2cread: i32,
1011    p2cwrite: i32,
1012    c2pread: i32,
1013    c2pwrite: i32,
1014    errread: i32,
1015    errwrite: i32,
1016    errpipe_read: i32,
1017) -> std::io::Result<()> {
1018    for &fd in fds_to_keep {
1019        if fd.as_raw_fd() != errpipe_write.as_raw_fd() {
1020            set_inheritable(fd, true)?;
1021        }
1022    }
1023
1024    for fd in [p2cwrite, c2pread, errread] {
1025        if fd >= 0 {
1026            nix::unistd::close(fd).map_err(std::io::Error::from)?;
1027        }
1028    }
1029    nix::unistd::close(errpipe_read).map_err(std::io::Error::from)?;
1030
1031    let c2pwrite = if c2pwrite == 0 {
1032        let fd = unsafe { BorrowedFd::borrow_raw(c2pwrite) };
1033        let dup = nix::unistd::dup(fd).map_err(std::io::Error::from)?;
1034        set_inheritable(dup.as_fd(), true)?;
1035        dup.into_raw_fd()
1036    } else {
1037        c2pwrite
1038    };
1039
1040    let mut errwrite = errwrite;
1041    while errwrite == 0 || errwrite == 1 {
1042        let fd = unsafe { BorrowedFd::borrow_raw(errwrite) };
1043        let dup = nix::unistd::dup(fd).map_err(std::io::Error::from)?;
1044        set_inheritable(dup.as_fd(), true)?;
1045        errwrite = dup.into_raw_fd();
1046    }
1047
1048    dup_into_stdio(p2cread, 0)?;
1049    dup_into_stdio(c2pwrite, 1)?;
1050    dup_into_stdio(errwrite, 2)?;
1051    Ok(())
1052}
1053
1054fn dup_into_stdio(fd: i32, io_fd: i32) -> std::io::Result<()> {
1055    if fd < 0 {
1056        return Ok(());
1057    }
1058    let fd = unsafe { BorrowedFd::borrow_raw(fd) };
1059    if fd.as_raw_fd() == io_fd {
1060        set_inheritable(fd, true)
1061    } else {
1062        match io_fd {
1063            0 => nix::unistd::dup2_stdin(fd).map_err(std::io::Error::from),
1064            1 => nix::unistd::dup2_stdout(fd).map_err(std::io::Error::from),
1065            2 => nix::unistd::dup2_stderr(fd).map_err(std::io::Error::from),
1066            _ => unreachable!(),
1067        }
1068    }
1069}
1070
1071pub fn chdir(cwd: &CStr) -> nix::Result<()> {
1072    nix::unistd::chdir(cwd)
1073}
1074
1075pub fn set_umask(child_umask: i32) {
1076    if child_umask >= 0 {
1077        unsafe { libc::umask(child_umask as libc::mode_t) };
1078    }
1079}
1080
1081pub fn umask(mask: libc::mode_t) -> libc::mode_t {
1082    unsafe { libc::umask(mask) }
1083}
1084
1085#[cfg(not(any(target_os = "redox", target_os = "android")))]
1086pub fn sync() {
1087    unsafe { libc::sync() };
1088}
1089
1090pub fn getlogin() -> Option<CString> {
1091    let ptr = unsafe { libc::getlogin() };
1092    if ptr.is_null() {
1093        None
1094    } else {
1095        Some(unsafe { CStr::from_ptr(ptr) }.to_owned())
1096    }
1097}
1098
1099pub fn restore_signals() {
1100    unsafe {
1101        libc::signal(libc::SIGPIPE, libc::SIG_DFL);
1102        libc::signal(libc::SIGXFSZ, libc::SIG_DFL);
1103    }
1104}
1105
1106pub fn setsid_if_needed(call_setsid: bool) -> nix::Result<()> {
1107    if call_setsid {
1108        nix::unistd::setsid()?;
1109    }
1110    Ok(())
1111}
1112
1113pub fn setpgid_if_needed(pgid_to_set: libc::pid_t) -> nix::Result<()> {
1114    if pgid_to_set > -1 {
1115        nix::unistd::setpgid(
1116            nix::unistd::Pid::from_raw(0),
1117            nix::unistd::Pid::from_raw(pgid_to_set),
1118        )?;
1119    }
1120    Ok(())
1121}
1122
1123pub fn setgroups_if_needed(groups: Option<&[u32]>) -> nix::Result<()> {
1124    #[cfg(not(any(target_os = "ios", target_os = "redox")))]
1125    if let Some(groups) = groups {
1126        // The caller prepares this array before fork.  Call libc directly so
1127        // macOS performs the requested operation too, and so the child does
1128        // not allocate a second array between fork and exec.
1129        let ret =
1130            unsafe { libc::setgroups(groups.len() as _, groups.as_ptr().cast::<libc::gid_t>()) };
1131        nix::Error::result(ret)?;
1132    }
1133    Ok(())
1134}
1135
1136pub fn setregid_if_needed(gid: Option<u32>) -> nix::Result<()> {
1137    if let Some(gid) = gid.filter(|&x| x != u32::MAX) {
1138        let ret = unsafe { libc::setregid(gid as libc::gid_t, gid as libc::gid_t) };
1139        nix::Error::result(ret)?;
1140    }
1141    Ok(())
1142}
1143
1144pub fn setreuid_if_needed(uid: Option<u32>) -> nix::Result<()> {
1145    if let Some(uid) = uid.filter(|&x| x != u32::MAX) {
1146        let ret = unsafe { libc::setreuid(uid as libc::uid_t, uid as libc::uid_t) };
1147        nix::Error::result(ret)?;
1148    }
1149    Ok(())
1150}
1151
1152pub fn getppid() -> libc::pid_t {
1153    nix::unistd::getppid().as_raw()
1154}
1155
1156pub fn getgid() -> u32 {
1157    nix::unistd::getgid().as_raw()
1158}
1159
1160pub fn getegid() -> u32 {
1161    nix::unistd::getegid().as_raw()
1162}
1163
1164pub fn getpgid(pid: u32) -> std::io::Result<libc::pid_t> {
1165    nix::unistd::getpgid(Some(nix::unistd::Pid::from_raw(pid as i32)))
1166        .map(nix::unistd::Pid::as_raw)
1167        .map_err(std::io::Error::from)
1168}
1169
1170pub fn getpgrp() -> libc::pid_t {
1171    nix::unistd::getpgrp().as_raw()
1172}
1173
1174#[cfg(not(target_os = "redox"))]
1175pub fn getsid(pid: u32) -> std::io::Result<libc::pid_t> {
1176    nix::unistd::getsid(Some(nix::unistd::Pid::from_raw(pid as i32)))
1177        .map(nix::unistd::Pid::as_raw)
1178        .map_err(std::io::Error::from)
1179}
1180
1181pub fn getuid() -> u32 {
1182    nix::unistd::getuid().as_raw()
1183}
1184
1185pub fn geteuid() -> u32 {
1186    nix::unistd::geteuid().as_raw()
1187}
1188
1189#[cfg(not(any(target_os = "wasi", target_os = "android")))]
1190pub fn setgid(gid: u32) -> std::io::Result<()> {
1191    nix::unistd::setgid(gid_from_raw(gid)).map_err(std::io::Error::from)
1192}
1193
1194#[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))]
1195pub fn setegid(egid: u32) -> std::io::Result<()> {
1196    nix::unistd::setegid(gid_from_raw(egid)).map_err(std::io::Error::from)
1197}
1198
1199pub fn setpgid(pid: u32, pgid: u32) -> std::io::Result<()> {
1200    nix::unistd::setpgid(
1201        nix::unistd::Pid::from_raw(pid as i32),
1202        nix::unistd::Pid::from_raw(pgid as i32),
1203    )
1204    .map_err(std::io::Error::from)
1205}
1206
1207pub fn setpgrp() -> std::io::Result<()> {
1208    nix::unistd::setpgid(nix::unistd::Pid::from_raw(0), nix::unistd::Pid::from_raw(0))
1209        .map_err(std::io::Error::from)
1210}
1211
1212#[cfg(not(any(target_os = "wasi", target_os = "redox")))]
1213pub fn setsid() -> std::io::Result<()> {
1214    nix::unistd::setsid()
1215        .map(drop)
1216        .map_err(std::io::Error::from)
1217}
1218
1219#[cfg(not(any(target_os = "wasi", target_os = "redox")))]
1220pub fn tcgetpgrp(fd: BorrowedFd<'_>) -> std::io::Result<libc::pid_t> {
1221    nix::unistd::tcgetpgrp(fd)
1222        .map(nix::unistd::Pid::as_raw)
1223        .map_err(std::io::Error::from)
1224}
1225
1226#[cfg(not(any(target_os = "wasi", target_os = "redox")))]
1227pub fn tcsetpgrp(fd: BorrowedFd<'_>, pgid: libc::pid_t) -> std::io::Result<()> {
1228    nix::unistd::tcsetpgrp(fd, nix::unistd::Pid::from_raw(pgid)).map_err(std::io::Error::from)
1229}
1230
1231#[cfg(not(target_os = "redox"))]
1232pub fn getpriority(which: PriorityWhichType, who: PriorityWhoType) -> std::io::Result<i32> {
1233    crate::os::clear_errno();
1234    let retval = unsafe { libc::getpriority(which, who) };
1235    if crate::os::get_errno() != 0 {
1236        Err(std::io::Error::last_os_error())
1237    } else {
1238        Ok(retval)
1239    }
1240}
1241
1242#[cfg(not(target_os = "redox"))]
1243pub fn setpriority(
1244    which: PriorityWhichType,
1245    who: PriorityWhoType,
1246    priority: i32,
1247) -> std::io::Result<()> {
1248    let retval = unsafe { libc::setpriority(which, who, priority) };
1249    if retval == -1 {
1250        Err(std::io::Error::last_os_error())
1251    } else {
1252        Ok(())
1253    }
1254}
1255
1256pub fn waitpid(pid: libc::pid_t, status: &mut i32, opt: i32) -> std::io::Result<libc::pid_t> {
1257    let res = unsafe { libc::waitpid(pid, status, opt) };
1258    if res == -1 {
1259        Err(std::io::Error::last_os_error())
1260    } else {
1261        Ok(res)
1262    }
1263}
1264
1265/// `wait3(2)` is `wait4(-1, ...)`.
1266pub fn wait3(options: i32) -> std::io::Result<(libc::pid_t, i32, crate::resource::RUsage)> {
1267    wait4(-1, options)
1268}
1269
1270/// `wait4(2)`. `rusage` is zeroed when `pid == 0` (WNOHANG, no child ready).
1271pub fn wait4(
1272    pid: libc::pid_t,
1273    options: i32,
1274) -> std::io::Result<(libc::pid_t, i32, crate::resource::RUsage)> {
1275    let mut status = 0;
1276    let mut ru = core::mem::MaybeUninit::<libc::rusage>::zeroed();
1277    let res = unsafe { libc::wait4(pid, &mut status, options, ru.as_mut_ptr()) };
1278    if res == -1 {
1279        return Err(std::io::Error::last_os_error());
1280    }
1281    let ru = if res == 0 {
1282        unsafe { core::mem::zeroed() }
1283    } else {
1284        unsafe { ru.assume_init() }
1285    };
1286    Ok((res, status, ru.into()))
1287}
1288
1289pub fn kill(pid: i32, sig: i32) -> std::io::Result<()> {
1290    let ret = unsafe { libc::kill(pid, sig) };
1291    if ret == -1 {
1292        Err(std::io::Error::last_os_error())
1293    } else {
1294        Ok(())
1295    }
1296}
1297
1298/// `killpg(2)`: signal every process in a process group.
1299///
1300/// Not `kill(-pgid, sig)`: that spelling reads a negative pid, and `kill`
1301/// takes its argument from Python where a caller can already pass one, so the
1302/// two are not interchangeable at this layer.
1303pub fn killpg(pgid: i32, sig: i32) -> std::io::Result<()> {
1304    let ret = unsafe { libc::killpg(pgid, sig) };
1305    if ret == -1 {
1306        Err(std::io::Error::last_os_error())
1307    } else {
1308        Ok(())
1309    }
1310}
1311
1312#[cfg(not(any(target_os = "wasi", target_os = "android")))]
1313pub fn setuid(uid: u32) -> std::io::Result<()> {
1314    nix::unistd::setuid(uid_from_raw(uid)).map_err(std::io::Error::from)
1315}
1316
1317#[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))]
1318pub fn seteuid(euid: u32) -> std::io::Result<()> {
1319    nix::unistd::seteuid(uid_from_raw(euid)).map_err(std::io::Error::from)
1320}
1321
1322#[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))]
1323pub fn setreuid(ruid: u32, euid: u32) -> std::io::Result<()> {
1324    let ret = unsafe { libc::setreuid(ruid as libc::uid_t, euid as libc::uid_t) };
1325    nix::Error::result(ret)
1326        .map(drop)
1327        .map_err(std::io::Error::from)
1328}
1329
1330#[cfg(any(
1331    target_os = "android",
1332    target_os = "freebsd",
1333    target_os = "linux",
1334    target_os = "openbsd"
1335))]
1336pub fn setresuid(ruid: u32, euid: u32, suid: u32) -> std::io::Result<()> {
1337    let ret = unsafe {
1338        libc::setresuid(
1339            ruid as libc::uid_t,
1340            euid as libc::uid_t,
1341            suid as libc::uid_t,
1342        )
1343    };
1344    nix::Error::result(ret)
1345        .map(drop)
1346        .map_err(std::io::Error::from)
1347}
1348
1349#[cfg(not(any(target_os = "wasi", target_os = "solaris", target_os = "illumos")))]
1350pub fn login_tty(fd: i32) -> std::io::Result<()> {
1351    let ret = unsafe { libc::login_tty(fd) };
1352    if ret < 0 {
1353        Err(std::io::Error::last_os_error())
1354    } else {
1355        Ok(())
1356    }
1357}
1358
1359#[cfg(any(target_os = "solaris", target_os = "illumos"))]
1360pub fn login_tty(fd: i32) -> std::io::Result<()> {
1361    if unsafe { libc::setsid() } < 0 {
1362        let err = std::io::Error::last_os_error();
1363        if err.raw_os_error() != Some(libc::EPERM) {
1364            return Err(err);
1365        }
1366    }
1367    if unsafe { libc::ioctl(fd, libc::TIOCSCTTY, core::ptr::null::<libc::c_char>()) } < 0 {
1368        return Err(std::io::Error::last_os_error());
1369    }
1370    if unsafe { libc::dup2(fd, 0) } < 0
1371        || unsafe { libc::dup2(fd, 1) } < 0
1372        || unsafe { libc::dup2(fd, 2) } < 0
1373    {
1374        return Err(std::io::Error::last_os_error());
1375    }
1376    if fd > 2 {
1377        let _ = unsafe { libc::close(fd) };
1378    }
1379    Ok(())
1380}
1381
1382#[cfg(not(target_os = "redox"))]
1383pub fn openpty() -> std::io::Result<(OwnedFd, OwnedFd)> {
1384    let pty = nix::pty::openpty(None, None).map_err(std::io::Error::from)?;
1385    set_inheritable(pty.master.as_fd(), false)?;
1386    set_inheritable(pty.slave.as_fd(), false)?;
1387    Ok((pty.master, pty.slave))
1388}
1389
1390/// `forkpty(3)`. The child is a session leader with the slave as its
1391/// controlling terminal. The parent receives the master fd. The child may
1392/// see `master_fd == -1` (Apple's `forkpty`); that is returned as `-1`.
1393///
1394/// rustix has no `forkpty`. `nix::pty::forkpty` wraps the master in
1395/// `OwnedFd`, which panics on Apple's child `-1`, and its `Child` arm
1396/// drops the master fd the caller returns as the second item.
1397#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
1398pub fn forkpty() -> std::io::Result<(pid_t, i32)> {
1399    let mut master_fd: libc::c_int = -1;
1400    let pid = unsafe {
1401        libc::forkpty(
1402            &mut master_fd,
1403            core::ptr::null_mut(),
1404            core::ptr::null_mut(),
1405            core::ptr::null_mut(),
1406        )
1407    };
1408    if pid < 0 {
1409        return Err(std::io::Error::last_os_error());
1410    }
1411    if pid != 0 && master_fd >= 0 {
1412        let fd = unsafe { BorrowedFd::borrow_raw(master_fd) };
1413        set_inheritable(fd, false)?;
1414    }
1415    Ok((pid, master_fd))
1416}
1417
1418pub fn ttyname(fd: BorrowedFd<'_>) -> std::io::Result<OsString> {
1419    nix::unistd::ttyname(fd)
1420        .map(std::path::PathBuf::into_os_string)
1421        .map_err(std::io::Error::from)
1422}
1423
1424pub fn execv(path: &CStr, argv: &[&CStr]) -> std::io::Result<()> {
1425    match nix::unistd::execv(path, argv) {
1426        Ok(never) => match never {},
1427        Err(err) => Err(err.into()),
1428    }
1429}
1430
1431pub fn execve(path: &CStr, argv: &[&CStr], env: &[&CStr]) -> std::io::Result<()> {
1432    match nix::unistd::execve(path, argv, env) {
1433        Ok(never) => match never {},
1434        Err(err) => Err(err.into()),
1435    }
1436}
1437
1438#[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))]
1439pub fn getresuid() -> std::io::Result<(u32, u32, u32)> {
1440    let ret = nix::unistd::getresuid().map_err(std::io::Error::from)?;
1441    Ok((
1442        ret.real.as_raw(),
1443        ret.effective.as_raw(),
1444        ret.saved.as_raw(),
1445    ))
1446}
1447
1448#[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))]
1449pub fn getresgid() -> std::io::Result<(u32, u32, u32)> {
1450    let ret = nix::unistd::getresgid().map_err(std::io::Error::from)?;
1451    Ok((
1452        ret.real.as_raw(),
1453        ret.effective.as_raw(),
1454        ret.saved.as_raw(),
1455    ))
1456}
1457
1458#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
1459pub fn setresgid(rgid: u32, egid: u32, sgid: u32) -> std::io::Result<()> {
1460    let ret = unsafe {
1461        libc::setresgid(
1462            rgid as libc::gid_t,
1463            egid as libc::gid_t,
1464            sgid as libc::gid_t,
1465        )
1466    };
1467    nix::Error::result(ret)
1468        .map(drop)
1469        .map_err(std::io::Error::from)
1470}
1471
1472#[cfg(not(any(target_os = "wasi", target_os = "android", target_os = "redox")))]
1473pub fn setregid(rgid: u32, egid: u32) -> std::io::Result<()> {
1474    let ret = unsafe { libc::setregid(rgid as libc::gid_t, egid as libc::gid_t) };
1475    nix::Error::result(ret)
1476        .map(drop)
1477        .map_err(std::io::Error::from)
1478}
1479
1480#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
1481pub fn initgroups(user: &CStr, gid: u32) -> std::io::Result<()> {
1482    nix::unistd::initgroups(user, gid_from_raw(gid)).map_err(std::io::Error::from)
1483}
1484
1485#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
1486pub fn setgroups_raw(groups: &[u32]) -> std::io::Result<()> {
1487    let gids = groups.iter().copied().map(gid_from_raw).collect::<Vec<_>>();
1488    nix::unistd::setgroups(&gids).map_err(std::io::Error::from)
1489}
1490
1491pub fn dup_noninheritable(fd: BorrowedFd<'_>) -> std::io::Result<OwnedFd> {
1492    let fd = nix::unistd::dup(fd).map_err(std::io::Error::from)?;
1493    set_inheritable(fd.as_fd(), false)?;
1494    Ok(fd)
1495}
1496
1497pub fn dup2(fd: BorrowedFd<'_>, fd2: OwnedFd, inheritable: bool) -> std::io::Result<OwnedFd> {
1498    let mut fd2 = core::mem::ManuallyDrop::new(fd2);
1499    nix::unistd::dup2(fd, &mut fd2).map_err(std::io::Error::from)?;
1500    let fd2 = core::mem::ManuallyDrop::into_inner(fd2);
1501    if !inheritable {
1502        set_inheritable(fd2.as_fd(), false)?;
1503    }
1504    Ok(fd2)
1505}
1506
1507#[cfg(all(unix, not(target_os = "redox")))]
1508impl FdDirStream {
1509    pub fn from_fd(fd: BorrowedFd<'_>) -> std::io::Result<Self> {
1510        let new_fd = dup_fd(fd)?;
1511        let raw_fd = new_fd.into_raw_fd();
1512        let ptr = unsafe { libc::fdopendir(raw_fd) };
1513        match NonNull::new(ptr) {
1514            Some(ptr) => Ok(Self(ptr)),
1515            None => {
1516                unsafe { libc::close(raw_fd) };
1517                Err(std::io::Error::last_os_error())
1518            }
1519        }
1520    }
1521
1522    pub fn next_entry(&mut self) -> std::io::Result<Option<RawDirEntry>> {
1523        loop {
1524            crate::os::clear_errno();
1525            let ptr = unsafe { libc::readdir(self.0.as_ptr()) };
1526            if ptr.is_null() {
1527                let err = crate::os::get_errno();
1528                return if err == 0 {
1529                    Ok(None)
1530                } else {
1531                    Err(std::io::Error::from_raw_os_error(err))
1532                };
1533            }
1534
1535            let entry = unsafe { &*ptr };
1536            let name = unsafe { CStr::from_ptr(entry.d_name.as_ptr()) }.to_bytes();
1537            if name == b"." || name == b".." {
1538                continue;
1539            }
1540            #[cfg(target_os = "freebsd")]
1541            let ino = entry.d_fileno as u64;
1542            #[cfg(not(target_os = "freebsd"))]
1543            let ino = entry.d_ino as u64;
1544
1545            return Ok(Some(RawDirEntry {
1546                name: name.to_vec(),
1547                d_type: (entry.d_type != libc::DT_UNKNOWN).then_some(entry.d_type),
1548                ino,
1549            }));
1550        }
1551    }
1552}
1553
1554#[cfg(all(unix, not(target_os = "redox")))]
1555impl Drop for FdDirStream {
1556    fn drop(&mut self) {
1557        unsafe {
1558            libc::rewinddir(self.0.as_ptr());
1559            libc::closedir(self.0.as_ptr());
1560        }
1561    }
1562}
1563
1564#[cfg(all(unix, not(target_os = "redox")))]
1565impl core::fmt::Debug for FdDirStream {
1566    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1567        f.debug_tuple("FdDirStream").field(&self.0).finish()
1568    }
1569}
1570
1571#[cfg(all(unix, not(target_os = "redox")))]
1572unsafe impl Send for FdDirStream {}
1573#[cfg(all(unix, not(target_os = "redox")))]
1574unsafe impl Sync for FdDirStream {}
1575
1576pub fn get_terminal_size(fd: libc::c_int) -> std::io::Result<(u16, u16)> {
1577    let mut w = libc::winsize {
1578        ws_row: 0,
1579        ws_col: 0,
1580        ws_xpixel: 0,
1581        ws_ypixel: 0,
1582    };
1583    let ret = unsafe { libc::ioctl(fd, libc::TIOCGWINSZ, &mut w) };
1584    if ret < 0 {
1585        Err(std::io::Error::last_os_error())
1586    } else {
1587        Ok((w.ws_col, w.ws_row))
1588    }
1589}
1590
1591#[cfg(target_os = "macos")]
1592pub fn full_fsync(fd: BorrowedFd<'_>) -> std::io::Result<()> {
1593    let ret = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_FULLFSYNC) };
1594    if ret < 0 {
1595        Err(std::io::Error::last_os_error())
1596    } else {
1597        Ok(())
1598    }
1599}
1600
1601#[cfg(all(unix, not(target_os = "redox")))]
1602pub fn madvise(addr: usize, len: usize, advice: i32) -> std::io::Result<()> {
1603    let ret = unsafe { libc::madvise(addr as *mut libc::c_void, len, advice) };
1604    if ret != 0 {
1605        Err(std::io::Error::last_os_error())
1606    } else {
1607        Ok(())
1608    }
1609}
1610
1611pub fn pathconf(path: &CStr, name: i32) -> std::io::Result<Option<libc::c_long>> {
1612    crate::os::clear_errno();
1613    debug_assert_eq!(crate::os::get_errno(), 0);
1614    let raw = unsafe { libc::pathconf(path.as_ptr(), name) };
1615    if raw == -1 {
1616        if crate::os::get_errno() == 0 {
1617            Ok(None)
1618        } else {
1619            Err(std::io::Error::last_os_error())
1620        }
1621    } else {
1622        Ok(Some(raw))
1623    }
1624}
1625
1626pub fn fpathconf(fd: i32, name: i32) -> std::io::Result<Option<libc::c_long>> {
1627    crate::os::clear_errno();
1628    debug_assert_eq!(crate::os::get_errno(), 0);
1629    let raw = unsafe { libc::fpathconf(fd, name) };
1630    if raw == -1 {
1631        if crate::os::get_errno() == 0 {
1632            Ok(None)
1633        } else {
1634            Err(std::io::Error::last_os_error())
1635        }
1636    } else {
1637        Ok(Some(raw))
1638    }
1639}
1640
1641pub fn sysconf(name: i32) -> std::io::Result<libc::c_long> {
1642    crate::os::set_errno(0);
1643    let raw = unsafe { libc::sysconf(name) };
1644    if raw == -1 && crate::os::get_errno() != 0 {
1645        Err(std::io::Error::last_os_error())
1646    } else {
1647        Ok(raw)
1648    }
1649}
1650
1651#[cfg(target_os = "linux")]
1652/// # Safety
1653///
1654/// `buf` must be valid for writes of `buflen` bytes.
1655pub unsafe fn getrandom(
1656    buf: *mut libc::c_void,
1657    buflen: usize,
1658    flags: u32,
1659) -> std::io::Result<usize> {
1660    let len = unsafe { libc::syscall(libc::SYS_getrandom, buf, buflen, flags as usize) as isize };
1661    if len < 0 {
1662        Err(std::io::Error::last_os_error())
1663    } else {
1664        Ok(len as usize)
1665    }
1666}
1667
1668pub fn wcoredump(status: i32) -> bool {
1669    libc::WCOREDUMP(status)
1670}
1671
1672pub fn wifcontinued(status: i32) -> bool {
1673    libc::WIFCONTINUED(status)
1674}
1675
1676pub fn wifstopped(status: i32) -> bool {
1677    libc::WIFSTOPPED(status)
1678}
1679
1680pub fn wifsignaled(status: i32) -> bool {
1681    libc::WIFSIGNALED(status)
1682}
1683
1684pub fn wifexited(status: i32) -> bool {
1685    libc::WIFEXITED(status)
1686}
1687
1688pub fn wexitstatus(status: i32) -> i32 {
1689    libc::WEXITSTATUS(status)
1690}
1691
1692pub fn wstopsig(status: i32) -> i32 {
1693    libc::WSTOPSIG(status)
1694}
1695
1696pub fn wtermsig(status: i32) -> i32 {
1697    libc::WTERMSIG(status)
1698}
1699
1700#[cfg(target_os = "linux")]
1701pub fn pidfd_open(pid: libc::pid_t, flags: u32) -> std::io::Result<OwnedFd> {
1702    let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, flags) as libc::c_long };
1703    if fd == -1 {
1704        Err(std::io::Error::last_os_error())
1705    } else {
1706        Ok(unsafe { OwnedFd::from_raw_fd(fd as libc::c_int) })
1707    }
1708}
1709
1710#[cfg(any(
1711    target_os = "android",
1712    target_os = "freebsd",
1713    target_os = "linux",
1714    target_os = "openbsd"
1715))]
1716pub fn getgrouplist(user: &CStr, gid: u32) -> std::io::Result<Vec<u32>> {
1717    nix::unistd::getgrouplist(user, gid_from_raw(gid))
1718        .map(|groups| groups.into_iter().map(|gid| gid.as_raw()).collect())
1719        .map_err(std::io::Error::from)
1720}
1721
1722#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1723pub fn validate_posix_spawn_signal(sig: i32) -> bool {
1724    nix::sys::signal::Signal::try_from(sig).is_ok()
1725}
1726
1727#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1728pub const fn supports_posix_spawn_setsid() -> bool {
1729    cfg!(any(
1730        target_os = "linux",
1731        target_os = "haiku",
1732        target_os = "solaris",
1733        target_os = "illumos",
1734        target_os = "hurd",
1735    ))
1736}
1737
1738#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1739fn build_posix_spawn_file_actions(
1740    actions: &[PosixSpawnFileAction],
1741) -> std::io::Result<nix::spawn::PosixSpawnFileActions> {
1742    let mut file_actions =
1743        nix::spawn::PosixSpawnFileActions::init().map_err(std::io::Error::from)?;
1744    for action in actions {
1745        match action {
1746            PosixSpawnFileAction::Open {
1747                fd,
1748                path,
1749                oflag,
1750                mode,
1751            } => file_actions
1752                .add_open(
1753                    *fd,
1754                    path.as_c_str(),
1755                    nix::fcntl::OFlag::from_bits_retain(*oflag),
1756                    nix::sys::stat::Mode::from_bits_retain(*mode as libc::mode_t),
1757                )
1758                .map_err(std::io::Error::from)?,
1759            PosixSpawnFileAction::Close { fd } => {
1760                file_actions.add_close(*fd).map_err(std::io::Error::from)?
1761            }
1762            PosixSpawnFileAction::Dup2 { fd, newfd } => file_actions
1763                .add_dup2(*fd, *newfd)
1764                .map_err(std::io::Error::from)?,
1765        }
1766    }
1767    Ok(file_actions)
1768}
1769
1770#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1771fn build_sigset(signals: &[i32]) -> nix::sys::signal::SigSet {
1772    let mut set = nix::sys::signal::SigSet::empty();
1773    for &sig in signals {
1774        let sig = nix::sys::signal::Signal::try_from(sig).expect("validated signal");
1775        set.add(sig);
1776    }
1777    set
1778}
1779
1780#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1781fn build_posix_spawn_attrs(
1782    config: &PosixSpawnConfig<'_>,
1783) -> std::io::Result<nix::spawn::PosixSpawnAttr> {
1784    let mut attrp = nix::spawn::PosixSpawnAttr::init().map_err(std::io::Error::from)?;
1785    let mut flags = nix::spawn::PosixSpawnFlags::empty();
1786
1787    if let Some(sigs) = config.setsigdef {
1788        let set = build_sigset(sigs);
1789        attrp.set_sigdefault(&set).map_err(std::io::Error::from)?;
1790        flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_SETSIGDEF);
1791    }
1792
1793    if let Some(pgid) = config.setpgroup {
1794        attrp
1795            .set_pgroup(nix::unistd::Pid::from_raw(pgid))
1796            .map_err(std::io::Error::from)?;
1797        flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_SETPGROUP);
1798    }
1799
1800    if config.resetids {
1801        flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_RESETIDS);
1802    }
1803
1804    if config.setsid {
1805        #[cfg(any(
1806            target_os = "linux",
1807            target_os = "haiku",
1808            target_os = "solaris",
1809            target_os = "illumos",
1810            target_os = "hurd",
1811        ))]
1812        {
1813            #[allow(clippy::useless_conversion)]
1814            flags.insert(nix::spawn::PosixSpawnFlags::from_bits_retain(
1815                libc::POSIX_SPAWN_SETSID.into(),
1816            ));
1817        }
1818        #[cfg(not(any(
1819            target_os = "linux",
1820            target_os = "haiku",
1821            target_os = "solaris",
1822            target_os = "illumos",
1823            target_os = "hurd",
1824        )))]
1825        {
1826            return Err(std::io::Error::new(
1827                std::io::ErrorKind::Unsupported,
1828                "setsid parameter is not supported on this platform",
1829            ));
1830        }
1831    }
1832
1833    if let Some(sigs) = config.setsigmask {
1834        let set = build_sigset(sigs);
1835        attrp.set_sigmask(&set).map_err(std::io::Error::from)?;
1836        flags.insert(nix::spawn::PosixSpawnFlags::POSIX_SPAWN_SETSIGMASK);
1837    }
1838
1839    #[cfg(all(
1840        any(target_os = "linux", target_os = "freebsd", target_os = "android"),
1841        not(target_env = "musl")
1842    ))]
1843    if let Some(scheduler) = &config.scheduler {
1844        // nix does not wrap these yet; the attr type is transparent over
1845        // posix_spawnattr_t.
1846        let attr_ptr = (&raw mut attrp).cast::<libc::posix_spawnattr_t>();
1847        if let Some(policy) = scheduler.policy {
1848            let err = unsafe { libc::posix_spawnattr_setschedpolicy(attr_ptr, policy) };
1849            if err != 0 {
1850                return Err(std::io::Error::from_raw_os_error(err));
1851            }
1852            flags.insert(nix::spawn::PosixSpawnFlags::from_bits_retain(
1853                libc::POSIX_SPAWN_SETSCHEDULER,
1854            ));
1855        }
1856        let err = unsafe { libc::posix_spawnattr_setschedparam(attr_ptr, &scheduler.param) };
1857        if err != 0 {
1858            return Err(std::io::Error::from_raw_os_error(err));
1859        }
1860        flags.insert(nix::spawn::PosixSpawnFlags::from_bits_retain(
1861            libc::POSIX_SPAWN_SETSCHEDPARAM,
1862        ));
1863    }
1864
1865    if !flags.is_empty() {
1866        attrp.set_flags(flags).map_err(std::io::Error::from)?;
1867    }
1868
1869    Ok(attrp)
1870}
1871
1872#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1873pub fn posix_spawn(config: PosixSpawnConfig<'_>) -> std::io::Result<libc::pid_t> {
1874    let file_actions = build_posix_spawn_file_actions(config.file_actions)?;
1875    let attrp = build_posix_spawn_attrs(&config)?;
1876    let pid = if config.spawnp {
1877        nix::spawn::posix_spawnp(config.path, &file_actions, &attrp, config.args, config.env)
1878    } else {
1879        nix::spawn::posix_spawn(config.path, &file_actions, &attrp, config.args, config.env)
1880    }
1881    .map_err(std::io::Error::from)?;
1882    Ok(pid.into())
1883}
1884
1885#[cfg(target_os = "linux")]
1886pub fn sendfile(
1887    out_fd: BorrowedFd<'_>,
1888    in_fd: BorrowedFd<'_>,
1889    offset: &mut crate::crt_fd::Offset,
1890    count: usize,
1891) -> std::io::Result<usize> {
1892    nix::sys::sendfile::sendfile(out_fd, in_fd, Some(offset), count).map_err(std::io::Error::from)
1893}
1894
1895#[cfg(target_os = "macos")]
1896pub fn sendfile(
1897    in_fd: BorrowedFd<'_>,
1898    out_fd: BorrowedFd<'_>,
1899    offset: crate::crt_fd::Offset,
1900    count: i64,
1901    headers: Option<&[&[u8]]>,
1902    trailers: Option<&[&[u8]]>,
1903) -> (std::io::Result<()>, i64) {
1904    let (res, written) =
1905        nix::sys::sendfile::sendfile(in_fd, out_fd, offset, Some(count), headers, trailers);
1906    (res.map_err(std::io::Error::from), written)
1907}
1908
1909#[cfg(any(
1910    target_os = "android",
1911    target_os = "freebsd",
1912    target_os = "linux",
1913    target_os = "netbsd"
1914))]
1915pub fn sched_getscheduler(pid: libc::pid_t) -> std::io::Result<i32> {
1916    let policy = unsafe { libc::sched_getscheduler(pid) };
1917    if policy == -1 {
1918        Err(std::io::Error::last_os_error())
1919    } else {
1920        Ok(policy)
1921    }
1922}
1923
1924#[cfg(all(
1925    not(target_env = "musl"),
1926    any(
1927        target_os = "android",
1928        target_os = "freebsd",
1929        target_os = "linux",
1930        target_os = "netbsd"
1931    )
1932))]
1933pub fn sched_setscheduler(
1934    pid: i32,
1935    policy: i32,
1936    param: &libc::sched_param,
1937) -> std::io::Result<i32> {
1938    let ret = unsafe { libc::sched_setscheduler(pid, policy, param) };
1939    if ret == -1 {
1940        Err(std::io::Error::last_os_error())
1941    } else {
1942        Ok(ret)
1943    }
1944}
1945
1946#[cfg(any(
1947    target_os = "android",
1948    target_os = "freebsd",
1949    target_os = "linux",
1950    target_os = "netbsd"
1951))]
1952pub fn sched_getparam(pid: libc::pid_t) -> std::io::Result<libc::sched_param> {
1953    let mut param = core::mem::MaybeUninit::uninit();
1954    let ret = unsafe { libc::sched_getparam(pid, param.as_mut_ptr()) };
1955    if ret == -1 {
1956        Err(std::io::Error::last_os_error())
1957    } else {
1958        Ok(unsafe { param.assume_init() })
1959    }
1960}
1961
1962#[cfg(all(
1963    not(target_env = "musl"),
1964    any(
1965        target_os = "android",
1966        target_os = "freebsd",
1967        target_os = "linux",
1968        target_os = "netbsd"
1969    )
1970))]
1971pub fn sched_setparam(pid: i32, param: &libc::sched_param) -> std::io::Result<i32> {
1972    let ret = unsafe { libc::sched_setparam(pid, param) };
1973    if ret == -1 {
1974        Err(std::io::Error::last_os_error())
1975    } else {
1976        Ok(ret)
1977    }
1978}
1979
1980#[allow(clippy::not_unsafe_ptr_arg_deref)]
1981pub fn exec_replace<T: AsRef<CStr>>(
1982    exec_list: &[T],
1983    argv: *const *const libc::c_char,
1984    envp: Option<*const *const libc::c_char>,
1985) -> nix::errno::Errno {
1986    let mut first_err = None;
1987    for exec in exec_list {
1988        if let Some(envp) = envp {
1989            unsafe { libc::execve(exec.as_ref().as_ptr(), argv, envp) };
1990        } else {
1991            unsafe { libc::execv(exec.as_ref().as_ptr(), argv) };
1992        }
1993        let e = nix::errno::Errno::last();
1994        if e != nix::errno::Errno::ENOENT && e != nix::errno::Errno::ENOTDIR && first_err.is_none()
1995        {
1996            first_err = Some(e);
1997        }
1998    }
1999    first_err.unwrap_or_else(nix::errno::Errno::last)
2000}
2001
2002fn should_keep(above: i32, keep: &[BorrowedFd<'_>], fd: i32) -> bool {
2003    fd > above
2004        && keep
2005            .binary_search_by_key(&fd, BorrowedFd::as_raw_fd)
2006            .is_err()
2007}
2008
2009#[cfg(not(target_os = "redox"))]
2010fn close_dir_fds(above: i32, keep: &[BorrowedFd<'_>]) -> nix::Result<()> {
2011    use nix::{dir::Dir, fcntl::OFlag};
2012    use std::os::fd::AsRawFd;
2013
2014    #[cfg(any(
2015        target_os = "dragonfly",
2016        target_os = "freebsd",
2017        target_os = "netbsd",
2018        target_os = "openbsd",
2019        target_vendor = "apple",
2020    ))]
2021    let fd_dir_name = c"/dev/fd";
2022
2023    #[cfg(any(target_os = "linux", target_os = "android"))]
2024    let fd_dir_name = c"/proc/self/fd";
2025
2026    let mut dir = Dir::open(
2027        fd_dir_name,
2028        OFlag::O_RDONLY | OFlag::O_DIRECTORY,
2029        nix::sys::stat::Mode::empty(),
2030    )?;
2031    let dirfd = dir.as_raw_fd();
2032    'outer: for e in dir.iter() {
2033        let e = e?;
2034        let mut parser = IntParser::default();
2035        for &c in e.file_name().to_bytes() {
2036            if parser.feed(c).is_err() {
2037                continue 'outer;
2038            }
2039        }
2040        let fd = parser.num;
2041        if fd != dirfd && should_keep(above, keep, fd) {
2042            let _ = nix::unistd::close(fd);
2043        }
2044    }
2045    Ok(())
2046}
2047
2048#[cfg(target_os = "redox")]
2049fn close_filetable_fds(above: i32, keep: &[BorrowedFd<'_>]) -> nix::Result<()> {
2050    use nix::fcntl;
2051    use std::os::fd::AsRawFd;
2052
2053    let filetable = fcntl::open(
2054        c"/scheme/thisproc/current/filetable",
2055        fcntl::OFlag::O_RDONLY,
2056        nix::sys::stat::Mode::empty(),
2057    )?;
2058    let read_one = || -> nix::Result<_> {
2059        let mut byte = 0;
2060        let n = nix::unistd::read(&filetable, std::slice::from_mut(&mut byte))?;
2061        Ok((n > 0).then_some(byte))
2062    };
2063    while let Some(c) = read_one()? {
2064        let mut parser = IntParser::default();
2065        if parser.feed(c).is_err() {
2066            continue;
2067        }
2068        let done = loop {
2069            let Some(c) = read_one()? else { break true };
2070            if parser.feed(c).is_err() {
2071                break false;
2072            }
2073        };
2074
2075        let fd = parser.num;
2076        if fd != filetable.as_raw_fd() && should_keep(above, keep, fd) {
2077            let _ = nix::unistd::close(fd);
2078        }
2079        if done {
2080            break;
2081        }
2082    }
2083    Ok(())
2084}
2085
2086fn close_fds_brute_force(above: i32, keep: &[BorrowedFd<'_>]) {
2087    debug_assert!(
2088        keep.windows(2)
2089            .all(|fds| fds[0].as_raw_fd() <= fds[1].as_raw_fd()),
2090        "close_fds_brute_force requires `keep` to be sorted ascending"
2091    );
2092
2093    let max_fd = nix::unistd::sysconf(nix::unistd::SysconfVar::OPEN_MAX)
2094        .ok()
2095        .flatten()
2096        .unwrap_or(256) as i32;
2097
2098    let mut prev = above;
2099    for fd in keep
2100        .iter()
2101        .map(BorrowedFd::as_raw_fd)
2102        .chain(core::iter::once(max_fd))
2103    {
2104        for candidate in prev + 1..fd {
2105            unsafe { libc::close(candidate) };
2106        }
2107        prev = fd;
2108    }
2109}
2110
2111#[derive(Default)]
2112struct IntParser {
2113    num: i32,
2114}
2115
2116struct NonDigit;
2117
2118impl IntParser {
2119    fn feed(&mut self, c: u8) -> Result<(), NonDigit> {
2120        let digit = (c as char).to_digit(10).ok_or(NonDigit)?;
2121        self.num *= 10;
2122        self.num += digit as i32;
2123        Ok(())
2124    }
2125}