• 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

87.56
/gpt_disk_types/src/partition_entry.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::{
10
    guid, Guid, GuidFromStrError, LbaLe, LbaRangeInclusive, U16Le, U64Le,
11
};
12
use core::fmt::{self, Display, Formatter};
13
use core::num::NonZeroU32;
14
use core::str::FromStr;
15

16
#[cfg(feature = "bytemuck")]
17
use bytemuck::{Pod, Zeroable};
18

19
/// Unique ID representing the type of a partition.
20
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
21
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
×
22
#[repr(transparent)]
23
pub struct GptPartitionType(pub Guid);
24

25
// This lint incorrectly says that "ChromeOS" should be in backticks.
26
#[allow(clippy::doc_markdown)]
27
impl GptPartitionType {
28
    /// Indicates an entry within the GPT partition array is not in use.
29
    pub const UNUSED: Self = Self(Guid::ZERO);
30

31
    /// EFI system partition.
32
    ///
33
    /// This constant is defined in the UEFI Specification in Table 5-7
34
    /// "Defined GPT Partition Entry - Partition Type GUIDs".
35
    pub const EFI_SYSTEM: Self =
36
        Self(guid!("c12a7328-f81f-11d2-ba4b-00a0c93ec93b"));
37

38
    /// Partition containing a legacy MBR.
39
    ///
40
    /// This constant is defined in the UEFI Specification in Table 5-7
41
    /// "Defined GPT Partition Entry - Partition Type GUIDs".
42
    pub const LEGACY_MBR: Self =
43
        Self(guid!("024dee41-33e7-11d3-9d69-0008c781f39f"));
44

45
    /// Basic data partition.
46
    pub const BASIC_DATA: Self =
47
        Self(guid!("ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"));
48

49
    /// ChromeOS kernel partition.
50
    pub const CHROME_OS_KERNEL: Self =
51
        Self(guid!("fe3a2a5d-4f32-41a7-b725-accc3285a309"));
52

53
    /// ChromeOS rootfs partition.
54
    pub const CHROME_OS_ROOT_FS: Self =
55
        Self(guid!("3cb8e202-3b7e-47dd-8a3c-7ff2a13cfcec"));
56

57
    // TODO: there are many more "known" partition types for which we
58
    // could add constants.
59
}
60

61
impl Display for GptPartitionType {
62
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
4✔
63
        if self == &Self::UNUSED {
4✔
64
            f.write_str("UNUSED")
3✔
65
        } else {
66
            write!(f, "{}", self.0)
1✔
67
        }
68
    }
4✔
69
}
70

71
impl FromStr for GptPartitionType {
72
    type Err = GuidFromStrError;
73

74
    /// Parse from a GUID string. See [`Guid::from_str`].
75
    fn from_str(s: &str) -> Result<Self, Self::Err> {
×
76
        Ok(Self(s.parse()?))
×
77
    }
×
78
}
79

80
/// Partition attribute bits.
81
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
82
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
×
83
#[repr(transparent)]
84
pub struct GptPartitionAttributes(pub U64Le);
85

