roxy_loader_utils/
build_image.rs1use 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
19pub 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
26pub 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 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 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 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
93pub 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")
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}