diff options
Diffstat (limited to 'library/std/src/sys')
69 files changed, 2724 insertions, 1308 deletions
diff --git a/library/std/src/sys/args/mod.rs b/library/std/src/sys/args/mod.rs index c9627322276..e11e8e5430f 100644 --- a/library/std/src/sys/args/mod.rs +++ b/library/std/src/sys/args/mod.rs @@ -32,9 +32,13 @@ cfg_select! { mod uefi; pub use uefi::*; } - target_os = "wasi" => { - mod wasi; - pub use wasi::*; + all(target_os = "wasi", target_env = "p1") => { + mod wasip1; + pub use wasip1::*; + } + all(target_os = "wasi", target_env = "p2") => { + mod wasip2; + pub use wasip2::*; } target_os = "xous" => { mod xous; diff --git a/library/std/src/sys/args/wasi.rs b/library/std/src/sys/args/wasip1.rs index 72063a87dc9..72063a87dc9 100644 --- a/library/std/src/sys/args/wasi.rs +++ b/library/std/src/sys/args/wasip1.rs diff --git a/library/std/src/sys/args/wasip2.rs b/library/std/src/sys/args/wasip2.rs new file mode 100644 index 00000000000..a57e4b97786 --- /dev/null +++ b/library/std/src/sys/args/wasip2.rs @@ -0,0 +1,6 @@ +pub use super::common::Args; + +/// Returns the command line arguments +pub fn args() -> Args { + Args::new(wasip2::cli::environment::get_arguments().into_iter().map(|arg| arg.into()).collect()) +} diff --git a/library/std/src/sys/fd/unix.rs b/library/std/src/sys/fd/unix.rs index a12f692e754..2b2dfe48e89 100644 --- a/library/std/src/sys/fd/unix.rs +++ b/library/std/src/sys/fd/unix.rs @@ -18,6 +18,21 @@ use libc::off_t as off64_t; ))] use libc::off64_t; +cfg_select! { + any( + all(target_os = "linux", not(target_env = "musl")), + target_os = "android", + target_os = "hurd", + ) => { + // Prefer explicit pread64 for 64-bit offset independently of libc + // #[cfg(gnu_file_offset_bits64)]. + use libc::pread64; + } + _ => { + use libc::pread as pread64; + } +} + use crate::cmp; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read}; use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; @@ -146,42 +161,47 @@ impl FileDesc { (&mut me).read_to_end(buf) } - #[cfg_attr(target_os = "vxworks", allow(unused_unsafe))] 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( + cvt(unsafe { + 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) - } + offset as off64_t, // EINVAL if offset + count overflows + ) + }) + .map(|n| n as usize) } pub fn read_buf(&self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> { + // SAFETY: `cursor.as_mut()` starts with `cursor.capacity()` writable bytes let ret = cvt(unsafe { libc::read( self.as_raw_fd(), - cursor.as_mut().as_mut_ptr() as *mut libc::c_void, + cursor.as_mut().as_mut_ptr().cast::<libc::c_void>(), + cmp::min(cursor.capacity(), READ_LIMIT), + ) + })?; + + // SAFETY: `ret` bytes were written to the initialized portion of the buffer + unsafe { + cursor.advance_unchecked(ret as usize); + } + Ok(()) + } + + pub fn read_buf_at(&self, mut cursor: BorrowedCursor<'_>, offset: u64) -> io::Result<()> { + // SAFETY: `cursor.as_mut()` starts with `cursor.capacity()` writable bytes + let ret = cvt(unsafe { + pread64( + self.as_raw_fd(), + cursor.as_mut().as_mut_ptr().cast::<libc::c_void>(), cmp::min(cursor.capacity(), READ_LIMIT), + offset as off64_t, // EINVAL if offset + count overflows ) })?; - // Safety: `ret` bytes were written to the initialized portion of the buffer + // SAFETY: `ret` bytes were written to the initialized portion of the buffer unsafe { cursor.advance_unchecked(ret as usize); } @@ -369,7 +389,6 @@ impl FileDesc { ))) } - #[cfg_attr(target_os = "vxworks", allow(unused_unsafe))] pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> { #[cfg(not(any( all(target_os = "linux", not(target_env = "musl")), diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index a89c3bbacfb..dfd6ce56a76 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1293,6 +1293,15 @@ impl File { return Ok(()); } + #[cfg(target_os = "solaris")] + pub fn lock(&self) -> io::Result<()> { + let mut flock: libc::flock = unsafe { mem::zeroed() }; + flock.l_type = libc::F_WRLCK as libc::c_short; + flock.l_whence = libc::SEEK_SET as libc::c_short; + cvt(unsafe { libc::fcntl(self.as_raw_fd(), libc::F_SETLKW, &flock) })?; + Ok(()) + } + #[cfg(not(any( target_os = "freebsd", target_os = "fuchsia", @@ -1300,6 +1309,7 @@ impl File { target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", + target_os = "solaris", target_vendor = "apple", )))] pub fn lock(&self) -> io::Result<()> { @@ -1320,6 +1330,15 @@ impl File { return Ok(()); } + #[cfg(target_os = "solaris")] + pub fn lock_shared(&self) -> io::Result<()> { + let mut flock: libc::flock = unsafe { mem::zeroed() }; + flock.l_type = libc::F_RDLCK as libc::c_short; + flock.l_whence = libc::SEEK_SET as libc::c_short; + cvt(unsafe { libc::fcntl(self.as_raw_fd(), libc::F_SETLKW, &flock) })?; + Ok(()) + } + #[cfg(not(any( target_os = "freebsd", target_os = "fuchsia", @@ -1327,6 +1346,7 @@ impl File { target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", + target_os = "solaris", target_vendor = "apple", )))] pub fn lock_shared(&self) -> io::Result<()> { @@ -1355,6 +1375,23 @@ impl File { } } + #[cfg(target_os = "solaris")] + pub fn try_lock(&self) -> Result<(), TryLockError> { + let mut flock: libc::flock = unsafe { mem::zeroed() }; + flock.l_type = libc::F_WRLCK as libc::c_short; + flock.l_whence = libc::SEEK_SET as libc::c_short; + let result = cvt(unsafe { libc::fcntl(self.as_raw_fd(), libc::F_SETLK, &flock) }); + if let Err(err) = result { + if err.kind() == io::ErrorKind::WouldBlock { + Err(TryLockError::WouldBlock) + } else { + Err(TryLockError::Error(err)) + } + } else { + Ok(()) + } + } + #[cfg(not(any( target_os = "freebsd", target_os = "fuchsia", @@ -1362,6 +1399,7 @@ impl File { target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", + target_os = "solaris", target_vendor = "apple", )))] pub fn try_lock(&self) -> Result<(), TryLockError> { @@ -1393,6 +1431,23 @@ impl File { } } + #[cfg(target_os = "solaris")] + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { + let mut flock: libc::flock = unsafe { mem::zeroed() }; + flock.l_type = libc::F_RDLCK as libc::c_short; + flock.l_whence = libc::SEEK_SET as libc::c_short; + let result = cvt(unsafe { libc::fcntl(self.as_raw_fd(), libc::F_SETLK, &flock) }); + if let Err(err) = result { + if err.kind() == io::ErrorKind::WouldBlock { + Err(TryLockError::WouldBlock) + } else { + Err(TryLockError::Error(err)) + } + } else { + Ok(()) + } + } + #[cfg(not(any( target_os = "freebsd", target_os = "fuchsia", @@ -1400,6 +1455,7 @@ impl File { target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", + target_os = "solaris", target_vendor = "apple", )))] pub fn try_lock_shared(&self) -> Result<(), TryLockError> { @@ -1423,6 +1479,15 @@ impl File { return Ok(()); } + #[cfg(target_os = "solaris")] + pub fn unlock(&self) -> io::Result<()> { + let mut flock: libc::flock = unsafe { mem::zeroed() }; + flock.l_type = libc::F_UNLCK as libc::c_short; + flock.l_whence = libc::SEEK_SET as libc::c_short; + cvt(unsafe { libc::fcntl(self.as_raw_fd(), libc::F_SETLKW, &flock) })?; + Ok(()) + } + #[cfg(not(any( target_os = "freebsd", target_os = "fuchsia", @@ -1430,6 +1495,7 @@ impl File { target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", + target_os = "solaris", target_vendor = "apple", )))] pub fn unlock(&self) -> io::Result<()> { @@ -1463,6 +1529,10 @@ impl File { self.0.read_buf(cursor) } + pub fn read_buf_at(&self, cursor: BorrowedCursor<'_>, offset: u64) -> io::Result<()> { + self.0.read_buf_at(cursor, offset) + } + pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { self.0.read_vectored_at(bufs, offset) } diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index bac278f7c8f..ccfe410627f 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -605,6 +605,10 @@ impl File { self.handle.read_buf(cursor) } + pub fn read_buf_at(&self, cursor: BorrowedCursor<'_>, offset: u64) -> io::Result<()> { + self.handle.read_buf_at(cursor, offset) + } + pub fn write(&self, buf: &[u8]) -> io::Result<usize> { self.handle.write(buf) } diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index 6324c1a232a..2dbdc8a4e02 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -26,10 +26,12 @@ pub mod io; pub mod net; pub mod os_str; pub mod path; +pub mod platform_version; pub mod process; pub mod random; pub mod stdio; pub mod sync; +pub mod thread; pub mod thread_local; // FIXME(117276): remove this, move feature implementations into individual diff --git a/library/std/src/sys/net/connection/mod.rs b/library/std/src/sys/net/connection/mod.rs new file mode 100644 index 00000000000..7f9636a8ccf --- /dev/null +++ b/library/std/src/sys/net/connection/mod.rs @@ -0,0 +1,57 @@ +cfg_select! { + any( + all(target_family = "unix", not(target_os = "l4re")), + target_os = "windows", + target_os = "hermit", + all(target_os = "wasi", target_env = "p2"), + target_os = "solid_asp3", + ) => { + mod socket; + pub use socket::*; + } + all(target_vendor = "fortanix", target_env = "sgx") => { + mod sgx; + pub use sgx::*; + } + all(target_os = "wasi", target_env = "p1") => { + mod wasip1; + pub use wasip1::*; + } + target_os = "xous" => { + mod xous; + pub use xous::*; + } + target_os = "uefi" => { + mod uefi; + pub use uefi::*; + } + _ => { + mod unsupported; + pub use unsupported::*; + } +} + +#[cfg_attr( + // Make sure that this is used on some platforms at least. + not(any(target_os = "linux", target_os = "windows")), + allow(dead_code) +)] +fn each_addr<A: crate::net::ToSocketAddrs, F, T>(addr: A, mut f: F) -> crate::io::Result<T> +where + F: FnMut(&crate::net::SocketAddr) -> crate::io::Result<T>, +{ + use crate::io::Error; + + let mut last_err = None; + for addr in addr.to_socket_addrs()? { + match f(&addr) { + Ok(l) => return Ok(l), + Err(e) => last_err = Some(e), + } + } + + match last_err { + Some(err) => Err(err), + None => Err(Error::NO_ADDRESSES), + } +} diff --git a/library/std/src/sys/net/connection/sgx.rs b/library/std/src/sys/net/connection/sgx.rs index 2389fd1bcb6..9b54571997d 100644 --- a/library/std/src/sys/net/connection/sgx.rs +++ b/library/std/src/sys/net/connection/sgx.rs @@ -1,3 +1,5 @@ +use crate::error; +use crate::fmt::{self, Write}; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, ToSocketAddrs}; use crate::sync::Arc; @@ -5,7 +7,6 @@ use crate::sys::abi::usercalls; use crate::sys::fd::FileDesc; use crate::sys::{AsInner, FromInner, IntoInner, TryIntoInner, sgx_ineffective, unsupported}; use crate::time::Duration; -use crate::{error, fmt}; const DEFAULT_FAKE_TTL: u32 = 64; @@ -63,18 +64,52 @@ impl fmt::Debug for TcpStream { } } -fn io_err_to_addr(result: io::Result<&SocketAddr>) -> io::Result<String> { - match result { - Ok(saddr) => Ok(saddr.to_string()), - // need to downcast twice because io::Error::into_inner doesn't return the original - // value if the conversion fails - Err(e) => { - if e.get_ref().and_then(|e| e.downcast_ref::<NonIpSockAddr>()).is_some() { - Ok(e.into_inner().unwrap().downcast::<NonIpSockAddr>().unwrap().host) - } else { - Err(e) +/// Converts each address in `addr` into a hostname. +/// +/// SGX doesn't support DNS resolution but rather accepts hostnames in +/// the same place as socket addresses. So, to make e.g. +/// ```rust +/// TcpStream::connect("example.com:80")` +/// ``` +/// work, the DNS lookup returns a special error (`NonIpSockAddr`) instead, +/// which contains the hostname being looked up. When `.to_socket_addrs()` +/// fails, we inspect the error and try recover the hostname from it. If that +/// succeeds, we thus continue with the hostname. +/// +/// This is a terrible hack and leads to buggy code. For instance, when users +/// use the result of `.to_socket_addrs()` in their own `ToSocketAddrs` +/// implementation to select from a list of possible URLs, the only URL used +/// will be that of the last item tried. +// FIXME: This is a terrible, terrible hack. Fixing this requires Fortanix to +// add a method for resolving addresses. +fn each_addr<A: ToSocketAddrs, F, T>(addr: A, mut f: F) -> io::Result<T> +where + F: FnMut(&str) -> io::Result<T>, +{ + match addr.to_socket_addrs() { + Ok(addrs) => { + let mut last_err = None; + let mut encoded = String::new(); + for addr in addrs { + // Format the IP address as a string, reusing the buffer. + encoded.clear(); + write!(encoded, "{}", &addr).unwrap(); + + match f(&encoded) { + Ok(val) => return Ok(val), + Err(err) => last_err = Some(err), + } + } + + match last_err { + Some(err) => Err(err), + None => Err(io::Error::NO_ADDRESSES), } } + Err(err) => match err.get_ref().and_then(|e| e.downcast_ref::<NonIpSockAddr>()) { + Some(NonIpSockAddr { host }) => f(host), + None => Err(err), + }, } } @@ -86,17 +121,18 @@ fn addr_to_sockaddr(addr: Option<&str>) -> io::Result<SocketAddr> { } impl TcpStream { - pub fn connect(addr: io::Result<&SocketAddr>) -> io::Result<TcpStream> { - let addr = io_err_to_addr(addr)?; - let (fd, local_addr, peer_addr) = usercalls::connect_stream(&addr)?; - Ok(TcpStream { inner: Socket::new(fd, local_addr), peer_addr: Some(peer_addr) }) + pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<TcpStream> { + each_addr(addr, |addr| { + let (fd, local_addr, peer_addr) = usercalls::connect_stream(addr)?; + Ok(TcpStream { inner: Socket::new(fd, local_addr), peer_addr: Some(peer_addr) }) + }) } pub fn connect_timeout(addr: &SocketAddr, dur: Duration) -> io::Result<TcpStream> { if dur == Duration::default() { return Err(io::Error::ZERO_TIMEOUT); } - Self::connect(Ok(addr)) // FIXME: ignoring timeout + Self::connect(addr) // FIXME: ignoring timeout } pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> { @@ -247,10 +283,11 @@ impl fmt::Debug for TcpListener { } impl TcpListener { - pub fn bind(addr: io::Result<&SocketAddr>) -> io::Result<TcpListener> { - let addr = io_err_to_addr(addr)?; - let (fd, local_addr) = usercalls::bind_stream(&addr)?; - Ok(TcpListener { inner: Socket::new(fd, local_addr) }) + pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<TcpListener> { + each_addr(addr, |addr| { + let (fd, local_addr) = usercalls::bind_stream(addr)?; + Ok(TcpListener { inner: Socket::new(fd, local_addr) }) + }) } pub fn socket_addr(&self) -> io::Result<SocketAddr> { @@ -316,7 +353,7 @@ impl FromInner<Socket> for TcpListener { pub struct UdpSocket(!); impl UdpSocket { - pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<UdpSocket> { + pub fn bind<A: ToSocketAddrs>(_: A) -> io::Result<UdpSocket> { unsupported() } @@ -436,7 +473,7 @@ impl UdpSocket { self.0 } - pub fn connect(&self, _: io::Result<&SocketAddr>) -> io::Result<()> { + pub fn connect<A: ToSocketAddrs>(&self, _: A) -> io::Result<()> { self.0 } } diff --git a/library/std/src/sys/net/connection/socket/hermit.rs b/library/std/src/sys/net/connection/socket/hermit.rs index f49821657d9..5200eaa5786 100644 --- a/library/std/src/sys/net/connection/socket/hermit.rs +++ b/library/std/src/sys/net/connection/socket/hermit.rs @@ -304,7 +304,8 @@ impl Socket { } pub fn take_error(&self) -> io::Result<Option<io::Error>> { - unimplemented!() + 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. diff --git a/library/std/src/sys/net/connection/socket.rs b/library/std/src/sys/net/connection/socket/mod.rs index aa83ed65d4c..564f2e3a01f 100644 --- a/library/std/src/sys/net/connection/socket.rs +++ b/library/std/src/sys/net/connection/socket/mod.rs @@ -3,8 +3,11 @@ mod tests; use crate::ffi::{c_int, c_void}; use crate::io::{self, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut}; -use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6}; +use crate::net::{ + Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs, +}; use crate::sys::common::small_c_string::run_with_cstr; +use crate::sys::net::connection::each_addr; use crate::sys_common::{AsInner, FromInner}; use crate::time::Duration; use crate::{cmp, fmt, mem, ptr}; @@ -342,14 +345,15 @@ pub struct TcpStream { } impl TcpStream { - pub fn connect(addr: io::Result<&SocketAddr>) -> io::Result<TcpStream> { - let addr = addr?; - + pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<TcpStream> { init(); + return each_addr(addr, inner); - let sock = Socket::new(addr, c::SOCK_STREAM)?; - sock.connect(addr)?; - Ok(TcpStream { inner: sock }) + fn inner(addr: &SocketAddr) -> io::Result<TcpStream> { + let sock = Socket::new(addr, c::SOCK_STREAM)?; + sock.connect(addr)?; + Ok(TcpStream { inner: sock }) + } } pub fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result<TcpStream> { @@ -512,48 +516,45 @@ pub struct TcpListener { } impl TcpListener { - pub fn bind(addr: io::Result<&SocketAddr>) -> io::Result<TcpListener> { - let addr = addr?; - + pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<TcpListener> { init(); - - let sock = Socket::new(addr, c::SOCK_STREAM)?; - - // On platforms with Berkeley-derived sockets, this allows to quickly - // rebind a socket, without needing to wait for the OS to clean up the - // previous one. - // - // On Windows, this allows rebinding sockets which are actively in use, - // which allows “socket hijacking”, so we explicitly don't set it here. - // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse - #[cfg(not(windows))] - setsockopt(&sock, c::SOL_SOCKET, c::SO_REUSEADDR, 1 as c_int)?; - - // Bind our new socket - let (addr, len) = socket_addr_to_c(addr); - cvt(unsafe { c::bind(sock.as_raw(), addr.as_ptr(), len as _) })?; - - cfg_select! { - target_os = "horizon" => { + return each_addr(addr, inner); + + fn inner(addr: &SocketAddr) -> io::Result<TcpListener> { + let sock = Socket::new(addr, c::SOCK_STREAM)?; + + // On platforms with Berkeley-derived sockets, this allows to quickly + // rebind a socket, without needing to wait for the OS to clean up the + // previous one. + // + // On Windows, this allows rebinding sockets which are actively in use, + // which allows “socket hijacking”, so we explicitly don't set it here. + // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse + #[cfg(not(windows))] + setsockopt(&sock, c::SOL_SOCKET, c::SO_REUSEADDR, 1 as c_int)?; + + // Bind our new socket + let (addr, len) = socket_addr_to_c(addr); + cvt(unsafe { c::bind(sock.as_raw(), addr.as_ptr(), len as _) })?; + + let backlog = if cfg!(target_os = "horizon") { // The 3DS doesn't support a big connection backlog. Sometimes // it allows up to about 37, but other times it doesn't even // accept 32. There may be a global limitation causing this. - let backlog = 20; - } - target_os = "haiku" => { + 20 + } else if cfg!(target_os = "haiku") { // Haiku does not support a queue length > 32 // https://github.com/haiku/haiku/blob/979a0bc487864675517fb2fab28f87dc8bf43041/headers/posix/sys/socket.h#L81 - let backlog = 32; - } - _ => { + 32 + } else { // The default for all other platforms - let backlog = 128; - } - } + 128 + }; - // Start listening - cvt(unsafe { c::listen(sock.as_raw(), backlog) })?; - Ok(TcpListener { inner: sock }) + // Start listening + cvt(unsafe { c::listen(sock.as_raw(), backlog) })?; + Ok(TcpListener { inner: sock }) + } } #[inline] @@ -639,15 +640,16 @@ pub struct UdpSocket { } impl UdpSocket { - pub fn bind(addr: io::Result<&SocketAddr>) -> io::Result<UdpSocket> { - let addr = addr?; - + pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<UdpSocket> { init(); + return each_addr(addr, inner); - let sock = Socket::new(addr, c::SOCK_DGRAM)?; - let (addr, len) = socket_addr_to_c(addr); - cvt(unsafe { c::bind(sock.as_raw(), addr.as_ptr(), len as _) })?; - Ok(UdpSocket { inner: sock }) + fn inner(addr: &SocketAddr) -> io::Result<UdpSocket> { + let sock = Socket::new(addr, c::SOCK_DGRAM)?; + let (addr, len) = socket_addr_to_c(addr); + cvt(unsafe { c::bind(sock.as_raw(), addr.as_ptr(), len as _) })?; + Ok(UdpSocket { inner: sock }) + } } #[inline] @@ -822,9 +824,13 @@ impl UdpSocket { Ok(ret as usize) } - pub fn connect(&self, addr: io::Result<&SocketAddr>) -> io::Result<()> { - let (addr, len) = socket_addr_to_c(addr?); - cvt_r(|| unsafe { c::connect(self.inner.as_raw(), addr.as_ptr(), len) }).map(drop) + pub fn connect<A: ToSocketAddrs>(&self, addr: A) -> io::Result<()> { + return each_addr(addr, |addr| inner(self, addr)); + + fn inner(this: &UdpSocket, addr: &SocketAddr) -> io::Result<()> { + let (addr, len) = socket_addr_to_c(addr); + cvt_r(|| unsafe { c::connect(this.inner.as_raw(), addr.as_ptr(), len) }).map(drop) + } } } diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index 8b5970d1494..8216f8d2fd5 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -361,7 +361,7 @@ impl Socket { self.recv_from_with_flags(buf, 0) } - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))] 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) @@ -384,7 +384,7 @@ impl Socket { self.0.is_write_vectored() } - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))] 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) @@ -472,12 +472,12 @@ impl Socket { Ok(raw != 0) } - #[cfg(any(target_os = "android", target_os = "linux",))] + #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))] 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",))] + #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))] pub fn quickack(&self) -> io::Result<bool> { let raw: c_int = getsockopt(self, libc::IPPROTO_TCP, libc::TCP_QUICKACK)?; Ok(raw != 0) @@ -541,12 +541,12 @@ impl Socket { Ok(raw != 0) } - #[cfg(any(target_os = "android", target_os = "linux",))] + #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))] 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",))] + #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))] pub fn passcred(&self) -> io::Result<bool> { let passcred: libc::c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_PASSCRED)?; Ok(passcred != 0) diff --git a/library/std/src/sys/net/connection/uefi/mod.rs b/library/std/src/sys/net/connection/uefi/mod.rs index 16e3487a174..00368042873 100644 --- a/library/std/src/sys/net/connection/uefi/mod.rs +++ b/library/std/src/sys/net/connection/uefi/mod.rs @@ -1,6 +1,7 @@ +use super::each_addr; use crate::fmt; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; -use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; +use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, ToSocketAddrs}; use crate::sync::{Arc, Mutex}; use crate::sys::unsupported; use crate::time::Duration; @@ -15,13 +16,17 @@ pub struct TcpStream { } impl TcpStream { - pub fn connect(addr: io::Result<&SocketAddr>) -> io::Result<TcpStream> { - let inner = tcp::Tcp::connect(addr?, None)?; - Ok(Self { - inner, - read_timeout: Arc::new(Mutex::new(None)), - write_timeout: Arc::new(Mutex::new(None)), - }) + pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<TcpStream> { + return each_addr(addr, inner); + + fn inner(addr: &SocketAddr) -> io::Result<TcpStream> { + let inner = tcp::Tcp::connect(addr, None)?; + Ok(TcpStream { + inner, + read_timeout: Arc::new(Mutex::new(None)), + write_timeout: Arc::new(Mutex::new(None)), + }) + } } pub fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result<TcpStream> { @@ -145,7 +150,7 @@ pub struct TcpListener { } impl TcpListener { - pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<TcpListener> { + pub fn bind<A: ToSocketAddrs>(_: A) -> io::Result<TcpListener> { unsupported() } @@ -195,7 +200,7 @@ impl fmt::Debug for TcpListener { pub struct UdpSocket(!); impl UdpSocket { - pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<UdpSocket> { + pub fn bind<A: ToSocketAddrs>(_: A) -> io::Result<UdpSocket> { unsupported() } @@ -315,7 +320,7 @@ impl UdpSocket { self.0 } - pub fn connect(&self, _: io::Result<&SocketAddr>) -> io::Result<()> { + pub fn connect<A: ToSocketAddrs>(&self, _: A) -> io::Result<()> { self.0 } } diff --git a/library/std/src/sys/net/connection/unsupported.rs b/library/std/src/sys/net/connection/unsupported.rs index da217439626..fbc86343272 100644 --- a/library/std/src/sys/net/connection/unsupported.rs +++ b/library/std/src/sys/net/connection/unsupported.rs @@ -1,13 +1,13 @@ use crate::fmt; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; -use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; +use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, ToSocketAddrs}; use crate::sys::unsupported; use crate::time::Duration; pub struct TcpStream(!); impl TcpStream { - pub fn connect(_: io::Result<&SocketAddr>) -> io::Result<TcpStream> { + pub fn connect<A: ToSocketAddrs>(_: A) -> io::Result<TcpStream> { unsupported() } @@ -121,7 +121,7 @@ impl fmt::Debug for TcpStream { pub struct TcpListener(!); impl TcpListener { - pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<TcpListener> { + pub fn bind<A: ToSocketAddrs>(_: A) -> io::Result<TcpListener> { unsupported() } @@ -171,7 +171,7 @@ impl fmt::Debug for TcpListener { pub struct UdpSocket(!); impl UdpSocket { - pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<UdpSocket> { + pub fn bind<A: ToSocketAddrs>(_: A) -> io::Result<UdpSocket> { unsupported() } @@ -291,7 +291,7 @@ impl UdpSocket { self.0 } - pub fn connect(&self, _: io::Result<&SocketAddr>) -> io::Result<()> { + pub fn connect<A: ToSocketAddrs>(&self, _: A) -> io::Result<()> { self.0 } } diff --git a/library/std/src/sys/net/connection/wasip1.rs b/library/std/src/sys/net/connection/wasip1.rs index 951dc65e5b4..cdfa25c8a44 100644 --- a/library/std/src/sys/net/connection/wasip1.rs +++ b/library/std/src/sys/net/connection/wasip1.rs @@ -2,7 +2,7 @@ use crate::fmt; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; -use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; +use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, ToSocketAddrs}; use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::sys::fd::WasiFd; use crate::sys::{err2io, unsupported}; @@ -60,7 +60,7 @@ impl FromRawFd for Socket { } impl TcpStream { - pub fn connect(_: io::Result<&SocketAddr>) -> io::Result<TcpStream> { + pub fn connect<A: ToSocketAddrs>(_: A) -> io::Result<TcpStream> { unsupported() } @@ -212,7 +212,7 @@ pub struct TcpListener { } impl TcpListener { - pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<TcpListener> { + pub fn bind<A: ToSocketAddrs>(_: A) -> io::Result<TcpListener> { unsupported() } @@ -316,7 +316,7 @@ pub struct UdpSocket { } impl UdpSocket { - pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<UdpSocket> { + pub fn bind<A: ToSocketAddrs>(_: A) -> io::Result<UdpSocket> { unsupported() } @@ -436,7 +436,7 @@ impl UdpSocket { unsupported() } - pub fn connect(&self, _: io::Result<&SocketAddr>) -> io::Result<()> { + pub fn connect<A: ToSocketAddrs>(&self, _: A) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/net/connection/xous/tcplistener.rs b/library/std/src/sys/net/connection/xous/tcplistener.rs index bdf1fcd9302..8818ef2ca9a 100644 --- a/library/std/src/sys/net/connection/xous/tcplistener.rs +++ b/library/std/src/sys/net/connection/xous/tcplistener.rs @@ -2,9 +2,10 @@ use core::convert::TryInto; use core::sync::atomic::{Atomic, AtomicBool, AtomicU16, AtomicUsize, Ordering}; use super::*; -use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs}; use crate::os::xous::services; use crate::sync::Arc; +use crate::sys::net::connection::each_addr; use crate::{fmt, io}; macro_rules! unimpl { @@ -25,16 +26,19 @@ pub struct TcpListener { } impl TcpListener { - pub fn bind(socketaddr: io::Result<&SocketAddr>) -> io::Result<TcpListener> { - let mut addr = *socketaddr?; - - let fd = TcpListener::bind_inner(&mut addr)?; - return Ok(TcpListener { - fd: Arc::new(AtomicU16::new(fd)), - local: addr, - handle_count: Arc::new(AtomicUsize::new(1)), - nonblocking: Arc::new(AtomicBool::new(false)), - }); + pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<TcpListener> { + return each_addr(addr, inner); + + fn inner(addr: &SocketAddr) -> io::Result<TcpListener> { + let mut addr = *addr; + let fd = TcpListener::bind_inner(&mut addr)?; + Ok(TcpListener { + fd: Arc::new(AtomicU16::new(fd)), + local: addr, + handle_count: Arc::new(AtomicUsize::new(1)), + nonblocking: Arc::new(AtomicBool::new(false)), + }) + } } /// This returns the raw fd of a Listener, so that it can also be used by the diff --git a/library/std/src/sys/net/connection/xous/tcpstream.rs b/library/std/src/sys/net/connection/xous/tcpstream.rs index 54524767452..4df75453d1f 100644 --- a/library/std/src/sys/net/connection/xous/tcpstream.rs +++ b/library/std/src/sys/net/connection/xous/tcpstream.rs @@ -3,9 +3,12 @@ use core::sync::atomic::{Atomic, AtomicBool, AtomicU32, AtomicUsize, Ordering}; use super::*; use crate::fmt; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; -use crate::net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6}; +use crate::net::{ + IpAddr, Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs, +}; use crate::os::xous::services; use crate::sync::Arc; +use crate::sys::net::connection::each_addr; use crate::time::Duration; macro_rules! unimpl { @@ -79,8 +82,8 @@ impl TcpStream { } } - pub fn connect(socketaddr: io::Result<&SocketAddr>) -> io::Result<TcpStream> { - Self::connect_timeout(socketaddr?, Duration::ZERO) + pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<TcpStream> { + each_addr(addr, |addr| Self::connect_timeout(addr, Duration::ZERO)) } pub fn connect_timeout(addr: &SocketAddr, duration: Duration) -> io::Result<TcpStream> { diff --git a/library/std/src/sys/net/connection/xous/udp.rs b/library/std/src/sys/net/connection/xous/udp.rs index 2127d3267ed..ce54ea3b79e 100644 --- a/library/std/src/sys/net/connection/xous/udp.rs +++ b/library/std/src/sys/net/connection/xous/udp.rs @@ -3,9 +3,10 @@ use core::sync::atomic::{Atomic, AtomicUsize, Ordering}; use super::*; use crate::cell::Cell; -use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs}; use crate::os::xous::services; use crate::sync::Arc; +use crate::sys::net::connection::each_addr; use crate::time::Duration; use crate::{fmt, io}; @@ -32,40 +33,45 @@ pub struct UdpSocket { } impl UdpSocket { - pub fn bind(socketaddr: io::Result<&SocketAddr>) -> io::Result<UdpSocket> { - let addr = socketaddr?; - // Construct the request - let mut connect_request = ConnectRequest { raw: [0u8; 4096] }; - - // Serialize the StdUdpBind structure. This is done "manually" because we don't want to - // make an auto-serdes (like bincode or rkyv) crate a dependency of Xous. - let port_bytes = addr.port().to_le_bytes(); - connect_request.raw[0] = port_bytes[0]; - connect_request.raw[1] = port_bytes[1]; - match addr.ip() { - IpAddr::V4(addr) => { - connect_request.raw[2] = 4; - for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { - *dest = src; + pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<UdpSocket> { + return each_addr(addr, inner); + + fn inner(addr: &SocketAddr) -> io::Result<UdpSocket> { + // Construct the request + let mut connect_request = ConnectRequest { raw: [0u8; 4096] }; + + // Serialize the StdUdpBind structure. This is done "manually" because we don't want to + // make an auto-serdes (like bincode or rkyv) crate a dependency of Xous. + let port_bytes = addr.port().to_le_bytes(); + connect_request.raw[0] = port_bytes[0]; + connect_request.raw[1] = port_bytes[1]; + match addr.ip() { + IpAddr::V4(addr) => { + connect_request.raw[2] = 4; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } } - } - IpAddr::V6(addr) => { - connect_request.raw[2] = 6; - for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { - *dest = src; + IpAddr::V6(addr) => { + connect_request.raw[2] = 6; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } } } - } - let response = crate::os::xous::ffi::lend_mut( - services::net_server(), - services::NetLendMut::StdUdpBind.into(), - &mut connect_request.raw, - 0, - 4096, - ); + let response = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdUdpBind.into(), + &mut connect_request.raw, + 0, + 4096, + ); + + let Ok((_, valid)) = response else { + return Err(io::const_error!(io::ErrorKind::InvalidInput, "invalid response")); + }; - if let Ok((_, valid)) = response { // The first four bytes should be zero upon success, and will be nonzero // for an error. let response = connect_request.raw; @@ -87,8 +93,9 @@ impl UdpSocket { )); } } + let fd = response[1] as u16; - return Ok(UdpSocket { + Ok(UdpSocket { fd, local: *addr, remote: Cell::new(None), @@ -96,9 +103,8 @@ impl UdpSocket { write_timeout: Cell::new(0), handle_count: Arc::new(AtomicUsize::new(1)), nonblocking: Cell::new(false), - }); + }) } - Err(io::const_error!(io::ErrorKind::InvalidInput, "invalid response")) } pub fn peer_addr(&self) -> io::Result<SocketAddr> { @@ -198,10 +204,11 @@ impl UdpSocket { self.peek_from(buf).map(|(len, _addr)| len) } - pub fn connect(&self, maybe_addr: io::Result<&SocketAddr>) -> io::Result<()> { - let addr = maybe_addr?; - self.remote.set(Some(*addr)); - Ok(()) + pub fn connect<A: ToSocketAddrs>(&self, addr: A) -> io::Result<()> { + each_addr(addr, |addr| { + self.remote.set(Some(*addr)); + Ok(()) + }) } pub fn send(&self, buf: &[u8]) -> io::Result<usize> { diff --git a/library/std/src/sys/net/mod.rs b/library/std/src/sys/net/mod.rs index 5df1fe138ab..dffc4ea7f81 100644 --- a/library/std/src/sys/net/mod.rs +++ b/library/std/src/sys/net/mod.rs @@ -1,46 +1,4 @@ -cfg_select! { - any( - all(target_family = "unix", not(target_os = "l4re")), - target_os = "windows", - target_os = "hermit", - all(target_os = "wasi", target_env = "p2"), - target_os = "solid_asp3", - ) => { - mod connection { - mod socket; - pub use socket::*; - } - } - all(target_vendor = "fortanix", target_env = "sgx") => { - mod connection { - mod sgx; - pub use sgx::*; - } - } - all(target_os = "wasi", target_env = "p1") => { - mod connection { - mod wasip1; - pub use wasip1::*; - } - } - target_os = "xous" => { - mod connection { - mod xous; - pub use xous::*; - } - } - target_os = "uefi" => { - mod connection { - mod uefi; - pub use uefi::*; - } - } - _ => { - mod connection { - mod unsupported; - pub use unsupported::*; - } - } -} - +/// This module contains the implementations of `TcpStream`, `TcpListener` and +/// `UdpSocket` as well as related functionality like DNS resolving. +mod connection; pub use connection::*; diff --git a/library/std/src/sys/pal/hermit/mod.rs b/library/std/src/sys/pal/hermit/mod.rs index fb8d69b7375..3ddf6e5acb0 100644 --- a/library/std/src/sys/pal/hermit/mod.rs +++ b/library/std/src/sys/pal/hermit/mod.rs @@ -25,7 +25,6 @@ pub mod futex; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -pub mod thread; pub mod time; pub fn unsupported<T>() -> crate::io::Result<T> { diff --git a/library/std/src/sys/pal/hermit/os.rs b/library/std/src/sys/pal/hermit/os.rs index 0fe713a503b..9681964ed9b 100644 --- a/library/std/src/sys/pal/hermit/os.rs +++ b/library/std/src/sys/pal/hermit/os.rs @@ -3,7 +3,7 @@ use crate::ffi::{OsStr, OsString}; use crate::marker::PhantomData; use crate::path::{self, PathBuf}; use crate::sys::unsupported; -use crate::{fmt, io, str}; +use crate::{fmt, io}; pub fn errno() -> i32 { unsafe { hermit_abi::get_errno() } diff --git a/library/std/src/sys/pal/mod.rs b/library/std/src/sys/pal/mod.rs index 9376f5249f1..513121c6d30 100644 --- a/library/std/src/sys/pal/mod.rs +++ b/library/std/src/sys/pal/mod.rs @@ -49,9 +49,9 @@ cfg_select! { mod wasip2; pub use self::wasip2::*; } - target_os = "wasi" => { - mod wasi; - pub use self::wasi::*; + all(target_os = "wasi", target_env = "p1") => { + mod wasip1; + pub use self::wasip1::*; } target_family = "wasm" => { mod wasm; diff --git a/library/std/src/sys/pal/sgx/abi/mod.rs b/library/std/src/sys/pal/sgx/abi/mod.rs index 57247cffad3..b8c4d7740c4 100644 --- a/library/std/src/sys/pal/sgx/abi/mod.rs +++ b/library/std/src/sys/pal/sgx/abi/mod.rs @@ -67,7 +67,7 @@ extern "C" fn entry(p1: u64, p2: u64, p3: u64, secondary: bool, p4: u64, p5: u64 let tls_guard = unsafe { tls.activate() }; if secondary { - let join_notifier = super::thread::Thread::entry(); + let join_notifier = crate::sys::thread::Thread::entry(); drop(tls_guard); drop(join_notifier); diff --git a/library/std/src/sys/pal/sgx/mod.rs b/library/std/src/sys/pal/sgx/mod.rs index 4a297b6823f..9a33873af58 100644 --- a/library/std/src/sys/pal/sgx/mod.rs +++ b/library/std/src/sys/pal/sgx/mod.rs @@ -13,7 +13,6 @@ mod libunwind_integration; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -pub mod thread; pub mod thread_parking; pub mod time; pub mod waitqueue; diff --git a/library/std/src/sys/pal/solid/mod.rs b/library/std/src/sys/pal/solid/mod.rs index 0011cf256df..9ca6dc58118 100644 --- a/library/std/src/sys/pal/solid/mod.rs +++ b/library/std/src/sys/pal/solid/mod.rs @@ -10,10 +10,8 @@ pub mod itron { pub mod error; pub mod spin; pub mod task; - pub mod thread; pub mod thread_parking; pub mod time; - use super::unsupported; } // `error` is `pub(crate)` so that it can be accessed by `itron/error.rs` as @@ -22,7 +20,7 @@ pub(crate) mod error; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -pub use self::itron::{thread, thread_parking}; +pub use self::itron::thread_parking; pub mod time; // SAFETY: must be called only once during runtime initialization. diff --git a/library/std/src/sys/pal/teeos/mod.rs b/library/std/src/sys/pal/teeos/mod.rs index c7b17777258..dd0155265da 100644 --- a/library/std/src/sys/pal/teeos/mod.rs +++ b/library/std/src/sys/pal/teeos/mod.rs @@ -9,7 +9,6 @@ pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -pub mod thread; #[allow(non_upper_case_globals)] #[path = "../unix/time.rs"] pub mod time; diff --git a/library/std/src/sys/pal/uefi/mod.rs b/library/std/src/sys/pal/uefi/mod.rs index 8911a2ee519..ebd311db1e1 100644 --- a/library/std/src/sys/pal/uefi/mod.rs +++ b/library/std/src/sys/pal/uefi/mod.rs @@ -17,7 +17,6 @@ pub mod helpers; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -pub mod thread; pub mod time; #[cfg(test)] diff --git a/library/std/src/sys/pal/uefi/thread.rs b/library/std/src/sys/pal/uefi/thread.rs deleted file mode 100644 index 47a48008c76..00000000000 --- a/library/std/src/sys/pal/uefi/thread.rs +++ /dev/null @@ -1,66 +0,0 @@ -use super::unsupported; -use crate::ffi::CStr; -use crate::io; -use crate::num::NonZero; -use crate::ptr::NonNull; -use crate::time::{Duration, Instant}; - -pub struct Thread(!); - -pub const DEFAULT_MIN_STACK_SIZE: usize = 64 * 1024; - -impl Thread { - // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new( - _stack: usize, - _name: Option<&str>, - _p: Box<dyn FnOnce()>, - ) -> io::Result<Thread> { - unsupported() - } - - pub fn yield_now() { - // do nothing - } - - pub fn set_name(_name: &CStr) { - // nope - } - - pub fn sleep(dur: Duration) { - let boot_services: NonNull<r_efi::efi::BootServices> = - crate::os::uefi::env::boot_services().expect("can't sleep").cast(); - let mut dur_ms = dur.as_micros(); - // ceil up to the nearest microsecond - if dur.subsec_nanos() % 1000 > 0 { - dur_ms += 1; - } - - while dur_ms > 0 { - let ms = crate::cmp::min(dur_ms, usize::MAX as u128); - let _ = unsafe { ((*boot_services.as_ptr()).stall)(ms as usize) }; - dur_ms -= ms; - } - } - - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - - pub fn join(self) { - self.0 - } -} - -pub(crate) fn current_os_id() -> Option<u64> { - None -} - -pub fn available_parallelism() -> io::Result<NonZero<usize>> { - // UEFI is single threaded - Ok(NonZero::new(1).unwrap()) -} diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 400128acf12..dd1059fe04a 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -17,7 +17,6 @@ pub mod os; pub mod pipe; pub mod stack_overflow; pub mod sync; -pub mod thread; pub mod thread_parking; pub mod time; @@ -55,7 +54,7 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { // 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_vendor = "apple") { - thread::Thread::set_name(&c"main"); + crate::sys::thread::set_name(c"main"); } unsafe fn sanitize_standard_fds() { @@ -364,6 +363,7 @@ pub fn cvt_nz(error: libc::c_int) -> crate::io::Result<()> { // 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. +#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub fn abort_internal() -> ! { unsafe { libc::abort() } } diff --git a/library/std/src/sys/pal/unix/weak.rs b/library/std/src/sys/pal/unix/weak.rs index c8cf75b876c..a3b980a3f3d 100644 --- a/library/std/src/sys/pal/unix/weak.rs +++ b/library/std/src/sys/pal/unix/weak.rs @@ -22,11 +22,24 @@ #![allow(dead_code, unused_macros)] #![forbid(unsafe_op_in_unsafe_fn)] -use crate::ffi::CStr; -use crate::marker::PhantomData; -use crate::sync::atomic::{self, Atomic, AtomicPtr, Ordering}; +use crate::ffi::{CStr, c_char, c_void}; +use crate::marker::{FnPtr, PhantomData}; +use crate::sync::atomic::{Atomic, AtomicPtr, Ordering}; use crate::{mem, ptr}; +// We currently only test `dlsym!`, but that doesn't work on all platforms, so +// we gate the tests to only the platforms where it is actually used. +// +// FIXME(joboet): add more tests, reorganise the whole module and get rid of +// `#[allow(dead_code, unused_macros)]`. +#[cfg(any( + target_vendor = "apple", + all(target_os = "linux", target_env = "gnu"), + target_os = "freebsd", +))] +#[cfg(test)] +mod tests; + // We can use true weak linkage on ELF targets. #[cfg(all(unix, not(target_vendor = "apple")))] pub(crate) macro weak { @@ -64,7 +77,7 @@ impl<F: Copy> ExternWeak<F> { pub(crate) macro dlsym { (fn $name:ident($($param:ident : $t:ty),* $(,)?) -> $ret:ty;) => ( - dlsym!( + dlsym!( #[link_name = stringify!($name)] fn $name($($param : $t),*) -> $ret; ); @@ -73,21 +86,39 @@ pub(crate) macro dlsym { #[link_name = $sym:expr] fn $name:ident($($param:ident : $t:ty),* $(,)?) -> $ret:ty; ) => ( - static DLSYM: DlsymWeak<unsafe extern "C" fn($($t),*) -> $ret> = - DlsymWeak::new(concat!($sym, '\0')); + static DLSYM: DlsymWeak<unsafe extern "C" fn($($t),*) -> $ret> = { + let Ok(name) = CStr::from_bytes_with_nul(concat!($sym, '\0').as_bytes()) else { + panic!("symbol name may not contain NUL") + }; + + // SAFETY: Whoever calls the function pointer returned by `get()` + // is responsible for ensuring that the signature is correct. Just + // like with extern blocks, this is syntactically enforced by making + // the function pointer be unsafe. + unsafe { DlsymWeak::new(name) } + }; + let $name = &DLSYM; ) } + pub(crate) struct DlsymWeak<F> { - name: &'static str, + /// A pointer to the nul-terminated name of the symbol. + // Use a pointer instead of `&'static CStr` to save space. + name: *const c_char, func: Atomic<*mut libc::c_void>, _marker: PhantomData<F>, } -impl<F> DlsymWeak<F> { - pub(crate) const fn new(name: &'static str) -> Self { +impl<F: FnPtr> DlsymWeak<F> { + /// # Safety + /// + /// If the signature of `F` does not match the signature of the symbol (if + /// it exists), calling the function pointer returned by `get()` is + /// undefined behaviour. + pub(crate) const unsafe fn new(name: &'static CStr) -> Self { DlsymWeak { - name, + name: name.as_ptr(), func: AtomicPtr::new(ptr::without_provenance_mut(1)), _marker: PhantomData, } @@ -95,62 +126,59 @@ impl<F> DlsymWeak<F> { #[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) - } - } + // 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 load (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*, so we use acquire. + match self.func.load(Ordering::Acquire) { + func if func.addr() == 1 => self.initialize(), + func if func.is_null() => None, + // SAFETY: + // `func` is not null and `F` implements `FnPtr`, thus this + // transmutation is well-defined. It is the responsibility of the + // creator of this `DlsymWeak` to ensure that calling the resulting + // function pointer does not result in undefined behaviour (though + // the `dlsym!` macro delegates this responsibility to the caller + // of the function by using `unsafe` function pointers). + // FIXME: use `transmute` once it stops complaining about generics. + func => Some(unsafe { mem::transmute_copy::<*mut c_void, F>(&func) }), } } // Cold because it should only happen during first-time initialization. #[cold] - unsafe fn initialize(&self) -> Option<F> { - assert_eq!(size_of::<F>(), size_of::<*mut libc::c_void>()); - - let val = unsafe { fetch(self.name) }; - // This synchronizes with the acquire fence in `get`. + fn initialize(&self) -> Option<F> { + // SAFETY: `self.name` was created from a `&'static CStr` and is + // therefore a valid C string pointer. + let val = unsafe { libc::dlsym(libc::RTLD_DEFAULT, self.name) }; + // This synchronizes with the acquire load in `get`. self.func.store(val, Ordering::Release); if val.is_null() { None } else { + // SAFETY: see the comment in `get`. + // FIXME: use `transmute` once it stops complaining about generics. Some(unsafe { 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(), - }; - unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr()) } -} +unsafe impl<F> Send for DlsymWeak<F> {} +unsafe impl<F> Sync for DlsymWeak<F> {} #[cfg(not(any(target_os = "linux", target_os = "android")))] pub(crate) macro syscall { diff --git a/library/std/src/sys/pal/unix/weak/tests.rs b/library/std/src/sys/pal/unix/weak/tests.rs new file mode 100644 index 00000000000..d807ba64e35 --- /dev/null +++ b/library/std/src/sys/pal/unix/weak/tests.rs @@ -0,0 +1,32 @@ +use super::*; + +#[test] +fn dlsym_existing() { + const TEST_STRING: &'static CStr = c"Ferris!"; + + // Try to find a symbol that definitely exists. + dlsym! { + fn strlen(cs: *const c_char) -> usize; + } + + dlsym! { + #[link_name = "strlen"] + fn custom_name(cs: *const c_char) -> usize; + } + + let strlen = strlen.get().unwrap(); + assert_eq!(unsafe { strlen(TEST_STRING.as_ptr()) }, TEST_STRING.count_bytes()); + + let custom_name = custom_name.get().unwrap(); + assert_eq!(unsafe { custom_name(TEST_STRING.as_ptr()) }, TEST_STRING.count_bytes()); +} + +#[test] +fn dlsym_missing() { + // Try to find a symbol that definitely does not exist. + dlsym! { + fn test_symbol_that_does_not_exist() -> i32; + } + + assert!(test_symbol_that_does_not_exist.get().is_none()); +} diff --git a/library/std/src/sys/pal/wasi/thread.rs b/library/std/src/sys/pal/wasi/thread.rs deleted file mode 100644 index e062b49bd7a..00000000000 --- a/library/std/src/sys/pal/wasi/thread.rs +++ /dev/null @@ -1,214 +0,0 @@ -#![forbid(unsafe_op_in_unsafe_fn)] - -use crate::ffi::CStr; -use crate::num::NonZero; -use crate::time::{Duration, Instant}; -use crate::{io, mem}; - -cfg_select! { - target_feature = "atomics" => { - use crate::cmp; - use crate::ptr; - use crate::sys::os; - // Add a few symbols not in upstream `libc` just yet. - mod libc { - pub use crate::ffi; - pub use libc::*; - - // defined in wasi-libc - // https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-top-half/musl/include/alltypes.h.in#L108 - #[repr(C)] - union pthread_attr_union { - __i: [ffi::c_int; if size_of::<ffi::c_long>() == 8 { 14 } else { 9 }], - __vi: [ffi::c_int; if size_of::<ffi::c_long>() == 8 { 14 } else { 9 }], - __s: [ffi::c_ulong; if size_of::<ffi::c_long>() == 8 { 7 } else { 9 }], - } - - #[repr(C)] - pub struct pthread_attr_t { - __u: pthread_attr_union, - } - - #[allow(non_camel_case_types)] - pub type pthread_t = *mut ffi::c_void; - - pub const _SC_NPROCESSORS_ONLN: ffi::c_int = 84; - - unsafe extern "C" { - pub fn pthread_create( - native: *mut pthread_t, - attr: *const pthread_attr_t, - f: extern "C" fn(*mut ffi::c_void) -> *mut ffi::c_void, - value: *mut ffi::c_void, - ) -> ffi::c_int; - pub fn pthread_join(native: pthread_t, value: *mut *mut ffi::c_void) -> ffi::c_int; - pub fn pthread_attr_init(attrp: *mut pthread_attr_t) -> ffi::c_int; - pub fn pthread_attr_setstacksize( - attr: *mut pthread_attr_t, - stack_size: libc::size_t, - ) -> ffi::c_int; - pub fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> ffi::c_int; - pub fn pthread_detach(thread: pthread_t) -> ffi::c_int; - } - } - - pub struct Thread { - id: libc::pthread_t, - } - - impl Drop for Thread { - fn drop(&mut self) { - let ret = unsafe { libc::pthread_detach(self.id) }; - debug_assert_eq!(ret, 0); - } - } - } - _ => { - pub struct Thread(!); - } -} - -pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024; - -impl Thread { - // unsafe: see thread::Builder::spawn_unchecked for safety requirements - cfg_select! { - target_feature = "atomics" => { - pub unsafe fn new(stack: usize, _name: Option<&str>, p: Box<dyn FnOnce()>) -> io::Result<Thread> { - let p = Box::into_raw(Box::new(p)); - let mut native: libc::pthread_t = unsafe { mem::zeroed() }; - let mut attr: libc::pthread_attr_t = unsafe { mem::zeroed() }; - assert_eq!(unsafe { libc::pthread_attr_init(&mut attr) }, 0); - - let stack_size = cmp::max(stack, DEFAULT_MIN_STACK_SIZE); - - match unsafe { 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!(unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) }, 0); - } - }; - - let ret = unsafe { 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!(unsafe {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. - unsafe { 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 { - // Finally, let's run some code. - Box::from_raw(main as *mut Box<dyn FnOnce()>)(); - } - ptr::null_mut() - } - } - } - _ => { - pub unsafe fn new(_stack: usize, _name: Option<&str>, _p: Box<dyn FnOnce()>) -> io::Result<Thread> { - crate::sys::unsupported() - } - } - } - - pub fn yield_now() { - let ret = unsafe { wasi::sched_yield() }; - debug_assert_eq!(ret, Ok(())); - } - - pub fn set_name(_name: &CStr) { - // nope - } - - pub fn sleep(dur: Duration) { - let mut nanos = dur.as_nanos(); - while nanos > 0 { - const USERDATA: wasi::Userdata = 0x0123_45678; - - let clock = wasi::SubscriptionClock { - id: wasi::CLOCKID_MONOTONIC, - timeout: u64::try_from(nanos).unwrap_or(u64::MAX), - precision: 0, - flags: 0, - }; - nanos -= u128::from(clock.timeout); - - let in_ = wasi::Subscription { - userdata: USERDATA, - u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } }, - }; - unsafe { - let mut event: wasi::Event = mem::zeroed(); - let res = wasi::poll_oneoff(&in_, &mut event, 1); - match (res, event) { - ( - Ok(1), - wasi::Event { - userdata: USERDATA, - error: wasi::ERRNO_SUCCESS, - type_: wasi::EVENTTYPE_CLOCK, - .. - }, - ) => {} - _ => panic!("thread::sleep(): unexpected result of poll_oneoff"), - } - } - } - } - - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - - pub fn join(self) { - cfg_select! { - target_feature = "atomics" => { - let id = mem::ManuallyDrop::new(self).id; - let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; - if ret != 0 { - rtabort!("failed to join thread: {}", io::Error::from_raw_os_error(ret)); - } - } - _ => { - self.0 - } - } - } -} - -pub(crate) fn current_os_id() -> Option<u64> { - None -} - -pub fn available_parallelism() -> io::Result<NonZero<usize>> { - cfg_select! { - target_feature = "atomics" => { - match unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) } { - -1 => Err(io::Error::last_os_error()), - cpus => NonZero::new(cpus as usize).ok_or(io::Error::UNKNOWN_THREAD_COUNT), - } - } - _ => crate::sys::unsupported(), - } -} diff --git a/library/std/src/sys/pal/wasi/helpers.rs b/library/std/src/sys/pal/wasip1/helpers.rs index 404747f0dc7..404747f0dc7 100644 --- a/library/std/src/sys/pal/wasi/helpers.rs +++ b/library/std/src/sys/pal/wasip1/helpers.rs diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasip1/mod.rs index 61dd1c3f98b..ae5da3c1f77 100644 --- a/library/std/src/sys/pal/wasi/mod.rs +++ b/library/std/src/sys/pal/wasip1/mod.rs @@ -20,7 +20,6 @@ pub mod futex; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -pub mod thread; pub mod time; #[path = "../unsupported/common.rs"] diff --git a/library/std/src/sys/pal/wasi/os.rs b/library/std/src/sys/pal/wasip1/os.rs index 151ba254ec4..151ba254ec4 100644 --- a/library/std/src/sys/pal/wasi/os.rs +++ b/library/std/src/sys/pal/wasip1/os.rs diff --git a/library/std/src/sys/pal/wasi/time.rs b/library/std/src/sys/pal/wasip1/time.rs index 892661b312b..892661b312b 100644 --- a/library/std/src/sys/pal/wasi/time.rs +++ b/library/std/src/sys/pal/wasip1/time.rs diff --git a/library/std/src/sys/pal/wasip2/mod.rs b/library/std/src/sys/pal/wasip2/mod.rs index 47fe3221c90..c1d89da2677 100644 --- a/library/std/src/sys/pal/wasip2/mod.rs +++ b/library/std/src/sys/pal/wasip2/mod.rs @@ -10,13 +10,10 @@ #[path = "../wasm/atomics/futex.rs"] pub mod futex; -#[path = "../wasi/os.rs"] +#[path = "../wasip1/os.rs"] pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../wasi/thread.rs"] -pub mod thread; -#[path = "../wasi/time.rs"] pub mod time; #[path = "../unsupported/common.rs"] @@ -26,7 +23,7 @@ mod common; pub use common::*; -#[path = "../wasi/helpers.rs"] +#[path = "../wasip1/helpers.rs"] mod helpers; // The following exports are listed individually to work around Rust's glob diff --git a/library/std/src/sys/pal/wasip2/time.rs b/library/std/src/sys/pal/wasip2/time.rs new file mode 100644 index 00000000000..980070e7b85 --- /dev/null +++ b/library/std/src/sys/pal/wasip2/time.rs @@ -0,0 +1,69 @@ +use crate::time::Duration; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Instant(Duration); + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SystemTime(Duration); + +pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); + +impl Instant { + pub fn now() -> Instant { + Instant(Duration::from_nanos(wasip2::clocks::monotonic_clock::now())) + } + + pub fn checked_sub_instant(&self, other: &Instant) -> Option<Duration> { + self.0.checked_sub(other.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option<Instant> { + Some(Instant(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option<Instant> { + Some(Instant(self.0.checked_sub(*other)?)) + } + + pub(crate) fn as_duration(&self) -> &Duration { + &self.0 + } +} + +impl SystemTime { + pub fn now() -> SystemTime { + let now = wasip2::clocks::wall_clock::now(); + SystemTime(Duration::new(now.seconds, now.nanoseconds)) + } + + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn from_wasi_timestamp(ts: wasi::Timestamp) -> SystemTime { + SystemTime(Duration::from_nanos(ts)) + } + + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn to_wasi_timestamp(&self) -> Option<wasi::Timestamp> { + // FIXME: const TryInto + let ns = self.0.as_nanos(); + if ns <= u64::MAX as u128 { Some(ns as u64) } else { None } + } + + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> { + // FIXME: ok_or_else with const closures + match self.0.checked_sub(other.0) { + Some(duration) => Ok(duration), + None => Err(other.0 - self.0), + } + } + + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> { + Some(SystemTime(self.0.checked_add(*other)?)) + } + + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> { + Some(SystemTime(self.0.checked_sub(*other)?)) + } +} diff --git a/library/std/src/sys/pal/wasm/atomics/thread.rs b/library/std/src/sys/pal/wasm/atomics/thread.rs deleted file mode 100644 index 42a7dbdf8b8..00000000000 --- a/library/std/src/sys/pal/wasm/atomics/thread.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::ffi::CStr; -use crate::io; -use crate::num::NonZero; -use crate::sys::unsupported; -use crate::time::{Duration, Instant}; - -pub struct Thread(!); - -pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024; - -impl Thread { - // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new( - _stack: usize, - _name: Option<&str>, - _p: Box<dyn FnOnce()>, - ) -> io::Result<Thread> { - unsupported() - } - - pub fn yield_now() {} - - pub fn set_name(_name: &CStr) {} - - pub fn sleep(dur: Duration) { - #[cfg(target_arch = "wasm32")] - use core::arch::wasm32 as wasm; - #[cfg(target_arch = "wasm64")] - use core::arch::wasm64 as wasm; - - use crate::cmp; - - // Use an atomic wait to block the current thread artificially with a - // timeout listed. Note that we should never be notified (return value - // of 0) or our comparison should never fail (return value of 1) so we - // should always only resume execution through a timeout (return value - // 2). - let mut nanos = dur.as_nanos(); - while nanos > 0 { - let amt = cmp::min(i64::MAX as u128, nanos); - let mut x = 0; - let val = unsafe { wasm::memory_atomic_wait32(&mut x, 0, amt as i64) }; - debug_assert_eq!(val, 2); - nanos -= amt; - } - } - - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - - pub fn join(self) {} -} - -pub(crate) fn current_os_id() -> Option<u64> { - None -} - -pub fn available_parallelism() -> io::Result<NonZero<usize>> { - unsupported() -} - -pub mod guard { - pub type Guard = !; - pub unsafe fn current() -> Option<Guard> { - None - } - pub unsafe fn init() -> Option<Guard> { - None - } -} diff --git a/library/std/src/sys/pal/wasm/mod.rs b/library/std/src/sys/pal/wasm/mod.rs index 346c9ff88c9..a20cd0e9ac7 100644 --- a/library/std/src/sys/pal/wasm/mod.rs +++ b/library/std/src/sys/pal/wasm/mod.rs @@ -23,18 +23,9 @@ pub mod pipe; #[path = "../unsupported/time.rs"] pub mod time; -cfg_select! { - target_feature = "atomics" => { - #[path = "atomics/futex.rs"] - pub mod futex; - #[path = "atomics/thread.rs"] - pub mod thread; - } - _ => { - #[path = "../unsupported/thread.rs"] - pub mod thread; - } -} +#[cfg(target_feature = "atomics")] +#[path = "atomics/futex.rs"] +pub mod futex; #[path = "../unsupported/common.rs"] #[deny(unsafe_op_in_unsafe_fn)] diff --git a/library/std/src/sys/pal/windows/handle.rs b/library/std/src/sys/pal/windows/handle.rs index 82a880faf5f..76c8aa939d3 100644 --- a/library/std/src/sys/pal/windows/handle.rs +++ b/library/std/src/sys/pal/windows/handle.rs @@ -136,6 +136,19 @@ impl Handle { } } + pub fn read_buf_at(&self, mut cursor: BorrowedCursor<'_>, offset: u64) -> io::Result<()> { + // SAFETY: `cursor.as_mut()` starts with `cursor.capacity()` writable bytes + let read = unsafe { + self.synchronous_read(cursor.as_mut().as_mut_ptr(), cursor.capacity(), Some(offset)) + }?; + + // SAFETY: `read` bytes were written to the initialized portion of the buffer + unsafe { + cursor.advance_unchecked(read); + } + Ok(()) + } + pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> { let mut me = self; diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index 10ad4541bed..3357946b8f7 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -20,7 +20,6 @@ pub mod futex; pub mod handle; pub mod os; pub mod pipe; -pub mod thread; pub mod time; cfg_select! { not(target_vendor = "uwp") => { @@ -48,9 +47,9 @@ pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) { unsafe { stack_overflow::init(); - // Normally, `thread::spawn` will call `Thread::set_name` but since this thread already + // Normally, `thread::spawn` will call `set_name` but since this thread already // exists, we have to call it ourselves. - thread::Thread::set_name_wide(wide_str!("main")); + crate::sys::thread::set_name_wide(wide_str!("main")); } } @@ -356,6 +355,7 @@ pub fn abort_internal() -> ! { } #[cfg(miri)] +#[track_caller] // even without panics, this helps for Miri backtraces pub fn abort_internal() -> ! { crate::intrinsics::abort(); } diff --git a/library/std/src/sys/pal/windows/time.rs b/library/std/src/sys/pal/windows/time.rs index a948c07e0a3..f8f9a9fd818 100644 --- a/library/std/src/sys/pal/windows/time.rs +++ b/library/std/src/sys/pal/windows/time.rs @@ -232,7 +232,7 @@ mod perf_counter { } /// A timer you can wait on. -pub(super) struct WaitableTimer { +pub(crate) struct WaitableTimer { handle: c::HANDLE, } impl WaitableTimer { diff --git a/library/std/src/sys/pal/xous/mod.rs b/library/std/src/sys/pal/xous/mod.rs index 042c4ff862f..e673157e0eb 100644 --- a/library/std/src/sys/pal/xous/mod.rs +++ b/library/std/src/sys/pal/xous/mod.rs @@ -5,7 +5,6 @@ use crate::os::xous::ffi::exit; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -pub mod thread; pub mod time; #[path = "../unsupported/common.rs"] diff --git a/library/std/src/sys/platform_version/darwin/core_foundation.rs b/library/std/src/sys/platform_version/darwin/core_foundation.rs new file mode 100644 index 00000000000..1e0d15fcf66 --- /dev/null +++ b/library/std/src/sys/platform_version/darwin/core_foundation.rs @@ -0,0 +1,180 @@ +//! Minimal utilities for interfacing with a dynamically loaded CoreFoundation. +#![allow(non_snake_case, non_upper_case_globals)] +use super::root_relative; +use crate::ffi::{CStr, c_char, c_void}; +use crate::ptr::null_mut; +use crate::sys::common::small_c_string::run_path_with_cstr; + +// MacTypes.h +pub(super) type Boolean = u8; +// CoreFoundation/CFBase.h +pub(super) type CFTypeID = usize; +pub(super) type CFOptionFlags = usize; +pub(super) type CFIndex = isize; +pub(super) type CFTypeRef = *mut c_void; +pub(super) type CFAllocatorRef = CFTypeRef; +pub(super) const kCFAllocatorDefault: CFAllocatorRef = null_mut(); +// CoreFoundation/CFError.h +pub(super) type CFErrorRef = CFTypeRef; +// CoreFoundation/CFData.h +pub(super) type CFDataRef = CFTypeRef; +// CoreFoundation/CFPropertyList.h +pub(super) const kCFPropertyListImmutable: CFOptionFlags = 0; +pub(super) type CFPropertyListFormat = CFIndex; +pub(super) type CFPropertyListRef = CFTypeRef; +// CoreFoundation/CFString.h +pub(super) type CFStringRef = CFTypeRef; +pub(super) type CFStringEncoding = u32; +pub(super) const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100; +// CoreFoundation/CFDictionary.h +pub(super) type CFDictionaryRef = CFTypeRef; + +/// An open handle to the dynamically loaded CoreFoundation framework. +/// +/// This is `dlopen`ed, and later `dlclose`d. This is done to try to avoid +/// "leaking" the CoreFoundation symbols to the rest of the user's binary if +/// they decided to not link CoreFoundation themselves. +/// +/// It is also faster to look up symbols directly via this handle than with +/// `RTLD_DEFAULT`. +pub(super) struct CFHandle(*mut c_void); + +macro_rules! dlsym_fn { + ( + unsafe fn $name:ident($($param:ident: $param_ty:ty),* $(,)?) $(-> $ret:ty)?; + ) => { + pub(super) unsafe fn $name(&self, $($param: $param_ty),*) $(-> $ret)? { + let ptr = unsafe { + libc::dlsym( + self.0, + concat!(stringify!($name), '\0').as_bytes().as_ptr().cast(), + ) + }; + if ptr.is_null() { + let err = unsafe { CStr::from_ptr(libc::dlerror()) }; + panic!("could not find function {}: {err:?}", stringify!($name)); + } + + // SAFETY: Just checked that the symbol isn't NULL, and macro invoker verifies that + // the signature is correct. + let fnptr = unsafe { + crate::mem::transmute::< + *mut c_void, + unsafe extern "C" fn($($param_ty),*) $(-> $ret)?, + >(ptr) + }; + + // SAFETY: Upheld by caller. + unsafe { fnptr($($param),*) } + } + }; +} + +impl CFHandle { + /// Link to the CoreFoundation dylib, and look up symbols from that. + pub(super) fn new() -> Self { + // We explicitly use non-versioned path here, to allow this to work on older iOS devices. + let cf_path = + root_relative("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"); + + let handle = run_path_with_cstr(&cf_path, &|path| unsafe { + Ok(libc::dlopen(path.as_ptr(), libc::RTLD_LAZY | libc::RTLD_LOCAL)) + }) + .expect("failed allocating string"); + + if handle.is_null() { + let err = unsafe { CStr::from_ptr(libc::dlerror()) }; + panic!("could not open CoreFoundation.framework: {err:?}"); + } + + Self(handle) + } + + pub(super) fn kCFAllocatorNull(&self) -> CFAllocatorRef { + // Available: in all CF versions. + let static_ptr = unsafe { libc::dlsym(self.0, c"kCFAllocatorNull".as_ptr()) }; + if static_ptr.is_null() { + let err = unsafe { CStr::from_ptr(libc::dlerror()) }; + panic!("could not find kCFAllocatorNull: {err:?}"); + } + unsafe { *static_ptr.cast() } + } + + // CoreFoundation/CFBase.h + dlsym_fn!( + // Available: in all CF versions. + unsafe fn CFRelease(cf: CFTypeRef); + ); + dlsym_fn!( + // Available: in all CF versions. + unsafe fn CFGetTypeID(cf: CFTypeRef) -> CFTypeID; + ); + + // CoreFoundation/CFData.h + dlsym_fn!( + // Available: in all CF versions. + unsafe fn CFDataCreateWithBytesNoCopy( + allocator: CFAllocatorRef, + bytes: *const u8, + length: CFIndex, + bytes_deallocator: CFAllocatorRef, + ) -> CFDataRef; + ); + + // CoreFoundation/CFPropertyList.h + dlsym_fn!( + // Available: since macOS 10.6. + unsafe fn CFPropertyListCreateWithData( + allocator: CFAllocatorRef, + data: CFDataRef, + options: CFOptionFlags, + format: *mut CFPropertyListFormat, + error: *mut CFErrorRef, + ) -> CFPropertyListRef; + ); + + // CoreFoundation/CFString.h + dlsym_fn!( + // Available: in all CF versions. + unsafe fn CFStringGetTypeID() -> CFTypeID; + ); + dlsym_fn!( + // Available: in all CF versions. + unsafe fn CFStringCreateWithCStringNoCopy( + alloc: CFAllocatorRef, + c_str: *const c_char, + encoding: CFStringEncoding, + contents_deallocator: CFAllocatorRef, + ) -> CFStringRef; + ); + dlsym_fn!( + // Available: in all CF versions. + unsafe fn CFStringGetCString( + the_string: CFStringRef, + buffer: *mut c_char, + buffer_size: CFIndex, + encoding: CFStringEncoding, + ) -> Boolean; + ); + + // CoreFoundation/CFDictionary.h + dlsym_fn!( + // Available: in all CF versions. + unsafe fn CFDictionaryGetTypeID() -> CFTypeID; + ); + dlsym_fn!( + // Available: in all CF versions. + unsafe fn CFDictionaryGetValue( + the_dict: CFDictionaryRef, + key: *const c_void, + ) -> *const c_void; + ); +} + +impl Drop for CFHandle { + fn drop(&mut self) { + // Ignore errors when closing. This is also what `libloading` does: + // https://docs.rs/libloading/0.8.6/src/libloading/os/unix/mod.rs.html#374 + let _ = unsafe { libc::dlclose(self.0) }; + } +} diff --git a/library/std/src/sys/platform_version/darwin/mod.rs b/library/std/src/sys/platform_version/darwin/mod.rs new file mode 100644 index 00000000000..06b97fcdef4 --- /dev/null +++ b/library/std/src/sys/platform_version/darwin/mod.rs @@ -0,0 +1,351 @@ +use self::core_foundation::{ + CFDictionaryRef, CFHandle, CFIndex, CFStringRef, CFTypeRef, kCFAllocatorDefault, + kCFPropertyListImmutable, kCFStringEncodingUTF8, +}; +use crate::borrow::Cow; +use crate::bstr::ByteStr; +use crate::ffi::{CStr, c_char}; +use crate::num::{NonZero, ParseIntError}; +use crate::path::{Path, PathBuf}; +use crate::ptr::null_mut; +use crate::sync::atomic::{AtomicU32, Ordering}; +use crate::{env, fs}; + +mod core_foundation; +mod public_extern; +#[cfg(test)] +mod tests; + +/// The version of the operating system. +/// +/// We use a packed u32 here to allow for fast comparisons and to match Mach-O's `LC_BUILD_VERSION`. +type OSVersion = u32; + +/// Combine parts of a version into an [`OSVersion`]. +/// +/// The size of the parts are inherently limited by Mach-O's `LC_BUILD_VERSION`. +#[inline] +const fn pack_os_version(major: u16, minor: u8, patch: u8) -> OSVersion { + let (major, minor, patch) = (major as u32, minor as u32, patch as u32); + (major << 16) | (minor << 8) | patch +} + +/// [`pack_os_version`], but takes `i32` and saturates. +/// +/// Instead of using e.g. `major as u16`, which truncates. +#[inline] +fn pack_i32_os_version(major: i32, minor: i32, patch: i32) -> OSVersion { + let major: u16 = major.try_into().unwrap_or(u16::MAX); + let minor: u8 = minor.try_into().unwrap_or(u8::MAX); + let patch: u8 = patch.try_into().unwrap_or(u8::MAX); + pack_os_version(major, minor, patch) +} + +/// Get the current OS version, packed according to [`pack_os_version`]. +/// +/// # Semantics +/// +/// The reported version on macOS might be 10.16 if the SDK version of the binary is less than 11.0. +/// This is a workaround that Apple implemented to handle applications that assumed that macOS +/// versions would always start with "10", see: +/// <https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.81.4/libsyscall/wrappers/system-version-compat.c> +/// +/// It _is_ possible to get the real version regardless of the SDK version of the binary, this is +/// what Zig does: +/// <https://github.com/ziglang/zig/blob/0.13.0/lib/std/zig/system/darwin/macos.zig> +/// +/// We choose to not do that, and instead follow Apple's behaviour here, and return 10.16 when +/// compiled with an older SDK; the user should instead upgrade their tooling. +/// +/// NOTE: `rustc` currently doesn't set the right SDK version when linking with ld64, so this will +/// have the wrong behaviour with `-Clinker=ld` on x86_64. But that's a `rustc` bug: +/// <https://github.com/rust-lang/rust/issues/129432> +#[inline] +fn current_version() -> OSVersion { + // Cache the lookup for performance. + // + // 0.0.0 is never going to be a valid version ("vtool" reports "n/a" on 0 versions), so we use + // that as our sentinel value. + static CURRENT_VERSION: AtomicU32 = AtomicU32::new(0); + + // We use relaxed atomics instead of e.g. a `Once`, it doesn't matter if multiple threads end up + // racing to read or write the version, `lookup_version` should be idempotent and always return + // the same value. + // + // `compiler-rt` uses `dispatch_once`, but that's overkill for the reasons above. + let version = CURRENT_VERSION.load(Ordering::Relaxed); + if version == 0 { + let version = lookup_version().get(); + CURRENT_VERSION.store(version, Ordering::Relaxed); + version + } else { + version + } +} + +/// Look up the os version. +/// +/// # Aborts +/// +/// Aborts if reading or parsing the version fails (or if the system was out of memory). +/// +/// We deliberately choose to abort, as having this silently return an invalid OS version would be +/// impossible for a user to debug. +// The lookup is costly and should be on the cold path because of the cache in `current_version`. +#[cold] +// Micro-optimization: We use `extern "C"` to abort on panic, allowing `current_version` (inlined) +// to be free of unwind handling. Aborting is required for `__isPlatformVersionAtLeast` anyhow. +extern "C" fn lookup_version() -> NonZero<OSVersion> { + // Try to read from `sysctl` first (faster), but if that fails, fall back to reading the + // property list (this is roughly what `_availability_version_check` does internally). + let version = version_from_sysctl().unwrap_or_else(version_from_plist); + + // Use `NonZero` to try to make it clearer to the optimizer that this will never return 0. + NonZero::new(version).expect("version cannot be 0.0.0") +} + +/// Read the version from `kern.osproductversion` or `kern.iossupportversion`. +/// +/// This is faster than `version_from_plist`, since it doesn't need to invoke `dlsym`. +fn version_from_sysctl() -> Option<OSVersion> { + // This won't work in the simulator, as `kern.osproductversion` returns the host macOS version, + // and `kern.iossupportversion` returns the host macOS' iOSSupportVersion (while you can run + // simulators with many different iOS versions). + if cfg!(target_abi = "sim") { + // Fall back to `version_from_plist` on these targets. + return None; + } + + let sysctl_version = |name: &CStr| { + let mut buf: [u8; 32] = [0; 32]; + let mut size = buf.len(); + let ptr = buf.as_mut_ptr().cast(); + let ret = unsafe { libc::sysctlbyname(name.as_ptr(), ptr, &mut size, null_mut(), 0) }; + if ret != 0 { + // This sysctl is not available. + return None; + } + let buf = &buf[..(size - 1)]; + + if buf.is_empty() { + // The buffer may be empty when using `kern.iossupportversion` on an actual iOS device, + // or on visionOS when running under "Designed for iPad". + // + // In that case, fall back to `kern.osproductversion`. + return None; + } + + Some(parse_os_version(buf).unwrap_or_else(|err| { + panic!("failed parsing version from sysctl ({}): {err}", ByteStr::new(buf)) + })) + }; + + // When `target_os = "ios"`, we may be in many different states: + // - Native iOS device. + // - iOS Simulator. + // - Mac Catalyst. + // - Mac + "Designed for iPad". + // - Native visionOS device + "Designed for iPad". + // - visionOS simulator + "Designed for iPad". + // + // Of these, only native, Mac Catalyst and simulators can be differentiated at compile-time + // (with `target_abi = ""`, `target_abi = "macabi"` and `target_abi = "sim"` respectively). + // + // That is, "Designed for iPad" will act as iOS at compile-time, but the `ProductVersion` will + // still be the host macOS or visionOS version. + // + // Furthermore, we can't even reliably differentiate between these at runtime, since + // `dyld_get_active_platform` isn't publicly available. + // + // Fortunately, we won't need to know any of that; we can simply attempt to get the + // `iOSSupportVersion` (which may be set on native iOS too, but then it will be set to the host + // iOS version), and if that fails, fall back to the `ProductVersion`. + if cfg!(target_os = "ios") { + // https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.81.4/bsd/kern/kern_sysctl.c#L2077-L2100 + if let Some(ios_support_version) = sysctl_version(c"kern.iossupportversion") { + return Some(ios_support_version); + } + + // On Mac Catalyst, if we failed looking up `iOSSupportVersion`, we don't want to + // accidentally fall back to `ProductVersion`. + if cfg!(target_abi = "macabi") { + return None; + } + } + + // Introduced in macOS 10.13.4. + // https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.81.4/bsd/kern/kern_sysctl.c#L2015-L2051 + sysctl_version(c"kern.osproductversion") +} + +/// Look up the current OS version(s) from `/System/Library/CoreServices/SystemVersion.plist`. +/// +/// More specifically, from the `ProductVersion` and `iOSSupportVersion` keys, and from +/// `$IPHONE_SIMULATOR_ROOT/System/Library/CoreServices/SystemVersion.plist` on the simulator. +/// +/// This file was introduced in macOS 10.3, which is well below the minimum supported version by +/// `rustc`, which is (at the time of writing) macOS 10.12. +/// +/// # Implementation +/// +/// We do roughly the same thing in here as `compiler-rt`, and dynamically look up CoreFoundation +/// utilities for parsing PLists (to avoid having to re-implement that in here, as pulling in a full +/// PList parser into `std` seems costly). +/// +/// If this is found to be undesirable, we _could_ possibly hack it by parsing the PList manually +/// (it seems to use the plain-text "xml1" encoding/format in all versions), but that seems brittle. +fn version_from_plist() -> OSVersion { + // Read `SystemVersion.plist`. Always present on Apple platforms, reading it cannot fail. + let path = root_relative("/System/Library/CoreServices/SystemVersion.plist"); + let plist_buffer = fs::read(&path).unwrap_or_else(|e| panic!("failed reading {path:?}: {e}")); + let cf_handle = CFHandle::new(); + parse_version_from_plist(&cf_handle, &plist_buffer) +} + +/// Parse OS version from the given PList. +/// +/// Split out from [`version_from_plist`] to allow for testing. +fn parse_version_from_plist(cf_handle: &CFHandle, plist_buffer: &[u8]) -> OSVersion { + let plist_data = unsafe { + cf_handle.CFDataCreateWithBytesNoCopy( + kCFAllocatorDefault, + plist_buffer.as_ptr(), + plist_buffer.len() as CFIndex, + cf_handle.kCFAllocatorNull(), + ) + }; + assert!(!plist_data.is_null(), "failed creating CFData"); + let _plist_data_release = Deferred(|| unsafe { cf_handle.CFRelease(plist_data) }); + + let plist = unsafe { + cf_handle.CFPropertyListCreateWithData( + kCFAllocatorDefault, + plist_data, + kCFPropertyListImmutable, + null_mut(), // Don't care about the format of the PList. + null_mut(), // Don't care about the error data. + ) + }; + assert!(!plist.is_null(), "failed reading PList in SystemVersion.plist"); + let _plist_release = Deferred(|| unsafe { cf_handle.CFRelease(plist) }); + + assert_eq!( + unsafe { cf_handle.CFGetTypeID(plist) }, + unsafe { cf_handle.CFDictionaryGetTypeID() }, + "SystemVersion.plist did not contain a dictionary at the top level" + ); + let plist: CFDictionaryRef = plist.cast(); + + // Same logic as in `version_from_sysctl`. + if cfg!(target_os = "ios") { + if let Some(ios_support_version) = + unsafe { string_version_key(cf_handle, plist, c"iOSSupportVersion") } + { + return ios_support_version; + } + + // Force Mac Catalyst to use iOSSupportVersion (do not fall back to ProductVersion). + if cfg!(target_abi = "macabi") { + panic!("expected iOSSupportVersion in SystemVersion.plist"); + } + } + + // On all other platforms, we can find the OS version by simply looking at `ProductVersion`. + unsafe { string_version_key(cf_handle, plist, c"ProductVersion") } + .expect("expected ProductVersion in SystemVersion.plist") +} + +/// Look up a string key in a CFDictionary, and convert it to an [`OSVersion`]. +unsafe fn string_version_key( + cf_handle: &CFHandle, + plist: CFDictionaryRef, + lookup_key: &CStr, +) -> Option<OSVersion> { + let cf_lookup_key = unsafe { + cf_handle.CFStringCreateWithCStringNoCopy( + kCFAllocatorDefault, + lookup_key.as_ptr(), + kCFStringEncodingUTF8, + cf_handle.kCFAllocatorNull(), + ) + }; + assert!(!cf_lookup_key.is_null(), "failed creating CFString"); + let _lookup_key_release = Deferred(|| unsafe { cf_handle.CFRelease(cf_lookup_key) }); + + let value: CFTypeRef = + unsafe { cf_handle.CFDictionaryGetValue(plist, cf_lookup_key) }.cast_mut(); + // `CFDictionaryGetValue` is a "getter", so we should not release, + // the value is held alive internally by the CFDictionary, see: + // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW12 + if value.is_null() { + return None; + } + + assert_eq!( + unsafe { cf_handle.CFGetTypeID(value) }, + unsafe { cf_handle.CFStringGetTypeID() }, + "key in SystemVersion.plist must be a string" + ); + let value: CFStringRef = value.cast(); + + let mut version_str = [0u8; 32]; + let ret = unsafe { + cf_handle.CFStringGetCString( + value, + version_str.as_mut_ptr().cast::<c_char>(), + version_str.len() as CFIndex, + kCFStringEncodingUTF8, + ) + }; + assert_ne!(ret, 0, "failed getting string from CFString"); + + let version_str = + CStr::from_bytes_until_nul(&version_str).expect("failed converting CFString to CStr"); + + Some(parse_os_version(version_str.to_bytes()).unwrap_or_else(|err| { + panic!( + "failed parsing version from PList ({}): {err}", + ByteStr::new(version_str.to_bytes()) + ) + })) +} + +/// Parse an OS version from a bytestring like b"10.1" or b"14.3.7". +fn parse_os_version(version: &[u8]) -> Result<OSVersion, ParseIntError> { + if let Some((major, minor)) = version.split_once(|&b| b == b'.') { + let major = u16::from_ascii(major)?; + if let Some((minor, patch)) = minor.split_once(|&b| b == b'.') { + let minor = u8::from_ascii(minor)?; + let patch = u8::from_ascii(patch)?; + Ok(pack_os_version(major, minor, patch)) + } else { + let minor = u8::from_ascii(minor)?; + Ok(pack_os_version(major, minor, 0)) + } + } else { + let major = u16::from_ascii(version)?; + Ok(pack_os_version(major, 0, 0)) + } +} + +/// Get a path relative to the root directory in which all files for the current env are located. +fn root_relative(path: &str) -> Cow<'_, Path> { + if cfg!(target_abi = "sim") { + let mut root = PathBuf::from(env::var_os("IPHONE_SIMULATOR_ROOT").expect( + "environment variable `IPHONE_SIMULATOR_ROOT` must be set when executing under simulator", + )); + // Convert absolute path to relative path, to make the `.push` work as expected. + root.push(Path::new(path).strip_prefix("/").unwrap()); + root.into() + } else { + Path::new(path).into() + } +} + +struct Deferred<F: FnMut()>(F); + +impl<F: FnMut()> Drop for Deferred<F> { + fn drop(&mut self) { + (self.0)(); + } +} diff --git a/library/std/src/sys/platform_version/darwin/public_extern.rs b/library/std/src/sys/platform_version/darwin/public_extern.rs new file mode 100644 index 00000000000..c0848d94798 --- /dev/null +++ b/library/std/src/sys/platform_version/darwin/public_extern.rs @@ -0,0 +1,156 @@ +//! # Runtime version checking ABI for other compilers. +//! +//! The symbols in this file are useful for us to expose to allow linking code written in the +//! following languages when using their version checking functionality: +//! - Clang's `__builtin_available` macro. +//! - Objective-C's `@available`. +//! - Swift's `#available`, +//! +//! Without Rust exposing these symbols, the user would encounter a linker error when linking to +//! C/Objective-C/Swift libraries using these features. +//! +//! The presence of these symbols is mostly considered a quality-of-implementation detail, and +//! should not be relied upon to be available. The intended effect is that linking with code built +//! with Clang's `__builtin_available` (or similar) will continue to work. For example, we may +//! decide to remove `__isOSVersionAtLeast` if support for Clang 11 (Xcode 11) is dropped. +//! +//! ## Background +//! +//! The original discussion of this feature can be found at: +//! - <https://lists.llvm.org/pipermail/cfe-dev/2016-July/049851.html> +//! - <https://reviews.llvm.org/D27827> +//! - <https://reviews.llvm.org/D30136> +//! +//! And the upstream implementation of these can be found in `compiler-rt`: +//! <https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/compiler-rt/lib/builtins/os_version_check.c> +//! +//! Ideally, these symbols should probably have been a part of Apple's `libSystem.dylib`, both +//! because their implementation is quite complex, using allocation, environment variables, file +//! access and dynamic library loading (and emitting all of this into every binary). +//! +//! The reason why Apple chose to not do that originally is lost to the sands of time, but a good +//! reason would be that implementing it as part of `compiler-rt` allowed them to back-deploy this +//! to older OSes immediately. +//! +//! In Rust's case, while we may provide a feature similar to `@available` in the future, we will +//! probably do so as a macro exposed by `std` (and not as a compiler builtin). So implementing this +//! in `std` makes sense, since then we can implement it using `std` utilities, and we can avoid +//! having `compiler-builtins` depend on `libSystem.dylib`. +//! +//! This does mean that users that attempt to link C/Objective-C/Swift code _and_ use `#![no_std]` +//! in all their crates may get a linker error because these symbols are missing. Using `no_std` is +//! quite uncommon on Apple systems though, so it's probably fine to not support this use-case. +//! +//! The workaround would be to link `libclang_rt.osx.a` or otherwise use Clang's `compiler-rt`. +//! +//! See also discussion in <https://github.com/rust-lang/compiler-builtins/pull/794>. +//! +//! ## Implementation details +//! +//! NOTE: Since macOS 10.15, `libSystem.dylib` _has_ actually provided the undocumented +//! `_availability_version_check` via `libxpc` for doing the version lookup (zippered, which is why +//! it requires a platform parameter to differentiate between macOS and Mac Catalyst), though its +//! usage may be a bit dangerous, see: +//! - <https://reviews.llvm.org/D150397> +//! - <https://github.com/llvm/llvm-project/issues/64227> +//! +//! Besides, we'd need to implement the version lookup via PList to support older versions anyhow, +//! so we might as well use that everywhere (since it can also be optimized more after inlining). + +#![allow(non_snake_case)] + +use super::{current_version, pack_i32_os_version}; + +/// Whether the current platform's OS version is higher than or equal to the given version. +/// +/// The first argument is the _base_ Mach-O platform (i.e. `PLATFORM_MACOS`, `PLATFORM_IOS`, etc., +/// but not `PLATFORM_IOSSIMULATOR` or `PLATFORM_MACCATALYST`) of the invoking binary. +/// +/// Arguments are specified statically by Clang. Inlining with LTO should allow the versions to be +/// combined into a single `u32`, which should make comparisons faster, and should make the +/// `BASE_TARGET_PLATFORM` check a no-op. +// +// SAFETY: The signature is the same as what Clang expects, and we export weakly to allow linking +// both this and `libclang_rt.*.a`, similar to how `compiler-builtins` does it: +// https://github.com/rust-lang/compiler-builtins/blob/0.1.113/src/macros.rs#L494 +// +// NOTE: This symbol has a workaround in the compiler's symbol mangling to avoid mangling it, while +// still not exposing it from non-cdylib (like `#[no_mangle]` would). +#[rustc_std_internal_symbol] +// NOTE: Making this a weak symbol might not be entirely the right solution for this, `compiler_rt` +// doesn't do that, it instead makes the symbol have "hidden" visibility. But since this is placed +// in `libstd`, which might be used as a dylib, we cannot do the same here. +#[linkage = "weak"] +// extern "C" is correct, Clang assumes the function cannot unwind: +// https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/clang/lib/CodeGen/CGObjC.cpp#L3980 +// +// If an error happens in this, we instead abort the process. +pub(super) extern "C" fn __isPlatformVersionAtLeast( + platform: i32, + major: i32, + minor: i32, + subminor: i32, +) -> i32 { + let version = pack_i32_os_version(major, minor, subminor); + + // Mac Catalyst is a technology that allows macOS to run in a different "mode" that closely + // resembles iOS (and has iOS libraries like UIKit available). + // + // (Apple has added a "Designed for iPad" mode later on that allows running iOS apps + // natively, but we don't need to think too much about those, since they link to + // iOS-specific system binaries as well). + // + // To support Mac Catalyst, Apple added the concept of a "zippered" binary, which is a single + // binary that can be run on both macOS and Mac Catalyst (has two `LC_BUILD_VERSION` Mach-O + // commands, one set to `PLATFORM_MACOS` and one to `PLATFORM_MACCATALYST`). + // + // Most system libraries are zippered, which allows re-use across macOS and Mac Catalyst. + // This includes the `libclang_rt.osx.a` shipped with Xcode! This means that `compiler-rt` + // can't statically know whether it's compiled for macOS or Mac Catalyst, and thus this new + // API (which replaces `__isOSVersionAtLeast`) is needed. + // + // In short: + // normal binary calls normal compiler-rt --> `__isOSVersionAtLeast` was enough + // normal binary calls zippered compiler-rt --> `__isPlatformVersionAtLeast` required + // zippered binary calls zippered compiler-rt --> `__isPlatformOrVariantPlatformVersionAtLeast` called + + // FIXME(madsmtm): `rustc` doesn't support zippered binaries yet, see rust-lang/rust#131216. + // But once it does, we need the pre-compiled `std` shipped with rustup to be zippered, and thus + // we also need to handle the `platform` difference here: + // + // if cfg!(target_os = "macos") && platform == 2 /* PLATFORM_IOS */ && cfg!(zippered) { + // return (version.to_u32() <= current_ios_version()) as i32; + // } + // + // `__isPlatformOrVariantPlatformVersionAtLeast` would also need to be implemented. + + // The base Mach-O platform for the current target. + const BASE_TARGET_PLATFORM: i32 = if cfg!(target_os = "macos") { + 1 // PLATFORM_MACOS + } else if cfg!(target_os = "ios") { + 2 // PLATFORM_IOS + } else if cfg!(target_os = "tvos") { + 3 // PLATFORM_TVOS + } else if cfg!(target_os = "watchos") { + 4 // PLATFORM_WATCHOS + } else if cfg!(target_os = "visionos") { + 11 // PLATFORM_VISIONOS + } else { + 0 // PLATFORM_UNKNOWN + }; + debug_assert_eq!( + platform, BASE_TARGET_PLATFORM, + "invalid platform provided to __isPlatformVersionAtLeast", + ); + + (version <= current_version()) as i32 +} + +/// Old entry point for availability. Used when compiling with older Clang versions. +// SAFETY: Same as for `__isPlatformVersionAtLeast`. +#[rustc_std_internal_symbol] +#[linkage = "weak"] +pub(super) extern "C" fn __isOSVersionAtLeast(major: i32, minor: i32, subminor: i32) -> i32 { + let version = pack_i32_os_version(major, minor, subminor); + (version <= current_version()) as i32 +} diff --git a/library/std/src/sys/platform_version/darwin/tests.rs b/library/std/src/sys/platform_version/darwin/tests.rs new file mode 100644 index 00000000000..17b2cc18ec0 --- /dev/null +++ b/library/std/src/sys/platform_version/darwin/tests.rs @@ -0,0 +1,379 @@ +use super::public_extern::*; +use super::*; +use crate::process::Command; + +#[test] +fn test_general_available() { + // Lowest version always available. + assert_eq!(__isOSVersionAtLeast(0, 0, 0), 1); + // This high version never available. + assert_eq!(__isOSVersionAtLeast(9999, 99, 99), 0); +} + +#[test] +fn test_saturating() { + // Higher version than supported by OSVersion -> make sure we saturate. + assert_eq!(__isOSVersionAtLeast(0x10000, 0, 0), 0); +} + +#[test] +#[cfg_attr(not(target_os = "macos"), ignore = "`sw_vers` is only available on host macOS")] +fn compare_against_sw_vers() { + let sw_vers = Command::new("sw_vers").arg("-productVersion").output().unwrap().stdout; + let sw_vers = String::from_utf8(sw_vers).unwrap(); + let mut sw_vers = sw_vers.trim().split('.'); + + let major: i32 = sw_vers.next().unwrap().parse().unwrap(); + let minor: i32 = sw_vers.next().unwrap_or("0").parse().unwrap(); + let subminor: i32 = sw_vers.next().unwrap_or("0").parse().unwrap(); + assert_eq!(sw_vers.count(), 0); + + // Test directly against the lookup + assert_eq!(lookup_version().get(), pack_os_version(major as _, minor as _, subminor as _)); + + // Current version is available + assert_eq!(__isOSVersionAtLeast(major, minor, subminor), 1); + + // One lower is available + assert_eq!(__isOSVersionAtLeast(major, minor, (subminor as u32).saturating_sub(1) as i32), 1); + assert_eq!(__isOSVersionAtLeast(major, (minor as u32).saturating_sub(1) as i32, subminor), 1); + assert_eq!(__isOSVersionAtLeast((major as u32).saturating_sub(1) as i32, minor, subminor), 1); + + // One higher isn't available + assert_eq!(__isOSVersionAtLeast(major, minor, subminor + 1), 0); + assert_eq!(__isOSVersionAtLeast(major, minor + 1, subminor), 0); + assert_eq!(__isOSVersionAtLeast(major + 1, minor, subminor), 0); +} + +#[test] +fn sysctl_same_as_in_plist() { + if let Some(version) = version_from_sysctl() { + assert_eq!(version, version_from_plist()); + } +} + +#[test] +fn lookup_idempotent() { + let version = lookup_version(); + for _ in 0..10 { + assert_eq!(version, lookup_version()); + } +} + +/// Test parsing a bunch of different PLists found in the wild, to ensure that +/// if we decide to parse it without CoreFoundation in the future, that it +/// would continue to work, even on older platforms. +#[test] +fn parse_plist() { + #[track_caller] + fn check( + (major, minor, patch): (u16, u8, u8), + ios_version: Option<(u16, u8, u8)>, + plist: &str, + ) { + let expected = if cfg!(target_os = "ios") { + if let Some((ios_major, ios_minor, ios_patch)) = ios_version { + pack_os_version(ios_major, ios_minor, ios_patch) + } else if cfg!(target_abi = "macabi") { + // Skip checking iOS version on Mac Catalyst. + return; + } else { + // iOS version will be parsed from ProductVersion + pack_os_version(major, minor, patch) + } + } else { + pack_os_version(major, minor, patch) + }; + let cf_handle = CFHandle::new(); + assert_eq!(expected, parse_version_from_plist(&cf_handle, plist.as_bytes())); + } + + // macOS 10.3.0 + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>ProductBuildVersion</key> + <string>7B85</string> + <key>ProductCopyright</key> + <string>Apple Computer, Inc. 1983-2003</string> + <key>ProductName</key> + <string>Mac OS X</string> + <key>ProductUserVisibleVersion</key> + <string>10.3</string> + <key>ProductVersion</key> + <string>10.3</string> + </dict> + </plist> + "#; + check((10, 3, 0), None, plist); + + // macOS 10.7.5 + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>ProductBuildVersion</key> + <string>11G63</string> + <key>ProductCopyright</key> + <string>1983-2012 Apple Inc.</string> + <key>ProductName</key> + <string>Mac OS X</string> + <key>ProductUserVisibleVersion</key> + <string>10.7.5</string> + <key>ProductVersion</key> + <string>10.7.5</string> + </dict> + </plist> + "#; + check((10, 7, 5), None, plist); + + // macOS 14.7.4 + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>BuildID</key> + <string>6A558D8A-E2EA-11EF-A1D3-6222CAA672A8</string> + <key>ProductBuildVersion</key> + <string>23H420</string> + <key>ProductCopyright</key> + <string>1983-2025 Apple Inc.</string> + <key>ProductName</key> + <string>macOS</string> + <key>ProductUserVisibleVersion</key> + <string>14.7.4</string> + <key>ProductVersion</key> + <string>14.7.4</string> + <key>iOSSupportVersion</key> + <string>17.7</string> + </dict> + </plist> + "#; + check((14, 7, 4), Some((17, 7, 0)), plist); + + // SystemVersionCompat.plist on macOS 14.7.4 + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>BuildID</key> + <string>6A558D8A-E2EA-11EF-A1D3-6222CAA672A8</string> + <key>ProductBuildVersion</key> + <string>23H420</string> + <key>ProductCopyright</key> + <string>1983-2025 Apple Inc.</string> + <key>ProductName</key> + <string>Mac OS X</string> + <key>ProductUserVisibleVersion</key> + <string>10.16</string> + <key>ProductVersion</key> + <string>10.16</string> + <key>iOSSupportVersion</key> + <string>17.7</string> + </dict> + </plist> + "#; + check((10, 16, 0), Some((17, 7, 0)), plist); + + // macOS 15.4 Beta 24E5238a + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>BuildID</key> + <string>67A50F62-00DA-11F0-BDB6-F99BB8310D2A</string> + <key>ProductBuildVersion</key> + <string>24E5238a</string> + <key>ProductCopyright</key> + <string>1983-2025 Apple Inc.</string> + <key>ProductName</key> + <string>macOS</string> + <key>ProductUserVisibleVersion</key> + <string>15.4</string> + <key>ProductVersion</key> + <string>15.4</string> + <key>iOSSupportVersion</key> + <string>18.4</string> + </dict> + </plist> + "#; + check((15, 4, 0), Some((18, 4, 0)), plist); + + // iOS Simulator 17.5 + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>BuildID</key> + <string>210B8A2C-09C3-11EF-9DB8-273A64AEFA1C</string> + <key>ProductBuildVersion</key> + <string>21F79</string> + <key>ProductCopyright</key> + <string>1983-2024 Apple Inc.</string> + <key>ProductName</key> + <string>iPhone OS</string> + <key>ProductVersion</key> + <string>17.5</string> + </dict> + </plist> + "#; + check((17, 5, 0), None, plist); + + // visionOS Simulator 2.3 + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>BuildID</key> + <string>57CEFDE6-D079-11EF-837C-8B8C7961D0AC</string> + <key>ProductBuildVersion</key> + <string>22N895</string> + <key>ProductCopyright</key> + <string>1983-2025 Apple Inc.</string> + <key>ProductName</key> + <string>xrOS</string> + <key>ProductVersion</key> + <string>2.3</string> + <key>SystemImageID</key> + <string>D332C7F1-08DF-4DD9-8122-94EF39A1FB92</string> + <key>iOSSupportVersion</key> + <string>18.3</string> + </dict> + </plist> + "#; + check((2, 3, 0), Some((18, 3, 0)), plist); + + // tvOS Simulator 18.2 + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>BuildID</key> + <string>617587B0-B059-11EF-BE70-4380EDE44645</string> + <key>ProductBuildVersion</key> + <string>22K154</string> + <key>ProductCopyright</key> + <string>1983-2024 Apple Inc.</string> + <key>ProductName</key> + <string>Apple TVOS</string> + <key>ProductVersion</key> + <string>18.2</string> + <key>SystemImageID</key> + <string>8BB5A425-33F0-4821-9F93-40E7ED92F4E0</string> + </dict> + </plist> + "#; + check((18, 2, 0), None, plist); + + // watchOS Simulator 11.2 + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>BuildID</key> + <string>BAAE2D54-B122-11EF-BF78-C6C6836B724A</string> + <key>ProductBuildVersion</key> + <string>22S99</string> + <key>ProductCopyright</key> + <string>1983-2024 Apple Inc.</string> + <key>ProductName</key> + <string>Watch OS</string> + <key>ProductVersion</key> + <string>11.2</string> + <key>SystemImageID</key> + <string>79F773E2-2041-43B4-98EE-FAE52402AE95</string> + </dict> + </plist> + "#; + check((11, 2, 0), None, plist); + + // iOS 9.3.6 + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + <key>ProductBuildVersion</key> + <string>13G37</string> + <key>ProductCopyright</key> + <string>1983-2019 Apple Inc.</string> + <key>ProductName</key> + <string>iPhone OS</string> + <key>ProductVersion</key> + <string>9.3.6</string> + </dict> + </plist> + "#; + check((9, 3, 6), None, plist); +} + +#[test] +#[should_panic = "SystemVersion.plist did not contain a dictionary at the top level"] +fn invalid_plist() { + let cf_handle = CFHandle::new(); + let _ = parse_version_from_plist(&cf_handle, b"INVALID"); +} + +#[test] +#[cfg_attr( + target_abi = "macabi", + should_panic = "expected iOSSupportVersion in SystemVersion.plist" +)] +#[cfg_attr( + not(target_abi = "macabi"), + should_panic = "expected ProductVersion in SystemVersion.plist" +)] +fn empty_plist() { + let plist = r#"<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + <plist version="1.0"> + <dict> + </dict> + </plist> + "#; + let cf_handle = CFHandle::new(); + let _ = parse_version_from_plist(&cf_handle, plist.as_bytes()); +} + +#[test] +fn parse_version() { + #[track_caller] + fn check(major: u16, minor: u8, patch: u8, version: &str) { + assert_eq!( + pack_os_version(major, minor, patch), + parse_os_version(version.as_bytes()).unwrap() + ) + } + + check(0, 0, 0, "0"); + check(0, 0, 0, "0.0.0"); + check(1, 0, 0, "1"); + check(1, 2, 0, "1.2"); + check(1, 2, 3, "1.2.3"); + check(9999, 99, 99, "9999.99.99"); + + // Check leading zeroes + check(10, 0, 0, "010"); + check(10, 20, 0, "010.020"); + check(10, 20, 30, "010.020.030"); + check(10000, 100, 100, "000010000.00100.00100"); + + // Too many parts + assert!(parse_os_version(b"1.2.3.4").is_err()); + + // Empty + assert!(parse_os_version(b"").is_err()); + + // Invalid digit + assert!(parse_os_version(b"A.B").is_err()); + + // Missing digits + assert!(parse_os_version(b".").is_err()); + assert!(parse_os_version(b".1").is_err()); + assert!(parse_os_version(b"1.").is_err()); + + // Too large + assert!(parse_os_version(b"100000").is_err()); + assert!(parse_os_version(b"1.1000").is_err()); + assert!(parse_os_version(b"1.1.1000").is_err()); +} diff --git a/library/std/src/sys/platform_version/mod.rs b/library/std/src/sys/platform_version/mod.rs new file mode 100644 index 00000000000..88896c97ea3 --- /dev/null +++ b/library/std/src/sys/platform_version/mod.rs @@ -0,0 +1,13 @@ +//! Runtime lookup of operating system / platform version. +//! +//! Related to [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750), which +//! does version detection at compile-time. +//! +//! See also the `os_info` crate. + +#[cfg(target_vendor = "apple")] +mod darwin; + +// In the future, we could expand this module with: +// - `RtlGetVersion` on Windows. +// - `__system_property_get` on Android. diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs index a7fbae86099..1e0eec07b50 100644 --- a/library/std/src/sys/random/mod.rs +++ b/library/std/src/sys/random/mod.rs @@ -86,9 +86,13 @@ cfg_select! { mod vxworks; pub use vxworks::fill_bytes; } - target_os = "wasi" => { - mod wasi; - pub use wasi::fill_bytes; + all(target_os = "wasi", target_env = "p1") => { + mod wasip1; + pub use wasip1::fill_bytes; + } + all(target_os = "wasi", target_env = "p2") => { + mod wasip2; + pub use wasip2::{fill_bytes, hashmap_random_keys}; } target_os = "zkvm" => { mod zkvm; @@ -110,6 +114,7 @@ cfg_select! { target_os = "linux", target_os = "android", all(target_family = "wasm", target_os = "unknown"), + all(target_os = "wasi", target_env = "p2"), target_os = "xous", )))] pub fn hashmap_random_keys() -> (u64, u64) { diff --git a/library/std/src/sys/random/wasi.rs b/library/std/src/sys/random/wasip1.rs index d41da3751fc..d41da3751fc 100644 --- a/library/std/src/sys/random/wasi.rs +++ b/library/std/src/sys/random/wasip1.rs diff --git a/library/std/src/sys/random/wasip2.rs b/library/std/src/sys/random/wasip2.rs new file mode 100644 index 00000000000..a67c8a6428d --- /dev/null +++ b/library/std/src/sys/random/wasip2.rs @@ -0,0 +1,9 @@ +pub fn fill_bytes(bytes: &mut [u8]) { + bytes.copy_from_slice(&wasip2::random::random::get_random_bytes( + u64::try_from(bytes.len()).unwrap(), + )); +} + +pub fn hashmap_random_keys() -> (u64, u64) { + wasip2::random::insecure_seed::insecure_seed() +} diff --git a/library/std/src/sys/stdio/mod.rs b/library/std/src/sys/stdio/mod.rs index 314f226f07b..7436e4d9de4 100644 --- a/library/std/src/sys/stdio/mod.rs +++ b/library/std/src/sys/stdio/mod.rs @@ -29,9 +29,13 @@ cfg_select! { mod uefi; pub use uefi::*; } - target_os = "wasi" => { - mod wasi; - pub use wasi::*; + all(target_os = "wasi", target_env = "p1") => { + mod wasip1; + pub use wasip1::*; + } + all(target_os = "wasi", target_env = "p2") => { + mod wasip2; + pub use wasip2::*; } target_os = "xous" => { mod xous; diff --git a/library/std/src/sys/stdio/wasi.rs b/library/std/src/sys/stdio/wasip1.rs index b70efd026f9..b70efd026f9 100644 --- a/library/std/src/sys/stdio/wasi.rs +++ b/library/std/src/sys/stdio/wasip1.rs diff --git a/library/std/src/sys/stdio/wasip2.rs b/library/std/src/sys/stdio/wasip2.rs new file mode 100644 index 00000000000..1fcb49a083d --- /dev/null +++ b/library/std/src/sys/stdio/wasip2.rs @@ -0,0 +1,120 @@ +use wasip2::cli; +use wasip2::io::streams::{Error, InputStream, OutputStream, StreamError}; + +use crate::io::{self, BorrowedBuf, BorrowedCursor}; + +pub struct Stdin(Option<InputStream>); +pub struct Stdout(Option<OutputStream>); +pub struct Stderr(Option<OutputStream>); + +fn error_to_io(err: Error) -> io::Error { + // There exists a function in `wasi:filesystem` to optionally acquire an + // error code from an error, but the streams in use in this module are + // exclusively used with stdio meaning that a filesystem error is not + // possible here. + // + // In lieu of an error code, which WASIp2 does not specify, this instead + // carries along the `to_debug_string` implementation that the host + // supplies. If this becomes too expensive in the future this could also + // become `io::Error::from_raw_os_error(libc::EIO)` or similar. + io::Error::new(io::ErrorKind::Other, err.to_debug_string()) +} + +impl Stdin { + pub const fn new() -> Stdin { + Stdin(None) + } + + fn stream(&mut self) -> &InputStream { + self.0.get_or_insert_with(cli::stdin::get_stdin) + } +} + +impl io::Read for Stdin { + fn read(&mut self, data: &mut [u8]) -> io::Result<usize> { + let mut buf = BorrowedBuf::from(data); + self.read_buf(buf.unfilled())?; + Ok(buf.len()) + } + + fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { + match self.stream().blocking_read(u64::try_from(buf.capacity()).unwrap()) { + Ok(result) => { + buf.append(&result); + Ok(()) + } + Err(StreamError::Closed) => Ok(()), + Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)), + } + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout(None) + } + + fn stream(&mut self) -> &OutputStream { + self.0.get_or_insert_with(cli::stdout::get_stdout) + } +} + +fn write(stream: &OutputStream, buf: &[u8]) -> io::Result<usize> { + // WASIp2's `blocking_write_and_flush` function is defined as accepting no + // more than 4096 bytes. Larger writes can be issued by manually using + // `check_write`, `write`, and `blocking_flush`, but for now just go ahead + // and use `blocking_write_and_flush` and report a short write and let a + // higher level loop over the result. + const MAX: usize = 4096; + let buf = &buf[..buf.len().min(MAX)]; + match stream.blocking_write_and_flush(buf) { + Ok(()) => Ok(buf.len()), + Err(StreamError::Closed) => Ok(0), + Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)), + } +} + +impl io::Write for Stdout { + fn write(&mut self, data: &[u8]) -> io::Result<usize> { + write(self.stream(), data) + } + + fn flush(&mut self) -> io::Result<()> { + // Note that `OutputStream` has a `flush` function but for stdio all + // writes are accompanied with a flush which means that this flush + // doesn't need to do anything. + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr(None) + } + + fn stream(&mut self) -> &OutputStream { + self.0.get_or_insert_with(cli::stderr::get_stderr) + } +} + +impl io::Write for Stderr { + fn write(&mut self, data: &[u8]) -> io::Result<usize> { + write(self.stream(), data) + } + + fn flush(&mut self) -> io::Result<()> { + // See `Stdout::flush` for why this is a noop. + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; + +pub fn is_ebadf(_err: &io::Error) -> bool { + // WASIp2 stdio streams are always available so ebadf never shows up. + false +} + +pub fn panic_output() -> Option<impl io::Write> { + Some(Stderr::new()) +} diff --git a/library/std/src/sys/sync/once/queue.rs b/library/std/src/sys/sync/once/queue.rs index 49e15d65f25..17d99cdb385 100644 --- a/library/std/src/sys/sync/once/queue.rs +++ b/library/std/src/sys/sync/once/queue.rs @@ -276,7 +276,9 @@ fn wait( // If the managing thread happens to signal and unpark us before we // can park ourselves, the result could be this thread never gets // unparked. Luckily `park` comes with the guarantee that if it got - // an `unpark` just before on an unparked thread it does not park. + // an `unpark` just before on an unparked thread it does not park. Crucially, we know + // the `unpark` must have happened between the `compare_exchange_weak` above and here, + // and there's no other `park` in that code that could steal our token. // SAFETY: we retrieved this handle on the current thread above. unsafe { node.thread.park() } } diff --git a/library/std/src/sys/pal/hermit/thread.rs b/library/std/src/sys/thread/hermit.rs index cc4734b6819..4d9f3b114c2 100644 --- a/library/std/src/sys/pal/hermit/thread.rs +++ b/library/std/src/sys/thread/hermit.rs @@ -1,10 +1,5 @@ -#![allow(dead_code)] - -use super::hermit_abi; -use crate::ffi::CStr; -use crate::mem::ManuallyDrop; use crate::num::NonZero; -use crate::time::{Duration, Instant}; +use crate::time::Duration; use crate::{io, ptr}; pub type Tid = hermit_abi::Tid; @@ -68,57 +63,30 @@ impl Thread { } } - #[inline] - pub fn yield_now() { - unsafe { - hermit_abi::yield_now(); - } - } - - #[inline] - pub fn set_name(_name: &CStr) { - // nope - } - - #[inline] - pub fn sleep(dur: Duration) { - let micros = dur.as_micros() + if dur.subsec_nanos() % 1_000 > 0 { 1 } else { 0 }; - let micros = u64::try_from(micros).unwrap_or(u64::MAX); - - unsafe { - hermit_abi::usleep(micros); - } - } - - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - pub fn join(self) { unsafe { let _ = hermit_abi::join(self.tid); } } +} - #[inline] - pub fn id(&self) -> Tid { - self.tid - } - - #[inline] - pub fn into_id(self) -> Tid { - ManuallyDrop::new(self).tid - } +pub fn available_parallelism() -> io::Result<NonZero<usize>> { + unsafe { Ok(NonZero::new_unchecked(hermit_abi::available_parallelism())) } } -pub(crate) fn current_os_id() -> Option<u64> { - None +#[inline] +pub fn sleep(dur: Duration) { + let micros = dur.as_micros() + if dur.subsec_nanos() % 1_000 > 0 { 1 } else { 0 }; + let micros = u64::try_from(micros).unwrap_or(u64::MAX); + + unsafe { + hermit_abi::usleep(micros); + } } -pub fn available_parallelism() -> io::Result<NonZero<usize>> { - unsafe { Ok(NonZero::new_unchecked(hermit_abi::available_parallelism())) } +#[inline] +pub fn yield_now() { + unsafe { + hermit_abi::yield_now(); + } } diff --git a/library/std/src/sys/thread/mod.rs b/library/std/src/sys/thread/mod.rs new file mode 100644 index 00000000000..6bb7fc1a20e --- /dev/null +++ b/library/std/src/sys/thread/mod.rs @@ -0,0 +1,152 @@ +cfg_select! { + target_os = "hermit" => { + mod hermit; + pub use hermit::{Thread, available_parallelism, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; + #[expect(dead_code)] + mod unsupported; + pub use unsupported::{current_os_id, set_name}; + } + all(target_vendor = "fortanix", target_env = "sgx") => { + mod sgx; + pub use sgx::{Thread, current_os_id, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; + + // SGX should protect in-enclave data from outside attackers, so there + // must not be any data leakage to the OS, particularly no 1-1 mapping + // between SGX thread names and OS thread names. Hence `set_name` is + // intentionally a no-op. + // + // Note that the internally visible SGX thread name is already provided + // by the platform-agnostic Rust thread code. This can be observed in + // the [`std::thread::tests::test_named_thread`] test, which succeeds + // as-is with the SGX target. + #[expect(dead_code)] + mod unsupported; + pub use unsupported::{available_parallelism, set_name}; + } + target_os = "solid_asp3" => { + mod solid; + pub use solid::{Thread, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; + #[expect(dead_code)] + mod unsupported; + pub use unsupported::{available_parallelism, current_os_id, set_name}; + } + target_os = "teeos" => { + mod teeos; + pub use teeos::{Thread, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; + #[expect(dead_code)] + mod unsupported; + pub use unsupported::{available_parallelism, current_os_id, set_name}; + } + target_os = "uefi" => { + mod uefi; + pub use uefi::{available_parallelism, sleep}; + #[expect(dead_code)] + mod unsupported; + pub use unsupported::{Thread, current_os_id, set_name, yield_now, DEFAULT_MIN_STACK_SIZE}; + } + target_family = "unix" => { + mod unix; + pub use unix::{Thread, available_parallelism, current_os_id, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; + #[cfg(not(any( + target_env = "newlib", + target_os = "l4re", + target_os = "emscripten", + target_os = "redox", + target_os = "hurd", + target_os = "aix", + )))] + pub use unix::set_name; + #[cfg(any( + target_os = "freebsd", + target_os = "netbsd", + target_os = "linux", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "dragonfly", + target_os = "hurd", + target_os = "fuchsia", + target_os = "vxworks", + ))] + pub use unix::sleep_until; + #[expect(dead_code)] + mod unsupported; + #[cfg(any( + target_env = "newlib", + target_os = "l4re", + target_os = "emscripten", + target_os = "redox", + target_os = "hurd", + target_os = "aix", + ))] + pub use unsupported::set_name; + } + all(target_os = "wasi", target_env = "p1") => { + mod wasip1; + pub use wasip1::{DEFAULT_MIN_STACK_SIZE, sleep, yield_now}; + #[cfg(target_feature = "atomics")] + pub use wasip1::{Thread, available_parallelism}; + #[expect(dead_code)] + mod unsupported; + pub use unsupported::{current_os_id, set_name}; + #[cfg(not(target_feature = "atomics"))] + pub use unsupported::{Thread, available_parallelism}; + } + all(target_os = "wasi", target_env = "p2") => { + mod wasip2; + pub use wasip2::{sleep, sleep_until}; + #[expect(dead_code)] + mod unsupported; + // Note that unlike WASIp1 even if the wasm `atomics` feature is enabled + // there is no support for threads, not even experimentally, not even in + // wasi-libc. Thus this is unconditionally unsupported. + pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, yield_now, DEFAULT_MIN_STACK_SIZE}; + } + all(target_family = "wasm", target_feature = "atomics") => { + mod wasm; + pub use wasm::sleep; + + #[expect(dead_code)] + mod unsupported; + pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, yield_now, DEFAULT_MIN_STACK_SIZE}; + } + target_os = "windows" => { + mod windows; + pub use windows::{Thread, available_parallelism, current_os_id, set_name, set_name_wide, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; + } + target_os = "xous" => { + mod xous; + pub use xous::{Thread, available_parallelism, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; + + #[expect(dead_code)] + mod unsupported; + pub use unsupported::{current_os_id, set_name}; + } + _ => { + mod unsupported; + pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; + } +} + +#[cfg(not(any( + target_os = "freebsd", + target_os = "netbsd", + target_os = "linux", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "dragonfly", + target_os = "hurd", + target_os = "fuchsia", + target_os = "vxworks", + all(target_os = "wasi", target_env = "p2"), +)))] +pub fn sleep_until(deadline: crate::time::Instant) { + use crate::time::Instant; + + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + sleep(delay); + } +} diff --git a/library/std/src/sys/pal/sgx/thread.rs b/library/std/src/sys/thread/sgx.rs index 1f613badcd7..f20ef7d86b9 100644 --- a/library/std/src/sys/pal/sgx/thread.rs +++ b/library/std/src/sys/thread/sgx.rs @@ -1,11 +1,8 @@ #![cfg_attr(test, allow(dead_code))] // why is this necessary? -use super::abi::{thread, usercalls}; -use super::unsupported; -use crate::ffi::CStr; use crate::io; -use crate::num::NonZero; -use crate::time::{Duration, Instant}; +use crate::sys::pal::abi::{thread, usercalls}; +use crate::time::Duration; pub struct Thread(task_queue::JoinHandle); @@ -108,51 +105,27 @@ impl Thread { Ok(Thread(handle)) } - pub(super) fn entry() -> JoinNotifier { + pub(crate) fn entry() -> JoinNotifier { let mut pending_tasks = task_queue::lock(); let task = rtunwrap!(Some, pending_tasks.pop()); drop(pending_tasks); // make sure to not hold the task queue lock longer than necessary task.run() } - pub fn yield_now() { - let wait_error = rtunwrap!(Err, usercalls::wait(0, usercalls::raw::WAIT_NO)); - rtassert!(wait_error.kind() == io::ErrorKind::WouldBlock); - } - - /// SGX should protect in-enclave data from the outside (attacker), - /// so there should be no data leakage to the OS, - /// and therefore also no 1-1 mapping between SGX thread names and OS thread names. - /// - /// This is why the method is intentionally No-Op. - pub fn set_name(_name: &CStr) { - // Note that the internally visible SGX thread name is already provided - // by the platform-agnostic (target-agnostic) Rust thread code. - // This can be observed in the [`std::thread::tests::test_named_thread`] test, - // which succeeds as-is with the SGX target. - } - - pub fn sleep(dur: Duration) { - usercalls::wait_timeout(0, dur, || true); - } - - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - pub fn join(self) { self.0.wait(); } } -pub(crate) fn current_os_id() -> Option<u64> { +pub fn current_os_id() -> Option<u64> { Some(thread::current().addr().get() as u64) } -pub fn available_parallelism() -> io::Result<NonZero<usize>> { - unsupported() +pub fn sleep(dur: Duration) { + usercalls::wait_timeout(0, dur, || true); +} + +pub fn yield_now() { + let wait_error = rtunwrap!(Err, usercalls::wait(0, usercalls::raw::WAIT_NO)); + rtassert!(wait_error.kind() == io::ErrorKind::WouldBlock); } diff --git a/library/std/src/sys/pal/itron/thread.rs b/library/std/src/sys/thread/solid.rs index 4e14cb3cbca..46a84faa802 100644 --- a/library/std/src/sys/pal/itron/thread.rs +++ b/library/std/src/sys/thread/solid.rs @@ -1,16 +1,14 @@ //! Thread implementation backed by μITRON tasks. Assumes `acre_tsk` and //! `exd_tsk` are available. -use super::error::{ItronError, expect_success, expect_success_aborting}; -use super::time::dur2reltims; -use super::{abi, task}; use crate::cell::UnsafeCell; -use crate::ffi::CStr; use crate::mem::ManuallyDrop; -use crate::num::NonZero; use crate::ptr::NonNull; use crate::sync::atomic::{Atomic, AtomicUsize, Ordering}; -use crate::time::{Duration, Instant}; +use crate::sys::pal::itron::error::{ItronError, expect_success, expect_success_aborting}; +use crate::sys::pal::itron::time::dur2reltims; +use crate::sys::pal::itron::{abi, task}; +use crate::time::Duration; use crate::{hint, io}; pub struct Thread { @@ -195,28 +193,6 @@ impl Thread { Ok(Self { p_inner, task: new_task }) } - pub fn yield_now() { - expect_success(unsafe { abi::rot_rdq(abi::TPRI_SELF) }, &"rot_rdq"); - } - - pub fn set_name(_name: &CStr) { - // nope - } - - pub fn sleep(dur: Duration) { - for timeout in dur2reltims(dur) { - expect_success(unsafe { abi::dly_tsk(timeout) }, &"dly_tsk"); - } - } - - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - pub fn join(self) { // Safety: `ThreadInner` is alive at this point let inner = unsafe { self.p_inner.as_ref() }; @@ -361,10 +337,12 @@ unsafe fn terminate_and_delete_current_task() -> ! { unsafe { crate::hint::unreachable_unchecked() }; } -pub(crate) fn current_os_id() -> Option<u64> { - None +pub fn yield_now() { + expect_success(unsafe { abi::rot_rdq(abi::TPRI_SELF) }, &"rot_rdq"); } -pub fn available_parallelism() -> io::Result<NonZero<usize>> { - super::unsupported() +pub fn sleep(dur: Duration) { + for timeout in dur2reltims(dur) { + expect_success(unsafe { abi::dly_tsk(timeout) }, &"dly_tsk"); + } } diff --git a/library/std/src/sys/pal/teeos/thread.rs b/library/std/src/sys/thread/teeos.rs index 1812d11e692..cad100395c9 100644 --- a/library/std/src/sys/pal/teeos/thread.rs +++ b/library/std/src/sys/thread/teeos.rs @@ -1,12 +1,18 @@ -use crate::ffi::CStr; use crate::mem::{self, ManuallyDrop}; -use crate::num::NonZero; use crate::sys::os; -use crate::time::{Duration, Instant}; +use crate::time::Duration; use crate::{cmp, io, ptr}; pub const DEFAULT_MIN_STACK_SIZE: usize = 8 * 1024; +unsafe extern "C" { + safe fn TEE_Wait(timeout: u32) -> u32; +} + +fn min_stack_size(_: *const libc::pthread_attr_t) -> usize { + libc::PTHREAD_STACK_MIN.try_into().expect("Infallible") +} + pub struct Thread { id: libc::pthread_t, } @@ -16,10 +22,6 @@ pub struct Thread { unsafe impl Send for Thread {} unsafe impl Sync for Thread {} -unsafe extern "C" { - pub fn TEE_Wait(timeout: u32) -> u32; -} - impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements pub unsafe fn new( @@ -74,7 +76,7 @@ impl Thread { } else { // The new thread will start running earliest after the next yield. // We add a yield here, so that the user does not have to. - Thread::yield_now(); + yield_now(); Ok(Thread { id: native }) }; @@ -91,36 +93,6 @@ impl Thread { } } - pub fn yield_now() { - let ret = unsafe { libc::sched_yield() }; - debug_assert_eq!(ret, 0); - } - - /// This does not do anything on teeos - pub fn set_name(_name: &CStr) { - // Both pthread_setname_np and prctl are not available to the TA, - // so we can't implement this currently. If the need arises please - // contact the teeos rustzone team. - } - - /// only main thread could wait for sometime in teeos - pub fn sleep(dur: Duration) { - let sleep_millis = dur.as_millis(); - let final_sleep: u32 = - if sleep_millis >= u32::MAX as u128 { u32::MAX } else { sleep_millis as u32 }; - unsafe { - let _ = TEE_Wait(final_sleep); - } - } - - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - /// must join, because no pthread_detach supported pub fn join(self) { let id = self.into_id(); @@ -128,10 +100,6 @@ impl Thread { 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 { ManuallyDrop::new(self).id } @@ -144,16 +112,15 @@ impl Drop for Thread { } } -pub(crate) fn current_os_id() -> Option<u64> { - None -} - -// Note: Both `sched_getaffinity` and `sysconf` are available but not functional on -// teeos, so this function always returns an Error! -pub fn available_parallelism() -> io::Result<NonZero<usize>> { - Err(io::Error::UNKNOWN_THREAD_COUNT) +pub fn yield_now() { + let ret = unsafe { libc::sched_yield() }; + debug_assert_eq!(ret, 0); } -fn min_stack_size(_: *const libc::pthread_attr_t) -> usize { - libc::PTHREAD_STACK_MIN.try_into().expect("Infallible") +/// only main thread could wait for sometime in teeos +pub fn sleep(dur: Duration) { + let sleep_millis = dur.as_millis(); + let final_sleep: u32 = + if sleep_millis >= u32::MAX as u128 { u32::MAX } else { sleep_millis as u32 }; + TEE_Wait(final_sleep); } diff --git a/library/std/src/sys/thread/uefi.rs b/library/std/src/sys/thread/uefi.rs new file mode 100644 index 00000000000..94f67d7ace2 --- /dev/null +++ b/library/std/src/sys/thread/uefi.rs @@ -0,0 +1,25 @@ +use crate::io; +use crate::num::NonZero; +use crate::ptr::NonNull; +use crate::time::Duration; + +pub fn available_parallelism() -> io::Result<NonZero<usize>> { + // UEFI is single threaded + Ok(NonZero::new(1).unwrap()) +} + +pub fn sleep(dur: Duration) { + let boot_services: NonNull<r_efi::efi::BootServices> = + crate::os::uefi::env::boot_services().expect("can't sleep").cast(); + let mut dur_ms = dur.as_micros(); + // ceil up to the nearest microsecond + if dur.subsec_nanos() % 1000 > 0 { + dur_ms += 1; + } + + while dur_ms > 0 { + let ms = crate::cmp::min(dur_ms, usize::MAX as u128); + let _ = unsafe { ((*boot_services.as_ptr()).stall)(ms as usize) }; + dur_ms -= ms; + } +} diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/thread/unix.rs index 3389b8c0c8a..2d2c4f90212 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/thread/unix.rs @@ -1,3 +1,11 @@ +#[cfg(not(any( + target_env = "newlib", + target_os = "l4re", + target_os = "emscripten", + target_os = "redox", + target_os = "hurd", + target_os = "aix", +)))] use crate::ffi::CStr; use crate::mem::{self, ManuallyDrop}; use crate::num::NonZero; @@ -6,7 +14,7 @@ use crate::sys::weak::dlsym; #[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto",))] use crate::sys::weak::weak; use crate::sys::{os, stack_overflow}; -use crate::time::{Duration, Instant}; +use crate::time::Duration; use crate::{cmp, io, ptr}; #[cfg(not(any( target_os = "l4re", @@ -121,273 +129,6 @@ impl Thread { } } - 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 { - let res = libc::prctl( - PR_SET_NAME, - name.as_ptr(), - 0 as libc::c_ulong, - 0 as libc::c_ulong, - 0 as libc::c_ulong, - ); - // 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 = "linux", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "nuttx", - target_os = "cygwin" - ))] - pub fn set_name(name: &CStr) { - unsafe { - cfg_select! { - any(target_os = "linux", target_os = "cygwin") => { - // Linux and Cygwin limits the allowed length of the name. - const TASK_COMM_LEN: usize = 16; - let name = truncate_cstr::<{ TASK_COMM_LEN }>(name); - } - _ => { - // FreeBSD, DragonFly BSD and NuttX do not enforce length limits. - } - }; - // Available since glibc 2.12, musl 1.1.16, and uClibc 1.0.20 for Linux, - // FreeBSD 12.2 and 13.0, and DragonFly BSD 6.0. - 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(target_os = "openbsd")] - pub fn set_name(name: &CStr) { - unsafe { - libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr()); - } - } - - #[cfg(target_vendor = "apple")] - 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( - thread: libc::pthread_t, - name: *const libc::c_char, - ) -> libc::c_int; - ); - - if let Some(f) = pthread_setname_np.get() { - #[cfg(target_os = "nto")] - const THREAD_NAME_MAX: usize = libc::_NTO_THREAD_NAME_MAX as usize; - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - const THREAD_NAME_MAX: usize = 32; - - let name = truncate_cstr::<{ THREAD_NAME_MAX }>(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 super::fuchsia::*; - 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(target_os = "vxworks")] - pub fn set_name(name: &CStr) { - let mut name = truncate_cstr::<{ (libc::VX_TASK_RENAME_LENGTH - 1) as usize }>(name); - let res = unsafe { libc::taskNameSet(libc::taskIdSelf(), name.as_mut_ptr()) }; - debug_assert_eq!(res, libc::OK); - } - - #[cfg(any( - target_env = "newlib", - target_os = "l4re", - target_os = "emscripten", - target_os = "redox", - target_os = "hurd", - target_os = "aix", - ))] - pub fn set_name(_name: &CStr) { - // Newlib and Emscripten 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 = &raw mut ts; - 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) { - // ESP-IDF does not have `nanosleep`, so we use `usleep` instead. - // As per the documentation of `usleep`, it is expected to support - // sleep times as big as at least up to 1 second. - // - // ESP-IDF does support almost up to `u32::MAX`, but due to a potential integer overflow in its - // `usleep` implementation - // (https://github.com/espressif/esp-idf/blob/d7ca8b94c852052e3bc33292287ef4dd62c9eeb1/components/newlib/time.c#L210), - // we limit the sleep time to the maximum one that would not cause the underlying `usleep` implementation to overflow - // (`portTICK_PERIOD_MS` can be anything between 1 to 1000, and is 10 by default). - const MAX_MICROS: u32 = u32::MAX - 1_000_000 - 1; - - // Add any nanoseconds smaller than a microsecond as an extra microsecond - // so as to comply with the `std::thread::sleep` contract which mandates - // implementations to sleep for _at least_ the provided `dur`. - // We can't overflow `micros` as it is a `u128`, while `Duration` is a pair of - // (`u64` secs, `u32` nanos), where the nanos are strictly smaller than 1 second - // (i.e. < 1_000_000_000) - let mut micros = dur.as_micros() + if dur.subsec_nanos() % 1_000 > 0 { 1 } else { 0 }; - - while micros > 0 { - let st = if micros > MAX_MICROS as u128 { MAX_MICROS } else { micros as u32 }; - unsafe { - libc::usleep(st); - } - - micros -= st as u128; - } - } - - // Any unix that has clock_nanosleep - // If this list changes update the MIRI chock_nanosleep shim - #[cfg(any( - target_os = "freebsd", - target_os = "netbsd", - target_os = "linux", - target_os = "android", - target_os = "solaris", - target_os = "illumos", - target_os = "dragonfly", - target_os = "hurd", - target_os = "fuchsia", - target_os = "vxworks", - ))] - pub fn sleep_until(deadline: Instant) { - let Some(ts) = deadline.into_inner().into_timespec().to_timespec() else { - // The deadline is further in the future then can be passed to - // clock_nanosleep. We have to use Self::sleep instead. This might - // happen on 32 bit platforms, especially closer to 2038. - let now = Instant::now(); - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - return; - }; - - unsafe { - // When we get interrupted (res = EINTR) call clock_nanosleep again - loop { - let res = libc::clock_nanosleep( - super::time::Instant::CLOCK_ID, - libc::TIMER_ABSTIME, - &ts, - core::ptr::null_mut(), // not required with TIMER_ABSTIME - ); - - if res == 0 { - break; - } else { - assert_eq!( - res, - libc::EINTR, - "timespec is in range, - clockid is valid and kernel should support it" - ); - } - } - } - } - - // Any unix that does not have clock_nanosleep - #[cfg(not(any( - target_os = "freebsd", - target_os = "netbsd", - target_os = "linux", - target_os = "android", - target_os = "solaris", - target_os = "illumos", - target_os = "dragonfly", - target_os = "hurd", - target_os = "fuchsia", - target_os = "vxworks", - )))] - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - pub fn join(self) { let id = self.into_id(); let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; @@ -410,84 +151,6 @@ impl Drop for Thread { } } -pub(crate) fn current_os_id() -> Option<u64> { - // Most Unix platforms have a way to query an integer ID of the current thread, all with - // slightly different spellings. - // - // The OS thread ID is used rather than `pthread_self` so as to match what will be displayed - // for process inspection (debuggers, trace, `top`, etc.). - cfg_select! { - // Most platforms have a function returning a `pid_t` or int, which is an `i32`. - any(target_os = "android", target_os = "linux") => { - use crate::sys::weak::syscall; - - // `libc::gettid` is only available on glibc 2.30+, but the syscall is available - // since Linux 2.4.11. - syscall!(fn gettid() -> libc::pid_t;); - - // SAFETY: FFI call with no preconditions. - let id: libc::pid_t = unsafe { gettid() }; - Some(id as u64) - } - target_os = "nto" => { - // SAFETY: FFI call with no preconditions. - let id: libc::pid_t = unsafe { libc::gettid() }; - Some(id as u64) - } - target_os = "openbsd" => { - // SAFETY: FFI call with no preconditions. - let id: libc::pid_t = unsafe { libc::getthrid() }; - Some(id as u64) - } - target_os = "freebsd" => { - // SAFETY: FFI call with no preconditions. - let id: libc::c_int = unsafe { libc::pthread_getthreadid_np() }; - Some(id as u64) - } - target_os = "netbsd" => { - // SAFETY: FFI call with no preconditions. - let id: libc::lwpid_t = unsafe { libc::_lwp_self() }; - Some(id as u64) - } - any(target_os = "illumos", target_os = "solaris") => { - // On Illumos and Solaris, the `pthread_t` is the same as the OS thread ID. - // SAFETY: FFI call with no preconditions. - let id: libc::pthread_t = unsafe { libc::pthread_self() }; - Some(id as u64) - } - target_vendor = "apple" => { - // Apple allows querying arbitrary thread IDs, `thread=NULL` queries the current thread. - let mut id = 0u64; - // SAFETY: `thread_id` is a valid pointer, no other preconditions. - let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) }; - if status == 0 { - Some(id) - } else { - None - } - } - // Other platforms don't have an OS thread ID or don't have a way to access it. - _ => None, - } -} - -#[cfg(any( - target_os = "linux", - target_os = "nto", - target_os = "solaris", - target_os = "illumos", - target_os = "vxworks", - target_os = "cygwin", - target_vendor = "apple", -))] -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<NonZero<usize>> { cfg_select! { any( @@ -668,6 +331,318 @@ pub fn available_parallelism() -> io::Result<NonZero<usize>> { } } +pub fn current_os_id() -> Option<u64> { + // Most Unix platforms have a way to query an integer ID of the current thread, all with + // slightly different spellings. + // + // The OS thread ID is used rather than `pthread_self` so as to match what will be displayed + // for process inspection (debuggers, trace, `top`, etc.). + cfg_select! { + // Most platforms have a function returning a `pid_t` or int, which is an `i32`. + any(target_os = "android", target_os = "linux") => { + use crate::sys::pal::weak::syscall; + + // `libc::gettid` is only available on glibc 2.30+, but the syscall is available + // since Linux 2.4.11. + syscall!(fn gettid() -> libc::pid_t;); + + // SAFETY: FFI call with no preconditions. + let id: libc::pid_t = unsafe { gettid() }; + Some(id as u64) + } + target_os = "nto" => { + // SAFETY: FFI call with no preconditions. + let id: libc::pid_t = unsafe { libc::gettid() }; + Some(id as u64) + } + target_os = "openbsd" => { + // SAFETY: FFI call with no preconditions. + let id: libc::pid_t = unsafe { libc::getthrid() }; + Some(id as u64) + } + target_os = "freebsd" => { + // SAFETY: FFI call with no preconditions. + let id: libc::c_int = unsafe { libc::pthread_getthreadid_np() }; + Some(id as u64) + } + target_os = "netbsd" => { + // SAFETY: FFI call with no preconditions. + let id: libc::lwpid_t = unsafe { libc::_lwp_self() }; + Some(id as u64) + } + any(target_os = "illumos", target_os = "solaris") => { + // On Illumos and Solaris, the `pthread_t` is the same as the OS thread ID. + // SAFETY: FFI call with no preconditions. + let id: libc::pthread_t = unsafe { libc::pthread_self() }; + Some(id as u64) + } + target_vendor = "apple" => { + // Apple allows querying arbitrary thread IDs, `thread=NULL` queries the current thread. + let mut id = 0u64; + // SAFETY: `thread_id` is a valid pointer, no other preconditions. + let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) }; + if status == 0 { + Some(id) + } else { + None + } + } + // Other platforms don't have an OS thread ID or don't have a way to access it. + _ => None, + } +} + +#[cfg(any( + target_os = "linux", + target_os = "nto", + target_os = "solaris", + target_os = "illumos", + target_os = "vxworks", + target_os = "cygwin", + target_vendor = "apple", +))] +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 +} + +#[cfg(target_os = "android")] +pub fn set_name(name: &CStr) { + const PR_SET_NAME: libc::c_int = 15; + unsafe { + let res = libc::prctl( + PR_SET_NAME, + name.as_ptr(), + 0 as libc::c_ulong, + 0 as libc::c_ulong, + 0 as libc::c_ulong, + ); + // 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 = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "nuttx", + target_os = "cygwin" +))] +pub fn set_name(name: &CStr) { + unsafe { + cfg_select! { + any(target_os = "linux", target_os = "cygwin") => { + // Linux and Cygwin limits the allowed length of the name. + const TASK_COMM_LEN: usize = 16; + let name = truncate_cstr::<{ TASK_COMM_LEN }>(name); + } + _ => { + // FreeBSD, DragonFly BSD and NuttX do not enforce length limits. + } + }; + // Available since glibc 2.12, musl 1.1.16, and uClibc 1.0.20 for Linux, + // FreeBSD 12.2 and 13.0, and DragonFly BSD 6.0. + 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(target_os = "openbsd")] +pub fn set_name(name: &CStr) { + unsafe { + libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr()); + } +} + +#[cfg(target_vendor = "apple")] +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(thread: libc::pthread_t, name: *const libc::c_char) -> libc::c_int; + ); + + if let Some(f) = pthread_setname_np.get() { + #[cfg(target_os = "nto")] + const THREAD_NAME_MAX: usize = libc::_NTO_THREAD_NAME_MAX as usize; + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + const THREAD_NAME_MAX: usize = 32; + + let name = truncate_cstr::<{ THREAD_NAME_MAX }>(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 crate::sys::pal::fuchsia::*; + 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(target_os = "vxworks")] +pub fn set_name(name: &CStr) { + let mut name = truncate_cstr::<{ (libc::VX_TASK_RENAME_LENGTH - 1) as usize }>(name); + let res = unsafe { libc::taskNameSet(libc::taskIdSelf(), name.as_mut_ptr()) }; + debug_assert_eq!(res, libc::OK); +} + +#[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 = &raw mut ts; + 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) { + // ESP-IDF does not have `nanosleep`, so we use `usleep` instead. + // As per the documentation of `usleep`, it is expected to support + // sleep times as big as at least up to 1 second. + // + // ESP-IDF does support almost up to `u32::MAX`, but due to a potential integer overflow in its + // `usleep` implementation + // (https://github.com/espressif/esp-idf/blob/d7ca8b94c852052e3bc33292287ef4dd62c9eeb1/components/newlib/time.c#L210), + // we limit the sleep time to the maximum one that would not cause the underlying `usleep` implementation to overflow + // (`portTICK_PERIOD_MS` can be anything between 1 to 1000, and is 10 by default). + const MAX_MICROS: u32 = u32::MAX - 1_000_000 - 1; + + // Add any nanoseconds smaller than a microsecond as an extra microsecond + // so as to comply with the `std::thread::sleep` contract which mandates + // implementations to sleep for _at least_ the provided `dur`. + // We can't overflow `micros` as it is a `u128`, while `Duration` is a pair of + // (`u64` secs, `u32` nanos), where the nanos are strictly smaller than 1 second + // (i.e. < 1_000_000_000) + let mut micros = dur.as_micros() + if dur.subsec_nanos() % 1_000 > 0 { 1 } else { 0 }; + + while micros > 0 { + let st = if micros > MAX_MICROS as u128 { MAX_MICROS } else { micros as u32 }; + unsafe { + libc::usleep(st); + } + + micros -= st as u128; + } +} + +// Any unix that has clock_nanosleep +// If this list changes update the MIRI chock_nanosleep shim +#[cfg(any( + target_os = "freebsd", + target_os = "netbsd", + target_os = "linux", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "dragonfly", + target_os = "hurd", + target_os = "fuchsia", + target_os = "vxworks", +))] +pub fn sleep_until(deadline: crate::time::Instant) { + use crate::time::Instant; + + let Some(ts) = deadline.into_inner().into_timespec().to_timespec() else { + // The deadline is further in the future then can be passed to + // clock_nanosleep. We have to use Self::sleep instead. This might + // happen on 32 bit platforms, especially closer to 2038. + let now = Instant::now(); + if let Some(delay) = deadline.checked_duration_since(now) { + sleep(delay); + } + return; + }; + + unsafe { + // When we get interrupted (res = EINTR) call clock_nanosleep again + loop { + let res = libc::clock_nanosleep( + crate::sys::time::Instant::CLOCK_ID, + libc::TIMER_ABSTIME, + &ts, + core::ptr::null_mut(), // not required with TIMER_ABSTIME + ); + + if res == 0 { + break; + } else { + assert_eq!( + res, + libc::EINTR, + "timespec is in range, + clockid is valid and kernel should support it" + ); + } + } + } +} + +pub fn yield_now() { + let ret = unsafe { libc::sched_yield() }; + debug_assert_eq!(ret, 0); +} + #[cfg(any(target_os = "android", target_os = "linux"))] mod cgroups { //! Currently not covered diff --git a/library/std/src/sys/pal/unsupported/thread.rs b/library/std/src/sys/thread/unsupported.rs index 34d9b5ec70c..a5001efa3b4 100644 --- a/library/std/src/sys/pal/unsupported/thread.rs +++ b/library/std/src/sys/thread/unsupported.rs @@ -1,8 +1,7 @@ -use super::unsupported; use crate::ffi::CStr; use crate::io; use crate::num::NonZero; -use crate::time::{Duration, Instant}; +use crate::time::Duration; pub struct Thread(!); @@ -15,23 +14,7 @@ impl Thread { _name: Option<&str>, _p: Box<dyn FnOnce()>, ) -> io::Result<Thread> { - unsupported() - } - - pub fn yield_now() { - // do nothing - } - - pub fn set_name(_name: &CStr) { - // nope - } - - pub fn sleep(_dur: Duration) { - panic!("can't sleep"); - } - - pub fn sleep_until(_deadline: Instant) { - panic!("can't sleep"); + Err(io::Error::UNSUPPORTED_PLATFORM) } pub fn join(self) { @@ -39,10 +22,22 @@ impl Thread { } } -pub(crate) fn current_os_id() -> Option<u64> { +pub fn available_parallelism() -> io::Result<NonZero<usize>> { + Err(io::Error::UNKNOWN_THREAD_COUNT) +} + +pub fn current_os_id() -> Option<u64> { None } -pub fn available_parallelism() -> io::Result<NonZero<usize>> { - unsupported() +pub fn yield_now() { + // do nothing +} + +pub fn set_name(_name: &CStr) { + // nope +} + +pub fn sleep(_dur: Duration) { + panic!("can't sleep"); } diff --git a/library/std/src/sys/thread/wasip1.rs b/library/std/src/sys/thread/wasip1.rs new file mode 100644 index 00000000000..83001fad49c --- /dev/null +++ b/library/std/src/sys/thread/wasip1.rs @@ -0,0 +1,185 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + +#[cfg(target_feature = "atomics")] +use crate::io; +use crate::mem; +#[cfg(target_feature = "atomics")] +use crate::num::NonZero; +#[cfg(target_feature = "atomics")] +use crate::sys::os; +use crate::time::Duration; +#[cfg(target_feature = "atomics")] +use crate::{cmp, ptr}; + +// Add a few symbols not in upstream `libc` just yet. +#[cfg(target_feature = "atomics")] +mod libc { + pub use libc::*; + + pub use crate::ffi; + + // defined in wasi-libc + // https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-top-half/musl/include/alltypes.h.in#L108 + #[repr(C)] + union pthread_attr_union { + __i: [ffi::c_int; if size_of::<ffi::c_long>() == 8 { 14 } else { 9 }], + __vi: [ffi::c_int; if size_of::<ffi::c_long>() == 8 { 14 } else { 9 }], + __s: [ffi::c_ulong; if size_of::<ffi::c_long>() == 8 { 7 } else { 9 }], + } + + #[repr(C)] + pub struct pthread_attr_t { + __u: pthread_attr_union, + } + + #[allow(non_camel_case_types)] + pub type pthread_t = *mut ffi::c_void; + + pub const _SC_NPROCESSORS_ONLN: ffi::c_int = 84; + + unsafe extern "C" { + pub fn pthread_create( + native: *mut pthread_t, + attr: *const pthread_attr_t, + f: extern "C" fn(*mut ffi::c_void) -> *mut ffi::c_void, + value: *mut ffi::c_void, + ) -> ffi::c_int; + pub fn pthread_join(native: pthread_t, value: *mut *mut ffi::c_void) -> ffi::c_int; + pub fn pthread_attr_init(attrp: *mut pthread_attr_t) -> ffi::c_int; + pub fn pthread_attr_setstacksize( + attr: *mut pthread_attr_t, + stack_size: libc::size_t, + ) -> ffi::c_int; + pub fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> ffi::c_int; + pub fn pthread_detach(thread: pthread_t) -> ffi::c_int; + } +} + +#[cfg(target_feature = "atomics")] +pub struct Thread { + id: libc::pthread_t, +} + +#[cfg(target_feature = "atomics")] +impl Drop for Thread { + fn drop(&mut self) { + let ret = unsafe { libc::pthread_detach(self.id) }; + debug_assert_eq!(ret, 0); + } +} + +pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024; + +#[cfg(target_feature = "atomics")] +impl Thread { + // unsafe: see thread::Builder::spawn_unchecked for safety requirements + pub unsafe fn new( + stack: usize, + _name: Option<&str>, + p: Box<dyn FnOnce()>, + ) -> io::Result<Thread> { + let p = Box::into_raw(Box::new(p)); + let mut native: libc::pthread_t = unsafe { mem::zeroed() }; + let mut attr: libc::pthread_attr_t = unsafe { mem::zeroed() }; + assert_eq!(unsafe { libc::pthread_attr_init(&mut attr) }, 0); + + let stack_size = cmp::max(stack, DEFAULT_MIN_STACK_SIZE); + + match unsafe { 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!(unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) }, 0); + } + }; + + let ret = unsafe { 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!(unsafe { 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. + unsafe { + 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 { + // Finally, let's run some code. + Box::from_raw(main as *mut Box<dyn FnOnce()>)(); + } + ptr::null_mut() + } + } + + pub fn join(self) { + let id = mem::ManuallyDrop::new(self).id; + let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; + if ret != 0 { + rtabort!("failed to join thread: {}", io::Error::from_raw_os_error(ret)); + } + } +} + +#[cfg(target_feature = "atomics")] +pub fn available_parallelism() -> io::Result<NonZero<usize>> { + match unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) } { + -1 => Err(io::Error::last_os_error()), + cpus => NonZero::new(cpus as usize).ok_or(io::Error::UNKNOWN_THREAD_COUNT), + } +} + +pub fn yield_now() { + let ret = unsafe { wasi::sched_yield() }; + debug_assert_eq!(ret, Ok(())); +} + +pub fn sleep(dur: Duration) { + let mut nanos = dur.as_nanos(); + while nanos > 0 { + const USERDATA: wasi::Userdata = 0x0123_45678; + + let clock = wasi::SubscriptionClock { + id: wasi::CLOCKID_MONOTONIC, + timeout: u64::try_from(nanos).unwrap_or(u64::MAX), + precision: 0, + flags: 0, + }; + nanos -= u128::from(clock.timeout); + + let in_ = wasi::Subscription { + userdata: USERDATA, + u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } }, + }; + unsafe { + let mut event: wasi::Event = mem::zeroed(); + let res = wasi::poll_oneoff(&in_, &mut event, 1); + match (res, event) { + ( + Ok(1), + wasi::Event { + userdata: USERDATA, + error: wasi::ERRNO_SUCCESS, + type_: wasi::EVENTTYPE_CLOCK, + .. + }, + ) => {} + _ => panic!("thread::sleep(): unexpected result of poll_oneoff"), + } + } + } +} diff --git a/library/std/src/sys/thread/wasip2.rs b/library/std/src/sys/thread/wasip2.rs new file mode 100644 index 00000000000..420cad2a5e4 --- /dev/null +++ b/library/std/src/sys/thread/wasip2.rs @@ -0,0 +1,32 @@ +use crate::time::{Duration, Instant}; + +pub fn sleep(dur: Duration) { + // Sleep in increments of `u64::MAX` nanoseconds until the `dur` is + // entirely drained. + let mut remaining = dur.as_nanos(); + while remaining > 0 { + let amt = u64::try_from(remaining).unwrap_or(u64::MAX); + wasip2::clocks::monotonic_clock::subscribe_duration(amt).block(); + remaining -= u128::from(amt); + } +} + +pub fn sleep_until(deadline: Instant) { + match u64::try_from(deadline.into_inner().as_duration().as_nanos()) { + // If the point in time we're sleeping to fits within a 64-bit + // number of nanoseconds then directly use `subscribe_instant`. + Ok(deadline) => { + wasip2::clocks::monotonic_clock::subscribe_instant(deadline).block(); + } + // ... otherwise we're sleeping for 500+ years relative to the + // "start" of what the system is using as a clock so speed/accuracy + // is not so much of a concern. Use `sleep` instead. + Err(_) => { + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + sleep(delay); + } + } + } +} diff --git a/library/std/src/sys/thread/wasm.rs b/library/std/src/sys/thread/wasm.rs new file mode 100644 index 00000000000..e843bc992ba --- /dev/null +++ b/library/std/src/sys/thread/wasm.rs @@ -0,0 +1,23 @@ +use crate::cmp; +use crate::time::Duration; + +pub fn sleep(dur: Duration) { + #[cfg(target_arch = "wasm32")] + use core::arch::wasm32 as wasm; + #[cfg(target_arch = "wasm64")] + use core::arch::wasm64 as wasm; + + // Use an atomic wait to block the current thread artificially with a + // timeout listed. Note that we should never be notified (return value + // of 0) or our comparison should never fail (return value of 1) so we + // should always only resume execution through a timeout (return value + // 2). + let mut nanos = dur.as_nanos(); + while nanos > 0 { + let amt = cmp::min(i64::MAX as u128, nanos); + let mut x = 0; + let val = unsafe { wasm::memory_atomic_wait32(&mut x, 0, amt as i64) }; + debug_assert_eq!(val, 2); + nanos -= amt; + } +} diff --git a/library/std/src/sys/pal/windows/thread.rs b/library/std/src/sys/thread/windows.rs index b0e38220a2d..a5640c51c4a 100644 --- a/library/std/src/sys/pal/windows/thread.rs +++ b/library/std/src/sys/thread/windows.rs @@ -1,14 +1,14 @@ use core::ffi::c_void; -use super::time::WaitableTimer; -use super::to_u16s; use crate::ffi::CStr; use crate::num::NonZero; use crate::os::windows::io::{AsRawHandle, HandleOrNull}; use crate::sys::handle::Handle; +use crate::sys::pal::time::WaitableTimer; +use crate::sys::pal::{dur2timeout, to_u16s}; use crate::sys::{c, stack_overflow}; use crate::sys_common::FromInner; -use crate::time::{Duration, Instant}; +use crate::time::Duration; use crate::{io, ptr}; pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024; @@ -62,24 +62,6 @@ impl Thread { } } - pub fn set_name(name: &CStr) { - if let Ok(utf8) = name.to_str() { - if let Ok(utf16) = to_u16s(utf8) { - unsafe { - // SAFETY: the vec returned by `to_u16s` ends with a zero value - Self::set_name_wide(&utf16) - } - }; - }; - } - - /// # Safety - /// - /// `name` must end with a zero value - pub unsafe fn set_name_wide(name: &[u16]) { - unsafe { c::SetThreadDescription(c::GetCurrentThread(), name.as_ptr()) }; - } - pub fn join(self) { let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) }; if rc == c::WAIT_FAILED { @@ -87,37 +69,6 @@ impl Thread { } } - pub fn yield_now() { - // This function will return 0 if there are no other threads to execute, - // but this also means that the yield was useless so this isn't really a - // case that needs to be worried about. - unsafe { - c::SwitchToThread(); - } - } - - pub fn sleep(dur: Duration) { - fn high_precision_sleep(dur: Duration) -> Result<(), ()> { - let timer = WaitableTimer::high_resolution()?; - timer.set(dur)?; - timer.wait() - } - // Attempt to use high-precision sleep (Windows 10, version 1803+). - // On error fallback to the standard `Sleep` function. - // Also preserves the zero duration behavior of `Sleep`. - if dur.is_zero() || high_precision_sleep(dur).is_err() { - unsafe { c::Sleep(super::dur2timeout(dur)) } - } - } - - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - pub fn handle(&self) -> &Handle { &self.handle } @@ -127,14 +78,6 @@ impl Thread { } } -pub(crate) fn current_os_id() -> Option<u64> { - // SAFETY: FFI call with no preconditions. - let id: u32 = unsafe { c::GetCurrentThreadId() }; - - // A return value of 0 indicates failed lookup. - if id == 0 { None } else { Some(id.into()) } -} - pub fn available_parallelism() -> io::Result<NonZero<usize>> { let res = unsafe { let mut sysinfo: c::SYSTEM_INFO = crate::mem::zeroed(); @@ -146,3 +89,52 @@ pub fn available_parallelism() -> io::Result<NonZero<usize>> { cpus => Ok(unsafe { NonZero::new_unchecked(cpus) }), } } + +pub fn current_os_id() -> Option<u64> { + // SAFETY: FFI call with no preconditions. + let id: u32 = unsafe { c::GetCurrentThreadId() }; + + // A return value of 0 indicates failed lookup. + if id == 0 { None } else { Some(id.into()) } +} + +pub fn set_name(name: &CStr) { + if let Ok(utf8) = name.to_str() { + if let Ok(utf16) = to_u16s(utf8) { + unsafe { + // SAFETY: the vec returned by `to_u16s` ends with a zero value + set_name_wide(&utf16) + } + }; + }; +} + +/// # Safety +/// +/// `name` must end with a zero value +pub unsafe fn set_name_wide(name: &[u16]) { + unsafe { c::SetThreadDescription(c::GetCurrentThread(), name.as_ptr()) }; +} + +pub fn sleep(dur: Duration) { + fn high_precision_sleep(dur: Duration) -> Result<(), ()> { + let timer = WaitableTimer::high_resolution()?; + timer.set(dur)?; + timer.wait() + } + // Attempt to use high-precision sleep (Windows 10, version 1803+). + // On error fallback to the standard `Sleep` function. + // Also preserves the zero duration behavior of `Sleep`. + if dur.is_zero() || high_precision_sleep(dur).is_err() { + unsafe { c::Sleep(dur2timeout(dur)) } + } +} + +pub fn yield_now() { + // This function will return 0 if there are no other threads to execute, + // but this also means that the yield was useless so this isn't really a + // case that needs to be worried about. + unsafe { + c::SwitchToThread(); + } +} diff --git a/library/std/src/sys/pal/xous/thread.rs b/library/std/src/sys/thread/xous.rs index 92803c94c6e..133e15a0928 100644 --- a/library/std/src/sys/pal/xous/thread.rs +++ b/library/std/src/sys/thread/xous.rs @@ -1,6 +1,5 @@ use core::arch::asm; -use crate::ffi::CStr; use crate::io; use crate::num::NonZero; use crate::os::xous::ffi::{ @@ -8,7 +7,7 @@ use crate::os::xous::ffi::{ map_memory, update_memory_flags, }; use crate::os::xous::services::{TicktimerScalar, ticktimer_server}; -use crate::time::{Duration, Instant}; +use crate::time::Duration; pub struct Thread { tid: ThreadId, @@ -110,46 +109,29 @@ impl Thread { Ok(Thread { tid }) } - pub fn yield_now() { - do_yield(); - } - - pub fn set_name(_name: &CStr) { - // nope - } - - pub fn sleep(dur: Duration) { - // Because the sleep server works on units of `usized milliseconds`, split - // the messages up into these chunks. This means we may run into issues - // if you try to sleep a thread for more than 49 days on a 32-bit system. - let mut millis = dur.as_millis(); - while millis > 0 { - let sleep_duration = - if millis > (usize::MAX as _) { usize::MAX } else { millis as usize }; - blocking_scalar(ticktimer_server(), TicktimerScalar::SleepMs(sleep_duration).into()) - .expect("failed to send message to ticktimer server"); - millis -= sleep_duration as u128; - } - } - - pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - Self::sleep(delay); - } - } - pub fn join(self) { join_thread(self.tid).unwrap(); } } -pub(crate) fn current_os_id() -> Option<u64> { - None -} - pub fn available_parallelism() -> io::Result<NonZero<usize>> { // We're unicore right now. Ok(unsafe { NonZero::new_unchecked(1) }) } + +pub fn yield_now() { + do_yield(); +} + +pub fn sleep(dur: Duration) { + // Because the sleep server works on units of `usized milliseconds`, split + // the messages up into these chunks. This means we may run into issues + // if you try to sleep a thread for more than 49 days on a 32-bit system. + let mut millis = dur.as_millis(); + while millis > 0 { + let sleep_duration = if millis > (usize::MAX as _) { usize::MAX } else { millis as usize }; + blocking_scalar(ticktimer_server(), TicktimerScalar::SleepMs(sleep_duration).into()) + .expect("failed to send message to ticktimer server"); + millis -= sleep_duration as u128; + } +} |
