gpt_disk_types/
partition_array.rs

1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use crate::{BlockSize, Crc32, GptPartitionEntrySize, Lba, U32Le};
10use core::fmt::{self, Display, Formatter};
11
12#[cfg(feature = "bytemuck")]
13use {
14    crate::GptPartitionEntry,
15    bytemuck::{from_bytes, from_bytes_mut},
16    core::mem,
17    core::ops::Range,
18};
19
20/// Disk layout of a GPT partition entry array.
21#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
22pub struct GptPartitionEntryArrayLayout {
23    /// First block of the array.
24    pub start_lba: Lba,
25
26    /// Size in bytes of each entry.
27    pub entry_size: GptPartitionEntrySize,
28
29    /// Number of entries in the array.
30    pub num_entries: u32,
31}
32
33impl GptPartitionEntryArrayLayout {
34    /// Get the number of blocks needed for this layout. Returns `None`
35    /// if overflow occurs.
36    #[must_use]
37    pub fn num_blocks(&self, block_size: BlockSize) -> Option<u64> {
38        let block_size = block_size.to_u64();
39        let num_bytes_exact = self.num_bytes_exact()?;
40
41        let mut num_blocks = num_bytes_exact / block_size;
42        if num_bytes_exact % block_size != 0 {
43            num_blocks = num_blocks.checked_add(1)?;
44        }
45
46        Some(num_blocks)
47    }
48
49    /// Get the number of blocks needed for this layout. Returns `None`
50    /// if overflow occurs.
51    #[must_use]
52    pub fn num_blocks_as_usize(&self, block_size: BlockSize) -> Option<usize> {
53        self.num_blocks(block_size)?.try_into().ok()
54    }
55
56    /// Get the number of bytes needed for the entries in this layout,
57    /// ignoring any padding needed at the end to match the block
58    /// size. This corresponds to the number of bytes that are covered
59    /// by the [`partition_entry_array_crc32`].
60    ///
61    /// Returns `None` if overflow occurs.
62    ///
63    /// [`partition_entry_array_crc32`]: crate::GptHeader::partition_entry_array_crc32
64    #[must_use]
65    pub fn num_bytes_exact(&self) -> Option<u64> {
66        let entry_size = self.entry_size.to_u64();
67        let num_entries = u64::from(self.num_entries);
68        entry_size.checked_mul(num_entries)
69    }
70
71    /// Get the number of bytes needed for the entries in this layout,
72    /// ignoring any padding needed at the end to match the block
73    /// size. This corresponds to the number of bytes that are covered
74    /// by the [`partition_entry_array_crc32`].
75    ///
76    /// Returns `None` if overflow occurs.
77    ///
78    /// [`partition_entry_array_crc32`]: crate::GptHeader::partition_entry_array_crc32
79    #[must_use]
80    pub fn num_bytes_exact_as_usize(&self) -> Option<usize> {
81        self.num_bytes_exact()?.try_into().ok()
82    }
83
84    /// Get the number of bytes needed for this layout, rounded up to
85    /// the nearest block. This is equivalent to [`num_blocks`] *
86    /// `block_size`.
87    ///
88    /// Returns `None` if overflow occurs.
89    ///
90    /// [`num_blocks`]: Self::num_blocks
91    #[must_use]
92    pub fn num_bytes_rounded_to_block(
93        &self,
94        block_size: BlockSize,
95    ) -> Option<u64> {
96        let num_blocks = self.num_blocks(block_size)?;
97        num_blocks.checked_mul(block_size.to_u64())
98    }
99
100    /// Get the number of bytes needed for this layout, rounded up to
101    /// the nearest block. This is equivalent to [`num_blocks`] *
102    /// `block_size`.
103    ///
104    /// Returns `None` if overflow occurs.
105    ///
106    /// [`num_blocks`]: Self::num_blocks
107    #[must_use]
108    pub fn num_bytes_rounded_to_block_as_usize(
109        &self,
110        block_size: BlockSize,
111    ) -> Option<usize> {
112        self.num_bytes_rounded_to_block(block_size)?.try_into().ok()
113    }
114}
115
116impl Display for GptPartitionEntryArrayLayout {
117    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
118        write!(
119            f,
120            "start_lba={}/entry_size={}/num_entries={}",
121            self.start_lba, self.entry_size, self.num_entries
122        )
123    }
124}
125
126/// Errors used by [`GptPartitionEntryArray`].
127#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
128pub enum GptPartitionEntryArrayError {
129    /// The storage buffer is not large enough. It must be at least
130    /// [`layout.num_bytes_rounded_to_block`] in size.
131    ///
132    /// [`layout.num_bytes_rounded_to_block`]: GptPartitionEntryArrayLayout::num_bytes_rounded_to_block
133    BufferTooSmall,
134
135    /// Numeric overflow occurred.
136    Overflow,
137}
138
139impl Display for GptPartitionEntryArrayError {
140    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
141        match self {
142            Self::BufferTooSmall => f.write_str("storage buffer is too small"),
143            Self::Overflow => f.write_str("numeric overflow occurred"),
144        }
145    }
146}
147
148impl core::error::Error for GptPartitionEntryArrayError {}
149
150/// Storage for a GPT partition entry array.
151#[allow(missing_debug_implementations)]
152pub struct GptPartitionEntryArray<'a> {
153    layout: GptPartitionEntryArrayLayout,
154    num_bytes_exact: usize,
155    storage: &'a mut [u8],
156}
157
158impl<'a> GptPartitionEntryArray<'a> {
159    /// Create a new `GptPartitionEntryArray` with the given
160    /// `layout`. The length of `storage` must be at least
161    /// [`layout.num_bytes_rounded_to_block`].
162    ///
163    /// [`layout.num_bytes_rounded_to_block`]: GptPartitionEntryArrayLayout::num_bytes_rounded_to_block
164    pub fn new(
165        layout: GptPartitionEntryArrayLayout,
166        block_size: BlockSize,
167        storage: &'a mut [u8],
168    ) -> Result<Self, GptPartitionEntryArrayError> {
169        let num_bytes_required = layout
170            .num_bytes_rounded_to_block_as_usize(block_size)
171            .ok_or(GptPartitionEntryArrayError::Overflow)?;
172
173        let num_bytes_exact = layout
174            .num_bytes_exact_as_usize()
175            .ok_or(GptPartitionEntryArrayError::Overflow)?;
176
177        let storage = storage
178            .get_mut(..num_bytes_required)
179            .ok_or(GptPartitionEntryArrayError::BufferTooSmall)?;
180
181        Ok(Self {
182            layout,
183            num_bytes_exact,
184            storage,
185        })
186    }
187
188    /// Get a reference to the storage buffer.
189    #[must_use]
190    pub fn storage(&self) -> &[u8] {
191        self.storage
192    }
193
194    /// Get a mutable reference to the storage buffer.
195    #[must_use]
196    pub fn storage_mut(&mut self) -> &mut [u8] {
197        self.storage
198    }
199
200    /// Get the partition entry array layout.
201    #[must_use]
202    pub fn layout(&self) -> &GptPartitionEntryArrayLayout {
203        &self.layout
204    }
205
206    /// Change the partition entry array's start [`Lba`].
207    pub fn set_start_lba(&mut self, start_lba: Lba) {
208        self.layout.start_lba = start_lba;
209    }
210
211    #[cfg(feature = "bytemuck")]
212    fn get_entry_byte_range(&self, index: u32) -> Option<Range<usize>> {
213        if index >= self.layout.num_entries {
214            return None;
215        }
216
217        let start = usize::try_from(
218            u64::from(index) * u64::from(self.layout.entry_size.to_u32()),
219        )
220        .ok()?;
221        Some(start..start + mem::size_of::<GptPartitionEntry>())
222    }
223
224    /// Get a partition entry reference. The `index` is zero-based.
225    #[cfg(feature = "bytemuck")]
226    #[must_use]
227    pub fn get_partition_entry(
228        &self,
229        index: u32,
230    ) -> Option<&GptPartitionEntry> {
231        Some(from_bytes(&self.storage[self.get_entry_byte_range(index)?]))
232    }
233
234    /// Get a mutable partition entry reference. The `index` is zero-based.
235    #[cfg(feature = "bytemuck")]
236    #[must_use]
237    pub fn get_partition_entry_mut(
238        &mut self,
239        index: u32,
240    ) -> Option<&mut GptPartitionEntry> {
241        let range = self.get_entry_byte_range(index)?;
242        Some(from_bytes_mut(&mut self.storage[range]))
243    }
244
245    /// Calculate the CRC32 checksum for the partition entry array. The
246    /// return value can then be set in the
247    /// [`GptHeader::partition_entry_array_crc32`] field.
248    ///
249    /// [`GptHeader::partition_entry_array_crc32`]: crate::GptHeader::partition_entry_array_crc32
250    #[must_use]
251    pub fn calculate_crc32(&self) -> Crc32 {
252        let crc = crc::Crc::<u32>::new(&Crc32::ALGORITHM);
253        let mut digest = crc.digest();
254        digest.update(&self.storage[..self.num_bytes_exact]);
255        Crc32(U32Le(digest.finalize().to_le_bytes()))
256    }
257}