1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
//! Read basic MBR and GPT partition tables from a reader.
//!
//! # Examples
//!
//! Load MBR or GPT partitions from a `reader`:
//!
//! ```rust
//! use std::io;
//! use bootsector::{list_partitions, open_partition, Options, Attributes};
//!
//! # fn go<R>(mut reader: R) -> io::Result<()>
//! # where R: io::Read + io::Seek {
//! // let reader = ...;
//! let partitions = list_partitions(&mut reader, &Options::default())?;
//! let part = &partitions[0];
//!
//! // See what type of partition this is
//! match part.attributes {
//!     Attributes::GPT {
//!         type_uuid,
//!         ..
//!     } => println!("gpt: {:?}", type_uuid),
//!     Attributes::MBR {
//!         type_code,
//!         ..
//!     } => println!("mbr: {:x}", type_code),
//! }
//!
//! let part_reader = open_partition(reader, part);
//! // part_reader.read_exact(...
//! # Ok(())
//! # }
//! ```

extern crate byteorder;
extern crate crc;

use std::io;

mod gpt;
mod mbr;
mod rangereader;

use rangereader::RangeReader;

/// Table-specific information about a partition.
#[cfg_attr(rustfmt, rustfmt_skip)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Attributes {
    MBR {
        bootable: bool,
        type_code: u8,
    },
    GPT {
        type_uuid: [u8; 16],
        partition_uuid: [u8; 16],
        attributes: [u8; 8],
        name: String,
    },
}

/// An entry in the partition table.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Partition {
    /// The number of this partition, 0-indexed.
    pub id: usize,

    /// The first byte of the reader that this partition represents.
    pub first_byte: u64,

    /// The length of this partition, in bytes.
    pub len: u64,

    /// Table-specific attributes about this partition.
    pub attributes: Attributes,
}

/// What type of MBR partition tables should we attempt to read?
pub enum ReadMBR {
    /// A compliant, modern MBR: CHS addressing is correctly set to the blind value.
    Modern,
    /// Require there to be a GPT partition present. The protective MBR is allowed, but ignored.
    Never,
}

/// What type of GPT partition tables should we attempt to read?
pub enum ReadGPT {
    /// A valid GPT partition table as of revision 1 (2010-2017 and counting)
    RevisionOne,

    /// Require that there be an MBR partition present. The protective MBR will be read literally.
    Never,
}

/// Settings for handling sector size
pub enum SectorSize {
    /// Attempt to identify a valid GPT partition table at various locations, and use this
    /// information to derive the sector size. For MBR, it's very likely that 512 is a safe
    /// assumption.
    GuessOrAssume,

    /// Use a specific known sector size.
    Known(u16),
}

/// Configuration for listing partitions.
pub struct Options {
    /// What type of MBR partitions should we read?
    pub mbr: ReadMBR,

    /// What type of GPT partitions should we read?
    pub gpt: ReadGPT,

    /// How should we handle sector sizes?
    pub sector_size: SectorSize,
}

impl Default for Options {
    /// The default options are to read any type of modern partition table,
    /// having guessed the sector size.
    fn default() -> Self {
        Options {
            mbr: ReadMBR::Modern,
            gpt: ReadGPT::RevisionOne,
            sector_size: SectorSize::GuessOrAssume,
        }
    }
}

/// Read the list of partitions.
///
/// # Returns
///
/// * A possibly empty list of partitions.
/// * `ErrorKind::NotFound` if the boot magic is not found,
///        or you asked for partition types that are not there
/// * `ErrorKind::InvalidData` if anything is not as we expect,
///       including it looking like there should be GPT but its magic is missing.
/// * Other IO errors directly from the underlying reader, including `UnexpectedEOF`.
pub fn list_partitions<R>(mut reader: R, options: &Options) -> io::Result<Vec<Partition>>
where
    R: io::Read + io::Seek,
{
    let header_table = {
        reader.seek(io::SeekFrom::Start(0))?;

        let mut disc_header = [0u8; 512];
        reader.read_exact(&mut disc_header)?;

        if 0x55 != disc_header[510] || 0xAA != disc_header[511] {
            return Err(io::ErrorKind::NotFound.into());
        }

        mbr::parse_partition_table(&disc_header)?
    };

    match header_table.len() {
        1 if gpt::is_protective(&header_table[0]) => {}
        _ => {
            return match options.mbr {
                ReadMBR::Modern => Ok(header_table),
                ReadMBR::Never => Err(io::ErrorKind::NotFound.into()),
            }
        }

    }

    match options.gpt {
        ReadGPT::Never => Ok(header_table),
        ReadGPT::RevisionOne => {
            let sector_size = match options.sector_size {
                SectorSize::Known(size) => size as usize,
                SectorSize::GuessOrAssume => header_table[0].first_byte as usize,
            };

            gpt::read(reader, sector_size)
        }
    }
}

/// Open the contents of a partition for reading.
pub fn open_partition<R>(inner: R, part: &Partition) -> io::Result<RangeReader<R>>
where
    R: io::Read + io::Seek,
{
    RangeReader::new(inner, part.first_byte, part.len)
}