Skip to content

Commit 7958259

Browse files
committed
blockdev.rs: add support for saving mbr partitions
Fixes #957. If a MBR partition is marked to be saved, it will be translated using best effort to GPT. To elaborate on "best effort"; there is metadata which is present in GPT but not MBR, so it might not translate 1:1.
1 parent 4cba801 commit 7958259

File tree

3 files changed

+119
-30
lines changed

3 files changed

+119
-30
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ flate2 = "^1.0"
6262
glob = "^0.3"
6363
# disable default-enabled cli in gptman 0.x
6464
gptman = { version = ">= 0.7, < 2", default-features = false }
65+
mbrman = ">= 0.5, < 0.6"
6566
hex = "^0.4"
6667
ignition-config = "0.2"
6768
lazy_static = "^1.4"
@@ -86,7 +87,6 @@ xz2 = "^0.1"
8687
zstd = { version = ">= 0.10.0, < 0.12.0", features = ["pkg-config"] }
8788

8889
[target.'cfg(target_arch = "s390x")'.dependencies]
89-
mbrman = ">= 0.5, < 0.6"
9090
rand = ">= 0.7, < 0.9"
9191

9292
[dev-dependencies]

docs/release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ nav_order: 8
77
## Upcoming coreos-installer 0.17.0 (unreleased)
88

99
Major changes:
10+
- install: if using `--save-partindex` add support for MBR device
1011

1112
Minor changes:
1213

src/blockdev.rs

Lines changed: 117 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
use anyhow::{anyhow, bail, Context, Result};
1616
use gptman::{GPTPartitionEntry, GPT};
17+
use mbrman::{MBRPartitionEntry, MBR};
1718
use nix::sys::stat::{major, minor};
1819
use nix::{errno::Errno, mount, sched};
1920
use regex::Regex;
@@ -32,7 +33,7 @@ use std::path::{Path, PathBuf};
3233
use std::process::Command;
3334
use std::thread::sleep;
3435
use std::time::Duration;
35-
use uuid::Uuid;
36+
use uuid::{uuid, Uuid};
3637

3738
use crate::cmdline::PartitionFilter;
3839
use crate::util::*;
@@ -519,6 +520,32 @@ pub struct SavedPartitions {
519520
partitions: Vec<(u32, GPTPartitionEntry)>,
520521
}
521522

523+
fn translate_mbr_types_to_gpt(sys: u8) -> Uuid {
524+
// non inclusive best effort mapping of MBR types to GPT types
525+
match sys {
526+
// Linux filesystem
527+
0x01 => uuid!("0FC63DAF-8483-4772-8E79-3D69D8477DE4"),
528+
// Linux Filesystem Data
529+
0x83 => uuid!("0FC63DAF-8483-4772-8E79-3D69D8477DE4"),
530+
// Linux LVM
531+
0x8e => uuid!("E6D6D379-F507-44C2-A23C-238F2A3DF928"),
532+
// Linux Swap
533+
0x82 => uuid!("0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"),
534+
_ => uuid!("00000000-0000-0000-0000-ffff00000000"),
535+
}
536+
}
537+
538+
fn translate_mbr_to_gpt(mbr_partition: &MBRPartitionEntry) -> GPTPartitionEntry {
539+
let mut gpt_partition = GPTPartitionEntry::empty();
540+
541+
gpt_partition.partition_type_guid = *translate_mbr_types_to_gpt(mbr_partition.sys).as_bytes();
542+
gpt_partition.unique_partition_guid = *Uuid::new_v4().as_bytes();
543+
gpt_partition.starting_lba = u64::from(mbr_partition.starting_lba);
544+
// -1 because the end is inclusive
545+
gpt_partition.ending_lba = u64::from(mbr_partition.starting_lba + mbr_partition.sectors) - 1;
546+
gpt_partition
547+
}
548+
522549
impl SavedPartitions {
523550
/// Create a SavedPartitions for a block device with a sector size.
524551
pub fn new_from_disk(disk: &mut File, filters: &[PartitionFilter]) -> Result<Self> {
@@ -566,39 +593,58 @@ impl SavedPartitions {
566593
});
567594
}
568595

