• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The ChromiumOS Authors
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;
6 use std::cmp::min;
7 use std::collections::HashSet;
8 use std::convert::TryInto;
9 use std::fs::File;
10 use std::fs::OpenOptions;
11 use std::io;
12 use std::io::ErrorKind;
13 use std::io::Read;
14 use std::io::Seek;
15 use std::io::SeekFrom;
16 use std::io::Write;
17 use std::ops::Range;
18 use std::path::Path;
19 use std::path::PathBuf;
20 use std::sync::atomic::AtomicBool;
21 use std::sync::atomic::Ordering;
22 use std::sync::Arc;
23 
24 use async_trait::async_trait;
25 use base::open_file_or_duplicate;
26 use base::AsRawDescriptors;
27 use base::FileAllocate;
28 use base::FileReadWriteAtVolatile;
29 use base::FileSetLen;
30 use base::RawDescriptor;
31 use base::VolatileSlice;
32 use crc32fast::Hasher;
33 use cros_async::BackingMemory;
34 use cros_async::Executor;
35 use cros_async::MemRegionIter;
36 use protobuf::Message;
37 use protos::cdisk_spec;
38 use protos::cdisk_spec::ComponentDisk;
39 use protos::cdisk_spec::CompositeDisk;
40 use protos::cdisk_spec::ReadWriteCapability;
41 use remain::sorted;
42 use thiserror::Error;
43 use uuid::Uuid;
44 
45 use crate::create_disk_file;
46 use crate::gpt;
47 use crate::gpt::write_gpt_header;
48 use crate::gpt::write_protective_mbr;
49 use crate::gpt::GptPartitionEntry;
50 use crate::gpt::GPT_BEGINNING_SIZE;
51 use crate::gpt::GPT_END_SIZE;
52 use crate::gpt::GPT_HEADER_SIZE;
53 use crate::gpt::GPT_NUM_PARTITIONS;
54 use crate::gpt::GPT_PARTITION_ENTRY_SIZE;
55 use crate::gpt::SECTOR_SIZE;
56 use crate::AsyncDisk;
57 use crate::DiskFile;
58 use crate::DiskGetLen;
59 use crate::ImageType;
60 use crate::ToAsyncDisk;
61 
62 /// The amount of padding needed between the last partition entry and the first partition, to align
63 /// the partition appropriately. The two sectors are for the MBR and the GPT header.
64 const PARTITION_ALIGNMENT_SIZE: usize = GPT_BEGINNING_SIZE as usize
65     - 2 * SECTOR_SIZE as usize
66     - GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize;
67 const HEADER_PADDING_LENGTH: usize = SECTOR_SIZE as usize - GPT_HEADER_SIZE as usize;
68 // Keep all partitions 4k aligned for performance.
69 const PARTITION_SIZE_SHIFT: u8 = 12;
70 // Keep the disk size a multiple of 64k for crosvm's virtio_blk driver.
71 const DISK_SIZE_SHIFT: u8 = 16;
72 
73 // From https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs.
74 const LINUX_FILESYSTEM_GUID: Uuid = Uuid::from_u128(0x0FC63DAF_8483_4772_8E79_3D69D8477DE4);
75 const EFI_SYSTEM_PARTITION_GUID: Uuid = Uuid::from_u128(0xC12A7328_F81F_11D2_BA4B_00A0C93EC93B);
76 
77 #[sorted]
78 #[derive(Error, Debug)]
79 pub enum Error {
80     #[error("failed to use underlying disk: \"{0}\"")]
81     DiskError(Box<crate::Error>),
82     #[error("duplicate GPT partition label \"{0}\"")]
83     DuplicatePartitionLabel(String),
84     #[error("failed to write GPT header: \"{0}\"")]
85     GptError(gpt::Error),
86     #[error("invalid magic header for composite disk format")]
87     InvalidMagicHeader,
88     #[error("invalid partition path {0:?}")]
89     InvalidPath(PathBuf),
90     #[error("failed to parse specification proto: \"{0}\"")]
91     InvalidProto(protobuf::Error),
92     #[error("invalid specification: \"{0}\"")]
93     InvalidSpecification(String),
94     #[error("no image files for partition {0:?}")]
95     NoImageFiles(PartitionInfo),
96     #[error("failed to open component file \"{1}\": \"{0}\"")]
97     OpenFile(io::Error, String),
98     #[error("failed to read specification: \"{0}\"")]
99     ReadSpecificationError(io::Error),
100     #[error("Read-write partition {0:?} size is not a multiple of {}.", 1 << PARTITION_SIZE_SHIFT)]
101     UnalignedReadWrite(PartitionInfo),
102     #[error("unknown version {0} in specification")]
103     UnknownVersion(u64),
104     #[error("unsupported component disk type \"{0:?}\"")]
105     UnsupportedComponent(ImageType),
106     #[error("failed to write composite disk header: \"{0}\"")]
107     WriteHeader(io::Error),
108     #[error("failed to write specification proto: \"{0}\"")]
109     WriteProto(protobuf::Error),
110     #[error("failed to write zero filler: \"{0}\"")]
111     WriteZeroFiller(io::Error),
112 }
113 
114 impl From<gpt::Error> for Error {
from(e: gpt::Error) -> Self115     fn from(e: gpt::Error) -> Self {
116         Self::GptError(e)
117     }
118 }
119 
120 pub type Result<T> = std::result::Result<T, Error>;
121 
122 #[derive(Debug)]
123 struct ComponentDiskPart {
124     file: Box<dyn DiskFile>,
125     offset: u64,
126     length: u64,
127     needs_fsync: bool,
128 }
129 
130 impl ComponentDiskPart {
range(&self) -> Range<u64>131     fn range(&self) -> Range<u64> {
132         self.offset..(self.offset + self.length)
133     }
134 }
135 
136 /// Represents a composite virtual disk made out of multiple component files. This is described on
137 /// disk by a protocol buffer file that lists out the component file locations and their offsets
138 /// and lengths on the virtual disk. The spaces covered by the component disks must be contiguous
139 /// and not overlapping.
140 #[derive(Debug)]
141 pub struct CompositeDiskFile {
142     component_disks: Vec<ComponentDiskPart>,
143 }
144 
145 // TODO(b/271381851): implement `try_clone`. It allows virtio-blk to run multiple workers.
146 impl DiskFile for CompositeDiskFile {}
147 
ranges_overlap(a: &Range<u64>, b: &Range<u64>) -> bool148 fn ranges_overlap(a: &Range<u64>, b: &Range<u64>) -> bool {
149     range_intersection(a, b).is_some()
150 }
151 
range_intersection(a: &Range<u64>, b: &Range<u64>) -> Option<Range<u64>>152 fn range_intersection(a: &Range<u64>, b: &Range<u64>) -> Option<Range<u64>> {
153     let r = Range {
154         start: max(a.start, b.start),
155         end: min(a.end, b.end),
156     };
157     if r.is_empty() {
158         None
159     } else {
160         Some(r)
161     }
162 }
163 
164 /// The version of the composite disk format supported by this implementation.
165 const COMPOSITE_DISK_VERSION: u64 = 2;
166 
167 /// A magic string placed at the beginning of a composite disk file to identify it.
168 pub const CDISK_MAGIC: &str = "composite_disk\x1d";
169 
170 impl CompositeDiskFile {
new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile>171     fn new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile> {
172         disks.sort_by(|d1, d2| d1.offset.cmp(&d2.offset));
173         for s in disks.windows(2) {
174             if s[0].offset == s[1].offset {
175                 return Err(Error::InvalidSpecification(format!(
176                     "Two disks at offset {}",
177                     s[0].offset
178                 )));
179             }
180         }
181         Ok(CompositeDiskFile {
182             component_disks: disks,
183         })
184     }
185 
186     /// Set up a composite disk by reading the specification from a file. The file must consist of
187     /// the CDISK_MAGIC string followed by one binary instance of the CompositeDisk protocol
188     /// buffer. Returns an error if it could not read the file or if the specification was invalid.
from_file( mut file: File, is_sparse_file: bool, max_nesting_depth: u32, image_path: &Path, ) -> Result<CompositeDiskFile>189     pub fn from_file(
190         mut file: File,
191         is_sparse_file: bool,
192         max_nesting_depth: u32,
193         image_path: &Path,
194     ) -> Result<CompositeDiskFile> {
195         file.seek(SeekFrom::Start(0))
196             .map_err(Error::ReadSpecificationError)?;
197         let mut magic_space = [0u8; CDISK_MAGIC.len()];
198         file.read_exact(&mut magic_space[..])
199             .map_err(Error::ReadSpecificationError)?;
200         if magic_space != CDISK_MAGIC.as_bytes() {
201             return Err(Error::InvalidMagicHeader);
202         }
203         let proto: cdisk_spec::CompositeDisk =
204             Message::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
205         if proto.version > COMPOSITE_DISK_VERSION {
206             return Err(Error::UnknownVersion(proto.version));
207         }
208         let mut disks: Vec<ComponentDiskPart> = proto
209             .component_disks
210             .iter()
211             .map(|disk| {
212                 let writable = disk.read_write_capability
213                     == cdisk_spec::ReadWriteCapability::READ_WRITE.into();
214                 let component_path = PathBuf::from(&disk.file_path);
215                 let path = if component_path.is_relative() || proto.version > 1 {
216                     image_path.parent().unwrap().join(component_path)
217                 } else {
218                     component_path
219                 };
220                 let comp_file = open_file_or_duplicate(
221                     &path,
222                     OpenOptions::new().read(true).write(writable), /* TODO(b/190435784): add
223                                                                     * support for O_DIRECT. */
224                 )
225                 .map_err(|e| Error::OpenFile(e.into(), disk.file_path.to_string()))?;
226 
227                 // Note that a read-only parts of a composite disk should NOT be marked sparse,
228                 // as the action of marking them sparse is a write. This may seem a little hacky,
229                 // and it is; however:
230                 //    (a)  there is not a good way to pass sparseness parameters per composite disk
231                 //         part (the proto does not have fields for it).
232                 //    (b)  this override of sorts always matches the correct user intent.
233                 Ok(ComponentDiskPart {
234                     file: create_disk_file(
235                         comp_file,
236                         is_sparse_file && writable,
237                         max_nesting_depth,
238                         &path,
239                     )
240                     .map_err(|e| Error::DiskError(Box::new(e)))?,
241                     offset: disk.offset,
242                     length: 0, // Assigned later
243                     needs_fsync: false,
244                 })
245             })
246             .collect::<Result<Vec<ComponentDiskPart>>>()?;
247         disks.sort_by(|d1, d2| d1.offset.cmp(&d2.offset));
248         for i in 0..(disks.len() - 1) {
249             let length = disks[i + 1].offset - disks[i].offset;
250             if length == 0 {
251                 let text = format!("Two disks at offset {}", disks[i].offset);
252                 return Err(Error::InvalidSpecification(text));
253             }
254             if let Some(disk) = disks.get_mut(i) {
255                 disk.length = length;
256             } else {
257                 let text = format!("Unable to set disk length {}", length);
258                 return Err(Error::InvalidSpecification(text));
259             }
260         }
261         if let Some(last_disk) = disks.last_mut() {
262             if proto.length <= last_disk.offset {
263                 let text = format!(
264                     "Full size of disk doesn't match last offset. {} <= {}",
265                     proto.length, last_disk.offset
266                 );
267                 return Err(Error::InvalidSpecification(text));
268             }
269             last_disk.length = proto.length - last_disk.offset;
270         } else {
271             let text = format!("Unable to set last disk length to end at {}", proto.length);
272             return Err(Error::InvalidSpecification(text));
273         }
274 
275         CompositeDiskFile::new(disks)
276     }
277 
length(&self) -> u64278     fn length(&self) -> u64 {
279         if let Some(disk) = self.component_disks.last() {
280             disk.offset + disk.length
281         } else {
282             0
283         }
284     }
285 
disk_at_offset(&mut self, offset: u64) -> io::Result<&mut ComponentDiskPart>286     fn disk_at_offset(&mut self, offset: u64) -> io::Result<&mut ComponentDiskPart> {
287         self.component_disks
288             .iter_mut()
289             .find(|disk| disk.range().contains(&offset))
290             .ok_or(io::Error::new(
291                 ErrorKind::InvalidData,
292                 format!("no disk at offset {}", offset),
293             ))
294     }
295 }
296 
297 impl DiskGetLen for CompositeDiskFile {
get_len(&self) -> io::Result<u64>298     fn get_len(&self) -> io::Result<u64> {
299         Ok(self.length())
300     }
301 }
302 
303 impl FileSetLen for CompositeDiskFile {
set_len(&self, _len: u64) -> io::Result<()>304     fn set_len(&self, _len: u64) -> io::Result<()> {
305         Err(io::Error::new(ErrorKind::Other, "unsupported operation"))
306     }
307 }
308 
309 // Implements Read and Write targeting volatile storage for composite disks.
310 //
311 // Note that reads and writes will return early if crossing component disk boundaries.
312 // This is allowed by the read and write specifications, which only say read and write
313 // have to return how many bytes were actually read or written. Use read_exact_volatile
314 // or write_all_volatile to make sure all bytes are received/transmitted.
315 //
316 // If one of the component disks does a partial read or write, that also gets passed
317 // transparently to the parent.
318 impl FileReadWriteAtVolatile for CompositeDiskFile {
read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize>319     fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
320         let cursor_location = offset;
321         let disk = self.disk_at_offset(cursor_location)?;
322         let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
323             let new_size = disk.offset + disk.length - cursor_location;
324             slice
325                 .sub_slice(0, new_size as usize)
326                 .map_err(|e| io::Error::new(ErrorKind::InvalidData, e.to_string()))?
327         } else {
328             slice
329         };
330         disk.file
331             .read_at_volatile(subslice, cursor_location - disk.offset)
332     }
write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize>333     fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
334         let cursor_location = offset;
335         let disk = self.disk_at_offset(cursor_location)?;
336         let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
337             let new_size = disk.offset + disk.length - cursor_location;
338             slice
339                 .sub_slice(0, new_size as usize)
340                 .map_err(|e| io::Error::new(ErrorKind::InvalidData, e.to_string()))?
341         } else {
342             slice
343         };
344 
345         let bytes = disk
346             .file
347             .write_at_volatile(subslice, cursor_location - disk.offset)?;
348         disk.needs_fsync = true;
349         Ok(bytes)
350     }
351 }
352 
353 impl AsRawDescriptors for CompositeDiskFile {
as_raw_descriptors(&self) -> Vec<RawDescriptor>354     fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
355         self.component_disks
356             .iter()
357             .flat_map(|d| d.file.as_raw_descriptors())
358             .collect()
359     }
360 }
361 
362 struct AsyncComponentDiskPart {
363     file: Box<dyn AsyncDisk>,
364     offset: u64,
365     length: u64,
366     needs_fsync: AtomicBool,
367 }
368 
369 pub struct AsyncCompositeDiskFile {
370     component_disks: Vec<AsyncComponentDiskPart>,
371 }
372 
373 impl DiskGetLen for AsyncCompositeDiskFile {
get_len(&self) -> io::Result<u64>374     fn get_len(&self) -> io::Result<u64> {
375         Ok(self.length())
376     }
377 }
378 
379 impl FileSetLen for AsyncCompositeDiskFile {
set_len(&self, _len: u64) -> io::Result<()>380     fn set_len(&self, _len: u64) -> io::Result<()> {
381         Err(io::Error::new(ErrorKind::Other, "unsupported operation"))
382     }
383 }
384 
385 impl FileAllocate for AsyncCompositeDiskFile {
allocate(&mut self, offset: u64, length: u64) -> io::Result<()>386     fn allocate(&mut self, offset: u64, length: u64) -> io::Result<()> {
387         let range = offset..(offset + length);
388         let disks = self
389             .component_disks
390             .iter_mut()
391             .filter(|disk| ranges_overlap(&disk.range(), &range));
392         for disk in disks {
393             if let Some(intersection) = range_intersection(&range, &disk.range()) {
394                 disk.file.allocate(
395                     intersection.start - disk.offset,
396                     intersection.end - intersection.start,
397                 )?;
398                 disk.needs_fsync.store(true, Ordering::SeqCst);
399             }
400         }
401         Ok(())
402     }
403 }
404 
405 impl ToAsyncDisk for CompositeDiskFile {
to_async_disk(self: Box<Self>, ex: &Executor) -> crate::Result<Box<dyn AsyncDisk>>406     fn to_async_disk(self: Box<Self>, ex: &Executor) -> crate::Result<Box<dyn AsyncDisk>> {
407         Ok(Box::new(AsyncCompositeDiskFile {
408             component_disks: self
409                 .component_disks
410                 .into_iter()
411                 .map(|disk| -> crate::Result<_> {
412                     Ok(AsyncComponentDiskPart {
413                         file: disk.file.to_async_disk(ex)?,
414                         offset: disk.offset,
415                         length: disk.length,
416                         needs_fsync: AtomicBool::new(disk.needs_fsync),
417                     })
418                 })
419                 .collect::<crate::Result<Vec<_>>>()?,
420         }))
421     }
422 }
423 
424 impl AsyncComponentDiskPart {
range(&self) -> Range<u64>425     fn range(&self) -> Range<u64> {
426         self.offset..(self.offset + self.length)
427     }
428 
set_needs_fsync(&self)429     fn set_needs_fsync(&self) {
430         self.needs_fsync.store(true, Ordering::SeqCst);
431     }
432 }
433 
434 impl AsyncCompositeDiskFile {
length(&self) -> u64435     fn length(&self) -> u64 {
436         if let Some(disk) = self.component_disks.last() {
437             disk.offset + disk.length
438         } else {
439             0
440         }
441     }
442 
disk_at_offset(&self, offset: u64) -> io::Result<&AsyncComponentDiskPart>443     fn disk_at_offset(&self, offset: u64) -> io::Result<&AsyncComponentDiskPart> {
444         self.component_disks
445             .iter()
446             .find(|disk| disk.range().contains(&offset))
447             .ok_or(io::Error::new(
448                 ErrorKind::InvalidData,
449                 format!("no disk at offset {}", offset),
450             ))
451     }
452 
disks_in_range<'a>(&'a self, range: &Range<u64>) -> Vec<&'a AsyncComponentDiskPart>453     fn disks_in_range<'a>(&'a self, range: &Range<u64>) -> Vec<&'a AsyncComponentDiskPart> {
454         self.component_disks
455             .iter()
456             .filter(|disk| ranges_overlap(&disk.range(), range))
457             .collect()
458     }
459 }
460 
461 #[async_trait(?Send)]
462 impl AsyncDisk for AsyncCompositeDiskFile {
into_inner(self: Box<Self>) -> Box<dyn DiskFile>463     fn into_inner(self: Box<Self>) -> Box<dyn DiskFile> {
464         Box::new(CompositeDiskFile {
465             component_disks: self
466                 .component_disks
467                 .into_iter()
468                 .map(|disk| ComponentDiskPart {
469                     file: disk.file.into_inner(),
470                     offset: disk.offset,
471                     length: disk.length,
472                     needs_fsync: disk.needs_fsync.into_inner(),
473                 })
474                 .collect(),
475         })
476     }
477 
flush(&self) -> crate::Result<()>478     async fn flush(&self) -> crate::Result<()> {
479         futures::future::try_join_all(self.component_disks.iter().map(|c| c.file.flush())).await?;
480         Ok(())
481     }
482 
fsync(&self) -> crate::Result<()>483     async fn fsync(&self) -> crate::Result<()> {
484         // TODO: handle the disks concurrently
485         for disk in self.component_disks.iter() {
486             if disk.needs_fsync.fetch_and(false, Ordering::SeqCst) {
487                 if let Err(e) = disk.file.fsync().await {
488                     disk.set_needs_fsync();
489                     return Err(e);
490                 }
491             }
492         }
493         Ok(())
494     }
495 
fdatasync(&self) -> crate::Result<()>496     async fn fdatasync(&self) -> crate::Result<()> {
497         // AsyncCompositeDiskFile does not implement fdatasync for now. Fallback to fsync.
498         self.fsync().await
499     }
500 
read_to_mem<'a>( &'a self, file_offset: u64, mem: Arc<dyn BackingMemory + Send + Sync>, mem_offsets: MemRegionIter<'a>, ) -> crate::Result<usize>501     async fn read_to_mem<'a>(
502         &'a self,
503         file_offset: u64,
504         mem: Arc<dyn BackingMemory + Send + Sync>,
505         mem_offsets: MemRegionIter<'a>,
506     ) -> crate::Result<usize> {
507         let disk = self
508             .disk_at_offset(file_offset)
509             .map_err(crate::Error::ReadingData)?;
510         let remaining_disk = disk.offset + disk.length - file_offset;
511         disk.file
512             .read_to_mem(
513                 file_offset - disk.offset,
514                 mem,
515                 mem_offsets.take_bytes(remaining_disk.try_into().unwrap()),
516             )
517             .await
518     }
519 
write_from_mem<'a>( &'a self, file_offset: u64, mem: Arc<dyn BackingMemory + Send + Sync>, mem_offsets: MemRegionIter<'a>, ) -> crate::Result<usize>520     async fn write_from_mem<'a>(
521         &'a self,
522         file_offset: u64,
523         mem: Arc<dyn BackingMemory + Send + Sync>,
524         mem_offsets: MemRegionIter<'a>,
525     ) -> crate::Result<usize> {
526         let disk = self
527             .disk_at_offset(file_offset)
528             .map_err(crate::Error::ReadingData)?;
529         let remaining_disk = disk.offset + disk.length - file_offset;
530         let n = disk
531             .file
532             .write_from_mem(
533                 file_offset - disk.offset,
534                 mem,
535                 mem_offsets.take_bytes(remaining_disk.try_into().unwrap()),
536             )
537             .await?;
538         disk.set_needs_fsync();
539         Ok(n)
540     }
541 
punch_hole(&self, file_offset: u64, length: u64) -> crate::Result<()>542     async fn punch_hole(&self, file_offset: u64, length: u64) -> crate::Result<()> {
543         let range = file_offset..(file_offset + length);
544         let disks = self.disks_in_range(&range);
545         for disk in disks {
546             if let Some(intersection) = range_intersection(&range, &disk.range()) {
547                 disk.file
548                     .punch_hole(
549                         intersection.start - disk.offset,
550                         intersection.end - intersection.start,
551                     )
552                     .await?;
553                 disk.set_needs_fsync();
554             }
555         }
556         Ok(())
557     }
558 
write_zeroes_at(&self, file_offset: u64, length: u64) -> crate::Result<()>559     async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> crate::Result<()> {
560         let range = file_offset..(file_offset + length);
561         let disks = self.disks_in_range(&range);
562         for disk in disks {
563             if let Some(intersection) = range_intersection(&range, &disk.range()) {
564                 disk.file
565                     .write_zeroes_at(
566                         intersection.start - disk.offset,
567                         intersection.end - intersection.start,
568                     )
569                     .await?;
570                 disk.set_needs_fsync();
571             }
572         }
573         Ok(())
574     }
575 }
576 
577 /// Information about a partition to create.
578 #[derive(Clone, Debug, Eq, PartialEq)]
579 pub struct PartitionInfo {
580     pub label: String,
581     pub path: PathBuf,
582     pub partition_type: ImagePartitionType,
583     pub writable: bool,
584     pub size: u64,
585 }
586 
587 /// Round `val` up to the next multiple of 2**`align_log`.
align_to_power_of_2(val: u64, align_log: u8) -> u64588 fn align_to_power_of_2(val: u64, align_log: u8) -> u64 {
589     let align = 1 << align_log;
590     ((val + (align - 1)) / align) * align
591 }
592 
593 impl PartitionInfo {
aligned_size(&self) -> u64594     fn aligned_size(&self) -> u64 {
595         align_to_power_of_2(self.size, PARTITION_SIZE_SHIFT)
596     }
597 }
598 
599 /// The type of partition.
600 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
601 pub enum ImagePartitionType {
602     LinuxFilesystem,
603     EfiSystemPartition,
604 }
605 
606 impl ImagePartitionType {
guid(self) -> Uuid607     fn guid(self) -> Uuid {
608         match self {
609             Self::LinuxFilesystem => LINUX_FILESYSTEM_GUID,
610             Self::EfiSystemPartition => EFI_SYSTEM_PARTITION_GUID,
611         }
612     }
613 }
614 
615 /// 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<()>616 fn write_beginning(
617     file: &mut impl Write,
618     disk_guid: Uuid,
619     partitions: &[u8],
620     partition_entries_crc32: u32,
621     secondary_table_offset: u64,
622     disk_size: u64,
623 ) -> Result<()> {
624     // Write the protective MBR to the first sector.
625     write_protective_mbr(file, disk_size)?;
626 
627     // Write the GPT header, and pad out to the end of the sector.
628     write_gpt_header(
629         file,
630         disk_guid,
631         partition_entries_crc32,
632         secondary_table_offset,
633         false,
634     )?;
635     file.write_all(&[0; HEADER_PADDING_LENGTH])
636         .map_err(Error::WriteHeader)?;
637 
638     // Write partition entries, including unused ones.
639     file.write_all(partitions).map_err(Error::WriteHeader)?;
640 
641     // Write zeroes to align the first partition appropriately.
642     file.write_all(&[0; PARTITION_ALIGNMENT_SIZE])
643         .map_err(Error::WriteHeader)?;
644 
645     Ok(())
646 }
647 
648 /// 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<()>649 fn write_end(
650     file: &mut impl Write,
651     disk_guid: Uuid,
652     partitions: &[u8],
653     partition_entries_crc32: u32,
654     secondary_table_offset: u64,
655     disk_size: u64,
656 ) -> Result<()> {
657     // Write partition entries, including unused ones.
658     file.write_all(partitions).map_err(Error::WriteHeader)?;
659 
660     // Write the GPT header, and pad out to the end of the sector.
661     write_gpt_header(
662         file,
663         disk_guid,
664         partition_entries_crc32,
665         secondary_table_offset,
666         true,
667     )?;
668     file.write_all(&[0; HEADER_PADDING_LENGTH])
669         .map_err(Error::WriteHeader)?;
670 
671     // Pad out to the aligned disk size.
672     let used_disk_size = secondary_table_offset + GPT_END_SIZE;
673     let padding = disk_size - used_disk_size;
674     file.write_all(&vec![0; padding as usize])
675         .map_err(Error::WriteHeader)?;
676 
677     Ok(())
678 }
679 
680 /// Create the `GptPartitionEntry` for the given partition.
create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry681 fn create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry {
682     let mut partition_name: Vec<u16> = partition.label.encode_utf16().collect();
683     partition_name.resize(36, 0);
684 
685     GptPartitionEntry {
686         partition_type_guid: partition.partition_type.guid(),
687         unique_partition_guid: Uuid::new_v4(),
688         first_lba: offset / SECTOR_SIZE,
689         last_lba: (offset + partition.aligned_size()) / SECTOR_SIZE - 1,
690         attributes: 0,
691         partition_name: partition_name.try_into().unwrap(),
692     }
693 }
694 
695 /// 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>>696 fn create_component_disks(
697     partition: &PartitionInfo,
698     offset: u64,
699     zero_filler_path: &str,
700 ) -> Result<Vec<ComponentDisk>> {
701     let aligned_size = partition.aligned_size();
702 
703     let mut component_disks = vec![ComponentDisk {
704         offset,
705         file_path: partition
706             .path
707             .to_str()
708             .ok_or_else(|| Error::InvalidPath(partition.path.to_owned()))?
709             .to_string(),
710         read_write_capability: if partition.writable {
711             ReadWriteCapability::READ_WRITE.into()
712         } else {
713             ReadWriteCapability::READ_ONLY.into()
714         },
715         ..ComponentDisk::new()
716     }];
717 
718     if partition.size != aligned_size {
719         if partition.writable {
720             return Err(Error::UnalignedReadWrite(partition.to_owned()));
721         } else {
722             // Fill in the gap by reusing the zero filler file, because we know it is always bigger
723             // than the alignment size. Its size is 1 << PARTITION_SIZE_SHIFT (4k).
724             component_disks.push(ComponentDisk {
725                 offset: offset + partition.size,
726                 file_path: zero_filler_path.to_owned(),
727                 read_write_capability: ReadWriteCapability::READ_ONLY.into(),
728                 ..ComponentDisk::new()
729             });
730         }
731     }
732 
733     Ok(component_disks)
734 }
735 
736 /// Create a new composite disk image containing the given partitions, and write it out to the given
737 /// 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<()>738 pub fn create_composite_disk(
739     partitions: &[PartitionInfo],
740     zero_filler_path: &Path,
741     header_path: &Path,
742     header_file: &mut File,
743     footer_path: &Path,
744     footer_file: &mut File,
745     output_composite: &mut File,
746 ) -> Result<()> {
747     let zero_filler_path = zero_filler_path
748         .to_str()
749         .ok_or_else(|| Error::InvalidPath(zero_filler_path.to_owned()))?
750         .to_string();
751     let header_path = header_path
752         .to_str()
753         .ok_or_else(|| Error::InvalidPath(header_path.to_owned()))?
754         .to_string();
755     let footer_path = footer_path
756         .to_str()
757         .ok_or_else(|| Error::InvalidPath(footer_path.to_owned()))?
758         .to_string();
759 
760     let mut composite_proto = CompositeDisk::new();
761     composite_proto.version = COMPOSITE_DISK_VERSION;
762     composite_proto.component_disks.push(ComponentDisk {
763         file_path: header_path,
764         offset: 0,
765         read_write_capability: ReadWriteCapability::READ_ONLY.into(),
766         ..ComponentDisk::new()
767     });
768 
769     // Write partitions to a temporary buffer so that we can calculate the CRC, and construct the
770     // ComponentDisk proto messages at the same time.
771     let mut partitions_buffer =
772         [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
773     let mut writer: &mut [u8] = &mut partitions_buffer;
774     let mut next_disk_offset = GPT_BEGINNING_SIZE;
775     let mut labels = HashSet::with_capacity(partitions.len());
776     for partition in partitions {
777         let gpt_entry = create_gpt_entry(partition, next_disk_offset);
778         if !labels.insert(gpt_entry.partition_name) {
779             return Err(Error::DuplicatePartitionLabel(partition.label.clone()));
780         }
781         gpt_entry.write_bytes(&mut writer)?;
782 
783         for component_disk in
784             create_component_disks(partition, next_disk_offset, &zero_filler_path)?
785         {
786             composite_proto.component_disks.push(component_disk);
787         }
788 
789         next_disk_offset += partition.aligned_size();
790     }
791     let secondary_table_offset = next_disk_offset;
792     let disk_size = align_to_power_of_2(secondary_table_offset + GPT_END_SIZE, DISK_SIZE_SHIFT);
793 
794     composite_proto.component_disks.push(ComponentDisk {
795         file_path: footer_path,
796         offset: secondary_table_offset,
797         read_write_capability: ReadWriteCapability::READ_ONLY.into(),
798         ..ComponentDisk::new()
799     });
800 
801     // Calculate CRC32 of partition entries.
802     let mut hasher = Hasher::new();
803     hasher.update(&partitions_buffer);
804     let partition_entries_crc32 = hasher.finalize();
805 
806     let disk_guid = Uuid::new_v4();
807     write_beginning(
808         header_file,
809         disk_guid,
810         &partitions_buffer,
811         partition_entries_crc32,
812         secondary_table_offset,
813         disk_size,
814     )?;
815     write_end(
816         footer_file,
817         disk_guid,
818         &partitions_buffer,
819         partition_entries_crc32,
820         secondary_table_offset,
821         disk_size,
822     )?;
823 
824     composite_proto.length = disk_size;
825     output_composite
826         .write_all(CDISK_MAGIC.as_bytes())
827         .map_err(Error::WriteHeader)?;
828     composite_proto
829         .write_to_writer(output_composite)
830         .map_err(Error::WriteProto)?;
831 
832     Ok(())
833 }
834 
835 /// Create a zero filler file which can be used to fill the gaps between partition files.
836 /// 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<()>837 pub fn create_zero_filler<P: AsRef<Path>>(zero_filler_path: P) -> Result<()> {
838     let f = OpenOptions::new()
839         .create(true)
840         .read(true)
841         .write(true)
842         .truncate(true)
843         .open(zero_filler_path.as_ref())
844         .map_err(Error::WriteZeroFiller)?;
845     f.set_len(1 << PARTITION_SIZE_SHIFT)
846         .map_err(Error::WriteZeroFiller)
847 }
848 
849 #[cfg(test)]
850 mod tests {
851     use std::fs::OpenOptions;
852     use std::io::Write;
853     use std::matches;
854 
855     use base::AsRawDescriptor;
856     use tempfile::tempfile;
857 
858     use super::*;
859 
860     #[test]
block_duplicate_offset_disks()861     fn block_duplicate_offset_disks() {
862         let file1 = tempfile().unwrap();
863         let file2 = tempfile().unwrap();
864         let disk_part1 = ComponentDiskPart {
865             file: Box::new(file1),
866             offset: 0,
867             length: 100,
868             needs_fsync: false,
869         };
870         let disk_part2 = ComponentDiskPart {
871             file: Box::new(file2),
872             offset: 0,
873             length: 100,
874             needs_fsync: false,
875         };
876         assert!(CompositeDiskFile::new(vec![disk_part1, disk_part2]).is_err());
877     }
878 
879     #[test]
get_len()880     fn get_len() {
881         let file1 = tempfile().unwrap();
882         let file2 = tempfile().unwrap();
883         let disk_part1 = ComponentDiskPart {
884             file: Box::new(file1),
885             offset: 0,
886             length: 100,
887             needs_fsync: false,
888         };
889         let disk_part2 = ComponentDiskPart {
890             file: Box::new(file2),
891             offset: 100,
892             length: 100,
893             needs_fsync: false,
894         };
895         let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2]).unwrap();
896         let len = composite.get_len().unwrap();
897         assert_eq!(len, 200);
898     }
899 
900     #[test]
async_get_len()901     fn async_get_len() {
902         let file1 = tempfile().unwrap();
903         let file2 = tempfile().unwrap();
904         let disk_part1 = ComponentDiskPart {
905             file: Box::new(file1),
906             offset: 0,
907             length: 100,
908             needs_fsync: false,
909         };
910         let disk_part2 = ComponentDiskPart {
911             file: Box::new(file2),
912             offset: 100,
913             length: 100,
914             needs_fsync: false,
915         };
916         let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2]).unwrap();
917 
918         let ex = Executor::new().unwrap();
919         let composite = Box::new(composite).to_async_disk(&ex).unwrap();
920         let len = composite.get_len().unwrap();
921         assert_eq!(len, 200);
922     }
923 
924     #[test]
single_file_passthrough()925     fn single_file_passthrough() {
926         let file = tempfile().unwrap();
927         let disk_part = ComponentDiskPart {
928             file: Box::new(file),
929             offset: 0,
930             length: 100,
931             needs_fsync: false,
932         };
933         let mut composite = CompositeDiskFile::new(vec![disk_part]).unwrap();
934         let mut input_memory = [55u8; 5];
935         let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
936         composite
937             .write_all_at_volatile(input_volatile_memory, 0)
938             .unwrap();
939         let mut output_memory = [0u8; 5];
940         let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
941         composite
942             .read_exact_at_volatile(output_volatile_memory, 0)
943             .unwrap();
944         assert_eq!(input_memory, output_memory);
945     }
946 
947     #[test]
async_single_file_passthrough()948     fn async_single_file_passthrough() {
949         let file = tempfile().unwrap();
950         let disk_part = ComponentDiskPart {
951             file: Box::new(file),
952             offset: 0,
953             length: 100,
954             needs_fsync: false,
955         };
956         let composite = CompositeDiskFile::new(vec![disk_part]).unwrap();
957         let ex = Executor::new().unwrap();
958         ex.run_until(async {
959             let composite = Box::new(composite).to_async_disk(&ex).unwrap();
960             let expected = [55u8; 5];
961             assert_eq!(
962                 composite.write_double_buffered(0, &expected).await.unwrap(),
963                 5
964             );
965             let mut buf = [0u8; 5];
966             assert_eq!(
967                 composite
968                     .read_double_buffered(0, &mut buf[..])
969                     .await
970                     .unwrap(),
971                 5
972             );
973             assert_eq!(buf, expected);
974         })
975         .unwrap();
976     }
977 
978     #[test]
triple_file_descriptors()979     fn triple_file_descriptors() {
980         let file1 = tempfile().unwrap();
981         let file2 = tempfile().unwrap();
982         let file3 = tempfile().unwrap();
983         let mut in_descriptors = vec![
984             file1.as_raw_descriptor(),
985             file2.as_raw_descriptor(),
986             file3.as_raw_descriptor(),
987         ];
988         in_descriptors.sort_unstable();
989         let disk_part1 = ComponentDiskPart {
990             file: Box::new(file1),
991             offset: 0,
992             length: 100,
993             needs_fsync: false,
994         };
995         let disk_part2 = ComponentDiskPart {
996             file: Box::new(file2),
997             offset: 100,
998             length: 100,
999             needs_fsync: false,
1000         };
1001         let disk_part3 = ComponentDiskPart {
1002             file: Box::new(file3),
1003             offset: 200,
1004             length: 100,
1005             needs_fsync: false,
1006         };
1007         let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
1008         let mut out_descriptors = composite.as_raw_descriptors();
1009         out_descriptors.sort_unstable();
1010         assert_eq!(in_descriptors, out_descriptors);
1011     }
1012 
1013     #[test]
triple_file_passthrough()1014     fn triple_file_passthrough() {
1015         let file1 = tempfile().unwrap();
1016         let file2 = tempfile().unwrap();
1017         let file3 = tempfile().unwrap();
1018         let disk_part1 = ComponentDiskPart {
1019             file: Box::new(file1),
1020             offset: 0,
1021             length: 100,
1022             needs_fsync: false,
1023         };
1024         let disk_part2 = ComponentDiskPart {
1025             file: Box::new(file2),
1026             offset: 100,
1027             length: 100,
1028             needs_fsync: false,
1029         };
1030         let disk_part3 = ComponentDiskPart {
1031             file: Box::new(file3),
1032             offset: 200,
1033             length: 100,
1034             needs_fsync: false,
1035         };
1036         let mut composite =
1037             CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
1038         let mut input_memory = [55u8; 200];
1039         let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
1040         composite
1041             .write_all_at_volatile(input_volatile_memory, 50)
1042             .unwrap();
1043         let mut output_memory = [0u8; 200];
1044         let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
1045         composite
1046             .read_exact_at_volatile(output_volatile_memory, 50)
1047             .unwrap();
1048         assert!(input_memory.iter().eq(output_memory.iter()));
1049     }
1050 
1051     #[test]
async_triple_file_passthrough()1052     fn async_triple_file_passthrough() {
1053         let file1 = tempfile().unwrap();
1054         let file2 = tempfile().unwrap();
1055         let file3 = tempfile().unwrap();
1056         let disk_part1 = ComponentDiskPart {
1057             file: Box::new(file1),
1058             offset: 0,
1059             length: 100,
1060             needs_fsync: false,
1061         };
1062         let disk_part2 = ComponentDiskPart {
1063             file: Box::new(file2),
1064             offset: 100,
1065             length: 100,
1066             needs_fsync: false,
1067         };
1068         let disk_part3 = ComponentDiskPart {
1069             file: Box::new(file3),
1070             offset: 200,
1071             length: 100,
1072             needs_fsync: false,
1073         };
1074         let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
1075         let ex = Executor::new().unwrap();
1076         ex.run_until(async {
1077             let composite = Box::new(composite).to_async_disk(&ex).unwrap();
1078 
1079             let expected = [55u8; 200];
1080             assert_eq!(
1081                 composite.write_double_buffered(0, &expected).await.unwrap(),
1082                 100
1083             );
1084             assert_eq!(
1085                 composite
1086                     .write_double_buffered(100, &expected[100..])
1087                     .await
1088                     .unwrap(),
1089                 100
1090             );
1091 
1092             let mut buf = [0u8; 200];
1093             assert_eq!(
1094                 composite
1095                     .read_double_buffered(0, &mut buf[..])
1096                     .await
1097                     .unwrap(),
1098                 100
1099             );
1100             assert_eq!(
1101                 composite
1102                     .read_double_buffered(100, &mut buf[100..])
1103                     .await
1104                     .unwrap(),
1105                 100
1106             );
1107             assert_eq!(buf, expected);
1108         })
1109         .unwrap();
1110     }
1111 
1112     #[test]
async_triple_file_punch_hole()1113     fn async_triple_file_punch_hole() {
1114         let file1 = tempfile().unwrap();
1115         let file2 = tempfile().unwrap();
1116         let file3 = tempfile().unwrap();
1117         let disk_part1 = ComponentDiskPart {
1118             file: Box::new(file1),
1119             offset: 0,
1120             length: 100,
1121             needs_fsync: false,
1122         };
1123         let disk_part2 = ComponentDiskPart {
1124             file: Box::new(file2),
1125             offset: 100,
1126             length: 100,
1127             needs_fsync: false,
1128         };
1129         let disk_part3 = ComponentDiskPart {
1130             file: Box::new(file3),
1131             offset: 200,
1132             length: 100,
1133             needs_fsync: false,
1134         };
1135         let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
1136         let ex = Executor::new().unwrap();
1137         ex.run_until(async {
1138             let composite = Box::new(composite).to_async_disk(&ex).unwrap();
1139 
1140             let input = [55u8; 300];
1141             assert_eq!(
1142                 composite.write_double_buffered(0, &input).await.unwrap(),
1143                 100
1144             );
1145             assert_eq!(
1146                 composite
1147                     .write_double_buffered(100, &input[100..])
1148                     .await
1149                     .unwrap(),
1150                 100
1151             );
1152             assert_eq!(
1153                 composite
1154                     .write_double_buffered(200, &input[200..])
1155                     .await
1156                     .unwrap(),
1157                 100
1158             );
1159 
1160             composite.punch_hole(50, 200).await.unwrap();
1161 
1162             let mut buf = [0u8; 300];
1163             assert_eq!(
1164                 composite
1165                     .read_double_buffered(0, &mut buf[..])
1166                     .await
1167                     .unwrap(),
1168                 100
1169             );
1170             assert_eq!(
1171                 composite
1172                     .read_double_buffered(100, &mut buf[100..])
1173                     .await
1174                     .unwrap(),
1175                 100
1176             );
1177             assert_eq!(
1178                 composite
1179                     .read_double_buffered(200, &mut buf[200..])
1180                     .await
1181                     .unwrap(),
1182                 100
1183             );
1184 
1185             let mut expected = input;
1186             expected[50..250].iter_mut().for_each(|x| *x = 0);
1187             assert_eq!(buf, expected);
1188         })
1189         .unwrap();
1190     }
1191 
1192     #[test]
async_triple_file_write_zeroes()1193     fn async_triple_file_write_zeroes() {
1194         let file1 = tempfile().unwrap();
1195         let file2 = tempfile().unwrap();
1196         let file3 = tempfile().unwrap();
1197         let disk_part1 = ComponentDiskPart {
1198             file: Box::new(file1),
1199             offset: 0,
1200             length: 100,
1201             needs_fsync: false,
1202         };
1203         let disk_part2 = ComponentDiskPart {
1204             file: Box::new(file2),
1205             offset: 100,
1206             length: 100,
1207             needs_fsync: false,
1208         };
1209         let disk_part3 = ComponentDiskPart {
1210             file: Box::new(file3),
1211             offset: 200,
1212             length: 100,
1213             needs_fsync: false,
1214         };
1215         let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
1216         let ex = Executor::new().unwrap();
1217         ex.run_until(async {
1218             let composite = Box::new(composite).to_async_disk(&ex).unwrap();
1219 
1220             let input = [55u8; 300];
1221             assert_eq!(
1222                 composite.write_double_buffered(0, &input).await.unwrap(),
1223                 100
1224             );
1225             assert_eq!(
1226                 composite
1227                     .write_double_buffered(100, &input[100..])
1228                     .await
1229                     .unwrap(),
1230                 100
1231             );
1232             assert_eq!(
1233                 composite
1234                     .write_double_buffered(200, &input[200..])
1235                     .await
1236                     .unwrap(),
1237                 100
1238             );
1239 
1240             composite.write_zeroes_at(50, 200).await.unwrap();
1241 
1242             let mut buf = [0u8; 300];
1243             assert_eq!(
1244                 composite
1245                     .read_double_buffered(0, &mut buf[..])
1246                     .await
1247                     .unwrap(),
1248                 100
1249             );
1250             assert_eq!(
1251                 composite
1252                     .read_double_buffered(100, &mut buf[100..])
1253                     .await
1254                     .unwrap(),
1255                 100
1256             );
1257             assert_eq!(
1258                 composite
1259                     .read_double_buffered(200, &mut buf[200..])
1260                     .await
1261                     .unwrap(),
1262                 100
1263             );
1264 
1265             let mut expected = input;
1266             expected[50..250].iter_mut().for_each(|x| *x = 0);
1267             assert_eq!(buf, expected);
1268         })
1269         .unwrap();
1270     }
1271 
1272     // TODO: fsync on a RO file is legal, this test doesn't work as expected. Consider using a mock
1273     // DiskFile to detect the fsync calls.
1274     #[test]
async_fsync_skips_unchanged_parts()1275     fn async_fsync_skips_unchanged_parts() {
1276         let mut rw_file = tempfile().unwrap();
1277         rw_file.write_all(&[0u8; 100]).unwrap();
1278         rw_file.seek(SeekFrom::Start(0)).unwrap();
1279         let mut ro_disk_image = tempfile::NamedTempFile::new().unwrap();
1280         ro_disk_image.write_all(&[0u8; 100]).unwrap();
1281         let ro_file = OpenOptions::new()
1282             .read(true)
1283             .open(ro_disk_image.path())
1284             .unwrap();
1285 
1286         let rw_part = ComponentDiskPart {
1287             file: Box::new(rw_file),
1288             offset: 0,
1289             length: 100,
1290             needs_fsync: false,
1291         };
1292         let ro_part = ComponentDiskPart {
1293             file: Box::new(ro_file),
1294             offset: 100,
1295             length: 100,
1296             needs_fsync: false,
1297         };
1298         let composite = CompositeDiskFile::new(vec![rw_part, ro_part]).unwrap();
1299         let ex = Executor::new().unwrap();
1300         ex.run_until(async {
1301             let composite = Box::new(composite).to_async_disk(&ex).unwrap();
1302 
1303             // Write to the RW part so that some fsync operation will occur.
1304             composite.write_zeroes_at(0, 20).await.unwrap();
1305 
1306             // This is the test's assert. fsyncing should NOT touch a read-only disk part. On
1307             // Windows, this would be an error.
1308             composite.fsync().await.expect(
1309                 "Failed to fsync composite disk. \
1310                      This can happen if the disk writable state is wrong.",
1311             );
1312         })
1313         .unwrap();
1314     }
1315 
1316     #[test]
beginning_size()1317     fn beginning_size() {
1318         let mut buffer = vec![];
1319         let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
1320         let disk_size = 1000 * SECTOR_SIZE;
1321         write_beginning(
1322             &mut buffer,
1323             Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
1324             &partitions,
1325             42,
1326             disk_size - GPT_END_SIZE,
1327             disk_size,
1328         )
1329         .unwrap();
1330 
1331         assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
1332     }
1333 
1334     #[test]
end_size()1335     fn end_size() {
1336         let mut buffer = vec![];
1337         let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
1338         let disk_size = 1000 * SECTOR_SIZE;
1339         write_end(
1340             &mut buffer,
1341             Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
1342             &partitions,
1343             42,
1344             disk_size - GPT_END_SIZE,
1345             disk_size,
1346         )
1347         .unwrap();
1348 
1349         assert_eq!(buffer.len(), GPT_END_SIZE as usize);
1350     }
1351 
1352     #[test]
end_size_with_padding()1353     fn end_size_with_padding() {
1354         let mut buffer = vec![];
1355         let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
1356         let disk_size = 1000 * SECTOR_SIZE;
1357         let padding = 3 * SECTOR_SIZE;
1358         write_end(
1359             &mut buffer,
1360             Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
1361             &partitions,
1362             42,
1363             disk_size - GPT_END_SIZE - padding,
1364             disk_size,
1365         )
1366         .unwrap();
1367 
1368         assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
1369     }
1370 
1371     /// Creates a composite disk image with no partitions.
1372     #[test]
create_composite_disk_empty()1373     fn create_composite_disk_empty() {
1374         let mut header_image = tempfile().unwrap();
1375         let mut footer_image = tempfile().unwrap();
1376         let mut composite_image = tempfile().unwrap();
1377 
1378         create_composite_disk(
1379             &[],
1380             Path::new("/zero_filler.img"),
1381             Path::new("/header_path.img"),
1382             &mut header_image,
1383             Path::new("/footer_path.img"),
1384             &mut footer_image,
1385             &mut composite_image,
1386         )
1387         .unwrap();
1388     }
1389 
1390     /// Creates a composite disk image with two partitions.
1391     #[test]
create_composite_disk_success()1392     fn create_composite_disk_success() {
1393         let mut header_image = tempfile().unwrap();
1394         let mut footer_image = tempfile().unwrap();
1395         let mut composite_image = tempfile().unwrap();
1396 
1397         create_composite_disk(
1398             &[
1399                 PartitionInfo {
1400                     label: "partition1".to_string(),
1401                     path: "/partition1.img".to_string().into(),
1402                     partition_type: ImagePartitionType::LinuxFilesystem,
1403                     writable: false,
1404                     size: 0,
1405                 },
1406                 PartitionInfo {
1407                     label: "partition2".to_string(),
1408                     path: "/partition2.img".to_string().into(),
1409                     partition_type: ImagePartitionType::LinuxFilesystem,
1410                     writable: true,
1411                     size: 0,
1412                 },
1413             ],
1414             Path::new("/zero_filler.img"),
1415             Path::new("/header_path.img"),
1416             &mut header_image,
1417             Path::new("/footer_path.img"),
1418             &mut footer_image,
1419             &mut composite_image,
1420         )
1421         .unwrap();
1422     }
1423 
1424     /// Attempts to create a composite disk image with two partitions with the same label.
1425     #[test]
create_composite_disk_duplicate_label()1426     fn create_composite_disk_duplicate_label() {
1427         let mut header_image = tempfile().unwrap();
1428         let mut footer_image = tempfile().unwrap();
1429         let mut composite_image = tempfile().unwrap();
1430 
1431         let result = create_composite_disk(
1432             &[
1433                 PartitionInfo {
1434                     label: "label".to_string(),
1435                     path: "/partition1.img".to_string().into(),
1436                     partition_type: ImagePartitionType::LinuxFilesystem,
1437                     writable: false,
1438                     size: 0,
1439                 },
1440                 PartitionInfo {
1441                     label: "label".to_string(),
1442                     path: "/partition2.img".to_string().into(),
1443                     partition_type: ImagePartitionType::LinuxFilesystem,
1444                     writable: true,
1445                     size: 0,
1446                 },
1447             ],
1448             Path::new("/zero_filler.img"),
1449             Path::new("/header_path.img"),
1450             &mut header_image,
1451             Path::new("/footer_path.img"),
1452             &mut footer_image,
1453             &mut composite_image,
1454         );
1455         assert!(matches!(result, Err(Error::DuplicatePartitionLabel(label)) if label == "label"));
1456     }
1457 }
1458