blob: 9d4b1e378ab92c8fc63fc20843ea60fbb830f202 [file] [log] [blame] [edit]
// 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(())
}
}