Skip to main content

roxy_loader_utils/
build_image.rs

1//! Helpers for producing bootable disk images for `roxy-loader`.
2//!
3//! The functions in this module are intended for host-side build tools that
4//! need to prepare a disk image containing the loader and a kernel binary.
5
6use std::{
7    fs::File,
8    io::{self, Seek, SeekFrom},
9    path::{Path, PathBuf},
10};
11
12use anyhow::Result;
13use cargo_artifact_dependency::ArtifactDependencyBuilder;
14use fatfs::{FileSystem, FormatVolumeOptions, FsOptions};
15use workspace_root::get_workspace_root;
16
17use crate::utils::cargo_target_dir;
18
19/// Builds a bootable disk image for a kernel artifact.
20pub fn build_image(kernel_binary: PathBuf) -> Result<PathBuf> {
21    let image_path = default_image_path()?;
22    build_image_from_paths(&image_path, &roxyloader_artifact()?, &kernel_binary)?;
23    Ok(image_path)
24}
25
26/// Builds a bootable disk image from explicit paths.
27///
28/// Use this when you want full control over the image path, loader path, or
29/// kernel path.
30///
31/// # Examples
32///
33/// ```
34/// use roxy_loader_utils::build_image::build_image_from_paths;
35///
36/// let temp = std::env::temp_dir().join(format!(
37///     "roxy-loader-doc-{}",
38///     std::process::id()
39/// ));
40/// std::fs::create_dir_all(&temp)?;
41///
42/// let image = temp.join("image.img");
43/// let loader = temp.join("loader.efi");
44/// let kernel = temp.join("kernel.bin");
45///
46/// std::fs::write(&loader, b"loader")?;
47/// std::fs::write(&kernel, b"kernel")?;
48///
49/// build_image_from_paths(&image, &loader, &kernel)?;
50///
51/// assert!(image.exists());
52///
53/// std::fs::remove_dir_all(&temp)?;
54/// # Ok::<(), Box<dyn std::error::Error>>(())
55/// ```
56pub fn build_image_from_paths(
57    image_path: &Path,
58    roxyloader_artifact: &Path,
59    kernel_binary: &Path,
60) -> Result<()> {
61    const IMAGE_SIZE: u64 = 64 * 1024 * 1024;
62
63    let mut image = open_image(image_path)?;
64
65    // truncate
66    image.set_len(IMAGE_SIZE)?;
67
68    fatfs::format_volume(&mut image, FormatVolumeOptions::new())?;
69
70    image.seek(SeekFrom::Start(0))?;
71
72    let fs = FileSystem::new(image, FsOptions::new())?;
73
74    let root_dir = fs.root_dir();
75    root_dir.create_dir("EFI")?;
76    let efi_dir = root_dir.open_dir("EFI")?;
77    efi_dir.create_dir("BOOT")?;
78    let boot_dir = efi_dir.open_dir("BOOT")?;
79
80    // Copies roxyloader artifact
81    let mut src = File::open(roxyloader_artifact)?;
82    let mut dst = boot_dir.create_file("BOOTX64.EFI")?;
83    io::copy(&mut src, &mut dst)?;
84
85    // Installs kernel binary
86    let mut dst = fs.root_dir().create_file("KERNEL")?;
87    let mut src = File::open(kernel_binary)?;
88    io::copy(&mut src, &mut dst)?;
89
90    Ok(())
91}
92
93/// Returns the default output path used by [`build_image`].
94pub fn default_image_path() -> Result<PathBuf> {
95    const IMAGE_NAME: &str = "image.img";
96    Ok(cargo_target_dir()?.join(IMAGE_NAME))
97}
98
99fn open_image(path: &Path) -> Result<File> {
100    Ok(File::options()
101        .read(true)
102        .write(true)
103        .create(true)
104        .truncate(true)
105        .open(path)?)
106}
107
108fn roxyloader_artifact() -> Result<PathBuf> {
109    Ok(ArtifactDependencyBuilder::default()
110        .crate_name("roxy-loader")
111        .path(get_workspace_root())
112        .version("0.1.2")
113        .target("x86_64-unknown-uefi")
114        .build()?
115        .resolve()?)
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use std::{
122        io::Read,
123        sync::atomic::{AtomicU64, Ordering},
124        time::{SystemTime, UNIX_EPOCH},
125    };
126
127    fn test_temp_dir() -> Result<std::path::PathBuf> {
128        static COUNTER: AtomicU64 = AtomicU64::new(0);
129
130        let unique = COUNTER.fetch_add(1, Ordering::Relaxed);
131        let dir = std::env::temp_dir().join(format!(
132            "roxy-loader-test-{}-{}",
133            std::process::id(),
134            SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos() + unique as u128
135        ));
136
137        std::fs::create_dir_all(&dir)?;
138        Ok(dir)
139    }
140
141    #[test]
142    fn build_image_contains_loader_and_kernel_payloads() -> Result<()> {
143        let dir = test_temp_dir()?;
144        let image_path = dir.join("image.img");
145        let loader_path = dir.join("loader.efi");
146        let kernel_path = dir.join("kernel.bin");
147
148        std::fs::write(&loader_path, b"loader-bytes")?;
149        std::fs::write(&kernel_path, b"kernel-bytes")?;
150
151        build_image_from_paths(&image_path, &loader_path, &kernel_path)?;
152
153        let image = File::options().read(true).write(true).open(&image_path)?;
154        let fs = FileSystem::new(image, FsOptions::new())?;
155
156        let root = fs.root_dir();
157        let efi_dir = root.open_dir("EFI")?;
158        let boot_dir = efi_dir.open_dir("BOOT")?;
159
160        let mut loader_file = boot_dir.open_file("BOOTX64.EFI")?;
161        let mut loader_bytes = Vec::new();
162        loader_file.read_to_end(&mut loader_bytes)?;
163        assert_eq!(loader_bytes, b"loader-bytes");
164
165        let mut kernel_file = root.open_file("KERNEL")?;
166        let mut kernel_bytes = Vec::new();
167        kernel_file.read_to_end(&mut kernel_bytes)?;
168        assert_eq!(kernel_bytes, b"kernel-bytes");
169
170        std::fs::remove_dir_all(&dir)?;
171        Ok(())
172    }
173
174    #[test]
175    fn build_image_truncates_existing_image_contents() -> Result<()> {
176        let dir = test_temp_dir()?;
177        let image_path = dir.join("image.img");
178        let loader_path = dir.join("loader.efi");
179        let kernel_path = dir.join("kernel.bin");
180
181        std::fs::write(&loader_path, b"a")?;
182        std::fs::write(&kernel_path, b"b")?;
183        std::fs::write(&image_path, b"stale-bytes")?;
184
185        build_image_from_paths(&image_path, &loader_path, &kernel_path)?;
186
187        let metadata = std::fs::metadata(&image_path)?;
188        assert_eq!(metadata.len(), 64 * 1024 * 1024);
189
190        std::fs::remove_dir_all(&dir)?;
191        Ok(())
192    }
193}