• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / gpt-disk-rs / 14182859342

31 Mar 2025 10:40PM CUT coverage: 94.803%. Remained the same
14182859342

Pull #277

github

web-flow
Merge 9a5384971 into 32b4a7e87
Pull Request #277: uguid: Unconditionally use the Error trait

1441 of 1520 relevant lines covered (94.8%)

13.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.99
/gpt_disk_io/src/disk.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

9
use crate::BlockIo;
10
use bytemuck::{bytes_of, from_bytes};
11
use core::fmt::{self, Debug, Display, Formatter};
12
use core::mem;
13
use gpt_disk_types::{
14
    GptHeader, GptPartitionEntry, GptPartitionEntryArray,
15
    GptPartitionEntryArrayError, GptPartitionEntryArrayLayout, Lba,
16
    MasterBootRecord,
17
};
18

19
/// Iterator over entries in a partition entry array.
20
struct GptPartitionEntryIter<'disk, 'buf, Io: BlockIo> {
21
    disk: &'disk mut Disk<Io>,
22
    block_buf: &'buf mut [u8],
23
    layout: GptPartitionEntryArrayLayout,
24
    next_index: u32,
25
    current_lba: Lba,
26
    byte_offset_within_lba: usize,
27
    entry_size: usize,
28
}
29

30
impl<'disk, 'buf, Io: BlockIo> GptPartitionEntryIter<'disk, 'buf, Io> {
31
    fn new(
6✔
32
        disk: &'disk mut Disk<Io>,
6✔
33
        layout: GptPartitionEntryArrayLayout,
6✔
34
        block_buf: &'buf mut [u8],
6✔
35
    ) -> Result<Self, DiskError<Io::Error>> {
6✔
36
        let mut iter = Self {
6✔
37
            disk,
6✔
38
            block_buf,
6✔
39
            next_index: 0,
6✔
40
            current_lba: layout.start_lba,
6✔
41
            byte_offset_within_lba: 0,
6✔
42
            layout,
6✔
43
            entry_size: layout
6✔
44
                .entry_size
6✔
45
                .to_usize()
6✔
46
                .ok_or(DiskError::Overflow)?,
6✔
47
        };
48
        iter.set_current_lba(iter.current_lba)?;
6✔
49
        Ok(iter)
6✔
50
    }
6✔
51

52
    fn set_current_lba(
6✔
53
        &mut self,
6✔
54
        lba: Lba,
6✔
55
    ) -> Result<(), DiskError<Io::Error>> {
6✔
56
        self.current_lba = lba;
6✔
57
        self.byte_offset_within_lba = 0;
6✔
58
        Ok(self.disk.io.read_blocks(self.current_lba, self.block_buf)?)
6✔
59
    }
6✔
60

61
    fn read_current_entry(&mut self) -> Option<<Self as Iterator>::Item> {
12✔
62
        let entry_bytes = self.block_buf.get(
12✔
63
            self.byte_offset_within_lba
12✔
64
                ..self.byte_offset_within_lba + self.entry_size,
12✔
65
        )?;
12✔
66

67
        self.byte_offset_within_lba += self.entry_size;
12✔
68

12✔
69
        self.next_index += 1;
12✔
70

12✔
71
        Some(Ok(*from_bytes::<GptPartitionEntry>(
12✔
72
            &entry_bytes[..mem::size_of::<GptPartitionEntry>()],
12✔
73
        )))
12✔
74
    }
12✔
75
}
76

77
impl<Io: BlockIo> Iterator for GptPartitionEntryIter<'_, '_, Io> {
78
    type Item = Result<GptPartitionEntry, DiskError<Io::Error>>;
79

80
    fn next(&mut self) -> Option<Self::Item> {
12✔
81
        if self.next_index >= self.layout.num_entries {
12✔
82
            return None;
×
83
        }
12✔
84

85
        if let Some(entry) = self.read_current_entry() {
12✔
86
            Some(entry)
12✔
87
        } else {
88
            let next_lba = Lba(self.current_lba.to_u64() + 1);
×
89
            if let Err(err) = self.set_current_lba(next_lba) {
×
90
                Some(Err(err))
×
91
            } else {
92
                self.read_current_entry()
×
93
            }
94
        }
95
    }
12✔
96
}
97

