blob: 8bec207c1cafac55af27c3fe5c3c6a8702733498 [file]
// Copyright 2023, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! APIs for reading/writing with non-block-aligned ranges and unaligned buffer.
//!
//! Most block devices require reading/writing in the unit of block and that the input/output
//! buffer satisfies certain alignment (i.e. DMA). This library provides APIs that build on top
//! of them and relax these constraints.
//!
//! A block device is represented by a type that implements the `BlockDevice` trait. The trait
//! provides information on block size, total size, alignment requirement and read/write methods.
//! The library offers two APIs `BlockDevice::read()` and `BlockDevice::write()` for
//! reading/writing with the trait without any alignment requirement on inputs.
//!
//! The trait also provides APIs `BlockDevice::sync_gpt()`, `BlockDevice::read_gpt_partition()` and
//! `BlockDevice::write_gpt_partition()` for loading/repairing, reading and writing GPT partitions.
//!
//! # Examples
//!
//! ```rust
//! use gbl_storage::{BlockDevice, required_scratch_size};
//!
//! pub struct RamBlockDevice {
//! storage: std::vec::Vec<u8>,
//! }
//!
//! impl BlockDevice for RamBlockDevice {
//! fn block_size(&mut self) -> u64 { 512 }
//!
//! fn num_blocks(&mut self) -> u64 { self.storage.len() as u64 / self.block_size() }
//!
//! fn alignment(&mut self) -> u64 { 64 }
//!
//! fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> bool {
//! let start = blk_offset * self.block_size();
//! let end = start + out.len() as u64;
//! out.clone_from_slice(&self.storage[start as usize..end as usize]);
//! true
//! }
//!
//! fn write_blocks(&mut self, blk_offset: u64, data: &[u8]) -> bool {
//! let start = blk_offset * self.block_size();
//! let end = start + data.len() as u64;
//! self.storage[start as usize..end as usize].clone_from_slice(&data);
//! true
//! }
//! }
//!
//! let mut ram_block_dev = RamBlockDevice {storage: vec![0u8; 32 * 1024]};
//!
//! // Prepare a scratch buffer, size calculated with `required_scratch_size()`.
//! let mut scratch = vec![0u8; required_scratch_size(&mut ram_block_dev).unwrap()];
//!
//! // Read/write with arbitrary range and buffer without worrying about alignment.
//! let mut out = vec![0u8;1234];
//! ram_block_dev.read(4321, &mut out[..], &mut scratch).unwrap();
//! let mut data = vec![0u8;5678];
//! // Mutable input. More efficient
//! ram_block_dev.write(8765, &mut data[..], &mut scratch).unwrap();
//! // Immutable input. Works too but not as efficient.
//! ram_block_dev.write(8765, &data[..], &mut scratch).unwrap();
//! ```
#![cfg_attr(not(test), no_std)]
use core::cmp::min;
// Selective export of submodule types.
mod gpt;
pub use gpt::Gpt;
pub use gpt::GptEntry;
/// The type of Result used in this library.
pub type Result<T> = core::result::Result<T, StorageError>;
/// Error code for this library.
#[derive(Debug, Copy, Clone)]
pub enum StorageError {
ArithmeticOverflow,
OutOfRange,
ScratchTooSmall,
BlockDeviceError,
InvalidInput,
NoValidGpt,
NotExist,
U64toUSizeOverflow,
}
impl Into<&'static str> for StorageError {
fn into(self) -> &'static str {
match self {
StorageError::ArithmeticOverflow => "Arithmetic overflow",
StorageError::OutOfRange => "Out of range",
StorageError::ScratchTooSmall => "Not enough scratch buffer",
StorageError::BlockDeviceError => "Block device error",
StorageError::InvalidInput => "Invalid input",
StorageError::NoValidGpt => "GPT not found",
StorageError::NotExist => "Not exists",
StorageError::U64toUSizeOverflow => "u64 to usize fails",
}
}
}
impl core::fmt::Display for StorageError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", Into::<&'static str>::into(*self))
}
}
/// A trait for reading/writing a block device
pub trait BlockDevice {
/// Returns the block size of the block device.
fn block_size(&mut self) -> u64;
/// Returns the total number of blocks of the block device.
fn num_blocks(&mut self) -> u64;
/// Returns the alignment requirement for buffers passed to the `write_blocks()` and
/// `read_blocks()` methods. For example, many block device drivers use DMA for data transfer,
/// which typically requires that the buffer address for DMA be aligned to 16/32/64 bytes etc.
/// If the block device has no alignment requirement, it can return 1.
fn alignment(&mut self) -> u64;
/// Read blocks of data from the block device
///
/// # Args
///
/// * `blk_offset`: Offset in number of blocks.
///
/// * `out`: Buffer to store the read data. Callers of this method ensure that it is
/// aligned according to alignment() and `out.len()` is multiples of `block_size()`.
///
/// # Returns
///
/// Returns true if exactly out.len() number of bytes are read. Otherwise false.
fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> bool;
/// Write blocks of data to the block device
///
/// # Args
///
/// * `blk_offset`: Offset in number of blocks.
///
/// * `data`: Data to write. Callers of this method ensure that it is aligned according to
/// `alignment()` and `data.len()` is multiples of `block_size()`.
///
/// # Returns
///
/// Returns true if exactly data.len() number of bytes are written. Otherwise false.
fn write_blocks(&mut self, blk_offset: u64, data: &[u8]) -> bool;
// TODO(298485845): Investigate adding rust doc test for the public document.
/// Read data from the block device.
///
/// # Args
///
/// * `offset`: Offset in number of bytes.
///
/// * `out`: Buffer to store the read data.
///
/// * `scratch`: A buffer for the API to handle misalignment and non-block-aligned read range.
/// Its size must be at least `required_scratch_size(blk_dev)`.
///
/// * Returns success when exactly `out.len()` number of bytes are read.
fn read(&mut self, offset: u64, out: &mut [u8], scratch: &mut [u8]) -> Result<()> {
read(self, offset, out, scratch)
}
/// Write data to the device.
///
/// # Args
///
/// * `offset`: Offset in number of bytes.
///
/// * `data`: Data to write. It can be `&[u8]`, `&mut [u8]` or other types that implement the
/// `WriteBuffer` trait.
///
/// * If `data` is an immutable bytes slice `&[u8]` or doesn't support `Bytes::as_mut_slice()`,
/// when offset`/`data.len()` is not aligned to `blk_dev.block_size()` or data.as_ptr() is
/// not aligned to `blk_dev.alignment()`, the API may reduce to a block by block
/// read-modify-write in the worst case, which can be inefficient.
///
/// * If `data` is a mutable bytes slice `&mut [u8]`, or support `Bytes::as_mut_slice()`. The
/// API enables optimization which temporarily changes `data` layout internally and reduces
/// the number of calls to `blk_dev.write_blocks()` down to O(1) regardless of inputs
/// alignment. This is the recommended usage.
///
/// * `scratch`: A buffer for the API to handle buffer and write range misalignment. Its size
/// must be at least `required_scratch_size(blk_dev)`.
///
/// * Returns success when exactly `data.len()` number of bytes are written.
fn write<B>(&mut self, offset: u64, data: B, scratch: &mut [u8]) -> Result<()>
where
B: WriteBuffer,
{
write(self, offset, data, scratch)
}
/// Parse and sync GPT from a block device.
///
/// # Args
///
/// * The API validates and restores primary/secondary GPT header.
///
/// * `blk_dev`: The block device to read GPT.
///
/// * `scratch`: A buffer for the API to handle buffer and write range misalignment. Its size
/// must be at least `required_scratch_size(blk_dev)`.
///
/// # Returns
///
/// Returns success if GPT is loaded/restored successfully.
fn sync_gpt(&mut self, gpt: &mut Gpt, scratch: &mut [u8]) -> Result<()> {
gpt::gpt_sync(self, gpt, scratch)
}
/// Read a GPT partition on a block device
///
/// # Args
///
/// * `gpt`: An instance of Gpt. If the GPT comes from the same block device, this should be
/// obtained from calling sync_gpt() of this BlockDevice.
///
/// * `part_name`: Name of the partition.
///
/// * `offset`: Offset in number of bytes into the partition.
///
/// * `out`: Buffer to store the read data.
///
/// * `scratch`: A scratch buffer for reading/writing `blk_dev`. Must have a size at least
/// `required_scratch_size(blk_dev)`. See `BlockDevice::read()/write()` for more details.
///
/// # Returns
///
/// Returns success when exactly `out.len()` of bytes are read successfully.
fn read_gpt_partition(
&mut self,
gpt: &Gpt,
part_name: &str,
offset: u64,
out: &mut [u8],
scratch: &mut [u8],
) -> Result<()> {
gpt::read_gpt_partition(self, gpt, part_name, offset, out, scratch)
}
/// Write a GPT partition on a block device
///
/// # Args
///
/// * `gpt`: An instance of Gpt. If the GPT comes from the same block device, this should be
/// obtained from calling sync_gpt() of this BlockDevice.
///
/// * `part_name`: Name of the partition.
///
/// * `offset`: Offset in number of bytes into the partition.
///
/// * `data`: Data to write. See `data` passed to `BlockDevice::write()` for details.
///
/// * `scratch`: A scratch buffer for reading/writing `blk_dev`. Must have a size at least
/// `required_scratch_size(blk_dev)`. See `BlockDevice::read()/write()` for more details.
///
/// # Returns
///
/// Returns success when exactly `data.len()` of bytes are written successfully.
fn write_gpt_partition<B>(
&mut self,
gpt: &Gpt,
part_name: &str,
offset: u64,
data: B,
scratch: &mut [u8],
) -> Result<()>
where
B: WriteBuffer,
{
gpt::write_gpt_partition(self, gpt, part_name, offset, data, scratch)
}
}
/// Add two u64 integers and check overflow
fn add(lhs: u64, rhs: u64) -> Result<u64> {
lhs.checked_add(rhs).ok_or_else(|| StorageError::ArithmeticOverflow)
}
/// Substract two u64 integers and check overflow
fn sub(lhs: u64, rhs: u64) -> Result<u64> {
lhs.checked_sub(rhs).ok_or_else(|| StorageError::ArithmeticOverflow)
}
/// Calculate remainders and check overflow
fn rem(lhs: u64, rhs: u64) -> Result<u64> {
lhs.checked_rem(rhs).ok_or_else(|| StorageError::ArithmeticOverflow)
}
/// Multiply two numbers and check overflow
fn mul(lhs: u64, rhs: u64) -> Result<u64> {
lhs.checked_mul(rhs).ok_or_else(|| StorageError::ArithmeticOverflow)
}
/// Divide two numbers and check overflow
fn div(lhs: u64, rhs: u64) -> Result<u64> {
lhs.checked_div(rhs).ok_or_else(|| StorageError::ArithmeticOverflow)
}
/// Check if `value` is aligned to (multiples of) `alignment`
/// It can fail if the remainider calculation fails overflow check.
fn is_aligned(value: u64, alignment: u64) -> Result<bool> {
Ok(rem(value, alignment)? == 0)
}
/// Check if `buffer` address is aligned to `alignment`
/// It can fail if the remainider calculation fails overflow check.
fn is_buffer_aligned(buffer: &[u8], alignment: u64) -> Result<bool> {
is_aligned(buffer.as_ptr() as u64, alignment)
}
/// Round down `size` according to `alignment`.
/// It can fail if any arithmetic operation fails overflow check
fn round_down(size: u64, alignment: u64) -> Result<u64> {
sub(size, rem(size, alignment)?)
}
/// Round up `size` according to `alignment`.
/// It can fail if any arithmetic operation fails overflow check
fn round_up(size: u64, alignment: u64) -> Result<u64> {
// equivalent to round_down(size + alignment - 1, alignment)
round_down(add(size, sub(alignment, 1)?)?, alignment)
}
/// Check and convert u64 into usize
fn to_usize(val: u64) -> Result<usize> {
val.try_into().map_err(|_| StorageError::U64toUSizeOverflow)
}
/// Check read/write range and calculate offset in number of blocks.
fn check_range(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
buffer: &[u8],
) -> Result<u64> {
// The following should be invariants if implementation is correct.
debug_assert!(is_aligned(offset, blk_dev.block_size())?);
debug_assert!(is_aligned(buffer.len() as u64, blk_dev.block_size())?);
debug_assert!(is_buffer_aligned(buffer, blk_dev.alignment())?);
let blk_offset = offset / blk_dev.block_size();
let blk_count = buffer.len() as u64 / blk_dev.block_size();
match add(blk_offset, blk_count)? <= blk_dev.num_blocks() {
true => Ok(blk_offset),
false => Err(StorageError::OutOfRange),
}
}
/// Read with block-aligned offset, length and aligned buffer
fn read_aligned_all(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
out: &mut [u8],
) -> Result<()> {
let blk_offset = check_range(blk_dev, offset, out)?;
if blk_dev.read_blocks(blk_offset, out) {
return Ok(());
}
Err(StorageError::BlockDeviceError)
}
/// Read with block-aligned offset and aligned buffer. Size don't need to be block aligned.
/// |~~~~~~~~~read~~~~~~~~~|
/// |---------|---------|---------|
fn read_aligned_offset_and_buffer(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
out: &mut [u8],
scratch: &mut [u8],
) -> Result<()> {
debug_assert!(is_aligned(offset, blk_dev.block_size())?);
debug_assert!(is_buffer_aligned(out, blk_dev.alignment())?);
let aligned_read = round_down(out.len() as u64, blk_dev.block_size())?;
if aligned_read > 0 {
read_aligned_all(blk_dev, offset, &mut out[..to_usize(aligned_read)?])?;
}
let unaligned = &mut out[to_usize(aligned_read)?..];
if unaligned.len() == 0 {
return Ok(());
}
// Read unalinged part.
let block_scratch = &mut scratch[..to_usize(blk_dev.block_size())?];
read_aligned_all(blk_dev, add(offset, aligned_read)?, block_scratch)?;
unaligned.clone_from_slice(&block_scratch[..unaligned.len()]);
Ok(())
}
/// Read with aligned buffer. Offset and size don't need to be block aligned.
/// Case 1:
/// |~~~~~~read~~~~~~~|
/// |------------|------------|
/// Case 2:
/// |~~~read~~~|
/// |---------------|--------------|
fn read_aligned_buffer(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
out: &mut [u8],
scratch: &mut [u8],
) -> Result<()> {
debug_assert!(is_buffer_aligned(out, blk_dev.alignment())?);
if is_aligned(offset, blk_dev.block_size())? {
return read_aligned_offset_and_buffer(blk_dev, offset, out, scratch);
}
let aligned_start =
min(round_up(offset, blk_dev.block_size())?, add(offset, out.len() as u64)?);
let aligned_relative_offset = sub(aligned_start, offset)?;
if aligned_relative_offset < out.len() as u64 {
if is_buffer_aligned(&out[to_usize(aligned_relative_offset)?..], blk_dev.alignment())? {
// If new output address is aligned, read directly.
read_aligned_offset_and_buffer(
blk_dev,
aligned_start,
&mut out[to_usize(aligned_relative_offset)?..],
scratch,
)?;
} else {
// Otherwise read into `out` (assumed aligned) and memmove to the correct
// position
let read_len = sub(out.len() as u64, aligned_relative_offset)?;
read_aligned_offset_and_buffer(
blk_dev,
aligned_start,
&mut out[..to_usize(read_len)?],
scratch,
)?;
out.copy_within(..to_usize(read_len)?, to_usize(aligned_relative_offset)?);
}
}
// Now read the unaligned part
let block_scratch = &mut scratch[..to_usize(blk_dev.block_size())?];
let round_down_offset = round_down(offset, blk_dev.block_size())?;
read_aligned_all(blk_dev, round_down_offset, block_scratch)?;
let offset_relative = sub(offset, round_down_offset)?;
let unaligned = &mut out[..to_usize(aligned_relative_offset)?];
unaligned.clone_from_slice(
&block_scratch
[to_usize(offset_relative)?..to_usize(add(offset_relative, unaligned.len() as u64)?)?],
);
Ok(())
}
/// Returns the necessary size of `scratch` to pass to `read()` for the given
/// `blk_dev`
pub fn required_scratch_size(blk_dev: &mut (impl BlockDevice + ?Sized)) -> Result<usize> {
// block_size() + 2 * alignment()
// This guarantees that we can craft out two aligned scratch buffers:
// [u8; blk_dev.alignment()] and [u8; blk_dev.block_size())] respectively. They are
// needed for handing buffer and read/write range misalignment.
to_usize(add(mul(2, blk_dev.alignment())?, blk_dev.block_size())?)
}
// Partition a scratch into two aligned parts: [u8; alignment()] and [u8; block_size())]
// for handling block and buffer misalignment respecitvely.
fn split_scratch<'a>(
blk_dev: &mut (impl BlockDevice + ?Sized),
scratch: &'a mut [u8],
) -> Result<(&'a mut [u8], &'a mut [u8])> {
if scratch.len() < required_scratch_size(blk_dev)? {
return Err(StorageError::ScratchTooSmall);
}
let addr_value = scratch.as_ptr() as u64;
let aligned_start = sub(round_up(addr_value, blk_dev.alignment())?, addr_value)?;
Ok(scratch[to_usize(aligned_start)?..].split_at_mut(to_usize(blk_dev.alignment())?))
}
/// Read with no alignment requirement.
fn read(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
out: &mut [u8],
scratch: &mut [u8],
) -> Result<()> {
let (buffer_alignment_scratch, block_alignment_scratch) = split_scratch(blk_dev, scratch)?;
if is_buffer_aligned(out, blk_dev.alignment())? {
return read_aligned_buffer(blk_dev, offset, out, block_alignment_scratch);
}
// Buffer misalignment:
// Case 1:
// |~~~~~~~~~~~~buffer~~~~~~~~~~~~|
// |----------------------|---------------------|
// blk_dev.alignment()
//
// Case 2:
// |~~~~~~buffer~~~~~|
// |----------------------|---------------------|
// blk_dev.alignment()
let out_addr_value = out.as_ptr() as u64;
let unaligned_read =
min(sub(round_up(out_addr_value, blk_dev.alignment())?, out_addr_value)?, out.len() as u64);
// Read unaligned part
let unaligned_out = &mut buffer_alignment_scratch[..to_usize(unaligned_read)?];
read_aligned_buffer(blk_dev, offset, unaligned_out, block_alignment_scratch)?;
out[..to_usize(unaligned_read)?].clone_from_slice(unaligned_out);
if unaligned_read == out.len() as u64 {
return Ok(());
}
// Read aligned part
read_aligned_buffer(
blk_dev,
add(offset, unaligned_read)?,
&mut out[to_usize(unaligned_read)?..],
block_alignment_scratch,
)
}
fn write_aligned_all(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
data: &[u8],
) -> Result<()> {
let blk_offset = check_range(blk_dev, offset, data)?;
if blk_dev.write_blocks(blk_offset, data) {
return Ok(());
}
Err(StorageError::BlockDeviceError)
}
/// Write with block-aligned offset and aligned buffer. `data.len()` can be unaligned.
/// |~~~~~~~~~size~~~~~~~~~|
/// |---------|---------|---------|
fn write_aligned_offset_and_buffer(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
data: &[u8],
scratch: &mut [u8],
) -> Result<()> {
debug_assert!(is_aligned(offset, blk_dev.block_size())?);
debug_assert!(is_buffer_aligned(data, blk_dev.alignment())?);
let aligned_write = round_down(data.len() as u64, blk_dev.block_size())?;
if aligned_write > 0 {
write_aligned_all(blk_dev, offset, &data[..to_usize(aligned_write)?])?;
}
let unaligned = &data[to_usize(aligned_write)?..];
if unaligned.len() == 0 {
return Ok(());
}
// Perform read-modify-write for the unaligned part
let unaligned_start = add(offset, aligned_write)?;
let block_scratch = &mut scratch[..to_usize(blk_dev.block_size())?];
read_aligned_all(blk_dev, unaligned_start, block_scratch)?;
block_scratch[..unaligned.len()].clone_from_slice(unaligned);
write_aligned_all(blk_dev, unaligned_start, block_scratch)
}
/// Write data to the device with immutable input bytes slice. However, if `offset`/`data.len()`
/// is not aligned to `blk_dev.block_size()` or data.as_ptr() is not aligned to
/// `blk_dev.alignment()`, the API may reduce to a block by block read-modify-write in the worst
/// case.
fn write_bytes(
blk_dev: &mut (impl BlockDevice + ?Sized),
mut offset: u64,
data: &[u8],
scratch: &mut [u8],
) -> Result<()> {
let (_, scratch) = split_scratch(blk_dev, scratch)?;
let block_size = blk_dev.block_size();
let block_scratch = &mut scratch[..to_usize(block_size)?];
let mut data_offset = 0;
while data_offset < data.len() as u64 {
if is_aligned(offset, blk_dev.block_size())?
&& is_buffer_aligned(&data[to_usize(data_offset)?..], blk_dev.alignment())?
{
return write_aligned_offset_and_buffer(
blk_dev,
offset,
&data[to_usize(data_offset)?..],
scratch,
);
}
let block_offset = round_down(offset, block_size)?;
let copy_offset = offset - block_offset;
let copy_size = min(data[to_usize(data_offset)?..].len() as u64, block_size - copy_offset);
if copy_size < block_size {
// Partial block copy. Perform read-modify-write
read_aligned_all(blk_dev, block_offset, block_scratch)?;
}
block_scratch[to_usize(copy_offset)?..to_usize(copy_offset + copy_size)?]
.clone_from_slice(&data[to_usize(data_offset)?..to_usize(data_offset + copy_size)?]);
write_aligned_all(blk_dev, block_offset, block_scratch)?;
data_offset += copy_size;
offset += copy_size;
}
Ok(())
}
/// Swap the position of sub segment [0..pos] and [pos..]
fn swap_slice(slice: &mut [u8], pos: usize) {
let (left, right) = slice.split_at_mut(pos);
left.reverse();
right.reverse();
slice.reverse();
}
/// Write with aligned buffer. Offset and size don't need to be block aligned.
/// Case 1:
/// |~~~~~~write~~~~~~~|
/// |------------|------------|
/// Case 2:
/// |~~~write~~~|
/// |---------------|--------------|
fn write_aligned_buffer(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
data: &mut [u8],
scratch: &mut [u8],
) -> Result<()> {
debug_assert!(is_buffer_aligned(data, blk_dev.alignment())?);
if is_aligned(offset, blk_dev.block_size())? {
return write_aligned_offset_and_buffer(blk_dev, offset, data, scratch);
}
let aligned_start =
min(round_up(offset, blk_dev.block_size())?, add(offset, data.len() as u64)?);
let aligned_relative_offset = sub(aligned_start, offset)?;
if aligned_relative_offset < data.len() as u64 {
if is_buffer_aligned(&data[to_usize(aligned_relative_offset)?..], blk_dev.alignment())? {
// If new address is aligned, write directly.
write_aligned_offset_and_buffer(
blk_dev,
aligned_start,
&data[to_usize(aligned_relative_offset)?..],
scratch,
)?;
} else {
let write_len = sub(data.len() as u64, aligned_relative_offset)?;
// Swap the offset-aligned part to the beginning of the buffer (assumed aligned)
swap_slice(data, to_usize(aligned_relative_offset)?);
let res = write_aligned_offset_and_buffer(
blk_dev,
aligned_start,
&data[..to_usize(write_len)?],
scratch,
);
// Swap the two parts back before checking the result.
swap_slice(data, to_usize(write_len)?);
res?;
}
}
// perform read-modify-write for the unaligned part.
let block_scratch = &mut scratch[..to_usize(blk_dev.block_size())?];
let round_down_offset = round_down(offset, blk_dev.block_size())?;
read_aligned_all(blk_dev, round_down_offset, block_scratch)?;
let offset_relative = sub(offset, round_down_offset)?;
block_scratch[to_usize(offset_relative)?..to_usize(offset_relative + aligned_relative_offset)?]
.clone_from_slice(&data[..to_usize(aligned_relative_offset)?]);
write_aligned_all(blk_dev, round_down_offset, block_scratch)
}
/// Same as write_bytes(). Expcet that the API takes a mutable input bytes slice instead.
/// It does internal optimization that temporarily modifies `data` layout to minimize number of
/// calls to `blk_dev.read_blocks()`/`blk_dev.write_blocks()` (down to O(1)).
fn write_bytes_mut(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
data: &mut [u8],
scratch: &mut [u8],
) -> Result<()> {
let (buffer_alignment_scratch, block_alignment_scratch) = split_scratch(blk_dev, scratch)?;
if is_buffer_aligned(data, blk_dev.alignment())? {
return write_aligned_buffer(blk_dev, offset, data, block_alignment_scratch);
}
// Buffer misalignment:
// Case 1:
// |~~~~~~~~~~~~buffer~~~~~~~~~~~~|
// |----------------------|---------------------|
// blk_dev.alignment()
//
// Case 2:
// |~~~~~~buffer~~~~~|
// |----------------------|---------------------|
// blk_dev.alignment()
// Write unaligned part
let data_addr_value = data.as_ptr() as u64;
let unaligned_write = min(
sub(round_up(data_addr_value, blk_dev.alignment())?, data_addr_value)?,
data.len() as u64,
);
let mut unaligned_data = &mut buffer_alignment_scratch[..to_usize(unaligned_write)?];
unaligned_data.clone_from_slice(&data[..to_usize(unaligned_write)?]);
write_aligned_buffer(blk_dev, offset, &mut unaligned_data, block_alignment_scratch)?;
if unaligned_write == data.len() as u64 {
return Ok(());
}
// Write aligned part
write_aligned_buffer(
blk_dev,
add(offset, unaligned_write)?,
&mut data[to_usize(unaligned_write)?..],
block_alignment_scratch,
)
}
/// Top level write API that accept mutable/immutable bytes slice
fn write<B>(
blk_dev: &mut (impl BlockDevice + ?Sized),
offset: u64,
mut data: B,
scratch: &mut [u8],
) -> Result<()>
where
B: WriteBuffer,
{
if let Some(slice) = data.as_mut_slice() {
return write_bytes_mut(blk_dev, offset, slice, scratch);
} else if let Some(slice) = data.as_slice() {
return write_bytes(blk_dev, offset, slice, scratch);
}
Err(StorageError::InvalidInput)
}
/// The trait is for abstracting the representation of bytes data. The `write()` API will
/// choose different algorithm based on the return result of the methods. The library provides
/// native implementation for `&[u8]` and `&mut [u8]`. It's technically possilbe for users to
/// implement other types such as `Vec<u8>` etc.
pub trait WriteBuffer {
/// Return a mutable bytes slice for the data. If the data cannot be mutable, returns None.
fn as_mut_slice(&mut self) -> Option<&mut [u8]>;
/// Return a bytes slice for the data. Return None if not supported.
fn as_slice(&mut self) -> Option<&[u8]>;
// Provided methods
/// Return the length of the data.
fn data_len(&mut self) -> Result<usize> {
self.as_slice()
.map(|v| v.len())
.or(self.as_mut_slice().map(|v| v.len()))
.ok_or(StorageError::InvalidInput)
}
}
impl WriteBuffer for &[u8] {
fn as_mut_slice(&mut self) -> Option<&mut [u8]> {
None
}
fn as_slice(&mut self) -> Option<&[u8]> {
Some(*self)
}
}
impl WriteBuffer for &mut [u8] {
fn as_mut_slice(&mut self) -> Option<&mut [u8]> {
Some(*self)
}
fn as_slice(&mut self) -> Option<&[u8]> {
None
}
}
#[cfg(test)]
mod test {
use super::*;
use core::mem::size_of;
use Vec;
pub struct TestBlockDevice {
pub block_size: u64,
pub alignment: u64,
pub storage: Vec<u8>,
}
impl TestBlockDevice {
pub fn new(alignment: u64, block_size: u64, storage_size: u64) -> Self {
// Initialize default storage content.
let storage: Vec<u8> = (0..storage_size).map(|x| x as u8).collect();
return Self::new_with_data(alignment, block_size, &storage);
}
pub fn new_with_data(alignment: u64, block_size: u64, data: &[u8]) -> Self {
Self { block_size: block_size, alignment: alignment, storage: Vec::from(data) }
}
}
impl BlockDevice for TestBlockDevice {
fn block_size(&mut self) -> u64 {
self.block_size
}
fn num_blocks(&mut self) -> u64 {
self.storage.len() as u64 / self.block_size()
}
fn alignment(&mut self) -> u64 {
self.alignment
}
fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> bool {
assert!(is_buffer_aligned(out, self.alignment()).unwrap());
assert!(is_aligned(out.len() as u64, self.block_size()).unwrap());
let start = blk_offset * self.block_size();
let end = start + out.len() as u64;
out.clone_from_slice(&self.storage[to_usize(start).unwrap()..to_usize(end).unwrap()]);
true
}
fn write_blocks(&mut self, blk_offset: u64, data: &[u8]) -> bool {
assert!(is_buffer_aligned(data, self.alignment()).unwrap());
assert!(is_aligned(data.len() as u64, self.block_size()).unwrap());
let start = blk_offset * self.block_size();
let end = start + data.len() as u64;
self.storage[to_usize(start).unwrap()..to_usize(end).unwrap()].clone_from_slice(&data);
true
}
}
#[derive(Debug)]
struct TestCase {
rw_offset: u64,
rw_size: u64,
misalignment: u64,
alignment: u64,
block_size: u64,
storage_size: u64,
}
impl TestCase {
fn new(
rw_offset: u64,
rw_size: u64,
misalignment: u64,
alignment: u64,
block_size: u64,
storage_size: u64,
) -> Self {
Self { rw_offset, rw_size, misalignment, alignment, block_size, storage_size }
}
}
// Helper object for allocating aligned buffer.
struct AlignedBuffer {
buffer: Vec<u8>,
alignment: u64,
size: u64,
}
impl AlignedBuffer {
pub fn new(alignment: u64, size: u64) -> Self {
let buffer = vec![0u8; to_usize(size + alignment).unwrap()];
Self { buffer, alignment, size }
}
pub fn get(&mut self) -> &mut [u8] {
let addr = self.buffer.as_ptr() as u64;
let aligned_start = round_up(addr, self.alignment).unwrap() - addr;
&mut self.buffer
[to_usize(aligned_start).unwrap()..to_usize(aligned_start + self.size).unwrap()]
}
}
fn read_test_helper(case: &TestCase) {
let mut blk_dev = TestBlockDevice::new(case.alignment, case.block_size, case.storage_size);
// Make an aligned buffer. A misaligned version is created by taking a sub slice that
// starts at an unaligned offset. Because of this we need to allocate
// `case.misalignment` more to accommodate it.
let mut aligned_buf = AlignedBuffer::new(case.alignment, case.rw_size + case.misalignment);
let out = &mut aligned_buf.get()[to_usize(case.misalignment).unwrap()
..to_usize(case.misalignment + case.rw_size).unwrap()];
let scratch = &mut vec![0u8; required_scratch_size(&mut blk_dev).unwrap()];
blk_dev.read(case.rw_offset, out, scratch).unwrap();
assert_eq!(
out.to_vec(),
blk_dev.storage[to_usize(case.rw_offset).unwrap()
..to_usize(case.rw_offset + case.rw_size).unwrap()]
.to_vec(),
"Failed. Test case {:?}",
case,
);
}
fn write_test_helper(
case: &TestCase,
write_func: fn(&mut TestBlockDevice, u64, &mut [u8]) -> Result<()>,
) {
let mut blk_dev = TestBlockDevice::new(case.alignment, case.block_size, case.storage_size);
// Write a reverse version of the current data.
let mut expected = blk_dev.storage
[to_usize(case.rw_offset).unwrap()..to_usize(case.rw_offset + case.rw_size).unwrap()]
.to_vec();
expected.reverse();
// Make an aligned buffer. A misaligned version is created by taking a sub slice that
// starts at an unaligned offset. Because of this we need to allocate
// `case.misalignment` more to accommodate it.
let mut aligned_buf = AlignedBuffer::new(case.alignment, case.rw_size + case.misalignment);
let data = &mut aligned_buf.get()[to_usize(case.misalignment).unwrap()
..to_usize(case.misalignment + case.rw_size).unwrap()];
data.clone_from_slice(&expected);
write_func(&mut blk_dev, case.rw_offset, data).unwrap();
assert_eq!(
expected,
blk_dev.storage[to_usize(case.rw_offset).unwrap()
..to_usize(case.rw_offset + case.rw_size).unwrap()]
.to_vec(),
"Failed. Test case {:?}",
case,
);
// Check that input is not modified.
assert_eq!(expected, data, "Input is modified. Test case {:?}", case,);
}
macro_rules! read_write_test {
($name:ident, $x0:expr, $x1:expr, $x2:expr, $x3:expr, $x4:expr, $x5:expr) => {
mod $name {
use super::*;
#[test]
fn read_test() {
read_test_helper(&TestCase::new($x0, $x1, $x2, $x3, $x4, $x5));
}
#[test]
fn read_scaled_test() {
// Scaled all parameters by double and test again.
let (x0, x1, x2, x3, x4, x5) =
(2 * $x0, 2 * $x1, 2 * $x2, 2 * $x3, 2 * $x4, 2 * $x5);
read_test_helper(&TestCase::new(x0, x1, x2, x3, x4, x5));
}
// Input bytes slice is an immutable reference
#[test]
fn write_test() {
let func = |blk_dev: &mut TestBlockDevice, offset: u64, data: &mut [u8]| {
let scratch = &mut vec![0u8; required_scratch_size(blk_dev).unwrap()];
blk_dev.write(offset, data as &[u8], scratch)
};
write_test_helper(&TestCase::new($x0, $x1, $x2, $x3, $x4, $x5), func);
}
#[test]
fn write_scaled_test() {
// Scaled all parameters by double and test again.
let func = |blk_dev: &mut TestBlockDevice, offset: u64, data: &mut [u8]| {
let scratch = &mut vec![0u8; required_scratch_size(blk_dev).unwrap()];
blk_dev.write(offset, data as &[u8], scratch)
};
let (x0, x1, x2, x3, x4, x5) =
(2 * $x0, 2 * $x1, 2 * $x2, 2 * $x3, 2 * $x4, 2 * $x5);
write_test_helper(&TestCase::new(x0, x1, x2, x3, x4, x5), func);
}
// Input bytes slice is a mutable reference
#[test]
fn write_mut_test() {
let func = |blk_dev: &mut TestBlockDevice, offset: u64, data: &mut [u8]| {
let scratch = &mut vec![0u8; required_scratch_size(blk_dev).unwrap()];
blk_dev.write(offset, data, scratch)
};
write_test_helper(&TestCase::new($x0, $x1, $x2, $x3, $x4, $x5), func);
}
#[test]
fn write_mut_scaled_test() {
// Scaled all parameters by double and test again.
let (x0, x1, x2, x3, x4, x5) =
(2 * $x0, 2 * $x1, 2 * $x2, 2 * $x3, 2 * $x4, 2 * $x5);
let func = |blk_dev: &mut TestBlockDevice, offset: u64, data: &mut [u8]| {
let scratch = &mut vec![0u8; required_scratch_size(blk_dev).unwrap()];
blk_dev.write(offset, data, scratch)
};
write_test_helper(&TestCase::new(x0, x1, x2, x3, x4, x5), func);
}
}
};
}
const BLOCK_SIZE: u64 = 512;
const ALIGNMENT: u64 = 64;
const STORAGE: u64 = BLOCK_SIZE * 3;
// Test cases for different scenarios of read/write windows w.r.t buffer/block alignmnet
// boundary.
// offset
// |~~~~~~~~~~~~~size~~~~~~~~~~~~|
// |---------|---------|---------|
read_write_test! {aligned_all, 0, STORAGE, 0, ALIGNMENT, BLOCK_SIZE, STORAGE
}
// offset
// |~~~~~~~~~size~~~~~~~~~|
// |---------|---------|---------|
read_write_test! {
aligned_offset_uanligned_size, 0, STORAGE - 1, 0, ALIGNMENT, BLOCK_SIZE, STORAGE
}
// offset
// |~~size~~|
// |---------|---------|---------|
read_write_test! {
aligned_offset_intra_block, 0, BLOCK_SIZE - 1, 0, ALIGNMENT, BLOCK_SIZE, STORAGE
}
// offset
// |~~~~~~~~~~~size~~~~~~~~~~|
// |---------|---------|---------|
read_write_test! {
unaligned_offset_aligned_end, 1, STORAGE - 1, 0, ALIGNMENT, BLOCK_SIZE, STORAGE
}
// offset
// |~~~~~~~~~size~~~~~~~~|
// |---------|---------|---------|
read_write_test! {unaligned_offset_len, 1, STORAGE - 2, 0, ALIGNMENT, BLOCK_SIZE, STORAGE
}
// offset
// |~~~size~~~|
// |---------|---------|---------|
read_write_test! {
unaligned_offset_len_partial_cross_block, 1, BLOCK_SIZE, 0, ALIGNMENT, BLOCK_SIZE, STORAGE
}
// offset
// |~size~|
// |---------|---------|---------|
read_write_test! {
ualigned_offset_len_partial_intra_block,
1,
BLOCK_SIZE - 2,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
// Same sets of test cases but with an additional block added to `rw_offset`
read_write_test! {
aligned_all_extra_offset,
BLOCK_SIZE,
STORAGE,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE + BLOCK_SIZE
}
read_write_test! {
aligned_offset_uanligned_size_extra_offset,
BLOCK_SIZE,
STORAGE - 1,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE + BLOCK_SIZE
}
read_write_test! {
aligned_offset_intra_block_extra_offset,
BLOCK_SIZE,
BLOCK_SIZE - 1,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE + BLOCK_SIZE
}
read_write_test! {
unaligned_offset_aligned_end_extra_offset,
BLOCK_SIZE + 1,
STORAGE - 1,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE + BLOCK_SIZE
}
read_write_test! {
unaligned_offset_len_extra_offset,
BLOCK_SIZE + 1,
STORAGE - 2,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE + BLOCK_SIZE
}
read_write_test! {
unaligned_offset_len_partial_cross_block_extra_offset,
BLOCK_SIZE + 1,
BLOCK_SIZE,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE + BLOCK_SIZE
}
read_write_test! {
ualigned_offset_len_partial_intra_block_extra_offset,
BLOCK_SIZE + 1,
BLOCK_SIZE - 2,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE + BLOCK_SIZE
}
// Same sets of test cases but with unaligned output buffer {'misALIGNMENT` != 0}
read_write_test! {
aligned_all_unaligned_buffer,
0,
STORAGE,
1,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
read_write_test! {
aligned_offset_uanligned_size_unaligned_buffer,
0,
STORAGE - 1,
1,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
read_write_test! {
aligned_offset_intra_block_unaligned_buffer,
0,
BLOCK_SIZE - 1,
1,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
read_write_test! {
unaligned_offset_aligned_end_unaligned_buffer,
1,
STORAGE - 1,
1,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
read_write_test! {
unaligned_offset_len_unaligned_buffer,
1,
STORAGE - 2,
1,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
read_write_test! {
unaligned_offset_len_partial_cross_block_unaligned_buffer,
1,
BLOCK_SIZE,
1,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
read_write_test! {
ualigned_offset_len_partial_intra_block_unaligned_buffer,
1,
BLOCK_SIZE - 2,
1,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
// Special cases where `rw_offset` is not block aligned but buffer aligned. This can
// trigger some internal optimization code path.
read_write_test! {
buffer_aligned_offset_and_len,
ALIGNMENT,
STORAGE - ALIGNMENT,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
read_write_test! {
buffer_aligned_offset,
ALIGNMENT,
STORAGE - ALIGNMENT - 1,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
read_write_test! {
buffer_aligned_offset_aligned_end,
ALIGNMENT,
BLOCK_SIZE,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
read_write_test! {
buffer_aligned_offset_intra_block,
ALIGNMENT,
BLOCK_SIZE - ALIGNMENT - 1,
0,
ALIGNMENT,
BLOCK_SIZE,
STORAGE
}
#[test]
fn test_scratch_too_small() {
let block_size: u64 = 512;
let alignment: u64 = 64;
let mut blk_dev = TestBlockDevice::new(alignment, block_size, block_size * 3);
let mut scratch = vec![0u8; to_usize(block_size + 2 * alignment - 1).unwrap()];
assert!(blk_dev
.read(0, &mut vec![0u8; to_usize(block_size).unwrap()], &mut scratch)
.is_err());
}
#[test]
fn test_read_overflow() {
let mut blk_dev = TestBlockDevice::new(1, 1, 512);
let scratch = &mut vec![0u8; required_scratch_size(&mut blk_dev).unwrap()];
assert!(blk_dev.read(512, &mut vec![0u8; 1], scratch).is_err());
assert!(blk_dev.read(0, &mut vec![0u8; 513], scratch).is_err());
}
#[test]
fn test_read_arithmetic_overflow() {
let mut blk_dev = TestBlockDevice::new(1, 1, 512);
let scratch = &mut vec![0u8; required_scratch_size(&mut blk_dev).unwrap()];
assert!(blk_dev.read(u64::MAX, &mut vec![0u8; 1], scratch).is_err());
}
#[test]
fn test_write_overflow() {
let mut blk_dev = TestBlockDevice::new(1, 1, 512);
let scratch = &mut vec![0u8; required_scratch_size(&mut blk_dev).unwrap()];
assert!(blk_dev.write(512, vec![0u8; 1].as_mut_slice(), scratch).is_err());
assert!(blk_dev.write(0, vec![0u8; 513].as_mut_slice(), scratch).is_err());
assert!(blk_dev.write(512, vec![0u8; 1].as_slice(), scratch).is_err());
assert!(blk_dev.write(0, vec![0u8; 513].as_slice(), scratch).is_err());
}
#[test]
fn test_write_arithmetic_overflow() {
let mut blk_dev = TestBlockDevice::new(1, 1, 512);
let scratch = &mut vec![0u8; required_scratch_size(&mut blk_dev).unwrap()];
assert!(blk_dev.write(u64::MAX, vec![0u8; 1].as_mut_slice(), scratch).is_err());
assert!(blk_dev.write(u64::MAX, vec![0u8; 1].as_slice(), scratch).is_err());
}
#[test]
fn test_u64_not_narrower_than_usize() {
// If this ever fails we need to adjust all code for >64 bit pointers and size.
assert!(size_of::<u64>() >= size_of::<*const u8>());
assert!(size_of::<u64>() >= size_of::<usize>());
}
}