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, Clone)]
18pub struct MshvBoot {
19    /// Development-only x64 UEFI host boot application executable.
20    pub host_boot: ResolvedArtifact,
21    /// Linux kernel with mshv EFI-launch support.
22    pub kernel: ResolvedArtifact,
23    /// Prepared initrd and its entry point.
24    pub initrd: PetriInitrd,
25    /// Extracted artifact directory containing `boot/`.
26    pub boot_artifacts: ResolvedArtifact,
27    /// Linux options; Petri appends the initrd entry point and vsock driver selection.
28    pub kernel_cmdline: String,
29    /// Hypervisor options for HvLoader, without the loader DLL path.
30    pub hypervisor_cmdline: String,
31}
32
33impl MshvBoot {
34    /// Compose a temporary raw disk for the guest's boot drive.
35    ///
36    /// Copies the prepared initrd unchanged. This neither prepares the guest
37    /// userspace nor selects its agent, network, or trust store.
38    pub(crate) fn build(&self, use_virtio_vsock: bool) -> anyhow::Result<Disk> {
39        let image = tempfile::NamedTempFile::new()?.into_temp_path();
40        fs_err::remove_file(&image)?;
41        let boot = self.boot_artifacts.get().join("boot");
42        MshvImage {
43            host_boot: self.host_boot.get(),
44            payload: &boot,
45            kernel: self.kernel.get(),
46            initrd: self.initrd.path.as_ref(),
47            kernel_cmdline: &format!(
48                "{} {} rdinit={}",
49                self.kernel_cmdline,
50                crate::vm::linux_vsock_blacklist(use_virtio_vsock),
51                self.initrd.rdinit_param
52            ),
53            hypervisor_cmdline: &self.hypervisor_cmdline,
54        }
55        .build(&image)?;
56        Ok(Disk::Temporary(Arc::new(image)))
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use std::io::Read;
64    use test_with_tracing::test;
65
66    #[test]
67    fn explicit_inputs_build_guest_media_without_a_vm() -> anyhow::Result<()> {
68        let temp = tempfile::tempdir()?;
69        let boot = temp.path().join("boot");
70        fs_err::create_dir_all(boot.join("Windows/System32"))?;
71        for name in [
72            "HvLoader.efi",
73            "lxhvloader.dll",
74            "Windows/System32/hvix64.exe",
75            "Windows/System32/hvax64.exe",
76            "mshv_host_boot.efi",
77        ] {
78            fs_err::write(boot.join(name), name)?;
79        }
80        let kernel = temp.path().join("root-kernel");
81        let initrd_path = temp.path().join("prepared-initrd");
82        fs_err::write(&kernel, b"explicit root kernel")?;
83        let prepared = b"prepared initrd contents";
84        fs_err::write(&initrd_path, prepared)?;
85        let initrd = PetriInitrd {
86            path: crate::TempOrPersistentPath::Persistent(initrd_path.clone()),
87            rdinit_param: "/caller-init".into(),
88        };
89        let disk = MshvBoot {
90            host_boot: ResolvedArtifact::from_local_path(boot.join("mshv_host_boot.efi")),
91            kernel: ResolvedArtifact::from_local_path(kernel),
92            initrd,
93            boot_artifacts: ResolvedArtifact::from_local_path(temp.path().to_path_buf()),
94            kernel_cmdline: "test_option=caller".into(),
95            hypervisor_cmdline: "MSHV_SCHEDULER_TYPE=ROOT".into(),
96        }
97        .build(true)?;
98        let Disk::Temporary(path) = &disk else {
99            panic!("expected owned temporary disk")
100        };
101        let output = path.to_path_buf();
102        let mut image = fs_err::File::open(&output)?;
103        let gpt = gptman::GPT::read_from(&mut image, 512)?;
104        let partition = fscommon::StreamSlice::new(
105            image,
106            gpt[1].starting_lba * 512,
107            (gpt[1].ending_lba + 1) * 512,
108        )?;
109        let fs = fatfs::FileSystem::new(partition, fatfs::FsOptions::new())?;
110        let mut bytes = Vec::new();
111        fs.root_dir()
112            .open_file("bzImage")?
113            .read_to_end(&mut bytes)?;
114        assert_eq!(bytes, b"explicit root kernel");
115        bytes.clear();
116        fs.root_dir()
117            .open_file("initramfs.cpio")?
118            .read_to_end(&mut bytes)?;
119        assert_eq!(bytes, prepared);
120        let mut config = String::new();
121        fs.root_dir()
122            .open_file(mshv_host_boot::KERNEL_CMDLINE_FILE)?
123            .read_to_string(&mut config)?;
124        assert_eq!(
125            config,
126            "initrd=\\initramfs.cpio test_option=caller initcall_blacklist=hv_sock_init rdinit=/caller-init\n"
127        );
128        config.clear();
129        fs.root_dir()
130            .open_file(mshv_host_boot::HYPERVISOR_CMDLINE_FILE)?
131            .read_to_string(&mut config)?;
132        assert_eq!(config, "\\lxhvloader.dll MSHV_SCHEDULER_TYPE=ROOT\n");
133        fs.unmount()?;
134        assert_eq!(fs_err::read(&initrd_path)?, prepared);
135        drop(disk);
136        assert!(!output.exists());
137        Ok(())
138    }
139}