98
/// Workaround for using `impl Trait` with multiple lifetimes. See
99
/// <https://stackoverflow.com/a/50548538>.
100
pub trait Captures<'a, 'b> {}
101

102
impl<T: ?Sized> Captures<'_, '_> for T {}
103

104
/// Error type used by [`Disk`] methods.
105
#[allow(clippy::module_name_repetitions)]
106
#[derive(Debug, PartialEq)]
107
pub enum DiskError<IoError: Debug + Display> {
108
    /// The storage buffer is not large enough.
109
    BufferTooSmall,
110

111
    /// Numeric overflow occurred.
112
    Overflow,
113

114
    /// The partition entry size is larger than a single block.
115
    BlockSizeSmallerThanPartitionEntry,
116

117
    /// Error from a [`BlockIo`] implementation (see [`BlockIo::Error`]).
118
    ///
119
    /// [`BlockIo`]: crate::BlockIo
120
    /// [`BlockIo::Error`]: crate::BlockIo::Error
121
    Io(IoError),
122
}
123

124
impl<IoError> From<IoError> for DiskError<IoError>
125
where
126
    IoError: Debug + Display,
127
{
128
    fn from(err: IoError) -> Self {
×
129
        DiskError::Io(err)
×
130
    }
×
131
}
132

133
impl<IoError> Display for DiskError<IoError>
134
where
135
    IoError: Debug + Display,
136
{
137
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
138
        match self {
×
139
            Self::BufferTooSmall => f.write_str("storage buffer is too small"),
×
140
            Self::Overflow => f.write_str("numeric overflow occurred"),
×
141
            Self::BlockSizeSmallerThanPartitionEntry => {
×
142
                f.write_str("partition entries are larger than a single block")
×
143
            }
144
            Self::Io(io) => Display::fmt(io, f),
×
145
        }
146
    }
×
147
}
148

149
/// Read and write GPT disk data.
150
///
151
/// The disk is accessed via an object implementing the [`BlockIo`]
152
/// trait, so all reads and writes are on block boundaries. Writes are
153
/// not guaranteed to be completed until [`flush`] is called. This
154
/// happens automatically when the `Disk` is dropped, but if an error
155
/// occurs at that point it will be silently ignored so it is
156
/// recommended to call [`flush`] directly before dropping the disk.
157
///
158
/// Many of the methods on `Disk` take a `block_buf` argument, which is
159
/// a mutable byte buffer with a length of at least one block. (The
160
/// [`read_gpt_partition_entry_array`] and
161
/// [`write_gpt_partition_entry_array`] methods take a larger `storage`
162
/// argument that is multiple blocks in size.) These buffer arguments
163
/// allow `Disk` to avoid doing any internal memory allocation.
164
///
165
/// # Partition entry arrays
166
///
167
/// Partition entry arrays can be read in two ways: one block at a time
168
/// with [`gpt_partition_entry_array_iter`], or all at once with
169
/// [`read_gpt_partition_entry_array`]. The former allows a smaller
170
/// amount of memory usage bounded to the block size, while the latter
171
/// may be more efficient since all the blocks can be read at once.
172
///
173
/// Writing the array can currently only be done all at once via
174
/// [`write_gpt_partition_entry_array`]; a block-at-a-time method may be
175
/// added in the future.
176
///
177
/// [`flush`]: Self::flush
178
/// [`gpt_partition_entry_array_iter`]: Self::gpt_partition_entry_array_iter
179
/// [`read_gpt_partition_entry_array`]: Self::read_gpt_partition_entry_array
180
/// [`write_gpt_partition_entry_array`]: Self::write_gpt_partition_entry_array
181
pub struct Disk<Io: BlockIo> {
182
    io: Io,
183
}
184

