blob: 1e23dba588c3a2ba71d69b7c2519614dcac766fe [file]
// Copyright 2024, 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.
//! This file implements storage and partition logic for libgbl.
use crate::fastboot::sparse::{is_sparse_image, write_sparse_image, SparseRawWriter};
use core::cell::{RefCell, RefMut};
use core::{
ffi::CStr,
mem::swap,
ops::{Deref, DerefMut},
};
use gbl_storage::{
BlockInfo, BlockIo, Disk, Gpt, GptBuilder, GptSyncResult, Partition as GptPartition,
SliceMaybeUninit,
};
use liberror::Error;
use safemath::SafeNum;
/// Maximum name length for raw partition.
const RAW_PARTITION_NAME_LEN: usize = 72;
/// Wraps a bytes buffer containing a null-terminated C string
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct RawName([u8; RAW_PARTITION_NAME_LEN]);
impl RawName {
fn new(name: &CStr) -> Result<Self, Error> {
let mut buf = [0u8; RAW_PARTITION_NAME_LEN];
name.to_str().map_err(|_| Error::InvalidInput)?;
let name = name.to_bytes_with_nul();
buf.get_mut(..name.len()).ok_or(Error::InvalidInput)?.clone_from_slice(name);
Ok(Self(buf))
}
/// Decodes to a string.
pub fn to_str(&self) -> &str {
CStr::from_bytes_until_nul(&self.0[..]).unwrap().to_str().unwrap()
}
}
/// Represents a GBL partition.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Partition {
/// Raw storage partition.
Raw(RawName, u64),
/// Gpt Partition.
Gpt(GptPartition),
}
impl Partition {
/// Returns the size.
pub fn size(&self) -> Result<u64, Error> {
let (start, end) = self.absolute_range()?;
Ok((SafeNum::from(end) - start).try_into()?)
}
/// Returns the name.
pub fn name(&self) -> Result<&str, Error> {
Ok(match self {
Partition::Gpt(gpt) => gpt.name().ok_or(Error::InvalidInput)?,
Partition::Raw(name, _) => name.to_str(),
})
}
/// Computes the absolute start and end offset for the partition in the whole block device.
pub fn absolute_range(&self) -> Result<(u64, u64), Error> {
Ok(match self {
Partition::Gpt(gpt) => gpt.absolute_range()?,
Partition::Raw(_, size) => (0, *size),
})
}
}
/// Represents the partition table for a block device. It can either be a GPT partition table or a
/// single whole device raw partition.
enum PartitionTable<G> {
Raw(RawName, u64),
Gpt(G),
}
/// The status of block device
pub enum BlockStatus {
/// Idle,
Idle,
/// An IO in progress.
Pending,
/// Error.
Error(Error),
}
impl BlockStatus {
/// Converts to str.
pub fn to_str(&self) -> &'static str {
match self {
BlockStatus::Idle => "idle",
BlockStatus::Pending => "IO pending",
BlockStatus::Error(_) => "error",
}
}
/// Converts to result.
pub fn result(&self) -> Result<(), Error> {
match self {
Self::Error(e) => Err(*e),
_ => Ok(()),
}
}
}
/// Represents a disk device that contains either GPT partitions or a single whole raw storage
/// partition.
pub struct GblDisk<D, G> {
// Contains a `Disk` for block IO and `Result` to track the most recent error.
// Wraps in `Mutex` as it will be used in parallel fastboot task.
//
// `blk` and `partitions` are wrapped in RefCell because they may be shared by multiple async
// blocks for operations such as parallel fastboot download/flashing. They are also wrapped
// separately in order to make operations on each independent and parallel for use cases such
// as getting partition info for `fastboot getvar` when disk IO is busy.
disk: RefCell<(D, Result<(), Error>)>,
partitions: RefCell<PartitionTable<G>>,
info_cache: BlockInfo,
}
impl<B, S, T> GblDisk<Disk<B, S>, Gpt<T>>
where
B: BlockIo,
S: DerefMut<Target = [u8]>,
T: DerefMut<Target = [u8]>,
{
/// Creates a new instance as a GPT device.
pub fn new_gpt(mut disk: Disk<B, S>, gpt: Gpt<T>) -> Self {
let info_cache = disk.io().info();
Self {
disk: (disk, Ok(())).into(),
info_cache,
partitions: PartitionTable::Gpt(gpt).into(),
}
}
/// Creates a new instance as a raw storage partition.
pub fn new_raw(mut disk: Disk<B, S>, name: &CStr) -> Result<Self, Error> {
let info_cache = disk.io().info();
Ok(Self {
disk: (disk, Ok(())).into(),
info_cache,
partitions: PartitionTable::Raw(RawName::new(name)?, info_cache.total_size()?).into(),
})
}
/// Gets the cached `BlockInfo`.
pub fn block_info(&self) -> BlockInfo {
self.info_cache
}
/// Gets the block status.
pub fn status(&self) -> BlockStatus {
match self.disk.try_borrow_mut().ok() {
None => BlockStatus::Pending,
Some(v) if v.1.is_err() => BlockStatus::Error(v.1.unwrap_err()),
_ => BlockStatus::Idle,
}
}
/// Borrows disk and last_err separately
fn get_disk_and_last_err(
&self,
) -> Result<(RefMut<'_, Disk<B, S>>, RefMut<'_, Result<(), Error>>), Error> {
let res = self.disk.try_borrow_mut().map_err(|_| Error::NotReady)?;
Ok(RefMut::map_split(res, |v| (&mut v.0, &mut v.1)))
}
/// Gets an instance of `PartitionIo` for a partition.
///
/// If `part` is `None`, an IO for the whole block device is returned.
pub fn partition_io(&self, part: Option<&str>) -> Result<PartitionIo<'_, B>, Error> {
let (part_start, part_end) = self.find_partition(part)?.absolute_range()?;
let (disk, last_err) = self.get_disk_and_last_err()?;
Ok(PartitionIo { disk: Disk::from_ref_mut(disk), last_err, part_start, part_end })
}
/// Finds a partition.
///
/// * If `part` is none, the method returns an unnamed `Partition` that represents the whole
// raw storage.
pub fn find_partition(&self, part: Option<&str>) -> Result<Partition, Error> {
let Some(part) = part else {
return Ok(Partition::Raw(RawName::new(c"").unwrap(), self.info_cache.total_size()?));
};
match self.partitions.try_borrow_mut().map_err(|_| Error::NotReady)?.deref() {
PartitionTable::Gpt(gpt) => Ok(Partition::Gpt(gpt.find_partition(part)?)),
PartitionTable::Raw(name, size) if name.to_str() == part => {
Ok(Partition::Raw(*name, *size))
}
_ => Err(Error::NotFound),
}
}
/// Get total number of partitions.
pub fn num_partitions(&self) -> Result<usize, Error> {
match self.partitions.try_borrow_mut().map_err(|_| Error::NotReady)?.deref() {
PartitionTable::Raw(_, _) => Ok(1),
PartitionTable::Gpt(gpt) => gpt.num_partitions(),
}
}
/// Gets a partition by index.
pub fn get_partition_by_idx(&self, idx: usize) -> Result<Partition, Error> {
match self.partitions.try_borrow_mut().map_err(|_| Error::NotReady)?.deref() {
PartitionTable::Raw(name, v) if idx == 0 => Ok(Partition::Raw(*name, *v)),
PartitionTable::Gpt(gpt) => Ok(Partition::Gpt(gpt.get_partition(idx)?)),
_ => Err(Error::InvalidInput),
}
}
/// Syncs GPT if the partition type is GPT.
///
/// # Returns
///
/// * Returns `Ok(Some(sync_res))` if partition type is GPT and disk access is successful, where
/// `sync_res` contains the GPT verification and restoration result.
/// * Returns `Ok(None)` if partition type is not GPT.
/// * Returns `Err` in other cases.
pub async fn sync_gpt(&self) -> Result<Option<GptSyncResult>, Error> {
match self.partitions.try_borrow_mut().map_err(|_| Error::NotReady)?.deref_mut() {
PartitionTable::Raw(_, _) => Ok(None),
PartitionTable::Gpt(ref mut gpt) => {
let mut blk = self.disk.try_borrow_mut().map_err(|_| Error::NotReady)?;
Ok(Some(blk.0.sync_gpt(gpt).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 last partition to cover the rest of the
/// storage.
///
/// # Returns
///
/// * Return `Err(Error::NotReady)` if device is busy.
/// * Return `Err(Error::Unsupported)` if partition type is not GPT.
/// * Return `Ok(())` new GPT is valid and device is updated and synced successfully.
pub async fn update_gpt(&self, mbr_primary: &mut [u8], resize: bool) -> Result<(), Error> {
match self.partitions.try_borrow_mut().map_err(|_| Error::NotReady)?.deref_mut() {
PartitionTable::Raw(_, _) => Err(Error::Unsupported),
PartitionTable::Gpt(ref mut gpt) => {
let mut blk = self.disk.try_borrow_mut().map_err(|_| Error::NotReady)?;
blk.0.update_gpt(mbr_primary, resize, gpt).await
}
}
}
/// Erases GPT on the disk.
///
/// # Returns
///
/// * Return `Err(Error::NotReady)` if device is busy.
/// * Return `Err(Error::Unsupported)` if partition type is not GPT.
pub async fn erase_gpt(&self) -> Result<(), Error> {
match self.partitions.try_borrow_mut().map_err(|_| Error::NotReady)?.deref_mut() {
PartitionTable::Raw(_, _) => Err(Error::Unsupported),
PartitionTable::Gpt(ref mut gpt) => {
let mut disk = self.disk.try_borrow_mut().map_err(|_| Error::NotReady)?;
disk.0.erase_gpt(gpt).await
}
}
}
/// Creates an instance of GptBuilder.
pub fn gpt_builder(
&self,
) -> Result<GptBuilder<RefMut<'_, Disk<B, S>>, RefMut<'_, Gpt<T>>>, Error> {
let mut parts = self.partitions.try_borrow_mut().map_err(|_| Error::NotReady)?;
match parts.deref_mut() {
PartitionTable::Raw(_, _) => Err(Error::Unsupported),
PartitionTable::Gpt(_) => {
let gpt = RefMut::map(parts, |v| match v {
PartitionTable::Gpt(v) => v,
_ => unreachable!(),
});
let (disk, err) = self.get_disk_and_last_err()?;
(*err)?;
Ok(GptBuilder::new(disk, gpt)?.0)
}
}
}
}
/// `PartitionIo` provides read/write APIs to a partition.
pub struct PartitionIo<'a, B: BlockIo> {
disk: Disk<RefMut<'a, B>, RefMut<'a, [u8]>>,
last_err: RefMut<'a, Result<(), Error>>,
part_start: u64,
part_end: u64,
}
impl<'a, B: BlockIo> PartitionIo<'a, B> {
/// Returns the size of the partition.
pub fn size(&self) -> u64 {
// Corrects by construction. Should not fail.
self.part_end.checked_sub(self.part_start).unwrap()
}
/// Gets the block device.
pub fn dev(&mut self) -> &mut Disk<RefMut<'a, B>, RefMut<'a, [u8]>> {
&mut self.disk
}
/// Checks the read/write parameters and returns the absolute offset in the block.
fn check_rw_range(&self, off: u64, size: impl Into<SafeNum>) -> Result<u64, Error> {
let ab_range_end = SafeNum::from(self.part_start) + off + size.into();
// Checks overflow by computing the difference between range end and partition end and
// making sure it succeeds.
let _end_diff: u64 = (SafeNum::from(self.part_end) - ab_range_end).try_into()?;
Ok((SafeNum::from(self.part_start) + off).try_into()?)
}
/// Writes to the partition.
pub async fn write(&mut self, off: u64, data: &mut [u8]) -> Result<(), Error> {
let res =
async { self.disk.write(self.check_rw_range(off, data.len())?, data).await }.await;
*self.last_err = res.and(*self.last_err);
res
}
/// Reads from the partition.
pub async fn read(
&mut self,
off: u64,
out: &mut (impl SliceMaybeUninit + ?Sized),
) -> Result<(), Error> {
let res = async { self.disk.read(self.check_rw_range(off, out.len())?, out).await }.await;
*self.last_err = res.and(*self.last_err);
res
}
/// Writes zeroes to the partition.
pub async fn zeroize(&mut self, scratch: &mut [u8]) -> Result<(), Error> {
let res = async { self.disk.fill(self.part_start, self.size(), 0, scratch).await }.await;
*self.last_err = res.and(*self.last_err);
*self.last_err
}
/// Writes sparse image to the partition.
pub async fn write_sparse(&mut self, off: u64, img: &mut [u8]) -> Result<(), Error> {
let res = async {
let sz = is_sparse_image(img).map_err(|_| Error::InvalidInput)?.data_size();
write_sparse_image(img, &mut (self.check_rw_range(off, sz)?, &mut self.disk)).await
}
.await;
*self.last_err = res.map(|_| ()).and(*self.last_err);
*self.last_err
}
/// Turns this IO into one for a subrange in the partition.
pub fn sub(self, off: u64, sz: u64) -> Result<Self, Error> {
self.check_rw_range(off, sz)?;
let mut sub = self;
sub.part_start += off;
sub.part_end = sub.part_start + sz;
Ok(sub)
}
/// Returns the most recent error.
pub fn last_err(&self) -> Result<(), Error> {
*self.last_err
}
/// Takes the error and resets it.
pub fn take_err(&mut self) -> Result<(), Error> {
let mut err = Ok(());
swap(&mut self.last_err as _, &mut err);
err
}
}
// Implements `SparseRawWriter` for tuple (<flash offset>, <block device>)
impl<B, S> SparseRawWriter for (u64, &mut Disk<B, S>)
where
B: BlockIo,
S: DerefMut<Target = [u8]>,
{
async fn write(&mut self, off: u64, data: &mut [u8]) -> Result<(), Error> {
Ok(self.1.write((SafeNum::from(off) + self.0).try_into()?, data).await?)
}
}
/// Checks that a partition is unique.
///
/// Returns a pair `(<block device index>, `Partition`)` if the partition exists and is unique.
pub fn check_part_unique(
devs: &'_ [GblDisk<
Disk<impl BlockIo, impl DerefMut<Target = [u8]>>,
Gpt<impl DerefMut<Target = [u8]>>,
>],
part: &str,
) -> Result<(usize, Partition), Error> {
let mut filtered = devs
.iter()
.enumerate()
.filter_map(|(i, v)| v.find_partition(Some(part)).ok().map(|v| (i, v)));
match (filtered.next(), filtered.next()) {
(Some(v), None) => Ok(v),
(Some(_), Some(_)) => Err(Error::NotUnique),
_ => Err(Error::NotFound),
}
}
/// Checks that a partition is unique among all block devices and reads from it.
pub async fn read_unique_partition(
devs: &'_ [GblDisk<
Disk<impl BlockIo, impl DerefMut<Target = [u8]>>,
Gpt<impl DerefMut<Target = [u8]>>,
>],
part: &str,
off: u64,
out: &mut (impl SliceMaybeUninit + ?Sized),
) -> Result<(), Error> {
devs[check_part_unique(devs, part)?.0].partition_io(Some(part))?.read(off, out).await
}
/// Checks that a partition is unique among all block devices and writes to it.
pub async fn write_unique_partition(
devs: &'_ [GblDisk<
Disk<impl BlockIo, impl DerefMut<Target = [u8]>>,
Gpt<impl DerefMut<Target = [u8]>>,
>],
part: &str,
off: u64,
data: &mut [u8],
) -> Result<(), Error> {
devs[check_part_unique(devs, part)?.0].partition_io(Some(part))?.write(off, data).await
}
/// Syncs all GPT type partition devices.
pub async fn sync_gpt(
devs: &'_ [GblDisk<
Disk<impl BlockIo, impl DerefMut<Target = [u8]>>,
Gpt<impl DerefMut<Target = [u8]>>,
>],
) -> Result<(), Error> {
for ele in &devs[..] {
ele.sync_gpt().await?;
}
Ok(())
}
#[cfg(test)]
pub(crate) mod test {
use super::*;
use crate::ops::test::{FakeGblOpsStorage, TestGblDisk};
use core::fmt::Debug;
use gbl_async::block_on;
/// Absolute start/end offset and size of "boot_a/b" partitions in
/// "../../libstorage/test/gpt_test_1.bin"
const BOOT_A_OFF: u64 = 17 * 1024;
const BOOT_A_END: u64 = 25 * 1024;
const BOOT_A_SZ: u64 = BOOT_A_END - BOOT_A_OFF;
const BOOT_B_OFF: u64 = 25 * 1024;
const BOOT_B_END: u64 = 37 * 1024;
const BOOT_B_SZ: u64 = BOOT_B_END - BOOT_B_OFF;
/// Total size of disk "../../libstorage/test/gpt_test_1.bin"
const GPT_DISK_1_SZ: u64 = 64 * 1024;
/// A helper to convert an integer into usize and panics on error.
fn to_usize(val: impl TryInto<usize, Error = impl Debug>) -> usize {
val.try_into().unwrap()
}
/// A helper to create a GPT type TestGblDisk
fn gpt_disk(data: impl AsRef<[u8]>) -> TestGblDisk {
let mut res = FakeGblOpsStorage::default();
res.add_gpt_device(data);
res.0.pop().unwrap()
}
/// A helper to create a raw disk partition type TestGblDisk
fn raw_disk(name: &CStr, data: impl AsRef<[u8]>) -> TestGblDisk {
let mut res = FakeGblOpsStorage::default();
res.add_raw_device(name, data);
res.0.pop().unwrap()
}
#[test]
fn test_find_partition_gpt() {
let gpt = gpt_disk(include_bytes!("../../libstorage/test/gpt_test_1.bin"));
assert_eq!(block_on(gpt.sync_gpt()).unwrap(), Some(GptSyncResult::BothValid));
let boot_a = gpt.find_partition(Some("boot_a")).unwrap();
assert_eq!(boot_a.name().unwrap(), "boot_a");
assert_eq!(boot_a.size().unwrap(), BOOT_A_SZ);
assert_eq!(boot_a.absolute_range().unwrap(), (BOOT_A_OFF, BOOT_A_END));
let boot_b = gpt.find_partition(Some("boot_b")).unwrap();
assert_eq!(boot_b.name().unwrap(), "boot_b");
assert_eq!(boot_b.size().unwrap(), BOOT_B_SZ);
assert_eq!(boot_b.absolute_range().unwrap(), (BOOT_B_OFF, BOOT_B_END));
let unnamed_whole = gpt.find_partition(None).unwrap();
assert_eq!(unnamed_whole.name().unwrap(), "");
assert_eq!(unnamed_whole.size().unwrap(), GPT_DISK_1_SZ);
assert_eq!(unnamed_whole.absolute_range().unwrap(), (0, GPT_DISK_1_SZ));
assert!(gpt.find_partition(Some("not-exist")).is_err());
}
#[test]
fn test_find_partition_raw() {
let disk = include_bytes!("../../libstorage/test/gpt_test_1.bin");
let raw = raw_disk(c"raw", &disk);
let raw_part = raw.find_partition(Some("raw")).unwrap();
assert_eq!(raw_part.name().unwrap(), "raw");
assert_eq!(raw_part.size().unwrap(), GPT_DISK_1_SZ);
assert_eq!(raw_part.absolute_range().unwrap(), (0, GPT_DISK_1_SZ));
let unnamed_whole = raw.find_partition(None).unwrap();
assert_eq!(unnamed_whole.name().unwrap(), "");
assert_eq!(unnamed_whole.size().unwrap(), GPT_DISK_1_SZ);
assert_eq!(unnamed_whole.absolute_range().unwrap(), (0, GPT_DISK_1_SZ));
assert!(raw.find_partition(Some("boot_a")).is_err());
}
/// A helper for testing partition read.
///
/// Tests that the content read at `off..off+sz` is the same as `part_content[off..off+sz]`.
fn test_part_read(
blk: &TestGblDisk,
part: Option<&str>,
part_content: &[u8],
off: u64,
sz: u64,
) {
let mut out = vec![0u8; to_usize(sz)];
block_on(blk.partition_io(part).unwrap().read(off, &mut out[..])).unwrap();
assert_eq!(out, part_content[to_usize(off)..][..out.len()].to_vec());
// Reads using the `sub()` and then read approach.
let mut out = vec![0u8; to_usize(sz)];
let mut io = blk.partition_io(part).unwrap().sub(off, sz).unwrap();
block_on(io.read(0, &mut out[..])).unwrap();
assert_eq!(out, part_content[to_usize(off)..][..out.len()].to_vec());
}
#[test]
fn test_read_partition_gpt() {
let disk = include_bytes!("../../libstorage/test/gpt_test_1.bin");
let gpt = gpt_disk(&disk[..]);
assert_eq!(block_on(gpt.sync_gpt()).unwrap(), Some(GptSyncResult::BothValid));
let expect_boot_a = include_bytes!("../../libstorage/test/boot_a.bin");
test_part_read(&gpt, Some("boot_a"), expect_boot_a, 1, 1024);
let expect_boot_b = include_bytes!("../../libstorage/test/boot_b.bin");
test_part_read(&gpt, Some("boot_b"), expect_boot_b, 1, 1024);
// Whole block read.
test_part_read(&gpt, None, disk, 1, 1024);
}
#[test]
fn test_read_partition_raw() {
let disk = include_bytes!("../../libstorage/test/gpt_test_1.bin");
let raw = raw_disk(c"raw", &disk);
test_part_read(&raw, Some("raw"), disk, 1, 1024);
test_part_read(&raw, None, disk, 1, 1024);
}
/// A helper for testing partition write.
fn test_part_write(blk: &TestGblDisk, part: Option<&str>, off: u64, sz: u64) {
// Reads the current partition content
let mut part_content = vec![0u8; to_usize(blk.partition_io(part).unwrap().size())];
block_on(blk.partition_io(part).unwrap().read(0, &mut part_content[..])).unwrap();
// Flips all the bits in the target range and writes back.
let seg = &mut part_content[to_usize(off)..][..to_usize(sz)];
seg.iter_mut().for_each(|v| *v = !(*v));
block_on(blk.partition_io(part).unwrap().write(off, seg)).unwrap();
// Checks that data is written.
test_part_read(blk, part, &part_content, off, sz);
// Writes using the `sub()` and then write approach.
let seg = &mut part_content[to_usize(off)..][..to_usize(sz)];
seg.iter_mut().for_each(|v| *v = !(*v));
block_on(blk.partition_io(part).unwrap().sub(off, sz).unwrap().write(0, seg)).unwrap();
test_part_read(blk, part, &part_content, off, sz);
}
#[test]
fn test_write_partition_gpt() {
let gpt = gpt_disk(include_bytes!("../../libstorage/test/gpt_test_1.bin"));
assert_eq!(block_on(gpt.sync_gpt()).unwrap(), Some(GptSyncResult::BothValid));
test_part_write(&gpt, Some("boot_a"), 1, 1024);
test_part_write(&gpt, Some("boot_b"), 1, 1024);
test_part_write(&gpt, None, 1, 1024);
}
#[test]
fn test_write_partition_raw() {
let mut raw = raw_disk(c"raw", include_bytes!("../../libstorage/test/gpt_test_1.bin"));
test_part_write(&mut raw, Some("raw"), 1, 1024);
test_part_write(&mut raw, None, 1, 1024);
}
#[test]
fn test_read_write_partition_overflow() {
let disk = include_bytes!("../../libstorage/test/gpt_test_1.bin");
let gpt = gpt_disk(&disk[..]);
assert_eq!(block_on(gpt.sync_gpt()).unwrap(), Some(GptSyncResult::BothValid));
let mut part_io = gpt.partition_io(Some("boot_a")).unwrap();
assert!(block_on(part_io.read(BOOT_A_END, &mut vec![0u8; 1][..])).is_err());
assert!(block_on(part_io.read(BOOT_A_OFF, &mut vec![0u8; to_usize(BOOT_A_SZ) + 1][..]))
.is_err());
assert!(block_on(part_io.write(BOOT_A_END, &mut vec![0u8; 1][..])).is_err());
assert!(block_on(part_io.write(BOOT_A_OFF, &mut vec![0u8; to_usize(BOOT_A_SZ) + 1][..]))
.is_err());
let raw = raw_disk(c"raw", &disk);
let mut part_io = raw.partition_io(Some("raw")).unwrap();
assert!(block_on(part_io.read(GPT_DISK_1_SZ, &mut vec![0u8; 1][..])).is_err());
assert!(block_on(part_io.read(0, &mut vec![0u8; to_usize(GPT_DISK_1_SZ) + 1][..])).is_err());
assert!(block_on(part_io.write(GPT_DISK_1_SZ, &mut vec![0u8; 1][..])).is_err());
assert!(
block_on(part_io.write(0, &mut vec![0u8; to_usize(GPT_DISK_1_SZ) + 1][..])).is_err()
);
}
#[test]
fn test_sub_overflow() {
let disk = include_bytes!("../../libstorage/test/gpt_test_1.bin");
let gpt = gpt_disk(&disk[..]);
assert_eq!(block_on(gpt.sync_gpt()).unwrap(), Some(GptSyncResult::BothValid));
assert!(gpt.partition_io(Some("boot_a")).unwrap().sub(0, BOOT_A_SZ + 1).is_err());
assert!(gpt.partition_io(Some("boot_a")).unwrap().sub(1, BOOT_A_SZ).is_err());
let raw = raw_disk(c"raw", &disk);
assert!(raw.partition_io(Some("raw")).unwrap().sub(0, GPT_DISK_1_SZ + 1).is_err());
assert!(raw.partition_io(Some("raw")).unwrap().sub(1, GPT_DISK_1_SZ).is_err());
}
#[test]
fn test_write_sparse() {
let sparse_raw = include_bytes!("../testdata/sparse_test_raw.bin");
let mut sparse = include_bytes!("../testdata/sparse_test.bin").to_vec();
let raw = &vec![0u8; sparse_raw.len() + 512][..];
let blk = raw_disk(c"raw", raw);
block_on(
blk.partition_io(Some("raw"))
.unwrap()
.sub(1, u64::try_from(raw.len() - 1).unwrap())
.unwrap()
.write_sparse(1, &mut sparse),
)
.unwrap();
let mut expected = vec![0u8; raw.len()];
expected[1 + 1..][..sparse_raw.len()].clone_from_slice(sparse_raw);
test_part_read(&blk, Some("raw"), &expected, 1, sparse_raw.len().try_into().unwrap());
}
#[test]
fn test_write_sparse_not_sparse_image() {
let sparse_raw = include_bytes!("../testdata/sparse_test_raw.bin");
let mut sparse = include_bytes!("../testdata/sparse_test.bin").to_vec();
sparse[0] = !sparse[0]; // Corrupt image.
let raw = raw_disk(c"raw", vec![0u8; sparse_raw.len() + 512]);
assert!(
block_on(raw.partition_io(Some("raw")).unwrap().write_sparse(1, &mut sparse)).is_err()
);
assert!(raw.partition_io(Some("raw")).unwrap().last_err().is_err());
}
#[test]
fn test_write_sparse_overflow_size() {
let sparse_raw = include_bytes!("../testdata/sparse_test_raw.bin");
let mut sparse = include_bytes!("../testdata/sparse_test.bin").to_vec();
let raw = raw_disk(c"raw", vec![0u8; sparse_raw.len()]);
assert!(
block_on(raw.partition_io(Some("raw")).unwrap().write_sparse(1, &mut sparse)).is_err()
);
assert!(raw.partition_io(Some("raw")).unwrap().last_err().is_err());
}
#[test]
fn test_partiton_last_err_read() {
let raw = raw_disk(c"raw", vec![0u8; 1024]);
let mut part_io = raw.partition_io(Some("raw")).unwrap();
// Causes some error by read
assert!(block_on(part_io.read(1024, &mut [0][..])).is_err());
assert!(part_io.last_err().is_err());
}
#[test]
fn test_partiton_last_err_write() {
let raw = raw_disk(c"raw", vec![0u8; 1024]);
let mut part_io = raw.partition_io(Some("raw")).unwrap();
// Causes some error by write
assert!(block_on(part_io.write(1024, &mut [0])).is_err());
assert!(part_io.last_err().is_err());
}
#[test]
fn test_partiton_last_err_persist_through_operation() {
let raw = raw_disk(c"raw", vec![0u8; 1024]);
// Causes some error by read
assert!(block_on(raw.partition_io(Some("raw")).unwrap().read(1024, &mut [0][..])).is_err());
// Tracked error should persist regardless of how many times we get partition io.
assert!(raw.partition_io(Some("raw")).unwrap().last_err().is_err());
assert!(raw.partition_io(None).unwrap().last_err().is_err());
// Should persist even after successful operations.
block_on(raw.partition_io(Some("raw")).unwrap().read(1023, &mut [0][..])).unwrap();
assert!(raw.partition_io(Some("raw")).unwrap().last_err().is_err());
block_on(raw.partition_io(Some("raw")).unwrap().write(1023, &mut [0][..])).unwrap();
assert!(raw.partition_io(Some("raw")).unwrap().last_err().is_err());
assert!(raw.partition_io(None).unwrap().last_err().is_err());
// Taking error should reset it.
assert!(raw.partition_io(None).unwrap().take_err().is_err());
assert!(raw.partition_io(None).unwrap().last_err().is_ok());
}
#[test]
fn test_partition_iter() {
let raw = raw_disk(c"raw", vec![0u8; 1024]);
assert_eq!(raw.num_partitions().unwrap(), 1);
assert_eq!(raw.get_partition_by_idx(0).unwrap().name(), Ok("raw"));
assert_eq!(raw.get_partition_by_idx(0).unwrap().size(), Ok(1024));
let gpt = gpt_disk(include_bytes!("../../libstorage/test/gpt_test_1.bin"));
block_on(gpt.sync_gpt()).unwrap();
assert_eq!(gpt.num_partitions().unwrap(), 2);
assert_eq!(gpt.get_partition_by_idx(0).unwrap().name().unwrap(), "boot_a");
assert_eq!(gpt.get_partition_by_idx(0).unwrap().size().unwrap(), 0x2000);
assert_eq!(gpt.get_partition_by_idx(1).unwrap().name().unwrap(), "boot_b");
assert_eq!(gpt.get_partition_by_idx(1).unwrap().size().unwrap(), 0x3000);
}
/// A test helper for `read_unique_partition`
/// It verifies that data read from partition `part` at offset `off` is the same as
/// `part_content[off..off+sz]`.
fn check_read_partition(
devs: &[TestGblDisk],
part: &str,
part_content: &[u8],
off: u64,
sz: u64,
) {
let mut out = vec![0u8; to_usize(sz)];
block_on(read_unique_partition(devs, part, off, &mut out[..])).unwrap();
assert_eq!(out, part_content[to_usize(off)..][..out.len()]);
}
#[test]
fn test_read_unique_partition() {
let mut devs = FakeGblOpsStorage::default();
devs.add_gpt_device(include_bytes!("../../libstorage/test/gpt_test_1.bin"));
devs.add_gpt_device(include_bytes!("../../libstorage/test/gpt_test_2.bin"));
devs.add_raw_device(c"raw_0", [0xaau8; 4 * 1024]);
devs.add_raw_device(c"raw_1", [0x55u8; 4 * 1024]);
let boot_a = include_bytes!("../../libstorage/test/boot_a.bin");
let boot_b = include_bytes!("../../libstorage/test/boot_b.bin");
let off = 512u64;
let sz = 1024u64;
check_read_partition(&mut devs, "boot_a", boot_a, off, sz);
check_read_partition(&mut devs, "boot_b", boot_b, off, sz);
let vendor_boot_a = include_bytes!("../../libstorage/test/vendor_boot_a.bin");
let vendor_boot_b = include_bytes!("../../libstorage/test/vendor_boot_b.bin");
check_read_partition(&mut devs, "vendor_boot_a", vendor_boot_a, off, sz);
check_read_partition(&mut devs, "vendor_boot_b", vendor_boot_b, off, sz);
check_read_partition(&mut devs, "raw_0", &[0xaau8; 4 * 1024][..], off, sz);
check_read_partition(&mut devs, "raw_1", &[0x55u8; 4 * 1024][..], off, sz);
}
/// A test helper for `write_unique_partition`
fn check_write_partition(devs: &[TestGblDisk], part: &str, off: u64, sz: u64) {
// Reads the current partition content
let (_, p) = check_part_unique(devs, part).unwrap();
let mut part_content = vec![0u8; to_usize(p.size().unwrap())];
block_on(read_unique_partition(devs, part, 0, &mut part_content[..])).unwrap();
// Flips all the bits in the target range and writes back.
let seg = &mut part_content[to_usize(off)..][..to_usize(sz)];
seg.iter_mut().for_each(|v| *v = !(*v));
block_on(write_unique_partition(devs, part, off, seg)).unwrap();
// Checks that data is written.
check_read_partition(devs, part, &part_content, off, sz);
}
#[test]
fn test_write_unique_partition() {
let mut devs = FakeGblOpsStorage::default();
devs.add_gpt_device(include_bytes!("../../libstorage/test/gpt_test_1.bin"));
devs.add_gpt_device(include_bytes!("../../libstorage/test/gpt_test_2.bin"));
devs.add_raw_device(c"raw_0", [0xaau8; 4 * 1024]);
devs.add_raw_device(c"raw_1", [0x55u8; 4 * 1024]);
let off = 512u64;
let sz = 1024u64;
check_write_partition(&mut devs, "boot_a", off, sz);
check_write_partition(&mut devs, "boot_b", off, sz);
check_write_partition(&mut devs, "vendor_boot_a", off, sz);
check_write_partition(&mut devs, "vendor_boot_b", off, sz);
check_write_partition(&mut devs, "raw_0", off, sz);
check_write_partition(&mut devs, "raw_1", off, sz);
}
#[test]
fn test_rw_fail_with_non_unique_partition() {
let mut devs = FakeGblOpsStorage::default();
devs.add_gpt_device(include_bytes!("../../libstorage/test/gpt_test_1.bin"));
devs.add_gpt_device(include_bytes!("../../libstorage/test/gpt_test_1.bin"));
devs.add_raw_device(c"raw", [0xaau8; 4 * 1024]);
devs.add_raw_device(c"raw", [0x55u8; 4 * 1024]);
assert!(block_on(read_unique_partition(&devs, "boot_a", 0, &mut [] as &mut [u8],)).is_err());
assert!(block_on(write_unique_partition(&devs, "boot_a", 0, &mut [],)).is_err());
assert!(block_on(read_unique_partition(&devs, "raw", 0, &mut [] as &mut [u8],)).is_err());
assert!(block_on(write_unique_partition(&devs, "raw", 0, &mut [],)).is_err());
}
}