// 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 { 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 { 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 { 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 { // 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>, info_cache: BlockInfo, } impl GblDisk, Gpt> where B: BlockIo, S: DerefMut, T: DerefMut, { /// Creates a new instance as a GPT device. pub fn new_gpt(mut disk: Disk, gpt: Gpt) -> 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, name: &CStr) -> Result { 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>, 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, 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 { 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 { 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 { 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, 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>, RefMut<'_, Gpt>>, 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, [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, [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) -> Result { 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.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 (, ) impl SparseRawWriter for (u64, &mut Disk) where B: BlockIo, S: DerefMut, { 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 `(, `Partition`)` if the partition exists and is unique. pub fn check_part_unique( devs: &'_ [GblDisk< Disk>, Gpt>, >], 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>, Gpt>, >], 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>, Gpt>, >], 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>, Gpt>, >], ) -> 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 { 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()); } }