185
impl<Io: BlockIo> Disk<Io> {
186
    /// Create a `Disk`.
187
    pub fn new(io: Io) -> Result<Self, DiskError<Io::Error>> {
5✔
188
        Ok(Self { io })
5✔
189
    }
5✔
190

191
    /// Clip the size of `block_buf` to a single block. Return
192
    /// `BufferTooSmall` if the buffer isn't big enough.
193
    fn clip_block_buf_size<'buf>(
18✔
194
        &self,
18✔
195
        block_buf: &'buf mut [u8],
18✔
196
    ) -> Result<&'buf mut [u8], DiskError<Io::Error>> {
18✔
197
        if let Some(block_size) = self.io.block_size().to_usize() {
18✔
198
            block_buf
18✔
199
                .get_mut(..block_size)
18✔
200
                .ok_or(DiskError::BufferTooSmall)
18✔
201
        } else {
202
            Err(DiskError::BufferTooSmall)
×
203
        }
204
    }
18✔
205

206
    /// Read the primary GPT header from the second block. No validation
207
    /// of the header is performed.
208
    pub fn read_primary_gpt_header(
3✔
209
        &mut self,
3✔
210
        block_buf: &mut [u8],
3✔
211
    ) -> Result<GptHeader, DiskError<Io::Error>> {
3✔
212
        self.read_gpt_header(Lba(1), block_buf)
3✔
213
    }
3✔
214

215
    /// Read the secondary GPT header from the last block. No validation
216
    /// of the header is performed.
217
    ///
218
    /// `block_buf` is a mutable byte buffer with a length of at least one block.
219
    pub fn read_secondary_gpt_header(
3✔
220
        &mut self,
3✔
221
        block_buf: &mut [u8],
3✔
222
    ) -> Result<GptHeader, DiskError<Io::Error>> {
3✔
223
        let num_blocks = self.io.num_blocks()?;
3✔
224
        let last_block =
3✔
225
            Lba(num_blocks.checked_sub(1).ok_or(DiskError::Overflow)?);
3✔
226
        self.read_gpt_header(last_block, block_buf)
3✔
227
    }
3✔
228

229
    /// Read a GPT header at the given [`Lba`]. No validation of the
230
    /// header is performed.
231
    ///
232
    /// `block_buf` is a mutable byte buffer with a length of at least one block.
233
    pub fn read_gpt_header(
6✔
234
        &mut self,
6✔
235
        lba: Lba,
6✔
236
        mut block_buf: &mut [u8],
6✔
237
    ) -> Result<GptHeader, DiskError<Io::Error>> {
6✔
238
        block_buf = self.clip_block_buf_size(block_buf)?;
6✔
239
        self.io.read_blocks(lba, block_buf)?;
6✔
240
        let bytes = block_buf
6✔
241
            .get(..mem::size_of::<GptHeader>())
6✔
242
            // OK to unwrap since the block size type guarantees a
6✔
243
            // minimum size greater than GptHeader.
6✔
244
            .unwrap();
6✔
245
        Ok(*from_bytes(bytes))
6✔
246
    }
6✔
247

248
    /// Read the entire partition entry array. The `storage` buffer must
249
    /// be at least [`layout.num_bytes_rounded_to_block`] in size.
250
    ///
251
    /// [`layout.num_bytes_rounded_to_block`]: GptPartitionEntryArrayLayout::num_bytes_rounded_to_block
252
    pub fn read_gpt_partition_entry_array<'buf>(
6✔
253
        &mut self,
6✔
254
        layout: GptPartitionEntryArrayLayout,
6✔
255
        storage: &'buf mut [u8],
6✔
256
    ) -> Result<GptPartitionEntryArray<'buf>, DiskError<Io::Error>> {
6✔
257
        let mut entry_array =
6✔
258
            GptPartitionEntryArray::new(layout, self.io.block_size(), storage)
6✔
259
                .map_err(|err| match err {
6✔
260
                    GptPartitionEntryArrayError::BufferTooSmall => {
261
                        DiskError::BufferTooSmall
×
262
                    }
263
                    GptPartitionEntryArrayError::Overflow => {
264
                        DiskError::Overflow
×
265
                    }
266
                })?;
6✔
267
        self.io