86
impl GptPartitionAttributes {
87
    /// If set, bit `0` indicates the partition is required for the
88
    /// platform to function.
89
    pub const REQUIRED_PARTITION_BIT: u8 = 0;
90

91
    /// If set, bit `1` tells the firmware not to provide
92
    /// `EFI_BLOCK_IO_PROTOCOL` for this partition.
93
    pub const NO_BLOCK_IO_PROTOCOL_BIT: u8 = 1;
94

95
    /// If set, bit `2` indicates to specialized software on legacy BIOS
96
    /// systems that the partition may be bootable. This bit is ignored
97
    /// by UEFI boot loaders.
98
    pub const LEGACY_BIOS_BOOTABLE_BIT: u8 = 2;
99

100
    fn get_bit(self, bit: u8) -> bool {
33✔
101
        self.0 .0[0] & (1 << bit) != 0
33✔
102
    }
33✔
103

104
    fn set_bit(&mut self, bit: u8, set: bool) {
12✔
105
        if set {
12✔
106
            self.0 .0[0] |= 1 << bit;
7✔
107
        } else {
7✔
108
            self.0 .0[0] &= !(1 << bit);
5✔
109
        }
5✔
110
    }
12✔
111

112
    /// Get the [`REQUIRED_PARTITION_BIT`] attribute value.
113
    ///
114
    /// [`REQUIRED_PARTITION_BIT`]: Self::REQUIRED_PARTITION_BIT
115
    #[must_use]
116
    pub fn required_partition(self) -> bool {
11✔
117
        self.get_bit(Self::REQUIRED_PARTITION_BIT)
11✔
118
    }
11✔
119

120
    /// Update the [`REQUIRED_PARTITION_BIT`] attribute value.
121
    ///
122
    /// [`REQUIRED_PARTITION_BIT`]: Self::REQUIRED_PARTITION_BIT
123
    pub fn update_required_partition(&mut self, required: bool) {
5✔
124
        self.set_bit(Self::REQUIRED_PARTITION_BIT, required);
5✔
125
    }
5✔
126

127
    /// Get the [`NO_BLOCK_IO_PROTOCOL_BIT`] attribute value.
128
    ///
129
    /// [`NO_BLOCK_IO_PROTOCOL_BIT`]: Self::NO_BLOCK_IO_PROTOCOL_BIT
130
    #[must_use]
131
    pub fn no_block_io_protocol(self) -> bool {
11✔
132
        self.get_bit(Self::NO_BLOCK_IO_PROTOCOL_BIT)
11✔
133
    }
11✔
134

135
    /// Update the [`NO_BLOCK_IO_PROTOCOL_BIT`] attribute value.
136
    ///
137
    /// [`NO_BLOCK_IO_PROTOCOL_BIT`]: Self::NO_BLOCK_IO_PROTOCOL_BIT
138
    pub fn update_no_block_io_protocol(&mut self, no_block_io_protocol: bool) {
4✔
139
        self.set_bit(Self::NO_BLOCK_IO_PROTOCOL_BIT, no_block_io_protocol);
4✔
140
    }
4✔
141

142
    /// Get the [`LEGACY_BIOS_BOOTABLE_BIT`] attribute value.
143
    ///
144
    /// [`LEGACY_BIOS_BOOTABLE_BIT`]: Self::LEGACY_BIOS_BOOTABLE_BIT
145
    #[must_use]
146
    pub fn legacy_bios_bootable(self) -> bool {
11✔
147
        self.get_bit(Self::LEGACY_BIOS_BOOTABLE_BIT)
11✔
148
    }
11✔
149

150
    /// Update the [`LEGACY_BIOS_BOOTABLE_BIT`] attribute value.
151
    ///
152
    /// [`LEGACY_BIOS_BOOTABLE_BIT`]: Self::LEGACY_BIOS_BOOTABLE_BIT
153
    pub fn update_legacy_bios_bootable(&mut self, legacy_bios_bootable: bool) {
3✔
154
        self.set_bit(Self::LEGACY_BIOS_BOOTABLE_BIT, legacy_bios_bootable);
3✔
155
    }
3✔
156

157
    /// Bits `48..=63` represented as a [`U16Le`]. These bits are
158
    /// reserved for custom use by the partition type, so their meaning
159
    /// depends on [`GptPartitionEntry::partition_type_guid`].
160
    #[must_use]
161
    pub fn type_specific_attributes(self) -> U16Le {
11✔
162
        U16Le([self.0 .0[6], self.0 .0[7]])
11✔
163
    }
11✔
164

165
    /// Set bits `48..=63`. These bits are reserved for custom use by
166
    /// the partition type, so their meaning depends on
167
    /// [`GptPartitionEntry::partition_type_guid`].
168
    pub fn update_type_specific_attributes(&mut self, attrs: U16Le) {
2✔
169
        self.0 .0[6] = attrs.0[0];
2✔
170
        self.0 .0[7] = attrs.0[1];
2✔
171
    }
2✔
172
}
173

174
impl Display for GptPartitionAttributes {
175
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
8✔
176
        let mut first = true;
8✔
177
        let mut sep = |f: &mut Formatter<'_>| {
8✔
178
            if first {
8✔
179
                first = false;
5✔
180
            } else {
5✔
181
                f.write_str(", ")?;
3✔
182
            }
183
            Ok(())
8✔
184
        };
8✔
185

186
        if self.required_partition() {
8✔
187
            sep(f)?;
3✔
188
            f.write_str("required_partition (1)")?;
3✔
189
        }
5✔
190
        if self.no_block_io_protocol() {
8✔
191
            sep(f)?;
1✔
192
            f.write_str("no_block_io_protocol (2)")?;
1✔
193
        }
