use core::mem::MaybeUninit;
use crate::io;
pub type ReadResult = io::Result<()>;
pub struct ReadBuf<'a> {
buf: &'a mut [MaybeUninit<u8>],
init: usize,
filled: usize,
}
impl<'a> ReadBuf<'a> {
pub(crate) fn uninit(buf: &'a mut [MaybeUninit<u8>]) -> Self {
Self {
buf,
init: 0,
filled: 0,
}
}
#[must_use]
pub fn capacity(&self) -> usize {
self.buf.len()
}
#[must_use]
pub fn filled(&self) -> usize {
self.filled
}
pub fn init_mut(&mut self) -> &mut [u8] {
if let Some(rest) = self.buf.get_mut(self.init..) {
rest.fill(MaybeUninit::new(0));
}
self.init = self.buf.len();
#[expect(unsafe_code, reason = "see safety")]
unsafe {
self.bytes_mut_unchecked()
}
}
pub fn set_filled(&mut self, filled: usize) -> io::Result<()> {
if filled > self.init {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"read reported more bytes than its buffer holds",
));
}
self.filled = filled;
Ok(())
}
#[must_use]
pub unsafe fn bytes_mut_unchecked(&mut self) -> &mut [u8] {
#[expect(unsafe_code, reason = "see safety")]
unsafe {
core::slice::from_raw_parts_mut(self.buf.as_mut_ptr().cast::<u8>(), self.buf.len())
}
}
pub unsafe fn assume_filled(&mut self, filled: usize) {
debug_assert!(filled <= self.buf.len(), "filled past the buffer");
self.filled = filled;
self.init = self.init.max(filled);
}
pub unsafe fn fill_with(
&mut self,
read: impl FnOnce(&mut [u8]) -> io::Result<usize>,
) -> io::Result<()> {
#[expect(unsafe_code, reason = "see safety")]
let filled = read(unsafe { self.bytes_mut_unchecked() })?;
#[expect(unsafe_code, reason = "see safety")]
unsafe {
self.assume_filled(filled);
}
Ok(())
}
pub(crate) fn append(&mut self, bytes: &[u8]) {
let end = self.filled + bytes.len();
#[expect(clippy::indexing_slicing, reason = "panics by contract")]
let target = &mut self.buf[self.filled..end];
for (to, from) in target.iter_mut().zip(bytes) {
to.write(*from);
}
self.filled = end;
self.init = self.init.max(end);
}
pub(crate) fn portion(&mut self, at: usize, len: usize) -> ReadBuf<'_> {
let init = (self.init.max(at) - at).min(len);
ReadBuf {
#[expect(clippy::indexing_slicing, reason = "panics by contract")]
buf: &mut self.buf[at..at + len],
init,
filled: 0,
}
}
pub(crate) fn extend_from_portion(&mut self, filled: usize) {
#[expect(unsafe_code, reason = "see safety")]
unsafe {
self.assume_filled(self.filled + filled);
}
}
}
pub(crate) fn read_each(
file: &(impl crate::fs::FsFile + ?Sized),
regions: &mut [(u64, ReadBuf<'_>)],
) -> io::Result<()> {
for (offset, buf) in regions {
file.read_at_buf(buf, *offset)?;
if buf.filled() != buf.capacity() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"read_many: short read on a fixed-size block region",
));
}
}
Ok(())
}
#[cfg(all(
target_os = "linux",
any(feature = "io-uring", feature = "io-uring-raw")
))]
pub(crate) trait ReadRegion {
unsafe fn target(&mut self) -> (u64, &mut [u8]);
}
#[cfg(all(
target_os = "linux",
any(feature = "io-uring", feature = "io-uring-raw")
))]
impl ReadRegion for (u64, &mut [u8]) {
unsafe fn target(&mut self) -> (u64, &mut [u8]) {
(self.0, &mut *self.1)
}
}
#[cfg(all(
target_os = "linux",
any(feature = "io-uring", feature = "io-uring-raw")
))]
impl ReadRegion for (u64, ReadBuf<'_>) {
unsafe fn target(&mut self) -> (u64, &mut [u8]) {
#[expect(unsafe_code, reason = "see safety")]
let bytes = unsafe { self.1.bytes_mut_unchecked() };
(self.0, bytes)
}
}
#[cfg(all(
target_os = "linux",
any(feature = "io-uring", feature = "io-uring-raw")
))]
pub(crate) unsafe fn assume_regions_filled(regions: &mut [(u64, ReadBuf<'_>)]) {
for (_, buf) in regions {
#[expect(unsafe_code, reason = "see safety")]
unsafe {
buf.assume_filled(buf.capacity());
}
}
}
#[cfg(test)]
#[expect(clippy::expect_used, reason = "test code")]
mod tests;
#[cfg(all(test, feature = "std"))]
#[expect(clippy::expect_used, clippy::indexing_slicing, reason = "test code")]
mod backend_tests;