1use std::io::Write;
8use std::path::{Path, PathBuf};
9
10use rvf_runtime::RvfStore;
11use rvf_types::kernel::KernelHeader;
12
13use crate::error::LaunchError;
14
15pub struct ExtractedKernel {
17 pub kernel_path: PathBuf,
19 pub initramfs_path: Option<PathBuf>,
21 pub header: KernelHeader,
23 pub cmdline: String,
25 _tempdir: tempfile::TempDir,
27}
28
29impl std::fmt::Debug for ExtractedKernel {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 f.debug_struct("ExtractedKernel")
32 .field("kernel_path", &self.kernel_path)
33 .field("initramfs_path", &self.initramfs_path)
34 .field("cmdline", &self.cmdline)
35 .finish_non_exhaustive()
36 }
37}
38
39pub fn extract_kernel(rvf_path: &Path) -> Result<ExtractedKernel, LaunchError> {
44 let store = RvfStore::open_readonly(rvf_path)
45 .map_err(|e| LaunchError::KernelExtraction(format!("failed to open store: {e:?}")))?;
46
47 let (header_bytes, remainder) = store
48 .extract_kernel()
49 .map_err(|e| LaunchError::KernelExtraction(format!("segment read error: {e:?}")))?
50 .ok_or_else(|| LaunchError::NoKernelSegment {
51 path: rvf_path.to_path_buf(),
52 })?;
53
54 if header_bytes.len() < 128 {
55 return Err(LaunchError::KernelExtraction(
56 "KernelHeader too short".into(),
57 ));
58 }
59
60 let mut hdr_array = [0u8; 128];
61 hdr_array.copy_from_slice(&header_bytes[..128]);
62 let header = KernelHeader::from_bytes(&hdr_array)
63 .map_err(|e| LaunchError::KernelExtraction(format!("bad KernelHeader: {e:?}")))?;
64
65 let image_size = header.image_size as usize;
78 let cmdline_length = header.cmdline_length as usize;
79
80 let (kernel_image, cmdline) = if image_size > 0 && image_size <= remainder.len() {
82 let img = &remainder[..image_size];
83 let cmd = if cmdline_length > 0 && image_size + cmdline_length <= remainder.len() {
84 String::from_utf8_lossy(&remainder[image_size..image_size + cmdline_length])
85 .into_owned()
86 } else {
87 String::new()
88 };
89 (img, cmd)
90 } else {
91 (&remainder[..], String::new())
93 };
94
95 let tempdir = tempfile::tempdir().map_err(LaunchError::TempFile)?;
97
98 let kernel_file_path = tempdir.path().join("vmlinuz");
99 {
100 let mut f =
101 std::fs::File::create(&kernel_file_path).map_err(LaunchError::TempFile)?;
102 f.write_all(kernel_image).map_err(LaunchError::TempFile)?;
103 f.sync_all().map_err(LaunchError::TempFile)?;
104 }
105
106 let initramfs_path = None;
110
111 Ok(ExtractedKernel {
112 kernel_path: kernel_file_path,
113 initramfs_path,
114 header,
115 cmdline,
116 _tempdir: tempdir,
117 })
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use rvf_runtime::options::RvfOptions;
124 use rvf_types::kernel::KernelArch;
125
126 #[test]
127 fn extract_from_store_with_kernel() {
128 let dir = tempfile::tempdir().unwrap();
129 let rvf_path = dir.path().join("test.rvf");
130
131 let opts = RvfOptions {
132 dimension: 4,
133 ..Default::default()
134 };
135 let mut store = RvfStore::create(&rvf_path, opts).unwrap();
136
137 let image = b"MZ\x00fake-kernel-image-for-testing";
138 store
139 .embed_kernel(
140 KernelArch::X86_64 as u8,
141 0x01,
142 0,
143 image,
144 8080,
145 Some("console=ttyS0"),
146 )
147 .unwrap();
148 store.close().unwrap();
149
150 let extracted = extract_kernel(&rvf_path).unwrap();
151 assert!(extracted.kernel_path.exists());
152
153 let on_disk = std::fs::read(&extracted.kernel_path).unwrap();
154 assert_eq!(on_disk, image);
155 assert_eq!(extracted.header.api_port, 8080);
156 assert_eq!(extracted.cmdline, "console=ttyS0");
157 }
158
159 #[test]
160 fn extract_kernel_no_cmdline() {
161 let dir = tempfile::tempdir().unwrap();
162 let rvf_path = dir.path().join("no_cmd.rvf");
163
164 let opts = RvfOptions {
165 dimension: 4,
166 ..Default::default()
167 };
168 let mut store = RvfStore::create(&rvf_path, opts).unwrap();
169
170 let image = b"fake-kernel";
171 store
172 .embed_kernel(
173 KernelArch::X86_64 as u8,
174 0x01,
175 0,
176 image,
177 9090,
178 None,
179 )
180 .unwrap();
181 store.close().unwrap();
182
183 let extracted = extract_kernel(&rvf_path).unwrap();
184 let on_disk = std::fs::read(&extracted.kernel_path).unwrap();
185 assert_eq!(on_disk, image);
186 assert!(extracted.cmdline.is_empty());
187 }
188
189 #[test]
190 fn extract_returns_error_when_no_kernel() {
191 let dir = tempfile::tempdir().unwrap();
192 let rvf_path = dir.path().join("no_kernel.rvf");
193
194 let opts = RvfOptions {
195 dimension: 4,
196 ..Default::default()
197 };
198 let store = RvfStore::create(&rvf_path, opts).unwrap();
199 store.close().unwrap();
200
201 let result = extract_kernel(&rvf_path);
202 assert!(result.is_err());
203 match result.unwrap_err() {
204 LaunchError::NoKernelSegment { .. } => {}
205 other => panic!("expected NoKernelSegment, got: {other}"),
206 }
207 }
208}