6✔
268
            .read_blocks(layout.start_lba, entry_array.storage_mut())?;
6✔
269
        Ok(entry_array)
6✔
270
    }
6✔
271

272
    /// Write an entire [`GptPartitionEntryArray`] to disk.
273
    pub fn write_gpt_partition_entry_array(
4✔
274
        &mut self,
4✔
275
        entry_array: &GptPartitionEntryArray,
4✔
276
    ) -> Result<(), DiskError<Io::Error>> {
4✔
277
        Ok(self.io.write_blocks(
4✔
278
            entry_array.layout().start_lba,
4✔
279
            entry_array.storage(),
4✔
280
        )?)
4✔
281
    }
4✔
282

283
    /// Get an iterator over partition entries. The `layout` parameter
284
    /// indicates where to read the entries from; see
285
    /// [`GptPartitionEntryArrayLayout`] for more.
286
    ///
287
    /// `block_buf` is a mutable byte buffer with a length of at least one block.
288
    #[allow(clippy::type_complexity)]
289
    pub fn gpt_partition_entry_array_iter<'disk, 'buf>(
6✔
290
        &'disk mut self,
6✔
291
        layout: GptPartitionEntryArrayLayout,
6✔
292
        mut block_buf: &'buf mut [u8],
6✔
293
    ) -> Result<
6✔
294
        impl Iterator<Item = Result<GptPartitionEntry, DiskError<Io::Error>>>
6✔
295
            + Captures<'disk, 'buf>,
6✔
296
        DiskError<Io::Error>,
6✔
297
    > {
6✔
298
        block_buf = self.clip_block_buf_size(block_buf)?;
6✔
299

300
        let entry_size =
6✔
301
            layout.entry_size.to_usize().ok_or(DiskError::Overflow)?;
6✔
302
        if entry_size > block_buf.len() {
6✔
303
            return Err(DiskError::BlockSizeSmallerThanPartitionEntry);
×
304
        }
6✔
305

6✔
306
        GptPartitionEntryIter::<'disk, 'buf>::new(self, layout, block_buf)
6✔
307
    }
6✔
308

309
    /// Write a protective MBR to the first block. If the block size is
310
    /// bigger than the MBR, the rest of the block will be filled with
311
    /// zeroes.
312
    ///
313
    /// `block_buf` is a mutable byte buffer with a length of at least one block.
314
    pub fn write_protective_mbr(
2✔
315
        &mut self,
2✔
316
        block_buf: &mut [u8],
2✔
317
    ) -> Result<(), DiskError<Io::Error>> {
2✔
318
        let mbr = MasterBootRecord::protective_mbr(self.io.num_blocks()?);
2✔
319
        self.write_mbr(&mbr, block_buf)
2✔
320
    }
2✔
321

322
    /// Write an MBR to the first block. If the block size is bigger
323
    /// than the MBR, the rest of the block will be filled with zeroes.
324
    ///
325
    /// `block_buf` is a mutable byte buffer with a length of at least one block.
326
    pub fn write_mbr(
2✔
327
        &mut self,
2✔
328
        mbr: &MasterBootRecord,
2✔
329
        mut block_buf: &mut [u8],
2✔
330
    ) -> Result<(), DiskError<Io::Error>> {
2✔
331
        block_buf = self.clip_block_buf_size(block_buf)?;
2✔
332

333
        let mbr_bytes = bytes_of(mbr);
2✔
334

2✔
335
        // This should always be true because the block_buf size is
2✔
336
        // already known to match the block size, and the block size is
2✔
337
        // enforced to be at least 512 bytes which is the size of the
2✔
338
        // MBR struct.
2✔
339
        assert!(block_buf.len() >= mbr_bytes.len());
2✔
340

341
        {
2✔
342
            let (left, right) = block_buf.split_at_mut(mbr_bytes.len());
2✔
343
            left.copy_from_slice(mbr_bytes);
2✔
344
            right.fill(0);
2✔
345
        }
2✔
346

2✔
347
        self.io.write_blocks(Lba(0), block_buf)?;
2✔
348
        Ok(())
2✔
349
    }