7✔
194
        if self.legacy_bios_bootable() {
8✔
195
            sep(f)?;
3✔
196
            f.write_str("legacy_bios_bootable (4)")?;
3✔
197
        }
5✔
198
        let type_specific = self.type_specific_attributes();
8✔
199
        if type_specific.to_u16() != 0 {
8✔
200
            sep(f)?;
1✔
201
            write!(f, "type_specific({:#x})", self.type_specific_attributes())?;
1✔
202
        }
7✔
203
        if first {
8✔
204
            write!(f, "(empty)")?;
3✔
205
        }
5✔
206
        Ok(())
8✔
207
    }
8✔
208
}
209

210
struct GptPartitionNameCharIter<'a> {
211
    name: &'a GptPartitionName,
212
    byte_index: usize,
213
}
214

215
impl Iterator for GptPartitionNameCharIter<'_> {
216
    type Item = char;
217

218
    fn next(&mut self) -> Option<Self::Item> {
44✔
219
        let bytes = &self.name.0;
44✔
220

44✔
221
        // Stop iteration at the end of the name.
44✔
222
        if self.byte_index >= bytes.len() {
44✔
223
            return None;
1✔
224
        }
43✔
225

43✔
226
        // UEFI strings are UCS-2, not UTF-16. That means that each
43✔
227
        // source character is exactly two bytes long.
43✔
228
        let c = (u16::from(bytes[self.byte_index + 1]) << 8)
43✔
229
            | u16::from(bytes[self.byte_index]);
43✔
230

43✔
231
        // Stop iteration at the first null terminator.
43✔
232
        if c == 0 {
43✔
233
            self.byte_index = bytes.len();
3✔
234
            return None;
3✔
235
        }
40✔
236

40✔
237
        self.byte_index += 2;
40✔
238

40✔
239
        Some(char::try_from(u32::from(c)).unwrap_or('�'))
40✔
240
    }
44✔
241
}
242

243
/// Error type for [`GptPartitionName::set_char`].
244
///
245
/// If the `std` feature is enabled, this type implements the [`Error`]
246
/// trait.
247
///
248
/// [`Error`]: std::error::Error
249
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
250
pub enum GptPartitionNameSetCharError {
251
    /// Character index is outside the range `0..36`.
252
    Index,
253

254
    /// Character cannot be represented in UCS-2.
255
    InvalidChar,
256
}
257

258
impl Display for GptPartitionNameSetCharError {
259
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
260
        match self {
×
261
            Self::Index => f.write_str("invalid character index"),
×
262
            Self::InvalidChar => {
263
                f.write_str("character cannot be represented in UCS-2")
×
264
            }
265
        }
266
    }
×
267
}
268

269
/// Human readable partition label encoded as a null-terminated UCS-2
270
/// string.
271
///
272
/// # Examples
273
///
274
/// Construct from a UTF-8 string:
275
///
276
/// ```
277
/// use gpt_disk_types::GptPartitionName;
278
///
279
/// let partition_name: GptPartitionName = "hacktheplanet".parse().unwrap();
280
/// ```
281
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
282
#[repr(transparent)]
283
pub struct GptPartitionName(pub [u8; 72]);
284

285
// Manual implementation needed because of the large array.
286
#[cfg(feature = "bytemuck")]
287
#[allow(unsafe_code)]
288
unsafe impl Pod for GptPartitionName {}
289
#[cfg(feature = "bytemuck")]
290
#[allow(unsafe_code)]
291
unsafe impl Zeroable for GptPartitionName {}
292

293
impl GptPartitionName {
294
    /// True if the first character is a null terminator, false otherwise.
295
    #[must_use]
296
    pub fn is_empty(&self) -> bool {
2✔
297
        self.0[0] == 0 && self.0[1] == 0
2✔
298
    }
2✔
299

300
    /// Get an iterator over the characters in the partition name, using
301
    /// UCS-2 decoding. Iteration ends when either the end of the array
302
    /// or a null terminator is reached. The null character is not
303
    /// included in the iteration output. Any invalid characters are
304
    /// replaced with the Unicode replacement character (`�`).
305
    pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
4✔
306
        GptPartitionNameCharIter {
4✔
307
            name: self,
4✔
308
            byte_index: 0,
4✔
309
        }
4✔
310
    }
4✔
311

