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