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}