312
    /// Set a UCS-2 character. The `index` is by UCS-2 character rather
313
    /// than byte (e.g. index 3 indicates byte offset 6). This is valid
314
    /// because UCS-2 is a fixed-width encoding.
315
    pub fn set_char(
3✔
316
        &mut self,
3✔
317
        index: usize,
3✔
318
        c: char,
3✔
319
    ) -> Result<(), GptPartitionNameSetCharError> {
3✔
320
        // Ensure the index is valid.
3✔
321
        if index > self.0.len() / 2 {
3✔
322
            return Err(GptPartitionNameSetCharError::Index);
×
323
        }
3✔
324

325
        let c = u16::try_from(u32::from(c))
3✔
326
            .map_err(|_| GptPartitionNameSetCharError::InvalidChar)?;
3✔
327
        let bytes = c.to_le_bytes();
3✔
328
        self.0[index * 2] = bytes[0];
3✔
329
        self.0[index * 2 + 1] = bytes[1];
3✔
330
        Ok(())
3✔
331
    }
3✔
332
}
333

334
impl Display for GptPartitionName {
335
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
4✔
336
        for c in self.chars() {
40✔
337
            write!(f, "{c}")?;
40✔
338
        }
339
        Ok(())
4✔
340
    }
4✔
341
}
342

343
impl Default for GptPartitionName {
344
    fn default() -> Self {
8✔
345
        Self([0; 72])
8✔
346
    }
8✔
347
}
348

349
/// Error type for [`GptPartitionName::from_str`].
350
///
351
/// If the `std` feature is enabled, this type implements the [`Error`]
352
/// trait.
353
///
354
/// [`Error`]: std::error::Error
355
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
356
pub enum GptPartitionNameFromStrError {
357
    /// Input string is too long.
358
    Length,
359

360
    /// Input string contains a character that cannot be represented in UCS-2.
361
    InvalidChar,
362
}
363

364
impl Display for GptPartitionNameFromStrError {
365
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
366
        match self {
×
367
            Self::Length => f.write_str("input string is too long"),
×
368
            Self::InvalidChar => f.write_str("input string contains a character that cannot be represented in UCS-2"),
×
369
        }
370
    }
×
371
}
372

373
impl From<ucs2::Error> for GptPartitionNameFromStrError {
374
    fn from(err: ucs2::Error) -> Self {
×
375
        match err {
×
376
            ucs2::Error::BufferOverflow => Self::Length,
×
377
            ucs2::Error::MultiByte => Self::InvalidChar,
×
378
        }
379
    }
×
380
}
381

382
impl FromStr for GptPartitionName {
383
    type Err = GptPartitionNameFromStrError;
384

385
    fn from_str(s: &str) -> Result<Self, Self::Err> {
5✔
386
        let mut name = Self::default();
5✔
387

5✔
388
        // Leave room for null terminator.
5✔
389
        let max_index = name.0.len() - 2 - 1;
5✔
390

5✔
391
        let mut index = 0;
5✔
392
        ucs2::encode_with(s, |c| {
60✔
393
            if index >= max_index {
60✔
394
                Err(ucs2::Error::BufferOverflow)
×
395
            } else {
396
                name.0[index] = u8::try_from(c & 0xff).unwrap();
60✔
397
                name.0[index + 1] = u8::try_from((c & 0xff00) >> 8).unwrap();
60✔
398
                index += 2;
60✔
399
                Ok(())
60✔
400
            }
401
        })?;
60✔
402
        Ok(name)
5✔
403
    }
5✔
404
}
405

406
/// An entry within the GPT partition array.
407
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
408
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
×
409
#[repr(C, packed)]
410
pub struct GptPartitionEntry {
411
    /// Unique ID representing the partition's type. If the type is
412
    /// [`GptPartitionType::UNUSED`], this entry in the partition array
413
    /// is not in use.
414
    pub partition_type_guid: GptPartitionType,
415

416
    /// GUID that is unique for every partition entry.
417
    pub unique_partition_guid: Guid,
418

419
    /// LBA of the partition's first block.
420
    pub starting_lba: LbaLe,
421

422
    /// LBA of the partition's last block.
423
    pub ending_lba: LbaLe,
424

425
    /// Attribute bit flags.
426
    pub attributes: GptPartitionAttributes,
427

428
    /// Human readable partition label encoded as a null-terminated
429
    /// UCS-2 string.
430
    pub name: GptPartitionName,
431
}
432

