1#![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#[cfg(all(target_os = "linux", feature = "nix"))]
90pub mod linux;
91
92const DEFAULT_ALIGN: u64 = 2048;
93const MAX_ALIGN: u64 = 16384;
94
95#[derive(Debug, Error)]
97#[non_exhaustive]
98pub enum Error {
99 #[error("deserialization failed")]
101 Deserialize(#[from] DecodeError),
102 #[error("seserialization failed")]
104 Seserialize(#[from] EncodeError),
105 #[error("generic I/O error")]
107 Io(#[from] io::Error),
108 #[error("invalid signature")]
111 InvalidSignature,
112 #[error("invalid revision")]
115 InvalidRevision,
116 #[error("invalid header size")]
118 InvalidHeaderSize,
119 #[error("corrupted CRC32 checksum ({0} != {1})")]
122 InvalidChecksum(u32, u32),
123 #[error("corrupted partition entry array CRC32 checksum ({0} != {1})")]
126 InvalidPartitionEntryArrayChecksum(u32, u32),
127 #[error("could not read primary header ({0}) nor backup header ({1})")]
132 ReadError(Box<Error>, Box<Error>),
133 #[error("no space left")]
135 NoSpaceLeft,
136 #[error("conflict of partition GUIDs")]
138 ConflictPartitionGUID,
139 #[error(
145 "invalid partition boundaries: partitions must have positive size, must not overlap, \
146 and must fit within the disk"
147 )]
148 InvalidPartitionBoundaries,
149 #[error("invalid partition number: {0}")]
154 InvalidPartitionNumber(u32),
155 #[error("unused partition")]
157 UnusedPartition,
158 #[error("partition not found")]
160 PartitionNotFound,
161 #[error("an arithmetic operation overflowed")]
163 Overflow,
164}
165
166pub type Result<T> = std::result::Result<T, Error>;
168
169#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
172pub struct GPTHeader {
173 pub signature: [u8; 8],
175 pub revision: [u8; 4],
177 pub header_size: u32,
179 pub crc32_checksum: u32,
181 pub reserved: [u8; 4],
183 pub primary_lba: u64,
185 pub backup_lba: u64,
187 pub first_usable_lba: u64,
189 pub last_usable_lba: u64,
191 pub disk_guid: [u8; 16],
193 pub partition_entry_lba: u64,
197 pub number_of_partition_entries: u32,
199 pub size_of_partition_entry: u32,
201 pub partition_entry_array_crc32: u32,
203}
204
205impl GPTHeader {
206 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 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 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 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 pub fn update_crc32_checksum(&mut self) {
304 self.crc32_checksum = self.generate_crc32_checksum();
305 }
306
307 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 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 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 if self.partition_entry_lba != 2 {
357 self.partition_entry_lba = self.last_usable_lba + 1;
358 }
359
360 Ok(())
361 }
362
363 pub fn is_primary(&self) -> bool {
366 self.primary_lba == 1
367 }
368
369 pub fn is_backup(&self) -> bool {
375 !self.is_primary()
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq)]
381pub struct PartitionName(String);
382
383impl PartitionName {
384 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#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
478pub struct GPTPartitionEntry {
479 pub partition_type_guid: [u8; 16],
481 pub unique_partition_guid: [u8; 16],
483 pub starting_lba: u64,
485 pub ending_lba: u64,
487 pub attribute_bits: u64,
492 pub partition_name: PartitionName,
503}
504
505impl GPTPartitionEntry {
506 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 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 pub fn is_unused(&self) -> bool {
544 self.partition_type_guid == [0; 16]
545 }
546
547 pub fn is_used(&self) -> bool {
549 !self.is_unused()
550 }
551
552 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 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#[derive(Debug, Clone, PartialEq, Eq)]
654pub struct GPT {
655 pub sector_size: u64,
660 pub header: GPTHeader,
662 partitions: Vec<GPTPartitionEntry>,
663 pub align: u64,
673}
674
675impl GPT {
676 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 if bootable {
1281 writer.write_all(&[0x80])?;
1282 } else {
1283 writer.write_all(&[0x00])?;
1284 }
1285 writer.write_all(&[
1286 0x00, 0x02, 0x00, 0xee, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, ])?;
1291 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])?; writer.write_all(&[0; 16])?; writer.write_all(&[0; 16])?; writer.write_all(&[0x55, 0xaa])?; Ok(())
1307 }
1308
1309 pub fn is_primary(&self) -> bool {
1312 self.header.is_primary()
1313 }
1314
1315 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 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 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 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 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 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 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 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 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); test(512, 2048); test(512, 2048 * 4); 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 macro_rules! check_doc {
1914 ($x:expr) => {
1915 #[doc = $x]
1916 extern "C" {}
1917 };
1918 }
1919 check_doc!(include_str!("../README.md"));
1920}