blob: 5f792fd9d2a583e196317ce2f7768915891e7aaf [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
339/// Given the AIDL config containing a list of partitions, with a [`ParcelFileDescriptor`] for each
340/// partition, return the list of file descriptors which must be passed to the mk_cdisk child
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000341/// process and the composite disk image partition configuration for it.
342fn 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| {
349 // TODO(b/187187765): This shouldn't be an Option.
350 let file = partition
351 .image
352 .as_ref()
353 .context("Invalid partition image file descriptor")?
354 .as_ref()
355 .try_clone()
356 .context("Failed to clone partition image file descriptor")?;
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000357 let size = get_partition_size(&file)?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000358 let fd = file.as_raw_fd();
359 files.push(file);
360
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000361 Ok(PartitionInfo {
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000362 label: partition.label.to_owned(),
Andrew Walbran1fee0d82021-06-17 14:49:25 +0000363 files: vec![PartitionFileInfo {
364 path: format!("/proc/self/fd/{}", fd).into(),
365 size,
366 }],
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000367 partition_type: ImagePartitionType::LinuxFilesystem,
368 writable: partition.writable,
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000369 })
370 })
371 .collect::<Result<_, Error>>()?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000372
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000373 Ok((partitions, files))
374}
375
376/// Find the size of the partition image in the given file by parsing the header.
377///
378/// This will work for raw, QCOW2, composite and Android sparse images.
379fn get_partition_size(partition: &File) -> Result<u64, Error> {
380 // TODO: Use `context` once disk::Error implements std::error::Error.
381 Ok(create_disk_file(partition.try_clone()?)
382 .map_err(|e| anyhow!("Failed to open partition image: {}", e))?
383 .get_len()?)
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn beginning_size() {
392 let mut buffer = vec![];
393 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
394 let disk_size = 1000 * SECTOR_SIZE;
395 write_beginning(
396 &mut buffer,
397 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
398 &partitions,
399 42,
400 disk_size - GPT_END_SIZE,
401 disk_size,
402 )
403 .unwrap();
404
405 assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
406 }
407
408 #[test]
409 fn end_size() {
410 let mut buffer = vec![];
411 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
412 let disk_size = 1000 * SECTOR_SIZE;
413 write_end(
414 &mut buffer,
415 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
416 &partitions,
417 42,
418 disk_size - GPT_END_SIZE,
419 disk_size,
420 )
421 .unwrap();
422
423 assert_eq!(buffer.len(), GPT_END_SIZE as usize);
424 }
425
426 #[test]
427 fn end_size_with_padding() {
428 let mut buffer = vec![];
429 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
430 let disk_size = 1000 * SECTOR_SIZE;
431 let padding = 3 * SECTOR_SIZE;
432 write_end(
433 &mut buffer,
434 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
435 &partitions,
436 42,
437 disk_size - GPT_END_SIZE - padding,
438 disk_size,
439 )
440 .unwrap();
441
442 assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
443 }
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000444}