433
impl GptPartitionEntry {
434
    /// Get the range of blocks covered by this partition. Returns
435
    /// `None` if the `ending_lba` is less than the `starting_lba`.
436
    #[must_use]
437
    pub fn lba_range(&self) -> Option<LbaRangeInclusive> {
×
438
        LbaRangeInclusive::new(self.starting_lba.into(), self.ending_lba.into())
×
439
    }
×
440

441
    /// Check if the entry is in use. If the [`partition_type_guid`] is
442
    /// [`GptPartitionType::UNUSED`], the entry is considered unused,
443
    /// which means there is no partition data associated with the entry.
444
    ///
445
    /// [`partition_type_guid`]: Self::partition_type_guid
446
    #[must_use]
447
    pub fn is_used(&self) -> bool {
24✔
448
        let partition_type_guid = self.partition_type_guid;
24✔
449
        partition_type_guid != GptPartitionType::UNUSED
24✔
450
    }
24✔
451
}
452

453
impl Display for GptPartitionEntry {
454
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1✔
455
        f.write_str("GptPartitionEntry { ")?;
1✔
456
        write!(f, "partition_type_guid: {}", &{ self.partition_type_guid })?;
1✔
457
        write!(f, ", unique_partition_guid: {}", &{
1✔
458
            self.unique_partition_guid
1✔
459
        })?;
1✔
460
        write!(f, ", starting_lba: {}", self.starting_lba)?;
1✔
461
        write!(f, ", ending_lba: {}", self.ending_lba)?;
1✔
462
        write!(f, ", attributes: {}", self.attributes)?;
1✔
463
        write!(f, ", name: \"{}\"", self.name)?;
1✔
464
        f.write_str(" }")
1✔
465
    }
1✔
466
}
467

468
/// Error returned by [`GptPartitionEntrySize::new`].
469
///
470
/// If the `std` feature is enabled, this type implements the [`Error`]
471
/// trait.
472
///
473
/// [`Error`]: std::error::Error
474
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
475
pub struct GptPartitionEntrySizeError;
476

477
impl Display for GptPartitionEntrySizeError {
478
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1✔
479
        f.write_str("partition entry size must be a power of two greater than or equal to 128")
1✔
480
    }
1✔
481
}
482

483
/// Size in bytes of entries in the partition entry array.
484
///
485
/// A valid partition entry size must be a value of 128×2ⁿ, where n is
486
/// an integer ≥0.
487
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
488
#[repr(transparent)]
489
pub struct GptPartitionEntrySize(NonZeroU32);
490

491
impl GptPartitionEntrySize {
492
    /// Create a new `GptPartitionEntrySize`. Returns
493
    /// [`GptPartitionEntrySizeError`] if the input is less than 128 or
494
    /// not a power of two.
495
    pub const fn new(val: u32) -> Result<Self, GptPartitionEntrySizeError> {
19✔
496
        if let Some(nz) = NonZeroU32::new(val) {
19✔
497
            if val >= 128 && val.is_power_of_two() {
18✔
498
                Ok(Self(nz))
15✔
499
            } else {
500
                Err(GptPartitionEntrySizeError)
3✔
501
            }
502
        } else {
503
            Err(GptPartitionEntrySizeError)
1✔
504
        }
505
    }
19✔
506

507
    /// Get the entry size in bytes as a [`u32`].
508
    #[must_use]
509
    pub const fn to_u32(self) -> u32 {
16✔
510
        self.0.get()
16✔
511
    }
16✔
512

513
    /// Get the entry size in bytes as a [`u64`].
514
    #[allow(clippy::as_conversions)]
515
    #[must_use]
516
    pub const fn to_u64(self) -> u64 {
27✔
517
        self.0.get() as u64
27✔
518
    }
27✔
519

520
    /// Get the entry size in bytes as a [`usize`].
521
    #[must_use]
522
    pub fn to_usize(self) -> Option<usize> {
12✔
523
        self.0.get().try_into().ok()
12✔
524
    }
12✔
525
}
526

527
impl Default for GptPartitionEntrySize {
528
    fn default() -> Self {
3✔
529
        Self::new(128).unwrap()
3✔
530
    }
3✔
531
}
532

533
impl Display for GptPartitionEntrySize {
534
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2✔
535
        self.0.fmt(f)
2✔
536
    }
2✔
537
}
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