Skip to content

Commit ee7e164

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 ee7e164

File tree

3 files changed

+123
-30
lines changed

3 files changed

+123
-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: Add support for MBR partition tables to `--save-partindex`
1011

1112
Minor changes:
1213

src/blockdev.rs

Lines changed: 121 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,36 @@ 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+
// https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs && https://tldp.org/HOWTO/Partition-Mass-Storage-Definitions-Naming-HOWTO/x190.html
526+
527+
match sys {
528+
// Linux filesystem
529+
0x01 => uuid!("0FC63DAF-8483-4772-8E79-3D69D8477DE4"),
530+
0x04 => uuid!("0FC63DAF-8483-4772-8E79-3D69D8477DE4"),
531+
0x06 => uuid!("0FC63DAF-8483-4772-8E79-3D69D8477DE4"),
532+
// Linux Filesystem Data
533+
0x83 => uuid!("0FC63DAF-8483-4772-8E79-3D69D8477DE4"),
534+
// Linux LVM
535+
0x8e => uuid!("E6D6D379-F507-44C2-A23C-238F2A3DF928"),
536+
// Linux Swap
537+
0x82 => uuid!("0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"),
538+
_ => uuid!("00000000-0000-0000-0000-000000000000"),
539+
}
540+
}
541+
542+
fn translate_mbr_to_gpt(mbr_partition: &MBRPartitionEntry) -> GPTPartitionEntry {
543+
let mut gpt_partition = GPTPartitionEntry::empty();
544+
545+
gpt_partition.partition_type_guid = *translate_mbr_types_to_gpt(mbr_partition.sys).as_bytes();
546+
gpt_partition.unique_partition_guid = *Uuid::new_v4().as_bytes();
547+
gpt_partition.starting_lba = u64::from(mbr_partition.starting_lba);
548+
// -1 because the end is inclusive
549+
gpt_partition.ending_lba = u64::from(mbr_partition.starting_lba + mbr_partition.sectors) - 1;
550+
gpt_partition
551+
}
552+
522553
impl SavedPartitions {
523554
/// Create a SavedPartitions for a block device with a sector size.
524555
pub fn new_from_disk(disk: &mut File, filters: &[PartitionFilter]) -> Result<Self> {
@@ -566,39 +597,58 @@ impl SavedPartitions {
566597
});
567598
}
568599

600+
// Create a vector of the partitions
601+
let mut partitions = Vec::new();
602+
569603
// read GPT
570-
let gpt = match GPT::find_from(disk) {
571-
Ok(gpt) => gpt,
604+
match GPT::find_from(disk) {
605+
Ok(gpt) => {
606+
// cross-check GPT sector size
607+
Self::verify_gpt_sector_size(&gpt, sector_size)?;
608+
609+
// save partitions accepted by filters
610+
for (i, p) in gpt.iter() {
611+
if Self::matches_filters(i, p, filters) {
612+
partitions.push((i, p.clone()));
613+
}
614+
}
615+
}
572616
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
617+
// no GPT, check for MBR
575618
if filters
576619
.iter()
577620
.any(|f| matches!(f, PartitionFilter::Index(_, _)))
578621
&& disk_has_mbr(disk).context("checking if disk has an MBR")?
579622
{
580-
bail!("saving partitions from an MBR disk is not yet supported");
623+
let mbr = MBR::read_from(disk, u32::try_from(sector_size)?)
624+
.context("reading disk as MBR")?;
625+
626+
// cross-check MBR sector size
627+
Self::verify_mbr_sector_size(&mbr, sector_size)?;
628+
629+
for (i, p) in mbr.iter() {
630+
if Self::matches_filters_mbr(i.try_into().unwrap(), p, filters) {
631+
// if the partition is using any of the last 33 sectors, we need to bail.
632+
if p.starting_lba + p.sectors < mbr.disk_size - 33 {
633+
partitions.push((
634+
i.try_into().context("convert usize into u32")?,
635+
translate_mbr_to_gpt(p),
636+
));
637+
} else {
638+
bail!("MBR partition {} is using the last 33 sectors of the disk, which is not supported by GPT", i);
639+
}
640+
}
641+
}
581642
}
582-
583643
// no GPT on this disk, so no partitions to save
584644
return Ok(Self {
585645
sector_size,
586-
partitions: Vec::new(),
646+
partitions: partitions,
587647
});
588648
}
589649
Err(e) => return Err(e).context("reading partition table"),
590650
};
591651

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-
}
602652
let result = Self {
603653
sector_size,
604654
partitions,
@@ -654,6 +704,17 @@ impl SavedPartitions {
654704
Ok(())
655705
}
656706

707+
fn verify_mbr_sector_size(mbr: &MBR, sector_size: u64) -> Result<()> {
708+
if u64::from(mbr.sector_size) != sector_size {
709+
bail!(
710+
"MBR sector size {} doesn't match expected {}",
711+
mbr.sector_size,
712+
sector_size
713+
);
714+
}
715+
Ok(())
716+
}
717+
657718
fn matches_filters(i: u32, p: &GPTPartitionEntry, filters: &[PartitionFilter]) -> bool {
658719
use PartitionFilter::*;
659720
if !p.is_used() {
@@ -668,6 +729,19 @@ impl SavedPartitions {
668729
})
669730
}
670731

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

15701644
// test trying to save partitions from a MBR disk
15711645
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();
1646+
let mut mbr = mbrman::MBR::new_from(&mut disk, 512 as u32, [0xff; 4])
1647+
.expect("could not create partition table");
1648+
1649+
// create a mbr partition to copy over to gpt
1650+
mbr[1] = mbrman::MBRPartitionEntry {
1651+
boot: mbrman::BOOT_INACTIVE,
1652+
first_chs: mbrman::CHS::empty(),
1653+
sys: 0x0f,
1654+
last_chs: mbrman::CHS::empty(),
1655+
starting_lba: 1,
1656+
sectors: mbr.disk_size - 5000,
1657+
};
1658+
1659+
mbr.write_into(&mut disk).unwrap();
15751660
// 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
1661+
let saved = SavedPartitions::new(&mut disk, 512, &vec![Index(index(1), index(1))]).unwrap();
1662+
assert!(saved.is_saved());
1663+
1664+
// create a mbr partition that uses some of the last 33 sectors of the disk
1665+
mbr[1] = mbrman::MBRPartitionEntry {
1666+
boot: mbrman::BOOT_INACTIVE,
1667+
first_chs: mbrman::CHS::empty(),
1668+
sys: 0x0f,
1669+
last_chs: mbrman::CHS::empty(),
1670+
starting_lba: 1,
1671+
sectors: mbr.disk_size - 22,
1672+
};
1673+
mbr.write_into(&mut disk).unwrap();
1674+
15831675
assert_eq!(
15841676
SavedPartitions::new(
15851677
&mut disk,
@@ -1588,7 +1680,7 @@ mod tests {
15881680
)
15891681
.unwrap_err()
15901682
.to_string(),
1591-
"saving partitions from an MBR disk is not yet supported"
1683+
"MBR partition 1 is using the last 33 sectors of the disk, which is not supported by GPT"
15921684
);
15931685

15941686
// test sector size mismatch

0 commit comments

Comments
 (0)