blob: 378ed785abc8438f2c3af4efb65853fa5ae2eb87 [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
Jooyung Han21e9b922021-06-26 04:14:16 +090075/// Round `val` to partition size(4K)
Jooyung Han95884632021-07-06 22:27:54 +090076fn align_to_partition_size(val: u64) -> u64 {
Jooyung Han21e9b922021-06-26 04:14:16 +090077 align_to_power_of_2(val, PARTITION_SIZE_SHIFT)
78}
79
Andrew Walbran3eca16c2021-06-14 11:15:14 +000080impl PartitionInfo {
81 fn aligned_size(&self) -> u64 {
Jooyung Han35edb8f2021-07-01 16:17:16 +090082 align_to_partition_size(self.files.iter().map(|file| file.size).sum())
Andrew Walbran3eca16c2021-06-14 11:15:14 +000083 }
84}
85
86/// The type of partition.
87#[allow(dead_code)]
88#[derive(Copy, Clone, Debug, Eq, PartialEq)]
89pub enum ImagePartitionType {
90 LinuxFilesystem,
91 EfiSystemPartition,
92}
93
94impl ImagePartitionType {
95 fn guid(self) -> Uuid {
96 match self {
97 Self::LinuxFilesystem => LINUX_FILESYSTEM_GUID,
98 Self::EfiSystemPartition => EFI_SYSTEM_PARTITION_GUID,
99 }
100 }
101}
102
103/// Write protective MBR and primary GPT table.
104fn write_beginning(
105 file: &mut impl Write,
106 disk_guid: Uuid,
107 partitions: &[u8],
108 partition_entries_crc32: u32,
109 secondary_table_offset: u64,
110 disk_size: u64,
111) -> Result<(), Error> {
112 // Write the protective MBR to the first sector.
113 write_protective_mbr(file, disk_size)?;
114
115 // Write the GPT header, and pad out to the end of the sector.
116 write_gpt_header(file, disk_guid, partition_entries_crc32, secondary_table_offset, false)?;
117 file.write_all(&[0; HEADER_PADDING_LENGTH])?;
118
119 // Write partition entries, including unused ones.
120 file.write_all(partitions)?;
121
122 // Write zeroes to align the first partition appropriately.
123 file.write_all(&[0; PARTITION_ALIGNMENT_SIZE])?;
124
125 Ok(())
126}
127
128/// Write secondary GPT table.
129fn write_end(
130 file: &mut impl Write,
131 disk_guid: Uuid,
132 partitions: &[u8],
133 partition_entries_crc32: u32,
134 secondary_table_offset: u64,
135 disk_size: u64,
136) -> Result<(), Error> {
137 // Write partition entries, including unused ones.
138 file.write_all(partitions)?;
139
140 // Write the GPT header, and pad out to the end of the sector.
141 write_gpt_header(file, disk_guid, partition_entries_crc32, secondary_table_offset, true)?;
142 file.write_all(&[0; HEADER_PADDING_LENGTH])?;
143
144 // Pad out to the aligned disk size.
145 let used_disk_size = secondary_table_offset + GPT_END_SIZE;
146 let padding = disk_size - used_disk_size;
147 file.write_all(&vec![0; padding as usize])?;
148
149 Ok(())
150}
151
152/// Create the `GptPartitionEntry` for the given partition.
153fn create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry {
154 let mut partition_name: Vec<u16> = partition.label.encode_utf16().collect();
155 partition_name.resize(36, 0);
156
157 GptPartitionEntry {
158 partition_type_guid: partition.partition_type.guid(),
159 unique_partition_guid: Uuid::new_v4(),
160 first_lba: offset / SECTOR_SIZE,
161 last_lba: (offset + partition.aligned_size()) / SECTOR_SIZE - 1,
162 attributes: 0,
163 partition_name: partition_name.try_into().unwrap(),
164 }
165}
166
167/// Create one or more `ComponentDisk` proto messages for the given partition.
168fn create_component_disks(
169 partition: &PartitionInfo,
170 offset: u64,
Jooyung Han95884632021-07-06 22:27:54 +0900171 zero_filler_path: &str,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000172) -> Result<Vec<ComponentDisk>, Error> {
173 let aligned_size = partition.aligned_size();
174
Andrew Walbran1fee0d82021-06-17 14:49:25 +0000175 if partition.files.is_empty() {
176 bail!("No image files for partition {:?}", partition);
177 }
178 let mut file_size_sum = 0;
179 let mut component_disks = vec![];
180 for file in &partition.files {
181 component_disks.push(ComponentDisk {
182 offset: offset + file_size_sum,
183 file_path: file.path.to_str().context("Invalid partition path")?.to_string(),
184 read_write_capability: if partition.writable {
185 ReadWriteCapability::READ_WRITE
186 } else {
187 ReadWriteCapability::READ_ONLY
188 },
189 ..ComponentDisk::new()
190 });
191 file_size_sum += file.size;
192 }
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000193
Andrew Walbran1fee0d82021-06-17 14:49:25 +0000194 if file_size_sum != aligned_size {
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000195 if partition.writable {
196 bail!(
197 "Read-write partition {:?} size is not a multiple of {}.",
198 partition,
199 1 << PARTITION_SIZE_SHIFT
200 );
201 } else {
202 // Fill in the gap by reusing the header file, because we know it is always bigger
203 // than the alignment size (i.e. GPT_BEGINNING_SIZE > 1 << PARTITION_SIZE_SHIFT).
204 warn!(
205 "Read-only partition {:?} size is not a multiple of {}, filling gap.",
206 partition,
207 1 << PARTITION_SIZE_SHIFT
208 );
209 component_disks.push(ComponentDisk {
Andrew Walbran1fee0d82021-06-17 14:49:25 +0000210 offset: offset + file_size_sum,
Jooyung Han95884632021-07-06 22:27:54 +0900211 file_path: zero_filler_path.to_owned(),
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000212 read_write_capability: ReadWriteCapability::READ_ONLY,
213 ..ComponentDisk::new()
214 });
215 }
216 }
217
218 Ok(component_disks)
219}
220
221/// Create a new composite disk containing the given partitions, and write it out to the given
222/// files.
223pub fn create_composite_disk(
224 partitions: &[PartitionInfo],
Jooyung Han95884632021-07-06 22:27:54 +0900225 zero_filler_path: &Path,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000226 header_path: &Path,
227 header_file: &mut File,
228 footer_path: &Path,
229 footer_file: &mut File,
230 output_composite: &mut File,
231) -> Result<(), Error> {
Jooyung Han95884632021-07-06 22:27:54 +0900232 let zero_filler_path =
233 zero_filler_path.to_str().context("Invalid zero filler path")?.to_string();
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000234 let header_path = header_path.to_str().context("Invalid header path")?.to_string();
235 let footer_path = footer_path.to_str().context("Invalid footer path")?.to_string();
236
237 let mut composite_proto = CompositeDisk::new();
238 composite_proto.version = COMPOSITE_DISK_VERSION;
239 composite_proto.component_disks.push(ComponentDisk {
Jooyung Han95884632021-07-06 22:27:54 +0900240 file_path: header_path,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000241 offset: 0,
242 read_write_capability: ReadWriteCapability::READ_ONLY,
243 ..ComponentDisk::new()
Andrew Walbran0c4d3df2021-05-27 14:00:42 +0000244 });
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000245
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000246 // Write partitions to a temporary buffer so that we can calculate the CRC, and construct the
247 // ComponentDisk proto messages at the same time.
248 let mut partitions_buffer =
249 [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
250 let mut writer: &mut [u8] = &mut partitions_buffer;
251 let mut next_disk_offset = GPT_BEGINNING_SIZE;
252 for partition in partitions {
253 create_gpt_entry(partition, next_disk_offset).write_bytes(&mut writer)?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000254
Jooyung Han95884632021-07-06 22:27:54 +0900255 for component_disk in
256 create_component_disks(partition, next_disk_offset, &zero_filler_path)?
257 {
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000258 composite_proto.component_disks.push(component_disk);
259 }
260
261 next_disk_offset += partition.aligned_size();
262 }
263 let secondary_table_offset = next_disk_offset;
264 let disk_size = align_to_power_of_2(secondary_table_offset + GPT_END_SIZE, DISK_SIZE_SHIFT);
265 trace!("Partitions: {:#?}", partitions);
266 trace!("Secondary table offset: {} disk size: {}", secondary_table_offset, disk_size);
267
268 composite_proto.component_disks.push(ComponentDisk {
269 file_path: footer_path,
270 offset: secondary_table_offset,
271 read_write_capability: ReadWriteCapability::READ_ONLY,
272 ..ComponentDisk::new()
273 });
274
275 // Calculate CRC32 of partition entries.
276 let mut hasher = Hasher::new();
277 hasher.update(&partitions_buffer);
278 let partition_entries_crc32 = hasher.finalize();
279
280 let disk_guid = Uuid::new_v4();
281 write_beginning(
282 header_file,
283 disk_guid,
284 &partitions_buffer,
285 partition_entries_crc32,
286 secondary_table_offset,
287 disk_size,
288 )?;
289 write_end(
290 footer_file,
291 disk_guid,
292 &partitions_buffer,
293 partition_entries_crc32,
294 secondary_table_offset,
295 disk_size,
296 )?;
297
298 composite_proto.length = disk_size;
299 output_composite.write_all(CDISK_MAGIC.as_bytes())?;
300 composite_proto.write_to_writer(output_composite)?;
301
302 Ok(())
303}
304
305/// Constructs a composite disk image for the given list of partitions, and opens it ready to use.
306///
307/// Returns the composite disk image file, and a list of FD mappings which must be applied to any
308/// process which wants to use it. This is necessary because the composite image contains paths of
309/// the form `/proc/self/fd/N` for the partition images.
310pub fn make_composite_image(
311 partitions: &[Partition],
Jooyung Han95884632021-07-06 22:27:54 +0900312 zero_filler_path: &Path,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000313 output_path: &Path,
314 header_path: &Path,
315 footer_path: &Path,
316) -> Result<(File, Vec<File>), Error> {
317 let (partitions, files) = convert_partitions(partitions)?;
318
319 let mut composite_image = OpenOptions::new()
320 .create_new(true)
321 .read(true)
322 .write(true)
323 .open(output_path)
324 .with_context(|| format!("Failed to create composite image {:?}", output_path))?;
325 let mut header_file =
326 OpenOptions::new().create_new(true).read(true).write(true).open(header_path).with_context(
327 || format!("Failed to create composite image header {:?}", header_path),
328 )?;
329 let mut footer_file =
330 OpenOptions::new().create_new(true).read(true).write(true).open(footer_path).with_context(
331 || format!("Failed to create composite image header {:?}", footer_path),
332 )?;
333
334 create_composite_disk(
335 &partitions,
Jooyung Han95884632021-07-06 22:27:54 +0900336 zero_filler_path,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000337 header_path,
338 &mut header_file,
339 footer_path,
340 &mut footer_file,
341 &mut composite_image,
342 )?;
343
344 // Re-open the composite image as read-only.
345 let composite_image = File::open(&output_path)
346 .with_context(|| format!("Failed to open composite image {:?}", output_path))?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000347
348 Ok((composite_image, files))
349}
350
Jooyung Han9713bd42021-06-26 03:00:18 +0900351/// Given the AIDL config containing a list of partitions, with [`ParcelFileDescriptor`]s for each
352/// partition, return the list of file descriptors which must be passed to the composite disk image
353/// partition configuration for it.
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000354fn convert_partitions(partitions: &[Partition]) -> Result<(Vec<PartitionInfo>, Vec<File>), Error> {
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000355 // File descriptors to pass to child process.
356 let mut files = vec![];
357
358 let partitions = partitions
359 .iter()
360 .map(|partition| {
Jooyung Han9713bd42021-06-26 03:00:18 +0900361 let image_files = partition
362 .images
363 .iter()
364 .map(|image| {
365 let file = image
366 .as_ref()
367 .try_clone()
368 .context("Failed to clone partition image file descriptor")?;
369
370 let size = get_partition_size(&file)?;
371 let fd = file.as_raw_fd();
372 let partition_info_file =
373 PartitionFileInfo { path: format!("/proc/self/fd/{}", fd).into(), size };
374 files.push(file);
375 Ok(partition_info_file)
376 })
377 .collect::<Result<Vec<_>, Error>>()?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000378
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000379 Ok(PartitionInfo {
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000380 label: partition.label.to_owned(),
Jooyung Han9713bd42021-06-26 03:00:18 +0900381 files: image_files,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000382 partition_type: ImagePartitionType::LinuxFilesystem,
383 writable: partition.writable,
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000384 })
385 })
386 .collect::<Result<_, Error>>()?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000387
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000388 Ok((partitions, files))
389}
390
391/// Find the size of the partition image in the given file by parsing the header.
392///
393/// This will work for raw, QCOW2, composite and Android sparse images.
394fn get_partition_size(partition: &File) -> Result<u64, Error> {
395 // TODO: Use `context` once disk::Error implements std::error::Error.
396 Ok(create_disk_file(partition.try_clone()?)
397 .map_err(|e| anyhow!("Failed to open partition image: {}", e))?
398 .get_len()?)
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn beginning_size() {
407 let mut buffer = vec![];
408 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
409 let disk_size = 1000 * SECTOR_SIZE;
410 write_beginning(
411 &mut buffer,
412 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
413 &partitions,
414 42,
415 disk_size - GPT_END_SIZE,
416 disk_size,
417 )
418 .unwrap();
419
420 assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
421 }
422
423 #[test]
424 fn end_size() {
425 let mut buffer = vec![];
426 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
427 let disk_size = 1000 * SECTOR_SIZE;
428 write_end(
429 &mut buffer,
430 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
431 &partitions,
432 42,
433 disk_size - GPT_END_SIZE,
434 disk_size,
435 )
436 .unwrap();
437
438 assert_eq!(buffer.len(), GPT_END_SIZE as usize);
439 }
440
441 #[test]
442 fn end_size_with_padding() {
443 let mut buffer = vec![];
444 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
445 let disk_size = 1000 * SECTOR_SIZE;
446 let padding = 3 * SECTOR_SIZE;
447 write_end(
448 &mut buffer,
449 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
450 &partitions,
451 42,
452 disk_size - GPT_END_SIZE - padding,
453 disk_size,
454 )
455 .unwrap();
456
457 assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
458 }
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000459}