596+
// Create a vector of the partitions
597+
let mut partitions = Vec::new();
598+
569599
// read GPT
570-
let gpt = match GPT::find_from(disk) {
571-
Ok(gpt) => gpt,
600+
match GPT::find_from(disk) {
601+
Ok(gpt) => {
602+
// cross-check GPT sector size
603+
Self::verify_gpt_sector_size(&gpt, sector_size)?;
604+
605+
// save partitions accepted by filters
606+
for (i, p) in gpt.iter() {
607+
if Self::matches_filters(i, p, filters) {
608+
partitions.push((i, p.clone()));
609+
}
610+
}
611+
}
572612
Err(gptman::Error::InvalidSignature) => {
573-
// ensure no indexes are listed to be saved from a MBR disk
574-
// we don't need to check for labels since MBR does not support them
613+
// no GPT, check for MBR
575614
if filters
576615
.iter()
577616
.any(|f| matches!(f, PartitionFilter::Index(_, _)))
578617
&& disk_has_mbr(disk).context("checking if disk has an MBR")?
579618
{
580-
bail!("saving partitions from an MBR disk is not yet supported");
619+
let mbr = MBR::read_from(disk, u32::try_from(sector_size)?)
620+
.context("reading disk as MBR")?;
621+
622+
// cross-check MBR sector size
623+
Self::verify_mbr_sector_size(&mbr, sector_size)?;
624+
625+
for (i, p) in mbr.iter() {
626+
if Self::matches_filters_mbr(i.try_into().unwrap(), p, filters) {
627+
// if the partition is using any of the last 33 sectors, we need to bail.
628+
if p.starting_lba + p.sectors < mbr.disk_size - 33 {
629+
partitions.push((
630+
i.try_into().context("convert usize into u32")?,
631+
translate_mbr_to_gpt(p),
632+
));
633+
} else {
634+
bail!("MBR partition {} is using the last 33 sectors of the disk, which is not supported by GPT", i);
635+
}
636+
}
637+
}
581638
}
582-
583639
// no GPT on this disk, so no partitions to save
584640
return Ok(Self {
585641
sector_size,
586-
partitions: Vec::new(),
642+
partitions: partitions,
587643
});
588644
}
589645
Err(e) => return Err(e).context("reading partition table"),
590646
};
591647

592-
// cross-check GPT sector size
593-
Self::verify_gpt_sector_size(&gpt, sector_size)?;
594-
595-
// save partitions accepted by filters
596-
let mut partitions = Vec::new();
597-
for (i, p) in gpt.iter() {
598-
if Self::matches_filters(i, p, filters) {
599-
partitions.push((i, p.clone()));
600-
}
601-
}
602648
let result = Self {
603649
sector_size,
604650
partitions,
@@ -654,6 +700,17 @@ impl SavedPartitions {
654700
Ok(())
655701
}
656702

703+
fn verify_mbr_sector_size(mbr: &MBR, sector_size: u64) -> Result<()> {
704+
if u64::from(mbr.sector_size) != sector_size {
705+
bail!(
706+
"MBR sector size {} doesn't match expected {}",
707+
mbr.sector_size,
708+
sector_size
709+
);
710+
}
711+
Ok(())
712+
}
713+
657714
fn matches_filters(i: u32, p: &GPTPartitionEntry, filters: &[PartitionFilter]) -> bool {
658715
use PartitionFilter::*;
659716
if !p.is_used() {
@@ -668,6 +725,19 @@ impl SavedPartitions {
668725
})
669726
}
670727

728+
fn matches_filters_mbr(i: u32, p: &MBRPartitionEntry, filters: &[PartitionFilter]) -> bool {
729+
use PartitionFilter::*;
730+
if !p.is_used() {
731+
return false;
732+
}
733+
filters.iter().any(|f| match f {
734+
Index(Some(first), _) if first.get() > i => false,
735+
Index(_, Some(last)) if last.get() < i => false,
736+
Index(_, _) => true,
737+
_ => false,
738+
})
739+
}
740+
671741
/// Unconditionally write the saved partitions, and only the saved
672742
/// partitions, to the disk. Write a protective MBR and overwrite any
673743
/// MBR boot code. Updating the kernel partition table is the caller's
@@ -1569,17 +1639,35 @@ mod tests {
15691639

15701640
// test trying to save partitions from a MBR disk
15711641
let mut disk = make_unformatted_disk();
1572-
gptman::GPT::write_protective_mbr_into(&mut disk, 512).unwrap();
1573-
// label only
1574-
SavedPartitions::new(&mut disk, 512, &vec![label("*i*")]).unwrap();
1642+
let mut mbr = mbrman::MBR::new_from(&mut disk, 512 as u32, [0xff; 4])
1643+
.expect("could not create partition table");
1644+
1645+
// create a mbr partition to copy over to gpt
1646+
mbr[1] = mbrman::MBRPartitionEntry {
1647+
boot: mbrman::BOOT_INACTIVE,
1648+
first_chs: mbrman::CHS::empty(),
1649+
sys: 0x0f,
1650+
last_chs: mbrman::CHS::empty(),
1651+
starting_lba: 1,
1652+
sectors: mbr.disk_size - 5000,
1653+
};
1654+
1655+
mbr.write_into(&mut disk).unwrap();
15751656
// index only
1576-
assert_eq!(
1577-
SavedPartitions::new(&mut disk, 512, &vec![Index(index(1), index(1))])
1578-
.unwrap_err()
1579-
.to_string(),
1580-
"saving partitions from an MBR disk is not yet supported"
1581-
);
1582-
// label and index
1657+
let saved = SavedPartitions::new(&mut disk, 512, &vec![Index(index(1), index(1))]).unwrap();
1658+
assert!(saved.is_saved());
1659+
1660+
// create a mbr partition that uses some of the last 33 sectors of the disk
1661+
mbr[1] = mbrman::MBRPartitionEntry {
1662+
boot: mbrman::BOOT_INACTIVE,
1663+
first_chs: mbrman::CHS::empty(),
1664+
sys: 0x0f,
1665+
last_chs: mbrman::CHS::empty(),
1666+
starting_lba: 1,
1667+
sectors: mbr.disk_size - 22,
1668+
};
1669+
mbr.write_into(&mut disk).unwrap();
1670+
15831671
assert_eq!(
15841672
SavedPartitions::new(
15851673
&mut disk,
@@ -1588,7 +1676,7 @@ mod tests {
15881676
)
15891677
.unwrap_err()
15901678
.to_string(),
1591-
"saving partitions from an MBR disk is not yet supported"
1679+
"MBR partition 1 is using the last 33 sectors of the disk, which is not supported by GPT"
15921680
);
15931681

15941682
// test sector size mismatch

0 commit comments

Comments
 (0)