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