Files
RedBear-OS/recipes/drivers/storage/driver-block/src/lib.rs
T
vasilito 15d0707e74 feat: USB storage read/write proof + full Red Bear OS tree sync
Add redbear-usb-storage-check in-guest binary that validates USB mass
storage read and write I/O: discovers /scheme/disk/ devices, writes a
test pattern to sector 2048, reads it back, verifies match, restores
original content. Updates test-usb-storage-qemu.sh with write-proof
verification step.

Includes all accumulated Red Bear OS work: kernel patches, relibc
patches, driver infrastructure, DRM/GPU, KDE recipes, firmware,
validation tooling, build system hardening, and documentation.
2026-05-03 23:03:24 +01:00

662 lines
22 KiB
Rust

use std::cmp;
use std::future::{Future, IntoFuture};
use std::io::{self, Read, Seek, SeekFrom};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fmt::Write;
use std::str;
use std::task::Poll;
use executor::LocalExecutor;
use libredox::Fd;
use partitionlib::{LogicalBlockSize, PartitionTable};
use redox_scheme::scheme::{register_scheme_inner, SchemeAsync, SchemeState};
use redox_scheme::{CallerCtx, OpenResult, RequestKind, Response, SignalBehavior, Socket};
use scheme_utils::{FpathWriter, HandleMap};
use syscall::dirent::DirentBuf;
use syscall::schemev2::NewFdFlags;
use syscall::{
Error, Result, Stat, EACCES, EAGAIN, EBADF, EINTR, EINVAL, EISDIR, ENOENT, ENOLCK, EOPNOTSUPP,
EOVERFLOW, EWOULDBLOCK, MODE_DIR, MODE_FILE, O_DIRECTORY, O_STAT,
};
/// Split the read operation into a series of block reads.
/// `read_fn` will be called with a block number to be read, and a buffer to be filled.
/// `read_fn` must return a full block of data.
/// Result will be the number of bytes read.
fn block_read(
offset: u64,
blksize: u32,
buf: &mut [u8],
mut read_fn: impl FnMut(u64, &mut [u8]) -> io::Result<()>,
) -> io::Result<usize> {
// TODO: Yield sometimes, perhaps after a few blocks or something.
if buf.len() == 0 {
return Ok(0);
}
let to_copy = usize::try_from(
offset.saturating_add(u64::try_from(buf.len()).expect("buf.len() larger than u64"))
- offset,
)
.expect("bytes to copy larger than usize");
let mut curr_buf = &mut buf[..to_copy];
let mut curr_offset = offset;
let blk_size = usize::try_from(blksize).expect("blksize larger than usize");
let mut total_read = 0;
let mut block_bytes = [0u8; 4096];
let block_bytes = &mut block_bytes[..blk_size];
while curr_buf.len() > 0 {
// TODO: Async/await? I mean, shouldn't AHCI be async?
let blk_offset =
usize::try_from(curr_offset % u64::from(blksize)).expect("usize smaller than blksize");
let to_copy = cmp::min(curr_buf.len(), blk_size - blk_offset);
assert!(blk_offset + to_copy <= blk_size);
read_fn(curr_offset / u64::from(blksize), block_bytes)?;
let src_buf = &block_bytes[blk_offset..];
curr_buf[..to_copy].copy_from_slice(&src_buf[..to_copy]);
curr_buf = &mut curr_buf[to_copy..];
curr_offset += u64::try_from(to_copy).expect("bytes to copy larger than u64");
total_read += to_copy;
}
Ok(total_read)
}
pub trait Disk {
fn block_size(&self) -> u32;
fn size(&self) -> u64;
// These operate on a whole multiple of the block size
// FIXME maybe only operate on a single block worth of data?
async fn read(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<usize>;
async fn write(&mut self, block: u64, buffer: &[u8]) -> syscall::Result<usize>;
}
impl<T: Disk + ?Sized> Disk for Box<T> {
fn block_size(&self) -> u32 {
(**self).block_size()
}
fn size(&self) -> u64 {
(**self).size()
}
async fn read(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
(**self).read(block, buffer).await
}
async fn write(&mut self, block: u64, buffer: &[u8]) -> syscall::Result<usize> {
(**self).write(block, buffer).await
}
}
pub struct DiskWrapper<T> {
pub disk: T,
pub pt: Option<PartitionTable>,
}
impl<T: Disk> DiskWrapper<T> {
pub fn pt(disk: &mut T, executor: &impl ExecutorTrait) -> Option<PartitionTable> {
let bs = match disk.block_size() {
512 => LogicalBlockSize::Lb512,
4096 => LogicalBlockSize::Lb4096,
_ => return None,
};
struct Device<'a, D: Disk, E: ExecutorTrait> {
disk: &'a mut D,
executor: &'a E,
offset: u64,
}
impl<'a, D: Disk, E: ExecutorTrait> Seek for Device<'a, D, E> {
fn seek(&mut self, from: SeekFrom) -> io::Result<u64> {
let size = i64::try_from(self.disk.size()).or(Err(io::Error::new(
io::ErrorKind::Other,
"Disk larger than 2^63 - 1 bytes",
)))?;
self.offset = match from {
SeekFrom::Start(new_pos) => cmp::min(self.disk.size(), new_pos),
SeekFrom::Current(new_pos) => {
cmp::max(0, cmp::min(size, self.offset as i64 + new_pos)) as u64
}
SeekFrom::End(new_pos) => cmp::max(0, cmp::min(size + new_pos, size)) as u64,
};
Ok(self.offset)
}
}
// TODO: Perhaps this impl should be used in the rest of the scheme.
impl<'a, D: Disk, E: ExecutorTrait> Read for Device<'a, D, E> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let blksize = self.disk.block_size();
let size_in_blocks = self.disk.size() / u64::from(blksize);
let disk = &mut self.disk;
let read_block = |block: u64, block_bytes: &mut [u8]| {
if block >= size_in_blocks {
return Err(io::Error::from_raw_os_error(syscall::EOVERFLOW));
}
let bytes = self.executor.block_on(disk.read(block, block_bytes))?;
assert_eq!(bytes, block_bytes.len());
Ok(())
};
let bytes_read = block_read(self.offset, blksize, buf, read_block)?;
self.offset += bytes_read as u64;
Ok(bytes_read)
}
}
partitionlib::get_partitions(
&mut Device {
disk,
offset: 0,
executor,
},
bs,
)
.ok()
.flatten()
}
pub fn new(mut disk: T, executor: &impl ExecutorTrait) -> Self {
Self {
pt: Self::pt(&mut disk, executor),
disk,
}
}
pub fn disk(&self) -> &T {
&self.disk
}
pub fn disk_mut(&mut self) -> &mut T {
&mut self.disk
}
pub fn block_size(&self) -> u32 {
self.disk.block_size()
}
pub fn size(&self) -> u64 {
self.disk.size()
}
pub async fn read(
&mut self,
part_num: Option<usize>,
block: u64,
buf: &mut [u8],
) -> syscall::Result<usize> {
if buf.len() as u64 % u64::from(self.disk.block_size()) != 0 {
return Err(Error::new(EINVAL));
}
if let Some(part_num) = part_num {
let part = self
.pt
.as_ref()
.ok_or(syscall::Error::new(EBADF))?
.partitions
.get(part_num)
.ok_or(syscall::Error::new(EBADF))?;
if block >= part.size {
return Err(syscall::Error::new(EOVERFLOW));
}
let abs_block = part.start_lba + block;
self.disk.read(abs_block, buf).await
} else {
self.disk.read(block, buf).await
}
}
pub async fn write(
&mut self,
part_num: Option<usize>,
block: u64,
buf: &[u8],
) -> syscall::Result<usize> {
if buf.len() as u64 % u64::from(self.disk.block_size()) != 0 {
return Err(Error::new(EINVAL));
}
if let Some(part_num) = part_num {
let part = self
.pt
.as_ref()
.ok_or(syscall::Error::new(EBADF))?
.partitions
.get(part_num)
.ok_or(syscall::Error::new(EBADF))?;
if block >= part.size {
return Err(syscall::Error::new(EOVERFLOW));
}
let abs_block = part.start_lba + block;
self.disk.write(abs_block, buf).await
} else {
self.disk.write(block, buf).await
}
}
}
pub struct DiskScheme<T> {
inner: DiskSchemeInner<T>,
state: SchemeState,
}
impl<T: Disk> DiskScheme<T> {
pub fn new(
daemon: Option<daemon::Daemon>,
scheme_name: String,
disks: BTreeMap<u32, T>,
executor: &impl ExecutorTrait,
) -> Self {
assert!(scheme_name.starts_with("disk"));
let socket = Socket::nonblock().expect("failed to create disk scheme");
let mut inner = DiskSchemeInner {
scheme_name: scheme_name,
socket,
disks: disks
.into_iter()
.map(|(k, disk)| (k, DiskWrapper::new(disk, executor)))
.collect(),
handles: HandleMap::new(),
};
let cap_id = inner.scheme_root().expect("failed to get this scheme root");
register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id)
.expect("failed to register disk scheme root");
if let Some(daemon) = daemon {
daemon.ready();
}
Self {
inner,
state: SchemeState::new(),
}
}
pub fn event_handle(&self) -> &Fd {
self.inner.socket.inner()
}
/// Process pending and new requests.
///
/// This needs to be called each time there is a new event on the scheme.
pub async fn tick(&mut self) -> io::Result<()> {
// Handle new scheme requests
loop {
let request = match self.inner.socket.next_request(SignalBehavior::Interrupt) {
Ok(Some(request)) => request,
Ok(None) => {
// Scheme likely got unmounted
// TODO: return this to caller instead
std::process::exit(0);
}
Err(error) if error.errno == EWOULDBLOCK || error.errno == EAGAIN => break,
Err(err) if err.errno == EINTR => continue,
Err(err) => return Err(err.into()),
};
let response = match request.kind() {
RequestKind::Call(call_request) => {
// TODO: Spawn a separate task for each scheme call. This would however require the
// use of a smarter buffer pool (or direct IO, or a buffer per fd) in order to do
// parallel IO. It might also require async-aware locks so that a close() is
// correctly ordered wrt IO on the same fd.
call_request
.handle_async(&mut self.inner, &mut self.state)
.await
}
RequestKind::SendFd(request) => Response::err(EOPNOTSUPP, request),
RequestKind::RecvFd(request) => Response::err(EOPNOTSUPP, request),
RequestKind::Cancellation(_cancellation_request) => {
// FIXME implement cancellation
continue;
}
RequestKind::MsyncMsg | RequestKind::MunmapMsg | RequestKind::MmapMsg => {
unreachable!()
}
RequestKind::OnClose { id } => {
self.inner.on_close(id);
continue;
}
RequestKind::OnDetach { .. } => continue,
};
self.inner
.socket
.write_response(response, SignalBehavior::Restart)?;
}
Ok(())
}
}
enum Handle {
List(Vec<u8>), // entries
Disk(u32), // disk num
Partition(u32, u32), // disk num, part num
SchemeRoot,
}
struct DiskSchemeInner<T> {
scheme_name: String,
socket: Socket,
disks: BTreeMap<u32, DiskWrapper<T>>,
handles: HandleMap<Handle>,
}
pub trait ExecutorTrait {
fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture<Output = O> + 'a) -> O;
}
impl<Hw: executor::Hardware> ExecutorTrait for LocalExecutor<Hw> {
fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture<Output = O> + 'a) -> O {
LocalExecutor::block_on(self, fut)
}
}
#[deprecated = "use custom executor"]
pub struct FuturesExecutor;
#[allow(deprecated)]
impl ExecutorTrait for FuturesExecutor {
fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture<Output = O> + 'a) -> O {
futures::executor::block_on(fut.into_future())
}
}
pub struct TrivialExecutor;
impl ExecutorTrait for TrivialExecutor {
fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture<Output = O> + 'a) -> O {
let mut fut = std::pin::pin!(fut.into_future());
let mut cx = std::task::Context::from_waker(std::task::Waker::noop());
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(v) => return v,
Poll::Pending => {
log::warn!("TrivialExecutor: future wasn't trivial");
continue;
}
}
}
}
}
impl<T: Disk> DiskSchemeInner<T> {
// Checks if any conflicting handles already exist
fn check_locks(&self, disk_i: u32, part_i_opt: Option<u32>) -> Result<()> {
for (_, handle) in self.handles.iter() {
match handle {
Handle::Disk(i) => {
if disk_i == *i {
return Err(Error::new(ENOLCK));
}
}
Handle::Partition(i, p) => {
if disk_i == *i {
match part_i_opt {
Some(part_i) => {
if part_i == *p {
return Err(Error::new(ENOLCK));
}
}
None => {
return Err(Error::new(ENOLCK));
}
}
}
}
_ => (),
}
}
Ok(())
}
}
impl<T: Disk> SchemeAsync for DiskSchemeInner<T> {
fn scheme_root(&mut self) -> Result<usize> {
Ok(self.handles.insert(Handle::SchemeRoot))
}
async fn openat(
&mut self,
dirfd: usize,
path_str: &str,
flags: usize,
_fcntl_flags: u32,
ctx: &CallerCtx,
) -> Result<OpenResult> {
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
return Err(Error::new(EACCES));
}
if ctx.uid != 0 {
return Err(Error::new(EACCES));
}
let path_str = path_str.trim_matches('/');
let handle = if path_str.is_empty() {
if flags & O_DIRECTORY == O_DIRECTORY || flags & O_STAT == O_STAT {
let mut list = String::new();
for (nsid, disk) in self.disks.iter() {
write!(list, "{}\n", nsid).unwrap();
if disk.pt.is_none() {
continue;
}
for part_num in 0..disk.pt.as_ref().unwrap().partitions.len() {
write!(list, "{}p{}\n", nsid, part_num).unwrap();
}
}
Handle::List(list.into_bytes())
} else {
return Err(Error::new(EISDIR));
}
} else if let Some(p_pos) = path_str.chars().position(|c| c == 'p') {
let nsid_str = &path_str[..p_pos];
if p_pos + 1 >= path_str.len() {
return Err(Error::new(ENOENT));
}
let part_num_str = &path_str[p_pos + 1..];
let nsid = nsid_str.parse::<u32>().or(Err(Error::new(ENOENT)))?;
let part_num = part_num_str.parse::<u32>().or(Err(Error::new(ENOENT)))?;
if let Some(disk) = self.disks.get(&nsid) {
if disk
.pt
.as_ref()
.ok_or(Error::new(ENOENT))?
.partitions
.get(part_num as usize)
.is_some()
{
self.check_locks(nsid, Some(part_num))?;
Handle::Partition(nsid, part_num)
} else {
return Err(Error::new(ENOENT));
}
} else {
return Err(Error::new(ENOENT));
}
} else {
let nsid = path_str.parse::<u32>().or(Err(Error::new(ENOENT)))?;
if self.disks.contains_key(&nsid) {
self.check_locks(nsid, None)?;
Handle::Disk(nsid)
} else {
return Err(Error::new(ENOENT));
}
};
let id = self.handles.insert(handle);
Ok(OpenResult::ThisScheme {
number: id,
flags: NewFdFlags::POSITIONED,
})
}
async fn getdents<'buf>(
&mut self,
_id: usize,
_buf: DirentBuf<&'buf mut [u8]>,
_opaque_offset: u64,
) -> Result<DirentBuf<&'buf mut [u8]>> {
// TODO
Err(Error::new(EOPNOTSUPP))
}
async fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
match *self.handles.get(id)? {
Handle::List(ref data) => {
stat.st_mode = MODE_DIR;
stat.st_size = data.len() as u64;
Ok(())
}
Handle::Disk(number) => {
let disk = self.disks.get_mut(&number).ok_or(Error::new(EBADF))?;
stat.st_mode = MODE_FILE;
stat.st_blocks = disk.disk().size() / u64::from(disk.block_size());
stat.st_blksize = disk.block_size();
stat.st_size = disk.size();
Ok(())
}
Handle::Partition(disk_num, part_num) => {
let disk = self.disks.get_mut(&disk_num).ok_or(Error::new(EBADF))?;
let part = disk
.pt
.as_ref()
.ok_or(Error::new(EBADF))?
.partitions
.get(part_num as usize)
.ok_or(Error::new(EBADF))?;
stat.st_mode = MODE_FILE;
stat.st_size = part.size * u64::from(disk.block_size());
stat.st_blocks = part.size;
stat.st_blksize = disk.block_size();
Ok(())
}
Handle::SchemeRoot => Err(Error::new(EBADF)),
}
}
async fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
FpathWriter::with_legacy(buf, &self.scheme_name, |w| {
match *self.handles.get(id)? {
Handle::List(_) => (),
Handle::Disk(number) => {
write!(w, "{number}").unwrap();
}
Handle::Partition(disk_num, part_num) => {
write!(w, "{disk_num}p{part_num}").unwrap();
}
Handle::SchemeRoot => return Err(Error::new(EBADF)),
}
Ok(())
})
}
async fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
match *self.handles.get_mut(id)? {
Handle::List(ref handle) => {
let src = usize::try_from(offset)
.ok()
.and_then(|o| handle.get(o..))
.unwrap_or(&[]);
let count = core::cmp::min(src.len(), buf.len());
buf[..count].copy_from_slice(&src[..count]);
Ok(count)
}
Handle::Disk(number) => {
let disk = self.disks.get_mut(&number).ok_or(Error::new(EBADF))?;
let block = offset / u64::from(disk.block_size());
disk.read(None, block, buf).await
}
Handle::Partition(disk_num, part_num) => {
let disk = self.disks.get_mut(&disk_num).ok_or(Error::new(EBADF))?;
let block = offset / u64::from(disk.block_size());
disk.read(Some(part_num as usize), block, buf).await
}
Handle::SchemeRoot => Err(Error::new(EBADF)),
}
}
async fn write(
&mut self,
id: usize,
buf: &[u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
match *self.handles.get_mut(id)? {
Handle::List(_) => Err(Error::new(EBADF)),
Handle::Disk(number) => {
let disk = self.disks.get_mut(&number).ok_or(Error::new(EBADF))?;
let block = offset / u64::from(disk.block_size());
disk.write(None, block, buf).await
}
Handle::Partition(disk_num, part_num) => {
let disk = self.disks.get_mut(&disk_num).ok_or(Error::new(EBADF))?;
let block = offset / u64::from(disk.block_size());
disk.write(Some(part_num as usize), block, buf).await
}
Handle::SchemeRoot => Err(Error::new(EBADF)),
}
}
async fn fsize(&mut self, id: usize, _ctx: &CallerCtx) -> Result<u64> {
Ok(match *self.handles.get_mut(id)? {
Handle::List(ref handle) => handle.len() as u64,
Handle::Disk(number) => {
let disk = self.disks.get_mut(&number).ok_or(Error::new(EBADF))?;
disk.size()
}
Handle::Partition(disk_num, part_num) => {
let disk = self.disks.get_mut(&disk_num).ok_or(Error::new(EBADF))?;
let part = disk
.pt
.as_ref()
.ok_or(Error::new(EBADF))?
.partitions
.get(part_num as usize)
.ok_or(Error::new(EBADF))?;
part.size * u64::from(disk.block_size())
}
Handle::SchemeRoot => return Err(Error::new(EBADF)),
})
}
}
impl<D: Disk> DiskSchemeInner<D> {
pub fn on_close(&mut self, id: usize) {
let _ = self.handles.remove(id);
}
}