roxy_loader_utils/
build_image.rs1#![allow(unused_mut)]
7
8use std::{
9 fs::File,
10 io::{self, Seek, SeekFrom},
11 path::{Path, PathBuf},
12};
13
14use anyhow::Result;
15use cargo_artifact_dependency::ArtifactDependencyBuilder;
16use fatfs::{FileSystem, FormatVolumeOptions, FsOptions};
17
18use crate::utils::cargo_target_dir;
19
20const ROXY_LOADER_ARTIFACT_VERSION: &str = "0.2.2";
21
22pub fn build_image(kernel_binary: PathBuf) -> Result<PathBuf> {
24 let image_path = default_image_path()?;
25 build_image_from_paths(&image_path, &roxyloader_artifact()?, &kernel_binary)?;
26 Ok(image_path)
27}
28
29pub fn build_image_from_paths(
60 image_path: &Path,
61 roxyloader_artifact: &Path,
62 kernel_binary: &Path,
63) -> Result<()> {
64 const IMAGE_SIZE: u64 = 64 * 1024 * 1024;
65
66 let mut image = open_image(image_path)?;
67
68 image.set_len(IMAGE_SIZE)?;
70
71 fatfs::format_volume(&mut image, FormatVolumeOptions::new())?;
72
73 image.seek(SeekFrom::Start(0))?;
74
75 let fs = FileSystem::new(image, FsOptions::new())?;
76
77 let root_dir = fs.root_dir();
78 root_dir.create_dir("EFI")?;
79 let efi_dir = root_dir.open_dir("EFI")?;
80 efi_dir.create_dir("BOOT")?;
81 let boot_dir = efi_dir.open_dir("BOOT")?;
82
83 let mut src = File::open(roxyloader_artifact)?;
85 let mut dst = boot_dir.create_file("BOOTX64.EFI")?;
86 io::copy(&mut src, &mut dst)?;
87
88 let mut dst = fs.root_dir().create_file("KERNEL")?;
90 let mut src = File::open(kernel_binary)?;
91 io::copy(&mut src, &mut dst)?;
92
93 Ok(())
94}
95
96pub fn default_image_path() -> Result<PathBuf> {
98 const IMAGE_NAME: &str = "image.img";
99 Ok(cargo_target_dir()?.join(IMAGE_NAME))
100}
101
102fn open_image(path: &Path) -> Result<File> {
103 Ok(File::options()
104 .read(true)
105 .write(true)
106 .create(true)
107 .truncate(true)
108 .open(path)?)
109}
110
111fn roxyloader_artifact() -> Result<PathBuf> {
112 let mut builder = ArtifactDependencyBuilder::default()
113 .crate_name("roxy-loader")
114 .version(ROXY_LOADER_ARTIFACT_VERSION)
115 .target("x86_64-unknown-uefi");
116
117 #[cfg(feature = "local-dev")]
118 {
119 use workspace_root::get_workspace_root;
120 builder = builder.path(get_workspace_root());
121 }
122
123 Ok(builder.build()?.resolve()?)
124}
125
126#[cfg(test)]
127mod tests {
128 use workspace_root::get_workspace_root;
129
130 use super::*;
131 use std::{
132 io::Read,
133 sync::atomic::{AtomicU64, Ordering},
134 time::{SystemTime, UNIX_EPOCH},
135 };
136
137 fn test_temp_dir() -> Result<std::path::PathBuf> {
138 static COUNTER: AtomicU64 = AtomicU64::new(0);
139
140 let unique = COUNTER.fetch_add(1, Ordering::Relaxed);
141 let dir = std::env::temp_dir().join(format!(
142 "roxy-loader-test-{}-{}",
143 std::process::id(),
144 SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos() + unique as u128
145 ));
146
147 std::fs::create_dir_all(&dir)?;
148 Ok(dir)
149 }
150
151 #[test]
152 fn roxyloader_artifact_version_matches_package_version() -> Result<()> {
153 let metadata = cargo_metadata::MetadataCommand::new()
154 .manifest_path(get_workspace_root().join("Cargo.toml"))
155 .exec()?;
156 let package = metadata
157 .packages
158 .iter()
159 .find(|package| package.name == "roxy-loader")
160 .expect("workspace should contain the roxy-loader package");
161
162 assert_eq!(
163 ROXY_LOADER_ARTIFACT_VERSION,
164 package.version.to_string(),
165 "update ROXY_LOADER_ARTIFACT_VERSION when bumping roxy-loader"
166 );
167
168 Ok(())
169 }
170
171 #[test]
172 fn roxyloader_artifact_resolves_to_valid_file() -> Result<()> {
173 let artifact_path = roxyloader_artifact()?;
174 let metadata = std::fs::metadata(&artifact_path)?;
175
176 assert!(
177 metadata.is_file(),
178 "roxy-loader artifact path should be a file: {}",
179 artifact_path.display()
180 );
181 assert!(
182 metadata.len() > 0,
183 "roxy-loader artifact should not be empty: {}",
184 artifact_path.display()
185 );
186
187 Ok(())
188 }
189
190 #[test]
191 fn build_image_contains_loader_and_kernel_payloads() -> Result<()> {
192 let dir = test_temp_dir()?;
193 let image_path = dir.join("image.img");
194 let loader_path = dir.join("loader.efi");
195 let kernel_path = dir.join("kernel.bin");
196
197 std::fs::write(&loader_path, b"loader-bytes")?;
198 std::fs::write(&kernel_path, b"kernel-bytes")?;
199
200 build_image_from_paths(&image_path, &loader_path, &kernel_path)?;
201
202 let image = File::options().read(true).write(true).open(&image_path)?;
203 let fs = FileSystem::new(image, FsOptions::new())?;
204
205 let root = fs.root_dir();
206 let efi_dir = root.open_dir("EFI")?;
207 let boot_dir = efi_dir.open_dir("BOOT")?;
208
209 let mut loader_file = boot_dir.open_file("BOOTX64.EFI")?;
210 let mut loader_bytes = Vec::new();
211 loader_file.read_to_end(&mut loader_bytes)?;
212 assert_eq!(loader_bytes, b"loader-bytes");
213
214 let mut kernel_file = root.open_file("KERNEL")?;
215 let mut kernel_bytes = Vec::new();
216 kernel_file.read_to_end(&mut kernel_bytes)?;
217 assert_eq!(kernel_bytes, b"kernel-bytes");
218
219 std::fs::remove_dir_all(&dir)?;
220 Ok(())
221 }
222
223 #[test]
224 fn build_image_truncates_existing_image_contents() -> Result<()> {
225 let dir = test_temp_dir()?;
226 let image_path = dir.join("image.img");
227 let loader_path = dir.join("loader.efi");
228 let kernel_path = dir.join("kernel.bin");
229
230 std::fs::write(&loader_path, b"a")?;
231 std::fs::write(&kernel_path, b"b")?;
232 std::fs::write(&image_path, b"stale-bytes")?;
233
234 build_image_from_paths(&image_path, &loader_path, &kernel_path)?;
235
236 let metadata = std::fs::metadata(&image_path)?;
237 assert_eq!(metadata.len(), 64 * 1024 * 1024);
238
239 std::fs::remove_dir_all(&dir)?;
240 Ok(())
241 }
242}