Skip to main content

composefs_boot/
lib.rs

1//! Boot integration for composefs filesystem images.
2//!
3//! This crate provides functionality to transform composefs filesystem images for boot
4//! scenarios by extracting boot resources, applying SELinux labels, and preparing
5//! bootloader entries. It supports both Boot Loader Specification (Type 1) entries
6//! and Unified Kernel Images (Type 2) for UEFI boot.
7
8#![forbid(unsafe_code)]
9#![deny(missing_debug_implementations)]
10
11pub mod bootloader;
12pub mod cmdline;
13pub mod os_release;
14pub mod selabel;
15pub mod uki;
16pub mod write_boot;
17
18use std::ffi::OsStr;
19
20use anyhow::Result;
21use rustix::fd::AsFd;
22
23use composefs::{fsverity::FsVerityHashValue, repository::Repository, tree::FileSystem};
24
25use crate::bootloader::{BootEntry, get_boot_resources};
26
27/// These directories are required to exist in images.
28/// They may have content in the container, but we don't
29/// want to expose them in the final merged root.
30///
31/// # /boot
32///
33/// This is how sealed UKIs are handled; the UKI in /boot has the composefs
34/// digest, so we can't include it in the rendered image.
35///
36/// # /sysroot
37///
38/// See https://github.com/containers/composefs-rs/issues/164
39/// Basically there is only content here in ostree-container cases,
40/// and us traversing there for SELinux labeling will cause problems.
41/// The ostree-container code special cases it in a different way, but
42/// here we can just ignore it.
43const REQUIRED_TOPLEVEL_TO_EMPTY_DIRS: &[&str] = &["boot", "sysroot"];
44
45/// Empty the required top-level directories and set their mtime to match /usr.
46fn empty_toplevel_dirs<ObjectID: FsVerityHashValue>(fs: &mut FileSystem<ObjectID>) -> Result<()> {
47    let usr_mtime = fs.root.get_directory(OsStr::new("usr"))?.stat.st_mtim_sec;
48
49    for d in REQUIRED_TOPLEVEL_TO_EMPTY_DIRS {
50        let d = fs.root.get_directory_mut(d.as_ref())?;
51        d.stat.st_mtim_sec = usr_mtime;
52        d.clear();
53    }
54
55    Ok(())
56}
57
58/// Trait for transforming filesystem images for boot scenarios.
59///
60/// This trait provides functionality to prepare composefs filesystem images for booting by
61/// extracting boot resources and applying necessary transformations like SELinux labeling.
62pub trait BootOps<ObjectID: FsVerityHashValue> {
63    /// Transforms a filesystem image for boot by extracting boot entries and applying SELinux labels.
64    ///
65    /// This method extracts boot resources from the filesystem, empties required top-level
66    /// directories (/boot, /sysroot), and applies SELinux security contexts.
67    ///
68    /// # Arguments
69    ///
70    /// * `repo` - The composefs repository containing filesystem objects
71    ///
72    /// # Returns
73    ///
74    /// A vector of boot entries extracted from the filesystem (Type 1 BLS entries, Type 2 UKIs, etc.)
75    fn transform_for_boot(
76        &mut self,
77        repo: &Repository<ObjectID>,
78    ) -> Result<Vec<BootEntry<ObjectID>>>;
79
80    /// Apply boot filesystem transformations using an on-disk directory for file content.
81    ///
82    /// This applies the same filesystem transformations as [`BootOps::transform_for_boot`]
83    /// (emptying /boot and /sysroot, SELinux relabeling) but reads SELinux policy files
84    /// directly from the on-disk filesystem via a directory fd rather than from the
85    /// composefs repository.
86    ///
87    /// This does not extract boot entries (Type 1 BLS entries, UKIs, etc.) since those
88    /// are only needed for writing to the boot partition, not for computing the composefs
89    /// digest.
90    ///
91    /// # Arguments
92    ///
93    /// * `rootfs` - A directory fd pointing to the root of the on-disk filesystem
94    fn transform_for_boot_from_dir(&mut self, rootfs: impl AsFd) -> Result<()>;
95}
96
97impl<ObjectID: FsVerityHashValue> BootOps<ObjectID> for FileSystem<ObjectID> {
98    fn transform_for_boot(
99        &mut self,
100        repo: &Repository<ObjectID>,
101    ) -> Result<Vec<BootEntry<ObjectID>>> {
102        let boot_entries = get_boot_resources(self, repo)?;
103        empty_toplevel_dirs(self)?;
104        // Compact the leaves table after clearing directories, so that leaves
105        // which were only referenced by /boot or /sysroot are removed and
106        // don't appear as orphans when the filesystem is validated.
107        self.compact();
108        selabel::selabel(self, repo)?;
109
110        Ok(boot_entries)
111    }
112
113    fn transform_for_boot_from_dir(&mut self, rootfs: impl AsFd) -> Result<()> {
114        empty_toplevel_dirs(self)?;
115        // Same as above: compact to remove leaves orphaned by clearing dirs.
116        self.compact();
117        selabel::selabel_from_dir(self, rootfs)?;
118        Ok(())
119    }
120}