blob: fb8e7837427ef3eba9fff8dd73e0eba37f8e51fb [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 Walbran3eca16c2021-06-14 11:15:14 +000053#[derive(Clone, Debug, Eq, PartialEq)]
54pub struct PartitionInfo {
55 label: String,
56 path: PathBuf,
57 partition_type: ImagePartitionType,
58 writable: bool,
59 size: u64,
60}
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +000061
Andrew Walbran3eca16c2021-06-14 11:15:14 +000062/// Round `val` up to the next multiple of 2**`align_log`.
63fn align_to_power_of_2(val: u64, align_log: u8) -> u64 {
64 let align = 1 << align_log;
65 ((val + (align - 1)) / align) * align
66}
67
68impl PartitionInfo {
69 fn aligned_size(&self) -> u64 {
70 align_to_power_of_2(self.size, PARTITION_SIZE_SHIFT)
71 }
72}
73
74/// The type of partition.
75#[allow(dead_code)]
76#[derive(Copy, Clone, Debug, Eq, PartialEq)]
77pub enum ImagePartitionType {
78 LinuxFilesystem,
79 EfiSystemPartition,
80}
81
82impl ImagePartitionType {
83 fn guid(self) -> Uuid {
84 match self {
85 Self::LinuxFilesystem => LINUX_FILESYSTEM_GUID,
86 Self::EfiSystemPartition => EFI_SYSTEM_PARTITION_GUID,
87 }
88 }
89}
90
91/// Write protective MBR and primary GPT table.
92fn write_beginning(
93 file: &mut impl Write,
94 disk_guid: Uuid,
95 partitions: &[u8],
96 partition_entries_crc32: u32,
97 secondary_table_offset: u64,
98 disk_size: u64,
99) -> Result<(), Error> {
100 // Write the protective MBR to the first sector.
101 write_protective_mbr(file, disk_size)?;
102
103 // Write the GPT header, and pad out to the end of the sector.
104 write_gpt_header(file, disk_guid, partition_entries_crc32, secondary_table_offset, false)?;
105 file.write_all(&[0; HEADER_PADDING_LENGTH])?;
106
107 // Write partition entries, including unused ones.
108 file.write_all(partitions)?;
109
110 // Write zeroes to align the first partition appropriately.
111 file.write_all(&[0; PARTITION_ALIGNMENT_SIZE])?;
112
113 Ok(())
114}
115
116/// Write secondary GPT table.
117fn write_end(
118 file: &mut impl Write,
119 disk_guid: Uuid,
120 partitions: &[u8],
121 partition_entries_crc32: u32,
122 secondary_table_offset: u64,
123 disk_size: u64,
124) -> Result<(), Error> {
125 // Write partition entries, including unused ones.
126 file.write_all(partitions)?;
127
128 // Write the GPT header, and pad out to the end of the sector.
129 write_gpt_header(file, disk_guid, partition_entries_crc32, secondary_table_offset, true)?;
130 file.write_all(&[0; HEADER_PADDING_LENGTH])?;
131
132 // Pad out to the aligned disk size.
133 let used_disk_size = secondary_table_offset + GPT_END_SIZE;
134 let padding = disk_size - used_disk_size;
135 file.write_all(&vec![0; padding as usize])?;
136
137 Ok(())
138}
139
140/// Create the `GptPartitionEntry` for the given partition.
141fn create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry {
142 let mut partition_name: Vec<u16> = partition.label.encode_utf16().collect();
143 partition_name.resize(36, 0);
144
145 GptPartitionEntry {
146 partition_type_guid: partition.partition_type.guid(),
147 unique_partition_guid: Uuid::new_v4(),
148 first_lba: offset / SECTOR_SIZE,
149 last_lba: (offset + partition.aligned_size()) / SECTOR_SIZE - 1,
150 attributes: 0,
151 partition_name: partition_name.try_into().unwrap(),
152 }
153}
154
155/// Create one or more `ComponentDisk` proto messages for the given partition.
156fn create_component_disks(
157 partition: &PartitionInfo,
158 offset: u64,
159 header_path: &str,
160) -> Result<Vec<ComponentDisk>, Error> {
161 let aligned_size = partition.aligned_size();
162
163 let mut component_disks = vec![ComponentDisk {
164 offset,
165 file_path: partition.path.to_str().context("Invalid partition path")?.to_string(),
166 read_write_capability: if partition.writable {
167 ReadWriteCapability::READ_WRITE
168 } else {
169 ReadWriteCapability::READ_ONLY
170 },
171 ..ComponentDisk::new()
172 }];
173
174 if partition.size != aligned_size {
175 if partition.writable {
176 bail!(
177 "Read-write partition {:?} size is not a multiple of {}.",
178 partition,
179 1 << PARTITION_SIZE_SHIFT
180 );
181 } else {
182 // Fill in the gap by reusing the header file, because we know it is always bigger
183 // than the alignment size (i.e. GPT_BEGINNING_SIZE > 1 << PARTITION_SIZE_SHIFT).
184 warn!(
185 "Read-only partition {:?} size is not a multiple of {}, filling gap.",
186 partition,
187 1 << PARTITION_SIZE_SHIFT
188 );
189 component_disks.push(ComponentDisk {
190 offset: offset + partition.size,
191 file_path: header_path.to_owned(),
192 read_write_capability: ReadWriteCapability::READ_ONLY,
193 ..ComponentDisk::new()
194 });
195 }
196 }
197
198 Ok(component_disks)
199}
200
201/// Create a new composite disk containing the given partitions, and write it out to the given
202/// files.
203pub fn create_composite_disk(
204 partitions: &[PartitionInfo],
205 header_path: &Path,
206 header_file: &mut File,
207 footer_path: &Path,
208 footer_file: &mut File,
209 output_composite: &mut File,
210) -> Result<(), Error> {
211 let header_path = header_path.to_str().context("Invalid header path")?.to_string();
212 let footer_path = footer_path.to_str().context("Invalid footer path")?.to_string();
213
214 let mut composite_proto = CompositeDisk::new();
215 composite_proto.version = COMPOSITE_DISK_VERSION;
216 composite_proto.component_disks.push(ComponentDisk {
217 file_path: header_path.clone(),
218 offset: 0,
219 read_write_capability: ReadWriteCapability::READ_ONLY,
220 ..ComponentDisk::new()
Andrew Walbran0c4d3df2021-05-27 14:00:42 +0000221 });
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000222
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000223 // Write partitions to a temporary buffer so that we can calculate the CRC, and construct the
224 // ComponentDisk proto messages at the same time.
225 let mut partitions_buffer =
226 [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
227 let mut writer: &mut [u8] = &mut partitions_buffer;
228 let mut next_disk_offset = GPT_BEGINNING_SIZE;
229 for partition in partitions {
230 create_gpt_entry(partition, next_disk_offset).write_bytes(&mut writer)?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000231
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000232 for component_disk in create_component_disks(partition, next_disk_offset, &header_path)? {
233 composite_proto.component_disks.push(component_disk);
234 }
235
236 next_disk_offset += partition.aligned_size();
237 }
238 let secondary_table_offset = next_disk_offset;
239 let disk_size = align_to_power_of_2(secondary_table_offset + GPT_END_SIZE, DISK_SIZE_SHIFT);
240 trace!("Partitions: {:#?}", partitions);
241 trace!("Secondary table offset: {} disk size: {}", secondary_table_offset, disk_size);
242
243 composite_proto.component_disks.push(ComponentDisk {
244 file_path: footer_path,
245 offset: secondary_table_offset,
246 read_write_capability: ReadWriteCapability::READ_ONLY,
247 ..ComponentDisk::new()
248 });
249
250 // Calculate CRC32 of partition entries.
251 let mut hasher = Hasher::new();
252 hasher.update(&partitions_buffer);
253 let partition_entries_crc32 = hasher.finalize();
254
255 let disk_guid = Uuid::new_v4();
256 write_beginning(
257 header_file,
258 disk_guid,
259 &partitions_buffer,
260 partition_entries_crc32,
261 secondary_table_offset,
262 disk_size,
263 )?;
264 write_end(
265 footer_file,
266 disk_guid,
267 &partitions_buffer,
268 partition_entries_crc32,
269 secondary_table_offset,
270 disk_size,
271 )?;
272
273 composite_proto.length = disk_size;
274 output_composite.write_all(CDISK_MAGIC.as_bytes())?;
275 composite_proto.write_to_writer(output_composite)?;
276
277 Ok(())
278}
279
280/// Constructs a composite disk image for the given list of partitions, and opens it ready to use.
281///
282/// Returns the composite disk image file, and a list of FD mappings which must be applied to any
283/// process which wants to use it. This is necessary because the composite image contains paths of
284/// the form `/proc/self/fd/N` for the partition images.
285pub fn make_composite_image(
286 partitions: &[Partition],
287 output_path: &Path,
288 header_path: &Path,
289 footer_path: &Path,
290) -> Result<(File, Vec<File>), Error> {
291 let (partitions, files) = convert_partitions(partitions)?;
292
293 let mut composite_image = OpenOptions::new()
294 .create_new(true)
295 .read(true)
296 .write(true)
297 .open(output_path)
298 .with_context(|| format!("Failed to create composite image {:?}", output_path))?;
299 let mut header_file =
300 OpenOptions::new().create_new(true).read(true).write(true).open(header_path).with_context(
301 || format!("Failed to create composite image header {:?}", header_path),
302 )?;
303 let mut footer_file =
304 OpenOptions::new().create_new(true).read(true).write(true).open(footer_path).with_context(
305 || format!("Failed to create composite image header {:?}", footer_path),
306 )?;
307
308 create_composite_disk(
309 &partitions,
310 header_path,
311 &mut header_file,
312 footer_path,
313 &mut footer_file,
314 &mut composite_image,
315 )?;
316
317 // Re-open the composite image as read-only.
318 let composite_image = File::open(&output_path)
319 .with_context(|| format!("Failed to open composite image {:?}", output_path))?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000320
321 Ok((composite_image, files))
322}
323
324/// Given the AIDL config containing a list of partitions, with a [`ParcelFileDescriptor`] for each
325/// partition, return the list of file descriptors which must be passed to the mk_cdisk child
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000326/// process and the composite disk image partition configuration for it.
327fn convert_partitions(partitions: &[Partition]) -> Result<(Vec<PartitionInfo>, Vec<File>), Error> {
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000328 // File descriptors to pass to child process.
329 let mut files = vec![];
330
331 let partitions = partitions
332 .iter()
333 .map(|partition| {
334 // TODO(b/187187765): This shouldn't be an Option.
335 let file = partition
336 .image
337 .as_ref()
338 .context("Invalid partition image file descriptor")?
339 .as_ref()
340 .try_clone()
341 .context("Failed to clone partition image file descriptor")?;
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000342 let size = get_partition_size(&file)?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000343 let fd = file.as_raw_fd();
344 files.push(file);
345
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000346 Ok(PartitionInfo {
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000347 label: partition.label.to_owned(),
348 path: format!("/proc/self/fd/{}", fd).into(),
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000349 partition_type: ImagePartitionType::LinuxFilesystem,
350 writable: partition.writable,
351 size,
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000352 })
353 })
354 .collect::<Result<_, Error>>()?;
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000355
Andrew Walbran3eca16c2021-06-14 11:15:14 +0000356 Ok((partitions, files))
357}
358
359/// Find the size of the partition image in the given file by parsing the header.
360///
361/// This will work for raw, QCOW2, composite and Android sparse images.
362fn get_partition_size(partition: &File) -> Result<u64, Error> {
363 // TODO: Use `context` once disk::Error implements std::error::Error.
364 Ok(create_disk_file(partition.try_clone()?)
365 .map_err(|e| anyhow!("Failed to open partition image: {}", e))?
366 .get_len()?)
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn beginning_size() {
375 let mut buffer = vec![];
376 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
377 let disk_size = 1000 * SECTOR_SIZE;
378 write_beginning(
379 &mut buffer,
380 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
381 &partitions,
382 42,
383 disk_size - GPT_END_SIZE,
384 disk_size,
385 )
386 .unwrap();
387
388 assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
389 }
390
391 #[test]
392 fn end_size() {
393 let mut buffer = vec![];
394 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
395 let disk_size = 1000 * SECTOR_SIZE;
396 write_end(
397 &mut buffer,
398 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
399 &partitions,
400 42,
401 disk_size - GPT_END_SIZE,
402 disk_size,
403 )
404 .unwrap();
405
406 assert_eq!(buffer.len(), GPT_END_SIZE as usize);
407 }
408
409 #[test]
410 fn end_size_with_padding() {
411 let mut buffer = vec![];
412 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
413 let disk_size = 1000 * SECTOR_SIZE;
414 let padding = 3 * SECTOR_SIZE;
415 write_end(
416 &mut buffer,
417 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
418 &partitions,
419 42,
420 disk_size - GPT_END_SIZE - padding,
421 disk_size,
422 )
423 .unwrap();
424
425 assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
426 }
Andrew Walbranf5fbb7d2021-05-12 17:15:48 +0000427}