• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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