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::{ensure, Result};
14
15use composefs::{fsverity::FsVerityHashValue, repository::Repository};
16
17use crate::{
18    bootloader::{BootEntry, Type1Entry, Type2Entry},
19    cmdline::get_cmdline_composefs,
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/// * `root_id` - The composefs root object ID
31/// * `insecure` - Whether to allow optional fs-verity verification
32/// * `cmdline_extra` - Additional kernel command line arguments
33/// * `repo` - The composefs repository
34pub fn write_t1_simple<ObjectID: FsVerityHashValue>(
35    mut t1: Type1Entry<ObjectID>,
36    bootdir: &Path,
37    boot_subdir: Option<&str>,
38    root_id: &ObjectID,
39    insecure: bool,
40    cmdline_extra: &[&str],
41    repo: &Repository<ObjectID>,
42) -> Result<()> {
43    let bootdir = if let Some(subdir) = boot_subdir {
44        let subdir_path = Path::new(subdir);
45        bootdir.join(subdir_path.strip_prefix("/").unwrap_or(subdir_path))
46    } else {
47        bootdir.to_path_buf()
48    };
49
50    t1.entry
51        .adjust_cmdline(Some(&root_id.to_hex()), insecure, cmdline_extra);
52
53    // Write the content before we write the loader entry
54    for (filename, file) in &t1.files {
55        let pathname = Path::new(filename.as_ref());
56        let file_path = bootdir.join(pathname.strip_prefix(Path::new("/"))?);
57        // SAFETY: what safety? :)
58        create_dir_all(file_path.parent().unwrap())?;
59        write(file_path, composefs::fs::read_file(file, repo)?)?;
60    }
61
62    // And now the loader entry itself
63    let loader_entries = bootdir.join("loader/entries");
64    create_dir_all(&loader_entries)?;
65    let entry = loader_entries.join(t1.filename.as_ref());
66    let entry_content = t1.entry.lines.join("\n") + "\n";
67    write(entry, entry_content)?;
68    Ok(())
69}
70
71/// Writes a Type 2 boot entry (UKI) to the boot directory.
72///
73/// Validates that the UKI's embedded composefs= parameter matches the expected root_id.
74///
75/// # Arguments
76///
77/// * `t2` - The Type 2 entry to write
78/// * `bootdir` - Path to the boot directory
79/// * `root_id` - The expected composefs root object ID
80/// * `repo` - The composefs repository
81pub fn write_t2_simple<ObjectID: FsVerityHashValue>(
82    t2: Type2Entry<ObjectID>,
83    bootdir: &Path,
84    root_id: &ObjectID,
85    repo: &Repository<ObjectID>,
86) -> Result<()> {
87    let efi_linux = bootdir.join("EFI/Linux");
88    create_dir_all(&efi_linux)?;
89    let filename = efi_linux.join(t2.file_path);
90    let content = composefs::fs::read_file(&t2.file, repo)?;
91    let (composefs, _) = get_cmdline_composefs::<ObjectID>(uki::get_cmdline(&content)?)?;
92
93    ensure!(
94        &composefs == root_id,
95        "The UKI has the wrong composefs= parameter (is '{composefs:?}', should be {root_id:?})"
96    );
97    write(filename, content)?;
98    Ok(())
99}
100
101/// Writes boot entry to the boot partition
102///
103/// # Arguments
104///
105/// * repo           - The composefs repository
106/// * entry          - Boot entry variant to be written
107/// * root_id        - The content hash of the generated EROFS image id
108/// * insecure       - Make fs-verity validation optional in case the filesystem doesn't support
109///   it, indicated by `composefs=?hash` cmdline argument
110/// * boot_partition - Path to the boot partition/directory
111/// * boot_subdir    - If `Some(path)`, the path is prepended to `initrd` and `linux` keys in the BLS entry
112///
113/// For example, if `boot_partition = "/boot"` and `boot_subdir = Some("1")` ,
114/// the BLS entry will contain
115///
116/// ```text
117/// linux /boot/1/<entry_id>/linux
118/// initrd /boot/1/<entry_id>/initrd
119/// ```
120///
121/// If `boot_partition = "/boot"` and `boot_subdir = None` , the BLS entry will contain
122///
123/// ```text
124/// linux /<entry_id>/linux
125/// initrd /<entry_id>/initrd
126/// ```
127///
128/// * entry_id       - In case of a BLS entry, the name of file to be generated in `loader/entries`
129/// * cmdline_extra  - Extra kernel command line arguments
130///
131#[allow(clippy::too_many_arguments)]
132pub fn write_boot_simple<ObjectID: FsVerityHashValue>(
133    repo: &Repository<ObjectID>,
134    entry: BootEntry<ObjectID>,
135    root_id: &ObjectID,
136    insecure: bool,
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(
148                t1,
149                boot_partition,
150                boot_subdir,
151                root_id,
152                insecure,
153                cmdline_extra,
154                repo,
155            )?;
156        }
157        BootEntry::Type2(mut t2) => {
158            if let Some(name) = entry_id {
159                t2.rename(name);
160            }
161            ensure!(cmdline_extra.is_empty(), "Can't add --cmdline args to UKIs");
162            write_t2_simple(t2, boot_partition, root_id, repo)?;
163        }
164        BootEntry::UsrLibModulesVmLinuz(entry) => {
165            let mut t1 = entry.into_type1(entry_id);
166            if let Some(name) = entry_id {
167                t1.relocate(boot_subdir, name);
168            }
169            write_t1_simple(
170                t1,
171                boot_partition,
172                boot_subdir,
173                root_id,
174                insecure,
175                cmdline_extra,
176                repo,
177            )?;
178        }
179    };
180
181    Ok(())
182}