blob: fed7b18d2700c68f024f02eb651fdc7f4ffd7c2f [file] [log] [blame]
//! Partition-related types and helper functions.
//!
//! This module provides access to low-level primitives
//! to work with GPT partitions.
use bitflags::*;
use crc::crc32;
use log::*;
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fmt;
use std::fs::{File, OpenOptions};
use std::io::{Cursor, Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
use std::path::Path;
use std::str::FromStr;
use crate::disk;
use crate::header::{parse_uuid, Header};
use crate::partition_types::Type;
use crate::DiskDevice;
bitflags! {
/// Partition entry attributes, defined for UEFI.
pub struct PartitionAttributes: u64 {
/// Required platform partition.
const PLATFORM = 1;
/// No Block-IO protocol.
const EFI = (1 << 1);
/// Legacy-BIOS bootable partition.
const BOOTABLE = (1 << 2);
}
}
/// A partition entry in a GPT partition table.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Partition {
/// GUID of the partition type.
pub part_type_guid: Type,
/// UUID of the partition.
pub part_guid: uuid::Uuid,
/// First LBA of the partition.
pub first_lba: u64,
/// Last LBA of the partition.
pub last_lba: u64,
/// Partition flags.
pub flags: u64,
/// Partition name.
pub name: String,
}
impl Partition {
/// Create a partition entry of type "unused", whose bytes are all 0s.
pub fn zero() -> Self {
Self {
part_type_guid: crate::partition_types::UNUSED,
part_guid: uuid::Uuid::nil(),
first_lba: 0,
last_lba: 0,
flags: 0,
name: "".to_string(),
}
}
/// Serialize this partition entry to its bytes representation.
fn as_bytes(&self, entry_size: u32) -> Result<Vec<u8>> {
let mut buf: Vec<u8> = Vec::with_capacity(entry_size as usize);
// Type GUID.
let tyguid = uuid::Uuid::from_str(self.part_type_guid.guid).map_err(|e| {
Error::new(ErrorKind::Other, format!("Invalid guid: {}", e.to_string()))
})?;
let tyguid = tyguid.as_fields();
buf.write_all(&tyguid.0.to_le_bytes())?;
buf.write_all(&tyguid.1.to_le_bytes())?;
buf.write_all(&tyguid.2.to_le_bytes())?;
buf.write_all(tyguid.3)?;
// Partition GUID.
let pguid = self.part_guid.as_fields();
buf.write_all(&pguid.0.to_le_bytes())?;
buf.write_all(&pguid.1.to_le_bytes())?;
buf.write_all(&pguid.2.to_le_bytes())?;
buf.write_all(pguid.3)?;
// LBAs and flags.
buf.write_all(&self.first_lba.to_le_bytes())?;
buf.write_all(&self.last_lba.to_le_bytes())?;
buf.write_all(&self.flags.to_le_bytes())?;
// Partition name as UTF16-LE.
for utf16_char in self.name.encode_utf16().take(36) {
buf.write_all(&utf16_char.to_le_bytes())?; // TODO: Check this
}
// Resize buffer to exact entry size.
buf.resize(usize::try_from(entry_size).unwrap(), 0x00);
Ok(buf)
}
/// Write the partition entry to the partitions area in the given file.
/// NOTE: does not update partitions array crc32 in the headers!
pub fn write(
&self,
p: &Path,
partition_index: u64,
start_lba: u64,
lb_size: disk::LogicalBlockSize,
) -> Result<()> {
let mut file = OpenOptions::new().write(true).read(true).open(p)?;
self.write_to_device(&mut file, partition_index, start_lba, lb_size, 128)
}
/// Write the partition entry to the partitions area in the given device.
/// NOTE: does not update partitions array crc32 in the headers!
pub fn write_to_device<D: DiskDevice>(
&self,
device: &mut D,
partition_index: u64,
start_lba: u64,
lb_size: disk::LogicalBlockSize,
bytes_per_partition: u32,
) -> Result<()> {
debug!("writing partition to: {:?}", device);
let pstart = start_lba
.checked_mul(lb_size.into())
.ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow - start offset"))?;
// The offset is bytes_per_partition * partition_index
let offset = partition_index
.checked_mul(u64::from(bytes_per_partition))
.ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow"))?;
trace!("seeking to partition start: {}", pstart + offset);
device.seek(SeekFrom::Start(pstart + offset))?;
trace!("writing {:?}", &self.as_bytes(bytes_per_partition));
device.write_all(&self.as_bytes(bytes_per_partition)?)?;
Ok(())
}
/// Write empty partition entries starting at the given index in the partition array
/// for the given number of entries...
pub fn write_zero_entries_to_device<D: DiskDevice>(
device: &mut D,
starting_partition_index: u64,
number_entries: u64,
start_lba: u64,
lb_size: disk::LogicalBlockSize,
bytes_per_partition: u32,
) -> Result<()> {
trace!("writing {} unused partition entries starting at index {}, start_lba={}",
number_entries, starting_partition_index, start_lba);
let pstart = start_lba
.checked_mul(lb_size.into())
.ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow - start offset"))?;
let offset = starting_partition_index
.checked_mul(u64::from(bytes_per_partition))
.ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow"))?;
trace!("seeking to starting partition start: {}", pstart + offset);
device.seek(SeekFrom::Start(pstart + offset))?;
let bytes_to_zero = u64::from(bytes_per_partition)
.checked_mul(number_entries)
.and_then(|x| usize::try_from(x).ok())
.ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow - bytes to zero"))?;
device.write_all(&vec![0u8; bytes_to_zero])?;
Ok(())
}
/// Return the length (in bytes) of this partition.
pub fn bytes_len(&self, lb_size: disk::LogicalBlockSize) -> Result<u64> {
let len = self
.last_lba
.checked_sub(self.first_lba)
.ok_or_else(|| Error::new(ErrorKind::Other, "partition length underflow - sectors"))?
.checked_mul(lb_size.into())
.ok_or_else(|| Error::new(ErrorKind::Other, "partition length overflow - bytes"))?;
Ok(len)
}
/// Return the starting offset (in bytes) of this partition.
pub fn bytes_start(&self, lb_size: disk::LogicalBlockSize) -> Result<u64> {
let len = self
.first_lba
.checked_mul(lb_size.into())
.ok_or_else(|| Error::new(ErrorKind::Other, "partition start overflow - bytes"))?;
Ok(len)
}
/// Check whether this partition is in use.
pub fn is_used(&self) -> bool {
self.part_type_guid.guid != crate::partition_types::UNUSED.guid
}
/// Return the number of sectors in the partition.
pub fn size(&self) -> Result<u64> {
match self.last_lba.checked_sub(self.first_lba) {
Some(size) => Ok(size),
None => Err(Error::new(
ErrorKind::Other,
"Invalid partition. last_lba < first_lba",
)),
}
}
}
impl fmt::Display for Partition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Partition:\t\t{}\nPartition GUID:\t\t{}\nPartition Type:\t\t{}\n\
Span:\t\t\t{} - {}\nFlags:\t\t\t{}",
self.name,
self.part_guid,
self.part_type_guid.guid,
self.first_lba,
self.last_lba,
self.flags,
)
}
}
fn read_part_name(rdr: &mut Cursor<&[u8]>) -> Result<String> {
trace!("Reading partition name");
let mut namebytes: Vec<u16> = Vec::new();
for _ in 0..36 {
let b = u16::from_le_bytes(read_exact_buff!(bbuff, rdr, 2));
if b != 0 {
namebytes.push(b);
} else {
break;
}
}
Ok(String::from_utf16_lossy(&namebytes))
}
/// Read a GPT partition table.
///
/// ## Example
///
/// ```rust,no_run
/// use gpt::{header, disk, partition};
/// use std::path::Path;
///
/// let lb_size = disk::DEFAULT_SECTOR_SIZE;
/// let diskpath = Path::new("/dev/sdz");
/// let hdr = header::read_header(diskpath, lb_size).unwrap();
/// let partitions = partition::read_partitions(diskpath, &hdr, lb_size).unwrap();
/// println!("{:#?}", partitions);
/// ```
pub fn read_partitions(
path: &Path,
header: &Header,
lb_size: disk::LogicalBlockSize,
) -> Result<BTreeMap<u32, Partition>> {
debug!("reading partitions from file: {}", path.display());
let mut file = File::open(path)?;
file_read_partitions(&mut file, header, lb_size)
}
/// Read a GPT partition table from an open `Read` + `Seek` object.
pub fn file_read_partitions<D: Read + Seek>(
file: &mut D,
header: &Header,
lb_size: disk::LogicalBlockSize,
) -> Result<BTreeMap<u32, Partition>> {
let pstart = header
.part_start
.checked_mul(lb_size.into())
.ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow - start offset"))?;
trace!("seeking to partitions start: {:#x}", pstart);
let _ = file.seek(SeekFrom::Start(pstart))?;
let mut parts: BTreeMap<u32, Partition> = BTreeMap::new();
trace!("scanning {} partitions", header.num_parts);
let mut count = 0;
for i in 0..header.num_parts {
let mut bytes: [u8; 56] = [0; 56];
let mut nameraw: [u8; 72] = [0; 72];
file.read_exact(&mut bytes)?;
file.read_exact(&mut nameraw)?;
let test: [u8; 56] = [0; 56];
let test2: [u8; 72] = [0; 72];
// Note: unused partition entries are zeroed, so skip them
if bytes.eq(&test[0..]) && nameraw.eq(&test2[0..]) {
count += 1;
} else {
let mut reader = Cursor::new(&bytes[..]);
let type_guid = parse_uuid(&mut reader)?;
let part_guid = parse_uuid(&mut reader)?;
let partname = read_part_name(&mut Cursor::new(&nameraw[..]))?;
let p = Partition {
part_type_guid: Type::from_uuid(&type_guid).map_err(|e| {
Error::new(ErrorKind::Other, format!("Unknown Partition Type: {}", e))
})?,
part_guid,
first_lba: u64::from_le_bytes(read_exact_buff!(flba, reader, 8)),
last_lba: u64::from_le_bytes(read_exact_buff!(llba, reader, 8)),
flags: u64::from_le_bytes(read_exact_buff!(flagbuff, reader, 8)),
name: partname.to_string(),
};
parts.insert(i + 1, p);
}
}
debug!("Num Zeroed partitions {:?}\n\n", count);
debug!("checking partition table CRC");
let _ = file.seek(SeekFrom::Start(pstart))?;
let pt_len = u64::from(header.num_parts)
.checked_mul(header.part_size.into())
.ok_or_else(|| Error::new(ErrorKind::Other, "partitions - size"))?;
let mut table = vec![0; pt_len as usize];
file.read_exact(&mut table)?;
let comp_crc = crc32::checksum_ieee(&table);
if comp_crc != header.crc32_parts {
return Err(Error::new(ErrorKind::Other, "partition table CRC mismatch"));
}
Ok(parts)
}
#[cfg(test)]
mod tests {
use crate::disk;
use crate::partition;
#[test]
fn test_zero_part() {
let p0 = partition::Partition::zero();
let b128 = p0.as_bytes(128).unwrap();
assert_eq!(b128.len(), 128);
assert_eq!(b128, vec![0u8; 128]);
let b256 = p0.as_bytes(256).unwrap();
assert_eq!(b256.len(), 256);
assert_eq!(b256, vec![0u8; 256]);
}
#[test]
fn test_part_bytes_len() {
{
// Zero.
let p0 = partition::Partition::zero();
let b512len = p0.bytes_len(disk::LogicalBlockSize::Lb512).unwrap();
let b4096len = p0.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap();
assert_eq!(b512len, 0);
assert_eq!(b4096len, 0);
}
{
// Negative length.
let mut p1 = partition::Partition::zero();
p1.first_lba = p1.last_lba + 1;
p1.bytes_len(disk::LogicalBlockSize::Lb512).unwrap_err();
p1.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap_err();
}
{
// Overflowing u64 length.
let mut p2 = partition::Partition::zero();
p2.last_lba = <u64>::max_value();
p2.bytes_len(disk::LogicalBlockSize::Lb512).unwrap_err();
p2.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap_err();
}
{
// Positive value.
let mut p3 = partition::Partition::zero();
p3.first_lba = 2;
p3.last_lba = 4;
let b512len = p3.bytes_len(disk::LogicalBlockSize::Lb512).unwrap();
let b4096len = p3.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap();
assert_eq!(b512len, 2 * 512);
assert_eq!(b4096len, 2 * 4096);
}
}
#[test]
fn test_part_bytes_start() {
{
// Zero.
let p0 = partition::Partition::zero();
let b512len = p0.bytes_start(disk::LogicalBlockSize::Lb512).unwrap();
let b4096len = p0.bytes_start(disk::LogicalBlockSize::Lb4096).unwrap();
assert_eq!(b512len, 0);
assert_eq!(b4096len, 0);
}
{
// Overflowing u64 start.
let mut p1 = partition::Partition::zero();
p1.first_lba = <u64>::max_value();
p1.bytes_len(disk::LogicalBlockSize::Lb512).unwrap_err();
p1.bytes_len(disk::LogicalBlockSize::Lb4096).unwrap_err();
}
{
// Positive value.
let mut p2 = partition::Partition::zero();
p2.first_lba = 2;
let b512start = p2.bytes_start(disk::LogicalBlockSize::Lb512).unwrap();
let b4096start = p2.bytes_start(disk::LogicalBlockSize::Lb4096).unwrap();
assert_eq!(b512start, 2 * 512);
assert_eq!(b4096start, 2 * 4096);
}
}
}