Merge branch 'out2' into 'master'

Expand Out wrapper and use it in utsname.

See merge request redox-os/relibc!736
This commit is contained in:
Jeremy Soller
2025-10-06 06:31:46 -06:00
9 changed files with 180 additions and 34 deletions
+5 -1
View File
@@ -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()
}
+1 -1
View File
@@ -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::<sys_utsname::utsname>::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 {
+8
View File
@@ -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<usize>;
fn read_out(&mut self, mut out: Out<[u8]>) -> Result<usize> {
// 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.
///
+1
View File
@@ -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)]
+67
View File
@@ -343,3 +343,70 @@ macro_rules! strto_float_impl {
}
}};
}
/// Project an `Out<struct X { field: Type }>` to `struct X { field: Out<Type> }`.
///
/// 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<U: $crate::out::OutProject>(_t: &$crate::out::Out<U>) {}
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
),*
}
};
// 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 {}
}
}
+65 -1
View File
@@ -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::<T>(), 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<T>
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<T> {
assert!(i <= self.len());
unsafe { Out::nonnull(self.as_mut_ptr().as_mut_ptr().add(i)) }
}
}
// TODO: different trait?
// TODO: use bytemuck
impl<T: plain::Plain> Out<'_, [T]> {
pub fn zero(&mut self) {
let l = self.ptr.len();
@@ -154,7 +184,28 @@ impl<T: plain::Plain> 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<U>,
{
assert_eq!(self.as_mut_ptr().as_mut_ptr() as usize % align_of::<U>(), 0);
let byte_length = self.as_mut_ptr().len() * size_of::<T>();
unsafe {
Out::from_raw_parts(
self.as_mut_ptr().as_mut_ptr().cast(),
byte_length / size_of::<U>(),
)
}
}
}
// TODO: use bytemuck
pub unsafe trait CastSlice<U> {}
unsafe impl CastSlice<i8> for u8 {}
unsafe impl CastSlice<u8> for i8 {}
impl<T: ?Sized> fmt::Pointer for Out<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:p}", self.ptr)
@@ -165,3 +216,16 @@ impl<T: ?Sized> fmt::Debug for Out<'_, T> {
write!(f, "[Out: {:p}]", self.ptr)
}
}
/// Marker trait for types where it is sound to turn `Out<struct { ... }>` 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
+2 -2
View File
@@ -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<utsname>) -> Result<()> {
e_raw(unsafe { syscall!(UNAME, utsname.as_mut_ptr(), 0) }).map(|_| ())
}
fn unlink(path: CStr) -> Result<()> {
+1 -1
View File
@@ -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<utsname>) -> Result<()>;
fn unlink(path: CStr) -> Result<()>;
+30 -28
View File
@@ -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<utsname>) -> 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::<u8>()) {
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::<u8>())?;
read_line(release.as_slice_mut().cast_slice_to::<u8>())?;
read_line(machine.as_slice_mut().cast_slice_to::<u8>())?;
// 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(())