microsoft/openvmm

Public

mirrored fromhttps://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
8bd6d8fd5fcd4e4efdee54f2c1fd165a93cfb9c3

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

petri/src/disk_image.rs

173lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use anyhow::Context;
5use fatfs::FormatVolumeOptions;
6use fatfs::FsOptions;
7use petri_artifacts_common::artifacts as common_artifacts;
8use petri_artifacts_common::tags::MachineArch;
9use petri_artifacts_common::tags::OsFlavor;
10use petri_artifacts_core::AsArtifactHandle;
11use petri_artifacts_core::TestArtifacts;
12use std::io::Read;
13use std::io::Seek;
14use std::io::Write;
15use std::ops::Range;
16use std::path::Path;
17
18/// Builds a disk image containing pipette and any files needed for the guest VM
19/// to run pipette.
20pub fn build_agent_image(
21 arch: MachineArch,
22 os_flavor: OsFlavor,
23 resolver: &TestArtifacts,
24) -> anyhow::Result<std::fs::File> {
25 match os_flavor {
26 OsFlavor::Windows => {
27 // Windows doesn't use cloud-init, so we only need pipette
28 // (which is configured via the IMC hive).
29 build_disk_image(
30 b"pipette ",
31 &[(
32 "pipette.exe",
33 PathOrBinary::Path(&resolver.resolve(match arch {
34 MachineArch::X86_64 => common_artifacts::PIPETTE_WINDOWS_X64.erase(),
35 MachineArch::Aarch64 => common_artifacts::PIPETTE_WINDOWS_AARCH64.erase(),
36 })),
37 )],
38 )
39 }
40 OsFlavor::Linux => {
41 // Linux uses cloud-init, so we need to include the cloud-init
42 // configuration files as well.
43 build_disk_image(
44 b"cidata ", // cloud-init looks for a volume label of "cidata",
45 &[
46 (
47 "pipette",
48 PathOrBinary::Path(&resolver.resolve(match arch {
49 MachineArch::X86_64 => common_artifacts::PIPETTE_LINUX_X64.erase(),
50 MachineArch::Aarch64 => common_artifacts::PIPETTE_LINUX_AARCH64.erase(),
51 })),
52 ),
53 (
54 "meta-data",
55 PathOrBinary::Binary(include_bytes!("../guest-bootstrap/meta-data")),
56 ),
57 (
58 "user-data",
59 PathOrBinary::Binary(include_bytes!("../guest-bootstrap/user-data")),
60 ),
61 ],
62 )
63 }
64 OsFlavor::FreeBsd | OsFlavor::Uefi => {
65 // No pipette binary yet.
66 todo!()
67 }
68 }
69}
70
71enum PathOrBinary<'a> {
72 Path(&'a Path),
73 Binary(&'a [u8]),
74}
75
76fn build_disk_image(
77 volume_label: &[u8; 11],
78 files: &[(&str, PathOrBinary<'_>)],
79) -> anyhow::Result<std::fs::File> {
80 let mut file = tempfile::tempfile().context("failed to make temp file")?;
81 file.set_len(64 * 1024 * 1024)
82 .context("failed to set file size")?;
83
84 let partition_range =
85 build_gpt(&mut file, "CIDATA").context("failed to construct partition table")?;
86 build_fat32(
87 &mut fscommon::StreamSlice::new(&mut file, partition_range.start, partition_range.end)?,
88 volume_label,
89 files,
90 )
91 .context("failed to format volume")?;
92 Ok(file)
93}
94
95fn build_gpt(file: &mut (impl Read + Write + Seek), name: &str) -> anyhow::Result<Range<u64>> {
96 const SECTOR_SIZE: u64 = 512;
97 // EBD0A0A2-B9E5-4433-87C0-68B6B72699C7
98 const BDP_GUID: [u8; 16] = [
99 0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, 0x33, 0x44, 0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99,
100 0xC7,
101 ];
102 const PARTITION_GUID: [u8; 16] = [
103 0x55, 0x29, 0x65, 0x69, 0x3A, 0xA7, 0x98, 0x41, 0xBA, 0xBD, 0xB5, 0x50, 0x77, 0x14, 0xA1,
104 0xF3,
105 ];
106
107 let mut mbr = mbrman::MBR::new_from(file, SECTOR_SIZE as u32, [0xff; 4])?;
108 let mut gpt = gptman::GPT::new_from(file, SECTOR_SIZE, [0xff; 16])?;
109
110 // Set up the "Protective" Master Boot Record
111 let first_chs = mbrman::CHS::new(0, 0, 2);
112 let last_chs = mbrman::CHS::empty(); // This is wrong but doesn't really matter.
113 mbr[1] = mbrman::MBRPartitionEntry {
114 boot: mbrman::BOOT_INACTIVE,
115 first_chs,
116 sys: 0xEE, // GPT protective
117 last_chs,
118 starting_lba: 1,
119 sectors: gpt.header.last_usable_lba.try_into().unwrap_or(0xFFFFFFFF),
120 };
121 mbr.write_into(file)?;
122
123 file.rewind()?;
124
125 // Set up the GPT Partition Table Header
126 gpt[1] = gptman::GPTPartitionEntry {
127 partition_type_guid: BDP_GUID,
128 unique_partition_guid: PARTITION_GUID,
129 starting_lba: gpt.header.first_usable_lba,
130 ending_lba: gpt.header.last_usable_lba,
131 attribute_bits: 0,
132 partition_name: name.into(),
133 };
134 gpt.write_into(file)?;
135
136 // calculate the EFI partition's usable range
137 let partition_start_byte = gpt[1].starting_lba * SECTOR_SIZE;
138 let partition_num_bytes = (gpt[1].ending_lba - gpt[1].starting_lba) * SECTOR_SIZE;
139 Ok(partition_start_byte..partition_start_byte + partition_num_bytes)
140}
141
142fn build_fat32(
143 file: &mut (impl Read + Write + Seek),
144 volume_label: &[u8; 11],
145 files: &[(&str, PathOrBinary<'_>)],
146) -> anyhow::Result<()> {
147 fatfs::format_volume(
148 &mut *file,
149 FormatVolumeOptions::new()
150 .volume_label(*volume_label)
151 .fat_type(fatfs::FatType::Fat32),
152 )
153 .context("failed to format volume")?;
154 let fs = fatfs::FileSystem::new(file, FsOptions::new()).context("failed to open fs")?;
155 for (path, src) in files {
156 let mut dest = fs
157 .root_dir()
158 .create_file(path)
159 .context("failed to create file")?;
160 match *src {
161 PathOrBinary::Path(src_path) => {
162 let mut src = fs_err::File::open(src_path)?;
163 std::io::copy(&mut src, &mut dest).context("failed to copy file")?;
164 }
165 PathOrBinary::Binary(src_data) => {
166 dest.write_all(src_data).context("failed to write file")?;
167 }
168 }
169 dest.flush().context("failed to flush file")?;
170 }
171 fs.unmount().context("failed to unmount fs")?;
172 Ok(())
173}
174