blob: 62ba7a81c0a4a4971996226d1ab64ae98586605b [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::{Error, Result},
byteorder::{LittleEndian, ReadBytesExt},
log::info,
serde::{Deserialize, Serialize},
std::collections::HashMap,
std::convert::{TryFrom, TryInto},
std::fmt,
std::io::{Cursor, Read, Seek, SeekFrom},
std::str,
thiserror::Error,
};
const FVM_MAGIC: u64 = 0x54524150204d5646;
const FVM_VERSION: u64 = 0x00000001;
const FVM_BLOCK_SIZE: u64 = 8192;
const FVM_MAX_VPARTITION_NAME_LEN: usize = 24;
const FVM_MAX_VPARTITIONS: usize = 1024;
const SHA256_BYTE_LEN: usize = 32;
const GPT_GUID_LEN: usize = 16;
// Masks and constants required to extract the opaque slice entry data.
const SLICE_ENTRY_VPARTITION_BITS: u64 = 16;
const SLICE_ENTRY_VSLICE_BITS: u64 = 32;
const SLICE_ENTRY_VSLICE_MAX: u64 = (1 << SLICE_ENTRY_VSLICE_BITS) - 1;
const SLICE_ENTRY_VSLICE_MASK: u64 = SLICE_ENTRY_VSLICE_MAX << SLICE_ENTRY_VPARTITION_BITS;
const VPARTITION_ENTRY_MASK: u64 = (1 << SLICE_ENTRY_VPARTITION_BITS) - 1;
/// The set of supported FVM partition types.
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum FvmPartitionType {
MinFs,
BlobFs,
}
impl fmt::Display for FvmPartitionType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
FvmPartitionType::MinFs => write!(f, "minfs"),
FvmPartitionType::BlobFs => write!(f, "blobfs"),
}
}
}
/// The FvmPartition represents a contiguous typed partition. The buffer
/// represents the entire set of vslices for the partition merged into a single
/// buffer. This representation is obviously memory intensive but is currently
/// designed for small buffers less than 1GB.
#[derive(Debug)]
pub struct FvmPartition {
pub partition_type: FvmPartitionType,
pub buffer: Vec<u8>,
}
/// Defines the FvmHeader for a particular partition. In this case we are
/// parsing the structure using a cursor so this structure doesn't have to
/// directly match the underlying type.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
struct FvmHeader {
magic: u64,
version: u64,
pslice_count: u64,
slice_size: u64,
fvm_partition_size: u64, // Total size of this partition.
vpartition_table_size: u64,
allocation_table_size: u64,
generation: u64,
hash: Vec<u8>,
}
impl FvmHeader {
/// Parses the entire FVM header from a binary cursor. This function will
/// error if either the cursor ends before it is expected to or the magic
/// number provided in the header is incorrect.
pub fn parse(cursor: &mut Cursor<Vec<u8>>) -> Result<Self> {
let starting_pos: i64 = cursor.position().try_into()?;
let magic: u64 = cursor.read_u64::<LittleEndian>()?;
if magic != FVM_MAGIC {
return Err(Error::new(FvmError::InvalidHeaderMagic));
}
let version: u64 = cursor.read_u64::<LittleEndian>()?;
if version != FVM_VERSION {
return Err(Error::new(FvmError::UnsupportedVersion));
}
let pslice_count: u64 = cursor.read_u64::<LittleEndian>()?;
let slice_size: u64 = cursor.read_u64::<LittleEndian>()?;
let fvm_partition_size: u64 = cursor.read_u64::<LittleEndian>()?;
let vpartition_table_size: u64 = cursor.read_u64::<LittleEndian>()?;
let allocation_table_size: u64 = cursor.read_u64::<LittleEndian>()?;
let generation: u64 = cursor.read_u64::<LittleEndian>()?;
let mut hash = vec![0u8; SHA256_BYTE_LEN];
cursor.read_exact(&mut hash)?;
// The FVM Header sits in a superblock so is padded for FVM_BLOCK_SIZE
// at the end of the structure.
let ending_pos: i64 = cursor.position().try_into()?;
let header_len: i64 = ending_pos - starting_pos;
if header_len < FVM_BLOCK_SIZE.try_into()? {
let padding: i64 = i64::try_from(FVM_BLOCK_SIZE)? - header_len;
cursor.seek(SeekFrom::Current(padding))?;
}
Ok(Self {
magic,
version,
pslice_count,
slice_size,
fvm_partition_size,
vpartition_table_size,
allocation_table_size,
generation,
hash,
})
}
}
/// Represents an entry in the FVM partition table which is a fixed contiguous
/// flat buffer.
#[allow(dead_code)]
pub struct VPartitionEntry {
partition_type: Vec<u8>,
guid: Vec<u8>,
slices: u32,
flags: u32,
unsafe_name: Vec<u8>,
}
impl VPartitionEntry {
pub fn parse(cursor: &mut Cursor<Vec<u8>>) -> Result<Self> {
let mut partition_type = vec![0u8; GPT_GUID_LEN];
cursor.read_exact(&mut partition_type)?;
let mut guid = vec![0u8; GPT_GUID_LEN];
cursor.read_exact(&mut guid)?;
let slices: u32 = cursor.read_u32::<LittleEndian>()?;
let flags: u32 = cursor.read_u32::<LittleEndian>()?;
let mut unsafe_name = vec![0u8; FVM_MAX_VPARTITION_NAME_LEN];
cursor.read_exact(&mut unsafe_name)?;
Ok(Self { partition_type, guid, slices, flags, unsafe_name })
}
}
#[allow(dead_code)]
pub struct SliceEntry {
data: u64,
}
impl SliceEntry {
pub fn parse(cursor: &mut Cursor<Vec<u8>>) -> Result<Self> {
Ok(Self { data: cursor.read_u64::<LittleEndian>()? })
}
pub fn is_allocated(&self) -> bool {
self.vpartition() != 0
}
pub fn vslice(&self) -> u64 {
(self.data & SLICE_ENTRY_VSLICE_MASK) >> SLICE_ENTRY_VPARTITION_BITS
}
pub fn vpartition(&self) -> u64 {
self.data & VPARTITION_ENTRY_MASK
}
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum FvmError {
#[error("Fvm header magic value doesn't match expected value")]
InvalidHeaderMagic,
#[error("Fvm header version is not supported")]
UnsupportedVersion,
}
/// The FvmReader parses the SuperBlock which fits inside a single block.
/// Following the SuperBlock is the Virtual Partition Table followed directly
/// by the Allocation Table. The reader will return a vector of FvmPartitions
/// which contain a filesystem type and the "virtual" representation of the
/// partition (sorted pslices by vslice).
pub struct FvmReader {
cursor: Cursor<Vec<u8>>,
}
impl FvmReader {
/// Constructs a new reader from an existing fvm_buffer.
pub fn new(fvm_buffer: Vec<u8>) -> Self {
Self { cursor: Cursor::new(fvm_buffer) }
}
/// Parses the FVM provided during construction returning the set of
/// partitions as buffers ordered by vslice.
pub fn parse(&mut self) -> Result<Vec<FvmPartition>> {
// Parse the SuperBlock header which always fits inside one block.
let header = FvmHeader::parse(&mut self.cursor)?;
// Read the partition table that directly follows the super block.
let mut partitions = Vec::with_capacity(FVM_MAX_VPARTITIONS);
for _i in 0..FVM_MAX_VPARTITIONS {
partitions.push(VPartitionEntry::parse(&mut self.cursor)?);
}
// The actual number of entries in the table.
let allocation_entries = header.fvm_partition_size / header.slice_size;
// The number of bits total that the entire allocation contains.
let total_allocation_entries = header.allocation_table_size / 8;
let padding_entries = total_allocation_entries - allocation_entries;
// Load in all the slice allocations from the alloctaion table.
let mut allocations = Vec::with_capacity(allocation_entries.try_into()?);
for _i in 0..allocation_entries {
let slice = SliceEntry::parse(&mut self.cursor)?;
allocations.push(slice);
}
// Push the cursor to the end of the block, ignore these allocation
// entries they are just padding.
for _i in 0..padding_entries {
let _ = SliceEntry::parse(&mut self.cursor);
}
// Calculate mapping from partition_id -> (vslice, allocation_index)
let mut partition_alloc_map: HashMap<u64, Vec<(u64, u64)>> = HashMap::new();
let mut allocation_index = 0;
for slice in allocations {
if slice.is_allocated() {
if !partition_alloc_map.contains_key(&slice.vpartition()) {
partition_alloc_map
.insert(slice.vpartition(), vec![(slice.vslice(), allocation_index)]);
} else {
partition_alloc_map
.get_mut(&slice.vpartition())
.unwrap()
.push((slice.vslice(), allocation_index));
}
}
allocation_index += 1;
}
// Sort the allocations by vslice 0 -> max, this uses the property that
// 2-tuple pairs are first sorted by their 1st param then their second.
for (_, v) in partition_alloc_map.iter_mut() {
v.sort();
}
// Convert the internal fragmented pslice allocation format to a
// simple contiguous buffer. This is the "virtual" representation per
// partition as informed by the sorted partition_alloc_map.
let mut fvm_partitions = vec![];
// Two copies of the metadata are always stored at the start of the FVM.
let slice_section_start = 2 * self.cursor.position();
self.cursor.seek(SeekFrom::Start(slice_section_start))?;
for (partition_index, vslice_pslice_mappings) in partition_alloc_map.iter() {
let idx = partition_index.clone() as usize;
let idx = usize::try_from(idx)?;
if let Ok(name) = str::from_utf8(&partitions[idx].unsafe_name) {
let fs_type = if name.starts_with("blobfs") {
info!("FVM: Found BlobFS volume with {} slices", vslice_pslice_mappings.len());
Some(FvmPartitionType::BlobFs)
} else if name.starts_with("minfs") {
info!("FVM: Found Minfs volume with {} slices", vslice_pslice_mappings.len());
Some(FvmPartitionType::MinFs)
} else {
None
};
// Only create FVM file systems from known types.
if let Some(fs_type) = fs_type {
// Note that the mappings are in sorted order already.
let mut partition_buffer = vec![];
for (vslice, pslice) in vslice_pslice_mappings.iter() {
self.cursor.seek(SeekFrom::Start(slice_section_start))?;
let offset = i64::try_from((pslice - 1) * header.slice_size)?;
info!(
"Seeking: {} {} {}",
vslice,
pslice,
slice_section_start + (pslice - 1) * header.slice_size
);
self.cursor.seek(SeekFrom::Current(offset))?;
let mut slice_buffer = vec![1; header.slice_size as usize];
self.cursor.read(&mut slice_buffer)?;
partition_buffer.append(&mut slice_buffer);
}
fvm_partitions
.push(FvmPartition { partition_type: fs_type, buffer: partition_buffer });
}
}
}
Ok(fvm_partitions)
}
}
#[cfg(test)]
mod tests {
use {super::*, std::convert::TryFrom};
#[test]
fn test_fvm_magic_invalid() {
let header_invalid_magic = FvmHeader {
magic: 1,
version: FVM_VERSION,
pslice_count: 1,
slice_size: FVM_BLOCK_SIZE,
fvm_partition_size: FVM_BLOCK_SIZE,
vpartition_table_size: FVM_BLOCK_SIZE,
allocation_table_size: FVM_BLOCK_SIZE,
generation: 0,
hash: vec![0u8; GPT_GUID_LEN],
};
let fvm_bytes = bincode::serialize(&header_invalid_magic).unwrap();
let mut reader = FvmReader::new(fvm_bytes);
let result = reader.parse();
assert_eq!(
result.unwrap_err().downcast::<FvmError>().unwrap(),
FvmError::InvalidHeaderMagic
);
}
#[test]
fn test_fvm_version_invalid() {
let header_invalid_magic = FvmHeader {
magic: FVM_MAGIC,
version: 123,
pslice_count: 1,
slice_size: FVM_BLOCK_SIZE,
fvm_partition_size: FVM_BLOCK_SIZE,
vpartition_table_size: FVM_BLOCK_SIZE,
allocation_table_size: FVM_BLOCK_SIZE,
generation: 0,
hash: vec![0u8; GPT_GUID_LEN],
};
let fvm_bytes = bincode::serialize(&header_invalid_magic).unwrap();
let mut reader = FvmReader::new(fvm_bytes);
let result = reader.parse();
assert_eq!(
result.unwrap_err().downcast::<FvmError>().unwrap(),
FvmError::UnsupportedVersion
);
}
#[test]
fn test_fvm_truncated_header() {
let header_invalid_magic = FvmHeader {
magic: FVM_MAGIC,
version: FVM_VERSION,
pslice_count: 1,
slice_size: FVM_BLOCK_SIZE,
fvm_partition_size: FVM_BLOCK_SIZE,
vpartition_table_size: FVM_BLOCK_SIZE,
allocation_table_size: FVM_BLOCK_SIZE,
generation: 0,
hash: vec![0u8; GPT_GUID_LEN],
};
let mut fvm_bytes = bincode::serialize(&header_invalid_magic).unwrap();
fvm_bytes.pop();
let mut reader = FvmReader::new(fvm_bytes);
let result = reader.parse();
assert_eq!(result.is_err(), true);
}
#[test]
fn test_fvm_empty() {
let header_invalid_magic = FvmHeader {
magic: FVM_MAGIC,
version: FVM_VERSION,
pslice_count: 1,
slice_size: FVM_BLOCK_SIZE,
fvm_partition_size: FVM_BLOCK_SIZE,
vpartition_table_size: FVM_BLOCK_SIZE,
allocation_table_size: FVM_BLOCK_SIZE,
generation: 0,
hash: vec![0u8; GPT_GUID_LEN],
};
let mut fvm_bytes = bincode::serialize(&header_invalid_magic).unwrap();
let mut padding = vec![0u8; usize::try_from(FVM_BLOCK_SIZE * 32).unwrap()];
fvm_bytes.append(&mut padding);
let mut reader = FvmReader::new(fvm_bytes);
let result = reader.parse();
assert_eq!(result.is_ok(), true);
}
}