mbrs/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[cfg(all(feature = "std", feature = "no-std"))]
4compile_error!("feature \"std\" and feature \"no-std\" cannot be enabled at the same time");
5
6use arbitrary_int::{u10, u6};
7use arrayref::array_ref;
8#[cfg(feature = "std")]
9use std::io::Read;
10#[cfg(feature = "std")]
11use thiserror::Error;
12#[cfg(feature = "no-std")]
13use thiserror_no_std::Error;
14
15#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
16pub enum AddrScheme {
17    Chs,
18    Lba,
19}
20
21#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
22pub enum PartType {
23    Empty,
24    Fat12 {
25        visible: bool,
26    },
27    Oem,
28    Fat16 {
29        visible: bool,
30        leq32mib: bool,
31        scheme: AddrScheme,
32    },
33    Extended {
34        scheme: AddrScheme,
35    },
36    /// Might also be HPFS or NTFS
37    ExFAT {
38        visible: bool,
39    },
40    Fat32 {
41        visible: bool,
42        scheme: AddrScheme,
43    },
44    WindowsRe,
45    DynamicDisk,
46    Gpfs,
47    LinuxSwap,
48    /// EXT3, EXT4, BTRFS etc.
49    LinuxNative,
50    IntelRapidStart,
51    LinuxLvm,
52    FreeBsd,
53    OpenBsd,
54    NetBsd,
55    MacOs,
56    Solaris,
57    BeOs,
58    ProtectiveMbr,
59    Efi,
60    LinuxRaid,
61}
62
63impl PartType {
64    /// returns the addressing scheme (CHS or LBA) used by partitions of the given type
65    pub fn addr_scheme(&self) -> AddrScheme {
66        match self {
67            PartType::Fat32 { scheme, .. }
68            | PartType::Fat16 { scheme, .. }
69            | PartType::Extended { scheme, .. } => *scheme,
70            // PartType::LinuxNative => AddrScheme::Lba,
71            _ => AddrScheme::Lba,
72        }
73    }
74
75    pub fn btrfs() -> Self {
76        PartType::LinuxNative
77    }
78
79    pub fn ext4() -> Self {
80        PartType::LinuxNative
81    }
82}
83
84impl TryFrom<u8> for PartType {
85    type Error = ();
86    fn try_from(value: u8) -> Result<Self, Self::Error> {
87        use AddrScheme::*;
88        use PartType::*;
89
90        match value {
91            0x00 => Ok(Empty),
92            0x01 => Ok(Fat12 { visible: true }),
93            0x02 => Ok(Fat12 { visible: false }),
94            0x12 => Ok(Oem),
95            0x04 => Ok(Fat16 {
96                visible: true,
97                leq32mib: true,
98                scheme: Chs,
99            }),
100            0x14 => Ok(Fat16 {
101                visible: false,
102                leq32mib: true,
103                scheme: Chs,
104            }),
105            0x05 => Ok(Extended { scheme: Chs }),
106            0x06 => Ok(Fat16 {
107                visible: true,
108                leq32mib: false,
109                scheme: Chs,
110            }),
111            0x07 => Ok(ExFAT { visible: true }),
112            0x17 => Ok(ExFAT { visible: false }),
113            0x0B => Ok(Fat32 {
114                visible: true,
115                scheme: Chs,
116            }),
117            0x1B => Ok(Fat32 {
118                visible: false,
119                scheme: Chs,
120            }),
121            0x0C => Ok(Fat32 {
122                visible: true,
123                scheme: Lba,
124            }),
125            0x1C => Ok(Fat32 {
126                visible: false,
127                scheme: Lba,
128            }),
129            0x0E => Ok(Fat16 {
130                visible: true,
131                leq32mib: false,
132                scheme: Lba,
133            }),
134            0x1E => Ok(Fat16 {
135                visible: false,
136                leq32mib: false,
137                scheme: Lba,
138            }),
139            0x0F => Ok(Extended { scheme: Lba }),
140            0x27 => Ok(WindowsRe),
141            0x42 => Ok(DynamicDisk),
142            0x75 => Ok(Gpfs),
143            0x82 => Ok(LinuxSwap),
144            0x83 => Ok(LinuxNative),
145            0x84 => Ok(IntelRapidStart),
146            0x8E => Ok(LinuxLvm),
147            0xA5 => Ok(FreeBsd),
148            0xA6 => Ok(OpenBsd),
149            0xA9 => Ok(NetBsd),
150            0xAF => Ok(MacOs),
151            0xBF => Ok(Solaris),
152            0xEB => Ok(BeOs),
153            0xEE => Ok(ProtectiveMbr),
154            0xEF => Ok(Efi),
155            0xFD => Ok(LinuxRaid),
156            _ => Err(()),
157        }
158    }
159}
160
161impl TryFrom<PartType> for u8 {
162    type Error = ();
163    fn try_from(part_type: PartType) -> Result<Self, Self::Error> {
164        use AddrScheme::*;
165        use PartType::*;
166
167        match part_type {
168            Empty => Ok(0x00),
169            Fat12 { visible: true } => Ok(0x01),
170            Fat12 { visible: false } => Ok(0x02),
171            Oem => Ok(0x12),
172            Fat16 {
173                visible: true,
174                leq32mib: true,
175                scheme: Chs,
176            } => Ok(0x04),
177            Fat16 {
178                visible: false,
179                leq32mib: true,
180                scheme: Chs,
181            } => Ok(0x14),
182            Extended { scheme: Chs } => Ok(0x05),
183            Fat16 {
184                visible: true,
185                leq32mib: false,
186                scheme: Chs,
187            } => Ok(0x06),
188            ExFAT { visible: true } => Ok(0x07),
189            ExFAT { visible: false } => Ok(0x17),
190            Fat32 {
191                visible: true,
192                scheme: Chs,
193            } => Ok(0x0B),
194            Fat32 {
195                visible: false,
196                scheme: Chs,
197            } => Ok(0x1B),
198            Fat32 {
199                visible: true,
200                scheme: Lba,
201            } => Ok(0x0C),
202            Fat32 {
203                visible: false,
204                scheme: Lba,
205            } => Ok(0x1C),
206            Fat16 {
207                visible: true,
208                leq32mib: false,
209                scheme: Lba,
210            } => Ok(0x0E),
211            Fat16 {
212                visible: false,
213                leq32mib: false,
214                scheme: Lba,
215            } => Ok(0x1E),
216            Extended { scheme: Lba } => Ok(0x0F),
217            WindowsRe => Ok(0x27),
218            DynamicDisk => Ok(0x42),
219            Gpfs => Ok(0x75),
220            LinuxSwap => Ok(0x82),
221            LinuxNative => Ok(0x83),
222            IntelRapidStart => Ok(0x84),
223            LinuxLvm => Ok(0x8E),
224            FreeBsd => Ok(0xA5),
225            OpenBsd => Ok(0xA6),
226            NetBsd => Ok(0xA9),
227            MacOs => Ok(0xAF),
228            Solaris => Ok(0xBF),
229            BeOs => Ok(0xEB),
230            ProtectiveMbr => Ok(0xEE),
231            Efi => Ok(0xEF),
232            LinuxRaid => Ok(0xFD),
233            _ => Err(()),
234        }
235    }
236}
237
238#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
239pub struct ChsEntry {
240    pub raw: [u8; 3],
241}
242
243/// masks off the lowest 6 bits
244const SECTOR_MASK: u8 = 0x3F;
245
246impl ChsEntry {
247    /// Note that only the 6 bottom bits of sector and 10 bottom bits of cylinder can be used for CHS.
248    /// If a value leaves this range this method returns None
249    // TODO: use https://crates.io/crates/arbitrary-int to fix the input types
250    pub fn from_chs(cylinder: u10, head: u8, sector: u6) -> Self {
251        ChsEntry {
252            raw: [
253                head,
254                (u8::from(sector) & SECTOR_MASK) | (((u16::from(cylinder) & 0x300) >> 2) as u8),
255                (u16::from(cylinder) & 0xFF) as u8,
256            ],
257        }
258    }
259
260    pub fn cylinder(&self) -> u10 {
261        u10::new((((self.raw[1] & !SECTOR_MASK) as u16) << 2) | (self.raw[2] as u16))
262    }
263
264    pub fn head(&self) -> u8 {
265        self.raw[0]
266    }
267
268    pub fn sector(&self) -> u6 {
269        u6::new(self.raw[1] & SECTOR_MASK)
270    }
271
272    pub fn chs(&self) -> (u10, u8, u6) {
273        (self.cylinder(), self.head(), self.sector())
274    }
275
276    pub fn try_from_lba(lba: u32) -> Result<Self, MbrError> {
277        // let heads_per_cylinder: u32 = 255;
278        // let sectors_per_track: u32 = 63;
279        let heads_per_cylinder: u32 = 4;
280        let sectors_per_track: u32 = 32;
281
282        let cylinder = lba / (heads_per_cylinder * sectors_per_track);
283        let head = (lba / sectors_per_track) % heads_per_cylinder;
284        let sector = lba % sectors_per_track + 1;
285        match (
286            u16::try_from(cylinder)
287                .ok()
288                .and_then(|x| u10::try_new(x).ok()),
289            u8::try_from(head),
290            u8::try_from(sector).ok().and_then(|x| u6::try_new(x).ok()),
291        ) {
292            (Some(c), Ok(h), Some(s)) => Ok(ChsEntry::from_chs(c, h, s)),
293            _ => Err(MbrError::InvalidAddressChs {
294                cylinder,
295                head,
296                sector,
297            }),
298        }
299    }
300
301    /// Like `try_from_lba` but silently truncates the input
302    pub fn from_lba_truncating(lba: u32) -> Self {
303        ChsEntry::try_from_lba(lba).unwrap_or_else(|e| {
304            if let MbrError::InvalidAddressChs {
305                cylinder,
306                head,
307                sector,
308            } = e
309            {
310                // If we're using LBA we don't really care about the CHS entries being correct. We simply
311                // truncate the "correct" values that CHS can't represent and use those instead.
312                ChsEntry::from_chs(
313                    u10::new(cylinder as u16 & 0x3FF),
314                    head as u8,
315                    u6::new(sector as u8 & SECTOR_MASK),
316                )
317            } else {
318                panic!("Unexpected error: {}", e)
319            }
320        })
321    }
322}
323
324#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
325pub struct PartInfo {
326    bootable: bool,
327    first_sector_chs: ChsEntry,
328    part_type: PartType,
329    last_sector_chs: ChsEntry,
330    start_sector_lba: u32,
331    sector_count_lba: u32,
332}
333
334// partition!(10 B, *, 100 MiB, true, Linux { lba: true })
335
336#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
337pub struct IncompletePartInfo {
338    pub start_lba: Option<u32>,
339    pub end_lba: Option<u32>,
340    pub size_lba: Option<u32>,
341    pub bootable: bool,
342    pub part_type: PartType,
343}
344
345impl TryFrom<IncompletePartInfo> for PartInfo {
346    type Error = MbrError;
347    fn try_from(value: IncompletePartInfo) -> Result<Self, Self::Error> {
348        // Use 0 = start + size - end - 1 in different forms to calculate missing fields
349        let (start, size) = match (value.start_lba, value.end_lba, value.size_lba) {
350            (Some(start), Some(end), Some(size)) if 1 == start + size - end && start < end => {
351                Ok((start, size))
352            }
353            (Some(start), Some(end), Some(size)) => {
354                Err(MbrError::InconsistentPartInfo { start, end, size })
355            }
356            (Some(start), Some(end), None) if start < end => Ok((start, 1 + end - start)),
357            (Some(_start), Some(_end), None) => Err(MbrError::BrokenPartitionBounds),
358            (Some(start), None, Some(size)) => Ok((start, size)),
359            (None, Some(end), Some(size)) if size <= end => Ok((1 + end - size, end)),
360            (None, Some(_end), Some(_size)) => Err(MbrError::BrokenPartitionBounds),
361            _ => Err(MbrError::IncompleteInput),
362        }?;
363        PartInfo::try_from_lba(value.bootable, start, size, value.part_type)
364    }
365}
366
367impl PartInfo {
368    pub fn try_from_lba(
369        bootable: bool,
370        start_sector_lba: u32,
371        sector_count_lba: u32,
372        part_type: PartType,
373    ) -> Result<Self, MbrError> {
374        let end_sector_lba = start_sector_lba + sector_count_lba - 1;
375        match part_type.addr_scheme() {
376            AddrScheme::Lba => Ok(Self {
377                bootable,
378                first_sector_chs: ChsEntry::from_lba_truncating(start_sector_lba),
379                last_sector_chs: ChsEntry::from_lba_truncating(end_sector_lba),
380                start_sector_lba,
381                sector_count_lba,
382                part_type,
383            }),
384            AddrScheme::Chs => {
385                let first_sector_chs = ChsEntry::try_from_lba(start_sector_lba)?;
386                let last_sector_chs = ChsEntry::try_from_lba(end_sector_lba)?;
387                Ok(Self {
388                    bootable,
389                    first_sector_chs,
390                    last_sector_chs,
391                    start_sector_lba,
392                    sector_count_lba,
393                    part_type,
394                })
395            }
396        }
397    }
398
399    pub fn try_from_lba_bounds(
400        bootable: bool,
401        start_sector_lba: u32,
402        end_sector_lba: u32,
403        part_type: PartType,
404    ) -> Result<Self, MbrError> {
405        let sector_count_lba = 1 + end_sector_lba - start_sector_lba;
406        match part_type.addr_scheme() {
407            AddrScheme::Lba => Ok(Self {
408                bootable,
409                first_sector_chs: ChsEntry::from_lba_truncating(start_sector_lba),
410                last_sector_chs: ChsEntry::from_lba_truncating(end_sector_lba),
411                start_sector_lba,
412                sector_count_lba,
413                part_type,
414            }),
415            AddrScheme::Chs => {
416                let first_sector_chs = ChsEntry::try_from_lba(start_sector_lba)?;
417                let last_sector_chs = ChsEntry::try_from_lba(end_sector_lba)?;
418                Ok(Self {
419                    bootable,
420                    first_sector_chs,
421                    last_sector_chs,
422                    start_sector_lba,
423                    sector_count_lba,
424                    part_type,
425                })
426            }
427        }
428    }
429
430    pub fn is_zeroed(&self) -> bool {
431        !self.bootable
432            && self.first_sector_chs.raw == [0, 0, 0]
433            && self.part_type == PartType::Empty
434            && self.last_sector_chs.raw == [0, 0, 0]
435            && self.start_sector_lba == 0
436            && self.sector_count_lba == 0
437    }
438
439    pub fn sector_count_lba(&self) -> u32 {
440        self.sector_count_lba
441    }
442
443    pub fn start_sector_lba(&self) -> u32 {
444        self.start_sector_lba
445    }
446
447    pub fn end_sector_lba(&self) -> u32 {
448        self.start_sector_lba + self.sector_count_lba - 1
449    }
450
451    pub fn part_type(&self) -> &PartType {
452        &self.part_type
453    }
454
455    pub fn bootable(&self) -> bool {
456        self.bootable
457    }
458
459    pub fn first_sector_chs(&self) -> ChsEntry {
460        self.first_sector_chs
461    }
462
463    pub fn last_sector_chs(&self) -> ChsEntry {
464        self.last_sector_chs
465    }
466}
467
468#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Error)]
469pub enum PartInfoErr {
470    #[error("the `bootable` value {0:#X} of the partition is invalid. Set to 0x80 for bootable and 0x00 for non-bootable.")]
471    UnclearBootable(u8),
472    #[error("the partition type is unknown.")]
473    UnknownPartType(u8),
474    #[error("the partition type is {0:?} is not valid for MBR.")]
475    NonMbrPartType(PartType),
476}
477
478impl TryFrom<&[u8; 16]> for PartInfo {
479    type Error = PartInfoErr;
480    fn try_from(buf: &[u8; 16]) -> Result<Self, Self::Error> {
481        let bootable = match buf[0] {
482            0x80 => Ok(true),
483            0x00 => Ok(false),
484            x => Err(PartInfoErr::UnclearBootable(x)),
485        }?;
486        let part_type =
487            PartType::try_from(buf[4]).map_err(|_| PartInfoErr::UnknownPartType(buf[4]))?;
488
489        Ok(PartInfo {
490            bootable,
491            part_type,
492            first_sector_chs: ChsEntry {
493                raw: *array_ref![buf, 1, 3],
494            },
495            last_sector_chs: ChsEntry {
496                raw: *array_ref![buf, 5, 3],
497            },
498            start_sector_lba: u32::from_le_bytes(*array_ref![buf, 8, 4]),
499            sector_count_lba: u32::from_le_bytes(*array_ref![buf, 12, 4]),
500        })
501    }
502}
503
504impl TryFrom<PartInfo> for [u8; 16] {
505    type Error = PartInfoErr;
506    fn try_from(part: PartInfo) -> Result<Self, Self::Error> {
507        let mut buf = [0; 16];
508        buf[0] = if part.bootable { 0x80 } else { 0x00 };
509        buf[4] = u8::try_from(part.part_type)
510            .map_err(|_| PartInfoErr::NonMbrPartType(part.part_type))?;
511        buf[1..4].copy_from_slice(&part.first_sector_chs.raw);
512        buf[5..8].copy_from_slice(&part.last_sector_chs.raw);
513        buf[8..12].copy_from_slice(&part.start_sector_lba.to_le_bytes());
514        buf[12..16].copy_from_slice(&part.sector_count_lba.to_le_bytes());
515        Ok(buf)
516    }
517}
518
519#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
520pub struct MbrPartTable {
521    pub entries: [Option<PartInfo>; 4],
522}
523
524impl From<&[u8; 64]> for MbrPartTable {
525    fn from(buf: &[u8; 64]) -> Self {
526        let from_slice = |arr| match PartInfo::try_from(arr).ok() {
527            // We'll convert empty partition types into Nones here
528            Some(p) if p.is_zeroed() => None,
529            x => x,
530        };
531        MbrPartTable {
532            entries: [
533                from_slice(array_ref![buf, 0, 16]),
534                from_slice(array_ref![buf, 16, 16]),
535                from_slice(array_ref![buf, 32, 16]),
536                from_slice(array_ref![buf, 48, 16]),
537            ],
538        }
539    }
540}
541
542impl TryFrom<MbrPartTable> for [u8; 64] {
543    type Error = PartInfoErr;
544    fn try_from(tbl: MbrPartTable) -> Result<Self, Self::Error> {
545        let mut buf = [0; 64];
546        buf[0..16].copy_from_slice(&match tbl.entries[0] {
547            Some(e) => <[u8; 16]>::try_from(e)?,
548            _ => Default::default(),
549        });
550        buf[16..32].copy_from_slice(&match tbl.entries[1] {
551            Some(e) => <[u8; 16]>::try_from(e)?,
552            _ => Default::default(),
553        });
554        buf[32..48].copy_from_slice(&match tbl.entries[2] {
555            Some(e) => <[u8; 16]>::try_from(e)?,
556            _ => Default::default(),
557        });
558        buf[48..64].copy_from_slice(&match tbl.entries[3] {
559            Some(e) => <[u8; 16]>::try_from(e)?,
560            _ => Default::default(),
561        });
562        Ok(buf)
563    }
564}
565
566#[cfg(feature = "std")]
567impl MbrPartTable {
568    pub fn try_from_reader<B>(mut reader: B) -> Result<Self, MbrError>
569    where
570        B: Read,
571    {
572        let mut buf = [0; 64];
573        match reader.read(&mut buf) {
574            Ok(64) => Ok(Self::from(&buf)),
575            Ok(n_bytes_read) => Err(MbrError::InputTooSmall {
576                input_size: n_bytes_read,
577                error_location: "partition table",
578            }),
579            Err(e) => Err(MbrError::IoError(e.to_string())),
580        }
581    }
582}
583
584impl MbrPartTable {
585    pub fn from_bytes<'a, B>(b: B) -> Self
586    where
587        B: Into<&'a [u8; 64]>,
588    {
589        Self::from(b.into())
590    }
591}
592
593#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
594pub struct Mbr {
595    pub bootloader: [u8; 440],
596    pub drive_signature: [u8; 4],
597    pub partition_table: MbrPartTable,
598    pub bootsector_signature: [u8; 2],
599}
600
601impl Default for Mbr {
602    /// Returns an empty MBR
603    fn default() -> Self {
604        Mbr {
605            bootloader: [0; 440],
606            drive_signature: [0; 4],
607            partition_table: MbrPartTable { entries: [None; 4] },
608            bootsector_signature: [0x55, 0xAA],
609        }
610    }
611}
612
613impl Mbr {
614    pub fn try_from_bytes<'a, B>(b: B) -> Result<Self, MbrError>
615    where
616        B: Into<&'a [u8; 512]>,
617    {
618        let buf = b.into();
619        let bootloader = *array_ref![buf, 0, 440];
620        let drive_signature = *array_ref![buf, 440, 4];
621        let zero_buf = *array_ref![buf, 444, 2];
622
623        match &zero_buf {
624            [0, 0] => Ok(()),
625            // Copy protected according to spec on wikipedia - we'll just ignore it
626            [0x5A, 0x5A] => Ok(()),
627            _ => Err(MbrError::NullSectorIsNotNull { val: zero_buf }),
628        }?;
629
630        let partition_table = MbrPartTable::from_bytes(array_ref![buf, 446, 64]);
631        let bootsector_signature = *array_ref![buf, 510, 2];
632
633        Ok(Self {
634            bootloader,
635            drive_signature,
636            partition_table,
637            bootsector_signature,
638        })
639    }
640}
641
642#[cfg(feature = "std")]
643impl Mbr {
644    pub fn try_from_reader<B>(mut reader: B) -> Result<Self, MbrError>
645    where
646        B: Read,
647    {
648        let mut bootloader = [0; 440];
649        match reader.read(&mut bootloader) {
650            Ok(440) => Ok(()),
651            Ok(n_bytes_read) => Err(MbrError::InputTooSmall {
652                input_size: n_bytes_read,
653                error_location: "bootloader",
654            }),
655            Err(e) => Err(MbrError::IoError(e.to_string())),
656        }?;
657
658        let mut drive_signature = [0; 4];
659        match reader.read(&mut drive_signature) {
660            Ok(4) => Ok(()),
661            Ok(n_bytes_read) => Err(MbrError::InputTooSmall {
662                input_size: n_bytes_read,
663                error_location: "drive signature",
664            }),
665            Err(e) => Err(MbrError::IoError(e.to_string())),
666        }?;
667
668        let mut zero_buf = [0; 2];
669        match reader.read(&mut zero_buf) {
670            Ok(2) if zero_buf == [0, 0] => Ok(()),
671            // Copy protected according to spec on wikipedia - we'll just ignore it
672            Ok(2) if zero_buf == [0x5A, 0x5A] => Ok(()),
673            Ok(2) => Err(MbrError::NullSectorIsNotNull { val: zero_buf }),
674            Ok(n_bytes_read) => Err(MbrError::InputTooSmall {
675                input_size: n_bytes_read,
676                error_location: "null sector",
677            }),
678            Err(e) => Err(MbrError::IoError(e.to_string())),
679        }?;
680
681        let partition_table = MbrPartTable::try_from_reader(&mut reader)?;
682        let mut bootsector_signature = [0; 2];
683
684        match reader.read(&mut bootsector_signature) {
685            Ok(2) => Ok(()),
686            Ok(n_bytes_read) => Err(MbrError::InputTooSmall {
687                input_size: n_bytes_read,
688                error_location: "bootsector signature",
689            }),
690            Err(e) => Err(MbrError::IoError(e.to_string())),
691        }?;
692        Ok(Self {
693            bootloader,
694            drive_signature,
695            partition_table,
696            bootsector_signature,
697        })
698    }
699}
700
701impl TryFrom<&Mbr> for [u8; 512] {
702    type Error = PartInfoErr;
703    fn try_from(mbr: &Mbr) -> Result<Self, Self::Error> {
704        let mut buf = [0; 512];
705        buf[0..440].copy_from_slice(&mbr.bootloader);
706        buf[440..444].copy_from_slice(&mbr.drive_signature);
707        // if we ever care about the copy protection it should go here
708        // buf[444..446].copy_from_slice(&mbr.drive_signature);
709        buf[446..510].copy_from_slice(&<[u8; 64]>::try_from(mbr.partition_table)?);
710        buf[510..512].copy_from_slice(&mbr.bootsector_signature);
711        Ok(buf)
712    }
713}
714
715#[derive(Debug, PartialEq, Eq, Hash, Error)]
716pub enum MbrError {
717    #[error("can't create partition type from value {part_id:#X}. If you think this value should be valid please open an issue.")]
718    UnknownPartitionType { part_id: u8 },
719    #[error("an MBR is 512 bytes long, however the input size was only {input_size} bytes long. Couldn't read {error_location}")]
720    InputTooSmall {
721        input_size: usize,
722        error_location: &'static str,
723    },
724    #[error("the null sector in the provided MBR should be identically 0 or contain 0x5A 0x5A, but it contains {:#X} {:#X}", val[0], val[1])]
725    NullSectorIsNotNull { val: [u8; 2] },
726    #[cfg(feature = "std")]
727    #[error("IO error: {0}")]
728    IoError(String),
729    #[error("a valid MBR should contain a bootsector signature of 0x55, 0xAA at addresses 0x01FE, 0x01FF, but it contains {:#X} {:#X}", sig[0], sig[1])]
730    BootloaderSignatureNotSet { sig: [u8; 2] },
731    #[error("too many missing fields in input, can't deduce remaining fields")]
732    IncompleteInput,
733    #[error("a MBR-based CHS partition can't support CHS address ({cylinder}, {head}, {sector}). Consider switching to LBA or making the partition smaller.")]
734    InvalidAddressChs {
735        cylinder: u32,
736        head: u32,
737        sector: u32,
738    },
739    #[error("the given partition information is inconsistent: there can be no partition of size {size}s starting at {start}s and ending at {end}s")]
740    InconsistentPartInfo { start: u32, end: u32, size: u32 },
741    #[error("you attempted to create a partition with a negative size or address")]
742    BrokenPartitionBounds,
743}
744
745#[cfg(feature = "std")]
746#[cfg(test)]
747mod tests {
748    use super::*;
749    use std::{fs::File, io::Write};
750
751    #[test]
752    fn read_mbr() {
753        let raspios_img = File::open("./raspios.img").unwrap();
754        let mbr = Mbr::try_from_reader(raspios_img).unwrap();
755        dbg!(mbr);
756        let partuuid = format!("{:x}", u32::from_le_bytes(mbr.drive_signature));
757        println!("PARTUUID: {}", partuuid);
758    }
759
760    #[test]
761    fn read_and_write() {
762        let raspios_img = File::open("./raspios.img").unwrap();
763        let mut mbr = Mbr::try_from_reader(raspios_img).unwrap();
764        mbr.drive_signature = 0x090b3d33_u32.to_le_bytes();
765        mbr.partition_table.entries[2] = Some(
766            PartInfo::try_from_lba(
767                true,
768                mbr.partition_table.entries[1].unwrap().end_sector_lba(),
769                1024,
770                PartType::btrfs(),
771            )
772            .unwrap(),
773        );
774        let mut out_file = File::create("./out.img").unwrap();
775        let buf = <[u8; 512]>::try_from(&mbr).unwrap();
776        out_file.write_all(&buf).unwrap();
777    }
778}
779
780#[cfg(test)]
781mod tests_no_std {
782    use super::*;
783
784    #[test]
785    fn chs_entry_inv() {
786        let e1 = ChsEntry { raw: [6, 4, 33] };
787        let (c, h, s) = e1.chs();
788        let e2 = ChsEntry::from_chs(c, h, s);
789        assert_eq!(e1, e2);
790
791        let e1 = ChsEntry { raw: [127, 42, 33] };
792        let (c, h, s) = e1.chs();
793        let e2 = ChsEntry::from_chs(c, h, s);
794        assert_eq!(e1, e2);
795
796        let e1 = ChsEntry {
797            raw: [42, 137, 0b11001010],
798        };
799        let (c, h, s) = e1.chs();
800        let e2 = ChsEntry::from_chs(c, h, s);
801        assert_eq!(e1, e2);
802    }
803
804    #[test]
805    fn lba_to_chs() {
806        assert_eq!(
807            ChsEntry::try_from_lba(8192),
808            Ok(ChsEntry::from_chs(u10::new(64), 0, u6::new(1)))
809        );
810        assert_eq!(
811            ChsEntry::try_from_lba(532479),
812            Err(MbrError::InvalidAddressChs {
813                cylinder: 4159,
814                head: 3,
815                sector: 32
816            })
817        );
818        assert_eq!(
819            ChsEntry::try_from_lba(532480),
820            Err(MbrError::InvalidAddressChs {
821                cylinder: 4160,
822                head: 0,
823                sector: 1
824            })
825        );
826        assert_eq!(
827            ChsEntry::try_from_lba(3842047),
828            Err(MbrError::InvalidAddressChs {
829                cylinder: 30015,
830                head: 3,
831                sector: 32
832            })
833        );
834    }
835
836    #[test]
837    fn chs_from_lba() {
838        assert_eq!(
839            PartInfo::try_from_lba(
840                false,
841                8192,
842                524288,
843                PartType::Fat32 {
844                    visible: true,
845                    scheme: AddrScheme::Lba,
846                }
847            )
848            .unwrap(),
849            PartInfo {
850                bootable: false,
851                first_sector_chs: ChsEntry { raw: [0, 1, 64,] },
852                part_type: PartType::Fat32 {
853                    visible: true,
854                    scheme: AddrScheme::Lba,
855                },
856                last_sector_chs: ChsEntry { raw: [3, 32, 63,] },
857                start_sector_lba: 8192,
858                sector_count_lba: 524288,
859            }
860        );
861    }
862}