Auto merge of #106673 - flba-eb:add_qnx_nto_stdlib, r=workingjubilee

Add support for QNX Neutrino to standard library

This change:

- adds standard library support for QNX Neutrino (7.1).
- upgrades `libc` to version `0.2.139` which supports QNX Neutrino

`@gh-tr`

⚠️ Backtraces on QNX require https://github.com/rust-lang/backtrace-rs/pull/507 which is not yet merged! (But everything else works without these changes) ⚠️

Tested mainly with a x86_64 virtual machine (see qnx-nto.md) and partially with an aarch64 hardware (some tests fail due to constrained resources).
This commit is contained in:
bors 2023-03-02 02:41:42 +00:00
commit 864b6258fc
43 changed files with 602 additions and 80 deletions

View file

@ -69,7 +69,8 @@ impl DoubleEndedIterator for Args {
target_os = "fuchsia",
target_os = "redox",
target_os = "vxworks",
target_os = "horizon"
target_os = "horizon",
target_os = "nto",
))]
mod imp {
use super::Args;

View file

@ -185,6 +185,17 @@ pub mod os {
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "nto")]
pub mod os {
pub const FAMILY: &str = "unix";
pub const OS: &str = "nto";
pub const DLL_PREFIX: &str = "lib";
pub const DLL_SUFFIX: &str = ".so";
pub const DLL_EXTENSION: &str = "so";
pub const EXE_SUFFIX: &str = "";
pub const EXE_EXTENSION: &str = "";
}
#[cfg(target_os = "redox")]
pub mod os {
pub const FAMILY: &str = "unix";

View file

@ -53,7 +53,12 @@ const fn max_iov() -> usize {
libc::IOV_MAX as usize
}
#[cfg(any(target_os = "android", target_os = "emscripten", target_os = "linux"))]
#[cfg(any(
target_os = "android",
target_os = "emscripten",
target_os = "linux",
target_os = "nto",
))]
const fn max_iov() -> usize {
libc::UIO_MAXIOV as usize
}
@ -67,6 +72,7 @@ const fn max_iov() -> usize {
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "nto",
target_os = "openbsd",
target_os = "horizon",
target_os = "watchos",
@ -207,7 +213,8 @@ impl FileDesc {
target_os = "linux",
target_os = "haiku",
target_os = "redox",
target_os = "vxworks"
target_os = "vxworks",
target_os = "nto",
)))]
pub fn set_cloexec(&self) -> io::Result<()> {
unsafe {
@ -225,7 +232,8 @@ impl FileDesc {
target_os = "linux",
target_os = "haiku",
target_os = "redox",
target_os = "vxworks"
target_os = "vxworks",
target_os = "nto",
))]
pub fn set_cloexec(&self) -> io::Result<()> {
unsafe {

View file

@ -13,7 +13,8 @@ use crate::mem;
target_os = "solaris",
target_os = "fuchsia",
target_os = "redox",
target_os = "illumos"
target_os = "illumos",
target_os = "nto",
))]
use crate::mem::MaybeUninit;
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd};
@ -54,7 +55,8 @@ use libc::fstatat64;
target_os = "solaris",
target_os = "fuchsia",
target_os = "redox",
target_os = "illumos"
target_os = "illumos",
target_os = "nto",
))]
use libc::readdir as readdir64;
#[cfg(target_os = "linux")]
@ -69,7 +71,8 @@ use libc::readdir64_r;
target_os = "illumos",
target_os = "l4re",
target_os = "fuchsia",
target_os = "redox"
target_os = "redox",
target_os = "nto",
)))]
use libc::readdir_r as readdir64_r;
#[cfg(target_os = "android")]
@ -277,7 +280,8 @@ unsafe impl Sync for Dir {}
target_os = "solaris",
target_os = "illumos",
target_os = "fuchsia",
target_os = "redox"
target_os = "redox",
target_os = "nto",
))]
pub struct DirEntry {
dir: Arc<InnerReadDir>,
@ -297,11 +301,12 @@ pub struct DirEntry {
target_os = "solaris",
target_os = "illumos",
target_os = "fuchsia",
target_os = "redox"
target_os = "redox",
target_os = "nto",
))]
struct dirent64_min {
d_ino: u64,
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
#[cfg(not(any(target_os = "solaris", target_os = "illumos", target_os = "nto")))]
d_type: u8,
}
@ -311,7 +316,8 @@ struct dirent64_min {
target_os = "solaris",
target_os = "illumos",
target_os = "fuchsia",
target_os = "redox"
target_os = "redox",
target_os = "nto",
)))]
pub struct DirEntry {
dir: Arc<InnerReadDir>,
@ -438,7 +444,7 @@ impl FileAttr {
}
}
#[cfg(not(target_os = "netbsd"))]
#[cfg(not(any(target_os = "netbsd", target_os = "nto")))]
impl FileAttr {
#[cfg(not(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon")))]
pub fn modified(&self) -> io::Result<SystemTime> {
@ -524,6 +530,21 @@ impl FileAttr {
}
}
#[cfg(target_os = "nto")]
impl FileAttr {
pub fn modified(&self) -> io::Result<SystemTime> {
Ok(SystemTime::new(self.stat.st_mtim.tv_sec, self.stat.st_mtim.tv_nsec))
}
pub fn accessed(&self) -> io::Result<SystemTime> {
Ok(SystemTime::new(self.stat.st_atim.tv_sec, self.stat.st_atim.tv_nsec))
}
pub fn created(&self) -> io::Result<SystemTime> {
Ok(SystemTime::new(self.stat.st_ctim.tv_sec, self.stat.st_ctim.tv_nsec))
}
}
impl AsInner<stat64> for FileAttr {
fn as_inner(&self) -> &stat64 {
&self.stat
@ -603,7 +624,8 @@ impl Iterator for ReadDir {
target_os = "solaris",
target_os = "fuchsia",
target_os = "redox",
target_os = "illumos"
target_os = "illumos",
target_os = "nto",
))]
fn next(&mut self) -> Option<io::Result<DirEntry>> {
if self.end_of_stream {
@ -686,7 +708,11 @@ impl Iterator for ReadDir {
let entry = dirent64_min {
d_ino: *offset_ptr!(entry_ptr, d_ino) as u64,
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
#[cfg(not(any(
target_os = "solaris",
target_os = "illumos",
target_os = "nto",
)))]
d_type: *offset_ptr!(entry_ptr, d_type) as u8,
};
@ -705,7 +731,8 @@ impl Iterator for ReadDir {
target_os = "solaris",
target_os = "fuchsia",
target_os = "redox",
target_os = "illumos"
target_os = "illumos",
target_os = "nto",
)))]
fn next(&mut self) -> Option<io::Result<DirEntry>> {
if self.end_of_stream {
@ -794,7 +821,8 @@ impl DirEntry {
target_os = "solaris",
target_os = "illumos",
target_os = "haiku",
target_os = "vxworks"
target_os = "vxworks",
target_os = "nto",
))]
pub fn file_type(&self) -> io::Result<FileType> {
self.metadata().map(|m| m.file_type())
@ -804,7 +832,8 @@ impl DirEntry {
target_os = "solaris",
target_os = "illumos",
target_os = "haiku",
target_os = "vxworks"
target_os = "vxworks",
target_os = "nto",
)))]
pub fn file_type(&self) -> io::Result<FileType> {
match self.entry.d_type {
@ -834,7 +863,8 @@ impl DirEntry {
target_os = "redox",
target_os = "vxworks",
target_os = "espidf",
target_os = "horizon"
target_os = "horizon",
target_os = "nto",
))]
pub fn ino(&self) -> u64 {
self.entry.d_ino as u64
@ -887,7 +917,8 @@ impl DirEntry {
target_os = "solaris",
target_os = "illumos",
target_os = "fuchsia",
target_os = "redox"
target_os = "redox",
target_os = "nto",
)))]
fn name_cstr(&self) -> &CStr {
unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) }
@ -898,7 +929,8 @@ impl DirEntry {
target_os = "solaris",
target_os = "illumos",
target_os = "fuchsia",
target_os = "redox"
target_os = "redox",
target_os = "nto",
))]
fn name_cstr(&self) -> &CStr {
&self.name
@ -1051,7 +1083,8 @@ impl File {
target_os = "linux",
target_os = "android",
target_os = "netbsd",
target_os = "openbsd"
target_os = "openbsd",
target_os = "nto",
))]
unsafe fn os_datasync(fd: c_int) -> c_int {
libc::fdatasync(fd)
@ -1065,6 +1098,7 @@ impl File {
target_os = "netbsd",
target_os = "openbsd",
target_os = "watchos",
target_os = "nto",
)))]
unsafe fn os_datasync(fd: c_int) -> c_int {
libc::fsync(fd)
@ -1750,13 +1784,25 @@ pub fn chroot(dir: &Path) -> io::Result<()> {
pub use remove_dir_impl::remove_dir_all;
// Fallback for REDOX, ESP-ID, Horizon, and Miri
#[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri))]
#[cfg(any(
target_os = "redox",
target_os = "espidf",
target_os = "horizon",
target_os = "nto",
miri
))]
mod remove_dir_impl {
pub use crate::sys_common::fs::remove_dir_all;
}
// Modern implementation using openat(), unlinkat() and fdopendir()
#[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri)))]
#[cfg(not(any(
target_os = "redox",
target_os = "espidf",
target_os = "horizon",
target_os = "nto",
miri
)))]
mod remove_dir_impl {
use super::{lstat, Dir, DirEntry, InnerReadDir, ReadDir};
use crate::ffi::CStr;

View file

@ -2,7 +2,10 @@ use crate::cell::UnsafeCell;
use crate::ptr;
use crate::sync::atomic::{AtomicPtr, Ordering::Relaxed};
use crate::sys::locks::{pthread_mutex, Mutex};
#[cfg(not(target_os = "nto"))]
use crate::sys::time::TIMESPEC_MAX;
#[cfg(target_os = "nto")]
use crate::sys::time::TIMESPEC_MAX_CAPPED;
use crate::sys_common::lazy_box::{LazyBox, LazyInit};
use crate::time::Duration;
@ -132,10 +135,18 @@ impl Condvar {
let mutex = pthread_mutex::raw(mutex);
self.verify(mutex);
#[cfg(not(target_os = "nto"))]
let timeout = Timespec::now(libc::CLOCK_MONOTONIC)
.checked_add_duration(&dur)
.and_then(|t| t.to_timespec())
.unwrap_or(TIMESPEC_MAX);
#[cfg(target_os = "nto")]
let timeout = Timespec::now(libc::CLOCK_MONOTONIC)
.checked_add_duration(&dur)
.and_then(|t| t.to_timespec_capped())
.unwrap_or(TIMESPEC_MAX_CAPPED);
let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout);
assert!(r == libc::ETIMEDOUT || r == 0);
r == 0

View file

@ -78,6 +78,7 @@ impl Socket {
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd",
target_os = "nto",
))] {
// On platforms that support it we pass the SOCK_CLOEXEC
// flag to atomically create the socket and set it as
@ -115,6 +116,7 @@ impl Socket {
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd",
target_os = "nto",
))] {
// Like above, set cloexec atomically
cvt(libc::socketpair(fam, ty | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr()))?;

View file

@ -62,6 +62,7 @@ extern "C" {
link_name = "__errno"
)]
#[cfg_attr(any(target_os = "solaris", target_os = "illumos"), link_name = "___errno")]
#[cfg_attr(target_os = "nto", link_name = "__get_errno_ptr")]
#[cfg_attr(
any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "watchos"),
link_name = "__error"
@ -361,6 +362,17 @@ pub fn current_exe() -> io::Result<PathBuf> {
}
}
#[cfg(target_os = "nto")]
pub fn current_exe() -> io::Result<PathBuf> {
let mut e = crate::fs::read("/proc/self/exefile")?;
// Current versions of QNX Neutrino provide a null-terminated path.
// Ensure the trailing null byte is not returned here.
if let Some(0) = e.last() {
e.pop();
}
Ok(PathBuf::from(OsString::from_vec(e)))
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
pub fn current_exe() -> io::Result<PathBuf> {
unsafe {

View file

@ -18,6 +18,7 @@ use crate::sys::weak::raw_syscall;
target_os = "freebsd",
all(target_os = "linux", target_env = "gnu"),
all(target_os = "linux", target_env = "musl"),
target_os = "nto",
))]
use crate::sys::weak::weak;
@ -30,6 +31,15 @@ use libc::{c_int, pid_t};
#[cfg(not(any(target_os = "vxworks", target_os = "l4re")))]
use libc::{gid_t, uid_t};
cfg_if::cfg_if! {
if #[cfg(all(target_os = "nto", target_env = "nto71"))] {
use crate::thread;
use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
// arbitrary number of tries:
const MAX_FORKSPAWN_TRIES: u32 = 4;
}
}
////////////////////////////////////////////////////////////////////////////////
// Command
////////////////////////////////////////////////////////////////////////////////
@ -140,11 +150,31 @@ impl Command {
// Attempts to fork the process. If successful, returns Ok((0, -1))
// in the child, and Ok((child_pid, -1)) in the parent.
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", all(target_os = "nto", target_env = "nto71"))))]
unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> {
cvt(libc::fork()).map(|res| (res, -1))
}
// On QNX Neutrino, fork can fail with EBADF in case "another thread might have opened
// or closed a file descriptor while the fork() was occurring".
// Documentation says "... or try calling fork() again". This is what we do here.
// See also https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/f/fork.html
#[cfg(all(target_os = "nto", target_env = "nto71"))]
unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> {
use crate::sys::os::errno;
let mut tries_left = MAX_FORKSPAWN_TRIES;
loop {
let r = libc::fork();
if r == -1 as libc::pid_t && tries_left > 0 && errno() as libc::c_int == libc::EBADF {
thread::yield_now();
tries_left -= 1;
} else {
return cvt(r).map(|res| (res, -1));
}
}
}
// Attempts to fork the process. If successful, returns Ok((0, -1))
// in the child, and Ok((child_pid, child_pidfd)) in the parent.
#[cfg(target_os = "linux")]
@ -389,6 +419,7 @@ impl Command {
target_os = "freebsd",
all(target_os = "linux", target_env = "gnu"),
all(target_os = "linux", target_env = "musl"),
target_os = "nto",
)))]
fn posix_spawn(
&mut self,
@ -405,6 +436,7 @@ impl Command {
target_os = "freebsd",
all(target_os = "linux", target_env = "gnu"),
all(target_os = "linux", target_env = "musl"),
target_os = "nto",
))]
fn posix_spawn(
&mut self,
@ -436,6 +468,34 @@ impl Command {
}
}
// On QNX Neutrino, posix_spawnp can fail with EBADF in case "another thread might have opened
// or closed a file descriptor while the posix_spawn() was occurring".
// Documentation says "... or try calling posix_spawn() again". This is what we do here.
// See also http://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/p/posix_spawn.html
#[cfg(all(target_os = "nto", target_env = "nto71"))]
unsafe fn retrying_libc_posix_spawnp(
pid: *mut pid_t,
file: *const c_char,
file_actions: *const posix_spawn_file_actions_t,
attrp: *const posix_spawnattr_t,
argv: *const *mut c_char,
envp: *const *mut c_char,
) -> i32 {
let mut tries_left = MAX_FORKSPAWN_TRIES;
loop {
match libc::posix_spawnp(pid, file, file_actions, attrp, argv, envp) {
libc::EBADF if tries_left > 0 => {
thread::yield_now();
tries_left -= 1;
continue;
}
r => {
return r;
}
}
}
}
// Solaris, glibc 2.29+, and musl 1.24+ can set a new working directory,
// and maybe others will gain this non-POSIX function too. We'll check
// for this weak symbol as soon as it's needed, so we can return early
@ -555,7 +615,12 @@ impl Command {
// Make sure we synchronize access to the global `environ` resource
let _env_lock = sys::os::env_read_lock();
let envp = envp.map(|c| c.as_ptr()).unwrap_or_else(|| *sys::os::environ() as *const _);
cvt_nz(libc::posix_spawnp(
#[cfg(not(target_os = "nto"))]
let spawn_fn = libc::posix_spawnp;
#[cfg(target_os = "nto")]
let spawn_fn = retrying_libc_posix_spawnp;
cvt_nz(spawn_fn(
&mut p.pid,
self.get_program_cstr().as_ptr(),
file_actions.0.as_ptr(),
@ -760,7 +825,7 @@ fn signal_string(signal: i32) -> &'static str {
)
))]
libc::SIGSTKFLT => " (SIGSTKFLT)",
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "nto"))]
libc::SIGPWR => " (SIGPWR)",
#[cfg(any(
target_os = "macos",
@ -769,7 +834,8 @@ fn signal_string(signal: i32) -> &'static str {
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
target_os = "dragonfly",
target_os = "nto",
))]
libc::SIGEMT => " (SIGEMT)",
#[cfg(any(

