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