blob: 71eb5690ff44de26caae9b798d7cb0b0d2a73e49 [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.
//! Support for parsing GUID partition tables.
use core::cmp::min;
use core::fmt;
use core::mem::size_of;
use core::ops::RangeInclusive;
use core::slice;
use static_assertions::const_assert;
use static_assertions::const_assert_eq;
use uuid::Uuid;
use virtio_drivers::device::blk::SECTOR_SIZE;
use vmbase::util::ceiling_div;
use vmbase::virtio::{pci, HalImpl};
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
type VirtIOBlk = pci::VirtIOBlk<HalImpl>;
pub enum Error {
/// VirtIO error during read operation.
FailedRead(virtio_drivers::Error),
/// VirtIO error during write operation.
FailedWrite(virtio_drivers::Error),
/// Invalid GPT header.
InvalidHeader,
/// Invalid partition block index.
BlockOutsidePartition(usize),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::FailedRead(e) => write!(f, "Failed to read from disk: {e}"),
Self::FailedWrite(e) => write!(f, "Failed to write to disk: {e}"),
Self::InvalidHeader => write!(f, "Found invalid GPT header"),
Self::BlockOutsidePartition(i) => write!(f, "Accessed invalid block index {i}"),
}
}
}
pub type Result<T> = core::result::Result<T, Error>;
pub struct Partition {
partitions: Partitions,
indices: RangeInclusive<usize>,
}
impl Partition {
pub fn get_by_name(device: VirtIOBlk, name: &str) -> Result<Option<Self>> {
Partitions::new(device)?.get_partition_by_name(name)
}
fn new(partitions: Partitions, entry: &Entry) -> Self {
let first = entry.first_lba().try_into().unwrap();
let last = entry.last_lba().try_into().unwrap();
Self { partitions, indices: first..=last }
}
pub fn indices(&self) -> RangeInclusive<usize> {
self.indices.clone()
}
pub fn read_block(&mut self, index: usize, blk: &mut [u8]) -> Result<()> {
let index = self.block_index(index).ok_or(Error::BlockOutsidePartition(index))?;
self.partitions.read_block(index, blk)
}
pub fn write_block(&mut self, index: usize, blk: &[u8]) -> Result<()> {
let index = self.block_index(index).ok_or(Error::BlockOutsidePartition(index))?;
self.partitions.write_block(index, blk)
}
fn block_index(&self, index: usize) -> Option<usize> {
if self.indices.contains(&index) {
Some(index)
} else {
None
}
}
}
pub struct Partitions {
device: VirtIOBlk,
entries_count: usize,
}
impl Partitions {
pub const LBA_SIZE: usize = SECTOR_SIZE;
fn new(mut device: VirtIOBlk) -> Result<Self> {
let mut blk = [0; Self::LBA_SIZE];
device.read_blocks(Header::LBA, &mut blk).map_err(Error::FailedRead)?;
let header = Header::read_from_prefix(blk.as_slice()).unwrap();
if !header.is_valid() {
return Err(Error::InvalidHeader);
}
let entries_count = usize::try_from(header.entries_count()).unwrap();
Ok(Self { device, entries_count })
}
fn get_partition_by_name(mut self, name: &str) -> Result<Option<Partition>> {
const_assert_eq!(Partitions::LBA_SIZE.rem_euclid(size_of::<Entry>()), 0);
let entries_per_blk = Partitions::LBA_SIZE.checked_div(size_of::<Entry>()).unwrap();
// Create a UTF-16 reference against which we'll compare partition names. Note that unlike
// the C99 wcslen(), this comparison will cover bytes past the first L'\0' character.
let mut needle = [0; Entry::NAME_SIZE / size_of::<u16>()];
for (dest, src) in needle.iter_mut().zip(name.encode_utf16()) {
*dest = src;
}
let mut blk = [0; Self::LBA_SIZE];
let mut rem = self.entries_count;
let num_blocks = ceiling_div(self.entries_count, entries_per_blk).unwrap();
for i in Header::ENTRIES_LBA..Header::ENTRIES_LBA.checked_add(num_blocks).unwrap() {
self.read_block(i, &mut blk)?;
let entries = blk.as_ptr().cast::<Entry>();
// SAFETY: blk is assumed to be properly aligned for Entry and its size is assert-ed
// above. All potential values of the slice will produce valid Entry values.
let entries = unsafe { slice::from_raw_parts(entries, min(rem, entries_per_blk)) };
for entry in entries {
let entry_name = entry.name;
if entry_name == needle {
return Ok(Some(Partition::new(self, entry)));
}
rem -= 1;
}
}
Ok(None)
}
fn read_block(&mut self, index: usize, blk: &mut [u8]) -> Result<()> {
self.device.read_blocks(index, blk).map_err(Error::FailedRead)
}
fn write_block(&mut self, index: usize, blk: &[u8]) -> Result<()> {
self.device.write_blocks(index, blk).map_err(Error::FailedWrite)
}
}
type Lba = u64;
/// Structure as defined in release 2.10 of the UEFI Specification (5.3.2 GPT Header).
#[derive(FromZeroes, FromBytes)]
#[repr(C, packed)]
struct Header {
signature: u64,
revision: u32,
header_size: u32,
header_crc32: u32,
reserved0: u32,
current_lba: Lba,
backup_lba: Lba,
first_lba: Lba,
last_lba: Lba,
disk_guid: u128,
entries_lba: Lba,
entries_count: u32,
entry_size: u32,
entries_crc32: u32,
}
const_assert!(size_of::<Header>() < Partitions::LBA_SIZE);
impl Header {
const SIGNATURE: u64 = u64::from_le_bytes(*b"EFI PART");
const REVISION_1_0: u32 = 1 << 16;
const LBA: usize = 1;
const ENTRIES_LBA: usize = 2;
fn is_valid(&self) -> bool {
self.signature() == Self::SIGNATURE
&& self.header_size() == size_of::<Self>().try_into().unwrap()
&& self.revision() == Self::REVISION_1_0
&& self.entry_size() == size_of::<Entry>().try_into().unwrap()
&& self.current_lba() == Self::LBA.try_into().unwrap()
&& self.entries_lba() == Self::ENTRIES_LBA.try_into().unwrap()
}
fn signature(&self) -> u64 {
u64::from_le(self.signature)
}
fn entries_count(&self) -> u32 {
u32::from_le(self.entries_count)
}
fn header_size(&self) -> u32 {
u32::from_le(self.header_size)
}
fn revision(&self) -> u32 {
u32::from_le(self.revision)
}
fn entry_size(&self) -> u32 {
u32::from_le(self.entry_size)
}
fn entries_lba(&self) -> Lba {
Lba::from_le(self.entries_lba)
}
fn current_lba(&self) -> Lba {
Lba::from_le(self.current_lba)
}
}
/// Structure as defined in release 2.10 of the UEFI Specification (5.3.3 GPT Partition Entry
/// Array).
#[repr(C, packed)]
struct Entry {
type_guid: Uuid,
guid: Uuid,
first_lba: Lba,
last_lba: Lba,
flags: u64,
name: [u16; Entry::NAME_SIZE / size_of::<u16>()], // UTF-16
}
impl Entry {
const NAME_SIZE: usize = 72;
fn first_lba(&self) -> Lba {
Lba::from_le(self.first_lba)
}
fn last_lba(&self) -> Lba {
Lba::from_le(self.last_lba)
}
}