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