about summary refs log tree commit diff
path: root/library/std/src/sys/pal/unix
diff options
context:
space:
mode:
authorjoboet <jonasboettiger@icloud.com>2024-01-11 20:10:25 +0100
committerjoboet <jonasboettiger@icloud.com>2024-01-11 20:10:25 +0100
commit99128b7e45f8b95d962da2e6ea584767f0c85455 (patch)
tree20874cb2d8526a427342c32a45bc63a21022499c /library/std/src/sys/pal/unix
parent062e7c6a951c1e4f33c0a6f6761755949cde15ec (diff)
downloadrust-99128b7e45f8b95d962da2e6ea584767f0c85455.tar.gz
rust-99128b7e45f8b95d962da2e6ea584767f0c85455.zip
std: begin moving platform support modules into `pal`
Diffstat (limited to 'library/std/src/sys/pal/unix')
-rw-r--r--library/std/src/sys/pal/unix/alloc.rs106
-rw-r--r--library/std/src/sys/pal/unix/android.rs81
-rw-r--r--library/std/src/sys/pal/unix/args.rs282
-rw-r--r--library/std/src/sys/pal/unix/cmath.rs37
-rw-r--r--library/std/src/sys/pal/unix/env.rs263
-rw-r--r--library/std/src/sys/pal/unix/fd.rs557
-rw-r--r--library/std/src/sys/pal/unix/fd/tests.rs10
-rw-r--r--library/std/src/sys/pal/unix/fs.rs2162
-rw-r--r--library/std/src/sys/pal/unix/futex.rs301
-rw-r--r--library/std/src/sys/pal/unix/io.rs82
-rw-r--r--library/std/src/sys/pal/unix/kernel_copy.rs730
-rw-r--r--library/std/src/sys/pal/unix/kernel_copy/tests.rs312
-rw-r--r--library/std/src/sys/pal/unix/l4re.rs560
-rw-r--r--library/std/src/sys/pal/unix/locks/fuchsia_mutex.rs164
-rw-r--r--library/std/src/sys/pal/unix/locks/futex_condvar.rs56
-rw-r--r--library/std/src/sys/pal/unix/locks/futex_mutex.rs96
-rw-r--r--library/std/src/sys/pal/unix/locks/futex_rwlock.rs320
-rw-r--r--library/std/src/sys/pal/unix/locks/mod.rs31
-rw-r--r--library/std/src/sys/pal/unix/locks/pthread_condvar.rs206
-rw-r--r--library/std/src/sys/pal/unix/locks/pthread_mutex.rs131
-rw-r--r--library/std/src/sys/pal/unix/locks/pthread_rwlock.rs195
-rw-r--r--library/std/src/sys/pal/unix/memchr.rs40
-rw-r--r--library/std/src/sys/pal/unix/mod.rs443
-rw-r--r--library/std/src/sys/pal/unix/net.rs591
-rw-r--r--library/std/src/sys/pal/unix/os.rs783
-rw-r--r--library/std/src/sys/pal/unix/os/tests.rs23
-rw-r--r--library/std/src/sys/pal/unix/os_str.rs288
-rw-r--r--library/std/src/sys/pal/unix/os_str/tests.rs17
-rw-r--r--library/std/src/sys/pal/unix/path.rs63
-rw-r--r--library/std/src/sys/pal/unix/pipe.rs167
-rw-r--r--library/std/src/sys/pal/unix/process/mod.rs27
-rw-r--r--library/std/src/sys/pal/unix/process/process_common.rs676
-rw-r--r--library/std/src/sys/pal/unix/process/process_common/tests.rs194
-rw-r--r--library/std/src/sys/pal/unix/process/process_fuchsia.rs330
-rw-r--r--library/std/src/sys/pal/unix/process/process_unix.rs1141
-rw-r--r--library/std/src/sys/pal/unix/process/process_unix/tests.rs100
-rw-r--r--library/std/src/sys/pal/unix/process/process_unsupported.rs74
-rw-r--r--library/std/src/sys/pal/unix/process/process_unsupported/wait_status.rs84
-rw-r--r--library/std/src/sys/pal/unix/process/process_unsupported/wait_status/tests.rs36
-rw-r--r--library/std/src/sys/pal/unix/process/process_vxworks.rs264
-rw-r--r--library/std/src/sys/pal/unix/process/zircon.rs309
-rw-r--r--library/std/src/sys/pal/unix/rand.rs301
-rw-r--r--library/std/src/sys/pal/unix/stack_overflow.rs223
-rw-r--r--library/std/src/sys/pal/unix/stdio.rs99
-rw-r--r--library/std/src/sys/pal/unix/thread.rs992
-rw-r--r--library/std/src/sys/pal/unix/thread_local_dtor.rs124
-rw-r--r--library/std/src/sys/pal/unix/thread_local_key.rs29
-rw-r--r--library/std/src/sys/pal/unix/thread_parking/darwin.rs130
-rw-r--r--library/std/src/sys/pal/unix/thread_parking/mod.rs32
-rw-r--r--library/std/src/sys/pal/unix/thread_parking/netbsd.rs52
-rw-r--r--library/std/src/sys/pal/unix/thread_parking/pthread.rs284
-rw-r--r--library/std/src/sys/pal/unix/time.rs330
-rw-r--r--library/std/src/sys/pal/unix/weak.rs195
53 files changed, 15123 insertions, 0 deletions
diff --git a/library/std/src/sys/pal/unix/alloc.rs b/library/std/src/sys/pal/unix/alloc.rs
new file mode 100644
index 00000000000..af0089978ec
--- /dev/null
+++ b/library/std/src/sys/pal/unix/alloc.rs
@@ -0,0 +1,106 @@
+use crate::alloc::{GlobalAlloc, Layout, System};
+use crate::ptr;
+use crate::sys::common::alloc::{realloc_fallback, MIN_ALIGN};
+
+#[stable(feature = "alloc_system_type", since = "1.28.0")]
+unsafe impl GlobalAlloc for System {
+    #[inline]
+    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+        // jemalloc provides alignment less than MIN_ALIGN for small allocations.
+        // So only rely on MIN_ALIGN if size >= align.
+        // Also see <https://github.com/rust-lang/rust/issues/45955> and
+        // <https://github.com/rust-lang/rust/issues/62251#issuecomment-507580914>.
+        if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() {
+            libc::malloc(layout.size()) as *mut u8
+        } else {
+            #[cfg(target_os = "macos")]
+            {
+                if layout.align() > (1 << 31) {
+                    return ptr::null_mut();
+                }
+            }
+            aligned_malloc(&layout)
+        }
+    }
+
+    #[inline]
+    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
+        // See the comment above in `alloc` for why this check looks the way it does.
+        if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() {
+            libc::calloc(layout.size(), 1) as *mut u8
+        } else {
+            let ptr = self.alloc(layout);
+            if !ptr.is_null() {
+                ptr::write_bytes(ptr, 0, layout.size());
+            }
+            ptr
+        }
+    }
+
+    #[inline]
+    unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
+        libc::free(ptr as *mut libc::c_void)
+    }
+
+    #[inline]
+    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
+        if layout.align() <= MIN_ALIGN && layout.align() <= new_size {
+            libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8
+        } else {
+            realloc_fallback(self, ptr, layout, new_size)
+        }
+    }
+}
+
+cfg_if::cfg_if! {
+    if #[cfg(any(
+        target_os = "android",
+        target_os = "illumos",
+        target_os = "redox",
+        target_os = "solaris",
+        target_os = "espidf",
+        target_os = "horizon",
+        target_os = "vita",
+    ))] {
+        #[inline]
+        unsafe fn aligned_malloc(layout: &Layout) -> *mut u8 {
+            // On android we currently target API level 9 which unfortunately
+            // doesn't have the `posix_memalign` API used below. Instead we use
+            // `memalign`, but this unfortunately has the property on some systems
+            // where the memory returned cannot be deallocated by `free`!
+            //
+            // Upon closer inspection, however, this appears to work just fine with
+            // Android, so for this platform we should be fine to call `memalign`
+            // (which is present in API level 9). Some helpful references could
+            // possibly be chromium using memalign [1], attempts at documenting that
+            // memalign + free is ok [2] [3], or the current source of chromium
+            // which still uses memalign on android [4].
+            //
+            // [1]: https://codereview.chromium.org/10796020/
+            // [2]: https://code.google.com/p/android/issues/detail?id=35391
+            // [3]: https://bugs.chromium.org/p/chromium/issues/detail?id=138579
+            // [4]: https://chromium.googlesource.com/chromium/src/base/+/master/
+            //                                       /memory/aligned_memory.cc
+            libc::memalign(layout.align(), layout.size()) as *mut u8
+        }
+    } else if #[cfg(target_os = "wasi")] {
+        #[inline]
+        unsafe fn aligned_malloc(layout: &Layout) -> *mut u8 {
+            // C11 aligned_alloc requires that the size be a multiple of the alignment.
+            // Layout already checks that the size rounded up doesn't overflow isize::MAX.
+            let align = layout.align();
+            let size = layout.size().next_multiple_of(align);
+            libc::aligned_alloc(align, size) as *mut u8
+        }
+    } else {
+        #[inline]
+        unsafe fn aligned_malloc(layout: &Layout) -> *mut u8 {
+            let mut out = ptr::null_mut();
+            // posix_memalign requires that the alignment be a multiple of `sizeof(void*)`.
+            // Since these are all powers of 2, we can just use max.
+            let align = layout.align().max(crate::mem::size_of::<usize>());
+            let ret = libc::posix_memalign(&mut out, align, layout.size());
+            if ret != 0 { ptr::null_mut() } else { out as *mut u8 }
+        }
+    }
+}
diff --git a/library/std/src/sys/pal/unix/android.rs b/library/std/src/sys/pal/unix/android.rs
new file mode 100644
index 00000000000..0f704994f55
--- /dev/null
+++ b/library/std/src/sys/pal/unix/android.rs
@@ -0,0 +1,81 @@
+//! Android ABI-compatibility module
+//!
+//! The ABI of Android has changed quite a bit over time, and std attempts to be
+//! both forwards and backwards compatible as much as possible. We want to
+//! always work with the most recent version of Android, but we also want to
+//! work with older versions of Android for whenever projects need to.
+//!
+//! Our current minimum supported Android version is `android-9`, e.g., Android
+//! with API level 9. We then in theory want to work on that and all future
+//! versions of Android!
+//!
+//! Some of the detection here is done at runtime via `dlopen` and
+//! introspection. Other times no detection is performed at all and we just
+//! provide a fallback implementation as some versions of Android we support
+//! don't have the function.
+//!
+//! You'll find more details below about why each compatibility shim is needed.
+
+#![cfg(target_os = "android")]
+
+use libc::{c_int, sighandler_t};
+
+use super::weak::weak;
+
+// The `log2` and `log2f` functions apparently appeared in android-18, or at
+// least you can see they're not present in the android-17 header [1] and they
+// are present in android-18 [2].
+//
+// [1]: https://chromium.googlesource.com/android_tools/+/20ee6d20/ndk/platforms
+//                                       /android-17/arch-arm/usr/include/math.h
+// [2]: https://chromium.googlesource.com/android_tools/+/20ee6d20/ndk/platforms
+//                                       /android-18/arch-arm/usr/include/math.h
+//
+// Note that these shims are likely less precise than directly calling `log2`,
+// but hopefully that should be enough for now...
+//
+// Note that mathematically, for any arbitrary `y`:
+//
+//      log_2(x) = log_y(x) / log_y(2)
+//               = log_y(x) / (1 / log_2(y))
+//               = log_y(x) * log_2(y)
+//
+// Hence because `ln` (log_e) is available on all Android we just choose `y = e`
+// and get:
+//
+//      log_2(x) = ln(x) * log_2(e)
+
+#[cfg(not(test))]
+pub fn log2f32(f: f32) -> f32 {
+    f.ln() * crate::f32::consts::LOG2_E
+}
+
+#[cfg(not(test))]
+pub fn log2f64(f: f64) -> f64 {
+    f.ln() * crate::f64::consts::LOG2_E
+}
+
+// Back in the day [1] the `signal` function was just an inline wrapper
+// around `bsd_signal`, but starting in API level android-20 the `signal`
+// symbols was introduced [2]. Finally, in android-21 the API `bsd_signal` was
+// removed [3].
+//
+// Basically this means that if we want to be binary compatible with multiple
+// Android releases (oldest being 9 and newest being 21) then we need to check
+// for both symbols and not actually link against either.
+//
+// [1]: https://chromium.googlesource.com/android_tools/+/20ee6d20/ndk/platforms
+//                                       /android-18/arch-arm/usr/include/signal.h
+// [2]: https://chromium.googlesource.com/android_tools/+/fbd420/ndk_experimental
+//                                       /platforms/android-20/arch-arm
+//                                       /usr/include/signal.h
+// [3]: https://chromium.googlesource.com/android_tools/+/20ee6d/ndk/platforms
+//                                       /android-21/arch-arm/usr/include/signal.h
+pub unsafe fn signal(signum: c_int, handler: sighandler_t) -> sighandler_t {
+    weak!(fn signal(c_int, sighandler_t) -> sighandler_t);
+    weak!(fn bsd_signal(c_int, sighandler_t) -> sighandler_t);
+
+    let f = signal.get().or_else(|| bsd_signal.get());
+    let f = f.expect("neither `signal` nor `bsd_signal` symbols found");
+    f(signum, handler)
+}
diff --git a/library/std/src/sys/pal/unix/args.rs b/library/std/src/sys/pal/unix/args.rs
new file mode 100644
index 00000000000..9f7dcc0416e
--- /dev/null
+++ b/library/std/src/sys/pal/unix/args.rs
@@ -0,0 +1,282 @@
+//! Global initialization and retrieval of command line arguments.
+//!
+//! On some platforms these are stored during runtime startup,
+//! and on some they are retrieved from the system on demand.
+
+#![allow(dead_code)] // runtime init functions not used during testing
+
+use crate::ffi::OsString;
+use crate::fmt;
+use crate::vec;
+
+/// One-time global initialization.
+pub unsafe fn init(argc: isize, argv: *const *const u8) {
+    imp::init(argc, argv)
+}
+
+/// Returns the command line arguments
+pub fn args() -> Args {
+    imp::args()
+}
+
+pub struct Args {
+    iter: vec::IntoIter<OsString>,
+}
+
+impl !Send for Args {}
+impl !Sync for Args {}
+
+impl fmt::Debug for Args {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.iter.as_slice().fmt(f)
+    }
+}
+
+impl Iterator for Args {
+    type Item = OsString;
+    fn next(&mut self) -> Option<OsString> {
+        self.iter.next()
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+impl ExactSizeIterator for Args {
+    fn len(&self) -> usize {
+        self.iter.len()
+    }
+}
+
+impl DoubleEndedIterator for Args {
+    fn next_back(&mut self) -> Option<OsString> {
+        self.iter.next_back()
+    }
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "android",
+    target_os = "freebsd",
+    target_os = "dragonfly",
+    target_os = "netbsd",
+    target_os = "openbsd",
+    target_os = "solaris",
+    target_os = "illumos",
+    target_os = "emscripten",
+    target_os = "haiku",
+    target_os = "l4re",
+    target_os = "fuchsia",
+    target_os = "redox",
+    target_os = "vxworks",
+    target_os = "horizon",
+    target_os = "aix",
+    target_os = "nto",
+    target_os = "hurd",
+))]
+mod imp {
+    use super::Args;
+    use crate::ffi::{CStr, OsString};
+    use crate::os::unix::prelude::*;
+    use crate::ptr;
+    use crate::sync::atomic::{AtomicIsize, AtomicPtr, Ordering};
+
+    // The system-provided argc and argv, which we store in static memory
+    // here so that we can defer the work of parsing them until its actually
+    // needed.
+    //
+    // Note that we never mutate argv/argc, the argv array, or the argv
+    // strings, which allows the code in this file to be very simple.
+    static ARGC: AtomicIsize = AtomicIsize::new(0);
+    static ARGV: AtomicPtr<*const u8> = AtomicPtr::new(ptr::null_mut());
+
+    unsafe fn really_init(argc: isize, argv: *const *const u8) {
+        // These don't need to be ordered with each other or other stores,
+        // because they only hold the unmodified system-provide argv/argc.
+        ARGC.store(argc, Ordering::Relaxed);
+        ARGV.store(argv as *mut _, Ordering::Relaxed);
+    }
+
+    #[inline(always)]
+    pub unsafe fn init(_argc: isize, _argv: *const *const u8) {
+        // On Linux-GNU, we rely on `ARGV_INIT_ARRAY` below to initialize
+        // `ARGC` and `ARGV`. But in Miri that does not actually happen so we
+        // still initialize here.
+        #[cfg(any(miri, not(all(target_os = "linux", target_env = "gnu"))))]
+        really_init(_argc, _argv);
+    }
+
+    /// glibc passes argc, argv, and envp to functions in .init_array, as a non-standard extension.
+    /// This allows `std::env::args` to work even in a `cdylib`, as it does on macOS and Windows.
+    #[cfg(all(target_os = "linux", target_env = "gnu"))]
+    #[used]
+    #[link_section = ".init_array.00099"]
+    static ARGV_INIT_ARRAY: extern "C" fn(
+        crate::os::raw::c_int,
+        *const *const u8,
+        *const *const u8,
+    ) = {
+        extern "C" fn init_wrapper(
+            argc: crate::os::raw::c_int,
+            argv: *const *const u8,
+            _envp: *const *const u8,
+        ) {
+            unsafe {
+                really_init(argc as isize, argv);
+            }
+        }
+        init_wrapper
+    };
+
+    pub fn args() -> Args {
+        Args { iter: clone().into_iter() }
+    }
+
+    fn clone() -> Vec<OsString> {
+        unsafe {
+            // Load ARGC and ARGV, which hold the unmodified system-provided
+            // argc/argv, so we can read the pointed-to memory without atomics
+            // or synchronization.
+            //
+            // If either ARGC or ARGV is still zero or null, then either there
+            // really are no arguments, or someone is asking for `args()`
+            // before initialization has completed, and we return an empty
+            // list.
+            let argv = ARGV.load(Ordering::Relaxed);
+            let argc = if argv.is_null() { 0 } else { ARGC.load(Ordering::Relaxed) };
+            let mut args = Vec::with_capacity(argc as usize);
+            for i in 0..argc {
+                let ptr = *argv.offset(i) as *const libc::c_char;
+
+                // Some C commandline parsers (e.g. GLib and Qt) are replacing already
+                // handled arguments in `argv` with `NULL` and move them to the end. That
+                // means that `argc` might be bigger than the actual number of non-`NULL`
+                // pointers in `argv` at this point.
+                //
+                // To handle this we simply stop iterating at the first `NULL` argument.
+                //
+                // `argv` is also guaranteed to be `NULL`-terminated so any non-`NULL` arguments
+                // after the first `NULL` can safely be ignored.
+                if ptr.is_null() {
+                    break;
+                }
+
+                let cstr = CStr::from_ptr(ptr);
+                args.push(OsStringExt::from_vec(cstr.to_bytes().to_vec()));
+            }
+
+            args
+        }
+    }
+}
+
+#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))]
+mod imp {
+    use super::Args;
+    use crate::ffi::CStr;
+
+    pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
+
+    #[cfg(target_os = "macos")]
+    pub fn args() -> Args {
+        use crate::os::unix::prelude::*;
+        extern "C" {
+            // These functions are in crt_externs.h.
+            fn _NSGetArgc() -> *mut libc::c_int;
+            fn _NSGetArgv() -> *mut *mut *mut libc::c_char;
+        }
+
+        let vec = unsafe {
+            let (argc, argv) =
+                (*_NSGetArgc() as isize, *_NSGetArgv() as *const *const libc::c_char);
+            (0..argc as isize)
+                .map(|i| {
+                    let bytes = CStr::from_ptr(*argv.offset(i)).to_bytes().to_vec();
+                    OsStringExt::from_vec(bytes)
+                })
+                .collect::<Vec<_>>()
+        };
+        Args { iter: vec.into_iter() }
+    }
+
+    // As _NSGetArgc and _NSGetArgv aren't mentioned in iOS docs
+    // and use underscores in their names - they're most probably
+    // are considered private and therefore should be avoided
+    // Here is another way to get arguments using Objective C
+    // runtime
+    //
+    // In general it looks like:
+    // res = Vec::new()
+    // let args = [[NSProcessInfo processInfo] arguments]
+    // for i in (0..[args count])
+    //      res.push([args objectAtIndex:i])
+    // res
+    #[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos"))]
+    pub fn args() -> Args {
+        use crate::ffi::OsString;
+        use crate::mem;
+        use crate::str;
+
+        extern "C" {
+            fn sel_registerName(name: *const libc::c_uchar) -> Sel;
+            fn objc_getClass(class_name: *const libc::c_uchar) -> NsId;
+        }
+
+        #[cfg(target_arch = "aarch64")]
+        extern "C" {
+            fn objc_msgSend(obj: NsId, sel: Sel) -> NsId;
+            #[allow(clashing_extern_declarations)]
+            #[link_name = "objc_msgSend"]
+            fn objc_msgSend_ul(obj: NsId, sel: Sel, i: libc::c_ulong) -> NsId;
+        }
+
+        #[cfg(not(target_arch = "aarch64"))]
+        extern "C" {
+            fn objc_msgSend(obj: NsId, sel: Sel, ...) -> NsId;
+            #[allow(clashing_extern_declarations)]
+            #[link_name = "objc_msgSend"]
+            fn objc_msgSend_ul(obj: NsId, sel: Sel, ...) -> NsId;
+        }
+
+        type Sel = *const libc::c_void;
+        type NsId = *const libc::c_void;
+
+        let mut res = Vec::new();
+
+        unsafe {
+            let process_info_sel =
+                sel_registerName(c"processInfo".as_ptr() as *const libc::c_uchar);
+            let arguments_sel = sel_registerName(c"arguments".as_ptr() as *const libc::c_uchar);
+            let utf8_sel = sel_registerName(c"UTF8String".as_ptr() as *const libc::c_uchar);
+            let count_sel = sel_registerName(c"count".as_ptr() as *const libc::c_uchar);
+            let object_at_sel =
+                sel_registerName(c"objectAtIndex:".as_ptr() as *const libc::c_uchar);
+
+            let klass = objc_getClass(c"NSProcessInfo".as_ptr() as *const libc::c_uchar);
+            let info = objc_msgSend(klass, process_info_sel);
+            let args = objc_msgSend(info, arguments_sel);
+
+            let cnt: usize = mem::transmute(objc_msgSend(args, count_sel));
+            for i in 0..cnt {
+                let tmp = objc_msgSend_ul(args, object_at_sel, i as libc::c_ulong);
+                let utf_c_str: *const libc::c_char = mem::transmute(objc_msgSend(tmp, utf8_sel));
+                let bytes = CStr::from_ptr(utf_c_str).to_bytes();
+                res.push(OsString::from(str::from_utf8(bytes).unwrap()))
+            }
+        }
+
+        Args { iter: res.into_iter() }
+    }
+}
+
+#[cfg(any(target_os = "espidf", target_os = "vita"))]
+mod imp {
+    use super::Args;
+
+    #[inline(always)]
+    pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
+
+    pub fn args() -> Args {
+        Args { iter: Vec::new().into_iter() }
+    }
+}
diff --git a/library/std/src/sys/pal/unix/cmath.rs b/library/std/src/sys/pal/unix/cmath.rs
new file mode 100644
index 00000000000..5346d229116
--- /dev/null
+++ b/library/std/src/sys/pal/unix/cmath.rs
@@ -0,0 +1,37 @@
+#![cfg(not(test))]
+
+// These symbols are all defined by `libm`,
+// or by `compiler-builtins` on unsupported platforms.
+
+extern "C" {
+    pub fn acos(n: f64) -> f64;
+    pub fn acosf(n: f32) -> f32;
+    pub fn asin(n: f64) -> f64;
+    pub fn asinf(n: f32) -> f32;
+    pub fn atan(n: f64) -> f64;
+    pub fn atan2(a: f64, b: f64) -> f64;
+    pub fn atan2f(a: f32, b: f32) -> f32;
+    pub fn atanf(n: f32) -> f32;
+    pub fn cbrt(n: f64) -> f64;
+    pub fn cbrtf(n: f32) -> f32;
+    pub fn cosh(n: f64) -> f64;
+    pub fn coshf(n: f32) -> f32;
+    pub fn expm1(n: f64) -> f64;
+    pub fn expm1f(n: f32) -> f32;
+    pub fn fdim(a: f64, b: f64) -> f64;
+    pub fn fdimf(a: f32, b: f32) -> f32;
+    pub fn hypot(x: f64, y: f64) -> f64;
+    pub fn hypotf(x: f32, y: f32) -> f32;
+    pub fn log1p(n: f64) -> f64;
+    pub fn log1pf(n: f32) -> f32;
+    pub fn sinh(n: f64) -> f64;
+    pub fn sinhf(n: f32) -> f32;
+    pub fn tan(n: f64) -> f64;
+    pub fn tanf(n: f32) -> f32;
+    pub fn tanh(n: f64) -> f64;
+    pub fn tanhf(n: f32) -> f32;
+    pub fn tgamma(n: f64) -> f64;
+    pub fn tgammaf(n: f32) -> f32;
+    pub fn lgamma_r(n: f64, s: &mut i32) -> f64;
+    pub fn lgammaf_r(n: f32, s: &mut i32) -> f32;
+}
diff --git a/library/std/src/sys/pal/unix/env.rs b/library/std/src/sys/pal/unix/env.rs
new file mode 100644
index 00000000000..3d4ba509829
--- /dev/null
+++ b/library/std/src/sys/pal/unix/env.rs
@@ -0,0 +1,263 @@
+#[cfg(target_os = "linux")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "linux";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "macos")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "macos";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".dylib";
+    pub const DLL_EXTENSION: &str = "dylib";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "ios")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "ios";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".dylib";
+    pub const DLL_EXTENSION: &str = "dylib";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "tvos")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "tvos";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".dylib";
+    pub const DLL_EXTENSION: &str = "dylib";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "watchos")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "watchos";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".dylib";
+    pub const DLL_EXTENSION: &str = "dylib";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "freebsd")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "freebsd";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "dragonfly")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "dragonfly";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "netbsd")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "netbsd";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "openbsd")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "openbsd";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "android")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "android";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "solaris")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "solaris";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "illumos")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "illumos";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "haiku")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "haiku";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "horizon")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "horizon";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = ".elf";
+    pub const EXE_EXTENSION: &str = "elf";
+}
+
+#[cfg(target_os = "hurd")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "hurd";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "vita")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "vita";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = ".elf";
+    pub const EXE_EXTENSION: &str = "elf";
+}
+
+#[cfg(all(target_os = "emscripten", target_arch = "wasm32"))]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "emscripten";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = ".js";
+    pub const EXE_EXTENSION: &str = "js";
+}
+
+#[cfg(target_os = "fuchsia")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "fuchsia";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "l4re")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "l4re";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "nto")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "nto";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "redox")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "redox";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "vxworks")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "vxworks";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "espidf")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "espidf";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".so";
+    pub const DLL_EXTENSION: &str = "so";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
+
+#[cfg(target_os = "aix")]
+pub mod os {
+    pub const FAMILY: &str = "unix";
+    pub const OS: &str = "aix";
+    pub const DLL_PREFIX: &str = "lib";
+    pub const DLL_SUFFIX: &str = ".a";
+    pub const DLL_EXTENSION: &str = "a";
+    pub const EXE_SUFFIX: &str = "";
+    pub const EXE_EXTENSION: &str = "";
+}
diff --git a/library/std/src/sys/pal/unix/fd.rs b/library/std/src/sys/pal/unix/fd.rs
new file mode 100644
index 00000000000..bf1fb3123c4
--- /dev/null
+++ b/library/std/src/sys/pal/unix/fd.rs
@@ -0,0 +1,557 @@
+#![unstable(reason = "not public", issue = "none", feature = "fd")]
+
+#[cfg(test)]
+mod tests;
+
+use crate::cmp;
+use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read};
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
+use crate::sys::cvt;
+use crate::sys_common::{AsInner, FromInner, IntoInner};
+
+#[cfg(any(
+    target_os = "android",
+    target_os = "linux",
+    target_os = "emscripten",
+    target_os = "l4re",
+    target_os = "hurd",
+))]
+use libc::off64_t;
+#[cfg(not(any(
+    target_os = "linux",
+    target_os = "emscripten",
+    target_os = "l4re",
+    target_os = "android",
+    target_os = "hurd",
+)))]
+use libc::off_t as off64_t;
+
+#[derive(Debug)]
+pub struct FileDesc(OwnedFd);
+
+// The maximum read limit on most POSIX-like systems is `SSIZE_MAX`,
+// with the man page quoting that if the count of bytes to read is
+// greater than `SSIZE_MAX` the result is "unspecified".
+//
+// On macOS, however, apparently the 64-bit libc is either buggy or
+// intentionally showing odd behavior by rejecting any read with a size
+// larger than or equal to INT_MAX. To handle both of these the read
+// size is capped on both platforms.
+#[cfg(target_os = "macos")]
+const READ_LIMIT: usize = libc::c_int::MAX as usize - 1;
+#[cfg(not(target_os = "macos"))]
+const READ_LIMIT: usize = libc::ssize_t::MAX as usize;
+
+#[cfg(any(
+    target_os = "dragonfly",
+    target_os = "freebsd",
+    target_os = "ios",
+    target_os = "tvos",
+    target_os = "macos",
+    target_os = "netbsd",
+    target_os = "openbsd",
+    target_os = "watchos",
+))]
+const fn max_iov() -> usize {
+    libc::IOV_MAX as usize
+}
+
+#[cfg(any(
+    target_os = "android",
+    target_os = "emscripten",
+    target_os = "linux",
+    target_os = "nto",
+))]
+const fn max_iov() -> usize {
+    libc::UIO_MAXIOV as usize
+}
+
+#[cfg(not(any(
+    target_os = "android",
+    target_os = "dragonfly",
+    target_os = "emscripten",
+    target_os = "freebsd",
+    target_os = "ios",
+    target_os = "tvos",
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "netbsd",
+    target_os = "nto",
+    target_os = "openbsd",
+    target_os = "horizon",
+    target_os = "vita",
+    target_os = "watchos",
+)))]
+const fn max_iov() -> usize {
+    16 // The minimum value required by POSIX.
+}
+
+impl FileDesc {
+    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
+        let ret = cvt(unsafe {
+            libc::read(
+                self.as_raw_fd(),
+                buf.as_mut_ptr() as *mut libc::c_void,
+                cmp::min(buf.len(), READ_LIMIT),
+            )
+        })?;
+        Ok(ret as usize)
+    }
+
+    #[cfg(not(any(target_os = "espidf", target_os = "horizon", target_os = "vita")))]
+    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+        let ret = cvt(unsafe {
+            libc::readv(
+                self.as_raw_fd(),
+                bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
+                cmp::min(bufs.len(), max_iov()) as libc::c_int,
+            )
+        })?;
+        Ok(ret as usize)
+    }
+
+    #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
+    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+        io::default_read_vectored(|b| self.read(b), bufs)
+    }
+
+    #[inline]
+    pub fn is_read_vectored(&self) -> bool {
+        cfg!(not(any(target_os = "espidf", target_os = "horizon", target_os = "vita")))
+    }
+
+    pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
+        let mut me = self;
+        (&mut me).read_to_end(buf)
+    }
+
+    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+        #[cfg(not(any(
+            all(target_os = "linux", not(target_env = "musl")),
+            target_os = "android",
+            target_os = "hurd"
+        )))]
+        use libc::pread as pread64;
+        #[cfg(any(
+            all(target_os = "linux", not(target_env = "musl")),
+            target_os = "android",
+            target_os = "hurd"
+        ))]
+        use libc::pread64;
+
+        unsafe {
+            cvt(pread64(
+                self.as_raw_fd(),
+                buf.as_mut_ptr() as *mut libc::c_void,
+                cmp::min(buf.len(), READ_LIMIT),
+                offset as off64_t,
+            ))
+            .map(|n| n as usize)
+        }
+    }
+
+    pub fn read_buf(&self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> {
+        let ret = cvt(unsafe {
+            libc::read(
+                self.as_raw_fd(),
+                cursor.as_mut().as_mut_ptr() as *mut libc::c_void,
+                cmp::min(cursor.capacity(), READ_LIMIT),
+            )
+        })?;
+
+        // Safety: `ret` bytes were written to the initialized portion of the buffer
+        unsafe {
+            cursor.advance(ret as usize);
+        }
+        Ok(())
+    }
+
+    #[cfg(any(
+        target_os = "emscripten",
+        target_os = "freebsd",
+        target_os = "fuchsia",
+        target_os = "hurd",
+        target_os = "illumos",
+        target_os = "linux",
+        target_os = "netbsd",
+    ))]
+    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
+        let ret = cvt(unsafe {
+            libc::preadv(
+                self.as_raw_fd(),
+                bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
+                cmp::min(bufs.len(), max_iov()) as libc::c_int,
+                offset as _,
+            )
+        })?;
+        Ok(ret as usize)
+    }
+
+    #[cfg(not(any(
+        target_os = "android",
+        target_os = "emscripten",
+        target_os = "freebsd",
+        target_os = "fuchsia",
+        target_os = "hurd",
+        target_os = "illumos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "netbsd",
+    )))]
+    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
+        io::default_read_vectored(|b| self.read_at(b, offset), bufs)
+    }
+
+    // We support some old Android versions that do not have `preadv` in libc,
+    // so we use weak linkage and fallback to a direct syscall if not available.
+    //
+    // On 32-bit targets, we don't want to deal with weird ABI issues around
+    // passing 64-bits parameters to syscalls, so we fallback to the default
+    // implementation if `preadv` is not available.
+    #[cfg(all(target_os = "android", target_pointer_width = "64"))]
+    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
+        super::weak::syscall! {
+            fn preadv(
+                fd: libc::c_int,
+                iovec: *const libc::iovec,
+                n_iovec: libc::c_int,
+                offset: off64_t
+            ) -> isize
+        }
+
+        let ret = cvt(unsafe {
+            preadv(
+                self.as_raw_fd(),
+                bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
+                cmp::min(bufs.len(), max_iov()) as libc::c_int,
+                offset as _,
+            )
+        })?;
+        Ok(ret as usize)
+    }
+
+    // We support old MacOS and iOS versions that do not have `preadv`. There is
+    // no `syscall` possible in these platform.
+    #[cfg(any(
+        all(target_os = "android", target_pointer_width = "32"),
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "macos",
+    ))]
+    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
+        super::weak::weak!(fn preadv64(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize);
+
+        match preadv64.get() {
+            Some(preadv) => {
+                let ret = cvt(unsafe {
+                    preadv(
+                        self.as_raw_fd(),
+                        bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
+                        cmp::min(bufs.len(), max_iov()) as libc::c_int,
+                        offset as _,
+                    )
+                })?;
+                Ok(ret as usize)
+            }
+            None => io::default_read_vectored(|b| self.read_at(b, offset), bufs),
+        }
+    }
+
+    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
+        let ret = cvt(unsafe {
+            libc::write(
+                self.as_raw_fd(),
+                buf.as_ptr() as *const libc::c_void,
+                cmp::min(buf.len(), READ_LIMIT),
+            )
+        })?;
+        Ok(ret as usize)
+    }
+
+    #[cfg(not(any(target_os = "espidf", target_os = "horizon", target_os = "vita")))]
+    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+        let ret = cvt(unsafe {
+            libc::writev(
+                self.as_raw_fd(),
+                bufs.as_ptr() as *const libc::iovec,
+                cmp::min(bufs.len(), max_iov()) as libc::c_int,
+            )
+        })?;
+        Ok(ret as usize)
+    }
+
+    #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
+    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+        io::default_write_vectored(|b| self.write(b), bufs)
+    }
+
+    #[inline]
+    pub fn is_write_vectored(&self) -> bool {
+        cfg!(not(any(target_os = "espidf", target_os = "horizon", target_os = "vita")))
+    }
+
+    pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
+        #[cfg(not(any(
+            all(target_os = "linux", not(target_env = "musl")),
+            target_os = "android",
+            target_os = "hurd"
+        )))]
+        use libc::pwrite as pwrite64;
+        #[cfg(any(
+            all(target_os = "linux", not(target_env = "musl")),
+            target_os = "android",
+            target_os = "hurd"
+        ))]
+        use libc::pwrite64;
+
+        unsafe {
+            cvt(pwrite64(
+                self.as_raw_fd(),
+                buf.as_ptr() as *const libc::c_void,
+                cmp::min(buf.len(), READ_LIMIT),
+                offset as off64_t,
+            ))
+            .map(|n| n as usize)
+        }
+    }
+
+    #[cfg(any(
+        target_os = "emscripten",
+        target_os = "freebsd",
+        target_os = "fuchsia",
+        target_os = "hurd",
+        target_os = "illumos",
+        target_os = "linux",
+        target_os = "netbsd",
+    ))]
+    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
+        let ret = cvt(unsafe {
+            libc::pwritev(
+                self.as_raw_fd(),
+                bufs.as_ptr() as *const libc::iovec,
+                cmp::min(bufs.len(), max_iov()) as libc::c_int,
+                offset as _,
+            )
+        })?;
+        Ok(ret as usize)
+    }
+
+    #[cfg(not(any(
+        target_os = "android",
+        target_os = "emscripten",
+        target_os = "freebsd",
+        target_os = "fuchsia",
+        target_os = "hurd",
+        target_os = "illumos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "netbsd",
+    )))]
+    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
+        io::default_write_vectored(|b| self.write_at(b, offset), bufs)
+    }
+
+    // We support some old Android versions that do not have `pwritev` in libc,
+    // so we use weak linkage and fallback to a direct syscall if not available.
+    //
+    // On 32-bit targets, we don't want to deal with weird ABI issues around
+    // passing 64-bits parameters to syscalls, so we fallback to the default
+    // implementation if `pwritev` is not available.
+    #[cfg(all(target_os = "android", target_pointer_width = "64"))]
+    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
+        super::weak::syscall! {
+            fn pwritev(
+                fd: libc::c_int,
+                iovec: *const libc::iovec,
+                n_iovec: libc::c_int,
+                offset: off64_t
+            ) -> isize
+        }
+
+        let ret = cvt(unsafe {
+            pwritev(
+                self.as_raw_fd(),
+                bufs.as_ptr() as *const libc::iovec,
+                cmp::min(bufs.len(), max_iov()) as libc::c_int,
+                offset as _,
+            )
+        })?;
+        Ok(ret as usize)
+    }
+
+    // We support old MacOS and iOS versions that do not have `pwritev`. There is
+    // no `syscall` possible in these platform.
+    #[cfg(any(
+        all(target_os = "android", target_pointer_width = "32"),
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "macos",
+    ))]
+    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
+        super::weak::weak!(fn pwritev64(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize);
+
+        match pwritev64.get() {
+            Some(pwritev) => {
+                let ret = cvt(unsafe {
+                    pwritev(
+                        self.as_raw_fd(),
+                        bufs.as_ptr() as *const libc::iovec,
+                        cmp::min(bufs.len(), max_iov()) as libc::c_int,
+                        offset as _,
+                    )
+                })?;
+                Ok(ret as usize)
+            }
+            None => io::default_write_vectored(|b| self.write_at(b, offset), bufs),
+        }
+    }
+
+    #[cfg(not(any(
+        target_env = "newlib",
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "emscripten",
+        target_os = "fuchsia",
+        target_os = "l4re",
+        target_os = "linux",
+        target_os = "haiku",
+        target_os = "redox",
+        target_os = "vxworks",
+        target_os = "nto",
+    )))]
+    pub fn set_cloexec(&self) -> io::Result<()> {
+        unsafe {
+            cvt(libc::ioctl(self.as_raw_fd(), libc::FIOCLEX))?;
+            Ok(())
+        }
+    }
+    #[cfg(any(
+        all(
+            target_env = "newlib",
+            not(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))
+        ),
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "emscripten",
+        target_os = "fuchsia",
+        target_os = "l4re",
+        target_os = "linux",
+        target_os = "haiku",
+        target_os = "redox",
+        target_os = "vxworks",
+        target_os = "nto",
+    ))]
+    pub fn set_cloexec(&self) -> io::Result<()> {
+        unsafe {
+            let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))?;
+            let new = previous | libc::FD_CLOEXEC;
+            if new != previous {
+                cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFD, new))?;
+            }
+            Ok(())
+        }
+    }
+    #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
+    pub fn set_cloexec(&self) -> io::Result<()> {
+        // FD_CLOEXEC is not supported in ESP-IDF, Horizon OS and Vita but there's no need to,
+        // because none of them supports spawning processes.
+        Ok(())
+    }
+
+    #[cfg(target_os = "linux")]
+    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
+        unsafe {
+            let v = nonblocking as libc::c_int;
+            cvt(libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &v))?;
+            Ok(())
+        }
+    }
+
+    #[cfg(not(target_os = "linux"))]
+    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
+        unsafe {
+            let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFL))?;
+            let new = if nonblocking {
+                previous | libc::O_NONBLOCK
+            } else {
+                previous & !libc::O_NONBLOCK
+            };
+            if new != previous {
+                cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFL, new))?;
+            }
+            Ok(())
+        }
+    }
+
+    #[inline]
+    pub fn duplicate(&self) -> io::Result<FileDesc> {
+        Ok(Self(self.0.try_clone()?))
+    }
+}
+
+impl<'a> Read for &'a FileDesc {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        (**self).read(buf)
+    }
+
+    fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
+        (**self).read_buf(cursor)
+    }
+
+    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+        (**self).read_vectored(bufs)
+    }
+
+    #[inline]
+    fn is_read_vectored(&self) -> bool {
+        (**self).is_read_vectored()
+    }
+}
+
+impl AsInner<OwnedFd> for FileDesc {
+    #[inline]
+    fn as_inner(&self) -> &OwnedFd {
+        &self.0
+    }
+}
+
+impl IntoInner<OwnedFd> for FileDesc {
+    fn into_inner(self) -> OwnedFd {
+        self.0
+    }
+}
+
+impl FromInner<OwnedFd> for FileDesc {
+    fn from_inner(owned_fd: OwnedFd) -> Self {
+        Self(owned_fd)
+    }
+}
+
+impl AsFd for FileDesc {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+
+impl AsRawFd for FileDesc {
+    #[inline]
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+
+impl IntoRawFd for FileDesc {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+
+impl FromRawFd for FileDesc {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FromRawFd::from_raw_fd(raw_fd))
+    }
+}
diff --git a/library/std/src/sys/pal/unix/fd/tests.rs b/library/std/src/sys/pal/unix/fd/tests.rs
new file mode 100644
index 00000000000..5d17e46786c
--- /dev/null
+++ b/library/std/src/sys/pal/unix/fd/tests.rs
@@ -0,0 +1,10 @@
+use super::{FileDesc, IoSlice};
+use crate::os::unix::io::FromRawFd;
+use core::mem::ManuallyDrop;
+
+#[test]
+fn limit_vector_count() {
+    let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) });
+    let bufs = (0..1500).map(|_| IoSlice::new(&[])).collect::<Vec<_>>();
+    assert!(stdout.write_vectored(&bufs).is_ok());
+}
diff --git a/library/std/src/sys/pal/unix/fs.rs b/library/std/src/sys/pal/unix/fs.rs
new file mode 100644
index 00000000000..72e7b1b1fc3
--- /dev/null
+++ b/library/std/src/sys/pal/unix/fs.rs
@@ -0,0 +1,2162 @@
+// miri has some special hacks here that make things unused.
+#![cfg_attr(miri, allow(unused))]
+
+use crate::os::unix::prelude::*;
+
+use crate::ffi::{CStr, OsStr, OsString};
+use crate::fmt;
+use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
+use crate::mem;
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd};
+use crate::path::{Path, PathBuf};
+use crate::ptr;
+use crate::sync::Arc;
+use crate::sys::common::small_c_string::run_path_with_cstr;
+use crate::sys::fd::FileDesc;
+use crate::sys::time::SystemTime;
+use crate::sys::{cvt, cvt_r};
+use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
+
+#[cfg(any(
+    all(target_os = "linux", target_env = "gnu"),
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "tvos",
+    target_os = "watchos",
+))]
+use crate::sys::weak::syscall;
+#[cfg(any(target_os = "android", target_os = "macos", target_os = "solaris"))]
+use crate::sys::weak::weak;
+
+use libc::{c_int, mode_t};
+
+#[cfg(any(
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "tvos",
+    target_os = "watchos",
+    target_os = "solaris",
+    all(target_os = "linux", target_env = "gnu")
+))]
+use libc::c_char;
+#[cfg(any(
+    all(target_os = "linux", not(target_env = "musl")),
+    target_os = "emscripten",
+    target_os = "android",
+    target_os = "hurd"
+))]
+use libc::dirfd;
+#[cfg(any(
+    all(target_os = "linux", not(target_env = "musl")),
+    target_os = "emscripten",
+    target_os = "hurd"
+))]
+use libc::fstatat64;
+#[cfg(any(
+    target_os = "android",
+    target_os = "solaris",
+    target_os = "fuchsia",
+    target_os = "redox",
+    target_os = "illumos",
+    target_os = "aix",
+    target_os = "nto",
+    target_os = "vita",
+    all(target_os = "linux", target_env = "musl"),
+))]
+use libc::readdir as readdir64;
+#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))]
+use libc::readdir64;
+#[cfg(any(target_os = "emscripten", target_os = "l4re"))]
+use libc::readdir64_r;
+#[cfg(not(any(
+    target_os = "android",
+    target_os = "linux",
+    target_os = "emscripten",
+    target_os = "solaris",
+    target_os = "illumos",
+    target_os = "l4re",
+    target_os = "fuchsia",
+    target_os = "redox",
+    target_os = "aix",
+    target_os = "nto",
+    target_os = "vita",
+    target_os = "hurd",
+)))]
+use libc::readdir_r as readdir64_r;
+#[cfg(target_os = "android")]
+use libc::{
+    dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64,
+    lstat as lstat64, off64_t, open as open64, stat as stat64,
+};
+#[cfg(not(any(
+    all(target_os = "linux", not(target_env = "musl")),
+    target_os = "emscripten",
+    target_os = "l4re",
+    target_os = "android",
+    target_os = "hurd",
+)))]
+use libc::{
+    dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
+    lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
+};
+#[cfg(any(
+    all(target_os = "linux", not(target_env = "musl")),
+    target_os = "emscripten",
+    target_os = "l4re",
+    target_os = "hurd"
+))]
+use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
+
+pub use crate::sys_common::fs::try_exists;
+
+pub struct File(FileDesc);
+
+// FIXME: This should be available on Linux with all `target_env`.
+// But currently only glibc exposes `statx` fn and structs.
+// We don't want to import unverified raw C structs here directly.
+// https://github.com/rust-lang/rust/pull/67774
+macro_rules! cfg_has_statx {
+    ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => {
+        cfg_if::cfg_if! {
+            if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
+                $($then_tt)*
+            } else {
+                $($else_tt)*
+            }
+        }
+    };
+    ($($block_inner:tt)*) => {
+        #[cfg(all(target_os = "linux", target_env = "gnu"))]
+        {
+            $($block_inner)*
+        }
+    };
+}
+
+cfg_has_statx! {{
+    #[derive(Clone)]
+    pub struct FileAttr {
+        stat: stat64,
+        statx_extra_fields: Option<StatxExtraFields>,
+    }
+
+    #[derive(Clone)]
+    struct StatxExtraFields {
+        // This is needed to check if btime is supported by the filesystem.
+        stx_mask: u32,
+        stx_btime: libc::statx_timestamp,
+        // With statx, we can overcome 32-bit `time_t` too.
+        #[cfg(target_pointer_width = "32")]
+        stx_atime: libc::statx_timestamp,
+        #[cfg(target_pointer_width = "32")]
+        stx_ctime: libc::statx_timestamp,
+        #[cfg(target_pointer_width = "32")]
+        stx_mtime: libc::statx_timestamp,
+
+    }
+
+    // We prefer `statx` on Linux if available, which contains file creation time,
+    // as well as 64-bit timestamps of all kinds.
+    // Default `stat64` contains no creation time and may have 32-bit `time_t`.
+    unsafe fn try_statx(
+        fd: c_int,
+        path: *const c_char,
+        flags: i32,
+        mask: u32,
+    ) -> Option<io::Result<FileAttr>> {
+        use crate::sync::atomic::{AtomicU8, Ordering};
+
+        // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`.
+        // We check for it on first failure and remember availability to avoid having to
+        // do it again.
+        #[repr(u8)]
+        enum STATX_STATE{ Unknown = 0, Present, Unavailable }
+        static STATX_SAVED_STATE: AtomicU8 = AtomicU8::new(STATX_STATE::Unknown as u8);
+
+        syscall! {
+            fn statx(
+                fd: c_int,
+                pathname: *const c_char,
+                flags: c_int,
+                mask: libc::c_uint,
+                statxbuf: *mut libc::statx
+            ) -> c_int
+        }
+
+        if STATX_SAVED_STATE.load(Ordering::Relaxed) == STATX_STATE::Unavailable as u8 {
+            return None;
+        }
+
+        let mut buf: libc::statx = mem::zeroed();
+        if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) {
+            if STATX_SAVED_STATE.load(Ordering::Relaxed) == STATX_STATE::Present as u8 {
+                return Some(Err(err));
+            }
+
+            // Availability not checked yet.
+            //
+            // First try the cheap way.
+            if err.raw_os_error() == Some(libc::ENOSYS) {
+                STATX_SAVED_STATE.store(STATX_STATE::Unavailable as u8, Ordering::Relaxed);
+                return None;
+            }
+
+            // Error other than `ENOSYS` is not a good enough indicator -- it is
+            // known that `EPERM` can be returned as a result of using seccomp to
+            // block the syscall.
+            // Availability is checked by performing a call which expects `EFAULT`
+            // if the syscall is usable.
+            // See: https://github.com/rust-lang/rust/issues/65662
+            // FIXME this can probably just do the call if `EPERM` was received, but
+            // previous iteration of the code checked it for all errors and for now
+            // this is retained.
+            // FIXME what about transient conditions like `ENOMEM`?
+            let err2 = cvt(statx(0, ptr::null(), 0, libc::STATX_ALL, ptr::null_mut()))
+                .err()
+                .and_then(|e| e.raw_os_error());
+            if err2 == Some(libc::EFAULT) {
+                STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed);
+                return Some(Err(err));
+            } else {
+                STATX_SAVED_STATE.store(STATX_STATE::Unavailable as u8, Ordering::Relaxed);
+                return None;
+            }
+        }
+
+        // We cannot fill `stat64` exhaustively because of private padding fields.
+        let mut stat: stat64 = mem::zeroed();
+        // `c_ulong` on gnu-mips, `dev_t` otherwise
+        stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _;
+        stat.st_ino = buf.stx_ino as libc::ino64_t;
+        stat.st_nlink = buf.stx_nlink as libc::nlink_t;
+        stat.st_mode = buf.stx_mode as libc::mode_t;
+        stat.st_uid = buf.stx_uid as libc::uid_t;
+        stat.st_gid = buf.stx_gid as libc::gid_t;
+        stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _;
+        stat.st_size = buf.stx_size as off64_t;
+        stat.st_blksize = buf.stx_blksize as libc::blksize_t;
+        stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
+        stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
+        // `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
+        stat.st_atime_nsec = buf.stx_atime.tv_nsec as _;
+        stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
+        stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _;
+        stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
+        stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _;
+
+        let extra = StatxExtraFields {
+            stx_mask: buf.stx_mask,
+            stx_btime: buf.stx_btime,
+            // Store full times to avoid 32-bit `time_t` truncation.
+            #[cfg(target_pointer_width = "32")]
+            stx_atime: buf.stx_atime,
+            #[cfg(target_pointer_width = "32")]
+            stx_ctime: buf.stx_ctime,
+            #[cfg(target_pointer_width = "32")]
+            stx_mtime: buf.stx_mtime,
+        };
+
+        Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
+    }
+
+} else {
+    #[derive(Clone)]
+    pub struct FileAttr {
+        stat: stat64,
+    }
+}}
+
+// all DirEntry's will have a reference to this struct
+struct InnerReadDir {
+    dirp: Dir,
+    root: PathBuf,
+}
+
+pub struct ReadDir {
+    inner: Arc<InnerReadDir>,
+    end_of_stream: bool,
+}
+
+impl ReadDir {
+    fn new(inner: InnerReadDir) -> Self {
+        Self { inner: Arc::new(inner), end_of_stream: false }
+    }
+}
+
+struct Dir(*mut libc::DIR);
+
+unsafe impl Send for Dir {}
+unsafe impl Sync for Dir {}
+
+#[cfg(any(
+    target_os = "android",
+    target_os = "linux",
+    target_os = "solaris",
+    target_os = "illumos",
+    target_os = "fuchsia",
+    target_os = "redox",
+    target_os = "aix",
+    target_os = "nto",
+    target_os = "vita",
+    target_os = "hurd",
+))]
+pub struct DirEntry {
+    dir: Arc<InnerReadDir>,
+    entry: dirent64_min,
+    // We need to store an owned copy of the entry name on platforms that use
+    // readdir() (not readdir_r()), because a) struct dirent may use a flexible
+    // array to store the name, b) it lives only until the next readdir() call.
+    name: crate::ffi::CString,
+}
+
+// Define a minimal subset of fields we need from `dirent64`, especially since
+// we're not using the immediate `d_name` on these targets. Keeping this as an
+// `entry` field in `DirEntry` helps reduce the `cfg` boilerplate elsewhere.
+#[cfg(any(
+    target_os = "android",
+    target_os = "linux",
+    target_os = "solaris",
+    target_os = "illumos",
+    target_os = "fuchsia",
+    target_os = "redox",
+    target_os = "aix",
+    target_os = "nto",
+    target_os = "vita",
+    target_os = "hurd",
+))]
+struct dirent64_min {
+    d_ino: u64,
+    #[cfg(not(any(
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "aix",
+        target_os = "nto",
+        target_os = "vita",
+    )))]
+    d_type: u8,
+}
+
+#[cfg(not(any(
+    target_os = "android",
+    target_os = "linux",
+    target_os = "solaris",
+    target_os = "illumos",
+    target_os = "fuchsia",
+    target_os = "redox",
+    target_os = "aix",
+    target_os = "nto",
+    target_os = "vita",
+    target_os = "hurd",
+)))]
+pub struct DirEntry {
+    dir: Arc<InnerReadDir>,
+    // The full entry includes a fixed-length `d_name`.
+    entry: dirent64,
+}
+
+#[derive(Clone, Debug)]
+pub struct OpenOptions {
+    // generic
+    read: bool,
+    write: bool,
+    append: bool,
+    truncate: bool,
+    create: bool,
+    create_new: bool,
+    // system-specific
+    custom_flags: i32,
+    mode: mode_t,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct FilePermissions {
+    mode: mode_t,
+}
+
+#[derive(Copy, Clone, Debug, Default)]
+pub struct FileTimes {
+    accessed: Option<SystemTime>,
+    modified: Option<SystemTime>,
+    #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos", target_os = "tvos"))]
+    created: Option<SystemTime>,
+}
+
+#[derive(Copy, Clone, Eq, Debug)]
+pub struct FileType {
+    mode: mode_t,
+}
+
+impl PartialEq for FileType {
+    fn eq(&self, other: &Self) -> bool {
+        self.masked() == other.masked()
+    }
+}
+
+impl core::hash::Hash for FileType {
+    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
+        self.masked().hash(state);
+    }
+}
+
+#[derive(Debug)]
+pub struct DirBuilder {
+    mode: mode_t,
+}
+
+cfg_has_statx! {{
+    impl FileAttr {
+        fn from_stat64(stat: stat64) -> Self {
+            Self { stat, statx_extra_fields: None }
+        }
+
+        #[cfg(target_pointer_width = "32")]
+        pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> {
+            if let Some(ext) = &self.statx_extra_fields {
+                if (ext.stx_mask & libc::STATX_MTIME) != 0 {
+                    return Some(&ext.stx_mtime);
+                }
+            }
+            None
+        }
+
+        #[cfg(target_pointer_width = "32")]
+        pub fn stx_atime(&self) -> Option<&libc::statx_timestamp> {
+            if let Some(ext) = &self.statx_extra_fields {
+                if (ext.stx_mask & libc::STATX_ATIME) != 0 {
+                    return Some(&ext.stx_atime);
+                }
+            }
+            None
+        }
+
+        #[cfg(target_pointer_width = "32")]
+        pub fn stx_ctime(&self) -> Option<&libc::statx_timestamp> {
+            if let Some(ext) = &self.statx_extra_fields {
+                if (ext.stx_mask & libc::STATX_CTIME) != 0 {
+                    return Some(&ext.stx_ctime);
+                }
+            }
+            None
+        }
+    }
+} else {
+    impl FileAttr {
+        fn from_stat64(stat: stat64) -> Self {
+            Self { stat }
+        }
+    }
+}}
+
+impl FileAttr {
+    pub fn size(&self) -> u64 {
+        self.stat.st_size as u64
+    }
+    pub fn perm(&self) -> FilePermissions {
+        FilePermissions { mode: (self.stat.st_mode as mode_t) }
+    }
+
+    pub fn file_type(&self) -> FileType {
+        FileType { mode: self.stat.st_mode as mode_t }
+    }
+}
+
+#[cfg(target_os = "netbsd")]
+impl FileAttr {
+    pub fn modified(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtimensec as i64))
+    }
+
+    pub fn accessed(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atimensec as i64))
+    }
+
+    pub fn created(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtimensec as i64))
+    }
+}
+
+#[cfg(target_os = "aix")]
+impl FileAttr {
+    pub fn modified(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_mtime.tv_sec as i64, self.stat.st_mtime.tv_nsec as i64))
+    }
+
+    pub fn accessed(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_atime.tv_sec as i64, self.stat.st_atime.tv_nsec as i64))
+    }
+
+    pub fn created(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_ctime.tv_sec as i64, self.stat.st_ctime.tv_nsec as i64))
+    }
+}
+
+#[cfg(not(any(target_os = "netbsd", target_os = "nto", target_os = "aix")))]
+impl FileAttr {
+    #[cfg(not(any(
+        target_os = "vxworks",
+        target_os = "espidf",
+        target_os = "horizon",
+        target_os = "vita",
+        target_os = "hurd",
+    )))]
+    pub fn modified(&self) -> io::Result<SystemTime> {
+        #[cfg(target_pointer_width = "32")]
+        cfg_has_statx! {
+            if let Some(mtime) = self.stx_mtime() {
+                return Ok(SystemTime::new(mtime.tv_sec, mtime.tv_nsec as i64));
+            }
+        }
+
+        Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtime_nsec as i64))
+    }
+
+    #[cfg(any(target_os = "vxworks", target_os = "espidf", target_os = "vita"))]
+    pub fn modified(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_mtime as i64, 0))
+    }
+
+    #[cfg(any(target_os = "horizon", target_os = "hurd"))]
+    pub fn modified(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::from(self.stat.st_mtim))
+    }
+
+    #[cfg(not(any(
+        target_os = "vxworks",
+        target_os = "espidf",
+        target_os = "horizon",
+        target_os = "vita",
+        target_os = "hurd",
+    )))]
+    pub fn accessed(&self) -> io::Result<SystemTime> {
+        #[cfg(target_pointer_width = "32")]
+        cfg_has_statx! {
+            if let Some(atime) = self.stx_atime() {
+                return Ok(SystemTime::new(atime.tv_sec, atime.tv_nsec as i64));
+            }
+        }
+
+        Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atime_nsec as i64))
+    }
+
+    #[cfg(any(target_os = "vxworks", target_os = "espidf", target_os = "vita"))]
+    pub fn accessed(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_atime as i64, 0))
+    }
+
+    #[cfg(any(target_os = "horizon", target_os = "hurd"))]
+    pub fn accessed(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::from(self.stat.st_atim))
+    }
+
+    #[cfg(any(
+        target_os = "freebsd",
+        target_os = "openbsd",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+    ))]
+    pub fn created(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtime_nsec as i64))
+    }
+
+    #[cfg(not(any(
+        target_os = "freebsd",
+        target_os = "openbsd",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "vita",
+    )))]
+    pub fn created(&self) -> io::Result<SystemTime> {
+        cfg_has_statx! {
+            if let Some(ext) = &self.statx_extra_fields {
+                return if (ext.stx_mask & libc::STATX_BTIME) != 0 {
+                    Ok(SystemTime::new(ext.stx_btime.tv_sec, ext.stx_btime.tv_nsec as i64))
+                } else {
+                    Err(io::const_io_error!(
+                        io::ErrorKind::Uncategorized,
+                        "creation time is not available for the filesystem",
+                    ))
+                };
+            }
+        }
+
+        Err(io::const_io_error!(
+            io::ErrorKind::Unsupported,
+            "creation time is not available on this platform \
+                            currently",
+        ))
+    }
+
+    #[cfg(target_os = "vita")]
+    pub fn created(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_ctime as i64, 0))
+    }
+}
+
+#[cfg(target_os = "nto")]
+impl FileAttr {
+    pub fn modified(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_mtim.tv_sec, self.stat.st_mtim.tv_nsec))
+    }
+
+    pub fn accessed(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_atim.tv_sec, self.stat.st_atim.tv_nsec))
+    }
+
+    pub fn created(&self) -> io::Result<SystemTime> {
+        Ok(SystemTime::new(self.stat.st_ctim.tv_sec, self.stat.st_ctim.tv_nsec))
+    }
+}
+
+impl AsInner<stat64> for FileAttr {
+    #[inline]
+    fn as_inner(&self) -> &stat64 {
+        &self.stat
+    }
+}
+
+impl FilePermissions {
+    pub fn readonly(&self) -> bool {
+        // check if any class (owner, group, others) has write permission
+        self.mode & 0o222 == 0
+    }
+
+    pub fn set_readonly(&mut self, readonly: bool) {
+        if readonly {
+            // remove write permission for all classes; equivalent to `chmod a-w <file>`
+            self.mode &= !0o222;
+        } else {
+            // add write permission for all classes; equivalent to `chmod a+w <file>`
+            self.mode |= 0o222;
+        }
+    }
+    pub fn mode(&self) -> u32 {
+        self.mode as u32
+    }
+}
+
+impl FileTimes {
+    pub fn set_accessed(&mut self, t: SystemTime) {
+        self.accessed = Some(t);
+    }
+
+    pub fn set_modified(&mut self, t: SystemTime) {
+        self.modified = Some(t);
+    }
+
+    #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos", target_os = "tvos"))]
+    pub fn set_created(&mut self, t: SystemTime) {
+        self.created = Some(t);
+    }
+}
+
+impl FileType {
+    pub fn is_dir(&self) -> bool {
+        self.is(libc::S_IFDIR)
+    }
+    pub fn is_file(&self) -> bool {
+        self.is(libc::S_IFREG)
+    }
+    pub fn is_symlink(&self) -> bool {
+        self.is(libc::S_IFLNK)
+    }
+
+    pub fn is(&self, mode: mode_t) -> bool {
+        self.masked() == mode
+    }
+
+    fn masked(&self) -> mode_t {
+        self.mode & libc::S_IFMT
+    }
+}
+
+impl FromInner<u32> for FilePermissions {
+    fn from_inner(mode: u32) -> FilePermissions {
+        FilePermissions { mode: mode as mode_t }
+    }
+}
+
+impl fmt::Debug for ReadDir {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
+        // Thus the result will be e g 'ReadDir("/home")'
+        fmt::Debug::fmt(&*self.inner.root, f)
+    }
+}
+
+impl Iterator for ReadDir {
+    type Item = io::Result<DirEntry>;
+
+    #[cfg(any(
+        target_os = "android",
+        target_os = "linux",
+        target_os = "solaris",
+        target_os = "fuchsia",
+        target_os = "redox",
+        target_os = "illumos",
+        target_os = "aix",
+        target_os = "nto",
+        target_os = "vita",
+        target_os = "hurd",
+    ))]
+    fn next(&mut self) -> Option<io::Result<DirEntry>> {
+        if self.end_of_stream {
+            return None;
+        }
+
+        unsafe {
+            loop {
+                // As of POSIX.1-2017, readdir() is not required to be thread safe; only
+                // readdir_r() is. However, readdir_r() cannot correctly handle platforms
+                // with unlimited or variable NAME_MAX. Many modern platforms guarantee
+                // thread safety for readdir() as long an individual DIR* is not accessed
+                // concurrently, which is sufficient for Rust.
+                super::os::set_errno(0);
+                let entry_ptr = readdir64(self.inner.dirp.0);
+                if entry_ptr.is_null() {
+                    // We either encountered an error, or reached the end. Either way,
+                    // the next call to next() should return None.
+                    self.end_of_stream = true;
+
+                    // To distinguish between errors and end-of-directory, we had to clear
+                    // errno beforehand to check for an error now.
+                    return match super::os::errno() {
+                        0 => None,
+                        e => Some(Err(Error::from_raw_os_error(e))),
+                    };
+                }
+
+                // The dirent64 struct is a weird imaginary thing that isn't ever supposed
+                // to be worked with by value. Its trailing d_name field is declared
+                // variously as [c_char; 256] or [c_char; 1] on different systems but
+                // either way that size is meaningless; only the offset of d_name is
+                // meaningful. The dirent64 pointers that libc returns from readdir64 are
+                // allowed to point to allocations smaller _or_ LARGER than implied by the
+                // definition of the struct.
+                //
+                // As such, we need to be even more careful with dirent64 than if its
+                // contents were "simply" partially initialized data.
+                //
+                // Like for uninitialized contents, converting entry_ptr to `&dirent64`
+                // would not be legal. However, unique to dirent64 is that we don't even
+                // get to use `addr_of!((*entry_ptr).d_name)` because that operation
+                // requires the full extent of *entry_ptr to be in bounds of the same
+                // allocation, which is not necessarily the case here.
+                //
+                // Instead we must access fields individually through their offsets.
+                macro_rules! offset_ptr {
+                    ($entry_ptr:expr, $field:ident) => {{
+                        const OFFSET: isize = mem::offset_of!(dirent64, $field) as isize;
+                        if true {
+                            // Cast to the same type determined by the else branch.
+                            $entry_ptr.byte_offset(OFFSET).cast::<_>()
+                        } else {
+                            #[allow(deref_nullptr)]
+                            {
+                                ptr::addr_of!((*ptr::null::<dirent64>()).$field)
+                            }
+                        }
+                    }};
+                }
+
+                // d_name is guaranteed to be null-terminated.
+                let name = CStr::from_ptr(offset_ptr!(entry_ptr, d_name).cast());
+                let name_bytes = name.to_bytes();
+                if name_bytes == b"." || name_bytes == b".." {
+                    continue;
+                }
+
+                #[cfg(not(target_os = "vita"))]
+                let entry = dirent64_min {
+                    d_ino: *offset_ptr!(entry_ptr, d_ino) as u64,
+                    #[cfg(not(any(
+                        target_os = "solaris",
+                        target_os = "illumos",
+                        target_os = "aix",
+                        target_os = "nto",
+                    )))]
+                    d_type: *offset_ptr!(entry_ptr, d_type) as u8,
+                };
+
+                #[cfg(target_os = "vita")]
+                let entry = dirent64_min { d_ino: 0u64 };
+
+                return Some(Ok(DirEntry {
+                    entry,
+                    name: name.to_owned(),
+                    dir: Arc::clone(&self.inner),
+                }));
+            }
+        }
+    }
+
+    #[cfg(not(any(
+        target_os = "android",
+        target_os = "linux",
+        target_os = "solaris",
+        target_os = "fuchsia",
+        target_os = "redox",
+        target_os = "illumos",
+        target_os = "aix",
+        target_os = "nto",
+        target_os = "vita",
+        target_os = "hurd",
+    )))]
+    fn next(&mut self) -> Option<io::Result<DirEntry>> {
+        if self.end_of_stream {
+            return None;
+        }
+
+        unsafe {
+            let mut ret = DirEntry { entry: mem::zeroed(), dir: Arc::clone(&self.inner) };
+            let mut entry_ptr = ptr::null_mut();
+            loop {
+                let err = readdir64_r(self.inner.dirp.0, &mut ret.entry, &mut entry_ptr);
+                if err != 0 {
+                    if entry_ptr.is_null() {
+                        // We encountered an error (which will be returned in this iteration), but
+                        // we also reached the end of the directory stream. The `end_of_stream`
+                        // flag is enabled to make sure that we return `None` in the next iteration
+                        // (instead of looping forever)
+                        self.end_of_stream = true;
+                    }
+                    return Some(Err(Error::from_raw_os_error(err)));
+                }
+                if entry_ptr.is_null() {
+                    return None;
+                }
+                if ret.name_bytes() != b"." && ret.name_bytes() != b".." {
+                    return Some(Ok(ret));
+                }
+            }
+        }
+    }
+}
+
+impl Drop for Dir {
+    fn drop(&mut self) {
+        let r = unsafe { libc::closedir(self.0) };
+        assert!(
+            r == 0 || crate::io::Error::last_os_error().is_interrupted(),
+            "unexpected error during closedir: {:?}",
+            crate::io::Error::last_os_error()
+        );
+    }
+}
+
+impl DirEntry {
+    pub fn path(&self) -> PathBuf {
+        self.dir.root.join(self.file_name_os_str())
+    }
+
+    pub fn file_name(&self) -> OsString {
+        self.file_name_os_str().to_os_string()
+    }
+
+    #[cfg(all(
+        any(
+            all(target_os = "linux", not(target_env = "musl")),
+            target_os = "emscripten",
+            target_os = "android",
+            target_os = "hurd"
+        ),
+        not(miri)
+    ))]
+    pub fn metadata(&self) -> io::Result<FileAttr> {
+        let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?;
+        let name = self.name_cstr().as_ptr();
+
+        cfg_has_statx! {
+            if let Some(ret) = unsafe { try_statx(
+                fd,
+                name,
+                libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
+                libc::STATX_ALL,
+            ) } {
+                return ret;
+            }
+        }
+
+        let mut stat: stat64 = unsafe { mem::zeroed() };
+        cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?;
+        Ok(FileAttr::from_stat64(stat))
+    }
+
+    #[cfg(any(
+        not(any(
+            all(target_os = "linux", not(target_env = "musl")),
+            target_os = "emscripten",
+            target_os = "android",
+            target_os = "hurd",
+        )),
+        miri
+    ))]
+    pub fn metadata(&self) -> io::Result<FileAttr> {
+        lstat(&self.path())
+    }
+
+    #[cfg(any(
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "haiku",
+        target_os = "vxworks",
+        target_os = "aix",
+        target_os = "nto",
+        target_os = "vita",
+    ))]
+    pub fn file_type(&self) -> io::Result<FileType> {
+        self.metadata().map(|m| m.file_type())
+    }
+
+    #[cfg(not(any(
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "haiku",
+        target_os = "vxworks",
+        target_os = "aix",
+        target_os = "nto",
+        target_os = "vita",
+    )))]
+    pub fn file_type(&self) -> io::Result<FileType> {
+        match self.entry.d_type {
+            libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }),
+            libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }),
+            libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }),
+            libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }),
+            libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }),
+            libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }),
+            libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }),
+            _ => self.metadata().map(|m| m.file_type()),
+        }
+    }
+
+    #[cfg(any(
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "linux",
+        target_os = "emscripten",
+        target_os = "android",
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "haiku",
+        target_os = "l4re",
+        target_os = "fuchsia",
+        target_os = "redox",
+        target_os = "vxworks",
+        target_os = "espidf",
+        target_os = "horizon",
+        target_os = "vita",
+        target_os = "aix",
+        target_os = "nto",
+        target_os = "hurd",
+    ))]
+    pub fn ino(&self) -> u64 {
+        self.entry.d_ino as u64
+    }
+
+    #[cfg(any(
+        target_os = "freebsd",
+        target_os = "openbsd",
+        target_os = "netbsd",
+        target_os = "dragonfly"
+    ))]
+    pub fn ino(&self) -> u64 {
+        self.entry.d_fileno as u64
+    }
+
+    #[cfg(any(
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "netbsd",
+        target_os = "openbsd",
+        target_os = "freebsd",
+        target_os = "dragonfly"
+    ))]
+    fn name_bytes(&self) -> &[u8] {
+        use crate::slice;
+        unsafe {
+            slice::from_raw_parts(
+                self.entry.d_name.as_ptr() as *const u8,
+                self.entry.d_namlen as usize,
+            )
+        }
+    }
+    #[cfg(not(any(
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "netbsd",
+        target_os = "openbsd",
+        target_os = "freebsd",
+        target_os = "dragonfly"
+    )))]
+    fn name_bytes(&self) -> &[u8] {
+        self.name_cstr().to_bytes()
+    }
+
+    #[cfg(not(any(
+        target_os = "android",
+        target_os = "linux",
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "fuchsia",
+        target_os = "redox",
+        target_os = "aix",
+        target_os = "nto",
+        target_os = "vita",
+        target_os = "hurd",
+    )))]
+    fn name_cstr(&self) -> &CStr {
+        unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) }
+    }
+    #[cfg(any(
+        target_os = "android",
+        target_os = "linux",
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "fuchsia",
+        target_os = "redox",
+        target_os = "aix",
+        target_os = "nto",
+        target_os = "vita",
+        target_os = "hurd",
+    ))]
+    fn name_cstr(&self) -> &CStr {
+        &self.name
+    }
+
+    pub fn file_name_os_str(&self) -> &OsStr {
+        OsStr::from_bytes(self.name_bytes())
+    }
+}
+
+impl OpenOptions {
+    pub fn new() -> OpenOptions {
+        OpenOptions {
+            // generic
+            read: false,
+            write: false,
+            append: false,
+            truncate: false,
+            create: false,
+            create_new: false,
+            // system-specific
+            custom_flags: 0,
+            mode: 0o666,
+        }
+    }
+
+    pub fn read(&mut self, read: bool) {
+        self.read = read;
+    }
+    pub fn write(&mut self, write: bool) {
+        self.write = write;
+    }
+    pub fn append(&mut self, append: bool) {
+        self.append = append;
+    }
+    pub fn truncate(&mut self, truncate: bool) {
+        self.truncate = truncate;
+    }
+    pub fn create(&mut self, create: bool) {
+        self.create = create;
+    }
+    pub fn create_new(&mut self, create_new: bool) {
+        self.create_new = create_new;
+    }
+
+    pub fn custom_flags(&mut self, flags: i32) {
+        self.custom_flags = flags;
+    }
+    pub fn mode(&mut self, mode: u32) {
+        self.mode = mode as mode_t;
+    }
+
+    fn get_access_mode(&self) -> io::Result<c_int> {
+        match (self.read, self.write, self.append) {
+            (true, false, false) => Ok(libc::O_RDONLY),
+            (false, true, false) => Ok(libc::O_WRONLY),
+            (true, true, false) => Ok(libc::O_RDWR),
+            (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND),
+            (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND),
+            (false, false, false) => Err(Error::from_raw_os_error(libc::EINVAL)),
+        }
+    }
+
+    fn get_creation_mode(&self) -> io::Result<c_int> {
+        match (self.write, self.append) {
+            (true, false) => {}
+            (false, false) => {
+                if self.truncate || self.create || self.create_new {
+                    return Err(Error::from_raw_os_error(libc::EINVAL));
+                }
+            }
+            (_, true) => {
+                if self.truncate && !self.create_new {
+                    return Err(Error::from_raw_os_error(libc::EINVAL));
+                }
+            }
+        }
+
+        Ok(match (self.create, self.truncate, self.create_new) {
+            (false, false, false) => 0,
+            (true, false, false) => libc::O_CREAT,
+            (false, true, false) => libc::O_TRUNC,
+            (true, true, false) => libc::O_CREAT | libc::O_TRUNC,
+            (_, _, true) => libc::O_CREAT | libc::O_EXCL,
+        })
+    }
+}
+
+impl File {
+    pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
+        run_path_with_cstr(path, |path| File::open_c(path, opts))
+    }
+
+    pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result<File> {
+        let flags = libc::O_CLOEXEC
+            | opts.get_access_mode()?
+            | opts.get_creation_mode()?
+            | (opts.custom_flags as c_int & !libc::O_ACCMODE);
+        // The third argument of `open64` is documented to have type `mode_t`. On
+        // some platforms (like macOS, where `open64` is actually `open`), `mode_t` is `u16`.
+        // However, since this is a variadic function, C integer promotion rules mean that on
+        // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms).
+        let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?;
+        Ok(File(unsafe { FileDesc::from_raw_fd(fd) }))
+    }
+
+    pub fn file_attr(&self) -> io::Result<FileAttr> {
+        let fd = self.as_raw_fd();
+
+        cfg_has_statx! {
+            if let Some(ret) = unsafe { try_statx(
+                fd,
+                c"".as_ptr() as *const c_char,
+                libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT,
+                libc::STATX_ALL,
+            ) } {
+                return ret;
+            }
+        }
+
+        let mut stat: stat64 = unsafe { mem::zeroed() };
+        cvt(unsafe { fstat64(fd, &mut stat) })?;
+        Ok(FileAttr::from_stat64(stat))
+    }
+
+    pub fn fsync(&self) -> io::Result<()> {
+        cvt_r(|| unsafe { os_fsync(self.as_raw_fd()) })?;
+        return Ok(());
+
+        #[cfg(any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "watchos",
+        ))]
+        unsafe fn os_fsync(fd: c_int) -> c_int {
+            libc::fcntl(fd, libc::F_FULLFSYNC)
+        }
+        #[cfg(not(any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "watchos",
+        )))]
+        unsafe fn os_fsync(fd: c_int) -> c_int {
+            libc::fsync(fd)
+        }
+    }
+
+    pub fn datasync(&self) -> io::Result<()> {
+        cvt_r(|| unsafe { os_datasync(self.as_raw_fd()) })?;
+        return Ok(());
+
+        #[cfg(any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "watchos",
+        ))]
+        unsafe fn os_datasync(fd: c_int) -> c_int {
+            libc::fcntl(fd, libc::F_FULLFSYNC)
+        }
+        #[cfg(any(
+            target_os = "freebsd",
+            target_os = "linux",
+            target_os = "android",
+            target_os = "netbsd",
+            target_os = "openbsd",
+            target_os = "nto",
+            target_os = "hurd",
+        ))]
+        unsafe fn os_datasync(fd: c_int) -> c_int {
+            libc::fdatasync(fd)
+        }
+        #[cfg(not(any(
+            target_os = "android",
+            target_os = "freebsd",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "linux",
+            target_os = "macos",
+            target_os = "netbsd",
+            target_os = "openbsd",
+            target_os = "watchos",
+            target_os = "nto",
+            target_os = "hurd",
+        )))]
+        unsafe fn os_datasync(fd: c_int) -> c_int {
+            libc::fsync(fd)
+        }
+    }
+
+    pub fn truncate(&self, size: u64) -> io::Result<()> {
+        let size: off64_t =
+            size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
+        cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }).map(drop)
+    }
+
+    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
+        self.0.read(buf)
+    }
+
+    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+        self.0.read_vectored(bufs)
+    }
+
+    #[inline]
+    pub fn is_read_vectored(&self) -> bool {
+        self.0.is_read_vectored()
+    }
+
+    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+        self.0.read_at(buf, offset)
+    }
+
+    pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
+        self.0.read_buf(cursor)
+    }
+
+    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
+        self.0.read_vectored_at(bufs, offset)
+    }
+
+    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
+        self.0.write(buf)
+    }
+
+    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+        self.0.write_vectored(bufs)
+    }
+
+    #[inline]
+    pub fn is_write_vectored(&self) -> bool {
+        self.0.is_write_vectored()
+    }
+
+    pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
+        self.0.write_at(buf, offset)
+    }
+
+    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
+        self.0.write_vectored_at(bufs, offset)
+    }
+
+    #[inline]
+    pub fn flush(&self) -> io::Result<()> {
+        Ok(())
+    }
+
+    pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
+        let (whence, pos) = match pos {
+            // Casting to `i64` is fine, too large values will end up as
+            // negative which will cause an error in `lseek64`.
+            SeekFrom::Start(off) => (libc::SEEK_SET, off as i64),
+            SeekFrom::End(off) => (libc::SEEK_END, off),
+            SeekFrom::Current(off) => (libc::SEEK_CUR, off),
+        };
+        let n = cvt(unsafe { lseek64(self.as_raw_fd(), pos as off64_t, whence) })?;
+        Ok(n as u64)
+    }
+
+    pub fn duplicate(&self) -> io::Result<File> {
+        self.0.duplicate().map(File)
+    }
+
+    pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> {
+        cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?;
+        Ok(())
+    }
+
+    pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
+        #[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon")))]
+        let to_timespec = |time: Option<SystemTime>| match time {
+            Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts),
+            Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_io_error!(
+                io::ErrorKind::InvalidInput,
+                "timestamp is too large to set as a file time"
+            )),
+            Some(_) => Err(io::const_io_error!(
+                io::ErrorKind::InvalidInput,
+                "timestamp is too small to set as a file time"
+            )),
+            None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }),
+        };
+        cfg_if::cfg_if! {
+            if #[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon"))] {
+                // Redox doesn't appear to support `UTIME_OMIT`.
+                // ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore
+                // the same as for Redox.
+                let _ = times;
+                Err(io::const_io_error!(
+                    io::ErrorKind::Unsupported,
+                    "setting file times not supported",
+                ))
+            } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] {
+                let mut buf = [mem::MaybeUninit::<libc::timespec>::uninit(); 3];
+                let mut num_times = 0;
+                let mut attrlist: libc::attrlist = unsafe { mem::zeroed() };
+                attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT;
+                if times.created.is_some() {
+                    buf[num_times].write(to_timespec(times.created)?);
+                    num_times += 1;
+                    attrlist.commonattr |= libc::ATTR_CMN_CRTIME;
+                }
+                if times.modified.is_some() {
+                    buf[num_times].write(to_timespec(times.modified)?);
+                    num_times += 1;
+                    attrlist.commonattr |= libc::ATTR_CMN_MODTIME;
+                }
+                if times.accessed.is_some() {
+                    buf[num_times].write(to_timespec(times.accessed)?);
+                    num_times += 1;
+                    attrlist.commonattr |= libc::ATTR_CMN_ACCTIME;
+                }
+                cvt(unsafe { libc::fsetattrlist(
+                    self.as_raw_fd(),
+                    (&attrlist as *const libc::attrlist).cast::<libc::c_void>().cast_mut(),
+                    buf.as_ptr().cast::<libc::c_void>().cast_mut(),
+                    num_times * mem::size_of::<libc::timespec>(),
+                    0
+                ) })?;
+                Ok(())
+            } else if #[cfg(target_os = "android")] {
+                let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?];
+                // futimens requires Android API level 19
+                cvt(unsafe {
+                    weak!(fn futimens(c_int, *const libc::timespec) -> c_int);
+                    match futimens.get() {
+                        Some(futimens) => futimens(self.as_raw_fd(), times.as_ptr()),
+                        None => return Err(io::const_io_error!(
+                            io::ErrorKind::Unsupported,
+                            "setting file times requires Android API level >= 19",
+                        )),
+                    }
+                })?;
+                Ok(())
+            } else {
+                #[cfg(all(target_os = "linux", target_env = "gnu", target_pointer_width = "32", not(target_arch = "riscv32")))]
+                {
+                    use crate::sys::{time::__timespec64, weak::weak};
+
+                    // Added in glibc 2.34
+                    weak!(fn __futimens64(libc::c_int, *const __timespec64) -> libc::c_int);
+
+                    if let Some(futimens64) = __futimens64.get() {
+                        let to_timespec = |time: Option<SystemTime>| time.map(|time| time.t.to_timespec64())
+                            .unwrap_or(__timespec64::new(0, libc::UTIME_OMIT as _));
+                        let times = [to_timespec(times.accessed), to_timespec(times.modified)];
+                        cvt(unsafe { futimens64(self.as_raw_fd(), times.as_ptr()) })?;
+                        return Ok(());
+                    }
+                }
+                let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?];
+                cvt(unsafe { libc::futimens(self.as_raw_fd(), times.as_ptr()) })?;
+                Ok(())
+            }
+        }
+    }
+}
+
+impl DirBuilder {
+    pub fn new() -> DirBuilder {
+        DirBuilder { mode: 0o777 }
+    }
+
+    pub fn mkdir(&self, p: &Path) -> io::Result<()> {
+        run_path_with_cstr(p, |p| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) }).map(|_| ()))
+    }
+
+    pub fn set_mode(&mut self, mode: u32) {
+        self.mode = mode as mode_t;
+    }
+}
+
+impl AsInner<FileDesc> for File {
+    #[inline]
+    fn as_inner(&self) -> &FileDesc {
+        &self.0
+    }
+}
+
+impl AsInnerMut<FileDesc> for File {
+    #[inline]
+    fn as_inner_mut(&mut self) -> &mut FileDesc {
+        &mut self.0
+    }
+}
+
+impl IntoInner<FileDesc> for File {
+    fn into_inner(self) -> FileDesc {
+        self.0
+    }
+}
+
+impl FromInner<FileDesc> for File {
+    fn from_inner(file_desc: FileDesc) -> Self {
+        Self(file_desc)
+    }
+}
+
+impl AsFd for File {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+
+impl AsRawFd for File {
+    #[inline]
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+
+impl IntoRawFd for File {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+
+impl FromRawFd for File {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FromRawFd::from_raw_fd(raw_fd))
+    }
+}
+
+impl fmt::Debug for File {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        #[cfg(any(
+            target_os = "linux",
+            target_os = "netbsd",
+            target_os = "illumos",
+            target_os = "solaris"
+        ))]
+        fn get_path(fd: c_int) -> Option<PathBuf> {
+            let mut p = PathBuf::from("/proc/self/fd");
+            p.push(&fd.to_string());
+            readlink(&p).ok()
+        }
+
+        #[cfg(target_os = "macos")]
+        fn get_path(fd: c_int) -> Option<PathBuf> {
+            // FIXME: The use of PATH_MAX is generally not encouraged, but it
+            // is inevitable in this case because macOS defines `fcntl` with
+            // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
+            // alternatives. If a better method is invented, it should be used
+            // instead.
+            let mut buf = vec![0; libc::PATH_MAX as usize];
+            let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) };
+            if n == -1 {
+                return None;
+            }
+            let l = buf.iter().position(|&c| c == 0).unwrap();
+            buf.truncate(l as usize);
+            buf.shrink_to_fit();
+            Some(PathBuf::from(OsString::from_vec(buf)))
+        }
+
+        #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))]
+        fn get_path(fd: c_int) -> Option<PathBuf> {
+            let info = Box::<libc::kinfo_file>::new_zeroed();
+            let mut info = unsafe { info.assume_init() };
+            info.kf_structsize = mem::size_of::<libc::kinfo_file>() as libc::c_int;
+            let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) };
+            if n == -1 {
+                return None;
+            }
+            let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() };
+            Some(PathBuf::from(OsString::from_vec(buf)))
+        }
+
+        #[cfg(target_os = "vxworks")]
+        fn get_path(fd: c_int) -> Option<PathBuf> {
+            let mut buf = vec![0; libc::PATH_MAX as usize];
+            let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) };
+            if n == -1 {
+                return None;
+            }
+            let l = buf.iter().position(|&c| c == 0).unwrap();
+            buf.truncate(l as usize);
+            Some(PathBuf::from(OsString::from_vec(buf)))
+        }
+
+        #[cfg(not(any(
+            target_os = "linux",
+            target_os = "macos",
+            target_os = "vxworks",
+            all(target_os = "freebsd", target_arch = "x86_64"),
+            target_os = "netbsd",
+            target_os = "illumos",
+            target_os = "solaris"
+        )))]
+        fn get_path(_fd: c_int) -> Option<PathBuf> {
+            // FIXME(#24570): implement this for other Unix platforms
+            None
+        }
+
+        #[cfg(any(
+            target_os = "linux",
+            target_os = "macos",
+            target_os = "freebsd",
+            target_os = "hurd",
+            target_os = "netbsd",
+            target_os = "openbsd",
+            target_os = "vxworks"
+        ))]
+        fn get_mode(fd: c_int) -> Option<(bool, bool)> {
+            let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) };
+            if mode == -1 {
+                return None;
+            }
+            match mode & libc::O_ACCMODE {
+                libc::O_RDONLY => Some((true, false)),
+                libc::O_RDWR => Some((true, true)),
+                libc::O_WRONLY => Some((false, true)),
+                _ => None,
+            }
+        }
+
+        #[cfg(not(any(
+            target_os = "linux",
+            target_os = "macos",
+            target_os = "freebsd",
+            target_os = "hurd",
+            target_os = "netbsd",
+            target_os = "openbsd",
+            target_os = "vxworks"
+        )))]
+        fn get_mode(_fd: c_int) -> Option<(bool, bool)> {
+            // FIXME(#24570): implement this for other Unix platforms
+            None
+        }
+
+        let fd = self.as_raw_fd();
+        let mut b = f.debug_struct("File");
+        b.field("fd", &fd);
+        if let Some(path) = get_path(fd) {
+            b.field("path", &path);
+        }
+        if let Some((read, write)) = get_mode(fd) {
+            b.field("read", &read).field("write", &write);
+        }
+        b.finish()
+    }
+}
+
+pub fn readdir(path: &Path) -> io::Result<ReadDir> {
+    let ptr = run_path_with_cstr(path, |p| unsafe { Ok(libc::opendir(p.as_ptr())) })?;
+    if ptr.is_null() {
+        Err(Error::last_os_error())
+    } else {
+        let root = path.to_path_buf();
+        let inner = InnerReadDir { dirp: Dir(ptr), root };
+        Ok(ReadDir::new(inner))
+    }
+}
+
+pub fn unlink(p: &Path) -> io::Result<()> {
+    run_path_with_cstr(p, |p| cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ()))
+}
+
+pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
+    run_path_with_cstr(old, |old| {
+        run_path_with_cstr(new, |new| {
+            cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ())
+        })
+    })
+}
+
+pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
+    run_path_with_cstr(p, |p| cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()))
+}
+
+pub fn rmdir(p: &Path) -> io::Result<()> {
+    run_path_with_cstr(p, |p| cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ()))
+}
+
+pub fn readlink(p: &Path) -> io::Result<PathBuf> {
+    run_path_with_cstr(p, |c_path| {
+        let p = c_path.as_ptr();
+
+        let mut buf = Vec::with_capacity(256);
+
+        loop {
+            let buf_read =
+                cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })?
+                    as usize;
+
+            unsafe {
+                buf.set_len(buf_read);
+            }
+
+            if buf_read != buf.capacity() {
+                buf.shrink_to_fit();
+
+                return Ok(PathBuf::from(OsString::from_vec(buf)));
+            }
+
+            // Trigger the internal buffer resizing logic of `Vec` by requiring
+            // more space than the current capacity. The length is guaranteed to be
+            // the same as the capacity due to the if statement above.
+            buf.reserve(1);
+        }
+    })
+}
+
+pub fn symlink(original: &Path, link: &Path) -> io::Result<()> {
+    run_path_with_cstr(original, |original| {
+        run_path_with_cstr(link, |link| {
+            cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) }).map(|_| ())
+        })
+    })
+}
+
+pub fn link(original: &Path, link: &Path) -> io::Result<()> {
+    run_path_with_cstr(original, |original| {
+        run_path_with_cstr(link, |link| {
+            cfg_if::cfg_if! {
+                if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon", target_os = "vita"))] {
+                    // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves
+                    // it implementation-defined whether `link` follows symlinks, so rely on the
+                    // `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior.
+                    // Android has `linkat` on newer versions, but we happen to know `link`
+                    // always has the correct behavior, so it's here as well.
+                    cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?;
+                } else if #[cfg(any(target_os = "macos", target_os = "solaris"))] {
+                    // MacOS (<=10.9) and Solaris 10 lack support for linkat while newer
+                    // versions have it. We want to use linkat if it is available, so we use weak!
+                    // to check. `linkat` is preferable to `link` because it gives us a flag to
+                    // specify how symlinks should be handled. We pass 0 as the flags argument,
+                    // meaning it shouldn't follow symlinks.
+                    weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int);
+
+                    if let Some(f) = linkat.get() {
+                        cvt(unsafe { f(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?;
+                    } else {
+                        cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?;
+                    };
+                } else {
+                    // Where we can, use `linkat` instead of `link`; see the comment above
+                    // this one for details on why.
+                    cvt(unsafe { libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?;
+                }
+            }
+            Ok(())
+        })
+    })
+}
+
+pub fn stat(p: &Path) -> io::Result<FileAttr> {
+    run_path_with_cstr(p, |p| {
+        cfg_has_statx! {
+            if let Some(ret) = unsafe { try_statx(
+                libc::AT_FDCWD,
+                p.as_ptr(),
+                libc::AT_STATX_SYNC_AS_STAT,
+                libc::STATX_ALL,
+            ) } {
+                return ret;
+            }
+        }
+
+        let mut stat: stat64 = unsafe { mem::zeroed() };
+        cvt(unsafe { stat64(p.as_ptr(), &mut stat) })?;
+        Ok(FileAttr::from_stat64(stat))
+    })
+}
+
+pub fn lstat(p: &Path) -> io::Result<FileAttr> {
+    run_path_with_cstr(p, |p| {
+        cfg_has_statx! {
+            if let Some(ret) = unsafe { try_statx(
+                libc::AT_FDCWD,
+                p.as_ptr(),
+                libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
+                libc::STATX_ALL,
+            ) } {
+                return ret;
+            }
+        }
+
+        let mut stat: stat64 = unsafe { mem::zeroed() };
+        cvt(unsafe { lstat64(p.as_ptr(), &mut stat) })?;
+        Ok(FileAttr::from_stat64(stat))
+    })
+}
+
+pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
+    let r = run_path_with_cstr(p, |path| unsafe {
+        Ok(libc::realpath(path.as_ptr(), ptr::null_mut()))
+    })?;
+    if r.is_null() {
+        return Err(io::Error::last_os_error());
+    }
+    Ok(PathBuf::from(OsString::from_vec(unsafe {
+        let buf = CStr::from_ptr(r).to_bytes().to_vec();
+        libc::free(r as *mut _);
+        buf
+    })))
+}
+
+fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
+    use crate::fs::File;
+    use crate::sys_common::fs::NOT_FILE_ERROR;
+
+    let reader = File::open(from)?;
+    let metadata = reader.metadata()?;
+    if !metadata.is_file() {
+        return Err(NOT_FILE_ERROR);
+    }
+    Ok((reader, metadata))
+}
+
+#[cfg(target_os = "espidf")]
+fn open_to_and_set_permissions(
+    to: &Path,
+    reader_metadata: crate::fs::Metadata,
+) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
+    use crate::fs::OpenOptions;
+    let writer = OpenOptions::new().open(to)?;
+    let writer_metadata = writer.metadata()?;
+    Ok((writer, writer_metadata))
+}
+
+#[cfg(not(target_os = "espidf"))]
+fn open_to_and_set_permissions(
+    to: &Path,
+    reader_metadata: crate::fs::Metadata,
+) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
+    use crate::fs::OpenOptions;
+    use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt};
+
+    let perm = reader_metadata.permissions();
+    let writer = OpenOptions::new()
+        // create the file with the correct mode right away
+        .mode(perm.mode())
+        .write(true)
+        .create(true)
+        .truncate(true)
+        .open(to)?;
+    let writer_metadata = writer.metadata()?;
+    // fchmod is broken on vita
+    #[cfg(not(target_os = "vita"))]
+    if writer_metadata.is_file() {
+        // Set the correct file permissions, in case the file already existed.
+        // Don't set the permissions on already existing non-files like
+        // pipes/FIFOs or device nodes.
+        writer.set_permissions(perm)?;
+    }
+    Ok((writer, writer_metadata))
+}
+
+#[cfg(not(any(
+    target_os = "linux",
+    target_os = "android",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "tvos",
+    target_os = "watchos",
+)))]
+pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
+    let (mut reader, reader_metadata) = open_from(from)?;
+    let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?;
+
+    io::copy(&mut reader, &mut writer)
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
+    let (mut reader, reader_metadata) = open_from(from)?;
+    let max_len = u64::MAX;
+    let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?;
+
+    use super::kernel_copy::{copy_regular_files, CopyResult};
+
+    match copy_regular_files(reader.as_raw_fd(), writer.as_raw_fd(), max_len) {
+        CopyResult::Ended(bytes) => Ok(bytes),
+        CopyResult::Error(e, _) => Err(e),
+        CopyResult::Fallback(written) => match io::copy::generic_copy(&mut reader, &mut writer) {
+            Ok(bytes) => Ok(bytes + written),
+            Err(e) => Err(e),
+        },
+    }
+}
+
+#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))]
+pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
+    use crate::sync::atomic::{AtomicBool, Ordering};
+
+    const COPYFILE_ACL: u32 = 1 << 0;
+    const COPYFILE_STAT: u32 = 1 << 1;
+    const COPYFILE_XATTR: u32 = 1 << 2;
+    const COPYFILE_DATA: u32 = 1 << 3;
+
+    const COPYFILE_SECURITY: u32 = COPYFILE_STAT | COPYFILE_ACL;
+    const COPYFILE_METADATA: u32 = COPYFILE_SECURITY | COPYFILE_XATTR;
+    const COPYFILE_ALL: u32 = COPYFILE_METADATA | COPYFILE_DATA;
+
+    const COPYFILE_STATE_COPIED: u32 = 8;
+
+    #[allow(non_camel_case_types)]
+    type copyfile_state_t = *mut libc::c_void;
+    #[allow(non_camel_case_types)]
+    type copyfile_flags_t = u32;
+
+    extern "C" {
+        fn fcopyfile(
+            from: libc::c_int,
+            to: libc::c_int,
+            state: copyfile_state_t,
+            flags: copyfile_flags_t,
+        ) -> libc::c_int;
+        fn copyfile_state_alloc() -> copyfile_state_t;
+        fn copyfile_state_free(state: copyfile_state_t) -> libc::c_int;
+        fn copyfile_state_get(
+            state: copyfile_state_t,
+            flag: u32,
+            dst: *mut libc::c_void,
+        ) -> libc::c_int;
+    }
+
+    struct FreeOnDrop(copyfile_state_t);
+    impl Drop for FreeOnDrop {
+        fn drop(&mut self) {
+            // The code below ensures that `FreeOnDrop` is never a null pointer
+            unsafe {
+                // `copyfile_state_free` returns -1 if the `to` or `from` files
+                // cannot be closed. However, this is not considered this an
+                // error.
+                copyfile_state_free(self.0);
+            }
+        }
+    }
+
+    // MacOS prior to 10.12 don't support `fclonefileat`
+    // We store the availability in a global to avoid unnecessary syscalls
+    static HAS_FCLONEFILEAT: AtomicBool = AtomicBool::new(true);
+    syscall! {
+        fn fclonefileat(
+            srcfd: libc::c_int,
+            dst_dirfd: libc::c_int,
+            dst: *const c_char,
+            flags: libc::c_int
+        ) -> libc::c_int
+    }
+
+    let (reader, reader_metadata) = open_from(from)?;
+
+    // Opportunistically attempt to create a copy-on-write clone of `from`
+    // using `fclonefileat`.
+    if HAS_FCLONEFILEAT.load(Ordering::Relaxed) {
+        let clonefile_result = run_path_with_cstr(to, |to| {
+            cvt(unsafe { fclonefileat(reader.as_raw_fd(), libc::AT_FDCWD, to.as_ptr(), 0) })
+        });
+        match clonefile_result {
+            Ok(_) => return Ok(reader_metadata.len()),
+            Err(err) => match err.raw_os_error() {
+                // `fclonefileat` will fail on non-APFS volumes, if the
+                // destination already exists, or if the source and destination
+                // are on different devices. In all these cases `fcopyfile`
+                // should succeed.
+                Some(libc::ENOTSUP) | Some(libc::EEXIST) | Some(libc::EXDEV) => (),
+                Some(libc::ENOSYS) => HAS_FCLONEFILEAT.store(false, Ordering::Relaxed),
+                _ => return Err(err),
+            },
+        }
+    }
+
+    // Fall back to using `fcopyfile` if `fclonefileat` does not succeed.
+    let (writer, writer_metadata) = open_to_and_set_permissions(to, reader_metadata)?;
+
+    // We ensure that `FreeOnDrop` never contains a null pointer so it is
+    // always safe to call `copyfile_state_free`
+    let state = unsafe {
+        let state = copyfile_state_alloc();
+        if state.is_null() {
+            return Err(crate::io::Error::last_os_error());
+        }
+        FreeOnDrop(state)
+    };
+
+    let flags = if writer_metadata.is_file() { COPYFILE_ALL } else { COPYFILE_DATA };
+
+    cvt(unsafe { fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), state.0, flags) })?;
+
+    let mut bytes_copied: libc::off_t = 0;
+    cvt(unsafe {
+        copyfile_state_get(
+            state.0,
+            COPYFILE_STATE_COPIED,
+            &mut bytes_copied as *mut libc::off_t as *mut libc::c_void,
+        )
+    })?;
+    Ok(bytes_copied as u64)
+}
+
+pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
+    run_path_with_cstr(path, |path| {
+        cvt(unsafe { libc::chown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })
+            .map(|_| ())
+    })
+}
+
+pub fn fchown(fd: c_int, uid: u32, gid: u32) -> io::Result<()> {
+    cvt(unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) })?;
+    Ok(())
+}
+
+pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
+    run_path_with_cstr(path, |path| {
+        cvt(unsafe { libc::lchown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })
+            .map(|_| ())
+    })
+}
+
+#[cfg(not(any(target_os = "fuchsia", target_os = "vxworks")))]
+pub fn chroot(dir: &Path) -> io::Result<()> {
+    run_path_with_cstr(dir, |dir| cvt(unsafe { libc::chroot(dir.as_ptr()) }).map(|_| ()))
+}
+
+pub use remove_dir_impl::remove_dir_all;
+
+// Fallback for REDOX, ESP-ID, Horizon, Vita and Miri
+#[cfg(any(
+    target_os = "redox",
+    target_os = "espidf",
+    target_os = "horizon",
+    target_os = "vita",
+    target_os = "nto",
+    miri
+))]
+mod remove_dir_impl {
+    pub use crate::sys_common::fs::remove_dir_all;
+}
+
+// Modern implementation using openat(), unlinkat() and fdopendir()
+#[cfg(not(any(
+    target_os = "redox",
+    target_os = "espidf",
+    target_os = "horizon",
+    target_os = "vita",
+    target_os = "nto",
+    miri
+)))]
+mod remove_dir_impl {
+    use super::{lstat, Dir, DirEntry, InnerReadDir, ReadDir};
+    use crate::ffi::CStr;
+    use crate::io;
+    use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
+    use crate::os::unix::prelude::{OwnedFd, RawFd};
+    use crate::path::{Path, PathBuf};
+    use crate::sys::common::small_c_string::run_path_with_cstr;
+    use crate::sys::{cvt, cvt_r};
+
+    #[cfg(not(any(
+        all(target_os = "linux", target_env = "gnu"),
+        all(target_os = "macos", not(target_arch = "aarch64"))
+    )))]
+    use libc::{fdopendir, openat, unlinkat};
+    #[cfg(all(target_os = "linux", target_env = "gnu"))]
+    use libc::{fdopendir, openat64 as openat, unlinkat};
+    #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
+    use macos_weak::{fdopendir, openat, unlinkat};
+
+    #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
+    mod macos_weak {
+        use crate::sys::weak::weak;
+        use libc::{c_char, c_int, DIR};
+
+        fn get_openat_fn() -> Option<unsafe extern "C" fn(c_int, *const c_char, c_int) -> c_int> {
+            weak!(fn openat(c_int, *const c_char, c_int) -> c_int);
+            openat.get()
+        }
+
+        pub fn has_openat() -> bool {
+            get_openat_fn().is_some()
+        }
+
+        pub unsafe fn openat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int {
+            get_openat_fn().map(|openat| openat(dirfd, pathname, flags)).unwrap_or_else(|| {
+                crate::sys::unix::os::set_errno(libc::ENOSYS);
+                -1
+            })
+        }
+
+        pub unsafe fn fdopendir(fd: c_int) -> *mut DIR {
+            #[cfg(all(target_os = "macos", target_arch = "x86"))]
+            weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64$UNIX2003");
+            #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
+            weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64");
+            fdopendir.get().map(|fdopendir| fdopendir(fd)).unwrap_or_else(|| {
+                crate::sys::unix::os::set_errno(libc::ENOSYS);
+                crate::ptr::null_mut()
+            })
+        }
+
+        pub unsafe fn unlinkat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int {
+            weak!(fn unlinkat(c_int, *const c_char, c_int) -> c_int);
+            unlinkat.get().map(|unlinkat| unlinkat(dirfd, pathname, flags)).unwrap_or_else(|| {
+                crate::sys::unix::os::set_errno(libc::ENOSYS);
+                -1
+            })
+        }
+    }
+
+    pub fn openat_nofollow_dironly(parent_fd: Option<RawFd>, p: &CStr) -> io::Result<OwnedFd> {
+        let fd = cvt_r(|| unsafe {
+            openat(
+                parent_fd.unwrap_or(libc::AT_FDCWD),
+                p.as_ptr(),
+                libc::O_CLOEXEC | libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_DIRECTORY,
+            )
+        })?;
+        Ok(unsafe { OwnedFd::from_raw_fd(fd) })
+    }
+
+    fn fdreaddir(dir_fd: OwnedFd) -> io::Result<(ReadDir, RawFd)> {
+        let ptr = unsafe { fdopendir(dir_fd.as_raw_fd()) };
+        if ptr.is_null() {
+            return Err(io::Error::last_os_error());
+        }
+        let dirp = Dir(ptr);
+        // file descriptor is automatically closed by libc::closedir() now, so give up ownership
+        let new_parent_fd = dir_fd.into_raw_fd();
+        // a valid root is not needed because we do not call any functions involving the full path
+        // of the `DirEntry`s.
+        let dummy_root = PathBuf::new();
+        let inner = InnerReadDir { dirp, root: dummy_root };
+        Ok((ReadDir::new(inner), new_parent_fd))
+    }
+
+    #[cfg(any(
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "haiku",
+        target_os = "vxworks",
+        target_os = "aix",
+    ))]
+    fn is_dir(_ent: &DirEntry) -> Option<bool> {
+        None
+    }
+
+    #[cfg(not(any(
+        target_os = "solaris",
+        target_os = "illumos",
+        target_os = "haiku",
+        target_os = "vxworks",
+        target_os = "aix",
+    )))]
+    fn is_dir(ent: &DirEntry) -> Option<bool> {
+        match ent.entry.d_type {
+            libc::DT_UNKNOWN => None,
+            libc::DT_DIR => Some(true),
+            _ => Some(false),
+        }
+    }
+
+    fn remove_dir_all_recursive(parent_fd: Option<RawFd>, path: &CStr) -> io::Result<()> {
+        // try opening as directory
+        let fd = match openat_nofollow_dironly(parent_fd, &path) {
+            Err(err) if matches!(err.raw_os_error(), Some(libc::ENOTDIR | libc::ELOOP)) => {
+                // not a directory - don't traverse further
+                // (for symlinks, older Linux kernels may return ELOOP instead of ENOTDIR)
+                return match parent_fd {
+                    // unlink...
+                    Some(parent_fd) => {
+                        cvt(unsafe { unlinkat(parent_fd, path.as_ptr(), 0) }).map(drop)
+                    }
+                    // ...unless this was supposed to be the deletion root directory
+                    None => Err(err),
+                };
+            }
+            result => result?,
+        };
+
+        // open the directory passing ownership of the fd
+        let (dir, fd) = fdreaddir(fd)?;
+        for child in dir {
+            let child = child?;
+            let child_name = child.name_cstr();
+            match is_dir(&child) {
+                Some(true) => {
+                    remove_dir_all_recursive(Some(fd), child_name)?;
+                }
+                Some(false) => {
+                    cvt(unsafe { unlinkat(fd, child_name.as_ptr(), 0) })?;
+                }
+                None => {
+                    // POSIX specifies that calling unlink()/unlinkat(..., 0) on a directory can succeed
+                    // if the process has the appropriate privileges. This however can causing orphaned
+                    // directories requiring an fsck e.g. on Solaris and Illumos. So we try recursing
+                    // into it first instead of trying to unlink() it.
+                    remove_dir_all_recursive(Some(fd), child_name)?;
+                }
+            }
+        }
+
+        // unlink the directory after removing its contents
+        cvt(unsafe {
+            unlinkat(parent_fd.unwrap_or(libc::AT_FDCWD), path.as_ptr(), libc::AT_REMOVEDIR)
+        })?;
+        Ok(())
+    }
+
+    fn remove_dir_all_modern(p: &Path) -> io::Result<()> {
+        // We cannot just call remove_dir_all_recursive() here because that would not delete a passed
+        // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
+        // into symlinks.
+        let attr = lstat(p)?;
+        if attr.file_type().is_symlink() {
+            crate::fs::remove_file(p)
+        } else {
+            run_path_with_cstr(p, |p| remove_dir_all_recursive(None, &p))
+        }
+    }
+
+    #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64"))))]
+    pub fn remove_dir_all(p: &Path) -> io::Result<()> {
+        remove_dir_all_modern(p)
+    }
+
+    #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
+    pub fn remove_dir_all(p: &Path) -> io::Result<()> {
+        if macos_weak::has_openat() {
+            // openat() is available with macOS 10.10+, just like unlinkat() and fdopendir()
+            remove_dir_all_modern(p)
+        } else {
+            // fall back to classic implementation
+            crate::sys_common::fs::remove_dir_all(p)
+        }
+    }
+}
diff --git a/library/std/src/sys/pal/unix/futex.rs b/library/std/src/sys/pal/unix/futex.rs
new file mode 100644
index 00000000000..d310be6c7a1
--- /dev/null
+++ b/library/std/src/sys/pal/unix/futex.rs
@@ -0,0 +1,301 @@
+#![cfg(any(
+    target_os = "linux",
+    target_os = "android",
+    all(target_os = "emscripten", target_feature = "atomics"),
+    target_os = "freebsd",
+    target_os = "openbsd",
+    target_os = "dragonfly",
+    target_os = "fuchsia",
+))]
+
+use crate::sync::atomic::AtomicU32;
+use crate::time::Duration;
+
+/// Wait for a futex_wake operation to wake us.
+///
+/// Returns directly if the futex doesn't hold the expected value.
+///
+/// Returns false on timeout, and true in all other cases.
+#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
+pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
+    use super::time::Timespec;
+    use crate::ptr::null;
+    use crate::sync::atomic::Ordering::Relaxed;
+
+    // Calculate the timeout as an absolute timespec.
+    //
+    // Overflows are rounded up to an infinite timeout (None).
+    let timespec = timeout
+        .and_then(|d| Timespec::now(libc::CLOCK_MONOTONIC).checked_add_duration(&d))
+        .and_then(|t| t.to_timespec());
+
+    loop {
+        // No need to wait if the value already changed.
+        if futex.load(Relaxed) != expected {
+            return true;
+        }
+
+        let r = unsafe {
+            cfg_if::cfg_if! {
+                if #[cfg(target_os = "freebsd")] {
+                    // FreeBSD doesn't have futex(), but it has
+                    // _umtx_op(UMTX_OP_WAIT_UINT_PRIVATE), which is nearly
+                    // identical. It supports absolute timeouts through a flag
+                    // in the _umtx_time struct.
+                    let umtx_timeout = timespec.map(|t| libc::_umtx_time {
+                        _timeout: t,
+                        _flags: libc::UMTX_ABSTIME,
+                        _clockid: libc::CLOCK_MONOTONIC as u32,
+                    });
+                    let umtx_timeout_ptr = umtx_timeout.as_ref().map_or(null(), |t| t as *const _);
+                    let umtx_timeout_size = umtx_timeout.as_ref().map_or(0, |t| crate::mem::size_of_val(t));
+                    libc::_umtx_op(
+                        futex as *const AtomicU32 as *mut _,
+                        libc::UMTX_OP_WAIT_UINT_PRIVATE,
+                        expected as libc::c_ulong,
+                        crate::ptr::invalid_mut(umtx_timeout_size),
+                        umtx_timeout_ptr as *mut _,
+                    )
+                } else if #[cfg(any(target_os = "linux", target_os = "android"))] {
+                    // Use FUTEX_WAIT_BITSET rather than FUTEX_WAIT to be able to give an
+                    // absolute time rather than a relative time.
+                    libc::syscall(
+                        libc::SYS_futex,
+                        futex as *const AtomicU32,
+                        libc::FUTEX_WAIT_BITSET | libc::FUTEX_PRIVATE_FLAG,
+                        expected,
+                        timespec.as_ref().map_or(null(), |t| t as *const libc::timespec),
+                        null::<u32>(), // This argument is unused for FUTEX_WAIT_BITSET.
+                        !0u32,         // A full bitmask, to make it behave like a regular FUTEX_WAIT.
+                    )
+                } else {
+                    compile_error!("unknown target_os");
+                }
+            }
+        };
+
+        match (r < 0).then(super::os::errno) {
+            Some(libc::ETIMEDOUT) => return false,
+            Some(libc::EINTR) => continue,
+            _ => return true,
+        }
+    }
+}
+
+/// Wake up one thread that's blocked on futex_wait on this futex.
+///
+/// Returns true if this actually woke up such a thread,
+/// or false if no thread was waiting on this futex.
+///
+/// On some platforms, this always returns false.
+#[cfg(any(target_os = "linux", target_os = "android"))]
+pub fn futex_wake(futex: &AtomicU32) -> bool {
+    let ptr = futex as *const AtomicU32;
+    let op = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG;
+    unsafe { libc::syscall(libc::SYS_futex, ptr, op, 1) > 0 }
+}
+
+/// Wake up all threads that are waiting on futex_wait on this futex.
+#[cfg(any(target_os = "linux", target_os = "android"))]
+pub fn futex_wake_all(futex: &AtomicU32) {
+    let ptr = futex as *const AtomicU32;
+    let op = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG;
+    unsafe {
+        libc::syscall(libc::SYS_futex, ptr, op, i32::MAX);
+    }
+}
+
+// FreeBSD doesn't tell us how many threads are woken up, so this always returns false.
+#[cfg(target_os = "freebsd")]
+pub fn futex_wake(futex: &AtomicU32) -> bool {
+    use crate::ptr::null_mut;
+    unsafe {
+        libc::_umtx_op(
+            futex as *const AtomicU32 as *mut _,
+            libc::UMTX_OP_WAKE_PRIVATE,
+            1,
+            null_mut(),
+            null_mut(),
+        )
+    };
+    false
+}
+
+#[cfg(target_os = "freebsd")]
+pub fn futex_wake_all(futex: &AtomicU32) {
+    use crate::ptr::null_mut;
+    unsafe {
+        libc::_umtx_op(
+            futex as *const AtomicU32 as *mut _,
+            libc::UMTX_OP_WAKE_PRIVATE,
+            i32::MAX as libc::c_ulong,
+            null_mut(),
+            null_mut(),
+        )
+    };
+}
+
+#[cfg(target_os = "openbsd")]
+pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
+    use super::time::Timespec;
+    use crate::ptr::{null, null_mut};
+
+    // Overflows are rounded up to an infinite timeout (None).
+    let timespec = timeout
+        .and_then(|d| Timespec::zero().checked_add_duration(&d))
+        .and_then(|t| t.to_timespec());
+
+    let r = unsafe {
+        libc::futex(
+            futex as *const AtomicU32 as *mut u32,
+            libc::FUTEX_WAIT,
+            expected as i32,
+            timespec.as_ref().map_or(null(), |t| t as *const libc::timespec),
+            null_mut(),
+        )
+    };
+
+    r == 0 || super::os::errno() != libc::ETIMEDOUT
+}
+
+#[cfg(target_os = "openbsd")]
+pub fn futex_wake(futex: &AtomicU32) -> bool {
+    use crate::ptr::{null, null_mut};
+    unsafe {
+        libc::futex(futex as *const AtomicU32 as *mut u32, libc::FUTEX_WAKE, 1, null(), null_mut())
+            > 0
+    }
+}
+
+#[cfg(target_os = "openbsd")]
+pub fn futex_wake_all(futex: &AtomicU32) {
+    use crate::ptr::{null, null_mut};
+    unsafe {
+        libc::futex(
+            futex as *const AtomicU32 as *mut u32,
+            libc::FUTEX_WAKE,
+            i32::MAX,
+            null(),
+            null_mut(),
+        );
+    }
+}
+
+#[cfg(target_os = "dragonfly")]
+pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
+    // A timeout of 0 means infinite.
+    // We round smaller timeouts up to 1 millisecond.
+    // Overflows are rounded up to an infinite timeout.
+    let timeout_ms =
+        timeout.and_then(|d| Some(i32::try_from(d.as_millis()).ok()?.max(1))).unwrap_or(0);
+
+    let r = unsafe {
+        libc::umtx_sleep(futex as *const AtomicU32 as *const i32, expected as i32, timeout_ms)
+    };
+
+    r == 0 || super::os::errno() != libc::ETIMEDOUT
+}
+
+// DragonflyBSD doesn't tell us how many threads are woken up, so this always returns false.
+#[cfg(target_os = "dragonfly")]
+pub fn futex_wake(futex: &AtomicU32) -> bool {
+    unsafe { libc::umtx_wakeup(futex as *const AtomicU32 as *const i32, 1) };
+    false
+}
+
+#[cfg(target_os = "dragonfly")]
+pub fn futex_wake_all(futex: &AtomicU32) {
+    unsafe { libc::umtx_wakeup(futex as *const AtomicU32 as *const i32, i32::MAX) };
+}
+
+#[cfg(target_os = "emscripten")]
+extern "C" {
+    fn emscripten_futex_wake(addr: *const AtomicU32, count: libc::c_int) -> libc::c_int;
+    fn emscripten_futex_wait(
+        addr: *const AtomicU32,
+        val: libc::c_uint,
+        max_wait_ms: libc::c_double,
+    ) -> libc::c_int;
+}
+
+#[cfg(target_os = "emscripten")]
+pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
+    unsafe {
+        emscripten_futex_wait(
+            futex,
+            expected,
+            timeout.map_or(f64::INFINITY, |d| d.as_secs_f64() * 1000.0),
+        ) != -libc::ETIMEDOUT
+    }
+}
+
+#[cfg(target_os = "emscripten")]
+pub fn futex_wake(futex: &AtomicU32) -> bool {
+    unsafe { emscripten_futex_wake(futex, 1) > 0 }
+}
+
+#[cfg(target_os = "emscripten")]
+pub fn futex_wake_all(futex: &AtomicU32) {
+    unsafe { emscripten_futex_wake(futex, i32::MAX) };
+}
+
+#[cfg(target_os = "fuchsia")]
+pub mod zircon {
+    pub type zx_futex_t = crate::sync::atomic::AtomicU32;
+    pub type zx_handle_t = u32;
+    pub type zx_status_t = i32;
+    pub type zx_time_t = i64;
+
+    pub const ZX_HANDLE_INVALID: zx_handle_t = 0;
+
+    pub const ZX_TIME_INFINITE: zx_time_t = zx_time_t::MAX;
+
+    pub const ZX_OK: zx_status_t = 0;
+    pub const ZX_ERR_INVALID_ARGS: zx_status_t = -10;
+    pub const ZX_ERR_BAD_HANDLE: zx_status_t = -11;
+    pub const ZX_ERR_WRONG_TYPE: zx_status_t = -12;
+    pub const ZX_ERR_BAD_STATE: zx_status_t = -20;
+    pub const ZX_ERR_TIMED_OUT: zx_status_t = -21;
+
+    extern "C" {
+        pub fn zx_clock_get_monotonic() -> zx_time_t;
+        pub fn zx_futex_wait(
+            value_ptr: *const zx_futex_t,
+            current_value: zx_futex_t,
+            new_futex_owner: zx_handle_t,
+            deadline: zx_time_t,
+        ) -> zx_status_t;
+        pub fn zx_futex_wake(value_ptr: *const zx_futex_t, wake_count: u32) -> zx_status_t;
+        pub fn zx_futex_wake_single_owner(value_ptr: *const zx_futex_t) -> zx_status_t;
+        pub fn zx_thread_self() -> zx_handle_t;
+    }
+}
+
+#[cfg(target_os = "fuchsia")]
+pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
+    // Sleep forever if the timeout is longer than fits in a i64.
+    let deadline = timeout
+        .and_then(|d| {
+            i64::try_from(d.as_nanos())
+                .ok()?
+                .checked_add(unsafe { zircon::zx_clock_get_monotonic() })
+        })
+        .unwrap_or(zircon::ZX_TIME_INFINITE);
+
+    unsafe {
+        zircon::zx_futex_wait(futex, AtomicU32::new(expected), zircon::ZX_HANDLE_INVALID, deadline)
+            != zircon::ZX_ERR_TIMED_OUT
+    }
+}
+
+// Fuchsia doesn't tell us how many threads are woken up, so this always returns false.
+#[cfg(target_os = "fuchsia")]
+pub fn futex_wake(futex: &AtomicU32) -> bool {
+    unsafe { zircon::zx_futex_wake(futex, 1) };
+    false
+}
+
+#[cfg(target_os = "fuchsia")]
+pub fn futex_wake_all(futex: &AtomicU32) {
+    unsafe { zircon::zx_futex_wake(futex, u32::MAX) };
+}
diff --git a/library/std/src/sys/pal/unix/io.rs b/library/std/src/sys/pal/unix/io.rs
new file mode 100644
index 00000000000..29c340dd349
--- /dev/null
+++ b/library/std/src/sys/pal/unix/io.rs
@@ -0,0 +1,82 @@
+use crate::marker::PhantomData;
+use crate::os::fd::{AsFd, AsRawFd};
+use crate::slice;
+
+use libc::{c_void, iovec};
+
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub struct IoSlice<'a> {
+    vec: iovec,
+    _p: PhantomData<&'a [u8]>,
+}
+
+impl<'a> IoSlice<'a> {
+    #[inline]
+    pub fn new(buf: &'a [u8]) -> IoSlice<'a> {
+        IoSlice {
+            vec: iovec { iov_base: buf.as_ptr() as *mut u8 as *mut c_void, iov_len: buf.len() },
+            _p: PhantomData,
+        }
+    }
+
+    #[inline]
+    pub fn advance(&mut self, n: usize) {
+        if self.vec.iov_len < n {
+            panic!("advancing IoSlice beyond its length");
+        }
+
+        unsafe {
+            self.vec.iov_len -= n;
+            self.vec.iov_base = self.vec.iov_base.add(n);
+        }
+    }
+
+    #[inline]
+    pub fn as_slice(&self) -> &[u8] {
+        unsafe { slice::from_raw_parts(self.vec.iov_base as *mut u8, self.vec.iov_len) }
+    }
+}
+
+#[repr(transparent)]
+pub struct IoSliceMut<'a> {
+    vec: iovec,
+    _p: PhantomData<&'a mut [u8]>,
+}
+
+impl<'a> IoSliceMut<'a> {
+    #[inline]
+    pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> {
+        IoSliceMut {
+            vec: iovec { iov_base: buf.as_mut_ptr() as *mut c_void, iov_len: buf.len() },
+            _p: PhantomData,
+        }
+    }
+
+    #[inline]
+    pub fn advance(&mut self, n: usize) {
+        if self.vec.iov_len < n {
+            panic!("advancing IoSliceMut beyond its length");
+        }
+
+        unsafe {
+            self.vec.iov_len -= n;
+            self.vec.iov_base = self.vec.iov_base.add(n);
+        }
+    }
+
+    #[inline]
+    pub fn as_slice(&self) -> &[u8] {
+        unsafe { slice::from_raw_parts(self.vec.iov_base as *mut u8, self.vec.iov_len) }
+    }
+
+    #[inline]
+    pub fn as_mut_slice(&mut self) -> &mut [u8] {
+        unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) }
+    }
+}
+
+pub fn is_terminal(fd: &impl AsFd) -> bool {
+    let fd = fd.as_fd();
+    unsafe { libc::isatty(fd.as_raw_fd()) != 0 }
+}
diff --git a/library/std/src/sys/pal/unix/kernel_copy.rs b/library/std/src/sys/pal/unix/kernel_copy.rs
new file mode 100644
index 00000000000..18acd5ecccd
--- /dev/null
+++ b/library/std/src/sys/pal/unix/kernel_copy.rs
@@ -0,0 +1,730 @@
+//! This module contains specializations that can offload `io::copy()` operations on file descriptor
+//! containing types (`File`, `TcpStream`, etc.) to more efficient syscalls than `read(2)` and `write(2)`.
+//!
+//! Specialization is only applied to wholly std-owned types so that user code can't observe
+//! that the `Read` and `Write` traits are not used.
+//!
+//! Since a copy operation involves a reader and writer side where each can consist of different types
+//! and also involve generic wrappers (e.g. `Take`, `BufReader`) it is not practical to specialize
+//! a single method on all possible combinations.
+//!
+//! Instead readers and writers are handled separately by the `CopyRead` and `CopyWrite` specialization
+//! traits and then specialized on by the `Copier::copy` method.
+//!
+//! `Copier` uses the specialization traits to unpack the underlying file descriptors and
+//! additional prerequisites and constraints imposed by the wrapper types.
+//!
+//! Once it has obtained all necessary pieces and brought any wrapper types into a state where they
+//! can be safely bypassed it will attempt to use the `copy_file_range(2)`,
+//! `sendfile(2)` or `splice(2)` syscalls to move data directly between file descriptors.
+//! Since those syscalls have requirements that cannot be fully checked in advance it attempts
+//! to use them one after another (guided by hints) to figure out which one works and
+//! falls back to the generic read-write copy loop if none of them does.
+//! Once a working syscall is found for a pair of file descriptors it will be called in a loop
+//! until the copy operation is completed.
+//!
+//! Advantages of using these syscalls:
+//!
+//! * fewer context switches since reads and writes are coalesced into a single syscall
+//!   and more bytes are transferred per syscall. This translates to higher throughput
+//!   and fewer CPU cycles, at least for sufficiently large transfers to amortize the initial probing.
+//! * `copy_file_range` creates reflink copies on CoW filesystems, thus moving less data and
+//!   consuming less disk space
+//! * `sendfile` and `splice` can perform zero-copy IO under some circumstances while
+//!   a naive copy loop would move every byte through the CPU.
+//!
+//! Drawbacks:
+//!
+//! * copy operations smaller than the default buffer size can under some circumstances, especially
+//!   on older kernels, incur more syscalls than the naive approach would. As mentioned above
+//!   the syscall selection is guided by hints to minimize this possibility but they are not perfect.
+//! * optimizations only apply to std types. If a user adds a custom wrapper type, e.g. to report
+//!   progress, they can hit a performance cliff.
+//! * complexity
+
+use crate::cmp::min;
+use crate::fs::{File, Metadata};
+use crate::io::copy::generic_copy;
+use crate::io::{
+    BufRead, BufReader, BufWriter, Error, Read, Result, StderrLock, StdinLock, StdoutLock, Take,
+    Write,
+};
+use crate::mem::ManuallyDrop;
+use crate::net::TcpStream;
+use crate::os::unix::fs::FileTypeExt;
+use crate::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use crate::os::unix::net::UnixStream;
+use crate::process::{ChildStderr, ChildStdin, ChildStdout};
+use crate::ptr;
+use crate::sync::atomic::{AtomicBool, AtomicU8, Ordering};
+use crate::sys::cvt;
+use crate::sys::weak::syscall;
+#[cfg(not(any(all(target_os = "linux", target_env = "gnu"), target_os = "hurd")))]
+use libc::sendfile as sendfile64;
+#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "hurd"))]
+use libc::sendfile64;
+use libc::{EBADF, EINVAL, ENOSYS, EOPNOTSUPP, EOVERFLOW, EPERM, EXDEV};
+
+#[cfg(test)]
+mod tests;
+
+pub(crate) fn copy_spec<R: Read + ?Sized, W: Write + ?Sized>(
+    read: &mut R,
+    write: &mut W,
+) -> Result<u64> {
+    let copier = Copier { read, write };
+    SpecCopy::copy(copier)
+}
+
+/// This type represents either the inferred `FileType` of a `RawFd` based on the source
+/// type from which it was extracted or the actual metadata
+///
+/// The methods on this type only provide hints, due to `AsRawFd` and `FromRawFd` the inferred
+/// type may be wrong.
+enum FdMeta {
+    Metadata(Metadata),
+    Socket,
+    Pipe,
+    /// We don't have any metadata because the stat syscall failed
+    NoneObtained,
+}
+
+#[derive(PartialEq)]
+enum FdHandle {
+    Input,
+    Output,
+}
+
+impl FdMeta {
+    fn maybe_fifo(&self) -> bool {
+        match self {
+            FdMeta::Metadata(meta) => meta.file_type().is_fifo(),
+            FdMeta::Socket => false,
+            FdMeta::Pipe => true,
+            FdMeta::NoneObtained => true,
+        }
+    }
+
+    fn potential_sendfile_source(&self) -> bool {
+        match self {
+            // procfs erroneously shows 0 length on non-empty readable files.
+            // and if a file is truly empty then a `read` syscall will determine that and skip the write syscall
+            // thus there would be benefit from attempting sendfile
+            FdMeta::Metadata(meta)
+                if meta.file_type().is_file() && meta.len() > 0
+                    || meta.file_type().is_block_device() =>
+            {
+                true
+            }
+            _ => false,
+        }
+    }
+
+    fn copy_file_range_candidate(&self, f: FdHandle) -> bool {
+        match self {
+            // copy_file_range will fail on empty procfs files. `read` can determine whether EOF has been reached
+            // without extra cost and skip the write, thus there is no benefit in attempting copy_file_range
+            FdMeta::Metadata(meta) if f == FdHandle::Input && meta.is_file() && meta.len() > 0 => {
+                true
+            }
+            FdMeta::Metadata(meta) if f == FdHandle::Output && meta.is_file() => true,
+            _ => false,
+        }
+    }
+}
+
+/// Returns true either if changes made to the source after a sendfile/splice call won't become
+/// visible in the sink or the source has explicitly opted into such behavior (e.g. by splicing
+/// a file into a pipe, the pipe being the source in this case).
+///
+/// This will prevent File -> Pipe and File -> Socket splicing/sendfile optimizations to uphold
+/// the Read/Write API semantics of io::copy.
+///
+/// Note: This is not 100% airtight, the caller can use the RawFd conversion methods to turn a
+/// regular file into a TcpSocket which will be treated as a socket here without checking.
+fn safe_kernel_copy(source: &FdMeta, sink: &FdMeta) -> bool {
+    match (source, sink) {
+        // Data arriving from a socket is safe because the sender can't modify the socket buffer.
+        // Data arriving from a pipe is safe(-ish) because either the sender *copied*
+        // the bytes into the pipe OR explicitly performed an operation that enables zero-copy,
+        // thus promising not to modify the data later.
+        (FdMeta::Socket, _) => true,
+        (FdMeta::Pipe, _) => true,
+        (FdMeta::Metadata(meta), _)
+            if meta.file_type().is_fifo() || meta.file_type().is_socket() =>
+        {
+            true
+        }
+        // Data going into non-pipes/non-sockets is safe because the "later changes may become visible" issue
+        // only happens for pages sitting in send buffers or pipes.
+        (_, FdMeta::Metadata(meta))
+            if !meta.file_type().is_fifo() && !meta.file_type().is_socket() =>
+        {
+            true
+        }
+        _ => false,
+    }
+}
+
+struct CopyParams(FdMeta, Option<RawFd>);
+
+struct Copier<'a, 'b, R: Read + ?Sized, W: Write + ?Sized> {
+    read: &'a mut R,
+    write: &'b mut W,
+}
+
+trait SpecCopy {
+    fn copy(self) -> Result<u64>;
+}
+
+impl<R: Read + ?Sized, W: Write + ?Sized> SpecCopy for Copier<'_, '_, R, W> {
+    default fn copy(self) -> Result<u64> {
+        generic_copy(self.read, self.write)
+    }
+}
+
+impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> {
+    fn copy(self) -> Result<u64> {
+        let (reader, writer) = (self.read, self.write);
+        let r_cfg = reader.properties();
+        let w_cfg = writer.properties();
+
+        // before direct operations on file descriptors ensure that all source and sink buffers are empty
+        let mut flush = || -> crate::io::Result<u64> {
+            let bytes = reader.drain_to(writer, u64::MAX)?;
+            // BufWriter buffered bytes have already been accounted for in earlier write() calls
+            writer.flush()?;
+            Ok(bytes)
+        };
+
+        let mut written = 0u64;
+
+        if let (CopyParams(input_meta, Some(readfd)), CopyParams(output_meta, Some(writefd))) =
+            (r_cfg, w_cfg)
+        {
+            written += flush()?;
+            let max_write = reader.min_limit();
+
+            if input_meta.copy_file_range_candidate(FdHandle::Input)
+                && output_meta.copy_file_range_candidate(FdHandle::Output)
+            {
+                let result = copy_regular_files(readfd, writefd, max_write);
+                result.update_take(reader);
+
+                match result {
+                    CopyResult::Ended(bytes_copied) => return Ok(bytes_copied + written),
+                    CopyResult::Error(e, _) => return Err(e),
+                    CopyResult::Fallback(bytes) => written += bytes,
+                }
+            }
+
+            // on modern kernels sendfile can copy from any mmapable type (some but not all regular files and block devices)
+            // to any writable file descriptor. On older kernels the writer side can only be a socket.
+            // So we just try and fallback if needed.
+            // If current file offsets + write sizes overflow it may also fail, we do not try to fix that and instead
+            // fall back to the generic copy loop.
+            if input_meta.potential_sendfile_source() && safe_kernel_copy(&input_meta, &output_meta)
+            {
+                let result = sendfile_splice(SpliceMode::Sendfile, readfd, writefd, max_write);
+                result.update_take(reader);
+
+                match result {
+                    CopyResult::Ended(bytes_copied) => return Ok(bytes_copied + written),
+                    CopyResult::Error(e, _) => return Err(e),
+                    CopyResult::Fallback(bytes) => written += bytes,
+                }
+            }
+
+            if (input_meta.maybe_fifo() || output_meta.maybe_fifo())
+                && safe_kernel_copy(&input_meta, &output_meta)
+            {
+                let result = sendfile_splice(SpliceMode::Splice, readfd, writefd, max_write);
+                result.update_take(reader);
+
+                match result {
+                    CopyResult::Ended(bytes_copied) => return Ok(bytes_copied + written),
+                    CopyResult::Error(e, _) => return Err(e),
+                    CopyResult::Fallback(0) => { /* use the fallback below */ }
+                    CopyResult::Fallback(_) => {
+                        unreachable!("splice should not return > 0 bytes on the fallback path")
+                    }
+                }
+            }
+        }
+
+        // fallback if none of the more specialized syscalls wants to work with these file descriptors
+        match generic_copy(reader, writer) {
+            Ok(bytes) => Ok(bytes + written),
+            err => err,
+        }
+    }
+}
+
+#[rustc_specialization_trait]
+trait CopyRead: Read {
+    /// Implementations that contain buffers (i.e. `BufReader`) must transfer data from their internal
+    /// buffers into `writer` until either the buffers are emptied or `limit` bytes have been
+    /// transferred, whichever occurs sooner.
+    /// If nested buffers are present the outer buffers must be drained first.
+    ///
+    /// This is necessary to directly bypass the wrapper types while preserving the data order
+    /// when operating directly on the underlying file descriptors.
+    fn drain_to<W: Write>(&mut self, _writer: &mut W, _limit: u64) -> Result<u64> {
+        Ok(0)
+    }
+
+    /// Updates `Take` wrappers to remove the number of bytes copied.
+    fn taken(&mut self, _bytes: u64) {}
+
+    /// The minimum of the limit of all `Take<_>` wrappers, `u64::MAX` otherwise.
+    /// This method does not account for data `BufReader` buffers and would underreport
+    /// the limit of a `Take<BufReader<Take<_>>>` type. Thus its result is only valid
+    /// after draining the buffers via `drain_to`.
+    fn min_limit(&self) -> u64 {
+        u64::MAX
+    }
+
+    /// Extracts the file descriptor and hints/metadata, delegating through wrappers if necessary.
+    fn properties(&self) -> CopyParams;
+}
+
+#[rustc_specialization_trait]
+trait CopyWrite: Write {
+    /// Extracts the file descriptor and hints/metadata, delegating through wrappers if necessary.
+    fn properties(&self) -> CopyParams;
+}
+
+impl<T> CopyRead for &mut T
+where
+    T: CopyRead,
+{
+    fn drain_to<W: Write>(&mut self, writer: &mut W, limit: u64) -> Result<u64> {
+        (**self).drain_to(writer, limit)
+    }
+
+    fn taken(&mut self, bytes: u64) {
+        (**self).taken(bytes);
+    }
+
+    fn min_limit(&self) -> u64 {
+        (**self).min_limit()
+    }
+
+    fn properties(&self) -> CopyParams {
+        (**self).properties()
+    }
+}
+
+impl<T> CopyWrite for &mut T
+where
+    T: CopyWrite,
+{
+    fn properties(&self) -> CopyParams {
+        (**self).properties()
+    }
+}
+
+impl CopyRead for File {
+    fn properties(&self) -> CopyParams {
+        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyRead for &File {
+    fn properties(&self) -> CopyParams {
+        CopyParams(fd_to_meta(*self), Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyWrite for File {
+    fn properties(&self) -> CopyParams {
+        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyWrite for &File {
+    fn properties(&self) -> CopyParams {
+        CopyParams(fd_to_meta(*self), Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyRead for TcpStream {
+    fn properties(&self) -> CopyParams {
+        // avoid the stat syscall since we can be fairly sure it's a socket
+        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyRead for &TcpStream {
+    fn properties(&self) -> CopyParams {
+        // avoid the stat syscall since we can be fairly sure it's a socket
+        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyWrite for TcpStream {
+    fn properties(&self) -> CopyParams {
+        // avoid the stat syscall since we can be fairly sure it's a socket
+        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyWrite for &TcpStream {
+    fn properties(&self) -> CopyParams {
+        // avoid the stat syscall since we can be fairly sure it's a socket
+        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyRead for UnixStream {
+    fn properties(&self) -> CopyParams {
+        // avoid the stat syscall since we can be fairly sure it's a socket
+        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyRead for &UnixStream {
+    fn properties(&self) -> CopyParams {
+        // avoid the stat syscall since we can be fairly sure it's a socket
+        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyWrite for UnixStream {
+    fn properties(&self) -> CopyParams {
+        // avoid the stat syscall since we can be fairly sure it's a socket
+        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyWrite for &UnixStream {
+    fn properties(&self) -> CopyParams {
+        // avoid the stat syscall since we can be fairly sure it's a socket
+        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyWrite for ChildStdin {
+    fn properties(&self) -> CopyParams {
+        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyRead for ChildStdout {
+    fn properties(&self) -> CopyParams {
+        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyRead for ChildStderr {
+    fn properties(&self) -> CopyParams {
+        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyRead for StdinLock<'_> {
+    fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
+        let buf_reader = self.as_mut_buf();
+        let buf = buf_reader.buffer();
+        let buf = &buf[0..min(buf.len(), outer_limit.try_into().unwrap_or(usize::MAX))];
+        let bytes_drained = buf.len();
+        writer.write_all(buf)?;
+        buf_reader.consume(bytes_drained);
+
+        Ok(bytes_drained as u64)
+    }
+
+    fn properties(&self) -> CopyParams {
+        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyWrite for StdoutLock<'_> {
+    fn properties(&self) -> CopyParams {
+        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
+    }
+}
+
+impl CopyWrite for StderrLock<'_> {
+    fn properties(&self) -> CopyParams {
+        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
+    }
+}
+
+impl<T: CopyRead> CopyRead for Take<T> {
+    fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
+        let local_limit = self.limit();
+        let combined_limit = min(outer_limit, local_limit);
+        let bytes_drained = self.get_mut().drain_to(writer, combined_limit)?;
+        // update limit since read() was bypassed
+        self.set_limit(local_limit - bytes_drained);
+
+        Ok(bytes_drained)
+    }
+
+    fn taken(&mut self, bytes: u64) {
+        self.set_limit(self.limit() - bytes);
+        self.get_mut().taken(bytes);
+    }
+
+    fn min_limit(&self) -> u64 {
+        min(Take::limit(self), self.get_ref().min_limit())
+    }
+
+    fn properties(&self) -> CopyParams {
+        self.get_ref().properties()
+    }
+}
+
+impl<T: ?Sized + CopyRead> CopyRead for BufReader<T> {
+    fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
+        let buf = self.buffer();
+        let buf = &buf[0..min(buf.len(), outer_limit.try_into().unwrap_or(usize::MAX))];
+        let bytes = buf.len();
+        writer.write_all(buf)?;
+        self.consume(bytes);
+
+        let remaining = outer_limit - bytes as u64;
+
+        // in case of nested bufreaders we also need to drain the ones closer to the source
+        let inner_bytes = self.get_mut().drain_to(writer, remaining)?;
+
+        Ok(bytes as u64 + inner_bytes)
+    }
+
+    fn taken(&mut self, bytes: u64) {
+        self.get_mut().taken(bytes);
+    }
+
+    fn min_limit(&self) -> u64 {
+        self.get_ref().min_limit()
+    }
+
+    fn properties(&self) -> CopyParams {
+        self.get_ref().properties()
+    }
+}
+
+impl<T: ?Sized + CopyWrite> CopyWrite for BufWriter<T> {
+    fn properties(&self) -> CopyParams {
+        self.get_ref().properties()
+    }
+}
+
+fn fd_to_meta<T: AsRawFd>(fd: &T) -> FdMeta {
+    let fd = fd.as_raw_fd();
+    let file: ManuallyDrop<File> = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
+    match file.metadata() {
+        Ok(meta) => FdMeta::Metadata(meta),
+        Err(_) => FdMeta::NoneObtained,
+    }
+}
+
+pub(super) enum CopyResult {
+    Ended(u64),
+    Error(Error, u64),
+    Fallback(u64),
+}
+
+impl CopyResult {
+    fn update_take(&self, reader: &mut impl CopyRead) {
+        match *self {
+            CopyResult::Fallback(bytes)
+            | CopyResult::Ended(bytes)
+            | CopyResult::Error(_, bytes) => reader.taken(bytes),
+        }
+    }
+}
+
+/// Invalid file descriptor.
+///
+/// Valid file descriptors are guaranteed to be positive numbers (see `open()` manpage)
+/// while negative values are used to indicate errors.
+/// Thus -1 will never be overlap with a valid open file.
+const INVALID_FD: RawFd = -1;
+
+/// Linux-specific implementation that will attempt to use copy_file_range for copy offloading.
+/// As the name says, it only works on regular files.
+///
+/// Callers must handle fallback to a generic copy loop.
+/// `Fallback` may indicate non-zero number of bytes already written
+/// if one of the files' cursor +`max_len` would exceed u64::MAX (`EOVERFLOW`).
+pub(super) fn copy_regular_files(reader: RawFd, writer: RawFd, max_len: u64) -> CopyResult {
+    use crate::cmp;
+
+    const NOT_PROBED: u8 = 0;
+    const UNAVAILABLE: u8 = 1;
+    const AVAILABLE: u8 = 2;
+
+    // Kernel prior to 4.5 don't have copy_file_range
+    // We store the availability in a global to avoid unnecessary syscalls
+    static HAS_COPY_FILE_RANGE: AtomicU8 = AtomicU8::new(NOT_PROBED);
+
+    syscall! {
+        fn copy_file_range(
+            fd_in: libc::c_int,
+            off_in: *mut libc::loff_t,
+            fd_out: libc::c_int,
+            off_out: *mut libc::loff_t,
+            len: libc::size_t,
+            flags: libc::c_uint
+        ) -> libc::ssize_t
+    }
+
+    match HAS_COPY_FILE_RANGE.load(Ordering::Relaxed) {
+        NOT_PROBED => {
+            // EPERM can indicate seccomp filters or an immutable file.
+            // To distinguish these cases we probe with invalid file descriptors which should result in EBADF if the syscall is supported
+            // and some other error (ENOSYS or EPERM) if it's not available
+            let result = unsafe {
+                cvt(copy_file_range(INVALID_FD, ptr::null_mut(), INVALID_FD, ptr::null_mut(), 1, 0))
+            };
+
+            if matches!(result.map_err(|e| e.raw_os_error()), Err(Some(EBADF))) {
+                HAS_COPY_FILE_RANGE.store(AVAILABLE, Ordering::Relaxed);
+            } else {
+                HAS_COPY_FILE_RANGE.store(UNAVAILABLE, Ordering::Relaxed);
+                return CopyResult::Fallback(0);
+            }
+        }
+        UNAVAILABLE => return CopyResult::Fallback(0),
+        _ => {}
+    };
+
+    let mut written = 0u64;
+    while written < max_len {
+        let bytes_to_copy = cmp::min(max_len - written, usize::MAX as u64);
+        // cap to 1GB chunks in case u64::MAX is passed as max_len and the file has a non-zero seek position
+        // this allows us to copy large chunks without hitting EOVERFLOW,
+        // unless someone sets a file offset close to u64::MAX - 1GB, in which case a fallback would be required
+        let bytes_to_copy = cmp::min(bytes_to_copy as usize, 0x4000_0000usize);
+        let copy_result = unsafe {
+            // We actually don't have to adjust the offsets,
+            // because copy_file_range adjusts the file offset automatically
+            cvt(copy_file_range(reader, ptr::null_mut(), writer, ptr::null_mut(), bytes_to_copy, 0))
+        };
+
+        match copy_result {
+            Ok(0) if written == 0 => {
+                // fallback to work around several kernel bugs where copy_file_range will fail to
+                // copy any bytes and return 0 instead of an error if
+                // - reading virtual files from the proc filesystem which appear to have 0 size
+                //   but are not empty. noted in coreutils to affect kernels at least up to 5.6.19.
+                // - copying from an overlay filesystem in docker. reported to occur on fedora 32.
+                return CopyResult::Fallback(0);
+            }
+            Ok(0) => return CopyResult::Ended(written), // reached EOF
+            Ok(ret) => written += ret as u64,
+            Err(err) => {
+                return match err.raw_os_error() {
+                    // when file offset + max_length > u64::MAX
+                    Some(EOVERFLOW) => CopyResult::Fallback(written),
+                    Some(ENOSYS | EXDEV | EINVAL | EPERM | EOPNOTSUPP | EBADF) if written == 0 => {
+                        // Try fallback io::copy if either:
+                        // - Kernel version is < 4.5 (ENOSYS¹)
+                        // - Files are mounted on different fs (EXDEV)
+                        // - copy_file_range is broken in various ways on RHEL/CentOS 7 (EOPNOTSUPP)
+                        // - copy_file_range file is immutable or syscall is blocked by seccomp¹ (EPERM)
+                        // - copy_file_range cannot be used with pipes or device nodes (EINVAL)
+                        // - the writer fd was opened with O_APPEND (EBADF²)
+                        // and no bytes were written successfully yet. (All these errnos should
+                        // not be returned if something was already written, but they happen in
+                        // the wild, see #91152.)
+                        //
+                        // ¹ these cases should be detected by the initial probe but we handle them here
+                        //   anyway in case syscall interception changes during runtime
+                        // ² actually invalid file descriptors would cause this too, but in that case
+                        //   the fallback code path is expected to encounter the same error again
+                        CopyResult::Fallback(0)
+                    }
+                    _ => CopyResult::Error(err, written),
+                };
+            }
+        }
+    }
+    CopyResult::Ended(written)
+}
+
+#[derive(PartialEq)]
+enum SpliceMode {
+    Sendfile,
+    Splice,
+}
+
+/// performs splice or sendfile between file descriptors
+/// Does _not_ fall back to a generic copy loop.
+fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) -> CopyResult {
+    static HAS_SENDFILE: AtomicBool = AtomicBool::new(true);
+    static HAS_SPLICE: AtomicBool = AtomicBool::new(true);
+
+    // Android builds use feature level 14, but the libc wrapper for splice is
+    // gated on feature level 21+, so we have to invoke the syscall directly.
+    #[cfg(target_os = "android")]
+    syscall! {
+        fn splice(
+            srcfd: libc::c_int,
+            src_offset: *const i64,
+            dstfd: libc::c_int,
+            dst_offset: *const i64,
+            len: libc::size_t,
+            flags: libc::c_int
+        ) -> libc::ssize_t
+    }
+
+    #[cfg(target_os = "linux")]
+    use libc::splice;
+
+    match mode {
+        SpliceMode::Sendfile if !HAS_SENDFILE.load(Ordering::Relaxed) => {
+            return CopyResult::Fallback(0);
+        }
+        SpliceMode::Splice if !HAS_SPLICE.load(Ordering::Relaxed) => {
+            return CopyResult::Fallback(0);
+        }
+        _ => (),
+    }
+
+    let mut written = 0u64;
+    while written < len {
+        // according to its manpage that's the maximum size sendfile() will copy per invocation
+        let chunk_size = crate::cmp::min(len - written, 0x7ffff000_u64) as usize;
+
+        let result = match mode {
+            SpliceMode::Sendfile => {
+                cvt(unsafe { sendfile64(writer, reader, ptr::null_mut(), chunk_size) })
+            }
+            SpliceMode::Splice => cvt(unsafe {
+                splice(reader, ptr::null_mut(), writer, ptr::null_mut(), chunk_size, 0)
+            }),
+        };
+
+        match result {
+            Ok(0) => break, // EOF
+            Ok(ret) => written += ret as u64,
+            Err(err) => {
+                return match err.raw_os_error() {
+                    Some(ENOSYS | EPERM) => {
+                        // syscall not supported (ENOSYS)
+                        // syscall is disallowed, e.g. by seccomp (EPERM)
+                        match mode {
+                            SpliceMode::Sendfile => HAS_SENDFILE.store(false, Ordering::Relaxed),
+                            SpliceMode::Splice => HAS_SPLICE.store(false, Ordering::Relaxed),
+                        }
+                        assert_eq!(written, 0);
+                        CopyResult::Fallback(0)
+                    }
+                    Some(EINVAL) => {
+                        // splice/sendfile do not support this particular file descriptor (EINVAL)
+                        assert_eq!(written, 0);
+                        CopyResult::Fallback(0)
+                    }
+                    Some(os_err) if mode == SpliceMode::Sendfile && os_err == EOVERFLOW => {
+                        CopyResult::Fallback(written)
+                    }
+                    _ => CopyResult::Error(err, written),
+                };
+            }
+        }
+    }
+    CopyResult::Ended(written)
+}
diff --git a/library/std/src/sys/pal/unix/kernel_copy/tests.rs b/library/std/src/sys/pal/unix/kernel_copy/tests.rs
new file mode 100644
index 00000000000..a524270e3fb
--- /dev/null
+++ b/library/std/src/sys/pal/unix/kernel_copy/tests.rs
@@ -0,0 +1,312 @@
+use crate::fs::OpenOptions;
+use crate::io;
+use crate::io::Result;
+use crate::io::SeekFrom;
+use crate::io::{BufRead, Read, Seek, Write};
+use crate::os::unix::io::AsRawFd;
+use crate::sys_common::io::test::tmpdir;
+
+#[test]
+fn copy_specialization() -> Result<()> {
+    use crate::io::{BufReader, BufWriter};
+
+    let tmp_path = tmpdir();
+    let source_path = tmp_path.join("copy-spec.source");
+    let sink_path = tmp_path.join("copy-spec.sink");
+
+    let result: Result<()> = try {
+        let mut source = crate::fs::OpenOptions::new()
+            .read(true)
+            .write(true)
+            .create(true)
+            .truncate(true)
+            .open(&source_path)?;
+        source.write_all(b"abcdefghiklmnopqr")?;
+        source.seek(SeekFrom::Start(8))?;
+        let mut source = BufReader::with_capacity(8, source.take(5));
+        source.fill_buf()?;
+        assert_eq!(source.buffer(), b"iklmn");
+        source.get_mut().set_limit(6);
+        source.get_mut().get_mut().seek(SeekFrom::Start(1))?; // "bcdefg"
+        let mut source = source.take(10); // "iklmnbcdef"
+
+        let mut sink = crate::fs::OpenOptions::new()
+            .read(true)
+            .write(true)
+            .create(true)
+            .truncate(true)
+            .open(&sink_path)?;
+        sink.write_all(b"000000")?;
+        let mut sink = BufWriter::with_capacity(5, sink);
+        sink.write_all(b"wxyz")?;
+        assert_eq!(sink.buffer(), b"wxyz");
+
+        let copied = crate::io::copy(&mut source, &mut sink)?;
+        assert_eq!(copied, 10, "copy obeyed limit imposed by Take");
+        assert_eq!(sink.buffer().len(), 0, "sink buffer was flushed");
+        assert_eq!(source.limit(), 0, "outer Take was exhausted");
+        assert_eq!(source.get_ref().buffer().len(), 0, "source buffer should be drained");
+        assert_eq!(
+            source.get_ref().get_ref().limit(),
+            1,
+            "inner Take allowed reading beyond end of file, some bytes should be left"
+        );
+
+        let mut sink = sink.into_inner()?;
+        sink.seek(SeekFrom::Start(0))?;
+        let mut copied = Vec::new();
+        sink.read_to_end(&mut copied)?;
+        assert_eq!(&copied, b"000000wxyziklmnbcdef");
+    };
+
+    let rm1 = crate::fs::remove_file(source_path);
+    let rm2 = crate::fs::remove_file(sink_path);
+
+    result.and(rm1).and(rm2)
+}
+
+#[test]
+fn copies_append_mode_sink() -> Result<()> {
+    let tmp_path = tmpdir();
+    let source_path = tmp_path.join("copies_append_mode.source");
+    let sink_path = tmp_path.join("copies_append_mode.sink");
+    let mut source =
+        OpenOptions::new().create(true).truncate(true).write(true).read(true).open(&source_path)?;
+    write!(source, "not empty")?;
+    source.seek(SeekFrom::Start(0))?;
+    let mut sink = OpenOptions::new().create(true).append(true).open(&sink_path)?;
+
+    let copied = crate::io::copy(&mut source, &mut sink)?;
+
+    assert_eq!(copied, 9);
+
+    Ok(())
+}
+
+#[test]
+fn dont_splice_pipes_from_files() -> Result<()> {
+    // splicing to a pipe and then modifying the source could lead to changes
+    // becoming visible in an unexpected order.
+
+    use crate::io::SeekFrom;
+    use crate::os::unix::fs::FileExt;
+    use crate::process::{ChildStdin, ChildStdout};
+    use crate::sys_common::FromInner;
+
+    let (read_end, write_end) = crate::sys::pipe::anon_pipe()?;
+
+    let mut read_end = ChildStdout::from_inner(read_end);
+    let mut write_end = ChildStdin::from_inner(write_end);
+
+    let tmp_path = tmpdir();
+    let file = tmp_path.join("to_be_modified");
+    let mut file =
+        crate::fs::OpenOptions::new().create_new(true).read(true).write(true).open(file)?;
+
+    const SZ: usize = libc::PIPE_BUF as usize;
+
+    // put data in page cache
+    let mut buf: [u8; SZ] = [0x01; SZ];
+    file.write_all(&buf).unwrap();
+
+    // copy page into pipe
+    file.seek(SeekFrom::Start(0)).unwrap();
+    assert!(io::copy(&mut file, &mut write_end).unwrap() == SZ as u64);
+
+    // modify file
+    buf[0] = 0x02;
+    file.write_at(&buf, 0).unwrap();
+
+    // read from pipe
+    read_end.read_exact(buf.as_mut_slice()).unwrap();
+
+    assert_eq!(buf[0], 0x01, "data in pipe should reflect the original, not later modifications");
+
+    Ok(())
+}
+
+#[bench]
+fn bench_file_to_file_copy(b: &mut test::Bencher) {
+    const BYTES: usize = 128 * 1024;
+    let temp_path = tmpdir();
+    let src_path = temp_path.join("file-copy-bench-src");
+    let mut src = crate::fs::OpenOptions::new()
+        .create(true)
+        .truncate(true)
+        .read(true)
+        .write(true)
+        .open(src_path)
+        .unwrap();
+    src.write(&vec![0u8; BYTES]).unwrap();
+
+    let sink_path = temp_path.join("file-copy-bench-sink");
+    let mut sink = crate::fs::OpenOptions::new()
+        .create(true)
+        .truncate(true)
+        .write(true)
+        .open(sink_path)
+        .unwrap();
+
+    b.bytes = BYTES as u64;
+    b.iter(|| {
+        src.seek(SeekFrom::Start(0)).unwrap();
+        sink.seek(SeekFrom::Start(0)).unwrap();
+        assert_eq!(BYTES as u64, io::copy(&mut src, &mut sink).unwrap());
+    });
+}
+
+#[bench]
+fn bench_file_to_socket_copy(b: &mut test::Bencher) {
+    const BYTES: usize = 128 * 1024;
+    let temp_path = tmpdir();
+    let src_path = temp_path.join("pipe-copy-bench-src");
+    let mut src = OpenOptions::new()
+        .create(true)
+        .truncate(true)
+        .read(true)
+        .write(true)
+        .open(src_path)
+        .unwrap();
+    src.write(&vec![0u8; BYTES]).unwrap();
+
+    let sink_drainer = crate::net::TcpListener::bind("localhost:0").unwrap();
+    let mut sink = crate::net::TcpStream::connect(sink_drainer.local_addr().unwrap()).unwrap();
+    let mut sink_drainer = sink_drainer.accept().unwrap().0;
+
+    crate::thread::spawn(move || {
+        let mut sink_buf = vec![0u8; 1024 * 1024];
+        loop {
+            sink_drainer.read(&mut sink_buf[..]).unwrap();
+        }
+    });
+
+    b.bytes = BYTES as u64;
+    b.iter(|| {
+        src.seek(SeekFrom::Start(0)).unwrap();
+        assert_eq!(BYTES as u64, io::copy(&mut src, &mut sink).unwrap());
+    });
+}
+
+#[bench]
+fn bench_file_to_uds_copy(b: &mut test::Bencher) {
+    const BYTES: usize = 128 * 1024;
+    let temp_path = tmpdir();
+    let src_path = temp_path.join("uds-copy-bench-src");
+    let mut src = OpenOptions::new()
+        .create(true)
+        .truncate(true)
+        .read(true)
+        .write(true)
+        .open(src_path)
+        .unwrap();
+    src.write(&vec![0u8; BYTES]).unwrap();
+
+    let (mut sink, mut sink_drainer) = crate::os::unix::net::UnixStream::pair().unwrap();
+
+    crate::thread::spawn(move || {
+        let mut sink_buf = vec![0u8; 1024 * 1024];
+        loop {
+            sink_drainer.read(&mut sink_buf[..]).unwrap();
+        }
+    });
+
+    b.bytes = BYTES as u64;
+    b.iter(|| {
+        src.seek(SeekFrom::Start(0)).unwrap();
+        assert_eq!(BYTES as u64, io::copy(&mut src, &mut sink).unwrap());
+    });
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+#[bench]
+fn bench_socket_pipe_socket_copy(b: &mut test::Bencher) {
+    use super::CopyResult;
+    use crate::io::ErrorKind;
+    use crate::process::{ChildStdin, ChildStdout};
+    use crate::sys_common::FromInner;
+
+    let (read_end, write_end) = crate::sys::pipe::anon_pipe().unwrap();
+
+    let mut read_end = ChildStdout::from_inner(read_end);
+    let write_end = ChildStdin::from_inner(write_end);
+
+    let acceptor = crate::net::TcpListener::bind("localhost:0").unwrap();
+    let mut remote_end = crate::net::TcpStream::connect(acceptor.local_addr().unwrap()).unwrap();
+
+    let local_end = crate::sync::Arc::new(acceptor.accept().unwrap().0);
+
+    // the data flow in this benchmark:
+    //
+    //                      socket(tx)  local_source
+    // remote_end (write)  +-------->   (splice to)
+    //                                  write_end
+    //                                     +
+    //                                     |
+    //                                     | pipe
+    //                                     v
+    //                                  read_end
+    // remote_end (read)   <---------+  (splice to) *
+    //                      socket(rx)  local_end
+    //
+    // * benchmark loop using io::copy
+
+    crate::thread::spawn(move || {
+        let mut sink_buf = vec![0u8; 1024 * 1024];
+        remote_end.set_nonblocking(true).unwrap();
+        loop {
+            match remote_end.write(&mut sink_buf[..]) {
+                Err(err) if err.kind() == ErrorKind::WouldBlock => {}
+                Ok(_) => {}
+                err => {
+                    err.expect("write failed");
+                }
+            };
+            match remote_end.read(&mut sink_buf[..]) {
+                Err(err) if err.kind() == ErrorKind::WouldBlock => {}
+                Ok(_) => {}
+                err => {
+                    err.expect("read failed");
+                }
+            };
+        }
+    });
+
+    // check that splice works, otherwise the benchmark would hang
+    let probe = super::sendfile_splice(
+        super::SpliceMode::Splice,
+        local_end.as_raw_fd(),
+        write_end.as_raw_fd(),
+        1,
+    );
+
+    match probe {
+        CopyResult::Ended(1) => {
+            // splice works
+        }
+        _ => {
+            eprintln!("splice failed, skipping benchmark");
+            return;
+        }
+    }
+
+    let local_source = local_end.clone();
+    crate::thread::spawn(move || {
+        loop {
+            super::sendfile_splice(
+                super::SpliceMode::Splice,
+                local_source.as_raw_fd(),
+                write_end.as_raw_fd(),
+                u64::MAX,
+            );
+        }
+    });
+
+    const BYTES: usize = 128 * 1024;
+    b.bytes = BYTES as u64;
+    b.iter(|| {
+        assert_eq!(
+            BYTES as u64,
+            io::copy(&mut (&mut read_end).take(BYTES as u64), &mut &*local_end).unwrap()
+        );
+    });
+}
diff --git a/library/std/src/sys/pal/unix/l4re.rs b/library/std/src/sys/pal/unix/l4re.rs
new file mode 100644
index 00000000000..fe9559f2a56
--- /dev/null
+++ b/library/std/src/sys/pal/unix/l4re.rs
@@ -0,0 +1,560 @@
+macro_rules! unimpl {
+    () => {
+        return Err(io::const_io_error!(
+            io::ErrorKind::Unsupported,
+            "No networking available on L4Re.",
+        ));
+    };
+}
+
+pub mod net {
+    #![allow(warnings)]
+    use crate::fmt;
+    use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
+    use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr};
+    use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
+    use crate::sys::fd::FileDesc;
+    use crate::sys_common::{AsInner, FromInner, IntoInner};
+    use crate::time::Duration;
+
+    #[allow(unused_extern_crates)]
+    pub extern crate libc as netc;
+
+    pub struct Socket(FileDesc);
+    impl Socket {
+        pub fn new(_: &SocketAddr, _: libc::c_int) -> io::Result<Socket> {
+            unimpl!();
+        }
+
+        pub fn new_raw(_: libc::c_int, _: libc::c_int) -> io::Result<Socket> {
+            unimpl!();
+        }
+
+        pub fn new_pair(_: libc::c_int, _: libc::c_int) -> io::Result<(Socket, Socket)> {
+            unimpl!();
+        }
+
+        pub fn connect_timeout(&self, _: &SocketAddr, _: Duration) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn accept(
+            &self,
+            _: *mut libc::sockaddr,
+            _: *mut libc::socklen_t,
+        ) -> io::Result<Socket> {
+            unimpl!();
+        }
+
+        pub fn duplicate(&self) -> io::Result<Socket> {
+            unimpl!();
+        }
+
+        pub fn read(&self, _: &mut [u8]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn read_vectored(&self, _: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn is_read_vectored(&self) -> bool {
+            false
+        }
+
+        pub fn peek(&self, _: &mut [u8]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn recv_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
+            unimpl!();
+        }
+
+        pub fn peek_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
+            unimpl!();
+        }
+
+        pub fn write(&self, _: &[u8]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn write_vectored(&self, _: &[IoSlice<'_>]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn is_write_vectored(&self) -> bool {
+            false
+        }
+
+        pub fn set_timeout(&self, _: Option<Duration>, _: libc::c_int) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn timeout(&self, _: libc::c_int) -> io::Result<Option<Duration>> {
+            unimpl!();
+        }
+
+        pub fn shutdown(&self, _: Shutdown) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn set_linger(&self, _: Option<Duration>) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn linger(&self) -> io::Result<Option<Duration>> {
+            unimpl!();
+        }
+
+        pub fn set_nodelay(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn nodelay(&self) -> io::Result<bool> {
+            unimpl!();
+        }
+
+        pub fn set_nonblocking(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn take_error(&self) -> io::Result<Option<io::Error>> {
+            unimpl!();
+        }
+
+        // This is used by sys_common code to abstract over Windows and Unix.
+        pub fn as_raw(&self) -> RawFd {
+            self.as_raw_fd()
+        }
+    }
+
+    impl AsInner<FileDesc> for Socket {
+        #[inline]
+        fn as_inner(&self) -> &FileDesc {
+            &self.0
+        }
+    }
+
+    impl FromInner<FileDesc> for Socket {
+        fn from_inner(file_desc: FileDesc) -> Socket {
+            Socket(file_desc)
+        }
+    }
+
+    impl IntoInner<FileDesc> for Socket {
+        fn into_inner(self) -> FileDesc {
+            self.0
+        }
+    }
+
+    impl AsFd for Socket {
+        fn as_fd(&self) -> BorrowedFd<'_> {
+            self.0.as_fd()
+        }
+    }
+
+    impl AsRawFd for Socket {
+        #[inline]
+        fn as_raw_fd(&self) -> RawFd {
+            self.0.as_raw_fd()
+        }
+    }
+
+    impl IntoRawFd for Socket {
+        fn into_raw_fd(self) -> RawFd {
+            self.0.into_raw_fd()
+        }
+    }
+
+    impl FromRawFd for Socket {
+        unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+            Self(FromRawFd::from_raw_fd(raw_fd))
+        }
+    }
+
+    pub struct TcpStream {
+        inner: Socket,
+    }
+
+    impl TcpStream {
+        pub fn connect(_: io::Result<&SocketAddr>) -> io::Result<TcpStream> {
+            unimpl!();
+        }
+
+        pub fn connect_timeout(_: &SocketAddr, _: Duration) -> io::Result<TcpStream> {
+            unimpl!();
+        }
+
+        #[inline]
+        pub fn socket(&self) -> &Socket {
+            &self.inner
+        }
+
+        pub fn into_socket(self) -> Socket {
+            self.inner
+        }
+
+        pub fn set_read_timeout(&self, _: Option<Duration>) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn set_write_timeout(&self, _: Option<Duration>) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn read_timeout(&self) -> io::Result<Option<Duration>> {
+            unimpl!();
+        }
+
+        pub fn write_timeout(&self) -> io::Result<Option<Duration>> {
+            unimpl!();
+        }
+
+        pub fn peek(&self, _: &mut [u8]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn read(&self, _: &mut [u8]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn read_buf(&self, _: BorrowedCursor<'_>) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn read_vectored(&self, _: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn is_read_vectored(&self) -> bool {
+            false
+        }
+
+        pub fn write(&self, _: &[u8]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn write_vectored(&self, _: &[IoSlice<'_>]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn is_write_vectored(&self) -> bool {
+            false
+        }
+
+        pub fn peer_addr(&self) -> io::Result<SocketAddr> {
+            unimpl!();
+        }
+
+        pub fn socket_addr(&self) -> io::Result<SocketAddr> {
+            unimpl!();
+        }
+
+        pub fn shutdown(&self, _: Shutdown) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn duplicate(&self) -> io::Result<TcpStream> {
+            unimpl!();
+        }
+
+        pub fn set_linger(&self, _: Option<Duration>) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn linger(&self) -> io::Result<Option<Duration>> {
+            unimpl!();
+        }
+
+        pub fn set_nodelay(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn nodelay(&self) -> io::Result<bool> {
+            unimpl!();
+        }
+
+        pub fn set_ttl(&self, _: u32) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn ttl(&self) -> io::Result<u32> {
+            unimpl!();
+        }
+
+        pub fn take_error(&self) -> io::Result<Option<io::Error>> {
+            unimpl!();
+        }
+
+        pub fn set_nonblocking(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+    }
+
+    impl FromInner<Socket> for TcpStream {
+        fn from_inner(socket: Socket) -> TcpStream {
+            TcpStream { inner: socket }
+        }
+    }
+
+    impl fmt::Debug for TcpStream {
+        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+            write!(f, "No networking support available on L4Re")
+        }
+    }
+
+    pub struct TcpListener {
+        inner: Socket,
+    }
+
+    impl TcpListener {
+        pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<TcpListener> {
+            unimpl!();
+        }
+
+        #[inline]
+        pub fn socket(&self) -> &Socket {
+            &self.inner
+        }
+
+        pub fn into_socket(self) -> Socket {
+            self.inner
+        }
+
+        pub fn socket_addr(&self) -> io::Result<SocketAddr> {
+            unimpl!();
+        }
+
+        pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> {
+            unimpl!();
+        }
+
+        pub fn duplicate(&self) -> io::Result<TcpListener> {
+            unimpl!();
+        }
+
+        pub fn set_ttl(&self, _: u32) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn ttl(&self) -> io::Result<u32> {
+            unimpl!();
+        }
+
+        pub fn set_only_v6(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn only_v6(&self) -> io::Result<bool> {
+            unimpl!();
+        }
+
+        pub fn take_error(&self) -> io::Result<Option<io::Error>> {
+            unimpl!();
+        }
+
+        pub fn set_nonblocking(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+    }
+
+    impl FromInner<Socket> for TcpListener {
+        fn from_inner(socket: Socket) -> TcpListener {
+            TcpListener { inner: socket }
+        }
+    }
+
+    impl fmt::Debug for TcpListener {
+        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+            write!(f, "No networking support available on L4Re.")
+        }
+    }
+
+    pub struct UdpSocket {
+        inner: Socket,
+    }
+
+    impl UdpSocket {
+        pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<UdpSocket> {
+            unimpl!();
+        }
+
+        #[inline]
+        pub fn socket(&self) -> &Socket {
+            &self.inner
+        }
+
+        pub fn into_socket(self) -> Socket {
+            self.inner
+        }
+
+        pub fn peer_addr(&self) -> io::Result<SocketAddr> {
+            unimpl!();
+        }
+
+        pub fn socket_addr(&self) -> io::Result<SocketAddr> {
+            unimpl!();
+        }
+
+        pub fn recv_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
+            unimpl!();
+        }
+
+        pub fn peek_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
+            unimpl!();
+        }
+
+        pub fn send_to(&self, _: &[u8], _: &SocketAddr) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn duplicate(&self) -> io::Result<UdpSocket> {
+            unimpl!();
+        }
+
+        pub fn set_read_timeout(&self, _: Option<Duration>) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn set_write_timeout(&self, _: Option<Duration>) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn read_timeout(&self) -> io::Result<Option<Duration>> {
+            unimpl!();
+        }
+
+        pub fn write_timeout(&self) -> io::Result<Option<Duration>> {
+            unimpl!();
+        }
+
+        pub fn set_broadcast(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn broadcast(&self) -> io::Result<bool> {
+            unimpl!();
+        }
+
+        pub fn set_multicast_loop_v4(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn multicast_loop_v4(&self) -> io::Result<bool> {
+            unimpl!();
+        }
+
+        pub fn set_multicast_ttl_v4(&self, _: u32) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn multicast_ttl_v4(&self) -> io::Result<u32> {
+            unimpl!();
+        }
+
+        pub fn set_multicast_loop_v6(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn multicast_loop_v6(&self) -> io::Result<bool> {
+            unimpl!();
+        }
+
+        pub fn join_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn join_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn leave_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn leave_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn set_ttl(&self, _: u32) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn ttl(&self) -> io::Result<u32> {
+            unimpl!();
+        }
+
+        pub fn take_error(&self) -> io::Result<Option<io::Error>> {
+            unimpl!();
+        }
+
+        pub fn set_nonblocking(&self, _: bool) -> io::Result<()> {
+            unimpl!();
+        }
+
+        pub fn recv(&self, _: &mut [u8]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn peek(&self, _: &mut [u8]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn send(&self, _: &[u8]) -> io::Result<usize> {
+            unimpl!();
+        }
+
+        pub fn connect(&self, _: io::Result<&SocketAddr>) -> io::Result<()> {
+            unimpl!();
+        }
+    }
+
+    impl FromInner<Socket> for UdpSocket {
+        fn from_inner(socket: Socket) -> UdpSocket {
+            UdpSocket { inner: socket }
+        }
+    }
+
+    impl fmt::Debug for UdpSocket {
+        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+            write!(f, "No networking support on L4Re available.")
+        }
+    }
+
+    pub struct LookupHost {
+        original: *mut libc::addrinfo,
+        cur: *mut libc::addrinfo,
+    }
+
+    impl Iterator for LookupHost {
+        type Item = SocketAddr;
+        fn next(&mut self) -> Option<SocketAddr> {
+            None
+        }
+    }
+
+    impl LookupHost {
+        pub fn port(&self) -> u16 {
+            0 // unimplemented
+        }
+    }
+
+    unsafe impl Sync for LookupHost {}
+    unsafe impl Send for LookupHost {}
+
+    impl TryFrom<&str> for LookupHost {
+        type Error = io::Error;
+
+        fn try_from(_v: &str) -> io::Result<LookupHost> {
+            unimpl!();
+        }
+    }
+
+    impl<'a> TryFrom<(&'a str, u16)> for LookupHost {
+        type Error = io::Error;
+
+        fn try_from(_v: (&'a str, u16)) -> io::Result<LookupHost> {
+            unimpl!();
+        }
+    }
+}
diff --git a/library/std/src/sys/pal/unix/locks/fuchsia_mutex.rs b/library/std/src/sys/pal/unix/locks/fuchsia_mutex.rs
new file mode 100644
index 00000000000..5d89e5a13fd
--- /dev/null
+++ b/library/std/src/sys/pal/unix/locks/fuchsia_mutex.rs
@@ -0,0 +1,164 @@
+//! A priority inheriting mutex for Fuchsia.
+//!
+//! This is a port of the [mutex in Fuchsia's libsync]. Contrary to the original,
+//! it does not abort the process when reentrant locking is detected, but deadlocks.
+//!
+//! Priority inheritance is achieved by storing the owning thread's handle in an
+//! atomic variable. Fuchsia's futex operations support setting an owner thread
+//! for a futex, which can boost that thread's priority while the futex is waited
+//! upon.
+//!
+//! libsync is licenced under the following BSD-style licence:
+//!
+//! Copyright 2016 The Fuchsia Authors.
+//!
+//! Redistribution and use in source and binary forms, with or without
+//! modification, are permitted provided that the following conditions are
+//! met:
+//!
+//!    * Redistributions of source code must retain the above copyright
+//!      notice, this list of conditions and the following disclaimer.
+//!    * Redistributions in binary form must reproduce the above
+//!      copyright notice, this list of conditions and the following
+//!      disclaimer in the documentation and/or other materials provided
+//!      with the distribution.
+//!
+//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+//! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+//! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+//! A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+//! OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+//! SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+//! LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+//! DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+//! THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+//! (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//!
+//! [mutex in Fuchsia's libsync]: https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/system/ulib/sync/mutex.c
+
+use crate::sync::atomic::{
+    AtomicU32,
+    Ordering::{Acquire, Relaxed, Release},
+};
+use crate::sys::futex::zircon::{
+    zx_futex_wait, zx_futex_wake_single_owner, zx_handle_t, zx_thread_self, ZX_ERR_BAD_HANDLE,
+    ZX_ERR_BAD_STATE, ZX_ERR_INVALID_ARGS, ZX_ERR_TIMED_OUT, ZX_ERR_WRONG_TYPE, ZX_OK,
+    ZX_TIME_INFINITE,
+};
+
+// The lowest two bits of a `zx_handle_t` are always set, so the lowest bit is used to mark the
+// mutex as contested by clearing it.
+const CONTESTED_BIT: u32 = 1;
+// This can never be a valid `zx_handle_t`.
+const UNLOCKED: u32 = 0;
+
+pub struct Mutex {
+    futex: AtomicU32,
+}
+
+#[inline]
+fn to_state(owner: zx_handle_t) -> u32 {
+    owner
+}
+
+#[inline]
+fn to_owner(state: u32) -> zx_handle_t {
+    state | CONTESTED_BIT
+}
+
+#[inline]
+fn is_contested(state: u32) -> bool {
+    state & CONTESTED_BIT == 0
+}
+
+#[inline]
+fn mark_contested(state: u32) -> u32 {
+    state & !CONTESTED_BIT
+}
+
+impl Mutex {
+    #[inline]
+    pub const fn new() -> Mutex {
+        Mutex { futex: AtomicU32::new(UNLOCKED) }
+    }
+
+    #[inline]
+    pub fn try_lock(&self) -> bool {
+        let thread_self = unsafe { zx_thread_self() };
+        self.futex.compare_exchange(UNLOCKED, to_state(thread_self), Acquire, Relaxed).is_ok()
+    }
+
+    #[inline]
+    pub fn lock(&self) {
+        let thread_self = unsafe { zx_thread_self() };
+        if let Err(state) =
+            self.futex.compare_exchange(UNLOCKED, to_state(thread_self), Acquire, Relaxed)
+        {
+            unsafe {
+                self.lock_contested(state, thread_self);
+            }
+        }
+    }
+
+    /// # Safety
+    /// `thread_self` must be the handle for the current thread.
+    #[cold]
+    unsafe fn lock_contested(&self, mut state: u32, thread_self: zx_handle_t) {
+        let owned_state = mark_contested(to_state(thread_self));
+        loop {
+            // Mark the mutex as contested if it is not already.
+            let contested = mark_contested(state);
+            if is_contested(state)
+                || self.futex.compare_exchange(state, contested, Relaxed, Relaxed).is_ok()
+            {
+                // The mutex has been marked as contested, wait for the state to change.
+                unsafe {
+                    match zx_futex_wait(
+                        &self.futex,
+                        AtomicU32::new(contested),
+                        to_owner(state),
+                        ZX_TIME_INFINITE,
+                    ) {
+                        ZX_OK | ZX_ERR_BAD_STATE | ZX_ERR_TIMED_OUT => (),
+                        // Note that if a thread handle is reused after its associated thread
+                        // exits without unlocking the mutex, an arbitrary thread's priority
+                        // could be boosted by the wait, but there is currently no way to
+                        // prevent that.
+                        ZX_ERR_INVALID_ARGS | ZX_ERR_BAD_HANDLE | ZX_ERR_WRONG_TYPE => {
+                            panic!(
+                                "either the current thread is trying to lock a mutex it has
+                                already locked, or the previous owner did not unlock the mutex
+                                before exiting"
+                            )
+                        }
+                        error => panic!("unexpected error in zx_futex_wait: {error}"),
+                    }
+                }
+            }
+
+            // The state has changed or a wakeup occurred, try to lock the mutex.
+            match self.futex.compare_exchange(UNLOCKED, owned_state, Acquire, Relaxed) {
+                Ok(_) => return,
+                Err(updated) => state = updated,
+            }
+        }
+    }
+
+    #[inline]
+    pub unsafe fn unlock(&self) {
+        if is_contested(self.futex.swap(UNLOCKED, Release)) {
+            // The woken thread will mark the mutex as contested again,
+            // and return here, waking until there are no waiters left,
+            // in which case this is a noop.
+            self.wake();
+        }
+    }
+
+    #[cold]
+    fn wake(&self) {
+        unsafe {
+            zx_futex_wake_single_owner(&self.futex);
+        }
+    }
+}
diff --git a/library/std/src/sys/pal/unix/locks/futex_condvar.rs b/library/std/src/sys/pal/unix/locks/futex_condvar.rs
new file mode 100644
index 00000000000..4bd65dd25c2
--- /dev/null
+++ b/library/std/src/sys/pal/unix/locks/futex_condvar.rs
@@ -0,0 +1,56 @@
+use super::Mutex;
+use crate::sync::atomic::{AtomicU32, Ordering::Relaxed};
+use crate::sys::futex::{futex_wait, futex_wake, futex_wake_all};
+use crate::time::Duration;
+
+pub struct Condvar {
+    // The value of this atomic is simply incremented on every notification.
+    // This is used by `.wait()` to not miss any notifications after
+    // unlocking the mutex and before waiting for notifications.
+    futex: AtomicU32,
+}
+
+impl Condvar {
+    #[inline]
+    pub const fn new() -> Self {
+        Self { futex: AtomicU32::new(0) }
+    }
+
+    // All the memory orderings here are `Relaxed`,
+    // because synchronization is done by unlocking and locking the mutex.
+
+    pub fn notify_one(&self) {
+        self.futex.fetch_add(1, Relaxed);
+        futex_wake(&self.futex);
+    }
+
+    pub fn notify_all(&self) {
+        self.futex.fetch_add(1, Relaxed);
+        futex_wake_all(&self.futex);
+    }
+
+    pub unsafe fn wait(&self, mutex: &Mutex) {
+        self.wait_optional_timeout(mutex, None);
+    }
+
+    pub unsafe fn wait_timeout(&self, mutex: &Mutex, timeout: Duration) -> bool {
+        self.wait_optional_timeout(mutex, Some(timeout))
+    }
+
+    unsafe fn wait_optional_timeout(&self, mutex: &Mutex, timeout: Option<Duration>) -> bool {
+        // Examine the notification counter _before_ we unlock the mutex.
+        let futex_value = self.futex.load(Relaxed);
+
+        // Unlock the mutex before going to sleep.
+        mutex.unlock();
+
+        // Wait, but only if there hasn't been any
+        // notification since we unlocked the mutex.
+        let r = futex_wait(&self.futex, futex_value, timeout);
+
+        // Lock the mutex again.
+        mutex.lock();
+
+        r
+    }
+}
diff --git a/library/std/src/sys/pal/unix/locks/futex_mutex.rs b/library/std/src/sys/pal/unix/locks/futex_mutex.rs
new file mode 100644
index 00000000000..c01229586c3
--- /dev/null
+++ b/library/std/src/sys/pal/unix/locks/futex_mutex.rs
@@ -0,0 +1,96 @@
+use crate::sync::atomic::{
+    AtomicU32,
+    Ordering::{Acquire, Relaxed, Release},
+};
+use crate::sys::futex::{futex_wait, futex_wake};
+
+pub struct Mutex {
+    /// 0: unlocked
+    /// 1: locked, no other threads waiting
+    /// 2: locked, and other threads waiting (contended)
+    futex: AtomicU32,
+}
+
+impl Mutex {
+    #[inline]
+    pub const fn new() -> Self {
+        Self { futex: AtomicU32::new(0) }
+    }
+
+    #[inline]
+    pub fn try_lock(&self) -> bool {
+        self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_ok()
+    }
+
+    #[inline]
+    pub fn lock(&self) {
+        if self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_err() {
+            self.lock_contended();
+        }
+    }
+
+    #[cold]
+    fn lock_contended(&self) {
+        // Spin first to speed things up if the lock is released quickly.
+        let mut state = self.spin();
+
+        // If it's unlocked now, attempt to take the lock
+        // without marking it as contended.
+        if state == 0 {
+            match self.futex.compare_exchange(0, 1, Acquire, Relaxed) {
+                Ok(_) => return, // Locked!
+                Err(s) => state = s,
+            }
+        }
+
+        loop {
+            // Put the lock in contended state.
+            // We avoid an unnecessary write if it as already set to 2,
+            // to be friendlier for the caches.
+            if state != 2 && self.futex.swap(2, Acquire) == 0 {
+                // We changed it from 0 to 2, so we just successfully locked it.
+                return;
+            }
+
+            // Wait for the futex to change state, assuming it is still 2.
+            futex_wait(&self.futex, 2, None);
+
+            // Spin again after waking up.
+            state = self.spin();
+        }
+    }
+
+    fn spin(&self) -> u32 {
+        let mut spin = 100;
+        loop {
+            // We only use `load` (and not `swap` or `compare_exchange`)
+            // while spinning, to be easier on the caches.
+            let state = self.futex.load(Relaxed);
+
+            // We stop spinning when the mutex is unlocked (0),
+            // but also when it's contended (2).
+            if state != 1 || spin == 0 {
+                return state;
+            }
+
+            crate::hint::spin_loop();
+            spin -= 1;
+        }
+    }
+
+    #[inline]
+    pub unsafe fn unlock(&self) {
+        if self.futex.swap(0, Release) == 2 {
+            // We only wake up one thread. When that thread locks the mutex, it
+            // will mark the mutex as contended (2) (see lock_contended above),
+            // which makes sure that any other waiting threads will also be
+            // woken up eventually.
+            self.wake();
+        }
+    }
+
+    #[cold]
+    fn wake(&self) {
+        futex_wake(&self.futex);
+    }
+}
diff --git a/library/std/src/sys/pal/unix/locks/futex_rwlock.rs b/library/std/src/sys/pal/unix/locks/futex_rwlock.rs
new file mode 100644
index 00000000000..aa0de900238
--- /dev/null
+++ b/library/std/src/sys/pal/unix/locks/futex_rwlock.rs
@@ -0,0 +1,320 @@
+use crate::sync::atomic::{
+    AtomicU32,
+    Ordering::{Acquire, Relaxed, Release},
+};
+use crate::sys::futex::{futex_wait, futex_wake, futex_wake_all};
+
+pub struct RwLock {
+    // The state consists of a 30-bit reader counter, a 'readers waiting' flag, and a 'writers waiting' flag.
+    // Bits 0..30:
+    //   0: Unlocked
+    //   1..=0x3FFF_FFFE: Locked by N readers
+    //   0x3FFF_FFFF: Write locked
+    // Bit 30: Readers are waiting on this futex.
+    // Bit 31: Writers are waiting on the writer_notify futex.
+    state: AtomicU32,
+    // The 'condition variable' to notify writers through.
+    // Incremented on every signal.
+    writer_notify: AtomicU32,
+}
+
+const READ_LOCKED: u32 = 1;
+const MASK: u32 = (1 << 30) - 1;
+const WRITE_LOCKED: u32 = MASK;
+const MAX_READERS: u32 = MASK - 1;
+const READERS_WAITING: u32 = 1 << 30;
+const WRITERS_WAITING: u32 = 1 << 31;
+
+#[inline]
+fn is_unlocked(state: u32) -> bool {
+    state & MASK == 0
+}
+
+#[inline]
+fn is_write_locked(state: u32) -> bool {
+    state & MASK == WRITE_LOCKED
+}
+
+#[inline]
+fn has_readers_waiting(state: u32) -> bool {
+    state & READERS_WAITING != 0
+}
+
+#[inline]
+fn has_writers_waiting(state: u32) -> bool {
+    state & WRITERS_WAITING != 0
+}
+
+#[inline]
+fn is_read_lockable(state: u32) -> bool {
+    // This also returns false if the counter could overflow if we tried to read lock it.
+    //
+    // We don't allow read-locking if there's readers waiting, even if the lock is unlocked
+    // and there's no writers waiting. The only situation when this happens is after unlocking,
+    // at which point the unlocking thread might be waking up writers, which have priority over readers.
+    // The unlocking thread will clear the readers waiting bit and wake up readers, if necessary.
+    state & MASK < MAX_READERS && !has_readers_waiting(state) && !has_writers_waiting(state)
+}
+
+#[inline]
+fn has_reached_max_readers(state: u32) -> bool {
+    state & MASK == MAX_READERS
+}
+
+impl RwLock {
+    #[inline]
+    pub const fn new() -> Self {
+        Self { state: AtomicU32::new(0), writer_notify: AtomicU32::new(0) }
+    }
+
+    #[inline]
+    pub fn try_read(&self) -> bool {
+        self.state
+            .fetch_update(Acquire, Relaxed, |s| is_read_lockable(s).then(|| s + READ_LOCKED))
+            .is_ok()
+    }
+
+    #[inline]
+    pub fn read(&self) {
+        let state = self.state.load(Relaxed);
+        if !is_read_lockable(state)
+            || self
+                .state
+                .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed)
+                .is_err()
+        {
+            self.read_contended();
+        }
+    }
+
+    #[inline]
+    pub unsafe fn read_unlock(&self) {
+        let state = self.state.fetch_sub(READ_LOCKED, Release) - READ_LOCKED;
+
+        // It's impossible for a reader to be waiting on a read-locked RwLock,
+        // except if there is also a writer waiting.
+        debug_assert!(!has_readers_waiting(state) || has_writers_waiting(state));
+
+        // Wake up a writer if we were the last reader and there's a writer waiting.
+        if is_unlocked(state) && has_writers_waiting(state) {
+            self.wake_writer_or_readers(state);
+        }
+    }
+
+    #[cold]
+    fn read_contended(&self) {
+        let mut state = self.spin_read();
+
+        loop {
+            // If we can lock it, lock it.
+            if is_read_lockable(state) {
+                match self.state.compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed)
+                {
+                    Ok(_) => return, // Locked!
+                    Err(s) => {
+                        state = s;
+                        continue;
+                    }
+                }
+            }
+
+            // Check for overflow.
+            if has_reached_max_readers(state) {
+                panic!("too many active read locks on RwLock");
+            }
+
+            // Make sure the readers waiting bit is set before we go to sleep.
+            if !has_readers_waiting(state) {
+                if let Err(s) =
+                    self.state.compare_exchange(state, state | READERS_WAITING, Relaxed, Relaxed)
+                {
+                    state = s;
+                    continue;
+                }
+            }
+
+            // Wait for the state to change.
+            futex_wait(&self.state, state | READERS_WAITING, None);
+
+            // Spin again after waking up.
+            state = self.spin_read();
+        }
+    }
+
+    #[inline]
+    pub fn try_write(&self) -> bool {
+        self.state
+            .fetch_update(Acquire, Relaxed, |s| is_unlocked(s).then(|| s + WRITE_LOCKED))
+            .is_ok()
+    }
+
+    #[inline]
+    pub fn write(&self) {
+        if self.state.compare_exchange_weak(0, WRITE_LOCKED, Acquire, Relaxed).is_err() {
+            self.write_contended();
+        }
+    }
+
+    #[inline]
+    pub unsafe fn write_unlock(&self) {
+        let state = self.state.fetch_sub(WRITE_LOCKED, Release) - WRITE_LOCKED;
+
+        debug_assert!(is_unlocked(state));
+
+        if has_writers_waiting(state) || has_readers_waiting(state) {
+            self.wake_writer_or_readers(state);
+        }
+    }
+
+    #[cold]
+    fn write_contended(&self) {
+        let mut state = self.spin_write();
+
+        let mut other_writers_waiting = 0;
+
+        loop {
+            // If it's unlocked, we try to lock it.
+            if is_unlocked(state) {
+                match self.state.compare_exchange_weak(
+                    state,
+                    state | WRITE_LOCKED | other_writers_waiting,
+                    Acquire,
+                    Relaxed,
+                ) {
+                    Ok(_) => return, // Locked!
+                    Err(s) => {
+                        state = s;
+                        continue;
+                    }
+                }
+            }
+
+            // Set the waiting bit indicating that we're waiting on it.
+            if !has_writers_waiting(state) {
+                if let Err(s) =
+                    self.state.compare_exchange(state, state | WRITERS_WAITING, Relaxed, Relaxed)
+                {
+                    state = s;
+                    continue;
+                }
+            }
+
+            // Other writers might be waiting now too, so we should make sure
+            // we keep that bit on once we manage lock it.
+            other_writers_waiting = WRITERS_WAITING;
+
+            // Examine the notification counter before we check if `state` has changed,
+            // to make sure we don't miss any notifications.
+            let seq = self.writer_notify.load(Acquire);
+
+            // Don't go to sleep if the lock has become available,
+            // or if the writers waiting bit is no longer set.
+            state = self.state.load(Relaxed);
+            if is_unlocked(state) || !has_writers_waiting(state) {
+                continue;
+            }
+
+            // Wait for the state to change.
+            futex_wait(&self.writer_notify, seq, None);
+
+            // Spin again after waking up.
+            state = self.spin_write();
+        }
+    }
+
+    /// Wake up waiting threads after unlocking.
+    ///
+    /// If both are waiting, this will wake up only one writer, but will fall
+    /// back to waking up readers if there was no writer to wake up.
+    #[cold]
+    fn wake_writer_or_readers(&self, mut state: u32) {
+        assert!(is_unlocked(state));
+
+        // The readers waiting bit might be turned on at any point now,
+        // since readers will block when there's anything waiting.
+        // Writers will just lock the lock though, regardless of the waiting bits,
+        // so we don't have to worry about the writer waiting bit.
+        //
+        // If the lock gets locked in the meantime, we don't have to do
+        // anything, because then the thread that locked the lock will take
+        // care of waking up waiters when it unlocks.
+
+        // If only writers are waiting, wake one of them up.
+        if state == WRITERS_WAITING {
+            match self.state.compare_exchange(state, 0, Relaxed, Relaxed) {
+                Ok(_) => {
+                    self.wake_writer();
+                    return;
+                }
+                Err(s) => {
+                    // Maybe some readers are now waiting too. So, continue to the next `if`.
+                    state = s;
+                }
+            }
+        }
+
+        // If both writers and readers are waiting, leave the readers waiting
+        // and only wake up one writer.
+        if state == READERS_WAITING + WRITERS_WAITING {
+            if self.state.compare_exchange(state, READERS_WAITING, Relaxed, Relaxed).is_err() {
+                // The lock got locked. Not our problem anymore.
+                return;
+            }
+            if self.wake_writer() {
+                return;
+            }
+            // No writers were actually blocked on futex_wait, so we continue
+            // to wake up readers instead, since we can't be sure if we notified a writer.
+            state = READERS_WAITING;
+        }
+
+        // If readers are waiting, wake them all up.
+        if state == READERS_WAITING {
+            if self.state.compare_exchange(state, 0, Relaxed, Relaxed).is_ok() {
+                futex_wake_all(&self.state);
+            }
+        }
+    }
+
+    /// This wakes one writer and returns true if we woke up a writer that was
+    /// blocked on futex_wait.
+    ///
+    /// If this returns false, it might still be the case that we notified a
+    /// writer that was about to go to sleep.
+    fn wake_writer(&self) -> bool {
+        self.writer_notify.fetch_add(1, Release);
+        futex_wake(&self.writer_notify)
+        // Note that FreeBSD and DragonFlyBSD don't tell us whether they woke
+        // up any threads or not, and always return `false` here. That still
+        // results in correct behaviour: it just means readers get woken up as
+        // well in case both readers and writers were waiting.
+    }
+
+    /// Spin for a while, but stop directly at the given condition.
+    #[inline]
+    fn spin_until(&self, f: impl Fn(u32) -> bool) -> u32 {
+        let mut spin = 100; // Chosen by fair dice roll.
+        loop {
+            let state = self.state.load(Relaxed);
+            if f(state) || spin == 0 {
+                return state;
+            }
+            crate::hint::spin_loop();
+            spin -= 1;
+        }
+    }
+
+    #[inline]
+    fn spin_write(&self) -> u32 {
+        // Stop spinning when it's unlocked or when there's waiting writers, to keep things somewhat fair.
+        self.spin_until(|state| is_unlocked(state) || has_writers_waiting(state))
+    }
+
+    #[inline]
+    fn spin_read(&self) -> u32 {
+        // Stop spinning when it's unlocked or read locked, or when there's waiting threads.
+        self.spin_until(|state| {
+            !is_write_locked(state) || has_readers_waiting(state) || has_writers_waiting(state)
+        })
+    }
+}
diff --git a/library/std/src/sys/pal/unix/locks/mod.rs b/library/std/src/sys/pal/unix/locks/mod.rs
new file mode 100644
index 00000000000..b2e0e49ad73
--- /dev/null
+++ b/library/std/src/sys/pal/unix/locks/mod.rs
@@ -0,0 +1,31 @@
+cfg_if::cfg_if! {
+    if #[cfg(any(
+        target_os = "linux",
+        target_os = "android",
+        all(target_os = "emscripten", target_feature = "atomics"),
+        target_os = "freebsd",
+        target_os = "openbsd",
+        target_os = "dragonfly",
+    ))] {
+        mod futex_mutex;
+        mod futex_rwlock;
+        mod futex_condvar;
+        pub(crate) use futex_mutex::Mutex;
+        pub(crate) use futex_rwlock::RwLock;
+        pub(crate) use futex_condvar::Condvar;
+    } else if #[cfg(target_os = "fuchsia")] {
+        mod fuchsia_mutex;
+        mod futex_rwlock;
+        mod futex_condvar;
+        pub(crate) use fuchsia_mutex::Mutex;
+        pub(crate) use futex_rwlock::RwLock;
+        pub(crate) use futex_condvar::Condvar;
+    } else {
+        mod pthread_mutex;
+        mod pthread_rwlock;
+        mod pthread_condvar;
+        pub(crate) use pthread_mutex::Mutex;
+        pub(crate) use pthread_rwlock::RwLock;
+        pub(crate) use pthread_condvar::Condvar;
+    }
+}
diff --git a/library/std/src/sys/pal/unix/locks/pthread_condvar.rs b/library/std/src/sys/pal/unix/locks/pthread_condvar.rs
new file mode 100644
index 00000000000..2dc1b0c601e
--- /dev/null
+++ b/library/std/src/sys/pal/unix/locks/pthread_condvar.rs
@@ -0,0 +1,206 @@
+use crate::cell::UnsafeCell;
+use crate::ptr;
+use crate::sync::atomic::{AtomicPtr, Ordering::Relaxed};
+use crate::sys::locks::{pthread_mutex, Mutex};
+#[cfg(not(target_os = "nto"))]
+use crate::sys::time::TIMESPEC_MAX;
+#[cfg(target_os = "nto")]
+use crate::sys::time::TIMESPEC_MAX_CAPPED;
+use crate::sys_common::lazy_box::{LazyBox, LazyInit};
+use crate::time::Duration;
+
+struct AllocatedCondvar(UnsafeCell<libc::pthread_cond_t>);
+
+pub struct Condvar {
+    inner: LazyBox<AllocatedCondvar>,
+    mutex: AtomicPtr<libc::pthread_mutex_t>,
+}
+
+#[inline]
+fn raw(c: &Condvar) -> *mut libc::pthread_cond_t {
+    c.inner.0.get()
+}
+
+unsafe impl Send for AllocatedCondvar {}
+unsafe impl Sync for AllocatedCondvar {}
+
+impl LazyInit for AllocatedCondvar {
+    fn init() -> Box<Self> {
+        let condvar = Box::new(AllocatedCondvar(UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER)));
+
+        cfg_if::cfg_if! {
+            if #[cfg(any(
+                target_os = "macos",
+                target_os = "ios",
+                target_os = "tvos",
+                target_os = "watchos",
+                target_os = "l4re",
+                target_os = "android",
+                target_os = "redox"
+            ))] {
+                // `pthread_condattr_setclock` is unfortunately not supported on these platforms.
+            } else if #[cfg(any(target_os = "espidf", target_os = "horizon"))] {
+                // NOTE: ESP-IDF's PTHREAD_COND_INITIALIZER support is not released yet
+                // So on that platform, init() should always be called
+                // Moreover, that platform does not have pthread_condattr_setclock support,
+                // hence that initialization should be skipped as well
+                //
+                // Similar story for the 3DS (horizon).
+                let r = unsafe { libc::pthread_cond_init(condvar.0.get(), crate::ptr::null()) };
+                assert_eq!(r, 0);
+            } else {
+                use crate::mem::MaybeUninit;
+                let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
+                let r = unsafe { libc::pthread_condattr_init(attr.as_mut_ptr()) };
+                assert_eq!(r, 0);
+                let r = unsafe { libc::pthread_condattr_setclock(attr.as_mut_ptr(), libc::CLOCK_MONOTONIC) };
+                assert_eq!(r, 0);
+                let r = unsafe { libc::pthread_cond_init(condvar.0.get(), attr.as_ptr()) };
+                assert_eq!(r, 0);
+                let r = unsafe { libc::pthread_condattr_destroy(attr.as_mut_ptr()) };
+                assert_eq!(r, 0);
+            }
+        }
+
+        condvar
+    }
+}
+
+impl Drop for AllocatedCondvar {
+    #[inline]
+    fn drop(&mut self) {
+        let r = unsafe { libc::pthread_cond_destroy(self.0.get()) };
+        if cfg!(target_os = "dragonfly") {
+            // On DragonFly pthread_cond_destroy() returns EINVAL if called on
+            // a condvar that was just initialized with
+            // libc::PTHREAD_COND_INITIALIZER. Once it is used or
+            // pthread_cond_init() is called, this behaviour no longer occurs.
+            debug_assert!(r == 0 || r == libc::EINVAL);
+        } else {
+            debug_assert_eq!(r, 0);
+        }
+    }
+}
+
+impl Condvar {
+    pub const fn new() -> Condvar {
+        Condvar { inner: LazyBox::new(), mutex: AtomicPtr::new(ptr::null_mut()) }
+    }
+
+    #[inline]
+    fn verify(&self, mutex: *mut libc::pthread_mutex_t) {
+        // Relaxed is okay here because we never read through `self.addr`, and only use it to
+        // compare addresses.
+        match self.mutex.compare_exchange(ptr::null_mut(), mutex, Relaxed, Relaxed) {
+            Ok(_) => {}                // Stored the address
+            Err(n) if n == mutex => {} // Lost a race to store the same address
+            _ => panic!("attempted to use a condition variable with two mutexes"),
+        }
+    }
+
+    #[inline]
+    pub fn notify_one(&self) {
+        let r = unsafe { libc::pthread_cond_signal(raw(self)) };
+        debug_assert_eq!(r, 0);
+    }
+
+    #[inline]
+    pub fn notify_all(&self) {
+        let r = unsafe { libc::pthread_cond_broadcast(raw(self)) };
+        debug_assert_eq!(r, 0);
+    }
+
+    #[inline]
+    pub unsafe fn wait(&self, mutex: &Mutex) {
+        let mutex = pthread_mutex::raw(mutex);
+        self.verify(mutex);
+        let r = libc::pthread_cond_wait(raw(self), mutex);
+        debug_assert_eq!(r, 0);
+    }
+
+    // This implementation is used on systems that support pthread_condattr_setclock
+    // where we configure condition variable to use monotonic clock (instead of
+    // default system clock). This approach avoids all problems that result
+    // from changes made to the system time.
+    #[cfg(not(any(
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "android",
+        target_os = "espidf",
+        target_os = "horizon"
+    )))]
+    pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
+        use crate::sys::time::Timespec;
+
+        let mutex = pthread_mutex::raw(mutex);
+        self.verify(mutex);
+
+        #[cfg(not(target_os = "nto"))]
+        let timeout = Timespec::now(libc::CLOCK_MONOTONIC)
+            .checked_add_duration(&dur)
+            .and_then(|t| t.to_timespec())
+            .unwrap_or(TIMESPEC_MAX);
+
+        #[cfg(target_os = "nto")]
+        let timeout = Timespec::now(libc::CLOCK_MONOTONIC)
+            .checked_add_duration(&dur)
+            .and_then(|t| t.to_timespec_capped())
+            .unwrap_or(TIMESPEC_MAX_CAPPED);
+
+        let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout);
+        assert!(r == libc::ETIMEDOUT || r == 0);
+        r == 0
+    }
+
+    // This implementation is modeled after libcxx's condition_variable
+    // https://github.com/llvm-mirror/libcxx/blob/release_35/src/condition_variable.cpp#L46
+    // https://github.com/llvm-mirror/libcxx/blob/release_35/include/__mutex_base#L367
+    #[cfg(any(
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "android",
+        target_os = "espidf",
+        target_os = "horizon"
+    ))]
+    pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
+        use crate::sys::time::SystemTime;
+        use crate::time::Instant;
+
+        let mutex = pthread_mutex::raw(mutex);
+        self.verify(mutex);
+
+        // OSX implementation of `pthread_cond_timedwait` is buggy
+        // with super long durations. When duration is greater than
+        // 0x100_0000_0000_0000 seconds, `pthread_cond_timedwait`
+        // in macOS Sierra returns error 316.
+        //
+        // This program demonstrates the issue:
+        // https://gist.github.com/stepancheg/198db4623a20aad2ad7cddb8fda4a63c
+        //
+        // To work around this issue, and possible bugs of other OSes, timeout
+        // is clamped to 1000 years, which is allowable per the API of `wait_timeout`
+        // because of spurious wakeups.
+        let dur = Duration::min(dur, Duration::from_secs(1000 * 365 * 86400));
+
+        // pthread_cond_timedwait uses system time, but we want to report timeout
+        // based on stable time.
+        let now = Instant::now();
+
+        let timeout = SystemTime::now()
+            .t
+            .checked_add_duration(&dur)
+            .and_then(|t| t.to_timespec())
+            .unwrap_or(TIMESPEC_MAX);
+
+        let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout);
+        debug_assert!(r == libc::ETIMEDOUT || r == 0);
+
+        // ETIMEDOUT is not a totally reliable method of determining timeout due
+        // to clock shifts, so do the check ourselves
+        now.elapsed() < dur
+    }
+}
diff --git a/library/std/src/sys/pal/unix/locks/pthread_mutex.rs b/library/std/src/sys/pal/unix/locks/pthread_mutex.rs
new file mode 100644
index 00000000000..8a78bc1fd73
--- /dev/null
+++ b/library/std/src/sys/pal/unix/locks/pthread_mutex.rs
@@ -0,0 +1,131 @@
+use crate::cell::UnsafeCell;
+use crate::mem::{forget, MaybeUninit};
+use crate::sys::cvt_nz;
+use crate::sys_common::lazy_box::{LazyBox, LazyInit};
+
+struct AllocatedMutex(UnsafeCell<libc::pthread_mutex_t>);
+
+pub struct Mutex {
+    inner: LazyBox<AllocatedMutex>,
+}
+
+#[inline]
+pub unsafe fn raw(m: &Mutex) -> *mut libc::pthread_mutex_t {
+    m.inner.0.get()
+}
+
+unsafe impl Send for AllocatedMutex {}
+unsafe impl Sync for AllocatedMutex {}
+
+impl LazyInit for AllocatedMutex {
+    fn init() -> Box<Self> {
+        let mutex = Box::new(AllocatedMutex(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)));
+
+        // Issue #33770
+        //
+        // A pthread mutex initialized with PTHREAD_MUTEX_INITIALIZER will have
+        // a type of PTHREAD_MUTEX_DEFAULT, which has undefined behavior if you
+        // try to re-lock it from the same thread when you already hold a lock
+        // (https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_init.html).
+        // This is the case even if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_NORMAL
+        // (https://github.com/rust-lang/rust/issues/33770#issuecomment-220847521) -- in that
+        // case, `pthread_mutexattr_settype(PTHREAD_MUTEX_DEFAULT)` will of course be the same
+        // as setting it to `PTHREAD_MUTEX_NORMAL`, but not setting any mode will result in
+        // a Mutex where re-locking is UB.
+        //
+        // In practice, glibc takes advantage of this undefined behavior to
+        // implement hardware lock elision, which uses hardware transactional
+        // memory to avoid acquiring the lock. While a transaction is in
+        // progress, the lock appears to be unlocked. This isn't a problem for
+        // other threads since the transactional memory will abort if a conflict
+        // is detected, however no abort is generated when re-locking from the
+        // same thread.
+        //
+        // Since locking the same mutex twice will result in two aliasing &mut
+        // references, we instead create the mutex with type
+        // PTHREAD_MUTEX_NORMAL which is guaranteed to deadlock if we try to
+        // re-lock it from the same thread, thus avoiding undefined behavior.
+        unsafe {
+            let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit();
+            cvt_nz(libc::pthread_mutexattr_init(attr.as_mut_ptr())).unwrap();
+            let attr = PthreadMutexAttr(&mut attr);
+            cvt_nz(libc::pthread_mutexattr_settype(
+                attr.0.as_mut_ptr(),
+                libc::PTHREAD_MUTEX_NORMAL,
+            ))
+            .unwrap();
+            cvt_nz(libc::pthread_mutex_init(mutex.0.get(), attr.0.as_ptr())).unwrap();
+        }
+
+        mutex
+    }
+
+    fn destroy(mutex: Box<Self>) {
+        // We're not allowed to pthread_mutex_destroy a locked mutex,
+        // so check first if it's unlocked.
+        if unsafe { libc::pthread_mutex_trylock(mutex.0.get()) == 0 } {
+            unsafe { libc::pthread_mutex_unlock(mutex.0.get()) };
+            drop(mutex);
+        } else {
+            // The mutex is locked. This happens if a MutexGuard is leaked.
+            // In this case, we just leak the Mutex too.
+            forget(mutex);
+        }
+    }
+
+    fn cancel_init(_: Box<Self>) {
+        // In this case, we can just drop it without any checks,
+        // since it cannot have been locked yet.
+    }
+}
+
+impl Drop for AllocatedMutex {
+    #[inline]
+    fn drop(&mut self) {
+        let r = unsafe { libc::pthread_mutex_destroy(self.0.get()) };
+        if cfg!(target_os = "dragonfly") {
+            // On DragonFly pthread_mutex_destroy() returns EINVAL if called on a
+            // mutex that was just initialized with libc::PTHREAD_MUTEX_INITIALIZER.
+            // Once it is used (locked/unlocked) or pthread_mutex_init() is called,
+            // this behaviour no longer occurs.
+            debug_assert!(r == 0 || r == libc::EINVAL);
+        } else {
+            debug_assert_eq!(r, 0);
+        }
+    }
+}
+
+impl Mutex {
+    #[inline]
+    pub const fn new() -> Mutex {
+        Mutex { inner: LazyBox::new() }
+    }
+
+    #[inline]
+    pub unsafe fn lock(&self) {
+        let r = libc::pthread_mutex_lock(raw(self));
+        debug_assert_eq!(r, 0);
+    }
+
+    #[inline]
+    pub unsafe fn unlock(&self) {
+        let r = libc::pthread_mutex_unlock(raw(self));
+        debug_assert_eq!(r, 0);
+    }
+
+    #[inline]
+    pub unsafe fn try_lock(&self) -> bool {
+        libc::pthread_mutex_trylock(raw(self)) == 0
+    }
+}
+
+pub(super) struct PthreadMutexAttr<'a>(pub &'a mut MaybeUninit<libc::pthread_mutexattr_t>);
+
+impl Drop for PthreadMutexAttr<'_> {
+    fn drop(&mut self) {
+        unsafe {
+            let result = libc::pthread_mutexattr_destroy(self.0.as_mut_ptr());
+            debug_assert_eq!(result, 0);
+        }
+    }
+}
diff --git a/library/std/src/sys/pal/unix/locks/pthread_rwlock.rs b/library/std/src/sys/pal/unix/locks/pthread_rwlock.rs
new file mode 100644
index 00000000000..04662be9d82
--- /dev/null
+++ b/library/std/src/sys/pal/unix/locks/pthread_rwlock.rs
@@ -0,0 +1,195 @@
+use crate::cell::UnsafeCell;
+use crate::mem::forget;
+use crate::sync::atomic::{AtomicUsize, Ordering};
+use crate::sys_common::lazy_box::{LazyBox, LazyInit};
+
+struct AllocatedRwLock {
+    inner: UnsafeCell<libc::pthread_rwlock_t>,
+    write_locked: UnsafeCell<bool>, // guarded by the `inner` RwLock
+    num_readers: AtomicUsize,
+}
+
+unsafe impl Send for AllocatedRwLock {}
+unsafe impl Sync for AllocatedRwLock {}
+
+pub struct RwLock {
+    inner: LazyBox<AllocatedRwLock>,
+}
+
+impl LazyInit for AllocatedRwLock {
+    fn init() -> Box<Self> {
+        Box::new(AllocatedRwLock {
+            inner: UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER),
+            write_locked: UnsafeCell::new(false),
+            num_readers: AtomicUsize::new(0),
+        })
+    }
+
+    fn destroy(mut rwlock: Box<Self>) {
+        // We're not allowed to pthread_rwlock_destroy a locked rwlock,
+        // so check first if it's unlocked.
+        if *rwlock.write_locked.get_mut() || *rwlock.num_readers.get_mut() != 0 {
+            // The rwlock is locked. This happens if a RwLock{Read,Write}Guard is leaked.
+            // In this case, we just leak the RwLock too.
+            forget(rwlock);
+        }
+    }
+
+    fn cancel_init(_: Box<Self>) {
+        // In this case, we can just drop it without any checks,
+        // since it cannot have been locked yet.
+    }
+}
+
+impl AllocatedRwLock {
+    #[inline]
+    unsafe fn raw_unlock(&self) {
+        let r = libc::pthread_rwlock_unlock(self.inner.get());
+        debug_assert_eq!(r, 0);
+    }
+}
+
+impl Drop for AllocatedRwLock {
+    fn drop(&mut self) {
+        let r = unsafe { libc::pthread_rwlock_destroy(self.inner.get()) };
+        // On DragonFly pthread_rwlock_destroy() returns EINVAL if called on a
+        // rwlock that was just initialized with
+        // libc::PTHREAD_RWLOCK_INITIALIZER. Once it is used (locked/unlocked)
+        // or pthread_rwlock_init() is called, this behaviour no longer occurs.
+        if cfg!(target_os = "dragonfly") {
+            debug_assert!(r == 0 || r == libc::EINVAL);
+        } else {
+            debug_assert_eq!(r, 0);
+        }
+    }
+}
+
+impl RwLock {
+    #[inline]
+    pub const fn new() -> RwLock {
+        RwLock { inner: LazyBox::new() }
+    }
+
+    #[inline]
+    pub fn read(&self) {
+        let lock = &*self.inner;
+        let r = unsafe { libc::pthread_rwlock_rdlock(lock.inner.get()) };
+
+        // According to POSIX, when a thread tries to acquire this read lock
+        // while it already holds the write lock
+        // (or vice versa, or tries to acquire the write lock twice),
+        // "the call shall either deadlock or return [EDEADLK]"
+        // (https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html,
+        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_rdlock.html).
+        // So, in principle, all we have to do here is check `r == 0` to be sure we properly
+        // got the lock.
+        //
+        // However, (at least) glibc before version 2.25 does not conform to this spec,
+        // and can return `r == 0` even when this thread already holds the write lock.
+        // We thus check for this situation ourselves and panic when detecting that a thread
+        // got the write lock more than once, or got a read and a write lock.
+        if r == libc::EAGAIN {
+            panic!("rwlock maximum reader count exceeded");
+        } else if r == libc::EDEADLK || (r == 0 && unsafe { *lock.write_locked.get() }) {
+            // Above, we make sure to only access `write_locked` when `r == 0` to avoid
+            // data races.
+            if r == 0 {
+                // `pthread_rwlock_rdlock` succeeded when it should not have.
+                unsafe {
+                    lock.raw_unlock();
+                }
+            }
+            panic!("rwlock read lock would result in deadlock");
+        } else {
+            // POSIX does not make guarantees about all the errors that may be returned.
+            // See issue #94705 for more details.
+            assert_eq!(r, 0, "unexpected error during rwlock read lock: {:?}", r);
+            lock.num_readers.fetch_add(1, Ordering::Relaxed);
+        }
+    }
+
+    #[inline]
+    pub fn try_read(&self) -> bool {
+        let lock = &*self.inner;
+        let r = unsafe { libc::pthread_rwlock_tryrdlock(lock.inner.get()) };
+        if r == 0 {
+            if unsafe { *lock.write_locked.get() } {
+                // `pthread_rwlock_tryrdlock` succeeded when it should not have.
+                unsafe {
+                    lock.raw_unlock();
+                }
+                false
+            } else {
+                lock.num_readers.fetch_add(1, Ordering::Relaxed);
+                true
+            }
+        } else {
+            false
+        }
+    }
+
+    #[inline]
+    pub fn write(&self) {
+        let lock = &*self.inner;
+        let r = unsafe { libc::pthread_rwlock_wrlock(lock.inner.get()) };
+        // See comments above for why we check for EDEADLK and write_locked. For the same reason,
+        // we also need to check that there are no readers (tracked in `num_readers`).
+        if r == libc::EDEADLK
+            || (r == 0 && unsafe { *lock.write_locked.get() })
+            || lock.num_readers.load(Ordering::Relaxed) != 0
+        {
+            // Above, we make sure to only access `write_locked` when `r == 0` to avoid
+            // data races.
+            if r == 0 {
+                // `pthread_rwlock_wrlock` succeeded when it should not have.
+                unsafe {
+                    lock.raw_unlock();
+                }
+            }
+            panic!("rwlock write lock would result in deadlock");
+        } else {
+            // According to POSIX, for a properly initialized rwlock this can only
+            // return EDEADLK or 0. We rely on that.
+            debug_assert_eq!(r, 0);
+        }
+
+        unsafe {
+            *lock.write_locked.get() = true;
+        }
+    }
+
+    #[inline]
+    pub unsafe fn try_write(&self) -> bool {
+        let lock = &*self.inner;
+        let r = libc::pthread_rwlock_trywrlock(lock.inner.get());
+        if r == 0 {
+            if *lock.write_locked.get() || lock.num_readers.load(Ordering::Relaxed) != 0 {
+                // `pthread_rwlock_trywrlock` succeeded when it should not have.
+                lock.raw_unlock();
+                false
+            } else {
+                *lock.write_locked.get() = true;
+                true
+            }
+        } else {
+            false
+        }
+    }
+
+    #[inline]
+    pub unsafe fn read_unlock(&self) {
+        let lock = &*self.inner;
+        debug_assert!(!*lock.write_locked.get());
+        lock.num_readers.fetch_sub(1, Ordering::Relaxed);
+        lock.raw_unlock();
+    }
+
+    #[inline]
+    pub unsafe fn write_unlock(&self) {
+        let lock = &*self.inner;
+        debug_assert_eq!(lock.num_readers.load(Ordering::Relaxed), 0);
+        debug_assert!(*lock.write_locked.get());
+        *lock.write_locked.get() = false;
+        lock.raw_unlock();
+    }
+}
diff --git a/library/std/src/sys/pal/unix/memchr.rs b/library/std/src/sys/pal/unix/memchr.rs
new file mode 100644
index 00000000000..73ba604eccb
--- /dev/null
+++ b/library/std/src/sys/pal/unix/memchr.rs
@@ -0,0 +1,40 @@
+// Original implementation taken from rust-memchr.
+// Copyright 2015 Andrew Gallant, bluss and Nicolas Koch
+
+pub fn memchr(needle: u8, haystack: &[u8]) -> Option<usize> {
+    let p = unsafe {
+        libc::memchr(
+            haystack.as_ptr() as *const libc::c_void,
+            needle as libc::c_int,
+            haystack.len(),
+        )
+    };
+    if p.is_null() { None } else { Some(p.addr() - haystack.as_ptr().addr()) }
+}
+
+pub fn memrchr(needle: u8, haystack: &[u8]) -> Option<usize> {
+    #[cfg(target_os = "linux")]
+    fn memrchr_specific(needle: u8, haystack: &[u8]) -> Option<usize> {
+        // GNU's memrchr() will - unlike memchr() - error if haystack is empty.
+        if haystack.is_empty() {
+            return None;
+        }
+        let p = unsafe {
+            libc::memrchr(
+                haystack.as_ptr() as *const libc::c_void,
+                needle as libc::c_int,
+                haystack.len(),
+            )
+        };
+        // FIXME: this should *likely* use `offset_from`, but more
+        // investigation is needed (including running tests in miri).
+        if p.is_null() { None } else { Some(p.addr() - haystack.as_ptr().addr()) }
+    }
+
+    #[cfg(not(target_os = "linux"))]
+    fn memrchr_specific(needle: u8, haystack: &[u8]) -> Option<usize> {
+        core::slice::memchr::memrchr(needle, haystack)
+    }
+
+    memrchr_specific(needle, haystack)
+}
diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs
new file mode 100644
index 00000000000..b5da5f870ec
--- /dev/null
+++ b/library/std/src/sys/pal/unix/mod.rs
@@ -0,0 +1,443 @@
+#![allow(missing_docs, nonstandard_style)]
+
+use crate::io::ErrorKind;
+
+pub use self::rand::hashmap_random_keys;
+
+#[cfg(not(target_os = "espidf"))]
+#[macro_use]
+pub mod weak;
+
+pub mod alloc;
+pub mod android;
+pub mod args;
+#[path = "../unix/cmath.rs"]
+pub mod cmath;
+pub mod env;
+pub mod fd;
+pub mod fs;
+pub mod futex;
+pub mod io;
+#[cfg(any(target_os = "linux", target_os = "android"))]
+pub mod kernel_copy;
+#[cfg(target_os = "l4re")]
+mod l4re;
+pub mod locks;
+pub mod memchr;
+#[cfg(not(target_os = "l4re"))]
+pub mod net;
+#[cfg(target_os = "l4re")]
+pub use self::l4re::net;
+pub mod os;
+pub mod os_str;
+pub mod path;
+pub mod pipe;
+pub mod process;
+pub mod rand;
+pub mod stack_overflow;
+pub mod stdio;
+pub mod thread;
+pub mod thread_local_dtor;
+pub mod thread_local_key;
+pub mod thread_parking;
+pub mod time;
+
+#[cfg(target_os = "espidf")]
+pub fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {}
+
+#[cfg(not(target_os = "espidf"))]
+// SAFETY: must be called only once during runtime initialization.
+// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
+// See `fn init()` in `library/std/src/rt.rs` for docs on `sigpipe`.
+pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
+    // The standard streams might be closed on application startup. To prevent
+    // std::io::{stdin, stdout,stderr} objects from using other unrelated file
+    // resources opened later, we reopen standards streams when they are closed.
+    sanitize_standard_fds();
+
+    // By default, some platforms will send a *signal* when an EPIPE error
+    // would otherwise be delivered. This runtime doesn't install a SIGPIPE
+    // handler, causing it to kill the program, which isn't exactly what we
+    // want!
+    //
+    // Hence, we set SIGPIPE to ignore when the program starts up in order
+    // to prevent this problem. Add `#[unix_sigpipe = "..."]` above `fn main()` to
+    // alter this behavior.
+    reset_sigpipe(sigpipe);
+
+    stack_overflow::init();
+    args::init(argc, argv);
+
+    // Normally, `thread::spawn` will call `Thread::set_name` but since this thread
+    // already exists, we have to call it ourselves. We only do this on macos
+    // because some unix-like operating systems such as Linux share process-id and
+    // thread-id for the main thread and so renaming the main thread will rename the
+    // process and we only want to enable this on platforms we've tested.
+    if cfg!(target_os = "macos") {
+        thread::Thread::set_name(&c"main");
+    }
+
+    unsafe fn sanitize_standard_fds() {
+        // fast path with a single syscall for systems with poll()
+        #[cfg(not(any(
+            miri,
+            target_os = "emscripten",
+            target_os = "fuchsia",
+            target_os = "vxworks",
+            // The poll on Darwin doesn't set POLLNVAL for closed fds.
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "watchos",
+            target_os = "redox",
+            target_os = "l4re",
+            target_os = "horizon",
+            target_os = "vita",
+        )))]
+        'poll: {
+            use crate::sys::os::errno;
+            #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
+            use libc::open as open64;
+            #[cfg(all(target_os = "linux", target_env = "gnu"))]
+            use libc::open64;
+            let pfds: &mut [_] = &mut [
+                libc::pollfd { fd: 0, events: 0, revents: 0 },
+                libc::pollfd { fd: 1, events: 0, revents: 0 },
+                libc::pollfd { fd: 2, events: 0, revents: 0 },
+            ];
+
+            while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 {
+                match errno() {
+                    libc::EINTR => continue,
+                    #[cfg(target_vendor = "unikraft")]
+                    libc::ENOSYS => {
+                        // Not all configurations of Unikraft enable `LIBPOSIX_EVENT`.
+                        break 'poll;
+                    }
+                    libc::EINVAL | libc::EAGAIN | libc::ENOMEM => {
+                        // RLIMIT_NOFILE or temporary allocation failures
+                        // may be preventing use of poll(), fall back to fcntl
+                        break 'poll;
+                    }
+                    _ => libc::abort(),
+                }
+            }
+            for pfd in pfds {
+                if pfd.revents & libc::POLLNVAL == 0 {
+                    continue;
+                }
+                if open64(c"/dev/null".as_ptr().cast(), libc::O_RDWR, 0) == -1 {
+                    // If the stream is closed but we failed to reopen it, abort the
+                    // process. Otherwise we wouldn't preserve the safety of
+                    // operations on the corresponding Rust object Stdin, Stdout, or
+                    // Stderr.
+                    libc::abort();
+                }
+            }
+            return;
+        }
+
+        // fallback in case poll isn't available or limited by RLIMIT_NOFILE
+        #[cfg(not(any(
+            // The standard fds are always available in Miri.
+            miri,
+            target_os = "emscripten",
+            target_os = "fuchsia",
+            target_os = "vxworks",
+            target_os = "l4re",
+            target_os = "horizon",
+            target_os = "vita",
+        )))]
+        {
+            use crate::sys::os::errno;
+            #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
+            use libc::open as open64;
+            #[cfg(all(target_os = "linux", target_env = "gnu"))]
+            use libc::open64;
+            for fd in 0..3 {
+                if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF {
+                    if open64(c"/dev/null".as_ptr().cast(), libc::O_RDWR, 0) == -1 {
+                        // If the stream is closed but we failed to reopen it, abort the
+                        // process. Otherwise we wouldn't preserve the safety of
+                        // operations on the corresponding Rust object Stdin, Stdout, or
+                        // Stderr.
+                        libc::abort();
+                    }
+                }
+            }
+        }
+    }
+
+    unsafe fn reset_sigpipe(#[allow(unused_variables)] sigpipe: u8) {
+        #[cfg(not(any(
+            target_os = "emscripten",
+            target_os = "fuchsia",
+            target_os = "horizon",
+            // Unikraft's `signal` implementation is currently broken:
+            // https://github.com/unikraft/lib-musl/issues/57
+            target_vendor = "unikraft",
+        )))]
+        {
+            // We don't want to add this as a public type to std, nor do we
+            // want to `include!` a file from the compiler (which would break
+            // Miri and xargo for example), so we choose to duplicate these
+            // constants from `compiler/rustc_session/src/config/sigpipe.rs`.
+            // See the other file for docs. NOTE: Make sure to keep them in
+            // sync!
+            mod sigpipe {
+                pub const DEFAULT: u8 = 0;
+                pub const INHERIT: u8 = 1;
+                pub const SIG_IGN: u8 = 2;
+                pub const SIG_DFL: u8 = 3;
+            }
+
+            let (sigpipe_attr_specified, handler) = match sigpipe {
+                sigpipe::DEFAULT => (false, Some(libc::SIG_IGN)),
+                sigpipe::INHERIT => (true, None),
+                sigpipe::SIG_IGN => (true, Some(libc::SIG_IGN)),
+                sigpipe::SIG_DFL => (true, Some(libc::SIG_DFL)),
+                _ => unreachable!(),
+            };
+            if sigpipe_attr_specified {
+                UNIX_SIGPIPE_ATTR_SPECIFIED.store(true, crate::sync::atomic::Ordering::Relaxed);
+            }
+            if let Some(handler) = handler {
+                rtassert!(signal(libc::SIGPIPE, handler) != libc::SIG_ERR);
+                #[cfg(target_os = "hurd")]
+                {
+                    rtassert!(signal(libc::SIGLOST, handler) != libc::SIG_ERR);
+                }
+            }
+        }
+    }
+}
+
+// This is set (up to once) in reset_sigpipe.
+#[cfg(not(any(
+    target_os = "espidf",
+    target_os = "emscripten",
+    target_os = "fuchsia",
+    target_os = "horizon",
+)))]
+static UNIX_SIGPIPE_ATTR_SPECIFIED: crate::sync::atomic::AtomicBool =
+    crate::sync::atomic::AtomicBool::new(false);
+
+#[cfg(not(any(
+    target_os = "espidf",
+    target_os = "emscripten",
+    target_os = "fuchsia",
+    target_os = "horizon",
+)))]
+pub(crate) fn unix_sigpipe_attr_specified() -> bool {
+    UNIX_SIGPIPE_ATTR_SPECIFIED.load(crate::sync::atomic::Ordering::Relaxed)
+}
+
+// SAFETY: must be called only once during runtime cleanup.
+// NOTE: this is not guaranteed to run, for example when the program aborts.
+pub unsafe fn cleanup() {
+    stack_overflow::cleanup();
+}
+
+#[cfg(target_os = "android")]
+pub use crate::sys::android::signal;
+#[allow(unused_imports)]
+#[cfg(not(target_os = "android"))]
+pub use libc::signal;
+
+#[inline]
+pub(crate) fn is_interrupted(errno: i32) -> bool {
+    errno == libc::EINTR
+}
+
+pub fn decode_error_kind(errno: i32) -> ErrorKind {
+    use ErrorKind::*;
+    match errno as libc::c_int {
+        libc::E2BIG => ArgumentListTooLong,
+        libc::EADDRINUSE => AddrInUse,
+        libc::EADDRNOTAVAIL => AddrNotAvailable,
+        libc::EBUSY => ResourceBusy,
+        libc::ECONNABORTED => ConnectionAborted,
+        libc::ECONNREFUSED => ConnectionRefused,
+        libc::ECONNRESET => ConnectionReset,
+        libc::EDEADLK => Deadlock,
+        libc::EDQUOT => FilesystemQuotaExceeded,
+        libc::EEXIST => AlreadyExists,
+        libc::EFBIG => FileTooLarge,
+        libc::EHOSTUNREACH => HostUnreachable,
+        libc::EINTR => Interrupted,
+        libc::EINVAL => InvalidInput,
+        libc::EISDIR => IsADirectory,
+        libc::ELOOP => FilesystemLoop,
+        libc::ENOENT => NotFound,
+        libc::ENOMEM => OutOfMemory,
+        libc::ENOSPC => StorageFull,
+        libc::ENOSYS => Unsupported,
+        libc::EMLINK => TooManyLinks,
+        libc::ENAMETOOLONG => InvalidFilename,
+        libc::ENETDOWN => NetworkDown,
+        libc::ENETUNREACH => NetworkUnreachable,
+        libc::ENOTCONN => NotConnected,
+        libc::ENOTDIR => NotADirectory,
+        #[cfg(not(target_os = "aix"))]
+        libc::ENOTEMPTY => DirectoryNotEmpty,
+        libc::EPIPE => BrokenPipe,
+        libc::EROFS => ReadOnlyFilesystem,
+        libc::ESPIPE => NotSeekable,
+        libc::ESTALE => StaleNetworkFileHandle,
+        libc::ETIMEDOUT => TimedOut,
+        libc::ETXTBSY => ExecutableFileBusy,
+        libc::EXDEV => CrossesDevices,
+
+        libc::EACCES | libc::EPERM => PermissionDenied,
+
+        // These two constants can have the same value on some systems,
+        // but different values on others, so we can't use a match
+        // clause
+        x if x == libc::EAGAIN || x == libc::EWOULDBLOCK => WouldBlock,
+
+        _ => Uncategorized,
+    }
+}
+
+#[doc(hidden)]
+pub trait IsMinusOne {
+    fn is_minus_one(&self) -> bool;
+}
+
+macro_rules! impl_is_minus_one {
+    ($($t:ident)*) => ($(impl IsMinusOne for $t {
+        fn is_minus_one(&self) -> bool {
+            *self == -1
+        }
+    })*)
+}
+
+impl_is_minus_one! { i8 i16 i32 i64 isize }
+
+pub fn cvt<T: IsMinusOne>(t: T) -> crate::io::Result<T> {
+    if t.is_minus_one() { Err(crate::io::Error::last_os_error()) } else { Ok(t) }
+}
+
+pub fn cvt_r<T, F>(mut f: F) -> crate::io::Result<T>
+where
+    T: IsMinusOne,
+    F: FnMut() -> T,
+{
+    loop {
+        match cvt(f()) {
+            Err(ref e) if e.is_interrupted() => {}
+            other => return other,
+        }
+    }
+}
+
+#[allow(dead_code)] // Not used on all platforms.
+pub fn cvt_nz(error: libc::c_int) -> crate::io::Result<()> {
+    if error == 0 { Ok(()) } else { Err(crate::io::Error::from_raw_os_error(error)) }
+}
+
+// libc::abort() will run the SIGABRT handler.  That's fine because anyone who
+// installs a SIGABRT handler already has to expect it to run in Very Bad
+// situations (eg, malloc crashing).
+//
+// Current glibc's abort() function unblocks SIGABRT, raises SIGABRT, clears the
+// SIGABRT handler and raises it again, and then starts to get creative.
+//
+// See the public documentation for `intrinsics::abort()` and `process::abort()`
+// for further discussion.
+//
+// There is confusion about whether libc::abort() flushes stdio streams.
+// libc::abort() is required by ISO C 99 (7.14.1.1p5) to be async-signal-safe,
+// so flushing streams is at least extremely hard, if not entirely impossible.
+//
+// However, some versions of POSIX (eg IEEE Std 1003.1-2001) required abort to
+// do so.  In 1003.1-2004 this was fixed.
+//
+// glibc's implementation did the flush, unsafely, before glibc commit
+// 91e7cf982d01 `abort: Do not flush stdio streams [BZ #15436]` by Florian
+// Weimer.  According to glibc's NEWS:
+//
+//    The abort function terminates the process immediately, without flushing
+//    stdio streams.  Previous glibc versions used to flush streams, resulting
+//    in deadlocks and further data corruption.  This change also affects
+//    process aborts as the result of assertion failures.
+//
+// This is an accurate description of the problem.  The only solution for
+// program with nontrivial use of C stdio is a fixed libc - one which does not
+// try to flush in abort - since even libc-internal errors, and assertion
+// failures generated from C, will go via abort().
+//
+// On systems with old, buggy, libcs, the impact can be severe for a
+// multithreaded C program.  It is much less severe for Rust, because Rust
+// stdlib doesn't use libc stdio buffering.  In a typical Rust program, which
+// does not use C stdio, even a buggy libc::abort() is, in fact, safe.
+pub fn abort_internal() -> ! {
+    unsafe { libc::abort() }
+}
+
+cfg_if::cfg_if! {
+    if #[cfg(target_os = "android")] {
+        #[link(name = "dl", kind = "static", modifiers = "-bundle",
+            cfg(target_feature = "crt-static"))]
+        #[link(name = "dl", cfg(not(target_feature = "crt-static")))]
+        #[link(name = "log", cfg(not(target_feature = "crt-static")))]
+        extern "C" {}
+    } else if #[cfg(target_os = "freebsd")] {
+        #[link(name = "execinfo")]
+        #[link(name = "pthread")]
+        extern "C" {}
+    } else if #[cfg(target_os = "netbsd")] {
+        #[link(name = "pthread")]
+        #[link(name = "rt")]
+        extern "C" {}
+    } else if #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))] {
+        #[link(name = "pthread")]
+        extern "C" {}
+    } else if #[cfg(target_os = "solaris")] {
+        #[link(name = "socket")]
+        #[link(name = "posix4")]
+        #[link(name = "pthread")]
+        #[link(name = "resolv")]
+        extern "C" {}
+    } else if #[cfg(target_os = "illumos")] {
+        #[link(name = "socket")]
+        #[link(name = "posix4")]
+        #[link(name = "pthread")]
+        #[link(name = "resolv")]
+        #[link(name = "nsl")]
+        // Use libumem for the (malloc-compatible) allocator
+        #[link(name = "umem")]
+        extern "C" {}
+    } else if #[cfg(target_os = "macos")] {
+        #[link(name = "System")]
+        extern "C" {}
+    } else if #[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos"))] {
+        #[link(name = "System")]
+        #[link(name = "objc")]
+        #[link(name = "Foundation", kind = "framework")]
+        extern "C" {}
+    } else if #[cfg(target_os = "fuchsia")] {
+        #[link(name = "zircon")]
+        #[link(name = "fdio")]
+        extern "C" {}
+    } else if #[cfg(all(target_os = "linux", target_env = "uclibc"))] {
+        #[link(name = "dl")]
+        extern "C" {}
+    } else if #[cfg(target_os = "vita")] {
+        #[link(name = "pthread", kind = "static", modifiers = "-bundle")]
+        extern "C" {}
+    }
+}
+
+#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
+mod unsupported {
+    use crate::io;
+
+    pub fn unsupported<T>() -> io::Result<T> {
+        Err(unsupported_err())
+    }
+
+    pub fn unsupported_err() -> io::Error {
+        io::const_io_error!(io::ErrorKind::Unsupported, "operation not supported on this platform",)
+    }
+}
diff --git a/library/std/src/sys/pal/unix/net.rs b/library/std/src/sys/pal/unix/net.rs
new file mode 100644
index 00000000000..ec861f9cb86
--- /dev/null
+++ b/library/std/src/sys/pal/unix/net.rs
@@ -0,0 +1,591 @@
+use crate::cmp;
+use crate::ffi::CStr;
+use crate::io::{self, BorrowedBuf, BorrowedCursor, IoSlice, IoSliceMut};
+use crate::mem;
+use crate::net::{Shutdown, SocketAddr};
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
+use crate::str;
+use crate::sys::fd::FileDesc;
+use crate::sys::unix::IsMinusOne;
+use crate::sys_common::net::{getsockopt, setsockopt, sockaddr_to_addr};
+use crate::sys_common::{AsInner, FromInner, IntoInner};
+use crate::time::{Duration, Instant};
+
+use libc::{c_int, c_void, size_t, sockaddr, socklen_t, MSG_PEEK};
+
+cfg_if::cfg_if! {
+    if #[cfg(target_vendor = "apple")] {
+        use libc::SO_LINGER_SEC as SO_LINGER;
+    } else {
+        use libc::SO_LINGER;
+    }
+}
+
+pub use crate::sys::{cvt, cvt_r};
+
+#[allow(unused_extern_crates)]
+pub extern crate libc as netc;
+
+pub type wrlen_t = size_t;
+
+pub struct Socket(FileDesc);
+
+pub fn init() {}
+
+pub fn cvt_gai(err: c_int) -> io::Result<()> {
+    if err == 0 {
+        return Ok(());
+    }
+
+    // We may need to trigger a glibc workaround. See on_resolver_failure() for details.
+    on_resolver_failure();
+
+    #[cfg(not(target_os = "espidf"))]
+    if err == libc::EAI_SYSTEM {
+        return Err(io::Error::last_os_error());
+    }
+
+    #[cfg(not(target_os = "espidf"))]
+    let detail = unsafe {
+        str::from_utf8(CStr::from_ptr(libc::gai_strerror(err)).to_bytes()).unwrap().to_owned()
+    };
+
+    #[cfg(target_os = "espidf")]
+    let detail = "";
+
+    Err(io::Error::new(
+        io::ErrorKind::Uncategorized,
+        &format!("failed to lookup address information: {detail}")[..],
+    ))
+}
+
+impl Socket {
+    pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result<Socket> {
+        let fam = match *addr {
+            SocketAddr::V4(..) => libc::AF_INET,
+            SocketAddr::V6(..) => libc::AF_INET6,
+        };
+        Socket::new_raw(fam, ty)
+    }
+
+    pub fn new_raw(fam: c_int, ty: c_int) -> io::Result<Socket> {
+        unsafe {
+            cfg_if::cfg_if! {
+                if #[cfg(any(
+                    target_os = "android",
+                    target_os = "dragonfly",
+                    target_os = "freebsd",
+                    target_os = "illumos",
+                    target_os = "hurd",
+                    target_os = "linux",
+                    target_os = "netbsd",
+                    target_os = "openbsd",
+                    target_os = "nto",
+                ))] {
+                    // On platforms that support it we pass the SOCK_CLOEXEC
+                    // flag to atomically create the socket and set it as
+                    // CLOEXEC. On Linux this was added in 2.6.27.
+                    let fd = cvt(libc::socket(fam, ty | libc::SOCK_CLOEXEC, 0))?;
+                    Ok(Socket(FileDesc::from_raw_fd(fd)))
+                } else {
+                    let fd = cvt(libc::socket(fam, ty, 0))?;
+                    let fd = FileDesc::from_raw_fd(fd);
+                    fd.set_cloexec()?;
+                    let socket = Socket(fd);
+
+                    // macOS and iOS use `SO_NOSIGPIPE` as a `setsockopt`
+                    // flag to disable `SIGPIPE` emission on socket.
+                    #[cfg(target_vendor = "apple")]
+                    setsockopt(&socket, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1)?;
+
+                    Ok(socket)
+                }
+            }
+        }
+    }
+
+    #[cfg(not(target_os = "vxworks"))]
+    pub fn new_pair(fam: c_int, ty: c_int) -> io::Result<(Socket, Socket)> {
+        unsafe {
+            let mut fds = [0, 0];
+
+            cfg_if::cfg_if! {
+                if #[cfg(any(
+                    target_os = "android",
+                    target_os = "dragonfly",
+                    target_os = "freebsd",
+                    target_os = "illumos",
+                    target_os = "linux",
+                    target_os = "hurd",
+                    target_os = "netbsd",
+                    target_os = "openbsd",
+                    target_os = "nto",
+                ))] {
+                    // Like above, set cloexec atomically
+                    cvt(libc::socketpair(fam, ty | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr()))?;
+                    Ok((Socket(FileDesc::from_raw_fd(fds[0])), Socket(FileDesc::from_raw_fd(fds[1]))))
+                } else {
+                    cvt(libc::socketpair(fam, ty, 0, fds.as_mut_ptr()))?;
+                    let a = FileDesc::from_raw_fd(fds[0]);
+                    let b = FileDesc::from_raw_fd(fds[1]);
+                    a.set_cloexec()?;
+                    b.set_cloexec()?;
+                    Ok((Socket(a), Socket(b)))
+                }
+            }
+        }
+    }
+
+    #[cfg(target_os = "vxworks")]
+    pub fn new_pair(_fam: c_int, _ty: c_int) -> io::Result<(Socket, Socket)> {
+        unimplemented!()
+    }
+
+    pub fn connect(&self, addr: &SocketAddr) -> io::Result<()> {
+        let (addr, len) = addr.into_inner();
+        loop {
+            let result = unsafe { libc::connect(self.as_raw_fd(), addr.as_ptr(), len) };
+            if result.is_minus_one() {
+                let err = crate::sys::os::errno();
+                match err {
+                    libc::EINTR => continue,
+                    libc::EISCONN => return Ok(()),
+                    _ => return Err(io::Error::from_raw_os_error(err)),
+                }
+            }
+            return Ok(());
+        }
+    }
+
+    pub fn connect_timeout(&self, addr: &SocketAddr, timeout: Duration) -> io::Result<()> {
+        self.set_nonblocking(true)?;
+        let r = unsafe {
+            let (addr, len) = addr.into_inner();
+            cvt(libc::connect(self.as_raw_fd(), addr.as_ptr(), len))
+        };
+        self.set_nonblocking(false)?;
+
+        match r {
+            Ok(_) => return Ok(()),
+            // there's no ErrorKind for EINPROGRESS :(
+            Err(ref e) if e.raw_os_error() == Some(libc::EINPROGRESS) => {}
+            Err(e) => return Err(e),
+        }
+
+        let mut pollfd = libc::pollfd { fd: self.as_raw_fd(), events: libc::POLLOUT, revents: 0 };
+
+        if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 {
+            return Err(io::const_io_error!(
+                io::ErrorKind::InvalidInput,
+                "cannot set a 0 duration timeout",
+            ));
+        }
+
+        let start = Instant::now();
+
+        loop {
+            let elapsed = start.elapsed();
+            if elapsed >= timeout {
+                return Err(io::const_io_error!(io::ErrorKind::TimedOut, "connection timed out"));
+            }
+
+            let timeout = timeout - elapsed;
+            let mut timeout = timeout
+                .as_secs()
+                .saturating_mul(1_000)
+                .saturating_add(timeout.subsec_nanos() as u64 / 1_000_000);
+            if timeout == 0 {
+                timeout = 1;
+            }
+
+            let timeout = cmp::min(timeout, c_int::MAX as u64) as c_int;
+
+            match unsafe { libc::poll(&mut pollfd, 1, timeout) } {
+                -1 => {
+                    let err = io::Error::last_os_error();
+                    if !err.is_interrupted() {
+                        return Err(err);
+                    }
+                }
+                0 => {}
+                _ => {
+                    // linux returns POLLOUT|POLLERR|POLLHUP for refused connections (!), so look
+                    // for POLLHUP rather than read readiness
+                    if pollfd.revents & libc::POLLHUP != 0 {
+                        let e = self.take_error()?.unwrap_or_else(|| {
+                            io::const_io_error!(
+                                io::ErrorKind::Uncategorized,
+                                "no error set after POLLHUP",
+                            )
+                        });
+                        return Err(e);
+                    }
+
+                    return Ok(());
+                }
+            }
+        }
+    }
+
+    pub fn accept(&self, storage: *mut sockaddr, len: *mut socklen_t) -> io::Result<Socket> {
+        // Unfortunately the only known way right now to accept a socket and
+        // atomically set the CLOEXEC flag is to use the `accept4` syscall on
+        // platforms that support it. On Linux, this was added in 2.6.28,
+        // glibc 2.10 and musl 0.9.5.
+        cfg_if::cfg_if! {
+            if #[cfg(any(
+                target_os = "android",
+                target_os = "dragonfly",
+                target_os = "freebsd",
+                target_os = "illumos",
+                target_os = "linux",
+                target_os = "hurd",
+                target_os = "netbsd",
+                target_os = "openbsd",
+            ))] {
+                unsafe {
+                    let fd = cvt_r(|| libc::accept4(self.as_raw_fd(), storage, len, libc::SOCK_CLOEXEC))?;
+                    Ok(Socket(FileDesc::from_raw_fd(fd)))
+                }
+            } else {
+                unsafe {
+                    let fd = cvt_r(|| libc::accept(self.as_raw_fd(), storage, len))?;
+                    let fd = FileDesc::from_raw_fd(fd);
+                    fd.set_cloexec()?;
+                    Ok(Socket(fd))
+                }
+            }
+        }
+    }
+
+    pub fn duplicate(&self) -> io::Result<Socket> {
+        self.0.duplicate().map(Socket)
+    }
+
+    fn recv_with_flags(&self, mut buf: BorrowedCursor<'_>, flags: c_int) -> io::Result<()> {
+        let ret = cvt(unsafe {
+            libc::recv(
+                self.as_raw_fd(),
+                buf.as_mut().as_mut_ptr() as *mut c_void,
+                buf.capacity(),
+                flags,
+            )
+        })?;
+        unsafe {
+            buf.advance(ret as usize);
+        }
+        Ok(())
+    }
+
+    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
+        let mut buf = BorrowedBuf::from(buf);
+        self.recv_with_flags(buf.unfilled(), 0)?;
+        Ok(buf.len())
+    }
+
+    pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
+        let mut buf = BorrowedBuf::from(buf);
+        self.recv_with_flags(buf.unfilled(), MSG_PEEK)?;
+        Ok(buf.len())
+    }
+
+    pub fn read_buf(&self, buf: BorrowedCursor<'_>) -> io::Result<()> {
+        self.recv_with_flags(buf, 0)
+    }
+
+    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+        self.0.read_vectored(bufs)
+    }
+
+    #[inline]
+    pub fn is_read_vectored(&self) -> bool {
+        self.0.is_read_vectored()
+    }
+
+    fn recv_from_with_flags(
+        &self,
+        buf: &mut [u8],
+        flags: c_int,
+    ) -> io::Result<(usize, SocketAddr)> {
+        let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
+        let mut addrlen = mem::size_of_val(&storage) as libc::socklen_t;
+
+        let n = cvt(unsafe {
+            libc::recvfrom(
+                self.as_raw_fd(),
+                buf.as_mut_ptr() as *mut c_void,
+                buf.len(),
+                flags,
+                &mut storage as *mut _ as *mut _,
+                &mut addrlen,
+            )
+        })?;
+        Ok((n as usize, sockaddr_to_addr(&storage, addrlen as usize)?))
+    }
+
+    pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
+        self.recv_from_with_flags(buf, 0)
+    }
+
+    #[cfg(any(target_os = "android", target_os = "linux"))]
+    pub fn recv_msg(&self, msg: &mut libc::msghdr) -> io::Result<usize> {
+        let n = cvt(unsafe { libc::recvmsg(self.as_raw_fd(), msg, libc::MSG_CMSG_CLOEXEC) })?;
+        Ok(n as usize)
+    }
+
+    pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
+        self.recv_from_with_flags(buf, MSG_PEEK)
+    }
+
+    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
+        self.0.write(buf)
+    }
+
+    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+        self.0.write_vectored(bufs)
+    }
+
+    #[inline]
+    pub fn is_write_vectored(&self) -> bool {
+        self.0.is_write_vectored()
+    }
+
+    #[cfg(any(target_os = "android", target_os = "linux"))]
+    pub fn send_msg(&self, msg: &mut libc::msghdr) -> io::Result<usize> {
+        let n = cvt(unsafe { libc::sendmsg(self.as_raw_fd(), msg, 0) })?;
+        Ok(n as usize)
+    }
+
+    pub fn set_timeout(&self, dur: Option<Duration>, kind: libc::c_int) -> io::Result<()> {
+        let timeout = match dur {
+            Some(dur) => {
+                if dur.as_secs() == 0 && dur.subsec_nanos() == 0 {
+                    return Err(io::const_io_error!(
+                        io::ErrorKind::InvalidInput,
+                        "cannot set a 0 duration timeout",
+                    ));
+                }
+
+                let secs = if dur.as_secs() > libc::time_t::MAX as u64 {
+                    libc::time_t::MAX
+                } else {
+                    dur.as_secs() as libc::time_t
+                };
+                let mut timeout = libc::timeval {
+                    tv_sec: secs,
+                    tv_usec: dur.subsec_micros() as libc::suseconds_t,
+                };
+                if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
+                    timeout.tv_usec = 1;
+                }
+                timeout
+            }
+            None => libc::timeval { tv_sec: 0, tv_usec: 0 },
+        };
+        setsockopt(self, libc::SOL_SOCKET, kind, timeout)
+    }
+
+    pub fn timeout(&self, kind: libc::c_int) -> io::Result<Option<Duration>> {
+        let raw: libc::timeval = getsockopt(self, libc::SOL_SOCKET, kind)?;
+        if raw.tv_sec == 0 && raw.tv_usec == 0 {
+            Ok(None)
+        } else {
+            let sec = raw.tv_sec as u64;
+            let nsec = (raw.tv_usec as u32) * 1000;
+            Ok(Some(Duration::new(sec, nsec)))
+        }
+    }
+
+    pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
+        let how = match how {
+            Shutdown::Write => libc::SHUT_WR,
+            Shutdown::Read => libc::SHUT_RD,
+            Shutdown::Both => libc::SHUT_RDWR,
+        };
+        cvt(unsafe { libc::shutdown(self.as_raw_fd(), how) })?;
+        Ok(())
+    }
+
+    pub fn set_linger(&self, linger: Option<Duration>) -> io::Result<()> {
+        let linger = libc::linger {
+            l_onoff: linger.is_some() as libc::c_int,
+            l_linger: linger.unwrap_or_default().as_secs() as libc::c_int,
+        };
+
+        setsockopt(self, libc::SOL_SOCKET, SO_LINGER, linger)
+    }
+
+    pub fn linger(&self) -> io::Result<Option<Duration>> {
+        let val: libc::linger = getsockopt(self, libc::SOL_SOCKET, SO_LINGER)?;
+
+        Ok((val.l_onoff != 0).then(|| Duration::from_secs(val.l_linger as u64)))
+    }
+
+    pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> {
+        setsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY, nodelay as c_int)
+    }
+
+    pub fn nodelay(&self) -> io::Result<bool> {
+        let raw: c_int = getsockopt(self, libc::IPPROTO_TCP, libc::TCP_NODELAY)?;
+        Ok(raw != 0)
+    }
+
+    #[cfg(any(target_os = "android", target_os = "linux",))]
+    pub fn set_quickack(&self, quickack: bool) -> io::Result<()> {
+        setsockopt(self, libc::IPPROTO_TCP, libc::TCP_QUICKACK, quickack as c_int)
+    }
+
+    #[cfg(any(target_os = "android", target_os = "linux",))]
+    pub fn quickack(&self) -> io::Result<bool> {
+        let raw: c_int = getsockopt(self, libc::IPPROTO_TCP, libc::TCP_QUICKACK)?;
+        Ok(raw != 0)
+    }
+
+    #[cfg(any(target_os = "android", target_os = "linux",))]
+    pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
+        setsockopt(self, libc::SOL_SOCKET, libc::SO_PASSCRED, passcred as libc::c_int)
+    }
+
+    #[cfg(any(target_os = "android", target_os = "linux",))]
+    pub fn passcred(&self) -> io::Result<bool> {
+        let passcred: libc::c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_PASSCRED)?;
+        Ok(passcred != 0)
+    }
+
+    #[cfg(target_os = "netbsd")]
+    pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
+        setsockopt(self, 0 as libc::c_int, libc::LOCAL_CREDS, passcred as libc::c_int)
+    }
+
+    #[cfg(target_os = "netbsd")]
+    pub fn passcred(&self) -> io::Result<bool> {
+        let passcred: libc::c_int = getsockopt(self, 0 as libc::c_int, libc::LOCAL_CREDS)?;
+        Ok(passcred != 0)
+    }
+
+    #[cfg(target_os = "freebsd")]
+    pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
+        setsockopt(self, libc::AF_LOCAL, libc::LOCAL_CREDS_PERSISTENT, passcred as libc::c_int)
+    }
+
+    #[cfg(target_os = "freebsd")]
+    pub fn passcred(&self) -> io::Result<bool> {
+        let passcred: libc::c_int = getsockopt(self, libc::AF_LOCAL, libc::LOCAL_CREDS_PERSISTENT)?;
+        Ok(passcred != 0)
+    }
+
+    #[cfg(not(any(target_os = "solaris", target_os = "illumos", target_os = "vita")))]
+    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
+        let mut nonblocking = nonblocking as libc::c_int;
+        cvt(unsafe { libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &mut nonblocking) }).map(drop)
+    }
+
+    #[cfg(target_os = "vita")]
+    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
+        let option = nonblocking as libc::c_int;
+        setsockopt(self, libc::SOL_SOCKET, libc::SO_NONBLOCK, option)
+    }
+
+    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
+        // FIONBIO is inadequate for sockets on illumos/Solaris, so use the
+        // fcntl(F_[GS]ETFL)-based method provided by FileDesc instead.
+        self.0.set_nonblocking(nonblocking)
+    }
+
+    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
+    pub fn set_mark(&self, mark: u32) -> io::Result<()> {
+        #[cfg(target_os = "linux")]
+        let option = libc::SO_MARK;
+        #[cfg(target_os = "freebsd")]
+        let option = libc::SO_USER_COOKIE;
+        #[cfg(target_os = "openbsd")]
+        let option = libc::SO_RTABLE;
+        setsockopt(self, libc::SOL_SOCKET, option, mark as libc::c_int)
+    }
+
+    pub fn take_error(&self) -> io::Result<Option<io::Error>> {
+        let raw: c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_ERROR)?;
+        if raw == 0 { Ok(None) } else { Ok(Some(io::Error::from_raw_os_error(raw as i32))) }
+    }
+
+    // This is used by sys_common code to abstract over Windows and Unix.
+    pub fn as_raw(&self) -> RawFd {
+        self.as_raw_fd()
+    }
+}
+
+impl AsInner<FileDesc> for Socket {
+    #[inline]
+    fn as_inner(&self) -> &FileDesc {
+        &self.0
+    }
+}
+
+impl IntoInner<FileDesc> for Socket {
+    fn into_inner(self) -> FileDesc {
+        self.0
+    }
+}
+
+impl FromInner<FileDesc> for Socket {
+    fn from_inner(file_desc: FileDesc) -> Self {
+        Self(file_desc)
+    }
+}
+
+impl AsFd for Socket {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+
+impl AsRawFd for Socket {
+    #[inline]
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+
+impl IntoRawFd for Socket {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+
+impl FromRawFd for Socket {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FromRawFd::from_raw_fd(raw_fd))
+    }
+}
+
+// In versions of glibc prior to 2.26, there's a bug where the DNS resolver
+// will cache the contents of /etc/resolv.conf, so changes to that file on disk
+// can be ignored by a long-running program. That can break DNS lookups on e.g.
+// laptops where the network comes and goes. See
+// https://sourceware.org/bugzilla/show_bug.cgi?id=984. Note however that some
+// distros including Debian have patched glibc to fix this for a long time.
+//
+// A workaround for this bug is to call the res_init libc function, to clear
+// the cached configs. Unfortunately, while we believe glibc's implementation
+// of res_init is thread-safe, we know that other implementations are not
+// (https://github.com/rust-lang/rust/issues/43592). Code here in std could
+// try to synchronize its res_init calls with a Mutex, but that wouldn't
+// protect programs that call into libc in other ways. So instead of calling
+// res_init unconditionally, we call it only when we detect we're linking
+// against glibc version < 2.26. (That is, when we both know its needed and
+// believe it's thread-safe).
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+fn on_resolver_failure() {
+    use crate::sys;
+
+    // If the version fails to parse, we treat it the same as "not glibc".
+    if let Some(version) = sys::os::glibc_version() {
+        if version < (2, 26) {
+            unsafe { libc::res_init() };
+        }
+    }
+}
+
+#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
+fn on_resolver_failure() {}
diff --git a/library/std/src/sys/pal/unix/os.rs b/library/std/src/sys/pal/unix/os.rs
new file mode 100644
index 00000000000..881b3a25c51
--- /dev/null
+++ b/library/std/src/sys/pal/unix/os.rs
@@ -0,0 +1,783 @@
+//! Implementation of `std::os` functionality for unix systems
+
+#![allow(unused_imports)] // lots of cfg code here
+
+#[cfg(test)]
+mod tests;
+
+use crate::os::unix::prelude::*;
+
+use crate::error::Error as StdError;
+use crate::ffi::{CStr, CString, OsStr, OsString};
+use crate::fmt;
+use crate::io;
+use crate::iter;
+use crate::mem;
+use crate::path::{self, PathBuf};
+use crate::ptr;
+use crate::slice;
+use crate::str;
+use crate::sync::{PoisonError, RwLock};
+use crate::sys::common::small_c_string::{run_path_with_cstr, run_with_cstr};
+use crate::sys::cvt;
+use crate::sys::fd;
+use crate::sys::memchr;
+use crate::vec;
+
+#[cfg(all(target_env = "gnu", not(target_os = "vxworks")))]
+use crate::sys::weak::weak;
+
+use libc::{c_char, c_int, c_void};
+
+const TMPBUF_SZ: usize = 128;
+
+cfg_if::cfg_if! {
+    if #[cfg(target_os = "redox")] {
+        const PATH_SEPARATOR: u8 = b';';
+    } else {
+        const PATH_SEPARATOR: u8 = b':';
+    }
+}
+
+extern "C" {
+    #[cfg(not(any(target_os = "dragonfly", target_os = "vxworks")))]
+    #[cfg_attr(
+        any(
+            target_os = "linux",
+            target_os = "emscripten",
+            target_os = "fuchsia",
+            target_os = "l4re",
+            target_os = "hurd",
+        ),
+        link_name = "__errno_location"
+    )]
+    #[cfg_attr(
+        any(
+            target_os = "netbsd",
+            target_os = "openbsd",
+            target_os = "android",
+            target_os = "redox",
+            target_env = "newlib"
+        ),
+        link_name = "__errno"
+    )]
+    #[cfg_attr(any(target_os = "solaris", target_os = "illumos"), link_name = "___errno")]
+    #[cfg_attr(target_os = "nto", link_name = "__get_errno_ptr")]
+    #[cfg_attr(
+        any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "freebsd",
+            target_os = "watchos"
+        ),
+        link_name = "__error"
+    )]
+    #[cfg_attr(target_os = "haiku", link_name = "_errnop")]
+    #[cfg_attr(target_os = "aix", link_name = "_Errno")]
+    fn errno_location() -> *mut c_int;
+}
+
+/// Returns the platform-specific value of errno
+#[cfg(not(any(target_os = "dragonfly", target_os = "vxworks")))]
+pub fn errno() -> i32 {
+    unsafe { (*errno_location()) as i32 }
+}
+
+/// Sets the platform-specific value of errno
+#[cfg(all(not(target_os = "dragonfly"), not(target_os = "vxworks")))] // needed for readdir and syscall!
+#[allow(dead_code)] // but not all target cfgs actually end up using it
+pub fn set_errno(e: i32) {
+    unsafe { *errno_location() = e as c_int }
+}
+
+#[cfg(target_os = "vxworks")]
+pub fn errno() -> i32 {
+    unsafe { libc::errnoGet() }
+}
+
+#[cfg(target_os = "dragonfly")]
+pub fn errno() -> i32 {
+    extern "C" {
+        #[thread_local]
+        static errno: c_int;
+    }
+
+    unsafe { errno as i32 }
+}
+
+#[cfg(target_os = "dragonfly")]
+#[allow(dead_code)]
+pub fn set_errno(e: i32) {
+    extern "C" {
+        #[thread_local]
+        static mut errno: c_int;
+    }
+
+    unsafe {
+        errno = e;
+    }
+}
+
+/// Gets a detailed string description for the given error number.
+pub fn error_string(errno: i32) -> String {
+    extern "C" {
+        #[cfg_attr(
+            all(
+                any(target_os = "linux", target_os = "hurd", target_env = "newlib"),
+                not(target_env = "ohos")
+            ),
+            link_name = "__xpg_strerror_r"
+        )]
+        fn strerror_r(errnum: c_int, buf: *mut c_char, buflen: libc::size_t) -> c_int;
+    }
+
+    let mut buf = [0 as c_char; TMPBUF_SZ];
+
+    let p = buf.as_mut_ptr();
+    unsafe {
+        if strerror_r(errno as c_int, p, buf.len()) < 0 {
+            panic!("strerror_r failure");
+        }
+
+        let p = p as *const _;
+        // We can't always expect a UTF-8 environment. When we don't get that luxury,
+        // it's better to give a low-quality error message than none at all.
+        String::from_utf8_lossy(CStr::from_ptr(p).to_bytes()).into()
+    }
+}
+
+#[cfg(target_os = "espidf")]
+pub fn getcwd() -> io::Result<PathBuf> {
+    Ok(PathBuf::from("/"))
+}
+
+#[cfg(not(target_os = "espidf"))]
+pub fn getcwd() -> io::Result<PathBuf> {
+    let mut buf = Vec::with_capacity(512);
+    loop {
+        unsafe {
+            let ptr = buf.as_mut_ptr() as *mut libc::c_char;
+            if !libc::getcwd(ptr, buf.capacity()).is_null() {
+                let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_bytes().len();
+                buf.set_len(len);
+                buf.shrink_to_fit();
+                return Ok(PathBuf::from(OsString::from_vec(buf)));
+            } else {
+                let error = io::Error::last_os_error();
+                if error.raw_os_error() != Some(libc::ERANGE) {
+                    return Err(error);
+                }
+            }
+
+            // Trigger the internal buffer resizing logic of `Vec` by requiring
+            // more space than the current capacity.
+            let cap = buf.capacity();
+            buf.set_len(cap);
+            buf.reserve(1);
+        }
+    }
+}
+
+#[cfg(target_os = "espidf")]
+pub fn chdir(_p: &path::Path) -> io::Result<()> {
+    super::unsupported::unsupported()
+}
+
+#[cfg(not(target_os = "espidf"))]
+pub fn chdir(p: &path::Path) -> io::Result<()> {
+    let result = run_path_with_cstr(p, |p| unsafe { Ok(libc::chdir(p.as_ptr())) })?;
+    if result == 0 { Ok(()) } else { Err(io::Error::last_os_error()) }
+}
+
+pub struct SplitPaths<'a> {
+    iter: iter::Map<slice::Split<'a, u8, fn(&u8) -> bool>, fn(&'a [u8]) -> PathBuf>,
+}
+
+pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
+    fn bytes_to_path(b: &[u8]) -> PathBuf {
+        PathBuf::from(<OsStr as OsStrExt>::from_bytes(b))
+    }
+    fn is_separator(b: &u8) -> bool {
+        *b == PATH_SEPARATOR
+    }
+    let unparsed = unparsed.as_bytes();
+    SplitPaths {
+        iter: unparsed
+            .split(is_separator as fn(&u8) -> bool)
+            .map(bytes_to_path as fn(&[u8]) -> PathBuf),
+    }
+}
+
+impl<'a> Iterator for SplitPaths<'a> {
+    type Item = PathBuf;
+    fn next(&mut self) -> Option<PathBuf> {
+        self.iter.next()
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+#[derive(Debug)]
+pub struct JoinPathsError;
+
+pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
+where
+    I: Iterator<Item = T>,
+    T: AsRef<OsStr>,
+{
+    let mut joined = Vec::new();
+
+    for (i, path) in paths.enumerate() {
+        let path = path.as_ref().as_bytes();
+        if i > 0 {
+            joined.push(PATH_SEPARATOR)
+        }
+        if path.contains(&PATH_SEPARATOR) {
+            return Err(JoinPathsError);
+        }
+        joined.extend_from_slice(path);
+    }
+    Ok(OsStringExt::from_vec(joined))
+}
+
+impl fmt::Display for JoinPathsError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "path segment contains separator `{}`", char::from(PATH_SEPARATOR))
+    }
+}
+
+impl StdError for JoinPathsError {
+    #[allow(deprecated)]
+    fn description(&self) -> &str {
+        "failed to join paths"
+    }
+}
+
+#[cfg(target_os = "aix")]
+pub fn current_exe() -> io::Result<PathBuf> {
+    use crate::io::ErrorKind;
+
+    #[cfg(test)]
+    use realstd::env;
+
+    #[cfg(not(test))]
+    use crate::env;
+
+    let exe_path = env::args().next().ok_or(io::const_io_error!(
+        ErrorKind::NotFound,
+        "an executable path was not found because no arguments were provided through argv"
+    ))?;
+    let path = PathBuf::from(exe_path);
+    if path.is_absolute() {
+        return path.canonicalize();
+    }
+    // Search PWD to infer current_exe.
+    if let Some(pstr) = path.to_str()
+        && pstr.contains("/")
+    {
+        return getcwd().map(|cwd| cwd.join(path))?.canonicalize();
+    }
+    // Search PATH to infer current_exe.
+    if let Some(p) = getenv(OsStr::from_bytes("PATH".as_bytes())) {
+        for search_path in split_paths(&p) {
+            let pb = search_path.join(&path);
+            if pb.is_file()
+                && let Ok(metadata) = crate::fs::metadata(&pb)
+                && metadata.permissions().mode() & 0o111 != 0
+            {
+                return pb.canonicalize();
+            }
+        }
+    }
+    Err(io::const_io_error!(ErrorKind::NotFound, "an executable path was not found"))
+}
+
+#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
+pub fn current_exe() -> io::Result<PathBuf> {
+    unsafe {
+        let mut mib = [
+            libc::CTL_KERN as c_int,
+            libc::KERN_PROC as c_int,
+            libc::KERN_PROC_PATHNAME as c_int,
+            -1 as c_int,
+        ];
+        let mut sz = 0;
+        cvt(libc::sysctl(
+            mib.as_mut_ptr(),
+            mib.len() as libc::c_uint,
+            ptr::null_mut(),
+            &mut sz,
+            ptr::null_mut(),
+            0,
+        ))?;
+        if sz == 0 {
+            return Err(io::Error::last_os_error());
+        }
+        let mut v: Vec<u8> = Vec::with_capacity(sz);
+        cvt(libc::sysctl(
+            mib.as_mut_ptr(),
+            mib.len() as libc::c_uint,
+            v.as_mut_ptr() as *mut libc::c_void,
+            &mut sz,
+            ptr::null_mut(),
+            0,
+        ))?;
+        if sz == 0 {
+            return Err(io::Error::last_os_error());
+        }
+        v.set_len(sz - 1); // chop off trailing NUL
+        Ok(PathBuf::from(OsString::from_vec(v)))
+    }
+}
+
+#[cfg(target_os = "netbsd")]
+pub fn current_exe() -> io::Result<PathBuf> {
+    fn sysctl() -> io::Result<PathBuf> {
+        unsafe {
+            let mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, -1, libc::KERN_PROC_PATHNAME];
+            let mut path_len: usize = 0;
+            cvt(libc::sysctl(
+                mib.as_ptr(),
+                mib.len() as libc::c_uint,
+                ptr::null_mut(),
+                &mut path_len,
+                ptr::null(),
+                0,
+            ))?;
+            if path_len <= 1 {
+                return Err(io::const_io_error!(
+                    io::ErrorKind::Uncategorized,
+                    "KERN_PROC_PATHNAME sysctl returned zero-length string",
+                ));
+            }
+            let mut path: Vec<u8> = Vec::with_capacity(path_len);
+            cvt(libc::sysctl(
+                mib.as_ptr(),
+                mib.len() as libc::c_uint,
+                path.as_ptr() as *mut libc::c_void,
+                &mut path_len,
+                ptr::null(),
+                0,
+            ))?;
+            path.set_len(path_len - 1); // chop off NUL
+            Ok(PathBuf::from(OsString::from_vec(path)))
+        }
+    }
+    fn procfs() -> io::Result<PathBuf> {
+        let curproc_exe = path::Path::new("/proc/curproc/exe");
+        if curproc_exe.is_file() {
+            return crate::fs::read_link(curproc_exe);
+        }
+        Err(io::const_io_error!(
+            io::ErrorKind::Uncategorized,
+            "/proc/curproc/exe doesn't point to regular file.",
+        ))
+    }
+    sysctl().or_else(|_| procfs())
+}
+
+#[cfg(target_os = "openbsd")]
+pub fn current_exe() -> io::Result<PathBuf> {
+    unsafe {
+        let mut mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, libc::getpid(), libc::KERN_PROC_ARGV];
+        let mib = mib.as_mut_ptr();
+        let mut argv_len = 0;
+        cvt(libc::sysctl(mib, 4, ptr::null_mut(), &mut argv_len, ptr::null_mut(), 0))?;
+        let mut argv = Vec::<*const libc::c_char>::with_capacity(argv_len as usize);
+        cvt(libc::sysctl(mib, 4, argv.as_mut_ptr() as *mut _, &mut argv_len, ptr::null_mut(), 0))?;
+        argv.set_len(argv_len as usize);
+        if argv[0].is_null() {
+            return Err(io::const_io_error!(
+                io::ErrorKind::Uncategorized,
+                "no current exe available",
+            ));
+        }
+        let argv0 = CStr::from_ptr(argv[0]).to_bytes();
+        if argv0[0] == b'.' || argv0.iter().any(|b| *b == b'/') {
+            crate::fs::canonicalize(OsStr::from_bytes(argv0))
+        } else {
+            Ok(PathBuf::from(OsStr::from_bytes(argv0)))
+        }
+    }
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "hurd",
+    target_os = "android",
+    target_os = "emscripten"
+))]
+pub fn current_exe() -> io::Result<PathBuf> {
+    match crate::fs::read_link("/proc/self/exe") {
+        Err(ref e) if e.kind() == io::ErrorKind::NotFound => Err(io::const_io_error!(
+            io::ErrorKind::Uncategorized,
+            "no /proc/self/exe available. Is /proc mounted?",
+        )),
+        other => other,
+    }
+}
+
+#[cfg(target_os = "nto")]
+pub fn current_exe() -> io::Result<PathBuf> {
+    let mut e = crate::fs::read("/proc/self/exefile")?;
+    // Current versions of QNX Neutrino provide a null-terminated path.
+    // Ensure the trailing null byte is not returned here.
+    if let Some(0) = e.last() {
+        e.pop();
+    }
+    Ok(PathBuf::from(OsString::from_vec(e)))
+}
+
+#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))]
+pub fn current_exe() -> io::Result<PathBuf> {
+    unsafe {
+        let mut sz: u32 = 0;
+        libc::_NSGetExecutablePath(ptr::null_mut(), &mut sz);
+        if sz == 0 {
+            return Err(io::Error::last_os_error());
+        }
+        let mut v: Vec<u8> = Vec::with_capacity(sz as usize);
+        let err = libc::_NSGetExecutablePath(v.as_mut_ptr() as *mut i8, &mut sz);
+        if err != 0 {
+            return Err(io::Error::last_os_error());
+        }
+        v.set_len(sz as usize - 1); // chop off trailing NUL
+        Ok(PathBuf::from(OsString::from_vec(v)))
+    }
+}
+
+#[cfg(any(target_os = "solaris", target_os = "illumos"))]
+pub fn current_exe() -> io::Result<PathBuf> {
+    if let Ok(path) = crate::fs::read_link("/proc/self/path/a.out") {
+        Ok(path)
+    } else {
+        unsafe {
+            let path = libc::getexecname();
+            if path.is_null() {
+                Err(io::Error::last_os_error())
+            } else {
+                let filename = CStr::from_ptr(path).to_bytes();
+                let path = PathBuf::from(<OsStr as OsStrExt>::from_bytes(filename));
+
+                // Prepend a current working directory to the path if
+                // it doesn't contain an absolute pathname.
+                if filename[0] == b'/' { Ok(path) } else { getcwd().map(|cwd| cwd.join(path)) }
+            }
+        }
+    }
+}
+
+#[cfg(target_os = "haiku")]
+pub fn current_exe() -> io::Result<PathBuf> {
+    unsafe {
+        let mut info: mem::MaybeUninit<libc::image_info> = mem::MaybeUninit::uninit();
+        let mut cookie: i32 = 0;
+        // the executable can be found at team id 0
+        let result = libc::_get_next_image_info(
+            0,
+            &mut cookie,
+            info.as_mut_ptr(),
+            mem::size_of::<libc::image_info>(),
+        );
+        if result != 0 {
+            use crate::io::ErrorKind;
+            Err(io::const_io_error!(ErrorKind::Uncategorized, "Error getting executable path"))
+        } else {
+            let name = CStr::from_ptr((*info.as_ptr()).name.as_ptr()).to_bytes();
+            Ok(PathBuf::from(OsStr::from_bytes(name)))
+        }
+    }
+}
+
+#[cfg(target_os = "redox")]
+pub fn current_exe() -> io::Result<PathBuf> {
+    crate::fs::read_to_string("sys:exe").map(PathBuf::from)
+}
+
+#[cfg(target_os = "l4re")]
+pub fn current_exe() -> io::Result<PathBuf> {
+    use crate::io::ErrorKind;
+    Err(io::const_io_error!(ErrorKind::Unsupported, "Not yet implemented!"))
+}
+
+#[cfg(target_os = "vxworks")]
+pub fn current_exe() -> io::Result<PathBuf> {
+    #[cfg(test)]
+    use realstd::env;
+
+    #[cfg(not(test))]
+    use crate::env;
+
+    let exe_path = env::args().next().unwrap();
+    let path = path::Path::new(&exe_path);
+    path.canonicalize()
+}
+
+#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
+pub fn current_exe() -> io::Result<PathBuf> {
+    super::unsupported::unsupported()
+}
+
+#[cfg(target_os = "fuchsia")]
+pub fn current_exe() -> io::Result<PathBuf> {
+    use crate::io::ErrorKind;
+
+    #[cfg(test)]
+    use realstd::env;
+
+    #[cfg(not(test))]
+    use crate::env;
+
+    let exe_path = env::args().next().ok_or(io::const_io_error!(
+        ErrorKind::Uncategorized,
+        "an executable path was not found because no arguments were provided through argv"
+    ))?;
+    let path = PathBuf::from(exe_path);
+
+    // Prepend the current working directory to the path if it's not absolute.
+    if !path.is_absolute() { getcwd().map(|cwd| cwd.join(path)) } else { Ok(path) }
+}
+
+pub struct Env {
+    iter: vec::IntoIter<(OsString, OsString)>,
+}
+
+// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when <OsStr as Debug>::fmt matches <str as Debug>::fmt.
+pub struct EnvStrDebug<'a> {
+    slice: &'a [(OsString, OsString)],
+}
+
+impl fmt::Debug for EnvStrDebug<'_> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let Self { slice } = self;
+        f.debug_list()
+            .entries(slice.iter().map(|(a, b)| (a.to_str().unwrap(), b.to_str().unwrap())))
+            .finish()
+    }
+}
+
+impl Env {
+    pub fn str_debug(&self) -> impl fmt::Debug + '_ {
+        let Self { iter } = self;
+        EnvStrDebug { slice: iter.as_slice() }
+    }
+}
+
+impl fmt::Debug for Env {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let Self { iter } = self;
+        f.debug_list().entries(iter.as_slice()).finish()
+    }
+}
+
+impl !Send for Env {}
+impl !Sync for Env {}
+
+impl Iterator for Env {
+    type Item = (OsString, OsString);
+    fn next(&mut self) -> Option<(OsString, OsString)> {
+        self.iter.next()
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+#[cfg(target_os = "macos")]
+pub unsafe fn environ() -> *mut *const *const c_char {
+    libc::_NSGetEnviron() as *mut *const *const c_char
+}
+
+#[cfg(not(target_os = "macos"))]
+pub unsafe fn environ() -> *mut *const *const c_char {
+    extern "C" {
+        static mut environ: *const *const c_char;
+    }
+    ptr::addr_of_mut!(environ)
+}
+
+static ENV_LOCK: RwLock<()> = RwLock::new(());
+
+pub fn env_read_lock() -> impl Drop {
+    ENV_LOCK.read().unwrap_or_else(PoisonError::into_inner)
+}
+
+/// Returns a vector of (variable, value) byte-vector pairs for all the
+/// environment variables of the current process.
+pub fn env() -> Env {
+    unsafe {
+        let _guard = env_read_lock();
+        let mut environ = *environ();
+        let mut result = Vec::new();
+        if !environ.is_null() {
+            while !(*environ).is_null() {
+                if let Some(key_value) = parse(CStr::from_ptr(*environ).to_bytes()) {
+                    result.push(key_value);
+                }
+                environ = environ.add(1);
+            }
+        }
+        return Env { iter: result.into_iter() };
+    }
+
+    fn parse(input: &[u8]) -> Option<(OsString, OsString)> {
+        // Strategy (copied from glibc): Variable name and value are separated
+        // by an ASCII equals sign '='. Since a variable name must not be
+        // empty, allow variable names starting with an equals sign. Skip all
+        // malformed lines.
+        if input.is_empty() {
+            return None;
+        }
+        let pos = memchr::memchr(b'=', &input[1..]).map(|p| p + 1);
+        pos.map(|p| {
+            (
+                OsStringExt::from_vec(input[..p].to_vec()),
+                OsStringExt::from_vec(input[p + 1..].to_vec()),
+            )
+        })
+    }
+}
+
+pub fn getenv(k: &OsStr) -> Option<OsString> {
+    // environment variables with a nul byte can't be set, so their value is
+    // always None as well
+    run_with_cstr(k.as_bytes(), |k| {
+        let _guard = env_read_lock();
+        let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char;
+
+        if v.is_null() {
+            Ok(None)
+        } else {
+            // SAFETY: `v` cannot be mutated while executing this line since we've a read lock
+            let bytes = unsafe { CStr::from_ptr(v) }.to_bytes().to_vec();
+
+            Ok(Some(OsStringExt::from_vec(bytes)))
+        }
+    })
+    .ok()
+    .flatten()
+}
+
+pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
+    run_with_cstr(k.as_bytes(), |k| {
+        run_with_cstr(v.as_bytes(), |v| {
+            let _guard = ENV_LOCK.write();
+            cvt(unsafe { libc::setenv(k.as_ptr(), v.as_ptr(), 1) }).map(drop)
+        })
+    })
+}
+
+pub fn unsetenv(n: &OsStr) -> io::Result<()> {
+    run_with_cstr(n.as_bytes(), |nbuf| {
+        let _guard = ENV_LOCK.write();
+        cvt(unsafe { libc::unsetenv(nbuf.as_ptr()) }).map(drop)
+    })
+}
+
+#[cfg(not(target_os = "espidf"))]
+pub fn page_size() -> usize {
+    unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
+}
+
+pub fn temp_dir() -> PathBuf {
+    crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
+        if cfg!(target_os = "android") {
+            PathBuf::from("/data/local/tmp")
+        } else {
+            PathBuf::from("/tmp")
+        }
+    })
+}
+
+pub fn home_dir() -> Option<PathBuf> {
+    return crate::env::var_os("HOME").or_else(|| unsafe { fallback() }).map(PathBuf::from);
+
+    #[cfg(any(
+        target_os = "android",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "emscripten",
+        target_os = "redox",
+        target_os = "vxworks",
+        target_os = "espidf",
+        target_os = "horizon",
+        target_os = "vita",
+    ))]
+    unsafe fn fallback() -> Option<OsString> {
+        None
+    }
+    #[cfg(not(any(
+        target_os = "android",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "emscripten",
+        target_os = "redox",
+        target_os = "vxworks",
+        target_os = "espidf",
+        target_os = "horizon",
+        target_os = "vita",
+    )))]
+    unsafe fn fallback() -> Option<OsString> {
+        let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
+            n if n < 0 => 512 as usize,
+            n => n as usize,
+        };
+        let mut buf = Vec::with_capacity(amt);
+        let mut passwd: libc::passwd = mem::zeroed();
+        let mut result = ptr::null_mut();
+        match libc::getpwuid_r(
+            libc::getuid(),
+            &mut passwd,
+            buf.as_mut_ptr(),
+            buf.capacity(),
+            &mut result,
+        ) {
+            0 if !result.is_null() => {
+                let ptr = passwd.pw_dir as *const _;
+                let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
+                Some(OsStringExt::from_vec(bytes))
+            }
+            _ => None,
+        }
+    }
+}
+
+pub fn exit(code: i32) -> ! {
+    unsafe { libc::exit(code as c_int) }
+}
+
+pub fn getpid() -> u32 {
+    unsafe { libc::getpid() as u32 }
+}
+
+pub fn getppid() -> u32 {
+    unsafe { libc::getppid() as u32 }
+}
+
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+pub fn glibc_version() -> Option<(usize, usize)> {
+    extern "C" {
+        fn gnu_get_libc_version() -> *const libc::c_char;
+    }
+    let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) };
+    if let Ok(version_str) = version_cstr.to_str() {
+        parse_glibc_version(version_str)
+    } else {
+        None
+    }
+}
+
+// Returns Some((major, minor)) if the string is a valid "x.y" version,
+// ignoring any extra dot-separated parts. Otherwise return None.
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
+    let mut parsed_ints = version.split('.').map(str::parse::<usize>).fuse();
+    match (parsed_ints.next(), parsed_ints.next()) {
+        (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
+        _ => None,
+    }
+}
diff --git a/library/std/src/sys/pal/unix/os/tests.rs b/library/std/src/sys/pal/unix/os/tests.rs
new file mode 100644
index 00000000000..efc29955b05
--- /dev/null
+++ b/library/std/src/sys/pal/unix/os/tests.rs
@@ -0,0 +1,23 @@
+#[test]
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+fn test_glibc_version() {
+    // This mostly just tests that the weak linkage doesn't panic wildly...
+    super::glibc_version();
+}
+
+#[test]
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+fn test_parse_glibc_version() {
+    let cases = [
+        ("0.0", Some((0, 0))),
+        ("01.+2", Some((1, 2))),
+        ("3.4.5.six", Some((3, 4))),
+        ("1", None),
+        ("1.-2", None),
+        ("1.foo", None),
+        ("foo.1", None),
+    ];
+    for &(version_str, parsed) in cases.iter() {
+        assert_eq!(parsed, super::parse_glibc_version(version_str));
+    }
+}
diff --git a/library/std/src/sys/pal/unix/os_str.rs b/library/std/src/sys/pal/unix/os_str.rs
new file mode 100644
index 00000000000..7bd2f656a24
--- /dev/null
+++ b/library/std/src/sys/pal/unix/os_str.rs
@@ -0,0 +1,288 @@
+//! The underlying OsString/OsStr implementation on Unix and many other
+//! systems: just a `Vec<u8>`/`[u8]`.
+
+use crate::borrow::Cow;
+use crate::collections::TryReserveError;
+use crate::fmt;
+use crate::fmt::Write;
+use crate::mem;
+use crate::rc::Rc;
+use crate::str;
+use crate::sync::Arc;
+use crate::sys_common::{AsInner, IntoInner};
+
+use core::str::Utf8Chunks;
+
+#[cfg(test)]
+#[path = "../unix/os_str/tests.rs"]
+mod tests;
+
+#[derive(Hash)]
+#[repr(transparent)]
+pub struct Buf {
+    pub inner: Vec<u8>,
+}
+
+#[repr(transparent)]
+pub struct Slice {
+    pub inner: [u8],
+}
+
+impl fmt::Debug for Slice {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Debug::fmt(&Utf8Chunks::new(&self.inner).debug(), f)
+    }
+}
+
+impl fmt::Display for Slice {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        // If we're the empty string then our iterator won't actually yield
+        // anything, so perform the formatting manually
+        if self.inner.is_empty() {
+            return "".fmt(f);
+        }
+
+        for chunk in Utf8Chunks::new(&self.inner) {
+            let valid = chunk.valid();
+            // If we successfully decoded the whole chunk as a valid string then
+            // we can return a direct formatting of the string which will also
+            // respect various formatting flags if possible.
+            if chunk.invalid().is_empty() {
+                return valid.fmt(f);
+            }
+
+            f.write_str(valid)?;
+            f.write_char(char::REPLACEMENT_CHARACTER)?;
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Debug for Buf {
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Debug::fmt(self.as_slice(), formatter)
+    }
+}
+
+impl fmt::Display for Buf {
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self.as_slice(), formatter)
+    }
+}
+
+impl Clone for Buf {
+    #[inline]
+    fn clone(&self) -> Self {
+        Buf { inner: self.inner.clone() }
+    }
+
+    #[inline]
+    fn clone_from(&mut self, source: &Self) {
+        self.inner.clone_from(&source.inner)
+    }
+}
+
+impl IntoInner<Vec<u8>> for Buf {
+    fn into_inner(self) -> Vec<u8> {
+        self.inner
+    }
+}
+
+impl AsInner<[u8]> for Buf {
+    #[inline]
+    fn as_inner(&self) -> &[u8] {
+        &self.inner
+    }
+}
+
+impl Buf {
+    #[inline]
+    pub fn into_encoded_bytes(self) -> Vec<u8> {
+        self.inner
+    }
+
+    #[inline]
+    pub unsafe fn from_encoded_bytes_unchecked(s: Vec<u8>) -> Self {
+        Self { inner: s }
+    }
+
+    pub fn from_string(s: String) -> Buf {
+        Buf { inner: s.into_bytes() }
+    }
+
+    #[inline]
+    pub fn with_capacity(capacity: usize) -> Buf {
+        Buf { inner: Vec::with_capacity(capacity) }
+    }
+
+    #[inline]
+    pub fn clear(&mut self) {
+        self.inner.clear()
+    }
+
+    #[inline]
+    pub fn capacity(&self) -> usize {
+        self.inner.capacity()
+    }
+
+    #[inline]
+    pub fn reserve(&mut self, additional: usize) {
+        self.inner.reserve(additional)
+    }
+
+    #[inline]
+    pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
+        self.inner.try_reserve(additional)
+    }
+
+    #[inline]
+    pub fn reserve_exact(&mut self, additional: usize) {
+        self.inner.reserve_exact(additional)
+    }
+
+    #[inline]
+    pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
+        self.inner.try_reserve_exact(additional)
+    }
+
+    #[inline]
+    pub fn shrink_to_fit(&mut self) {
+        self.inner.shrink_to_fit()
+    }
+
+    #[inline]
+    pub fn shrink_to(&mut self, min_capacity: usize) {
+        self.inner.shrink_to(min_capacity)
+    }
+
+    #[inline]
+    pub fn as_slice(&self) -> &Slice {
+        // SAFETY: Slice just wraps [u8],
+        // and &*self.inner is &[u8], therefore
+        // transmuting &[u8] to &Slice is safe.
+        unsafe { mem::transmute(&*self.inner) }
+    }
+
+    #[inline]
+    pub fn as_mut_slice(&mut self) -> &mut Slice {
+        // SAFETY: Slice just wraps [u8],
+        // and &mut *self.inner is &mut [u8], therefore
+        // transmuting &mut [u8] to &mut Slice is safe.
+        unsafe { mem::transmute(&mut *self.inner) }
+    }
+
+    pub fn into_string(self) -> Result<String, Buf> {
+        String::from_utf8(self.inner).map_err(|p| Buf { inner: p.into_bytes() })
+    }
+
+    pub fn push_slice(&mut self, s: &Slice) {
+        self.inner.extend_from_slice(&s.inner)
+    }
+
+    #[inline]
+    pub fn into_box(self) -> Box<Slice> {
+        unsafe { mem::transmute(self.inner.into_boxed_slice()) }
+    }
+
+    #[inline]
+    pub fn from_box(boxed: Box<Slice>) -> Buf {
+        let inner: Box<[u8]> = unsafe { mem::transmute(boxed) };
+        Buf { inner: inner.into_vec() }
+    }
+
+    #[inline]
+    pub fn into_arc(&self) -> Arc<Slice> {
+        self.as_slice().into_arc()
+    }
+
+    #[inline]
+    pub fn into_rc(&self) -> Rc<Slice> {
+        self.as_slice().into_rc()
+    }
+}
+
+impl Slice {
+    #[inline]
+    pub fn as_encoded_bytes(&self) -> &[u8] {
+        &self.inner
+    }
+
+    #[inline]
+    pub unsafe fn from_encoded_bytes_unchecked(s: &[u8]) -> &Slice {
+        unsafe { mem::transmute(s) }
+    }
+
+    #[inline]
+    pub fn from_str(s: &str) -> &Slice {
+        unsafe { Slice::from_encoded_bytes_unchecked(s.as_bytes()) }
+    }
+
+    pub fn to_str(&self) -> Result<&str, crate::str::Utf8Error> {
+        str::from_utf8(&self.inner)
+    }
+
+    pub fn to_string_lossy(&self) -> Cow<'_, str> {
+        String::from_utf8_lossy(&self.inner)
+    }
+
+    pub fn to_owned(&self) -> Buf {
+        Buf { inner: self.inner.to_vec() }
+    }
+
+    pub fn clone_into(&self, buf: &mut Buf) {
+        self.inner.clone_into(&mut buf.inner)
+    }
+
+    #[inline]
+    pub fn into_box(&self) -> Box<Slice> {
+        let boxed: Box<[u8]> = self.inner.into();
+        unsafe { mem::transmute(boxed) }
+    }
+
+    pub fn empty_box() -> Box<Slice> {
+        let boxed: Box<[u8]> = Default::default();
+        unsafe { mem::transmute(boxed) }
+    }
+
+    #[inline]
+    pub fn into_arc(&self) -> Arc<Slice> {
+        let arc: Arc<[u8]> = Arc::from(&self.inner);
+        unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Slice) }
+    }
+
+    #[inline]
+    pub fn into_rc(&self) -> Rc<Slice> {
+        let rc: Rc<[u8]> = Rc::from(&self.inner);
+        unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Slice) }
+    }
+
+    #[inline]
+    pub fn make_ascii_lowercase(&mut self) {
+        self.inner.make_ascii_lowercase()
+    }
+
+    #[inline]
+    pub fn make_ascii_uppercase(&mut self) {
+        self.inner.make_ascii_uppercase()
+    }
+
+    #[inline]
+    pub fn to_ascii_lowercase(&self) -> Buf {
+        Buf { inner: self.inner.to_ascii_lowercase() }
+    }
+
+    #[inline]
+    pub fn to_ascii_uppercase(&self) -> Buf {
+        Buf { inner: self.inner.to_ascii_uppercase() }
+    }
+
+    #[inline]
+    pub fn is_ascii(&self) -> bool {
+        self.inner.is_ascii()
+    }
+
+    #[inline]
+    pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
+        self.inner.eq_ignore_ascii_case(&other.inner)
+    }
+}
diff --git a/library/std/src/sys/pal/unix/os_str/tests.rs b/library/std/src/sys/pal/unix/os_str/tests.rs
new file mode 100644
index 00000000000..e2a99045e41
--- /dev/null
+++ b/library/std/src/sys/pal/unix/os_str/tests.rs
@@ -0,0 +1,17 @@
+use super::*;
+
+#[test]
+fn slice_debug_output() {
+    let input = unsafe { Slice::from_encoded_bytes_unchecked(b"\xF0hello,\tworld") };
+    let expected = r#""\xF0hello,\tworld""#;
+    let output = format!("{input:?}");
+
+    assert_eq!(output, expected);
+}
+
+#[test]
+fn display() {
+    assert_eq!("Hello\u{FFFD}\u{FFFD} There\u{FFFD} Goodbye", unsafe {
+        Slice::from_encoded_bytes_unchecked(b"Hello\xC0\x80 There\xE6\x83 Goodbye").to_string()
+    },);
+}
diff --git a/library/std/src/sys/pal/unix/path.rs b/library/std/src/sys/pal/unix/path.rs
new file mode 100644
index 00000000000..837f68d3eaf
--- /dev/null
+++ b/library/std/src/sys/pal/unix/path.rs
@@ -0,0 +1,63 @@
+use crate::env;
+use crate::ffi::OsStr;
+use crate::io;
+use crate::path::{Path, PathBuf, Prefix};
+
+#[inline]
+pub fn is_sep_byte(b: u8) -> bool {
+    b == b'/'
+}
+
+#[inline]
+pub fn is_verbatim_sep(b: u8) -> bool {
+    b == b'/'
+}
+
+#[inline]
+pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
+    None
+}
+
+pub const MAIN_SEP_STR: &str = "/";
+pub const MAIN_SEP: char = '/';
+
+/// Make a POSIX path absolute without changing its semantics.
+pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
+    // This is mostly a wrapper around collecting `Path::components`, with
+    // exceptions made where this conflicts with the POSIX specification.
+    // See 4.13 Pathname Resolution, IEEE Std 1003.1-2017
+    // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
+
+    // Get the components, skipping the redundant leading "." component if it exists.
+    let mut components = path.strip_prefix(".").unwrap_or(path).components();
+    let path_os = path.as_os_str().as_encoded_bytes();
+
+    let mut normalized = if path.is_absolute() {
+        // "If a pathname begins with two successive <slash> characters, the
+        // first component following the leading <slash> characters may be
+        // interpreted in an implementation-defined manner, although more than
+        // two leading <slash> characters shall be treated as a single <slash>
+        // character."
+        if path_os.starts_with(b"//") && !path_os.starts_with(b"///") {
+            components.next();
+            PathBuf::from("//")
+        } else {
+            PathBuf::new()
+        }
+    } else {
+        env::current_dir()?
+    };
+    normalized.extend(components);
+
+    // "Interfaces using pathname resolution may specify additional constraints
+    // when a pathname that does not name an existing directory contains at
+    // least one non- <slash> character and contains one or more trailing
+    // <slash> characters".
+    // A trailing <slash> is also meaningful if "a symbolic link is
+    // encountered during pathname resolution".
+    if path_os.ends_with(b"/") {
+        normalized.push("");
+    }
+
+    Ok(normalized)
+}
diff --git a/library/std/src/sys/pal/unix/pipe.rs b/library/std/src/sys/pal/unix/pipe.rs
new file mode 100644
index 00000000000..33db24e77e4
--- /dev/null
+++ b/library/std/src/sys/pal/unix/pipe.rs
@@ -0,0 +1,167 @@
+use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
+use crate::mem;
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
+use crate::sys::fd::FileDesc;
+use crate::sys::{cvt, cvt_r};
+use crate::sys_common::{FromInner, IntoInner};
+
+////////////////////////////////////////////////////////////////////////////////
+// Anonymous pipes
+////////////////////////////////////////////////////////////////////////////////
+
+pub struct AnonPipe(FileDesc);
+
+pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
+    let mut fds = [0; 2];
+
+    // The only known way right now to create atomically set the CLOEXEC flag is
+    // to use the `pipe2` syscall. This was added to Linux in 2.6.27, glibc 2.9
+    // and musl 0.9.3, and some other targets also have it.
+    cfg_if::cfg_if! {
+        if #[cfg(any(
+            target_os = "dragonfly",
+            target_os = "freebsd",
+            target_os = "hurd",
+            target_os = "linux",
+            target_os = "netbsd",
+            target_os = "openbsd",
+            target_os = "redox"
+        ))] {
+            unsafe {
+                cvt(libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC))?;
+                Ok((AnonPipe(FileDesc::from_raw_fd(fds[0])), AnonPipe(FileDesc::from_raw_fd(fds[1]))))
+            }
+        } else {
+            unsafe {
+                cvt(libc::pipe(fds.as_mut_ptr()))?;
+
+                let fd0 = FileDesc::from_raw_fd(fds[0]);
+                let fd1 = FileDesc::from_raw_fd(fds[1]);
+                fd0.set_cloexec()?;
+                fd1.set_cloexec()?;
+                Ok((AnonPipe(fd0), AnonPipe(fd1)))
+            }
+        }
+    }
+}
+
+impl AnonPipe {
+    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
+        self.0.read(buf)
+    }
+
+    pub fn read_buf(&self, buf: BorrowedCursor<'_>) -> io::Result<()> {
+        self.0.read_buf(buf)
+    }
+
+    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+        self.0.read_vectored(bufs)
+    }
+
+    #[inline]
+    pub fn is_read_vectored(&self) -> bool {
+        self.0.is_read_vectored()
+    }
+
+    pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
+        self.0.read_to_end(buf)
+    }
+
+    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
+        self.0.write(buf)
+    }
+
+    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+        self.0.write_vectored(bufs)
+    }
+
+    #[inline]
+    pub fn is_write_vectored(&self) -> bool {
+        self.0.is_write_vectored()
+    }
+}
+
+impl IntoInner<FileDesc> for AnonPipe {
+    fn into_inner(self) -> FileDesc {
+        self.0
+    }
+}
+
+pub fn read2(p1: AnonPipe, v1: &mut Vec<u8>, p2: AnonPipe, v2: &mut Vec<u8>) -> io::Result<()> {
+    // Set both pipes into nonblocking mode as we're gonna be reading from both
+    // in the `select` loop below, and we wouldn't want one to block the other!
+    let p1 = p1.into_inner();
+    let p2 = p2.into_inner();
+    p1.set_nonblocking(true)?;
+    p2.set_nonblocking(true)?;
+
+    let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() };
+    fds[0].fd = p1.as_raw_fd();
+    fds[0].events = libc::POLLIN;
+    fds[1].fd = p2.as_raw_fd();
+    fds[1].events = libc::POLLIN;
+    loop {
+        // wait for either pipe to become readable using `poll`
+        cvt_r(|| unsafe { libc::poll(fds.as_mut_ptr(), 2, -1) })?;
+
+        if fds[0].revents != 0 && read(&p1, v1)? {
+            p2.set_nonblocking(false)?;
+            return p2.read_to_end(v2).map(drop);
+        }
+        if fds[1].revents != 0 && read(&p2, v2)? {
+            p1.set_nonblocking(false)?;
+            return p1.read_to_end(v1).map(drop);
+        }
+    }
+
+    // Read as much as we can from each pipe, ignoring EWOULDBLOCK or
+    // EAGAIN. If we hit EOF, then this will happen because the underlying
+    // reader will return Ok(0), in which case we'll see `Ok` ourselves. In
+    // this case we flip the other fd back into blocking mode and read
+    // whatever's leftover on that file descriptor.
+    fn read(fd: &FileDesc, dst: &mut Vec<u8>) -> Result<bool, io::Error> {
+        match fd.read_to_end(dst) {
+            Ok(_) => Ok(true),
+            Err(e) => {
+                if e.raw_os_error() == Some(libc::EWOULDBLOCK)
+                    || e.raw_os_error() == Some(libc::EAGAIN)
+                {
+                    Ok(false)
+                } else {
+                    Err(e)
+                }
+            }
+        }
+    }
+}
+
+impl AsRawFd for AnonPipe {
+    #[inline]
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+
+impl AsFd for AnonPipe {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+
+impl IntoRawFd for AnonPipe {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+
+impl FromRawFd for AnonPipe {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FromRawFd::from_raw_fd(raw_fd))
+    }
+}
+
+impl FromInner<FileDesc> for AnonPipe {
+    fn from_inner(fd: FileDesc) -> Self {
+        Self(fd)
+    }
+}
diff --git a/library/std/src/sys/pal/unix/process/mod.rs b/library/std/src/sys/pal/unix/process/mod.rs
new file mode 100644
index 00000000000..074f0a105e3
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/mod.rs
@@ -0,0 +1,27 @@
+pub use self::process_common::{Command, CommandArgs, ExitCode, Stdio, StdioPipes};
+pub use self::process_inner::{ExitStatus, ExitStatusError, Process};
+pub use crate::ffi::OsString as EnvKey;
+
+#[cfg_attr(any(target_os = "espidf", target_os = "horizon"), allow(unused))]
+mod process_common;
+
+#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
+mod process_unsupported;
+
+cfg_if::cfg_if! {
+    if #[cfg(target_os = "fuchsia")] {
+        #[path = "process_fuchsia.rs"]
+        mod process_inner;
+        mod zircon;
+    } else if #[cfg(target_os = "vxworks")] {
+        #[path = "process_vxworks.rs"]
+        mod process_inner;
+    } else if #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))] {
+        mod process_inner {
+            pub use super::process_unsupported::*;
+        }
+    } else {
+        #[path = "process_unix.rs"]
+        mod process_inner;
+    }
+}
diff --git a/library/std/src/sys/pal/unix/process/process_common.rs b/library/std/src/sys/pal/unix/process/process_common.rs
new file mode 100644
index 00000000000..c5f04fb8b3b
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/process_common.rs
@@ -0,0 +1,676 @@
+#[cfg(all(test, not(target_os = "emscripten")))]
+mod tests;
+
+use crate::os::unix::prelude::*;
+
+use crate::collections::BTreeMap;
+use crate::ffi::{CStr, CString, OsStr, OsString};
+use crate::fmt;
+use crate::io;
+use crate::path::Path;
+use crate::ptr;
+use crate::sys::fd::FileDesc;
+use crate::sys::fs::File;
+use crate::sys::pipe::{self, AnonPipe};
+use crate::sys_common::process::{CommandEnv, CommandEnvs};
+use crate::sys_common::{FromInner, IntoInner};
+
+#[cfg(not(target_os = "fuchsia"))]
+use crate::sys::fs::OpenOptions;
+
+use libc::{c_char, c_int, gid_t, pid_t, uid_t, EXIT_FAILURE, EXIT_SUCCESS};
+
+cfg_if::cfg_if! {
+    if #[cfg(target_os = "fuchsia")] {
+        // fuchsia doesn't have /dev/null
+    } else if #[cfg(target_os = "redox")] {
+        const DEV_NULL: &CStr = c"null:";
+    } else if #[cfg(target_os = "vxworks")] {
+        const DEV_NULL: &CStr = c"/null";
+    } else {
+        const DEV_NULL: &CStr = c"/dev/null";
+    }
+}
+
+// Android with api less than 21 define sig* functions inline, so it is not
+// available for dynamic link. Implementing sigemptyset and sigaddset allow us
+// to support older Android version (independent of libc version).
+// The following implementations are based on
+// https://github.com/aosp-mirror/platform_bionic/blob/ad8dcd6023294b646e5a8288c0ed431b0845da49/libc/include/android/legacy_signal_inlines.h
+cfg_if::cfg_if! {
+    if #[cfg(target_os = "android")] {
+        #[allow(dead_code)]
+        pub unsafe fn sigemptyset(set: *mut libc::sigset_t) -> libc::c_int {
+            set.write_bytes(0u8, 1);
+            return 0;
+        }
+
+        #[allow(dead_code)]
+        pub unsafe fn sigaddset(set: *mut libc::sigset_t, signum: libc::c_int) -> libc::c_int {
+            use crate::{
+                mem::{align_of, size_of},
+                slice,
+            };
+            use libc::{c_ulong, sigset_t};
+
+            // The implementations from bionic (android libc) type pun `sigset_t` as an
+            // array of `c_ulong`. This works, but lets add a smoke check to make sure
+            // that doesn't change.
+            const _: () = assert!(
+                align_of::<c_ulong>() == align_of::<sigset_t>()
+                    && (size_of::<sigset_t>() % size_of::<c_ulong>()) == 0
+            );
+
+            let bit = (signum - 1) as usize;
+            if set.is_null() || bit >= (8 * size_of::<sigset_t>()) {
+                crate::sys::unix::os::set_errno(libc::EINVAL);
+                return -1;
+            }
+            let raw = slice::from_raw_parts_mut(
+                set as *mut c_ulong,
+                size_of::<sigset_t>() / size_of::<c_ulong>(),
+            );
+            const LONG_BIT: usize = size_of::<c_ulong>() * 8;
+            raw[bit / LONG_BIT] |= 1 << (bit % LONG_BIT);
+            return 0;
+        }
+    } else {
+        #[allow(unused_imports)]
+        pub use libc::{sigemptyset, sigaddset};
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Command
+////////////////////////////////////////////////////////////////////////////////
+
+pub struct Command {
+    program: CString,
+    args: Vec<CString>,
+    /// Exactly what will be passed to `execvp`.
+    ///
+    /// First element is a pointer to `program`, followed by pointers to
+    /// `args`, followed by a `null`. Be careful when modifying `program` or
+    /// `args` to properly update this as well.
+    argv: Argv,
+    env: CommandEnv,
+
+    program_kind: ProgramKind,
+    cwd: Option<CString>,
+    uid: Option<uid_t>,
+    gid: Option<gid_t>,
+    saw_nul: bool,
+    closures: Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>>,
+    groups: Option<Box<[gid_t]>>,
+    stdin: Option<Stdio>,
+    stdout: Option<Stdio>,
+    stderr: Option<Stdio>,
+    #[cfg(target_os = "linux")]
+    create_pidfd: bool,
+    pgroup: Option<pid_t>,
+}
+
+// Create a new type for argv, so that we can make it `Send` and `Sync`
+struct Argv(Vec<*const c_char>);
+
+// It is safe to make `Argv` `Send` and `Sync`, because it contains
+// pointers to memory owned by `Command.args`
+unsafe impl Send for Argv {}
+unsafe impl Sync for Argv {}
+
+// passed back to std::process with the pipes connected to the child, if any
+// were requested
+pub struct StdioPipes {
+    pub stdin: Option<AnonPipe>,
+    pub stdout: Option<AnonPipe>,
+    pub stderr: Option<AnonPipe>,
+}
+
+// passed to do_exec() with configuration of what the child stdio should look
+// like
+pub struct ChildPipes {
+    pub stdin: ChildStdio,
+    pub stdout: ChildStdio,
+    pub stderr: ChildStdio,
+}
+
+pub enum ChildStdio {
+    Inherit,
+    Explicit(c_int),
+    Owned(FileDesc),
+
+    // On Fuchsia, null stdio is the default, so we simply don't specify
+    // any actions at the time of spawning.
+    #[cfg(target_os = "fuchsia")]
+    Null,
+}
+
+#[derive(Debug)]
+pub enum Stdio {
+    Inherit,
+    Null,
+    MakePipe,
+    Fd(FileDesc),
+    StaticFd(BorrowedFd<'static>),
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum ProgramKind {
+    /// A program that would be looked up on the PATH (e.g. `ls`)
+    PathLookup,
+    /// A relative path (e.g. `my-dir/foo`, `../foo`, `./foo`)
+    Relative,
+    /// An absolute path.
+    Absolute,
+}
+
+impl ProgramKind {
+    fn new(program: &OsStr) -> Self {
+        if program.as_encoded_bytes().starts_with(b"/") {
+            Self::Absolute
+        } else if program.as_encoded_bytes().contains(&b'/') {
+            // If the program has more than one component in it, it is a relative path.
+            Self::Relative
+        } else {
+            Self::PathLookup
+        }
+    }
+}
+
+impl Command {
+    #[cfg(not(target_os = "linux"))]
+    pub fn new(program: &OsStr) -> Command {
+        let mut saw_nul = false;
+        let program_kind = ProgramKind::new(program.as_ref());
+        let program = os2c(program, &mut saw_nul);
+        Command {
+            argv: Argv(vec![program.as_ptr(), ptr::null()]),
+            args: vec![program.clone()],
+            program,
+            program_kind,
+            env: Default::default(),
+            cwd: None,
+            uid: None,
+            gid: None,
+            saw_nul,
+            closures: Vec::new(),
+            groups: None,
+            stdin: None,
+            stdout: None,
+            stderr: None,
+            pgroup: None,
+        }
+    }
+
+    #[cfg(target_os = "linux")]
+    pub fn new(program: &OsStr) -> Command {
+        let mut saw_nul = false;
+        let program_kind = ProgramKind::new(program.as_ref());
+        let program = os2c(program, &mut saw_nul);
+        Command {
+            argv: Argv(vec![program.as_ptr(), ptr::null()]),
+            args: vec![program.clone()],
+            program,
+            program_kind,
+            env: Default::default(),
+            cwd: None,
+            uid: None,
+            gid: None,
+            saw_nul,
+            closures: Vec::new(),
+            groups: None,
+            stdin: None,
+            stdout: None,
+            stderr: None,
+            create_pidfd: false,
+            pgroup: None,
+        }
+    }
+
+    pub fn set_arg_0(&mut self, arg: &OsStr) {
+        // Set a new arg0
+        let arg = os2c(arg, &mut self.saw_nul);
+        debug_assert!(self.argv.0.len() > 1);
+        self.argv.0[0] = arg.as_ptr();
+        self.args[0] = arg;
+    }
+
+    pub fn arg(&mut self, arg: &OsStr) {
+        // Overwrite the trailing null pointer in `argv` and then add a new null
+        // pointer.
+        let arg = os2c(arg, &mut self.saw_nul);
+        self.argv.0[self.args.len()] = arg.as_ptr();
+        self.argv.0.push(ptr::null());
+
+        // Also make sure we keep track of the owned value to schedule a
+        // destructor for this memory.
+        self.args.push(arg);
+    }
+
+    pub fn cwd(&mut self, dir: &OsStr) {
+        self.cwd = Some(os2c(dir, &mut self.saw_nul));
+    }
+    pub fn uid(&mut self, id: uid_t) {
+        self.uid = Some(id);
+    }
+    pub fn gid(&mut self, id: gid_t) {
+        self.gid = Some(id);
+    }
+    pub fn groups(&mut self, groups: &[gid_t]) {
+        self.groups = Some(Box::from(groups));
+    }
+    pub fn pgroup(&mut self, pgroup: pid_t) {
+        self.pgroup = Some(pgroup);
+    }
+
+    #[cfg(target_os = "linux")]
+    pub fn create_pidfd(&mut self, val: bool) {
+        self.create_pidfd = val;
+    }
+
+    #[cfg(not(target_os = "linux"))]
+    #[allow(dead_code)]
+    pub fn get_create_pidfd(&self) -> bool {
+        false
+    }
+
+    #[cfg(target_os = "linux")]
+    pub fn get_create_pidfd(&self) -> bool {
+        self.create_pidfd
+    }
+
+    pub fn saw_nul(&self) -> bool {
+        self.saw_nul
+    }
+
+    pub fn get_program(&self) -> &OsStr {
+        OsStr::from_bytes(self.program.as_bytes())
+    }
+
+    #[allow(dead_code)]
+    pub fn get_program_kind(&self) -> ProgramKind {
+        self.program_kind
+    }
+
+    pub fn get_args(&self) -> CommandArgs<'_> {
+        let mut iter = self.args.iter();
+        iter.next();
+        CommandArgs { iter }
+    }
+
+    pub fn get_envs(&self) -> CommandEnvs<'_> {
+        self.env.iter()
+    }
+
+    pub fn get_current_dir(&self) -> Option<&Path> {
+        self.cwd.as_ref().map(|cs| Path::new(OsStr::from_bytes(cs.as_bytes())))
+    }
+
+    pub fn get_argv(&self) -> &Vec<*const c_char> {
+        &self.argv.0
+    }
+
+    pub fn get_program_cstr(&self) -> &CStr {
+        &*self.program
+    }
+
+    #[allow(dead_code)]
+    pub fn get_cwd(&self) -> &Option<CString> {
+        &self.cwd
+    }
+    #[allow(dead_code)]
+    pub fn get_uid(&self) -> Option<uid_t> {
+        self.uid
+    }
+    #[allow(dead_code)]
+    pub fn get_gid(&self) -> Option<gid_t> {
+        self.gid
+    }
+    #[allow(dead_code)]
+    pub fn get_groups(&self) -> Option<&[gid_t]> {
+        self.groups.as_deref()
+    }
+    #[allow(dead_code)]
+    pub fn get_pgroup(&self) -> Option<pid_t> {
+        self.pgroup
+    }
+
+    pub fn get_closures(&mut self) -> &mut Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>> {
+        &mut self.closures
+    }
+
+    pub unsafe fn pre_exec(&mut self, f: Box<dyn FnMut() -> io::Result<()> + Send + Sync>) {
+        self.closures.push(f);
+    }
+
+    pub fn stdin(&mut self, stdin: Stdio) {
+        self.stdin = Some(stdin);
+    }
+
+    pub fn stdout(&mut self, stdout: Stdio) {
+        self.stdout = Some(stdout);
+    }
+
+    pub fn stderr(&mut self, stderr: Stdio) {
+        self.stderr = Some(stderr);
+    }
+
+    pub fn env_mut(&mut self) -> &mut CommandEnv {
+        &mut self.env
+    }
+
+    pub fn capture_env(&mut self) -> Option<CStringArray> {
+        let maybe_env = self.env.capture_if_changed();
+        maybe_env.map(|env| construct_envp(env, &mut self.saw_nul))
+    }
+
+    #[allow(dead_code)]
+    pub fn env_saw_path(&self) -> bool {
+        self.env.have_changed_path()
+    }
+
+    #[allow(dead_code)]
+    pub fn program_is_path(&self) -> bool {
+        self.program.to_bytes().contains(&b'/')
+    }
+
+    pub fn setup_io(
+        &self,
+        default: Stdio,
+        needs_stdin: bool,
+    ) -> io::Result<(StdioPipes, ChildPipes)> {
+        let null = Stdio::Null;
+        let default_stdin = if needs_stdin { &default } else { &null };
+        let stdin = self.stdin.as_ref().unwrap_or(default_stdin);
+        let stdout = self.stdout.as_ref().unwrap_or(&default);
+        let stderr = self.stderr.as_ref().unwrap_or(&default);
+        let (their_stdin, our_stdin) = stdin.to_child_stdio(true)?;
+        let (their_stdout, our_stdout) = stdout.to_child_stdio(false)?;
+        let (their_stderr, our_stderr) = stderr.to_child_stdio(false)?;
+        let ours = StdioPipes { stdin: our_stdin, stdout: our_stdout, stderr: our_stderr };
+        let theirs = ChildPipes { stdin: their_stdin, stdout: their_stdout, stderr: their_stderr };
+        Ok((ours, theirs))
+    }
+}
+
+fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
+    CString::new(s.as_bytes()).unwrap_or_else(|_e| {
+        *saw_nul = true;
+        CString::new("<string-with-nul>").unwrap()
+    })
+}
+
+// Helper type to manage ownership of the strings within a C-style array.
+pub struct CStringArray {
+    items: Vec<CString>,
+    ptrs: Vec<*const c_char>,
+}
+
+impl CStringArray {
+    pub fn with_capacity(capacity: usize) -> Self {
+        let mut result = CStringArray {
+            items: Vec::with_capacity(capacity),
+            ptrs: Vec::with_capacity(capacity + 1),
+        };
+        result.ptrs.push(ptr::null());
+        result
+    }
+    pub fn push(&mut self, item: CString) {
+        let l = self.ptrs.len();
+        self.ptrs[l - 1] = item.as_ptr();
+        self.ptrs.push(ptr::null());
+        self.items.push(item);
+    }
+    pub fn as_ptr(&self) -> *const *const c_char {
+        self.ptrs.as_ptr()
+    }
+}
+
+fn construct_envp(env: BTreeMap<OsString, OsString>, saw_nul: &mut bool) -> CStringArray {
+    let mut result = CStringArray::with_capacity(env.len());
+    for (mut k, v) in env {
+        // Reserve additional space for '=' and null terminator
+        k.reserve_exact(v.len() + 2);
+        k.push("=");
+        k.push(&v);
+
+        // Add the new entry into the array
+        if let Ok(item) = CString::new(k.into_vec()) {
+            result.push(item);
+        } else {
+            *saw_nul = true;
+        }
+    }
+
+    result
+}
+
+impl Stdio {
+    pub fn to_child_stdio(&self, readable: bool) -> io::Result<(ChildStdio, Option<AnonPipe>)> {
+        match *self {
+            Stdio::Inherit => Ok((ChildStdio::Inherit, None)),
+
+            // Make sure that the source descriptors are not an stdio
+            // descriptor, otherwise the order which we set the child's
+            // descriptors may blow away a descriptor which we are hoping to
+            // save. For example, suppose we want the child's stderr to be the
+            // parent's stdout, and the child's stdout to be the parent's
+            // stderr. No matter which we dup first, the second will get
+            // overwritten prematurely.
+            Stdio::Fd(ref fd) => {
+                if fd.as_raw_fd() >= 0 && fd.as_raw_fd() <= libc::STDERR_FILENO {
+                    Ok((ChildStdio::Owned(fd.duplicate()?), None))
+                } else {
+                    Ok((ChildStdio::Explicit(fd.as_raw_fd()), None))
+                }
+            }
+
+            Stdio::StaticFd(fd) => {
+                let fd = FileDesc::from_inner(fd.try_clone_to_owned()?);
+                Ok((ChildStdio::Owned(fd), None))
+            }
+
+            Stdio::MakePipe => {
+                let (reader, writer) = pipe::anon_pipe()?;
+                let (ours, theirs) = if readable { (writer, reader) } else { (reader, writer) };
+                Ok((ChildStdio::Owned(theirs.into_inner()), Some(ours)))
+            }
+
+            #[cfg(not(target_os = "fuchsia"))]
+            Stdio::Null => {
+                let mut opts = OpenOptions::new();
+                opts.read(readable);
+                opts.write(!readable);
+                let fd = File::open_c(DEV_NULL, &opts)?;
+                Ok((ChildStdio::Owned(fd.into_inner()), None))
+            }
+
+            #[cfg(target_os = "fuchsia")]
+            Stdio::Null => Ok((ChildStdio::Null, None)),
+        }
+    }
+}
+
+impl From<AnonPipe> for Stdio {
+    fn from(pipe: AnonPipe) -> Stdio {
+        Stdio::Fd(pipe.into_inner())
+    }
+}
+
+impl From<File> for Stdio {
+    fn from(file: File) -> Stdio {
+        Stdio::Fd(file.into_inner())
+    }
+}
+
+impl From<io::Stdout> for Stdio {
+    fn from(_: io::Stdout) -> Stdio {
+        // This ought really to be is Stdio::StaticFd(input_argument.as_fd()).
+        // But AsFd::as_fd takes its argument by reference, and yields
+        // a bounded lifetime, so it's no use here. There is no AsStaticFd.
+        //
+        // Additionally AsFd is only implemented for the *locked* versions.
+        // We don't want to lock them here.  (The implications of not locking
+        // are the same as those for process::Stdio::inherit().)
+        //
+        // Arguably the hypothetical AsStaticFd and AsFd<'static>
+        // should be implemented for io::Stdout, not just for StdoutLocked.
+        Stdio::StaticFd(unsafe { BorrowedFd::borrow_raw(libc::STDOUT_FILENO) })
+    }
+}
+
+impl From<io::Stderr> for Stdio {
+    fn from(_: io::Stderr) -> Stdio {
+        Stdio::StaticFd(unsafe { BorrowedFd::borrow_raw(libc::STDERR_FILENO) })
+    }
+}
+
+impl ChildStdio {
+    pub fn fd(&self) -> Option<c_int> {
+        match *self {
+            ChildStdio::Inherit => None,
+            ChildStdio::Explicit(fd) => Some(fd),
+            ChildStdio::Owned(ref fd) => Some(fd.as_raw_fd()),
+
+            #[cfg(target_os = "fuchsia")]
+            ChildStdio::Null => None,
+        }
+    }
+}
+
+impl fmt::Debug for Command {
+    // show all attributes but `self.closures` which does not implement `Debug`
+    // and `self.argv` which is not useful for debugging
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if f.alternate() {
+            let mut debug_command = f.debug_struct("Command");
+            debug_command.field("program", &self.program).field("args", &self.args);
+            if !self.env.is_unchanged() {
+                debug_command.field("env", &self.env);
+            }
+
+            if self.cwd.is_some() {
+                debug_command.field("cwd", &self.cwd);
+            }
+            if self.uid.is_some() {
+                debug_command.field("uid", &self.uid);
+            }
+            if self.gid.is_some() {
+                debug_command.field("gid", &self.gid);
+            }
+
+            if self.groups.is_some() {
+                debug_command.field("groups", &self.groups);
+            }
+
+            if self.stdin.is_some() {
+                debug_command.field("stdin", &self.stdin);
+            }
+            if self.stdout.is_some() {
+                debug_command.field("stdout", &self.stdout);
+            }
+            if self.stderr.is_some() {
+                debug_command.field("stderr", &self.stderr);
+            }
+            if self.pgroup.is_some() {
+                debug_command.field("pgroup", &self.pgroup);
+            }
+
+            #[cfg(target_os = "linux")]
+            {
+                debug_command.field("create_pidfd", &self.create_pidfd);
+            }
+
+            debug_command.finish()
+        } else {
+            if let Some(ref cwd) = self.cwd {
+                write!(f, "cd {cwd:?} && ")?;
+            }
+            if self.env.does_clear() {
+                write!(f, "env -i ")?;
+                // Altered env vars will be printed next, that should exactly work as expected.
+            } else {
+                // Removed env vars need the command to be wrapped in `env`.
+                let mut any_removed = false;
+                for (key, value_opt) in self.get_envs() {
+                    if value_opt.is_none() {
+                        if !any_removed {
+                            write!(f, "env ")?;
+                            any_removed = true;
+                        }
+                        write!(f, "-u {} ", key.to_string_lossy())?;
+                    }
+                }
+            }
+            // Altered env vars can just be added in front of the program.
+            for (key, value_opt) in self.get_envs() {
+                if let Some(value) = value_opt {
+                    write!(f, "{}={value:?} ", key.to_string_lossy())?;
+                }
+            }
+            if self.program != self.args[0] {
+                write!(f, "[{:?}] ", self.program)?;
+            }
+            write!(f, "{:?}", self.args[0])?;
+
+            for arg in &self.args[1..] {
+                write!(f, " {:?}", arg)?;
+            }
+            Ok(())
+        }
+    }
+}
+
+#[derive(PartialEq, Eq, Clone, Copy)]
+pub struct ExitCode(u8);
+
+impl fmt::Debug for ExitCode {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_tuple("unix_exit_status").field(&self.0).finish()
+    }
+}
+
+impl ExitCode {
+    pub const SUCCESS: ExitCode = ExitCode(EXIT_SUCCESS as _);
+    pub const FAILURE: ExitCode = ExitCode(EXIT_FAILURE as _);
+
+    #[inline]
+    pub fn as_i32(&self) -> i32 {
+        self.0 as i32
+    }
+}
+
+impl From<u8> for ExitCode {
+    fn from(code: u8) -> Self {
+        Self(code)
+    }
+}
+
+pub struct CommandArgs<'a> {
+    iter: crate::slice::Iter<'a, CString>,
+}
+
+impl<'a> Iterator for CommandArgs<'a> {
+    type Item = &'a OsStr;
+    fn next(&mut self) -> Option<&'a OsStr> {
+        self.iter.next().map(|cs| OsStr::from_bytes(cs.as_bytes()))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+impl<'a> ExactSizeIterator for CommandArgs<'a> {
+    fn len(&self) -> usize {
+        self.iter.len()
+    }
+    fn is_empty(&self) -> bool {
+        self.iter.is_empty()
+    }
+}
+
+impl<'a> fmt::Debug for CommandArgs<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_list().entries(self.iter.clone()).finish()
+    }
+}
diff --git a/library/std/src/sys/pal/unix/process/process_common/tests.rs b/library/std/src/sys/pal/unix/process/process_common/tests.rs
new file mode 100644
index 00000000000..4e41efc9096
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/process_common/tests.rs
@@ -0,0 +1,194 @@
+use super::*;
+
+use crate::ffi::OsStr;
+use crate::mem;
+use crate::ptr;
+use crate::sys::{cvt, cvt_nz};
+
+macro_rules! t {
+    ($e:expr) => {
+        match $e {
+            Ok(t) => t,
+            Err(e) => panic!("received error for `{}`: {}", stringify!($e), e),
+        }
+    };
+}
+
+#[test]
+#[cfg_attr(
+    any(
+        // See #14232 for more information, but it appears that signal delivery to a
+        // newly spawned process may just be raced in the macOS, so to prevent this
+        // test from being flaky we ignore it on macOS.
+        target_os = "macos",
+        // When run under our current QEMU emulation test suite this test fails,
+        // although the reason isn't very clear as to why. For now this test is
+        // ignored there.
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "riscv64",
+    ),
+    ignore
+)]
+fn test_process_mask() {
+    // Test to make sure that a signal mask *does* get inherited.
+    fn test_inner(mut cmd: Command) {
+        unsafe {
+            let mut set = mem::MaybeUninit::<libc::sigset_t>::uninit();
+            let mut old_set = mem::MaybeUninit::<libc::sigset_t>::uninit();
+            t!(cvt(sigemptyset(set.as_mut_ptr())));
+            t!(cvt(sigaddset(set.as_mut_ptr(), libc::SIGINT)));
+            t!(cvt_nz(libc::pthread_sigmask(
+                libc::SIG_SETMASK,
+                set.as_ptr(),
+                old_set.as_mut_ptr()
+            )));
+
+            cmd.stdin(Stdio::MakePipe);
+            cmd.stdout(Stdio::MakePipe);
+
+            let (mut cat, mut pipes) = t!(cmd.spawn(Stdio::Null, true));
+            let stdin_write = pipes.stdin.take().unwrap();
+            let stdout_read = pipes.stdout.take().unwrap();
+
+            t!(cvt_nz(libc::pthread_sigmask(libc::SIG_SETMASK, old_set.as_ptr(), ptr::null_mut())));
+
+            t!(cvt(libc::kill(cat.id() as libc::pid_t, libc::SIGINT)));
+            // We need to wait until SIGINT is definitely delivered. The
+            // easiest way is to write something to cat, and try to read it
+            // back: if SIGINT is unmasked, it'll get delivered when cat is
+            // next scheduled.
+            let _ = stdin_write.write(b"Hello");
+            drop(stdin_write);
+
+            // Exactly 5 bytes should be read.
+            let mut buf = [0; 5];
+            let ret = t!(stdout_read.read(&mut buf));
+            assert_eq!(ret, 5);
+            assert_eq!(&buf, b"Hello");
+
+            t!(cat.wait());
+        }
+    }
+
+    // A plain `Command::new` uses the posix_spawn path on many platforms.
+    let cmd = Command::new(OsStr::new("cat"));
+    test_inner(cmd);
+
+    // Specifying `pre_exec` forces the fork/exec path.
+    let mut cmd = Command::new(OsStr::new("cat"));
+    unsafe { cmd.pre_exec(Box::new(|| Ok(()))) };
+    test_inner(cmd);
+}
+
+#[test]
+#[cfg_attr(
+    any(
+        // See test_process_mask
+        target_os = "macos",
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "riscv64",
+    ),
+    ignore
+)]
+fn test_process_group_posix_spawn() {
+    unsafe {
+        // Spawn a cat subprocess that's just going to hang since there is no I/O.
+        let mut cmd = Command::new(OsStr::new("cat"));
+        cmd.pgroup(0);
+        cmd.stdin(Stdio::MakePipe);
+        cmd.stdout(Stdio::MakePipe);
+        let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true));
+
+        // Check that we can kill its process group, which means there *is* one.
+        t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT)));
+
+        t!(cat.wait());
+    }
+}
+
+#[test]
+#[cfg_attr(
+    any(
+        // See test_process_mask
+        target_os = "macos",
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "riscv64",
+    ),
+    ignore
+)]
+fn test_process_group_no_posix_spawn() {
+    unsafe {
+        // Same as above, create hang-y cat. This time, force using the non-posix_spawnp path.
+        let mut cmd = Command::new(OsStr::new("cat"));
+        cmd.pgroup(0);
+        cmd.pre_exec(Box::new(|| Ok(()))); // pre_exec forces fork + exec
+        cmd.stdin(Stdio::MakePipe);
+        cmd.stdout(Stdio::MakePipe);
+        let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true));
+
+        // Check that we can kill its process group, which means there *is* one.
+        t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT)));
+
+        t!(cat.wait());
+    }
+}
+
+#[test]
+fn test_program_kind() {
+    let vectors = &[
+        ("foo", ProgramKind::PathLookup),
+        ("foo.out", ProgramKind::PathLookup),
+        ("./foo", ProgramKind::Relative),
+        ("../foo", ProgramKind::Relative),
+        ("dir/foo", ProgramKind::Relative),
+        // Note that paths on Unix can't contain / in them, so this is actually the directory "fo\\"
+        // followed by the file "o".
+        ("fo\\/o", ProgramKind::Relative),
+        ("/foo", ProgramKind::Absolute),
+        ("/dir/../foo", ProgramKind::Absolute),
+    ];
+
+    for (program, expected_kind) in vectors {
+        assert_eq!(
+            ProgramKind::new(program.as_ref()),
+            *expected_kind,
+            "actual != expected program kind for input {program}",
+        );
+    }
+}
+
+// Test that Rust std handles wait status values (`ExitStatus`) the way that Unix does,
+// at least for the values which represent a Unix exit status (`ExitCode`).
+// Should work on every #[cfg(unix)] platform.  However:
+#[cfg(not(any(
+    // Fuchsia is not Unix and has totally broken std::os::unix.
+    // https://github.com/rust-lang/rust/issues/58590#issuecomment-836535609
+    target_os = "fuchsia",
+)))]
+#[test]
+fn unix_exit_statuses() {
+    use crate::num::NonZeroI32;
+    use crate::os::unix::process::ExitStatusExt;
+    use crate::process::*;
+
+    for exit_code in 0..=0xff {
+        // FIXME impl From<ExitCode> for ExitStatus and then test that here too;
+        // the two ExitStatus values should be the same
+        let raw_wait_status = exit_code << 8;
+        let exit_status = ExitStatus::from_raw(raw_wait_status);
+
+        assert_eq!(exit_status.code(), Some(exit_code));
+
+        if let Ok(nz) = NonZeroI32::try_from(exit_code) {
+            assert!(!exit_status.success());
+            let es_error = exit_status.exit_ok().unwrap_err();
+            assert_eq!(es_error.code().unwrap(), i32::from(nz));
+        } else {
+            assert!(exit_status.success());
+            assert_eq!(exit_status.exit_ok(), Ok(()));
+        }
+    }
+}
diff --git a/library/std/src/sys/pal/unix/process/process_fuchsia.rs b/library/std/src/sys/pal/unix/process/process_fuchsia.rs
new file mode 100644
index 00000000000..9931c2af2f1
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/process_fuchsia.rs
@@ -0,0 +1,330 @@
+use crate::fmt;
+use crate::io;
+use crate::mem;
+use crate::num::{NonZeroI32, NonZeroI64};
+use crate::ptr;
+
+use crate::sys::process::process_common::*;
+use crate::sys::process::zircon::{zx_handle_t, Handle};
+
+use libc::{c_int, size_t};
+
+////////////////////////////////////////////////////////////////////////////////
+// Command
+////////////////////////////////////////////////////////////////////////////////
+
+impl Command {
+    pub fn spawn(
+        &mut self,
+        default: Stdio,
+        needs_stdin: bool,
+    ) -> io::Result<(Process, StdioPipes)> {
+        let envp = self.capture_env();
+
+        if self.saw_nul() {
+            return Err(io::const_io_error!(
+                io::ErrorKind::InvalidInput,
+                "nul byte found in provided data",
+            ));
+        }
+
+        let (ours, theirs) = self.setup_io(default, needs_stdin)?;
+
+        let process_handle = unsafe { self.do_exec(theirs, envp.as_ref())? };
+
+        Ok((Process { handle: Handle::new(process_handle) }, ours))
+    }
+
+    pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> {
+        let (proc, pipes) = self.spawn(Stdio::MakePipe, false)?;
+        crate::sys_common::process::wait_with_output(proc, pipes)
+    }
+
+    pub fn exec(&mut self, default: Stdio) -> io::Error {
+        if self.saw_nul() {
+            return io::const_io_error!(
+                io::ErrorKind::InvalidInput,
+                "nul byte found in provided data",
+            );
+        }
+
+        match self.setup_io(default, true) {
+            Ok((_, _)) => {
+                // FIXME: This is tough because we don't support the exec syscalls
+                unimplemented!();
+            }
+            Err(e) => e,
+        }
+    }
+
+    unsafe fn do_exec(
+        &mut self,
+        stdio: ChildPipes,
+        maybe_envp: Option<&CStringArray>,
+    ) -> io::Result<zx_handle_t> {
+        use crate::sys::process::zircon::*;
+
+        let envp = match maybe_envp {
+            // None means to clone the current environment, which is done in the
+            // flags below.
+            None => ptr::null(),
+            Some(envp) => envp.as_ptr(),
+        };
+
+        let make_action = |local_io: &ChildStdio, target_fd| -> io::Result<fdio_spawn_action_t> {
+            if let Some(local_fd) = local_io.fd() {
+                Ok(fdio_spawn_action_t {
+                    action: FDIO_SPAWN_ACTION_TRANSFER_FD,
+                    local_fd,
+                    target_fd,
+                    ..Default::default()
+                })
+            } else {
+                if let ChildStdio::Null = local_io {
+                    // acts as no-op
+                    return Ok(Default::default());
+                }
+
+                let mut handle = ZX_HANDLE_INVALID;
+                let status = fdio_fd_clone(target_fd, &mut handle);
+                if status == ERR_INVALID_ARGS || status == ERR_NOT_SUPPORTED {
+                    // This descriptor is closed; skip it rather than generating an
+                    // error.
+                    return Ok(Default::default());
+                }
+                zx_cvt(status)?;
+
+                let mut cloned_fd = 0;
+                zx_cvt(fdio_fd_create(handle, &mut cloned_fd))?;
+
+                Ok(fdio_spawn_action_t {
+                    action: FDIO_SPAWN_ACTION_TRANSFER_FD,
+                    local_fd: cloned_fd as i32,
+                    target_fd,
+                    ..Default::default()
+                })
+            }
+        };
+
+        // Clone stdin, stdout, and stderr
+        let action1 = make_action(&stdio.stdin, 0)?;
+        let action2 = make_action(&stdio.stdout, 1)?;
+        let action3 = make_action(&stdio.stderr, 2)?;
+        let actions = [action1, action2, action3];
+
+        // We don't want FileDesc::drop to be called on any stdio. fdio_spawn_etc
+        // always consumes transferred file descriptors.
+        mem::forget(stdio);
+
+        for callback in self.get_closures().iter_mut() {
+            callback()?;
+        }
+
+        let mut process_handle: zx_handle_t = 0;
+        zx_cvt(fdio_spawn_etc(
+            ZX_HANDLE_INVALID,
+            FDIO_SPAWN_CLONE_JOB
+                | FDIO_SPAWN_CLONE_LDSVC
+                | FDIO_SPAWN_CLONE_NAMESPACE
+                | FDIO_SPAWN_CLONE_ENVIRON // this is ignored when envp is non-null
+                | FDIO_SPAWN_CLONE_UTC_CLOCK,
+            self.get_program_cstr().as_ptr(),
+            self.get_argv().as_ptr(),
+            envp,
+            actions.len() as size_t,
+            actions.as_ptr(),
+            &mut process_handle,
+            ptr::null_mut(),
+        ))?;
+        // FIXME: See if we want to do something with that err_msg
+
+        Ok(process_handle)
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Processes
+////////////////////////////////////////////////////////////////////////////////
+
+pub struct Process {
+    handle: Handle,
+}
+
+impl Process {
+    pub fn id(&self) -> u32 {
+        self.handle.raw() as u32
+    }
+
+    pub fn kill(&mut self) -> io::Result<()> {
+        use crate::sys::process::zircon::*;
+
+        unsafe {
+            zx_cvt(zx_task_kill(self.handle.raw()))?;
+        }
+
+        Ok(())
+    }
+
+    pub fn wait(&mut self) -> io::Result<ExitStatus> {
+        use crate::sys::process::zircon::*;
+
+        let mut proc_info: zx_info_process_t = Default::default();
+        let mut actual: size_t = 0;
+        let mut avail: size_t = 0;
+
+        unsafe {
+            zx_cvt(zx_object_wait_one(
+                self.handle.raw(),
+                ZX_TASK_TERMINATED,
+                ZX_TIME_INFINITE,
+                ptr::null_mut(),
+            ))?;
+            zx_cvt(zx_object_get_info(
+                self.handle.raw(),
+                ZX_INFO_PROCESS,
+                &mut proc_info as *mut _ as *mut libc::c_void,
+                mem::size_of::<zx_info_process_t>(),
+                &mut actual,
+                &mut avail,
+            ))?;
+        }
+        if actual != 1 {
+            return Err(io::const_io_error!(
+                io::ErrorKind::InvalidData,
+                "Failed to get exit status of process",
+            ));
+        }
+        Ok(ExitStatus(proc_info.return_code))
+    }
+
+    pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
+        use crate::sys::process::zircon::*;
+
+        let mut proc_info: zx_info_process_t = Default::default();
+        let mut actual: size_t = 0;
+        let mut avail: size_t = 0;
+
+        unsafe {
+            let status =
+                zx_object_wait_one(self.handle.raw(), ZX_TASK_TERMINATED, 0, ptr::null_mut());
+            match status {
+                0 => {} // Success
+                x if x == ERR_TIMED_OUT => {
+                    return Ok(None);
+                }
+                _ => {
+                    panic!("Failed to wait on process handle: {status}");
+                }
+            }
+            zx_cvt(zx_object_get_info(
+                self.handle.raw(),
+                ZX_INFO_PROCESS,
+                &mut proc_info as *mut _ as *mut libc::c_void,
+                mem::size_of::<zx_info_process_t>(),
+                &mut actual,
+                &mut avail,
+            ))?;
+        }
+        if actual != 1 {
+            return Err(io::const_io_error!(
+                io::ErrorKind::InvalidData,
+                "Failed to get exit status of process",
+            ));
+        }
+        Ok(Some(ExitStatus(proc_info.return_code)))
+    }
+}
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
+pub struct ExitStatus(i64);
+
+impl ExitStatus {
+    pub fn exit_ok(&self) -> Result<(), ExitStatusError> {
+        match NonZeroI64::try_from(self.0) {
+            /* was nonzero */ Ok(failure) => Err(ExitStatusError(failure)),
+            /* was zero, couldn't convert */ Err(_) => Ok(()),
+        }
+    }
+
+    pub fn code(&self) -> Option<i32> {
+        // FIXME: support extracting return code as an i64
+        self.0.try_into().ok()
+    }
+
+    pub fn signal(&self) -> Option<i32> {
+        None
+    }
+
+    // FIXME: The actually-Unix implementation in process_unix.rs uses WSTOPSIG, WCOREDUMP et al.
+    // I infer from the implementation of `success`, `code` and `signal` above that these are not
+    // available on Fuchsia.
+    //
+    // It does not appear that Fuchsia is Unix-like enough to implement ExitStatus (or indeed many
+    // other things from std::os::unix) properly. This veneer is always going to be a bodge. So
+    // while I don't know if these implementations are actually correct, I think they will do for
+    // now at least.
+    pub fn core_dumped(&self) -> bool {
+        false
+    }
+    pub fn stopped_signal(&self) -> Option<i32> {
+        None
+    }
+    pub fn continued(&self) -> bool {
+        false
+    }
+
+    pub fn into_raw(&self) -> c_int {
+        // We don't know what someone who calls into_raw() will do with this value, but it should
+        // have the conventional Unix representation. Despite the fact that this is not
+        // standardised in SuS or POSIX, all Unix systems encode the signal and exit status the
+        // same way. (Ie the WIFEXITED, WEXITSTATUS etc. macros have identical behaviour on every
+        // Unix.)
+        //
+        // The caller of `std::os::unix::into_raw` is probably wanting a Unix exit status, and may
+        // do their own shifting and masking, or even pass the status to another computer running a
+        // different Unix variant.
+        //
+        // The other view would be to say that the caller on Fuchsia ought to know that `into_raw`
+        // will give a raw Fuchsia status (whatever that is - I don't know, personally). That is
+        // not possible here because we must return a c_int because that's what Unix (including
+        // SuS and POSIX) say a wait status is, but Fuchsia apparently uses a u64, so it won't
+        // necessarily fit.
+        //
+        // It seems to me that the right answer would be to provide std::os::fuchsia with its
+        // own ExitStatusExt, rather that trying to provide a not very convincing imitation of
+        // Unix. Ie, std::os::unix::process:ExitStatusExt ought not to exist on Fuchsia. But
+        // fixing this up that is beyond the scope of my efforts now.
+        let exit_status_as_if_unix: u8 = self.0.try_into().expect("Fuchsia process return code bigger than 8 bits, but std::os::unix::ExitStatusExt::into_raw() was called to try to convert the value into a traditional Unix-style wait status, which cannot represent values greater than 255.");
+        let wait_status_as_if_unix = (exit_status_as_if_unix as c_int) << 8;
+        wait_status_as_if_unix
+    }
+}
+
+/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying.
+impl From<c_int> for ExitStatus {
+    fn from(a: c_int) -> ExitStatus {
+        ExitStatus(a as i64)
+    }
+}
+
+impl fmt::Display for ExitStatus {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "exit code: {}", self.0)
+    }
+}
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub struct ExitStatusError(NonZeroI64);
+
+impl Into<ExitStatus> for ExitStatusError {
+    fn into(self) -> ExitStatus {
+        ExitStatus(self.0.into())
+    }
+}
+
+impl ExitStatusError {
+    pub fn code(self) -> Option<NonZeroI32> {
+        // fixme: affected by the same bug as ExitStatus::code()
+        ExitStatus(self.0.into()).code().map(|st| st.try_into().unwrap())
+    }
+}
diff --git a/library/std/src/sys/pal/unix/process/process_unix.rs b/library/std/src/sys/pal/unix/process/process_unix.rs
new file mode 100644
index 00000000000..ee86a5f88dd
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/process_unix.rs
@@ -0,0 +1,1141 @@
+use crate::fmt;
+use crate::io::{self, Error, ErrorKind};
+use crate::mem;
+use crate::num::NonZeroI32;
+use crate::sys;
+use crate::sys::cvt;
+use crate::sys::process::process_common::*;
+use core::ffi::NonZero_c_int;
+
+#[cfg(target_os = "linux")]
+use crate::os::linux::process::PidFd;
+#[cfg(target_os = "linux")]
+use crate::os::unix::io::AsRawFd;
+
+#[cfg(any(
+    target_os = "macos",
+    target_os = "watchos",
+    target_os = "tvos",
+    target_os = "freebsd",
+    all(target_os = "linux", target_env = "gnu"),
+    all(target_os = "linux", target_env = "musl"),
+    target_os = "nto",
+))]
+use crate::sys::weak::weak;
+
+#[cfg(target_os = "vxworks")]
+use libc::RTP_ID as pid_t;
+
+#[cfg(not(target_os = "vxworks"))]
+use libc::{c_int, pid_t};
+
+#[cfg(not(any(
+    target_os = "vxworks",
+    target_os = "l4re",
+    target_os = "tvos",
+    target_os = "watchos",
+)))]
+use libc::{gid_t, uid_t};
+
+cfg_if::cfg_if! {
+    if #[cfg(all(target_os = "nto", target_env = "nto71"))] {
+        use crate::thread;
+        use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
+        use crate::time::Duration;
+        use crate::sync::LazyLock;
+        // Get smallest amount of time we can sleep.
+        // Return a common value if it cannot be determined.
+        fn get_clock_resolution() -> Duration {
+            static MIN_DELAY: LazyLock<Duration, fn() -> Duration> = LazyLock::new(|| {
+                let mut mindelay = libc::timespec { tv_sec: 0, tv_nsec: 0 };
+                if unsafe { libc::clock_getres(libc::CLOCK_MONOTONIC, &mut mindelay) } == 0
+                {
+                    Duration::from_nanos(mindelay.tv_nsec as u64)
+                } else {
+                    Duration::from_millis(1)
+                }
+            });
+            *MIN_DELAY
+        }
+        // Arbitrary minimum sleep duration for retrying fork/spawn
+        const MIN_FORKSPAWN_SLEEP: Duration = Duration::from_nanos(1);
+        // Maximum duration of sleeping before giving up and returning an error
+        const MAX_FORKSPAWN_SLEEP: Duration = Duration::from_millis(1000);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Command
+////////////////////////////////////////////////////////////////////////////////
+
+impl Command {
+    pub fn spawn(
+        &mut self,
+        default: Stdio,
+        needs_stdin: bool,
+    ) -> io::Result<(Process, StdioPipes)> {
+        const CLOEXEC_MSG_FOOTER: [u8; 4] = *b"NOEX";
+
+        let envp = self.capture_env();
+
+        if self.saw_nul() {
+            return Err(io::const_io_error!(
+                ErrorKind::InvalidInput,
+                "nul byte found in provided data",
+            ));
+        }
+
+        let (ours, theirs) = self.setup_io(default, needs_stdin)?;
+
+        if let Some(ret) = self.posix_spawn(&theirs, envp.as_ref())? {
+            return Ok((ret, ours));
+        }
+
+        #[cfg(target_os = "linux")]
+        let (input, output) = sys::net::Socket::new_pair(libc::AF_UNIX, libc::SOCK_SEQPACKET)?;
+
+        #[cfg(not(target_os = "linux"))]
+        let (input, output) = sys::pipe::anon_pipe()?;
+
+        // Whatever happens after the fork is almost for sure going to touch or
+        // look at the environment in one way or another (PATH in `execvp` or
+        // accessing the `environ` pointer ourselves). Make sure no other thread
+        // is accessing the environment when we do the fork itself.
+        //
+        // Note that as soon as we're done with the fork there's no need to hold
+        // a lock any more because the parent won't do anything and the child is
+        // in its own process. Thus the parent drops the lock guard immediately.
+        // The child calls `mem::forget` to leak the lock, which is crucial because
+        // releasing a lock is not async-signal-safe.
+        let env_lock = sys::os::env_read_lock();
+        let pid = unsafe { self.do_fork()? };
+
+        if pid == 0 {
+            crate::panic::always_abort();
+            mem::forget(env_lock); // avoid non-async-signal-safe unlocking
+            drop(input);
+            #[cfg(target_os = "linux")]
+            if self.get_create_pidfd() {
+                self.send_pidfd(&output);
+            }
+            let Err(err) = unsafe { self.do_exec(theirs, envp.as_ref()) };
+            let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32;
+            let errno = errno.to_be_bytes();
+            let bytes = [
+                errno[0],
+                errno[1],
+                errno[2],
+                errno[3],
+                CLOEXEC_MSG_FOOTER[0],
+                CLOEXEC_MSG_FOOTER[1],
+                CLOEXEC_MSG_FOOTER[2],
+                CLOEXEC_MSG_FOOTER[3],
+            ];
+            // pipe I/O up to PIPE_BUF bytes should be atomic, and then
+            // we want to be sure we *don't* run at_exit destructors as
+            // we're being torn down regardless
+            rtassert!(output.write(&bytes).is_ok());
+            unsafe { libc::_exit(1) }
+        }
+
+        drop(env_lock);
+        drop(output);
+
+        #[cfg(target_os = "linux")]
+        let pidfd = if self.get_create_pidfd() { self.recv_pidfd(&input) } else { -1 };
+
+        #[cfg(not(target_os = "linux"))]
+        let pidfd = -1;
+
+        // Safety: We obtained the pidfd from calling `clone3` with
+        // `CLONE_PIDFD` so it's valid an otherwise unowned.
+        let mut p = unsafe { Process::new(pid, pidfd) };
+        let mut bytes = [0; 8];
+
+        // loop to handle EINTR
+        loop {
+            match input.read(&mut bytes) {
+                Ok(0) => return Ok((p, ours)),
+                Ok(8) => {
+                    let (errno, footer) = bytes.split_at(4);
+                    assert_eq!(
+                        CLOEXEC_MSG_FOOTER, footer,
+                        "Validation on the CLOEXEC pipe failed: {:?}",
+                        bytes
+                    );
+                    let errno = i32::from_be_bytes(errno.try_into().unwrap());
+                    assert!(p.wait().is_ok(), "wait() should either return Ok or panic");
+                    return Err(Error::from_raw_os_error(errno));
+                }
+                Err(ref e) if e.is_interrupted() => {}
+                Err(e) => {
+                    assert!(p.wait().is_ok(), "wait() should either return Ok or panic");
+                    panic!("the CLOEXEC pipe failed: {e:?}")
+                }
+                Ok(..) => {
+                    // pipe I/O up to PIPE_BUF bytes should be atomic
+                    // similarly SOCK_SEQPACKET messages should arrive whole
+                    assert!(p.wait().is_ok(), "wait() should either return Ok or panic");
+                    panic!("short read on the CLOEXEC pipe")
+                }
+            }
+        }
+    }
+
+    pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> {
+        let (proc, pipes) = self.spawn(Stdio::MakePipe, false)?;
+        crate::sys_common::process::wait_with_output(proc, pipes)
+    }
+
+    // WatchOS and TVOS headers mark the `fork`/`exec*` functions with
+    // `__WATCHOS_PROHIBITED __TVOS_PROHIBITED`, and indicate that the
+    // `posix_spawn*` functions should be used instead. It isn't entirely clear
+    // what `PROHIBITED` means here (e.g. if calls to these functions are
+    // allowed to exist in dead code), but it sounds bad, so we go out of our
+    // way to avoid that all-together.
+    #[cfg(any(target_os = "tvos", target_os = "watchos"))]
+    const ERR_APPLE_TV_WATCH_NO_FORK_EXEC: Error = io::const_io_error!(
+        ErrorKind::Unsupported,
+        "`fork`+`exec`-based process spawning is not supported on this target",
+    );
+
+    #[cfg(any(target_os = "tvos", target_os = "watchos"))]
+    unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> {
+        return Err(Self::ERR_APPLE_TV_WATCH_NO_FORK_EXEC);
+    }
+
+    // Attempts to fork the process. If successful, returns Ok((0, -1))
+    // in the child, and Ok((child_pid, -1)) in the parent.
+    #[cfg(not(any(
+        target_os = "watchos",
+        target_os = "tvos",
+        all(target_os = "nto", target_env = "nto71"),
+    )))]
+    unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> {
+        cvt(libc::fork())
+    }
+
+    // On QNX Neutrino, fork can fail with EBADF in case "another thread might have opened
+    // or closed a file descriptor while the fork() was occurring".
+    // Documentation says "... or try calling fork() again". This is what we do here.
+    // See also https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/f/fork.html
+    #[cfg(all(target_os = "nto", target_env = "nto71"))]
+    unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> {
+        use crate::sys::os::errno;
+
+        let mut delay = MIN_FORKSPAWN_SLEEP;
+
+        loop {
+            let r = libc::fork();
+            if r == -1 as libc::pid_t && errno() as libc::c_int == libc::EBADF {
+                if delay < get_clock_resolution() {
+                    // We cannot sleep this short (it would be longer).
+                    // Yield instead.
+                    thread::yield_now();
+                } else if delay < MAX_FORKSPAWN_SLEEP {
+                    thread::sleep(delay);
+                } else {
+                    return Err(io::const_io_error!(
+                        ErrorKind::WouldBlock,
+                        "forking returned EBADF too often",
+                    ));
+                }
+                delay *= 2;
+                continue;
+            } else {
+                return cvt(r);
+            }
+        }
+    }
+
+    pub fn exec(&mut self, default: Stdio) -> io::Error {
+        let envp = self.capture_env();
+
+        if self.saw_nul() {
+            return io::const_io_error!(ErrorKind::InvalidInput, "nul byte found in provided data",);
+        }
+
+        match self.setup_io(default, true) {
+            Ok((_, theirs)) => {
+                unsafe {
+                    // Similar to when forking, we want to ensure that access to
+                    // the environment is synchronized, so make sure to grab the
+                    // environment lock before we try to exec.
+                    let _lock = sys::os::env_read_lock();
+
+                    let Err(e) = self.do_exec(theirs, envp.as_ref());
+                    e
+                }
+            }
+            Err(e) => e,
+        }
+    }
+
+    // And at this point we've reached a special time in the life of the
+    // child. The child must now be considered hamstrung and unable to
+    // do anything other than syscalls really. Consider the following
+    // scenario:
+    //
+    //      1. Thread A of process 1 grabs the malloc() mutex
+    //      2. Thread B of process 1 forks(), creating thread C
+    //      3. Thread C of process 2 then attempts to malloc()
+    //      4. The memory of process 2 is the same as the memory of
+    //         process 1, so the mutex is locked.
+    //
+    // This situation looks a lot like deadlock, right? It turns out
+    // that this is what pthread_atfork() takes care of, which is
+    // presumably implemented across platforms. The first thing that
+    // threads to *before* forking is to do things like grab the malloc
+    // mutex, and then after the fork they unlock it.
+    //
+    // Despite this information, libnative's spawn has been witnessed to
+    // deadlock on both macOS and FreeBSD. I'm not entirely sure why, but
+    // all collected backtraces point at malloc/free traffic in the
+    // child spawned process.
+    //
+    // For this reason, the block of code below should contain 0
+    // invocations of either malloc of free (or their related friends).
+    //
+    // As an example of not having malloc/free traffic, we don't close
+    // this file descriptor by dropping the FileDesc (which contains an
+    // allocation). Instead we just close it manually. This will never
+    // have the drop glue anyway because this code never returns (the
+    // child will either exec() or invoke libc::exit)
+    #[cfg(not(any(target_os = "tvos", target_os = "watchos")))]
+    unsafe fn do_exec(
+        &mut self,
+        stdio: ChildPipes,
+        maybe_envp: Option<&CStringArray>,
+    ) -> Result<!, io::Error> {
+        use crate::sys::{self, cvt_r};
+
+        if let Some(fd) = stdio.stdin.fd() {
+            cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO))?;
+        }
+        if let Some(fd) = stdio.stdout.fd() {
+            cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO))?;
+        }
+        if let Some(fd) = stdio.stderr.fd() {
+            cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO))?;
+        }
+
+        #[cfg(not(target_os = "l4re"))]
+        {
+            if let Some(_g) = self.get_groups() {
+                //FIXME: Redox kernel does not support setgroups yet
+                #[cfg(not(target_os = "redox"))]
+                cvt(libc::setgroups(_g.len().try_into().unwrap(), _g.as_ptr()))?;
+            }
+            if let Some(u) = self.get_gid() {
+                cvt(libc::setgid(u as gid_t))?;
+            }
+            if let Some(u) = self.get_uid() {
+                // When dropping privileges from root, the `setgroups` call
+                // will remove any extraneous groups. We only drop groups
+                // if the current uid is 0 and we weren't given an explicit
+                // set of groups. If we don't call this, then even though our
+                // uid has dropped, we may still have groups that enable us to
+                // do super-user things.
+                //FIXME: Redox kernel does not support setgroups yet
+                #[cfg(not(target_os = "redox"))]
+                if libc::getuid() == 0 && self.get_groups().is_none() {
+                    cvt(libc::setgroups(0, crate::ptr::null()))?;
+                }
+                cvt(libc::setuid(u as uid_t))?;
+            }
+        }
+        if let Some(ref cwd) = *self.get_cwd() {
+            cvt(libc::chdir(cwd.as_ptr()))?;
+        }
+
+        if let Some(pgroup) = self.get_pgroup() {
+            cvt(libc::setpgid(0, pgroup))?;
+        }
+
+        // emscripten has no signal support.
+        #[cfg(not(target_os = "emscripten"))]
+        {
+            // Inherit the signal mask from the parent rather than resetting it (i.e. do not call
+            // pthread_sigmask).
+
+            // If #[unix_sigpipe] is specified, don't reset SIGPIPE to SIG_DFL.
+            // If #[unix_sigpipe] is not specified, reset SIGPIPE to SIG_DFL for backward compatibility.
+            //
+            // #[unix_sigpipe] is an opportunity to change the default here.
+            if !crate::sys::unix_sigpipe_attr_specified() {
+                #[cfg(target_os = "android")] // see issue #88585
+                {
+                    let mut action: libc::sigaction = mem::zeroed();
+                    action.sa_sigaction = libc::SIG_DFL;
+                    cvt(libc::sigaction(libc::SIGPIPE, &action, crate::ptr::null_mut()))?;
+                }
+                #[cfg(not(target_os = "android"))]
+                {
+                    let ret = sys::signal(libc::SIGPIPE, libc::SIG_DFL);
+                    if ret == libc::SIG_ERR {
+                        return Err(io::Error::last_os_error());
+                    }
+                }
+                #[cfg(target_os = "hurd")]
+                {
+                    let ret = sys::signal(libc::SIGLOST, libc::SIG_DFL);
+                    if ret == libc::SIG_ERR {
+                        return Err(io::Error::last_os_error());
+                    }
+                }
+            }
+        }
+
+        for callback in self.get_closures().iter_mut() {
+            callback()?;
+        }
+
+        // Although we're performing an exec here we may also return with an
+        // error from this function (without actually exec'ing) in which case we
+        // want to be sure to restore the global environment back to what it
+        // once was, ensuring that our temporary override, when free'd, doesn't
+        // corrupt our process's environment.
+        let mut _reset = None;
+        if let Some(envp) = maybe_envp {
+            struct Reset(*const *const libc::c_char);
+
+            impl Drop for Reset {
+                fn drop(&mut self) {
+                    unsafe {
+                        *sys::os::environ() = self.0;
+                    }
+                }
+            }
+
+            _reset = Some(Reset(*sys::os::environ()));
+            *sys::os::environ() = envp.as_ptr();
+        }
+
+        libc::execvp(self.get_program_cstr().as_ptr(), self.get_argv().as_ptr());
+        Err(io::Error::last_os_error())
+    }
+
+    #[cfg(any(target_os = "tvos", target_os = "watchos"))]
+    unsafe fn do_exec(
+        &mut self,
+        _stdio: ChildPipes,
+        _maybe_envp: Option<&CStringArray>,
+    ) -> Result<!, io::Error> {
+        return Err(Self::ERR_APPLE_TV_WATCH_NO_FORK_EXEC);
+    }
+
+    #[cfg(not(any(
+        target_os = "macos",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "freebsd",
+        all(target_os = "linux", target_env = "gnu"),
+        all(target_os = "linux", target_env = "musl"),
+        target_os = "nto",
+    )))]
+    fn posix_spawn(
+        &mut self,
+        _: &ChildPipes,
+        _: Option<&CStringArray>,
+    ) -> io::Result<Option<Process>> {
+        Ok(None)
+    }
+
+    // Only support platforms for which posix_spawn() can return ENOENT
+    // directly.
+    #[cfg(any(
+        target_os = "macos",
+        // FIXME: `target_os = "ios"`?
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "freebsd",
+        all(target_os = "linux", target_env = "gnu"),
+        all(target_os = "linux", target_env = "musl"),
+        target_os = "nto",
+    ))]
+    fn posix_spawn(
+        &mut self,
+        stdio: &ChildPipes,
+        envp: Option<&CStringArray>,
+    ) -> io::Result<Option<Process>> {
+        use crate::mem::MaybeUninit;
+        use crate::sys::{self, cvt_nz, unix_sigpipe_attr_specified};
+
+        if self.get_gid().is_some()
+            || self.get_uid().is_some()
+            || (self.env_saw_path() && !self.program_is_path())
+            || !self.get_closures().is_empty()
+            || self.get_groups().is_some()
+            || self.get_create_pidfd()
+        {
+            return Ok(None);
+        }
+
+        // Only glibc 2.24+ posix_spawn() supports returning ENOENT directly.
+        #[cfg(all(target_os = "linux", target_env = "gnu"))]
+        {
+            if let Some(version) = sys::os::glibc_version() {
+                if version < (2, 24) {
+                    return Ok(None);
+                }
+            } else {
+                return Ok(None);
+            }
+        }
+
+        // On QNX Neutrino, posix_spawnp can fail with EBADF in case "another thread might have opened
+        // or closed a file descriptor while the posix_spawn() was occurring".
+        // Documentation says "... or try calling posix_spawn() again". This is what we do here.
+        // See also http://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/p/posix_spawn.html
+        #[cfg(all(target_os = "nto", target_env = "nto71"))]
+        unsafe fn retrying_libc_posix_spawnp(
+            pid: *mut pid_t,
+            file: *const c_char,
+            file_actions: *const posix_spawn_file_actions_t,
+            attrp: *const posix_spawnattr_t,
+            argv: *const *mut c_char,
+            envp: *const *mut c_char,
+        ) -> io::Result<i32> {
+            let mut delay = MIN_FORKSPAWN_SLEEP;
+            loop {
+                match libc::posix_spawnp(pid, file, file_actions, attrp, argv, envp) {
+                    libc::EBADF => {
+                        if delay < get_clock_resolution() {
+                            // We cannot sleep this short (it would be longer).
+                            // Yield instead.
+                            thread::yield_now();
+                        } else if delay < MAX_FORKSPAWN_SLEEP {
+                            thread::sleep(delay);
+                        } else {
+                            return Err(io::const_io_error!(
+                                ErrorKind::WouldBlock,
+                                "posix_spawnp returned EBADF too often",
+                            ));
+                        }
+                        delay *= 2;
+                        continue;
+                    }
+                    r => {
+                        return Ok(r);
+                    }
+                }
+            }
+        }
+
+        // Solaris, glibc 2.29+, and musl 1.24+ can set a new working directory,
+        // and maybe others will gain this non-POSIX function too. We'll check
+        // for this weak symbol as soon as it's needed, so we can return early
+        // otherwise to do a manual chdir before exec.
+        weak! {
+            fn posix_spawn_file_actions_addchdir_np(
+                *mut libc::posix_spawn_file_actions_t,
+                *const libc::c_char
+            ) -> libc::c_int
+        }
+        let addchdir = match self.get_cwd() {
+            Some(cwd) => {
+                if cfg!(any(target_os = "macos", target_os = "tvos", target_os = "watchos")) {
+                    // There is a bug in macOS where a relative executable
+                    // path like "../myprogram" will cause `posix_spawn` to
+                    // successfully launch the program, but erroneously return
+                    // ENOENT when used with posix_spawn_file_actions_addchdir_np
+                    // which was introduced in macOS 10.15.
+                    if self.get_program_kind() == ProgramKind::Relative {
+                        return Ok(None);
+                    }
+                }
+                match posix_spawn_file_actions_addchdir_np.get() {
+                    Some(f) => Some((f, cwd)),
+                    None => return Ok(None),
+                }
+            }
+            None => None,
+        };
+
+        let pgroup = self.get_pgroup();
+
+        // Safety: -1 indicates we don't have a pidfd.
+        let mut p = unsafe { Process::new(0, -1) };
+
+        struct PosixSpawnFileActions<'a>(&'a mut MaybeUninit<libc::posix_spawn_file_actions_t>);
+
+        impl Drop for PosixSpawnFileActions<'_> {
+            fn drop(&mut self) {
+                unsafe {
+                    libc::posix_spawn_file_actions_destroy(self.0.as_mut_ptr());
+                }
+            }
+        }
+
+        struct PosixSpawnattr<'a>(&'a mut MaybeUninit<libc::posix_spawnattr_t>);
+
+        impl Drop for PosixSpawnattr<'_> {
+            fn drop(&mut self) {
+                unsafe {
+                    libc::posix_spawnattr_destroy(self.0.as_mut_ptr());
+                }
+            }
+        }
+
+        unsafe {
+            let mut attrs = MaybeUninit::uninit();
+            cvt_nz(libc::posix_spawnattr_init(attrs.as_mut_ptr()))?;
+            let attrs = PosixSpawnattr(&mut attrs);
+
+            let mut flags = 0;
+
+            let mut file_actions = MaybeUninit::uninit();
+            cvt_nz(libc::posix_spawn_file_actions_init(file_actions.as_mut_ptr()))?;
+            let file_actions = PosixSpawnFileActions(&mut file_actions);
+
+            if let Some(fd) = stdio.stdin.fd() {
+                cvt_nz(libc::posix_spawn_file_actions_adddup2(
+                    file_actions.0.as_mut_ptr(),
+                    fd,
+                    libc::STDIN_FILENO,
+                ))?;
+            }
+            if let Some(fd) = stdio.stdout.fd() {
+                cvt_nz(libc::posix_spawn_file_actions_adddup2(
+                    file_actions.0.as_mut_ptr(),
+                    fd,
+                    libc::STDOUT_FILENO,
+                ))?;
+            }
+            if let Some(fd) = stdio.stderr.fd() {
+                cvt_nz(libc::posix_spawn_file_actions_adddup2(
+                    file_actions.0.as_mut_ptr(),
+                    fd,
+                    libc::STDERR_FILENO,
+                ))?;
+            }
+            if let Some((f, cwd)) = addchdir {
+                cvt_nz(f(file_actions.0.as_mut_ptr(), cwd.as_ptr()))?;
+            }
+
+            if let Some(pgroup) = pgroup {
+                flags |= libc::POSIX_SPAWN_SETPGROUP;
+                cvt_nz(libc::posix_spawnattr_setpgroup(attrs.0.as_mut_ptr(), pgroup))?;
+            }
+
+            // Inherit the signal mask from this process rather than resetting it (i.e. do not call
+            // posix_spawnattr_setsigmask).
+
+            // If #[unix_sigpipe] is specified, don't reset SIGPIPE to SIG_DFL.
+            // If #[unix_sigpipe] is not specified, reset SIGPIPE to SIG_DFL for backward compatibility.
+            //
+            // #[unix_sigpipe] is an opportunity to change the default here.
+            if !unix_sigpipe_attr_specified() {
+                let mut default_set = MaybeUninit::<libc::sigset_t>::uninit();
+                cvt(sigemptyset(default_set.as_mut_ptr()))?;
+                cvt(sigaddset(default_set.as_mut_ptr(), libc::SIGPIPE))?;
+                #[cfg(target_os = "hurd")]
+                {
+                    cvt(sigaddset(default_set.as_mut_ptr(), libc::SIGLOST))?;
+                }
+                cvt_nz(libc::posix_spawnattr_setsigdefault(
+                    attrs.0.as_mut_ptr(),
+                    default_set.as_ptr(),
+                ))?;
+                flags |= libc::POSIX_SPAWN_SETSIGDEF;
+            }
+
+            cvt_nz(libc::posix_spawnattr_setflags(attrs.0.as_mut_ptr(), flags as _))?;
+
+            // Make sure we synchronize access to the global `environ` resource
+            let _env_lock = sys::os::env_read_lock();
+            let envp = envp.map(|c| c.as_ptr()).unwrap_or_else(|| *sys::os::environ() as *const _);
+
+            #[cfg(not(target_os = "nto"))]
+            let spawn_fn = libc::posix_spawnp;
+            #[cfg(target_os = "nto")]
+            let spawn_fn = retrying_libc_posix_spawnp;
+
+            let spawn_res = spawn_fn(
+                &mut p.pid,
+                self.get_program_cstr().as_ptr(),
+                file_actions.0.as_ptr(),
+                attrs.0.as_ptr(),
+                self.get_argv().as_ptr() as *const _,
+                envp as *const _,
+            );
+
+            #[cfg(target_os = "nto")]
+            let spawn_res = spawn_res?;
+
+            cvt_nz(spawn_res)?;
+            Ok(Some(p))
+        }
+    }
+
+    #[cfg(target_os = "linux")]
+    fn send_pidfd(&self, sock: &crate::sys::net::Socket) {
+        use crate::io::IoSlice;
+        use crate::os::fd::RawFd;
+        use crate::sys::cvt_r;
+        use libc::{CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_SPACE, SCM_RIGHTS, SOL_SOCKET};
+
+        unsafe {
+            let child_pid = libc::getpid();
+            // pidfd_open sets CLOEXEC by default
+            let pidfd = libc::syscall(libc::SYS_pidfd_open, child_pid, 0);
+
+            let fds: [c_int; 1] = [pidfd as RawFd];
+
+            const SCM_MSG_LEN: usize = mem::size_of::<[c_int; 1]>();
+
+            #[repr(C)]
+            union Cmsg {
+                buf: [u8; unsafe { CMSG_SPACE(SCM_MSG_LEN as u32) as usize }],
+                _align: libc::cmsghdr,
+            }
+
+            let mut cmsg: Cmsg = mem::zeroed();
+
+            // 0-length message to send through the socket so we can pass along the fd
+            let mut iov = [IoSlice::new(b"")];
+            let mut msg: libc::msghdr = mem::zeroed();
+
+            msg.msg_iov = &mut iov as *mut _ as *mut _;
+            msg.msg_iovlen = 1;
+
+            // only attach cmsg if we successfully acquired the pidfd
+            if pidfd >= 0 {
+                msg.msg_controllen = mem::size_of_val(&cmsg.buf) as _;
+                msg.msg_control = &mut cmsg.buf as *mut _ as *mut _;
+
+                let hdr = CMSG_FIRSTHDR(&mut msg as *mut _ as *mut _);
+                (*hdr).cmsg_level = SOL_SOCKET;
+                (*hdr).cmsg_type = SCM_RIGHTS;
+                (*hdr).cmsg_len = CMSG_LEN(SCM_MSG_LEN as _) as _;
+                let data = CMSG_DATA(hdr);
+                crate::ptr::copy_nonoverlapping(
+                    fds.as_ptr().cast::<u8>(),
+                    data as *mut _,
+                    SCM_MSG_LEN,
+                );
+            }
+
+            // we send the 0-length message even if we failed to acquire the pidfd
+            // so we get a consistent SEQPACKET order
+            match cvt_r(|| libc::sendmsg(sock.as_raw(), &msg, 0)) {
+                Ok(0) => {}
+                other => rtabort!("failed to communicate with parent process. {:?}", other),
+            }
+        }
+    }
+
+    #[cfg(target_os = "linux")]
+    fn recv_pidfd(&self, sock: &crate::sys::net::Socket) -> pid_t {
+        use crate::io::IoSliceMut;
+        use crate::sys::cvt_r;
+
+        use libc::{CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_SPACE, SCM_RIGHTS, SOL_SOCKET};
+
+        unsafe {
+            const SCM_MSG_LEN: usize = mem::size_of::<[c_int; 1]>();
+
+            #[repr(C)]
+            union Cmsg {
+                _buf: [u8; unsafe { CMSG_SPACE(SCM_MSG_LEN as u32) as usize }],
+                _align: libc::cmsghdr,
+            }
+            let mut cmsg: Cmsg = mem::zeroed();
+            // 0-length read to get the fd
+            let mut iov = [IoSliceMut::new(&mut [])];
+
+            let mut msg: libc::msghdr = mem::zeroed();
+
+            msg.msg_iov = &mut iov as *mut _ as *mut _;
+            msg.msg_iovlen = 1;
+            msg.msg_controllen = mem::size_of::<Cmsg>() as _;
+            msg.msg_control = &mut cmsg as *mut _ as *mut _;
+
+            match cvt_r(|| libc::recvmsg(sock.as_raw(), &mut msg, libc::MSG_CMSG_CLOEXEC)) {
+                Err(_) => return -1,
+                Ok(_) => {}
+            }
+
+            let hdr = CMSG_FIRSTHDR(&mut msg as *mut _ as *mut _);
+            if hdr.is_null()
+                || (*hdr).cmsg_level != SOL_SOCKET
+                || (*hdr).cmsg_type != SCM_RIGHTS
+                || (*hdr).cmsg_len != CMSG_LEN(SCM_MSG_LEN as _) as _
+            {
+                return -1;
+            }
+            let data = CMSG_DATA(hdr);
+
+            let mut fds = [-1 as c_int];
+
+            crate::ptr::copy_nonoverlapping(
+                data as *const _,
+                fds.as_mut_ptr().cast::<u8>(),
+                SCM_MSG_LEN,
+            );
+
+            fds[0]
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Processes
+////////////////////////////////////////////////////////////////////////////////
+
+/// The unique ID of the process (this should never be negative).
+pub struct Process {
+    pid: pid_t,
+    status: Option<ExitStatus>,
+    // On Linux, stores the pidfd created for this child.
+    // This is None if the user did not request pidfd creation,
+    // or if the pidfd could not be created for some reason
+    // (e.g. the `pidfd_open` syscall was not available).
+    #[cfg(target_os = "linux")]
+    pidfd: Option<PidFd>,
+}
+
+impl Process {
+    #[cfg(target_os = "linux")]
+    unsafe fn new(pid: pid_t, pidfd: pid_t) -> Self {
+        use crate::os::unix::io::FromRawFd;
+        use crate::sys_common::FromInner;
+        // Safety: If `pidfd` is nonnegative, we assume it's valid and otherwise unowned.
+        let pidfd = (pidfd >= 0).then(|| PidFd::from_inner(sys::fd::FileDesc::from_raw_fd(pidfd)));
+        Process { pid, status: None, pidfd }
+    }
+
+    #[cfg(not(target_os = "linux"))]
+    unsafe fn new(pid: pid_t, _pidfd: pid_t) -> Self {
+        Process { pid, status: None }
+    }
+
+    pub fn id(&self) -> u32 {
+        self.pid as u32
+    }
+
+    pub fn kill(&mut self) -> io::Result<()> {
+        // If we've already waited on this process then the pid can be recycled
+        // and used for another process, and we probably shouldn't be killing
+        // random processes, so return Ok because the process has exited already.
+        if self.status.is_some() {
+            return Ok(());
+        }
+        #[cfg(target_os = "linux")]
+        if let Some(pid_fd) = self.pidfd.as_ref() {
+            // pidfd_send_signal predates pidfd_open. so if we were able to get an fd then sending signals will work too
+            return cvt(unsafe {
+                libc::syscall(
+                    libc::SYS_pidfd_send_signal,
+                    pid_fd.as_raw_fd(),
+                    libc::SIGKILL,
+                    crate::ptr::null::<()>(),
+                    0,
+                )
+            })
+            .map(drop);
+        }
+        cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop)
+    }
+
+    pub fn wait(&mut self) -> io::Result<ExitStatus> {
+        use crate::sys::cvt_r;
+        if let Some(status) = self.status {
+            return Ok(status);
+        }
+        #[cfg(target_os = "linux")]
+        if let Some(pid_fd) = self.pidfd.as_ref() {
+            let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
+
+            cvt_r(|| unsafe {
+                libc::waitid(libc::P_PIDFD, pid_fd.as_raw_fd() as u32, &mut siginfo, libc::WEXITED)
+            })?;
+            let status = ExitStatus::from_waitid_siginfo(siginfo);
+            self.status = Some(status);
+            return Ok(status);
+        }
+        let mut status = 0 as c_int;
+        cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?;
+        self.status = Some(ExitStatus::new(status));
+        Ok(ExitStatus::new(status))
+    }
+
+    pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
+        if let Some(status) = self.status {
+            return Ok(Some(status));
+        }
+        #[cfg(target_os = "linux")]
+        if let Some(pid_fd) = self.pidfd.as_ref() {
+            let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
+
+            cvt(unsafe {
+                libc::waitid(
+                    libc::P_PIDFD,
+                    pid_fd.as_raw_fd() as u32,
+                    &mut siginfo,
+                    libc::WEXITED | libc::WNOHANG,
+                )
+            })?;
+            if unsafe { siginfo.si_pid() } == 0 {
+                return Ok(None);
+            }
+            let status = ExitStatus::from_waitid_siginfo(siginfo);
+            self.status = Some(status);
+            return Ok(Some(status));
+        }
+        let mut status = 0 as c_int;
+        let pid = cvt(unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) })?;
+        if pid == 0 {
+            Ok(None)
+        } else {
+            self.status = Some(ExitStatus::new(status));
+            Ok(Some(ExitStatus::new(status)))
+        }
+    }
+}
+
+/// Unix exit statuses
+//
+// This is not actually an "exit status" in Unix terminology.  Rather, it is a "wait status".
+// See the discussion in comments and doc comments for `std::process::ExitStatus`.
+#[derive(PartialEq, Eq, Clone, Copy, Default)]
+pub struct ExitStatus(c_int);
+
+impl fmt::Debug for ExitStatus {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_tuple("unix_wait_status").field(&self.0).finish()
+    }
+}
+
+impl ExitStatus {
+    pub fn new(status: c_int) -> ExitStatus {
+        ExitStatus(status)
+    }
+
+    #[cfg(target_os = "linux")]
+    pub fn from_waitid_siginfo(siginfo: libc::siginfo_t) -> ExitStatus {
+        let status = unsafe { siginfo.si_status() };
+
+        match siginfo.si_code {
+            libc::CLD_EXITED => ExitStatus((status & 0xff) << 8),
+            libc::CLD_KILLED => ExitStatus(status),
+            libc::CLD_DUMPED => ExitStatus(status | 0x80),
+            libc::CLD_CONTINUED => ExitStatus(0xffff),
+            libc::CLD_STOPPED | libc::CLD_TRAPPED => ExitStatus(((status & 0xff) << 8) | 0x7f),
+            _ => unreachable!("waitid() should only return the above codes"),
+        }
+    }
+
+    fn exited(&self) -> bool {
+        libc::WIFEXITED(self.0)
+    }
+
+    pub fn exit_ok(&self) -> Result<(), ExitStatusError> {
+        // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0. This is
+        // true on all actual versions of Unix, is widely assumed, and is specified in SuS
+        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html. If it is not
+        // true for a platform pretending to be Unix, the tests (our doctests, and also
+        // process_unix/tests.rs) will spot it. `ExitStatusError::code` assumes this too.
+        match NonZero_c_int::try_from(self.0) {
+            /* was nonzero */ Ok(failure) => Err(ExitStatusError(failure)),
+            /* was zero, couldn't convert */ Err(_) => Ok(()),
+        }
+    }
+
+    pub fn code(&self) -> Option<i32> {
+        self.exited().then(|| libc::WEXITSTATUS(self.0))
+    }
+
+    pub fn signal(&self) -> Option<i32> {
+        libc::WIFSIGNALED(self.0).then(|| libc::WTERMSIG(self.0))
+    }
+
+    pub fn core_dumped(&self) -> bool {
+        libc::WIFSIGNALED(self.0) && libc::WCOREDUMP(self.0)
+    }
+
+    pub fn stopped_signal(&self) -> Option<i32> {
+        libc::WIFSTOPPED(self.0).then(|| libc::WSTOPSIG(self.0))
+    }
+
+    pub fn continued(&self) -> bool {
+        libc::WIFCONTINUED(self.0)
+    }
+
+    pub fn into_raw(&self) -> c_int {
+        self.0
+    }
+}
+
+/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying.
+impl From<c_int> for ExitStatus {
+    fn from(a: c_int) -> ExitStatus {
+        ExitStatus(a)
+    }
+}
+
+/// Convert a signal number to a readable, searchable name.
+///
+/// This string should be displayed right after the signal number.
+/// If a signal is unrecognized, it returns the empty string, so that
+/// you just get the number like "0". If it is recognized, you'll get
+/// something like "9 (SIGKILL)".
+fn signal_string(signal: i32) -> &'static str {
+    match signal {
+        libc::SIGHUP => " (SIGHUP)",
+        libc::SIGINT => " (SIGINT)",
+        libc::SIGQUIT => " (SIGQUIT)",
+        libc::SIGILL => " (SIGILL)",
+        libc::SIGTRAP => " (SIGTRAP)",
+        libc::SIGABRT => " (SIGABRT)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGBUS => " (SIGBUS)",
+        libc::SIGFPE => " (SIGFPE)",
+        libc::SIGKILL => " (SIGKILL)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGUSR1 => " (SIGUSR1)",
+        libc::SIGSEGV => " (SIGSEGV)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGUSR2 => " (SIGUSR2)",
+        libc::SIGPIPE => " (SIGPIPE)",
+        libc::SIGALRM => " (SIGALRM)",
+        libc::SIGTERM => " (SIGTERM)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGCHLD => " (SIGCHLD)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGCONT => " (SIGCONT)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGSTOP => " (SIGSTOP)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGTSTP => " (SIGTSTP)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGTTIN => " (SIGTTIN)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGTTOU => " (SIGTTOU)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGURG => " (SIGURG)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGXCPU => " (SIGXCPU)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGXFSZ => " (SIGXFSZ)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGVTALRM => " (SIGVTALRM)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGPROF => " (SIGPROF)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGWINCH => " (SIGWINCH)",
+        #[cfg(not(any(target_os = "haiku", target_os = "l4re")))]
+        libc::SIGIO => " (SIGIO)",
+        #[cfg(target_os = "haiku")]
+        libc::SIGPOLL => " (SIGPOLL)",
+        #[cfg(not(target_os = "l4re"))]
+        libc::SIGSYS => " (SIGSYS)",
+        // For information on Linux signals, run `man 7 signal`
+        #[cfg(all(
+            target_os = "linux",
+            any(
+                target_arch = "x86_64",
+                target_arch = "x86",
+                target_arch = "arm",
+                target_arch = "aarch64"
+            )
+        ))]
+        libc::SIGSTKFLT => " (SIGSTKFLT)",
+        #[cfg(any(target_os = "linux", target_os = "nto"))]
+        libc::SIGPWR => " (SIGPWR)",
+        #[cfg(any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "freebsd",
+            target_os = "netbsd",
+            target_os = "openbsd",
+            target_os = "dragonfly",
+            target_os = "nto",
+        ))]
+        libc::SIGEMT => " (SIGEMT)",
+        #[cfg(any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "freebsd",
+            target_os = "netbsd",
+            target_os = "openbsd",
+            target_os = "dragonfly"
+        ))]
+        libc::SIGINFO => " (SIGINFO)",
+        #[cfg(target_os = "hurd")]
+        libc::SIGLOST => " (SIGLOST)",
+        _ => "",
+    }
+}
+
+impl fmt::Display for ExitStatus {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(code) = self.code() {
+            write!(f, "exit status: {code}")
+        } else if let Some(signal) = self.signal() {
+            let signal_string = signal_string(signal);
+            if self.core_dumped() {
+                write!(f, "signal: {signal}{signal_string} (core dumped)")
+            } else {
+                write!(f, "signal: {signal}{signal_string}")
+            }
+        } else if let Some(signal) = self.stopped_signal() {
+            let signal_string = signal_string(signal);
+            write!(f, "stopped (not terminated) by signal: {signal}{signal_string}")
+        } else if self.continued() {
+            write!(f, "continued (WIFCONTINUED)")
+        } else {
+            write!(f, "unrecognised wait status: {} {:#x}", self.0, self.0)
+        }
+    }
+}
+
+#[derive(PartialEq, Eq, Clone, Copy)]
+pub struct ExitStatusError(NonZero_c_int);
+
+impl Into<ExitStatus> for ExitStatusError {
+    fn into(self) -> ExitStatus {
+        ExitStatus(self.0.into())
+    }
+}
+
+impl fmt::Debug for ExitStatusError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_tuple("unix_wait_status").field(&self.0).finish()
+    }
+}
+
+impl ExitStatusError {
+    pub fn code(self) -> Option<NonZeroI32> {
+        ExitStatus(self.0.into()).code().map(|st| st.try_into().unwrap())
+    }
+}
+
+#[cfg(target_os = "linux")]
+#[unstable(feature = "linux_pidfd", issue = "82971")]
+impl crate::os::linux::process::ChildExt for crate::process::Child {
+    fn pidfd(&self) -> io::Result<&PidFd> {
+        self.handle
+            .pidfd
+            .as_ref()
+            .ok_or_else(|| Error::new(ErrorKind::Uncategorized, "No pidfd was created."))
+    }
+
+    fn take_pidfd(&mut self) -> io::Result<PidFd> {
+        self.handle
+            .pidfd
+            .take()
+            .ok_or_else(|| Error::new(ErrorKind::Uncategorized, "No pidfd was created."))
+    }
+}
+
+#[cfg(test)]
+#[path = "process_unix/tests.rs"]
+mod tests;
+
+// See [`process_unsupported_wait_status::compare_with_linux`];
+#[cfg(all(test, target_os = "linux"))]
+#[path = "process_unsupported/wait_status.rs"]
+mod process_unsupported_wait_status;
diff --git a/library/std/src/sys/pal/unix/process/process_unix/tests.rs b/library/std/src/sys/pal/unix/process/process_unix/tests.rs
new file mode 100644
index 00000000000..6e952ed7c42
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/process_unix/tests.rs
@@ -0,0 +1,100 @@
+use crate::os::unix::process::{CommandExt, ExitStatusExt};
+use crate::panic::catch_unwind;
+use crate::process::Command;
+
+// Many of the other aspects of this situation, including heap alloc concurrency
+// safety etc., are tested in tests/ui/process/process-panic-after-fork.rs
+
+#[test]
+fn exitstatus_display_tests() {
+    // In practice this is the same on every Unix.
+    // If some weird platform turns out to be different, and this test fails, use #[cfg].
+    use crate::os::unix::process::ExitStatusExt;
+    use crate::process::ExitStatus;
+
+    let t = |v, s| assert_eq!(s, format!("{}", <ExitStatus as ExitStatusExt>::from_raw(v)));
+
+    t(0x0000f, "signal: 15 (SIGTERM)");
+    t(0x0008b, "signal: 11 (SIGSEGV) (core dumped)");
+    t(0x00000, "exit status: 0");
+    t(0x0ff00, "exit status: 255");
+
+    // On MacOS, 0x0137f is WIFCONTINUED, not WIFSTOPPED. Probably *BSD is similar.
+    //   https://github.com/rust-lang/rust/pull/82749#issuecomment-790525956
+    // The purpose of this test is to test our string formatting, not our understanding of the wait
+    // status magic numbers. So restrict these to Linux.
+    if cfg!(target_os = "linux") {
+        t(0x0137f, "stopped (not terminated) by signal: 19 (SIGSTOP)");
+        t(0x0ffff, "continued (WIFCONTINUED)");
+    }
+
+    // Testing "unrecognised wait status" is hard because the wait.h macros typically
+    // assume that the value came from wait and isn't mad. With the glibc I have here
+    // this works:
+    if cfg!(all(target_os = "linux", target_env = "gnu")) {
+        t(0x000ff, "unrecognised wait status: 255 0xff");
+    }
+}
+
+#[test]
+#[cfg_attr(target_os = "emscripten", ignore)]
+fn test_command_fork_no_unwind() {
+    let got = catch_unwind(|| {
+        let mut c = Command::new("echo");
+        c.arg("hi");
+        unsafe {
+            c.pre_exec(|| panic!("{}", "crash now!"));
+        }
+        let st = c.status().expect("failed to get command status");
+        dbg!(st);
+        st
+    });
+    dbg!(&got);
+    let status = got.expect("panic unexpectedly propagated");
+    dbg!(status);
+    let signal = status.signal().expect("expected child process to die of signal");
+    assert!(
+        signal == libc::SIGABRT
+            || signal == libc::SIGILL
+            || signal == libc::SIGTRAP
+            || signal == libc::SIGSEGV
+    );
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_command_pidfd() {
+    use crate::assert_matches::assert_matches;
+    use crate::os::fd::{AsRawFd, RawFd};
+    use crate::os::linux::process::{ChildExt, CommandExt};
+    use crate::process::Command;
+
+    let our_pid = crate::process::id();
+    let pidfd = unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) };
+    let pidfd_open_available = if pidfd >= 0 {
+        unsafe { libc::close(pidfd as RawFd) };
+        true
+    } else {
+        false
+    };
+
+    // always exercise creation attempts
+    let mut child = Command::new("false").create_pidfd(true).spawn().unwrap();
+
+    // but only check if we know that the kernel supports pidfds
+    if pidfd_open_available {
+        assert!(child.pidfd().is_ok());
+    }
+    if let Ok(pidfd) = child.pidfd() {
+        let flags = super::cvt(unsafe { libc::fcntl(pidfd.as_raw_fd(), libc::F_GETFD) }).unwrap();
+        assert!(flags & libc::FD_CLOEXEC != 0);
+    }
+    let status = child.wait().expect("error waiting on pidfd");
+    assert_eq!(status.code(), Some(1));
+
+    let mut child = Command::new("sleep").arg("1000").create_pidfd(true).spawn().unwrap();
+    assert_matches!(child.try_wait(), Ok(None));
+    child.kill().expect("failed to kill child");
+    let status = child.wait().expect("error waiting on pidfd");
+    assert_eq!(status.signal(), Some(libc::SIGKILL));
+}
diff --git a/library/std/src/sys/pal/unix/process/process_unsupported.rs b/library/std/src/sys/pal/unix/process/process_unsupported.rs
new file mode 100644
index 00000000000..2fbb3192265
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/process_unsupported.rs
@@ -0,0 +1,74 @@
+use crate::fmt;
+use crate::io;
+use crate::num::NonZeroI32;
+use crate::sys::process::process_common::*;
+use crate::sys::unix::unsupported::*;
+use core::ffi::NonZero_c_int;
+
+use libc::{c_int, pid_t};
+
+////////////////////////////////////////////////////////////////////////////////
+// Command
+////////////////////////////////////////////////////////////////////////////////
+
+impl Command {
+    pub fn spawn(
+        &mut self,
+        _default: Stdio,
+        _needs_stdin: bool,
+    ) -> io::Result<(Process, StdioPipes)> {
+        unsupported()
+    }
+
+    pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> {
+        unsupported()
+    }
+
+    pub fn exec(&mut self, _default: Stdio) -> io::Error {
+        unsupported_err()
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Processes
+////////////////////////////////////////////////////////////////////////////////
+
+pub struct Process {
+    _handle: pid_t,
+}
+
+impl Process {
+    pub fn id(&self) -> u32 {
+        0
+    }
+
+    pub fn kill(&mut self) -> io::Result<()> {
+        unsupported()
+    }
+
+    pub fn wait(&mut self) -> io::Result<ExitStatus> {
+        unsupported()
+    }
+
+    pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
+        unsupported()
+    }
+}
+
+mod wait_status;
+pub use wait_status::ExitStatus;
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub struct ExitStatusError(NonZero_c_int);
+
+impl Into<ExitStatus> for ExitStatusError {
+    fn into(self) -> ExitStatus {
+        ExitStatus::from(c_int::from(self.0))
+    }
+}
+
+impl ExitStatusError {
+    pub fn code(self) -> Option<NonZeroI32> {
+        ExitStatus::from(c_int::from(self.0)).code().map(|st| st.try_into().unwrap())
+    }
+}
diff --git a/library/std/src/sys/pal/unix/process/process_unsupported/wait_status.rs b/library/std/src/sys/pal/unix/process/process_unsupported/wait_status.rs
new file mode 100644
index 00000000000..72b7ae18cff
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/process_unsupported/wait_status.rs
@@ -0,0 +1,84 @@
+//! Emulated wait status for non-Unix #[cfg(unix) platforms
+//!
+//! Separate module to facilitate testing against a real Unix implementation.
+use core::ffi::NonZero_c_int;
+
+use crate::ffi::c_int;
+use crate::fmt;
+
+use super::ExitStatusError;
+
+/// Emulated wait status for use by `process_unsupported.rs`
+///
+/// Uses the "traditional unix" encoding.  For use on platfors which are `#[cfg(unix)]`
+/// but do not actually support subprocesses at all.
+///
+/// These platforms aren't Unix, but are simply pretending to be for porting convenience.
+/// So, we provide a faithful pretence here.
+#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
+pub struct ExitStatus {
+    wait_status: c_int,
+}
+
+/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it
+impl From<c_int> for ExitStatus {
+    fn from(wait_status: c_int) -> ExitStatus {
+        ExitStatus { wait_status }
+    }
+}
+
+impl fmt::Display for ExitStatus {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "emulated wait status: {}", self.wait_status)
+    }
+}
+
+impl ExitStatus {
+    pub fn code(&self) -> Option<i32> {
+        // Linux and FreeBSD both agree that values linux 0x80
+        // count as "WIFEXITED" even though this is quite mad.
+        // Likewise the macros disregard all the high bits, so are happy to declare
+        // out-of-range values to be WIFEXITED, WIFSTOPPED, etc.
+        let w = self.wait_status;
+        if (w & 0x7f) == 0 { Some((w & 0xff00) >> 8) } else { None }
+    }
+
+    #[allow(unused)]
+    pub fn exit_ok(&self) -> Result<(), ExitStatusError> {
+        // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0. This is
+        // true on all actual versions of Unix, is widely assumed, and is specified in SuS
+        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html. If it is not
+        // true for a platform pretending to be Unix, the tests (our doctests, and also
+        // process_unix/tests.rs) will spot it. `ExitStatusError::code` assumes this too.
+        match NonZero_c_int::try_from(self.wait_status) {
+            /* was nonzero */ Ok(failure) => Err(ExitStatusError(failure)),
+            /* was zero, couldn't convert */ Err(_) => Ok(()),
+        }
+    }
+
+    pub fn signal(&self) -> Option<i32> {
+        let signal = self.wait_status & 0x007f;
+        if signal > 0 && signal < 0x7f { Some(signal) } else { None }
+    }
+
+    pub fn core_dumped(&self) -> bool {
+        self.signal().is_some() && (self.wait_status & 0x80) != 0
+    }
+
+    pub fn stopped_signal(&self) -> Option<i32> {
+        let w = self.wait_status;
+        if (w & 0xff) == 0x7f { Some((w & 0xff00) >> 8) } else { None }
+    }
+
+    pub fn continued(&self) -> bool {
+        self.wait_status == 0xffff
+    }
+
+    pub fn into_raw(&self) -> c_int {
+        self.wait_status
+    }
+}
+
+#[cfg(test)]
+#[path = "wait_status/tests.rs"] // needed because of strange layout of process_unsupported
+mod tests;
diff --git a/library/std/src/sys/pal/unix/process/process_unsupported/wait_status/tests.rs b/library/std/src/sys/pal/unix/process/process_unsupported/wait_status/tests.rs
new file mode 100644
index 00000000000..5132eab10a1
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/process_unsupported/wait_status/tests.rs
@@ -0,0 +1,36 @@
+// Note that tests in this file are run on Linux as well as on platforms using process_unsupported
+
+// Test that our emulation exactly matches Linux
+//
+// This test runs *on Linux* but it tests
+// the implementation used on non-Unix `#[cfg(unix)]` platforms.
+//
+// I.e. we're using Linux as a proxy for "trad unix".
+#[cfg(target_os = "linux")]
+#[test]
+fn compare_with_linux() {
+    use super::ExitStatus as Emulated;
+    use crate::os::unix::process::ExitStatusExt as _;
+    use crate::process::ExitStatus as Real;
+
+    // Check that we handle out-of-range values similarly, too.
+    for wstatus in -0xf_ffff..0xf_ffff {
+        let emulated = Emulated::from(wstatus);
+        let real = Real::from_raw(wstatus);
+
+        macro_rules! compare { { $method:ident } => {
+            assert_eq!(
+                emulated.$method(),
+                real.$method(),
+                "{wstatus:#x}.{}()",
+                stringify!($method),
+            );
+        } }
+        compare!(code);
+        compare!(signal);
+        compare!(core_dumped);
+        compare!(stopped_signal);
+        compare!(continued);
+        compare!(into_raw);
+    }
+}
diff --git a/library/std/src/sys/pal/unix/process/process_vxworks.rs b/library/std/src/sys/pal/unix/process/process_vxworks.rs
new file mode 100644
index 00000000000..1ff2b2fb383
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/process_vxworks.rs
@@ -0,0 +1,264 @@
+use crate::fmt;
+use crate::io::{self, Error, ErrorKind};
+use crate::num::NonZeroI32;
+use crate::sys;
+use crate::sys::cvt;
+use crate::sys::process::process_common::*;
+use crate::sys_common::thread;
+use core::ffi::NonZero_c_int;
+use libc::RTP_ID;
+use libc::{self, c_char, c_int};
+
+////////////////////////////////////////////////////////////////////////////////
+// Command
+////////////////////////////////////////////////////////////////////////////////
+
+impl Command {
+    pub fn spawn(
+        &mut self,
+        default: Stdio,
+        needs_stdin: bool,
+    ) -> io::Result<(Process, StdioPipes)> {
+        use crate::sys::cvt_r;
+        let envp = self.capture_env();
+
+        if self.saw_nul() {
+            return Err(io::const_io_error!(
+                ErrorKind::InvalidInput,
+                "nul byte found in provided data",
+            ));
+        }
+        let (ours, theirs) = self.setup_io(default, needs_stdin)?;
+        let mut p = Process { pid: 0, status: None };
+
+        unsafe {
+            macro_rules! t {
+                ($e:expr) => {
+                    match $e {
+                        Ok(e) => e,
+                        Err(e) => return Err(e.into()),
+                    }
+                };
+            }
+
+            let mut orig_stdin = libc::STDIN_FILENO;
+            let mut orig_stdout = libc::STDOUT_FILENO;
+            let mut orig_stderr = libc::STDERR_FILENO;
+
+            if let Some(fd) = theirs.stdin.fd() {
+                orig_stdin = t!(cvt_r(|| libc::dup(libc::STDIN_FILENO)));
+                t!(cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO)));
+            }
+            if let Some(fd) = theirs.stdout.fd() {
+                orig_stdout = t!(cvt_r(|| libc::dup(libc::STDOUT_FILENO)));
+                t!(cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO)));
+            }
+            if let Some(fd) = theirs.stderr.fd() {
+                orig_stderr = t!(cvt_r(|| libc::dup(libc::STDERR_FILENO)));
+                t!(cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO)));
+            }
+
+            if let Some(ref cwd) = *self.get_cwd() {
+                t!(cvt(libc::chdir(cwd.as_ptr())));
+            }
+
+            // pre_exec closures are ignored on VxWorks
+            let _ = self.get_closures();
+
+            let c_envp = envp
+                .as_ref()
+                .map(|c| c.as_ptr())
+                .unwrap_or_else(|| *sys::os::environ() as *const _);
+            let stack_size = thread::min_stack();
+
+            // ensure that access to the environment is synchronized
+            let _lock = sys::os::env_read_lock();
+
+            let ret = libc::rtpSpawn(
+                self.get_program_cstr().as_ptr(),
+                self.get_argv().as_ptr() as *mut *const c_char, // argv
+                c_envp as *mut *const c_char,
+                100 as c_int, // initial priority
+                stack_size,   // initial stack size.
+                0,            // options
+                0,            // task options
+            );
+
+            // Because FileDesc was not used, each duplicated file descriptor
+            // needs to be closed manually
+            if orig_stdin != libc::STDIN_FILENO {
+                t!(cvt_r(|| libc::dup2(orig_stdin, libc::STDIN_FILENO)));
+                libc::close(orig_stdin);
+            }
+            if orig_stdout != libc::STDOUT_FILENO {
+                t!(cvt_r(|| libc::dup2(orig_stdout, libc::STDOUT_FILENO)));
+                libc::close(orig_stdout);
+            }
+            if orig_stderr != libc::STDERR_FILENO {
+                t!(cvt_r(|| libc::dup2(orig_stderr, libc::STDERR_FILENO)));
+                libc::close(orig_stderr);
+            }
+
+            if ret != libc::RTP_ID_ERROR {
+                p.pid = ret;
+                Ok((p, ours))
+            } else {
+                Err(io::Error::last_os_error())
+            }
+        }
+    }
+
+    pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> {
+        let (proc, pipes) = self.spawn(Stdio::MakePipe, false)?;
+        crate::sys_common::process::wait_with_output(proc, pipes)
+    }
+
+    pub fn exec(&mut self, default: Stdio) -> io::Error {
+        let ret = Command::spawn(self, default, false);
+        match ret {
+            Ok(t) => unsafe {
+                let mut status = 0 as c_int;
+                libc::waitpid(t.0.pid, &mut status, 0);
+                libc::exit(0);
+            },
+            Err(e) => e,
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Processes
+////////////////////////////////////////////////////////////////////////////////
+
+/// The unique id of the process (this should never be negative).
+pub struct Process {
+    pid: RTP_ID,
+    status: Option<ExitStatus>,
+}
+
+impl Process {
+    pub fn id(&self) -> u32 {
+        self.pid as u32
+    }
+
+    pub fn kill(&mut self) -> io::Result<()> {
+        // If we've already waited on this process then the pid can be recycled
+        // and used for another process, and we probably shouldn't be killing
+        // random processes, so return Ok because the process has exited already.
+        if self.status.is_some() {
+            Ok(())
+        } else {
+            cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop)
+        }
+    }
+
+    pub fn wait(&mut self) -> io::Result<ExitStatus> {
+        use crate::sys::cvt_r;
+        if let Some(status) = self.status {
+            return Ok(status);
+        }
+        let mut status = 0 as c_int;
+        cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?;
+        self.status = Some(ExitStatus::new(status));
+        Ok(ExitStatus::new(status))
+    }
+
+    pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
+        if let Some(status) = self.status {
+            return Ok(Some(status));
+        }
+        let mut status = 0 as c_int;
+        let pid = cvt(unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) })?;
+        if pid == 0 {
+            Ok(None)
+        } else {
+            self.status = Some(ExitStatus::new(status));
+            Ok(Some(ExitStatus::new(status)))
+        }
+    }
+}
+
+/// Unix exit statuses
+#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
+pub struct ExitStatus(c_int);
+
+impl ExitStatus {
+    pub fn new(status: c_int) -> ExitStatus {
+        ExitStatus(status)
+    }
+
+    fn exited(&self) -> bool {
+        libc::WIFEXITED(self.0)
+    }
+
+    pub fn exit_ok(&self) -> Result<(), ExitStatusError> {
+        // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0. This is
+        // true on all actual versions of Unix, is widely assumed, and is specified in SuS
+        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html. If it is not
+        // true for a platform pretending to be Unix, the tests (our doctests, and also
+        // process_unix/tests.rs) will spot it. `ExitStatusError::code` assumes this too.
+        match NonZero_c_int::try_from(self.0) {
+            Ok(failure) => Err(ExitStatusError(failure)),
+            Err(_) => Ok(()),
+        }
+    }
+
+    pub fn code(&self) -> Option<i32> {
+        if self.exited() { Some(libc::WEXITSTATUS(self.0)) } else { None }
+    }
+
+    pub fn signal(&self) -> Option<i32> {
+        if !self.exited() { Some(libc::WTERMSIG(self.0)) } else { None }
+    }
+
+    pub fn core_dumped(&self) -> bool {
+        // This method is not yet properly implemented on VxWorks
+        false
+    }
+
+    pub fn stopped_signal(&self) -> Option<i32> {
+        if libc::WIFSTOPPED(self.0) { Some(libc::WSTOPSIG(self.0)) } else { None }
+    }
+
+    pub fn continued(&self) -> bool {
+        // This method is not yet properly implemented on VxWorks
+        false
+    }
+
+    pub fn into_raw(&self) -> c_int {
+        self.0
+    }
+}
+
+/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying.
+impl From<c_int> for ExitStatus {
+    fn from(a: c_int) -> ExitStatus {
+        ExitStatus(a)
+    }
+}
+
+impl fmt::Display for ExitStatus {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(code) = self.code() {
+            write!(f, "exit code: {code}")
+        } else {
+            let signal = self.signal().unwrap();
+            write!(f, "signal: {signal}")
+        }
+    }
+}
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub struct ExitStatusError(NonZero_c_int);
+
+impl Into<ExitStatus> for ExitStatusError {
+    fn into(self) -> ExitStatus {
+        ExitStatus(self.0.into())
+    }
+}
+
+impl ExitStatusError {
+    pub fn code(self) -> Option<NonZeroI32> {
+        ExitStatus(self.0.into()).code().map(|st| st.try_into().unwrap())
+    }
+}
diff --git a/library/std/src/sys/pal/unix/process/zircon.rs b/library/std/src/sys/pal/unix/process/zircon.rs
new file mode 100644
index 00000000000..2e596486f9c
--- /dev/null
+++ b/library/std/src/sys/pal/unix/process/zircon.rs
@@ -0,0 +1,309 @@
+#![allow(non_camel_case_types, unused)]
+
+use crate::io;
+use crate::mem::MaybeUninit;
+use crate::os::raw::c_char;
+
+use libc::{c_int, c_void, size_t};
+
+pub type zx_handle_t = u32;
+pub type zx_vaddr_t = usize;
+pub type zx_rights_t = u32;
+pub type zx_status_t = i32;
+
+pub const ZX_HANDLE_INVALID: zx_handle_t = 0;
+
+pub type zx_time_t = i64;
+pub const ZX_TIME_INFINITE: zx_time_t = i64::MAX;
+
+pub type zx_signals_t = u32;
+
+pub const ZX_OBJECT_SIGNAL_3: zx_signals_t = 1 << 3;
+
+pub const ZX_TASK_TERMINATED: zx_signals_t = ZX_OBJECT_SIGNAL_3;
+
+pub const ZX_RIGHT_SAME_RIGHTS: zx_rights_t = 1 << 31;
+
+// The upper four bits gives the minor version.
+pub type zx_object_info_topic_t = u32;
+
+pub const ZX_INFO_PROCESS: zx_object_info_topic_t = 3 | (1 << 28);
+
+pub type zx_info_process_flags_t = u32;
+
+pub fn zx_cvt<T>(t: T) -> io::Result<T>
+where
+    T: TryInto<zx_status_t> + Copy,
+{
+    if let Ok(status) = TryInto::try_into(t) {
+        if status < 0 { Err(io::Error::from_raw_os_error(status)) } else { Ok(t) }
+    } else {
+        Err(io::Error::last_os_error())
+    }
+}
+
+// Safe wrapper around zx_handle_t
+pub struct Handle {
+    raw: zx_handle_t,
+}
+
+impl Handle {
+    pub fn new(raw: zx_handle_t) -> Handle {
+        Handle { raw }
+    }
+
+    pub fn raw(&self) -> zx_handle_t {
+        self.raw
+    }
+}
+
+impl Drop for Handle {
+    fn drop(&mut self) {
+        unsafe {
+            zx_cvt(zx_handle_close(self.raw)).expect("Failed to close zx_handle_t");
+        }
+    }
+}
+
+// Returned for topic ZX_INFO_PROCESS
+#[derive(Default)]
+#[repr(C)]
+pub struct zx_info_process_t {
+    pub return_code: i64,
+    pub start_time: zx_time_t,
+    pub flags: zx_info_process_flags_t,
+    pub reserved1: u32,
+}
+
+extern "C" {
+    pub fn zx_job_default() -> zx_handle_t;
+
+    pub fn zx_task_kill(handle: zx_handle_t) -> zx_status_t;
+
+    pub fn zx_handle_close(handle: zx_handle_t) -> zx_status_t;
+
+    pub fn zx_handle_duplicate(
+        handle: zx_handle_t,
+        rights: zx_rights_t,
+        out: *const zx_handle_t,
+    ) -> zx_handle_t;
+
+    pub fn zx_object_wait_one(
+        handle: zx_handle_t,
+        signals: zx_signals_t,
+        timeout: zx_time_t,
+        pending: *mut zx_signals_t,
+    ) -> zx_status_t;
+
+    pub fn zx_object_get_info(
+        handle: zx_handle_t,
+        topic: u32,
+        buffer: *mut c_void,
+        buffer_size: size_t,
+        actual_size: *mut size_t,
+        avail: *mut size_t,
+    ) -> zx_status_t;
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct fdio_spawn_action_t {
+    pub action: u32,
+    pub reserved0: u32,
+    pub local_fd: i32,
+    pub target_fd: i32,
+    pub reserved1: u64,
+}
+
+extern "C" {
+    pub fn fdio_spawn_etc(
+        job: zx_handle_t,
+        flags: u32,
+        path: *const c_char,
+        argv: *const *const c_char,
+        envp: *const *const c_char,
+        action_count: size_t,
+        actions: *const fdio_spawn_action_t,
+        process: *mut zx_handle_t,
+        err_msg: *mut c_char,
+    ) -> zx_status_t;
+
+    pub fn fdio_fd_clone(fd: c_int, out_handle: *mut zx_handle_t) -> zx_status_t;
+    pub fn fdio_fd_create(handle: zx_handle_t, fd: *mut c_int) -> zx_status_t;
+}
+
+// fdio_spawn_etc flags
+
+pub const FDIO_SPAWN_CLONE_JOB: u32 = 0x0001;
+pub const FDIO_SPAWN_CLONE_LDSVC: u32 = 0x0002;
+pub const FDIO_SPAWN_CLONE_NAMESPACE: u32 = 0x0004;
+pub const FDIO_SPAWN_CLONE_STDIO: u32 = 0x0008;
+pub const FDIO_SPAWN_CLONE_ENVIRON: u32 = 0x0010;
+pub const FDIO_SPAWN_CLONE_UTC_CLOCK: u32 = 0x0020;
+pub const FDIO_SPAWN_CLONE_ALL: u32 = 0xFFFF;
+
+// fdio_spawn_etc actions
+
+pub const FDIO_SPAWN_ACTION_CLONE_FD: u32 = 0x0001;
+pub const FDIO_SPAWN_ACTION_TRANSFER_FD: u32 = 0x0002;
+
+// Errors
+
+#[allow(unused)]
+pub const ERR_INTERNAL: zx_status_t = -1;
+
+// ERR_NOT_SUPPORTED: The operation is not implemented, supported,
+// or enabled.
+#[allow(unused)]
+pub const ERR_NOT_SUPPORTED: zx_status_t = -2;
+
+// ERR_NO_RESOURCES: The system was not able to allocate some resource
+// needed for the operation.
+#[allow(unused)]
+pub const ERR_NO_RESOURCES: zx_status_t = -3;
+
+// ERR_NO_MEMORY: The system was not able to allocate memory needed
+// for the operation.
+#[allow(unused)]
+pub const ERR_NO_MEMORY: zx_status_t = -4;
+
+// ERR_CALL_FAILED: The second phase of zx_channel_call(; did not complete
+// successfully.
+#[allow(unused)]
+pub const ERR_CALL_FAILED: zx_status_t = -5;
+
+// ERR_INTERRUPTED_RETRY: The system call was interrupted, but should be
+// retried.  This should not be seen outside of the VDSO.
+#[allow(unused)]
+pub const ERR_INTERRUPTED_RETRY: zx_status_t = -6;
+
+// ======= Parameter errors =======
+// ERR_INVALID_ARGS: an argument is invalid, ex. null pointer
+#[allow(unused)]
+pub const ERR_INVALID_ARGS: zx_status_t = -10;
+
+// ERR_BAD_HANDLE: A specified handle value does not refer to a handle.
+#[allow(unused)]
+pub const ERR_BAD_HANDLE: zx_status_t = -11;
+
+// ERR_WRONG_TYPE: The subject of the operation is the wrong type to
+// perform the operation.
+// Example: Attempting a message_read on a thread handle.
+#[allow(unused)]
+pub const ERR_WRONG_TYPE: zx_status_t = -12;
+
+// ERR_BAD_SYSCALL: The specified syscall number is invalid.
+#[allow(unused)]
+pub const ERR_BAD_SYSCALL: zx_status_t = -13;
+
+// ERR_OUT_OF_RANGE: An argument is outside the valid range for this
+// operation.
+#[allow(unused)]
+pub const ERR_OUT_OF_RANGE: zx_status_t = -14;
+
+// ERR_BUFFER_TOO_SMALL: A caller provided buffer is too small for
+// this operation.
+#[allow(unused)]
+pub const ERR_BUFFER_TOO_SMALL: zx_status_t = -15;
+
+// ======= Precondition or state errors =======
+// ERR_BAD_STATE: operation failed because the current state of the
+// object does not allow it, or a precondition of the operation is
+// not satisfied
+#[allow(unused)]
+pub const ERR_BAD_STATE: zx_status_t = -20;
+
+// ERR_TIMED_OUT: The time limit for the operation elapsed before
+// the operation completed.
+#[allow(unused)]
+pub const ERR_TIMED_OUT: zx_status_t = -21;
+
+// ERR_SHOULD_WAIT: The operation cannot be performed currently but
+// potentially could succeed if the caller waits for a prerequisite
+// to be satisfied, for example waiting for a handle to be readable
+// or writable.
+// Example: Attempting to read from a message pipe that has no
+// messages waiting but has an open remote will return ERR_SHOULD_WAIT.
+// Attempting to read from a message pipe that has no messages waiting
+// and has a closed remote end will return ERR_REMOTE_CLOSED.
+#[allow(unused)]
+pub const ERR_SHOULD_WAIT: zx_status_t = -22;
+
+// ERR_CANCELED: The in-progress operation (e.g., a wait) has been
+// // canceled.
+#[allow(unused)]
+pub const ERR_CANCELED: zx_status_t = -23;
+
+// ERR_PEER_CLOSED: The operation failed because the remote end
+// of the subject of the operation was closed.
+#[allow(unused)]
+pub const ERR_PEER_CLOSED: zx_status_t = -24;
+
+// ERR_NOT_FOUND: The requested entity is not found.
+#[allow(unused)]
+pub const ERR_NOT_FOUND: zx_status_t = -25;
+
+// ERR_ALREADY_EXISTS: An object with the specified identifier
+// already exists.
+// Example: Attempting to create a file when a file already exists
+// with that name.
+#[allow(unused)]
+pub const ERR_ALREADY_EXISTS: zx_status_t = -26;
+
+// ERR_ALREADY_BOUND: The operation failed because the named entity
+// is already owned or controlled by another entity. The operation
+// could succeed later if the current owner releases the entity.
+#[allow(unused)]
+pub const ERR_ALREADY_BOUND: zx_status_t = -27;
+
+// ERR_UNAVAILABLE: The subject of the operation is currently unable
+// to perform the operation.
+// Note: This is used when there's no direct way for the caller to
+// observe when the subject will be able to perform the operation
+// and should thus retry.
+#[allow(unused)]
+pub const ERR_UNAVAILABLE: zx_status_t = -28;
+
+// ======= Permission check errors =======
+// ERR_ACCESS_DENIED: The caller did not have permission to perform
+// the specified operation.
+#[allow(unused)]
+pub const ERR_ACCESS_DENIED: zx_status_t = -30;
+
+// ======= Input-output errors =======
+// ERR_IO: Otherwise unspecified error occurred during I/O.
+#[allow(unused)]
+pub const ERR_IO: zx_status_t = -40;
+
+// ERR_REFUSED: The entity the I/O operation is being performed on
+// rejected the operation.
+// Example: an I2C device NAK'ing a transaction or a disk controller
+// rejecting an invalid command.
+#[allow(unused)]
+pub const ERR_IO_REFUSED: zx_status_t = -41;
+
+// ERR_IO_DATA_INTEGRITY: The data in the operation failed an integrity
+// check and is possibly corrupted.
+// Example: CRC or Parity error.
+#[allow(unused)]
+pub const ERR_IO_DATA_INTEGRITY: zx_status_t = -42;
+
+// ERR_IO_DATA_LOSS: The data in the operation is currently unavailable
+// and may be permanently lost.
+// Example: A disk block is irrecoverably damaged.
+#[allow(unused)]
+pub const ERR_IO_DATA_LOSS: zx_status_t = -43;
+
+// Filesystem specific errors
+#[allow(unused)]
+pub const ERR_BAD_PATH: zx_status_t = -50;
+#[allow(unused)]
+pub const ERR_NOT_DIR: zx_status_t = -51;
+#[allow(unused)]
+pub const ERR_NOT_FILE: zx_status_t = -52;
+// ERR_FILE_BIG: A file exceeds a filesystem-specific size limit.
+#[allow(unused)]
+pub const ERR_FILE_BIG: zx_status_t = -53;
+// ERR_NO_SPACE: Filesystem or device space is exhausted.
+#[allow(unused)]
+pub const ERR_NO_SPACE: zx_status_t = -54;
diff --git a/library/std/src/sys/pal/unix/rand.rs b/library/std/src/sys/pal/unix/rand.rs
new file mode 100644
index 00000000000..cf0fe0f47c5
--- /dev/null
+++ b/library/std/src/sys/pal/unix/rand.rs
@@ -0,0 +1,301 @@
+pub fn hashmap_random_keys() -> (u64, u64) {
+    const KEY_LEN: usize = core::mem::size_of::<u64>();
+
+    let mut v = [0u8; KEY_LEN * 2];
+    imp::fill_bytes(&mut v);
+
+    let key1 = v[0..KEY_LEN].try_into().unwrap();
+    let key2 = v[KEY_LEN..].try_into().unwrap();
+
+    (u64::from_ne_bytes(key1), u64::from_ne_bytes(key2))
+}
+
+#[cfg(all(
+    unix,
+    not(target_os = "macos"),
+    not(target_os = "ios"),
+    not(target_os = "tvos"),
+    not(target_os = "watchos"),
+    not(target_os = "openbsd"),
+    not(target_os = "netbsd"),
+    not(target_os = "fuchsia"),
+    not(target_os = "redox"),
+    not(target_os = "vxworks"),
+    not(target_os = "emscripten"),
+    not(target_os = "vita"),
+))]
+mod imp {
+    use crate::fs::File;
+    use crate::io::Read;
+
+    #[cfg(any(target_os = "linux", target_os = "android"))]
+    use crate::sys::weak::syscall;
+
+    #[cfg(any(target_os = "linux", target_os = "android"))]
+    fn getrandom(buf: &mut [u8]) -> libc::ssize_t {
+        use crate::sync::atomic::{AtomicBool, Ordering};
+        use crate::sys::os::errno;
+
+        // A weak symbol allows interposition, e.g. for perf measurements that want to
+        // disable randomness for consistency. Otherwise, we'll try a raw syscall.
+        // (`getrandom` was added in glibc 2.25, musl 1.1.20, android API level 28)
+        syscall! {
+            fn getrandom(
+                buffer: *mut libc::c_void,
+                length: libc::size_t,
+                flags: libc::c_uint
+            ) -> libc::ssize_t
+        }
+
+        // This provides the best quality random numbers available at the given moment
+        // without ever blocking, and is preferable to falling back to /dev/urandom.
+        static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true);
+        if GRND_INSECURE_AVAILABLE.load(Ordering::Relaxed) {
+            let ret = unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_INSECURE) };
+            if ret == -1 && errno() as libc::c_int == libc::EINVAL {
+                GRND_INSECURE_AVAILABLE.store(false, Ordering::Relaxed);
+            } else {
+                return ret;
+            }
+        }
+
+        unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_NONBLOCK) }
+    }
+
+    #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "freebsd"))]
+    fn getrandom(buf: &mut [u8]) -> libc::ssize_t {
+        unsafe { libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) }
+    }
+
+    #[cfg(not(any(
+        target_os = "linux",
+        target_os = "android",
+        target_os = "espidf",
+        target_os = "horizon",
+        target_os = "freebsd"
+    )))]
+    fn getrandom_fill_bytes(_buf: &mut [u8]) -> bool {
+        false
+    }
+
+    #[cfg(any(
+        target_os = "linux",
+        target_os = "android",
+        target_os = "espidf",
+        target_os = "horizon",
+        target_os = "freebsd"
+    ))]
+    fn getrandom_fill_bytes(v: &mut [u8]) -> bool {
+        use crate::sync::atomic::{AtomicBool, Ordering};
+        use crate::sys::os::errno;
+
+        static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false);
+        if GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed) {
+            return false;
+        }
+
+        let mut read = 0;
+        while read < v.len() {
+            let result = getrandom(&mut v[read..]);
+            if result == -1 {
+                let err = errno() as libc::c_int;
+                if err == libc::EINTR {
+                    continue;
+                } else if err == libc::ENOSYS || err == libc::EPERM {
+                    // Fall back to reading /dev/urandom if `getrandom` is not
+                    // supported on the current kernel.
+                    //
+                    // Also fall back in case it is disabled by something like
+                    // seccomp or inside of virtual machines.
+                    GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed);
+                    return false;
+                } else if err == libc::EAGAIN {
+                    return false;
+                } else {
+                    panic!("unexpected getrandom error: {err}");
+                }
+            } else {
+                read += result as usize;
+            }
+        }
+        true
+    }
+
+    pub fn fill_bytes(v: &mut [u8]) {
+        // getrandom_fill_bytes here can fail if getrandom() returns EAGAIN,
+        // meaning it would have blocked because the non-blocking pool (urandom)
+        // has not initialized in the kernel yet due to a lack of entropy. The
+        // fallback we do here is to avoid blocking applications which could
+        // depend on this call without ever knowing they do and don't have a
+        // work around. The PRNG of /dev/urandom will still be used but over a
+        // possibly predictable entropy pool.
+        if getrandom_fill_bytes(v) {
+            return;
+        }
+
+        // getrandom failed because it is permanently or temporarily (because
+        // of missing entropy) unavailable. Open /dev/urandom, read from it,
+        // and close it again.
+        let mut file = File::open("/dev/urandom").expect("failed to open /dev/urandom");
+        file.read_exact(v).expect("failed to read /dev/urandom")
+    }
+}
+
+#[cfg(target_vendor = "apple")]
+mod imp {
+    use crate::io;
+    use libc::{c_int, c_void, size_t};
+
+    #[inline(always)]
+    fn random_failure() -> ! {
+        panic!("unexpected random generation error: {}", io::Error::last_os_error());
+    }
+
+    #[cfg(target_os = "macos")]
+    fn getentropy_fill_bytes(v: &mut [u8]) {
+        extern "C" {
+            fn getentropy(bytes: *mut c_void, count: size_t) -> c_int;
+        }
+
+        // getentropy(2) permits a maximum buffer size of 256 bytes
+        for s in v.chunks_mut(256) {
+            let ret = unsafe { getentropy(s.as_mut_ptr().cast(), s.len()) };
+            if ret == -1 {
+                random_failure()
+            }
+        }
+    }
+
+    #[cfg(not(target_os = "macos"))]
+    fn ccrandom_fill_bytes(v: &mut [u8]) {
+        extern "C" {
+            fn CCRandomGenerateBytes(bytes: *mut c_void, count: size_t) -> c_int;
+        }
+
+        let ret = unsafe { CCRandomGenerateBytes(v.as_mut_ptr().cast(), v.len()) };
+        if ret == -1 {
+            random_failure()
+        }
+    }
+
+    pub fn fill_bytes(v: &mut [u8]) {
+        // All supported versions of macOS (10.12+) support getentropy.
+        //
+        // `getentropy` is measurably faster (via Divan) then the other alternatives so its preferred
+        // when usable.
+        #[cfg(target_os = "macos")]
+        getentropy_fill_bytes(v);
+
+        // On Apple platforms, `CCRandomGenerateBytes` and `SecRandomCopyBytes` simply
+        // call into `CCRandomCopyBytes` with `kCCRandomDefault`. `CCRandomCopyBytes`
+        // manages a CSPRNG which is seeded from the kernel's CSPRNG and which runs on
+        // its own thread accessed via GCD. This seems needlessly heavyweight for our purposes
+        // so we only use it on non-Mac OSes where the better entrypoints are blocked.
+        //
+        // `CCRandomGenerateBytes` is used instead of `SecRandomCopyBytes` because the former is accessible
+        // via `libSystem` (libc) while the other needs to link to `Security.framework`.
+        //
+        // Note that while `getentropy` has a available attribute in the macOS headers, the lack
+        // of a header in the iOS (and others) SDK means that its can cause app store rejections.
+        // Just use `CCRandomGenerateBytes` instead.
+        #[cfg(not(target_os = "macos"))]
+        ccrandom_fill_bytes(v);
+    }
+}
+
+#[cfg(any(target_os = "openbsd", target_os = "emscripten", target_os = "vita"))]
+mod imp {
+    use crate::sys::os::errno;
+
+    pub fn fill_bytes(v: &mut [u8]) {
+        // getentropy(2) permits a maximum buffer size of 256 bytes
+        for s in v.chunks_mut(256) {
+            let ret = unsafe { libc::getentropy(s.as_mut_ptr() as *mut libc::c_void, s.len()) };
+            if ret == -1 {
+                panic!("unexpected getentropy error: {}", errno());
+            }
+        }
+    }
+}
+
+// FIXME: once the 10.x release becomes the minimum, this can be dropped for simplification.
+#[cfg(target_os = "netbsd")]
+mod imp {
+    use crate::ptr;
+
+    pub fn fill_bytes(v: &mut [u8]) {
+        let mib = [libc::CTL_KERN, libc::KERN_ARND];
+        // kern.arandom permits a maximum buffer size of 256 bytes
+        for s in v.chunks_mut(256) {
+            let mut s_len = s.len();
+            let ret = unsafe {
+                libc::sysctl(
+                    mib.as_ptr(),
+                    mib.len() as libc::c_uint,
+                    s.as_mut_ptr() as *mut _,
+                    &mut s_len,
+                    ptr::null(),
+                    0,
+                )
+            };
+            if ret == -1 || s_len != s.len() {
+                panic!(
+                    "kern.arandom sysctl failed! (returned {}, s.len() {}, oldlenp {})",
+                    ret,
+                    s.len(),
+                    s_len
+                );
+            }
+        }
+    }
+}
+
+#[cfg(target_os = "fuchsia")]
+mod imp {
+    #[link(name = "zircon")]
+    extern "C" {
+        fn zx_cprng_draw(buffer: *mut u8, len: usize);
+    }
+
+    pub fn fill_bytes(v: &mut [u8]) {
+        unsafe { zx_cprng_draw(v.as_mut_ptr(), v.len()) }
+    }
+}
+
+#[cfg(target_os = "redox")]
+mod imp {
+    use crate::fs::File;
+    use crate::io::Read;
+
+    pub fn fill_bytes(v: &mut [u8]) {
+        // Open rand:, read from it, and close it again.
+        let mut file = File::open("rand:").expect("failed to open rand:");
+        file.read_exact(v).expect("failed to read rand:")
+    }
+}
+
+#[cfg(target_os = "vxworks")]
+mod imp {
+    use crate::io;
+    use core::sync::atomic::{AtomicBool, Ordering::Relaxed};
+
+    pub fn fill_bytes(v: &mut [u8]) {
+        static RNG_INIT: AtomicBool = AtomicBool::new(false);
+        while !RNG_INIT.load(Relaxed) {
+            let ret = unsafe { libc::randSecure() };
+            if ret < 0 {
+                panic!("couldn't generate random bytes: {}", io::Error::last_os_error());
+            } else if ret > 0 {
+                RNG_INIT.store(true, Relaxed);
+                break;
+            }
+            unsafe { libc::usleep(10) };
+        }
+        let ret = unsafe {
+            libc::randABytes(v.as_mut_ptr() as *mut libc::c_uchar, v.len() as libc::c_int)
+        };
+        if ret < 0 {
+            panic!("couldn't generate random bytes: {}", io::Error::last_os_error());
+        }
+    }
+}
diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs
new file mode 100644
index 00000000000..3dbab4cc486
--- /dev/null
+++ b/library/std/src/sys/pal/unix/stack_overflow.rs
@@ -0,0 +1,223 @@
+#![cfg_attr(test, allow(dead_code))]
+
+use self::imp::{drop_handler, make_handler};
+
+pub use self::imp::cleanup;
+pub use self::imp::init;
+
+pub struct Handler {
+    data: *mut libc::c_void,
+}
+
+impl Handler {
+    pub unsafe fn new() -> Handler {
+        make_handler()
+    }
+
+    fn null() -> Handler {
+        Handler { data: crate::ptr::null_mut() }
+    }
+}
+
+impl Drop for Handler {
+    fn drop(&mut self) {
+        unsafe {
+            drop_handler(self.data);
+        }
+    }
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "dragonfly",
+    target_os = "freebsd",
+    target_os = "hurd",
+    target_os = "solaris",
+    target_os = "illumos",
+    target_os = "netbsd",
+    target_os = "openbsd"
+))]
+mod imp {
+    use super::Handler;
+    use crate::io;
+    use crate::mem;
+    use crate::ptr;
+    use crate::thread;
+
+    use libc::MAP_FAILED;
+    #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
+    use libc::{mmap as mmap64, munmap};
+    #[cfg(all(target_os = "linux", target_env = "gnu"))]
+    use libc::{mmap64, munmap};
+    use libc::{sigaction, sighandler_t, SA_ONSTACK, SA_SIGINFO, SIGBUS, SIG_DFL};
+    use libc::{sigaltstack, SIGSTKSZ, SS_DISABLE};
+    use libc::{MAP_ANON, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SIGSEGV};
+
+    use crate::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
+    use crate::sys::unix::os::page_size;
+    use crate::sys_common::thread_info;
+
+    // Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages
+    // (unmapped pages) at the end of every thread's stack, so if a thread ends
+    // up running into the guard page it'll trigger this handler. We want to
+    // detect these cases and print out a helpful error saying that the stack
+    // has overflowed. All other signals, however, should go back to what they
+    // were originally supposed to do.
+    //
+    // This handler currently exists purely to print an informative message
+    // whenever a thread overflows its stack. We then abort to exit and
+    // indicate a crash, but to avoid a misleading SIGSEGV that might lead
+    // users to believe that unsafe code has accessed an invalid pointer; the
+    // SIGSEGV encountered when overflowing the stack is expected and
+    // well-defined.
+    //
+    // If this is not a stack overflow, the handler un-registers itself and
+    // then returns (to allow the original signal to be delivered again).
+    // Returning from this kind of signal handler is technically not defined
+    // to work when reading the POSIX spec strictly, but in practice it turns
+    // out many large systems and all implementations allow returning from a
+    // signal handler to work. For a more detailed explanation see the
+    // comments on #26458.
+    unsafe extern "C" fn signal_handler(
+        signum: libc::c_int,
+        info: *mut libc::siginfo_t,
+        _data: *mut libc::c_void,
+    ) {
+        let guard = thread_info::stack_guard().unwrap_or(0..0);
+        let addr = (*info).si_addr() as usize;
+
+        // If the faulting address is within the guard page, then we print a
+        // message saying so and abort.
+        if guard.start <= addr && addr < guard.end {
+            rtprintpanic!(
+                "\nthread '{}' has overflowed its stack\n",
+                thread::current().name().unwrap_or("<unknown>")
+            );
+            rtabort!("stack overflow");
+        } else {
+            // Unregister ourselves by reverting back to the default behavior.
+            let mut action: sigaction = mem::zeroed();
+            action.sa_sigaction = SIG_DFL;
+            sigaction(signum, &action, ptr::null_mut());
+
+            // See comment above for why this function returns.
+        }
+    }
+
+    static MAIN_ALTSTACK: AtomicPtr<libc::c_void> = AtomicPtr::new(ptr::null_mut());
+    static NEED_ALTSTACK: AtomicBool = AtomicBool::new(false);
+
+    pub unsafe fn init() {
+        let mut action: sigaction = mem::zeroed();
+        for &signal in &[SIGSEGV, SIGBUS] {
+            sigaction(signal, ptr::null_mut(), &mut action);
+            // Configure our signal handler if one is not already set.
+            if action.sa_sigaction == SIG_DFL {
+                action.sa_flags = SA_SIGINFO | SA_ONSTACK;
+                action.sa_sigaction = signal_handler as sighandler_t;
+                sigaction(signal, &action, ptr::null_mut());
+                NEED_ALTSTACK.store(true, Ordering::Relaxed);
+            }
+        }
+
+        let handler = make_handler();
+        MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
+        mem::forget(handler);
+    }
+
+    pub unsafe fn cleanup() {
+        drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed));
+    }
+
+    unsafe fn get_stackp() -> *mut libc::c_void {
+        // OpenBSD requires this flag for stack mapping
+        // otherwise the said mapping will fail as a no-op on most systems
+        // and has a different meaning on FreeBSD
+        #[cfg(any(
+            target_os = "openbsd",
+            target_os = "netbsd",
+            target_os = "linux",
+            target_os = "dragonfly",
+        ))]
+        let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK;
+        #[cfg(not(any(
+            target_os = "openbsd",
+            target_os = "netbsd",
+            target_os = "linux",
+            target_os = "dragonfly",
+        )))]
+        let flags = MAP_PRIVATE | MAP_ANON;
+        let stackp =
+            mmap64(ptr::null_mut(), SIGSTKSZ + page_size(), PROT_READ | PROT_WRITE, flags, -1, 0);
+        if stackp == MAP_FAILED {
+            panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error());
+        }
+        let guard_result = libc::mprotect(stackp, page_size(), PROT_NONE);
+        if guard_result != 0 {
+            panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error());
+        }
+        stackp.add(page_size())
+    }
+
+    unsafe fn get_stack() -> libc::stack_t {
+        libc::stack_t { ss_sp: get_stackp(), ss_flags: 0, ss_size: SIGSTKSZ }
+    }
+
+    pub unsafe fn make_handler() -> Handler {
+        if !NEED_ALTSTACK.load(Ordering::Relaxed) {
+            return Handler::null();
+        }
+        let mut stack = mem::zeroed();
+        sigaltstack(ptr::null(), &mut stack);
+        // Configure alternate signal stack, if one is not already set.
+        if stack.ss_flags & SS_DISABLE != 0 {
+            stack = get_stack();
+            sigaltstack(&stack, ptr::null_mut());
+            Handler { data: stack.ss_sp as *mut libc::c_void }
+        } else {
+            Handler::null()
+        }
+    }
+
+    pub unsafe fn drop_handler(data: *mut libc::c_void) {
+        if !data.is_null() {
+            let stack = libc::stack_t {
+                ss_sp: ptr::null_mut(),
+                ss_flags: SS_DISABLE,
+                // Workaround for bug in macOS implementation of sigaltstack
+                // UNIX2003 which returns ENOMEM when disabling a stack while
+                // passing ss_size smaller than MINSIGSTKSZ. According to POSIX
+                // both ss_sp and ss_size should be ignored in this case.
+                ss_size: SIGSTKSZ,
+            };
+            sigaltstack(&stack, ptr::null_mut());
+            // We know from `get_stackp` that the alternate stack we installed is part of a mapping
+            // that started one page earlier, so walk back a page and unmap from there.
+            munmap(data.sub(page_size()), SIGSTKSZ + page_size());
+        }
+    }
+}
+
+#[cfg(not(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "dragonfly",
+    target_os = "freebsd",
+    target_os = "hurd",
+    target_os = "solaris",
+    target_os = "illumos",
+    target_os = "netbsd",
+    target_os = "openbsd",
+)))]
+mod imp {
+    pub unsafe fn init() {}
+
+    pub unsafe fn cleanup() {}
+
+    pub unsafe fn make_handler() -> super::Handler {
+        super::Handler::null()
+    }
+
+    pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
+}
diff --git a/library/std/src/sys/pal/unix/stdio.rs b/library/std/src/sys/pal/unix/stdio.rs
new file mode 100644
index 00000000000..97e75f1b5b6
--- /dev/null
+++ b/library/std/src/sys/pal/unix/stdio.rs
@@ -0,0 +1,99 @@
+use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
+use crate::mem::ManuallyDrop;
+use crate::os::unix::io::FromRawFd;
+use crate::sys::fd::FileDesc;
+
+pub struct Stdin(());
+pub struct Stdout(());
+pub struct Stderr(());
+
+impl Stdin {
+    pub const fn new() -> Stdin {
+        Stdin(())
+    }
+}
+
+impl io::Read for Stdin {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDIN_FILENO)).read(buf) }
+    }
+
+    fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> io::Result<()> {
+        unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDIN_FILENO)).read_buf(buf) }
+    }
+
+    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+        unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDIN_FILENO)).read_vectored(bufs) }
+    }
+
+    #[inline]
+    fn is_read_vectored(&self) -> bool {
+        true
+    }
+}
+
+impl Stdout {
+    pub const fn new() -> Stdout {
+        Stdout(())
+    }
+}
+
+impl io::Write for Stdout {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDOUT_FILENO)).write(buf) }
+    }
+
+    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+        unsafe {
+            ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDOUT_FILENO)).write_vectored(bufs)
+        }
+    }
+
+    #[inline]
+    fn is_write_vectored(&self) -> bool {
+        true
+    }
+
+    #[inline]
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+impl Stderr {
+    pub const fn new() -> Stderr {
+        Stderr(())
+    }
+}
+
+impl io::Write for Stderr {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDERR_FILENO)).write(buf) }
+    }
+
+    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+        unsafe {
+            ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDERR_FILENO)).write_vectored(bufs)
+        }
+    }
+
+    #[inline]
+    fn is_write_vectored(&self) -> bool {
+        true
+    }
+
+    #[inline]
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+pub fn is_ebadf(err: &io::Error) -> bool {
+    err.raw_os_error() == Some(libc::EBADF as i32)
+}
+
+pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;
+
+pub fn panic_output() -> Option<impl io::Write> {
+    Some(Stderr::new())
+}
diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs
new file mode 100644
index 00000000000..7e4a01a5ecd
--- /dev/null
+++ b/library/std/src/sys/pal/unix/thread.rs
@@ -0,0 +1,992 @@
+use crate::cmp;
+use crate::ffi::CStr;
+use crate::io;
+use crate::mem;
+use crate::num::NonZeroUsize;
+use crate::ptr;
+use crate::sys::{os, stack_overflow};
+use crate::time::Duration;
+
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+use crate::sys::weak::dlsym;
+#[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto"))]
+use crate::sys::weak::weak;
+#[cfg(not(any(target_os = "l4re", target_os = "vxworks", target_os = "espidf")))]
+pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024;
+#[cfg(target_os = "l4re")]
+pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024;
+#[cfg(target_os = "vxworks")]
+pub const DEFAULT_MIN_STACK_SIZE: usize = 256 * 1024;
+#[cfg(target_os = "espidf")]
+pub const DEFAULT_MIN_STACK_SIZE: usize = 0; // 0 indicates that the stack size configured in the ESP-IDF menuconfig system should be used
+
+#[cfg(target_os = "fuchsia")]
+mod zircon {
+    type zx_handle_t = u32;
+    type zx_status_t = i32;
+    pub const ZX_PROP_NAME: u32 = 3;
+
+    extern "C" {
+        pub fn zx_object_set_property(
+            handle: zx_handle_t,
+            property: u32,
+            value: *const libc::c_void,
+            value_size: libc::size_t,
+        ) -> zx_status_t;
+        pub fn zx_thread_self() -> zx_handle_t;
+    }
+}
+
+pub struct Thread {
+    id: libc::pthread_t,
+}
+
+// Some platforms may have pthread_t as a pointer in which case we still want
+// a thread to be Send/Sync
+unsafe impl Send for Thread {}
+unsafe impl Sync for Thread {}
+
+impl Thread {
+    // unsafe: see thread::Builder::spawn_unchecked for safety requirements
+    pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+        let p = Box::into_raw(Box::new(p));
+        let mut native: libc::pthread_t = mem::zeroed();
+        let mut attr: libc::pthread_attr_t = mem::zeroed();
+        assert_eq!(libc::pthread_attr_init(&mut attr), 0);
+
+        #[cfg(target_os = "espidf")]
+        if stack > 0 {
+            // Only set the stack if a non-zero value is passed
+            // 0 is used as an indication that the default stack size configured in the ESP-IDF menuconfig system should be used
+            assert_eq!(
+                libc::pthread_attr_setstacksize(&mut attr, cmp::max(stack, min_stack_size(&attr))),
+                0
+            );
+        }
+
+        #[cfg(not(target_os = "espidf"))]
+        {
+            let stack_size = cmp::max(stack, min_stack_size(&attr));
+
+            match libc::pthread_attr_setstacksize(&mut attr, stack_size) {
+                0 => {}
+                n => {
+                    assert_eq!(n, libc::EINVAL);
+                    // EINVAL means |stack_size| is either too small or not a
+                    // multiple of the system page size. Because it's definitely
+                    // >= PTHREAD_STACK_MIN, it must be an alignment issue.
+                    // Round up to the nearest page and try again.
+                    let page_size = os::page_size();
+                    let stack_size =
+                        (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1);
+                    assert_eq!(libc::pthread_attr_setstacksize(&mut attr, stack_size), 0);
+                }
+            };
+        }
+
+        let ret = libc::pthread_create(&mut native, &attr, thread_start, p as *mut _);
+        // Note: if the thread creation fails and this assert fails, then p will
+        // be leaked. However, an alternative design could cause double-free
+        // which is clearly worse.
+        assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
+
+        return if ret != 0 {
+            // The thread failed to start and as a result p was not consumed. Therefore, it is
+            // safe to reconstruct the box so that it gets deallocated.
+            drop(Box::from_raw(p));
+            Err(io::Error::from_raw_os_error(ret))
+        } else {
+            Ok(Thread { id: native })
+        };
+
+        extern "C" fn thread_start(main: *mut libc::c_void) -> *mut libc::c_void {
+            unsafe {
+                // Next, set up our stack overflow handler which may get triggered if we run
+                // out of stack.
+                let _handler = stack_overflow::Handler::new();
+                // Finally, let's run some code.
+                Box::from_raw(main as *mut Box<dyn FnOnce()>)();
+            }
+            ptr::null_mut()
+        }
+    }
+
+    pub fn yield_now() {
+        let ret = unsafe { libc::sched_yield() };
+        debug_assert_eq!(ret, 0);
+    }
+
+    #[cfg(target_os = "android")]
+    pub fn set_name(name: &CStr) {
+        const PR_SET_NAME: libc::c_int = 15;
+        unsafe {
+            libc::prctl(
+                PR_SET_NAME,
+                name.as_ptr(),
+                0 as libc::c_ulong,
+                0 as libc::c_ulong,
+                0 as libc::c_ulong,
+            );
+        }
+    }
+
+    #[cfg(target_os = "linux")]
+    pub fn set_name(name: &CStr) {
+        const TASK_COMM_LEN: usize = 16;
+
+        unsafe {
+            // Available since glibc 2.12, musl 1.1.16, and uClibc 1.0.20.
+            let name = truncate_cstr::<{ TASK_COMM_LEN }>(name);
+            let res = libc::pthread_setname_np(libc::pthread_self(), name.as_ptr());
+            // We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
+            debug_assert_eq!(res, 0);
+        }
+    }
+
+    #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
+    pub fn set_name(name: &CStr) {
+        unsafe {
+            libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr());
+        }
+    }
+
+    #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))]
+    pub fn set_name(name: &CStr) {
+        unsafe {
+            let name = truncate_cstr::<{ libc::MAXTHREADNAMESIZE }>(name);
+            let res = libc::pthread_setname_np(name.as_ptr());
+            // We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
+            debug_assert_eq!(res, 0);
+        }
+    }
+
+    #[cfg(target_os = "netbsd")]
+    pub fn set_name(name: &CStr) {
+        unsafe {
+            let res = libc::pthread_setname_np(
+                libc::pthread_self(),
+                c"%s".as_ptr(),
+                name.as_ptr() as *mut libc::c_void,
+            );
+            debug_assert_eq!(res, 0);
+        }
+    }
+
+    #[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto"))]
+    pub fn set_name(name: &CStr) {
+        weak! {
+            fn pthread_setname_np(
+                libc::pthread_t, *const libc::c_char
+            ) -> libc::c_int
+        }
+
+        if let Some(f) = pthread_setname_np.get() {
+            #[cfg(target_os = "nto")]
+            let name = truncate_cstr::<{ libc::_NTO_THREAD_NAME_MAX as usize }>(name);
+
+            let res = unsafe { f(libc::pthread_self(), name.as_ptr()) };
+            debug_assert_eq!(res, 0);
+        }
+    }
+
+    #[cfg(target_os = "fuchsia")]
+    pub fn set_name(name: &CStr) {
+        use self::zircon::*;
+        unsafe {
+            zx_object_set_property(
+                zx_thread_self(),
+                ZX_PROP_NAME,
+                name.as_ptr() as *const libc::c_void,
+                name.to_bytes().len(),
+            );
+        }
+    }
+
+    #[cfg(target_os = "haiku")]
+    pub fn set_name(name: &CStr) {
+        unsafe {
+            let thread_self = libc::find_thread(ptr::null_mut());
+            let res = libc::rename_thread(thread_self, name.as_ptr());
+            // We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
+            debug_assert_eq!(res, libc::B_OK);
+        }
+    }
+
+    #[cfg(any(
+        target_env = "newlib",
+        target_os = "l4re",
+        target_os = "emscripten",
+        target_os = "redox",
+        target_os = "vxworks",
+        target_os = "hurd",
+        target_os = "aix",
+    ))]
+    pub fn set_name(_name: &CStr) {
+        // Newlib, Emscripten, and VxWorks have no way to set a thread name.
+    }
+
+    #[cfg(not(target_os = "espidf"))]
+    pub fn sleep(dur: Duration) {
+        let mut secs = dur.as_secs();
+        let mut nsecs = dur.subsec_nanos() as _;
+
+        // If we're awoken with a signal then the return value will be -1 and
+        // nanosleep will fill in `ts` with the remaining time.
+        unsafe {
+            while secs > 0 || nsecs > 0 {
+                let mut ts = libc::timespec {
+                    tv_sec: cmp::min(libc::time_t::MAX as u64, secs) as libc::time_t,
+                    tv_nsec: nsecs,
+                };
+                secs -= ts.tv_sec as u64;
+                let ts_ptr = &mut ts as *mut _;
+                if libc::nanosleep(ts_ptr, ts_ptr) == -1 {
+                    assert_eq!(os::errno(), libc::EINTR);
+                    secs += ts.tv_sec as u64;
+                    nsecs = ts.tv_nsec;
+                } else {
+                    nsecs = 0;
+                }
+            }
+        }
+    }
+
+    #[cfg(target_os = "espidf")]
+    pub fn sleep(dur: Duration) {
+        let mut micros = dur.as_micros();
+        unsafe {
+            while micros > 0 {
+                let st = if micros > u32::MAX as u128 { u32::MAX } else { micros as u32 };
+                libc::usleep(st);
+
+                micros -= st as u128;
+            }
+        }
+    }
+
+    pub fn join(self) {
+        unsafe {
+            let ret = libc::pthread_join(self.id, ptr::null_mut());
+            mem::forget(self);
+            assert!(ret == 0, "failed to join thread: {}", io::Error::from_raw_os_error(ret));
+        }
+    }
+
+    pub fn id(&self) -> libc::pthread_t {
+        self.id
+    }
+
+    pub fn into_id(self) -> libc::pthread_t {
+        let id = self.id;
+        mem::forget(self);
+        id
+    }
+}
+
+impl Drop for Thread {
+    fn drop(&mut self) {
+        let ret = unsafe { libc::pthread_detach(self.id) };
+        debug_assert_eq!(ret, 0);
+    }
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "tvos",
+    target_os = "watchos",
+    target_os = "nto",
+))]
+fn truncate_cstr<const MAX_WITH_NUL: usize>(cstr: &CStr) -> [libc::c_char; MAX_WITH_NUL] {
+    let mut result = [0; MAX_WITH_NUL];
+    for (src, dst) in cstr.to_bytes().iter().zip(&mut result[..MAX_WITH_NUL - 1]) {
+        *dst = *src as libc::c_char;
+    }
+    result
+}
+
+pub fn available_parallelism() -> io::Result<NonZeroUsize> {
+    cfg_if::cfg_if! {
+        if #[cfg(any(
+            target_os = "android",
+            target_os = "emscripten",
+            target_os = "fuchsia",
+            target_os = "hurd",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "linux",
+            target_os = "macos",
+            target_os = "solaris",
+            target_os = "illumos",
+            target_os = "aix",
+        ))] {
+            #[allow(unused_assignments)]
+            #[allow(unused_mut)]
+            let mut quota = usize::MAX;
+
+            #[cfg(any(target_os = "android", target_os = "linux"))]
+            {
+                quota = cgroups::quota().max(1);
+                let mut set: libc::cpu_set_t = unsafe { mem::zeroed() };
+                unsafe {
+                    if libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut set) == 0 {
+                        let count = libc::CPU_COUNT(&set) as usize;
+                        let count = count.min(quota);
+
+                        // According to sched_getaffinity's API it should always be non-zero, but
+                        // some old MIPS kernels were buggy and zero-initialized the mask if
+                        // none was explicitly set.
+                        // In that case we use the sysconf fallback.
+                        if let Some(count) = NonZeroUsize::new(count) {
+                            return Ok(count)
+                        }
+                    }
+                }
+            }
+            match unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) } {
+                -1 => Err(io::Error::last_os_error()),
+                0 => Err(io::const_io_error!(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform")),
+                cpus => {
+                    let count = cpus as usize;
+                    // Cover the unusual situation where we were able to get the quota but not the affinity mask
+                    let count = count.min(quota);
+                    Ok(unsafe { NonZeroUsize::new_unchecked(count) })
+                }
+            }
+        } else if #[cfg(any(
+                   target_os = "freebsd",
+                   target_os = "dragonfly",
+                   target_os = "openbsd",
+                   target_os = "netbsd",
+               ))] {
+            use crate::ptr;
+
+            #[cfg(target_os = "freebsd")]
+            {
+                let mut set: libc::cpuset_t = unsafe { mem::zeroed() };
+                unsafe {
+                    if libc::cpuset_getaffinity(
+                        libc::CPU_LEVEL_WHICH,
+                        libc::CPU_WHICH_PID,
+                        -1,
+                        mem::size_of::<libc::cpuset_t>(),
+                        &mut set,
+                    ) == 0 {
+                        let count = libc::CPU_COUNT(&set) as usize;
+                        if count > 0 {
+                            return Ok(NonZeroUsize::new_unchecked(count));
+                        }
+                    }
+                }
+            }
+
+            #[cfg(target_os = "netbsd")]
+            {
+                unsafe {
+                    let set = libc::_cpuset_create();
+                    if !set.is_null() {
+                        let mut count: usize = 0;
+                        if libc::pthread_getaffinity_np(libc::pthread_self(), libc::_cpuset_size(set), set) == 0 {
+                            for i in 0..u64::MAX {
+                                match libc::_cpuset_isset(i, set) {
+                                    -1 => break,
+                                    0 => continue,
+                                    _ => count = count + 1,
+                                }
+                            }
+                        }
+                        libc::_cpuset_destroy(set);
+                        if let Some(count) = NonZeroUsize::new(count) {
+                            return Ok(count);
+                        }
+                    }
+                }
+            }
+
+            let mut cpus: libc::c_uint = 0;
+            let mut cpus_size = crate::mem::size_of_val(&cpus);
+
+            unsafe {
+                cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint;
+            }
+
+            // Fallback approach in case of errors or no hardware threads.
+            if cpus < 1 {
+                let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
+                let res = unsafe {
+                    libc::sysctl(
+                        mib.as_mut_ptr(),
+                        2,
+                        &mut cpus as *mut _ as *mut _,
+                        &mut cpus_size as *mut _ as *mut _,
+                        ptr::null_mut(),
+                        0,
+                    )
+                };
+
+                // Handle errors if any.
+                if res == -1 {
+                    return Err(io::Error::last_os_error());
+                } else if cpus == 0 {
+                    return Err(io::const_io_error!(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"));
+                }
+            }
+
+            Ok(unsafe { NonZeroUsize::new_unchecked(cpus as usize) })
+        } else if #[cfg(target_os = "nto")] {
+            unsafe {
+                use libc::_syspage_ptr;
+                if _syspage_ptr.is_null() {
+                    Err(io::const_io_error!(io::ErrorKind::NotFound, "No syspage available"))
+                } else {
+                    let cpus = (*_syspage_ptr).num_cpu;
+                    NonZeroUsize::new(cpus as usize)
+                        .ok_or(io::const_io_error!(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"))
+                }
+            }
+        } else if #[cfg(target_os = "haiku")] {
+            // system_info cpu_count field gets the static data set at boot time with `smp_set_num_cpus`
+            // `get_system_info` calls then `smp_get_num_cpus`
+            unsafe {
+                let mut sinfo: libc::system_info = crate::mem::zeroed();
+                let res = libc::get_system_info(&mut sinfo);
+
+                if res != libc::B_OK {
+                    return Err(io::const_io_error!(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"));
+                }
+
+                Ok(NonZeroUsize::new_unchecked(sinfo.cpu_count as usize))
+            }
+        } else {
+            // FIXME: implement on vxWorks, Redox, l4re
+            Err(io::const_io_error!(io::ErrorKind::Unsupported, "Getting the number of hardware threads is not supported on the target platform"))
+        }
+    }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+mod cgroups {
+    //! Currently not covered
+    //! * cgroup v2 in non-standard mountpoints
+    //! * paths containing control characters or spaces, since those would be escaped in procfs
+    //!   output and we don't unescape
+    use crate::borrow::Cow;
+    use crate::ffi::OsString;
+    use crate::fs::{try_exists, File};
+    use crate::io::Read;
+    use crate::io::{BufRead, BufReader};
+    use crate::os::unix::ffi::OsStringExt;
+    use crate::path::Path;
+    use crate::path::PathBuf;
+    use crate::str::from_utf8;
+
+    #[derive(PartialEq)]
+    enum Cgroup {
+        V1,
+        V2,
+    }
+
+    /// Returns cgroup CPU quota in core-equivalents, rounded down or usize::MAX if the quota cannot
+    /// be determined or is not set.
+    pub(super) fn quota() -> usize {
+        let mut quota = usize::MAX;
+        if cfg!(miri) {
+            // Attempting to open a file fails under default flags due to isolation.
+            // And Miri does not have parallelism anyway.
+            return quota;
+        }
+
+        let _: Option<()> = try {
+            let mut buf = Vec::with_capacity(128);
+            // find our place in the cgroup hierarchy
+            File::open("/proc/self/cgroup").ok()?.read_to_end(&mut buf).ok()?;
+            let (cgroup_path, version) =
+                buf.split(|&c| c == b'\n').fold(None, |previous, line| {
+                    let mut fields = line.splitn(3, |&c| c == b':');
+                    // 2nd field is a list of controllers for v1 or empty for v2
+                    let version = match fields.nth(1) {
+                        Some(b"") => Cgroup::V2,
+                        Some(controllers)
+                            if from_utf8(controllers)
+                                .is_ok_and(|c| c.split(',').any(|c| c == "cpu")) =>
+                        {
+                            Cgroup::V1
+                        }
+                        _ => return previous,
+                    };
+
+                    // already-found v1 trumps v2 since it explicitly specifies its controllers
+                    if previous.is_some() && version == Cgroup::V2 {
+                        return previous;
+                    }
+
+                    let path = fields.last()?;
+                    // skip leading slash
+                    Some((path[1..].to_owned(), version))
+                })?;
+            let cgroup_path = PathBuf::from(OsString::from_vec(cgroup_path));
+
+            quota = match version {
+                Cgroup::V1 => quota_v1(cgroup_path),
+                Cgroup::V2 => quota_v2(cgroup_path),
+            };
+        };
+
+        quota
+    }
+
+    fn quota_v2(group_path: PathBuf) -> usize {
+        let mut quota = usize::MAX;
+
+        let mut path = PathBuf::with_capacity(128);
+        let mut read_buf = String::with_capacity(20);
+
+        // standard mount location defined in file-hierarchy(7) manpage
+        let cgroup_mount = "/sys/fs/cgroup";
+
+        path.push(cgroup_mount);
+        path.push(&group_path);
+
+        path.push("cgroup.controllers");
+
+        // skip if we're not looking at cgroup2
+        if matches!(try_exists(&path), Err(_) | Ok(false)) {
+            return usize::MAX;
+        };
+
+        path.pop();
+
+        let _: Option<()> = try {
+            while path.starts_with(cgroup_mount) {
+                path.push("cpu.max");
+
+                read_buf.clear();
+
+                if File::open(&path).and_then(|mut f| f.read_to_string(&mut read_buf)).is_ok() {
+                    let raw_quota = read_buf.lines().next()?;
+                    let mut raw_quota = raw_quota.split(' ');
+                    let limit = raw_quota.next()?;
+                    let period = raw_quota.next()?;
+                    match (limit.parse::<usize>(), period.parse::<usize>()) {
+                        (Ok(limit), Ok(period)) if period > 0 => {
+                            quota = quota.min(limit / period);
+                        }
+                        _ => {}
+                    }
+                }
+
+                path.pop(); // pop filename
+                path.pop(); // pop dir
+            }
+        };
+
+        quota
+    }
+
+    fn quota_v1(group_path: PathBuf) -> usize {
+        let mut quota = usize::MAX;
+        let mut path = PathBuf::with_capacity(128);
+        let mut read_buf = String::with_capacity(20);
+
+        // Hardcode commonly used locations mentioned in the cgroups(7) manpage
+        // if that doesn't work scan mountinfo and adjust `group_path` for bind-mounts
+        let mounts: &[fn(&Path) -> Option<(_, &Path)>] = &[
+            |p| Some((Cow::Borrowed("/sys/fs/cgroup/cpu"), p)),
+            |p| Some((Cow::Borrowed("/sys/fs/cgroup/cpu,cpuacct"), p)),
+            // this can be expensive on systems with tons of mountpoints
+            // but we only get to this point when /proc/self/cgroups explicitly indicated
+            // this process belongs to a cpu-controller cgroup v1 and the defaults didn't work
+            find_mountpoint,
+        ];
+
+        for mount in mounts {
+            let Some((mount, group_path)) = mount(&group_path) else { continue };
+
+            path.clear();
+            path.push(mount.as_ref());
+            path.push(&group_path);
+
+            // skip if we guessed the mount incorrectly
+            if matches!(try_exists(&path), Err(_) | Ok(false)) {
+                continue;
+            }
+
+            while path.starts_with(mount.as_ref()) {
+                let mut parse_file = |name| {
+                    path.push(name);
+                    read_buf.clear();
+
+                    let f = File::open(&path);
+                    path.pop(); // restore buffer before any early returns
+                    f.ok()?.read_to_string(&mut read_buf).ok()?;
+                    let parsed = read_buf.trim().parse::<usize>().ok()?;
+
+                    Some(parsed)
+                };
+
+                let limit = parse_file("cpu.cfs_quota_us");
+                let period = parse_file("cpu.cfs_period_us");
+
+                match (limit, period) {
+                    (Some(limit), Some(period)) if period > 0 => quota = quota.min(limit / period),
+                    _ => {}
+                }
+
+                path.pop();
+            }
+
+            // we passed the try_exists above so we should have traversed the correct hierarchy
+            // when reaching this line
+            break;
+        }
+
+        quota
+    }
+
+    /// Scan mountinfo for cgroup v1 mountpoint with a cpu controller
+    ///
+    /// If the cgroupfs is a bind mount then `group_path` is adjusted to skip
+    /// over the already-included prefix
+    fn find_mountpoint(group_path: &Path) -> Option<(Cow<'static, str>, &Path)> {
+        let mut reader = BufReader::new(File::open("/proc/self/mountinfo").ok()?);
+        let mut line = String::with_capacity(256);
+        loop {
+            line.clear();
+            if reader.read_line(&mut line).ok()? == 0 {
+                break;
+            }
+
+            let line = line.trim();
+            let mut items = line.split(' ');
+
+            let sub_path = items.nth(3)?;
+            let mount_point = items.next()?;
+            let mount_opts = items.next_back()?;
+            let filesystem_type = items.nth_back(1)?;
+
+            if filesystem_type != "cgroup" || !mount_opts.split(',').any(|opt| opt == "cpu") {
+                // not a cgroup / not a cpu-controller
+                continue;
+            }
+
+            let sub_path = Path::new(sub_path).strip_prefix("/").ok()?;
+
+            if !group_path.starts_with(sub_path) {
+                // this is a bind-mount and the bound subdirectory
+                // does not contain the cgroup this process belongs to
+                continue;
+            }
+
+            let trimmed_group_path = group_path.strip_prefix(sub_path).ok()?;
+
+            return Some((Cow::Owned(mount_point.to_owned()), trimmed_group_path));
+        }
+
+        None
+    }
+}
+
+#[cfg(all(
+    not(target_os = "linux"),
+    not(target_os = "freebsd"),
+    not(target_os = "hurd"),
+    not(target_os = "macos"),
+    not(target_os = "netbsd"),
+    not(target_os = "openbsd"),
+    not(target_os = "solaris")
+))]
+#[cfg_attr(test, allow(dead_code))]
+pub mod guard {
+    use crate::ops::Range;
+    pub type Guard = Range<usize>;
+    pub unsafe fn current() -> Option<Guard> {
+        None
+    }
+    pub unsafe fn init() -> Option<Guard> {
+        None
+    }
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "freebsd",
+    target_os = "hurd",
+    target_os = "macos",
+    target_os = "netbsd",
+    target_os = "openbsd",
+    target_os = "solaris"
+))]
+#[cfg_attr(test, allow(dead_code))]
+pub mod guard {
+    #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
+    use libc::{mmap as mmap64, mprotect};
+    #[cfg(all(target_os = "linux", target_env = "gnu"))]
+    use libc::{mmap64, mprotect};
+    use libc::{MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE};
+
+    use crate::io;
+    use crate::ops::Range;
+    use crate::sync::atomic::{AtomicUsize, Ordering};
+    use crate::sys::os;
+
+    // This is initialized in init() and only read from after
+    static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
+
+    pub type Guard = Range<usize>;
+
+    #[cfg(target_os = "solaris")]
+    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
+        let mut current_stack: libc::stack_t = crate::mem::zeroed();
+        assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
+        Some(current_stack.ss_sp)
+    }
+
+    #[cfg(target_os = "macos")]
+    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
+        let th = libc::pthread_self();
+        let stackptr = libc::pthread_get_stackaddr_np(th);
+        Some(stackptr.map_addr(|addr| addr - libc::pthread_get_stacksize_np(th)))
+    }
+
+    #[cfg(target_os = "openbsd")]
+    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
+        let mut current_stack: libc::stack_t = crate::mem::zeroed();
+        assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
+
+        let stack_ptr = current_stack.ss_sp;
+        let stackaddr = if libc::pthread_main_np() == 1 {
+            // main thread
+            stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
+        } else {
+            // new thread
+            stack_ptr.addr() - current_stack.ss_size
+        };
+        Some(stack_ptr.with_addr(stackaddr))
+    }
+
+    #[cfg(any(
+        target_os = "android",
+        target_os = "freebsd",
+        target_os = "hurd",
+        target_os = "linux",
+        target_os = "netbsd",
+        target_os = "l4re"
+    ))]
+    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
+        let mut ret = None;
+        let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
+        #[cfg(target_os = "freebsd")]
+        assert_eq!(libc::pthread_attr_init(&mut attr), 0);
+        #[cfg(target_os = "freebsd")]
+        let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
+        #[cfg(not(target_os = "freebsd"))]
+        let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
+        if e == 0 {
+            let mut stackaddr = crate::ptr::null_mut();
+            let mut stacksize = 0;
+            assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, &mut stacksize), 0);
+            ret = Some(stackaddr);
+        }
+        if e == 0 || cfg!(target_os = "freebsd") {
+            assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
+        }
+        ret
+    }
+
+    // Precondition: PAGE_SIZE is initialized.
+    unsafe fn get_stack_start_aligned() -> Option<*mut libc::c_void> {
+        let page_size = PAGE_SIZE.load(Ordering::Relaxed);
+        assert!(page_size != 0);
+        let stackptr = get_stack_start()?;
+        let stackaddr = stackptr.addr();
+
+        // Ensure stackaddr is page aligned! A parent process might
+        // have reset RLIMIT_STACK to be non-page aligned. The
+        // pthread_attr_getstack() reports the usable stack area
+        // stackaddr < stackaddr + stacksize, so if stackaddr is not
+        // page-aligned, calculate the fix such that stackaddr <
+        // new_page_aligned_stackaddr < stackaddr + stacksize
+        let remainder = stackaddr % page_size;
+        Some(if remainder == 0 {
+            stackptr
+        } else {
+            stackptr.with_addr(stackaddr + page_size - remainder)
+        })
+    }
+
+    pub unsafe fn init() -> Option<Guard> {
+        let page_size = os::page_size();
+        PAGE_SIZE.store(page_size, Ordering::Relaxed);
+
+        if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
+            // Linux doesn't allocate the whole stack right away, and
+            // the kernel has its own stack-guard mechanism to fault
+            // when growing too close to an existing mapping. If we map
+            // our own guard, then the kernel starts enforcing a rather
+            // large gap above that, rendering much of the possible
+            // stack space useless. See #43052.
+            //
+            // Instead, we'll just note where we expect rlimit to start
+            // faulting, so our handler can report "stack overflow", and
+            // trust that the kernel's own stack guard will work.
+            let stackptr = get_stack_start_aligned()?;
+            let stackaddr = stackptr.addr();
+            Some(stackaddr - page_size..stackaddr)
+        } else if cfg!(all(target_os = "linux", target_env = "musl")) {
+            // For the main thread, the musl's pthread_attr_getstack
+            // returns the current stack size, rather than maximum size
+            // it can eventually grow to. It cannot be used to determine
+            // the position of kernel's stack guard.
+            None
+        } else if cfg!(target_os = "freebsd") {
+            // FreeBSD's stack autogrows, and optionally includes a guard page
+            // at the bottom. If we try to remap the bottom of the stack
+            // ourselves, FreeBSD's guard page moves upwards. So we'll just use
+            // the builtin guard page.
+            let stackptr = get_stack_start_aligned()?;
+            let guardaddr = stackptr.addr();
+            // Technically the number of guard pages is tunable and controlled
+            // by the security.bsd.stack_guard_page sysctl, but there are
+            // few reasons to change it from the default. The default value has
+            // been 1 ever since FreeBSD 11.1 and 10.4.
+            const GUARD_PAGES: usize = 1;
+            let guard = guardaddr..guardaddr + GUARD_PAGES * page_size;
+            Some(guard)
+        } else if cfg!(target_os = "openbsd") {
+            // OpenBSD stack already includes a guard page, and stack is
+            // immutable.
+            //
+            // We'll just note where we expect rlimit to start
+            // faulting, so our handler can report "stack overflow", and
+            // trust that the kernel's own stack guard will work.
+            let stackptr = get_stack_start_aligned()?;
+            let stackaddr = stackptr.addr();
+            Some(stackaddr - page_size..stackaddr)
+        } else {
+            // Reallocate the last page of the stack.
+            // This ensures SIGBUS will be raised on
+            // stack overflow.
+            // Systems which enforce strict PAX MPROTECT do not allow
+            // to mprotect() a mapping with less restrictive permissions
+            // than the initial mmap() used, so we mmap() here with
+            // read/write permissions and only then mprotect() it to
+            // no permissions at all. See issue #50313.
+            let stackptr = get_stack_start_aligned()?;
+            let result = mmap64(
+                stackptr,
+                page_size,
+                PROT_READ | PROT_WRITE,
+                MAP_PRIVATE | MAP_ANON | MAP_FIXED,
+                -1,
+                0,
+            );
+            if result != stackptr || result == MAP_FAILED {
+                panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
+            }
+
+            let result = mprotect(stackptr, page_size, PROT_NONE);
+            if result != 0 {
+                panic!("failed to protect the guard page: {}", io::Error::last_os_error());
+            }
+
+            let guardaddr = stackptr.addr();
+
+            Some(guardaddr..guardaddr + page_size)
+        }
+    }
+
+    #[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "solaris"))]
+    pub unsafe fn current() -> Option<Guard> {
+        let stackptr = get_stack_start()?;
+        let stackaddr = stackptr.addr();
+        Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
+    }
+
+    #[cfg(any(
+        target_os = "android",
+        target_os = "freebsd",
+        target_os = "hurd",
+        target_os = "linux",
+        target_os = "netbsd",
+        target_os = "l4re"
+    ))]
+    pub unsafe fn current() -> Option<Guard> {
+        let mut ret = None;
+        let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
+        #[cfg(target_os = "freebsd")]
+        assert_eq!(libc::pthread_attr_init(&mut attr), 0);
+        #[cfg(target_os = "freebsd")]
+        let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
+        #[cfg(not(target_os = "freebsd"))]
+        let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
+        if e == 0 {
+            let mut guardsize = 0;
+            assert_eq!(libc::pthread_attr_getguardsize(&attr, &mut guardsize), 0);
+            if guardsize == 0 {
+                if cfg!(all(target_os = "linux", target_env = "musl")) {
+                    // musl versions before 1.1.19 always reported guard
+                    // size obtained from pthread_attr_get_np as zero.
+                    // Use page size as a fallback.
+                    guardsize = PAGE_SIZE.load(Ordering::Relaxed);
+                } else {
+                    panic!("there is no guard page");
+                }
+            }
+            let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
+            let mut size = 0;
+            assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackptr, &mut size), 0);
+
+            let stackaddr = stackptr.addr();
+            ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) {
+                Some(stackaddr - guardsize..stackaddr)
+            } else if cfg!(all(target_os = "linux", target_env = "musl")) {
+                Some(stackaddr - guardsize..stackaddr)
+            } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))
+            {
+                // glibc used to include the guard area within the stack, as noted in the BUGS
+                // section of `man pthread_attr_getguardsize`. This has been corrected starting
+                // with glibc 2.27, and in some distro backports, so the guard is now placed at the
+                // end (below) the stack. There's no easy way for us to know which we have at
+                // runtime, so we'll just match any fault in the range right above or below the
+                // stack base to call that fault a stack overflow.
+                Some(stackaddr - guardsize..stackaddr + guardsize)
+            } else {
+                Some(stackaddr..stackaddr + guardsize)
+            };
+        }
+        if e == 0 || cfg!(target_os = "freebsd") {
+            assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
+        }
+        ret
+    }
+}
+
+// glibc >= 2.15 has a __pthread_get_minstack() function that returns
+// PTHREAD_STACK_MIN plus bytes needed for thread-local storage.
+// We need that information to avoid blowing up when a small stack
+// is created in an application with big thread-local storage requirements.
+// See #6233 for rationale and details.
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+fn min_stack_size(attr: *const libc::pthread_attr_t) -> usize {
+    // We use dlsym to avoid an ELF version dependency on GLIBC_PRIVATE. (#23628)
+    // We shouldn't really be using such an internal symbol, but there's currently
+    // no other way to account for the TLS size.
+    dlsym!(fn __pthread_get_minstack(*const libc::pthread_attr_t) -> libc::size_t);
+
+    match __pthread_get_minstack.get() {
+        None => libc::PTHREAD_STACK_MIN,
+        Some(f) => unsafe { f(attr) },
+    }
+}
+
+// No point in looking up __pthread_get_minstack() on non-glibc platforms.
+#[cfg(all(not(all(target_os = "linux", target_env = "gnu")), not(target_os = "netbsd")))]
+fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
+    libc::PTHREAD_STACK_MIN
+}
+
+#[cfg(target_os = "netbsd")]
+fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
+    2048 // just a guess
+}
diff --git a/library/std/src/sys/pal/unix/thread_local_dtor.rs b/library/std/src/sys/pal/unix/thread_local_dtor.rs
new file mode 100644
index 00000000000..58f7ab84101
--- /dev/null
+++ b/library/std/src/sys/pal/unix/thread_local_dtor.rs
@@ -0,0 +1,124 @@
+#![cfg(target_thread_local)]
+#![unstable(feature = "thread_local_internals", issue = "none")]
+
+//! Provides thread-local destructors without an associated "key", which
+//! can be more efficient.
+
+// Since what appears to be glibc 2.18 this symbol has been shipped which
+// GCC and clang both use to invoke destructors in thread_local globals, so
+// let's do the same!
+//
+// Note, however, that we run on lots older linuxes, as well as cross
+// compiling from a newer linux to an older linux, so we also have a
+// fallback implementation to use as well.
+#[cfg_attr(bootstrap, allow(unexpected_cfgs))]
+#[cfg(any(
+    target_os = "linux",
+    target_os = "android",
+    target_os = "fuchsia",
+    target_os = "redox",
+    target_os = "hurd"
+))]
+// FIXME: The Rust compiler currently omits weakly function definitions (i.e.,
+// __cxa_thread_atexit_impl) and its metadata from LLVM IR.
+#[no_sanitize(cfi, kcfi)]
+pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
+    use crate::mem;
+    use crate::sys_common::thread_local_dtor::register_dtor_fallback;
+
+    /// This is necessary because the __cxa_thread_atexit_impl implementation
+    /// std links to by default may be a C or C++ implementation that was not
+    /// compiled using the Clang integer normalization option.
+    #[cfg(sanitizer_cfi_normalize_integers)]
+    use core::ffi::c_int;
+    #[cfg(not(sanitizer_cfi_normalize_integers))]
+    #[cfi_encoding = "i"]
+    #[repr(transparent)]
+    pub struct c_int(pub libc::c_int);
+
+    extern "C" {
+        #[linkage = "extern_weak"]
+        static __dso_handle: *mut u8;
+        #[linkage = "extern_weak"]
+        static __cxa_thread_atexit_impl: Option<
+            extern "C" fn(
+                unsafe extern "C" fn(*mut libc::c_void),
+                *mut libc::c_void,
+                *mut libc::c_void,
+            ) -> c_int,
+        >;
+    }
+
+    if let Some(f) = __cxa_thread_atexit_impl {
+        unsafe {
+            f(
+                mem::transmute::<
+                    unsafe extern "C" fn(*mut u8),
+                    unsafe extern "C" fn(*mut libc::c_void),
+                >(dtor),
+                t.cast(),
+                &__dso_handle as *const _ as *mut _,
+            );
+        }
+        return;
+    }
+    register_dtor_fallback(t, dtor);
+}
+
+// This implementation is very similar to register_dtor_fallback in
+// sys_common/thread_local.rs. The main difference is that we want to hook into
+// macOS's analog of the above linux function, _tlv_atexit. OSX will run the
+// registered dtors before any TLS slots get freed, and when the main thread
+// exits.
+//
+// Unfortunately, calling _tlv_atexit while tls dtors are running is UB. The
+// workaround below is to register, via _tlv_atexit, a custom DTOR list once per
+// thread. thread_local dtors are pushed to the DTOR list without calling
+// _tlv_atexit.
+#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos", target_os = "tvos"))]
+pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
+    use crate::cell::{Cell, RefCell};
+    use crate::ptr;
+
+    #[thread_local]
+    static REGISTERED: Cell<bool> = Cell::new(false);
+
+    #[thread_local]
+    static DTORS: RefCell<Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>> = RefCell::new(Vec::new());
+
+    if !REGISTERED.get() {
+        _tlv_atexit(run_dtors, ptr::null_mut());
+        REGISTERED.set(true);
+    }
+
+    extern "C" {
+        fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8);
+    }
+
+    match DTORS.try_borrow_mut() {
+        Ok(mut dtors) => dtors.push((t, dtor)),
+        Err(_) => rtabort!("global allocator may not use TLS"),
+    }
+
+    unsafe extern "C" fn run_dtors(_: *mut u8) {
+        let mut list = DTORS.take();
+        while !list.is_empty() {
+            for (ptr, dtor) in list {
+                dtor(ptr);
+            }
+            list = DTORS.take();
+        }
+    }
+}
+
+#[cfg(any(
+    target_os = "vxworks",
+    target_os = "horizon",
+    target_os = "emscripten",
+    target_os = "aix"
+))]
+#[cfg_attr(target_family = "wasm", allow(unused))] // might remain unused depending on target details (e.g. wasm32-unknown-emscripten)
+pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
+    use crate::sys_common::thread_local_dtor::register_dtor_fallback;
+    register_dtor_fallback(t, dtor);
+}
diff --git a/library/std/src/sys/pal/unix/thread_local_key.rs b/library/std/src/sys/pal/unix/thread_local_key.rs
new file mode 100644
index 00000000000..2b2d079ee4d
--- /dev/null
+++ b/library/std/src/sys/pal/unix/thread_local_key.rs
@@ -0,0 +1,29 @@
+#![allow(dead_code)] // not used on all platforms
+
+use crate::mem;
+
+pub type Key = libc::pthread_key_t;
+
+#[inline]
+pub unsafe fn create(dtor: Option<unsafe extern "C" fn(*mut u8)>) -> Key {
+    let mut key = 0;
+    assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0);
+    key
+}
+
+#[inline]
+pub unsafe fn set(key: Key, value: *mut u8) {
+    let r = libc::pthread_setspecific(key, value as *mut _);
+    debug_assert_eq!(r, 0);
+}
+
+#[inline]
+pub unsafe fn get(key: Key) -> *mut u8 {
+    libc::pthread_getspecific(key) as *mut u8
+}
+
+#[inline]
+pub unsafe fn destroy(key: Key) {
+    let r = libc::pthread_key_delete(key);
+    debug_assert_eq!(r, 0);
+}
diff --git a/library/std/src/sys/pal/unix/thread_parking/darwin.rs b/library/std/src/sys/pal/unix/thread_parking/darwin.rs
new file mode 100644
index 00000000000..8231f3cba2d
--- /dev/null
+++ b/library/std/src/sys/pal/unix/thread_parking/darwin.rs
@@ -0,0 +1,130 @@
+//! Thread parking for Darwin-based systems.
+//!
+//! Darwin actually has futex syscalls (`__ulock_wait`/`__ulock_wake`), but they
+//! cannot be used in `std` because they are non-public (their use will lead to
+//! rejection from the App Store).
+//!
+//! Therefore, we need to look for other synchronization primitives. Luckily, Darwin
+//! supports semaphores, which allow us to implement the behaviour we need with
+//! only one primitive (as opposed to a mutex-condvar pair). We use the semaphore
+//! provided by libdispatch, as the underlying Mach semaphore is only dubiously
+//! public.
+
+use crate::pin::Pin;
+use crate::sync::atomic::{
+    AtomicI8,
+    Ordering::{Acquire, Release},
+};
+use crate::time::Duration;
+
+type dispatch_semaphore_t = *mut crate::ffi::c_void;
+type dispatch_time_t = u64;
+
+const DISPATCH_TIME_NOW: dispatch_time_t = 0;
+const DISPATCH_TIME_FOREVER: dispatch_time_t = !0;
+
+// Contained in libSystem.dylib, which is linked by default.
+extern "C" {
+    fn dispatch_time(when: dispatch_time_t, delta: i64) -> dispatch_time_t;
+    fn dispatch_semaphore_create(val: isize) -> dispatch_semaphore_t;
+    fn dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t) -> isize;
+    fn dispatch_semaphore_signal(dsema: dispatch_semaphore_t) -> isize;
+    fn dispatch_release(object: *mut crate::ffi::c_void);
+}
+
+const EMPTY: i8 = 0;
+const NOTIFIED: i8 = 1;
+const PARKED: i8 = -1;
+
+pub struct Parker {
+    semaphore: dispatch_semaphore_t,
+    state: AtomicI8,
+}
+
+unsafe impl Sync for Parker {}
+unsafe impl Send for Parker {}
+
+impl Parker {
+    pub unsafe fn new_in_place(parker: *mut Parker) {
+        let semaphore = dispatch_semaphore_create(0);
+        assert!(
+            !semaphore.is_null(),
+            "failed to create dispatch semaphore for thread synchronization"
+        );
+        parker.write(Parker { semaphore, state: AtomicI8::new(EMPTY) })
+    }
+
+    // Does not need `Pin`, but other implementation do.
+    pub unsafe fn park(self: Pin<&Self>) {
+        // The semaphore counter must be zero at this point, because unparking
+        // threads will not actually increase it until we signalled that we
+        // are waiting.
+
+        // Change NOTIFIED to EMPTY and EMPTY to PARKED.
+        if self.state.fetch_sub(1, Acquire) == NOTIFIED {
+            return;
+        }
+
+        // Another thread may increase the semaphore counter from this point on.
+        // If it is faster than us, we will decrement it again immediately below.
+        // If we are faster, we wait.
+
+        // Ensure that the semaphore counter has actually been decremented, even
+        // if the call timed out for some reason.
+        while dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER) != 0 {}
+
+        // At this point, the semaphore counter is zero again.
+
+        // We were definitely woken up, so we don't need to check the state.
+        // Still, we need to reset the state using a swap to observe the state
+        // change with acquire ordering.
+        self.state.swap(EMPTY, Acquire);
+    }
+
+    // Does not need `Pin`, but other implementation do.
+    pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
+        if self.state.fetch_sub(1, Acquire) == NOTIFIED {
+            return;
+        }
+
+        let nanos = dur.as_nanos().try_into().unwrap_or(i64::MAX);
+        let timeout = dispatch_time(DISPATCH_TIME_NOW, nanos);
+
+        let timeout = dispatch_semaphore_wait(self.semaphore, timeout) != 0;
+
+        let state = self.state.swap(EMPTY, Acquire);
+        if state == NOTIFIED && timeout {
+            // If the state was NOTIFIED but semaphore_wait returned without
+            // decrementing the count because of a timeout, it means another
+            // thread is about to call semaphore_signal. We must wait for that
+            // to happen to ensure the semaphore count is reset.
+            while dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER) != 0 {}
+        } else {
+            // Either a timeout occurred and we reset the state before any thread
+            // tried to wake us up, or we were woken up and reset the state,
+            // making sure to observe the state change with acquire ordering.
+            // Either way, the semaphore counter is now zero again.
+        }
+    }
+
+    // Does not need `Pin`, but other implementation do.
+    pub fn unpark(self: Pin<&Self>) {
+        let state = self.state.swap(NOTIFIED, Release);
+        if state == PARKED {
+            unsafe {
+                dispatch_semaphore_signal(self.semaphore);
+            }
+        }
+    }
+}
+
+impl Drop for Parker {
+    fn drop(&mut self) {
+        // SAFETY:
+        // We always ensure that the semaphore count is reset, so this will
+        // never cause an exception.
+        unsafe {
+            dispatch_release(self.semaphore);
+        }
+    }
+}
diff --git a/library/std/src/sys/pal/unix/thread_parking/mod.rs b/library/std/src/sys/pal/unix/thread_parking/mod.rs
new file mode 100644
index 00000000000..185333c072f
--- /dev/null
+++ b/library/std/src/sys/pal/unix/thread_parking/mod.rs
@@ -0,0 +1,32 @@
+//! Thread parking on systems without futex support.
+
+#![cfg(not(any(
+    target_os = "linux",
+    target_os = "android",
+    all(target_os = "emscripten", target_feature = "atomics"),
+    target_os = "freebsd",
+    target_os = "openbsd",
+    target_os = "dragonfly",
+    target_os = "fuchsia",
+)))]
+
+cfg_if::cfg_if! {
+    if #[cfg(all(
+        any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "watchos",
+            target_os = "tvos",
+        ),
+        not(miri),
+    ))] {
+        mod darwin;
+        pub use darwin::Parker;
+    } else if #[cfg(target_os = "netbsd")] {
+        mod netbsd;
+        pub use netbsd::{current, park, park_timeout, unpark, ThreadId};
+    } else {
+        mod pthread;
+        pub use pthread::Parker;
+    }
+}
diff --git a/library/std/src/sys/pal/unix/thread_parking/netbsd.rs b/library/std/src/sys/pal/unix/thread_parking/netbsd.rs
new file mode 100644
index 00000000000..3be08122138
--- /dev/null
+++ b/library/std/src/sys/pal/unix/thread_parking/netbsd.rs
@@ -0,0 +1,52 @@
+use crate::ffi::{c_int, c_void};
+use crate::ptr;
+use crate::time::Duration;
+use libc::{_lwp_self, clockid_t, lwpid_t, time_t, timespec, CLOCK_MONOTONIC};
+
+extern "C" {
+    fn ___lwp_park60(
+        clock_id: clockid_t,
+        flags: c_int,
+        ts: *mut timespec,
+        unpark: lwpid_t,
+        hint: *const c_void,
+        unparkhint: *const c_void,
+    ) -> c_int;
+    fn _lwp_unpark(lwp: lwpid_t, hint: *const c_void) -> c_int;
+}
+
+pub type ThreadId = lwpid_t;
+
+#[inline]
+pub fn current() -> ThreadId {
+    unsafe { _lwp_self() }
+}
+
+#[inline]
+pub fn park(hint: usize) {
+    unsafe {
+        ___lwp_park60(0, 0, ptr::null_mut(), 0, ptr::invalid(hint), ptr::null());
+    }
+}
+
+pub fn park_timeout(dur: Duration, hint: usize) {
+    let mut timeout = timespec {
+        // Saturate so that the operation will definitely time out
+        // (even if it is after the heat death of the universe).
+        tv_sec: dur.as_secs().try_into().ok().unwrap_or(time_t::MAX),
+        tv_nsec: dur.subsec_nanos().into(),
+    };
+
+    // Timeout needs to be mutable since it is modified on NetBSD 9.0 and
+    // above.
+    unsafe {
+        ___lwp_park60(CLOCK_MONOTONIC, 0, &mut timeout, 0, ptr::invalid(hint), ptr::null());
+    }
+}
+
+#[inline]
+pub fn unpark(tid: ThreadId, hint: usize) {
+    unsafe {
+        _lwp_unpark(tid, ptr::invalid(hint));
+    }
+}
diff --git a/library/std/src/sys/pal/unix/thread_parking/pthread.rs b/library/std/src/sys/pal/unix/thread_parking/pthread.rs
new file mode 100644
index 00000000000..ae805d84399
--- /dev/null
+++ b/library/std/src/sys/pal/unix/thread_parking/pthread.rs
@@ -0,0 +1,284 @@
+//! Thread parking without `futex` using the `pthread` synchronization primitives.
+
+use crate::cell::UnsafeCell;
+use crate::marker::PhantomPinned;
+use crate::pin::Pin;
+use crate::ptr::addr_of_mut;
+use crate::sync::atomic::AtomicUsize;
+use crate::sync::atomic::Ordering::SeqCst;
+#[cfg(not(target_os = "nto"))]
+use crate::sys::time::TIMESPEC_MAX;
+#[cfg(target_os = "nto")]
+use crate::sys::time::TIMESPEC_MAX_CAPPED;
+use crate::time::Duration;
+
+const EMPTY: usize = 0;
+const PARKED: usize = 1;
+const NOTIFIED: usize = 2;
+
+unsafe fn lock(lock: *mut libc::pthread_mutex_t) {
+    let r = libc::pthread_mutex_lock(lock);
+    debug_assert_eq!(r, 0);
+}
+
+unsafe fn unlock(lock: *mut libc::pthread_mutex_t) {
+    let r = libc::pthread_mutex_unlock(lock);
+    debug_assert_eq!(r, 0);
+}
+
+unsafe fn notify_one(cond: *mut libc::pthread_cond_t) {
+    let r = libc::pthread_cond_signal(cond);
+    debug_assert_eq!(r, 0);
+}
+
+unsafe fn wait(cond: *mut libc::pthread_cond_t, lock: *mut libc::pthread_mutex_t) {
+    let r = libc::pthread_cond_wait(cond, lock);
+    debug_assert_eq!(r, 0);
+}
+
+unsafe fn wait_timeout(
+    cond: *mut libc::pthread_cond_t,
+    lock: *mut libc::pthread_mutex_t,
+    dur: Duration,
+) {
+    // Use the system clock on systems that do not support pthread_condattr_setclock.
+    // This unfortunately results in problems when the system time changes.
+    #[cfg(any(
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "espidf",
+        target_os = "horizon",
+    ))]
+    let (now, dur) = {
+        use crate::cmp::min;
+        use crate::sys::time::SystemTime;
+
+        // OSX implementation of `pthread_cond_timedwait` is buggy
+        // with super long durations. When duration is greater than
+        // 0x100_0000_0000_0000 seconds, `pthread_cond_timedwait`
+        // in macOS Sierra return error 316.
+        //
+        // This program demonstrates the issue:
+        // https://gist.github.com/stepancheg/198db4623a20aad2ad7cddb8fda4a63c
+        //
+        // To work around this issue, and possible bugs of other OSes, timeout
+        // is clamped to 1000 years, which is allowable per the API of `park_timeout`
+        // because of spurious wakeups.
+        let dur = min(dur, Duration::from_secs(1000 * 365 * 86400));
+        let now = SystemTime::now().t;
+        (now, dur)
+    };
+    // Use the monotonic clock on other systems.
+    #[cfg(not(any(
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos",
+        target_os = "espidf",
+        target_os = "horizon",
+    )))]
+    let (now, dur) = {
+        use crate::sys::time::Timespec;
+
+        (Timespec::now(libc::CLOCK_MONOTONIC), dur)
+    };
+
+    #[cfg(not(target_os = "nto"))]
+    let timeout =
+        now.checked_add_duration(&dur).and_then(|t| t.to_timespec()).unwrap_or(TIMESPEC_MAX);
+    #[cfg(target_os = "nto")]
+    let timeout = now
+        .checked_add_duration(&dur)
+        .and_then(|t| t.to_timespec_capped())
+        .unwrap_or(TIMESPEC_MAX_CAPPED);
+    let r = libc::pthread_cond_timedwait(cond, lock, &timeout);
+    debug_assert!(r == libc::ETIMEDOUT || r == 0);
+}
+
+pub struct Parker {
+    state: AtomicUsize,
+    lock: UnsafeCell<libc::pthread_mutex_t>,
+    cvar: UnsafeCell<libc::pthread_cond_t>,
+    // The `pthread` primitives require a stable address, so make this struct `!Unpin`.
+    _pinned: PhantomPinned,
+}
+
+impl Parker {
+    /// Construct the UNIX parker in-place.
+    ///
+    /// # Safety
+    /// The constructed parker must never be moved.
+    pub unsafe fn new_in_place(parker: *mut Parker) {
+        // Use the default mutex implementation to allow for simpler initialization.
+        // This could lead to undefined behaviour when deadlocking. This is avoided
+        // by not deadlocking. Note in particular the unlocking operation before any
+        // panic, as code after the panic could try to park again.
+        addr_of_mut!((*parker).state).write(AtomicUsize::new(EMPTY));
+        addr_of_mut!((*parker).lock).write(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER));
+
+        cfg_if::cfg_if! {
+            if #[cfg(any(
+                target_os = "macos",
+                target_os = "ios",
+                target_os = "tvos",
+                target_os = "watchos",
+                target_os = "l4re",
+                target_os = "android",
+                target_os = "redox",
+                target_os = "vita",
+            ))] {
+                addr_of_mut!((*parker).cvar).write(UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER));
+            } else if #[cfg(any(target_os = "espidf", target_os = "horizon"))] {
+                let r = libc::pthread_cond_init(addr_of_mut!((*parker).cvar).cast(), crate::ptr::null());
+                assert_eq!(r, 0);
+            } else {
+                use crate::mem::MaybeUninit;
+                let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
+                let r = libc::pthread_condattr_init(attr.as_mut_ptr());
+                assert_eq!(r, 0);
+                let r = libc::pthread_condattr_setclock(attr.as_mut_ptr(), libc::CLOCK_MONOTONIC);
+                assert_eq!(r, 0);
+                let r = libc::pthread_cond_init(addr_of_mut!((*parker).cvar).cast(), attr.as_ptr());
+                assert_eq!(r, 0);
+                let r = libc::pthread_condattr_destroy(attr.as_mut_ptr());
+                assert_eq!(r, 0);
+            }
+        }
+    }
+
+    // This implementation doesn't require `unsafe`, but other implementations
+    // may assume this is only called by the thread that owns the Parker.
+    pub unsafe fn park(self: Pin<&Self>) {
+        // If we were previously notified then we consume this notification and
+        // return quickly.
+        if self.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst).is_ok() {
+            return;
+        }
+
+        // Otherwise we need to coordinate going to sleep
+        lock(self.lock.get());
+        match self.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
+            Ok(_) => {}
+            Err(NOTIFIED) => {
+                // We must read here, even though we know it will be `NOTIFIED`.
+                // This is because `unpark` may have been called again since we read
+                // `NOTIFIED` in the `compare_exchange` above. We must perform an
+                // acquire operation that synchronizes with that `unpark` to observe
+                // any writes it made before the call to unpark. To do that we must
+                // read from the write it made to `state`.
+                let old = self.state.swap(EMPTY, SeqCst);
+
+                unlock(self.lock.get());
+
+                assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
+                return;
+            } // should consume this notification, so prohibit spurious wakeups in next park.
+            Err(_) => {
+                unlock(self.lock.get());
+
+                panic!("inconsistent park state")
+            }
+        }
+
+        loop {
+            wait(self.cvar.get(), self.lock.get());
+
+            match self.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst) {
+                Ok(_) => break, // got a notification
+                Err(_) => {}    // spurious wakeup, go back to sleep
+            }
+        }
+
+        unlock(self.lock.get());
+    }
+
+    // This implementation doesn't require `unsafe`, but other implementations
+    // may assume this is only called by the thread that owns the Parker. Use
+    // `Pin` to guarantee a stable address for the mutex and condition variable.
+    pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
+        // Like `park` above we have a fast path for an already-notified thread, and
+        // afterwards we start coordinating for a sleep.
+        // return quickly.
+        if self.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst).is_ok() {
+            return;
+        }
+
+        lock(self.lock.get());
+        match self.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
+            Ok(_) => {}
+            Err(NOTIFIED) => {
+                // We must read again here, see `park`.
+                let old = self.state.swap(EMPTY, SeqCst);
+                unlock(self.lock.get());
+
+                assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
+                return;
+            } // should consume this notification, so prohibit spurious wakeups in next park.
+            Err(_) => {
+                unlock(self.lock.get());
+                panic!("inconsistent park_timeout state")
+            }
+        }
+
+        // Wait with a timeout, and if we spuriously wake up or otherwise wake up
+        // from a notification we just want to unconditionally set the state back to
+        // empty, either consuming a notification or un-flagging ourselves as
+        // parked.
+        wait_timeout(self.cvar.get(), self.lock.get(), dur);
+
+        match self.state.swap(EMPTY, SeqCst) {
+            NOTIFIED => unlock(self.lock.get()), // got a notification, hurray!
+            PARKED => unlock(self.lock.get()),   // no notification, alas
+            n => {
+                unlock(self.lock.get());
+                panic!("inconsistent park_timeout state: {n}")
+            }
+        }
+    }
+
+    pub fn unpark(self: Pin<&Self>) {
+        // To ensure the unparked thread will observe any writes we made
+        // before this call, we must perform a release operation that `park`
+        // can synchronize with. To do that we must write `NOTIFIED` even if
+        // `state` is already `NOTIFIED`. That is why this must be a swap
+        // rather than a compare-and-swap that returns if it reads `NOTIFIED`
+        // on failure.
+        match self.state.swap(NOTIFIED, SeqCst) {
+            EMPTY => return,    // no one was waiting
+            NOTIFIED => return, // already unparked
+            PARKED => {}        // gotta go wake someone up
+            _ => panic!("inconsistent state in unpark"),
+        }
+
+        // There is a period between when the parked thread sets `state` to
+        // `PARKED` (or last checked `state` in the case of a spurious wake
+        // up) and when it actually waits on `cvar`. If we were to notify
+        // during this period it would be ignored and then when the parked
+        // thread went to sleep it would never wake up. Fortunately, it has
+        // `lock` locked at this stage so we can acquire `lock` to wait until
+        // it is ready to receive the notification.
+        //
+        // Releasing `lock` before the call to `notify_one` means that when the
+        // parked thread wakes it doesn't get woken only to have to wait for us
+        // to release `lock`.
+        unsafe {
+            lock(self.lock.get());
+            unlock(self.lock.get());
+            notify_one(self.cvar.get());
+        }
+    }
+}
+
+impl Drop for Parker {
+    fn drop(&mut self) {
+        unsafe {
+            libc::pthread_cond_destroy(self.cvar.get_mut());
+            libc::pthread_mutex_destroy(self.lock.get_mut());
+        }
+    }
+}
+
+unsafe impl Sync for Parker {}
+unsafe impl Send for Parker {}
diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs
new file mode 100644
index 00000000000..f62eb828ee5
--- /dev/null
+++ b/library/std/src/sys/pal/unix/time.rs
@@ -0,0 +1,330 @@
+use crate::fmt;
+use crate::time::Duration;
+
+const NSEC_PER_SEC: u64 = 1_000_000_000;
+pub const UNIX_EPOCH: SystemTime = SystemTime { t: Timespec::zero() };
+#[allow(dead_code)] // Used for pthread condvar timeouts
+pub const TIMESPEC_MAX: libc::timespec =
+    libc::timespec { tv_sec: <libc::time_t>::MAX, tv_nsec: 1_000_000_000 - 1 };
+
+// This additional constant is only used when calling
+// `libc::pthread_cond_timedwait`.
+#[cfg(target_os = "nto")]
+pub(super) const TIMESPEC_MAX_CAPPED: libc::timespec = libc::timespec {
+    tv_sec: (u64::MAX / NSEC_PER_SEC) as i64,
+    tv_nsec: (u64::MAX % NSEC_PER_SEC) as i64,
+};
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[repr(transparent)]
+#[rustc_layout_scalar_valid_range_start(0)]
+#[rustc_layout_scalar_valid_range_end(999_999_999)]
+struct Nanoseconds(u32);
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct SystemTime {
+    pub(crate) t: Timespec,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub(crate) struct Timespec {
+    tv_sec: i64,
+    tv_nsec: Nanoseconds,
+}
+
+impl SystemTime {
+    #[cfg_attr(any(target_os = "horizon", target_os = "hurd"), allow(unused))]
+    pub fn new(tv_sec: i64, tv_nsec: i64) -> SystemTime {
+        SystemTime { t: Timespec::new(tv_sec, tv_nsec) }
+    }
+
+    pub fn now() -> SystemTime {
+        SystemTime { t: Timespec::now(libc::CLOCK_REALTIME) }
+    }
+
+    pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
+        self.t.sub_timespec(&other.t)
+    }
+
+    pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
+        Some(SystemTime { t: self.t.checked_add_duration(other)? })
+    }
+
+    pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
+        Some(SystemTime { t: self.t.checked_sub_duration(other)? })
+    }
+}
+
+impl From<libc::timespec> for SystemTime {
+    fn from(t: libc::timespec) -> SystemTime {
+        SystemTime { t: Timespec::from(t) }
+    }
+}
+
+impl fmt::Debug for SystemTime {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("SystemTime")
+            .field("tv_sec", &self.t.tv_sec)
+            .field("tv_nsec", &self.t.tv_nsec.0)
+            .finish()
+    }
+}
+
+impl Timespec {
+    pub const fn zero() -> Timespec {
+        Timespec::new(0, 0)
+    }
+
+    const fn new(tv_sec: i64, tv_nsec: i64) -> Timespec {
+        // On Apple OS, dates before epoch are represented differently than on other
+        // Unix platforms: e.g. 1/10th of a second before epoch is represented as `seconds=-1`
+        // and `nanoseconds=100_000_000` on other platforms, but is `seconds=0` and
+        // `nanoseconds=-900_000_000` on Apple OS.
+        //
+        // To compensate, we first detect this special case by checking if both
+        // seconds and nanoseconds are in range, and then correct the value for seconds
+        // and nanoseconds to match the common unix representation.
+        //
+        // Please note that Apple OS nonetheless accepts the standard unix format when
+        // setting file times, which makes this compensation round-trippable and generally
+        // transparent.
+        #[cfg(any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "tvos",
+            target_os = "watchos"
+        ))]
+        let (tv_sec, tv_nsec) =
+            if (tv_sec <= 0 && tv_sec > i64::MIN) && (tv_nsec < 0 && tv_nsec > -1_000_000_000) {
+                (tv_sec - 1, tv_nsec + 1_000_000_000)
+            } else {
+                (tv_sec, tv_nsec)
+            };
+        assert!(tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC as i64);
+        // SAFETY: The assert above checks tv_nsec is within the valid range
+        Timespec { tv_sec, tv_nsec: unsafe { Nanoseconds(tv_nsec as u32) } }
+    }
+
+    pub fn now(clock: libc::clockid_t) -> Timespec {
+        use crate::mem::MaybeUninit;
+        use crate::sys::cvt;
+
+        // Try to use 64-bit time in preparation for Y2038.
+        #[cfg(all(
+            target_os = "linux",
+            target_env = "gnu",
+            target_pointer_width = "32",
+            not(target_arch = "riscv32")
+        ))]
+        {
+            use crate::sys::weak::weak;
+
+            // __clock_gettime64 was added to 32-bit arches in glibc 2.34,
+            // and it handles both vDSO calls and ENOSYS fallbacks itself.
+            weak!(fn __clock_gettime64(libc::clockid_t, *mut __timespec64) -> libc::c_int);
+
+            if let Some(clock_gettime64) = __clock_gettime64.get() {
+                let mut t = MaybeUninit::uninit();
+                cvt(unsafe { clock_gettime64(clock, t.as_mut_ptr()) }).unwrap();
+                return Timespec::from(unsafe { t.assume_init() });
+            }
+        }
+
+        let mut t = MaybeUninit::uninit();
+        cvt(unsafe { libc::clock_gettime(clock, t.as_mut_ptr()) }).unwrap();
+        Timespec::from(unsafe { t.assume_init() })
+    }
+
+    pub fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
+        if self >= other {
+            // NOTE(eddyb) two aspects of this `if`-`else` are required for LLVM
+            // to optimize it into a branchless form (see also #75545):
+            //
+            // 1. `self.tv_sec - other.tv_sec` shows up as a common expression
+            //    in both branches, i.e. the `else` must have its `- 1`
+            //    subtraction after the common one, not interleaved with it
+            //    (it used to be `self.tv_sec - 1 - other.tv_sec`)
+            //
+            // 2. the `Duration::new` call (or any other additional complexity)
+            //    is outside of the `if`-`else`, not duplicated in both branches
+            //
+            // Ideally this code could be rearranged such that it more
+            // directly expresses the lower-cost behavior we want from it.
+            let (secs, nsec) = if self.tv_nsec.0 >= other.tv_nsec.0 {
+                ((self.tv_sec - other.tv_sec) as u64, self.tv_nsec.0 - other.tv_nsec.0)
+            } else {
+                (
+                    (self.tv_sec - other.tv_sec - 1) as u64,
+                    self.tv_nsec.0 + (NSEC_PER_SEC as u32) - other.tv_nsec.0,
+                )
+            };
+
+            Ok(Duration::new(secs, nsec))
+        } else {
+            match other.sub_timespec(self) {
+                Ok(d) => Err(d),
+                Err(d) => Ok(d),
+            }
+        }
+    }
+
+    pub fn checked_add_duration(&self, other: &Duration) -> Option<Timespec> {
+        let mut secs = self.tv_sec.checked_add_unsigned(other.as_secs())?;
+
+        // Nano calculations can't overflow because nanos are <1B which fit
+        // in a u32.
+        let mut nsec = other.subsec_nanos() + self.tv_nsec.0;
+        if nsec >= NSEC_PER_SEC as u32 {
+            nsec -= NSEC_PER_SEC as u32;
+            secs = secs.checked_add(1)?;
+        }
+        Some(Timespec::new(secs, nsec.into()))
+    }
+
+    pub fn checked_sub_duration(&self, other: &Duration) -> Option<Timespec> {
+        let mut secs = self.tv_sec.checked_sub_unsigned(other.as_secs())?;
+
+        // Similar to above, nanos can't overflow.
+        let mut nsec = self.tv_nsec.0 as i32 - other.subsec_nanos() as i32;
+        if nsec < 0 {
+            nsec += NSEC_PER_SEC as i32;
+            secs = secs.checked_sub(1)?;
+        }
+        Some(Timespec::new(secs, nsec.into()))
+    }
+
+    #[allow(dead_code)]
+    pub fn to_timespec(&self) -> Option<libc::timespec> {
+        Some(libc::timespec {
+            tv_sec: self.tv_sec.try_into().ok()?,
+            tv_nsec: self.tv_nsec.0.try_into().ok()?,
+        })
+    }
+
+    // On QNX Neutrino, the maximum timespec for e.g. pthread_cond_timedwait
+    // is 2^64 nanoseconds
+    #[cfg(target_os = "nto")]
+    pub(super) fn to_timespec_capped(&self) -> Option<libc::timespec> {
+        // Check if timeout in nanoseconds would fit into an u64
+        if (self.tv_nsec.0 as u64)
+            .checked_add((self.tv_sec as u64).checked_mul(NSEC_PER_SEC)?)
+            .is_none()
+        {
+            return None;
+        }
+        self.to_timespec()
+    }
+
+    #[cfg(all(
+        target_os = "linux",
+        target_env = "gnu",
+        target_pointer_width = "32",
+        not(target_arch = "riscv32")
+    ))]
+    pub fn to_timespec64(&self) -> __timespec64 {
+        __timespec64::new(self.tv_sec, self.tv_nsec.0 as _)
+    }
+}
+
+impl From<libc::timespec> for Timespec {
+    fn from(t: libc::timespec) -> Timespec {
+        Timespec::new(t.tv_sec as i64, t.tv_nsec as i64)
+    }
+}
+
+#[cfg(all(
+    target_os = "linux",
+    target_env = "gnu",
+    target_pointer_width = "32",
+    not(target_arch = "riscv32")
+))]
+#[repr(C)]
+pub(crate) struct __timespec64 {
+    pub(crate) tv_sec: i64,
+    #[cfg(target_endian = "big")]
+    _padding: i32,
+    pub(crate) tv_nsec: i32,
+    #[cfg(target_endian = "little")]
+    _padding: i32,
+}
+
+#[cfg(all(
+    target_os = "linux",
+    target_env = "gnu",
+    target_pointer_width = "32",
+    not(target_arch = "riscv32")
+))]
+impl __timespec64 {
+    pub(crate) fn new(tv_sec: i64, tv_nsec: i32) -> Self {
+        Self { tv_sec, tv_nsec, _padding: 0 }
+    }
+}
+
+#[cfg(all(
+    target_os = "linux",
+    target_env = "gnu",
+    target_pointer_width = "32",
+    not(target_arch = "riscv32")
+))]
+impl From<__timespec64> for Timespec {
+    fn from(t: __timespec64) -> Timespec {
+        Timespec::new(t.tv_sec, t.tv_nsec.into())
+    }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Instant {
+    t: Timespec,
+}
+
+impl Instant {
+    pub fn now() -> Instant {
+        // https://www.manpagez.com/man/3/clock_gettime/
+        //
+        // CLOCK_UPTIME_RAW   clock that increments monotonically, in the same man-
+        //                    ner as CLOCK_MONOTONIC_RAW, but that does not incre-
+        //                    ment while the system is asleep.  The returned value
+        //                    is identical to the result of mach_absolute_time()
+        //                    after the appropriate mach_timebase conversion is
+        //                    applied.
+        //
+        // Instant on macos was historically implemented using mach_absolute_time;
+        // we preserve this value domain out of an abundance of caution.
+        #[cfg(any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "watchos",
+            target_os = "tvos"
+        ))]
+        const clock_id: libc::clockid_t = libc::CLOCK_UPTIME_RAW;
+        #[cfg(not(any(
+            target_os = "macos",
+            target_os = "ios",
+            target_os = "watchos",
+            target_os = "tvos"
+        )))]
+        const clock_id: libc::clockid_t = libc::CLOCK_MONOTONIC;
+        Instant { t: Timespec::now(clock_id) }
+    }
+
+    pub fn checked_sub_instant(&self, other: &Instant) -> Option<Duration> {
+        self.t.sub_timespec(&other.t).ok()
+    }
+
+    pub fn checked_add_duration(&self, other: &Duration) -> Option<Instant> {
+        Some(Instant { t: self.t.checked_add_duration(other)? })
+    }
+
+    pub fn checked_sub_duration(&self, other: &Duration) -> Option<Instant> {
+        Some(Instant { t: self.t.checked_sub_duration(other)? })
+    }
+}
+
+impl fmt::Debug for Instant {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("Instant")
+            .field("tv_sec", &self.t.tv_sec)
+            .field("tv_nsec", &self.t.tv_nsec.0)
+            .finish()
+    }
+}
diff --git a/library/std/src/sys/pal/unix/weak.rs b/library/std/src/sys/pal/unix/weak.rs
new file mode 100644
index 00000000000..61088ff16ed
--- /dev/null
+++ b/library/std/src/sys/pal/unix/weak.rs
@@ -0,0 +1,195 @@
+//! Support for "weak linkage" to symbols on Unix
+//!
+//! Some I/O operations we do in std require newer versions of OSes but we need
+//! to maintain binary compatibility with older releases for now. In order to
+//! use the new functionality when available we use this module for detection.
+//!
+//! One option to use here is weak linkage, but that is unfortunately only
+//! really workable with ELF. Otherwise, use dlsym to get the symbol value at
+//! runtime. This is also done for compatibility with older versions of glibc,
+//! and to avoid creating dependencies on GLIBC_PRIVATE symbols. It assumes that
+//! we've been dynamically linked to the library the symbol comes from, but that
+//! is currently always the case for things like libpthread/libc.
+//!
+//! A long time ago this used weak linkage for the __pthread_get_minstack
+//! symbol, but that caused Debian to detect an unnecessarily strict versioned
+//! dependency on libc6 (#23628) because it is GLIBC_PRIVATE. We now use `dlsym`
+//! for a runtime lookup of that symbol to avoid the ELF versioned dependency.
+
+// There are a variety of `#[cfg]`s controlling which targets are involved in
+// each instance of `weak!` and `syscall!`. Rather than trying to unify all of
+// that, we'll just allow that some unix targets don't use this module at all.
+#![allow(dead_code, unused_macros)]
+
+use crate::ffi::CStr;
+use crate::marker::PhantomData;
+use crate::mem;
+use crate::ptr;
+use crate::sync::atomic::{self, AtomicPtr, Ordering};
+
+// We can use true weak linkage on ELF targets.
+#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos")))]
+pub(crate) macro weak {
+    (fn $name:ident($($t:ty),*) -> $ret:ty) => (
+        let ref $name: ExternWeak<unsafe extern "C" fn($($t),*) -> $ret> = {
+            extern "C" {
+                #[linkage = "extern_weak"]
+                static $name: Option<unsafe extern "C" fn($($t),*) -> $ret>;
+            }
+            #[allow(unused_unsafe)]
+            ExternWeak::new(unsafe { $name })
+        };
+    )
+}
+
+// On non-ELF targets, use the dlsym approximation of weak linkage.
+#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))]
+pub(crate) use self::dlsym as weak;
+
+pub(crate) struct ExternWeak<F: Copy> {
+    weak_ptr: Option<F>,
+}
+
+impl<F: Copy> ExternWeak<F> {
+    #[inline]
+    pub(crate) fn new(weak_ptr: Option<F>) -> Self {
+        ExternWeak { weak_ptr }
+    }
+
+    #[inline]
+    pub(crate) fn get(&self) -> Option<F> {
+        self.weak_ptr
+    }
+}
+
+pub(crate) macro dlsym {
+    (fn $name:ident($($t:ty),*) -> $ret:ty) => (
+         dlsym!(fn $name($($t),*) -> $ret, stringify!($name));
+    ),
+    (fn $name:ident($($t:ty),*) -> $ret:ty, $sym:expr) => (
+        static DLSYM: DlsymWeak<unsafe extern "C" fn($($t),*) -> $ret> =
+            DlsymWeak::new(concat!($sym, '\0'));
+        let $name = &DLSYM;
+    )
+}
+pub(crate) struct DlsymWeak<F> {
+    name: &'static str,
+    func: AtomicPtr<libc::c_void>,
+    _marker: PhantomData<F>,
+}
+
+impl<F> DlsymWeak<F> {
+    pub(crate) const fn new(name: &'static str) -> Self {
+        DlsymWeak { name, func: AtomicPtr::new(ptr::invalid_mut(1)), _marker: PhantomData }
+    }
+
+    #[inline]
+    pub(crate) fn get(&self) -> Option<F> {
+        unsafe {
+            // Relaxed is fine here because we fence before reading through the
+            // pointer (see the comment below).
+            match self.func.load(Ordering::Relaxed) {
+                func if func.addr() == 1 => self.initialize(),
+                func if func.is_null() => None,
+                func => {
+                    let func = mem::transmute_copy::<*mut libc::c_void, F>(&func);
+                    // The caller is presumably going to read through this value
+                    // (by calling the function we've dlsymed). This means we'd
+                    // need to have loaded it with at least C11's consume
+                    // ordering in order to be guaranteed that the data we read
+                    // from the pointer isn't from before the pointer was
+                    // stored. Rust has no equivalent to memory_order_consume,
+                    // so we use an acquire fence (sorry, ARM).
+                    //
+                    // Now, in practice this likely isn't needed even on CPUs
+                    // where relaxed and consume mean different things. The
+                    // symbols we're loading are probably present (or not) at
+                    // init, and even if they aren't the runtime dynamic loader
+                    // is extremely likely have sufficient barriers internally
+                    // (possibly implicitly, for example the ones provided by
+                    // invoking `mprotect`).
+                    //
+                    // That said, none of that's *guaranteed*, and so we fence.
+                    atomic::fence(Ordering::Acquire);
+                    Some(func)
+                }
+            }
+        }
+    }
+
+    // Cold because it should only happen during first-time initialization.
+    #[cold]
+    unsafe fn initialize(&self) -> Option<F> {
+        assert_eq!(mem::size_of::<F>(), mem::size_of::<*mut libc::c_void>());
+
+        let val = fetch(self.name);
+        // This synchronizes with the acquire fence in `get`.
+        self.func.store(val, Ordering::Release);
+
+        if val.is_null() { None } else { Some(mem::transmute_copy::<*mut libc::c_void, F>(&val)) }
+    }
+}
+
+unsafe fn fetch(name: &str) -> *mut libc::c_void {
+    let name = match CStr::from_bytes_with_nul(name.as_bytes()) {
+        Ok(cstr) => cstr,
+        Err(..) => return ptr::null_mut(),
+    };
+    libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr())
+}
+
+#[cfg(not(any(target_os = "linux", target_os = "android")))]
+pub(crate) macro syscall {
+    (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => (
+        unsafe fn $name($($arg_name: $t),*) -> $ret {
+            weak! { fn $name($($t),*) -> $ret }
+
+            if let Some(fun) = $name.get() {
+                fun($($arg_name),*)
+            } else {
+                super::os::set_errno(libc::ENOSYS);
+                -1
+            }
+        }
+    )
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+pub(crate) macro syscall {
+    (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => (
+        unsafe fn $name($($arg_name:$t),*) -> $ret {
+            weak! { fn $name($($t),*) -> $ret }
+
+            // Use a weak symbol from libc when possible, allowing `LD_PRELOAD`
+            // interposition, but if it's not found just use a raw syscall.
+            if let Some(fun) = $name.get() {
+                fun($($arg_name),*)
+            } else {
+                // This looks like a hack, but concat_idents only accepts idents
+                // (not paths).
+                use libc::*;
+
+                syscall(
+                    concat_idents!(SYS_, $name),
+                    $($arg_name),*
+                ) as $ret
+            }
+        }
+    )
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+pub(crate) macro raw_syscall {
+    (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => (
+        unsafe fn $name($($arg_name:$t),*) -> $ret {
+            // This looks like a hack, but concat_idents only accepts idents
+            // (not paths).
+            use libc::*;
+
+            syscall(
+                concat_idents!(SYS_, $name),
+                $($arg_name),*
+            ) as $ret
+        }
+    )
+}