1use anyhow::Context;
7use fatfs::FormatVolumeOptions;
8use fatfs::FsOptions;
9use petri_artifacts_common::artifacts as common_artifacts;
10use petri_artifacts_common::tags::MachineArch;
11use petri_artifacts_common::tags::OsFlavor;
12use petri_artifacts_core::ArtifactResolver;
13use petri_artifacts_core::ResolvedArtifact;
14use std::io::Read;
15use std::io::Seek;
16use std::io::Write;
17use std::ops::Range;
18use std::path::Path;
19
20pub struct AgentImage {
22 os_flavor: OsFlavor,
23 pipette: Option<ResolvedArtifact>,
24 extras: Vec<(String, ResolvedArtifact)>,
25}
26
27impl AgentImage {
28 pub fn new(os_flavor: OsFlavor) -> Self {
30 Self {
31 os_flavor,
32 pipette: None,
33 extras: Vec::new(),
34 }
35 }
36
37 pub fn with_pipette(mut self, resolver: &ArtifactResolver<'_>, arch: MachineArch) -> Self {
39 self.pipette = match (self.os_flavor, arch) {
40 (OsFlavor::Windows, MachineArch::X86_64) => Some(
41 resolver
42 .require(common_artifacts::PIPETTE_WINDOWS_X64)
43 .erase(),
44 ),
45 (OsFlavor::Linux, MachineArch::X86_64) => Some(
46 resolver
47 .require(common_artifacts::PIPETTE_LINUX_X64)
48 .erase(),
49 ),
50 (OsFlavor::Windows, MachineArch::Aarch64) => Some(
51 resolver
52 .require(common_artifacts::PIPETTE_WINDOWS_AARCH64)
53 .erase(),
54 ),
55 (OsFlavor::Linux, MachineArch::Aarch64) => Some(
56 resolver
57 .require(common_artifacts::PIPETTE_LINUX_AARCH64)
58 .erase(),
59 ),
60 (OsFlavor::FreeBsd | OsFlavor::Uefi, _) => {
61 todo!("No pipette binary yet for os");
62 }
63 };
64 self
65 }
66
67 pub fn contains_pipette(&self) -> bool {
69 self.pipette.is_some()
70 }
71
72 pub fn add_file(&mut self, name: &str, artifact: ResolvedArtifact) {
74 self.extras.push((name.to_string(), artifact));
75 }
76
77 pub fn build(&self) -> anyhow::Result<Option<tempfile::NamedTempFile>> {
80 let mut files = self
81 .extras
82 .iter()
83 .map(|(name, artifact)| (name.as_str(), PathOrBinary::Path(artifact.as_ref())))
84 .collect::<Vec<_>>();
85 let volume_label = match self.os_flavor {
86 OsFlavor::Windows => {
87 if let Some(pipette) = self.pipette.as_ref() {
90 files.push(("pipette.exe", PathOrBinary::Path(pipette.as_ref())));
91 }
92 b"pipette "
93 }
94 OsFlavor::Linux => {
95 if let Some(pipette) = self.pipette.as_ref() {
96 files.push(("pipette", PathOrBinary::Path(pipette.as_ref())));
97 }
98 files.extend([
101 (
102 "meta-data",
103 PathOrBinary::Binary(include_bytes!("../guest-bootstrap/meta-data")),
104 ),
105 (
106 "user-data",
107 if self.pipette.is_some() {
108 PathOrBinary::Binary(include_bytes!("../guest-bootstrap/user-data"))
109 } else {
110 PathOrBinary::Binary(include_bytes!(
111 "../guest-bootstrap/user-data-no-agent"
112 ))
113 },
114 ),
115 (
118 "network-config",
119 PathOrBinary::Binary(include_bytes!("../guest-bootstrap/network-config")),
120 ),
121 ]);
122 b"cidata " }
124 _ => b"cidata ",
126 };
127
128 if files.is_empty() {
129 Ok(None)
130 } else {
131 Ok(Some(build_disk_image(volume_label, &files)?))
132 }
133 }
134}
135
136enum PathOrBinary<'a> {
137 Path(&'a Path),
138 Binary(&'a [u8]),
139}
140
141fn build_disk_image(
142 volume_label: &[u8; 11],
143 files: &[(&str, PathOrBinary<'_>)],
144) -> anyhow::Result<tempfile::NamedTempFile> {
145 let mut file = tempfile::NamedTempFile::new()?;
146 file.as_file()
147 .set_len(64 * 1024 * 1024)
148 .context("failed to set file size")?;
149
150 let partition_range =
151 build_gpt(&mut file, "CIDATA").context("failed to construct partition table")?;
152 build_fat32(
153 &mut fscommon::StreamSlice::new(&mut file, partition_range.start, partition_range.end)?,
154 volume_label,
155 files,
156 )
157 .context("failed to format volume")?;
158 Ok(file)
159}
160
161fn build_gpt(file: &mut (impl Read + Write + Seek), name: &str) -> anyhow::Result<Range<u64>> {
162 const SECTOR_SIZE: u64 = 512;
163 const BDP_GUID: [u8; 16] = [
165 0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, 0x33, 0x44, 0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99,
166 0xC7,
167 ];
168 const PARTITION_GUID: [u8; 16] = [
169 0x55, 0x29, 0x65, 0x69, 0x3A, 0xA7, 0x98, 0x41, 0xBA, 0xBD, 0xB5, 0x50, 0x77, 0x14, 0xA1,
170 0xF3,
171 ];
172
173 let mut mbr = mbrman::MBR::new_from(file, SECTOR_SIZE as u32, [0xff; 4])?;
174 let mut gpt = gptman::GPT::new_from(file, SECTOR_SIZE, [0xff; 16])?;
175
176 let first_chs = mbrman::CHS::new(0, 0, 2);
178 let last_chs = mbrman::CHS::empty(); mbr[1] = mbrman::MBRPartitionEntry {
180 boot: mbrman::BOOT_INACTIVE,
181 first_chs,
182 sys: 0xEE, last_chs,
184 starting_lba: 1,
185 sectors: gpt.header.last_usable_lba.try_into().unwrap_or(0xFFFFFFFF),
186 };
187 mbr.write_into(file)?;
188
189 file.rewind()?;
190
191 gpt[1] = gptman::GPTPartitionEntry {
193 partition_type_guid: BDP_GUID,
194 unique_partition_guid: PARTITION_GUID,
195 starting_lba: gpt.header.first_usable_lba,
196 ending_lba: gpt.header.last_usable_lba,
197 attribute_bits: 0,
198 partition_name: name.into(),
199 };
200 gpt.write_into(file)?;
201
202 let partition_start_byte = gpt[1].starting_lba * SECTOR_SIZE;
204 let partition_num_bytes = (gpt[1].ending_lba - gpt[1].starting_lba) * SECTOR_SIZE;
205 Ok(partition_start_byte..partition_start_byte + partition_num_bytes)
206}
207
208fn build_fat32(
209 file: &mut (impl Read + Write + Seek),
210 volume_label: &[u8; 11],
211 files: &[(&str, PathOrBinary<'_>)],
212) -> anyhow::Result<()> {
213 fatfs::format_volume(
214 &mut *file,
215 FormatVolumeOptions::new()
216 .volume_label(*volume_label)
217 .fat_type(fatfs::FatType::Fat32),
218 )
219 .context("failed to format volume")?;
220 let fs = fatfs::FileSystem::new(file, FsOptions::new()).context("failed to open fs")?;
221 for (path, src) in files {
222 let mut dest = fs
223 .root_dir()
224 .create_file(path)
225 .context("failed to create file")?;
226 match *src {
227 PathOrBinary::Path(src_path) => {
228 let mut src = fs_err::File::open(src_path)?;
229 std::io::copy(&mut src, &mut dest).context("failed to copy file")?;
230 }
231 PathOrBinary::Binary(src_data) => {
232 dest.write_all(src_data).context("failed to write file")?;
233 }
234 }
235 dest.flush().context("failed to flush file")?;
236 }
237 fs.unmount().context("failed to unmount fs")?;
238 Ok(())
239}