forked from brl/citadel-tools
refactor boot selection algorithm
This commit is contained in:
parent
8f8cbab72f
commit
d244c07483
@ -1,140 +0,0 @@
|
|||||||
|
|
||||||
use libcitadel::{Partition,Result,ImageHeader};
|
|
||||||
|
|
||||||
pub struct BootSelection {
|
|
||||||
partitions: Vec<Partition>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BootSelection {
|
|
||||||
pub fn choose_install_partition() -> Result<Partition> {
|
|
||||||
let bs = BootSelection::load_partitions()?;
|
|
||||||
match bs._choose_install_partition() {
|
|
||||||
Some(p) => Ok(p.clone()),
|
|
||||||
None => bail!("no partition found for installation"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn choose_boot_partition() -> Result<Partition> {
|
|
||||||
let bs = BootSelection::load_partitions()?;
|
|
||||||
match bs._choose_boot_partition() {
|
|
||||||
Some(p) => Ok(p.clone()),
|
|
||||||
None => bail!("no partition found to boot from"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_partitions() -> Result<BootSelection> {
|
|
||||||
let partitions = Partition::rootfs_partitions()
|
|
||||||
.map_err(|e| format_err!("Could not load rootfs partition info: {}", e))?;
|
|
||||||
|
|
||||||
Ok(BootSelection {
|
|
||||||
partitions
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _choose_install_partition(&self) -> Option<&Partition> {
|
|
||||||
self.choose(|p| {
|
|
||||||
// first pass, if there is a partition which is not mounted and
|
|
||||||
// not initialized use that one
|
|
||||||
!p.is_mounted() && !p.is_initialized()
|
|
||||||
}).or_else(|| self.choose(|p| {
|
|
||||||
// second pass, just find one that's not mounted
|
|
||||||
!p.is_mounted()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn choose<F>(&self, pred: F) -> Option<&Partition>
|
|
||||||
where F: Sized + Fn(&&Partition) -> bool
|
|
||||||
{
|
|
||||||
self.partitions.iter().find(pred)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the best rootfs partition to boot from
|
|
||||||
fn _choose_boot_partition(&self) -> Option<&Partition> {
|
|
||||||
let mut best: Option<&Partition> = None;
|
|
||||||
|
|
||||||
for p in &self.partitions {
|
|
||||||
if is_better(&best, p) {
|
|
||||||
best = Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
best
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Perform checks for error states at boot time.
|
|
||||||
pub fn scan_boot_partitions(&mut self) -> Result<()> {
|
|
||||||
for mut p in &mut self.partitions {
|
|
||||||
if let Err(e) = boot_scan_partition(&mut p) {
|
|
||||||
warn!("error in bootscan of partition {}: {}", p.path().display(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called at boot to perform various checks and possibly
|
|
||||||
/// update the status field to an error state.
|
|
||||||
///
|
|
||||||
/// Mark `STATUS_TRY_BOOT` partition as `STATUS_FAILED`.
|
|
||||||
///
|
|
||||||
/// If metainfo cannot be parsed, mark as `STATUS_BAD_META`.
|
|
||||||
///
|
|
||||||
/// Verify metainfo signature and mark `STATUS_BAD_SIG` if
|
|
||||||
/// signature verification fails.
|
|
||||||
///
|
|
||||||
fn boot_scan_partition(p: &mut Partition) -> Result<()> {
|
|
||||||
if !p.is_initialized() {
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
if p.header().status() == ImageHeader::STATUS_TRY_BOOT {
|
|
||||||
warn!("Partition {} has STATUS_TRY_BOOT, assuming it failed boot attempt and marking STATUS_FAILED", p.path().display());
|
|
||||||
p.write_status(ImageHeader::STATUS_FAILED)?;
|
|
||||||
}
|
|
||||||
p.header().verify_signature()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_better<'a>(current_best: &Option<&'a Partition>, other: &'a Partition) -> bool {
|
|
||||||
|
|
||||||
if !other.is_initialized() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only consider partitions in state NEW or state GOOD
|
|
||||||
if !other.is_good() && !other.is_new() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If metainfo is broken, then no, it's not better
|
|
||||||
//if !other.metainfo().is_ok() {
|
|
||||||
// return false;
|
|
||||||
//}
|
|
||||||
|
|
||||||
let best = match *current_best {
|
|
||||||
Some(p) => p,
|
|
||||||
// No current 'best', so 'other' is better, whatever it is.
|
|
||||||
None => return true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// First parition with PREFER flag trumps everything else
|
|
||||||
if best.is_preferred() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let best_version = best.metainfo().version();
|
|
||||||
let other_version = other.metainfo().version();
|
|
||||||
|
|
||||||
if best_version > other_version {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if other_version > best_version {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// choose NEW over GOOD if versions are the same
|
|
||||||
if other.is_new() && best.is_good() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// ... but if all things otherwise match, return first match
|
|
||||||
false
|
|
||||||
}
|
|
@ -10,11 +10,7 @@ use std::fs;
|
|||||||
use libcitadel::{Result,CommandLine,set_verbose,format_error,ResourceImage,util};
|
use libcitadel::{Result,CommandLine,set_verbose,format_error,ResourceImage,util};
|
||||||
|
|
||||||
|
|
||||||
mod boot_select;
|
|
||||||
mod rootfs;
|
mod rootfs;
|
||||||
pub use boot_select::BootSelection;
|
|
||||||
use rootfs::Rootfs;
|
|
||||||
|
|
||||||
|
|
||||||
/// mount command supports 4 subcommands
|
/// mount command supports 4 subcommands
|
||||||
///
|
///
|
||||||
@ -27,7 +23,7 @@ use rootfs::Rootfs;
|
|||||||
///
|
///
|
||||||
/// 'kernel' mounts a resource bundle containing kernel modules
|
/// 'kernel' mounts a resource bundle containing kernel modules
|
||||||
/// 'extra' mounts a resource bundle containing extra files
|
/// 'extra' mounts a resource bundle containing extra files
|
||||||
///
|
/// 'overlay' mounts a tmpfs overlay over rootfs filesystem only if citadel.overlay is set
|
||||||
///
|
///
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -54,8 +50,7 @@ fn main() {
|
|||||||
|
|
||||||
fn mount_rootfs() -> Result<()> {
|
fn mount_rootfs() -> Result<()> {
|
||||||
info!("citadel-mount rootfs");
|
info!("citadel-mount rootfs");
|
||||||
let rootfs = Rootfs::new();
|
rootfs::setup_rootfs()
|
||||||
rootfs.setup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_kernel() -> Result<()> {
|
fn mount_kernel() -> Result<()> {
|
||||||
|
@ -1,93 +1,174 @@
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use libcitadel::{BlockDev,CommandLine,Partition,Result,verity};
|
use libcitadel::{BlockDev,CommandLine,ImageHeader,Partition,Result,verity};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use BootSelection;
|
|
||||||
use ResourceImage;
|
use ResourceImage;
|
||||||
|
|
||||||
pub struct Rootfs {
|
pub fn setup_rootfs() -> Result<()> {
|
||||||
}
|
if CommandLine::install_mode() || CommandLine::live_mode() {
|
||||||
|
setup_rootfs_resource()
|
||||||
impl Rootfs {
|
} else {
|
||||||
pub fn new() -> Rootfs {
|
let p = choose_boot_partiton(true)?;
|
||||||
Rootfs {}
|
setup_partition(p)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup(&self) -> Result<()> {
|
|
||||||
if CommandLine::install_mode() || CommandLine::live_mode() {
|
|
||||||
self.setup_rootfs_resource()
|
|
||||||
} else {
|
|
||||||
let partition = BootSelection::choose_boot_partition()?;
|
|
||||||
self.setup_partition(partition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_partition(&self, partition: Partition) -> Result<()> {
|
|
||||||
if CommandLine::noverity() {
|
|
||||||
self.setup_partition_unverified(&partition)
|
|
||||||
} else {
|
|
||||||
self.setup_partition_verified(&partition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_rootfs_resource(&self) -> Result<()> {
|
|
||||||
info!("Searching for rootfs resource image");
|
|
||||||
|
|
||||||
let img = ResourceImage::find_rootfs()?;
|
|
||||||
|
|
||||||
if CommandLine::noverity() {
|
|
||||||
self.setup_resource_unverified(&img)
|
|
||||||
} else {
|
|
||||||
self.setup_resource_verified(&img)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_resource_unverified(&self, img: &ResourceImage) -> Result<()> {
|
|
||||||
if img.is_compressed() {
|
|
||||||
img.decompress()?;
|
|
||||||
}
|
|
||||||
let loopdev = img.create_loopdev()?;
|
|
||||||
info!("Loop device created: {}", loopdev.display());
|
|
||||||
self.setup_linear_mapping(&loopdev)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_resource_verified(&self, img: &ResourceImage) -> Result<()> {
|
|
||||||
let _ = img.setup_verity_device()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_partition_unverified(&self, partition: &Partition) -> Result<()> {
|
|
||||||
info!("Creating /dev/mapper/rootfs device with linear device mapping of partition (no verity)");
|
|
||||||
self.setup_linear_mapping(partition.path())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_partition_verified(&self, partition: &Partition) -> Result<()> {
|
|
||||||
info!("Creating /dev/mapper/rootfs dm-verity device");
|
|
||||||
if !CommandLine::nosignatures() {
|
|
||||||
partition.header().verify_signature()?;
|
|
||||||
info!("Image signature is valid for channel {}", partition.metainfo().channel());
|
|
||||||
}
|
|
||||||
verity::setup_partition_device(partition)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_linear_mapping(&self, blockdev: &Path) -> Result<()> {
|
|
||||||
let dev = BlockDev::open_ro(blockdev)?;
|
|
||||||
let table = format!("0 {} linear {} 0", dev.nsectors()?, blockdev.display());
|
|
||||||
|
|
||||||
info!("/usr/sbin/dmsetup create rootfs --table '{}'", table);
|
|
||||||
|
|
||||||
let ok = Command::new("/usr/sbin/dmsetup")
|
|
||||||
.args(&["create", "rootfs", "--table", &table])
|
|
||||||
.stderr(Stdio::inherit())
|
|
||||||
.status()
|
|
||||||
.expect("unable to execute /usr/sbin/dmsetup")
|
|
||||||
.success();
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
bail!("Failed to set up linear identity mapping with /usr/sbin/dmsetup");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_partition(mut p: Partition) -> Result<()> {
|
||||||
|
if CommandLine::noverity() {
|
||||||
|
setup_partition_unverified(&p)
|
||||||
|
} else {
|
||||||
|
setup_partition_verified(&mut p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_rootfs_resource() -> Result<()> {
|
||||||
|
info!("Searching for rootfs resource image");
|
||||||
|
|
||||||
|
let img = ResourceImage::find_rootfs()?;
|
||||||
|
|
||||||
|
if CommandLine::noverity() {
|
||||||
|
setup_resource_unverified(&img)
|
||||||
|
} else {
|
||||||
|
setup_resource_verified(&img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_resource_unverified(img: &ResourceImage) -> Result<()> {
|
||||||
|
if img.is_compressed() {
|
||||||
|
img.decompress()?;
|
||||||
|
}
|
||||||
|
let loopdev = img.create_loopdev()?;
|
||||||
|
info!("Loop device created: {}", loopdev.display());
|
||||||
|
setup_linear_mapping(&loopdev)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_resource_verified(img: &ResourceImage) -> Result<()> {
|
||||||
|
let _ = img.setup_verity_device()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_partition_unverified(p: &Partition) -> Result<()> {
|
||||||
|
info!("Creating /dev/mapper/rootfs device with linear device mapping of partition (no verity)");
|
||||||
|
setup_linear_mapping(p.path())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_partition_verified(p: &mut Partition) -> Result<()> {
|
||||||
|
info!("Creating /dev/mapper/rootfs dm-verity device");
|
||||||
|
if !CommandLine::nosignatures() {
|
||||||
|
if !p.has_public_key() {
|
||||||
|
bail!("No public key available for channel {}", p.metainfo().channel())
|
||||||
|
}
|
||||||
|
if !p.is_signature_valid() {
|
||||||
|
p.write_status(ImageHeader::STATUS_BAD_SIG)?;
|
||||||
|
bail!("Signature verification failed on partition");
|
||||||
|
}
|
||||||
|
info!("Image signature is valid for channel {}", p.metainfo().channel());
|
||||||
|
}
|
||||||
|
verity::setup_partition_device(p)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_linear_mapping(blockdev: &Path) -> Result<()> {
|
||||||
|
let dev = BlockDev::open_ro(blockdev)?;
|
||||||
|
let table = format!("0 {} linear {} 0", dev.nsectors()?, blockdev.display());
|
||||||
|
|
||||||
|
info!("/usr/sbin/dmsetup create rootfs --table '{}'", table);
|
||||||
|
|
||||||
|
let ok = Command::new("/usr/sbin/dmsetup")
|
||||||
|
.args(&["create", "rootfs", "--table", &table])
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.status()
|
||||||
|
.expect("unable to execute /usr/sbin/dmsetup")
|
||||||
|
.success();
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
bail!("Failed to set up linear identity mapping with /usr/sbin/dmsetup");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_boot_partiton(scan: bool) -> Result<Partition> {
|
||||||
|
let mut partitions = Partition::rootfs_partitions()?;
|
||||||
|
|
||||||
|
if scan {
|
||||||
|
for p in &mut partitions {
|
||||||
|
p.boot_scan()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut best = None;
|
||||||
|
for p in partitions {
|
||||||
|
best = compare_boot_partitions(best, p);
|
||||||
|
}
|
||||||
|
best.ok_or(format_err!("No partition found to boot from"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_boot_partitions(a: Option<Partition>, b: Partition) -> Option<Partition> {
|
||||||
|
if !is_bootable(&b) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// b is bootable, so if a is None, then just return b
|
||||||
|
let a = match a {
|
||||||
|
Some(partition) => partition,
|
||||||
|
None => return Some(b),
|
||||||
|
};
|
||||||
|
|
||||||
|
// First partition with FLAG_PREFER_BOOT trumps everything
|
||||||
|
if a.is_preferred() {
|
||||||
|
return Some(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.is_preferred() {
|
||||||
|
return Some(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare versions and channels
|
||||||
|
let a_v = a.metainfo().version();
|
||||||
|
let b_v = b.metainfo().version();
|
||||||
|
|
||||||
|
// Compare versions only if channels match
|
||||||
|
if a.metainfo().channel() == b.metainfo().channel() {
|
||||||
|
if a_v > b_v {
|
||||||
|
return Some(a);
|
||||||
|
} else if b_v > a_v {
|
||||||
|
return Some(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose NEW over GOOD if versions are the same or
|
||||||
|
// if versions cannot be compared because channels differ
|
||||||
|
if b.is_new() && a.is_good() {
|
||||||
|
return Some(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_bootable(p: &Partition) -> bool {
|
||||||
|
if !p.is_initialized() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// signatures enabled so not bootable without pubkey
|
||||||
|
if signatures_enabled() && !p.has_public_key() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.is_new() || p.is_good() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If signatures are disabled then don't disqualify an
|
||||||
|
// image which failed a prior signature verification
|
||||||
|
if !signatures_enabled() && p.is_sig_failed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signatures_enabled() -> bool {
|
||||||
|
!(CommandLine::nosignatures() || CommandLine::noverity())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user