Skip to main content

petri/
mshv_boot.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Inputs for an mshv root partition booted through UEFI.
5
6use crate::Disk;
7use crate::PetriInitrd;
8use crate::ResolvedArtifact;
9use crate::mshv_image::MshvImage;
10use std::sync::Arc;
11
12/// Explicit boot media for an x86-64 mshv root partition.
13///
14/// Pass this recipe to [`crate::UefiGuest::Mshv`]. Petri composes and owns the
15/// boot disk when preparing the VM. The caller supplies UEFI firmware and
16/// configures nested virtualization, storage, networking, and file shares.
17#[derive(Debug)]
18pub struct MshvBoot {
19    /// Linux kernel with mshv EFI-launch support.
20    pub kernel: ResolvedArtifact,
21    /// Prepared initrd and its entry point.
22    pub initrd: PetriInitrd,
23    /// Extracted artifact directory containing `boot/`.
24    pub boot_artifacts: ResolvedArtifact,
25}
26
27impl MshvBoot {
28    /// Compose a temporary raw disk for the guest's boot drive.
29    ///
30    /// Copies the prepared initrd unchanged. This neither prepares the guest
31    /// userspace nor selects its agent, network, or trust store.
32    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}