nogpt/
lib.rs

1#![cfg_attr(not(any(feature = "std", test, doc)), no_std)]
2#![deny(unsafe_op_in_unsafe_fn)]
3
4#[cfg(any(feature = "alloc", doc))]
5extern crate alloc;
6
7#[cfg(any(feature = "alloc", doc))]
8use alloc::vec::Vec;
9
10extern crate core;
11
12use block_device::BlockDevice;
13
14pub use crate::error::{GPTError, GPTParseError, GptRepair, Result};
15use crate::header::{GPTHeader, GptHeaderType};
16
17pub const BLOCK_SIZE: u32 = 512;
18//pub const BLOCK_SIZE: u32 = 4096;
19pub const DEFAULT_PARTTABLE_SIZE: u32 = 16384;
20pub const DEFAULT_PARTTABLE_BLOCKS: u32 = DEFAULT_PARTTABLE_SIZE / BLOCK_SIZE;
21
22macro_rules! read_le_bytes {
23    ($in:tt, $size:tt, $pos:expr) => {
24        // TODO: remove unwrap
25        $size::from_le_bytes(($in[$pos]).try_into().unwrap())
26    };
27
28    ($in:tt, $pos:expr) => {
29        ($in[$pos]).try_into().unwrap()
30    };
31}
32pub(crate) use read_le_bytes; // trick to export to crate
33
34mod guid;
35
36pub mod error;
37pub mod header;
38pub mod mbr;
39pub mod part;
40#[cfg(any(feature = "std", doc))]
41pub mod std;
42
43use crate::mbr::{MBRPartitionRecord, MasterBootRecord};
44use crate::part::{GPTPartHeader, GPTTypeGuid};
45
46#[doc(inline)]
47pub use guid::GUID;
48
49pub struct GPT<T> {
50    block: T,
51    header: GPTHeader,
52}
53
54impl<T> GPT<T>
55where
56    T: BlockDevice,
57    GPTError: From<T::Error>,
58{
59    // This checks that the other header is okay, so cannot unwrap it in if header
60    #[allow(clippy::unnecessary_unwrap)]
61    pub fn open(block: T) -> Result<Self, GPTParseError<T>> {
62        #[cfg(not(feature = "alloc"))]
63        let mut buf = [0u8; DEFAULT_PARTTABLE_SIZE as usize];
64
65        #[cfg(feature = "alloc")]
66        let mut buf = {
67            let mut buf = Vec::with_capacity(DEFAULT_PARTTABLE_SIZE as usize);
68            buf.try_reserve_exact(DEFAULT_PARTTABLE_SIZE as usize)?; // Catch allocation errors
69            buf.resize(DEFAULT_PARTTABLE_SIZE as usize, 0);
70            buf
71        };
72
73        // TODO: read address from MBR
74        block.read(&mut buf, 0, 1)?;
75        let mbr = unsafe { MasterBootRecord::from_buf(&buf) }?;
76
77        mbr.verify(None)?;
78        if mbr.partition[0].os_indicator != MBRPartitionRecord::GPT_PROTECTIVE_OS_TYPE {
79            // This is not a protective MBR, but a possible a MBR with one or more GPT partitions.
80            // Bailing out
81            return Err(GPTError::NoGPT.into());
82        }
83
84        let header_lba = mbr.partition[0].starting_lba() as usize;
85
86        // mbr has to be clean up, before buf is used again, as mbr points into buf.
87        drop(mbr);
88        block.read(&mut buf, header_lba, 1)?;
89
90        let m_header = GPTHeader::parse(&buf)?;
91
92        let p_table_size = m_header.size_of_p_entry * m_header.num_parts;
93        #[cfg(not(feature = "alloc"))]
94        if p_table_size > DEFAULT_PARTTABLE_SIZE {
95            return Err(GPTError::NoAllocator.into());
96        }
97
98        #[cfg(feature = "alloc")]
99        if p_table_size > buf.len() as u32 {
100            buf.try_reserve_exact(p_table_size as usize - buf.len())?; // Catch allocation errors
101            buf.resize(p_table_size as usize, 0);
102        }
103
104        let blocks = if p_table_size > DEFAULT_PARTTABLE_SIZE as u32 {
105            p_table_size / BLOCK_SIZE + 1 // TODO: round up properly
106        } else {
107            DEFAULT_PARTTABLE_BLOCKS
108        };
109
110        block.read(&mut buf, m_header.p_entry_lba as usize, blocks as usize)?;
111
112        let m_header_valid = m_header.validate(header_lba as u64, &buf);
113
114        block.read(&mut buf, m_header.other_lba as usize, 1)?;
115        let b_header = GPTHeader::parse(&buf)?;
116
117        block.read(&mut buf, b_header.p_entry_lba as usize, blocks as usize)?;
118
119        let b_header_valid = b_header.validate(m_header.other_lba as u64, &buf);
120
121        if m_header_valid.is_err() || b_header_valid.is_err() {
122            return if m_header_valid.is_ok() {
123                Err(GPTParseError::BrokenHeader(
124                    Self {
125                        block,
126                        header: m_header,
127                    },
128                    GptHeaderType::Backup,
129                    b_header_valid.unwrap_err(),
130                ))
131            } else if b_header_valid.is_ok() {
132                Err(GPTParseError::BrokenHeader(
133                    Self {
134                        block,
135                        header: b_header,
136                    },
137                    GptHeaderType::Main,
138                    m_header_valid.unwrap_err(),
139                ))
140            } else {
141                Err(GPTError::NoGPT.into())
142            };
143        }
144
145        Ok(Self {
146            block,
147            header: m_header,
148        })
149    }
150
151    pub fn get_partition_buf<PT, PA>(&self, idx: u32, buf: &[u8]) -> Result<GPTPartHeader<PT, PA>>
152    where
153        PT: GPTTypeGuid,
154        GPTError: From<<PT as TryFrom<[u8; 16]>>::Error>,
155        GPTError: From<<PT as TryInto<[u8; 16]>>::Error>,
156        PA: TryFrom<u64>,
157        GPTError: From<<PA as TryFrom<u64>>::Error>,
158    {
159        if idx >= self.header.num_parts {
160            return Err(GPTError::InvalidData);
161        }
162
163        // TODO: check size
164        let offset: u64 = self.header.size_of_p_entry as u64 * idx as u64;
165
166        GPTPartHeader::parse(&buf[offset as usize..])
167    }
168
169    pub fn get_partition<PT, PA>(&self, idx: u32) -> Result<GPTPartHeader<PT, PA>>
170    where
171        PT: GPTTypeGuid,
172        GPTError: From<<PT as TryFrom<[u8; 16]>>::Error>,
173        GPTError: From<<PT as TryInto<[u8; 16]>>::Error>,
174        PA: TryFrom<u64>,
175        GPTError: From<<PA as TryFrom<u64>>::Error>,
176    {
177        if idx >= self.header.num_parts {
178            return Err(GPTError::InvalidData);
179        }
180
181        let p_table_size = self.header.size_of_p_entry as usize * self.header.num_parts as usize;
182
183        let blocks = if p_table_size > DEFAULT_PARTTABLE_SIZE as usize {
184            (p_table_size / BLOCK_SIZE as usize + 1) as usize // TODO: round up properly
185        } else {
186            DEFAULT_PARTTABLE_BLOCKS as usize
187        };
188
189        let buf = read_buf(
190            self.header.p_entry_lba as usize,
191            p_table_size,
192            &self.block,
193            blocks,
194        )?;
195
196        self.get_partition_buf(idx, &buf)
197    }
198
199    pub fn get_first_partition_of_type_buf<PT, PA>(
200        &self,
201        guid: PT,
202        buf: &[u8],
203    ) -> Result<GPTPartHeader<PT, PA>>
204    where
205        PT: GPTTypeGuid,
206        GPTError: From<<PT as TryFrom<[u8; 16]>>::Error>,
207        GPTError: From<<PT as TryInto<[u8; 16]>>::Error>,
208        PA: TryFrom<u64>,
209        GPTError: From<<PA as TryFrom<u64>>::Error>,
210        PT: Eq,
211    {
212        let mut idx = 0;
213
214        loop {
215            let part = self.get_partition_buf(idx, buf)?;
216            if part.type_guid == guid {
217                return Ok(part);
218            }
219
220            idx += 1;
221        }
222    }
223
224    pub fn get_first_partition_of_type<PT, PA>(&self, guid: PT) -> Result<GPTPartHeader<PT, PA>>
225    where
226        PT: GPTTypeGuid,
227        GPTError: From<<PT as TryFrom<[u8; 16]>>::Error>,
228        GPTError: From<<PT as TryInto<[u8; 16]>>::Error>,
229        PA: TryFrom<u64>,
230        GPTError: From<<PA as TryFrom<u64>>::Error>,
231        PT: Eq,
232    {
233        let p_table_size = self.header.size_of_p_entry as usize * self.header.num_parts as usize;
234
235        let blocks = if p_table_size > DEFAULT_PARTTABLE_SIZE as usize {
236            (p_table_size / BLOCK_SIZE as usize + 1) as usize // TODO: round up properly
237        } else {
238            DEFAULT_PARTTABLE_BLOCKS as usize
239        };
240
241        let buf = read_buf(
242            self.header.p_entry_lba as usize,
243            p_table_size,
244            &self.block,
245            blocks,
246        )?;
247
248        self.get_first_partition_of_type_buf(guid, &buf)
249    }
250}
251
252#[cfg(not(feature = "alloc"))]
253fn read_buf<T: BlockDevice>(
254    start_lba: usize,
255    size: usize,
256    block: &T,
257    blocks: usize,
258) -> Result<[u8; DEFAULT_PARTTABLE_SIZE as usize]>
259where
260    GPTError: From<T::Error>,
261{
262    let mut buf = [0u8; DEFAULT_PARTTABLE_SIZE as usize];
263    if size > DEFAULT_PARTTABLE_SIZE as usize {
264        return Err(GPTError::NoAllocator);
265    }
266
267    block.read(&mut buf, start_lba, blocks)?;
268
269    Ok(buf)
270}
271
272#[cfg(feature = "alloc")]
273fn read_buf<T: BlockDevice>(
274    start_lba: usize,
275    size: usize,
276    block: &T,
277    blocks: usize,
278) -> Result<alloc::vec::Vec<u8>>
279where
280    GPTError: From<T::Error>,
281{
282    let mut buf = {
283        let mut buf = Vec::with_capacity(DEFAULT_PARTTABLE_SIZE as usize);
284        buf.try_reserve_exact(DEFAULT_PARTTABLE_SIZE as usize)?; // Catch allocation errors
285        buf.resize(DEFAULT_PARTTABLE_SIZE as usize, 0);
286        buf
287    };
288
289    if size > buf.len() {
290        buf.try_reserve_exact(size - buf.len())?; // Catch allocation errors
291        buf.resize(size, 0);
292    }
293
294    block.read(&mut buf, start_lba, blocks)?;
295
296    Ok(buf)
297}