From 24cf96393c1ce085c286bbaac094cdbbdb6d183b Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sun, 5 Oct 2025 19:58:50 +0200 Subject: [PATCH 1/2] Expand Out wrapper and use it in utsname. --- src/header/sys_utsname/mod.rs | 6 +++- src/header/unistd/mod.rs | 2 +- src/io/mod.rs | 8 +++++ src/lib.rs | 1 + src/macros.rs | 62 ++++++++++++++++++++++++++++++++ src/out.rs | 66 ++++++++++++++++++++++++++++++++++- src/platform/linux/mod.rs | 4 +-- src/platform/pal/mod.rs | 2 +- src/platform/redox/mod.rs | 58 +++++++++++++++--------------- 9 files changed, 175 insertions(+), 34 deletions(-) diff --git a/src/header/sys_utsname/mod.rs b/src/header/sys_utsname/mod.rs index 8fe6116db1..2ca93e38b5 100644 --- a/src/header/sys_utsname/mod.rs +++ b/src/header/sys_utsname/mod.rs @@ -2,12 +2,14 @@ use crate::{ error::ResultExt, + out::Out, platform::{Pal, Sys, types::*}, }; pub const UTSLENGTH: usize = 65; #[repr(C)] +#[derive(Clone, Copy, Debug, OutProject)] pub struct utsname { pub sysname: [c_char; UTSLENGTH], pub nodename: [c_char; UTSLENGTH], @@ -19,5 +21,7 @@ pub struct utsname { #[unsafe(no_mangle)] pub unsafe extern "C" fn uname(uts: *mut utsname) -> c_int { - Sys::uname(uts).map(|()| 0).or_minus_one_errno() + Sys::uname(Out::nonnull(uts)) + .map(|()| 0) + .or_minus_one_errno() } diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs index c28df697d4..9bde8bf59d 100644 --- a/src/header/unistd/mod.rs +++ b/src/header/unistd/mod.rs @@ -563,7 +563,7 @@ pub extern "C" fn gethostid() -> c_long { pub unsafe extern "C" fn gethostname(mut name: *mut c_char, mut len: size_t) -> c_int { let mut uts = mem::MaybeUninit::::uninit(); // TODO - let err = Sys::uname(uts.as_mut_ptr()) + let err = Sys::uname(Out::from_uninit_mut(&mut uts)) .map(|()| 0) .or_minus_one_errno(); if err < 0 { diff --git a/src/io/mod.rs b/src/io/mod.rs index db21fe9ca4..96c5813a17 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -273,6 +273,8 @@ pub mod error; mod impls; pub mod prelude; +use crate::out::Out; + pub use self::{buffered::*, cursor::*, error::*}; use self::prelude::*; @@ -550,6 +552,12 @@ pub trait Read { /// ``` fn read(&mut self, buf: &mut [u8]) -> Result; + fn read_out(&mut self, mut out: Out<[u8]>) -> Result { + // XXX: Technically incorrect but hopefully not UB + let slice: &mut [u8] = unsafe { &mut *out.as_mut_ptr() }; + self.read(slice) + } + /// Determines if this `Read`er can work with buffers of uninitialized /// memory. /// diff --git a/src/lib.rs b/src/lib.rs index 950780868a..30b1d2e081 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ #![feature(asm_const)] #![feature(c_variadic)] #![feature(core_intrinsics)] +#![feature(macro_derive)] #![feature(maybe_uninit_slice)] #![feature(lang_items)] #![feature(let_chains)] diff --git a/src/macros.rs b/src/macros.rs index fbe1579c06..569e35907a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -343,3 +343,65 @@ macro_rules! strto_float_impl { } }}; } + +/// Project an `Out` to `struct X { field: Out }`. +/// +/// It is allowed to include only a subset of the struct's fields. The struct must implement +/// `OutProject`. +#[macro_export] +macro_rules! out_project { + { + let $struct:ty { $($field:ident : $fieldty:ty),*$(,)? } = $src:ident; + } => { + // Verify $src actually has type Out<$struct>. Also verify it implements `OutProject`. This + // excludes + // + // - the case where $src is Out<&Struct>, where it would be very UB to just construct a + // writable reference to $src.$field, or a smart pointer + // - the case where there are unaligned fields where it would be UB to call ptr::write to + // them (requiring packed structs) + { + fn ensure_type(_t: &$crate::out::Out) {} + ensure_type::<$crate::out::Out<$struct>, $struct>(&$src); + } + // Verify there are no duplicate struct fields. This is not strictly necessary as Out lacks + // the noalias requirement, but forbidding the same field to occur multiple times would + // allow both cases. The compiler will reject any struct that reuses the same identifier. + const _: () = { + struct S { + $( + $field: $fieldty + ),* + } + }; + + // Finally, create an Out<$fieldty> for each field. + $( + // getting the pointer to $field is safe + let $field = unsafe { &raw mut (*$crate::out::Out::<_>::as_mut_ptr(&mut $src)).$field }; + )* + $( + let mut $field: $crate::out::Out<$fieldty> = unsafe { + // SAFETY: the only guarantee is that the pointer is valid and writable for the + // duration of 'b where $src: Out<'b, T>. But if so, and T is a struct, that + // must also be true for all the struct fields. + $crate::out::Out::with_lifetime_of( + $crate::out::Out::nonnull($field), + &$src, + ) + }; + )* + } +} +#[macro_export] +macro_rules! OutProject { + derive() { $(#[$($attrs:meta),*])* $v:vis struct $name:ident { + $( + $(#[$($fa:meta),*])* $fv:vis $field:ident : $type:ty + ),*$(,)? + } } => { + // SAFETY: As simple as it is, OutProject is valid for any struct, and the pattern we have + // matched above ensures $name is one. + unsafe impl $crate::out::OutProject for $name {} + } +} diff --git a/src/out.rs b/src/out.rs index 4bf241b8ad..ba099c4de7 100644 --- a/src/out.rs +++ b/src/out.rs @@ -73,6 +73,15 @@ impl<'a, T> Out<'a, T> { } } } +impl<'a, T, const N: usize> Out<'a, [T; N]> { + #[inline] + pub fn as_slice_mut<'b>(&'b mut self) -> Out<'b, [T]> { + unsafe { + let ptr: *mut [T; N] = self.as_mut_ptr(); + Out::from_raw_parts(ptr.cast::(), N) + } + } +} impl<'a, T> Out<'a, [T]> { /// # Safety /// @@ -90,6 +99,9 @@ impl<'a, T> Out<'a, [T]> { pub fn len(&self) -> usize { self.ptr.as_ptr().len() } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } // TODO: Maybe strengthen lifetimes? #[inline] pub fn split_at_checked<'b>(&'b mut self, n: usize) -> Option<[Out<'b, [T]>; 2]> { @@ -142,8 +154,26 @@ impl<'a, T> Out<'a, [T]> { .copy_from_nonoverlapping(src.as_ptr(), src.len()); } } + pub fn copy_common_length_from_slice(&mut self, src: &[T]) -> usize + where + T: Copy, + { + let l = src.len().min(self.len()); + self.split_at_checked(l).unwrap()[0].copy_from_slice(&src[..l]); + l + } + // TODO: better API, impl RangeBounds, also fn get(usize) -> Out + pub fn subslice<'b>(&'b mut self, start: usize, end: usize) -> Out<[T]> { + assert!(start <= end); + assert!(end <= self.len()); + unsafe { Self::from_raw_parts(self.as_mut_ptr().as_mut_ptr().add(start), end - start) } + } + pub fn index<'b>(&'b mut self, i: usize) -> Out { + assert!(i <= self.len()); + unsafe { Out::nonnull(self.as_mut_ptr().as_mut_ptr().add(i)) } + } } -// TODO: different trait? +// TODO: use bytemuck impl Out<'_, [T]> { pub fn zero(&mut self) { let l = self.ptr.len(); @@ -154,7 +184,28 @@ impl Out<'_, [T]> { self.ptr.as_mut_ptr().write_bytes(0, l) } } + #[inline] + pub fn cast_slice_to<'b, U>(mut self) -> Out<'b, [U]> + where + T: CastSlice, + { + assert_eq!(self.as_mut_ptr().as_mut_ptr() as usize % align_of::(), 0); + + let byte_length = self.as_mut_ptr().len() * size_of::(); + + unsafe { + Out::from_raw_parts( + self.as_mut_ptr().as_mut_ptr().cast(), + byte_length / size_of::(), + ) + } + } } +// TODO: use bytemuck +pub unsafe trait CastSlice {} +unsafe impl CastSlice for u8 {} +unsafe impl CastSlice for i8 {} + impl fmt::Pointer for Out<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:p}", self.ptr) @@ -165,3 +216,16 @@ impl fmt::Debug for Out<'_, T> { write!(f, "[Out: {:p}]", self.ptr) } } +/// Marker trait for types where it is sound to turn `Out` into `struct { ...: +/// Out<...> }` by simply referencing fields. This is safe for any struct but must not be +/// implemented for `Deref` types so that `Out<&struct { ... }>` is never projected in a way that +/// adds mutability. +pub unsafe trait OutProject {} + +impl<'a, T: ?Sized> Out<'a, T> { + pub unsafe fn with_lifetime_of<'b, U: ?Sized>(mut self, u: &'b U) -> Out<'b, T> { + Out::nonnull(self.as_mut_ptr()) + } +} + +// TODO: unit tests diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 5ee3bbbecd..53a51b8668 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -729,8 +729,8 @@ impl Pal for Sys { unsafe { syscall!(UMASK, mask) as mode_t } } - unsafe fn uname(utsname: *mut utsname) -> Result<()> { - e_raw(unsafe { syscall!(UNAME, utsname, 0) }).map(|_| ()) + fn uname(mut utsname: Out) -> Result<()> { + e_raw(unsafe { syscall!(UNAME, utsname.as_mut_ptr(), 0) }).map(|_| ()) } fn unlink(path: CStr) -> Result<()> { diff --git a/src/platform/pal/mod.rs b/src/platform/pal/mod.rs index 0c158a20c0..d497d1fcea 100644 --- a/src/platform/pal/mod.rs +++ b/src/platform/pal/mod.rs @@ -255,7 +255,7 @@ pub trait Pal { // Always successful fn umask(mask: mode_t) -> mode_t; - unsafe fn uname(utsname: *mut utsname) -> Result<()>; + fn uname(utsname: Out) -> Result<()>; fn unlink(path: CStr) -> Result<()>; diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 72dc10dce1..4d0a4a80ad 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -1013,8 +1013,8 @@ impl Pal for Sys { (redox_rt::sys::swap_umask(new_effective_mask as u32) as mode_t) & !S_ISVTX } - unsafe fn uname(utsname: *mut utsname) -> Result<(), Errno> { - fn gethostname(name: &mut [u8]) -> io::Result<()> { + fn uname(mut utsname: Out) -> Result<(), Errno> { + fn gethostname(mut name: Out<[u8]>) -> io::Result<()> { if name.is_empty() { return Ok(()); } @@ -1024,21 +1024,26 @@ impl Pal for Sys { let mut read = 0; let name_len = name.len(); loop { - match file.read(&mut name[read..name_len - 1])? { + match file.read_out(name.subslice(read, name_len - 1))? { 0 => break, n => read += n, } } - name[read] = 0; + name.index(read).write(0); Ok(()) } + out_project! { + let utsname { + nodename: [c_char; UTSLENGTH], + sysname: [c_char; UTSLENGTH], + release: [c_char; UTSLENGTH], + machine: [c_char; UTSLENGTH], + version: [c_char; UTSLENGTH], + domainname: [c_char; UTSLENGTH], + } = utsname; + } - match gethostname(unsafe { - slice::from_raw_parts_mut( - (*utsname).nodename.as_mut_ptr() as *mut u8, - (*utsname).nodename.len(), - ) - }) { + match gethostname(nodename.as_slice_mut().cast_slice_to::()) { Ok(_) => (), Err(_) => return Err(Errno(EIO)), } @@ -1050,36 +1055,33 @@ impl Pal for Sys { }; let mut lines = BufReader::new(&mut file).lines(); - let mut read_line = |dst: &mut [c_char]| { + let mut read_line = |mut dst: Out<[u8]>| { + // TODO: set nul byte without allocating CString let line = match lines.next() { - Some(Ok(l)) => match CString::new(l) { - Ok(l) => l, - Err(_) => return Err(Errno(EIO)), - }, + Some(Ok(l)) => CString::new(l).map_err(|_| Errno(EIO))?, None | Some(Err(_)) => return Err(Errno(EIO)), }; - let line_slice: &[c_char] = unsafe { mem::transmute(line.as_bytes_with_nul()) }; - - if line_slice.len() <= UTSLENGTH { - dst[..line_slice.len()].copy_from_slice(line_slice); - Ok(()) - } else { - Err(Errno(EIO)) + let line_slice: &[u8] = line.as_bytes_with_nul(); + if line_slice.len() > UTSLENGTH { + return Err(Errno(EIO)); } + + dst.copy_common_length_from_slice(line_slice); + Ok(()) }; unsafe { - read_line(&mut (*utsname).sysname)?; - read_line(&mut (*utsname).release)?; - read_line(&mut (*utsname).machine)?; + read_line(sysname.as_slice_mut().cast_slice_to::())?; + read_line(release.as_slice_mut().cast_slice_to::())?; + read_line(machine.as_slice_mut().cast_slice_to::())?; // Version is not provided - ptr::write_bytes((*utsname).version.as_mut_ptr(), 0, UTSLENGTH); + version.as_slice_mut().zero(); // Redox doesn't provide domainname in sys:uname - //read_line(&mut (*utsname).domainname)?; - ptr::write_bytes((*utsname).domainname.as_mut_ptr(), 0, UTSLENGTH); + //read_line(domainname.as_slice_mut())?; + domainname.as_slice_mut().zero(); } Ok(()) From f4cd024c2b6e8598911c614d2ed652486d7d09df Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sun, 5 Oct 2025 21:07:00 +0200 Subject: [PATCH 2/2] Also add align check to projection macro. --- src/macros.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 569e35907a..4261077f65 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -361,13 +361,18 @@ macro_rules! out_project { // - the case where there are unaligned fields where it would be UB to call ptr::write to // them (requiring packed structs) { - fn ensure_type(_t: &$crate::out::Out) {} - ensure_type::<$crate::out::Out<$struct>, $struct>(&$src); + fn ensure_type(_t: &$crate::out::Out) {} + ensure_type::<$struct>(&$src); } // Verify there are no duplicate struct fields. This is not strictly necessary as Out lacks // the noalias requirement, but forbidding the same field to occur multiple times would // allow both cases. The compiler will reject any struct that reuses the same identifier. const _: () = { + $( + if ::core::mem::offset_of!($struct, $field) % ::core::mem::align_of::<$fieldty>() != 0 { + panic!(concat!("unaligned field ", stringify!($field), " of struct ", stringify!($struct), ".")); + } + )* struct S { $( $field: $fieldty