Skip to main content

composefs_boot/
write_boot.rs

1//! Boot entry writing and installation functionality.
2//!
3//! This module provides functions to write boot entries to the filesystem, handling both
4//! Boot Loader Specification Type 1 entries (separate kernel/initrd files) and Type 2
5//! Unified Kernel Images. It manages file placement, directory creation, and command line
6//! argument injection for composefs boot scenarios.
7
8use std::{
9    fs::{create_dir_all, write},
10    path::Path,
11};
12
13use anyhow::{Context, Result, ensure};
14
15use composefs::{fsverity::FsVerityHashValue, repository::Repository};
16
17use crate::{
18    bootloader::{BootEntry, Type1Entry, Type2Entry},
19    cmdline::ComposefsCmdline,
20    uki,
21};
22
23/// Writes a Type 1 boot entry to the boot directory.
24///
25/// # Arguments
26///
27/// * `t1` - The Type 1 entry to write
28/// * `bootdir` - Path to the boot directory
29/// * `boot_subdir` - Optional subdirectory to prepend to paths
30/// * `karg` - The composefs kernel argument (encodes format version, digest, and insecure flag)
31/// * `cmdline_extra` - Additional kernel command line arguments
32/// * `repo` - The composefs repository
33pub fn write_t1_simple<ObjectID: FsVerityHashValue>(
34    mut t1: Type1Entry<ObjectID>,
35    bootdir: &Path,
36    boot_subdir: Option<&str>,
37    karg: &ComposefsCmdline<ObjectID>,
38    cmdline_extra: &[&str],
39    repo: &Repository<ObjectID>,
40) -> Result<()> {
41    let bootdir = if let Some(subdir) = boot_subdir {
42        let subdir_path = Path::new(subdir);
43        bootdir.join(subdir_path.strip_prefix("/").unwrap_or(subdir_path))
44    } else {
45        bootdir.to_path_buf()
46    };
47
48    let karg_str = karg.to_cmdline_arg();
49    t1.entry.adjust_cmdline(Some(&karg_str), cmdline_extra);
50
51    // Write the content before we write the loader entry
52    for (filename, file) in &t1.files {
53        let pathname = Path::new(filename.as_ref());
54        let file_path = bootdir.join(pathname.strip_prefix(Path::new("/"))?);
55        // SAFETY: what safety? :)
56        create_dir_all(file_path.parent().unwrap())?;
57        write(file_path, composefs::fs::read_file(file, repo)?)?;
58    }
59
60    // And now the loader entry itself
61    let loader_entries = bootdir.join("loader/entries");
62    create_dir_all(&loader_entries)?;
63    let entry = loader_entries.join(t1.filename.as_ref());
64    let entry_content = t1.entry.lines.join("\n") + "\n";
65    write(entry, entry_content)?;
66    Ok(())
67}
68
69/// Writes a Type 2 boot entry (UKI) to the boot directory.
70///
71/// Validates that the UKI's embedded composefs karg (`composefs=` or `composefs.digest=`)
72/// matches one of the expected `acceptable_digests`.
73///
74/// # Arguments
75///
76/// * `t2` - The Type 2 entry to write
77/// * `bootdir` - Path to the boot directory
78/// * `acceptable_digests` - The composefs root object IDs the UKI may carry
79/// * `repo` - The composefs repository
80pub fn write_t2_simple<ObjectID: FsVerityHashValue>(
81    t2: Type2Entry<ObjectID>,
82    bootdir: &Path,
83    acceptable_digests: &[&ObjectID],
84    repo: &Repository<ObjectID>,
85) -> Result<()> {
86    let efi_linux = bootdir.join("EFI/Linux");
87    create_dir_all(&efi_linux)?;
88    let filename = efi_linux.join(t2.file_path);
89    let content = composefs::fs::read_file(&t2.file, repo)?;
90    let cmdline = uki::get_cmdline(&content)?;
91    let parsed = ComposefsCmdline::<ObjectID>::from_cmdline(cmdline)
92        .with_context(|| format!("parsing UKI .cmdline section: {cmdline:?}"))?
93        .ok_or_else(|| {
94            anyhow::anyhow!(
95                "UKI .cmdline has no composefs karg (composefs= or composefs.digest=): {cmdline:?}"
96            )
97        })?;
98
99    parsed.validate_digest(acceptable_digests.iter().copied())?;
100    write(filename, content)?;
101    Ok(())
102}
103
104/// Writes boot entry to the boot partition
105///
106/// # Arguments
107///
108/// * repo           - The composefs repository
109/// * entry          - Boot entry variant to be written
110/// * karg           - The composefs kernel argument (encodes format version, digest, and insecure
111///   flag); used to build the `composefs=` or `composefs.digest=` cmdline argument
112/// * boot_partition - Path to the boot partition/directory
113/// * boot_subdir    - If `Some(path)`, the path is prepended to `initrd` and `linux` keys in the BLS entry
114///
115/// For example, if `boot_partition = "/boot"` and `boot_subdir = Some("1")` ,
116/// the BLS entry will contain
117///
118/// ```text
119/// linux /boot/1/<entry_id>/linux
120/// initrd /boot/1/<entry_id>/initrd
121/// ```
122///
123/// If `boot_partition = "/boot"` and `boot_subdir = None` , the BLS entry will contain
124///
125/// ```text
126/// linux /<entry_id>/linux
127/// initrd /<entry_id>/initrd
128/// ```
129///
130/// * entry_id       - In case of a BLS entry, the name of file to be generated in `loader/entries`
131/// * cmdline_extra  - Extra kernel command line arguments
132///
133pub fn write_boot_simple<ObjectID: FsVerityHashValue>(
134    repo: &Repository<ObjectID>,
135    entry: BootEntry<ObjectID>,
136    karg: &ComposefsCmdline<ObjectID>,
137    boot_partition: &Path,
138    boot_subdir: Option<&str>,
139    entry_id: Option<&str>,
140    cmdline_extra: &[&str],
141) -> Result<()> {
142    match entry {
143        BootEntry::Type1(mut t1) => {
144            if let Some(name) = entry_id {
145                t1.relocate(boot_subdir, name);
146            }
147            write_t1_simple(t1, boot_partition, boot_subdir, karg, cmdline_extra, repo)?;
148        }
149        BootEntry::Type2(mut t2) => {
150            if let Some(name) = entry_id {
151                t2.rename(name);
152            }
153            ensure!(cmdline_extra.is_empty(), "Can't add --cmdline args to UKIs");
154            write_t2_simple(t2, boot_partition, &[karg.digest()], repo)?;
155        }
156        BootEntry::UsrLibModulesVmLinuz(entry) => {
157            let mut t1 = entry.into_type1(entry_id)?;
158            if let Some(name) = entry_id {
159                t1.relocate(boot_subdir, name);
160            }
161            write_t1_simple(t1, boot_partition, boot_subdir, karg, cmdline_extra, repo)?;
162        }
163    };
164
165    Ok(())
166}