From e8994784b3c0a38cec8416d55fb3d7c2ff237ad1 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovskii Date: Wed, 29 Sep 2021 10:42:14 +0200 Subject: [PATCH] install: print warning when system may have more than one boot/root partition Inspired by: https://github.com/coreos/fedora-coreos-tracker/issues/976 Signed-off-by: Nikita Dubrovskii --- src/blockdev.rs | 338 +++++++++++++++++++++++++++++++++++++++++++++++- src/install.rs | 10 ++ 2 files changed, 347 insertions(+), 1 deletion(-) diff --git a/src/blockdev.rs b/src/blockdev.rs index 652ef1b33..39ffcc6c1 100644 --- a/src/blockdev.rs +++ b/src/blockdev.rs @@ -17,13 +17,15 @@ use gptman::{GPTPartitionEntry, GPT}; use nix::sys::stat::{major, minor}; use nix::{errno::Errno, mount, sched}; use regex::Regex; -use std::collections::HashMap; +use serde::Deserialize; +use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use std::env; use std::fs::{ canonicalize, metadata, read_dir, read_to_string, remove_dir, symlink_metadata, File, OpenOptions, }; +use std::hash::{Hash, Hasher}; use std::io::{Read, Seek, SeekFrom, Write}; use std::num::{NonZeroU32, NonZeroU64}; use std::os::linux::fs::MetadataExt; @@ -1515,3 +1517,337 @@ mod tests { ); } } + +#[derive(Debug, Deserialize)] +pub struct BlockDevices { + pub blockdevices: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Device { + pub name: String, + pub label: Option, + pub uuid: Option, + pub children: Option>, +} + +impl PartialEq for Device { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} +impl Eq for Device {} + +impl Hash for Device { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +pub fn get_all_block_devices() -> Result { + runcmd!("partprobe")?; + let mut cmd = Command::new("lsblk"); + cmd.arg("-o") + .arg("NAME,LABEL,UUID") + .arg("--noheadings") + .arg("--json") + .arg("--paths"); + let output = cmd_output(&mut cmd)?; + let devices = serde_json::from_str::(&output)?; + Ok(devices) +} + +pub fn get_partitions_with_label<'a>( + label: &str, + devices: &'a [Device], +) -> Option> { + fn disks_flatten<'a>(label: &str, devices: &'a [Device], out: &mut HashSet<&'a Device>) { + for dev in devices { + if let Some(l) = dev.label.as_ref() { + if l == label { + out.insert(dev); + } + } + if let Some(children) = dev.children.as_ref() { + disks_flatten(label, children, out) + } + } + } + + let mut out = HashSet::new(); + disks_flatten(label, devices, &mut out); + if out.is_empty() { + None + } else { + Some(out) + } +} + +pub fn count_partitions_with_label(label: &str, devices: &[Device]) -> usize { + let partitions = get_partitions_with_label(label, devices); + match partitions { + Some(v) => { + let with_uuids = v + .iter() + .filter_map(|d| d.uuid.as_ref()) + .collect::>() + .len(); + let without_uuids = v.iter().filter(|d| d.uuid.is_none()).count(); + with_uuids + without_uuids + } + None => 0, + } +} + +#[cfg(test)] +mod partitions_by_label_tests { + use super::*; + + macro_rules! assert_same_partitions { + ($expected:expr, $actual:expr) => {{ + let mut left = $expected; + let mut right = $actual.iter().map(|f| f.name.as_str()).collect::>(); + right.sort(); + left.sort(); + assert_eq!(left, right); + }}; + } + + #[test] + fn count_two_disks() { + let json = r#" + { + "blockdevices": [ + {"name":"/dev/sda", "label":null, "uuid":null, + "children": [ + {"name":"/dev/sda3", "label":"boot", "uuid":"b168dfd5-7ea6-49fb-a006-a241c35b46da"}, + {"name":"/dev/sda4", "label":"root", "uuid":"50a003eb-6708-4f52-8ece-3e9a4a6c838f"} + ] + }, + {"name":"/dev/dasda", "label":null, "uuid":null, + "children": [ + {"name":"/dev/dasda1", "label":"boot", "uuid":"0ca76601-1ddd-4e1f-9d5a-f8a50fdcd091"}, + {"name":"/dev/dasda2", "label":"root", "uuid":"82496045-9743-4484-89d5-869265d4a15c"} + ] + } + ] + }"#; + + //when + let disks: BlockDevices = serde_json::from_str(&json).unwrap(); + let pts = get_partitions_with_label("boot", &disks.blockdevices).unwrap(); + + //then + assert_eq!(2, count_partitions_with_label("boot", &disks.blockdevices)); + assert_same_partitions!(vec!["/dev/dasda1", "/dev/sda3"], pts); + } + + #[test] + fn count_two_paths_to_disk() { + let json = r#" + { + "blockdevices": [ + {"name":"/dev/sda", "label":null, "uuid":null, + "children": [ + {"name":"/dev/sda3", "label":"boot", "uuid":"b168dfd5-7ea6-49fb-a006-a241c35b46da"}, + {"name":"/dev/sda4", "label":"root", "uuid":"50a003eb-6708-4f52-8ece-3e9a4a6c838f"} + ] + }, + {"name":"/dev/sdb", "label":null, "uuid":null, + "children": [ + {"name":"/dev/sdb3", "label":"boot", "uuid":"b168dfd5-7ea6-49fb-a006-a241c35b46da"}, + {"name":"/dev/sdb4", "label":"root", "uuid":"50a003eb-6708-4f52-8ece-3e9a4a6c838f"} + ] + } + ] + }"#; + + //when + let disks: BlockDevices = serde_json::from_str(&json).unwrap(); + let pts = get_partitions_with_label("boot", &disks.blockdevices).unwrap(); + + //then + assert_eq!(1, count_partitions_with_label("boot", &disks.blockdevices)); + assert_same_partitions!(vec!["/dev/sdb3", "/dev/sda3"], pts); + } + + #[test] + fn count_multipath() { + // given + let json = r#" + { + "blockdevices": [ + {"name":"/dev/sda", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha3", "label":"boot", "uuid":"b168dfd5-7ea6-49fb-a006-a241c35b46da"}, + {"name":"/dev/mapper/mpatha4", "label":"root", "uuid":"50a003eb-6708-4f52-8ece-3e9a4a6c838f"} + ] + } + ] + }, + {"name":"/dev/sdb", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha3", "label":"boot", "uuid":"b168dfd5-7ea6-49fb-a006-a241c35b46da"}, + {"name":"/dev/mapper/mpatha4", "label":"root", "uuid":"50a003eb-6708-4f52-8ece-3e9a4a6c838f"} + ] + } + ] + } + ] + }"#; + + //when + let disks: BlockDevices = serde_json::from_str(&json).unwrap(); + let pts = get_partitions_with_label("boot", &disks.blockdevices).unwrap(); + + //then + assert_eq!(1, count_partitions_with_label("boot", &disks.blockdevices)); + assert_same_partitions!(vec!["/dev/mapper/mpatha3"], pts); + } + + #[test] + fn count_multipath_with_blockdevice() { + // given + let json = r#" + { + "blockdevices": [ + {"name":"/dev/sda", "label":null, "uuid":null, + "children": [ + {"name":"/dev/sda3", "label":"boot", "uuid":"b5aea785-3d60-4455-ae8d-a3ba06132157"}, + {"name":"/dev/sda4", "label":"root", "uuid":"1948b11c-40ec-47d0-af67-0869928e50b8"}, + {"name":"/dev/mapper/mpatha", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha3", "label":"boot", "uuid":"b5aea785-3d60-4455-ae8d-a3ba06132157"}, + {"name":"/dev/mapper/mpatha4", "label":"root", "uuid":"1948b11c-40ec-47d0-af67-0869928e50b8"} + ] + } + ] + }, + {"name":"/dev/sdb", "label":null, "uuid":null, + "children": [ + {"name":"/dev/sdb3", "label":"boot", "uuid":"b5aea785-3d60-4455-ae8d-a3ba06132157"}, + {"name":"/dev/sdb4", "label":"root", "uuid":"1948b11c-40ec-47d0-af67-0869928e50b8"}, + {"name":"/dev/mapper/mpatha", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha3", "label":"boot", "uuid":"b5aea785-3d60-4455-ae8d-a3ba06132157"}, + {"name":"/dev/mapper/mpatha4", "label":"root", "uuid":"1948b11c-40ec-47d0-af67-0869928e50b8"} + ] + } + ] + } + ] + }"#; + + //when + let disks: BlockDevices = serde_json::from_str(&json).unwrap(); + let pts = get_partitions_with_label("boot", &disks.blockdevices).unwrap(); + + //then + assert_eq!(1, count_partitions_with_label("boot", &disks.blockdevices)); + assert_same_partitions!(vec!["/dev/sda3", "/dev/mapper/mpatha3", "/dev/sdb3"], pts); + } + + #[test] + fn count_disk_and_multipath() { + // given + let json = r#" + { + "blockdevices": [ + {"name":"/dev/sda", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha3", "label":"boot", "uuid":"b168dfd5-7ea6-49fb-a006-a241c35b46da"}, + {"name":"/dev/mapper/mpatha4", "label":"root", "uuid":"50a003eb-6708-4f52-8ece-3e9a4a6c838f"} + ] + } + ] + }, + {"name":"/dev/sdb", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha3", "label":"boot", "uuid":"b168dfd5-7ea6-49fb-a006-a241c35b46da"}, + {"name":"/dev/mapper/mpatha4", "label":"root", "uuid":"50a003eb-6708-4f52-8ece-3e9a4a6c838f"} + ] + } + ] + }, + {"name":"/dev/dasda", "label":null, "uuid":null, + "children": [ + {"name":"/dev/dasda1", "label":"boot", "uuid":"0ca76601-1ddd-4e1f-9d5a-f8a50fdcd091"}, + {"name":"/dev/dasda2", "label":"root", "uuid":"82496045-9743-4484-89d5-869265d4a15c"} + ] + } + ] + }"#; + + //when + let disks: BlockDevices = serde_json::from_str(&json).unwrap(); + let pts = get_partitions_with_label("boot", &disks.blockdevices).unwrap(); + + //then + assert_eq!(2, count_partitions_with_label("boot", &disks.blockdevices)); + assert_same_partitions!(vec!["/dev/mapper/mpatha3", "/dev/dasda1"], pts); + } + + #[test] + fn count_disk_and_multipath_with_blockdevice() { + // given + let json = r#" + { + "blockdevices": [ + {"name":"/dev/sda", "label":null, "uuid":null, + "children": [ + {"name":"/dev/sda3", "label":"boot", "uuid":"b5aea785-3d60-4455-ae8d-a3ba06132157"}, + {"name":"/dev/sda4", "label":"root", "uuid":"1948b11c-40ec-47d0-af67-0869928e50b8"}, + {"name":"/dev/mapper/mpatha", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha3", "label":"boot", "uuid":"b5aea785-3d60-4455-ae8d-a3ba06132157"}, + {"name":"/dev/mapper/mpatha4", "label":"root", "uuid":"1948b11c-40ec-47d0-af67-0869928e50b8"} + ] + } + ] + }, + {"name":"/dev/sdb", "label":null, "uuid":null, + "children": [ + {"name":"/dev/sdb3", "label":"boot", "uuid":"b5aea785-3d60-4455-ae8d-a3ba06132157"}, + {"name":"/dev/sdb4", "label":"root", "uuid":"1948b11c-40ec-47d0-af67-0869928e50b8"}, + {"name":"/dev/mapper/mpatha", "label":null, "uuid":null, + "children": [ + {"name":"/dev/mapper/mpatha3", "label":"boot", "uuid":"b5aea785-3d60-4455-ae8d-a3ba06132157"}, + {"name":"/dev/mapper/mpatha4", "label":"root", "uuid":"1948b11c-40ec-47d0-af67-0869928e50b8"} + ] + } + ] + }, + {"name":"/dev/dasda", "label":null, "uuid":null, + "children": [ + {"name":"/dev/dasda1", "label":"boot", "uuid":"0ca76601-1ddd-4e1f-9d5a-f8a50fdcd091"}, + {"name":"/dev/dasda2", "label":"root", "uuid":"82496045-9743-4484-89d5-869265d4a15c"} + ] + } + ] + }"#; + + //when + let disks: BlockDevices = serde_json::from_str(&json).unwrap(); + let pts = get_partitions_with_label("boot", &disks.blockdevices).unwrap(); + + //then + assert_eq!(2, count_partitions_with_label("boot", &disks.blockdevices)); + assert_same_partitions!( + vec![ + "/dev/mapper/mpatha3", + "/dev/sda3", + "/dev/sdb3", + "/dev/dasda1" + ], + pts + ); + } +} diff --git a/src/install.rs b/src/install.rs index 973fa1d44..bea69931b 100644 --- a/src/install.rs +++ b/src/install.rs @@ -241,6 +241,16 @@ pub fn install(config: &InstallConfig) -> Result<()> { bail!("install failed"); } + // warn if we have more than 1 partition with boot label + let devices = get_all_block_devices()?; + let amount = count_partitions_with_label("boot", &devices.blockdevices); + if amount != 1 { + eprintln!( + "System has {} partitions with boot label. Please 'wipefs' all except {}", + amount, config.device + ); + } + eprintln!("Install complete."); Ok(()) }