// 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; 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 = core::result::Result; pub struct Partition { partitions: Partitions, indices: RangeInclusive, } impl Partition { pub fn get_by_name(device: VirtIOBlk, name: &str) -> Result> { 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 { 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 { 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 { 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> { const_assert_eq!(Partitions::LBA_SIZE.rem_euclid(size_of::()), 0); let entries_per_blk = Partitions::LBA_SIZE.checked_div(size_of::()).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::()]; 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::(); // 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::
() < 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::().try_into().unwrap() && self.revision() == Self::REVISION_1_0 && self.entry_size() == size_of::().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::()], // 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) } }