blob: 601afa82f1b978f66d9538515938d849556c8cba [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. The library supports reading/writing raw block content
//! as well as parsing/reading/writing GPT partitions.
//!
//! # Examples
//!
//! ```rust
//! use gbl_storage::{
//! AsBlockDevice, BlockIoSync, BlockDevice, required_scratch_size, BlockInfo,
//! };
//!
//! /// Mocks a block device using a buffer.
//! pub struct RamBlockIo {
//! storage: std::vec::Vec<u8>,
//! }
//!
//! use liberror::Error;
//!
//! impl BlockIoSync for RamBlockIo {
//!
//! fn info(&mut self) -> BlockInfo {
//! BlockInfo {
//! block_size: 512,
//! num_blocks: self.storage.len() as u64 / 512,
//! alignment: 64,
//! }
//! }
//!
//! fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> Result<(), Error> {
//! let start = blk_offset * self.info().block_size;
//! let end = start + out.len() as u64;
//! out.clone_from_slice(&self.storage[start as usize..end as usize]);
//! Ok(())
//! }
//!
//! fn write_blocks(&mut self, blk_offset: u64, data: &mut [u8]) -> Result<(), Error> {
//! let start = blk_offset * self.info().block_size;
//! let end = start + data.len() as u64;
//! self.storage[start as usize..end as usize].clone_from_slice(&data);
//! Ok(())
//! }
//! }
//!
//! const MAX_GPT_ENTRIES: u64 = 128;
//!
//! let mut ram_block_io = RamBlockIo { storage: vec![0u8; 64 * 1024] };
//! // Prepare a scratch buffer, size calculated with `required_scratch_size()`.
//! let mut scratch =
//! vec![0u8; required_scratch_size(ram_block_io.info(), MAX_GPT_ENTRIES).unwrap()];
//! // Create a `BlockDevice`
//! let mut ram_block_dev =
//! BlockDevice::new(&mut ram_block_io, &mut scratch[..], MAX_GPT_ENTRIES);
//!
//! // Read/write with arbitrary range and buffer without worrying about alignment.
//! let mut out = vec![0u8; 1234];
//! ram_block_dev.read(4321, &mut out[..]).unwrap();
//! let mut data = vec![0u8; 5678];
//! // Mutable input. More efficient
//! ram_block_dev.write(8765, data.as_mut_slice()).unwrap();
//!
//! // Sync GPT
//! let _ = ram_block_dev.sync_gpt();
//! // Access GPT entries
//! let _ = ram_block_dev.find_partition("some-partition");
//! // Read/Write GPT partitions with arbitrary offset, size, buffer
//! let _ = ram_block_dev.read_gpt_partition("partition", 4321, &mut out[..]);
//! let _ = ram_block_dev.write_gpt_partition("partition", 8765, data.as_mut_slice());
//!
//! // Alterantively, you can also define a custom type that internally owns and binds the
//! // implementation of `BlockIoSync` and scratch buffer together, and then implement the
//! // `AsBlockDevice` trait. This gives a cleaner management of resources.
//! pub struct OwnedBlockDevice {
//! io: RamBlockIo,
//! scratch: std::vec::Vec<u8>,
//! }
//!
//! impl AsBlockDevice for OwnedBlockDevice {
//! fn with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIoSync, &mut [u8], u64)) {
//! f(&mut self.io, &mut self.scratch[..], MAX_GPT_ENTRIES)
//! }
//! }
//!
//! // `owned_block_dev` has the same APIs as `BlockDevice`.
//! let mut owned_block_dev = OwnedBlockDevice { io: ram_block_io, scratch: scratch };
//! ```
#![cfg_attr(not(test), no_std)]
#![allow(async_fn_in_trait)]
use gbl_async::block_on;
// Selective export of submodule types.
mod gpt;
use gpt::check_gpt_rw_params;
pub use gpt::{
GptCache, GptEntry, GptHeader, GptSyncResult, Partition, PartitionIterator, GPT_GUID_LEN,
GPT_MAGIC, GPT_NAME_LEN_U16,
};
use safemath::SafeNum;
mod algorithm;
pub use algorithm::{read_async, write_async, AsyncAsSync, SyncAsAsync};
use liberror::{Error, Result};
/// `BlockInfo` contains information for a block device.
#[derive(Clone, Copy, Debug)]
pub struct BlockInfo {
/// Native block size of the block device.
pub block_size: u64,
/// Total number of blocks of the block device.
pub num_blocks: u64,
/// The alignment requirement for IO buffers. 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.
pub alignment: u64,
}
impl BlockInfo {
/// Computes the total size in bytes of the block device.
pub fn total_size(&self) -> Result<u64> {
Ok((SafeNum::from(self.block_size) * self.num_blocks).try_into()?)
}
}
/// `BlockIoAsync` provides interfaces for asynchronous read and write.
pub trait BlockIoAsync {
/// Returns the `BlockInfo` for this block device.
fn info(&mut self) -> BlockInfo;
/// 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.
async fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> Result<()>;
/// 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.
async fn write_blocks(&mut self, blk_offset: u64, data: &mut [u8]) -> Result<()>;
}
impl<T: BlockIoAsync> BlockIoAsync for &mut T {
fn info(&mut self) -> BlockInfo {
(*self).info()
}
async fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> Result<()> {
(*self).read_blocks(blk_offset, out).await
}
async fn write_blocks(&mut self, blk_offset: u64, data: &mut [u8]) -> Result<()> {
(*self).write_blocks(blk_offset, data).await
}
}
/// An implementation of `BlockIoAsync` of where all required methods are `unimplemented!()`
pub struct BlockIoNull {}
impl BlockIoAsync for BlockIoNull {
fn info(&mut self) -> BlockInfo {
unimplemented!();
}
async fn read_blocks(&mut self, _: u64, _: &mut [u8]) -> Result<()> {
unimplemented!();
}
async fn write_blocks(&mut self, _: u64, _: &mut [u8]) -> Result<()> {
unimplemented!();
}
}
/// `BlockIoSync` provide interfaces for synchronous read and write.
pub trait BlockIoSync {
/// Gets the `BlockInfo` for this block device
fn info(&mut self) -> BlockInfo;
/// 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]) -> Result<()>;
/// 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: &mut [u8]) -> Result<()>;
}
impl BlockIoSync for &mut dyn BlockIoSync {
fn info(&mut self) -> BlockInfo {
(*self).info()
}
fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> Result<()> {
(*self).read_blocks(blk_offset, out)
}
fn write_blocks(&mut self, blk_offset: u64, data: &mut [u8]) -> Result<()> {
(*self).write_blocks(blk_offset, data)
}
}
impl<T: BlockIoAsync> BlockIoSync for T {
fn info(&mut self) -> BlockInfo {
(*self).info()
}
fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> Result<()> {
block_on((*self).read_blocks(blk_offset, out))
}
fn write_blocks(&mut self, blk_offset: u64, data: &mut [u8]) -> Result<()> {
block_on((*self).write_blocks(blk_offset, data))
}
}
/// `AsBlockDevice` provides APIs for synchronous read/write of raw block storage or GPT
/// partitions.
///
/// Users that need to perform asynchronous non-blocking read/write should use
/// `read_async`, `write_async` APIs instead.
pub trait AsBlockDevice {
/// Runs the provided closure `f` with the following parameters:
///
/// 1. An implementation of block IO `&mut dyn BlockIoSync`.
/// 2. A scratch buffer `&mut [u8]`.
/// 3. A `u64` specifying the maximum allowed number of GPT entries.
///
/// * The scratch buffer is internally used for two purposes: 1. to handle read/write with
/// offset, size that are not multiples of block size or input/output buffer that are not
/// aligned, and 2. to load and sync GPT headers.
///
/// * The necessary size for the scratch buffer depends on `BlockInfo::alignment`,
/// `BlockInfo::block_size` and maximum allowed GPT entries. It can be computed using the
/// helper API `required_scratch_size()`. If maximum allowed GPT entries is 0, GPT is
/// considered unavailable and no buffer will be reserved for GPT headers. If additionally,
/// `BlockIoSync` has no alignment requirement, i.e. both alignment and block size are 1, the
/// total required scratch size is 0.
///
/// * GPT headers will be cached in the scratch buffer after calling `Self::sync_gpt()` and
/// returning success. Subsequent call of `Self:read_gpt_partiton()`,
/// `Self::write_gpt_partition()`, and `Self::write_gpt_partition()`
/// will look up partition entries from the cached GPT header.
/// Thus callers should make sure to always return the same scratch buffer and avoid
/// modifying its content.
///
/// * A smaller value of maximum allowed GPT entries gives smaller required scratch buffer
/// size. However if the `entries_count` field in the GPT header is greater than this value,
/// GPT parsing will fail. Note that most tools and OS fix the `entries_count` value to the
/// max value 128 regardless of the actual number of partition entries used. Thus unless you
/// have full control of GPT generation in your entire system where you can always ensure a
/// smaller bound on it, it is recommended to always return 128.
fn with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIoSync, &mut [u8], u64));
/// Returns the `BlockInfo` of the provided Block IO.
fn info(&mut self) -> BlockInfo {
let mut res = None;
self.with(&mut |io, _, _| res = Some(io.info()));
res.unwrap()
}
/// Read data from the block device.
///
/// # Args
///
/// * `offset`: Offset in number of bytes.
///
/// * `out`: Buffer to store the read data.
///
/// * Returns success when exactly `out.len()` number of bytes are read.
fn read(&mut self, offset: u64, out: &mut [u8]) -> Result<()> {
with_partitioned_scratch(self, |io, alignment, _, _| {
block_on(AsyncBlockDevice::new(SyncAsAsync::new(io), alignment)?.read(offset, out))
})?
}
/// Write data to the device.
///
/// # Args
///
/// * `offset`: Offset in number of bytes.
///
/// * `data`: Data to write.
///
/// * The API enables an optimization which temporarily changes `data` layout internally and
/// reduces the number of calls to `Self::write_blocks()` down to O(1) regardless of input's
/// alignment. This is the recommended usage.
///
/// * Returns success when exactly `data.len()` number of bytes are written.
fn write(&mut self, offset: u64, data: &mut [u8]) -> Result<()> {
with_partitioned_scratch(self, |io, alignment, _, _| {
block_on(AsyncBlockDevice::new(SyncAsAsync::new(io), alignment)?.write(offset, data))
})?
}
/// Parse and sync GPT from a block device.
///
/// The API validates and restores primary/secondary GPT header.
///
/// # Returns
///
/// Returns success if GPT is loaded/restored successfully.
fn sync_gpt(&mut self) -> Result<()> {
with_partitioned_scratch(self, |io, alignment_scratch, gpt_buffer, max_entries| {
block_on(
AsyncBlockDevice::new(SyncAsAsync::new(io), alignment_scratch)?
.sync_gpt(&mut GptCache::from_uninit(max_entries, gpt_buffer)?),
)?
.res()
})?
}
/// Returns the `Partition` for a partition.
///
/// # Args
///
/// * `part`: Name of the partition.
fn find_partition(&mut self, part: &str) -> Result<Partition> {
with_partitioned_scratch(self, |_, _, gpt_buffer, _| {
GptCache::from_existing(gpt_buffer)?.find_partition(part)
})?
}
/// Read a GPT partition on a block device
///
/// # Args
///
/// * `part_name`: Name of the partition.
///
/// * `offset`: Offset in number of bytes into the partition.
///
/// * `out`: Buffer to store the read data.
///
/// # Returns
///
/// Returns success when exactly `out.len()` of bytes are read successfully.
fn read_gpt_partition(&mut self, part_name: &str, offset: u64, out: &mut [u8]) -> Result<()> {
let offset = with_partitioned_scratch(self, |_, _, gpt_buffer, _| {
check_gpt_rw_params(gpt_buffer, part_name, offset, out.len())
})??;
self.read(offset, out)
}
/// Write a GPT partition on a block device.
/// Optimization for mutable buffers.
/// See `AsBlockDevice::write` for details on alignment requirements
/// for optimized performance.
///
/// # Args
///
/// * `part_name`: Name of the partition.
///
/// * `offset`: Offset in number of bytes into the partition.
///
/// * `data`: Data to write. See `data` passed to `BlockIoSync::write()` for details.
///
/// # Returns
///
/// Returns success when exactly `data.len()` of bytes are written successfully.
fn write_gpt_partition(&mut self, part_name: &str, offset: u64, data: &mut [u8]) -> Result<()> {
let offset = with_partitioned_scratch(self, |_, _, gpt_buffer, _| {
check_gpt_rw_params(gpt_buffer, part_name, offset, data.len())
})??;
self.write(offset, data)
}
}
impl<T: ?Sized + AsBlockDevice> AsBlockDevice for &mut T {
fn with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIoSync, &mut [u8], u64)) {
(*self).with(f)
}
}
/// `BlockDevice` borrows a `BlockIoSync`, scratch buffer and implements `AsBlockDevice`.
pub struct BlockDevice<'a, 'b> {
io: &'a mut dyn BlockIoSync,
scratch: &'b mut [u8],
max_gpt_entries: u64,
}
impl<'a, 'b> BlockDevice<'a, 'b> {
/// Creates a new [BlockDevice].
///
/// # Arguments
/// * `io`: the [BlockIoSync] implementation to use.
/// * `scratch`: scratch buffer to use; if this is smaller than the size indicated by
/// [required_scratch_size], operations on the returned device may return
/// [Error::BufferTooSmall].
/// * `max_gpt_entries`: the maximum GPT entries to support.
pub fn new(io: &'a mut dyn BlockIoSync, scratch: &'b mut [u8], max_gpt_entries: u64) -> Self {
Self { io, scratch, max_gpt_entries }
}
}
impl AsBlockDevice for BlockDevice<'_, '_> {
fn with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIoSync, &mut [u8], u64)) {
f(self.io, self.scratch, self.max_gpt_entries)
}
}
/// Iterates all partitions in a `AsBlockDevice`.
pub fn for_each_partition<F: FnMut(&Partition) -> Result<()>>(
dev: &mut dyn AsBlockDevice,
mut f: F,
) -> Result<Result<()>> {
with_partitioned_scratch(dev, |_, _, gpt_buffer, _| {
for ele in GptCache::from_existing(gpt_buffer)?.partition_iter() {
match f(&ele) {
Err(e) => return Ok(Err(e)),
_ => {}
}
}
Ok(Ok(()))
})?
}
/// Calculates the required scratch buffer size given a `BlockInfo` and maximum GPT entries.
pub fn required_scratch_size(info: BlockInfo, max_gpt_entries: u64) -> Result<usize> {
let alignment_size: SafeNum = alignment_scratch_size(info)?.into();
let gpt_buffer_size = match max_gpt_entries {
0 => 0,
v => GptCache::required_buffer_size(v)?,
};
(alignment_size + gpt_buffer_size).try_into().map_err(Into::into)
}
/// Partitions a raw buffer into scratch buffer and GPT cache buffer.
fn partition_scratch(
info: BlockInfo,
max_gpt_entries: u64,
buffer: &mut [u8],
) -> Result<(&mut [u8], &mut [u8])> {
let scratch_size = required_scratch_size(info, max_gpt_entries)?;
if buffer.len() < scratch_size {
return Err(Error::BufferTooSmall(Some(scratch_size)));
}
Ok(buffer.split_at_mut(alignment_scratch_size(info)?))
}
/// A helper that wraps `AsBlockDevice::with` and additionally partitions the scratch buffer into
/// alignment scratch and GPT buffers.
fn with_partitioned_scratch<F, R>(dev: &mut (impl AsBlockDevice + ?Sized), mut f: F) -> Result<R>
where
F: FnMut(&mut dyn BlockIoSync, &mut [u8], &mut [u8], u64) -> R,
{
let mut res: Result<R> = Err(Error::InvalidInput);
dev.with(&mut |io, scratch, max_entries| {
res = (|| {
let (alignment, gpt) = partition_scratch(io.info(), max_entries, scratch)?;
Ok(f(io, alignment, gpt, max_entries))
})();
});
res
}
/// Check if `value` is aligned to (multiples of) `alignment`
/// It can fail if the remainider calculation fails overflow check.
pub fn is_aligned(value: SafeNum, alignment: SafeNum) -> Result<bool> {
Ok(u64::try_from(value % alignment)? == 0)
}
/// Check if `buffer` address is aligned to `alignment`
/// It can fail if the remainider calculation fails overflow check.
pub fn is_buffer_aligned(buffer: &[u8], alignment: u64) -> Result<bool> {
is_aligned((buffer.as_ptr() as usize).into(), alignment.into())
}
/// Check read/write range and calculate offset in number of blocks.
fn check_range(info: BlockInfo, offset: u64, buffer: &[u8]) -> Result<SafeNum> {
let offset: SafeNum = offset.into();
let block_size: SafeNum = info.block_size.into();
debug_assert!(is_aligned(offset, block_size)?, "{:?}, {:?}", offset, block_size);
debug_assert!(is_aligned(buffer.len().into(), block_size)?);
debug_assert!(is_buffer_aligned(buffer, info.alignment)?);
let blk_offset = offset / block_size;
let blk_count = SafeNum::from(buffer.len()) / block_size;
let end: u64 = (blk_offset + blk_count).try_into()?;
match end <= info.num_blocks {
true => Ok(blk_offset),
false => Err(Error::BadIndex(end as usize)),
}
}
/// Calculates the necessary scratch buffer size for handling block and buffer misalignment.
pub fn alignment_scratch_size(info: BlockInfo) -> Result<usize> {
let block_alignment = match info.block_size {
1 => 0,
v => v,
};
((SafeNum::from(info.alignment) - 1) * 2 + block_alignment).try_into().map_err(Into::into)
}
/// `AsyncBlockDevice` provides APIs for asynchronous read/write of raw block or GPT partitions.
pub struct AsyncBlockDevice<'a, T: BlockIoAsync> {
io: T,
scratch: &'a mut [u8],
}
impl<'a, T: BlockIoAsync> AsyncBlockDevice<'a, T> {
/// Creates a new instance with the given IO, scratch buffer and maximum GPT entries.
///
/// * The scratch buffer is internally used for handling partial block read/write and unaligned
/// input/output user buffers.
///
/// * The necessary size for the scratch buffer depends on `BlockInfo::alignment`,
/// `BlockInfo::block_size`. It can be computed using the helper API
/// `Self::required_scratch_size()`. If the block device has no alignment requirement,
/// i.e. both alignment and block size are 1, the total required scratch size is 0.
pub fn new(mut io: T, scratch: &'a mut [u8]) -> Result<Self> {
let scratch_size = Self::required_scratch_size(&mut io)?;
match scratch.len() < scratch_size {
true => Err(Error::BufferTooSmall(Some(scratch_size))),
_ => Ok(Self { io, scratch }),
}
}
/// Creates a new `AsyncBlockDevice` instance that borrows the internal data of this instance.
pub fn as_mut_instance(&mut self) -> AsyncBlockDevice<'_, &'_ mut T> {
AsyncBlockDevice::<&mut T>::new(&mut self.io, &mut self.scratch[..]).unwrap()
}
/// Computes the required scratch size.
pub fn required_scratch_size<B: BlockIoAsync>(io: &mut B) -> Result<usize> {
// `AsyncBlockDevice` doesn't manage GPT internally, thus max_entries passed here is 0.
required_scratch_size(io.info(), 0)
}
/// Returns the IO
pub fn io(&mut self) -> &mut T {
&mut self.io
}
/// Reads data from the block device.
///
/// # Args
///
/// * `offset`: Offset in number of bytes.
/// * `out`: Buffer to store the read data.
/// * Returns success when exactly `out.len()` number of bytes are read.
pub async fn read(&mut self, offset: u64, data: &mut [u8]) -> Result<()> {
read_async(&mut self.io, offset, data, self.scratch).await
}
/// Writes data to the device.
///
/// # Args
///
/// * `offset`: Offset in number of bytes.
/// * `data`: Data to write.
/// * The API enables an optimization which temporarily changes `data` layout internally and
/// reduces the number of calls to `Self::write_blocks()` down to O(1) regardless of input's
/// alignment. This is the recommended usage.
/// * Returns success when exactly `data.len()` number of bytes are written.
pub async fn write(&mut self, offset: u64, data: &mut [u8]) -> Result<()> {
write_async(&mut self.io, offset, data, self.scratch).await
}
/// Loads and syncs GPT from a block device.
///
/// The API validates and restores primary/secondary GPT header.
///
/// # Returns
///
/// * Returns Ok(sync_result) if disk IO is successful, where `sync_result` contains the GPT
/// verification and restoration result.
/// * Returns Err() if disk IO encounters errors.
pub async fn sync_gpt(&mut self, gpt_cache: &mut GptCache<'_>) -> Result<GptSyncResult> {
gpt_cache.load_and_sync(&mut self.io, self.scratch).await
}
/// Updates GPT to the block device and sync primary and secondary GPT.
///
/// # Args
///
/// * `mbr_primary`: A buffer containing the MBR block, primary GPT header and entries.
/// * `gpt_cache`: The GPT cache to update.
///
/// # Returns
///
/// * Return `Ok(())` if new GPT is valid and device is updated and synced successfully.
pub async fn update_gpt(
&mut self,
mbr_primary: &mut [u8],
gpt_cache: &mut GptCache<'_>,
) -> Result<()> {
gpt::update_gpt(&mut self.io, self.scratch, mbr_primary, gpt_cache).await
}
/// Reads a GPT partition on a block device
///
/// # Args
///
/// * `gpt_cache`: A `GptCache` initialized with `Self::sync_gpt()`.
/// * `part_name`: Name of the partition.
/// * `offset`: Offset in number of bytes into the partition.
/// * `out`: Buffer to store the read data.
///
/// # Returns
///
/// Returns success when exactly `out.len()` of bytes are read successfully.
pub async fn read_gpt_partition(
&mut self,
gpt_cache: &GptCache<'_>,
part_name: &str,
offset: u64,
out: &mut [u8],
) -> Result<()> {
let offset = gpt_cache.check_range(part_name, offset, out.len())?;
self.read(offset, out).await
}
/// Writes a GPT partition on a block device.
///
///
/// # Args
///
/// * `gpt_cache`: A `GptCache` initialized with `Self::sync_gpt()`.
/// * `part_name`: Name of the partition.
/// * `offset`: Offset in number of bytes into the partition.
/// * `data`: Data to write. See `data` passed to `BlockIoSync::write()` for details.
///
/// # Returns
///
/// Returns success when exactly `data.len()` of bytes are written successfully.
pub async fn write_gpt_partition(
&mut self,
gpt_cache: &GptCache<'_>,
part_name: &str,
offset: u64,
data: &mut [u8],
) -> Result<()> {
let offset = gpt_cache.check_range(part_name, offset, data.len())?;
self.write(offset, data).await
}
}
/// `AsyncGptDevice` wraps a `AsyncBlockDevice` and `GptCache` and provides simpler APIs for
/// reading/writing GPT partitions.
pub struct AsyncGptDevice<'a, T: BlockIoAsync> {
blk: AsyncBlockDevice<'a, T>,
gpt_cache: GptCache<'a>,
}
impl<'a, T: BlockIoAsync> AsyncGptDevice<'a, T> {
/// Creates a new instance.
pub fn new(blk: AsyncBlockDevice<'a, T>, gpt_cache: GptCache<'a>) -> Self {
Self { blk, gpt_cache }
}
/// Creates a new instance from a raw `BlockIoAsync` and a raw buffer. The API will attempt to
/// partition the `buffer` into scratch buffer and GPT cache buffer.
pub fn new_from_monotonic_buffer(
mut io: T,
buffer: &'a mut [u8],
max_gpt_entries: u64,
) -> Result<Self> {
let (scratch, gpt) = partition_scratch(io.info(), max_gpt_entries, buffer)?;
Ok(Self::new(
AsyncBlockDevice::new(io, scratch)?,
GptCache::from_uninit(max_gpt_entries, gpt)?,
))
}
/// Returns the `AsyncBlockDevice`.
pub fn blk(&mut self) -> &mut AsyncBlockDevice<'a, T> {
&mut self.blk
}
/// Returns the `GptCache`.
pub fn gpt_cache(&self) -> &GptCache<'a> {
&self.gpt_cache
}
/// Creates a new `AsyncGptDevice` instance that borrows the internal data of this instance.
pub fn as_mut_instance(&mut self) -> AsyncGptDevice<'_, &'_ mut T> {
AsyncGptDevice::new(self.blk.as_mut_instance(), self.gpt_cache.as_mut_instance())
}
/// Unpacks into an `AsyncBlockDevice` and `GptCache`
pub fn into_blk_and_gpt(self) -> (AsyncBlockDevice<'a, T>, GptCache<'a>) {
(self.blk, self.gpt_cache)
}
/// Loads and syncs GPT from a block device.
///
/// The API validates and restores primary/secondary GPT header.
///
/// # Returns
///
/// Returns success if GPT is loaded/restored successfully.
pub async fn sync_gpt(&mut self) -> Result<GptSyncResult> {
self.blk.sync_gpt(&mut self.gpt_cache).await
}
/// Reads a GPT partition on a block device
///
/// # Args
///
/// * `part_name`: Name of the partition.
/// * `offset`: Offset in number of bytes into the partition.
/// * `out`: Buffer to store the read data.
///
/// # Returns
///
/// Returns success when exactly `out.len()` of bytes are read successfully.
pub async fn read_gpt_partition(
&mut self,
part_name: &str,
offset: u64,
out: &mut [u8],
) -> Result<()> {
self.blk.read_gpt_partition(&mut self.gpt_cache, part_name, offset, out).await
}
/// Writes a GPT partition on a block device.
///
///
/// # Args
///
/// * `part_name`: Name of the partition.
/// * `offset`: Offset in number of bytes into the partition.
/// * `data`: Data to write. See `data` passed to `BlockIoSync::write()` for details.
///
/// # Returns
///
/// Returns success when exactly `data.len()` of bytes are written successfully.
pub async fn write_gpt_partition(
&mut self,
part_name: &str,
offset: u64,
data: &mut [u8],
) -> Result<()> {
self.blk.write_gpt_partition(&mut self.gpt_cache, part_name, offset, data).await
}
}
/// `AsAsyncGptDeviceIter` defines an iterface for getting an iterator of `AsyncGptDevice`.
pub trait AsAsyncGptDeviceIter {
/// The type that implements the `BlockIoAsync` trait for `AsyncGptDevice`.
type BlockIo<'a>: BlockIoAsync
where
Self: 'a;
/// Gets an iterator for `AsyncGptDevice`.
fn iter(&mut self) -> impl IntoIterator<Item = AsyncGptDevice<'_, Self::BlockIo<'_>>>;
}
// Built-in implementation for an array of `AsyncGptDevice`.
impl<B: BlockIoAsync> AsAsyncGptDeviceIter for [AsyncGptDevice<'_, B>] {
type BlockIo<'a> = &'a mut B where Self: 'a;
fn iter(&mut self) -> impl IntoIterator<Item = AsyncGptDevice<'_, Self::BlockIo<'_>>> {
self.iter_mut().map(move |v| v.as_mut_instance())
}
}
/// Checks that a partition exists and is unique.
///
/// On Success, returns the offset of the block device in the list, the `Partition` and the
/// corresponding `AsyncGptDevice<'a, B>`.
pub fn check_part_unique<'a, B: BlockIoAsync>(
devs: &'a mut (impl AsAsyncGptDeviceIter<BlockIo<'a> = B> + ?Sized),
part: &str,
) -> Result<(usize, Partition, AsyncGptDevice<'a, B>)> {
let mut idx = 0usize;
let mut res = Err(Error::NotFound);
for dev in devs.iter() {
res = match dev.gpt_cache().find_partition(part).map(|v| (idx, v, dev)) {
Ok(_) if res.is_ok() => return Err(Error::NotUnique),
v => v.or(res),
};
idx += 1;
}
res
}
#[cfg(test)]
mod test {
use core::mem::size_of;
use gbl_storage_testlib::{
required_scratch_size, AsBlockDevice, TestBlockDevice, TestBlockDeviceBuilder,
};
use safemath::SafeNum;
#[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 aligned_size = (SafeNum::from(size) + alignment).try_into().unwrap();
let buffer = vec![0u8; aligned_size];
Self { buffer, alignment, size }
}
pub fn get(&mut self) -> &mut [u8] {
let addr = SafeNum::from(self.buffer.as_ptr() as usize);
let aligned_start = addr.round_up(self.alignment) - addr;
&mut self.buffer
[aligned_start.try_into().unwrap()..(aligned_start + self.size).try_into().unwrap()]
}
}
/// Upper bound on the number of `read_blocks_async()/write_blocks_async()` calls by
/// `AsBlockDevice::read()` and `AsBlockDevice::write()`.
///
/// * `fn read_aligned_all()`: At most 1 call to `read_blocks_async()`.
/// * `fn read_aligned_offset_and_buffer()`: At most 2 calls to `read_aligned_all()`.
/// * `fn read_aligned_buffer()`: At most 1 call to `read_aligned_offset_and_buffer()` plus 1
/// call to `read_blocks_async()`.
/// * `fn read_async()`: At most 2 calls to `read_aligned_buffer()`.
///
/// Analysis is similar for `fn write_async()`.
const READ_WRITE_BLOCKS_UPPER_BOUND: usize = 6;
fn read_test_helper(case: &TestCase) {
let data = (0..case.storage_size).map(|v| v as u8).collect::<Vec<_>>();
let mut blk = TestBlockDeviceBuilder::new()
.set_alignment(case.alignment)
.set_block_size(case.block_size)
.set_data(&data)
.build();
// 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 misalignment = SafeNum::from(case.misalignment);
let out = &mut aligned_buf.get()
[misalignment.try_into().unwrap()..(misalignment + case.rw_size).try_into().unwrap()];
blk.read(case.rw_offset, out).unwrap();
let rw_offset = SafeNum::from(case.rw_offset);
assert_eq!(
out.to_vec(),
blk.io.storage
[rw_offset.try_into().unwrap()..(rw_offset + case.rw_size).try_into().unwrap()]
.to_vec(),
"Failed. Test case {:?}",
case,
);
assert!(blk.io.num_reads <= READ_WRITE_BLOCKS_UPPER_BOUND);
}
fn write_test_helper(case: &TestCase, write_func: fn(&mut TestBlockDevice, u64, &mut [u8])) {
let data = (0..case.storage_size).map(|v| v as u8).collect::<Vec<_>>();
let mut blk = TestBlockDeviceBuilder::new()
.set_alignment(case.alignment)
.set_block_size(case.block_size)
.set_data(&data)
.build();
// Write a reverse version of the current data.
let rw_offset = SafeNum::from(case.rw_offset);
let mut expected = blk.io.storage
[rw_offset.try_into().unwrap()..(rw_offset + case.rw_size).try_into().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 misalignment = SafeNum::from(case.misalignment);
let mut aligned_buf = AlignedBuffer::new(case.alignment, case.rw_size + case.misalignment);
let data = &mut aligned_buf.get()
[misalignment.try_into().unwrap()..(misalignment + case.rw_size).try_into().unwrap()];
data.clone_from_slice(&expected);
write_func(&mut blk, case.rw_offset, data);
let rw_offset = SafeNum::from(case.rw_offset);
assert_eq!(
expected,
blk.io.storage
[rw_offset.try_into().unwrap()..(rw_offset + case.rw_size).try_into().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 a mutable reference
#[test]
fn write_mut_test() {
let func = |blk: &mut TestBlockDevice, offset: u64, data: &mut [u8]| {
blk.write(offset, data).unwrap();
assert!(blk.io.num_reads <= READ_WRITE_BLOCKS_UPPER_BOUND);
assert!(blk.io.num_writes <= READ_WRITE_BLOCKS_UPPER_BOUND);
};
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: &mut TestBlockDevice, offset: u64, data: &mut [u8]| {
blk.write(offset, data).unwrap();
assert!(blk.io.num_reads <= READ_WRITE_BLOCKS_UPPER_BOUND);
assert!(blk.io.num_writes <= READ_WRITE_BLOCKS_UPPER_BOUND);
};
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 * 32;
// 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_no_alignment_require_zero_size_scratch() {
let mut blk = TestBlockDeviceBuilder::new()
.set_alignment(1)
.set_block_size(1)
.set_max_gpt_entries(0)
.set_size(1)
.build();
assert_eq!(required_scratch_size(blk.info(), 0).unwrap(), 0);
}
#[test]
fn test_scratch_too_small() {
let storage_size = (TestBlockDeviceBuilder::DEFAULT_BLOCK_SIZE * 3) as usize;
let scratch_size =
TestBlockDeviceBuilder::new().set_size(storage_size).build().scratch.len() - 1;
let mut blk = TestBlockDeviceBuilder::new()
.set_size(storage_size)
.set_scratch_size(scratch_size)
.build();
let block_size = TestBlockDeviceBuilder::DEFAULT_BLOCK_SIZE;
assert!(blk.read(0, &mut vec![0u8; block_size.try_into().unwrap()]).is_err());
}
#[test]
fn test_read_overflow() {
let mut blk = TestBlockDeviceBuilder::new()
.set_alignment(1)
.set_block_size(1)
.set_max_gpt_entries(0)
.set_size(512)
.build();
assert!(blk.read(512, &mut vec![0u8; 1]).is_err());
assert!(blk.read(0, &mut vec![0u8; 513]).is_err());
}
#[test]
fn test_read_arithmetic_overflow() {
let mut blk = TestBlockDeviceBuilder::new()
.set_alignment(1)
.set_block_size(1)
.set_max_gpt_entries(0)
.set_size(512)
.build();
assert!(blk.read(u64::MAX, &mut vec![0u8; 1]).is_err());
}
#[test]
fn test_write_overflow() {
let mut blk = TestBlockDeviceBuilder::new()
.set_alignment(1)
.set_block_size(1)
.set_max_gpt_entries(0)
.set_size(512)
.build();
assert!(blk.write(512, vec![0u8; 1].as_mut_slice()).is_err());
assert!(blk.write(0, vec![0u8; 513].as_mut_slice()).is_err());
}
#[test]
fn test_write_arithmetic_overflow() {
let mut blk = TestBlockDeviceBuilder::new()
.set_alignment(1)
.set_block_size(1)
.set_max_gpt_entries(0)
.set_size(512)
.build();
assert!(blk.write(u64::MAX, vec![0u8; 1].as_mut_slice()).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>());
}
}