| // 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::{BlockIo, Disk, Result}; |
| use core::{ |
| array::from_fn, |
| cmp::min, |
| convert::TryFrom, |
| default::Default, |
| fmt::{Debug, Formatter}, |
| mem::size_of, |
| num::NonZeroU64, |
| ops::{Deref, DerefMut}, |
| str::from_utf8, |
| }; |
| use crc32fast::Hasher; |
| use gbl_async::block_on; |
| use liberror::{Error, GptError}; |
| use safemath::SafeNum; |
| use zerocopy::{ |
| ByteSlice, FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, |
| }; |
| |
| /// Number of bytes in GUID. |
| pub const GPT_GUID_LEN: usize = 16; |
| /// The maximum number of UTF-16 characters in a GPT partition name, including termination. |
| pub const GPT_NAME_LEN_U16: usize = 36; |
| const GPT_NAME_LEN_U8: usize = 2 * GPT_GUID_LEN; |
| |
| /// The top-level GPT header. |
| #[repr(C, packed)] |
| #[derive( |
| Debug, Default, Copy, Clone, Immutable, IntoBytes, FromBytes, KnownLayout, PartialEq, Eq, |
| )] |
| pub struct GptHeader { |
| /// Magic bytes; must be [GPT_MAGIC]. |
| pub magic: u64, |
| /// Header version. |
| pub revision: u32, |
| /// Header size in bytes. |
| pub size: u32, |
| /// CRC of the first `size` bytes, calculated with this field zeroed. |
| pub crc32: u32, |
| /// Reserved; must be set to 0. |
| pub reserved0: u32, |
| /// The on-disk block location of this header. |
| pub current: u64, |
| /// The on-disk block location of the other header. |
| pub backup: u64, |
| /// First usable block for partition contents. |
| pub first: u64, |
| /// Last usable block for partition contents (inclusive). |
| pub last: u64, |
| /// Disk GUID. |
| pub guid: [u8; GPT_GUID_LEN], |
| /// Starting block for the partition entries array. |
| pub entries: u64, |
| /// Number of partition entries. |
| pub entries_count: u32, |
| /// The size of each partition entry in bytes. |
| pub entries_size: u32, |
| /// CRC of the partition entries array. |
| pub entries_crc: u32, |
| } |
| |
| impl GptHeader { |
| /// Casts a bytes slice into a mutable GptHeader structure. |
| pub fn from_bytes_mut(bytes: &mut [u8]) -> &mut GptHeader { |
| Ref::into_mut(Ref::<_, GptHeader>::new_from_prefix(bytes).unwrap().0) |
| } |
| |
| /// Computes the actual crc32 value. |
| fn calculate_header_crc(&self) -> u32 { |
| let mut hasher = Hasher::new(); |
| hasher.update(&self.as_bytes()[..GPT_CRC32_OFFSET]); |
| hasher.update(&[0u8; size_of::<u32>()]); |
| hasher.update(&self.as_bytes()[GPT_CRC32_OFFSET + size_of::<u32>()..]); |
| hasher.finalize() |
| } |
| |
| /// Update the header crc32 value. |
| pub fn update_crc(&mut self) { |
| self.crc32 = self.calculate_header_crc(); |
| } |
| |
| /// Updates entries and header crc according to the given entries buffer. |
| fn update_entries_crc(&mut self, entries: &[u8]) { |
| let size = SafeNum::from(self.entries_count) * self.entries_size; |
| self.entries_crc = crc32(&entries[..size.try_into().unwrap()]); |
| self.update_crc(); |
| } |
| } |
| |
| /// Computes the number of blocks for the 128 partition entries reserved space in GPT. |
| fn gpt_entries_blk(block_size: u64) -> Result<u64> { |
| let size = u64::try_from(GPT_MAX_NUM_ENTRIES_SIZE).unwrap(); |
| match size % block_size { |
| 0 => Ok(size / block_size), |
| _ => Err(Error::InvalidInput), |
| } |
| } |
| |
| /// Checks a header against a block device. |
| /// |
| /// # Args |
| /// |
| /// * `io`: An implementation of [BlockIo], |
| /// * `header`: The GPT header to verify. |
| /// * `is_primary`: If the header is a primary header. |
| fn check_header(io: &mut impl BlockIo, header: &GptHeader, is_primary: bool) -> Result<()> { |
| let num_blks = SafeNum::from(io.info().num_blocks); |
| let blk_sz = io.info().block_size; |
| |
| // GPT spec requires that at least 128 entries worth of space be reserved. |
| let min_reserved_entries_blk = gpt_entries_blk(blk_sz)?; |
| // Minimum space needed: 2 * (header + entries) + MBR. |
| let min_disk_blks: u64 = ((min_reserved_entries_blk + 1) * 2 + 1).try_into().unwrap(); |
| if min_disk_blks > u64::try_from(num_blks).unwrap() { |
| return Err(Error::GptError(GptError::DiskTooSmall)); |
| } |
| |
| if header.magic != GPT_MAGIC { |
| return Err(Error::GptError(GptError::IncorrectMagic(header.magic))); |
| } |
| |
| if header.calculate_header_crc() != header.crc32 { |
| return Err(Error::GptError(GptError::IncorrectHeaderCrc)); |
| } |
| |
| if header.size != size_of::<GptHeader>().try_into().unwrap() { |
| return Err(Error::GptError(GptError::UnexpectedHeaderSize { |
| actual: header.size, |
| expect: size_of::<GptHeader>(), |
| })); |
| } |
| |
| if header.entries_size != size_of::<GptEntry>().try_into().unwrap() { |
| return Err(Error::GptError(GptError::UnexpectedEntrySize { |
| actual: header.entries_size, |
| expect: size_of::<GptEntry>(), |
| })); |
| } |
| |
| // Checks first/last usable block. |
| // |
| // Assuming maximum range where partition entries are adjacent to GPT headers. |
| // |
| // Should leave a minimum space for MBR + primary header + primary entries before. |
| let min_first: u64 = (min_reserved_entries_blk + 2).try_into().unwrap(); |
| // Should leave a minimum space for secondary header + secondary entries space after. |
| let max_last: u64 = (num_blks - 1 - min_reserved_entries_blk - 1).try_into().unwrap(); |
| if header.first > header.last + 1 || header.first < min_first || header.last > max_last { |
| return Err(Error::GptError(GptError::InvalidFirstLastUsableBlock { |
| first: header.first, |
| last: header.last, |
| range: (min_first, max_last), |
| })); |
| } |
| |
| // Checks entries starting block. |
| if is_primary { |
| // For primary header, entries must be before first usable block and can hold up to |
| // `GPT_MAX_NUM_ENTRIES` entries |
| let right: u64 = |
| (SafeNum::from(header.first) - min_reserved_entries_blk).try_into().unwrap(); |
| if !(header.entries >= 2 && header.entries <= right) { |
| return Err(Error::GptError(GptError::InvalidPrimaryEntriesStart { |
| value: header.entries, |
| expect_range: (2, right), |
| })); |
| } |
| } else { |
| // For secondary header, entries must be after last usable block and can hold up to |
| // `GPT_MAX_NUM_ENTRIES` entries. |
| if !(header.entries > header.last && header.entries <= max_last + 1) { |
| return Err(Error::GptError(GptError::InvalidSecondaryEntriesStart { |
| value: header.entries, |
| expect_range: (header.last + 1, max_last + 1), |
| })); |
| } |
| } |
| |
| if header.entries_count > GPT_MAX_NUM_ENTRIES.try_into().unwrap() { |
| return Err(Error::GptError(GptError::NumberOfEntriesOverflow { |
| entries: header.entries_count, |
| max_allowed: GPT_MAX_NUM_ENTRIES, |
| })); |
| } |
| |
| Ok(()) |
| } |
| |
| /// Verifies the given entries against a verifed GPT header. |
| /// |
| /// # Args |
| /// |
| /// * `header`: The verified GPT header corresponding to the entries. |
| /// * `entries`: The buffer containing the entries. |
| fn check_entries(header: &GptHeader, entries: &[u8]) -> Result<()> { |
| // Checks entries CRC. |
| assert!(header.entries_count <= GPT_MAX_NUM_ENTRIES.try_into().unwrap()); |
| let entries_size: usize = |
| (SafeNum::from(header.entries_count) * GPT_ENTRY_SIZE).try_into().unwrap(); |
| let entries = entries.get(..entries_size).ok_or(Error::GptError(GptError::EntriesTruncated))?; |
| if header.entries_crc != crc32(entries) { |
| return Err(Error::GptError(GptError::IncorrectEntriesCrc)); |
| } |
| |
| // Checks each entry. |
| let entries = Ref::<_, [GptEntry]>::new_slice(entries) |
| .ok_or(Error::GptError(GptError::EntriesTruncated))? |
| .into_slice(); |
| let entries = &entries[..header.entries_count.try_into().unwrap()]; |
| for (idx, ele) in entries.iter().take_while(|v| !v.is_null()).enumerate() { |
| // Error information uses 1-base partition index. |
| let idx = idx.checked_add(1).unwrap(); |
| let (first, last) = (ele.first, ele.last); |
| if first > last + 1 || last > header.last || first < header.first { |
| return Err(Error::GptError(GptError::InvalidPartitionRange { |
| idx, |
| part_range: (first, last), |
| usable_range: (header.first, header.last), |
| })); |
| } else if ele.part_type == [0u8; GPT_GUID_LEN] { |
| return Err(Error::GptError(GptError::ZeroPartitionTypeGUID { idx })); |
| } else if ele.guid == [0u8; GPT_GUID_LEN] { |
| return Err(Error::GptError(GptError::ZeroPartitionUniqueGUID { idx })); |
| } |
| } |
| |
| // Checks overlap between partition ranges. |
| // Sorts an index array because we don't want to modify input. |
| let mut sorted_indices: [u8; GPT_MAX_NUM_ENTRIES] = from_fn(|i| i.try_into().unwrap()); |
| sorted_indices.sort_unstable_by_key(|v| match entries.get(usize::try_from(*v).unwrap()) { |
| Some(v) if !v.is_null() => v.first, |
| _ => u64::MAX, |
| }); |
| |
| let actual = entries.iter().position(|v| v.is_null()).unwrap_or(entries.len()); |
| if actual > 1 { |
| for i in 0..actual - 1 { |
| let prev: usize = sorted_indices[i].try_into().unwrap(); |
| let next: usize = sorted_indices[i + 1].try_into().unwrap(); |
| if entries[prev].last >= entries[next].first { |
| return Err(Error::GptError(GptError::PartitionRangeOverlap { |
| prev: (prev + 1, entries[prev].first, entries[prev].last), |
| next: (next + 1, entries[next].first, entries[next].last), |
| })); |
| } |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| /// GptEntry is the partition entry data structure in the GPT. |
| #[repr(C, packed)] |
| #[derive(Debug, Copy, Clone, Immutable, IntoBytes, FromBytes, KnownLayout, PartialEq)] |
| pub struct GptEntry { |
| /// Partition type GUID. |
| pub part_type: [u8; GPT_GUID_LEN], |
| /// Unique partition GUID. |
| pub guid: [u8; GPT_GUID_LEN], |
| /// First block. |
| pub first: u64, |
| /// Last block (inclusive). |
| pub last: u64, |
| /// Partition flags. |
| pub flags: u64, |
| /// Partition name in UTF-16. |
| pub name: [u16; GPT_NAME_LEN_U16], |
| } |
| |
| impl GptEntry { |
| /// Return the partition entry size in blocks. |
| pub fn blocks(&self) -> Result<u64> { |
| // Must perform "+1" first before subtracting `self.first`. Otherwise if partition size is |
| // zero, where `self.first > self.last`, arithmetic will overflow. |
| u64::try_from(SafeNum::from(self.last) + 1 - self.first).map_err(Into::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(Error::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])) } |
| } |
| |
| /// Checks if the partition name is the same as the given. |
| pub fn match_name(&self, part: &str) -> Result<bool> { |
| Ok(self.name_to_str(&mut [0u8; GPT_NAME_LEN_U16 * 2][..])? == part) |
| } |
| } |
| |
| 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)?; |
| // Note: The bracket around `{ self.first }` is for forcing a copy of the field because |
| // GptEntry is a packed structure. |
| write!(f, "partition: \"{}\", 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: usize = 16; |
| const GPT_ENTRY_SIZE: usize = size_of::<GptEntry>(); |
| const GPT_MAX_NUM_ENTRIES: usize = 128; |
| const GPT_MAX_NUM_ENTRIES_SIZE: usize = GPT_MAX_NUM_ENTRIES * GPT_ENTRY_SIZE; |
| /// GPT header magic bytes ("EFI PART" in ASCII). |
| pub const GPT_MAGIC: u64 = 0x5452415020494645; |
| |
| enum HeaderType { |
| Primary, |
| Secondary, |
| } |
| |
| /// `Partition` contains information about a GPT partition. |
| #[derive(Debug, Copy, Clone, PartialEq)] |
| pub struct Partition { |
| entry: GptEntry, |
| block_size: u64, |
| decoded_name: Option<([u8; GPT_NAME_LEN_U8], usize)>, |
| } |
| |
| impl Partition { |
| /// Creates a new instance. |
| fn new(entry: GptEntry, block_size: u64) -> Self { |
| let mut buf = [0u8; GPT_NAME_LEN_U8]; |
| let decoded_name = match entry.name_to_str(&mut buf[..]).ok().map(|v| v.len()) { |
| Some(len) => Some((buf, len)), |
| _ => None, |
| }; |
| Self { entry, block_size, decoded_name } |
| } |
| |
| /// Gets the decoded partition name. |
| pub fn name(&self) -> Option<&str> { |
| // Correct by construction. `from_utf8` should not fail. |
| self.decoded_name.as_ref().map(|(buf, sz)| from_utf8(&buf[..*sz]).unwrap()) |
| } |
| |
| /// Returns the partition size in bytes. |
| pub fn size(&self) -> Result<u64> { |
| u64::try_from(SafeNum::from(self.entry.blocks()?) * self.block_size).map_err(Error::from) |
| } |
| |
| /// Returns the block size of this partition. |
| pub fn block_size(&self) -> u64 { |
| self.block_size |
| } |
| |
| /// Returns the partition entry structure in the GPT header. |
| pub fn gpt_entry(&self) -> &GptEntry { |
| &self.entry |
| } |
| |
| /// Returns the partition's absolute start/end offset in number of bytes. |
| pub fn absolute_range(&self) -> Result<(u64, u64)> { |
| let start = SafeNum::from(self.entry.first) * self.block_size; |
| let end = (SafeNum::from(self.entry.last) + 1) * self.block_size; |
| Ok((start.try_into()?, end.try_into()?)) |
| } |
| |
| /// Checks a given sub range and returns its absolute offset. |
| pub fn check_range(&self, off: u64, size: u64) -> Result<u64> { |
| let off = SafeNum::from(off); |
| let end: u64 = (off + size).try_into()?; |
| match end > self.size()? { |
| true => Err(Error::BadIndex(end as usize)), |
| _ => Ok((off + self.absolute_range()?.0).try_into()?), |
| } |
| } |
| } |
| |
| /// `PartitionIterator` iterates all GPT partition entries. |
| pub struct PartitionIterator<'a> { |
| entries: &'a [GptEntry], |
| block_size: u64, |
| idx: usize, |
| } |
| |
| impl Iterator for PartitionIterator<'_> { |
| type Item = Partition; |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| let res = self |
| .entries |
| .get(self.idx) |
| .filter(|v| !v.is_null()) |
| .map(|v| Partition::new(*v, self.block_size))?; |
| self.idx += 1; |
| Some(res) |
| } |
| } |
| |
| /// Contains result of GPT syncing/restoration. |
| #[derive(Copy, Clone, PartialEq, Debug, Default)] |
| pub enum GptSyncResult { |
| /// Both primary and secondary GPT are valid. |
| #[default] |
| BothValid, |
| /// Primary GPT is invalid and restored. |
| PrimaryRestored(Error), |
| /// Secondary GPT is invalid and restored. |
| SecondaryRestored(Error), |
| /// Neither primary or secondary GPT is valid. |
| NoValidGpt { |
| /// Primary GPT verify error. |
| primary: Error, |
| /// Secondary GPT verify error. |
| secondary: Error, |
| }, |
| } |
| |
| impl GptSyncResult { |
| /// Combined into a result |
| pub fn res(&self) -> Result<()> { |
| match self { |
| Self::NoValidGpt { primary: e, .. } => Err(*e), |
| _ => Ok(()), |
| } |
| } |
| } |
| |
| impl core::fmt::Display for GptSyncResult { |
| fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| match self { |
| Self::BothValid => write!(f, "Found valid GPT."), |
| Self::PrimaryRestored(e) => write!(f, "Primary GPT restored due to {e:?}."), |
| Self::SecondaryRestored(e) => write!(f, "Secondary GPT restored due to {e:?}."), |
| Self::NoValidGpt { primary, secondary } => { |
| write!(f, "No valid GPT. primary: {primary:?}, secondary: {secondary:?}.") |
| } |
| } |
| } |
| } |
| |
| /// A packed wrapper of `Option<NonZeroU64>` |
| #[repr(C, packed)] |
| #[derive(Debug, Copy, Clone, Immutable, IntoBytes, FromBytes, KnownLayout)] |
| struct BlockSize(Option<NonZeroU64>); |
| |
| /// Represents the structure of a load buffer for loading/verifying/syncing up to N GPT entries. |
| #[repr(C, packed)] |
| #[derive(Debug, Copy, Clone, Immutable, IntoBytes, FromBytes)] |
| pub struct GptLoadBufferN<const N: usize> { |
| // GPT doesn't care about block size. But it's easier to have it available for computing offset |
| // and size in bytes for partitions. It's also used as a flag for indicating whether a valid |
| // GPT is loaded. |
| block_size: BlockSize, |
| primary_header: GptHeader, |
| secondary_header: GptHeader, |
| primary_entries: [GptEntry; N], |
| secondary_entries: [GptEntry; N], |
| } |
| |
| impl<const N: usize> Deref for GptLoadBufferN<N> { |
| type Target = [u8]; |
| |
| fn deref(&self) -> &Self::Target { |
| self.as_bytes() |
| } |
| } |
| |
| impl<const N: usize> DerefMut for GptLoadBufferN<N> { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| self.as_bytes_mut() |
| } |
| } |
| |
| /// Contains references corresponding to different GPT load entities parsed from a load buffer. |
| /// |
| /// The structure is simply for organizing together the individual references of fields in |
| /// `GptLoadBufferN` parsed from a raw buffer. Note that we can't parse a `Ref<B, GptLoadBufferN>` |
| /// directly from a buffer because the number of entries (length of [GptEntry]) in this case needs |
| /// to be computed at run time based on the buffer size. |
| struct LoadBufferRef<B: ByteSlice> { |
| block_size: Ref<B, BlockSize>, |
| primary_header: Ref<B, GptHeader>, |
| secondary_header: Ref<B, GptHeader>, |
| primary_entries: Ref<B, [GptEntry]>, |
| secondary_entries: Ref<B, [GptEntry]>, |
| } |
| |
| impl<B: SplitByteSlice> LoadBufferRef<B> { |
| fn from(buffer: B) -> Self { |
| let n = min(GPT_MAX_NUM_ENTRIES, max_supported_entries(&buffer[..]).unwrap()); |
| let (block_size, rest) = Ref::new_from_prefix(buffer).unwrap(); |
| let (primary_header, rest) = Ref::new_from_prefix(rest).unwrap(); |
| let (secondary_header, rest) = Ref::new_from_prefix(rest).unwrap(); |
| let (primary_entries, rest) = Ref::new_slice_from_prefix(rest, n).unwrap(); |
| let (secondary_entries, _) = Ref::new_slice_from_prefix(rest, n).unwrap(); |
| Self { block_size, primary_header, secondary_header, primary_entries, secondary_entries } |
| } |
| |
| /// Unpacks into the secondary GPT header/entries |
| fn secondary(self) -> (Ref<B, GptHeader>, Ref<B, [GptEntry]>) { |
| (self.secondary_header, self.secondary_entries) |
| } |
| } |
| |
| /// The minimum buffer size needed for creating a [Gpt] that can load `entries` number of |
| /// partitions. |
| /// |
| /// # Returns |
| /// |
| /// * Returns Ok(size) on success. |
| /// * Returns Err(Error::InvalidInput) if max_entries is greater than 128. |
| pub fn gpt_buffer_size(entries: usize) -> Result<usize> { |
| match entries > GPT_MAX_NUM_ENTRIES { |
| true => Err(Error::InvalidInput), |
| _ => Ok(size_of::<GptLoadBufferN<0>>() + entries * GPT_ENTRY_SIZE * 2), |
| } |
| } |
| |
| /// Computes the maximum number of entries that can be loaded if using the given buffer for [Gpt]. |
| fn max_supported_entries(buf: &[u8]) -> Result<usize> { |
| match buf.len() < size_of::<GptLoadBufferN<0>>() { |
| true => Err(Error::BufferTooSmall(Some(size_of::<GptLoadBufferN<0>>()))), |
| _ => Ok((buf.len() - size_of::<GptLoadBufferN<0>>()) / 2 / GPT_ENTRY_SIZE), |
| } |
| } |
| |
| /// [Gpt] manages a buffer for loading, verifying and syncing GPT. |
| pub struct Gpt<B> { |
| buffer: B, |
| } |
| |
| impl<B: DerefMut<Target = [u8]>> Gpt<B> { |
| /// Create an uninitialized Gpt instance from a provided buffer. |
| /// |
| /// The created [Gpt] can then be used in `Disk::sync_gpt()` for loading, verifying and syncing |
| /// GPT on disk. |
| /// |
| /// # Args: |
| /// |
| /// * `buffer`: A buffer to use for loading, verifying and syncing primary and secondary GPT. |
| /// The size of the buffer determines the maximum number of partition entries that can be |
| /// loaded. If actual number of partitions, specified by `entries_count` in the GPT header, |
| /// exceeds it, verification and sync will eventually fail with `Error::BufferTooSmall`. |
| /// `gpt_buffer_size(num_entries)` can be used to compute the required size of buffer for |
| /// loading a specific number of entries. Note that most tools and OS fix the `entries_count` |
| /// value to the max 128 regardless of the actual number of partition entries used. Thus |
| /// unless you have full control of GPT generation in your entire system where you can always |
| /// ensure a smaller bound on it, it is recommended to always provide enough buffer for |
| /// loading 128 entries. |
| /// |
| /// # Returns |
| /// |
| /// * Returns Ok(Self) on success. |
| /// * Returns Err(Error::BufferTooSmall) if buffer is less than the minimum size. |
| pub fn new(mut buffer: B) -> Result<Self> { |
| max_supported_entries(&buffer[..])?; |
| LoadBufferRef::from(&mut buffer[..]).block_size.0 = None; |
| Ok(Self { buffer }) |
| } |
| |
| /// Returns the maximum allowed entries. |
| pub fn max_entries(&self) -> usize { |
| max_supported_entries(&self.buffer[..]).unwrap() |
| } |
| |
| /// Creates an instance of `Gpt<&mut [u8]>` that borrows the internal GPT buffer. |
| pub fn as_borrowed(&mut self) -> Gpt<&mut [u8]> { |
| Gpt { buffer: &mut self.buffer[..] } |
| } |
| |
| /// Returns an iterator to GPT partition entries. |
| /// |
| /// If the object does not contain a valid GPT, the method returns Error. |
| pub fn partition_iter(&self) -> Result<PartitionIterator> { |
| let block_size = self.check_valid()?; |
| let entries = LoadBufferRef::from(&self.buffer[..]).primary_entries.into_slice(); |
| Ok(PartitionIterator { entries, idx: 0, block_size }) |
| } |
| |
| /// Checks if a read/write range into a GPT partition overflows and returns the range's absolute |
| /// offset in number of bytes. |
| pub fn check_range(&self, part_name: &str, offset: u64, size: usize) -> Result<u64> { |
| self.find_partition(part_name)?.check_range(offset, u64::try_from(size)?) |
| } |
| |
| /// Return the list of GPT entries. |
| /// |
| /// If there is not a valid GPT, the method returns Error. |
| pub fn entries(&self) -> Result<&[GptEntry]> { |
| self.check_valid()?; |
| let entries = LoadBufferRef::from(&self.buffer[..]).primary_entries.into_slice(); |
| let n = entries.iter().position(|v| v.is_null()).unwrap_or(entries.len()); |
| Ok(&entries[..n]) |
| } |
| |
| /// Returns the total number of partitions. |
| pub fn num_partitions(&self) -> Result<usize> { |
| Ok(self.entries()?.len()) |
| } |
| |
| /// Gets the `idx`th partition. |
| pub fn get_partition(&self, idx: usize) -> Result<Partition> { |
| let block_size = self.check_valid()?; |
| let entry = *self.entries()?.get(idx).ok_or(Error::BadIndex(idx))?; |
| Ok(Partition::new(entry, block_size)) |
| } |
| |
| /// Returns the `Partition` for a partition. |
| /// |
| /// # Args |
| /// |
| /// * `part`: Name of the partition. |
| pub fn find_partition(&self, part: &str) -> Result<Partition> { |
| let block_size = self.check_valid()?; |
| 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(Partition::new(*entry, block_size)); |
| } |
| Err(Error::NotFound) |
| } |
| |
| /// Checks whether the Gpt has been initialized and returns the block size. |
| fn check_valid(&self) -> Result<u64> { |
| Ok(LoadBufferRef::from(&self.buffer[..]).block_size.0.ok_or(Error::InvalidState)?.get()) |
| } |
| |
| /// Helper function for loading and validating GPT header and entries. |
| async fn load_and_validate_gpt( |
| &mut self, |
| disk: &mut Disk<impl BlockIo, impl DerefMut<Target = [u8]>>, |
| hdr_type: HeaderType, |
| ) -> Result<()> { |
| let blk_sz = disk.io().info().block_size; |
| let load = LoadBufferRef::from(&mut self.buffer[..]); |
| let (header_start, mut header, mut entries) = match hdr_type { |
| HeaderType::Primary => (blk_sz, load.primary_header, load.primary_entries), |
| HeaderType::Secondary => ( |
| ((SafeNum::from(disk.io().info().num_blocks) - 1) * blk_sz).try_into()?, |
| load.secondary_header, |
| load.secondary_entries, |
| ), |
| }; |
| |
| // Loads the header |
| disk.read(header_start, Ref::bytes_mut(&mut header)).await?; |
| // Checks header. |
| check_header(disk.io(), &header, matches!(hdr_type, HeaderType::Primary))?; |
| // Loads the entries. |
| let entries_size = SafeNum::from(header.entries_count) * GPT_ENTRY_SIZE; |
| let entries_offset = SafeNum::from(header.entries) * blk_sz; |
| let out = entries.as_bytes_mut().get_mut(..entries_size.try_into().unwrap()).ok_or( |
| Error::BufferTooSmall(Some( |
| gpt_buffer_size(header.entries_count.try_into().unwrap()).unwrap(), |
| )), |
| )?; |
| disk.read(entries_offset.try_into().unwrap(), out).await?; |
| // Checks entries. |
| check_entries(&header, entries.as_bytes()) |
| } |
| |
| /// Loads and syncs GPT from a block device. |
| /// |
| /// * Returns Ok(sync_result) if disk IO is successful, where `sync_result` contains the GPT |
| /// verification and restoration result, |
| /// * Returns Err() if disk IO encounters error. |
| pub(crate) async fn load_and_sync( |
| &mut self, |
| disk: &mut Disk<impl BlockIo, impl DerefMut<Target = [u8]>>, |
| ) -> Result<GptSyncResult> { |
| let blk_sz = disk.io().info().block_size; |
| let nonzero_blk_sz = NonZeroU64::new(blk_sz).ok_or(Error::InvalidInput)?; |
| let total_blocks: SafeNum = disk.io().info().num_blocks.into(); |
| |
| let primary_header_blk = 1; |
| let primary_header_pos = blk_sz; |
| let secondary_header_blk = total_blocks - 1; |
| |
| // Entries position for restoring. |
| let primary_entries_blk = 2; |
| let primary_entries_pos = SafeNum::from(primary_entries_blk) * blk_sz; |
| let primary_res = self.load_and_validate_gpt(disk, HeaderType::Primary).await; |
| let secondary_res = self.load_and_validate_gpt(disk, HeaderType::Secondary).await; |
| |
| let LoadBufferRef { |
| mut block_size, |
| mut primary_header, |
| mut secondary_header, |
| mut primary_entries, |
| mut secondary_entries, |
| } = LoadBufferRef::from(&mut self.buffer[..]); |
| block_size.0 = None; |
| let primary_entries = primary_entries.as_bytes_mut(); |
| let secondary_entries = secondary_entries.as_bytes_mut(); |
| let sync_res = match (primary_res, secondary_res) { |
| (Err(primary), Err(secondary)) => GptSyncResult::NoValidGpt { primary, secondary }, |
| (Ok(()), Ok(())) if is_consistent(&primary_header, &secondary_header) => { |
| GptSyncResult::BothValid |
| } |
| (Err(e), Ok(())) => { |
| // Restores to primary |
| primary_header.as_bytes_mut().clone_from_slice(secondary_header.as_bytes()); |
| primary_entries.clone_from_slice(&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(); |
| |
| disk.write(primary_header_pos, primary_header.as_bytes_mut()).await?; |
| disk.write(primary_entries_pos.try_into()?, primary_entries).await?; |
| GptSyncResult::PrimaryRestored(e) |
| } |
| (Ok(()), v) => { |
| // Restores to secondary |
| let pos = secondary_header_blk * blk_sz; |
| let secondary_entries_pos = pos - GPT_MAX_NUM_ENTRIES_SIZE; |
| let secondary_entries_blk = secondary_entries_pos / blk_sz; |
| |
| secondary_header.as_bytes_mut().clone_from_slice(primary_header.as_bytes()); |
| secondary_entries.clone_from_slice(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(); |
| |
| disk.write(pos.try_into()?, secondary_header.as_bytes_mut()).await?; |
| disk.write(secondary_entries_pos.try_into()?, secondary_entries).await?; |
| |
| GptSyncResult::SecondaryRestored(match v { |
| Err(e) => e, |
| _ => Error::GptError(GptError::DifferentFromPrimary), |
| }) |
| } |
| }; |
| |
| block_size.0 = Some(nonzero_blk_sz); |
| Ok(sync_res) |
| } |
| } |
| |
| /// Checks whether primary and secondary header |
| fn is_consistent(primary: &GptHeader, secondary: &GptHeader) -> bool { |
| let mut expected_secondary = *primary; |
| expected_secondary.crc32 = secondary.crc32; |
| expected_secondary.current = secondary.current; |
| expected_secondary.backup = 1; |
| expected_secondary.entries = secondary.entries; |
| &expected_secondary == secondary |
| } |
| |
| /// A [Gpt] that owns a `GptLoadBufferN<N>` and can load up to N partition entries. |
| /// |
| /// Note: The size of this type increases with N and can be expensive to store on stack. It is |
| /// typically intended for resource abundant environment such as test. |
| pub type GptN<const N: usize> = Gpt<GptLoadBufferN<N>>; |
| |
| /// Creates an instance of GptN. |
| pub fn new_gpt_n<const N: usize>() -> GptN<N> { |
| Gpt::new(GptLoadBufferN::<N>::new_zeroed()).unwrap() |
| } |
| |
| /// A [Gpt] that owns a `GptLoadBufferN<128>` and can load the maximum 128 partition entries. |
| /// |
| /// Note: The size of this type is approximately 34K and can be expensive to store on stack. It |
| /// is typically intended for resource abundant environment such as test. |
| pub type GptMax = GptN<GPT_MAX_NUM_ENTRIES>; |
| |
| /// Creates an instance of GptMax. |
| pub fn new_gpt_max() -> GptMax { |
| new_gpt_n::<GPT_MAX_NUM_ENTRIES>() |
| } |
| |
| /// Updates GPT on a block device. |
| /// |
| /// # Args |
| /// |
| /// * `io`: An implementation of [BlockIo] |
| /// * `scratch`: Scratch buffer for unaligned read write. |
| /// * `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. |
| /// * `gpt`: The output [Gpt] to update. |
| pub(crate) async fn update_gpt( |
| disk: &mut Disk<impl BlockIo, impl DerefMut<Target = [u8]>>, |
| mbr_primary: &mut [u8], |
| resize: bool, |
| gpt: &mut Gpt<impl DerefMut<Target = [u8]>>, |
| ) -> Result<()> { |
| let blk_sz: usize = disk.io().info().block_size.try_into()?; |
| let (header, remain) = mbr_primary |
| .get_mut(blk_sz..) |
| .map(|v| v.split_at_mut_checked(blk_sz)) |
| .flatten() |
| .ok_or(Error::BufferTooSmall(Some(blk_sz * 2)))?; |
| let header = Ref::into_mut(Ref::<_, GptHeader>::new_from_prefix(&mut header[..]).unwrap().0); |
| |
| // Adjusts last usable block according to this device in case the GPT was generated for a |
| // different disk size. If this results in some partition being out of range, it will be |
| // caught during `check_header()`. |
| let entries_blk = SafeNum::from(GPT_MAX_NUM_ENTRIES_SIZE) / blk_sz; |
| // Reserves only secondary GPT header and entries. |
| let num_blks = SafeNum::from(disk.io().info().num_blocks); |
| header.last = (num_blks - entries_blk - 2).try_into().unwrap(); |
| header.backup = (num_blks - 1).try_into().unwrap(); |
| header.update_crc(); |
| |
| check_header(disk.io(), &header, true)?; |
| // Computes entries offset in bytes relative to `remain` |
| let entries_off: usize = ((SafeNum::from(header.entries) - 2) * blk_sz).try_into().unwrap(); |
| let entries_size: usize = |
| (SafeNum::from(header.entries_count) * header.entries_size).try_into().unwrap(); |
| let entries = remain |
| .get_mut(entries_off..) |
| .map(|v| v.get_mut(..entries_size)) |
| .flatten() |
| .ok_or(Error::BufferTooSmall(Some(2 * blk_sz + entries_off + entries_size)))?; |
| check_entries(&header, entries)?; |
| |
| if resize { |
| // Updates the last entry to cover the rest of the storage. |
| let gpt_entries = |
| Ref::<_, [GptEntry]>::new_slice(&mut entries[..]).unwrap().into_mut_slice(); |
| gpt_entries.iter_mut().filter(|e| !e.is_null()).last().map(|v| v.last = header.last); |
| header.update_entries_crc(entries); |
| // Re-verifies everything. |
| check_header(disk.io(), &header, true).unwrap(); |
| check_entries(&header, entries).unwrap(); |
| } |
| |
| disk.write(0, mbr_primary).await?; |
| disk.sync_gpt(gpt).await?.res() |
| } |
| |
| /// Erases GPT if there is one on the device. |
| pub(crate) async fn erase_gpt( |
| disk: &mut Disk<impl BlockIo, impl DerefMut<Target = [u8]>>, |
| gpt: &mut Gpt<impl DerefMut<Target = [u8]>>, |
| ) -> Result<()> { |
| match disk.sync_gpt(gpt).await?.res() { |
| Err(_) => Ok(()), // No valid GPT. Nothing to erase. |
| _ => { |
| let blk_sz = disk.block_info().block_size; |
| let mut load = LoadBufferRef::from(&mut gpt.buffer[..]); |
| let entries_size = SafeNum::from(load.primary_header.entries_count) * GPT_ENTRY_SIZE; |
| let scratch = load.primary_entries.as_bytes_mut(); |
| // Invalidate GPT first. |
| load.block_size.0 = None; |
| // Erases primary header/entries. |
| let header = load.primary_header.current; |
| let entries = load.primary_header.entries; |
| disk.fill(header * blk_sz, blk_sz, 0, scratch).await?; |
| disk.fill(entries * blk_sz, entries_size.try_into().unwrap(), 0, scratch).await?; |
| // Erases secondary header/entries. |
| let header = load.secondary_header.current; |
| let entries = load.secondary_header.entries; |
| disk.fill(header * blk_sz, blk_sz, 0, scratch).await?; |
| disk.fill(entries * blk_sz, entries_size.try_into().unwrap(), 0, scratch).await?; |
| Ok(()) |
| } |
| } |
| } |
| |
| /// Computes the minimum blocks needed for creating a GPT. |
| fn min_required_blocks(block_size: u64) -> Result<u64> { |
| // MBR + primary/secondary GPT header block + primary/secondary entries blocks. |
| Ok(1 + (1 + gpt_entries_blk(block_size)?) * 2) |
| } |
| |
| /// `GptBuilder` provides API for modifying/creating GPT partition table on a disk. |
| pub struct GptBuilder<D, G> { |
| disk: D, |
| gpt: G, |
| } |
| |
| impl<D: Debug, G: Debug> Debug for GptBuilder<D, G> { |
| fn fmt(&self, f: &mut Formatter<'_>) -> core::result::Result<(), core::fmt::Error> { |
| write!(f, "GptBuilder {{ disk: {:?}, gpt: {:?} }}", self.disk, self.gpt) |
| } |
| } |
| // Generic parameters: |
| // |
| // * T: The type that implement BlockIo. |
| // * S: The type for the scratch buffer in `Self::disk`. |
| // * B: The type for the GPT buffer in `Self::gpt`. |
| // * D: The type for `Self::disk` which can dereference to a Disk<T, S>. |
| // * G: The type for `Self::gpt` which can dereference to a Gpt<B>. |
| impl<'a, T, S, B, D, G> GptBuilder<D, G> |
| where |
| T: BlockIo, |
| S: DerefMut<Target = [u8]>, |
| B: DerefMut<Target = [u8]>, |
| D: DerefMut<Target = Disk<T, S>>, |
| G: DerefMut<Target = Gpt<B>>, |
| { |
| /// Creates a new instance. |
| /// |
| /// The method always re-syncs the GPT. If `disk` does not contain a valid GPT, a new GPT is |
| /// started from scratch. |
| /// |
| /// The partition entries will always be sorted when writing back to disk by `Self::persist()`. |
| /// |
| /// # Returns |
| /// |
| /// * Returns Ok((Self, true)) if an instance is created and the disk has a valid GPT. |
| /// * Returns Ok((Self, false)) if an instance is created but disk does not have a valid GPT. |
| /// * Returns Err() otherwise. |
| pub fn new(mut disk: D, mut gpt: G) -> Result<(Self, bool)> { |
| if disk.block_info().num_blocks < min_required_blocks(disk.block_info().block_size)? { |
| return Err(Error::GptError(GptError::DiskTooSmall)); |
| } |
| let has_valid_gpt = block_on(disk.sync_gpt(&mut gpt))?.res().is_ok(); |
| // Uses the buffer for secondary GPT header/entries as construction buffer, as it is not |
| // used by Gpt once loaded and synced. |
| let (mut header, mut entries) = LoadBufferRef::from(&mut gpt.buffer[..]).secondary(); |
| if !has_valid_gpt { |
| header.as_bytes_mut().fill(0); |
| entries.as_bytes_mut().fill(0); |
| let entries_blk = gpt_entries_blk(disk.block_info().block_size).unwrap(); |
| // Initializes a secondary header. |
| let num_blks = SafeNum::from(disk.block_info().num_blocks); |
| header.magic = GPT_MAGIC; |
| header.current = (num_blks - 1).try_into().unwrap(); |
| header.backup = 1; |
| header.size = size_of::<GptHeader>().try_into().unwrap(); |
| header.first = 1 + 1 + entries_blk; // MBR + GPT header blocks + entries block |
| header.last = (num_blks - 1 - entries_blk - 1).try_into().unwrap(); |
| header.entries = (num_blks - 1 - entries_blk).try_into().unwrap(); |
| header.entries_count = 0; |
| header.entries_size = size_of::<GptEntry>().try_into().unwrap(); |
| } |
| // Normalizes `entries_count` to actual valid entries. Some GPT disk fixes `entry_count` to |
| // 128. |
| header.entries_count = |
| entries.iter().position(|v| v.is_null()).unwrap_or(entries.len()).try_into().unwrap(); |
| entries.sort_unstable_by_key(|v| match v.is_null() { |
| true => u64::MAX, |
| _ => v.first, |
| }); |
| Ok((Self { disk, gpt }, has_valid_gpt)) |
| } |
| |
| /// Removes a partition. |
| /// |
| /// # Returns |
| /// |
| /// * Returns Ok(true) if found and removed. |
| /// * Returns Ok(false) if not found. |
| /// * Returns Err() otherwise. |
| pub fn remove(&mut self, part: &str) -> Result<bool> { |
| let (mut header, mut entries) = LoadBufferRef::from(&mut self.gpt.buffer[..]).secondary(); |
| let entries = &mut entries[..header.entries_count.try_into().unwrap()]; |
| match entries.iter().position(|v| v.match_name(part).unwrap_or(false)) { |
| Some(n) => { |
| // Shift the elements behind forward. |
| entries[n..].rotate_left(1); |
| // Zeroizes the last element. |
| entries.last_mut().unwrap().as_bytes_mut().fill(0); |
| header.entries_count -= 1; |
| Ok(true) |
| } |
| _ => Ok(false), |
| } |
| } |
| |
| /// Inserts a new partition before a partition. |
| /// |
| /// # Args |
| /// |
| /// * `idx`: Index of the partition to insert before. If index is out of range of valid entries, |
| /// the partition will be inserted at the last. |
| /// * `name`: Name of the partition. |
| /// * `part_type`: Type GUID. |
| /// * `unique_guid`: Unique GUID. |
| /// * `flags`: Partition flag. |
| /// * `size`: If Some(_), specifies the size in number of bytes for the partition. The method |
| /// will round it up to multiple of disk block size and check that there is enough space for |
| /// the partition. If None, the method will insert the partition and consumes all the |
| /// available space in between. |
| fn insert_before( |
| &mut self, |
| idx: usize, |
| name: &str, |
| part_type: [u8; GPT_GUID_LEN], |
| unique_guid: [u8; GPT_GUID_LEN], |
| flags: u64, |
| size: Option<u64>, |
| ) -> Result<()> { |
| let (mut header, mut entries) = LoadBufferRef::from(&mut self.gpt.buffer[..]).secondary(); |
| // Gets position to the first NULL entry. |
| let n = entries.iter().position(|v| v.is_null()).ok_or(Error::OutOfResources)?; |
| let entries = &mut entries[..n + 1]; |
| // Caps `idx` to no more than the first NULL entry. |
| let idx = min(n, idx); |
| // Comptues the ending block index (non-inclusive) of the previous partition entry. |
| // Entries are guaranteed sorted in `Self::new()`. |
| let prev_end = match idx { |
| 0 => header.first, |
| _ => entries[idx - 1].last + 1, |
| }; |
| // Comptues the starting block index (inclusive) of the next partition entry. |
| let next_start = match idx == n { |
| true => header.last + 1, |
| _ => entries[idx].first, |
| }; |
| // Computes the size in number of blocks |
| let blk_sz = self.disk.block_info().block_size; |
| let blocks: u64 = match size { |
| Some(v) => (SafeNum::from(v).round_up(blk_sz) / blk_sz).try_into()?, |
| _ => next_start - prev_end, // If not given, uses up all the gap space |
| }; |
| // Checks if there is enough space. |
| if next_start - prev_end < blocks { |
| return Err(Error::OutOfResources); |
| } |
| // Inserts the new entry. |
| entries[idx..].rotate_right(1); |
| let entry = &mut entries[idx]; |
| assert!(entry.is_null()); |
| entry.part_type = part_type; |
| entry.guid = unique_guid; |
| entry.flags = flags; |
| entry.first = prev_end; |
| entry.last = prev_end + blocks - 1; |
| for (idx, ele) in name.encode_utf16().enumerate() { |
| match idx < GPT_NAME_LEN_U16 { |
| true => entry.name[idx] = ele, |
| _ => break, |
| } |
| } |
| header.entries_count += 1; |
| Ok(()) |
| } |
| |
| /// Adds a partition. |
| /// |
| /// # Args |
| /// |
| /// * `name`: Name of the partition. |
| /// * `part_type`: Type GUID. |
| /// * `unique_guid`: Unique GUID. |
| /// * `flags`: Partition flag. |
| /// * `size`: If Some(_), specifies the size in number of bytes for the partition. The method |
| /// will round it up to multiple of disk block size and search for the first large enough |
| /// space in the unused spae for putting the partition. If None, the method will add the |
| /// partition at the last and have it consume all remaining usable disk space. |
| pub fn add( |
| &mut self, |
| name: &str, |
| part_type: [u8; GPT_GUID_LEN], |
| unique_guid: [u8; GPT_GUID_LEN], |
| flags: u64, |
| size: Option<u64>, |
| ) -> Result<()> { |
| let (header, _) = LoadBufferRef::from(&mut self.gpt.buffer[..]).secondary(); |
| let entry_count = usize::try_from(header.entries_count).unwrap(); |
| let search_start = size.is_some().then_some(0).unwrap_or(entry_count); |
| for i in search_start..entry_count + 1 { |
| if self.insert_before(i, name, part_type, unique_guid, flags, size).is_ok() { |
| return Ok(()); |
| } |
| } |
| Err(Error::OutOfResources) |
| } |
| |
| /// Persists the constructed GPT table to the disk and syncs. The builder is consumed. |
| pub async fn persist(mut self) -> Result<()> { |
| let (mut header, mut entries) = LoadBufferRef::from(&mut self.gpt.buffer[..]).secondary(); |
| header.update_entries_crc(entries.as_bytes()); |
| // Check validity. Should not fail if implementation is correct. |
| check_header(self.disk.io(), &header, false).unwrap(); |
| check_entries(&header, entries.as_bytes()).unwrap(); |
| let blk_sz = self.disk.block_info().block_size; |
| // Writes to secondary header/ entries |
| self.disk.write(header.current * blk_sz, header.as_bytes_mut()).await?; |
| self.disk.write(header.entries * blk_sz, entries.as_bytes_mut()).await?; |
| // Clears primary header magic |
| self.disk.write(blk_sz, &mut 0u64.to_be_bytes()).await?; |
| // Re-syncs GPT |
| self.disk.sync_gpt(&mut self.gpt).await?.res() |
| } |
| } |
| |
| /// Helper for calculcating the Crc32. |
| 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::TestDisk; |
| use gbl_async::block_on; |
| |
| /// A helper for creating a [TestDisk] from given data. |
| fn test_disk(data: impl AsRef<[u8]>) -> TestDisk { |
| // All tests cases use pre-generated GPT disk of 512 block size. |
| TestDisk::new_ram_alloc(512, 512, data.as_ref().to_vec()).unwrap() |
| } |
| |
| /// A helper for creating a [TestDisk] from given data and a [Gpt] for 128 entries. |
| fn test_disk_and_gpt(data: impl AsRef<[u8]>) -> (TestDisk, GptMax) { |
| (test_disk(data), new_gpt_max()) |
| } |
| |
| #[test] |
| fn test_load_and_sync() { |
| let (mut dev, mut gpt) = test_disk_and_gpt(include_bytes!("../test/gpt_test_1.bin")); |
| block_on(dev.sync_gpt(&mut gpt)).unwrap(); |
| |
| assert_eq!(gpt.partition_iter().unwrap().count(), 2); |
| gpt.find_partition("boot_a").unwrap(); |
| gpt.find_partition("boot_b").unwrap(); |
| assert!(gpt.find_partition("boot_c").is_err()); |
| |
| // Creating a new [Gpt] using the same buffer should reset the valid state. |
| let gpt = Gpt::new(gpt.buffer).unwrap(); |
| assert!(gpt.partition_iter().is_err()); |
| assert!(gpt.find_partition("boot_a").is_err()); |
| assert!(gpt.find_partition("boot_b").is_err()); |
| } |
| |
| #[test] |
| fn test_load_with_unaligned_buffer() { |
| #[repr(align(8))] |
| struct AlignedBuffer([u8; 34 * 1024]); |
| let mut buffer = AlignedBuffer([0u8; 34 * 1024]); |
| let buffer = &mut buffer.0[1..]; |
| assert_ne!(buffer.as_ptr() as usize % 2, 0); |
| let mut disk = test_disk(include_bytes!("../test/gpt_test_1.bin")); |
| let mut gpt = Gpt::new(buffer).unwrap(); |
| block_on(disk.sync_gpt(&mut gpt)).unwrap(); |
| } |
| |
| #[test] |
| fn test_gpt_buffer_too_small() { |
| assert!(Gpt::new(vec![0u8; size_of::<GptLoadBufferN<0>>() - 1]).is_err()); |
| } |
| |
| #[test] |
| fn test_gpt_buffer_not_enough_for_all_entries() { |
| let mut dev = test_disk(include_bytes!("../test/gpt_test_1.bin")); |
| let mut gpt = new_gpt_n::<127>(); |
| assert_eq!(gpt.max_entries(), 127); |
| // Actual entries_count is 128 in the GPT. |
| assert!(block_on(dev.sync_gpt(&mut gpt)).unwrap().res().is_err()); |
| } |
| |
| #[test] |
| fn test_good_gpt_no_repair_write() { |
| let (mut dev, mut gpt) = test_disk_and_gpt(include_bytes!("../test/gpt_test_1.bin")); |
| assert_eq!(block_on(dev.sync_gpt(&mut gpt)).unwrap(), GptSyncResult::BothValid); |
| } |
| |
| /// A helper for testing restoration of invalid primary/secondary header modified by caller. |
| fn test_gpt_sync_restore<'a>( |
| modify_primary: impl FnOnce(&mut GptHeader, Ref<&mut [u8], [GptEntry]>), |
| modify_secondary: impl FnOnce(&mut GptHeader, Ref<&mut [u8], [GptEntry]>), |
| expect_primary_err: Error, |
| expect_secondary_err: Error, |
| ) { |
| let disk_orig = include_bytes!("../test/gpt_test_1.bin"); |
| |
| // Restores from secondary to primary. |
| let mut disk = disk_orig.to_vec(); |
| let (header, entries) = (&mut disk[512..]).split_at_mut(512); |
| let mut header = GptHeader::from_bytes_mut(header); |
| modify_primary(&mut header, Ref::<_, [GptEntry]>::new_slice(entries).unwrap()); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| assert_ne!(dev.io().storage(), disk_orig); |
| let sync_res = block_on(dev.sync_gpt(&mut gpt)).unwrap(); |
| assert_eq!(sync_res, GptSyncResult::PrimaryRestored(expect_primary_err)); |
| assert_eq!(dev.io().storage(), disk_orig); |
| |
| // Restores from primary to secondary. |
| let mut disk = disk_orig.to_vec(); |
| let (entries, header) = (&mut disk[512..]).split_last_chunk_mut::<512>().unwrap(); |
| let (_, entries) = entries.split_last_chunk_mut::<{ 512 * 32 }>().unwrap(); |
| let mut header = GptHeader::from_bytes_mut(&mut header[..]); |
| modify_secondary(&mut header, Ref::<_, [GptEntry]>::new_slice(&mut entries[..]).unwrap()); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| assert_ne!(dev.io().storage(), disk_orig); |
| let sync_res = block_on(dev.sync_gpt(&mut gpt)).unwrap(); |
| assert_eq!(sync_res, GptSyncResult::SecondaryRestored(expect_secondary_err)); |
| assert_eq!(dev.io().storage(), disk_orig); |
| } |
| |
| #[test] |
| fn test_sync_gpt_incorrect_magic() { |
| fn modify(hdr: &mut GptHeader, _: Ref<&mut [u8], [GptEntry]>) { |
| hdr.magic = 0x123456; |
| hdr.update_crc(); |
| } |
| let err = Error::GptError(GptError::IncorrectMagic(0x123456)); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_incorrect_crc() { |
| fn modify(hdr: &mut GptHeader, _: Ref<&mut [u8], [GptEntry]>) { |
| hdr.crc32 = !hdr.crc32; |
| } |
| let err = Error::GptError(GptError::IncorrectHeaderCrc); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_unexpected_header_size() { |
| fn modify(hdr: &mut GptHeader, _: Ref<&mut [u8], [GptEntry]>) { |
| hdr.size += 1; |
| hdr.update_crc(); |
| } |
| let err = Error::GptError(GptError::UnexpectedHeaderSize { actual: 93, expect: 92 }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_unexpected_entry_size() { |
| fn modify(hdr: &mut GptHeader, _: Ref<&mut [u8], [GptEntry]>) { |
| hdr.entries_size += 1; |
| hdr.update_crc(); |
| } |
| let err = Error::GptError(GptError::UnexpectedEntrySize { actual: 129, expect: 128 }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_first_usable_gt_last() { |
| fn modify(hdr: &mut GptHeader, _: Ref<&mut [u8], [GptEntry]>) { |
| hdr.first = hdr.last; |
| hdr.last = hdr.first - 2; |
| hdr.update_crc(); |
| } |
| let err = Error::GptError(GptError::InvalidFirstLastUsableBlock { |
| first: 94, |
| last: 92, |
| range: (34, 94), |
| }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_first_usable_out_of_range() { |
| fn modify(hdr: &mut GptHeader, _: Ref<&mut [u8], [GptEntry]>) { |
| hdr.first = 33; |
| hdr.update_crc(); |
| } |
| let err = Error::GptError(GptError::InvalidFirstLastUsableBlock { |
| first: 33, |
| last: 94, |
| range: (34, 94), |
| }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_last_usable_out_of_range() { |
| fn modify(hdr: &mut GptHeader, _: Ref<&mut [u8], [GptEntry]>) { |
| hdr.last += 1; |
| hdr.update_crc(); |
| } |
| let err = Error::GptError(GptError::InvalidFirstLastUsableBlock { |
| first: 34, |
| last: 95, |
| range: (34, 94), |
| }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_primary_entries_out_of_range() { |
| test_gpt_sync_restore( |
| |hdr, _| { |
| hdr.entries = 1; |
| hdr.update_crc(); |
| }, |
| |hdr, _| { |
| hdr.entries = hdr.last; |
| hdr.update_crc(); |
| }, |
| Error::GptError(GptError::InvalidPrimaryEntriesStart { |
| value: 1, |
| expect_range: (2, 2), |
| }), |
| Error::GptError(GptError::InvalidSecondaryEntriesStart { |
| value: 94, |
| expect_range: (95, 95), |
| }), |
| ); |
| } |
| |
| #[test] |
| fn test_sync_gpt_incorrect_entry_crc() { |
| fn modify(hdr: &mut GptHeader, _: Ref<&mut [u8], [GptEntry]>) { |
| hdr.entries_crc = !hdr.entries_crc; |
| hdr.update_crc(); |
| } |
| let err = Error::GptError(GptError::IncorrectEntriesCrc); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_partition_range_overflow() { |
| fn modify(hdr: &mut GptHeader, mut entries: Ref<&mut [u8], [GptEntry]>) { |
| entries[1].last = hdr.last + 1; |
| hdr.update_entries_crc(entries.as_bytes()); |
| } |
| let err = Error::GptError(GptError::InvalidPartitionRange { |
| idx: 2, |
| part_range: (50, 95), |
| usable_range: (34, 94), |
| }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_invalid_partition_range() { |
| fn modify(hdr: &mut GptHeader, mut entries: Ref<&mut [u8], [GptEntry]>) { |
| entries[1].first = entries[1].last; |
| entries[1].last = entries[1].first - 2; |
| hdr.update_entries_crc(entries.as_bytes()); |
| } |
| let err = Error::GptError(GptError::InvalidPartitionRange { |
| idx: 2, |
| part_range: (73, 71), |
| usable_range: (34, 94), |
| }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_partition_overlap() { |
| fn modify(hdr: &mut GptHeader, mut entries: Ref<&mut [u8], [GptEntry]>) { |
| entries[0].last = entries[1].first; |
| entries.swap(0, 1); |
| hdr.update_entries_crc(entries.as_bytes()); |
| } |
| let err = Error::GptError(GptError::PartitionRangeOverlap { |
| prev: (2, 34, 50), |
| next: (1, 50, 73), |
| }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_zero_partition_type_guid() { |
| fn modify(hdr: &mut GptHeader, mut entries: Ref<&mut [u8], [GptEntry]>) { |
| entries[1].part_type = [0u8; GPT_GUID_LEN]; |
| hdr.update_entries_crc(entries.as_bytes()); |
| } |
| let err = Error::GptError(GptError::ZeroPartitionTypeGUID { idx: 2 }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_sync_gpt_zero_partition_unique_guid() { |
| fn modify(hdr: &mut GptHeader, mut entries: Ref<&mut [u8], [GptEntry]>) { |
| entries[1].guid = [0u8; GPT_GUID_LEN]; |
| hdr.update_entries_crc(entries.as_bytes()); |
| } |
| let err = Error::GptError(GptError::ZeroPartitionUniqueGUID { idx: 2 }); |
| test_gpt_sync_restore(modify, modify, err, err); |
| } |
| |
| #[test] |
| fn test_load_gpt_disk_primary_override_secondary() { |
| let mut disk = include_bytes!("../test/gpt_test_1.bin").to_vec(); |
| // Modifies secondary header. |
| let secondary_hdr = GptHeader::from_bytes_mut(disk.last_chunk_mut::<512>().unwrap()); |
| secondary_hdr.revision = !secondary_hdr.revision; |
| secondary_hdr.update_crc(); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| assert_eq!( |
| block_on(dev.sync_gpt(&mut gpt)).unwrap(), |
| GptSyncResult::SecondaryRestored(Error::GptError(GptError::DifferentFromPrimary)), |
| ); |
| } |
| |
| #[test] |
| fn test_load_gpt_disk_too_small() { |
| let disk_orig = include_bytes!("../test/gpt_test_1.bin"); |
| let mut disk = disk_orig.to_vec(); |
| // Resizes so that it's not enough to hold a full 128 maximum entries. |
| // MBR + (header + entries) * 2 - 1 |
| disk.resize((1 + (32 + 1) * 2 - 1) * 512, 0); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| let sync_res = block_on(dev.sync_gpt(&mut gpt)).unwrap(); |
| let err = Error::GptError(GptError::DiskTooSmall); |
| assert_eq!(sync_res, GptSyncResult::NoValidGpt { primary: err, secondary: err }); |
| } |
| |
| #[test] |
| fn test_uninitialized_gpt() { |
| let disk = include_bytes!("../test/gpt_test_1.bin"); |
| // Load a good GPT first. |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| assert_eq!(block_on(dev.sync_gpt(&mut gpt)).unwrap(), GptSyncResult::BothValid); |
| gpt.find_partition("boot_a").unwrap(); |
| // Corrupt GPT. |
| block_on(dev.write(0, &mut vec![0u8; disk.len()])).unwrap(); |
| assert!(block_on(dev.sync_gpt(&mut gpt)).unwrap().res().is_err()); |
| assert!(gpt.find_partition("").is_err()); |
| } |
| |
| #[test] |
| fn test_update_gpt() { |
| let disk_orig = include_bytes!("../test/gpt_test_1.bin"); |
| let mut disk = disk_orig.to_vec(); |
| // Erases all GPT headers. |
| disk[512..][..512].fill(0); |
| disk.last_chunk_mut::<512>().unwrap().fill(0); |
| |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| |
| assert_ne!(dev.io().storage(), disk_orig); |
| let mut mbr_primary = disk_orig[..34 * 512].to_vec(); |
| block_on(dev.update_gpt(&mut mbr_primary, false, &mut gpt)).unwrap(); |
| assert_eq!(dev.io().storage(), disk_orig); |
| } |
| |
| #[test] |
| fn test_update_gpt_has_existing_valid_secondary() { |
| let disk_orig = include_bytes!("../test/gpt_test_1.bin"); |
| let mut disk = disk_orig.to_vec(); |
| // Erases all GPT headers. |
| disk[512..][..512].fill(0); |
| // Leaves a valid but different secondary GPT. |
| let secondary_hdr = GptHeader::from_bytes_mut(disk.last_chunk_mut::<512>().unwrap()); |
| secondary_hdr.revision = !secondary_hdr.revision; |
| secondary_hdr.update_crc(); |
| |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| |
| assert_ne!(dev.io().storage(), disk_orig); |
| let mut mbr_primary = disk_orig[..34 * 512].to_vec(); |
| block_on(dev.update_gpt(&mut mbr_primary, false, &mut gpt)).unwrap(); |
| assert_eq!(dev.io().storage(), disk_orig); |
| } |
| |
| #[test] |
| fn test_update_gpt_last_usable_adjusted() { |
| let disk_orig = include_bytes!("../test/gpt_test_1.bin"); |
| let mut disk = disk_orig.to_vec(); |
| // Erases all GPT headers. |
| disk[512..][..512].fill(0); |
| disk.last_chunk_mut::<512>().unwrap().fill(0); |
| // Doubles the disk size. |
| disk.resize(disk_orig.len() * 2, 0); |
| |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| |
| assert_ne!(dev.io().storage, disk_orig); |
| let mut mbr_primary = disk_orig[..34 * 512].to_vec(); |
| block_on(dev.update_gpt(&mut mbr_primary, true, &mut gpt)).unwrap(); |
| let expected_last = (disk.len() - GPT_MAX_NUM_ENTRIES_SIZE - 512) / 512 - 1; |
| |
| let (primary, secondary) = dev.io().storage().split_last_chunk_mut::<512>().unwrap(); |
| let primary_hdr = GptHeader::from_bytes_mut(&mut primary[512..]); |
| let secondary_hdr = GptHeader::from_bytes_mut(secondary); |
| // Header's last usable block is updated. |
| assert_eq!({ primary_hdr.last }, expected_last.try_into().unwrap()); |
| assert_eq!({ primary_hdr.backup }, (disk.len() / 512 - 1).try_into().unwrap()); |
| assert_eq!({ secondary_hdr.last }, expected_last.try_into().unwrap()); |
| } |
| |
| #[test] |
| fn test_update_gpt_resize() { |
| let disk_orig = include_bytes!("../test/gpt_test_1.bin"); |
| let mut disk = disk_orig.to_vec(); |
| // Erases all GPT headers. |
| disk[512..][..512].fill(0); |
| disk.last_chunk_mut::<512>().unwrap().fill(0); |
| // Doubles the disk size. |
| disk.resize(disk_orig.len() * 2, 0); |
| |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| |
| assert_ne!(dev.io().storage, disk_orig); |
| let mut mbr_primary = disk_orig[..34 * 512].to_vec(); |
| block_on(dev.update_gpt(&mut mbr_primary, true, &mut gpt)).unwrap(); |
| // Last entry is extended. |
| let expected_last = (disk.len() - GPT_MAX_NUM_ENTRIES_SIZE - 512) / 512 - 1; |
| assert_eq!({ gpt.entries().unwrap()[1].last }, expected_last.try_into().unwrap()); |
| } |
| |
| #[test] |
| fn test_update_gpt_new_partition_out_of_range() { |
| // `gpt_test_1.bin` has a 8k "boot_a" and a 12k "boot_b". Thus partitions space is 40 |
| // blocks (512 bytes block size) and in total the GPT disk needs (40 + 1 + (33) * 2) = 107 |
| // blocks. |
| let (mut dev, mut gpt) = test_disk_and_gpt(&vec![0u8; 106 * 512]); |
| let mut mbr_primary = include_bytes!("../test/gpt_test_1.bin")[..34 * 512].to_vec(); |
| assert!(block_on(dev.update_gpt(&mut mbr_primary, true, &mut gpt)).is_err()); |
| } |
| |
| #[test] |
| fn test_update_gpt_buffer_truncated() { |
| let mut disk = include_bytes!("../test/gpt_test_1.bin").to_vec(); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| |
| // Less than 1 MBR block. |
| assert_eq!( |
| block_on(dev.update_gpt(&mut disk[..511], false, &mut gpt)), |
| Err(Error::BufferTooSmall(Some(1024))) |
| ); |
| |
| // Less than MBR + GPT header. |
| assert_eq!( |
| block_on(dev.update_gpt(&mut disk[..1023], false, &mut gpt)), |
| Err(Error::BufferTooSmall(Some(1024))) |
| ); |
| |
| // Less than MBR + GPT header + entries. |
| assert_eq!( |
| block_on(dev.update_gpt(&mut disk[..34 * 512 - 1], false, &mut gpt)), |
| Err(Error::BufferTooSmall(Some(34 * 512))) |
| ); |
| } |
| |
| #[test] |
| fn test_update_gpt_check_header_fail() { |
| let disk = include_bytes!("../test/gpt_test_1.bin"); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| let mut mbr_primary = disk[..34 * 512].to_vec(); |
| // Corrupts the first byte of the GPT header. |
| mbr_primary[512] = !mbr_primary[512]; |
| assert_eq!( |
| block_on(dev.update_gpt(&mut mbr_primary, false, &mut gpt)), |
| Err(Error::GptError(GptError::IncorrectMagic(0x54524150204946BA))) |
| ); |
| } |
| |
| #[test] |
| fn test_update_gpt_check_entries_fail() { |
| let disk = include_bytes!("../test/gpt_test_1.bin"); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| let mut mbr_primary = disk[..34 * 512].to_vec(); |
| // Corrupts the first byte of the entries. |
| mbr_primary[1024] = !mbr_primary[1024]; |
| assert_eq!( |
| block_on(dev.update_gpt(&mut mbr_primary, false, &mut gpt)), |
| Err(Error::GptError(GptError::IncorrectEntriesCrc)) |
| ); |
| } |
| |
| #[test] |
| fn test_erase_gpt_no_gpt() { |
| let (mut dev, mut gpt) = test_disk_and_gpt(&[0u8; 1024 * 1024]); |
| block_on(dev.erase_gpt(&mut gpt)).unwrap(); |
| } |
| |
| #[test] |
| fn test_erase_gpt() { |
| let (mut dev, mut gpt) = test_disk_and_gpt(include_bytes!("../test/gpt_test_1.bin")); |
| block_on(dev.erase_gpt(&mut gpt)).unwrap(); |
| const GPT_SECTOR: usize = 33 * 512; |
| assert_eq!(dev.io().storage[512..][..GPT_SECTOR], vec![0u8; GPT_SECTOR]); |
| assert_eq!(*dev.io().storage.last_chunk::<GPT_SECTOR>().unwrap(), *vec![0u8; GPT_SECTOR]); |
| assert!(matches!( |
| block_on(dev.sync_gpt(&mut gpt)).unwrap(), |
| GptSyncResult::NoValidGpt { .. } |
| )); |
| } |
| |
| #[test] |
| fn test_zero_partition_size() { |
| let disk = include_bytes!("../test/gpt_test_1.bin").to_vec(); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| let (mut builder, _) = GptBuilder::new(&mut dev, &mut gpt).unwrap(); |
| assert_eq!(builder.remove("boot_a"), Ok(true)); |
| assert_eq!(builder.remove("boot_b"), Ok(true)); |
| builder.add("boot_b", [1u8; GPT_GUID_LEN], [1u8; GPT_GUID_LEN], 0, Some(0)).unwrap(); |
| block_on(builder.persist()).unwrap(); |
| assert_eq!(gpt.partition_iter().unwrap().next().unwrap().size().unwrap(), 0); |
| } |
| |
| #[test] |
| fn test_sync_gpt_non_sorted_entries() { |
| let mut disk = include_bytes!("../test/gpt_test_1.bin").to_vec(); |
| let (header, entries) = disk[512..].split_at_mut(512); |
| let header = GptHeader::from_bytes_mut(header); |
| let mut entries = Ref::<_, [GptEntry]>::new_slice(entries).unwrap(); |
| // Makes partition non-sorted. |
| entries.swap(0, 1); |
| header.update_entries_crc(entries.as_bytes()); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| block_on(dev.sync_gpt(&mut gpt)).unwrap().res().unwrap(); |
| } |
| |
| #[test] |
| fn test_gpt_builder_initialize_gpt_if_no_valid_gpt() { |
| let (mut dev, mut gpt) = test_disk_and_gpt(vec![0u8; 1024 * 1024]); |
| let (builder, valid) = GptBuilder::new(&mut dev, &mut gpt).unwrap(); |
| assert!(!valid); |
| block_on(builder.persist()).unwrap(); |
| // A new GPT is created. |
| block_on(dev.sync_gpt(&mut gpt)).unwrap().res().unwrap(); |
| assert!(gpt.partition_iter().unwrap().next().is_none()); |
| } |
| |
| #[test] |
| fn test_gpt_builder_remove_partition() { |
| let (mut dev, mut gpt) = test_disk_and_gpt(include_bytes!("../test/gpt_test_1.bin")); |
| let (mut builder, valid) = GptBuilder::new(&mut dev, &mut gpt).unwrap(); |
| assert!(valid); |
| assert_eq!(builder.remove("boot_b"), Ok(true)); |
| assert_eq!(builder.remove("non-existent"), Ok(false)); |
| block_on(builder.persist()).unwrap(); |
| block_on(dev.sync_gpt(&mut gpt)).unwrap().res().unwrap(); |
| let part_iter = gpt.partition_iter().unwrap(); |
| assert_eq!( |
| part_iter.map(|v| v.name().unwrap().into()).collect::<Vec<String>>(), |
| ["boot_a"] |
| ); |
| } |
| |
| #[test] |
| fn test_gpt_builder_add_partition_find_first() { |
| let (mut dev, mut gpt) = test_disk_and_gpt(include_bytes!("../test/gpt_test_1.bin")); |
| let (mut builder, _) = GptBuilder::new(&mut dev, &mut gpt).unwrap(); |
| assert!(builder.remove("boot_a").unwrap()); |
| // Adds at the beginning. |
| builder.add("new_0", [1u8; GPT_GUID_LEN], [1u8; GPT_GUID_LEN], 0, Some(1024)).unwrap(); |
| // Adds following "new_0" |
| builder.add("new_1", [1u8; GPT_GUID_LEN], [1u8; GPT_GUID_LEN], 0, Some(1)).unwrap(); |
| block_on(builder.persist()).unwrap(); |
| block_on(dev.sync_gpt(&mut gpt)).unwrap().res().unwrap(); |
| assert_eq!(gpt.find_partition("new_0").unwrap().absolute_range().unwrap(), (17408, 18432)); |
| assert_eq!(gpt.find_partition("new_1").unwrap().absolute_range().unwrap(), (18432, 18944)); |
| assert_eq!(gpt.find_partition("boot_b").unwrap().absolute_range().unwrap(), (25600, 37888)); |
| } |
| |
| #[test] |
| fn test_gpt_builder_non_sorted_add_partition() { |
| let mut disk = include_bytes!("../test/gpt_test_1.bin").to_vec(); |
| let (mut dev, mut gpt) = test_disk_and_gpt(&disk); |
| let (header, entries) = disk[512..].split_at_mut(512); |
| let header = GptHeader::from_bytes_mut(header); |
| let mut entries = Ref::<_, [GptEntry]>::new_slice(entries).unwrap(); |
| // Makes partition non-sorted. |
| entries.swap(0, 1); |
| header.update_entries_crc(entries.as_bytes()); |
| |
| let (mut builder, _) = GptBuilder::new(&mut dev, &mut gpt).unwrap(); |
| // Adds following boot_b. |
| builder.add("new", [1u8; GPT_GUID_LEN], [1u8; GPT_GUID_LEN], 0, Some(1024)).unwrap(); |
| block_on(builder.persist()).unwrap(); |
| assert_eq!(gpt.find_partition("boot_a").unwrap().absolute_range().unwrap(), (17408, 25600)); |
| assert_eq!(gpt.find_partition("boot_b").unwrap().absolute_range().unwrap(), (25600, 37888)); |
| assert_eq!(gpt.find_partition("new").unwrap().absolute_range().unwrap(), (37888, 38912)); |
| } |
| |
| #[test] |
| fn test_gpt_builder_add_partition_append() { |
| let (mut dev, mut gpt) = test_disk_and_gpt(include_bytes!("../test/gpt_test_1.bin")); |
| let (mut builder, _) = GptBuilder::new(&mut dev, &mut gpt).unwrap(); |
| assert!(builder.remove("boot_b").unwrap()); |
| // Adds following "boot_a". |
| builder.add("new_0", [1u8; GPT_GUID_LEN], [1u8; GPT_GUID_LEN], 0, Some(1024)).unwrap(); |
| // Consumes the rest of the space. |
| builder.add("new_1", [1u8; GPT_GUID_LEN], [1u8; GPT_GUID_LEN], 0, None).unwrap(); |
| block_on(builder.persist()).unwrap(); |
| block_on(dev.sync_gpt(&mut gpt)).unwrap().res().unwrap(); |
| assert_eq!(gpt.find_partition("boot_a").unwrap().absolute_range().unwrap(), (17408, 25600)); |
| assert_eq!(gpt.find_partition("new_0").unwrap().absolute_range().unwrap(), (25600, 26624)); |
| assert_eq!(gpt.find_partition("new_1").unwrap().absolute_range().unwrap(), (26624, 48640)); |
| } |
| |
| #[test] |
| fn test_gpt_builder_not_enough_resource() { |
| // Create a Gpt that can only load 1 entry. |
| let mut gpt = new_gpt_n::<1>(); |
| let mut dev = test_disk(vec![0u8; 64 * 1024]); |
| let (mut builder, _) = GptBuilder::new(&mut dev, &mut gpt).unwrap(); |
| builder.add("new_0", [1u8; GPT_GUID_LEN], [1u8; GPT_GUID_LEN], 0, Some(1024)).unwrap(); |
| assert!(builder.add("new_1", [1u8; GPT_GUID_LEN], [1u8; GPT_GUID_LEN], 0, None).is_err()); |
| } |
| } |