blob: beb2cc1a35c931fb8996f25eebe0c49127ada57e [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.
//! The library provides APIs for reading/writing with block devices with arbitrary alignment,
//! ranges and parsing and manipulation GPT.
#![cfg_attr(not(test), no_std)]
#![allow(async_fn_in_trait)]
use liberror::{Error, Result};
use safemath::SafeNum;
// Selective export of submodule types.
mod gpt;
pub use gpt::{
GptCache, GptEntry, GptHeader, GptSyncResult, Partition, PartitionIterator, GPT_GUID_LEN,
GPT_MAGIC, GPT_NAME_LEN_U16,
};
mod algorithm;
pub use algorithm::{read_async, write_async};
/// `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()?)
}
}
/// `BlockIo` provides interfaces for reading and writing block storage medium.
pub trait BlockIo {
/// 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: BlockIo> BlockIo 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 `BlockIo` of where all required methods are `unimplemented!()`
pub struct BlockIoNull {}
impl BlockIo 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!();
}
}
/// 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)),
}
}
/// Computes the required scratch size for initializing a [AsyncBlockDevice].
pub fn scratch_size(io: &mut impl BlockIo) -> Result<usize> {
let info = io.info();
let block_alignment = match info.block_size {
1 => 0,
v => v,
};
Ok(((SafeNum::from(info.alignment) - 1) * 2 + block_alignment).try_into()?)
}
/// `AsyncBlockDevice` provides APIs for asynchronous read/write of raw block or GPT partitions.
pub struct AsyncBlockDevice<'a, T: BlockIo> {
io: T,
scratch: &'a mut [u8],
}
impl<'a, T: BlockIo> 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 `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 = scratch_size(&mut io)?;
match scratch.len() < scratch_size {
true => Err(Error::BufferTooSmall(Some(scratch_size))),
_ => Ok(Self { io, scratch }),
}
}
/// 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.
/// * `resize`: If set to true, the method updates the value of last usable block in the header
/// and the extends last partition to cover the rest of the storage.
/// * `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],
resize: bool,
gpt_cache: &mut GptCache<'_>,
) -> Result<()> {
gpt::update_gpt(&mut self.io, self.scratch, mbr_primary, resize, 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
}
}
#[cfg(test)]
mod test {
use core::mem::size_of;
use gbl_async::block_on;
use gbl_storage_testlib::{
scratch_size, AsyncBlockDevice, TestBlockDeviceBuilder, TestBlockIo,
};
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()];
block_on(blk.new_blk_and_gpt().0.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,
mut write_func: impl FnMut(&mut AsyncBlockDevice<'_, &mut TestBlockIo>, 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.new_blk_and_gpt().0, 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() {
write_test_helper(
&TestCase::new($x0, $x1, $x2, $x3, $x4, $x5),
|blk, offset, data| {
block_on(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);
},
);
}
#[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);
write_test_helper(
&TestCase::new(x0, x1, x2, x3, x4, x5),
|blk, offset, data| {
block_on(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);
},
);
}
}
};
}
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 io = TestBlockIo::new(1, 1, vec![]);
assert_eq!(scratch_size(&mut io).unwrap(), 0);
}
#[test]
fn test_scratch_too_small() {
let mut io = TestBlockIo::new(512, 512, vec![]);
let mut scratch = vec![0u8; scratch_size(&mut io).unwrap() - 1];
assert!(AsyncBlockDevice::new(&mut io, &mut scratch).is_err());
}
#[test]
fn test_read_overflow() {
let mut io = TestBlockIo::new(512, 512, vec![0u8; 512]);
let mut scratch = vec![0u8; scratch_size(&mut io).unwrap()];
let mut blk = AsyncBlockDevice::new(&mut io, &mut scratch).unwrap();
assert!(block_on(blk.read(512, &mut vec![0u8; 1])).is_err());
assert!(block_on(blk.read(0, &mut vec![0u8; 513])).is_err());
}
#[test]
fn test_read_arithmetic_overflow() {
let mut io = TestBlockIo::new(512, 512, vec![0u8; 512]);
let mut scratch = vec![0u8; scratch_size(&mut io).unwrap()];
let mut blk = AsyncBlockDevice::new(&mut io, &mut scratch).unwrap();
assert!(block_on(blk.read(u64::MAX, &mut vec![0u8; 1])).is_err());
}
#[test]
fn test_write_overflow() {
let mut io = TestBlockIo::new(512, 512, vec![0u8; 512]);
let mut scratch = vec![0u8; scratch_size(&mut io).unwrap()];
let mut blk = AsyncBlockDevice::new(&mut io, &mut scratch).unwrap();
assert!(block_on(blk.write(512, &mut vec![0u8; 1])).is_err());
assert!(block_on(blk.write(0, &mut vec![0u8; 513])).is_err());
}
#[test]
fn test_write_arithmetic_overflow() {
let mut io = TestBlockIo::new(512, 512, vec![0u8; 512]);
let mut scratch = vec![0u8; scratch_size(&mut io).unwrap()];
let mut blk = AsyncBlockDevice::new(&mut io, &mut scratch).unwrap();
assert!(block_on(blk.write(u64::MAX, &mut vec![0u8; 1])).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>());
}
}