blob: 2f88d6927b0455b8e93253a13938d047bc68ed09 [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.
use crate::{aligned_subslice, read, write_bytes_mut, BlockIo, Result, StorageError};
use core::default::Default;
use core::mem::{align_of, size_of};
use core::num::NonZeroU64;
use crc32fast::Hasher;
use safemath::SafeNum;
use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref};
const GPT_GUID_LEN: usize = 16;
pub const GPT_NAME_LEN_U16: usize = 36;
#[repr(C, packed)]
#[derive(Debug, Default, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
pub struct GptHeader {
pub magic: u64,
pub revision: u32,
pub size: u32,
pub crc32: u32,
pub reserved0: u32,
pub current: u64,
pub backup: u64,
pub first: u64,
pub last: u64,
pub guid: [u8; GPT_GUID_LEN],
pub entries: u64,
pub entries_count: u32,
pub entries_size: u32,
pub entries_crc: u32,
}
impl GptHeader {
/// Cast a bytes slice into a GptHeader structure.
fn from_bytes(bytes: &mut [u8]) -> &mut GptHeader {
Ref::<_, GptHeader>::new_from_prefix(bytes).unwrap().0.into_mut()
}
/// Update the header crc32 value.
pub fn update_crc(&mut self) {
self.crc32 = 0;
self.crc32 = crc32(self.as_bytes());
}
}
/// GptEntry is the partition entry data structure in the GPT.
#[repr(C)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
pub struct GptEntry {
pub part_type: [u8; GPT_GUID_LEN],
pub guid: [u8; GPT_GUID_LEN],
pub first: u64,
pub last: u64,
pub flags: u64,
pub name: [u16; GPT_NAME_LEN_U16],
}
impl GptEntry {
/// Return the partition entry size in blocks.
pub fn blocks(&self) -> Result<u64> {
u64::try_from((SafeNum::from(self.last) - self.first) + 1).map_err(|e| e.into())
}
/// Return whether this is a `NULL` entry. The first null entry marks the end of the partition
/// entries.
fn is_null(&self) -> bool {
self.first == 0 && self.last == 0
}
/// Decode the partition name into a string. A length N utf16 string can be at most 2N utf8
/// bytes. Therefore, a safe size of `buffer` is 2*GPT_NAME_LEN_U16 = 72.
pub fn name_to_str<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a str> {
let mut index = 0;
for c in char::decode_utf16(self.name) {
match c.unwrap_or(char::REPLACEMENT_CHARACTER) {
'\0' => break,
c if c.len_utf8() <= buffer[index..].len() => {
index += c.encode_utf8(&mut buffer[index..]).len()
}
_ => return Err(StorageError::InvalidInput), // Not enough space in `buffer`.
}
}
// SAFETY:
// _unchecked should be OK here since we wrote each utf8 byte ourselves,
// but it's just an optimization, checked version would be fine also.
unsafe { Ok(core::str::from_utf8_unchecked(&buffer[..index])) }
}
}
impl core::fmt::Display for GptEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// Format: partition name: "abc", [first, last]: [123, 456]
let mut name_conversion_buffer = [0u8; GPT_NAME_LEN_U16 * 2];
let name = self.name_to_str(&mut name_conversion_buffer).map_err(|_| core::fmt::Error)?;
write!(f, "partition name: \"{}\", [first, last]: [{}, {}]", name, self.first, self.last)
}
}
// core::mem::offset_of!(GptHeader, crc32) is unsatble feature and rejected by the compiler in our
// settings. We pre-compute the value here.
const GPT_CRC32_OFFSET: u64 = 16;
const GPT_ENTRY_ALIGNMENT: u64 = align_of::<GptEntry>() as u64;
const GPT_ENTRY_SIZE: u64 = size_of::<GptEntry>() as u64;
const GPT_MAX_NUM_ENTRIES: u64 = 128;
const GPT_HEADER_SIZE: u64 = size_of::<GptHeader>() as u64; // 92 bytes.
const GPT_HEADER_SIZE_PADDED: u64 =
(GPT_HEADER_SIZE + GPT_ENTRY_ALIGNMENT - 1) / GPT_ENTRY_ALIGNMENT * GPT_ENTRY_ALIGNMENT;
pub const GPT_MAGIC: u64 = 0x5452415020494645;
enum HeaderType {
Primary,
Secondary,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
struct GptInfo {
// The number of valid entries in the entries array.
// May change as partitions are added or removed.
num_valid_entries: Option<NonZeroU64>,
// The maximum number of elements available in the entries array.
// Note: this is GREATER THAN OR EQUAL TO the number of valid entries
// and LESS THAN OR EQUAL TO the value of GPT_MAX_NUM_ENTRIES.
// Values other than GPT_MAX_NUM_ENTRIES are mostly used in unit tests.
max_entries: u64,
// Block size of the GPT disk.
block_size: u64,
}
impl GptInfo {
fn from_bytes(bytes: &mut [u8]) -> &mut Self {
Ref::<_, GptInfo>::new_from_prefix(bytes).unwrap().0.into_mut()
}
fn num_valid_entries(&self) -> Result<u64> {
Ok(self.num_valid_entries.ok_or_else(|| StorageError::InvalidInput)?.get())
}
}
/// An object that contains the GPT header/entries information.
pub(crate) struct Gpt<'a> {
info: &'a mut GptInfo,
/// Raw bytes of primary GPT header.
primary_header: &'a mut [u8],
/// Raw bytes of primary GPT entries.
primary_entries: &'a mut [u8],
/// Raw bytes of secondary GPT header.
secondary_header: &'a mut [u8],
/// Raw bytes of secondary GPT entries.
secondary_entries: &'a mut [u8],
}
impl<'a> Gpt<'a> {
/// Create an uninitialized Gpt instance from a provided buffer.
///
/// # Args:
///
/// * `max_entries`: Maximum number of entries allowed.
///
/// * `buffer`: Buffer for creating the object. Must have a size at least
/// `Gpt::required_buffer_size(max_entries)`.
pub(crate) fn new_from_buffer(max_entries: u64, buffer: &'a mut [u8]) -> Result<Gpt<'a>> {
if max_entries > GPT_MAX_NUM_ENTRIES
|| buffer.len() < Self::required_buffer_size(max_entries)?
{
return Err(StorageError::InvalidInput);
}
let buffer = aligned_subslice(buffer, GPT_ENTRY_ALIGNMENT)?;
*GptInfo::from_bytes(buffer) =
GptInfo { num_valid_entries: None, max_entries, block_size: 0 };
Self::from_existing(buffer)
}
/// Reconstruct an existing Gpt struct from a buffer previously created with `new_from_buffer`.
///
/// The method simply partitions the input buffer and populate the `GptInfo` struct and
/// primary/secondary header/entries slices. It assumes that the buffer contains a valid
/// GptInfo struct.
pub fn from_existing(buffer: &'a mut [u8]) -> Result<Gpt<'a>> {
let buffer = aligned_subslice(buffer, GPT_ENTRY_ALIGNMENT)?;
let (info, remain) = Ref::<_, GptInfo>::new_from_prefix(buffer).unwrap();
let entries_size = SafeNum::from(info.max_entries) * GPT_ENTRY_SIZE;
let header_size: usize = SafeNum::from(GPT_HEADER_SIZE_PADDED).try_into()?;
let split_pos = entries_size + header_size;
let (primary, secondary) = remain.split_at_mut(split_pos.try_into()?);
let (primary_header, primary_entries) = primary.split_at_mut(header_size);
let (secondary_header, secondary_entries) = secondary.split_at_mut(header_size);
Ok(Self {
info: info.into_mut(),
primary_header,
primary_entries,
secondary_header,
secondary_entries: &mut secondary_entries[..entries_size.try_into()?],
})
}
/// The minimum buffer size needed for `new_from_buffer()`
pub(crate) fn required_buffer_size(max_entries: u64) -> Result<usize> {
let entries_size = SafeNum::from(max_entries) * GPT_ENTRY_SIZE;
(((entries_size + GPT_HEADER_SIZE_PADDED) * 2) + size_of::<GptInfo>() + GPT_ENTRY_ALIGNMENT
- 1)
.try_into()
.map_err(|e: safemath::Error| e.into())
}
/// Return the list of GPT entries.
///
/// If the object does not contain a valid GPT, the method returns Error.
pub(crate) fn entries(&self) -> Result<&[GptEntry]> {
self.check_valid()?;
Ok(&Ref::<_, [GptEntry]>::new_slice(&self.primary_entries[..]).unwrap().into_slice()
[..self.info.num_valid_entries()?.try_into()?])
}
/// Search for a partition entry.
///
/// If partition doesn't exist, the method returns `Ok(None)`.
///
/// If the object does not contain a valid GPT, the method returns Error.
pub(crate) fn find_partition(&self, part: &str) -> Result<&GptEntry> {
for entry in self.entries()? {
let mut name_conversion_buffer = [0u8; GPT_NAME_LEN_U16 * 2];
if entry.name_to_str(&mut name_conversion_buffer)? != part {
continue;
}
return Ok(entry);
}
Err(StorageError::NotExist)
}
/// Check whether the Gpt has been initialized.
fn check_valid(&self) -> Result<()> {
self.info.num_valid_entries()?;
Ok(())
}
/// Helper function for loading and validating GPT header and entries.
fn validate_gpt(
&mut self,
blk_dev: &mut (impl BlockIo + ?Sized),
scratch: &mut [u8],
header_type: HeaderType,
) -> Result<bool> {
let (header_start, header_bytes, entries) = match header_type {
HeaderType::Primary => {
(blk_dev.block_size().into(), &mut self.primary_header, &mut self.primary_entries)
}
HeaderType::Secondary => (
(SafeNum::from(blk_dev.num_blocks()) - 1) * blk_dev.block_size(),
&mut self.secondary_header,
&mut self.secondary_entries,
),
};
read(blk_dev, header_start.try_into()?, header_bytes, scratch)?;
let header =
Ref::<_, GptHeader>::new_from_prefix(header_bytes.as_bytes()).unwrap().0.into_ref();
if header.magic != GPT_MAGIC {
return Ok(false);
}
let entries_size = SafeNum::from(header.entries_count) * GPT_ENTRY_SIZE;
let entries_offset = SafeNum::from(header.entries) * blk_dev.block_size();
if self.info.max_entries < header.entries_count.into()
|| u64::try_from(entries_size + entries_offset)?
> ((SafeNum::from(blk_dev.num_blocks()) - 1) * blk_dev.block_size()).try_into()?
{
return Ok(false);
}
let crc32_offset = SafeNum::from(GPT_CRC32_OFFSET).try_into()?;
let mut hasher = Hasher::new();
hasher.update(&header.as_bytes()[..crc32_offset]);
hasher.update(&[0u8; size_of::<u32>()]);
hasher.update(&header.as_bytes()[crc32_offset + size_of::<u32>()..]);
if hasher.finalize() != header.crc32 {
return Ok(false);
}
// Load the entries
let out = &mut entries[..entries_size.try_into()?];
read(blk_dev, entries_offset.try_into()?, out, scratch)?;
// Validate entries crc32.
Ok(header.entries_crc == crc32(out))
}
/// Load and sync GPT from a block device.
fn load_and_sync(
&mut self,
blk_dev: &mut (impl BlockIo + ?Sized),
scratch: &mut [u8],
) -> Result<()> {
self.info.num_valid_entries = None;
let block_size = blk_dev.block_size();
let total_blocks: SafeNum = blk_dev.num_blocks().into();
let primary_header_blk = 1;
let primary_header_pos = block_size;
let secondary_header_blk = total_blocks - 1;
let secondary_header_pos = secondary_header_blk * block_size;
// Entries position for restoring.
let primary_entries_blk = 2;
let primary_entries_pos = SafeNum::from(primary_entries_blk) * block_size;
let primary_valid = self.validate_gpt(blk_dev, scratch, HeaderType::Primary)?;
let secondary_valid = self.validate_gpt(blk_dev, scratch, HeaderType::Secondary)?;
let primary_header = GptHeader::from_bytes(self.primary_header);
let secondary_header = GptHeader::from_bytes(self.secondary_header);
if !primary_valid {
if !secondary_valid {
return Err(StorageError::NoValidGpt);
}
// Restore to primary
primary_header.as_bytes_mut().clone_from_slice(secondary_header.as_bytes());
self.primary_entries.clone_from_slice(&self.secondary_entries);
primary_header.current = primary_header_blk;
primary_header.backup = secondary_header_blk.try_into()?;
primary_header.entries = primary_entries_blk;
primary_header.update_crc();
write_bytes_mut(blk_dev, primary_header_pos, primary_header.as_bytes_mut(), scratch)?;
write_bytes_mut(
blk_dev,
primary_entries_pos.try_into()?,
self.primary_entries,
scratch,
)?
} else if !secondary_valid {
// Restore to secondary
let secondary_entries_pos = secondary_header_pos
- (SafeNum::from(self.info.max_entries) * core::mem::size_of::<GptEntry>());
let secondary_entries_blk = secondary_entries_pos / block_size;
secondary_header.as_bytes_mut().clone_from_slice(primary_header.as_bytes());
self.secondary_entries.clone_from_slice(&self.primary_entries);
secondary_header.current = secondary_header_blk.try_into()?;
secondary_header.backup = primary_header_blk;
secondary_header.entries = secondary_entries_blk.try_into()?;
secondary_header.update_crc();
write_bytes_mut(
blk_dev,
secondary_header_pos.try_into()?,
secondary_header.as_bytes_mut(),
scratch,
)?;
write_bytes_mut(
blk_dev,
secondary_entries_pos.try_into()?,
self.secondary_entries,
scratch,
)?;
}
// Calculate actual number of GPT entries by finding the first invalid entry.
let entries =
Ref::<_, [GptEntry]>::new_slice(&self.primary_entries[..]).unwrap().into_slice();
self.info.num_valid_entries =
NonZeroU64::new(match entries.iter().position(|e| e.is_null()) {
Some(idx) => idx as u64,
_ => self.info.max_entries,
});
self.info.block_size = block_size;
Ok(())
}
}
/// Wrapper of gpt.load_and_sync(). Library internal helper for AsBlockDevice::sync_gpt().
pub(crate) fn gpt_sync(
blk_dev: &mut (impl BlockIo + ?Sized),
gpt: &mut Gpt,
scratch: &mut [u8],
) -> Result<()> {
gpt.load_and_sync(blk_dev, scratch)
}
/// Checks if a read/write range into a GPT partition overflows and returns the range's absolute
/// offset in the block device.
pub(crate) fn check_gpt_rw_params(
gpt_cache_buffer: &mut [u8],
part_name: &str,
offset: u64,
size: usize,
) -> Result<u64> {
let gpt = Gpt::from_existing(gpt_cache_buffer)?;
let entry = gpt.find_partition(part_name)?;
let end: u64 = (SafeNum::from(offset) + size).try_into()?;
let total_size = SafeNum::from(entry.blocks()?) * gpt.info.block_size;
match end <= total_size.try_into()? {
true => Ok((SafeNum::from(entry.first) * gpt.info.block_size + offset).try_into()?),
false => Err(StorageError::OutOfRange),
}
}
fn crc32(data: &[u8]) -> u32 {
let mut hasher = Hasher::new();
hasher.update(data);
hasher.finalize()
}
#[cfg(test)]
pub(crate) mod test {
use super::*;
use gbl_storage_testlib::{
alignment_scratch_size, AsBlockDevice, TestBlockDevice, TestBlockDeviceBuilder,
};
/// Helper function to extract the gpt header from a test block device.
/// This function lives here and not as a method of TestBlockDevice so that
/// the Gpt type doesn't have to be exported.
fn gpt(dev: &mut TestBlockDevice) -> Gpt {
let (_, gpt) = dev.scratch.split_at_mut(alignment_scratch_size(&mut dev.io).unwrap());
Gpt::from_existing(gpt).unwrap()
}
#[test]
fn test_new_from_buffer() {
let mut dev: TestBlockDevice = include_bytes!("../test/gpt_test_1.bin").as_slice().into();
dev.sync_gpt().unwrap();
assert_eq!(dev.partition_iter().count(), 2);
dev.find_partition("boot_a").unwrap();
dev.find_partition("boot_b").unwrap();
assert!(dev.find_partition("boot_c").is_err());
}
#[test]
fn test_gpt_buffer_too_small() {
let mut dev: TestBlockDevice = include_bytes!("../test/gpt_test_1.bin").as_slice().into();
dev.scratch = vec![0u8; dev.scratch.len() - 1];
assert!(dev.sync_gpt().is_err());
}
#[test]
fn test_gpt_too_many_entries() {
let mut dev = TestBlockDeviceBuilder::new()
.set_data(include_bytes!("../test/gpt_test_1.bin"))
.set_max_gpt_entries(129)
.build();
assert!(dev.sync_gpt().is_err());
}
#[test]
fn test_load_gpt_primary() {
let disk = include_bytes!("../test/gpt_test_1.bin");
let mut dev: TestBlockDevice = disk.as_slice().into();
// Corrupt secondary.
dev.io.storage[disk.len() - 512..].fill(0);
dev.sync_gpt().unwrap();
assert_eq!(dev.partition_iter().count(), 2);
dev.find_partition("boot_a").unwrap();
dev.find_partition("boot_b").unwrap();
assert!(dev.find_partition("boot_c").is_err());
// Check that secondary is restored
assert_eq!(dev.io.storage, disk);
}
#[test]
fn test_load_gpt_secondary() {
let disk = include_bytes!("../test/gpt_test_1.bin");
let mut dev: TestBlockDevice = disk.as_slice().into();
// Corrupt primary.
dev.io.storage[512..1024].fill(0);
dev.sync_gpt().unwrap();
assert_eq!(dev.partition_iter().count(), 2);
dev.find_partition("boot_a").unwrap();
dev.find_partition("boot_b").unwrap();
// Check that primary is restored
assert_eq!(dev.io.storage, disk);
}
#[test]
fn test_good_gpt_no_repair_write() {
let mut dev: TestBlockDevice = include_bytes!("../test/gpt_test_1.bin").as_slice().into();
dev.sync_gpt().unwrap();
assert_eq!(dev.io.num_writes, 0);
}
#[test]
fn test_load_gpt_incorrect_magic() {
let disk = include_bytes!("../test/gpt_test_1.bin");
let mut dev = TestBlockDeviceBuilder::new().set_data(disk).build();
dev.sync_gpt().unwrap();
let gpt = gpt(&mut dev);
let primary_header = &mut gpt.primary_header[..GPT_HEADER_SIZE.try_into().unwrap()];
let gpt_header = GptHeader::from_bytes(primary_header);
gpt_header.magic = 0x123456;
gpt_header.update_crc();
let primary_header = Vec::from(primary_header);
dev.io.storage[512..512 + primary_header.len()].clone_from_slice(&primary_header);
dev.sync_gpt().unwrap();
// Check that incorrect magic header is restored
assert_eq!(dev.io.storage, disk);
}
#[test]
fn test_load_gpt_exceeds_max_entries() {
let mut dev = TestBlockDeviceBuilder::new()
.set_data(include_bytes!("../test/gpt_test_1.bin"))
.set_max_gpt_entries(127)
.build();
assert!(dev.sync_gpt().is_err());
}
#[test]
fn test_load_gpt_non_max_entries() {
// Create a header with non-max entries_count
let disk = include_bytes!("../test/gpt_test_1.bin");
let mut dev = TestBlockDeviceBuilder::new().set_data(disk).build();
let block_size: usize = dev.io.block_size.try_into().unwrap();
dev.sync_gpt().unwrap();
let gpt = gpt(&mut dev);
let primary_header = &mut gpt.primary_header[..GPT_HEADER_SIZE.try_into().unwrap()];
let gpt_header = GptHeader::from_bytes(primary_header);
gpt_header.entries_count = 2;
// Update entries crc32
gpt_header.entries_crc =
crc32(&gpt.primary_entries[..(2 * GPT_ENTRY_SIZE).try_into().unwrap()]);
gpt_header.update_crc();
// Update to primary.
let primary_header = Vec::from(primary_header);
dev.io.storage[block_size..block_size + primary_header.len()]
.clone_from_slice(&primary_header);
// Corrupt secondary. Sync ok
dev.io.storage[disk.len() - block_size..].fill(0);
dev.sync_gpt().unwrap();
// Corrup primary. Sync ok
dev.io.storage[block_size..(block_size * 2)].fill(0);
dev.sync_gpt().unwrap();
}
#[test]
fn test_uninitialized_gpt() {
// Load a good GPT first.
let mut dev = TestBlockDeviceBuilder::new()
.set_data(include_bytes!("../test/gpt_test_1.bin"))
.build();
dev.sync_gpt().unwrap();
dev.io.storage[..64 * 1024].fill(0);
// Load a bad GPT. Validate that the valid state is reset.
assert!(dev.sync_gpt().is_err());
assert!(dev.find_partition("").is_err());
}
#[test]
fn test_gpt_read() {
let mut dev = TestBlockDeviceBuilder::new()
.set_data(include_bytes!("../test/gpt_test_1.bin"))
.build();
dev.sync_gpt().unwrap();
let expect_boot_a = include_bytes!("../test/boot_a.bin");
let expect_boot_b = include_bytes!("../test/boot_b.bin");
let mut actual_boot_a = vec![0u8; expect_boot_a.len()];
let mut actual_boot_b = vec![0u8; expect_boot_b.len()];
dev.read_gpt_partition("boot_a", 0, &mut actual_boot_a).unwrap();
assert_eq!(expect_boot_a.to_vec(), actual_boot_a);
// partial read
actual_boot_a = actual_boot_a[1..].to_vec();
dev.read_gpt_partition("boot_a", 1, &mut actual_boot_a).unwrap();
assert_eq!(expect_boot_a[1..].to_vec(), actual_boot_a);
dev.read_gpt_partition("boot_b", 0, &mut actual_boot_b).unwrap();
assert_eq!(expect_boot_b.to_vec(), actual_boot_b);
// partial read
actual_boot_b = actual_boot_b[1..].to_vec();
dev.read_gpt_partition("boot_b", 1, &mut actual_boot_b).unwrap();
assert_eq!(expect_boot_b[1..].to_vec(), actual_boot_b);
}
#[test]
fn test_gpt_write() {
let mut dev = TestBlockDeviceBuilder::new()
.set_data(include_bytes!("../test/gpt_test_1.bin"))
.build();
dev.sync_gpt().unwrap();
let mut expect_boot_a = include_bytes!("../test/boot_a.bin").to_vec();
expect_boot_a.reverse();
let mut expect_boot_b = include_bytes!("../test/boot_b.bin").to_vec();
expect_boot_b.reverse();
let mut actual_boot_a = vec![0u8; expect_boot_a.len()];
let mut actual_boot_b = vec![0u8; expect_boot_b.len()];
// "boot_a" partition
// Mutable version
dev.write_gpt_partition("boot_a", 0, expect_boot_a.as_mut_slice()).unwrap();
dev.read_gpt_partition("boot_a", 0, &mut actual_boot_a).unwrap();
assert_eq!(expect_boot_a.to_vec(), actual_boot_a);
// Mutable version, partial write.
dev.write_gpt_partition("boot_a", 1, expect_boot_a[1..].as_mut()).unwrap();
dev.read_gpt_partition("boot_a", 1, &mut actual_boot_a[1..]).unwrap();
assert_eq!(expect_boot_a[1..], actual_boot_a[1..]);
// "boot_b" partition
// Mutable version
dev.write_gpt_partition("boot_b", 0, expect_boot_b.as_mut_slice()).unwrap();
dev.read_gpt_partition("boot_b", 0, &mut actual_boot_b).unwrap();
assert_eq!(expect_boot_b.to_vec(), actual_boot_b);
// Mutable version, partial write.
dev.write_gpt_partition("boot_b", 1, expect_boot_b[1..].as_mut()).unwrap();
dev.read_gpt_partition("boot_b", 1, &mut actual_boot_b[1..]).unwrap();
assert_eq!(expect_boot_b[1..], actual_boot_b[1..]);
}
#[test]
fn test_gpt_rw_overflow() {
let mut dev = TestBlockDeviceBuilder::new()
.set_data(include_bytes!("../../libstorage/test/gpt_test_1.bin"))
.build();
dev.sync_gpt().unwrap();
let mut boot_a = [0u8; include_bytes!("../test/boot_a.bin").len()];
let mut boot_b = [0u8; include_bytes!("../test/boot_b.bin").len()];
assert!(dev.read_gpt_partition("boot_a", 1, &mut boot_a).is_err());
assert!(dev.write_gpt_partition("boot_a", 1, boot_a.as_mut_slice()).is_err());
assert!(dev.read_gpt_partition("boot_b", 1, &mut boot_b).is_err());
assert!(dev.write_gpt_partition("boot_b", 1, boot_b.as_mut_slice()).is_err());
}
}