Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype a new Buffer trait. #1290

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/backend/libc/io/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ use {
crate::io::{IoSlice, IoSliceMut},
};

pub(crate) unsafe fn read(fd: BorrowedFd<'_>, buf: *mut u8, len: usize) -> io::Result<usize> {
ret_usize(c::read(borrowed_fd(fd), buf.cast(), min(len, READ_LIMIT)))
pub(crate) unsafe fn read(fd: BorrowedFd<'_>, buf: (*mut u8, usize)) -> io::Result<usize> {
ret_usize(c::read(
borrowed_fd(fd),
buf.0.cast(),
min(buf.1, READ_LIMIT),
))
}

pub(crate) fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
Expand Down
4 changes: 2 additions & 2 deletions src/backend/linux_raw/io/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ use core::cmp;
use linux_raw_sys::general::{F_DUPFD_CLOEXEC, F_GETFD, F_SETFD};

#[inline]
pub(crate) unsafe fn read(fd: BorrowedFd<'_>, buf: *mut u8, len: usize) -> io::Result<usize> {
ret_usize(syscall!(__NR_read, fd, buf, pass_usize(len)))
pub(crate) unsafe fn read(fd: BorrowedFd<'_>, buf: (*mut u8, usize)) -> io::Result<usize> {
ret_usize(syscall!(__NR_read, fd, buf.0, pass_usize(buf.1)))
}

#[inline]
Expand Down
2 changes: 1 addition & 1 deletion src/backend/linux_raw/param/auxv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ impl Iterator for AuxFile {
let mut buf = [0_u8; size_of::<Self::Item>()];
let mut slice = &mut buf[..];
while !slice.is_empty() {
match crate::io::read(&self.0, slice) {
match crate::io::read(&self.0, &mut *slice) {
Ok(0) => panic!("unexpected end of auxv file"),
Ok(n) => slice = &mut slice[n..],
Err(crate::io::Errno::INTR) => continue,
Expand Down
264 changes: 264 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,197 @@

#![allow(unsafe_code)]

#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::mem::MaybeUninit;
use core::slice;

/// A memory buffer that may be uninitialized.
///
/// If you see errors like "move occurs because `x` has type `&mut [u8]`,
/// which does not implement the `Copy` trait", replace `x` with `&mut *x`.
pub trait Buffer<T>: private::Sealed<T> {}

// Implement `Buffer` for all the types that implement `Sealed`.
impl<T> Buffer<T> for &mut [T] {}
impl<T, const N: usize> Buffer<T> for &mut [T; N] {}
#[cfg(feature = "alloc")]
impl<T> Buffer<T> for &mut Vec<T> {}
impl<T> Buffer<T> for &mut [MaybeUninit<T>] {}
impl<T, const N: usize> Buffer<T> for &mut [MaybeUninit<T>; N] {}
#[cfg(feature = "alloc")]
impl<T> Buffer<T> for &mut Vec<MaybeUninit<T>> {}
#[cfg(feature = "alloc")]
impl<'a, T> Buffer<T> for Extend<'a, T> {}

impl<T> private::Sealed<T> for &mut [T] {
type Result = usize;

#[inline]
fn as_raw_parts_mut(&mut self) -> (*mut T, usize) {
(self.as_mut_ptr(), self.len())
}

#[inline]
unsafe fn finish(self, len: usize) -> Self::Result {
len
}
}

impl<T, const N: usize> private::Sealed<T> for &mut [T; N] {
sunfishcode marked this conversation as resolved.
Show resolved Hide resolved
type Result = usize;

#[inline]
fn as_raw_parts_mut(&mut self) -> (*mut T, usize) {
(self.as_mut_ptr(), N)
}

#[inline]
unsafe fn finish(self, len: usize) -> Self::Result {
len
}
}

// `Vec` implements `DerefMut` to `&mut [T]`, however it doesn't get
// auto-derefed in a `impl Buffer<u8>`, so we add this `impl` so that our users
// don't have to add an extra `*` in these situations.
#[cfg(feature = "alloc")]
impl<T> private::Sealed<T> for &mut Vec<T> {
type Result = usize;

#[inline]
fn as_raw_parts_mut(&mut self) -> (*mut T, usize) {
(self.as_mut_ptr(), self.len())
}

#[inline]
unsafe fn finish(self, len: usize) -> Self::Result {
len
}
}

impl<'a, T> private::Sealed<T> for &'a mut [MaybeUninit<T>] {
type Result = (&'a mut [T], &'a mut [MaybeUninit<T>]);

#[inline]
fn as_raw_parts_mut(&mut self) -> (*mut T, usize) {
(self.as_mut_ptr().cast(), self.len())
}

#[inline]
unsafe fn finish(self, len: usize) -> Self::Result {
let (init, uninit) = self.split_at_mut(len);

// SAFETY: The user asserts that the slice is now initialized.
let init = slice::from_raw_parts_mut(init.as_mut_ptr().cast::<T>(), init.len());

(init, uninit)
}
}

impl<'a, T, const N: usize> private::Sealed<T> for &'a mut [MaybeUninit<T>; N] {
type Result = (&'a mut [T], &'a mut [MaybeUninit<T>]);

#[inline]
fn as_raw_parts_mut(&mut self) -> (*mut T, usize) {
(self.as_mut_ptr().cast(), self.len())
}

#[inline]
unsafe fn finish(self, len: usize) -> Self::Result {
let (init, uninit) = self.split_at_mut(len);

// SAFETY: The user asserts that the slice is now initialized.
let init = slice::from_raw_parts_mut(init.as_mut_ptr().cast::<T>(), init.len());

(init, uninit)
}
}

#[cfg(feature = "alloc")]
impl<'a, T> private::Sealed<T> for &'a mut Vec<MaybeUninit<T>> {
type Result = (&'a mut [T], &'a mut [MaybeUninit<T>]);

#[inline]
fn as_raw_parts_mut(&mut self) -> (*mut T, usize) {
(self.as_mut_ptr().cast(), self.len())
}

#[inline]
unsafe fn finish(self, len: usize) -> Self::Result {
let (init, uninit) = self.split_at_mut(len);

// SAFETY: The user asserts that the slice is now initialized.
let init = slice::from_raw_parts_mut(init.as_mut_ptr().cast::<T>(), init.len());

(init, uninit)
}
}

/// A type that implements [`Buffer`] by appending to a `Vec`, up to its
/// capacity.
///
/// Because this uses the capacity, and never reallocates, the `Vec` should
/// have some non-empty spare capacity.
#[cfg(feature = "alloc")]
pub struct Extend<'a, T>(&'a mut Vec<T>);

/// Construct an [`Extend`], which implements [`Buffer`].
///
/// This wraps a `Vec` and uses the spare capacity of the `Vec` as the buffer
/// to receive data in, automaically resizing the `Vec` to include the
/// received elements.
///
/// This uses the existing capacity, and never allocates, so the `Vec` should
/// have some non-empty spare capacity!
///
/// # Examples
///
/// ```
/// # fn test(input: &std::fs::File) -> rustix::io::Result<()> {
/// use rustix::io::{read, Errno};
/// use rustix::buffer::extend;
///
/// let mut buf = Vec::with_capacity(1024);
/// match read(input, extend(&mut buf)) {
/// Ok(0) => { /* end of stream */ }
/// Ok(n) => { /* `buf` is now `n` bytes longer */ }
/// Err(Errno::INTR) => { /* `buf` is unmodified */ }
/// Err(e) => { return Err(e); }
/// }
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "alloc")]
pub fn extend<'a, T>(v: &'a mut Vec<T>) -> Extend<'a, T> {
Extend(v)
}

#[cfg(feature = "alloc")]
impl<'a, T> private::Sealed<T> for Extend<'a, T> {
/// The mutated `Vec` reflects the number of bytes read. We also return
/// this number, and a value of 0 indicates the end of the stream has
/// been reached.
type Result = usize;

#[inline]
fn as_raw_parts_mut(&mut self) -> (*mut T, usize) {
let spare = self.0.spare_capacity_mut();

debug_assert!(!spare.is_empty(), "`extend` uses spare capacity, and never allocates new memory, so the `Vec` passed to it should have some spare capacity.");

(spare.as_mut_ptr().cast(), spare.len())
}

#[inline]
unsafe fn finish(self, len: usize) -> Self::Result {
// We initialized `len` elements; extend the `Vec` to include them.
self.0.set_len(self.0.len() + len);
len
}
}

/// Split an uninitialized byte slice into initialized and uninitialized parts.
///
/// # Safety
Expand All @@ -24,10 +212,86 @@ pub(super) unsafe fn split_init(
(init, uninit)
}

mod private {
pub trait Sealed<T> {
/// The result of the process operation.
type Result;

/// Return a pointer and length for this buffer.
fn as_raw_parts_mut(&mut self) -> (*mut T, usize);

/// Convert a finished buffer pointer into its result.
///
/// # Safety
///
/// At least `len` bytes of the buffer must now be initialized.
unsafe fn finish(self, len: usize) -> Self::Result;
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[cfg(not(windows))]
fn test_buffer() {
use crate::io::read;
use core::mem::MaybeUninit;

let input = std::fs::File::open("Cargo.toml").unwrap();

let mut buf = vec![0_u8; 3];
buf.reserve(32);
let _x: usize = read(&input, extend(&mut buf)).unwrap();
let _x: (&mut [u8], &mut [MaybeUninit<u8>]) =
read(&input, buf.spare_capacity_mut()).unwrap();
let _x: usize = read(&input, &mut buf).unwrap();
let _x: usize = read(&input, &mut *buf).unwrap();
let _x: usize = read(&input, &mut buf[..]).unwrap();
let _x: usize = read(&input, &mut (*buf)[..]).unwrap();

let mut buf = [0, 0, 0];
let _x: usize = read(&input, &mut buf).unwrap();
let _x: usize = read(&input, &mut buf[..]).unwrap();

let mut buf = [
MaybeUninit::uninit(),
MaybeUninit::uninit(),
MaybeUninit::uninit(),
];
let _x: (&mut [u8], &mut [MaybeUninit<u8>]) = read(&input, &mut buf).unwrap();
let _x: (&mut [u8], &mut [MaybeUninit<u8>]) = read(&input, &mut buf[..]).unwrap();

let mut buf = vec![
MaybeUninit::uninit(),
MaybeUninit::uninit(),
MaybeUninit::uninit(),
];
let _x: (&mut [u8], &mut [MaybeUninit<u8>]) = read(&input, &mut buf).unwrap();
let _x: (&mut [u8], &mut [MaybeUninit<u8>]) = read(&input, &mut buf[..]).unwrap();

// This is reduced from src/fs/inotify.rs line 177.
struct Wrapper<'a>(&'a mut [u8]);
impl<'a> Wrapper<'a> {
fn read(&mut self) {
let input = std::fs::File::open("Cargo.toml").unwrap();

// Ideally we'd write this.
//let _x: usize = read(&input, self.0).unwrap();
// But we need to write this instead.
let _x: usize = read(&input, &mut *self.0).unwrap();
}
}
let mut buf = vec![0_u8; 3];
let mut wrapper = Wrapper(&mut buf);
wrapper.read();

// Why does this get two error messages?
//let mut buf = [0, 0, 0];
//let _x = read(&input, buf).unwrap();
}

#[test]
fn test_split_init() {
let mut input_array = [
Expand Down
4 changes: 2 additions & 2 deletions src/fs/inotify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use crate::backend::fs::syscalls;
use crate::fd::{AsFd, OwnedFd};
use crate::ffi::CStr;
use crate::io;
use crate::io::{read_uninit, Errno};
use crate::io::{read, Errno};
use core::mem::{align_of, size_of, MaybeUninit};
use linux_raw_sys::general::inotify_event;

Expand Down Expand Up @@ -174,7 +174,7 @@ impl<'buf, Fd: AsFd> Reader<'buf, Fd> {
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> io::Result<Event<'_>> {
if self.is_buffer_empty() {
match read_uninit(self.fd.as_fd(), self.buf).map(|(init, _)| init.len()) {
match read(self.fd.as_fd(), &mut *self.buf).map(|(init, _)| init.len()) {
Ok(0) => return Err(Errno::INVAL),
Ok(bytes_read) => {
self.initialized = bytes_read;
Expand Down
31 changes: 6 additions & 25 deletions src/io/read_write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#![allow(unsafe_code)]

use crate::buffer::split_init;
use crate::buffer::{split_init, Buffer};
use crate::{backend, io};
use backend::fd::AsFd;
use core::mem::MaybeUninit;
Expand All @@ -16,9 +16,6 @@ pub use backend::io::types::ReadWriteFlags;

/// `read(fd, buf)`—Reads from a stream.
///
/// This takes a `&mut [u8]` which Rust requires to contain initialized memory.
/// To use an uninitialized buffer, use [`read_uninit`].
///
/// # References
/// - [POSIX]
/// - [Linux]
Expand All @@ -40,27 +37,11 @@ pub use backend::io::types::ReadWriteFlags;
/// [illumos]: https://illumos.org/man/2/read
/// [glibc]: https://sourceware.org/glibc/manual/latest/html_node/I_002fO-Primitives.html#index-reading-from-a-file-descriptor
#[inline]
pub fn read<Fd: AsFd>(fd: Fd, buf: &mut [u8]) -> io::Result<usize> {
unsafe { backend::io::syscalls::read(fd.as_fd(), buf.as_mut_ptr(), buf.len()) }
}

/// `read(fd, buf)`—Reads from a stream.
///
/// This is equivalent to [`read`], except that it can read into uninitialized
/// memory. It returns the slice that was initialized by this function and the
/// slice that remains uninitialized.
#[inline]
pub fn read_uninit<Fd: AsFd>(
fd: Fd,
buf: &mut [MaybeUninit<u8>],
) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
// Get number of initialized bytes.
let length = unsafe {
backend::io::syscalls::read(fd.as_fd(), buf.as_mut_ptr().cast::<u8>(), buf.len())
};

// Split into the initialized and uninitialized portions.
Ok(unsafe { split_init(buf, length?) })
pub fn read<Fd: AsFd, Buf: Buffer<u8>>(fd: Fd, mut buf: Buf) -> io::Result<Buf::Result> {
// SAFETY: `read` behaves.
let len = unsafe { backend::io::syscalls::read(fd.as_fd(), buf.as_raw_parts_mut())? };
// SAFETY: `read` works.
unsafe { Ok(buf.finish(len)) }
}

/// `write(fd, buf)`—Writes to a stream.
Expand Down
3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ extern crate static_assertions;
#[allow(unused_imports)]
mod static_assertions;

// Internal utilities.
mod buffer;
pub mod buffer;
#[cfg(not(windows))]
#[macro_use]
pub(crate) mod cstr;
Expand Down
Loading
Loading