| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::fs::File; |
| |
| use anyhow::{anyhow, bail, Context, Result}; |
| use gpt_disk_io::{BlockIo, BlockIoAdapter, Disk}; |
| use gpt_disk_types::{BlockSize, GptPartitionEntry, GptPartitionName}; |
| |
| /// Holds information about a GPT formatted disk. |
| pub struct Gpt<T: BlockIo> { |
| disk: Disk<T>, |
| block_size: BlockSize, |
| } |
| |
| impl Gpt<BlockIoAdapter<File>> { |
| /// Creates a new [`Gpt`] from a file. The file should point to a disk with a |
| /// valid GPT header table on it, e.g. when opening "/dev/nvme0n1". |
| pub fn from_file(file: File, block_size: BlockSize) -> Result<Self> { |
| let block_io = BlockIoAdapter::new(file, block_size); |
| |
| Self::new(block_io) |
| } |
| } |
| |
| #[cfg(test)] |
| impl Gpt<BlockIoAdapter<Vec<u8>>> { |
| /// Used for testing, creates a [`Gpt`] from a vector that should contain |
| /// a valid GPT header table. |
| fn from_slice(vec: Vec<u8>, block_size: BlockSize) -> Result<Self> { |
| let block_io = BlockIoAdapter::new(vec, block_size); |
| |
| Self::new(block_io) |
| } |
| } |
| |
| impl<T: BlockIo> Gpt<T> { |
| /// Creates a new [`Gpt`] backed by `io`. |
| fn new(io: T) -> Result<Self> { |
| let block_size = io.block_size(); |
| let mut disk = Disk::new(io)?; |
| Self::verify_disk(&mut disk, block_size)?; |
| |
| Ok(Self { disk, block_size }) |
| } |
| |
| fn verify_disk(disk: &mut Disk<T>, block_size: BlockSize) -> Result<()> { |
| let mut block_buf = vec![0u8; block_size.to_usize().context("Failed to read block size")?]; |
| let primary_header = disk.read_primary_gpt_header(&mut block_buf)?; |
| |
| if !primary_header.is_signature_valid() { |
| return Err(anyhow!("Error: Invalid GPT header signature")); |
| } |
| |
| Ok(()) |
| } |
| |
| /// Reads the GPT partition table and returns information about the first |
| /// partition with `label` that is found. |
| pub fn get_entry_for_partition_with_label( |
| &mut self, |
| label: GptPartitionName, |
| ) -> Result<GptPartitionEntry> { |
| let mut block_buf = vec![ |
| 0; |
| self.block_size |
| .to_usize() |
| .context("Failed to read block size")? |
| ]; |
| let header_table = self.disk.read_primary_gpt_header(&mut block_buf)?; |
| let layout = header_table.get_partition_entry_array_layout()?; |
| |
| let mut block_buf = vec![ |
| 0; |
| layout |
| .num_bytes_rounded_to_block_as_usize(self.block_size) |
| .unwrap() |
| ]; |
| let entry_array = self |
| .disk |
| .read_gpt_partition_entry_array(layout, &mut block_buf)?; |
| |
| for entry_array_index in 0..layout.num_entries { |
| let entry = entry_array.get_partition_entry(entry_array_index).unwrap(); |
| if entry.name == label && entry.is_used() { |
| return Ok(*entry); |
| } |
| } |
| bail!("Unable to find matching partition"); |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use anyhow::Result; |
| use gpt_disk_io::{BlockIoAdapter, Disk}; |
| use gpt_disk_types::{ |
| BlockSize, GptHeader, GptPartitionEntry, GptPartitionEntryArray, GptPartitionType, LbaLe, |
| U32Le, |
| }; |
| |
| use crate::gpt::Gpt; |
| |
| const ENTRY_LABEL: &str = "STATE"; |
| |
| fn setup_disk_with_valid_header(disk_storage: &mut [u8], block_size: BlockSize) -> Result<()> { |
| let block_io = BlockIoAdapter::new(disk_storage, block_size); |
| let mut disk = Disk::new(block_io)?; |
| |
| let primary_header = GptHeader { |
| partition_entry_lba: LbaLe::from_u64(2), |
| number_of_partition_entries: U32Le::from_u32(128), |
| ..Default::default() |
| }; |
| let partition_entry = GptPartitionEntry { |
| name: ENTRY_LABEL.parse().unwrap(), |
| partition_type_guid: GptPartitionType::EFI_SYSTEM, |
| ..Default::default() |
| }; |
| let layout = primary_header.get_partition_entry_array_layout()?; |
| let mut bytes = vec![ |
| 0; |
| layout |
| .num_bytes_rounded_to_block_as_usize(block_size) |
| .unwrap() |
| ]; |
| let mut entry_array = GptPartitionEntryArray::new(layout, block_size, &mut bytes)?; |
| *entry_array.get_partition_entry_mut(0).unwrap() = partition_entry; |
| |
| let mut block_buf = vec![0u8; block_size.to_usize().unwrap()]; |
| disk.write_protective_mbr(&mut block_buf)?; |
| disk.write_primary_gpt_header(&primary_header, &mut block_buf)?; |
| disk.write_gpt_partition_entry_array(&entry_array)?; |
| |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_can_construct() -> Result<()> { |
| let mut disk = vec![0; 4 * 1024 * 1024]; |
| let block_size = BlockSize::BS_512; |
| setup_disk_with_valid_header(&mut disk, block_size)?; |
| |
| let gpt = Gpt::from_slice(disk, block_size); |
| |
| assert!(gpt.is_ok()); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_get_entry_for_partition_with_label() -> Result<()> { |
| let mut disk = vec![0; 4 * 1024 * 1024]; |
| let block_size = BlockSize::BS_512; |
| setup_disk_with_valid_header(&mut disk, block_size)?; |
| |
| let mut gpt = Gpt::from_slice(disk, block_size)?; |
| let state_entry = gpt.get_entry_for_partition_with_label(ENTRY_LABEL.parse().unwrap()); |
| assert!(state_entry.is_ok()); |
| |
| Ok(()) |
| } |
| } |