blob: ded0053ed7f97ce9afd1a58e7e547ef46edd9260 [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
Jooyung Han631d5882021-07-29 06:34:05 +090053/// Information about a partition to create.
Andrew Walbran3eca16c2021-06-14 11:15:14 +000054#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct PartitionInfo {
56 label: String,
Jooyung Han631d5882021-07-29 06:34:05 +090057 path: PathBuf,
Andrew Walbran3eca16c2021-06-14 11:15:14 +000058 partition_type: ImagePartitionType,
59 writable: bool,
Jooyung Han631d5882021-07-29 06:34:05 +090060 size: u64,
Andrew Walbran3eca16c2021-06-14 11:15:14 +000061}
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +000062
Andrew Walbran3eca16c2021-06-14 11:15:14 +000063/// Round `val` up to the next multiple of 2**`align_log`.
64fn align_to_power_of_2(val: u64, align_log: u8) -> u64 {
65 let align = 1 << align_log;
66 ((val + (align - 1)) / align) * align
67}
68
Jooyung Han21e9b922021-06-26 04:14:16 +090069/// Round `val` to partition size(4K)
Jooyung Han95884632021-07-06 22:27:54 +090070fn align_to_partition_size(val: u64) -> u64 {
Jooyung Han21e9b922021-06-26 04:14:16 +090071 align_to_power_of_2(val, PARTITION_SIZE_SHIFT)
72}
73
Andrew Walbran3eca16c2021-06-14 11:15:14 +000074impl PartitionInfo {
75 fn aligned_size(&self) -> u64 {
Jooyung Han631d5882021-07-29 06:34:05 +090076 align_to_partition_size(self.size)
Andrew Walbran3eca16c2021-06-14 11:15:14 +000077 }
78}
79
80/// The type of partition.
81#[allow(dead_code)]
82#[derive(Copy, Clone, Debug, Eq, PartialEq)]
83pub enum ImagePartitionType {
84 LinuxFilesystem,
85 EfiSystemPartition,
86}
87
88impl ImagePartitionType {
89 fn guid(self) -> Uuid {
90 match self {
91 Self::LinuxFilesystem => LINUX_FILESYSTEM_GUID,
92 Self::EfiSystemPartition => EFI_SYSTEM_PARTITION_GUID,
93 }
94 }
95}
96
97/// Write protective MBR and primary GPT table.
98fn write_beginning(
99 file: &mut impl Write,
100 disk_guid: Uuid,
101 partitions: &[u8],
102 partition_entries_crc32: u32,
103 secondary_table_offset: u64,
104 disk_size: u64,
105) -> Result<(), Error> {
106 // Write the protective MBR to the first sector.
107 write_protective_mbr(file, disk_size)?;
108
109 // Write the GPT header, and pad out to the end of the sector.
110 write_gpt_header(file, disk_guid, partition_entries_crc32, secondary_table_offset, false)?;
111 file.write_all(&[0; HEADER_PADDING_LENGTH])?;
112
113 // Write partition entries, including unused ones.
114 file.write_all(partitions)?;
115
116 // Write zeroes to align the first partition appropriately.
117 file.write_all(&[0; PARTITION_ALIGNMENT_SIZE])?;
118
119 Ok(())
120}
121
122/// Write secondary GPT table.
123fn write_end(
124 file: &mut impl Write,
125 disk_guid: Uuid,
126 partitions: &[u8],
127 partition_entries_crc32: u32,
128 secondary_table_offset: u64,
129 disk_size: u64,
130) -> Result<(), Error> {
131 // Write partition entries, including unused ones.
132 file.write_all(partitions)?;
133
134 // Write the GPT header, and pad out to the end of the sector.
135 write_gpt_header(file, disk_guid, partition_entries_crc32, secondary_table_offset, true)?;
136 file.write_all(&[0; HEADER_PADDING_LENGTH])?;
137
138 // Pad out to the aligned disk size.
139 let used_disk_size = secondary_table_offset + GPT_END_SIZE;
140 let padding = disk_size - used_disk_size;
141 file.write_all(&vec![0; padding as usize])?;
142
143 Ok(())
144}
145
146/// Create the `GptPartitionEntry` for the given partition.
147fn create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry {
148 let mut partition_name: Vec<u16> = partition.label.encode_utf16().collect();
149 partition_name.resize(36, 0);
150
151 GptPartitionEntry {
152 partition_type_guid: partition.partition_type.guid(),
153 unique_partition_guid: Uuid::new_v4(),
154 first_lba: offset / SECTOR_SIZE,
155 last_lba: (offset + partition.aligned_size()) / SECTOR_SIZE - 1,
156 attributes: 0,
157 partition_name: partition_name.try_into().unwrap(),
158 }
159}
160
161/// Create one or more `ComponentDisk` proto messages for the given partition.
162fn create_component_disks(
163 partition: &PartitionInfo,
164 offset: u64,
Jooyung Han95884632021-07-06 22:27:54 +0900165 zero_filler_path: &str,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000166) -> Result<Vec<ComponentDisk>, Error> {
167 let aligned_size = partition.aligned_size();
168
Jooyung Han631d5882021-07-29 06:34:05 +0900169 let mut component_disks = vec![ComponentDisk {
170 offset,
171 file_path: partition.path.to_str().context("Invalid partition path")?.to_string(),
172 read_write_capability: if partition.writable {
173 ReadWriteCapability::READ_WRITE
174 } else {
175 ReadWriteCapability::READ_ONLY
176 },
177 ..ComponentDisk::new()
178 }];
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000179
Jooyung Han631d5882021-07-29 06:34:05 +0900180 if partition.size != aligned_size {
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000181 if partition.writable {
182 bail!(
183 "Read-write partition {:?} size is not a multiple of {}.",
184 partition,
185 1 << PARTITION_SIZE_SHIFT
186 );
187 } else {
188 // Fill in the gap by reusing the header file, because we know it is always bigger
189 // than the alignment size (i.e. GPT_BEGINNING_SIZE > 1 << PARTITION_SIZE_SHIFT).
190 warn!(
191 "Read-only partition {:?} size is not a multiple of {}, filling gap.",
192 partition,
193 1 << PARTITION_SIZE_SHIFT
194 );
195 component_disks.push(ComponentDisk {
Jooyung Han631d5882021-07-29 06:34:05 +0900196 offset: offset + partition.size,
Jooyung Han95884632021-07-06 22:27:54 +0900197 file_path: zero_filler_path.to_owned(),
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000198 read_write_capability: ReadWriteCapability::READ_ONLY,
199 ..ComponentDisk::new()
200 });
201 }
202 }
203
204 Ok(component_disks)
205}
206
207/// Create a new composite disk containing the given partitions, and write it out to the given
208/// files.
209pub fn create_composite_disk(
210 partitions: &[PartitionInfo],
Jooyung Han95884632021-07-06 22:27:54 +0900211 zero_filler_path: &Path,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000212 header_path: &Path,
213 header_file: &mut File,
214 footer_path: &Path,
215 footer_file: &mut File,
216 output_composite: &mut File,
217) -> Result<(), Error> {
Jooyung Han95884632021-07-06 22:27:54 +0900218 let zero_filler_path =
219 zero_filler_path.to_str().context("Invalid zero filler path")?.to_string();
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000220 let header_path = header_path.to_str().context("Invalid header path")?.to_string();
221 let footer_path = footer_path.to_str().context("Invalid footer path")?.to_string();
222
223 let mut composite_proto = CompositeDisk::new();
224 composite_proto.version = COMPOSITE_DISK_VERSION;
225 composite_proto.component_disks.push(ComponentDisk {
Jooyung Han95884632021-07-06 22:27:54 +0900226 file_path: header_path,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000227 offset: 0,
228 read_write_capability: ReadWriteCapability::READ_ONLY,
229 ..ComponentDisk::new()
Andrew Walbran0c4d3df2021-05-27 14:00:42 +0000230 });
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000231
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000232 // Write partitions to a temporary buffer so that we can calculate the CRC, and construct the
233 // ComponentDisk proto messages at the same time.
234 let mut partitions_buffer =
235 [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
236 let mut writer: &mut [u8] = &mut partitions_buffer;
237 let mut next_disk_offset = GPT_BEGINNING_SIZE;
238 for partition in partitions {
239 create_gpt_entry(partition, next_disk_offset).write_bytes(&mut writer)?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000240
Jooyung Han95884632021-07-06 22:27:54 +0900241 for component_disk in
242 create_component_disks(partition, next_disk_offset, &zero_filler_path)?
243 {
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000244 composite_proto.component_disks.push(component_disk);
245 }
246
247 next_disk_offset += partition.aligned_size();
248 }
249 let secondary_table_offset = next_disk_offset;
250 let disk_size = align_to_power_of_2(secondary_table_offset + GPT_END_SIZE, DISK_SIZE_SHIFT);
251 trace!("Partitions: {:#?}", partitions);
252 trace!("Secondary table offset: {} disk size: {}", secondary_table_offset, disk_size);
253
254 composite_proto.component_disks.push(ComponentDisk {
255 file_path: footer_path,
256 offset: secondary_table_offset,
257 read_write_capability: ReadWriteCapability::READ_ONLY,
258 ..ComponentDisk::new()
259 });
260
261 // Calculate CRC32 of partition entries.
262 let mut hasher = Hasher::new();
263 hasher.update(&partitions_buffer);
264 let partition_entries_crc32 = hasher.finalize();
265
266 let disk_guid = Uuid::new_v4();
267 write_beginning(
268 header_file,
269 disk_guid,
270 &partitions_buffer,
271 partition_entries_crc32,
272 secondary_table_offset,
273 disk_size,
274 )?;
275 write_end(
276 footer_file,
277 disk_guid,
278 &partitions_buffer,
279 partition_entries_crc32,
280 secondary_table_offset,
281 disk_size,
282 )?;
283
284 composite_proto.length = disk_size;
285 output_composite.write_all(CDISK_MAGIC.as_bytes())?;
286 composite_proto.write_to_writer(output_composite)?;
287
288 Ok(())
289}
290
291/// Constructs a composite disk image for the given list of partitions, and opens it ready to use.
292///
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000293/// Returns the composite disk image file, and a list of files whose file descriptors must be passed
294/// to any process which wants to use it. This is necessary because the composite image contains
295/// paths of the form `/proc/self/fd/N` for the partition images.
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000296pub fn make_composite_image(
297 partitions: &[Partition],
Jooyung Han95884632021-07-06 22:27:54 +0900298 zero_filler_path: &Path,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000299 output_path: &Path,
300 header_path: &Path,
301 footer_path: &Path,
302) -> Result<(File, Vec<File>), Error> {
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000303 let (partitions, mut files) = convert_partitions(partitions)?;
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000304
305 let mut composite_image = OpenOptions::new()
306 .create_new(true)
307 .read(true)
308 .write(true)
309 .open(output_path)
310 .with_context(|| format!("Failed to create composite image {:?}", output_path))?;
311 let mut header_file =
312 OpenOptions::new().create_new(true).read(true).write(true).open(header_path).with_context(
313 || format!("Failed to create composite image header {:?}", header_path),
314 )?;
315 let mut footer_file =
316 OpenOptions::new().create_new(true).read(true).write(true).open(footer_path).with_context(
317 || format!("Failed to create composite image header {:?}", footer_path),
318 )?;
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000319 let zero_filler_file = File::open(&zero_filler_path).with_context(|| {
320 format!("Failed to open composite image zero filler {:?}", zero_filler_path)
321 })?;
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000322
323 create_composite_disk(
324 &partitions,
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000325 &fd_path_for_file(&zero_filler_file),
326 &fd_path_for_file(&header_file),
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000327 &mut header_file,
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000328 &fd_path_for_file(&footer_file),
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000329 &mut footer_file,
330 &mut composite_image,
331 )?;
332
333 // Re-open the composite image as read-only.
334 let composite_image = File::open(&output_path)
335 .with_context(|| format!("Failed to open composite image {:?}", output_path))?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000336
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000337 files.push(header_file);
338 files.push(footer_file);
339 files.push(zero_filler_file);
340
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000341 Ok((composite_image, files))
342}
343
Jooyung Han631d5882021-07-29 06:34:05 +0900344/// Given the AIDL config containing a list of partitions, with a [`ParcelFileDescriptor`] for each
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000345/// partition, returns the corresponding list of PartitionInfo and the list of files whose file
346/// descriptors must be passed to any process using the composite image.
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000347fn convert_partitions(partitions: &[Partition]) -> Result<(Vec<PartitionInfo>, Vec<File>), Error> {
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000348 // File descriptors to pass to child process.
349 let mut files = vec![];
350
351 let partitions = partitions
352 .iter()
353 .map(|partition| {
Jooyung Han631d5882021-07-29 06:34:05 +0900354 // TODO(b/187187765): This shouldn't be an Option.
355 let file = partition
356 .image
357 .as_ref()
358 .context("Invalid partition image file descriptor")?
359 .as_ref()
360 .try_clone()
361 .context("Failed to clone partition image file descriptor")?;
362 let size = get_partition_size(&file)?;
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000363 let path = fd_path_for_file(&file);
Jooyung Han631d5882021-07-29 06:34:05 +0900364 files.push(file);
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000365
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000366 Ok(PartitionInfo {
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000367 label: partition.label.to_owned(),
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000368 path,
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000369 partition_type: ImagePartitionType::LinuxFilesystem,
370 writable: partition.writable,
Jooyung Han631d5882021-07-29 06:34:05 +0900371 size,
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
Andrew Walbranfbb39d22021-07-28 17:01:25 +0000379fn fd_path_for_file(file: &File) -> PathBuf {
380 let fd = file.as_raw_fd();
381 format!("/proc/self/fd/{}", fd).into()
382}
383
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000384/// Find the size of the partition image in the given file by parsing the header.
385///
386/// This will work for raw, QCOW2, composite and Android sparse images.
387fn get_partition_size(partition: &File) -> Result<u64, Error> {
388 // TODO: Use `context` once disk::Error implements std::error::Error.
389 Ok(create_disk_file(partition.try_clone()?)
390 .map_err(|e| anyhow!("Failed to open partition image: {}", e))?
391 .get_len()?)
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn beginning_size() {
400 let mut buffer = vec![];
401 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
402 let disk_size = 1000 * SECTOR_SIZE;
403 write_beginning(
404 &mut buffer,
405 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
406 &partitions,
407 42,
408 disk_size - GPT_END_SIZE,
409 disk_size,
410 )
411 .unwrap();
412
413 assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
414 }
415
416 #[test]
417 fn end_size() {
418 let mut buffer = vec![];
419 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
420 let disk_size = 1000 * SECTOR_SIZE;
421 write_end(
422 &mut buffer,
423 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
424 &partitions,
425 42,
426 disk_size - GPT_END_SIZE,
427 disk_size,
428 )
429 .unwrap();
430
431 assert_eq!(buffer.len(), GPT_END_SIZE as usize);
432 }
433
434 #[test]
435 fn end_size_with_padding() {
436 let mut buffer = vec![];
437 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
438 let disk_size = 1000 * SECTOR_SIZE;
439 let padding = 3 * SECTOR_SIZE;
440 write_end(
441 &mut buffer,
442 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
443 &partitions,
444 42,
445 disk_size - GPT_END_SIZE - padding,
446 disk_size,
447 )
448 .unwrap();
449
450 assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
451 }
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000452}