blob: d1c0f446e6211c5b1429d4bfc8e77182e2642327 [file] [log] [blame]
// 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::{
add, aligned_subslice, div, mul, read, sub, to_usize, write_bytes, 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 zerocopy::{AsBytes, FromBytes, FromZeroes, Ref};
const GPT_GUID_LEN: usize = 16;
pub(crate) const GPT_NAME_LEN: usize = 36;
#[repr(C, packed)]
#[derive(Debug, Default, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
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.
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],
}
impl GptEntry {
/// Return the partition entry size in blocks.
pub fn blocks(&self) -> Result<u64> {
add(sub(self.last, self.first)?, 1)
}
/// 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 = 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 * 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_MAX_ENTRIES_SIZE: u64 = GPT_MAX_NUM_ENTRIES * GPT_ENTRY_SIZE;
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;
const GPT_MAGIC: u64 = 0x5452415020494645;
enum HeaderType {
Primary,
Secondary,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
struct GptInfo {
num_valid_entries: Option<NonZeroU64>,
max_entries: 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: max_entries };
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(crate) 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 = mul(info.max_entries, GPT_ENTRY_SIZE)?;
let split_pos = add(GPT_HEADER_SIZE_PADDED, entries_size)?;
let (primary, secondary) = remain.split_at_mut(to_usize(split_pos)?);
let (primary_header, primary_entries) =
primary.split_at_mut(to_usize(GPT_HEADER_SIZE_PADDED)?);
let (secondary_header, secondary_entries) =
secondary.split_at_mut(to_usize(GPT_HEADER_SIZE_PADDED)?);
Ok(Self {
info: info.into_mut(),
primary_header: primary_header,
primary_entries: primary_entries,
secondary_header: secondary_header,
secondary_entries: &mut secondary_entries[..to_usize(entries_size)?],
})
}
/// The minimum buffer size needed for `new_from_buffer()`
pub(crate) fn required_buffer_size(max_entries: u64) -> Result<usize> {
// Add 7 more bytes to accommodate 8-byte alignment.
let entries_size = mul(max_entries, GPT_ENTRY_SIZE)?;
let info_size = size_of::<GptInfo>() as u64;
to_usize(add(
add(info_size, mul(2, add(GPT_HEADER_SIZE_PADDED, entries_size)?)?)?,
GPT_ENTRY_ALIGNMENT - 1,
)?)
}
/// 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()
[..to_usize(self.info.num_valid_entries()?)?])
}
/// 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 * 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(), &mut self.primary_header, &mut self.primary_entries)
}
HeaderType::Secondary => (
mul(sub(blk_dev.num_blocks(), 1)?, blk_dev.block_size())?,
&mut self.secondary_header,
&mut self.secondary_entries,
),
};
read(blk_dev, header_start, header_bytes, scratch)?;
let header = Ref::<_, GptHeader>::new_from_prefix(&header_bytes[..]).unwrap().0.into_ref();
if header.magic != GPT_MAGIC {
return Ok(false);
}
let entries_size = mul(header.entries_count as u64, GPT_ENTRY_SIZE)?;
let entries_offset = mul(header.entries as u64, blk_dev.block_size())?;
if header.entries_count as u64 > self.info.max_entries
|| add(entries_size, entries_offset)?
> mul(sub(blk_dev.num_blocks(), 1)?, blk_dev.block_size())?
{
return Ok(false);
}
let mut hasher = Hasher::new();
hasher.update(&header.as_bytes()[..to_usize(GPT_CRC32_OFFSET)?]);
hasher.update(&[0u8; size_of::<u32>()]);
hasher.update(&header.as_bytes()[to_usize(GPT_CRC32_OFFSET)? + size_of::<u32>()..]);
if hasher.finalize() != header.crc32 {
return Ok(false);
}
// Load the entries
let out = &mut entries[..to_usize(entries_size)?];
read(blk_dev, entries_offset, 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 = blk_dev.num_blocks();
let primary_header_blk = 1;
let primary_header_pos = block_size;
let secondary_header_blk = sub(total_blocks, 1)?;
let secondary_header_pos = mul(secondary_header_blk, block_size)?;
// Entries position for restoring.
let primary_entries_blk = 2;
let primary_entries_pos = mul(primary_entries_blk, block_size)?;
let secondary_entries_pos = sub(secondary_header_pos, GPT_MAX_ENTRIES_SIZE)?;
let secondary_entries_blk = div(secondary_entries_pos, 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(&mut 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;
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, self.primary_entries, scratch)?
} else if !secondary_valid {
// Restore to secondary
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;
secondary_header.backup = primary_header_blk;
secondary_header.entries = secondary_entries_blk;
secondary_header.update_crc();
write_bytes_mut(
blk_dev,
secondary_header_pos,
secondary_header.as_bytes_mut(),
scratch,
)?;
write_bytes_mut(blk_dev, secondary_entries_pos, 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,
});
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)
}
/// Check if a relative offset/len into a partition overflows and returns the absolute offset.
fn check_offset(
blk_dev: &mut (impl BlockIo + ?Sized),
entry: &GptEntry,
offset: u64,
len: u64,
) -> Result<u64> {
match add(offset, len)? <= mul(entry.blocks()?, blk_dev.block_size())? {
true => Ok(add(mul(entry.first, blk_dev.block_size())?, offset)?),
false => Err(StorageError::OutOfRange),
}
}
/// Read GPT partition. Library internal helper for AsBlockDevice::read_gpt_partition().
pub(crate) fn read_gpt_partition(
blk_dev: &mut (impl BlockIo + ?Sized),
gpt: &Gpt,
part_name: &str,
offset: u64,
out: &mut [u8],
scratch: &mut [u8],
) -> Result<()> {
let e = gpt.find_partition(part_name)?;
let abs_offset = check_offset(blk_dev, e, offset, out.len() as u64)?;
read(blk_dev, abs_offset, out, scratch)
}
/// Write GPT partition. Library internal helper for AsBlockDevice::write_gpt_partition().
pub(crate) fn write_gpt_partition(
blk_dev: &mut (impl BlockIo + ?Sized),
gpt: &Gpt,
part_name: &str,
offset: u64,
data: &[u8],
scratch: &mut [u8],
) -> Result<()> {
let e = gpt.find_partition(part_name)?;
let abs_offset = check_offset(blk_dev, e, offset, data.len() as u64)?;
write_bytes(blk_dev, abs_offset, data, scratch)
}
/// Write GPT partition. Library internal helper for AsBlockDevice::write_gpt_partition().
/// Optimized version for mutable buffers.
pub(crate) fn write_gpt_partition_mut(
blk_dev: &mut (impl BlockIo + ?Sized),
gpt: &Gpt,
part_name: &str,
offset: u64,
data: &mut [u8],
scratch: &mut [u8],
) -> Result<()> {
let e = gpt.find_partition(part_name)?;
let abs_offset = check_offset(blk_dev, e, offset, data.as_ref().len() as u64)?;
write_bytes_mut(blk_dev, abs_offset, data.as_mut(), scratch)
}
fn crc32(data: &[u8]) -> u32 {
let mut hasher = Hasher::new();
hasher.update(data);
hasher.finalize()
}
#[cfg(test)]
pub(crate) mod test {
use super::*;
use crate::test::TestBlockDevice;
use crate::AsBlockDevice;
pub(crate) fn gpt_block_device(max_entries: u64, data: &[u8]) -> TestBlockDevice {
TestBlockDevice::new_with_data(512, 512, max_entries, data)
}
#[test]
fn test_new_from_buffer() {
let disk = include_bytes!("../test/gpt_test_1.bin").to_vec();
let mut dev = gpt_block_device(128, &disk);
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 disk = include_bytes!("../test/gpt_test_1.bin").to_vec();
let mut dev = gpt_block_device(128, &disk);
dev.scratch = vec![0u8; dev.scratch.len() - 1];
assert!(dev.sync_gpt().is_err());
}
#[test]
fn test_gpt_too_many_entries() {
let disk = include_bytes!("../test/gpt_test_1.bin").to_vec();
let mut dev = gpt_block_device(129, &disk);
assert!(dev.sync_gpt().is_err());
}
#[test]
fn test_load_gpt_primary() {
let disk = include_bytes!("../test/gpt_test_1.bin").to_vec();
let mut dev = gpt_block_device(128, &disk);
// 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").to_vec();
let mut dev = gpt_block_device(128, &disk);
// 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 disk = include_bytes!("../test/gpt_test_1.bin").to_vec();
let mut dev = gpt_block_device(128, &disk);
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").to_vec();
let mut dev = gpt_block_device(128, &disk);
dev.sync_gpt().unwrap();
let gpt = dev.gpt();
let primary_header = &mut gpt.primary_header[..to_usize(GPT_HEADER_SIZE).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 disk = include_bytes!("../test/gpt_test_1.bin").to_vec();
assert!(gpt_block_device(127, &disk).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").to_vec();
let mut dev = gpt_block_device(128, &disk);
dev.sync_gpt().unwrap();
let gpt = dev.gpt();
let primary_header = &mut gpt.primary_header[..to_usize(GPT_HEADER_SIZE).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[..to_usize(2 * GPT_ENTRY_SIZE).unwrap()]);
gpt_header.update_crc();
// Update to primary.
let primary_header = Vec::from(primary_header);
dev.io.storage[512..512 + primary_header.len()].clone_from_slice(&primary_header);
// Corrupt secondary. Sync ok
dev.io.storage[disk.len() - 512..].fill(0);
dev.sync_gpt().unwrap();
// Corrup primary. Sync ok
dev.io.storage[512..1024].fill(0);
dev.sync_gpt().unwrap();
}
#[test]
fn test_uninitialized_gpt() {
// Load a good GPT first.
let disk = include_bytes!("../test/gpt_test_1.bin").to_vec();
let mut dev = gpt_block_device(128, &disk);
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 = gpt_block_device(128, include_bytes!("../test/gpt_test_1.bin"));
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 = gpt_block_device(128, include_bytes!("../test/gpt_test_1.bin"));
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_mut("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_mut("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..]);
// Immutable version
dev.write_gpt_partition("boot_a", 0, &expect_boot_a).unwrap();
dev.read_gpt_partition("boot_a", 0, &mut actual_boot_a).unwrap();
assert_eq!(expect_boot_a.to_vec(), actual_boot_a);
// Immutable version, partial write.
dev.write_gpt_partition("boot_a", 1, &expect_boot_a[1..]).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_mut("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_mut("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..]);
// Immutable version
dev.write_gpt_partition("boot_b", 0, &expect_boot_b).unwrap();
dev.read_gpt_partition("boot_b", 0, &mut actual_boot_b).unwrap();
assert_eq!(expect_boot_b.to_vec(), actual_boot_b);
// Immutable version, partial write.
dev.write_gpt_partition("boot_b", 1, &expect_boot_b[1..]).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 = gpt_block_device(128, include_bytes!("../test/gpt_test_1.bin"));
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_mut("boot_a", 1, boot_a.as_mut_slice()).is_err());
assert!(dev.write_gpt_partition("boot_a", 1, &boot_a).is_err());
assert!(dev.read_gpt_partition("boot_b", 1, &mut boot_b).is_err());
assert!(dev.write_gpt_partition_mut("boot_b", 1, boot_b.as_mut_slice()).is_err());
assert!(dev.write_gpt_partition("boot_b", 1, &boot_b).is_err());
}
}