blob: 7b5a25890609eceb69d600ad66990abadeb0e271 [file] [log] [blame]
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +00001// Copyright 2021, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Andrew Walbran3eca16c2021-06-14 11:15:14 +000015//! Functions for creating a composite disk image.
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +000016
Andrew Walbran3eca16c2021-06-14 11:15:14 +000017use crate::gpt::{
18 write_gpt_header, write_protective_mbr, GptPartitionEntry, GPT_BEGINNING_SIZE, GPT_END_SIZE,
19 GPT_HEADER_SIZE, GPT_NUM_PARTITIONS, GPT_PARTITION_ENTRY_SIZE, SECTOR_SIZE,
20};
21use android_system_virtualizationservice::aidl::android::system::virtualizationservice::Partition::Partition;
22use anyhow::{anyhow, bail, Context, Error};
23use crc32fast::Hasher;
24use disk::create_disk_file;
25use log::{trace, warn};
26use protobuf::Message;
27use protos::cdisk_spec::{ComponentDisk, CompositeDisk, ReadWriteCapability};
28use std::convert::TryInto;
29use std::fs::{File, OpenOptions};
30use std::io::Write;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +000031use std::os::unix::io::AsRawFd;
Andrew Walbran3eca16c2021-06-14 11:15:14 +000032use std::path::{Path, PathBuf};
33use uuid::Uuid;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +000034
Andrew Walbran3eca16c2021-06-14 11:15:14 +000035/// A magic string placed at the beginning of a composite disk file to identify it.
36const CDISK_MAGIC: &str = "composite_disk\x1d";
37/// The version of the composite disk format supported by this implementation.
38const COMPOSITE_DISK_VERSION: u64 = 1;
39/// The amount of padding needed between the last partition entry and the first partition, to align
40/// the partition appropriately. The two sectors are for the MBR and the GPT header.
41const PARTITION_ALIGNMENT_SIZE: usize = GPT_BEGINNING_SIZE as usize
42 - 2 * SECTOR_SIZE as usize
43 - GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize;
44const HEADER_PADDING_LENGTH: usize = SECTOR_SIZE as usize - GPT_HEADER_SIZE as usize;
45// Keep all partitions 4k aligned for performance.
46const PARTITION_SIZE_SHIFT: u8 = 12;
47// Keep the disk size a multiple of 64k for crosvm's virtio_blk driver.
48const DISK_SIZE_SHIFT: u8 = 16;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +000049
Andrew Walbran3eca16c2021-06-14 11:15:14 +000050const LINUX_FILESYSTEM_GUID: Uuid = Uuid::from_u128(0x0FC63DAF_8483_4772_8E79_3D69D8477DE4);
51const EFI_SYSTEM_PARTITION_GUID: Uuid = Uuid::from_u128(0xC12A7328_F81F_11D2_BA4B_00A0C93EC93B);
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +000052
Andrew Walbran1fee0d82021-06-17 14:49:25 +000053/// Information about a single image file to be included in a partition.
54#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct PartitionFileInfo {
56 path: PathBuf,
57 size: u64,
58}
59
60/// Information about a partition to create, including the set of image files which make it up.
Andrew Walbran3eca16c2021-06-14 11:15:14 +000061#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct PartitionInfo {
63 label: String,
Andrew Walbran1fee0d82021-06-17 14:49:25 +000064 files: Vec<PartitionFileInfo>,
Andrew Walbran3eca16c2021-06-14 11:15:14 +000065 partition_type: ImagePartitionType,
66 writable: bool,
Andrew Walbran3eca16c2021-06-14 11:15:14 +000067}
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +000068
Andrew Walbran3eca16c2021-06-14 11:15:14 +000069/// Round `val` up to the next multiple of 2**`align_log`.
70fn align_to_power_of_2(val: u64, align_log: u8) -> u64 {
71 let align = 1 << align_log;
72 ((val + (align - 1)) / align) * align
73}
74
75impl PartitionInfo {
76 fn aligned_size(&self) -> u64 {
Andrew Walbran1fee0d82021-06-17 14:49:25 +000077 align_to_power_of_2(self.files.iter().map(|file| file.size).sum(), PARTITION_SIZE_SHIFT)
Andrew Walbran3eca16c2021-06-14 11:15:14 +000078 }
79}
80
81/// The type of partition.
82#[allow(dead_code)]
83#[derive(Copy, Clone, Debug, Eq, PartialEq)]
84pub enum ImagePartitionType {
85 LinuxFilesystem,
86 EfiSystemPartition,
87}
88
89impl ImagePartitionType {
90 fn guid(self) -> Uuid {
91 match self {
92 Self::LinuxFilesystem => LINUX_FILESYSTEM_GUID,
93 Self::EfiSystemPartition => EFI_SYSTEM_PARTITION_GUID,
94 }
95 }
96}
97
98/// Write protective MBR and primary GPT table.
99fn write_beginning(
100 file: &mut impl Write,
101 disk_guid: Uuid,
102 partitions: &[u8],
103 partition_entries_crc32: u32,
104 secondary_table_offset: u64,
105 disk_size: u64,
106) -> Result<(), Error> {
107 // Write the protective MBR to the first sector.
108 write_protective_mbr(file, disk_size)?;
109
110 // Write the GPT header, and pad out to the end of the sector.
111 write_gpt_header(file, disk_guid, partition_entries_crc32, secondary_table_offset, false)?;
112 file.write_all(&[0; HEADER_PADDING_LENGTH])?;
113
114 // Write partition entries, including unused ones.
115 file.write_all(partitions)?;
116
117 // Write zeroes to align the first partition appropriately.
118 file.write_all(&[0; PARTITION_ALIGNMENT_SIZE])?;
119
120 Ok(())
121}
122
123/// Write secondary GPT table.
124fn write_end(
125 file: &mut impl Write,
126 disk_guid: Uuid,
127 partitions: &[u8],
128 partition_entries_crc32: u32,
129 secondary_table_offset: u64,
130 disk_size: u64,
131) -> Result<(), Error> {
132 // Write partition entries, including unused ones.
133 file.write_all(partitions)?;
134
135 // Write the GPT header, and pad out to the end of the sector.
136 write_gpt_header(file, disk_guid, partition_entries_crc32, secondary_table_offset, true)?;
137 file.write_all(&[0; HEADER_PADDING_LENGTH])?;
138
139 // Pad out to the aligned disk size.
140 let used_disk_size = secondary_table_offset + GPT_END_SIZE;
141 let padding = disk_size - used_disk_size;
142 file.write_all(&vec![0; padding as usize])?;
143
144 Ok(())
145}
146
147/// Create the `GptPartitionEntry` for the given partition.
148fn create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry {
149 let mut partition_name: Vec<u16> = partition.label.encode_utf16().collect();
150 partition_name.resize(36, 0);
151
152 GptPartitionEntry {
153 partition_type_guid: partition.partition_type.guid(),
154 unique_partition_guid: Uuid::new_v4(),
155 first_lba: offset / SECTOR_SIZE,
156 last_lba: (offset + partition.aligned_size()) / SECTOR_SIZE - 1,
157 attributes: 0,
158 partition_name: partition_name.try_into().unwrap(),
159 }
160}
161
162/// Create one or more `ComponentDisk` proto messages for the given partition.
163fn create_component_disks(
164 partition: &PartitionInfo,
165 offset: u64,
166 header_path: &str,
167) -> Result<Vec<ComponentDisk>, Error> {
168 let aligned_size = partition.aligned_size();
169
Andrew Walbran1fee0d82021-06-17 14:49:25 +0000170 if partition.files.is_empty() {
171 bail!("No image files for partition {:?}", partition);
172 }
173 let mut file_size_sum = 0;
174 let mut component_disks = vec![];
175 for file in &partition.files {
176 component_disks.push(ComponentDisk {
177 offset: offset + file_size_sum,
178 file_path: file.path.to_str().context("Invalid partition path")?.to_string(),
179 read_write_capability: if partition.writable {
180 ReadWriteCapability::READ_WRITE
181 } else {
182 ReadWriteCapability::READ_ONLY
183 },
184 ..ComponentDisk::new()
185 });
186 file_size_sum += file.size;
187 }
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000188
Andrew Walbran1fee0d82021-06-17 14:49:25 +0000189 if file_size_sum != aligned_size {
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000190 if partition.writable {
191 bail!(
192 "Read-write partition {:?} size is not a multiple of {}.",
193 partition,
194 1 << PARTITION_SIZE_SHIFT
195 );
196 } else {
197 // Fill in the gap by reusing the header file, because we know it is always bigger
198 // than the alignment size (i.e. GPT_BEGINNING_SIZE > 1 << PARTITION_SIZE_SHIFT).
199 warn!(
200 "Read-only partition {:?} size is not a multiple of {}, filling gap.",
201 partition,
202 1 << PARTITION_SIZE_SHIFT
203 );
204 component_disks.push(ComponentDisk {
Andrew Walbran1fee0d82021-06-17 14:49:25 +0000205 offset: offset + file_size_sum,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000206 file_path: header_path.to_owned(),
207 read_write_capability: ReadWriteCapability::READ_ONLY,
208 ..ComponentDisk::new()
209 });
210 }
211 }
212
213 Ok(component_disks)
214}
215
216/// Create a new composite disk containing the given partitions, and write it out to the given
217/// files.
218pub fn create_composite_disk(
219 partitions: &[PartitionInfo],
220 header_path: &Path,
221 header_file: &mut File,
222 footer_path: &Path,
223 footer_file: &mut File,
224 output_composite: &mut File,
225) -> Result<(), Error> {
226 let header_path = header_path.to_str().context("Invalid header path")?.to_string();
227 let footer_path = footer_path.to_str().context("Invalid footer path")?.to_string();
228
229 let mut composite_proto = CompositeDisk::new();
230 composite_proto.version = COMPOSITE_DISK_VERSION;
231 composite_proto.component_disks.push(ComponentDisk {
232 file_path: header_path.clone(),
233 offset: 0,
234 read_write_capability: ReadWriteCapability::READ_ONLY,
235 ..ComponentDisk::new()
Andrew Walbran0c4d3df2021-05-27 14:00:42 +0000236 });
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000237
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000238 // Write partitions to a temporary buffer so that we can calculate the CRC, and construct the
239 // ComponentDisk proto messages at the same time.
240 let mut partitions_buffer =
241 [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
242 let mut writer: &mut [u8] = &mut partitions_buffer;
243 let mut next_disk_offset = GPT_BEGINNING_SIZE;
244 for partition in partitions {
245 create_gpt_entry(partition, next_disk_offset).write_bytes(&mut writer)?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000246
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000247 for component_disk in create_component_disks(partition, next_disk_offset, &header_path)? {
248 composite_proto.component_disks.push(component_disk);
249 }
250
251 next_disk_offset += partition.aligned_size();
252 }
253 let secondary_table_offset = next_disk_offset;
254 let disk_size = align_to_power_of_2(secondary_table_offset + GPT_END_SIZE, DISK_SIZE_SHIFT);
255 trace!("Partitions: {:#?}", partitions);
256 trace!("Secondary table offset: {} disk size: {}", secondary_table_offset, disk_size);
257
258 composite_proto.component_disks.push(ComponentDisk {
259 file_path: footer_path,
260 offset: secondary_table_offset,
261 read_write_capability: ReadWriteCapability::READ_ONLY,
262 ..ComponentDisk::new()
263 });
264
265 // Calculate CRC32 of partition entries.
266 let mut hasher = Hasher::new();
267 hasher.update(&partitions_buffer);
268 let partition_entries_crc32 = hasher.finalize();
269
270 let disk_guid = Uuid::new_v4();
271 write_beginning(
272 header_file,
273 disk_guid,
274 &partitions_buffer,
275 partition_entries_crc32,
276 secondary_table_offset,
277 disk_size,
278 )?;
279 write_end(
280 footer_file,
281 disk_guid,
282 &partitions_buffer,
283 partition_entries_crc32,
284 secondary_table_offset,
285 disk_size,
286 )?;
287
288 composite_proto.length = disk_size;
289 output_composite.write_all(CDISK_MAGIC.as_bytes())?;
290 composite_proto.write_to_writer(output_composite)?;
291
292 Ok(())
293}
294
295/// Constructs a composite disk image for the given list of partitions, and opens it ready to use.
296///
297/// Returns the composite disk image file, and a list of FD mappings which must be applied to any
298/// process which wants to use it. This is necessary because the composite image contains paths of
299/// the form `/proc/self/fd/N` for the partition images.
300pub fn make_composite_image(
301 partitions: &[Partition],
302 output_path: &Path,
303 header_path: &Path,
304 footer_path: &Path,
305) -> Result<(File, Vec<File>), Error> {
306 let (partitions, files) = convert_partitions(partitions)?;
307
308 let mut composite_image = OpenOptions::new()
309 .create_new(true)
310 .read(true)
311 .write(true)
312 .open(output_path)
313 .with_context(|| format!("Failed to create composite image {:?}", output_path))?;
314 let mut header_file =
315 OpenOptions::new().create_new(true).read(true).write(true).open(header_path).with_context(
316 || format!("Failed to create composite image header {:?}", header_path),
317 )?;
318 let mut footer_file =
319 OpenOptions::new().create_new(true).read(true).write(true).open(footer_path).with_context(
320 || format!("Failed to create composite image header {:?}", footer_path),
321 )?;
322
323 create_composite_disk(
324 &partitions,
325 header_path,
326 &mut header_file,
327 footer_path,
328 &mut footer_file,
329 &mut composite_image,
330 )?;
331
332 // Re-open the composite image as read-only.
333 let composite_image = File::open(&output_path)
334 .with_context(|| format!("Failed to open composite image {:?}", output_path))?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000335
336 Ok((composite_image, files))
337}
338
Jooyung Han9713bd42021-06-26 03:00:18 +0900339/// Given the AIDL config containing a list of partitions, with [`ParcelFileDescriptor`]s for each
340/// partition, return the list of file descriptors which must be passed to the composite disk image
341/// partition configuration for it.
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000342fn convert_partitions(partitions: &[Partition]) -> Result<(Vec<PartitionInfo>, Vec<File>), Error> {
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000343 // File descriptors to pass to child process.
344 let mut files = vec![];
345
346 let partitions = partitions
347 .iter()
348 .map(|partition| {
Jooyung Han9713bd42021-06-26 03:00:18 +0900349 let image_files = partition
350 .images
351 .iter()
352 .map(|image| {
353 let file = image
354 .as_ref()
355 .try_clone()
356 .context("Failed to clone partition image file descriptor")?;
357
358 let size = get_partition_size(&file)?;
359 let fd = file.as_raw_fd();
360 let partition_info_file =
361 PartitionFileInfo { path: format!("/proc/self/fd/{}", fd).into(), size };
362 files.push(file);
363 Ok(partition_info_file)
364 })
365 .collect::<Result<Vec<_>, Error>>()?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000366
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000367 Ok(PartitionInfo {
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000368 label: partition.label.to_owned(),
Jooyung Han9713bd42021-06-26 03:00:18 +0900369 files: image_files,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000370 partition_type: ImagePartitionType::LinuxFilesystem,
371 writable: partition.writable,
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000372 })
373 })
374 .collect::<Result<_, Error>>()?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000375
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000376 Ok((partitions, files))
377}
378
379/// Find the size of the partition image in the given file by parsing the header.
380///
381/// This will work for raw, QCOW2, composite and Android sparse images.
382fn get_partition_size(partition: &File) -> Result<u64, Error> {
383 // TODO: Use `context` once disk::Error implements std::error::Error.
384 Ok(create_disk_file(partition.try_clone()?)
385 .map_err(|e| anyhow!("Failed to open partition image: {}", e))?
386 .get_len()?)
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392
393 #[test]
394 fn beginning_size() {
395 let mut buffer = vec![];
396 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
397 let disk_size = 1000 * SECTOR_SIZE;
398 write_beginning(
399 &mut buffer,
400 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
401 &partitions,
402 42,
403 disk_size - GPT_END_SIZE,
404 disk_size,
405 )
406 .unwrap();
407
408 assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
409 }
410
411 #[test]
412 fn end_size() {
413 let mut buffer = vec![];
414 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
415 let disk_size = 1000 * SECTOR_SIZE;
416 write_end(
417 &mut buffer,
418 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
419 &partitions,
420 42,
421 disk_size - GPT_END_SIZE,
422 disk_size,
423 )
424 .unwrap();
425
426 assert_eq!(buffer.len(), GPT_END_SIZE as usize);
427 }
428
429 #[test]
430 fn end_size_with_padding() {
431 let mut buffer = vec![];
432 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
433 let disk_size = 1000 * SECTOR_SIZE;
434 let padding = 3 * SECTOR_SIZE;
435 write_end(
436 &mut buffer,
437 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
438 &partitions,
439 42,
440 disk_size - GPT_END_SIZE - padding,
441 disk_size,
442 )
443 .unwrap();
444
445 assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
446 }
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000447}