//! A library that allows managing MBR partition tables.
//!
//! ## Features
//!
//! * Create primary partitions and logical volumes
//! * Delete primary partitions and logical volumes
//! * Automatically generate logical volume's EBR (or can be provided manually)
//! * If the disk geometry is set, the partition CHS addresses will be calculated
//! automatically when writing to disk
//!
//! ## Examples
//!
//! ### Read all the partitions of a disk
//!
//! ```rust
//! let mut f = std::fs::File::open("tests/fixtures/disk1.img")
//! .expect("could not open disk");
//! let mbr = mbrman::MBR::read_from(&mut f, 512)
//! .expect("could not find MBR");
//!
//! println!("Disk signature: {:?}", mbr.header.disk_signature);
//!
//! for (i, p) in mbr.iter() {
//! // NOTE: The first four partitions are always provided by iter()
//! if p.is_used() {
//! println!("Partition #{}: type = {:?}, size = {} bytes, starting lba = {}",
//! i,
//! p.sys,
//! p.sectors * mbr.sector_size,
//! p.starting_lba);
//! }
//! }
//! ```
//!
//! ### Create and delete primary partitions
//!
//! ```rust
//! let mut f = std::fs::File::open("tests/fixtures/disk1.img")
//! .expect("could not open disk");
//! let mut mbr = mbrman::MBR::read_from(&mut f, 512)
//! .expect("could not find MBR");
//!
//! let free_partition_number = mbr.iter().find(|(i, p)| p.is_unused()).map(|(i, _)| i)
//! .expect("no more places available");
//! let sectors = mbr.get_maximum_partition_size()
//! .expect("no more space available");
//! let starting_lba = mbr.find_optimal_place(sectors)
//! .expect("could not find a place to put the partition");
//!
//! mbr[free_partition_number] = mbrman::MBRPartitionEntry {
//! boot: mbrman::BOOT_INACTIVE, // boot flag
//! first_chs: mbrman::CHS::empty(), // first CHS address (only useful for old computers)
//! sys: 0x83, // Linux filesystem
//! last_chs: mbrman::CHS::empty(), // last CHS address (only useful for old computers)
//! starting_lba, // the sector where the partition starts
//! sectors, // the number of sectors in that partition
//! };
//!
//! mbr[free_partition_number] = mbrman::MBRPartitionEntry::empty();
//!
//! // NOTE: no modification is committed to the disk until we call mbr.write_into()
//! ```
//!
//! ### Create a new partition table from an empty disk
//!
//! ```rust
//! let ss = 512; // sector size
//! let data = vec![0; 100 * ss as usize];
//! let mut cur = std::io::Cursor::new(data);
//!
//! let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
//! .expect("could not create partition table");
//!
//! // NOTE: commit the change to the in-memory buffer
//! mbr.write_into(&mut cur);
//! ```
//!
//! ### Add a new logical volume to the disk
//!
//! ```rust
//! let ss = 512; // sector size
//! let data = vec![0; 100 * ss as usize];
//! let mut cur = std::io::Cursor::new(data);
//!
//! let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
//! .expect("could not create partition table");
//!
//! mbr[1] = mbrman::MBRPartitionEntry {
//! boot: mbrman::BOOT_INACTIVE, // boot flag
//! first_chs: mbrman::CHS::empty(), // first CHS address (only useful for old computers)
//! sys: 0x0f, // extended partition with LBA
//! last_chs: mbrman::CHS::empty(), // last CHS address (only useful for old computers)
//! starting_lba: 1, // the sector where the partition starts
//! sectors: mbr.disk_size - 1, // the number of sectors in that partition
//! };
//!
//! // this helper function will do all the hard work for you
//! // here it creates a logical volume with Linux filesystem that occupies the entire disk
//! // NOTE: you will lose 1 sector because it is used by the EBR
//! mbr.push(0x83, 1, mbr.disk_size - 1);
//!
//! // NOTE: commit the change to the in-memory buffer
//! mbr.write_into(&mut cur);
//! ```
//!
//! ### Add a new logical volume manually to the disk
//!
//! This is useful only if you need to specify exactly where goes the EBR and the partition itself.
//!
//! ```rust
//! let ss = 512; // sector size
//! let data = vec![0; 100 * ss as usize];
//! let mut cur = std::io::Cursor::new(data);
//!
//! let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
//! .expect("could not create partition table");
//!
//! mbr[1] = mbrman::MBRPartitionEntry {
//! boot: mbrman::BOOT_INACTIVE, // boot flag
//! first_chs: mbrman::CHS::empty(), // first CHS address (only useful for old computers)
//! sys: 0x0f, // extended partition with LBA
//! last_chs: mbrman::CHS::empty(), // last CHS address (only useful for old computers)
//! starting_lba: 1, // the sector where the partition starts
//! sectors: mbr.disk_size - 1, // the number of sectors in that partition
//! };
//!
//! // NOTE: mbrman won't check the consistency of the partition you have created manually
//! mbr.logical_partitions.push(
//! mbrman::LogicalPartition {
//! // this is the actual partition entry for the logical volume
//! partition: mbrman::MBRPartitionEntry {
//! boot: mbrman::BOOT_INACTIVE,
//! first_chs: mbrman::CHS::empty(),
//! sys: 0x83,
//! last_chs: mbrman::CHS::empty(),
//! starting_lba: 2, // the sector index 1 is used by the EBR
//! sectors: mbr.disk_size - 2,
//! },
//! // this is the absolute LBA address of the EBR
//! absolute_ebr_lba: 1,
//! // the number of sectors in the first EBR is never known
//! ebr_sectors: None,
//! // empty boot sector in the EBR
//! bootstrap_code: [0; 446],
//! // this is the absolute CHS address of the EBR (only used by old computers)
//! ebr_first_chs: mbrman::CHS::empty(), // only for old computers
//! // this is the absolute CHS address of the last EBR (only used by old computers)
//! // NOTE: this is not know the first EBR
//! ebr_last_chs: None,
//! }
//! );
//!
//! // NOTE: commit the change to the in-memory buffer
//! mbr.write_into(&mut cur);
//! ```
#![deny(missing_docs)]
use bincode::{deserialize_from, serialize_into};
use bitvec::prelude::*;
use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeTuple;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_big_array::BigArray;
use std::convert::TryFrom;
use std::io::{Read, Seek, SeekFrom, Write};
use std::iter::{once, repeat};
use std::ops::{Index, IndexMut};
use thiserror::Error;
const DEFAULT_ALIGN: u32 = 2048;
const MAX_ALIGN: u32 = 16384;
const FIRST_USABLE_LBA: u32 = 1;
const BOOT_SIGNATURE: [u8; 2] = [0x55, 0xaa];
/// Boot flag for a bootable partition
pub const BOOT_ACTIVE: u8 = 0x80;
/// Boot flag for a non-bootable partition
pub const BOOT_INACTIVE: u8 = 0x00;
/// The result of reading, writing or managing a MBR.
pub type Result<T> = std::result::Result<T, Error>;
/// An error
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
/// The CHS address requested cannot be represented in CHS
///
/// # Remark
///
/// There is a hard limit around 8GB for CHS addressing.
#[error("exceeded the maximum limit of CHS")]
LBAExceedsMaximumCHS,
/// The CHS address requested exceeds the number of cylinders in the disk
#[error("exceeded the maximum number of cylinders on disk")]
LBAExceedsMaximumCylinders,
/// Deserialization errors.
#[error("deserialization failed")]
Deserialize(#[from] bincode::Error),
/// I/O errors.
#[error("generic I/O error")]
Io(#[from] std::io::Error),
/// Inconsistent extended boot record
#[error("inconsistent extended boot record")]
InconsistentEBR,
/// No extended partition
#[error("no extended partition")]
NoExtendedPartition,
/// The EBR starts before the extended partition
#[error("EBR starts before the extended partition")]
EBRStartsBeforeExtendedPartition,
/// The EBR starts too close to the extended partition
#[error("EBR starts too close to the end of the extended partition")]
EBRStartsTooCloseToTheEndOfExtendedPartition,
/// The EBR ends after the extended partition
#[error("EBR ends after the extended partition")]
EBREndsAfterExtendedPartition,
/// Not enough sectors to create a logical partition
#[error("not enough sectors to create a logical partition")]
NotEnoughSectorsToCreateLogicalPartition,
/// An operation that required to find a partition, was unable to find that partition.
#[error("partition not found")]
PartitionNotFound,
/// An error that occurs when there is not enough space left on the table to continue.
#[error("no space left")]
NoSpaceLeft,
/// MBR doesn't have the expected signature value
#[error("invalid MBR signature")]
InvalidSignature,
/// Partition has invalid boot flag
#[error("partition has invalid boot flag")]
InvalidBootFlag,
}
/// A type representing a MBR partition table including its partition, the sector size of the disk
/// and the alignment of the partitions to the sectors.
///
/// # Examples:
/// Read an existing MBR on a reader and list its partitions:
/// ```
/// let mut f = std::fs::File::open("tests/fixtures/disk1.img")
/// .expect("could not open disk");
/// let mbr = mbrman::MBR::read_from(&mut f, 512)
/// .expect("could not find MBR");
///
/// println!("Disk signature: {:?}", mbr.header.disk_signature);
///
/// for (i, p) in mbr.iter() {
/// if p.is_used() {
/// println!("Partition #{}: type = {:?}, size = {} bytes, starting lba = {}",
/// i,
/// p.sys,
/// p.sectors * mbr.sector_size,
/// p.starting_lba);
/// }
/// }
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MBR {
/// Sector size of the disk.
///
/// You should not change this, otherwise the starting locations of your partitions will be
/// different in bytes.
pub sector_size: u32,
/// MBR partition header (disk GUID, first/last usable LBA, etc...)
pub header: MBRHeader,
/// A vector with all the logical partitions. You can push new ones (even empty ones)
pub logical_partitions: Vec<LogicalPartition>,
/// Partitions alignment (in sectors)
///
/// This field change the behavior of the methods `get_maximum_partition_size()`,
/// `find_free_sectors()`, `find_first_place()`, `find_last_place()` and `find_optimal_place()`
/// so they return only values aligned to the alignment.
///
/// # Panics
/// The value must be greater than 0, otherwise you will encounter divisions by zero.
pub align: u32,
/// Disk geometry: number of cylinders
pub cylinders: u16,
/// Disk geometry: number of heads
pub heads: u8,
/// Disk geometry: number of sectors
pub sectors: u8,
/// Disk size in sectors
pub disk_size: u32,
}
impl MBR {
/// Get an iterator over the partition entries and their index. The
/// index always starts at 1.
pub fn iter(&self) -> impl Iterator<Item = (usize, &MBRPartitionEntry)> {
self.header.iter().chain(
self.logical_partitions
.iter()
.map(|x| &x.partition)
.enumerate()
.map(|(i, x)| (i + 5, x)),
)
}
/// Get a mutable iterator over the partition entries and their
/// index. The index always starts at 1.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (usize, &mut MBRPartitionEntry)> {
let mut partitions: Vec<_> = vec![
&mut self.header.partition_1,
&mut self.header.partition_2,
&mut self.header.partition_3,
&mut self.header.partition_4,
];
partitions.extend(self.logical_partitions.iter_mut().map(|x| &mut x.partition));
partitions.into_iter().enumerate().map(|(i, x)| (i + 1, x))
}
/// Get `Some(&MBRPartitionEntry)` if it exists, None otherwise.
///
/// # Remarks
///
/// - The partitions start at index 1
/// - The first 4 partitions always exist
pub fn get(&self, i: usize) -> Option<&MBRPartitionEntry> {
match i {
0 => None,
1 => Some(&self.header.partition_1),
2 => Some(&self.header.partition_2),
3 => Some(&self.header.partition_3),
4 => Some(&self.header.partition_4),
i => self.logical_partitions.get(i - 5).map(|x| &x.partition),
}
}
/// Get `Some(&mut MBRPartitionEntry)` if it exists, None otherwise.
///
/// # Remarks
///
/// - The partitions start at index 1
/// - The first 4 partitions always exist
pub fn get_mut(&mut self, i: usize) -> Option<&mut MBRPartitionEntry> {
match i {
0 => None,
1 => Some(&mut self.header.partition_1),
2 => Some(&mut self.header.partition_2),
3 => Some(&mut self.header.partition_3),
4 => Some(&mut self.header.partition_4),
i => self
.logical_partitions
.get_mut(i - 5)
.map(|x| &mut x.partition),
}
}
/// The total number of partitions on the disk: primary partitions and logical partitions.
///
/// # Remark
///
/// The primary partitions are always counted even if they are empty.
pub fn len(&self) -> usize {
4 + self.logical_partitions.len()
}
/// Always false: primary partitions are always counted even if they are empty.
pub fn is_empty(&self) -> bool {
false
}
/// Make a new MBR
///
/// # Examples
/// Basic usage:
/// ```
/// let mut f = std::fs::File::open("tests/fixtures/disk1.img")
/// .expect("could not open disk");
/// let mbr = mbrman::MBR::new_from(&mut f, 512, [0x01, 0x02, 0x03, 0x04])
/// .expect("could not make a partition table");
/// ```
pub fn new_from<S>(seeker: &mut S, sector_size: u32, disk_signature: [u8; 4]) -> Result<MBR>
where
S: Seek,
{
let disk_size = u32::try_from(seeker.seek(SeekFrom::End(0))? / u64::from(sector_size))
.unwrap_or(u32::max_value());
let header = MBRHeader::new(disk_signature);
Ok(MBR {
sector_size,
header,
logical_partitions: Vec::new(),
align: DEFAULT_ALIGN,
cylinders: 0,
heads: 0,
sectors: 0,
disk_size,
})
}
/// Read the MBR on a reader.
///
/// # Examples
/// Basic usage:
/// ```
/// let mut f = std::fs::File::open("tests/fixtures/disk1.img")
/// .expect("could not open disk");
/// let mbr = mbrman::MBR::read_from(&mut f, 512)
/// .expect("could not read the partition table");
/// ```
pub fn read_from<R: ?Sized>(mut reader: &mut R, sector_size: u32) -> Result<MBR>
where
R: Read + Seek,
{
let disk_size = u32::try_from(reader.seek(SeekFrom::End(0))? / u64::from(sector_size))
.unwrap_or(u32::max_value());
let header = MBRHeader::read_from(&mut reader)?;
let mut logical_partitions = Vec::new();
if let Some(extended) = header.get_extended_partition() {
// NOTE: The number of sectors is an index field; thus, the zero
// value is invalid, reserved and must not be used in normal
// partition entries. The entry is used by operating systems
// in certain circumstances; in such cases the CHS addresses
// are ignored.
let mut relative_ebr_lba = 0;
let mut ebr_sectors = None;
let mut ebr_first_chs = extended.first_chs;
let mut ebr_last_chs = None;
loop {
reader.seek(SeekFrom::Start(u64::from(
(extended.starting_lba + relative_ebr_lba) * sector_size,
)))?;
let (partition, next, bootstrap_code) = match EBRHeader::read_from(&mut reader) {
Ok(ebr) => ebr.unwrap(),
Err(err) => {
if relative_ebr_lba == 0 {
// NOTE: if the extended partition is empty, it is not required that an
// EBR exists
break;
} else {
return Err(err);
}
}
};
let absolute_ebr_lba = extended.starting_lba + relative_ebr_lba;
logical_partitions.push(LogicalPartition {
partition: MBRPartitionEntry {
starting_lba: partition.starting_lba + absolute_ebr_lba,
..partition
},
absolute_ebr_lba,
ebr_sectors,
ebr_first_chs,
ebr_last_chs,
bootstrap_code,
});
if next.starting_lba > 0 && relative_ebr_lba >= next.starting_lba {
return Err(Error::InconsistentEBR);
}
relative_ebr_lba = next.starting_lba;
ebr_sectors = Some(next.sectors);
ebr_first_chs = next.first_chs;
ebr_last_chs = Some(next.last_chs);
if relative_ebr_lba == 0 {
break;
}
}
}
let align = MBR::find_alignment(&header, &logical_partitions);
Ok(MBR {
sector_size,
header,
logical_partitions,
align,
cylinders: 0,
heads: 0,
sectors: 0,
disk_size,
})
}
fn find_alignment(header: &MBRHeader, logical_partitions: &[LogicalPartition]) -> u32 {
let lbas = header
.iter()
.map(|(_, x)| x)
.chain(logical_partitions.iter().map(|x| &x.partition))
.filter(|x| x.is_used())
.map(|x| x.starting_lba)
.collect::<Vec<_>>();
if lbas.is_empty() {
return DEFAULT_ALIGN;
}
if lbas.len() == 1 && lbas[0] == FIRST_USABLE_LBA {
return FIRST_USABLE_LBA;
}
(1..=MAX_ALIGN.min(*lbas.iter().max().unwrap_or(&1)))
.filter(|div| lbas.iter().all(|x| x % div == 0))
.max()
.unwrap()
}
/// Return `true` if the MBR has a valid geometry. The geometry can be set by setting
/// the fiels `cylinders`, `heads` and `sectors`.
///
/// Remarks
///
/// The cylinders, heads and sectors must have a value greater than zero.
///
/// The cylinders cannot exceed 1023.
///
/// The sectors cannot exceed 63.
pub fn check_geometry(&self) -> bool {
self.cylinders > 0
&& self.cylinders <= 1023
&& self.heads > 0
&& self.sectors > 0
&& self.sectors <= 63
}
/// Write the MBR to a writer. This function will seek automatically in the writer.
/// This function will update the CHS address of the partitions automatically if a valid
/// geometry has been set. See `check_geometry`.
///
/// # Examples
/// Basic usage:
/// ```
/// let ss = 512;
/// let data = vec![0; 100 * ss as usize];
/// let mut cur = std::io::Cursor::new(data);
/// let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
/// .expect("could not make a partition table");
///
/// // actually write:
/// mbr.write_into(&mut cur)
/// .expect("could not write MBR to disk")
/// ```
pub fn write_into<W: ?Sized>(&mut self, mut writer: &mut W) -> Result<()>
where
W: Write + Seek,
{
self.header.write_into(&mut writer)?;
if let Some(extended) = self.header.get_extended_partition() {
if let Some(first) = self.logical_partitions.get_mut(0) {
first.absolute_ebr_lba = extended.starting_lba;
first.ebr_sectors = None;
first.ebr_last_chs = None;
}
if self.check_geometry() {
for l in self.logical_partitions.iter_mut() {
l.update_chs(self.cylinders, self.heads, self.sectors)?;
}
}
// newtype for bootstrap code so we can serialize it via BigArray
// pending https://github.com/serde-rs/serde/issues/1937
#[derive(Serialize)]
struct BootstrapCode446(#[serde(with = "BigArray")] [u8; 446]);
let next_logical_partitions = self
.logical_partitions
.iter()
.skip(1)
.map(Some)
.chain(once(None));
for (l, next) in self.logical_partitions.iter().zip(next_logical_partitions) {
let partition = MBRPartitionEntry {
starting_lba: l.partition.starting_lba.saturating_sub(l.absolute_ebr_lba),
..l.partition
};
partition.check()?;
writer.seek(SeekFrom::Start(u64::from(
l.absolute_ebr_lba * self.sector_size,
)))?;
serialize_into(&mut writer, &BootstrapCode446(l.bootstrap_code))?;
serialize_into(&mut writer, &partition)?;
if let Some(next) = next {
serialize_into(
&mut writer,
&MBRPartitionEntry {
boot: BOOT_INACTIVE,
first_chs: next.ebr_first_chs,
sys: extended.sys,
last_chs: next.ebr_last_chs.unwrap(),
starting_lba: next
.absolute_ebr_lba
.saturating_sub(extended.starting_lba),
sectors: next.ebr_sectors.unwrap(),
},
)?;
} else {
serialize_into(&mut writer, &MBRPartitionEntry::empty())?;
}
writer.write_all(&[0; 16 * 2])?;
serialize_into(&mut writer, &BOOT_SIGNATURE)?;
}
}
Ok(())
}
/// Get a cylinder size in sectors. This function is useful if you want to
/// align your partitions to the cylinder.
pub fn get_cylinder_size(&self) -> u32 {
u32::from(self.heads) * u32::from(self.sectors)
}
/// Finds the primary partition (ignoring extended partitions) or logical
/// partition where the given sector resides.
pub fn find_at_sector(&self, sector: u32) -> Option<usize> {
let between = |sector, start, len| sector >= start && sector < start + len;
let primary = self
.header
.iter()
.find(|(_, x)| x.is_used() && between(sector, x.starting_lba, x.sectors));
match primary {
Some((_, x)) if x.is_extended() => self
.logical_partitions
.iter()
.enumerate()
.find(|(_, x)| {
x.partition.is_used()
&& between(sector, x.partition.starting_lba, x.partition.sectors)
})
.map(|(i, _)| 5 + i),
Some((i, _)) => Some(i),
None => None,
}
}
/// Remove a partition entry that resides at a given sector. If the partition is the extended
/// partition, it will delete also all the logical partitions.
///
/// # Errors
/// It is an error to provide a sector which does not belong to a partition.
pub fn remove_at_sector(&mut self, sector: u32) -> Result<()> {
let i = self
.find_at_sector(sector)
.ok_or(Error::PartitionNotFound)?;
if i >= 5 {
self.remove(i);
} else {
if self[i].is_extended() {
self.logical_partitions.clear();
}
self[i] = MBRPartitionEntry::empty();
}
Ok(())
}
/// Find free spots in the partition table.
/// This function will return a vector of tuple with on the left: the starting LBA of the free
/// spot; and on the right: the size (in sectors) of the free spot.
/// This function will automatically align with the alignment defined in the `MBR`.
///
/// # Examples
/// Basic usage:
/// ```
/// let ss = 512;
/// let data = vec![0; 100 * ss as usize];
/// let mut cur = std::io::Cursor::new(data);
/// let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
/// .expect("could not create partition table");
///
/// mbr[1] = mbrman::MBRPartitionEntry {
/// boot: mbrman::BOOT_INACTIVE,
/// first_chs: mbrman::CHS::empty(),
/// sys: 0x83,
/// last_chs: mbrman::CHS::empty(),
/// starting_lba: 6,
/// sectors: mbr.disk_size - 11,
/// };
///
/// // NOTE: align to the sectors, so we can use every last one of them
/// // NOTE: this is only for the demonstration purpose, this is not recommended
/// mbr.align = 1;
///
/// assert_eq!(
/// mbr.find_free_sectors(),
/// vec![(1, 5), (mbr.disk_size - 5, 5)]
/// );
/// ```
pub fn find_free_sectors(&self) -> Vec<(u32, u32)> {
assert!(self.align > 0, "align must be greater than 0");
let collect_free_sectors = |positions: Vec<u32>| {
positions
.chunks(2)
.map(|x| (x[0] + 1, x[1] - x[0] - 1))
.filter(|(_, l)| *l > 0)
.map(|(i, l)| (i, l, ((i - 1) / self.align + 1) * self.align - i))
.map(|(i, l, s)| (i + s, l.saturating_sub(s)))
.filter(|(_, l)| *l > 0)
.collect::<Vec<_>>()
};
let mut positions = vec![0];
for (_, partition) in self.header.iter().filter(|(_, x)| x.is_used()) {
positions.push(partition.starting_lba);
positions.push(partition.starting_lba + partition.sectors - 1);
}
positions.push(self.disk_size);
positions.sort_unstable();
let mut res = collect_free_sectors(positions);
if let Some(extended) = self.header.get_extended_partition() {
let mut positions = vec![extended.starting_lba];
for l in self
.logical_partitions
.iter()
.filter(|x| x.partition.is_used())
{
let starting_lba = l.absolute_ebr_lba + l.partition.starting_lba;
positions.push(starting_lba);
positions.push(starting_lba + l.partition.sectors - 1);
}
positions.push(extended.starting_lba + extended.sectors);
positions.sort_unstable();
res.extend(collect_free_sectors(positions));
}
res
}
/// Find the first place (most on the left) where you could start a new partition of the size
/// given in parameter.
/// This function will automatically align with the alignment defined in the `MBR`.
///
/// # Examples:
/// Basic usage:
/// ```
/// let ss = 512;
/// let data = vec![0; 100 * ss as usize];
/// let mut cur = std::io::Cursor::new(data);
/// let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
/// .expect("could not create partition table");
///
/// mbr[1] = mbrman::MBRPartitionEntry {
/// boot: mbrman::BOOT_INACTIVE,
/// first_chs: mbrman::CHS::empty(),
/// sys: 0x83,
/// last_chs: mbrman::CHS::empty(),
/// starting_lba: 6,
/// sectors: mbr.disk_size - 6,
/// };
///
/// // NOTE: align to the sectors, so we can use every last one of them
/// // NOTE: this is only for the demonstration purpose, this is not recommended
/// mbr.align = 1;
///
/// assert_eq!(mbr.find_first_place(5), Some(1));
/// ```
pub fn find_first_place(&self, size: u32) -> Option<u32> {
self.find_free_sectors()
.iter()
.find(|(_, l)| *l >= size)
.map(|(i, _)| *i)
}
/// Find the last place (most on the right) where you could start a new partition of the size
/// given in parameter.
/// This function will automatically align with the alignment defined in the `MBR`.
///
/// # Examples:
/// Basic usage:
/// ```
/// let ss = 512;
/// let data = vec![0; 100 * ss as usize];
/// let mut cur = std::io::Cursor::new(data);
/// let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
/// .expect("could not create partition table");
///
/// mbr[1] = mbrman::MBRPartitionEntry {
/// boot: mbrman::BOOT_INACTIVE,
/// first_chs: mbrman::CHS::empty(),
/// sys: 0x83,
/// last_chs: mbrman::CHS::empty(),
/// starting_lba: 6,
/// sectors: 5,
/// };
///
/// // NOTE: align to the sectors, so we can use every last one of them
/// // NOTE: this is only for the demonstration purpose, this is not recommended
/// mbr.align = 1;
///
/// assert_eq!(mbr.find_last_place(5), Some(mbr.disk_size - 5));
/// ```
pub fn find_last_place(&self, size: u32) -> Option<u32> {
self.find_free_sectors()
.iter()
.filter(|(_, l)| *l >= size)
.last()
.map(|(i, l)| (i + l - size) / self.align * self.align)
}
/// Find the most optimal place (in the smallest free space) where you could start a new
/// partition of the size given in parameter.
/// This function will automatically align with the alignment defined in the `MBR`.
///
/// # Examples:
/// Basic usage:
/// ```
/// let ss = 512;
/// let data = vec![0; 100 * ss as usize];
/// let mut cur = std::io::Cursor::new(data);
/// let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
/// .expect("could not create partition table");
///
/// mbr[1] = mbrman::MBRPartitionEntry {
/// boot: mbrman::BOOT_INACTIVE,
/// first_chs: mbrman::CHS::empty(),
/// sys: 0x83,
/// last_chs: mbrman::CHS::empty(),
/// starting_lba: 11,
/// sectors: mbr.disk_size - 11 - 5,
/// };
///
/// // NOTE: align to the sectors, so we can use every last one of them
/// // NOTE: this is only for the demonstration purpose, this is not recommended
/// mbr.align = 1;
///
/// // NOTE: the space as the end is more optimal because it will allow you to still be able to
/// // insert a bigger partition later
/// assert_eq!(mbr.find_optimal_place(5), Some(mbr.disk_size - 5));
/// ```
pub fn find_optimal_place(&self, size: u32) -> Option<u32> {
let mut slots = self
.find_free_sectors()
.into_iter()
.filter(|(_, l)| *l >= size)
.collect::<Vec<_>>();
slots.sort_by(|(_, l1), (_, l2)| l1.cmp(l2));
slots.first().map(|&(i, _)| i)
}
/// Get the maximum size (in sectors) of a partition you could create in the MBR.
/// This function will automatically align with the alignment defined in the `MBR`.
///
/// # Examples:
/// Basic usage:
/// ```
/// let ss = 512;
/// let data = vec![0; 100 * ss as usize];
/// let mut cur = std::io::Cursor::new(data);
/// let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
/// .expect("could not create partition table");
///
/// // NOTE: align to the sectors, so we can use every last one of them
/// // NOTE: this is only for the demonstration purpose, this is not recommended
/// mbr.align = 1;
///
/// assert_eq!(
/// mbr.get_maximum_partition_size().unwrap_or(0),
/// mbr.disk_size - 1
/// );
/// ```
pub fn get_maximum_partition_size(&self) -> Result<u32> {
self.find_free_sectors()
.into_iter()
.map(|(_, l)| l / self.align * self.align)
.max()
.ok_or(Error::NoSpaceLeft)
}
/// Push a new logical partition to the end of the extended partition list. This function will
/// take care of creating the EBR for you. The EBR will be located at `starting_lba` (provided
/// in input) and the logical partition itself will be located a block further to stay
/// aligned. The size of the logical partition will be one block smaller than the `sectors`
/// provided in input.
pub fn push(
&mut self,
sys: u8,
mut starting_lba: u32,
mut sectors: u32,
) -> Result<&mut LogicalPartition> {
let extended = self
.header
.get_extended_partition()
.ok_or(Error::NoExtendedPartition)?;
starting_lba = ((starting_lba - 1) / self.align + 1) * self.align;
sectors = ((sectors - 1) / self.align + 1) * self.align;
if sectors < 2 * self.align {
return Err(Error::NotEnoughSectorsToCreateLogicalPartition);
}
let mut l = LogicalPartition {
partition: MBRPartitionEntry {
boot: BOOT_INACTIVE,
first_chs: CHS::empty(),
sys,
last_chs: CHS::empty(),
starting_lba: starting_lba + self.align,
sectors: sectors - self.align,
},
absolute_ebr_lba: starting_lba,
ebr_sectors: if self.logical_partitions.is_empty() {
None
} else {
Some(sectors)
},
ebr_first_chs: CHS::empty(),
ebr_last_chs: if self.logical_partitions.is_empty() {
None
} else {
Some(CHS::empty())
},
bootstrap_code: [0; 446],
};
if l.absolute_ebr_lba < extended.starting_lba {
return Err(Error::EBRStartsBeforeExtendedPartition);
}
if l.absolute_ebr_lba > extended.starting_lba + extended.sectors - 2 * self.align {
return Err(Error::EBRStartsTooCloseToTheEndOfExtendedPartition);
}
if let Some(ebr_sectors) = l.ebr_sectors {
let ending_ebr_lba = l.absolute_ebr_lba + ebr_sectors - 1;
if ending_ebr_lba > extended.starting_lba + extended.sectors - 1 {
return Err(Error::EBREndsAfterExtendedPartition);
}
}
if self.check_geometry() {
l.update_chs(self.cylinders, self.heads, self.sectors)?;
}
self.logical_partitions.push(l);
Ok(self.logical_partitions.last_mut().unwrap())
}
/// Remove a logical partition. This will remove a logical partition in the array.
///
/// # Remark
///
/// This operation will decrease by one the index of every logical partition after the one that
/// has been removed.
///
/// # Panics
///
/// Panics if `index` is out of bounds.
pub fn remove(&mut self, index: usize) -> LogicalPartition {
assert!(index >= 5, "logical partitions start at 5");
self.logical_partitions.remove(index - 5)
}
}
impl Index<usize> for MBR {
type Output = MBRPartitionEntry;
fn index(&self, i: usize) -> &Self::Output {
assert!(i != 0, "invalid partition index: 0");
self.get(i).expect("invalid partition")
}
}
impl IndexMut<usize> for MBR {
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
assert!(i != 0, "invalid partition index: 0");
self.get_mut(i).expect("invalid partition")
}
}
/// An MBR partition table header
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct MBRHeader {
/// Bootstrap code area
#[serde(with = "BigArray")]
pub bootstrap_code: [u8; 440],
/// 32-bit disk signature
pub disk_signature: [u8; 4],
/// `[0x5a, 0x5a]` if protected, `[0x00, 0x00]` if not
pub copy_protected: [u8; 2],
/// Partition 1
pub partition_1: MBRPartitionEntry,
/// Partition 2
pub partition_2: MBRPartitionEntry,
/// Partition 3
pub partition_3: MBRPartitionEntry,
/// Partition 4
pub partition_4: MBRPartitionEntry,
/// Boot signature
pub boot_signature: [u8; 2],
}
impl MBRHeader {
/// Check if the partition table is copy-protected
pub fn is_copy_protected(&self) -> Option<bool> {
match self.copy_protected {
[0x00, 0x00] => Some(false),
[0x5a, 0x5a] => Some(true),
_ => None,
}
}
/// Get `Some(&MBRPartitionEntry)` if it exists, None otherwise.
///
/// # Remarks
///
/// - The partitions start at index 1
/// - The first 4 partitions always exist
/// - This function does not return logical partitions
pub fn get(&self, i: usize) -> Option<&MBRPartitionEntry> {
match i {
1 => Some(&self.partition_1),
2 => Some(&self.partition_2),
3 => Some(&self.partition_3),
4 => Some(&self.partition_4),
_ => None,
}
}
/// Get `Some(&mut MBRPartitionEntry)` if it exists, None otherwise.
///
/// # Remarks
///
/// - The partitions start at index 1
/// - The first 4 partitions always exist
/// - This function does not return logical partitions
pub fn get_mut(&mut self, i: usize) -> Option<&mut MBRPartitionEntry> {
match i {
1 => Some(&mut self.partition_1),
2 => Some(&mut self.partition_2),
3 => Some(&mut self.partition_3),
4 => Some(&mut self.partition_4),
_ => None,
}
}
/// Get an iterator over the primary partition entries and their index. The
/// index always starts at 1.
pub fn iter(&self) -> impl Iterator<Item = (usize, &MBRPartitionEntry)> {
vec![
&self.partition_1,
&self.partition_2,
&self.partition_3,
&self.partition_4,
]
.into_iter()
.enumerate()
.map(|(i, x)| (i + 1, x))
}
/// Get a mutable iterator over the primary partition entries and their
/// index. The index always starts at 1.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (usize, &mut MBRPartitionEntry)> {
vec![
&mut self.partition_1,
&mut self.partition_2,
&mut self.partition_3,
&mut self.partition_4,
]
.into_iter()
.enumerate()
.map(|(i, x)| (i + 1, x))
}
fn get_extended_partition(&self) -> Option<&MBRPartitionEntry> {
self.iter().find(|(_, x)| x.is_extended()).map(|(_, x)| x)
}
/// Attempt to read a MBR header from a reader. This operation will seek at the
/// correct location before trying to write to disk.
pub fn read_from<R: ?Sized>(mut reader: &mut R) -> Result<MBRHeader>
where
R: Read + Seek,
{
reader.seek(SeekFrom::Start(0))?;
let header: Self = deserialize_from(&mut reader)?;
header.check()?;
Ok(header)
}
/// Make a new MBR header
pub fn new(disk_signature: [u8; 4]) -> MBRHeader {
MBRHeader {
bootstrap_code: [0; 440],
disk_signature,
copy_protected: [0x00, 0x00],
partition_1: MBRPartitionEntry::empty(),
partition_2: MBRPartitionEntry::empty(),
partition_3: MBRPartitionEntry::empty(),
partition_4: MBRPartitionEntry::empty(),
boot_signature: BOOT_SIGNATURE,
}
}
/// Write the MBR header into a writer. This operation will seek at the
/// correct location before trying to write to disk.
pub fn write_into<W: ?Sized>(&self, mut writer: &mut W) -> Result<()>
where
W: Write + Seek,
{
self.check()?;
writer.seek(SeekFrom::Start(0))?;
serialize_into(&mut writer, &self)?;
Ok(())
}
/// Validate invariants
fn check(&self) -> Result<()> {
if self.boot_signature != BOOT_SIGNATURE {
return Err(Error::InvalidSignature);
}
self.iter().try_for_each(|(_, partition)| partition.check())
}
}
impl Index<usize> for MBRHeader {
type Output = MBRPartitionEntry;
fn index(&self, i: usize) -> &Self::Output {
assert!(i != 0, "invalid partition index: 0");
self.get(i).expect("invalid partition")
}
}
impl IndexMut<usize> for MBRHeader {
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
assert!(i != 0, "invalid partition index: 0");
self.get_mut(i).expect("invalid partition")
}
}
#[derive(Debug, Clone, Deserialize)]
struct EBRHeader {
#[serde(with = "BigArray")]
bootstrap_code: [u8; 446],
partition_1: MBRPartitionEntry,
partition_2: MBRPartitionEntry,
_unused_partition_3: [u8; 16],
_unused_partition_4: [u8; 16],
boot_signature: [u8; 2],
}
impl EBRHeader {
fn read_from<R: ?Sized>(mut reader: &mut R) -> Result<EBRHeader>
where
R: Read,
{
let header: Self = deserialize_from(&mut reader)?;
header.check()?;
Ok(header)
}
/// Validate invariants
fn check(&self) -> Result<()> {
if self.boot_signature != BOOT_SIGNATURE {
return Err(Error::InvalidSignature);
}
self.partition_1.check()?;
self.partition_2.check()
}
fn unwrap(self) -> (MBRPartitionEntry, MBRPartitionEntry, [u8; 446]) {
(self.partition_1, self.partition_2, self.bootstrap_code)
}
}
/// An MBR partition entry
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct MBRPartitionEntry {
/// Boot flag
pub boot: u8,
/// CHS address of the first sector in the partition
pub first_chs: CHS,
/// Partition type (file system ID)
pub sys: u8,
/// CHS address of the last sector in the partition
pub last_chs: CHS,
/// Starting LBA of the partition
pub starting_lba: u32,
/// Number of sectors allocated to the partition
pub sectors: u32,
}
impl MBRPartitionEntry {
/// Creates an empty partition entry
///
/// # Examples
/// Basic usage:
/// ```
/// let ss = 512;
/// let data = vec![0; 100 * ss as usize];
/// let mut cur = std::io::Cursor::new(data);
/// let mut mbr = mbrman::MBR::new_from(&mut cur, ss as u32, [0xff; 4])
/// .expect("could not create partition table");
///
/// mbr[1] = mbrman::MBRPartitionEntry::empty();
///
/// // NOTE: an empty partition entry is considered as not allocated
/// assert!(mbr[1].is_unused());
/// ```
pub fn empty() -> MBRPartitionEntry {
MBRPartitionEntry {
boot: BOOT_INACTIVE,
first_chs: CHS::empty(),
sys: 0,
last_chs: CHS::empty(),
starting_lba: 0,
sectors: 0,
}
}
/// Returns `true` if the partition entry is used (type (sys) != 0)
pub fn is_used(&self) -> bool {
self.sys > 0
}
/// Returns `true` if the partition entry is not used (type (sys) == 0)
pub fn is_unused(&self) -> bool {
!self.is_used()
}
/// Returns `true` if the partition is an extended type partition
pub fn is_extended(&self) -> bool {
self.sys == 0x05
|| self.sys == 0x0f
|| self.sys == 0x85
|| self.sys == 0xc5
|| self.sys == 0xd5
}
/// Returns `true` if the partition is marked active (bootable)
pub fn is_active(&self) -> bool {
self.boot == BOOT_ACTIVE
}
/// Validate invariants
fn check(&self) -> Result<()> {
if self.boot != BOOT_ACTIVE && self.boot != BOOT_INACTIVE {
return Err(Error::InvalidBootFlag);
}
Ok(())
}
}
/// An abstraction struct for a logical partition
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LogicalPartition {
/// MBR partition entry of the logical partition
pub partition: MBRPartitionEntry,
/// Absolute LBA of the EBR partition table
pub absolute_ebr_lba: u32,
/// Number of sectors in the EBR
///
/// # Remark
///
/// This information is known for all the EBR except the first logical partition
pub ebr_sectors: Option<u32>,
/// CHS address of the EBR header
///
/// # Remark
///
/// This information is copied from the extended partition for the first logical partition
pub ebr_first_chs: CHS,
/// CHS address of the last sector of the extended partition in the EBR header
///
/// # Remark
///
/// This information is known for all the EBR except the first logical partition
pub ebr_last_chs: Option<CHS>,
/// Bootstrap code area
pub bootstrap_code: [u8; 446],
}
impl LogicalPartition {
/// Update the fields `partition.first_chs`, `partition.last_chs`.
/// `ebr_first_chs` and `ebr_last_chs` using the disk geometry provided in parameter.
pub fn update_chs(&mut self, cylinders: u16, heads: u8, sectors: u8) -> Result<()> {
self.partition.first_chs =
CHS::from_lba_exact(self.partition.starting_lba, cylinders, heads, sectors)?;
self.partition.last_chs = CHS::from_lba_exact(
self.partition.starting_lba + self.partition.sectors - 1,
cylinders,
heads,
sectors,
)?;
self.ebr_first_chs = CHS::from_lba_exact(self.absolute_ebr_lba, cylinders, heads, sectors)?;
self.ebr_last_chs = if let Some(ebr_sectors) = self.ebr_sectors {
Some(CHS::from_lba_exact(
self.absolute_ebr_lba + ebr_sectors - 1,
cylinders,
heads,
sectors,
)?)
} else {
None
};
Ok(())
}
}
/// A CHS address (cylinder/head/sector)
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CHS {
/// Cylinder
pub cylinder: u16,
/// Head
pub head: u8,
/// Sector
pub sector: u8,
}
impl CHS {
/// Creates a new CHS address based on input parameters.
///
/// # Remark
///
/// The values entered in input are not checked.
pub fn new(cylinder: u16, head: u8, sector: u8) -> CHS {
CHS {
cylinder,
head,
sector,
}
}
/// Creates an empty CHS addressing (0/0/0).
///
/// # Remark
///
/// This is what you need on recent hardware because CHS is never used.
pub fn empty() -> CHS {
CHS {
cylinder: 0,
head: 0,
sector: 0,
}
}
/// Creates a CHS address calculated from the number of cylinders, heads
/// and sectors per track of the hard disk.
///
/// # Remarks
///
/// * You probably don't need to do this at all! This is only useful if you
/// intend to use partitions that use the CHS addressing. Check the column
/// "Access" of [this table on Wikipedia](https://en.wikipedia.org/wiki/Partition_type).
/// * On old systems, partitions must be aligned on cylinders.
pub fn from_lba_exact(lba: u32, cylinders: u16, heads: u8, sectors: u8) -> Result<CHS> {
// NOTE: code inspired from libfdisk (long2chs)
let cylinders = u32::from(cylinders);
let heads = u32::from(heads);
let sectors = u32::from(sectors);
let cylinder_size = heads * sectors;
let cylinder = lba / cylinder_size;
let rem = lba % cylinder_size;
let head = rem / sectors;
let sector = rem % sectors + 1;
if cylinder > 1023 {
return Err(Error::LBAExceedsMaximumCHS);
}
if cylinder > cylinders {
return Err(Error::LBAExceedsMaximumCylinders);
}
Ok(CHS {
cylinder: u16::try_from(cylinder).unwrap(),
head: u8::try_from(head).unwrap(),
sector: u8::try_from(sector).unwrap(),
})
}
/// Creates a CHS address, aligned to the nearest cylinder. The cylinder
/// chosen will always be the exact cylinder (if the LBA is exactly at the
/// beginning of a cylinder); or the next cylinder. But it will never
/// choose the previous cylinder.
pub fn from_lba_aligned(lba: u32, cylinders: u16, heads: u8, sectors: u8) -> Result<CHS> {
let cylinders = u32::from(cylinders);
let heads = u32::from(heads);
let sectors = u32::from(sectors);
let cylinder_size = heads * sectors;
let cylinder = ((lba - 1) / cylinder_size) + 1;
if cylinder > 1023 {
return Err(Error::LBAExceedsMaximumCHS);
}
if cylinder > cylinders {
return Err(Error::LBAExceedsMaximumCylinders);
}
// NOTE: In CHS addressing the sector numbers always start at 1, there is no sector 0
// https://en.wikipedia.org/wiki/Cylinder-head-sector
Ok(CHS {
cylinder: u16::try_from(cylinder).unwrap(),
head: 0,
sector: 1,
})
}
/// Convert a CHS address to LBA
pub fn to_lba(self, heads: u8, sectors: u8) -> u32 {
let heads = u32::from(heads);
let sectors = u32::from(sectors);
let c = u32::from(self.cylinder);
let h = u32::from(self.head);
let s = u32::from(self.sector);
// NOTE: In CHS addressing the sector numbers always start at 1, there is no sector 0
// https://en.wikipedia.org/wiki/Cylinder-head-sector
c * (heads * sectors) + h * sectors + s - 1
}
/// Check if the CHS address is empty
///
/// # Remark
///
/// This function does not check if the CHS address is withing range of
/// possible values for a provided hard disk.
pub fn is_empty(self) -> bool {
self.cylinder == 0 && self.head == 0 && self.sector == 0
}
/// Check if the CHS address is valid and within range of the possible
/// values for the hard disk geometry provided in argument.
pub fn is_valid(self, cylinders: u16, heads: u8, sectors: u8) -> bool {
// NOTE: In CHS addressing the sector numbers always start at 1, there is no sector 0
// https://en.wikipedia.org/wiki/Cylinder-head-sector
self.sector > 0 && self.sector <= sectors && self.head < heads && self.cylinder < cylinders
}
}
struct CHSVisitor;
impl<'de> Visitor<'de> for CHSVisitor {
type Value = CHS;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("CHS addressing")
}
fn visit_seq<A>(self, mut seq: A) -> std::result::Result<CHS, A::Error>
where
A: SeqAccess<'de>,
{
let head = BitVec::<u8, Msb0>::from_vec(vec![seq.next_element::<u8>()?.unwrap()]);
let mut bv = BitVec::<u8, Msb0>::from_vec(vec![seq.next_element::<u8>()?.unwrap()]);
let mut cylinder = BitVec::<u16, Msb0>::with_capacity(10);
cylinder.extend(repeat(false).take(6));
cylinder.extend(bv.drain(..2));
cylinder.extend(BitVec::<u8, Msb0>::from_vec(vec![seq
.next_element::<u8>()?
.unwrap()]));
let mut sector = BitVec::<u8, Msb0>::with_capacity(8);
sector.push(false);
sector.push(false);
sector.extend(bv.drain(..));
Ok(CHS {
cylinder: cylinder.as_raw_slice()[0],
head: head.as_raw_slice()[0],
sector: sector.as_raw_slice()[0],
})
}
}
impl<'de> Deserialize<'de> for CHS {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_tuple(3, CHSVisitor)
}
}
impl Serialize for CHS {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut bv = BitVec::<u8, Msb0>::from_vec(vec![self.head]);
let mut sector = BitVec::<u8, Msb0>::from_vec(vec![self.sector]);
let mut cylinder = BitVec::<u16, Msb0>::from_vec(vec![self.cylinder]);
bv.extend(cylinder.drain(..8).skip(6));
bv.extend(sector.drain(2..));
bv.extend(cylinder.drain(..));
let mut seq = serializer.serialize_tuple(3)?;
for x in bv.as_raw_slice() {
seq.serialize_element(&x)?;
}
seq.end()
}
}
#[cfg(test)]
#[allow(clippy::cognitive_complexity)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Cursor;
const DISK1: &str = "tests/fixtures/disk1.img";
const DISK2: &str = "tests/fixtures/disk2.img";
#[test]
fn deserialize_maximum_chs_value() {
let chs: CHS = bincode::deserialize(&[0xff, 0xff, 0xff]).unwrap();
assert_eq!(
chs,
CHS {
cylinder: 1023,
head: 255,
sector: 63,
}
);
}
#[test]
fn serialize_maximum_chs_value() {
let chs = CHS {
cylinder: 1023,
head: 255,
sector: 63,
};
let out = bincode::serialize(&chs).unwrap();
assert_eq!(out, &[0xff, 0xff, 0xff]);
}
#[test]
fn serialize_and_deserialize_some_chs_value() {
let chs: CHS = bincode::deserialize(&[0xaa, 0xaa, 0xaa]).unwrap();
assert_eq!(
chs,
CHS {
cylinder: 682,
head: 170,
sector: 42,
}
);
let out = bincode::serialize(&chs).unwrap();
assert_eq!(out, &[0xaa, 0xaa, 0xaa]);
}
#[test]
fn align_chs_to_cylinder() {
fn lba2c(lba: u32) -> u16 {
let chs = CHS::from_lba_aligned(lba, 100, 2, 2).unwrap();
assert_eq!(chs.head, 0);
assert_eq!(chs.sector, 1);
chs.cylinder
}
assert_eq!(lba2c(12), 3);
assert_eq!(lba2c(10), 3);
}
#[test]
fn convert_chs_to_lba_and_back() {
// NOTE: 2484/16/63 is taken from a real life example of hard disk of 1280MB
// LBA address 666666 is around 341MB for a sector size of 512 bytes
let chs = CHS::from_lba_exact(666_666, 2484, 16, 63).unwrap();
assert_eq!(chs.to_lba(16, 63), 666_666);
let chs = CHS::from_lba_aligned(666_666, 2484, 16, 63).unwrap();
assert_eq!(chs.to_lba(16, 63), 667_296);
let chs = CHS::from_lba_exact(667_296, 2484, 16, 63).unwrap();
assert_eq!(chs.head, 0);
assert_eq!(chs.sector, 1);
}
#[test]
#[allow(clippy::bool_assert_comparison)]
fn read_disk1() {
let mut mbr = MBR::read_from(&mut File::open(DISK1).unwrap(), 512).unwrap();
assert!(mbr.header.partition_1.is_used());
assert!(mbr.header.partition_2.is_used());
assert!(mbr.header.partition_3.is_unused());
assert!(mbr.header.partition_4.is_unused());
assert!(mbr.header.partition_1.is_active());
assert!(!mbr.header.partition_2.is_active());
assert!(!mbr.header.partition_3.is_active());
assert!(!mbr.header.partition_4.is_active());
assert_eq!(mbr.len(), 4);
assert_eq!(mbr.header.iter().count(), 4);
assert_eq!(mbr.header.iter_mut().count(), 4);
assert_eq!(mbr.iter_mut().count(), 4);
assert_eq!(mbr.header.partition_1.boot, BOOT_ACTIVE);
assert_eq!(mbr.header.partition_1.sys, 0x06);
assert_eq!(mbr.header.partition_1.starting_lba, 1);
assert_eq!(mbr.header.partition_1.sectors, 1);
assert_eq!(mbr.header.partition_2.boot, BOOT_INACTIVE);
assert_eq!(mbr.header.partition_2.sys, 0x0b);
assert_eq!(mbr.header.partition_2.starting_lba, 3);
assert_eq!(mbr.header.partition_2.sectors, 1);
}
#[test]
fn read_disk2() {
let mut mbr = MBR::read_from(&mut File::open(DISK2).unwrap(), 512).unwrap();
assert!(mbr.header.partition_1.is_used());
assert!(mbr.header.partition_2.is_used());
assert!(mbr.header.partition_3.is_unused());
assert!(mbr.header.partition_4.is_unused());
assert!(!mbr.header.partition_1.is_active());
assert!(!mbr.header.partition_2.is_active());
assert!(!mbr.header.partition_3.is_active());
assert!(!mbr.header.partition_4.is_active());
assert_eq!(mbr.header.partition_2.sys, 0x05);
assert_eq!(mbr.header.partition_2.starting_lba, 5);
assert_eq!(mbr.header.partition_2.first_chs, CHS::new(0, 1, 3));
assert_eq!(mbr.header.partition_2.last_chs, CHS::new(3, 0, 2));
assert_eq!(mbr.header.partition_2.sectors, 15);
assert_eq!(mbr.len(), 9);
assert_eq!(mbr.iter().count(), 9);
assert_eq!(mbr.iter_mut().count(), 9);
assert_eq!(mbr.iter().filter(|(_, x)| x.is_used()).count(), 7);
assert!(mbr
.iter()
.filter(|(_, x)| x.is_used() && !x.is_extended())
.all(|(_, x)| x.sys == 0x83));
assert!(mbr.get(9).is_some());
assert!(mbr.get(10).is_none());
assert_eq!(mbr.logical_partitions[0].absolute_ebr_lba, 5);
assert_eq!(mbr.logical_partitions[0].ebr_sectors, None);
assert_eq!(mbr.logical_partitions[1].absolute_ebr_lba, 7);
assert_eq!(mbr.logical_partitions[1].ebr_sectors, Some(3));
assert_eq!(mbr.logical_partitions[2].absolute_ebr_lba, 10);
assert_eq!(mbr.logical_partitions[2].ebr_sectors, Some(4));
// NOTE: this is actually for testing the CHS conversion to and from LBA
assert_eq!(
mbr.logical_partitions[2].partition.first_chs,
CHS::new(1, 1, 3)
);
assert_eq!(
mbr.logical_partitions[2].partition.first_chs.to_lba(2, 3),
mbr.logical_partitions[2].partition.starting_lba
);
assert_eq!(
CHS::from_lba_exact(
mbr.logical_partitions[2].partition.starting_lba,
u16::max_value(),
2,
3
)
.unwrap(),
mbr.logical_partitions[2].partition.first_chs
);
assert_eq!(mbr.logical_partitions[3].absolute_ebr_lba, 14);
assert_eq!(mbr.logical_partitions[3].ebr_sectors, Some(2));
assert_eq!(mbr.logical_partitions[4].absolute_ebr_lba, 16);
assert_eq!(mbr.logical_partitions[4].ebr_sectors, Some(2));
assert!(mbr.logical_partitions.get(5).is_none());
}
#[test]
fn read_empty_extended_partition() {
let ss = 512_u32;
let data = vec![0; 10 * ss as usize];
let mut cur = Cursor::new(data);
let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
mbr.header.partition_1.sys = 0x0f;
mbr.header.partition_1.starting_lba = 1;
mbr.header.partition_1.sectors = 10;
mbr.write_into(&mut cur).unwrap();
let mbr = MBR::read_from(&mut cur, ss).unwrap();
assert_eq!(mbr.header.partition_1.sys, 0x0f);
assert_eq!(mbr.header.partition_1.starting_lba, 1);
assert_eq!(mbr.header.partition_1.sectors, 10);
assert!(mbr.logical_partitions.is_empty());
}
#[test]
#[allow(clippy::bool_assert_comparison)]
fn new_mbr_then_write_then_read_twice() {
let ss = 512_u32;
let data = vec![0; 12 * ss as usize];
let mut cur = Cursor::new(data);
let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
mbr.header.partition_1.sys = 0x83;
mbr.header.partition_1.starting_lba = 1;
mbr.header.partition_1.sectors = 4;
mbr.header.partition_3.sys = 0x0f;
mbr.header.partition_3.starting_lba = 5;
mbr.header.partition_3.sectors = 6;
let mut empty = MBRPartitionEntry::empty();
empty.starting_lba = 6;
empty.sectors = 1;
mbr.logical_partitions.push(LogicalPartition {
partition: empty,
absolute_ebr_lba: 5,
ebr_sectors: None,
ebr_first_chs: CHS::empty(),
ebr_last_chs: None,
bootstrap_code: [0; 446],
});
mbr.logical_partitions.push(LogicalPartition {
partition: MBRPartitionEntry {
boot: BOOT_ACTIVE,
first_chs: CHS::empty(),
sys: 0x83,
last_chs: CHS::empty(),
starting_lba: 9,
sectors: 1,
},
absolute_ebr_lba: 7,
ebr_sectors: Some(3),
ebr_first_chs: CHS::empty(),
ebr_last_chs: Some(CHS::empty()),
bootstrap_code: [0; 446],
});
// NOTE: don't put this in a loop otherwise it's hard to guess if the error come from
// reading, writing or reading after writing again
mbr.write_into(&mut cur).unwrap();
let mut mbr = MBR::read_from(&mut cur, ss).unwrap();
assert_eq!(mbr.header.partition_1.boot, BOOT_INACTIVE);
assert_eq!(mbr.header.partition_1.sys, 0x83);
assert_eq!(mbr.header.partition_1.starting_lba, 1);
assert_eq!(mbr.header.partition_1.sectors, 4);
assert_eq!(mbr.logical_partitions.len(), 2);
assert_eq!(mbr.logical_partitions[0].absolute_ebr_lba, 5);
assert_eq!(mbr.logical_partitions[0].partition.boot, BOOT_INACTIVE);
assert_eq!(mbr.logical_partitions[0].partition.starting_lba, 6);
assert_eq!(mbr.logical_partitions[0].partition.sectors, 1);
assert_eq!(mbr.logical_partitions[0].partition.sys, 0);
assert_eq!(mbr.logical_partitions[0].ebr_sectors, None);
assert_eq!(mbr.logical_partitions[1].absolute_ebr_lba, 7);
assert_eq!(mbr.logical_partitions[1].partition.boot, BOOT_ACTIVE);
assert_eq!(mbr.logical_partitions[1].partition.starting_lba, 9);
assert_eq!(mbr.logical_partitions[1].partition.sectors, 1);
assert_eq!(mbr.logical_partitions[1].partition.sys, 0x83);
assert_eq!(mbr.logical_partitions[1].ebr_sectors, Some(3));
mbr.write_into(&mut cur).unwrap();
let mbr = MBR::read_from(&mut cur, ss).unwrap();
assert_eq!(mbr.header.partition_1.boot, BOOT_INACTIVE);
assert_eq!(mbr.header.partition_1.sys, 0x83);
assert_eq!(mbr.header.partition_1.starting_lba, 1);
assert_eq!(mbr.header.partition_1.sectors, 4);
assert_eq!(mbr.logical_partitions.len(), 2);
assert_eq!(mbr.logical_partitions[0].absolute_ebr_lba, 5);
assert_eq!(mbr.logical_partitions[0].partition.boot, BOOT_INACTIVE);
assert_eq!(mbr.logical_partitions[0].partition.starting_lba, 6);
assert_eq!(mbr.logical_partitions[0].partition.sectors, 1);
assert_eq!(mbr.logical_partitions[0].partition.sys, 0);
assert_eq!(mbr.logical_partitions[0].ebr_sectors, None);
assert_eq!(mbr.logical_partitions[1].absolute_ebr_lba, 7);
assert_eq!(mbr.logical_partitions[1].partition.boot, BOOT_ACTIVE);
assert_eq!(mbr.logical_partitions[1].partition.starting_lba, 9);
assert_eq!(mbr.logical_partitions[1].partition.sectors, 1);
assert_eq!(mbr.logical_partitions[1].partition.sys, 0x83);
assert_eq!(mbr.logical_partitions[1].ebr_sectors, Some(3));
}
#[test]
fn find_at_sector() {
let mbr = MBR::read_from(&mut File::open(DISK2).unwrap(), 512).unwrap();
assert_eq!(mbr.find_at_sector(2), Some(1));
assert_eq!(mbr.find_at_sector(4), None);
assert_eq!(mbr.find_at_sector(8), Some(6));
assert_eq!(mbr.find_at_sector(7), None);
}
#[test]
fn find_free_sectors() {
let ss = 512_u32;
let data = vec![0; 10 * ss as usize];
let mut cur = Cursor::new(data);
let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
mbr.align = 1;
mbr.header.partition_1.sys = 0x83;
mbr.header.partition_1.starting_lba = 1;
mbr.header.partition_1.sectors = 1;
mbr.header.partition_3.sys = 0x0f;
mbr.header.partition_3.starting_lba = 5;
mbr.header.partition_3.sectors = 5;
mbr.logical_partitions.push(LogicalPartition {
bootstrap_code: [0; 446],
partition: MBRPartitionEntry {
boot: BOOT_INACTIVE,
first_chs: CHS::empty(),
sys: 0x83,
last_chs: CHS::empty(),
starting_lba: 2,
sectors: 1,
},
absolute_ebr_lba: 5,
ebr_sectors: None,
ebr_first_chs: CHS::empty(),
ebr_last_chs: None,
});
assert_eq!(mbr.find_free_sectors(), vec![(2, 3), (6, 1), (8, 2)]);
}
#[test]
fn push_logical_partition_aligned_to_cylinder() {
let ss = 512_u32;
let data = vec![0; 54 * ss as usize];
let mut cur = Cursor::new(data);
let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
mbr.cylinders = 6;
mbr.heads = 3;
mbr.sectors = 3;
let align = 9;
mbr.align = mbr.get_cylinder_size();
assert_eq!(mbr.align, align);
mbr.header.partition_1.sys = 0x05;
mbr.header.partition_1.first_chs = CHS::new(1, 0, 1);
mbr.header.partition_1.last_chs = CHS::new(5, 0, 1);
mbr.header.partition_1.starting_lba = align;
mbr.header.partition_1.sectors = mbr.disk_size;
let p = mbr.push(0x00, 2, 2 * align).unwrap();
assert_eq!(p.absolute_ebr_lba, align);
assert_eq!(p.partition.starting_lba, 2 * align);
assert_eq!(p.ebr_sectors, None);
assert_eq!(p.partition.sectors, align);
assert_eq!(p.ebr_first_chs, CHS::new(1, 0, 1));
assert_eq!(p.ebr_last_chs, None);
assert_eq!(p.partition.first_chs, CHS::new(2, 0, 1));
assert_eq!(p.partition.last_chs, CHS::new(2, 2, 3));
let p = mbr
.push(
0x83,
CHS::new(3, 0, 1).to_lba(mbr.heads, mbr.sectors),
align * 3,
)
.unwrap();
assert_eq!(p.absolute_ebr_lba, align * 3);
assert_eq!(p.partition.starting_lba, 4 * align);
assert_eq!(p.ebr_sectors, Some(align * 3));
assert_eq!(p.partition.sectors, align * 2);
assert_eq!(p.ebr_first_chs, CHS::new(3, 0, 1));
assert_eq!(p.ebr_last_chs, Some(CHS::new(5, 2, 3)));
assert_eq!(p.partition.first_chs, CHS::new(4, 0, 1));
assert_eq!(p.partition.last_chs, CHS::new(5, 2, 3));
mbr.write_into(&mut cur).unwrap();
let mut same_mbr = MBR::read_from(&mut cur, ss).unwrap();
same_mbr.cylinders = mbr.cylinders;
same_mbr.heads = mbr.heads;
same_mbr.sectors = mbr.sectors;
assert_eq!(mbr, same_mbr);
}
#[test]
fn push_logical_partition_check_within_range() {
let ss = 512_u32;
let data = vec![0; 10 * ss as usize];
let mut cur = Cursor::new(data);
let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
mbr.align = 1;
mbr.header.partition_1.sys = 0x0f;
mbr.header.partition_1.starting_lba = 4;
mbr.header.partition_1.sectors = 2;
assert!(mbr.push(0x83, 3, 2).is_err());
assert!(mbr.push(0x83, 6, 2).is_err());
mbr.push(0x83, 4, 2).unwrap();
assert!(mbr.push(0x83, 4, 3).is_err());
}
#[test]
fn remove_logical_partition() {
let ss = 512_u32;
let data = vec![0; 10 * ss as usize];
let mut cur = Cursor::new(data);
let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
mbr.align = 1;
mbr.header.partition_1.sys = 0x0f;
mbr.header.partition_1.starting_lba = 1;
mbr.header.partition_1.sectors = mbr.disk_size - 1;
mbr.push(0x00, 1, 2).unwrap();
mbr.push(0x83, 4, 3).unwrap();
mbr.logical_partitions.remove(0);
mbr.write_into(&mut cur).unwrap();
let same_mbr = MBR::read_from(&mut cur, ss).unwrap();
assert_eq!(mbr, same_mbr);
assert_eq!(mbr.logical_partitions[0].partition.starting_lba, 5);
assert_eq!(mbr.logical_partitions[0].absolute_ebr_lba, 1);
assert_eq!(mbr.logical_partitions[0].ebr_sectors, None);
}
fn read_corrupt_mbr(path: &str, bad_offset: usize, bad_data: u8) -> Error {
let mut data = Vec::new();
File::open(path).unwrap().read_to_end(&mut data).unwrap();
data[bad_offset] = bad_data;
let mut cur = Cursor::new(&data);
MBR::read_from(&mut cur, 512).unwrap_err()
}
#[test]
fn read_invalid_boot_signature() {
// MBR
assert!(matches!(
read_corrupt_mbr(DISK2, 0xffe, 0xaa),
Error::InvalidSignature
));
// EBR
assert!(matches!(
read_corrupt_mbr(DISK2, 0x21fe, 0xaa),
Error::InvalidSignature
));
}
#[test]
fn write_invalid_boot_signature() {
let ss = 512_u32;
let data = vec![0; 10 * ss as usize];
let mut cur = Cursor::new(data);
// invalid in MBRHeader struct
let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
mbr.header.partition_1.sys = 0x0a;
mbr.header.partition_1.starting_lba = 1;
mbr.header.partition_1.sectors = 10;
mbr.header.boot_signature = [0, 0];
assert!(matches!(
mbr.write_into(&mut cur).unwrap_err(),
Error::InvalidSignature
));
}
#[test]
fn read_invalid_boot_flag() {
// primary partition
assert!(matches!(
read_corrupt_mbr(DISK2, 0x1be, BOOT_ACTIVE | 0x01),
Error::InvalidBootFlag
));
// EBR first partition
assert!(matches!(
read_corrupt_mbr(DISK2, 0xfbe, BOOT_ACTIVE | 0x01),
Error::InvalidBootFlag
));
// EBR second partition
assert!(matches!(
read_corrupt_mbr(DISK2, 0xfce, BOOT_ACTIVE | 0x01),
Error::InvalidBootFlag
));
}
#[test]
fn write_invalid_boot_flag() {
let ss = 512_u32;
let data = vec![0; 10 * ss as usize];
let mut cur = Cursor::new(data);
// primary partition
let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
mbr.header.partition_2.boot = BOOT_ACTIVE | 0x01;
mbr.header.partition_2.sys = 0x0a;
mbr.header.partition_2.starting_lba = 1;
mbr.header.partition_2.sectors = 10;
assert!(matches!(
mbr.write_into(&mut cur).unwrap_err(),
Error::InvalidBootFlag
));
// logical partition
let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
mbr.align = 1;
mbr.header.partition_1.sys = 0x0f;
mbr.header.partition_1.starting_lba = 1;
mbr.header.partition_1.sectors = 10;
mbr.push(0x0f, 1, 9).unwrap().partition.boot = BOOT_ACTIVE | 0x01;
assert!(matches!(
mbr.write_into(&mut cur).unwrap_err(),
Error::InvalidBootFlag
));
}
}
#[cfg(doctest)]
mod test_readme {
// for Rust < 1.54
// https://blog.guillaume-gomez.fr/articles/2021-08-03+Improvements+for+%23%5Bdoc%5D+attributes+in+Rust
macro_rules! check_doc {
($x:expr) => {
#[doc = $x]
extern "C" {}
};
}
check_doc!(include_str!("../README.md"));
}