gptman/
lib.rs

1//! Pure Rust library to read and modify GUID partition tables.
2//!
3//! # Examples
4//!
5//! Reading all the partitions of a disk:
6//!
7//! ```
8//! let mut f = std::fs::File::open("tests/fixtures/disk1.img")
9//!     .expect("could not open disk");
10//! let gpt = gptman::GPT::find_from(&mut f)
11//!     .expect("could not find GPT");
12//!
13//! println!("Disk GUID: {:?}", gpt.header.disk_guid);
14//!
15//! for (i, p) in gpt.iter() {
16//!     if p.is_used() {
17//!         println!("Partition #{}: type = {:?}, size = {} bytes, starting lba = {}",
18//!             i,
19//!             p.partition_type_guid,
20//!             p.size().unwrap() * gpt.sector_size,
21//!             p.starting_lba);
22//!     }
23//! }
24//! ```
25//!
26//! Creating new partitions:
27//!
28//! ```
29//! let mut f = std::fs::File::open("tests/fixtures/disk1.img")
30//!     .expect("could not open disk");
31//! let mut gpt = gptman::GPT::find_from(&mut f)
32//!     .expect("could not find GPT");
33//!
34//! let free_partition_number = gpt.iter().find(|(i, p)| p.is_unused()).map(|(i, _)| i)
35//!     .expect("no more places available");
36//! let size = gpt.get_maximum_partition_size()
37//!     .expect("no more space available");
38//! let starting_lba = gpt.find_optimal_place(size)
39//!     .expect("could not find a place to put the partition");
40//! let ending_lba = starting_lba + size - 1;
41//!
42//! gpt[free_partition_number] = gptman::GPTPartitionEntry {
43//!     partition_type_guid: [0xff; 16],
44//!     unique_partition_guid: [0xff; 16],
45//!     starting_lba,
46//!     ending_lba,
47//!     attribute_bits: 0,
48//!     partition_name: "A Robot Named Fight!".into(),
49//! };
50//! ```
51//!
52//! Creating a new partition table with one entry that fills the entire disk:
53//!
54//! ```
55//! let ss = 512;
56//! let data = vec![0; 100 * ss as usize];
57//! let mut cur = std::io::Cursor::new(data);
58//! let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
59//!     .expect("could not create partition table");
60//!
61//! gpt[1] = gptman::GPTPartitionEntry {
62//!     partition_type_guid: [0xff; 16],
63//!     unique_partition_guid: [0xff; 16],
64//!     starting_lba: gpt.header.first_usable_lba,
65//!     ending_lba: gpt.header.last_usable_lba,
66//!     attribute_bits: 0,
67//!     partition_name: "A Robot Named Fight!".into(),
68//! };
69//! ```
70
71#![deny(missing_docs)]
72
73use crc::{Crc, CRC_32_ISO_HDLC};
74use std::cmp::Ordering;
75use std::collections::HashSet;
76use std::io;
77use std::io::{Read, Seek, SeekFrom, Write};
78use std::ops::{Index, IndexMut, RangeInclusive};
79use thiserror::Error;
80use wincode::{ReadError, SchemaRead, SchemaWrite, WriteError};
81
82/// Linux specific helpers
83#[cfg(all(target_os = "linux", feature = "nix"))]
84pub mod linux;
85
86const DEFAULT_ALIGN: u64 = 2048;
87const MAX_ALIGN: u64 = 16384;
88
89/// An error that can be produced while reading, writing or managing a GPT.
90#[derive(Debug, Error)]
91#[non_exhaustive]
92pub enum Error {
93    /// Derialization errors.
94    #[error("deserialization failed")]
95    Deserialize(#[from] ReadError),
96    /// Serialization errors.
97    #[error("seserialization failed")]
98    Seserialize(#[from] WriteError),
99    /// I/O errors.
100    #[error("generic I/O error")]
101    Io(#[from] io::Error),
102    /// An error that occurs when the signature of the GPT isn't what would be expected ("EFI
103    /// PART").
104    #[error("invalid signature")]
105    InvalidSignature,
106    /// An error that occurs when the revision of the GPT isn't what would be expected (00 00 01
107    /// 00).
108    #[error("invalid revision")]
109    InvalidRevision,
110    /// An error that occurs when the header's size (in bytes) isn't what would be expected (92).
111    #[error("invalid header size")]
112    InvalidHeaderSize,
113    /// An error that occurs when the CRC32 checksum of the header doesn't match the expected
114    /// checksum for the actual header.
115    #[error("corrupted CRC32 checksum ({0} != {1})")]
116    InvalidChecksum(u32, u32),
117    /// An error that occurs when the CRC32 checksum of the partition entries array doesn't match
118    /// the expected checksum for the actual partition entries array.
119    #[error("corrupted partition entry array CRC32 checksum ({0} != {1})")]
120    InvalidPartitionEntryArrayChecksum(u32, u32),
121    /// An error that occurs when reading a GPT from a file did not succeeded.
122    ///
123    /// The first argument is the error that occurred when trying to read the primary header.
124    /// The second argument is the error that occurred when trying to read the backup header.
125    #[error("could not read primary header ({0}) nor backup header ({1})")]
126    ReadError(Box<Error>, Box<Error>),
127    /// An error that occurs when there is not enough space left on the table to continue.
128    #[error("no space left")]
129    NoSpaceLeft,
130    /// An error that occurs when there are partitions with the same GUID in the same array.
131    #[error("conflict of partition GUIDs")]
132    ConflictPartitionGUID,
133    /// An error that occurs when a partition has an invalid boundary.
134    ///
135    /// The end sector must be greater or equal to the start sector of the partition.
136    ///
137    /// Partitions must fit within the disk and must not overlap.
138    #[error(
139        "invalid partition boundaries: partitions must have positive size, must not overlap, \
140        and must fit within the disk"
141    )]
142    InvalidPartitionBoundaries,
143    /// An error that occurs when the user provide an invalid partition number.
144    ///
145    /// The partition number must be between 1 and `number_of_partition_entries` (usually 128)
146    /// included.
147    #[error("invalid partition number: {0}")]
148    InvalidPartitionNumber(u32),
149    /// An error that occurs when the user attempts to access information for an unused partition.
150    #[error("unused partition")]
151    UnusedPartition,
152    /// An operation that required to find a partition, was unable to find that partition.
153    #[error("partition not found")]
154    PartitionNotFound,
155    /// An arithmetic operation overflowed.
156    #[error("an arithmetic operation overflowed")]
157    Overflow,
158}
159
160/// The result of reading, writing or managing a GPT.
161pub type Result<T> = std::result::Result<T, Error>;
162
163fn encode_into_std_write<T>(src: &T, mut writer: impl Write) -> Result<()>
164where
165    T: SchemaWrite<Src = T>,
166{
167    let buf = wincode::serialize(src)?;
168    Ok(writer.write_all(&buf)?)
169}
170
171fn decode_from_std_read<T>(mut src: impl Read) -> Result<T>
172where
173    T: for<'de> SchemaRead<'de, Dst = T>,
174{
175    let mut buf = vec![0; size_of::<T>()];
176    src.read_exact(&mut buf)?;
177    Ok(wincode::deserialize(&buf)?)
178}
179
180/// A GUID Partition Table header as describe on
181/// [Wikipedia's page](https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_table_header_(LBA_1)).
182#[derive(Debug, SchemaRead, SchemaWrite, Clone, PartialEq, Eq)]
183pub struct GPTHeader {
184    /// GPT signature (must be "EFI PART").
185    pub signature: [u8; 8],
186    /// GPT revision (must be 00 00 01 00).
187    pub revision: [u8; 4],
188    /// GPT header size (must be 92).
189    pub header_size: u32,
190    /// CRC32 checksum of the header.
191    pub crc32_checksum: u32,
192    /// Reserved bytes of the header.
193    pub reserved: [u8; 4],
194    /// Location (in sectors) of the primary header.
195    pub primary_lba: u64,
196    /// Location (in sectors) of the backup header.
197    pub backup_lba: u64,
198    /// Location (in sectors) of the first usable sector.
199    pub first_usable_lba: u64,
200    /// Location (in sectors) of the last usable sector.
201    pub last_usable_lba: u64,
202    /// 16 bytes representing the UUID of the GPT.
203    pub disk_guid: [u8; 16],
204    /// Location (in sectors) of the partition entries array.
205    ///
206    /// This is always `2` if the header is a primary header and not a backup header.
207    pub partition_entry_lba: u64,
208    /// Number of partition entries in the array.
209    pub number_of_partition_entries: u32,
210    /// Size (in bytes) of a partition entry.
211    pub size_of_partition_entry: u32,
212    /// CRC32 checksum of the partition array.
213    pub partition_entry_array_crc32: u32,
214}
215
216impl GPTHeader {
217    /// Make a new GPT header based on a reader. (This operation does not write anything to disk!)
218    pub fn new_from<R>(reader: &mut R, sector_size: u64, disk_guid: [u8; 16]) -> Result<GPTHeader>
219    where
220        R: Read + Seek,
221    {
222        let mut gpt = GPTHeader {
223            signature: [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54],
224            revision: [0x00, 0x00, 0x01, 0x00],
225            header_size: 92,
226            crc32_checksum: 0,
227            reserved: [0; 4],
228            primary_lba: 1,
229            backup_lba: 0,
230            first_usable_lba: 0,
231            last_usable_lba: 0,
232            disk_guid,
233            partition_entry_lba: 2,
234            number_of_partition_entries: 128,
235            size_of_partition_entry: 128,
236            partition_entry_array_crc32: 0,
237        };
238        gpt.update_from(reader, sector_size)?;
239
240        Ok(gpt)
241    }
242
243    /// Attempt to read a GPT header from a reader.
244    ///
245    /// # Implementation notes
246    ///
247    /// The field `last_usable_lba` is not updated to reflect the actual size of the disk. You must
248    /// do this yourself by calling `update_from`.
249    pub fn read_from<R>(reader: &mut R) -> Result<GPTHeader>
250    where
251        R: Read + Seek + ?Sized,
252    {
253        let gpt: GPTHeader = decode_from_std_read(reader)?;
254
255        if &gpt.signature != b"EFI PART" {
256            return Err(Error::InvalidSignature);
257        }
258
259        if gpt.revision != [0x00, 0x00, 0x01, 0x00] {
260            return Err(Error::InvalidRevision);
261        }
262
263        if gpt.header_size != 92 {
264            return Err(Error::InvalidHeaderSize);
265        }
266
267        let sum = gpt.generate_crc32_checksum();
268        if gpt.crc32_checksum != sum {
269            return Err(Error::InvalidChecksum(gpt.crc32_checksum, sum));
270        }
271
272        Ok(gpt)
273    }
274
275    /// Write the GPT header into a writer. This operation will update the CRC32 checksums of the
276    /// current struct and seek at the location `primary_lba` before trying to write to disk.
277    pub fn write_into<W>(
278        &mut self,
279        mut writer: &mut W,
280        sector_size: u64,
281        partitions: &[GPTPartitionEntry],
282    ) -> Result<()>
283    where
284        W: Write + Seek + ?Sized,
285    {
286        self.update_partition_entry_array_crc32(partitions);
287        self.update_crc32_checksum();
288
289        writer.seek(SeekFrom::Start(self.primary_lba * sector_size))?;
290        encode_into_std_write(self, &mut writer)?;
291
292        for i in 0..self.number_of_partition_entries {
293            writer.seek(SeekFrom::Start(
294                self.partition_entry_lba * sector_size
295                    + u64::from(i) * u64::from(self.size_of_partition_entry),
296            ))?;
297            encode_into_std_write(&partitions[i as usize], &mut writer)?;
298        }
299
300        Ok(())
301    }
302
303    /// Generate the CRC32 checksum of the partition header only.
304    pub fn generate_crc32_checksum(&self) -> u32 {
305        let mut clone = self.clone();
306        clone.crc32_checksum = 0;
307        let data = wincode::serialize(&clone).expect("could not serialize");
308        assert_eq!(data.len() as u32, clone.header_size);
309
310        Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(&data)
311    }
312
313    /// Update the CRC32 checksum of this header.
314    pub fn update_crc32_checksum(&mut self) {
315        self.crc32_checksum = self.generate_crc32_checksum();
316    }
317
318    /// Generate the CRC32 checksum of the partition entry array.
319    pub fn generate_partition_entry_array_crc32(&self, partitions: &[GPTPartitionEntry]) -> u32 {
320        let mut clone = self.clone();
321        clone.partition_entry_array_crc32 = 0;
322        let crc = Crc::<u32>::new(&CRC_32_ISO_HDLC);
323        let mut digest = crc.digest();
324        let mut wrote = 0;
325        for x in partitions {
326            let data = wincode::serialize(x).expect("could not serialize");
327            digest.update(&data);
328            wrote += data.len();
329        }
330        assert_eq!(
331            wrote as u32,
332            clone.size_of_partition_entry * clone.number_of_partition_entries
333        );
334
335        digest.finalize()
336    }
337
338    /// Update the CRC32 checksum of the partition entry array.
339    pub fn update_partition_entry_array_crc32(&mut self, partitions: &[GPTPartitionEntry]) {
340        self.partition_entry_array_crc32 = self.generate_partition_entry_array_crc32(partitions);
341    }
342
343    /// Updates the header to match the specifications of the seeker given in argument.
344    /// `first_usable_lba`, `last_usable_lba`, `primary_lba`, `backup_lba`, `partition_entry_lba`
345    /// will be updated after this operation.
346    pub fn update_from<S>(&mut self, seeker: &mut S, sector_size: u64) -> Result<()>
347    where
348        S: Seek + ?Sized,
349    {
350        let partition_array_size = (u64::from(self.number_of_partition_entries)
351            * u64::from(self.size_of_partition_entry)
352            - 1)
353            / sector_size
354            + 1;
355        let len = seeker.seek(SeekFrom::End(0))? / sector_size;
356        if self.primary_lba == 1 {
357            self.backup_lba = len - 1;
358        } else {
359            self.primary_lba = len - 1;
360        }
361        self.last_usable_lba = len - partition_array_size - 1 - 1;
362        self.first_usable_lba = 2 + partition_array_size;
363        // NOTE: the partition_entry_lba is either 2 either something near the end of the disk.
364        //       If it is something near the end of the disk, it means the self object is a backup
365        //       GPT header (which is located at the end of the disk) and its partition_entry_lba
366        //       must be updated accordingly
367        if self.partition_entry_lba != 2 {
368            self.partition_entry_lba = self.last_usable_lba + 1;
369        }
370
371        Ok(())
372    }
373
374    /// Returns `true` if the `GPTHeader` is a primary copy (the header is located at the beginning
375    /// of the disk).
376    pub fn is_primary(&self) -> bool {
377        self.primary_lba == 1
378    }
379
380    /// Returns `true` if the `GPTHeader` is a backup copy (the header is located at the end of the
381    /// disk).
382    ///
383    /// Note that when the header is a backup copy, the `primary_lba` is the LBA of the backup copy
384    /// and the `backup_lba` is the LBA of the primary copy.
385    pub fn is_backup(&self) -> bool {
386        !self.is_primary()
387    }
388}
389
390/// A wrapper type for `String` that represents a partition's name.
391#[derive(Debug, Clone, PartialEq, Eq)]
392pub struct PartitionName {
393    string: String,
394    raw_buf: [u16; 36],
395}
396
397impl PartitionName {
398    /// Extracts a string slice containing the entire `PartitionName`.
399    pub fn as_str(&self) -> &str {
400        &self.string
401    }
402}
403
404impl std::fmt::Display for PartitionName {
405    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406        write!(f, "{}", &self.string)
407    }
408}
409
410impl From<&str> for PartitionName {
411    fn from(value: &str) -> PartitionName {
412        let utf16_converted: Vec<_> = value
413            .encode_utf16()
414            .chain([0].into_iter().cycle())
415            .take(36)
416            .collect();
417        let mut raw_buf = [0; 36];
418        raw_buf.copy_from_slice(&utf16_converted);
419        PartitionName {
420            string: value.to_string(),
421            raw_buf,
422        }
423    }
424}
425
426impl<'de> SchemaRead<'de> for PartitionName {
427    type Dst = PartitionName;
428
429    fn read(
430        reader: &mut impl wincode::io::Reader<'de>,
431        dst: &mut std::mem::MaybeUninit<Self::Dst>,
432    ) -> wincode::ReadResult<()> {
433        // 36 UTF-16LE code units (72 bytes)
434        let n_bytes = 36 * size_of::<u16>();
435        let buf = reader.fill_exact(n_bytes)?;
436        let mut raw_buf = [0; 36];
437        for i in 0..n_bytes / 2 {
438            raw_buf[i] = ((buf[2 * i + 1] as u16) << 8) | buf[2 * i] as u16;
439        }
440
441        reader.consume(n_bytes)?;
442        let zero_pos = raw_buf
443            .iter()
444            .position(|x| *x == 0)
445            .unwrap_or(raw_buf.len());
446
447        let name = PartitionName {
448            string: String::from_utf16_lossy(&raw_buf[..zero_pos]),
449            raw_buf,
450        };
451        dst.write(name);
452        Ok(())
453    }
454}
455
456impl SchemaWrite for PartitionName {
457    type Src = PartitionName;
458
459    fn size_of(_: &Self::Src) -> wincode::WriteResult<usize> {
460        Ok(36 * size_of::<u16>())
461    }
462
463    fn write(writer: &mut impl wincode::io::Writer, src: &Self::Src) -> wincode::WriteResult<()> {
464        // Favor using the content in the raw buffer in case there is garbage left (used for the CRC)
465        for x in src.raw_buf {
466            <u16 as SchemaWrite>::write(writer, &x)?;
467        }
468        Ok(())
469    }
470}
471
472/// A GPT partition's entry in the partition array.
473///
474/// # Examples
475///
476/// Basic usage:
477/// ```
478/// let ss = 512;
479/// let data = vec![0; 100 * ss as usize];
480/// let mut cur = std::io::Cursor::new(data);
481/// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
482///     .expect("could not create partition table");
483///
484/// // NOTE: partition entries starts at 1
485/// gpt[1] = gptman::GPTPartitionEntry {
486///     partition_type_guid: [0xff; 16],
487///     unique_partition_guid: [0xff; 16],
488///     starting_lba: gpt.header.first_usable_lba,
489///     ending_lba: gpt.header.last_usable_lba,
490///     attribute_bits: 0,
491///     partition_name: "A Robot Named Fight!".into(),
492/// };
493///
494/// assert_eq!(gpt[1].partition_name.as_str(), "A Robot Named Fight!");
495/// ```
496#[derive(Debug, SchemaRead, SchemaWrite, Clone, PartialEq, Eq)]
497pub struct GPTPartitionEntry {
498    /// 16 bytes representing the UUID of the partition's type.
499    pub partition_type_guid: [u8; 16],
500    /// 16 bytes representing the UUID of the partition.
501    pub unique_partition_guid: [u8; 16],
502    /// The position (in sectors) of the first sector (used) of the partition.
503    pub starting_lba: u64,
504    /// The position (in sectors) of the last sector (used) of the partition.
505    pub ending_lba: u64,
506    /// The attribute bits.
507    ///
508    /// See [Wikipedia's page](https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries_(LBA_2%E2%80%9333))
509    /// for more information.
510    pub attribute_bits: u64,
511    /// The partition name.
512    ///
513    /// # Examples
514    ///
515    /// Basic usage:
516    /// ```
517    /// let name: gptman::PartitionName = "A Robot Named Fight!".into();
518    ///
519    /// assert_eq!(name.as_str(), "A Robot Named Fight!");
520    /// ```
521    pub partition_name: PartitionName,
522}
523
524impl GPTPartitionEntry {
525    /// Creates an empty partition entry
526    ///
527    /// # Examples
528    ///
529    /// Basic usage:
530    /// ```
531    /// let ss = 512;
532    /// let data = vec![0; 100 * ss as usize];
533    /// let mut cur = std::io::Cursor::new(data);
534    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
535    ///     .expect("could not create partition table");
536    ///
537    /// gpt[1] = gptman::GPTPartitionEntry::empty();
538    ///
539    /// // NOTE: an empty partition entry is considered as not allocated
540    /// assert!(gpt[1].is_unused());
541    /// ```
542    pub fn empty() -> GPTPartitionEntry {
543        GPTPartitionEntry {
544            partition_type_guid: [0; 16],
545            unique_partition_guid: [0; 16],
546            starting_lba: 0,
547            ending_lba: 0,
548            attribute_bits: 0,
549            partition_name: "".into(),
550        }
551    }
552
553    /// Read a partition entry from the reader at the current position.
554    pub fn read_from<R>(reader: &mut R) -> Result<GPTPartitionEntry>
555    where
556        R: Read + ?Sized,
557    {
558        decode_from_std_read(reader)
559    }
560
561    /// Returns `true` if the partition entry is not used (type GUID == `[0; 16]`)
562    pub fn is_unused(&self) -> bool {
563        self.partition_type_guid == [0; 16]
564    }
565
566    /// Returns `true` if the partition entry is used (type GUID != `[0; 16]`)
567    pub fn is_used(&self) -> bool {
568        !self.is_unused()
569    }
570
571    /// Returns the number of sectors in the partition. A partition entry must always be 1 sector
572    /// long at minimum.
573    ///
574    /// # Errors
575    ///
576    /// This function will return an error if the `ending_lba` is lesser than the `starting_lba`.
577    ///
578    /// # Examples
579    ///
580    /// Basic usage:
581    /// ```
582    /// let ss = 512;
583    /// let data = vec![0; 100 * ss as usize];
584    /// let mut cur = std::io::Cursor::new(data);
585    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
586    ///     .expect("could not create partition table");
587    ///
588    /// gpt[1] = gptman::GPTPartitionEntry {
589    ///     partition_type_guid: [0xff; 16],
590    ///     unique_partition_guid: [0xff; 16],
591    ///     starting_lba: gpt.header.first_usable_lba,
592    ///     ending_lba: gpt.header.last_usable_lba,
593    ///     attribute_bits: 0,
594    ///     partition_name: "A Robot Named Fight!".into(),
595    /// };
596    ///
597    /// assert_eq!(
598    ///     gpt[1].size().ok(),
599    ///     Some(gpt.header.last_usable_lba + 1 - gpt.header.first_usable_lba)
600    /// );
601    /// ```
602    pub fn size(&self) -> Result<u64> {
603        if self.ending_lba < self.starting_lba {
604            return Err(Error::InvalidPartitionBoundaries);
605        }
606
607        Ok(self.ending_lba - self.starting_lba + 1)
608    }
609
610    /// Get the range of sectors covered by a partition.
611    ///
612    /// # Errors
613    ///
614    /// This function will return an error if the partition is unused or if
615    /// `ending_lba` is lesser than the `starting_lba`.
616    ///
617    /// # Examples
618    ///
619    /// ```
620    /// let ss = 512;
621    /// let data = vec![0; 100 * ss as usize];
622    /// let mut cur = std::io::Cursor::new(data);
623    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
624    ///     .expect("could not create partition table");
625    /// gpt[1] = gptman::GPTPartitionEntry {
626    ///     partition_type_guid: [0xff; 16],
627    ///     unique_partition_guid: [0xff; 16],
628    ///     starting_lba: 2048,
629    ///     ending_lba: 4096,
630    ///     attribute_bits: 0,
631    ///     partition_name: "A Robot Named Fight!".into(),
632    /// };
633    ///
634    /// assert_eq!(gpt[1].range().unwrap(), 2048..=4096);
635    /// ```
636    pub fn range(&self) -> Result<RangeInclusive<u64>> {
637        if self.is_unused() {
638            return Err(Error::UnusedPartition);
639        }
640        if self.ending_lba < self.starting_lba {
641            return Err(Error::InvalidPartitionBoundaries);
642        }
643
644        Ok(self.starting_lba..=self.ending_lba)
645    }
646}
647
648/// A type representing a GUID partition table including its partitions, the sector size of the
649/// disk and the alignment of the partitions to the sectors.
650///
651/// # Examples
652///
653/// Read an existing GPT on a reader and list its partitions:
654/// ```
655/// let mut f = std::fs::File::open("tests/fixtures/disk1.img")
656///     .expect("could not open disk");
657/// let gpt = gptman::GPT::find_from(&mut f)
658///     .expect("could not find GPT");
659///
660/// println!("Disk GUID: {:?}", gpt.header.disk_guid);
661///
662/// for (i, p) in gpt.iter() {
663///     if p.is_used() {
664///         println!("Partition #{}: type = {:?}, size = {} bytes, starting lba = {}",
665///             i,
666///             p.partition_type_guid,
667///             p.size().unwrap() * gpt.sector_size,
668///             p.starting_lba);
669///     }
670/// }
671/// ```
672#[derive(Debug, Clone, PartialEq, Eq)]
673pub struct GPT {
674    /// Sector size of the disk.
675    ///
676    /// You should not change this, otherwise the starting locations of your partitions will be
677    /// different in bytes.
678    pub sector_size: u64,
679    /// GPT partition header (disk GUID, first/last usable LBA, etc...)
680    pub header: GPTHeader,
681    partitions: Vec<GPTPartitionEntry>,
682    /// Partitions alignment (in sectors)
683    ///
684    /// This field change the behavior of the methods `get_maximum_partition_size()`,
685    /// `find_free_sectors()`, `find_first_place()`, `find_last_place()` and `find_optimal_place()`
686    /// so they return only values aligned to the alignment.
687    ///
688    /// # Panics
689    ///
690    /// The value must be greater than 0, otherwise you will encounter divisions by zero.
691    pub align: u64,
692}
693
694impl GPT {
695    /// Make a new GPT based on a reader. (This operation does not write anything to disk!)
696    ///
697    /// # Examples
698    ///
699    /// Basic usage:
700    /// ```
701    /// let ss = 512;
702    /// let data = vec![0; 100 * ss as usize];
703    /// let mut cur = std::io::Cursor::new(data);
704    /// let gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
705    ///     .expect("could not make a partition table");
706    /// ```
707    pub fn new_from<R>(reader: &mut R, sector_size: u64, disk_guid: [u8; 16]) -> Result<GPT>
708    where
709        R: Read + Seek,
710    {
711        let header = GPTHeader::new_from(reader, sector_size, disk_guid)?;
712        let mut partitions = Vec::with_capacity(header.number_of_partition_entries as usize);
713        for _ in 0..header.number_of_partition_entries {
714            partitions.push(GPTPartitionEntry::empty());
715        }
716
717        Ok(GPT {
718            sector_size,
719            header,
720            partitions,
721            align: DEFAULT_ALIGN,
722        })
723    }
724
725    /// Read the GPT on a reader. This function will try to read the backup header if the primary
726    /// header could not be read.
727    ///
728    /// # Implementation notes
729    ///
730    /// The field `last_usable_lba` on the header is not updated to reflect the actual size of the
731    /// disk. You must do this yourself by calling `update_from`.
732    ///
733    /// # Examples
734    ///
735    /// Basic usage:
736    /// ```
737    /// let mut f = std::fs::File::open("tests/fixtures/disk1.img")
738    ///     .expect("could not open disk");
739    /// let gpt = gptman::GPT::read_from(&mut f, 512)
740    ///     .expect("could not read the partition table");
741    /// ```
742    pub fn read_from<R>(mut reader: &mut R, sector_size: u64) -> Result<GPT>
743    where
744        R: Read + Seek + ?Sized,
745    {
746        use self::Error::*;
747
748        reader.seek(SeekFrom::Start(sector_size))?;
749        let header = GPTHeader::read_from(&mut reader).or_else(|primary_err| {
750            let len = reader.seek(SeekFrom::End(0))?;
751            if len < sector_size {
752                return Err(InvalidSignature);
753            }
754
755            reader.seek(SeekFrom::Start((len / sector_size - 1) * sector_size))?;
756
757            GPTHeader::read_from(&mut reader).map_err(|backup_err| {
758                match (primary_err, backup_err) {
759                    (InvalidSignature, InvalidSignature) => InvalidSignature,
760                    (x, y) => Error::ReadError(Box::new(x), Box::new(y)),
761                }
762            })
763        })?;
764
765        let mut partitions = Vec::with_capacity(header.number_of_partition_entries as usize);
766        for i in 0..header.number_of_partition_entries {
767            reader.seek(SeekFrom::Start(
768                header.partition_entry_lba * sector_size
769                    + u64::from(i) * u64::from(header.size_of_partition_entry),
770            ))?;
771            partitions.push(GPTPartitionEntry::read_from(&mut reader)?);
772        }
773
774        let sum = header.generate_partition_entry_array_crc32(&partitions);
775        if header.partition_entry_array_crc32 != sum {
776            return Err(Error::InvalidPartitionEntryArrayChecksum(
777                header.partition_entry_array_crc32,
778                sum,
779            ));
780        }
781
782        let align = GPT::find_alignment(&header, &partitions);
783
784        Ok(GPT {
785            sector_size,
786            header,
787            partitions,
788            align,
789        })
790    }
791
792    /// Find the GPT on a reader. This function will try to read the GPT on a disk using a sector
793    /// size of 512 but if it fails it will automatically try to read the GPT using a sector size
794    /// of 4096.
795    ///
796    /// # Examples
797    ///
798    /// Basic usage:
799    /// ```
800    /// let mut f_512 = std::fs::File::open("tests/fixtures/disk1.img")
801    ///     .expect("could not open disk");
802    /// let gpt_512 = gptman::GPT::find_from(&mut f_512)
803    ///     .expect("could not read the partition table");
804    ///
805    /// let mut f_4096 = std::fs::File::open("tests/fixtures/disk2.img")
806    ///     .expect("could not open disk");
807    /// let gpt_4096 = gptman::GPT::find_from(&mut f_4096)
808    ///     .expect("could not read the partition table");
809    /// ```
810    pub fn find_from<R>(mut reader: &mut R) -> Result<GPT>
811    where
812        R: Read + Seek + ?Sized,
813    {
814        use self::Error::*;
815
816        Self::read_from(&mut reader, 512).or_else(|err_at_512| match err_at_512 {
817            InvalidSignature => Self::read_from(&mut reader, 4096),
818            err => Err(err),
819        })
820    }
821
822    fn find_alignment(header: &GPTHeader, partitions: &[GPTPartitionEntry]) -> u64 {
823        let lbas = partitions
824            .iter()
825            .filter(|x| x.is_used())
826            .map(|x| x.starting_lba)
827            .collect::<Vec<_>>();
828
829        if lbas.is_empty() {
830            return DEFAULT_ALIGN;
831        }
832
833        if lbas.len() == 1 && lbas[0] == header.first_usable_lba {
834            return 1;
835        }
836
837        (1..=MAX_ALIGN.min(*lbas.iter().max().unwrap_or(&1)))
838            .filter(|div| lbas.iter().all(|x| x % div == 0))
839            .max()
840            .unwrap()
841    }
842
843    fn check_partition_guids(&self) -> Result<()> {
844        let guids: Vec<_> = self
845            .partitions
846            .iter()
847            .filter(|x| x.is_used())
848            .map(|x| x.unique_partition_guid)
849            .collect();
850        if guids.len() != guids.iter().collect::<HashSet<_>>().len() {
851            return Err(Error::ConflictPartitionGUID);
852        }
853
854        Ok(())
855    }
856
857    fn check_partition_boundaries(&self) -> Result<()> {
858        if self
859            .partitions
860            .iter()
861            .any(|x| x.ending_lba < x.starting_lba)
862        {
863            return Err(Error::InvalidPartitionBoundaries);
864        }
865
866        let mut partitions: Vec<&GPTPartitionEntry> =
867            self.partitions.iter().filter(|x| x.is_used()).collect();
868        partitions.sort_unstable_by_key(|x| x.starting_lba);
869        let first_available =
870            partitions
871                .iter()
872                .try_fold(self.header.first_usable_lba, |first_available, x| {
873                    if x.starting_lba >= first_available {
874                        Ok(x.ending_lba + 1)
875                    } else {
876                        Err(Error::InvalidPartitionBoundaries)
877                    }
878                })?;
879        if first_available > self.header.last_usable_lba + 1 {
880            return Err(Error::InvalidPartitionBoundaries);
881        }
882
883        Ok(())
884    }
885
886    /// Write the GPT to a writer. This function will seek automatically in the writer to write the
887    /// primary header and the backup header at their proper location.
888    ///
889    /// Returns the backup `GPTHeader` that has been wrote in case of success (or the primary
890    /// `GPTHeader` if `self` was using a backup header).
891    ///
892    /// Note that the checksums are re-calculated, thus updating the header.
893    ///
894    /// # Errors
895    ///
896    /// The partitions will be checked for consistency before being wrote to disk:
897    ///
898    /// * the partition GUIDs must be unique,
899    /// * the partitions must have positive size,
900    /// * the partitions must not overlap,
901    /// * the partitions must fit within the disk.
902    ///
903    /// # Examples
904    ///
905    /// Basic usage:
906    /// ```
907    /// let ss = 512;
908    /// let data = vec![0; 100 * ss as usize];
909    /// let mut cur = std::io::Cursor::new(data);
910    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
911    ///     .expect("could not make a partition table");
912    ///
913    /// // actually write:
914    /// gpt.write_into(&mut cur)
915    ///     .expect("could not write GPT to disk");
916    /// ```
917    pub fn write_into<W>(&mut self, mut writer: &mut W) -> Result<GPTHeader>
918    where
919        W: Write + Seek + ?Sized,
920    {
921        self.check_partition_guids()?;
922        self.check_partition_boundaries()?;
923
924        let mut backup = self.header.clone();
925        backup.primary_lba = self.header.backup_lba;
926        backup.backup_lba = self.header.primary_lba;
927        backup.partition_entry_lba = if self.header.partition_entry_lba == 2 {
928            self.header.last_usable_lba + 1
929        } else {
930            2
931        };
932
933        self.header
934            .write_into(&mut writer, self.sector_size, &self.partitions)?;
935        backup.write_into(&mut writer, self.sector_size, &self.partitions)?;
936
937        Ok(backup)
938    }
939
940    /// Finds the partition where the given sector resides.
941    pub fn find_at_sector(&self, sector: u64) -> Option<u32> {
942        fn between(partition: &GPTPartitionEntry, sector: u64) -> bool {
943            sector >= partition.starting_lba && sector <= partition.ending_lba
944        }
945
946        self.iter()
947            .find(|(_, partition)| partition.is_used() && between(partition, sector))
948            .map(|(id, _)| id)
949    }
950
951    /// Find free spots in the partition table.
952    ///
953    /// This function will return a vector of tuple with on the left: the starting LBA of the free
954    /// spot; and on the right: the size (in sectors) of the free spot.
955    ///
956    /// This function will automatically align with the alignment defined in the `GPT`.
957    ///
958    /// # Examples
959    ///
960    /// Basic usage:
961    /// ```
962    /// let ss = 512;
963    /// let data = vec![0; 100 * ss as usize];
964    /// let mut cur = std::io::Cursor::new(data);
965    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
966    ///     .expect("could not create partition table");
967    ///
968    /// gpt[1] = gptman::GPTPartitionEntry {
969    ///     partition_type_guid: [0xff; 16],
970    ///     unique_partition_guid: [0xff; 16],
971    ///     starting_lba: gpt.header.first_usable_lba + 5,
972    ///     ending_lba: gpt.header.last_usable_lba - 5,
973    ///     attribute_bits: 0,
974    ///     partition_name: "A Robot Named Fight!".into(),
975    /// };
976    ///
977    /// // NOTE: align to the sectors, so we can use every last one of them
978    /// // NOTE: this is only for the demonstration purpose, this is not recommended
979    /// gpt.align = 1;
980    ///
981    /// assert_eq!(
982    ///     gpt.find_free_sectors(),
983    ///     vec![(gpt.header.first_usable_lba, 5), (gpt.header.last_usable_lba - 4, 5)]
984    /// );
985    /// ```
986    pub fn find_free_sectors(&self) -> Vec<(u64, u64)> {
987        assert!(self.align > 0, "align must be greater than 0");
988        let mut positions = vec![self.header.first_usable_lba - 1];
989        for partition in self.partitions.iter().filter(|x| x.is_used()) {
990            positions.push(partition.starting_lba);
991            positions.push(partition.ending_lba);
992        }
993        positions.push(self.header.last_usable_lba + 1);
994        positions.sort_unstable();
995
996        positions
997            .chunks(2)
998            .map(|x| (x[0] + 1, x[1] - x[0] - 1))
999            .filter(|(_, l)| *l > 0)
1000            .map(|(i, l)| (i, l, ((i - 1) / self.align + 1) * self.align - i))
1001            .map(|(i, l, s)| (i + s, l.saturating_sub(s)))
1002            .filter(|(_, l)| *l > 0)
1003            .collect()
1004    }
1005
1006    /// Find the first place (most on the left) where you could start a new partition of the size
1007    /// given in parameter.
1008    /// This function will automatically align with the alignment defined in the `GPT`.
1009    ///
1010    /// # Examples
1011    ///
1012    /// Basic usage:
1013    /// ```
1014    /// let ss = 512;
1015    /// let data = vec![0; 100 * ss as usize];
1016    /// let mut cur = std::io::Cursor::new(data);
1017    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
1018    ///     .expect("could not create partition table");
1019    ///
1020    /// gpt[1] = gptman::GPTPartitionEntry {
1021    ///     partition_type_guid: [0xff; 16],
1022    ///     unique_partition_guid: [0xff; 16],
1023    ///     starting_lba: gpt.header.first_usable_lba + 5,
1024    ///     ending_lba: gpt.header.last_usable_lba - 5,
1025    ///     attribute_bits: 0,
1026    ///     partition_name: "A Robot Named Fight!".into(),
1027    /// };
1028    ///
1029    /// // NOTE: align to the sectors, so we can use every last one of them
1030    /// // NOTE: this is only for the demonstration purpose, this is not recommended
1031    /// gpt.align = 1;
1032    ///
1033    /// assert_eq!(gpt.find_first_place(5), Some(gpt.header.first_usable_lba));
1034    /// ```
1035    pub fn find_first_place(&self, size: u64) -> Option<u64> {
1036        self.find_free_sectors()
1037            .iter()
1038            .find(|(_, l)| *l >= size)
1039            .map(|(i, _)| *i)
1040    }
1041
1042    /// Find the last place (most on the right) where you could start a new partition of the size
1043    /// given in parameter.
1044    ///
1045    /// This function will automatically align with the alignment defined in the `GPT`.
1046    ///
1047    /// # Examples
1048    ///
1049    /// Basic usage:
1050    /// ```
1051    /// let ss = 512;
1052    /// let data = vec![0; 100 * ss as usize];
1053    /// let mut cur = std::io::Cursor::new(data);
1054    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
1055    ///     .expect("could not create partition table");
1056    ///
1057    /// gpt[1] = gptman::GPTPartitionEntry {
1058    ///     partition_type_guid: [0xff; 16],
1059    ///     unique_partition_guid: [0xff; 16],
1060    ///     starting_lba: gpt.header.first_usable_lba + 5,
1061    ///     ending_lba: gpt.header.last_usable_lba - 5,
1062    ///     attribute_bits: 0,
1063    ///     partition_name: "A Robot Named Fight!".into(),
1064    /// };
1065    ///
1066    /// // NOTE: align to the sectors, so we can use every last one of them
1067    /// // NOTE: this is only for the demonstration purpose, this is not recommended
1068    /// gpt.align = 1;
1069    ///
1070    /// assert_eq!(gpt.find_last_place(5), Some(gpt.header.last_usable_lba - 4));
1071    /// ```
1072    pub fn find_last_place(&self, size: u64) -> Option<u64> {
1073        self.find_free_sectors()
1074            .iter()
1075            .filter(|(_, l)| *l >= size)
1076            .last()
1077            .map(|(i, l)| (i + l - size) / self.align * self.align)
1078    }
1079
1080    /// Find the most optimal place (in the smallest free space) where you could start a new
1081    /// partition of the size given in parameter.
1082    /// This function will automatically align with the alignment defined in the `GPT`.
1083    ///
1084    /// # Examples
1085    ///
1086    /// Basic usage:
1087    /// ```
1088    /// let ss = 512;
1089    /// let data = vec![0; 100 * ss as usize];
1090    /// let mut cur = std::io::Cursor::new(data);
1091    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
1092    ///     .expect("could not create partition table");
1093    ///
1094    /// gpt[1] = gptman::GPTPartitionEntry {
1095    ///     partition_type_guid: [0xff; 16],
1096    ///     unique_partition_guid: [0xff; 16],
1097    ///     starting_lba: gpt.header.first_usable_lba + 10,
1098    ///     ending_lba: gpt.header.last_usable_lba - 5,
1099    ///     attribute_bits: 0,
1100    ///     partition_name: "A Robot Named Fight!".into(),
1101    /// };
1102    ///
1103    /// // NOTE: align to the sectors, so we can use every last one of them
1104    /// // NOTE: this is only for the demonstration purpose, this is not recommended
1105    /// gpt.align = 1;
1106    ///
1107    /// // NOTE: the space as the end is more optimal because it will allow you to still be able to
1108    /// //       insert a bigger partition later
1109    /// assert_eq!(gpt.find_optimal_place(5), Some(gpt.header.last_usable_lba - 4));
1110    /// ```
1111    pub fn find_optimal_place(&self, size: u64) -> Option<u64> {
1112        let mut slots = self
1113            .find_free_sectors()
1114            .into_iter()
1115            .filter(|(_, l)| *l >= size)
1116            .collect::<Vec<_>>();
1117        slots.sort_by(|(_, l1), (_, l2)| l1.cmp(l2));
1118        slots.first().map(|&(i, _)| i)
1119    }
1120
1121    /// Get the maximum size (in sectors) of a partition you could create in the GPT.
1122    /// This function will automatically align with the alignment defined in the `GPT`.
1123    ///
1124    /// # Examples
1125    ///
1126    /// Basic usage:
1127    /// ```
1128    /// let ss = 512;
1129    /// let data = vec![0; 100 * ss as usize];
1130    /// let mut cur = std::io::Cursor::new(data);
1131    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
1132    ///     .expect("could not create partition table");
1133    ///
1134    /// // NOTE: align to the sectors, so we can use every last one of them
1135    /// // NOTE: this is only for the demonstration purpose, this is not recommended
1136    /// gpt.align = 1;
1137    ///
1138    /// assert_eq!(
1139    ///     gpt.get_maximum_partition_size().unwrap_or(0),
1140    ///     gpt.header.last_usable_lba + 1 - gpt.header.first_usable_lba
1141    /// );
1142    /// ```
1143    pub fn get_maximum_partition_size(&self) -> Result<u64> {
1144        self.find_free_sectors()
1145            .into_iter()
1146            .map(|(_, l)| l / self.align * self.align)
1147            .max()
1148            .ok_or(Error::NoSpaceLeft)
1149    }
1150
1151    /// Get the range of bytes covered by a partition.
1152    ///
1153    /// # Errors
1154    ///
1155    /// This function will return an error if the partition number is invalid, or if
1156    /// the partition is unused, or if the partition's `ending_lba` is less than its
1157    /// `starting_lba`.
1158    ///
1159    /// # Examples
1160    ///
1161    /// ```
1162    /// let ss = 512;
1163    /// let data = vec![0; 100 * ss as usize];
1164    /// let mut cur = std::io::Cursor::new(data);
1165    /// let mut gpt = gptman::GPT::new_from(&mut cur, ss as u64, [0xff; 16])
1166    ///     .expect("could not create partition table");
1167    /// gpt[1] = gptman::GPTPartitionEntry {
1168    ///     partition_type_guid: [0xff; 16],
1169    ///     unique_partition_guid: [0xff; 16],
1170    ///     starting_lba: 2048,
1171    ///     ending_lba: 2048,
1172    ///     attribute_bits: 0,
1173    ///     partition_name: "A Robot Named Fight!".into(),
1174    /// };
1175    ///
1176    /// assert_eq!(gpt.get_partition_byte_range(1).unwrap(), 1048576..=1049087);
1177    /// ```
1178    pub fn get_partition_byte_range(&self, partition_number: u32) -> Result<RangeInclusive<u64>> {
1179        if partition_number == 0 || partition_number > self.header.number_of_partition_entries {
1180            return Err(Error::InvalidPartitionNumber(partition_number));
1181        }
1182        let partition = &self[partition_number];
1183
1184        let sector_range = partition.range()?;
1185
1186        let start_byte = sector_range
1187            .start()
1188            .checked_mul(self.sector_size)
1189            .ok_or(Error::Overflow)?;
1190        // Sector ranges are inclusive, so to get the position of the end byte we need
1191        // to add the size of another sector, less one byte because the byte range
1192        // returned from this function is also inclusive.
1193        let end_byte = sector_range
1194            .end()
1195            .checked_mul(self.sector_size)
1196            .and_then(|v| v.checked_add(self.sector_size - 1))
1197            .ok_or(Error::Overflow)?;
1198        Ok(start_byte..=end_byte)
1199    }
1200
1201    /// Sort the partition entries in the array by the starting LBA.
1202    pub fn sort(&mut self) {
1203        self.partitions
1204            .sort_by(|a, b| match (a.is_used(), b.is_used()) {
1205                (true, true) => a.starting_lba.cmp(&b.starting_lba),
1206                (true, false) => Ordering::Less,
1207                (false, true) => Ordering::Greater,
1208                (false, false) => Ordering::Equal,
1209            });
1210    }
1211
1212    /// Remove a partition entry in the array.
1213    ///
1214    /// This is the equivalent of:
1215    /// `gpt[i] = gptman::GPTPartitionEntry::empty();`
1216    ///
1217    /// # Errors
1218    ///
1219    /// This function will return an error if `i` is lesser or equal to `0` or greater than the
1220    /// number of partition entries (which can be obtained in the header).
1221    pub fn remove(&mut self, i: u32) -> Result<()> {
1222        if i == 0 || i > self.header.number_of_partition_entries {
1223            return Err(Error::InvalidPartitionNumber(i));
1224        }
1225
1226        self.partitions[i as usize - 1] = GPTPartitionEntry::empty();
1227
1228        Ok(())
1229    }
1230
1231    /// Remove a partition entry in the array that resides at a given sector.
1232    ///
1233    /// # Errors
1234    ///
1235    /// It is an error to provide a sector which does not belong to a partition.
1236    pub fn remove_at_sector(&mut self, sector: u64) -> Result<()> {
1237        self.remove(
1238            self.find_at_sector(sector)
1239                .ok_or(Error::PartitionNotFound)?,
1240        )
1241    }
1242
1243    /// Get an iterator over the partition entries and their index. The index always starts at 1.
1244    pub fn iter(&self) -> impl Iterator<Item = (u32, &GPTPartitionEntry)> {
1245        self.partitions
1246            .iter()
1247            .enumerate()
1248            .map(|(i, x)| (i as u32 + 1, x))
1249    }
1250
1251    /// Get a mutable iterator over the partition entries and their index. The index always starts
1252    /// at 1.
1253    pub fn iter_mut(&mut self) -> impl Iterator<Item = (u32, &mut GPTPartitionEntry)> {
1254        self.partitions
1255            .iter_mut()
1256            .enumerate()
1257            .map(|(i, x)| (i as u32 + 1, x))
1258    }
1259
1260    /// This function writes a protective MBR in the first sector of the disk
1261    /// starting at byte 446 and ending at byte 511. Any existing data will be overwritten.
1262    ///
1263    /// See also: [`Self::write_bootable_protective_mbr_into`].
1264    pub fn write_protective_mbr_into<W>(mut writer: &mut W, sector_size: u64) -> Result<()>
1265    where
1266        W: Write + Seek + ?Sized,
1267    {
1268        Self::write_protective_mbr_into_impl(&mut writer, sector_size, false)
1269    }
1270
1271    /// This function writes a protective MBR in the first sector of the disk
1272    /// starting at byte 446 and ending at byte 511. Any existing data will be overwritten.
1273    /// This function differs from [`Self::write_protective_mbr_into`] in that the partition in the
1274    /// MBR partition table is marked as bootable. Some legacy BIOS systems do not consider a disk
1275    /// to be bootable if there isn't an MBR partition marked as bootable in the MBR partition
1276    /// table.
1277    ///
1278    /// <div class="warning">Some systems will not consider a disk to be bootable in UEFI mode
1279    /// if the pMBR is marked as bootable, so this should only be used if booting on legacy BIOS
1280    /// systems is a requirement.</div>
1281    pub fn write_bootable_protective_mbr_into<W>(mut writer: &mut W, sector_size: u64) -> Result<()>
1282    where
1283        W: Write + Seek + ?Sized,
1284    {
1285        Self::write_protective_mbr_into_impl(&mut writer, sector_size, true)
1286    }
1287
1288    fn write_protective_mbr_into_impl<W>(
1289        mut writer: &mut W,
1290        sector_size: u64,
1291        bootable: bool,
1292    ) -> Result<()>
1293    where
1294        W: Write + Seek + ?Sized,
1295    {
1296        let size = writer.seek(SeekFrom::End(0))? / sector_size - 1;
1297        writer.seek(SeekFrom::Start(446))?;
1298        // partition 1
1299        if bootable {
1300            writer.write_all(&[0x80])?;
1301        } else {
1302            writer.write_all(&[0x00])?;
1303        }
1304        writer.write_all(&[
1305            0x00, 0x02, 0x00, // CHS address of first absolute sector
1306            0xee, // partition type
1307            0xff, 0xff, 0xff, // CHS address of last absolute sector
1308            0x01, 0x00, 0x00, 0x00, // LBA of first absolute sector
1309        ])?;
1310        // number of sectors in partition 1
1311        let n_sectors = if size > u64::from(u32::MAX) {
1312            u32::MAX
1313        } else {
1314            size as u32
1315        };
1316        encode_into_std_write(&n_sectors, &mut writer)?;
1317        writer.write_all(&[0; 16])?; // partition 2
1318        writer.write_all(&[0; 16])?; // partition 3
1319        writer.write_all(&[0; 16])?; // partition 4
1320        writer.write_all(&[0x55, 0xaa])?; // signature
1321
1322        Ok(())
1323    }
1324
1325    /// Returns `true` if the `GPTHeader` is a primary copy (the header is located at the beginning
1326    /// of the disk).
1327    pub fn is_primary(&self) -> bool {
1328        self.header.is_primary()
1329    }
1330
1331    /// Returns `true` if the `GPTHeader` is a backup copy (the header is located at the end of the
1332    /// disk).
1333    ///
1334    /// Note that when the header is a backup copy, the `primary_lba` is the LBA of the backup copy
1335    /// and the `backup_lba` is the LBA of the primary copy.
1336    pub fn is_backup(&self) -> bool {
1337        self.header.is_backup()
1338    }
1339}
1340
1341impl Index<u32> for GPT {
1342    type Output = GPTPartitionEntry;
1343
1344    fn index(&self, i: u32) -> &GPTPartitionEntry {
1345        assert!(i != 0, "invalid partition index: 0");
1346        &self.partitions[i as usize - 1]
1347    }
1348}
1349
1350impl IndexMut<u32> for GPT {
1351    fn index_mut(&mut self, i: u32) -> &mut GPTPartitionEntry {
1352        assert!(i != 0, "invalid partition index: 0");
1353        &mut self.partitions[i as usize - 1]
1354    }
1355}
1356
1357#[cfg(test)]
1358mod test {
1359    #![allow(clippy::disallowed_names)]
1360
1361    use super::*;
1362    use std::fs;
1363
1364    const DISK1: &str = "tests/fixtures/disk1.img";
1365    const DISK2: &str = "tests/fixtures/disk2.img";
1366    const DISK3: &str = "tests/fixtures/disk3.img";
1367    const DISK4: &str = "tests/fixtures/disk4.img";
1368
1369    #[test]
1370    fn read_header_and_partition_entries() {
1371        fn test(path: &str, ss: u64) {
1372            let mut f = fs::File::open(path).unwrap();
1373
1374            f.seek(SeekFrom::Start(ss)).unwrap();
1375            let mut gpt = GPTHeader::read_from(&mut f).unwrap();
1376
1377            f.seek(SeekFrom::Start(gpt.backup_lba * ss)).unwrap();
1378            assert!(GPTHeader::read_from(&mut f).is_ok());
1379
1380            f.seek(SeekFrom::Start(gpt.partition_entry_lba * ss))
1381                .unwrap();
1382            let foo = GPTPartitionEntry::read_from(&mut f).unwrap();
1383            assert!(!foo.is_unused());
1384
1385            f.seek(SeekFrom::Start(
1386                gpt.partition_entry_lba * ss + u64::from(gpt.size_of_partition_entry),
1387            ))
1388            .unwrap();
1389            let bar = GPTPartitionEntry::read_from(&mut f).unwrap();
1390            assert!(!bar.is_unused());
1391
1392            let mut unused = 0;
1393            let mut used = 0;
1394            let mut partitions = Vec::new();
1395            for i in 0..gpt.number_of_partition_entries {
1396                f.seek(SeekFrom::Start(
1397                    gpt.partition_entry_lba * ss
1398                        + u64::from(i) * u64::from(gpt.size_of_partition_entry),
1399                ))
1400                .unwrap();
1401                let partition = GPTPartitionEntry::read_from(&mut f).unwrap();
1402
1403                if partition.is_unused() {
1404                    unused += 1;
1405                } else {
1406                    used += 1;
1407                }
1408
1409                // NOTE: testing that serializing the PartitionName (and the whole struct) works
1410                let data1 = wincode::serialize(&partition).unwrap();
1411                f.seek(SeekFrom::Start(
1412                    gpt.partition_entry_lba * ss
1413                        + u64::from(i) * u64::from(gpt.size_of_partition_entry),
1414                ))
1415                .unwrap();
1416                let mut data2 = vec![0; gpt.size_of_partition_entry as usize];
1417                f.read_exact(&mut data2).unwrap();
1418                assert_eq!(data1, data2);
1419
1420                partitions.push(partition);
1421            }
1422            assert_eq!(unused, 126);
1423            assert_eq!(used, 2);
1424
1425            let sum = gpt.crc32_checksum;
1426            gpt.update_crc32_checksum();
1427            assert_eq!(gpt.crc32_checksum, sum);
1428            assert_eq!(gpt.generate_crc32_checksum(), sum);
1429            assert_ne!(gpt.crc32_checksum, 0);
1430
1431            let sum = gpt.partition_entry_array_crc32;
1432            gpt.update_partition_entry_array_crc32(&partitions);
1433            assert_eq!(gpt.partition_entry_array_crc32, sum);
1434            assert_eq!(gpt.generate_partition_entry_array_crc32(&partitions), sum);
1435            assert_ne!(gpt.partition_entry_array_crc32, 0);
1436        }
1437
1438        test(DISK1, 512);
1439        test(DISK2, 4096);
1440    }
1441
1442    #[test]
1443    fn read_and_find_from_primary() {
1444        assert!(GPT::read_from(&mut fs::File::open(DISK1).unwrap(), 512).is_ok());
1445        assert!(GPT::read_from(&mut fs::File::open(DISK1).unwrap(), 4096).is_err());
1446        assert!(GPT::read_from(&mut fs::File::open(DISK2).unwrap(), 512).is_err());
1447        assert!(GPT::read_from(&mut fs::File::open(DISK2).unwrap(), 4096).is_ok());
1448        assert!(GPT::find_from(&mut fs::File::open(DISK1).unwrap()).is_ok());
1449        assert!(GPT::find_from(&mut fs::File::open(DISK2).unwrap()).is_ok());
1450    }
1451
1452    #[test]
1453    fn input_too_short() {
1454        let mut empty = io::Cursor::new(vec![1; 5]);
1455        assert!(matches!(
1456            GPT::read_from(&mut empty, 512).expect_err("Should fail on short input"),
1457            Error::InvalidSignature
1458        ));
1459    }
1460
1461    #[test]
1462    fn find_backup() {
1463        fn test(path: &str, ss: u64) {
1464            let mut cur = io::Cursor::new(fs::read(path).unwrap());
1465            let mut gpt = GPT::read_from(&mut cur, ss).unwrap();
1466            assert_eq!(gpt.header.partition_entry_lba, 2);
1467            gpt.header.crc32_checksum = 1;
1468            cur.seek(SeekFrom::Start(gpt.sector_size)).unwrap();
1469            encode_into_std_write(&gpt.header, &mut cur).unwrap();
1470            let maybe_gpt = GPT::read_from(&mut cur, gpt.sector_size);
1471            assert!(maybe_gpt.is_ok());
1472            let gpt = maybe_gpt.unwrap();
1473            let end = cur.seek(SeekFrom::End(0)).unwrap() / gpt.sector_size - 1;
1474            assert_eq!(gpt.header.primary_lba, end);
1475            assert_eq!(gpt.header.backup_lba, 1);
1476            assert_eq!(
1477                gpt.header.partition_entry_lba,
1478                gpt.header.last_usable_lba + 1
1479            );
1480            assert!(GPT::find_from(&mut cur).is_ok());
1481        }
1482
1483        test(DISK1, 512);
1484        test(DISK2, 4096);
1485    }
1486
1487    #[test]
1488    fn add_partition_left() {
1489        let mut gpt = GPT::find_from(&mut fs::File::open(DISK1).unwrap()).unwrap();
1490        gpt.align = 1;
1491
1492        assert_eq!(gpt.find_first_place(10000), None);
1493        assert_eq!(gpt.find_first_place(4), Some(44));
1494        assert_eq!(gpt.find_first_place(8), Some(53));
1495    }
1496
1497    #[test]
1498    fn add_partition_left_aligned() {
1499        let mut gpt = GPT::find_from(&mut fs::File::open(DISK1).unwrap()).unwrap();
1500
1501        gpt.align = 10000;
1502        assert_eq!(gpt.find_first_place(1), None);
1503        gpt.align = 4;
1504        assert_eq!(gpt.find_first_place(4), Some(44));
1505        gpt.align = 6;
1506        assert_eq!(gpt.find_first_place(4), Some(54));
1507    }
1508
1509    #[test]
1510    fn add_partition_right() {
1511        let mut gpt = GPT::find_from(&mut fs::File::open(DISK2).unwrap()).unwrap();
1512        gpt.align = 1;
1513
1514        assert_eq!(gpt.find_last_place(10000), None);
1515        assert_eq!(gpt.find_last_place(5), Some(90));
1516        assert_eq!(gpt.find_last_place(20), Some(50));
1517    }
1518
1519    #[test]
1520    fn add_partition_right_aligned() {
1521        let mut gpt = GPT::find_from(&mut fs::File::open(DISK2).unwrap()).unwrap();
1522
1523        gpt.align = 10000;
1524        assert_eq!(gpt.find_last_place(1), None);
1525        gpt.align = 4;
1526        assert_eq!(gpt.find_last_place(5), Some(88));
1527        gpt.align = 8;
1528        assert_eq!(gpt.find_last_place(20), Some(48));
1529
1530        // NOTE: special case where there is just enough space but it's not aligned
1531        gpt.align = 1;
1532        assert_eq!(gpt.find_last_place(54), Some(16));
1533        assert_eq!(gpt.find_last_place(55), None);
1534        gpt.align = 10;
1535        assert_eq!(gpt.find_last_place(54), None);
1536    }
1537
1538    #[test]
1539    fn add_partition_optimal() {
1540        let mut gpt = GPT::find_from(&mut fs::File::open(DISK2).unwrap()).unwrap();
1541        gpt.align = 1;
1542
1543        assert_eq!(gpt.find_optimal_place(10000), None);
1544        assert_eq!(gpt.find_optimal_place(5), Some(80));
1545        assert_eq!(gpt.find_optimal_place(20), Some(16));
1546    }
1547
1548    #[test]
1549    fn add_partition_optimal_aligned() {
1550        let mut gpt = GPT::find_from(&mut fs::File::open(DISK2).unwrap()).unwrap();
1551
1552        gpt.align = 10000;
1553        assert_eq!(gpt.find_optimal_place(1), None);
1554        gpt.align = 6;
1555        assert_eq!(gpt.find_optimal_place(5), Some(84));
1556        gpt.align = 9;
1557        assert_eq!(gpt.find_optimal_place(20), Some(18));
1558    }
1559
1560    #[test]
1561    fn sort_partitions() {
1562        let mut gpt = GPT::find_from(&mut fs::File::open(DISK1).unwrap()).unwrap();
1563        gpt.align = 1;
1564
1565        let starting_lba = gpt.find_first_place(4).unwrap();
1566        gpt[10] = GPTPartitionEntry {
1567            starting_lba,
1568            ending_lba: starting_lba + 3,
1569            attribute_bits: 0,
1570            partition_type_guid: [1; 16],
1571            partition_name: "Baz".into(),
1572            unique_partition_guid: [1; 16],
1573        };
1574
1575        assert_eq!(
1576            gpt.iter()
1577                .filter(|(_, x)| x.is_used())
1578                .map(|(i, x)| (i, x.partition_name.as_str()))
1579                .collect::<Vec<_>>(),
1580            vec![(1, "Foo"), (2, "Bar"), (10, "Baz")]
1581        );
1582        gpt.sort();
1583        assert_eq!(
1584            gpt.iter()
1585                .filter(|(_, x)| x.is_used())
1586                .map(|(i, x)| (i, x.partition_name.as_str()))
1587                .collect::<Vec<_>>(),
1588            vec![(1, "Foo"), (2, "Baz"), (3, "Bar")]
1589        );
1590    }
1591
1592    #[test]
1593    fn add_partition_on_unsorted_table() {
1594        let mut gpt = GPT::find_from(&mut fs::File::open(DISK1).unwrap()).unwrap();
1595        gpt.align = 1;
1596
1597        let starting_lba = gpt.find_first_place(4).unwrap();
1598        gpt.partitions[10] = GPTPartitionEntry {
1599            starting_lba,
1600            ending_lba: starting_lba + 3,
1601            attribute_bits: 0,
1602            partition_type_guid: [1; 16],
1603            partition_name: "Baz".into(),
1604            unique_partition_guid: [1; 16],
1605        };
1606
1607        assert_eq!(gpt.find_first_place(8), Some(53));
1608    }
1609
1610    #[test]
1611    fn write_from_primary() {
1612        fn test(path: &str, ss: u64) {
1613            let mut f = fs::File::open(path).unwrap();
1614            let len = f.seek(SeekFrom::End(0)).unwrap();
1615            let data = vec![0; len as usize];
1616            let mut cur = io::Cursor::new(data);
1617            let mut gpt = GPT::read_from(&mut f, ss).unwrap();
1618            let backup_lba = gpt.header.backup_lba;
1619            gpt.write_into(&mut cur).unwrap();
1620            assert!(GPT::read_from(&mut cur, ss).is_ok());
1621
1622            gpt.header.crc32_checksum = 1;
1623            cur.seek(SeekFrom::Start(ss)).unwrap();
1624            encode_into_std_write(&gpt.header, &mut cur).unwrap();
1625            let maybe_gpt = GPT::read_from(&mut cur, ss);
1626            assert!(maybe_gpt.is_ok());
1627            let gpt = maybe_gpt.unwrap();
1628            assert_eq!(gpt.header.primary_lba, backup_lba);
1629            assert_eq!(gpt.header.backup_lba, 1);
1630        }
1631
1632        test(DISK1, 512);
1633        test(DISK2, 4096);
1634    }
1635
1636    #[test]
1637    fn write_from_backup() {
1638        fn test(path: &str, ss: u64) {
1639            let mut cur = io::Cursor::new(fs::read(path).unwrap());
1640            let mut gpt = GPT::read_from(&mut cur, ss).unwrap();
1641            let primary = gpt.clone();
1642            gpt.header.crc32_checksum = 1;
1643            let backup_lba = gpt.header.backup_lba;
1644            cur.seek(SeekFrom::Start(ss)).unwrap();
1645            encode_into_std_write(&gpt.header, &mut cur).unwrap();
1646            let mut gpt = GPT::read_from(&mut cur, ss).unwrap();
1647            assert!(!gpt.is_primary());
1648            assert!(gpt.is_backup());
1649            let partition_entry_lba = gpt.header.partition_entry_lba;
1650            let first_usable_lba = gpt.header.first_usable_lba;
1651            let last_usable_lba = gpt.header.last_usable_lba;
1652            let primary_header = gpt.write_into(&mut cur).unwrap();
1653            assert!(primary_header.is_primary());
1654            assert!(!primary_header.is_backup());
1655            let mut gpt = GPT::read_from(&mut cur, ss).unwrap();
1656            assert_eq!(gpt.header.primary_lba, 1);
1657            assert_eq!(gpt.header.backup_lba, backup_lba);
1658            assert_eq!(gpt.header.partition_entry_lba, 2);
1659            assert_eq!(gpt.header.first_usable_lba, first_usable_lba);
1660            assert_eq!(gpt.header.last_usable_lba, last_usable_lba);
1661            assert_eq!(primary, gpt);
1662
1663            gpt.header.crc32_checksum = 1;
1664            cur.seek(SeekFrom::Start(ss)).unwrap();
1665            encode_into_std_write(&gpt.header, &mut cur).unwrap();
1666            let maybe_gpt = GPT::read_from(&mut cur, ss);
1667            assert!(maybe_gpt.is_ok());
1668            let gpt = maybe_gpt.unwrap();
1669            assert_eq!(gpt.header.primary_lba, backup_lba);
1670            assert_eq!(gpt.header.backup_lba, 1);
1671            assert_eq!(gpt.header.partition_entry_lba, partition_entry_lba);
1672        }
1673
1674        test(DISK1, 512);
1675        test(DISK2, 4096);
1676    }
1677
1678    #[test]
1679    fn write_with_changes() {
1680        fn test(path: &str, ss: u64) {
1681            let mut f = fs::File::open(path).unwrap();
1682            let len = f.seek(SeekFrom::End(0)).unwrap();
1683            let data = vec![0; len as usize];
1684            let mut cur = io::Cursor::new(data);
1685            let mut gpt = GPT::read_from(&mut f, ss).unwrap();
1686            let backup_lba = gpt.header.backup_lba;
1687
1688            assert!(gpt.remove(1).is_ok());
1689            gpt.write_into(&mut cur).unwrap();
1690            let maybe_gpt = GPT::read_from(&mut cur, ss);
1691            assert!(maybe_gpt.is_ok(), "{:?}", maybe_gpt.err());
1692
1693            gpt.header.crc32_checksum = 1;
1694            cur.seek(SeekFrom::Start(ss)).unwrap();
1695            encode_into_std_write(&gpt.header, &mut cur).unwrap();
1696            let maybe_gpt = GPT::read_from(&mut cur, ss);
1697            assert!(maybe_gpt.is_ok());
1698            let gpt = maybe_gpt.unwrap();
1699            assert_eq!(gpt.header.primary_lba, backup_lba);
1700            assert_eq!(gpt.header.backup_lba, 1);
1701        }
1702
1703        test(DISK1, 512);
1704        test(DISK2, 4096);
1705    }
1706
1707    #[test]
1708    #[allow(clippy::manual_swap)]
1709    fn write_invalid_boundaries() {
1710        fn test(path: &str, ss: u64) {
1711            let mut cur = io::Cursor::new(fs::read(path).unwrap());
1712            // start before first_usable_lba
1713            let mut gpt = GPT::read_from(&mut cur, ss).unwrap();
1714            gpt[1].starting_lba = gpt.header.first_usable_lba - 1;
1715            gpt.write_into(&mut cur).unwrap_err();
1716            // end before start
1717            let mut gpt = GPT::read_from(&mut cur, ss).unwrap();
1718            let start = gpt[1].starting_lba;
1719            gpt[1].starting_lba = gpt[1].ending_lba;
1720            gpt[1].ending_lba = start;
1721            gpt.write_into(&mut cur).unwrap_err();
1722            // overlap
1723            let mut gpt = GPT::read_from(&mut cur, ss).unwrap();
1724            gpt[1].ending_lba = gpt[2].starting_lba;
1725            gpt.write_into(&mut cur).unwrap_err();
1726            // end after last_usable_lba
1727            let mut gpt = GPT::read_from(&mut cur, ss).unwrap();
1728            gpt[2].ending_lba = gpt.header.last_usable_lba + 1;
1729            gpt.write_into(&mut cur).unwrap_err();
1730            // round-trip, everything valid
1731            let mut gpt = GPT::read_from(&mut cur, ss).unwrap();
1732            gpt.write_into(&mut cur).unwrap();
1733        }
1734        test(DISK1, 512);
1735        test(DISK2, 4096);
1736    }
1737
1738    #[test]
1739    fn get_maximum_partition_size_on_empty_disk() {
1740        let mut gpt = GPT::find_from(&mut fs::File::open(DISK1).unwrap()).unwrap();
1741        gpt.align = 1;
1742
1743        for i in 1..=gpt.header.number_of_partition_entries {
1744            assert!(gpt.remove(i).is_ok());
1745        }
1746
1747        assert_eq!(gpt.get_maximum_partition_size().ok(), Some(33));
1748    }
1749
1750    #[test]
1751    fn get_maximum_partition_size_on_disk_full() {
1752        let mut gpt = GPT::find_from(&mut fs::File::open(DISK1).unwrap()).unwrap();
1753        gpt.align = 1;
1754
1755        for partition in gpt.partitions.iter_mut().skip(1) {
1756            partition.partition_type_guid = [0; 16];
1757        }
1758        gpt.partitions[0].starting_lba = gpt.header.first_usable_lba;
1759        gpt.partitions[0].ending_lba = gpt.header.last_usable_lba;
1760
1761        assert!(gpt.get_maximum_partition_size().is_err());
1762    }
1763
1764    #[test]
1765    fn get_maximum_partition_size_on_empty_disk_and_aligned() {
1766        let mut gpt = GPT::find_from(&mut fs::File::open(DISK1).unwrap()).unwrap();
1767
1768        for i in 1..=gpt.header.number_of_partition_entries {
1769            assert!(gpt.remove(i).is_ok());
1770        }
1771
1772        gpt.align = 10;
1773        assert_eq!(gpt.get_maximum_partition_size().ok(), Some(20));
1774        gpt.align = 6;
1775        assert_eq!(gpt.get_maximum_partition_size().ok(), Some(30));
1776    }
1777
1778    #[test]
1779    fn create_new_gpt() {
1780        fn test(path: &str, ss: u64) {
1781            let mut f = fs::File::open(path).unwrap();
1782            let gpt1 = GPT::read_from(&mut f, ss).unwrap();
1783            let gpt2 = GPT::new_from(&mut f, ss, [1; 16]).unwrap();
1784            assert_eq!(gpt2.header.backup_lba, gpt1.header.backup_lba);
1785            assert_eq!(gpt2.header.last_usable_lba, gpt1.header.last_usable_lba);
1786            assert_eq!(gpt2.header.first_usable_lba, gpt1.header.first_usable_lba);
1787        }
1788
1789        test(DISK1, 512);
1790        test(DISK2, 4096);
1791    }
1792
1793    #[test]
1794    fn determine_partition_alignment_no_partition() {
1795        fn test(ss: u64) {
1796            let data = vec![0; ss as usize * DEFAULT_ALIGN as usize * 10];
1797            let mut cur = io::Cursor::new(data);
1798            let mut gpt = GPT::new_from(&mut cur, ss, [1; 16]).unwrap();
1799            assert_eq!(gpt.align, DEFAULT_ALIGN);
1800            gpt.write_into(&mut cur).unwrap();
1801            let gpt = GPT::read_from(&mut cur, ss).unwrap();
1802            assert_eq!(gpt.align, DEFAULT_ALIGN);
1803        }
1804
1805        test(512);
1806        test(4096);
1807    }
1808
1809    #[test]
1810    fn determine_partition_alignment() {
1811        fn test(ss: u64, align: u64) {
1812            let data = vec![0; ss as usize * align as usize * 21];
1813            let mut cur = io::Cursor::new(data);
1814            let mut gpt = GPT::new_from(&mut cur, ss, [1; 16]).unwrap();
1815            gpt[1] = GPTPartitionEntry {
1816                attribute_bits: 0,
1817                ending_lba: 6 * align,
1818                partition_name: "".into(),
1819                partition_type_guid: [1; 16],
1820                // start at least at first_usable_lba in smallest case
1821                starting_lba: 5 * align,
1822                unique_partition_guid: [1; 16],
1823            };
1824            gpt[2] = GPTPartitionEntry {
1825                attribute_bits: 0,
1826                ending_lba: 16 * align,
1827                partition_name: "".into(),
1828                partition_type_guid: [1; 16],
1829                starting_lba: 8 * align,
1830                unique_partition_guid: [2; 16],
1831            };
1832            gpt.write_into(&mut cur).unwrap();
1833            let gpt = GPT::read_from(&mut cur, ss).unwrap();
1834            assert_eq!(gpt.align, align);
1835        }
1836
1837        test(512, 8); // 4096 bytes
1838        test(512, 2048); // 1MB
1839        test(512, 2048 * 4); // 4MB
1840        test(4096, 8);
1841        test(4096, 2048);
1842        test(4096, 2048 * 4);
1843    }
1844
1845    #[test]
1846    fn determine_partition_alignment_full_disk() {
1847        fn test(ss: u64) {
1848            let data = vec![0; ss as usize * 100];
1849            let mut cur = io::Cursor::new(data);
1850            let mut gpt = GPT::new_from(&mut cur, ss, [1; 16]).unwrap();
1851            gpt[1] = GPTPartitionEntry {
1852                attribute_bits: 0,
1853                ending_lba: gpt.header.last_usable_lba,
1854                partition_name: "".into(),
1855                partition_type_guid: [1; 16],
1856                starting_lba: gpt.header.first_usable_lba,
1857                unique_partition_guid: [1; 16],
1858            };
1859            gpt.write_into(&mut cur).unwrap();
1860            let gpt = GPT::read_from(&mut cur, ss).unwrap();
1861            assert_eq!(gpt.align, 1);
1862
1863            let mut gpt = GPT::new_from(&mut cur, ss, [1; 16]).unwrap();
1864            gpt[1] = GPTPartitionEntry {
1865                attribute_bits: 0,
1866                ending_lba: gpt.header.last_usable_lba,
1867                partition_name: "".into(),
1868                partition_type_guid: [1; 16],
1869                starting_lba: gpt.header.first_usable_lba + 1,
1870                unique_partition_guid: [1; 16],
1871            };
1872            gpt.write_into(&mut cur).unwrap();
1873            let gpt = GPT::read_from(&mut cur, ss).unwrap();
1874            assert_eq!(gpt.align, gpt.header.first_usable_lba + 1);
1875        }
1876
1877        test(512);
1878        test(4096);
1879    }
1880
1881    #[test]
1882    fn writing_protective_mbr() {
1883        fn test(ss: u64) {
1884            let data = vec![2; ss as usize * 100];
1885            let mut cur = io::Cursor::new(data);
1886            GPT::write_protective_mbr_into(&mut cur, ss).unwrap();
1887            let data = cur.get_ref();
1888
1889            assert_eq!(data[510], 0x55);
1890            assert_eq!(data[511], 0xaa);
1891            assert_eq!(data[446 + 4], 0xee);
1892            for (i, x) in data.iter().enumerate() {
1893                if !(446..512).contains(&i) {
1894                    assert_eq!(*x, 2);
1895                }
1896            }
1897
1898            cur.seek(SeekFrom::Start(446 + 8)).unwrap();
1899            let first_lba: u32 = decode_from_std_read(&mut cur).unwrap();
1900            let sectors: u32 = decode_from_std_read(&mut cur).unwrap();
1901            assert_eq!(first_lba, 1);
1902            assert_eq!(sectors, 99);
1903        }
1904
1905        test(512);
1906        test(4096);
1907    }
1908
1909    #[test]
1910    fn read_from_smaller_disk_and_write_to_bigger_disk() {
1911        fn test(path: &str, ss: u64) {
1912            let mut f = fs::File::open(path).unwrap();
1913            let len = f.seek(SeekFrom::End(0)).unwrap();
1914            let gpt1 = GPT::read_from(&mut f, ss).unwrap();
1915            let data = vec![0; len as usize * 2];
1916            let mut cur = io::Cursor::new(data);
1917            gpt1.clone().write_into(&mut cur).unwrap();
1918            let gpt2 = GPT::read_from(&mut cur, ss).unwrap();
1919            assert_eq!(gpt1, gpt2);
1920        }
1921
1922        test(DISK1, 512);
1923        test(DISK2, 4096);
1924    }
1925
1926    #[test]
1927    fn read_label_with_trailing_garbage_in_names() {
1928        fn test(path: &str, ss: u64) {
1929            let mut f = fs::File::open(path).unwrap();
1930            let gpt = GPT::read_from(&mut f, ss);
1931            // CRC check passes with trailing garbage within partition names
1932            assert!(gpt.is_ok());
1933            // Trailing garbage data are visible using hexdump
1934            assert_eq!(
1935                gpt.unwrap()
1936                    .partitions
1937                    .get(1)
1938                    .unwrap()
1939                    .partition_name
1940                    .as_str(),
1941                "Name with garbage"
1942            );
1943        }
1944        test(DISK3, 512);
1945        test(DISK4, 4096);
1946    }
1947}
1948
1949#[cfg(doctest)]
1950mod test_readme {
1951    // for Rust < 1.54
1952    // https://blog.guillaume-gomez.fr/articles/2021-08-03+Improvements+for+%23%5Bdoc%5D+attributes+in+Rust
1953    macro_rules! check_doc {
1954        ($x:expr) => {
1955            #[doc = $x]
1956            extern "C" {}
1957        };
1958    }
1959    check_doc!(include_str!("../README.md"));
1960}