View file

@ -9,7 +9,7 @@ use crate::time::Duration;
#[cfg(all(target_os = "linux", target_env = "gnu"))]
use crate::sys::weak::dlsym;
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
#[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto"))]
use crate::sys::weak::weak;
#[cfg(not(any(target_os = "l4re", target_os = "vxworks", target_os = "espidf")))]
pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024;
@ -173,7 +173,7 @@ impl Thread {
}
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
#[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto"))]
pub fn set_name(name: &CStr) {
weak! {
fn pthread_setname_np(
@ -381,6 +381,17 @@ pub fn available_parallelism() -> io::Result<NonZeroUsize> {
}
Ok(unsafe { NonZeroUsize::new_unchecked(cpus as usize) })
} else if #[cfg(target_os = "nto")] {
unsafe {
use libc::_syspage_ptr;
if _syspage_ptr.is_null() {
Err(io::const_io_error!(io::ErrorKind::NotFound, "No syspage available"))
} else {
let cpus = (*_syspage_ptr).num_cpu;
NonZeroUsize::new(cpus as usize)
.ok_or(io::const_io_error!(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"))
}
}
} else if #[cfg(target_os = "haiku")] {
// system_info cpu_count field gets the static data set at boot time with `smp_set_num_cpus`
// `get_system_info` calls then `smp_get_num_cpus`

View file

@ -6,7 +6,10 @@ use crate::pin::Pin;
use crate::ptr::addr_of_mut;
use crate::sync::atomic::AtomicUsize;
use crate::sync::atomic::Ordering::SeqCst;
#[cfg(not(target_os = "nto"))]
use crate::sys::time::TIMESPEC_MAX;
#[cfg(target_os = "nto")]
use crate::sys::time::TIMESPEC_MAX_CAPPED;
use crate::time::Duration;
const EMPTY: usize = 0;
@ -80,8 +83,14 @@ unsafe fn wait_timeout(
(Timespec::now(libc::CLOCK_MONOTONIC), dur)
};
#[cfg(not(target_os = "nto"))]
let timeout =
now.checked_add_duration(&dur).and_then(|t| t.to_timespec()).unwrap_or(TIMESPEC_MAX);
#[cfg(target_os = "nto")]
let timeout = now
.checked_add_duration(&dur)
.and_then(|t| t.to_timespec_capped())
.unwrap_or(TIMESPEC_MAX_CAPPED);
let r = libc::pthread_cond_timedwait(cond, lock, &timeout);
debug_assert!(r == libc::ETIMEDOUT || r == 0);
}

View file

@ -9,6 +9,14 @@ pub const UNIX_EPOCH: SystemTime = SystemTime { t: Timespec::zero() };
pub const TIMESPEC_MAX: libc::timespec =
libc::timespec { tv_sec: <libc::time_t>::MAX, tv_nsec: 1_000_000_000 - 1 };
// This additional constant is only used when calling
// `libc::pthread_cond_timedwait`.
#[cfg(target_os = "nto")]
pub(super) const TIMESPEC_MAX_CAPPED: libc::timespec = libc::timespec {
tv_sec: (u64::MAX / NSEC_PER_SEC) as i64,
tv_nsec: (u64::MAX % NSEC_PER_SEC) as i64,
};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[rustc_layout_scalar_valid_range_start(0)]
@ -144,6 +152,20 @@ impl Timespec {
tv_nsec: self.tv_nsec.0.try_into().ok()?,
})
}
// On QNX Neutrino, the maximum timespec for e.g. pthread_cond_timedwait
// is 2^64 nanoseconds
#[cfg(target_os = "nto")]
pub(super) fn to_timespec_capped(&self) -> Option<libc::timespec> {
// Check if timeout in nanoseconds would fit into an u64
if (self.tv_nsec.0 as u64)
.checked_add((self.tv_sec as u64).checked_mul(NSEC_PER_SEC)?)
.is_none()
{
return None;
}
self.to_timespec()
}
}
impl From<libc::timespec> for Timespec {