2✔
350

351
    /// Write the primary GPT header to the second block.
352
    ///
353
    /// The header is written to the beginning of the block, and all
354
    /// remaining bytes in the block are set to zero (see Table 5-5 "GPT
355
    /// Header" in the UEFI Specification: "The rest of the block is
356
    /// reserved by UEFI and must be zero").
357
    ///
358
    /// `block_buf` is a mutable byte buffer with a length of at least one block.
359
    pub fn write_primary_gpt_header(
2✔
360
        &mut self,
2✔
361
        header: &GptHeader,
2✔
362
        block_buf: &mut [u8],
2✔
363
    ) -> Result<(), DiskError<Io::Error>> {
2✔
364
        self.write_gpt_header(Lba(1), header, block_buf)
2✔
365
    }
2✔
366

367
    /// Write the secondary GPT header to the last block.
368
    ///
369
    /// The header is written to the beginning of the block, and all
370
    /// remaining bytes in the block are set to zero (see Table 5-5 "GPT
371
    /// Header" in the UEFI Specification: "The rest of the block is
372
    /// reserved by UEFI and must be zero").
373
    ///
374
    /// `block_buf` is a mutable byte buffer with a length of at least one block.
375
    pub fn write_secondary_gpt_header(
2✔
376
        &mut self,
2✔
377
        header: &GptHeader,
2✔
378
        block_buf: &mut [u8],
2✔
379
    ) -> Result<(), DiskError<Io::Error>> {
2✔
380
        let num_blocks = self.io.num_blocks()?;
2✔
381
        let last_block =
2✔
382
            Lba(num_blocks.checked_sub(1).ok_or(DiskError::Overflow)?);
2✔
383
        self.write_gpt_header(last_block, header, block_buf)
2✔
384
    }
2✔
385

386
    /// Write a [`GptHeader`] to the specified [`Lba`].
387
    ///
388
    /// The header is written to the beginning of the block, and all
389
    /// remaining bytes in the block are set to zero (see Table 5-5 "GPT
390
    /// Header" in the UEFI Specification: "The rest of the block is
391
    /// reserved by UEFI and must be zero").
392
    ///
393
    /// `block_buf` is a mutable byte buffer with a length of at least one block.
394
    pub fn write_gpt_header(
4✔
395
        &mut self,
4✔
396
        lba: Lba,
4✔
397
        header: &GptHeader,
4✔
398
        mut block_buf: &mut [u8],
4✔
399
    ) -> Result<(), DiskError<Io::Error>> {
4✔
400
        block_buf = self.clip_block_buf_size(block_buf)?;
4✔
401

402
        let header_bytes = bytes_of(header);
4✔
403

4✔
404
        // This should always be true because the block_buf size is
4✔
405
        // already known to match the block size, and the block size is
4✔
406
        // enforced to be at least 512 bytes which is much larger than
4✔
407
        // the size of the GptHeader struct.
4✔
408
        assert!(block_buf.len() >= header_bytes.len());
4✔
409

410
        {
4✔
411
            let (left, right) = block_buf.split_at_mut(header_bytes.len());
4✔
412
            left.copy_from_slice(header_bytes);
4✔
413
            right.fill(0);
4✔
414
        }
4✔
415

4✔
416
        self.io.write_blocks(lba, block_buf)?;
4✔
417
        Ok(())
4✔
418
    }
4✔
419

420
    /// Flush any pending writes to the disk.
421
    ///
422
    /// This is called automatically when the disk is dropped, but if an
423
    /// error occurs at that point it will be silently ignored. It is
424
    /// recommended to call this method directly before dropping the disk.
425
    pub fn flush(&mut self) -> Result<(), DiskError<Io::Error>> {
7✔
426
        Ok(self.io.flush()?)
7✔
427
    }
7✔
428
}
429

430
impl<Io: BlockIo> Drop for Disk<Io> {
431
    fn drop(&mut self) {
5✔
432
        // Throw away any errors.
5✔
433
        let _r = self.flush();
5✔
434
    }
5✔
435
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc