1use crate::Disk;
7use crate::PetriInitrd;
8use crate::ResolvedArtifact;
9use crate::mshv_image::MshvImage;
10use std::sync::Arc;
11
12#[derive(Debug)]
18pub struct MshvBoot {
19 pub kernel: ResolvedArtifact,
21 pub initrd: PetriInitrd,
23 pub boot_artifacts: ResolvedArtifact,
25}
26
27impl MshvBoot {
28 pub(crate) fn build(&self) -> anyhow::Result<Disk> {
33 let image = tempfile::NamedTempFile::new()?.into_temp_path();
34 fs_err::remove_file(&image)?;
35 let boot = self.boot_artifacts.get().join("boot");
36 MshvImage {
37 shim: &boot.join("EFI/BOOT/bootx64.efi"),
38 grub: &boot.join("EFI/BOOT/grubx64.efi"),
39 payload: &boot,
40 kernel: self.kernel.get(),
41 initrd: self.initrd.path.as_ref(),
42 kernel_cmdline: &format!("console=ttyS0 rdinit={} panic=-1", self.initrd.rdinit_param),
43 }
44 .build(&image)?;
45 Ok(Disk::Temporary(Arc::new(image)))
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use std::io::Read;
53 use test_with_tracing::test;
54
55 #[test]
56 fn explicit_inputs_build_guest_media_without_a_vm() -> anyhow::Result<()> {
57 let temp = tempfile::tempdir()?;
58 let boot = temp.path().join("boot");
59 fs_err::create_dir_all(boot.join("Windows/System32"))?;
60 fs_err::create_dir_all(boot.join("EFI/BOOT"))?;
61 for name in [
62 "HvLoader.efi",
63 "lxhvloader.dll",
64 "Windows/System32/hvix64.exe",
65 "Windows/System32/hvax64.exe",
66 "EFI/BOOT/bootx64.efi",
67 "EFI/BOOT/grubx64.efi",
68 ] {
69 fs_err::write(boot.join(name), name)?;
70 }
71 let kernel = temp.path().join("root-kernel");
72 let initrd_path = temp.path().join("prepared-initrd");
73 fs_err::write(&kernel, b"explicit root kernel")?;
74 let prepared = b"prepared initrd contents";
75 fs_err::write(&initrd_path, prepared)?;
76 let initrd = PetriInitrd {
77 path: crate::TempOrPersistentPath::Persistent(initrd_path.clone()),
78 rdinit_param: "/caller-init".into(),
79 };
80 let disk = MshvBoot {
81 kernel: ResolvedArtifact::from_local_path(kernel),
82 initrd,
83 boot_artifacts: ResolvedArtifact::from_local_path(temp.path().to_path_buf()),
84 }
85 .build()?;
86 let Disk::Temporary(path) = &disk else {
87 panic!("expected owned temporary disk")
88 };
89 let output = path.to_path_buf();
90 let mut image = fs_err::File::open(&output)?;
91 let gpt = gptman::GPT::read_from(&mut image, 512)?;
92 let partition = fscommon::StreamSlice::new(
93 image,
94 gpt[1].starting_lba * 512,
95 (gpt[1].ending_lba + 1) * 512,
96 )?;
97 let fs = fatfs::FileSystem::new(partition, fatfs::FsOptions::new())?;
98 let mut bytes = Vec::new();
99 fs.root_dir()
100 .open_file("bzImage")?
101 .read_to_end(&mut bytes)?;
102 assert_eq!(bytes, b"explicit root kernel");
103 bytes.clear();
104 fs.root_dir()
105 .open_file("initramfs.cpio")?
106 .read_to_end(&mut bytes)?;
107 assert_eq!(bytes, prepared);
108 let mut config = String::new();
109 fs.root_dir()
110 .open_file("boot/grub2/grub.cfg")?
111 .read_to_string(&mut config)?;
112 assert!(config.contains("'rdinit=/caller-init'"));
113 fs.unmount()?;
114 assert_eq!(fs_err::read(&initrd_path)?, prepared);
115 drop(disk);
116 assert!(!output.exists());
117 Ok(())
118 }
119}