1 // Copyright 2019 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::cmp::{max, min};
6 use std::collections::HashSet;
7 use std::convert::TryInto;
8 use std::fs::{File, OpenOptions};
9 use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
10 use std::ops::Range;
11 use std::path::{Path, PathBuf};
12
13 use base::{
14 open_file, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync,
15 PunchHole, RawDescriptor, WriteZeroesAt,
16 };
17 use crc32fast::Hasher;
18 use data_model::VolatileSlice;
19 use protobuf::Message;
20 use protos::cdisk_spec::{self, ComponentDisk, CompositeDisk, ReadWriteCapability};
21 use remain::sorted;
22 use thiserror::Error;
23 use uuid::Uuid;
24
25 use crate::gpt::{
26 self, write_gpt_header, write_protective_mbr, GptPartitionEntry, GPT_BEGINNING_SIZE,
27 GPT_END_SIZE, GPT_HEADER_SIZE, GPT_NUM_PARTITIONS, GPT_PARTITION_ENTRY_SIZE, SECTOR_SIZE,
28 };
29 use crate::{create_disk_file, DiskFile, DiskGetLen, ImageType};
30
31 /// The amount of padding needed between the last partition entry and the first partition, to align
32 /// the partition appropriately. The two sectors are for the MBR and the GPT header.
33 const PARTITION_ALIGNMENT_SIZE: usize = GPT_BEGINNING_SIZE as usize
34 - 2 * SECTOR_SIZE as usize
35 - GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize;
36 const HEADER_PADDING_LENGTH: usize = SECTOR_SIZE as usize - GPT_HEADER_SIZE as usize;
37 // Keep all partitions 4k aligned for performance.
38 const PARTITION_SIZE_SHIFT: u8 = 12;
39 // Keep the disk size a multiple of 64k for crosvm's virtio_blk driver.
40 const DISK_SIZE_SHIFT: u8 = 16;
41
42 // From https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs.
43 const LINUX_FILESYSTEM_GUID: Uuid = Uuid::from_u128(0x0FC63DAF_8483_4772_8E79_3D69D8477DE4);
44 const EFI_SYSTEM_PARTITION_GUID: Uuid = Uuid::from_u128(0xC12A7328_F81F_11D2_BA4B_00A0C93EC93B);
45
46 #[sorted]
47 #[derive(Error, Debug)]
48 pub enum Error {
49 #[error("failed to use underlying disk: \"{0}\"")]
50 DiskError(Box<crate::Error>),
51 #[error("duplicate GPT partition label \"{0}\"")]
52 DuplicatePartitionLabel(String),
53 #[error("failed to write GPT header: \"{0}\"")]
54 GptError(gpt::Error),
55 #[error("invalid magic header for composite disk format")]
56 InvalidMagicHeader,
57 #[error("invalid partition path {0:?}")]
58 InvalidPath(PathBuf),
59 #[error("failed to parse specification proto: \"{0}\"")]
60 InvalidProto(protobuf::ProtobufError),
61 #[error("invalid specification: \"{0}\"")]
62 InvalidSpecification(String),
63 #[error("no image files for partition {0:?}")]
64 NoImageFiles(PartitionInfo),
65 #[error("failed to open component file \"{1}\": \"{0}\"")]
66 OpenFile(io::Error, String),
67 #[error("failed to read specification: \"{0}\"")]
68 ReadSpecificationError(io::Error),
69 #[error("Read-write partition {0:?} size is not a multiple of {}.", 1 << PARTITION_SIZE_SHIFT)]
70 UnalignedReadWrite(PartitionInfo),
71 #[error("unknown version {0} in specification")]
72 UnknownVersion(u64),
73 #[error("unsupported component disk type \"{0:?}\"")]
74 UnsupportedComponent(ImageType),
75 #[error("failed to write composite disk header: \"{0}\"")]
76 WriteHeader(io::Error),
77 #[error("failed to write specification proto: \"{0}\"")]
78 WriteProto(protobuf::ProtobufError),
79 #[error("failed to write zero filler: \"{0}\"")]
80 WriteZeroFiller(io::Error),
81 }
82
83 impl From<gpt::Error> for Error {
from(e: gpt::Error) -> Self84 fn from(e: gpt::Error) -> Self {
85 Self::GptError(e)
86 }
87 }
88
89 pub type Result<T> = std::result::Result<T, Error>;
90
91 #[derive(Debug)]
92 struct ComponentDiskPart {
93 file: Box<dyn DiskFile>,
94 offset: u64,
95 length: u64,
96 }
97
98 impl ComponentDiskPart {
range(&self) -> Range<u64>99 fn range(&self) -> Range<u64> {
100 self.offset..(self.offset + self.length)
101 }
102 }
103
104 /// Represents a composite virtual disk made out of multiple component files. This is described on
105 /// disk by a protocol buffer file that lists out the component file locations and their offsets
106 /// and lengths on the virtual disk. The spaces covered by the component disks must be contiguous
107 /// and not overlapping.
108 #[derive(Debug)]
109 pub struct CompositeDiskFile {
110 component_disks: Vec<ComponentDiskPart>,
111 }
112
ranges_overlap(a: &Range<u64>, b: &Range<u64>) -> bool113 fn ranges_overlap(a: &Range<u64>, b: &Range<u64>) -> bool {
114 // essentially !range_intersection(a, b).is_empty(), but that's experimental
115 let intersection = range_intersection(a, b);
116 intersection.start < intersection.end
117 }
118
range_intersection(a: &Range<u64>, b: &Range<u64>) -> Range<u64>119 fn range_intersection(a: &Range<u64>, b: &Range<u64>) -> Range<u64> {
120 Range {
121 start: max(a.start, b.start),
122 end: min(a.end, b.end),
123 }
124 }
125
126 /// The version of the composite disk format supported by this implementation.
127 const COMPOSITE_DISK_VERSION: u64 = 2;
128
129 /// A magic string placed at the beginning of a composite disk file to identify it.
130 pub const CDISK_MAGIC: &str = "composite_disk\x1d";
131 /// The length of the CDISK_MAGIC string. Created explicitly as a static constant so that it is
132 /// possible to create a character array of the same length.
133 pub const CDISK_MAGIC_LEN: usize = CDISK_MAGIC.len();
134
135 impl CompositeDiskFile {
new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile>136 fn new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile> {
137 disks.sort_by(|d1, d2| d1.offset.cmp(&d2.offset));
138 let contiguous_err = disks
139 .windows(2)
140 .map(|s| {
141 if s[0].offset == s[1].offset {
142 let text = format!("Two disks at offset {}", s[0].offset);
143 Err(Error::InvalidSpecification(text))
144 } else {
145 Ok(())
146 }
147 })
148 .find(|r| r.is_err());
149 if let Some(Err(e)) = contiguous_err {
150 return Err(e);
151 }
152 Ok(CompositeDiskFile {
153 component_disks: disks,
154 })
155 }
156
157 /// Set up a composite disk by reading the specification from a file. The file must consist of
158 /// the CDISK_MAGIC string followed by one binary instance of the CompositeDisk protocol
159 /// buffer. Returns an error if it could not read the file or if the specification was invalid.
from_file( mut file: File, max_nesting_depth: u32, image_path: &Path, ) -> Result<CompositeDiskFile>160 pub fn from_file(
161 mut file: File,
162 max_nesting_depth: u32,
163 image_path: &Path,
164 ) -> Result<CompositeDiskFile> {
165 file.seek(SeekFrom::Start(0))
166 .map_err(Error::ReadSpecificationError)?;
167 let mut magic_space = [0u8; CDISK_MAGIC_LEN];
168 file.read_exact(&mut magic_space[..])
169 .map_err(Error::ReadSpecificationError)?;
170 if magic_space != CDISK_MAGIC.as_bytes() {
171 return Err(Error::InvalidMagicHeader);
172 }
173 let proto: cdisk_spec::CompositeDisk =
174 Message::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
175 if proto.get_version() > COMPOSITE_DISK_VERSION {
176 return Err(Error::UnknownVersion(proto.get_version()));
177 }
178 let mut disks: Vec<ComponentDiskPart> = proto
179 .get_component_disks()
180 .iter()
181 .map(|disk| {
182 let path = if proto.get_version() == 1 {
183 PathBuf::from(disk.get_file_path())
184 } else {
185 image_path.parent().unwrap().join(disk.get_file_path())
186 };
187 let comp_file = open_file(
188 &path,
189 OpenOptions::new().read(true).write(
190 disk.get_read_write_capability()
191 == cdisk_spec::ReadWriteCapability::READ_WRITE,
192 ), // TODO(b/190435784): add support for O_DIRECT.
193 )
194 .map_err(|e| Error::OpenFile(e.into(), disk.get_file_path().to_string()))?;
195 Ok(ComponentDiskPart {
196 file: create_disk_file(comp_file, max_nesting_depth, &path)
197 .map_err(|e| Error::DiskError(Box::new(e)))?,
198 offset: disk.get_offset(),
199 length: 0, // Assigned later
200 })
201 })
202 .collect::<Result<Vec<ComponentDiskPart>>>()?;
203 disks.sort_by(|d1, d2| d1.offset.cmp(&d2.offset));
204 for i in 0..(disks.len() - 1) {
205 let length = disks[i + 1].offset - disks[i].offset;
206 if length == 0 {
207 let text = format!("Two disks at offset {}", disks[i].offset);
208 return Err(Error::InvalidSpecification(text));
209 }
210 if let Some(disk) = disks.get_mut(i) {
211 disk.length = length;
212 } else {
213 let text = format!("Unable to set disk length {}", length);
214 return Err(Error::InvalidSpecification(text));
215 }
216 }
217 let num_disks = disks.len();
218 if let Some(last_disk) = disks.get_mut(num_disks - 1) {
219 if proto.get_length() <= last_disk.offset {
220 let text = format!(
221 "Full size of disk doesn't match last offset. {} <= {}",
222 proto.get_length(),
223 last_disk.offset
224 );
225 return Err(Error::InvalidSpecification(text));
226 }
227 last_disk.length = proto.get_length() - last_disk.offset;
228 } else {
229 let text = format!(
230 "Unable to set last disk length to end at {}",
231 proto.get_length()
232 );
233 return Err(Error::InvalidSpecification(text));
234 }
235
236 CompositeDiskFile::new(disks)
237 }
238
length(&self) -> u64239 fn length(&self) -> u64 {
240 if let Some(disk) = self.component_disks.last() {
241 disk.offset + disk.length
242 } else {
243 0
244 }
245 }
246
disk_at_offset(&mut self, offset: u64) -> io::Result<&mut ComponentDiskPart>247 fn disk_at_offset(&mut self, offset: u64) -> io::Result<&mut ComponentDiskPart> {
248 self.component_disks
249 .iter_mut()
250 .find(|disk| disk.range().contains(&offset))
251 .ok_or(io::Error::new(
252 ErrorKind::InvalidData,
253 format!("no disk at offset {}", offset),
254 ))
255 }
256
disks_in_range<'a>(&'a mut self, range: &Range<u64>) -> Vec<&'a mut ComponentDiskPart>257 fn disks_in_range<'a>(&'a mut self, range: &Range<u64>) -> Vec<&'a mut ComponentDiskPart> {
258 self.component_disks
259 .iter_mut()
260 .filter(|disk| ranges_overlap(&disk.range(), range))
261 .collect()
262 }
263 }
264
265 impl DiskGetLen for CompositeDiskFile {
get_len(&self) -> io::Result<u64>266 fn get_len(&self) -> io::Result<u64> {
267 Ok(self.length())
268 }
269 }
270
271 impl FileSetLen for CompositeDiskFile {
set_len(&self, _len: u64) -> io::Result<()>272 fn set_len(&self, _len: u64) -> io::Result<()> {
273 Err(io::Error::new(ErrorKind::Other, "unsupported operation"))
274 }
275 }
276
277 impl FileSync for CompositeDiskFile {
fsync(&mut self) -> io::Result<()>278 fn fsync(&mut self) -> io::Result<()> {
279 for disk in self.component_disks.iter_mut() {
280 disk.file.fsync()?;
281 }
282 Ok(())
283 }
284 }
285
286 // Implements Read and Write targeting volatile storage for composite disks.
287 //
288 // Note that reads and writes will return early if crossing component disk boundaries.
289 // This is allowed by the read and write specifications, which only say read and write
290 // have to return how many bytes were actually read or written. Use read_exact_volatile
291 // or write_all_volatile to make sure all bytes are received/transmitted.
292 //
293 // If one of the component disks does a partial read or write, that also gets passed
294 // transparently to the parent.
295 impl FileReadWriteAtVolatile for CompositeDiskFile {
read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize>296 fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
297 let cursor_location = offset;
298 let disk = self.disk_at_offset(cursor_location)?;
299 let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
300 let new_size = disk.offset + disk.length - cursor_location;
301 slice
302 .sub_slice(0, new_size as usize)
303 .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
304 } else {
305 slice
306 };
307 disk.file
308 .read_at_volatile(subslice, cursor_location - disk.offset)
309 }
write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize>310 fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
311 let cursor_location = offset;
312 let disk = self.disk_at_offset(cursor_location)?;
313 let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
314 let new_size = disk.offset + disk.length - cursor_location;
315 slice
316 .sub_slice(0, new_size as usize)
317 .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
318 } else {
319 slice
320 };
321 disk.file
322 .write_at_volatile(subslice, cursor_location - disk.offset)
323 }
324 }
325
326 impl PunchHole for CompositeDiskFile {
punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()>327 fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
328 let range = offset..(offset + length);
329 let disks = self.disks_in_range(&range);
330 for disk in disks {
331 let intersection = range_intersection(&range, &disk.range());
332 if intersection.start >= intersection.end {
333 continue;
334 }
335 let result = disk.file.punch_hole(
336 intersection.start - disk.offset,
337 intersection.end - intersection.start,
338 );
339 result?;
340 }
341 Ok(())
342 }
343 }
344
345 impl FileAllocate for CompositeDiskFile {
allocate(&mut self, offset: u64, length: u64) -> io::Result<()>346 fn allocate(&mut self, offset: u64, length: u64) -> io::Result<()> {
347 let range = offset..(offset + length);
348 let disks = self.disks_in_range(&range);
349 for disk in disks {
350 let intersection = range_intersection(&range, &disk.range());
351 if intersection.start >= intersection.end {
352 continue;
353 }
354 let result = disk.file.allocate(
355 intersection.start - disk.offset,
356 intersection.end - intersection.start,
357 );
358 result?;
359 }
360 Ok(())
361 }
362 }
363
364 impl WriteZeroesAt for CompositeDiskFile {
write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize>365 fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
366 let cursor_location = offset;
367 let disk = self.disk_at_offset(cursor_location)?;
368 let offset_within_disk = cursor_location - disk.offset;
369 let new_length = if cursor_location + length as u64 > disk.offset + disk.length {
370 (disk.offset + disk.length - cursor_location) as usize
371 } else {
372 length
373 };
374 disk.file.write_zeroes_at(offset_within_disk, new_length)
375 }
376 }
377
378 impl AsRawDescriptors for CompositeDiskFile {
as_raw_descriptors(&self) -> Vec<RawDescriptor>379 fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
380 self.component_disks
381 .iter()
382 .flat_map(|d| d.file.as_raw_descriptors())
383 .collect()
384 }
385 }
386
387 /// Information about a partition to create.
388 #[derive(Clone, Debug, Eq, PartialEq)]
389 pub struct PartitionInfo {
390 pub label: String,
391 pub path: PathBuf,
392 pub partition_type: ImagePartitionType,
393 pub writable: bool,
394 pub size: u64,
395 }
396
397 /// Round `val` up to the next multiple of 2**`align_log`.
align_to_power_of_2(val: u64, align_log: u8) -> u64398 fn align_to_power_of_2(val: u64, align_log: u8) -> u64 {
399 let align = 1 << align_log;
400 ((val + (align - 1)) / align) * align
401 }
402
403 impl PartitionInfo {
aligned_size(&self) -> u64404 fn aligned_size(&self) -> u64 {
405 align_to_power_of_2(self.size, PARTITION_SIZE_SHIFT)
406 }
407 }
408
409 /// The type of partition.
410 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
411 pub enum ImagePartitionType {
412 LinuxFilesystem,
413 EfiSystemPartition,
414 }
415
416 impl ImagePartitionType {
guid(self) -> Uuid417 fn guid(self) -> Uuid {
418 match self {
419 Self::LinuxFilesystem => LINUX_FILESYSTEM_GUID,
420 Self::EfiSystemPartition => EFI_SYSTEM_PARTITION_GUID,
421 }
422 }
423 }
424
425 /// Write protective MBR and primary GPT table.
write_beginning( file: &mut impl Write, disk_guid: Uuid, partitions: &[u8], partition_entries_crc32: u32, secondary_table_offset: u64, disk_size: u64, ) -> Result<()>426 fn write_beginning(
427 file: &mut impl Write,
428 disk_guid: Uuid,
429 partitions: &[u8],
430 partition_entries_crc32: u32,
431 secondary_table_offset: u64,
432 disk_size: u64,
433 ) -> Result<()> {
434 // Write the protective MBR to the first sector.
435 write_protective_mbr(file, disk_size)?;
436
437 // Write the GPT header, and pad out to the end of the sector.
438 write_gpt_header(
439 file,
440 disk_guid,
441 partition_entries_crc32,
442 secondary_table_offset,
443 false,
444 )?;
445 file.write_all(&[0; HEADER_PADDING_LENGTH])
446 .map_err(Error::WriteHeader)?;
447
448 // Write partition entries, including unused ones.
449 file.write_all(partitions).map_err(Error::WriteHeader)?;
450
451 // Write zeroes to align the first partition appropriately.
452 file.write_all(&[0; PARTITION_ALIGNMENT_SIZE])
453 .map_err(Error::WriteHeader)?;
454
455 Ok(())
456 }
457
458 /// Write secondary GPT table.
write_end( file: &mut impl Write, disk_guid: Uuid, partitions: &[u8], partition_entries_crc32: u32, secondary_table_offset: u64, disk_size: u64, ) -> Result<()>459 fn write_end(
460 file: &mut impl Write,
461 disk_guid: Uuid,
462 partitions: &[u8],
463 partition_entries_crc32: u32,
464 secondary_table_offset: u64,
465 disk_size: u64,
466 ) -> Result<()> {
467 // Write partition entries, including unused ones.
468 file.write_all(partitions).map_err(Error::WriteHeader)?;
469
470 // Write the GPT header, and pad out to the end of the sector.
471 write_gpt_header(
472 file,
473 disk_guid,
474 partition_entries_crc32,
475 secondary_table_offset,
476 true,
477 )?;
478 file.write_all(&[0; HEADER_PADDING_LENGTH])
479 .map_err(Error::WriteHeader)?;
480
481 // Pad out to the aligned disk size.
482 let used_disk_size = secondary_table_offset + GPT_END_SIZE;
483 let padding = disk_size - used_disk_size;
484 file.write_all(&vec![0; padding as usize])
485 .map_err(Error::WriteHeader)?;
486
487 Ok(())
488 }
489
490 /// Create the `GptPartitionEntry` for the given partition.
create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry491 fn create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry {
492 let mut partition_name: Vec<u16> = partition.label.encode_utf16().collect();
493 partition_name.resize(36, 0);
494
495 GptPartitionEntry {
496 partition_type_guid: partition.partition_type.guid(),
497 unique_partition_guid: Uuid::new_v4(),
498 first_lba: offset / SECTOR_SIZE,
499 last_lba: (offset + partition.aligned_size()) / SECTOR_SIZE - 1,
500 attributes: 0,
501 partition_name: partition_name.try_into().unwrap(),
502 }
503 }
504
505 /// Create one or more `ComponentDisk` proto messages for the given partition.
create_component_disks( partition: &PartitionInfo, offset: u64, zero_filler_path: &str, ) -> Result<Vec<ComponentDisk>>506 fn create_component_disks(
507 partition: &PartitionInfo,
508 offset: u64,
509 zero_filler_path: &str,
510 ) -> Result<Vec<ComponentDisk>> {
511 let aligned_size = partition.aligned_size();
512
513 let mut component_disks = vec![ComponentDisk {
514 offset,
515 file_path: partition
516 .path
517 .to_str()
518 .ok_or_else(|| Error::InvalidPath(partition.path.to_owned()))?
519 .to_string(),
520 read_write_capability: if partition.writable {
521 ReadWriteCapability::READ_WRITE
522 } else {
523 ReadWriteCapability::READ_ONLY
524 },
525 ..ComponentDisk::new()
526 }];
527
528 if partition.size != aligned_size {
529 if partition.writable {
530 return Err(Error::UnalignedReadWrite(partition.to_owned()));
531 } else {
532 // Fill in the gap by reusing the zero filler file, because we know it is always bigger
533 // than the alignment size. Its size is 1 << PARTITION_SIZE_SHIFT (4k).
534 component_disks.push(ComponentDisk {
535 offset: offset + partition.size,
536 file_path: zero_filler_path.to_owned(),
537 read_write_capability: ReadWriteCapability::READ_ONLY,
538 ..ComponentDisk::new()
539 });
540 }
541 }
542
543 Ok(component_disks)
544 }
545
546 /// Create a new composite disk image containing the given partitions, and write it out to the given
547 /// files.
create_composite_disk( partitions: &[PartitionInfo], zero_filler_path: &Path, header_path: &Path, header_file: &mut File, footer_path: &Path, footer_file: &mut File, output_composite: &mut File, ) -> Result<()>548 pub fn create_composite_disk(
549 partitions: &[PartitionInfo],
550 zero_filler_path: &Path,
551 header_path: &Path,
552 header_file: &mut File,
553 footer_path: &Path,
554 footer_file: &mut File,
555 output_composite: &mut File,
556 ) -> Result<()> {
557 let zero_filler_path = zero_filler_path
558 .to_str()
559 .ok_or_else(|| Error::InvalidPath(zero_filler_path.to_owned()))?
560 .to_string();
561 let header_path = header_path
562 .to_str()
563 .ok_or_else(|| Error::InvalidPath(header_path.to_owned()))?
564 .to_string();
565 let footer_path = footer_path
566 .to_str()
567 .ok_or_else(|| Error::InvalidPath(footer_path.to_owned()))?
568 .to_string();
569
570 let mut composite_proto = CompositeDisk::new();
571 composite_proto.version = COMPOSITE_DISK_VERSION;
572 composite_proto.component_disks.push(ComponentDisk {
573 file_path: header_path,
574 offset: 0,
575 read_write_capability: ReadWriteCapability::READ_ONLY,
576 ..ComponentDisk::new()
577 });
578
579 // Write partitions to a temporary buffer so that we can calculate the CRC, and construct the
580 // ComponentDisk proto messages at the same time.
581 let mut partitions_buffer =
582 [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
583 let mut writer: &mut [u8] = &mut partitions_buffer;
584 let mut next_disk_offset = GPT_BEGINNING_SIZE;
585 let mut labels = HashSet::with_capacity(partitions.len());
586 for partition in partitions {
587 let gpt_entry = create_gpt_entry(partition, next_disk_offset);
588 if !labels.insert(gpt_entry.partition_name) {
589 return Err(Error::DuplicatePartitionLabel(partition.label.clone()));
590 }
591 gpt_entry.write_bytes(&mut writer)?;
592
593 for component_disk in
594 create_component_disks(partition, next_disk_offset, &zero_filler_path)?
595 {
596 composite_proto.component_disks.push(component_disk);
597 }
598
599 next_disk_offset += partition.aligned_size();
600 }
601 let secondary_table_offset = next_disk_offset;
602 let disk_size = align_to_power_of_2(secondary_table_offset + GPT_END_SIZE, DISK_SIZE_SHIFT);
603
604 composite_proto.component_disks.push(ComponentDisk {
605 file_path: footer_path,
606 offset: secondary_table_offset,
607 read_write_capability: ReadWriteCapability::READ_ONLY,
608 ..ComponentDisk::new()
609 });
610
611 // Calculate CRC32 of partition entries.
612 let mut hasher = Hasher::new();
613 hasher.update(&partitions_buffer);
614 let partition_entries_crc32 = hasher.finalize();
615
616 let disk_guid = Uuid::new_v4();
617 write_beginning(
618 header_file,
619 disk_guid,
620 &partitions_buffer,
621 partition_entries_crc32,
622 secondary_table_offset,
623 disk_size,
624 )?;
625 write_end(
626 footer_file,
627 disk_guid,
628 &partitions_buffer,
629 partition_entries_crc32,
630 secondary_table_offset,
631 disk_size,
632 )?;
633
634 composite_proto.length = disk_size;
635 output_composite
636 .write_all(CDISK_MAGIC.as_bytes())
637 .map_err(Error::WriteHeader)?;
638 composite_proto
639 .write_to_writer(output_composite)
640 .map_err(Error::WriteProto)?;
641
642 Ok(())
643 }
644
645 /// Create a zero filler file which can be used to fill the gaps between partition files.
646 /// The filler is sized to be big enough to fill the gaps. (1 << PARTITION_SIZE_SHIFT)
create_zero_filler<P: AsRef<Path>>(zero_filler_path: P) -> Result<()>647 pub fn create_zero_filler<P: AsRef<Path>>(zero_filler_path: P) -> Result<()> {
648 let f = OpenOptions::new()
649 .create(true)
650 .read(true)
651 .write(true)
652 .truncate(true)
653 .open(zero_filler_path.as_ref())
654 .map_err(Error::WriteZeroFiller)?;
655 f.set_len(1 << PARTITION_SIZE_SHIFT)
656 .map_err(Error::WriteZeroFiller)
657 }
658
659 #[cfg(test)]
660 mod tests {
661 use super::*;
662
663 use std::matches;
664
665 use base::AsRawDescriptor;
666 use data_model::VolatileMemory;
667 use tempfile::tempfile;
668
669 #[test]
block_duplicate_offset_disks()670 fn block_duplicate_offset_disks() {
671 let file1 = tempfile().unwrap();
672 let file2 = tempfile().unwrap();
673 let disk_part1 = ComponentDiskPart {
674 file: Box::new(file1),
675 offset: 0,
676 length: 100,
677 };
678 let disk_part2 = ComponentDiskPart {
679 file: Box::new(file2),
680 offset: 0,
681 length: 100,
682 };
683 assert!(CompositeDiskFile::new(vec![disk_part1, disk_part2]).is_err());
684 }
685
686 #[test]
get_len()687 fn get_len() {
688 let file1 = tempfile().unwrap();
689 let file2 = tempfile().unwrap();
690 let disk_part1 = ComponentDiskPart {
691 file: Box::new(file1),
692 offset: 0,
693 length: 100,
694 };
695 let disk_part2 = ComponentDiskPart {
696 file: Box::new(file2),
697 offset: 100,
698 length: 100,
699 };
700 let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2]).unwrap();
701 let len = composite.get_len().unwrap();
702 assert_eq!(len, 200);
703 }
704
705 #[test]
single_file_passthrough()706 fn single_file_passthrough() {
707 let file = tempfile().unwrap();
708 let disk_part = ComponentDiskPart {
709 file: Box::new(file),
710 offset: 0,
711 length: 100,
712 };
713 let mut composite = CompositeDiskFile::new(vec![disk_part]).unwrap();
714 let mut input_memory = [55u8; 5];
715 let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
716 composite
717 .write_all_at_volatile(input_volatile_memory.get_slice(0, 5).unwrap(), 0)
718 .unwrap();
719 let mut output_memory = [0u8; 5];
720 let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
721 composite
722 .read_exact_at_volatile(output_volatile_memory.get_slice(0, 5).unwrap(), 0)
723 .unwrap();
724 assert_eq!(input_memory, output_memory);
725 }
726
727 #[test]
triple_file_fds()728 fn triple_file_fds() {
729 let file1 = tempfile().unwrap();
730 let file2 = tempfile().unwrap();
731 let file3 = tempfile().unwrap();
732 let mut in_fds = vec![
733 file1.as_raw_descriptor(),
734 file2.as_raw_descriptor(),
735 file3.as_raw_descriptor(),
736 ];
737 in_fds.sort_unstable();
738 let disk_part1 = ComponentDiskPart {
739 file: Box::new(file1),
740 offset: 0,
741 length: 100,
742 };
743 let disk_part2 = ComponentDiskPart {
744 file: Box::new(file2),
745 offset: 100,
746 length: 100,
747 };
748 let disk_part3 = ComponentDiskPart {
749 file: Box::new(file3),
750 offset: 200,
751 length: 100,
752 };
753 let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
754 let mut out_fds = composite.as_raw_descriptors();
755 out_fds.sort_unstable();
756 assert_eq!(in_fds, out_fds);
757 }
758
759 #[test]
triple_file_passthrough()760 fn triple_file_passthrough() {
761 let file1 = tempfile().unwrap();
762 let file2 = tempfile().unwrap();
763 let file3 = tempfile().unwrap();
764 let disk_part1 = ComponentDiskPart {
765 file: Box::new(file1),
766 offset: 0,
767 length: 100,
768 };
769 let disk_part2 = ComponentDiskPart {
770 file: Box::new(file2),
771 offset: 100,
772 length: 100,
773 };
774 let disk_part3 = ComponentDiskPart {
775 file: Box::new(file3),
776 offset: 200,
777 length: 100,
778 };
779 let mut composite =
780 CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
781 let mut input_memory = [55u8; 200];
782 let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
783 composite
784 .write_all_at_volatile(input_volatile_memory.get_slice(0, 200).unwrap(), 50)
785 .unwrap();
786 let mut output_memory = [0u8; 200];
787 let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
788 composite
789 .read_exact_at_volatile(output_volatile_memory.get_slice(0, 200).unwrap(), 50)
790 .unwrap();
791 assert!(input_memory.iter().eq(output_memory.iter()));
792 }
793
794 #[test]
triple_file_punch_hole()795 fn triple_file_punch_hole() {
796 let file1 = tempfile().unwrap();
797 let file2 = tempfile().unwrap();
798 let file3 = tempfile().unwrap();
799 let disk_part1 = ComponentDiskPart {
800 file: Box::new(file1),
801 offset: 0,
802 length: 100,
803 };
804 let disk_part2 = ComponentDiskPart {
805 file: Box::new(file2),
806 offset: 100,
807 length: 100,
808 };
809 let disk_part3 = ComponentDiskPart {
810 file: Box::new(file3),
811 offset: 200,
812 length: 100,
813 };
814 let mut composite =
815 CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
816 let mut input_memory = [55u8; 300];
817 let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
818 composite
819 .write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0)
820 .unwrap();
821 composite.punch_hole(50, 200).unwrap();
822 let mut output_memory = [0u8; 300];
823 let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
824 composite
825 .read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
826 .unwrap();
827
828 input_memory[50..250].iter_mut().for_each(|x| *x = 0);
829 assert!(input_memory.iter().eq(output_memory.iter()));
830 }
831
832 #[test]
triple_file_write_zeroes()833 fn triple_file_write_zeroes() {
834 let file1 = tempfile().unwrap();
835 let file2 = tempfile().unwrap();
836 let file3 = tempfile().unwrap();
837 let disk_part1 = ComponentDiskPart {
838 file: Box::new(file1),
839 offset: 0,
840 length: 100,
841 };
842 let disk_part2 = ComponentDiskPart {
843 file: Box::new(file2),
844 offset: 100,
845 length: 100,
846 };
847 let disk_part3 = ComponentDiskPart {
848 file: Box::new(file3),
849 offset: 200,
850 length: 100,
851 };
852 let mut composite =
853 CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
854 let mut input_memory = [55u8; 300];
855 let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
856 composite
857 .write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0)
858 .unwrap();
859 let mut zeroes_written = 0;
860 while zeroes_written < 200 {
861 zeroes_written += composite
862 .write_zeroes_at(50 + zeroes_written as u64, 200 - zeroes_written)
863 .unwrap();
864 }
865 let mut output_memory = [0u8; 300];
866 let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
867 composite
868 .read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
869 .unwrap();
870
871 input_memory[50..250].iter_mut().for_each(|x| *x = 0);
872 for i in 0..300 {
873 println!(
874 "input[{0}] = {1}, output[{0}] = {2}",
875 i, input_memory[i], output_memory[i]
876 );
877 }
878 assert!(input_memory.iter().eq(output_memory.iter()));
879 }
880
881 #[test]
beginning_size()882 fn beginning_size() {
883 let mut buffer = vec![];
884 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
885 let disk_size = 1000 * SECTOR_SIZE;
886 write_beginning(
887 &mut buffer,
888 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
889 &partitions,
890 42,
891 disk_size - GPT_END_SIZE,
892 disk_size,
893 )
894 .unwrap();
895
896 assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
897 }
898
899 #[test]
end_size()900 fn end_size() {
901 let mut buffer = vec![];
902 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
903 let disk_size = 1000 * SECTOR_SIZE;
904 write_end(
905 &mut buffer,
906 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
907 &partitions,
908 42,
909 disk_size - GPT_END_SIZE,
910 disk_size,
911 )
912 .unwrap();
913
914 assert_eq!(buffer.len(), GPT_END_SIZE as usize);
915 }
916
917 #[test]
end_size_with_padding()918 fn end_size_with_padding() {
919 let mut buffer = vec![];
920 let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
921 let disk_size = 1000 * SECTOR_SIZE;
922 let padding = 3 * SECTOR_SIZE;
923 write_end(
924 &mut buffer,
925 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
926 &partitions,
927 42,
928 disk_size - GPT_END_SIZE - padding,
929 disk_size,
930 )
931 .unwrap();
932
933 assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
934 }
935
936 /// Creates a composite disk image with no partitions.
937 #[test]
create_composite_disk_empty()938 fn create_composite_disk_empty() {
939 let mut header_image = tempfile().unwrap();
940 let mut footer_image = tempfile().unwrap();
941 let mut composite_image = tempfile().unwrap();
942
943 create_composite_disk(
944 &[],
945 Path::new("/zero_filler.img"),
946 Path::new("/header_path.img"),
947 &mut header_image,
948 Path::new("/footer_path.img"),
949 &mut footer_image,
950 &mut composite_image,
951 )
952 .unwrap();
953 }
954
955 /// Creates a composite disk image with two partitions.
956 #[test]
create_composite_disk_success()957 fn create_composite_disk_success() {
958 let mut header_image = tempfile().unwrap();
959 let mut footer_image = tempfile().unwrap();
960 let mut composite_image = tempfile().unwrap();
961
962 create_composite_disk(
963 &[
964 PartitionInfo {
965 label: "partition1".to_string(),
966 path: "/partition1.img".to_string().into(),
967 partition_type: ImagePartitionType::LinuxFilesystem,
968 writable: false,
969 size: 0,
970 },
971 PartitionInfo {
972 label: "partition2".to_string(),
973 path: "/partition2.img".to_string().into(),
974 partition_type: ImagePartitionType::LinuxFilesystem,
975 writable: true,
976 size: 0,
977 },
978 ],
979 Path::new("/zero_filler.img"),
980 Path::new("/header_path.img"),
981 &mut header_image,
982 Path::new("/footer_path.img"),
983 &mut footer_image,
984 &mut composite_image,
985 )
986 .unwrap();
987 }
988
989 /// Attempts to create a composite disk image with two partitions with the same label.
990 #[test]
create_composite_disk_duplicate_label()991 fn create_composite_disk_duplicate_label() {
992 let mut header_image = tempfile().unwrap();
993 let mut footer_image = tempfile().unwrap();
994 let mut composite_image = tempfile().unwrap();
995
996 let result = create_composite_disk(
997 &[
998 PartitionInfo {
999 label: "label".to_string(),
1000 path: "/partition1.img".to_string().into(),
1001 partition_type: ImagePartitionType::LinuxFilesystem,
1002 writable: false,
1003 size: 0,
1004 },
1005 PartitionInfo {
1006 label: "label".to_string(),
1007 path: "/partition2.img".to_string().into(),
1008 partition_type: ImagePartitionType::LinuxFilesystem,
1009 writable: true,
1010 size: 0,
1011 },
1012 ],
1013 Path::new("/zero_filler.img"),
1014 Path::new("/header_path.img"),
1015 &mut header_image,
1016 Path::new("/footer_path.img"),
1017 &mut footer_image,
1018 &mut composite_image,
1019 );
1020 assert!(matches!(result, Err(Error::DuplicatePartitionLabel(label)) if label == "label"));
1021 }
1022 }
1023